From d23efeb4811c5aded10b547fc0f4c0fcc3fec8dc Mon Sep 17 00:00:00 2001 From: Sean H <50758691+cyanghsieh@users.noreply.github.com> Date: Fri, 7 Jul 2023 22:19:15 +0800 Subject: [PATCH] Add fake automotive `vin` number function (#1879) (#1884) --- faker/providers/automotive/__init__.py | 41 ++++++++++++++++++++++++++ tests/providers/test_automotive.py | 14 +++++++++ 2 files changed, 55 insertions(+) diff --git a/faker/providers/automotive/__init__.py b/faker/providers/automotive/__init__.py index 4a3156e34c..1f89d37c2a 100644 --- a/faker/providers/automotive/__init__.py +++ b/faker/providers/automotive/__init__.py @@ -7,6 +7,36 @@ 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 @@ def license_plate(self) -> str: 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/tests/providers/test_automotive.py b/tests/providers/test_automotive.py index 62f725b087..8373e79959 100644 --- a/tests/providers/test_automotive.py +++ b/tests/providers/test_automotive.py @@ -8,6 +8,7 @@ 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 @@ def test_license_plate(self, faker, num_samples): 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"""