From 17410de9d804f8f840cafe24e122824c8d46ba8d Mon Sep 17 00:00:00 2001 From: Ali Madihi Date: Sun, 19 Mar 2023 23:16:33 +0330 Subject: [PATCH] version 0.0.11 (#12) * add legal id module * add phone number normalize function * fix phone number operator data * improve code coverage * some minimal improvements --- .github/workflows/test.yml | 8 +- README.md | 23 +- persian_tools/bank/banks_code.py | 284 +++++++++--------- .../economic_national_id/__init__.py | 49 --- persian_tools/legal_id/__init__.py | 35 +++ persian_tools/phone_number/__init__.py | 26 +- persian_tools/phone_number/exceptions.py | 7 + persian_tools/phone_number/operators.py | 135 ++++++++- setup.py | 3 +- tests/test_bank_sheba.py | 38 ++- tests/test_digis.py | 12 +- tests/test_economic_national_id.py | 33 -- tests/test_legal_id.py | 40 +++ tests/test_phone_number.py | 38 ++- tests/test_separator.py | 3 + 15 files changed, 472 insertions(+), 262 deletions(-) delete mode 100644 persian_tools/economic_national_id/__init__.py create mode 100644 persian_tools/legal_id/__init__.py delete mode 100644 tests/test_economic_national_id.py create mode 100644 tests/test_legal_id.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 079677c..0b2bf9e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,15 +7,15 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: - matrix: - python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] fail-fast: false + matrix: + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 - - name: Set up Python + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/README.md b/README.md index 5411fed..bf0ee63 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ An anthology of a variety of tools for the Persian language in Python 1. [card number](#card-number) 2. [sheba](#sheba) 5. [national id](#national-id) -6. [economic national id](#economic-national-id) +6. [legal id](#legal-id) 7. [phone number](#phone-number) 8. [bill](#bill) 9. [plate](#plate) @@ -197,21 +197,21 @@ national_id.find_place('0906582709') # {'code': ['089', '090'], 'city': 'کا national_id.find_place('0643005846') # {'code': ['064', '065'], 'city': 'بیرجند', 'province': 'خراسان جنوبی'} ``` -### economic national id -This module contains two functions to generate random iranian economic national id (shenas-e melli) and validate any given id. +### legal id +This module contains two functions to generate random iranian legal id (shenas-e melli) and validate any given id. ```python -from persian_tools import economic_national_id +from persian_tools import legal_id -economic_national_id.validate('10380284790') # True -economic_national_id.validate('11053639140') # False +legal_id.validate('10380284790') # True +legal_id.validate('11053639140') # False -economic_national_id.generate_random() # '10100387143' -economic_national_id.generate_random() # '77111986110' +legal_id.generate_random() # '10100387143' +legal_id.generate_random() # '77111986110' ``` ### phone number -This module can validate and give you some data from a phone number. +This module can validate, normalize and give you some data from a phone number. ```python from persian_tools import phone_number @@ -221,6 +221,11 @@ phone_number.validate('+989123456789') # True phone_number.validate('989123456789') # True phone_number.validate('98912345678') # False +phone_number.normalize('00989022002580') # 09022002580 +phone_number.normalize('+989022002580') # 09022002580 +phone_number.normalize('9022002580') # 09022002580 +phone_number.normalize('9022002580', '0') # 09022002580 +phone_number.normalize('9022002580', '+98') # 09022002580 phone_number.operator_data('09123456789') # {'province': ['البرز', 'زنجان', 'سمنان', 'قزوین', 'قم', 'برخی از شهرستان های استان مرکزی'], 'base': 'تهران', 'type': ['permanent'], 'operator': 'همراه اول'} diff --git a/persian_tools/bank/banks_code.py b/persian_tools/bank/banks_code.py index 6442f7a..4817cab 100644 --- a/persian_tools/bank/banks_code.py +++ b/persian_tools/bank/banks_code.py @@ -34,55 +34,60 @@ def shahr_account_number_calculator(sheba: str) -> dict: data = [ { - 'nickname': 'ayandeh', - 'name': 'Ayandeh Bank', - 'persian_name': 'بانک آینده', - 'card_prefix': ['636214'], - 'sheba_code': ['062'], + 'nickname': 'central-bank', + 'name': 'Central Bank of Iran', + 'persian_name': 'بانک مرکزی ایران', + 'card_prefix': ['636797'], + 'sheba_code': ['010'], }, { - 'nickname': 'eghtesad-novin', - 'name': 'Eghtesad Novin Bank', - 'persian_name': 'بانک اقتصاد نوین', - 'card_prefix': ['627412'], - 'sheba_code': ['055'], + 'nickname': 'sanat-o-madan', + 'name': 'Sanat O Madan Bank', + 'persian_name': 'بانک صنعت و معدن', + 'card_prefix': ['627961'], + 'sheba_code': ['011'], }, { - 'nickname': 'ansar', - 'name': 'Ansar Bank', - 'persian_name': 'بانک انصار', - 'card_prefix': ['627381'], - 'sheba_code': ['063'], + 'nickname': 'mellat', + 'name': 'Mellat Bank', + 'persian_name': 'بانک ملت', + 'card_prefix': ['610433', '991975'], + 'sheba_code': ['012'], }, { - 'nickname': 'iran-zamin', - 'name': 'Iran Zamin Bank', - 'persian_name': 'بانک ایران زمین', - 'card_prefix': ['505785'], - 'sheba_code': ['069'], + 'nickname': 'refah', + 'name': 'Refah Bank', + 'persian_name': 'بانک رفاه کارگران', + 'card_prefix': ['589463'], + 'sheba_code': ['013'], }, { - 'nickname': 'parsian', - 'name': 'Parsian Bank', - 'persian_name': 'بانک پارسیان', - 'card_prefix': ['622106', '627884'], - 'sheba_code': ['054'], - 'account_number_calculator': parsian_account_number_calculator + 'nickname': 'maskan', + 'name': 'Maskan Bank', + 'persian_name': 'بانک مسکن', + 'card_prefix': ['628023'], + 'sheba_code': ['014'], }, { - 'nickname': 'pasargad', - 'name': 'Pasargad Bank', - 'persian_name': 'بانک پاسارگاد', - 'card_prefix': ['502229', '639347'], - 'sheba_code': ['057'], - 'account_number_calculator': pasargad_account_number_calculator + 'nickname': 'sepah', + 'name': 'Sepah Bank', + 'persian_name': 'بانک سپه', + 'card_prefix': ['589210'], + 'sheba_code': ['015'], }, { - 'nickname': 'post', - 'name': 'Post Bank', - 'persian_name': 'پست بانک ایران', - 'card_prefix': ['627760'], - 'sheba_code': ['021'], + 'nickname': 'keshavarzi', + 'name': 'Keshavarzi', + 'persian_name': 'بانک کشاورزی', + 'card_prefix': ['603770', '639217'], + 'sheba_code': ['016'], + }, + { + 'nickname': 'melli', + 'name': 'Melli', + 'persian_name': 'بانک ملی ایران', + 'card_prefix': ['170019', '603799'], + 'sheba_code': ['017'], }, { 'nickname': 'tejarat', @@ -92,11 +97,11 @@ def shahr_account_number_calculator(sheba: str) -> dict: 'sheba_code': ['018'], }, { - 'nickname': 'toose-taavon', - 'name': 'Tosee Taavon Bank', - 'persian_name': 'بانک توسعه تعاون', - 'card_prefix': ['502908'], - 'sheba_code': ['022'], + 'nickname': 'saderat', + 'name': 'Saderat Bank', + 'persian_name': 'بانک صادرات ایران', + 'card_prefix': ['603769', '903769'], + 'sheba_code': ['019'], }, { 'nickname': 'tosee-saderat', @@ -106,39 +111,54 @@ def shahr_account_number_calculator(sheba: str) -> dict: 'sheba_code': ['020'], }, { - 'nickname': 'hekmat-iranian', - 'name': 'Hekmat Iranian Bank', - 'persian_name': 'بانک حکمت ایرانیان', - 'card_prefix': ['636949'], - 'sheba_code': ['065'], + 'nickname': 'post', + 'name': 'Post Bank', + 'persian_name': 'پست بانک ایران', + 'card_prefix': ['627760'], + 'sheba_code': ['021'], }, { - 'nickname': 'middle-east-bank', - 'name': 'Middle East Bank', - 'persian_name': 'بانک خاورمیانه', - 'card_prefix': ['585949'], - 'sheba_code': ['078'], + 'nickname': 'toose-taavon', + 'name': 'Tosee Taavon Bank', + 'persian_name': 'بانک توسعه تعاون', + 'card_prefix': ['502908'], + 'sheba_code': ['022'], }, { - 'nickname': 'dey', - 'name': 'Dey Bank', - 'persian_name': 'بانک دی', - 'card_prefix': ['502938'], - 'sheba_code': ['066'], + 'nickname': 'tosee', + 'name': 'Tosee Bank', + 'persian_name': 'موسسه اعتباری توسعه', + 'card_prefix': ['628157'], + 'sheba_code': '051', }, { - 'nickname': 'resalat', - 'name': 'Resalat Bank', - 'persian_name': 'بانک رسالت', - 'card_prefix': ['504172'], - 'sheba_code': ['070'], + 'nickname': 'ghavamin', + 'name': 'Ghavamin Bank', + 'persian_name': 'بانک قوامین', + 'card_prefix': ['639599'], + 'sheba_code': ['052'], }, { - 'nickname': 'refah', - 'name': 'Refah Bank', - 'persian_name': 'بانک رفاه کارگران', - 'card_prefix': ['589463'], - 'sheba_code': ['013'], + 'nickname': 'karafarin', + 'name': 'Karafarin Bank', + 'persian_name': 'بانک کارآفرین', + 'card_prefix': ['627488'], + 'sheba_code': ['053'], + }, + { + 'nickname': 'parsian', + 'name': 'Parsian Bank', + 'persian_name': 'بانک پارسیان', + 'card_prefix': ['622106', '627884'], + 'sheba_code': ['054'], + 'account_number_calculator': parsian_account_number_calculator + }, + { + 'nickname': 'eghtesad-novin', + 'name': 'Eghtesad Novin Bank', + 'persian_name': 'بانک اقتصاد نوین', + 'card_prefix': ['627412'], + 'sheba_code': ['055'], }, { 'nickname': 'saman', @@ -148,11 +168,12 @@ def shahr_account_number_calculator(sheba: str) -> dict: 'sheba_code': ['056'], }, { - 'nickname': 'sepah', - 'name': 'Sepah Bank', - 'persian_name': 'بانک سپه', - 'card_prefix': ['589210'], - 'sheba_code': ['015'], + 'nickname': 'pasargad', + 'name': 'Pasargad Bank', + 'persian_name': 'بانک پاسارگاد', + 'card_prefix': ['502229', '639347'], + 'sheba_code': ['057'], + 'account_number_calculator': pasargad_account_number_calculator }, { 'nickname': 'sarmayeh', @@ -168,6 +189,13 @@ def shahr_account_number_calculator(sheba: str) -> dict: 'card_prefix': ['639346'], 'sheba_code': ['059'], }, + { + 'nickname': 'mehr-iran', + 'name': 'Mehr Iran Bank', + 'persian_name': 'بانک مهر ایران', + 'card_prefix': ['606373'], + 'sheba_code': ['090', '060'], + }, { 'nickname': 'shahr', 'name': 'City Bank', @@ -177,46 +205,18 @@ def shahr_account_number_calculator(sheba: str) -> dict: 'account_number_calculator': shahr_account_number_calculator }, { - 'nickname': 'saderat', - 'name': 'Saderat Bank', - 'persian_name': 'بانک صادرات ایران', - 'card_prefix': ['603769', '903769'], - 'sheba_code': ['019'], - }, - { - 'nickname': 'sanat-o-madan', - 'name': 'Sanat O Madan Bank', - 'persian_name': 'بانک صنعت و معدن', - 'card_prefix': ['627961'], - 'sheba_code': ['011'], - }, - { - 'nickname': 'mehr-eqtesad', - 'name': 'Mehr Eqtesad Bank', - 'persian_name': 'بانک قرض الحسنه مهر', - 'card_prefix': ['639370'], - 'sheba_code': ['079'], - }, - { - 'nickname': 'ghavamin', - 'name': 'Ghavamin Bank', - 'persian_name': 'بانک قوامین', - 'card_prefix': ['639599'], - 'sheba_code': ['052'], - }, - { - 'nickname': 'karafarin', - 'name': 'Karafarin Bank', - 'persian_name': 'بانک کارآفرین', - 'card_prefix': ['627488'], - 'sheba_code': ['053'], + 'nickname': 'ayandeh', + 'name': 'Ayandeh Bank', + 'persian_name': 'بانک آینده', + 'card_prefix': ['636214'], + 'sheba_code': ['062'], }, { - 'nickname': 'keshavarzi', - 'name': 'Keshavarzi', - 'persian_name': 'بانک کشاورزی', - 'card_prefix': ['603770', '639217'], - 'sheba_code': ['016'], + 'nickname': 'ansar', + 'name': 'Ansar Bank', + 'persian_name': 'بانک انصار', + 'card_prefix': ['627381'], + 'sheba_code': ['063'], }, { 'nickname': 'gardeshgari', @@ -226,39 +226,32 @@ def shahr_account_number_calculator(sheba: str) -> dict: 'sheba_code': ['064'], }, { - 'nickname': 'central-bank', - 'name': 'Central Bank of Iran', - 'persian_name': 'بانک مرکزی ایران', - 'card_prefix': ['636797'], - 'sheba_code': ['010'], - }, - { - 'nickname': 'maskan', - 'name': 'Maskan Bank', - 'persian_name': 'بانک مسکن', - 'card_prefix': ['628023'], - 'sheba_code': ['014'], + 'nickname': 'hekmat-iranian', + 'name': 'Hekmat Iranian Bank', + 'persian_name': 'بانک حکمت ایرانیان', + 'card_prefix': ['636949'], + 'sheba_code': ['065'], }, { - 'nickname': 'mellat', - 'name': 'Mellat Bank', - 'persian_name': 'بانک ملت', - 'card_prefix': ['610433', '991975'], - 'sheba_code': ['012'], + 'nickname': 'dey', + 'name': 'Dey Bank', + 'persian_name': 'بانک دی', + 'card_prefix': ['502938'], + 'sheba_code': ['066'], }, { - 'nickname': 'melli', - 'name': 'Melli', - 'persian_name': 'بانک ملی ایران', - 'card_prefix': ['170019', '603799'], - 'sheba_code': ['017'], + 'nickname': 'iran-zamin', + 'name': 'Iran Zamin Bank', + 'persian_name': 'بانک ایران زمین', + 'card_prefix': ['505785'], + 'sheba_code': ['069'], }, { - 'nickname': 'mehr-iran', - 'name': 'Mehr Iran Bank', - 'persian_name': 'بانک مهر ایران', - 'card_prefix': ['606373'], - 'sheba_code': ['090', '060'], + 'nickname': 'resalat', + 'name': 'Resalat Bank', + 'persian_name': 'بانک رسالت', + 'card_prefix': ['504172'], + 'sheba_code': ['070'], }, { 'nickname': 'kosar', @@ -275,11 +268,18 @@ def shahr_account_number_calculator(sheba: str) -> dict: 'sheba_code': ['075'], }, { - 'nickname': 'tosee', - 'name': 'Tosee Bank', - 'persian_name': 'موسسه اعتباری توسعه', - 'card_prefix': ['628157'], - 'sheba_code': '051', + 'nickname': 'middle-east-bank', + 'name': 'Middle East Bank', + 'persian_name': 'بانک خاورمیانه', + 'card_prefix': ['585949'], + 'sheba_code': ['078'], + }, + { + 'nickname': 'mehr-eqtesad', + 'name': 'Mehr Eqtesad Bank', + 'persian_name': 'بانک قرض الحسنه مهر', + 'card_prefix': ['639370'], + 'sheba_code': ['079'], }, { 'nickname': 'noor-bank', diff --git a/persian_tools/economic_national_id/__init__.py b/persian_tools/economic_national_id/__init__.py deleted file mode 100644 index 3f123e0..0000000 --- a/persian_tools/economic_national_id/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -import random - - -def validate(economic_national_id: str) -> bool: - """Validate the given economic national ID. - - Args: - economic_national_id (str): A 11-digit length numerical string. - - Returns: - bool: True if the ID is valid, otherwise False. - """ - coef = [29, 27, 23, 19, 17] - code = economic_national_id.zfill(11) - - if not code.isdigit() or len(code) != 11: - return False - - _constant = int(code[-2]) + 2 - _sum = 0 - for i in range(10): - _sum += (int(code[i]) + int(_constant)) * coef[i % 5] - - remainder = 0 if _sum % 11 == 10 else _sum % 11 - - return remainder == int(code[-1]) - - -def generate_random() -> str: - """Generate a valid Economic National ID. - - Returns: - str: A valid 11-digit economic national ID. - """ - coef = [29, 27, 23, 19, 17] - - random_number = random.randint(10 ** 8, 10 ** 10 - 1) - random_number = str(random_number).zfill(10) - - _constant = int(random_number[-1]) + 2 - _sum = 0 - for i in range(10): - _sum += (int(random_number[i]) + int(_constant)) * coef[i % 5] - - remainder = _sum % 11 - if _sum % 11 == 10: - remainder = 0 - - return random_number + str(remainder) diff --git a/persian_tools/legal_id/__init__.py b/persian_tools/legal_id/__init__.py new file mode 100644 index 0000000..5a8e148 --- /dev/null +++ b/persian_tools/legal_id/__init__.py @@ -0,0 +1,35 @@ +import random + +_COEFFICIENT = [29, 27, 23, 19, 17] + + +def validate(legal_id: str) -> bool: + code = legal_id.zfill(11) + + if not code.isdigit() or len(code) != 11: + return False + + _constant = int(code[-2]) + 2 + _sum = 0 + for i in range(10): + _sum += (int(code[i]) + int(_constant)) * _COEFFICIENT[i % 5] + + remainder = 0 if _sum % 11 == 10 else _sum % 11 + + return remainder == int(code[-1]) + + +def generate_random() -> str: + random_number = random.randint(10 ** 8, 10 ** 10 - 1) + random_number = str(random_number).zfill(10) + + _constant = int(random_number[-1]) + 2 + _sum = 0 + for i in range(10): + _sum += (int(random_number[i]) + int(_constant)) * _COEFFICIENT[i % 5] + + remainder = _sum % 11 + if _sum % 11 == 10: + remainder = 0 + + return random_number + str(remainder) diff --git a/persian_tools/phone_number/__init__.py b/persian_tools/phone_number/__init__.py index b89acea..0b3d432 100644 --- a/persian_tools/phone_number/__init__.py +++ b/persian_tools/phone_number/__init__.py @@ -1,16 +1,19 @@ import re from typing import Union from .operators import data as operators_data -from .exceptions import InvalidPhoneNumber +from .exceptions import InvalidPhoneNumber, InvalidToken MOBILE_REGEX = '(?:[+|0{2}]?98)?(?:0)?(\\d{3})+(\\d{3})+(\\d{4})' +VALID_TOKENS = ['+98', '98', '0098', '0'] +MOBILE_SUFFIX_REGEX = '^(?:\+98|98|0098|0)' def _get_operator_data(phone_number: str) -> Union[dict, bool]: - found_number = re.findall(MOBILE_REGEX, phone_number) - if found_number: - prefix = found_number[0][0] - return operators_data.get(prefix, False) + phone_number_suffix = re.split(MOBILE_SUFFIX_REGEX, phone_number).pop() + for prefix_length in range(3, 6): + operator_prefix = phone_number_suffix[:prefix_length] + if operators_data.get(operator_prefix): + return operators_data.get(operator_prefix) return False @@ -21,8 +24,19 @@ def validate(phone_number: str) -> bool: def operator_data(phone_number: str) -> bool: res = _get_operator_data(phone_number) - if res is False: raise InvalidPhoneNumber(phone_number) return res + + +def normalize(phone_number: str, token: str = '0') -> str: + if token not in VALID_TOKENS: + raise InvalidToken(token) + + valid = validate(phone_number) + if valid is False: + raise InvalidPhoneNumber(phone_number) + + phone_number_suffix = re.split(MOBILE_SUFFIX_REGEX, phone_number).pop() + return token + phone_number_suffix diff --git a/persian_tools/phone_number/exceptions.py b/persian_tools/phone_number/exceptions.py index 8963f10..3ed84ce 100644 --- a/persian_tools/phone_number/exceptions.py +++ b/persian_tools/phone_number/exceptions.py @@ -3,3 +3,10 @@ def __init__(self, phone_number): self.phone_number = phone_number self.message = phone_number + ' is an invalid phone number' super().__init__(self.message) + + +class InvalidToken(Exception): + def __init__(self, token): + self.token = token + self.message = token + ' is an invalid token' + super().__init__(self.message) diff --git a/persian_tools/phone_number/operators.py b/persian_tools/phone_number/operators.py index 24ee68b..88445be 100644 --- a/persian_tools/phone_number/operators.py +++ b/persian_tools/phone_number/operators.py @@ -4,6 +4,9 @@ 'Irancell': 'ایرانسل', 'Taliya': 'تالیا', 'RightTel': 'رایتل', + 'TeleKish': 'تله کیش', + 'ApTel': 'آپتل', + 'SamanTel': 'سامانتل', } IRANCELL_SAMPLE = { @@ -14,18 +17,13 @@ } data = { + # MCI '910': { 'base': 'کشوری', 'province': [], 'type': ['permanent', 'credit'], 'operator': OPERATORS['MCI'], }, - '914': { - 'province': ['آذربایجان شرقی', 'اردبیل', 'اصفهان'], - 'base': 'آذربایجان غربی', - 'type': ['permanent', 'credit'], - 'operator': OPERATORS['MCI'], - }, '911': { 'province': ['گلستان', 'گیلان'], 'base': 'مازندران', @@ -44,6 +42,12 @@ 'type': ['permanent', 'credit'], 'operator': OPERATORS['MCI'], }, + '914': { + 'province': ['آذربایجان شرقی', 'اردبیل', 'اصفهان'], + 'base': 'آذربایجان غربی', + 'type': ['permanent', 'credit'], + 'operator': OPERATORS['MCI'], + }, '915': { 'province': ['خراسان شمالی', 'خراسان جنوبی', 'سیستان و بلوچستان'], 'base': 'خراسان رضوی', @@ -104,12 +108,28 @@ 'type': ['credit'], 'operator': OPERATORS['MCI'], }, + '995': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit', 'permanent'], + 'operator': OPERATORS['MCI'], + }, + '996': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit', 'permanent'], + 'operator': OPERATORS['MCI'], + }, + + # Taliya '932': { 'province': [], 'base': 'کشوری', 'type': ['credit'], 'operator': OPERATORS['Taliya'], }, + + # RightTel '920': { 'province': [], 'base': 'کشوری', @@ -128,6 +148,14 @@ 'type': ['credit'], 'operator': OPERATORS['RightTel'], }, + '923': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit'], + 'operator': OPERATORS['RightTel'], + }, + + # Irancell '930': IRANCELL_SAMPLE, '933': IRANCELL_SAMPLE, '935': IRANCELL_SAMPLE, @@ -135,6 +163,7 @@ '937': IRANCELL_SAMPLE, '938': IRANCELL_SAMPLE, '939': IRANCELL_SAMPLE, + '900': IRANCELL_SAMPLE, '901': IRANCELL_SAMPLE, '902': IRANCELL_SAMPLE, '903': IRANCELL_SAMPLE, @@ -153,10 +182,102 @@ 'type': ['credit'], 'operator': OPERATORS['Irancell'], }, - '998': { + + # ShatelMobile + '99810': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit'], + 'operator': OPERATORS['ShatelMobile'], + }, + '99811': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit'], + 'operator': OPERATORS['ShatelMobile'], + }, + '99812': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit'], + 'operator': OPERATORS['ShatelMobile'], + }, + '99813': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit'], + 'operator': OPERATORS['ShatelMobile'], + }, + '99814': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit'], + 'operator': OPERATORS['ShatelMobile'], + }, + '99815': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit'], + 'operator': OPERATORS['ShatelMobile'], + }, + '99816': { 'province': [], 'base': 'کشوری', 'type': ['credit'], 'operator': OPERATORS['ShatelMobile'], }, + '99817': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit'], + 'operator': OPERATORS['ShatelMobile'], + }, + + # TeleKish + '934': { + 'province': [], + 'base': 'کشوری', + 'type': ['permanent'], + 'operator': OPERATORS['TeleKish'], + }, + + # ApTel + '99910': { + 'province': [], + 'base': 'کشوری', + 'type': ['permanent'], + 'operator': OPERATORS['ApTel'], + }, + '99911': { + 'province': [], + 'base': 'کشوری', + 'type': ['permanent'], + 'operator': OPERATORS['ApTel'], + }, + '99913': { + 'province': [], + 'base': 'کشوری', + 'type': ['permanent'], + 'operator': OPERATORS['ApTel'], + }, + '99914': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit'], + 'operator': OPERATORS['ApTel'], + }, + + # SamanTel + '9999': { + 'province': [], + 'base': 'کشوری', + 'type': ['credit', 'permanent'], + 'operator': OPERATORS['SamanTel'], + }, + '99999': { + 'province': [], + 'base': 'کشوری', + 'type': ['permanent'], + 'operator': OPERATORS['SamanTel'], + }, } diff --git a/setup.py b/setup.py index 34e2bc7..319d573 100644 --- a/setup.py +++ b/setup.py @@ -23,11 +23,12 @@ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Topic :: Utilities' ], ) diff --git a/tests/test_bank_sheba.py b/tests/test_bank_sheba.py index c763a62..5dd3202 100644 --- a/tests/test_bank_sheba.py +++ b/tests/test_bank_sheba.py @@ -5,6 +5,7 @@ def test_validate(): assert sheba.validate('IR820540102680020817909002') + assert sheba.validate('IR550570022080013447370101') assert sheba.validate('IR01234567890123456789') is False assert sheba.validate('IR012345678901234567890123456789') is False @@ -16,18 +17,41 @@ def test_validate(): def test_bank_data(): actual = sheba.bank_data('IR820540102680020817909002') - expected = {'nickname': 'parsian', 'name': 'Parsian Bank', 'persian_name': 'بانک پارسیان', - 'card_prefix': ['622106', '627884'], 'sheba_code': ['054'], 'account_number': '020817909002', - 'formatted_account_number': '002-00817909-002'} - + expected = { + 'nickname': 'parsian', + 'name': 'Parsian Bank', + 'persian_name': 'بانک پارسیان', + 'card_prefix': ['622106', '627884'], + 'sheba_code': ['054'], + 'account_number': '020817909002', + 'formatted_account_number': '002-00817909-002' + } assert all(v == actual[k] for k, v in expected.items()) assert len(expected) == len(actual) actual = sheba.bank_data('IR550570022080013447370101') - expected = {'nickname': 'pasargad', 'name': 'Pasargad Bank', 'persian_name': 'بانک پاسارگاد', - 'card_prefix': ['502229', '639347'], 'sheba_code': ['057'], 'account_number': '220800134473701', - 'formatted_account_number': '220-800-13447370-1'} + expected = { + 'nickname': 'pasargad', + 'name': 'Pasargad Bank', + 'persian_name': 'بانک پاسارگاد', + 'card_prefix': ['502229', '639347'], + 'sheba_code': ['057'], + 'account_number': '220800134473701', + 'formatted_account_number': '220-800-13447370-1' + } + assert all(v == actual[k] for k, v in expected.items()) + assert len(expected) == len(actual) + actual = sheba.bank_data('IR790610000000700796858044') + expected = { + 'nickname': 'shahr', + 'name': 'City Bank', + 'persian_name': 'بانک شهر', + 'card_prefix': ['502806', '504706'], + 'sheba_code': ['061'], + 'account_number': '700796858044', + 'formatted_account_number': '700796858044' + } assert all(v == actual[k] for k, v in expected.items()) assert len(expected) == len(actual) diff --git a/tests/test_digis.py b/tests/test_digis.py index 31a8d6d..954f632 100644 --- a/tests/test_digis.py +++ b/tests/test_digis.py @@ -15,21 +15,30 @@ def test_convert_to_en(): assert digits.convert_to_en('۱۲۳٤٥٦') == '123456' +def test_chaining_behavior(): + assert digits.convert_to_en(digits.convert_to_fa('123٤٥٦')) == '123456' + + def test_conversion_function(): assert digits._conversion('123', 'de') is None def test_convert_to_word(): assert digits.convert_to_word(500443) == 'پانصد هزار و چهارصد و چهل و سه' + assert digits.convert_to_word(987654321) == \ + 'نهصد و هشتاد و هفت میلیون و ششصد و پنجاه و چهار هزار و سیصد و بیست و یک' assert len(digits.convert_to_word(500)) == 5 assert digits.convert_to_word(30000000000) == 'سی میلیارد' assert digits.convert_to_word(30000000000000) == 'سی بیلیون' assert digits.convert_to_word(30000000000000000) == 'سی بیلیارد' - assert digits.convert_to_word(30000000000000000000) is None + assert digits.convert_to_word(300000000000000000000) is None assert digits.convert_to_word(0) == 'صفر' + assert digits.convert_to_word(4) == 'چهار' + assert digits.convert_to_word(33) == 'سی و سه' assert digits.convert_to_word(500443, ordinal=True) == 'پانصد هزار و چهارصد و چهل و سوم' assert digits.convert_to_word(-30, ordinal=True) == 'منفی سی اُم' + assert digits.convert_to_word(-123, ordinal=True) == 'منفی صد و بیست و سوم' assert digits.convert_to_word(33, ordinal=True) == 'سی و سوم' assert digits.convert_to_word(45, ordinal=True) == 'چهل و پنجم' @@ -39,6 +48,7 @@ def test_convert_from_word(): assert digits.convert_from_word(None) is None assert digits.convert_from_word('متن بدون عدد') == 0 assert digits.convert_from_word('صفر') == 0 + assert digits.convert_from_word('منفی سه هزار') == -3000 assert digits.convert_from_word('سه هزار دویست و دوازده') == 3212 assert digits.convert_from_word('دوازده هزار بیست دو') == 12022 diff --git a/tests/test_economic_national_id.py b/tests/test_economic_national_id.py deleted file mode 100644 index 6d21276..0000000 --- a/tests/test_economic_national_id.py +++ /dev/null @@ -1,33 +0,0 @@ -from persian_tools import economic_national_id - - -def test_validate_economic_national_id(): - # These economic national ids are valid and registered - assert economic_national_id.validate('10380284790') - assert economic_national_id.validate('10100387143') - - # These economic national ids are all valid - assert economic_national_id.validate('77111986110') - assert economic_national_id.validate('18061055669') - assert economic_national_id.validate('15292247120') - assert economic_national_id.validate('42118547230') - assert economic_national_id.validate('27388385445') - - assert economic_national_id.validate('55828465992') - assert economic_national_id.validate('32882926778') - assert economic_national_id.validate('97006806007') - assert economic_national_id.validate('21272129339') - assert economic_national_id.validate('18706641333') - - # These economic national ids are all invalid - assert economic_national_id.validate('84735069516') is False - assert economic_national_id.validate('65399567978') is False - assert economic_national_id.validate('99969101031') is False - assert economic_national_id.validate('15826563190') is False - assert economic_national_id.validate('11053639140') is False - assert economic_national_id.validate('18090089802') is False - - -def test_generate_random_economic_national_id(): - for _ in range(15): - assert economic_national_id.validate(economic_national_id.generate_random()) diff --git a/tests/test_legal_id.py b/tests/test_legal_id.py new file mode 100644 index 0000000..d875977 --- /dev/null +++ b/tests/test_legal_id.py @@ -0,0 +1,40 @@ +from persian_tools import legal_id + + +def test_validate_legal_id(): + # These legal ids are valid and registered + assert legal_id.validate('10380284790') + assert legal_id.validate('10100387143') + + # These legal ids are all valid + assert legal_id.validate('77111986110') + assert legal_id.validate('18061055669') + assert legal_id.validate('15292247120') + assert legal_id.validate('42118547230') + assert legal_id.validate('27388385445') + + assert legal_id.validate('55828465992') + assert legal_id.validate('32882926778') + assert legal_id.validate('97006806007') + assert legal_id.validate('21272129339') + assert legal_id.validate('18706641333') + + # These legal ids are all invalid + assert legal_id.validate('84735069516') is False + assert legal_id.validate('65399567978') is False + assert legal_id.validate('99969101031') is False + assert legal_id.validate('15826563190') is False + assert legal_id.validate('11053639140') is False + assert legal_id.validate('18090089802') is False + assert legal_id.validate('123001000') is False + assert legal_id.validate('11111111111') is False + assert legal_id.validate('10380284792') is False + assert legal_id.validate('10380285692') is False + assert legal_id.validate('09748208301') is False + assert legal_id.validate('097482083013') is False + assert legal_id.validate('0974820830a') is False + + +def test_generate_random_legal_id(): + for _ in range(15): + assert legal_id.validate(legal_id.generate_random()) diff --git a/tests/test_phone_number.py b/tests/test_phone_number.py index fa651fc..8cb1c70 100644 --- a/tests/test_phone_number.py +++ b/tests/test_phone_number.py @@ -1,5 +1,5 @@ from persian_tools import phone_number -from persian_tools.phone_number.exceptions import InvalidPhoneNumber +from persian_tools.phone_number.exceptions import InvalidPhoneNumber, InvalidToken import pytest @@ -21,21 +21,53 @@ def test_validate(): def test_operator_data(): actual = phone_number.operator_data('09022002580') expected = {'province': [], 'base': 'کشوری', 'type': ['permanent', 'credit'],'operator': 'ایرانسل'} - assert all(v == actual[k] for k, v in expected.items()) assert len(expected) == len(actual) actual = phone_number.operator_data('09981000000') expected = {'province': [], 'base': 'کشوری', 'type': ['credit'], 'operator': 'شاتل موبایل'} - assert all(v == actual[k] for k, v in expected.items()) assert len(expected) == len(actual) actual = phone_number.operator_data('09300880440') expected = {'province': [], 'base': 'کشوری', 'type': ['permanent', 'credit'], 'operator': 'ایرانسل'} + assert all(v == actual[k] for k, v in expected.items()) + assert len(expected) == len(actual) + actual = phone_number.operator_data('09999980440') + expected = {'province': [], 'base': 'کشوری', 'type': ['credit', 'permanent'], 'operator': 'سامانتل'} assert all(v == actual[k] for k, v in expected.items()) assert len(expected) == len(actual) with pytest.raises(InvalidPhoneNumber, match='.*is an invalid phone number'): phone_number.operator_data('+989990467927') + + +def test_normalize(): + assert phone_number.normalize('+989373708555', '0') == '09373708555' + assert phone_number.normalize('989373708555', '0') == '09373708555' + assert phone_number.normalize('00989022002580', '0') == '09022002580' + assert phone_number.normalize('09122002580', '0') == '09122002580' + assert phone_number.normalize('9322002580', '0') == '09322002580' + + assert phone_number.normalize('09373708555', '+98') == '+989373708555' + assert phone_number.normalize('09022002580', '+98') == '+989022002580' + assert phone_number.normalize('09122002580', '+98') == '+989122002580' + assert phone_number.normalize('9322002580', '+98') == '+989322002580' + assert phone_number.normalize('00989022002580', '+98') == '+989022002580' + + assert phone_number.normalize('9322002580', '+98') == '+989322002580' + assert phone_number.normalize('9322002580', '98') == '989322002580' + assert phone_number.normalize('9322002580', '0098') == '00989322002580' + assert phone_number.normalize('9322002580', '0') == '09322002580' + assert phone_number.normalize('9322002580') == '09322002580' + + with pytest.raises(InvalidPhoneNumber, match='.*is an invalid phone number'): + phone_number.normalize('0', '+98') + phone_number.normalize('+98', '+98') + phone_number.normalize('99999999999', '+98') + phone_number.normalize(' 99999999 ', '+98') + phone_number.normalize('09802002580', '0') + + with pytest.raises(InvalidToken, match='.*is an invalid token'): + phone_number.normalize('9322002580', '098') diff --git a/tests/test_separator.py b/tests/test_separator.py index a8671e3..670bfb3 100644 --- a/tests/test_separator.py +++ b/tests/test_separator.py @@ -9,6 +9,9 @@ def test_add_separator(): assert separator.add('۱۲۳۴۵۶۷۸۹۰', '٫') == '۱٫۲۳۴٫۵۶۷٫۸۹۰' assert separator.add('١٢٣٤٥٦٧٨٩٠') == '١,٢٣٤,٥٦٧,٨٩٠' + assert separator.add('') == '' + assert separator.add(0) == '0' + def test_remove_separator(): assert separator.remove('1,000,000') == '1000000'