Repository: joke2k/faker

Issue:
Update python_requires in setup.py
https://github.com/joke2k/faker/blob/146f205b942d15c95160df35d3e431624697d079/setup.py#L65
Finnish IBAN should be 18 characters of length
* Faker version: 4.1.1

Finnish IBAN should be 18 characters of length. Currently returned Finnish IBAN has 20 characters.

### Steps to reproduce

```
from faker import Faker
>>> fake = Faker('fi_FI')
>>> fin_iban = fake.iban()
>>> fin_iban
'FI807370583252728936'
>>> len(fin_iban)
20
```

### Expected behavior

```
>>> len(fin_iban)
18
```

### Actual behavior

```
>>> len(fin_iban)
20
```



Discussion:

Resolving patch:
diff --git a/faker/generator.py b/faker/generator.py
--- a/faker/generator.py
+++ b/faker/generator.py
@@ -1,14 +1,16 @@
 import random as random_module
 import re
 
-_re_token = re.compile(r'\{\{(\s?)(\w+)(\s?)\}\}')
+_re_token = re.compile(r'\{\{\s*(\w+)(:\s*\w+?)?\s*\}\}')
 random = random_module.Random()
 mod_random = random  # compat with name released in 0.8
 
 
 class Generator:
 
-    __config = {}
+    __config = {
+        'arguments': {},
+    }
 
     def __init__(self, **config):
         self.providers = []
@@ -71,7 +73,6 @@ def format(self, formatter, *args, **kwargs):
         """
         This is a secure way to make a fake from another Provider.
         """
-        # TODO: data export?
         return self.get_formatter(formatter)(*args, **kwargs)
 
     def get_formatter(self, formatter):
@@ -95,14 +96,84 @@ def set_formatter(self, name, method):
         """
         setattr(self, name, method)
 
+    def set_arguments(self, group, argument, value=None):
+        """
+        Creates an argument group, with an individual argument or a dictionary
+        of arguments.  Used with the Generator.parse method.
+
+        generator.set_arguments('small', 'max_value', 10)
+        generator.set_arguments('small', {'min_value': 5, 'max_value': 10})
+        """
+        if group not in self.__config['arguments']:
+            self.__config['arguments'][group] = {}
+
+        if isinstance(argument, dict):
+            self.__config['arguments'][group] = argument
+        elif not isinstance(argument, str):
+            raise ValueError("Arguments must be either a string or dictionary")
+        else:
+            self.__config['arguments'][group][argument] = value
+
+    def get_arguments(self, group, argument=None):
+        """
+        Get the value of an argument configured within a argument group, or
+        the entire group as a dictionary.
+
+        generator.get_arguments('small', 'max_value')
+        generator.get_arguments('small')
+        """
+        if group in self.__config['arguments'] and argument:
+            result = self.__config['arguments'][group].get(argument)
+        else:
+            result = self.__config['arguments'].get(group)
+
+        return result
+
+    def del_arguments(self, group, argument=None):
+        """
+        Delete an argument from an argument group or the entire
+        argument group.
+
+        generator.del_arguments('small')
+        generator.del_arguments('small', 'max_value')
+        """
+        if group in self.__config['arguments']:
+            if argument:
+                result = self.__config['arguments'][group].pop(argument)
+            else:
+                result = self.__config['arguments'].pop(group)
+        else:
+            result = None
+
+        return result
+
     def parse(self, text):
         """
         Replaces tokens (like '{{ tokenName }}' or '{{tokenName}}')
-        with the result from the token method call.
+        with the result from the token method call. Arguments can be
+        parsed by using an argument group. '{{ tokenName:group }}'
+
+        Example:
+
+        generator.set_arguments('red_rgb', {'hue': 'red', 'color_format': 'rgb'})
+        generator.set_arguments('small', 'max_value', 10)
+
+        generator.parse('{{ color:red_rgb }} - {{ pyint:small }}')
         """
         return _re_token.sub(self.__format_token, text)
 
     def __format_token(self, matches):
-        formatter = list(matches.groups())
-        formatter[1] = str(self.format(formatter[1]))
-        return ''.join(formatter)
+        formatter, argument_group = list(matches.groups())
+        argument_group = argument_group.lstrip(":").strip() if argument_group else ''
+
+        if argument_group:
+            try:
+                arguments = self.__config['arguments'][argument_group]
+            except KeyError:
+                raise AttributeError('Unknown argument group "{}"'.format(argument_group))
+
+            formatted = str(self.format(formatter, **arguments))
+        else:
+            formatted = str(self.format(formatter))
+
+        return ''.join(formatted)


Test patch:
diff --git a/tests/test_generator.py b/tests/test_generator.py
--- a/tests/test_generator.py
+++ b/tests/test_generator.py
@@ -17,7 +17,7 @@ def foo_formatter(self):
         return 'foobar'
 
     def foo_formatter_with_arguments(self, param='', append=''):
-        return 'baz' + param + append
+        return 'baz' + str(param) + str(append)
 
 
 @pytest.fixture(autouse=True)
@@ -61,7 +61,41 @@ def test_parse_without_formatter_tokens(self, generator):
 
     def test_parse_with_valid_formatter_tokens(self, generator):
         result = generator.parse('This is {{foo_formatter}} a text with "{{ foo_formatter }}"')
-        assert result == 'This is foobar a text with " foobar "'
+        assert result == 'This is foobar a text with "foobar"'
+
+    def test_arguments_group_with_values(self, generator):
+        generator.set_arguments('group1', 'argument1', 1)
+        generator.set_arguments('group1', 'argument2', 2)
+        assert generator.get_arguments('group1', 'argument1') == 1
+        assert generator.del_arguments('group1', 'argument2') == 2
+        assert generator.get_arguments('group1', 'argument2') is None
+        assert generator.get_arguments('group1') == {'argument1': 1}
+
+    def test_arguments_group_with_dictionaries(self, generator):
+        generator.set_arguments('group2', {'argument1': 3, 'argument2': 4})
+        assert generator.get_arguments('group2') == {'argument1': 3, 'argument2': 4}
+        assert generator.del_arguments('group2') == {'argument1': 3, 'argument2': 4}
+        assert generator.get_arguments('group2') is None
+
+    def test_arguments_group_with_invalid_name(self, generator):
+        assert generator.get_arguments('group3') is None
+        assert generator.del_arguments('group3') is None
+
+    def test_arguments_group_with_invalid_argument_type(self, generator):
+        with pytest.raises(ValueError) as excinfo:
+            generator.set_arguments('group', ['foo', 'bar'])
+        assert str(excinfo.value) == "Arguments must be either a string or dictionary"
+
+    def test_parse_with_valid_formatter_arguments(self, generator):
+        generator.set_arguments('format_name', {"param": "foo", "append": "bar"})
+        result = generator.parse('This is "{{foo_formatter_with_arguments:format_name}}"')
+        generator.del_arguments('format_name')
+        assert result == 'This is "bazfoobar"'
+
+    def test_parse_with_unknown_arguments_group(self, generator):
+        with pytest.raises(AttributeError) as excinfo:
+            generator.parse('This is "{{foo_formatter_with_arguments:unknown}}"')
+        assert str(excinfo.value) == 'Unknown argument group "unknown"'
 
     def test_parse_with_unknown_formatter_token(self, generator):
         with pytest.raises(AttributeError) as excinfo: