Repository: joke2k/faker
Pull request #1783 (2023-01-12 11:33:31)

Add method to generate JSON as bytes
### What does this change

Add wrapper method around the JSON provider to generate JSON as bytes.

### What was wrong

I use Faker through factory boy and couldn't find a convenient way to achieve that. The change is quite small so I thought it could be a nice addition to the library.

### How this fixes it

Wraps the existing JSON provider and encode its output to bytes.



Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 0275c100..394611da 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 16.5.0
+current_version = 16.6.1
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..02e8b626
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,13 @@
+# These are supported funding model platforms
+
+github: [joke2k]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5185c91d..8c09d00b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 ## Changelog
 
+### [v16.6.1 - 2023-01-24](https://github.com/joke2k/faker/compare/v16.6.0...v16.6.1)
+
+* Fix `siret` and `siren` generation for `fr_FR` locale company information. Thanks @daamien.
+
+### [v16.6.0 - 2023-01-17](https://github.com/joke2k/faker/compare/v16.5.0...v16.6.0)
+
+* Add `fr_CA` address provider. Thanks @davebulaval.
+
 ### [v16.5.0 - 2023-01-17](https://github.com/joke2k/faker/compare/v16.4.0...v16.5.0)
 
 * Add `zipcode`, `postcode` and `postalcode` to `pl_PL`. Thanks @bukowa.
@@ -1268,17 +1276,17 @@
 
 ### [0.7.16 - 2017-06-09](https://github.com/joke2k/faker/compare/v0.7.15...v0.7.16)
 
-* fix timezone issues with ``date_time_between`` provider.
+* Fix timezone issues with ``date_time_between`` provider.
 * Add ``ext_word_list`` parameter to methods in the ``Lorem`` generator.
   Thanks @guinslym.
 
 ### [0.7.15 - 2017-06-02](https://github.com/joke2k/faker/compare/v0.7.14...v0.7.15)
 
-* fix start and end date for datetime provider methods.
+* Fix start and end date for datetime provider methods.
 
 ### [0.7.14 - 2017-06-02](https://github.com/joke2k/faker/compare/v0.7.13...v0.7.14)
 
-* fix ``future_date``, `and ``past_date`` bounds.
+* Fix ``future_date``, `and ``past_date`` bounds.
 
 ### [0.7.13 - 2017-06-02](https://github.com/joke2k/faker/compare/v0.7.12...v0.7.13)
 
@@ -1291,7 +1299,6 @@
 * Add ``future_date``, ``future_datetime``, ``past_date`` and ``past_datetime``
   to DateTime Provider
 
-
 ### [0.7.12 - 2017-05-10](https://github.com/joke2k/faker/compare/v0.7.11...v0.7.12)
 
 * Add Japanese lorem provider. Thanks @richmondwang.
@@ -1309,14 +1316,14 @@
 
 
 ### [0.7.11 - 2017-04-09](https://github.com/joke2k/faker/compare/v0.7.10...v0.7.11)
--
+
 * Added french words for days and months. Thanks @sblondon.
 * Reorganized tests. Thanks @grantbachman.
 * Added file path provider. Thanks @diegommarino.
 * Fixed packaging issue with tests module. Thanks @eukreign for the report.
 
 ### [0.7.10 - 2017-03-13](https://github.com/joke2k/faker/compare/v0.7.9...v0.7.10)
---
+
 * Add ISBN-10 and ISBN-13. Thanks @grantbachman.
 * Add colors for ``fr_FR``. Thanks @sblondon.
 
@@ -1343,7 +1350,7 @@
 * Fix ``fa_IR`` city generator. Thanks @kdeldycke.
 
 ### [0.7.6 - 2016-12-19](https://github.com/joke2k/faker/compare/v0.7.5...v0.7.6)
--
+
 * Fix packaging issue with ``docs`` directory. Thanks @wyattanderson.
 
 ### [0.7.5 - 2016-12-16](https://github.com/joke2k/faker/compare/v0.7.4...v0.7.5)
@@ -1382,9 +1389,8 @@
 * Rename PyPI package to ``Faker``.
 
 ### [0.6.0 - 2016-08-09](https://github.com/joke2k/faker/compare/v0.5.11...v0.6.0)
--
-* Dropped Python 2.6 support
 
+* Dropped Python 2.6 support
 
 ### [0.5.11 - 2016-08-09](https://github.com/joke2k/faker/compare/v0.5.10...v0.5.11)
 
@@ -1482,7 +1488,7 @@
 * Added en_AU provider. Thanks @xfxf.
 
 ### [0.5.2 - 2015-06-11](https://github.com/joke2k/faker/compare/v0.5.1...v0.5.2)
--
+
 * Added ``uuid4`` to ``misc`` provider. Thanks Jared Culp.
 * Fixed ``jcb15`` and ``jcb16`` in ``credit_card`` provider.
   Thanks Rodrigo Braz.
@@ -1491,9 +1497,7 @@
 * Added ``--include`` flag to command line tool. Thanks Flavio Curella.
 * Added ``country_code`` to `address`` provider. Thanks @elad101 and Tobin Brown.
 
-
 ### [0.5.1 - 2015-05-21](https://github.com/joke2k/faker/compare/v0.5...v0.5.1)
------------------------------------------------------------------------------
 
 * Fixed egg installation. Thanks David R. MacIver, @kecaps
 * Updated person names for ``ru_RU``. Thanks @mousebaiker.
@@ -1523,7 +1527,6 @@
   ``misc`` provider.
 
 ### [0.5.0 - 2015-02-16](https://github.com/joke2k/faker/compare/v0.4.2...v0.5)
-------------------------------------------------------------------------------
 
 * Localized providers
 * Updated ``ko_KR`` provider. Thanks Lee Yeonjae.
@@ -1562,12 +1565,12 @@
 * Added ``hi_IN`` localized provider. Thanks Pratik Kabra.
 * Refactoring of command line
 
-### 0.3.2 - 2013-11-11
+### [0.3.2 - 2013-11-11](https://github.com/joke2k/faker/compare/v0.3.1...v0.3.2)
 
 * New provider: Credit card generator
 * Improved Documentor
 
-### 0.3.1 - 2013-10-18
+### [0.3.1 - 2013-10-18](https://github.com/joke2k/faker/compare/v0.3...v0.3.1)
 
 * FIX setup.py
 
@@ -1579,7 +1582,6 @@
 * documentor prints localized provider after all defaults
 * FIX tests for python 2.6
 
-
 ### 0.2 - 2010-12-01
 
 * New providers: ``Python``, ``File``
diff --git a/VERSION b/VERSION
index 28ebe8b4..d9216d9a 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-16.5.0
+16.6.1
diff --git a/docs/communityproviders.rst b/docs/communityproviders.rst
index fc211029..e458f1ed 100644
--- a/docs/communityproviders.rst
+++ b/docs/communityproviders.rst
@@ -18,6 +18,9 @@ Here's a list of Providers written by the community:
 | Credit Score  | Fake credit score data    | `faker_credit_score`_            |
 |               | for testing purposes      |                                  |
 +---------------+---------------------------+----------------------------------+
+| Datasets      | Build providers based     | `faker-datasets`_                |
+|               | on datasets               |                                  |
++---------------+---------------------------+----------------------------------+
 | Education     | Public school name and    | `faker_education`_               |
 |               | info for testing purposes |                                  |
 +---------------+---------------------------+----------------------------------+
@@ -71,6 +74,7 @@ In order to be included, your provider must satisfy these requirement:
 .. _faker_airtravel: https://pypi.org/project/faker_airtravel/
 .. _faker_biology: https://pypi.org/project/faker_biology/
 .. _faker_credit_score: https://pypi.org/project/faker-credit-score/
+.. _faker-datasets: https://pypi.org/project/faker-datasets/
 .. _faker_education: https://pypi.org/project/faker_education/
 .. _faker-file: https://pypi.org/project/faker-file/
 .. _faker_geoscience: https://pypi.org/project/faker-geoscience/
diff --git a/docs/conf.py b/docs/conf.py
index b364ce9a..dc0fbc09 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 = "16.5.0"
+version = "16.6.1"
 # The full version, including alpha/beta/rc tags.
-release = "16.5.0"
+release = "16.6.1"
 
 # 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 ac4ee7e4..f60273f3 100644
--- a/faker/__init__.py
+++ b/faker/__init__.py
@@ -2,6 +2,6 @@ from faker.factory import Factory
 from faker.generator import Generator
 from faker.proxy import Faker
 
-VERSION = "16.5.0"
+VERSION = "16.6.1"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/address/en_CA/__init__.py b/faker/providers/address/en_CA/__init__.py
index 4602bb0e..f2ac4caf 100644
--- a/faker/providers/address/en_CA/__init__.py
+++ b/faker/providers/address/en_CA/__init__.py
@@ -2,6 +2,8 @@ import re
 
 from typing import Optional
 
+from faker.providers import ElementsType
+
 from ..en import Provider as AddressProvider
 
 
@@ -33,9 +35,9 @@ class Provider(AddressProvider):
         "Y",
     )
 
-    city_prefixes = ("North", "East", "West", "South", "New", "Lake", "Port")
+    city_prefixes: ElementsType[str] = ("North", "East", "West", "South", "New", "Lake", "Port")
 
-    city_suffixes = (
+    city_suffixes: ElementsType[str] = (
         "town",
         "ton",
         "land",
@@ -337,7 +339,7 @@ class Provider(AddressProvider):
         "YT": ["Y"],
     }
 
-    city_formats = (
+    city_formats: ElementsType[str] = (
         "{{city_prefix}} {{first_name}}{{city_suffix}}",
         "{{city_prefix}} {{first_name}}",
         "{{first_name}}{{city_suffix}}",
diff --git a/faker/providers/address/fr_CA/__init__.py b/faker/providers/address/fr_CA/__init__.py
new file mode 100644
index 00000000..40a5ba13
--- /dev/null
+++ b/faker/providers/address/fr_CA/__init__.py
@@ -0,0 +1,81 @@
+from typing import Any
+
+from ..en_CA import Provider as EnCaProvider
+
+
+class Provider(EnCaProvider):
+    #  Most of the parts are identical to en_CA, we simply override those who are not shared between the two.
+
+    city_prefixes = (
+        "Ville",
+        "Baie",
+        "Saint-",
+        "Sainte-",
+        "Mont-",
+        "La",
+        "Lac-",
+        "L'",
+        "L'Île-",
+    )
+
+    city_suffixes = (
+        "Est",
+        "Ouest",
+        "-sur-Mer",
+    )
+
+    street_prefixes = (
+        "rue",
+        "rue",
+        "chemin",
+        "avenue",
+        "boulevard",
+        "route",
+        "rang",
+        "allé",
+        "montée",
+    )
+
+    provinces = (
+        "Alberta",
+        "Colombie-Britannique",
+        "Manitoba",
+        "Nouveau-Brunswick",
+        "Terre-Neuve-et-Labrador",
+        "Territoires du Nord-Ouest",
+        "Nouvelle-Écosse",
+        "Nunavut",
+        "Ontario",
+        "Île-du-Prince-Édouard",
+        "Québec",
+        "Saskatchewan",
+        "Yukon",
+    )
+
+    street_name_formats = (
+        "{{street_prefix}} {{first_name}}",
+        "{{street_prefix}} {{last_name}}",
+    )
+
+    city_formats = (
+        "{{city_prefix}} {{last_name}}",
+        "{{city_prefix}} {{last_name}}",
+        "{{city_prefix}}-{{city_prefix}}-{{last_name}}",
+        "{{city_prefix}} {{first_name}} {{city_suffix}}",
+        "{{city_prefix}} {{first_name}}",
+        "{{city_prefix}} {{first_name}}",
+        "{{city_prefix}} {{first_name}}",
+        "{{last_name}}",
+        "{{last_name}}",
+        "{{first_name}} {{city_suffix}}",
+        "{{last_name}} {{city_suffix}}",
+    )
+
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
+        super().__init__(*args, **kwargs)
+
+    def street_prefix(self) -> str:
+        """
+        :example: 'rue'
+        """
+        return self.random_element(self.street_prefixes)
diff --git a/faker/providers/company/fr_FR/__init__.py b/faker/providers/company/fr_FR/__init__.py
index b8115cf4..8cafce67 100644
--- a/faker/providers/company/fr_FR/__init__.py
+++ b/faker/providers/company/fr_FR/__init__.py
@@ -1,5 +1,7 @@
 from typing import Tuple
 
+from faker.utils.checksums import calculate_luhn
+
 from .. import Provider as CompanyProvider
 
 
@@ -121,9 +123,11 @@ class Provider(CompanyProvider):
 
     def siren(self) -> str:
         """
-        Generates a siren number (9 digits).
+        Generates a siren number (9 digits). Formatted as '### ### ###'.
         """
-        return self.numerify(self.siren_format)
+        code = self.numerify("########")
+        luhn_checksum = str(calculate_luhn(float(code)))
+        return f"{code[:3]} {code[3:6]} {code[6:]}{luhn_checksum}"
 
     def siret(self, max_sequential_digits: int = 2) -> str:
         """
@@ -131,10 +135,15 @@ class Provider(CompanyProvider):
         It is in fact the result of the concatenation of a siren number (9 digits),
         a sequential number (4 digits) and a control number (1 digit) concatenation.
         If $max_sequential_digits is invalid, it is set to 2.
-        :param max_sequential_digits: The maximum number of digits for the sequential number (> 0 && <= 4).
+
+        The siret number is formatted as '### ### ### #####'.
+        :param max_sequential_digits The maximum number of digits for the sequential number (> 0 && <= 4).
         """
         if max_sequential_digits > 4 or max_sequential_digits <= 0:
             max_sequential_digits = 2
 
         sequential_number = str(self.random_number(max_sequential_digits)).zfill(4)
-        return self.numerify(self.siren() + " " + sequential_number + "#")
+
+        code = self.siren().replace(" ", "") + sequential_number
+        luhn_checksum = str(calculate_luhn(float(code)))
+        return f"{code[:3]} {code[3:6]} {code[6:9]} {code[9:]}{luhn_checksum}"
diff --git a/faker/providers/misc/__init__.py b/faker/providers/misc/__init__.py
index b37953ab..5fd22ea0 100644
--- a/faker/providers/misc/__init__.py
+++ b/faker/providers/misc/__init__.py
@@ -488,6 +488,21 @@ class Provider(BaseProvider):
             delimiter="|",
         )
 
+    def json_bytes(
+        self,
+        data_columns: Optional[List] = None,
+        num_rows: int = 10,
+        indent: Optional[int] = None,
+        cls: Optional[Type[json.JSONEncoder]] = None,
+    ) -> bytes:
+        """
+        Generate random JSON structure and return as bytes.
+
+        For more information on the different arguments of this method, refer to
+        :meth:`json() <faker.providers.misc.Provider.json>` which is used under the hood.
+        """
+        return self.json(data_columns=data_columns, num_rows=num_rows, indent=indent, cls=cls).encode()
+
     def json(
         self,
         data_columns: Optional[List] = None,


Test patch:
diff --git a/tests/providers/test_address.py b/tests/providers/test_address.py
index 564012c9..d9cf2e8b 100644
--- a/tests/providers/test_address.py
+++ b/tests/providers/test_address.py
@@ -26,6 +26,7 @@ from faker.providers.address.es_ES import Provider as EsEsAddressProvider
 from faker.providers.address.es_MX import Provider as EsMxAddressProvider
 from faker.providers.address.fa_IR import Provider as FaIrAddressProvider
 from faker.providers.address.fi_FI import Provider as FiFiAddressProvider
+from faker.providers.address.fr_CA import Provider as FrCaAddressProvider
 from faker.providers.address.fr_CH import Provider as FrChAddressProvider
 from faker.providers.address.fr_FR import Provider as FrFrAddressProvider
 from faker.providers.address.he_IL import Provider as HeIlAddressProvider
@@ -2222,6 +2223,46 @@ class TestUkUa:
             assert region in UkUaAddressProvider.region_names
 
 
+class TestFrCa:
+    """Test fr_CA 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 FrCaAddressProvider.provinces
+
+    def test_province_abbr(self, faker, num_samples):
+        for _ in range(num_samples):
+            province_abbr = faker.province_abbr()
+            assert isinstance(province_abbr, str)
+            assert province_abbr in FrCaAddressProvider.provinces_abbr
+
+    def test_city_prefixes(self, faker, num_samples):
+        for _ in range(num_samples):
+            city_prefix = faker.city_prefix()
+            assert isinstance(city_prefix, str)
+            assert city_prefix in FrCaAddressProvider.city_prefixes
+
+    def test_city_suffixes(self, faker, num_samples):
+        for _ in range(num_samples):
+            city_suffixes = faker.city_suffix()
+            assert isinstance(city_suffixes, str)
+            assert city_suffixes in FrCaAddressProvider.city_suffixes
+
+    def test_street_prefixes(self, faker, num_samples):
+        for _ in range(num_samples):
+            street_prefix = faker.street_prefix()
+            assert isinstance(street_prefix, str)
+            assert street_prefix in FrCaAddressProvider.street_prefixes
+
+    def test_administrative_unit(self, faker, num_samples):
+        for _ in range(num_samples):
+            province = faker.administrative_unit()
+            assert isinstance(province, str)
+            assert province in FrCaAddressProvider.provinces
+
+
 class TestPlPl:
     """Test pl_PL address provider methods"""
 
diff --git a/tests/providers/test_company.py b/tests/providers/test_company.py
index 6df034b1..85dd1ec3 100644
--- a/tests/providers/test_company.py
+++ b/tests/providers/test_company.py
@@ -23,6 +23,7 @@ from faker.providers.company.ru_RU import Provider as RuRuCompanyProvider
 from faker.providers.company.ru_RU import calculate_checksum
 from faker.providers.company.th_TH import Provider as ThThCompanyProvider
 from faker.providers.company.tr_TR import Provider as TrTrCompanyProvider
+from faker.utils.checksums import luhn_checksum
 
 
 class TestAzAz:
@@ -60,6 +61,22 @@ class TestFiFi:
             assert self._has_valid_checksum(company_id)
 
 
+class TestFrFr:
+    """Test fr_FR company provider methods"""
+
+    def test_siren(self, faker, num_samples):
+        for _ in range(num_samples):
+            siren = faker.siren()
+            assert len(siren) == 11
+            assert luhn_checksum(siren.replace(" ", "")) == 0
+
+    def test_siret(self, faker, num_samples):
+        for _ in range(num_samples):
+            siret = faker.siret()
+            assert len(siret) == 17
+            assert luhn_checksum(siret.replace(" ", "")) == 0
+
+
 class TestHyAm:
     """Test hy_AM company provider methods"""
 
diff --git a/tests/providers/test_misc.py b/tests/providers/test_misc.py
index 695defe7..f50822f3 100644
--- a/tests/providers/test_misc.py
+++ b/tests/providers/test_misc.py
@@ -697,6 +697,13 @@ class TestMiscProvider:
         with pytest.raises(TypeError):
             faker_with_foobar.json(**kwargs)
 
+    def test_json_bytes(self, faker_with_foobar):
+        kwargs = {"data_columns": {"item1": "foo_bar"}, "num_rows": 1}
+        json_data_bytes = faker_with_foobar.json_bytes(**kwargs)
+        assert isinstance(json_data_bytes, bytes)
+        json_data = json.loads(json_data_bytes.decode())
+        assert json_data["item1"] == "FooBar"
+
     def test_fixed_width_with_arguments(self, faker_with_foobar):
         kwargs = {
             "data_columns": [