Repository: joke2k/faker
Pull request #1244 (2020-08-13 14:54:52)

Add es_ES autonomous communities (Spanish regions).
### What does this changes

Add Spanish regions (locally known as "autonomous communities") to `es_ES` address provider using government spreadsheet as source.



Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index bfbb6731..f701b3c3 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,6 +1,5 @@
 [bumpversion]
-current_version = 4.1.1
+current_version = 4.1.2
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
-
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 87c978ee..6677edf7 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,22 @@
 Changelog
 =========
 
+`4.1.2 - 1&-August-2020 <https://github.com/joke2k/faker/compare/v4.1.1...v4.1.2>`__
+------------------------------------------------------------------------------------
+
+* Extend Person Provider to support non-binary suffixes and prefixes. Thank you @crd.
+* Add ``safe_domain_name`` generator. Thanks @crd.
+* Fix max_value/positive ``pyfloat`` interaction. Thanks @coiax.
+* Update ``bban_format`` for ``fi_FI`` locale. Thanks @ALMP-SallaH.
+* Fix ``person.ja_JP`` kana and roman characters. Thanks @yameholo.
+* Add ``json`` and ``fixed_width`` generators. Thank you @johnbrandborg.
+* Add SWIFT code provider methods: ``swift``, ``swift8`` and ``swift11``. Thanks @malefice.
+* Add ``es_ES`` internet provider. Thanks @mondeja.
+* Update ``bban_format`` for ``fr_FR`` locale. Thanks @r3gis3r.
+* Update ``fr_FR`` ``job`` provider. Thanks @mondeja.
+* Add ``es_ES`` ``barcode`` provider. Thanks @mondeja.
+* Add parser argument support. Thanks @johnbrandborg.
+
 `4.1.1 - 15-June-2020 <https://github.com/joke2k/faker/compare/v4.1.0...v4.1.1>`__
 ----------------------------------------------------------------------------------
 
diff --git a/VERSION b/VERSION
index 627a3f43..4d0dcda0 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-4.1.1
+4.1.2
diff --git a/docs/conf.py b/docs/conf.py
index 6dce9a36..36b84f6f 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -50,9 +50,9 @@ copyright = '2014, Daniele Faraglia'
 # built documents.
 #
 # The short X.Y version.
-version = '4.1.1'
+version = '4.1.2'
 # The full version, including alpha/beta/rc tags.
-release = '4.1.1'
+release = '4.1.2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/faker/__init__.py b/faker/__init__.py
index e13fe1c0..61970939 100644
--- a/faker/__init__.py
+++ b/faker/__init__.py
@@ -2,4 +2,4 @@ from faker.factory import Factory  # noqa F401
 from faker.generator import Generator  # noqa F401
 from faker.proxy import Faker  # noqa F401
 
-VERSION = '4.1.1'
+VERSION = '4.1.2'
diff --git a/faker/generator.py b/faker/generator.py
index 0402659b..46f176d1 100644
--- 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 @@ class Generator:
         """
         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 @@ class Generator:
         """
         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)
diff --git a/faker/providers/address/es_ES/__init__.py b/faker/providers/address/es_ES/__init__.py
index 1fd46d26..4438dbed 100644
--- a/faker/providers/address/es_ES/__init__.py
+++ b/faker/providers/address/es_ES/__init__.py
@@ -63,6 +63,30 @@ class Provider(AddressProvider):
         'Zamora',
         'Zaragoza')
 
+    # Source:
+    # https://administracionelectronica.gob.es/ctt/resources/Soluciones
+    # /238/Descargas/Catalogo-de-Comunidades-Autonomas.xlsx
+    regions = (
+        'Andalucía',
+        'Aragón',
+        'Principado de Asturias',
+        'Illes Balears',
+        'Canarias',
+        'Cantabria',
+        'Castilla y León',
+        'Castilla-La Mancha',
+        'Cataluña',
+        'Comunitat Valenciana',
+        'Extremadura',
+        'Galicia',
+        'Comunidad de Madrid',
+        'Región de Murcia',
+        'Comunidad Foral de Navarra',
+        'País Vasco',
+        'La Rioja',
+        'Ciudad Autónoma de Ceuta',
+        'Ciudad Autónoma de Melilla')
+
     city_formats = (
         '{{state_name}}',
     )
@@ -94,3 +118,8 @@ class Provider(AddressProvider):
 
     def state(self):
         return self.random_element(self.states)
+
+    def region(self):
+        return self.random_element(self.regions)
+
+    autonomous_community = region
diff --git a/tox.ini b/tox.ini
index 72cab4a1..0bf3dbcd 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,7 +6,7 @@ skip_missing_interpreters = true
 deps =
     coverage
     freezegun<0.4
-    pytest>=5.4.0,<5.5
+    pytest>=6.0.1
     random2
     ukpostcodeparser>=1.1.1
     validators>=0.13.0


Test patch:
diff --git a/tests/providers/test_address.py b/tests/providers/test_address.py
index 2e3f52cb..e705f359 100644
--- a/tests/providers/test_address.py
+++ b/tests/providers/test_address.py
@@ -463,6 +463,16 @@ class TestEsES(unittest.TestCase):
         secondary_address = self.fake.secondary_address()
         assert isinstance(secondary_address, str)
 
+    def test_regions(self):
+        region = self.fake.region()
+        assert isinstance(region, str)
+        assert region in EsEsProvider.regions
+
+        # Spanish regions, also known as "autonomous communities"
+        autonomous_community = self.fake.autonomous_community()
+        assert isinstance(autonomous_community, str)
+        assert autonomous_community in EsEsProvider.regions
+
 
 class TestEsMX(unittest.TestCase):
     """ Tests the addresses in the es_MX locale """
diff --git a/tests/providers/test_barcode.py b/tests/providers/test_barcode.py
index 8fee0fc3..614e1a5d 100644
--- a/tests/providers/test_barcode.py
+++ b/tests/providers/test_barcode.py
@@ -66,7 +66,7 @@ class TestBarcodeProvider:
             assert (sum(ean13_digits) + 2 * sum(ean13_digits[1::2])) % 10 == 0
 
 
-@pytest.fixture('class')
+@pytest.fixture(scope='class')
 def provider_class(request):
     if hasattr(request.cls, 'get_provider_class') and callable(request.cls.get_provider_class):
         _provider_class = request.cls.get_provider_class()
diff --git a/tests/test_generator.py b/tests/test_generator.py
index b7e58a0d..c38cbd75 100644
--- a/tests/test_generator.py
+++ b/tests/test_generator.py
@@ -17,7 +17,7 @@ class FooProvider:
         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 @@ class TestGenerator:
 
     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: