Repository: joke2k/faker
Pull request #1884 (2023-06-27 10:17:44)

Add fake automotive vin number function (#1879)
### What does this change

base on https://en.wikipedia.org/wiki/Vehicle_identification_number to create fake vin number data function.

### What was wrong

new provider function

### How this fixes it
Note: I'm not quite familiar with pytest module, therefore I'm not quite confident on creating a new auto test flow for this vin function. Please forgive me.

themattmorris: This is cool!  I am also interested in using this feature, so if needed, I'd be happy to collaborate on building the tests for the feature.

Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index d8b52355..a392635f 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 18.11.1
+current_version = 18.11.2
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 761465b0..199e41b6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,9 +4,8 @@ on:
   push:
     branches:
       - master
-  pull_request:
-    branches:
-      - master
+  pull_request_target:
+    types: [opened, synchronize, reopened]
 
 permissions:
   contents: read
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 246e5cf9..81411978 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 ## Changelog
 
+### [v18.11.2 - 2023-06-27](https://github.com/joke2k/faker/compare/v18.11.1...v18.11.2)
+
+* Fix area codes for `fr_FR` mobile phone numbers. Thanks @QuentinFchx.
+
 ### [v18.11.1 - 2023-06-20](https://github.com/joke2k/faker/compare/v18.11.0...v18.11.1)
 
 *  Update Ukraine Country Code in `phone_number`. Thanks @lexxai.
diff --git a/VERSION b/VERSION
index 75beb73c..0ec67166 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-18.11.1
+18.11.2
diff --git a/docs/communityproviders.rst b/docs/communityproviders.rst
index 4aa887ca..c87ee2cf 100644
--- a/docs/communityproviders.rst
+++ b/docs/communityproviders.rst
@@ -44,6 +44,9 @@ Here's a list of Providers written by the community:
 | Posts         | Fake posts in markdown    | `mdgen`_                         |
 |               | format                    |                                  |
 +---------------+---------------------------+----------------------------------+
+| PySpark       | Fake PySpark DataFrane    | `faker_pyspark`_                 |
+|               | and Schema generator      |                                  |
++---------------+---------------------------+----------------------------------+
 | Vehicle       | Fake vehicle information  | `faker_vehicle`_                 |
 |               | includes Year Make Model  |                                  |
 +---------------+---------------------------+----------------------------------+
@@ -87,6 +90,7 @@ In order to be included, your provider must satisfy these requirement:
 .. _faker_microservice: https://pypi.org/project/faker-microservice/
 .. _faker_music: https://pypi.org/project/faker_music/
 .. _mdgen: https://pypi.org/project/mdgen/
+.. _faker_pyspark: https://pypi.org/project/faker-pyspark/
 .. _faker_vehicle: https://pypi.org/project/faker-vehicle/
 .. _faker_web: https://pypi.org/project/faker_web/
 .. _faker_wifi_essid: https://pypi.org/project/faker-wifi-essid/
diff --git a/docs/conf.py b/docs/conf.py
index 925e51c9..de2064d9 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 = "18.11.1"
+version = "18.11.2"
 # The full version, including alpha/beta/rc tags.
-release = "18.11.1"
+release = "18.11.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 eab1dd81..cfbf27ff 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 = "18.11.1"
+VERSION = "18.11.2"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/automotive/__init__.py b/faker/providers/automotive/__init__.py
index 4a3156e3..1f89d37c 100644
--- a/faker/providers/automotive/__init__.py
+++ b/faker/providers/automotive/__init__.py
@@ -7,6 +7,36 @@ from .. import BaseProvider, ElementsType
 localized = True
 
 
+def calculate_vin_str_weight(s: str, weight_factor: list) -> int:
+    """
+    multiply s(str) by weight_factor char by char
+    e.g.
+    input: s="ABCDE", weight_factor=[1, 2, 3, 4, 5]
+    return: A*1 + B*2 + C*3 + D*4 + E*5
+
+    will multiply 0 when len(weight_factor) less than len(s)
+    """
+
+    def _get_char_weight(c: str) -> int:
+        """A=1, B=2, ...., I=9,
+        J=1, K=2, ..., R=9,
+        S=2, T=3, ..., Z=9
+        """
+        if ord(c) <= 64:  # 0-9
+            return int(c)
+        if ord(c) <= 73:  # A-I
+            return ord(c) - 64
+        if ord(c) <= 82:  # J-R
+            return ord(c) - 73
+        # S-Z
+        return ord(c) - 81
+
+    res = 0
+    for i, c in enumerate(s):
+        res += _get_char_weight(c) * weight_factor[i] if i < len(weight_factor) else 0
+    return res
+
+
 class Provider(BaseProvider):
     """Implement default automotive provider for Faker."""
 
@@ -20,3 +50,14 @@ class Provider(BaseProvider):
             self.random_element(self.license_formats),
         )
         return self.numerify(temp)
+
+    def vin(self) -> str:
+        """Generate vin number."""
+        vin_chars = "1234567890ABCDEFGHJKLMNPRSTUVWXYZ"  # I, O, Q are restricted
+        front_part = self.bothify("????????", letters=vin_chars)
+        rear_part = self.bothify("????????", letters=vin_chars)
+        front_part_weight = calculate_vin_str_weight(front_part, [8, 7, 6, 5, 4, 3, 2, 10])
+        rear_part_weight = calculate_vin_str_weight(rear_part, [9, 8, 7, 6, 5, 4, 3, 2])
+        checksum = (front_part_weight + rear_part_weight) % 11
+        checksum_char = "X" if checksum == 10 else str(checksum)
+        return front_part + checksum_char + rear_part
diff --git a/faker/providers/phone_number/fr_FR/__init__.py b/faker/providers/phone_number/fr_FR/__init__.py
index 1631b7c7..2b37951f 100644
--- a/faker/providers/phone_number/fr_FR/__init__.py
+++ b/faker/providers/phone_number/fr_FR/__init__.py
@@ -241,8 +241,34 @@ class Provider(PhoneNumberProvider):
         "594",
         "596",
         # mobile numbers
-        "6##",
-        "7##",
+        "60#",
+        "61#",
+        "62#",
+        "630",
+        "631",
+        "632",
+        "633",
+        "634",
+        "635",
+        "636",
+        "637",
+        "638",
+        "64#",
+        "65#",
+        "66#",
+        "67#",
+        "68#",
+        "695",
+        "698",
+        "699",
+        "73#",
+        "74#",
+        "75#",
+        "76#",
+        "77#",
+        "78#",
+        "79#",
+        # special numbers
         "80#",
     )
 


Test patch:
diff --git a/tests/providers/test_automotive.py b/tests/providers/test_automotive.py
index 62f725b0..8373e799 100644
--- a/tests/providers/test_automotive.py
+++ b/tests/providers/test_automotive.py
@@ -8,6 +8,7 @@ from faker.providers.automotive.ro_RO import Provider as RoRoAutomotiveProvider
 from faker.providers.automotive.ru_RU import Provider as RuRuAutomotiveProvider
 from faker.providers.automotive.sk_SK import Provider as SkSkAutomotiveProvider
 from faker.providers.automotive.tr_TR import Provider as TrTrAutomotiveProvider
+from faker.providers.automotive import calculate_vin_str_weight
 
 
 class _SimpleAutomotiveTestMixin:
@@ -23,6 +24,19 @@ class _SimpleAutomotiveTestMixin:
             assert match is not None
             self.perform_extra_checks(license_plate, match)
 
+    def test_vin(self, faker, num_samples):
+        for _ in range(num_samples):
+            vin_number = faker.vin()
+            # length check: 17
+            assert len(vin_number) == 17
+
+            # verify checksum: vin_number[8]
+            front_part_weight = calculate_vin_str_weight(vin_number[:8], [8, 7, 6, 5, 4, 3, 2, 10])
+            rear_part_weight = calculate_vin_str_weight(vin_number[9:], [9, 8, 7, 6, 5, 4, 3, 2])
+            checksum = (front_part_weight + rear_part_weight) % 11
+            checksum_str = "X" if checksum == 10 else str(checksum)
+            assert vin_number[8] == checksum_str
+
 
 class TestArBh(_SimpleAutomotiveTestMixin):
     """Test ar_BH automotive provider methods"""