Repository: joke2k/faker
Pull request #1289 (2020-10-01 20:13:45)

Add date_time provider for pt_PT
### What does this changes

Adds a date_time provider for European Portuguese language.

### What was wrong

There isn't an European Portuguese date_time provider, at the moment.

### How this fixes it

Adds the European Portuguese date_time provider with days of the week and month names.

gplgps: There was a missing line required for flake8 tests. Added it now
gplgps: So the issue wasn't a missing line at the end, but a line between test classes. Hopefully it's fixed now.

Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 2a02bbc6..7a0157f8 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 4.4.0
+current_version = 4.9.0
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/.github/workflows/autobump.yml b/.github/workflows/autobump.yml
index 8559d029..2d0b55bb 100644
--- a/.github/workflows/autobump.yml
+++ b/.github/workflows/autobump.yml
@@ -29,7 +29,8 @@ jobs:
           githubToken: ${{ secrets.GITHUB_TOKEN }}
       - uses: actions/checkout@v2
         with:
-          fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
+         persist-credentials: false
+         fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
       - name: Get Next Version
         uses: "fcurella/gh-action-next-version@main"
         id: nextversion
diff --git a/.gitignore b/.gitignore
index ad8222c2..f3259eea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,3 +52,4 @@ nosetests.xml
 *.iml
 *.ipr
 venv/
+.vscode
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 685d3604..7be7e9c7 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,32 @@
 Changelog
 =========
 
+`4.9.0 - 2020-10-08 <https://github.com/joke2k/faker/compare/v4.8.0...v4.9.0>`_
+-------------------------------------------------------------------------------
+
+* Add ``.unique()`` for unique values. Thanks @coiax.
+
+`4.8.0 - 2020-10-08 <https://github.com/joke2k/faker/compare/v4.7.0...v4.8.0>`_
+-------------------------------------------------------------------------------
+
+* Add automotive provider for ``tr_TR``. Thanks @molcay.
+
+`4.7.0 - 2020-10-08 <https://github.com/joke2k/faker/compare/v4.6.0...v4.7.0>`_
+-------------------------------------------------------------------------------
+
+* Add province list and add 2 new district to ``ne_NP``. Thanks @iamsauravsharma.
+
+
+`4.6.0 - 2020-10-08 <https://github.com/joke2k/faker/compare/v4.5.0...v4.6.0>`_
+-------------------------------------------------------------------------------
+
+* Add Currency provider for ``sv_SE``. Thanks @frangiz.
+
+`4.5.0 - 2020-10-06 <https://github.com/joke2k/faker/compare/v4.4.0...v4.5.0>`_
+-------------------------------------------------------------------------------
+
+* Add ``pt_PT`` credit card provider. Thanks @rubenandre.
+
 `4.4.0 - 2020-10-02 <https://github.com/joke2k/faker/compare/v4.3.0...v4.4.0>`_
 -------------------------------------------------------------------------------
 
diff --git a/README.rst b/README.rst
index 172bac71..e99f40db 100644
--- a/README.rst
+++ b/README.rst
@@ -310,6 +310,38 @@ By default all generators share the same instance of ``random.Random``, which
 can be accessed with ``from faker.generator import random``. Using this may
 be useful for plugins that want to affect all faker instances.
 
+Unique values
+-------------
+
+Through use of the ``.unique`` property on the generator, you can guarantee
+that any generated values are unique for this specific instance.
+
+.. code:: python
+
+   from faker import Faker
+   fake = Faker()
+   names = [fake.unique.first_name() for i in range(500)]
+   assert len(set(names)) == len(names)
+
+Calling ``fake.unique.clear()`` clears the already seen values.
+Note, to avoid infinite loops, after a number of attempts to find a unique
+value, Faker will throw a ``UniquenessException``. Beware of the `birthday
+paradox <https://en.wikipedia.org/wiki/Birthday_problem>`_, collisions
+are more likely than you'd think.
+
+
+.. code:: python
+
+   from faker import Faker
+
+   fake = Faker()
+   for i in range(3):
+        # Raises a UniquenessException
+        fake.unique.boolean()
+
+In addition, only hashable arguments and return values can be used
+with ``.unique``.
+
 Seeding the Generator
 ---------------------
 
diff --git a/VERSION b/VERSION
index fdc66988..6ed7776b 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-4.4.0
+4.9.0
diff --git a/docs/communityproviders.rst b/docs/communityproviders.rst
index b26dc41b..e4cd0ff5 100644
--- a/docs/communityproviders.rst
+++ b/docs/communityproviders.rst
@@ -8,24 +8,23 @@ Here's a list of Providers written by the community:
 +---------------+--------------------------+----------------------------------+
 | Provider name | Description              | URL                              |
 +===============+==========================+==================================+
-| WebProvider   | Web-related data such as | `faker_web`_                     +
-|               | mime-type and web server |                                  +
-|               | versions.                |                                  +
-+---------------+--------------------------+----------------------------------+
-| CloudProvider | Cloud-related data       | `faker_cloud`_                   +
-|               | generic or specialized   |                                  +
-|               | by cloud.                |                                  +
-+---------------+--------------------------+----------------------------------+
-| Wi-Fi ESSID   | Fake Wi-Fi ESSIDs.       | `faker_wifi_essid`_              +
-+---------------+--------------------------+----------------------------------+
 | Credit Score  | Fake credit score data   | `faker_credit_score`_            |
 |               | for testing purposes     |                                  |
 +---------------+--------------------------+----------------------------------+
 | Microservice  | Fake microservice names  | `faker_microservice`_            |
 +---------------+--------------------------+----------------------------------+
+| Posts         | Fake posts in markdown   | `mdgen`_                         |
+|               | format                   |                                  |
++---------------+--------------------------+----------------------------------+
 | Vehicle       | Fake vehicle information | `faker_vehicle`_                 |
 |               | includes Year Make Model |                                  |
 +---------------+--------------------------+----------------------------------+
+| WebProvider   | Web-related data such as | `faker_web`_                     +
+|               | mime-type and web server |                                  +
+|               | versions.                |                                  +
++---------------+--------------------------+----------------------------------+
+| Wi-Fi ESSID   | Fake Wi-Fi ESSIDs.       | `faker_wifi_essid`_              +
++---------------+--------------------------+----------------------------------+
 
 If you want to add your own provider to this list, please submit a Pull Request to our `repo`_.
 
@@ -41,8 +40,8 @@ In order to be inlcuded, your provider must satisfy these requirement:
 .. _repo: https://github.com/joke2k/faker/
 .. _OSI-Approved: https://opensource.org/licenses/alphabetical
 .. _faker_web: https://pypi.org/project/faker_web/
-.. _faker_cloud: https://pypi.org/project/faker-cloud/
 .. _faker_wifi_essid: https://pypi.org/project/faker-wifi-essid/
 .. _faker_credit_score: https://pypi.org/project/faker-credit-score/
 .. _faker_microservice: https://pypi.org/project/faker-microservice/
 .. _faker_vehicle: https://pypi.org/project/faker-vehicle/
+.. _mdgen: https://pypi.org/project/mdgen/
diff --git a/docs/conf.py b/docs/conf.py
index 53ff5f27..710f5d9c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -51,9 +51,9 @@ copyright = '2014, Daniele Faraglia'
 # built documents.
 #
 # The short X.Y version.
-version = '4.4.0'
+version = '4.9.0'
 # The full version, including alpha/beta/rc tags.
-release = '4.4.0'
+release = '4.9.0'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/docs/fakerclass.rst b/docs/fakerclass.rst
index c7a13304..656b4c8b 100644
--- a/docs/fakerclass.rst
+++ b/docs/fakerclass.rst
@@ -287,3 +287,72 @@ a cheatsheet for new ``Faker`` in multiple locale mode.
     fake['ja_JP'].luzon_province()
 
 .. _the relevant PR: https://github.com/joke2k/faker/pull/1052#issuecomment-557170225
+
+
+Unique Values
+-------------
+
+New in version ``v4.2.0` is the ``.unique`` attribute on the
+``Faker`` proxy.
+
+Accessing provider methods through this attribute guarantees that
+the returned values are unique for the lifetime of the ``Faker`` instance.
+
+
+.. code:: python
+
+   import faker
+   
+   fake = faker.Faker()
+
+   numbers = set(fake.unique.random_int() for i in range(1000))
+   assert len(numbers) == 1000
+
+To clear already seen values, simply call ``fake.unique.clear()``, which will
+allow previous values generated to be returned again.
+
+Different argument signatures for provider methods do not share
+a uniqueness pool.
+
+
+.. code:: python
+
+   import faker
+   
+   fake = faker.Faker()
+
+   numbers = set(fake.unique.random_int(min=1, max=10) for i in range(10))
+   other_numbers = set(fake.unique.random_int(min=1, max=5) for i in range(5))
+
+   assert other_numbers.issubset(numbers)
+
+If the range of possible values generated by a provider function is small
+and the ``.unique`` attribute has been used, it's possible that no
+suitable unique value will be found after a certain number of iterations.
+
+To avoid infinite loops, at that point, a ``UniquenessException`` will
+be raised.
+
+
+.. code:: python
+
+   import faker
+   
+   fake = faker.Faker()
+
+   for i in range(3):
+        fake.unique.boolean()  # UniquenessException!
+
+
+As a final caveat, only hashable arguments and return values can be used
+with the ``.unique`` attribute, as it is backed internally by a set for
+fast membership testing.
+
+
+.. code:: python
+
+   import faker
+   
+   fake = faker.Faker()
+
+   fake.unique.profile()  # TypeError: unhashable type: 'dict'
diff --git a/faker/__init__.py b/faker/__init__.py
index 8f2c303c..af92858c 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.4.0'
+VERSION = '4.9.0'
diff --git a/faker/exceptions.py b/faker/exceptions.py
new file mode 100644
index 00000000..bb744b9a
--- /dev/null
+++ b/faker/exceptions.py
@@ -0,0 +1,8 @@
+class BaseFakerException(Exception):
+    """The base exception for all Faker exceptions."""
+
+
+class UniquenessException(BaseFakerException):
+    """To avoid infinite loops, after a certain number of attempts,
+    the "unique" attribute of the Proxy will throw this exception.
+    """
diff --git a/faker/providers/address/ne_NP/__init__.py b/faker/providers/address/ne_NP/__init__.py
index acff4d35..ecdb181e 100644
--- a/faker/providers/address/ne_NP/__init__.py
+++ b/faker/providers/address/ne_NP/__init__.py
@@ -502,6 +502,8 @@ class Provider(AddressProvider):
         'ईश्वरपुर',
     )
 
+    # district taken from
+    # https://www.election.gov.np/election/np/district-wise-constituency-map.html
     districts = (
         'अछाम',
         'अर्घाखाँची',
@@ -535,7 +537,8 @@ class Provider(AddressProvider):
         'धनकुटा',
         'धनुषा',
         'धादिङ',
-        'नवलपरासी',
+        'नवलपरासी (बर्दघाट सुस्ता पूर्व)',
+        'नवलपरासी (बर्दघाट सुस्ता पश्चिम)',
         'नुवाकोट',
         'पर्वत',
         'पर्सा',
@@ -559,7 +562,8 @@ class Provider(AddressProvider):
         'म्याग्दी',
         'रसुवा',
         'रामेछाप',
-        'रुकुम',
+        '‍रुकुम पूर्व',
+        'रुकुम पश्चिम',
         'रूपन्देही',
         'रोल्पा',
         'रौतहट',
@@ -580,6 +584,24 @@ class Provider(AddressProvider):
         'हुम्ला',
     )
 
+    # province taken from
+    # https://ne.wikipedia.org/wiki/%E0%A4%A8%E0%A5%87%E0%A4%AA%E0%A4%BE%E0%A4%B2%E0%A4%95%E0%A4%BE_%E0%A4%AA%E0%A5%8D%E0%A4%B0%E0%A4%A6%E0%A5%87%E0%A4%B6%E0%A4%B9%E0%A4%B0%E0%A5%82  # noqa: E501
+    provinces = (
+        'प्रदेश नं १',
+        'प्रदेश नं २',
+        'बाग्मती प्रदेश',
+        'गण्डकी प्रदेश',
+        'प्रदेश नं ५',
+        'कर्णाली प्रदेश',
+        'सुदूरपश्चिम प्रदेश',
+    )
+
+    def province(self):
+        """
+        :example सुदूरपश्चिम प्रदेश
+        """
+        return self.random_element(self.provinces)
+
     def district(self):
         """
         :example अछाम
@@ -588,7 +610,7 @@ class Provider(AddressProvider):
 
     def city(self):
         """
-        :example कावासोत
+        :example कावासोती
         """
         return self.random_element(self.cities)
 
diff --git a/faker/providers/automotive/tr_TR/__init__.py b/faker/providers/automotive/tr_TR/__init__.py
new file mode 100644
index 00000000..e5498b23
--- /dev/null
+++ b/faker/providers/automotive/tr_TR/__init__.py
@@ -0,0 +1,31 @@
+import re
+
+from .. import Provider as AutomotiveProvider
+
+
+class Provider(AutomotiveProvider):
+    """Implement automotive provider for ``tr_TR`` locale.
+
+    Sources:
+
+    - https://en.wikipedia.org/wiki/Vehicle_registration_plates_of_Turkey
+    """
+
+    license_formats = (
+        '## ? ####',
+        '## ? #####',
+        '## ?? ###',
+        '## ?? ####',
+        '## ??? ##',
+        '## ??? ###',
+    )
+    ascii_uppercase_turkish = 'ABCDEFGHIJKLMNOPRSTUVYZ'
+
+    def license_plate(self) -> str:
+        """Generate a license plate."""
+        temp = re.sub(r'\?',
+                      lambda x: self.random_element(self.ascii_uppercase_turkish),
+                      self.random_element(self.license_formats))
+        temp = temp.replace('##', '{:02d}', 1)
+        temp = temp.format(self.random_element(range(1, 82)))
+        return self.numerify(temp)
diff --git a/faker/providers/credit_card/pt_PT/__init__.py b/faker/providers/credit_card/pt_PT/__init__.py
new file mode 100644
index 00000000..57f2e74f
--- /dev/null
+++ b/faker/providers/credit_card/pt_PT/__init__.py
@@ -0,0 +1,68 @@
+from collections import OrderedDict
+
+from .. import CreditCard
+from .. import Provider as CreditCardProvider
+
+
+class Provider(CreditCardProvider):
+    """Implementation of ``pt_PT`` locale credit card
+
+    For all methods that take ``card_type`` as an argument a random card type
+    will be used if the supplied value is ``None``. The list of valid card types
+    includes ``'visa'``, ``'mastercard'`` and ``'maestro'``.
+
+    Source: https://bincheck.org/portugal
+    """
+
+    prefix_visa = ['400131', '400190', '400817', '402192', '402947', '402956', '403005', '403006', '403007',
+                   '403008', '403271', '404520', '404530', '405758', '406170', '406475', '407548', '407549',
+                   '407575', '408237', '408239', '409842', '409843', '410000', '410344', '410345', '410553',
+                   '410557', '411635', '411700', '411701', '411869', '412487', '412488', '412489', '412657',
+                   '412782', '412990', '413014', '413793', '413871', '415158', '415159', '415170', '415171',
+                   '415174', '415175', '415194', '415195', '415238', '415272', '415273', '415403', '415404',
+                   '415405', '415440', '415441', '415569', '415920', '415961', '416952', '416963', '416970',
+                   '417005', '417091', '417092', '417337', '418847', '419022', '419682', '419683', '419684',
+                   '421149', '421510', '422080', '422240', '422241', '422414', '422417', '422597', '422869',
+                   '423392', '423393', '424118', '424184', '424208', '424661', '425509', '425510', '425906',
+                   '426150', '426360', '426370', '427256', '427304', '427729', '427770', '427867', '428139',
+                   '428184', '428185', '428186', '428187', '429711', '430240', '430241', '431926', '433390',
+                   '433391', '433511', '433512', '433513', '433599', '433618', '433622', '433966', '437886',
+                   '438257', '439070', '440637', '440644', '440645', '442664', '443977', '443978', '444224',
+                   '444227', '445961', '445962', '446140', '446144', '449389', '450915', '451156', '451166',
+                   '454755', '455250', '455290', '455292', '455658', '456811', '456812', '457031', '458058',
+                   '458059', '459432', '459433', '459449', '460340', '460341', '460342', '461247', '461248',
+                   '461249', '462731', '462732', '464406', '465964', '476066', '476067', '476068', '476069',
+                   '476070', '476071', '476329', '477920', '477921', '477922', '477947', '477989', '478062',
+                   '478063', '479702', '479736', '483088', '485672', '486449', '486457', '489434', '489485',
+                   '490772', '490830', '490831', '490832', '490841', '490863', '491213', '491546', '491547',
+                   '491613', '492194', '493402', '493480', '493800', '493801', '493830', '498800', '499968',
+                   '499969', '499986', '422239', '422041', '464409', '464408']
+
+    prefix_mastercard = ['510122', '510123', '512556', '518772', '519744', '519774', '520342', '524552',
+                         '524878', '525625', '525808', '526819', '527014', '528024', '529119', '530267',
+                         '530770', '532355', '536468', '541171', '541557', '542081', '542098', '542858',
+                         '543099', '543116', '543123', '544051', '544052', '544233', '547260', '547459',
+                         '548168', '548169', '552727', '552755', '553057', '554506', '554517', '554518',
+                         '556660', '557836', '557882', '557883', '557888']
+
+    prefix_maestro = ['501654', '501659', '670530', '670811', '670812', '676938', '676938', '677393',
+                      '677707', '670835', '670817']
+
+    credit_card_types = OrderedDict((
+        ('maestro', CreditCard('Maestro', prefix_maestro, 16, security_code='CVV2')),
+        ('mastercard', CreditCard('Mastercard', prefix_mastercard, 16, security_code='CVV2')),
+        ('visa', CreditCard('Visa', prefix_visa, 16, security_code='CVV2')),
+    ))
+
+    def credit_card_expire(self, start='now', end='+4y', date_format='%m/%y'):
+        """Generate a credit card expiry date.
+
+        This method uses |date_time_between| under the hood to generate the
+        expiry date, so the ``start`` and ``end`` arguments work in the same way
+        here as it would in that method. For the actual formatting of the expiry
+        date, |strftime| is used and ``date_format`` is simply passed
+        to that method.
+        """
+
+        expire_date = self.generator.date_time_between(start, end)
+        return expire_date.strftime(date_format)
diff --git a/faker/providers/currency/sv_SE/__init__.py b/faker/providers/currency/sv_SE/__init__.py
new file mode 100644
index 00000000..69919686
--- /dev/null
+++ b/faker/providers/currency/sv_SE/__init__.py
@@ -0,0 +1,178 @@
+from .. import Provider as CurrencyProvider
+
+# Names taken from https://www.iban.se/currency-codes
+
+
+class Provider(CurrencyProvider):
+    # Format: (code, name)
+    currencies = (
+        ("AED", "UAE Dirham"),
+        ("AFN", "Afghani"),
+        ("ALL", "Lek"),
+        ("AMD", "Armenisk Dram"),
+        ("ANG", "Gulden från Nederländska Antillerna"),
+        ("AOA", "Kwanza"),
+        ("ARS", "Argentinsk Peso"),
+        ("AUD", "Australisk Dollar"),
+        ("AWG", "Arubisk Florin"),
+        ("AZN", "Azerbajdzjansk Manat"),
+        ("BAM", "Konvertibel Mark"),
+        ("BBD", "Barbadosdollar"),
+        ("BDT", "Taka"),
+        ("BGN", "Bulgarisk Lev"),
+        ("BHD", "Bahraini Dinar"),
+        ("BIF", "Burundi-franc"),
+        ("BMD", "Bermuda-dollar"),
+        ("BND", "Brunei-dollar"),
+        ("BOB", "Boliviano"),
+        ("BOV", "Mvdol"),
+        ("BRL", "Brasilisk Real"),
+        ("BSD", "Bahamasdollar"),
+        ("BTN", "Ngultrum"),
+        ("BWP", "Pula"),
+        ("BYR", "Vitrysk Rubel"),
+        ("BZD", "Belize-dollar"),
+        ("CAD", "Kanadensisk Dollar"),
+        ("CDF", "Kongolesisk Franc"),
+        ("CHE", "WIR Euro"),
+        ("CHF", "Schweizerfranc"),
+        ("CHW", "WIR Franc"),
+        ("CLF", "Unidad de Fomento"),
+        ("CLP", "Chilensk Peso"),
+        ("CNY", "Yuan Renminbi"),
+        ("COP", "Colombiansk Peso"),
+        ("COU", "Unidad de Valor Real"),
+        ("CRC", "Costa Ricansk Colon"),
+        ("CUC", "Peso Convertible"),
+        ("CUP", "Kubansk Peso"),
+        ("CVE", "Kap Verde Escudo"),
+        ("CZK", "Tjeckisk Koruna"),
+        ("DJF", "Djibouti-franc"),
+        ("DKK", "Dansk Krone"),
+        ("DOP", "Dominicansk Peso"),
+        ("DZD", "Algerisk Dinar"),
+        ("EGP", "Egyptiskt pund"),
+        ("ERN", "Nakfa"),
+        ("ETB", "Etiopisk Birr"),
+        ("EUR", "Euro"),
+        ("FJD", "Fiji Dollar"),
+        ("FKP", "Pund från Falklandöarna"),
+        ("GBP", "Pund Sterling"),
+        ("GEL", "Lari"),
+        ("GHS", "Ghana Cedi"),
+        ("GIP", "Gibraltar-pund"),
+        ("GMD", "Dalasi"),
+        ("GNF", "Guinea-franc"),
+        ("GTQ", "Quetzal"),
+        ("GYD", "Guyana-dollar"),
+        ("HKD", "Hong Kong-dollar"),
+        ("HNL", "Lempira"),
+        ("HRK", "Kuna"),
+        ("HTG", "Gourde"),
+        ("HUF", "Forint"),
+        ("IDR", "Rupiah"),
+        ("ILS", "Ny Israelisk Shekel"),
+        ("INR", "Indisk Rupie"),
+        ("IQD", "Irakisk Dinar"),
+        ("IRR", "Iransk Rial"),
+        ("ISK", "Isländsk Krona"),
+        ("JMD", "Jamaica-dollar"),
+        ("JOD", "Jordanisk Dinar"),
+        ("JPY", "Yen"),
+        ("KES", "Kenyansk Shilling"),
+        ("KGS", "Som"),
+        ("KHR", "Riel"),
+        ("KMF", "Comoros-franc"),
+        ("KPW", "Nordkoreansk Won"),
+        ("KRW", "Won"),
+        ("KWD", "Kuwaiti Dinar"),
+        ("KYD", "Caymanöar-dollar"),
+        ("KZT", "Tenge"),
+        ("LAK", "Kip"),
+        ("LBP", "Libanesiskt pund"),
+        ("LKR", "Sri Lanka Rupie"),
+        ("LRD", "Liberiansk Dollar"),
+        ("LSL", "Loti"),
+        ("LYD", "Libysk Dinar"),
+        ("MAD", "Marockansk Dirham"),
+        ("MDL", "Moldavisk Leu"),
+        ("MGA", "Malagasy Ariary"),
+        ("MKD", "Denar"),
+        ("MMK", "Kyat"),
+        ("MNT", "Tugrik"),
+        ("MOP", "Pataca"),
+        ("MRO", "Ouguiya"),
+        ("MUR", "Mauritius Rupie"),
+        ("MVR", "Rufiyaa"),
+        ("MWK", "Kwacha"),
+        ("MXN", "Mexikansk Peso"),
+        ("MXV", "Mexikansk Unidad de Inversion (UDI)"),
+        ("MYR", "Malaysisk Ringgit"),
+        ("MZN", "Mozambique Metical"),
+        ("NAD", "Namibia Dollar"),
+        ("NGN", "Naira"),
+        ("NIO", "Cordoba Oro"),
+        ("NOK", "Norsk Krone"),
+        ("NOK", "Norwegian Krone"),
+        ("NPR", "Nepalesisk Rupie"),
+        ("NZD", "Nya Zealand-dollar"),
+        ("OMR", "Rial Omani"),
+        ("PAB", "Balboa"),
+        ("PEN", "Nuevo Sol"),
+        ("PGK", "Kina"),
+        ("PHP", "Filippinsk Peso"),
+        ("PKR", "Pakistansk Rupie"),
+        ("PLN", "Zloty"),
+        ("PYG", "Guarani"),
+        ("QAR", "Qatari Rial"),
+        ("RON", "Rumänsk Leu"),
+        ("RSD", "Serbisk Dinar"),
+        ("RUB", "Rysk Rubel"),
+        ("RWF", "Rwanda Franc"),
+        ("SAR", "Saudi Riyal"),
+        ("SBD", "Dollar från Salomonöarna"),
+        ("SCR", "Seychell-rupie"),
+        ("SDG", "Sudanesiskt pund"),
+        ("SEK", "Svensk Krona"),
+        ("SGD", "Singapore Dollar"),
+        ("SHP", "Saint Helena pund"),
+        ("SLL", "Leone"),
+        ("SOS", "Somalisk Shilling"),
+        ("SRD", "Surinam Dollar"),
+        ("SSP", "Sydsudanesiskt pund"),
+        ("STD", "Dobra"),
+        ("SVC", "El Salvador Colon"),
+        ("SYP", "Syriskt pund"),
+        ("SZL", "Lilangeni"),
+        ("THB", "Baht"),
+        ("TJS", "Somoni"),
+        ("TMT", "Turkmenistansk Ny Manat"),
+        ("TND", "Tunisisk Dinar"),
+        ("TOP", "Pa’anga"),
+        ("TRY", "Turkisk Lira"),
+        ("TTD", "Trinidad och Tobago Dollar"),
+        ("TWD", "Ny Taiwanesisk Dollar"),
+        ("TZS", "Tanzanisk Shilling"),
+        ("UAH", "Hryvnia"),
+        ("UGX", "Uganda Shilling"),
+        ("USD", "US Dollar"),
+        ("USN", "US Dollar (Nästa dag)"),
+        ("UYI", "Uruguay Peso en Unidades Indexadas (URUIURUI)"),
+        ("UYU", "Peso Uruguayo"),
+        ("UZS", "Uzbekistansk Sum"),
+        ("VEF", "Bolivar"),
+        ("VND", "Dong"),
+        ("VUV", "Vatu"),
+        ("WST", "Tala"),
+        ("XAF", "CFA Franc BEAC"),
+        ("XCD", "East Caribbean Dollar"),
+        ("XDR", "SDR (Särskild dragningsrätt)"),
+        ("XOF", "CFA Franc BCEAO"),
+        ("XPF", "CFP Franc"),
+        ("XSU", "Sucre"),
+        ("XUA", "ADB Beräkningsenhet"),
+        ("YER", "Yemeni Rial"),
+        ("ZAR", "Rand"),
+        ("ZMW", "Zambian Kwacha"),
+        ("ZWL", "Zimbabwe Dollar"),
+    )
diff --git a/faker/providers/date_time/pt_PT/__init__.py b/faker/providers/date_time/pt_PT/__init__.py
new file mode 100644
index 00000000..e853211f
--- /dev/null
+++ b/faker/providers/date_time/pt_PT/__init__.py
@@ -0,0 +1,37 @@
+from .. import Provider as DateTimeProvider
+
+
+class Provider(DateTimeProvider):
+
+    DAY_NAMES = {
+        "0": "domingo",
+        "1": "segunda-feira",
+        "2": "terça-feira",
+        "3": "quarta-feira",
+        "4": "quinta-feira",
+        "5": "sexta-feira",
+        "6": "sábado",
+    }
+
+    MONTH_NAMES = {
+        "01": "janeiro",
+        "02": "fevereiro",
+        "03": "março",
+        "04": "abril",
+        "05": "maio",
+        "06": "junho",
+        "07": "julho",
+        "08": "agosto",
+        "09": "setembro",
+        "10": "outubro",
+        "11": "novembro",
+        "12": "dezembro",
+    }
+
+    def day_of_week(self):
+        day = self.date("%w")
+        return self.DAY_NAMES[day]
+
+    def month_name(self):
+        month = self.month()
+        return self.MONTH_NAMES[month]
diff --git a/faker/proxy.py b/faker/proxy.py
index 67f261ef..d3910562 100644
--- a/faker/proxy.py
+++ b/faker/proxy.py
@@ -1,13 +1,17 @@
+import functools
 import random
 import re
 
 from collections import OrderedDict
 
 from faker.config import DEFAULT_LOCALE
+from faker.exceptions import UniquenessException
 from faker.factory import Factory
 from faker.generator import Generator
 from faker.utils.distribution import choices_distribution
 
+_UNIQUE_ATTEMPTS = 1000
+
 
 class Faker:
     """Proxy class capable of supporting multiple locales"""
@@ -23,6 +27,7 @@ class Faker:
                  generator=None, includes=None, **config):
         self._factory_map = OrderedDict()
         self._weights = None
+        self._unique_proxy = UniqueProxy(self)
 
         if isinstance(locale, str):
             locales = [locale.replace('-', '_')]
@@ -105,6 +110,10 @@ class Faker:
             factory = self._select_factory(attr)
             return getattr(factory, attr)
 
+    @property
+    def unique(self):
+        return self._unique_proxy
+
     def _select_factory(self, method_name):
         """
         Returns a random factory that supports the provider method
@@ -236,3 +245,44 @@ class Faker:
 
     def items(self):
         return self._factory_map.items()
+
+
+class UniqueProxy:
+    def __init__(self, proxy):
+        self._proxy = proxy
+        self._seen = {}
+        self._sentinel = object()
+
+    def clear(self):
+        self._seen = {}
+
+    def __getattr__(self, name: str):
+        obj = getattr(self._proxy, name)
+        if callable(obj):
+            return self._wrap(name, obj)
+        else:
+            raise TypeError("Accessing non-functions through .unique is not supported.")
+
+    def _wrap(self, name, function):
+        @functools.wraps(function)
+        def wrapper(*args, **kwargs):
+            key = (name, args, tuple(sorted(kwargs.items())))
+
+            generated = self._seen.setdefault(key, {self._sentinel})
+
+            # With use of a sentinel value rather than None, we leave
+            # None open as a valid return value.
+            retval = self._sentinel
+
+            for i in range(_UNIQUE_ATTEMPTS):
+                if retval not in generated:
+                    break
+                retval = function(*args, **kwargs)
+            else:
+                raise UniquenessException("Got duplicated values after {0:,} iterations.".format(_UNIQUE_ATTEMPTS))
+
+            generated.add(retval)
+
+            return retval
+
+        return wrapper


Test patch:
diff --git a/docs/pytest-fixtures.rst b/docs/pytest-fixtures.rst
index ef9e0233..7c44a983 100644
--- a/docs/pytest-fixtures.rst
+++ b/docs/pytest-fixtures.rst
@@ -9,8 +9,9 @@ Pytest Fixtures
        assert isinstance(faker.name(), str)
 
 Out of the box, the ``faker`` fixture returns a session-scoped ``Faker`` instance to be used across
-all tests in your test suite. This instance defaults to the ``en-US`` locale, and it is reseeded
-using a seed value of ``0`` prior to each test.
+all tests in your test suite. This instance defaults to the ``en-US`` locale, it is reseeded
+using a seed value of ``0`` prior to each test, and the ``.unique`` remembered generated values
+are cleared.
 
 To change the default locale, you can define a session-scoped autouse ``faker_session_locale``
 fixture in your top level ``conftest.py``. To change the default seed value, you define a
diff --git a/faker/contrib/pytest/plugin.py b/faker/contrib/pytest/plugin.py
index 48c0eea9..2d866db8 100644
--- a/faker/contrib/pytest/plugin.py
+++ b/faker/contrib/pytest/plugin.py
@@ -33,5 +33,6 @@ def faker(request):
     if 'faker_seed' in request.fixturenames:
         seed = request.getfixturevalue('faker_seed')
     fake.seed_instance(seed=seed)
+    fake.unique.clear()
 
     return fake
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 00000000..c6481d5f
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,1 @@
+pytest_plugins = ["pytester"]
diff --git a/tests/providers/test_address.py b/tests/providers/test_address.py
index 0494ccec..0aa21db7 100644
--- a/tests/providers/test_address.py
+++ b/tests/providers/test_address.py
@@ -981,6 +981,12 @@ class TestKoKr:
 class TestNeNp:
     """Test ne_NP address provider methods"""
 
+    def test_province(self, faker, num_samples):
+        for _ in range(num_samples):
+            province = faker.province()
+            assert isinstance(province, str)
+            assert province in NeNpAddressProvider.provinces
+
     def test_district(self, faker, num_samples):
         for _ in range(num_samples):
             district = faker.district()
diff --git a/tests/providers/test_automotive.py b/tests/providers/test_automotive.py
index 65bc0c34..a971f7d0 100644
--- a/tests/providers/test_automotive.py
+++ b/tests/providers/test_automotive.py
@@ -3,6 +3,7 @@ import re
 from faker.providers.automotive.de_DE import Provider as DeDeAutomotiveProvider
 from faker.providers.automotive.es_ES import Provider as EsEsAutomotiveProvider
 from faker.providers.automotive.ru_RU import Provider as RuRuAutomotiveProvider
+from faker.providers.automotive.tr_TR import Provider as TrTrAutomotiveProvider
 
 
 class _SimpleAutomotiveTestMixin:
@@ -155,7 +156,25 @@ class TestEsEs:
             assert self.old_format_pattern.match(plate)
             assert plate[:2] == "CA"
 
-    def test_plate_format(self, faker):
-        plate = faker.license_plate()
-        assert isinstance(plate, str)
-        assert self.new_format_pattern.match(plate) or self.old_format_pattern.match(plate)
+    def test_plate_format(self, faker, num_samples):
+        for _ in range(num_samples):
+            plate = faker.license_plate()
+            assert isinstance(plate, str)
+            assert self.new_format_pattern.match(plate) or self.old_format_pattern.match(plate)
+
+
+class TestTrTr(_SimpleAutomotiveTestMixin):
+    """Test tr_TR automotive provider methods"""
+    license_plate_pattern = re.compile(
+        r'\d{2} [A-Z] \d{4}|'
+        r'\d{2} [A-Z] \d{5}|'
+        r'\d{2} [A-Z]{2} \d{3}|'
+        r'\d{2} [A-Z]{2} \d{4}|'
+        r'\d{2} [A-Z]{3} \d{2}|'
+        r'\d{2} [A-Z]{3} \d{3}',
+    )
+
+    def perform_extra_checks(self, license_plate, match):
+        [city_code, letters, _] = license_plate.split(' ')
+        assert int(city_code) in range(1, 82)
+        assert all(letter in TrTrAutomotiveProvider.ascii_uppercase_turkish for letter in letters)
diff --git a/tests/providers/test_credit_card.py b/tests/providers/test_credit_card.py
index 11b3df83..6c2561eb 100644
--- a/tests/providers/test_credit_card.py
+++ b/tests/providers/test_credit_card.py
@@ -125,3 +125,26 @@ class TestRuRu:
             card_data = faker.credit_card_full().split('\n')
             assert re.match('[A-Za-z]+', card_data[1])
             assert card_data[4] in RuRuBankProvider.banks
+
+
+class TestPtPt:
+    """Test pt_PT credit card provider methods"""
+
+    visa_pattern = re.compile(r'4[0-9]{15}')
+    mastercard_pattern = re.compile(r'5[1-5][0-9]{14}')
+    maestro_pattern = re.compile(r'(50|67)[0-9]{14}')
+
+    def test_visa(self, faker, num_samples):
+        for _ in range(num_samples):
+            number = faker.credit_card_number('visa')
+            assert self.visa_pattern.fullmatch(number)
+
+    def test_mastercard(self, faker, num_samples):
+        for _ in range(num_samples):
+            number = faker.credit_card_number('mastercard')
+            assert self.mastercard_pattern.fullmatch(number)
+
+    def test_maestro(self, faker, num_samples):
+        for _ in range(num_samples):
+            number = faker.credit_card_number('maestro')
+            assert self.maestro_pattern.fullmatch(number)
diff --git a/tests/providers/test_currency.py b/tests/providers/test_currency.py
index d8823169..81bd2220 100644
--- a/tests/providers/test_currency.py
+++ b/tests/providers/test_currency.py
@@ -110,3 +110,25 @@ class TestEsEs:
         for _ in range(num_samples):
             name = faker.currency_name()
             assert name in self.currency_names
+
+
+class TestSvSe:
+    """Test sv_SE currency provider"""
+    num_samples = 100
+
+    @classmethod
+    def setup_class(cls):
+        from faker.providers.currency.sv_SE import Provider as SvSeCurrencyProvider
+        cls.provider = SvSeCurrencyProvider
+        cls.currencies = cls.provider.currencies
+        cls.currency_codes, cls.currency_names = tuple(zip(*cls.currencies))
+
+    def test_currency(self, faker, num_samples):
+        for _ in range(num_samples):
+            cur = faker.currency()
+            assert cur in self.currencies
+
+    def test_currency_name(self, faker, num_samples):
+        for _ in range(num_samples):
+            name = faker.currency_name()
+            assert name in self.currency_names
diff --git a/tests/providers/test_date_time.py b/tests/providers/test_date_time.py
index 98f33076..4be1edcb 100644
--- a/tests/providers/test_date_time.py
+++ b/tests/providers/test_date_time.py
@@ -25,6 +25,7 @@ from faker.providers.date_time.es_ES import Provider as EsEsProvider
 from faker.providers.date_time.hy_AM import Provider as HyAmProvider
 from faker.providers.date_time.it_IT import Provider as ItItProvider
 from faker.providers.date_time.pl_PL import Provider as PlProvider
+from faker.providers.date_time.pt_PT import Provider as PtPtProvider
 from faker.providers.date_time.ru_RU import Provider as RuProvider
 from faker.providers.date_time.sk_SK import Provider as SkSkProvider
 from faker.providers.date_time.ta_IN import Provider as TaInProvider
@@ -868,3 +869,18 @@ class TestTrTr(unittest.TestCase):
     def test_month(self):
         month = self.fake.month_name()
         assert month in TrTrProvider.MONTH_NAMES.values()
+
+
+class TestPtPt(unittest.TestCase):
+
+    def setUp(self):
+        self.fake = Faker('pt_PT')
+        Faker.seed(0)
+
+    def test_day(self):
+        day = self.fake.day_of_week()
+        assert day in PtPtProvider.DAY_NAMES.values()
+
+    def test_month(self):
+        month = self.fake.month_name()
+        assert month in PtPtProvider.MONTH_NAMES.values()
diff --git a/tests/pytest/test_unique_clear.py b/tests/pytest/test_unique_clear.py
new file mode 100644
index 00000000..4bf382c9
--- /dev/null
+++ b/tests/pytest/test_unique_clear.py
@@ -0,0 +1,37 @@
+def test_unique_clears(testdir):
+    """Successive uses of the `faker` pytest fixture have the
+    generated unique values cleared between functions."""
+
+    testdir.makepyfile(
+        """
+        import pytest
+        from faker.exceptions import UniquenessException
+
+        NUM_SAMPLES = 100
+
+        def test_fully_exhaust_unique_booleans(faker):
+            _dummy = [faker.boolean() for _ in range(NUM_SAMPLES)]
+
+            faker.unique.boolean()
+            faker.unique.boolean()
+            with pytest.raises(UniquenessException):
+                faker.unique.boolean()
+            _dummy = [faker.boolean() for _ in range(NUM_SAMPLES)]
+
+        def test_do_not_exhaust_booleans(faker):
+            faker.unique.boolean()
+
+        def test_fully_exhaust_unique_booleans_again(faker):
+            _dummy = [faker.boolean() for _ in range(NUM_SAMPLES)]
+
+            faker.unique.boolean()
+            faker.unique.boolean()
+            with pytest.raises(UniquenessException):
+                faker.unique.boolean()
+            _dummy = [faker.boolean() for _ in range(NUM_SAMPLES)]
+        """,
+    )
+
+    result = testdir.runpytest()
+
+    result.assert_outcomes(passed=3)
diff --git a/tests/test_proxy.py b/tests/test_proxy.py
index 2b48d2bf..44eb73d5 100644
--- a/tests/test_proxy.py
+++ b/tests/test_proxy.py
@@ -322,6 +322,7 @@ class TestFakerProxyClass:
         fake = Faker(['en_US', 'en_PH'])
         expected = set(dir(Faker) + [
             '_factories', '_locales', '_factory_map', '_weights',
+            '_unique_proxy',
         ])
         for factory in fake.factories:
             expected |= {
diff --git a/tests/test_unique.py b/tests/test_unique.py
new file mode 100644
index 00000000..f939a6ab
--- /dev/null
+++ b/tests/test_unique.py
@@ -0,0 +1,62 @@
+import pytest
+
+from faker import Faker
+from faker.exceptions import UniquenessException
+
+
+class TestUniquenessClass:
+    def test_uniqueness(self):
+        fake = Faker("en_US")
+
+        names = set()
+        # There are (at time of writing 690) first names in the
+        # US identity provider. Birthday paradox puts the chances of
+        # no duplicates in 250 selections as low enough to be impossible
+        for i in range(250):
+            first_name = fake.unique.first_name()
+            assert first_name not in names
+            names.add(first_name)
+
+    def test_sanity_escape(self):
+        fake = Faker()
+
+        # Those of you who are especially astute may realise
+        # there are only 2 booleans, so the third boolean cannot
+        # be unique.
+        with pytest.raises(UniquenessException, match=r"Got duplicated values after [\d,]+ iterations."):
+            for i in range(3):
+                _ = fake.unique.boolean()
+
+    def test_uniqueness_clear(self):
+        fake = Faker()
+
+        for i in range(2):
+            fake.unique.boolean()
+
+        fake.unique.clear()
+
+        # Because we cleared the generated values, this will not
+        # throw an exception
+        fake.unique.boolean()
+
+    def test_exclusive_arguments(self):
+        """Calls through the "unique" portal will only affect
+        calls with that specific function signature.
+        """
+        fake = Faker()
+
+        for i in range(10):
+            fake.unique.random_int(min=1, max=10)
+
+        # Different signature, so new pool. If they shared a pool
+        # this would throw a sanity exception
+        fake.unique.random_int(min=2, max=10)
+
+    def test_functions_only(self):
+        """Accessing non-functions through the `.unique` attribute
+        will throw a TypeError."""
+
+        fake = Faker()
+
+        with pytest.raises(TypeError, match="Accessing non-functions through .unique is not supported."):
+            fake.unique.locales