Repository: joke2k/faker
Pull request #1145 (2020-04-02 15:10:34)

Implements __dir__ method to Faker proxy
### What does this changes

Implements __dir__ magic method to Faker proxy. This allows when trying to list/show all methods of all providers in IPython, for example:

![image](https://user-images.githubusercontent.com/3184883/78265490-e0b6f280-74da-11ea-9f5b-672e01226355.png)


Patch:
diff --git a/faker/providers/automotive/es_ES/__init__.py b/faker/providers/automotive/es_ES/__init__.py
new file mode 100644
index 00000000..ebbd7f10
--- /dev/null
+++ b/faker/providers/automotive/es_ES/__init__.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from .. import Provider as AutomotiveProvider
+
+
+class Provider(AutomotiveProvider):
+    # Source (english): https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Spain
+    license_formats = (
+        # New format
+        '#### ???',
+    )
+
+    # New format suffix letters (excluding vocals and Q from ascii uppercase)
+    license_plate_new_format_suffix_letters = "BCDFGHJKLMNPRSTVWXYZ"
+
+    # Old format suffix letters (excluding Q and R from ascii uppercase)
+    license_plate_old_format_suffix_letters = "ABCDEFGHIJKLMNOPSTUVWXYZ"
+
+    # Province prefixes (for old format)
+    province_prefix = (
+        "A",   # Alicante
+        "AB",  # Albacete
+        "AL",  # Almería
+        "AV",  # Ávila
+        "B",   # Barcelona
+        "BA",  # Badajoz
+        "BI",  # Bilbao
+        "BU",  # Burgos
+        "C",   # La Coruña
+        "CA",  # Cádiz
+        "CC",  # Cáceres
+        "CS",  # Castellón de la Plana
+        "CE",  # Ceuta
+        "CO",  # Córdoba
+        "CR",  # Ciudad Real
+        "CU",  # Cuenca
+        "GC",  # Las Palmas (Gran Canaria)
+        "GE",  # Girona (until 1992)
+        "GI",  # Girona (since 1992)
+        "GR",  # Granada
+        "GU",  # Guadalajara
+        "H",   # Huelva
+        "HU",  # Huesca
+        "PM",  # Palma de Mallorca (until 1997)
+        "IB",  # Islas Baleares (since 1997)
+        "J",   # Jaén
+        "L",   # Lleida
+        "LE",  # León
+        "LO",  # Logroño
+        "LU",  # Lugo
+        "M",   # Madrid
+        "MA",  # Málaga
+        "ML",  # Melilla
+        "MU",  # Murcia
+        "O",   # Oviedo
+        "OR",  # Ourense (until 1998)
+        "OU",  # Ourense (since 1998)
+        "P",   # Palencia
+        "NA",  # Navarra
+        "PO",  # Pontevedra
+        "S",   # Santander
+        "SA",  # Salamanca
+        "SE",  # Sevilla
+        "SG",  # Segovia
+        "SO",  # Soria
+        "SS",  # Donostia/San Sebastián
+        "T",   # Tarragona
+        "TE",  # Teruel
+        "TF",  # Santa Cruz de Tenerife
+        "TO",  # Toledo
+        "V",   # Valencia
+        "VA",  # Valladolid
+        "VI",  # Vitoria
+        "Z",   # Zaragoza
+        "ZA",  # Zamora
+    )
+
+    def license_plate_unified(self):
+        temp = re.sub(r'\?',
+                      lambda x: self.random_element(
+                          self.license_plate_new_format_suffix_letters),
+                      self.license_formats[0])
+        return self.numerify(temp)
+
+    def license_plate_by_province(self, province_prefix=None):
+        province_prefix = province_prefix if province_prefix is not None else \
+            self.random_element(self.province_prefix)
+        temp = re.sub(r'\?',
+                      lambda x: self.random_element(
+                          self.license_plate_old_format_suffix_letters),
+                      "#### ??")
+        return province_prefix + " " + self.numerify(temp)
+
+    def license_plate(self):
+        if self.generator.random.randint(0, 1):
+            return self.license_plate_unified()
+        return self.license_plate_by_province()
diff --git a/faker/proxy.py b/faker/proxy.py
index 7225f96f..b91a0709 100644
--- a/faker/proxy.py
+++ b/faker/proxy.py
@@ -55,6 +55,14 @@ class Faker:
         self._locales = locales
         self._factories = list(self._factory_map.values())
 
+    def __dir__(self):
+        attributes = set(super(Faker, self).__dir__())
+        for factory in self.factories:
+            attributes |= {
+                attr for attr in dir(factory) if not attr.startswith('_')
+            }
+        return sorted(attributes)
+
     def __getitem__(self, locale):
         return self._factory_map[locale.replace('-', '_')]
 


Test patch:
diff --git a/tests/providers/test_automotive.py b/tests/providers/test_automotive.py
index ddffb945..a23b2664 100644
--- a/tests/providers/test_automotive.py
+++ b/tests/providers/test_automotive.py
@@ -146,3 +146,34 @@ class TestFrFR(unittest.TestCase):
         plate = self.fake.license_plate()
         assert isinstance(plate, str)
         assert self.pattern.match(plate)
+
+
+class TestEsES(unittest.TestCase):
+
+    def setUp(self):
+        self.fake = Faker('es_ES')
+        Faker.seed(0)
+        self.new_format_pattern = re.compile(r'\d{4}\s[A-Z]{3}')
+        self.old_format_pattern = re.compile(r'[A-Z]{1,2}\s\d{4}\s[A-Z]{2}')
+
+    def test_es_ES_plate_new_format(self):
+        plate = self.fake.license_plate_unified()
+        assert isinstance(plate, str)
+        assert self.new_format_pattern.match(plate)
+
+    def test_es_ES_plate_old_format(self):
+        plate = self.fake.license_plate_by_province()
+        assert isinstance(plate, str)
+        assert self.old_format_pattern.match(plate)
+
+    def test_es_ES_plate_old_format_explicit_province_prefix(self):
+        plate = self.fake.license_plate_by_province(province_prefix="CA")
+        assert isinstance(plate, str)
+        assert self.old_format_pattern.match(plate)
+        assert plate[:2] == "CA"
+
+    def test_es_ES_plate_format(self):
+        plate = self.fake.license_plate()
+        assert isinstance(plate, str)
+        assert self.new_format_pattern.match(plate) or \
+            self.old_format_pattern.match(plate)
diff --git a/tests/test_proxy.py b/tests/test_proxy.py
index 69d3dcb0..7aa93385 100644
--- a/tests/test_proxy.py
+++ b/tests/test_proxy.py
@@ -299,3 +299,18 @@ class TestFakerProxyClass(unittest.TestCase):
         self.fake = Faker(['en_US', 'en_PH'])
         with self.assertRaises(AttributeError):
             self.fake.obviously_invalid_provider_method_a23f()
+
+    def test_dir_include_all_providers_attribute_in_list(self):
+        self.fake = Faker(['en_US', 'en_PH'])
+        expected = set(dir(Faker) + [
+            '_factories', '_locales', '_factory_map', '_weights',
+        ])
+        for factory in self.fake.factories:
+            expected |= {
+                attr for attr in dir(factory) if not attr.startswith('_')
+            }
+        expected = sorted(expected)
+
+        attributes = dir(self.fake)
+
+        assert attributes == expected