├── __init__.py ├── pyfiscal ├── __init__.py ├── utils.py ├── validators.py ├── helpers.py ├── constants.py ├── generate.py └── base.py ├── setup.cfg ├── run.py ├── img ├── CURP.jpg ├── NSS.png └── RFC.jpg ├── requirements.txt ├── MANIFEST.in ├── .code_quality ├── .snyk ├── mypy.ini ├── .flake8 ├── pyproject_black.toml ├── bandit.yaml └── .pylintrc ├── docker-compose.yml ├── Dockerfile ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE.txt ├── setup.py ├── tests ├── validator_test.py └── data_fiscal_test.py └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pyfiscal/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | 2 | if __name__ == '__main__': 3 | print("Start PyFiscal...") 4 | -------------------------------------------------------------------------------- /img/CURP.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rootsantiago/pyfiscal/HEAD/img/CURP.jpg -------------------------------------------------------------------------------- /img/NSS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rootsantiago/pyfiscal/HEAD/img/NSS.png -------------------------------------------------------------------------------- /img/RFC.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rootsantiago/pyfiscal/HEAD/img/RFC.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | black==23.3.0 2 | coverage==7.0.5 3 | pre-commit==3.0.1 4 | pytest==7.2.2 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the README 2 | include *.md 3 | 4 | # Include the license file 5 | include LICENSE.txt 6 | -------------------------------------------------------------------------------- /.code_quality/.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.22.1 3 | language-settings: 4 | python: "3.10" -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | back: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | container_name: pyfiscal 9 | volumes: 10 | - .:/code 11 | tty: true -------------------------------------------------------------------------------- /.code_quality/mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | show_error_codes = True 3 | warn_return_any = True 4 | warn_unused_configs = True 5 | disallow_untyped_defs = True 6 | disallow_untyped_calls = True 7 | check_untyped_defs = True 8 | ignore_missing_imports = True 9 | -------------------------------------------------------------------------------- /.code_quality/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 80 3 | ignore = E203 4 | exclude = .git,_pycache_,old,build,dist,venv,env,.env,.venv,*migrations* 5 | per-file-ignores = 6 | # Module level import not at top of file. This is due newrelic initialization. 7 | manage.py: E402 8 | trikare/*: E402 -------------------------------------------------------------------------------- /.code_quality/pyproject_black.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 80 3 | exclude = ''' 4 | ^/( 5 | ( 6 | \.eggs # exclude a few common directories in the 7 | | \.git # root of the project 8 | | \.hg 9 | | \.mypy_cache 10 | | \.tox 11 | | venv 12 | | .venv 13 | | env 14 | | .env 15 | | _build 16 | | buck-out 17 | | build 18 | | dist 19 | | manage.py 20 | )/ 21 | ) 22 | ''' -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Django 2 | FROM python:3.9 3 | 4 | ENV PYTHONUNBUFFERED 1 5 | 6 | ARG APP=/code 7 | 8 | WORKDIR ${APP} 9 | 10 | RUN apt-get update && apt-get install -y --no-install-recommends \ 11 | vim 12 | 13 | COPY requirements.txt . 14 | 15 | # It will install the framework and the dependencies 16 | # in the `requirements.txt` file. 17 | RUN pip install --upgrade pip 18 | RUN pip install --no-cache-dir -r requirements.txt 19 | 20 | COPY . ${APP} 21 | 22 | #ENTRYPOINT ["python", "./run.py"] 23 | 24 | CMD ["python3"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | venv/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pycqa/flake8 3 | rev: 4.0.1 4 | hooks: 5 | - id: flake8 6 | args: 7 | - --config=.code_quality/.flake8 8 | - --ignore=W503 9 | - repo: https://github.com/pycqa/pylint 10 | rev: v2.12.2 11 | hooks: 12 | - id: pylint 13 | args: 14 | - --rcfile=.code_quality/.pylintrc 15 | exclude: (trikare/manage.py|trikare/trikare/|migrations) 16 | - repo: https://github.com/ambv/black 17 | rev: 22.1.0 18 | hooks: 19 | - id: black 20 | language_version: python3 21 | additional_dependencies: [ 'click==8.0.4' ] # lock click (breaks black in his latest versions) 22 | args: 23 | - --config 24 | - .code_quality/pyproject_black.toml 25 | - --diff # show format suggestions 26 | - --line-length=80 27 | - repo: https://github.com/PyCQA/bandit 28 | rev: 1.7.1 29 | hooks: 30 | - id: bandit 31 | args: 32 | - -c 33 | - .code_quality/bandit.yaml -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2017-2018 Tomás Santiago 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from setuptools import setup 3 | 4 | this_directory = Path(__file__).parent 5 | long_description = (this_directory / "README.md").read_text() 6 | 7 | 8 | VERSION = '2.1.2' 9 | DESCRIPTION = 'Pyfiscal calculation of tax data.' 10 | PACKAGE_NAME = 'pyfiscal' 11 | AUTHOR = 'Tomás Santiago' 12 | EMAIL = 'thom.sgonzalez@gmail.com' 13 | GITHUB_URL = 'https://github.com/roottsantiago/pyfiscal' 14 | 15 | 16 | setup( 17 | name = PACKAGE_NAME, 18 | packages = [PACKAGE_NAME], 19 | version = VERSION, 20 | license='MIT', 21 | description = DESCRIPTION, 22 | long_description_content_type = "text/markdown", 23 | long_description = long_description, 24 | author = AUTHOR, 25 | author_email = EMAIL, 26 | url = GITHUB_URL, 27 | keywords = [ 28 | 'RFC', 29 | 'CURP', 30 | 'NSS', 31 | 'fiscal', 32 | 'tax', 33 | 'SAT' 34 | ], 35 | classifiers = [ 36 | 'Topic :: Software Development :: Build Tools', 37 | 'License :: OSI Approved :: MIT License', 38 | 'Programming Language :: Python', 39 | 'Programming Language :: Python :: 3', 40 | ], 41 | python_requires='>=3.9', 42 | ) 43 | 44 | -------------------------------------------------------------------------------- /pyfiscal/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | File manage utilities 4 | """ 5 | import datetime 6 | from enum import Enum 7 | from .constants import VOWELS, CONSONANTS 8 | 9 | 10 | def get_current_year(): 11 | """ 12 | Get current year. 13 | """ 14 | return datetime.datetime.now().year 15 | 16 | 17 | def search_consonant(word: str) -> str: 18 | """ 19 | Search and get consonant 20 | """ 21 | consonant = '' 22 | data = word[1:len(word)] if word else None 23 | for item in data: 24 | if item == 'Ñ': 25 | consonant = 'X' 26 | elif get_consonant(item): 27 | consonant = item 28 | break 29 | return consonant 30 | 31 | 32 | def get_consonant(param: str) -> bool: 33 | """ 34 | Iterate list and get consonant. 35 | """ 36 | exist = [True for cons in CONSONANTS if cons == param] 37 | data = exist[0] if exist else False 38 | return data 39 | 40 | 41 | def search_vowel(last_name: str) -> str: 42 | """ 43 | Search for paternal surname vowel. 44 | """ 45 | size = len(last_name) - 1 46 | last_name = last_name[1:size] 47 | vowel = '' 48 | 49 | for vow in last_name: 50 | if get_vocal(vow): 51 | vowel = vow 52 | break 53 | return vowel 54 | 55 | 56 | def get_vocal(param: str) -> bool: 57 | """ 58 | Iterate list and get vowel 59 | """ 60 | exist = [True for vowel in VOWELS if vowel == param] 61 | data = exist[0] if exist else False 62 | return data 63 | 64 | 65 | def to_upper(data: str) -> str: 66 | """ 67 | Convert text to uppercase. 68 | """ 69 | return data.upper().strip() 70 | 71 | 72 | class GenderEnum(Enum): 73 | """ 74 | Gender Enum 75 | """ 76 | HOMBRE = 'H' 77 | MUJER = 'M' 78 | -------------------------------------------------------------------------------- /tests/validator_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | File manages validator tests 3 | """ 4 | import unittest 5 | 6 | from pyfiscal.helpers import DataFiscalValidator 7 | from pyfiscal.validators import validate_curp, validate_nss, validate_rfc 8 | 9 | validator = DataFiscalValidator() 10 | 11 | 12 | class ValidatorTestCase(unittest.TestCase): 13 | """ 14 | Validator testing class. 15 | """ 16 | curp = 'SABC560626MDFLRN01' 17 | social_security_number = '72795608040' 18 | rfc = 'JUMM420313PA9' 19 | 20 | def test_validate_format_curp(self): 21 | """ 22 | Validation of the curp format. 23 | """ 24 | is_valid = validate_curp(self.curp) 25 | self.assertTrue(is_valid, 'The curp does not have the valid format.') 26 | 27 | def test_validate_digit_curp(self): 28 | """ 29 | Validation of the curp check digit. 30 | """ 31 | self.assertEqual( 32 | validator.check_digit_curp(self.curp[0:17]), 33 | self.curp[-1], 'The check digit is not correct' 34 | ) 35 | 36 | def test_validate_format_nss(self): 37 | is_valid = validate_nss(self.social_security_number) 38 | self.assertTrue(is_valid, 'The NSS does not have the valid format.') 39 | 40 | def test_validate_nss(self): 41 | is_valid = validator.check_nss_registration_date(self.social_security_number) 42 | self.assertTrue(is_valid, 'Discharged before birth') 43 | 44 | def test_validate_digit_nss(self): 45 | """ 46 | Validation of the NSS check digit. 47 | """ 48 | is_valid = validator.check_digit_nss(self.social_security_number) 49 | self.assertTrue(is_valid, 'The check digit is not correct') 50 | 51 | def test_validate_format_rfc(self): 52 | """ 53 | Validation of the RFC format. 54 | """ 55 | is_valid = validate_rfc(self.rfc) 56 | self.assertTrue(is_valid, 'The RFC does not have the valid format.') 57 | 58 | 59 | if __name__ == '__main__': 60 | unittest.main() 61 | -------------------------------------------------------------------------------- /tests/data_fiscal_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test file 3 | """ 4 | import unittest 5 | from pyfiscal.generate import ( 6 | GenerateRFC, 7 | GenerateCURP, 8 | GenerateNSS, 9 | GenerateDataFiscal 10 | ) 11 | 12 | 13 | class DataFiscalTestCase(unittest.TestCase): 14 | """ 15 | Test class for tax data. 16 | """ 17 | DFT = {'curp': 'GODE561231MHGMZM07', 'rfc': 'GODE561231GR8'} 18 | 19 | rfc_params = { 20 | 'name': 'Luz María', 21 | 'last_name': 'Fernández', 22 | 'mother_last_name': 'Juárez', 23 | 'birth_date': '05-02-2020' 24 | } 25 | 26 | curp_params = { 27 | 'name': 'Concepción', 28 | 'last_name': 'Salgado', 29 | 'mother_last_name': 'Briseño', 30 | 'birth_date': '26-06-1956', 31 | 'gender': 'Mujer', 32 | 'state': 'Distrito Federal' 33 | } 34 | 35 | params = { 36 | 'name': 'Emma', 37 | 'last_name': 'Gómez', 38 | 'mother_last_name': 'Díaz', 39 | 'birth_date': '31-12-1956', 40 | 'gender': 'Mujer', 41 | 'state': 'Hidalgo' 42 | } 43 | 44 | def test_generate_rfc(self): 45 | """ 46 | Method that calculates RFC 47 | """ 48 | rfc = GenerateRFC(**self.rfc_params).data 49 | print(f'RFC: {rfc}') 50 | self.assertEqual(len(rfc), 13, 'The length of the RFC is not valid.') 51 | 52 | def test_generate_curp(self): 53 | """ 54 | Method that calculates CURP 55 | """ 56 | curp = GenerateCURP(**self.curp_params).data 57 | print(f'CURP: {curp}') 58 | self.assertEqual(len(curp), 18, 'The length of the CURP is not valid.') 59 | 60 | def test_digit_nns(self): 61 | """ 62 | Method gets the digit of the social security number 63 | """ 64 | nss = GenerateNSS(nss="7279560804").data 65 | self.assertTrue(str(nss).isdigit(), 'It is not a valid digit.') 66 | 67 | def test_generate_data_fiscal(self): 68 | """ 69 | Get tax data 70 | """ 71 | data = GenerateDataFiscal(**self.params).data 72 | self.assertDictEqual(self.DFT, data) 73 | 74 | 75 | if __name__ == '__main__': 76 | unittest.main() 77 | -------------------------------------------------------------------------------- /pyfiscal/validators.py: -------------------------------------------------------------------------------- 1 | """ 2 | Script manages tax data validators 3 | """ 4 | import re 5 | from datetime import datetime 6 | 7 | 8 | class ValidationError(Exception): 9 | """ 10 | Base class for all exceptions. 11 | """ 12 | default = "Format: Invalid" 13 | 14 | 15 | def validate_curp(value): 16 | """ 17 | CURP validator method 18 | """ 19 | PATTERN_CURP = "^([A-Z][AEIOUX][A-Z]{2}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d" \ 20 | "|3[01])[HM](?:AS|B[CS]|C[CLMSH]|D[FG]|G[TR]|HG|JC|M[CNS]|N[ETL]|OC|PL|" \ 21 | "Q[TR]|S[PLR]|T[CSL]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[A-Z\d])(\d)$" 22 | message = 'Does not match the format of a CURP' 23 | regex = re.compile(PATTERN_CURP) 24 | 25 | if not regex.match(value): 26 | raise ValidationError(message) 27 | 28 | return True 29 | 30 | 31 | def validate_nss(value): 32 | """ 33 | Social security number (NSS) validator 34 | """ 35 | PATTERN_NSS = "^(\d{2})(\d{2})(\d{2})\d{5}$" 36 | message = 'Does not match the format of a NSS' 37 | regex = re.compile(PATTERN_NSS) 38 | 39 | if not regex.match(value): 40 | raise ValidationError(message) 41 | 42 | return True 43 | 44 | 45 | def validate_rfc(value): 46 | """ 47 | RFC validator 48 | """ 49 | PATTERN_RFC = "^(([A-ZÑ&]{4})([0-9]{2})([0][13578]|[1][02])" \ 50 | "(([0][1-9]|[12][\\d])|[3][01])([A-Z0-9]{3}))|(([A-ZÑ&]{4})([0-9]{2})" \ 51 | "([0][13456789]|[1][012])(([0][1-9]|[12][\\d])|[3][0])([A-Z0-9]{3}))|" \ 52 | "(([A-ZÑ&]{4})([02468][048]|[13579][26])[0][2]([0][1-9]|[12][\\d])" \ 53 | "([A-Z0-9]{3}))|(([A-ZÑ&]{4})([0-9]{2})[0][2]" \ 54 | "([0][1-9]|[1][0-9]|[2][0-8])([A-Z0-9]{3}))$" 55 | 56 | message = 'Does not match the format of a RFC' 57 | regex = re.compile(PATTERN_RFC) 58 | 59 | if not regex.match(value): 60 | raise ValidationError(message) 61 | 62 | return True 63 | 64 | 65 | def validate_date(value: str): 66 | """ 67 | Try to parse a date using several formats, warn about 68 | problematic value if the possible_date does not match 69 | any of the formats tried 70 | """ 71 | FORMATS_DATE_FIELD = ('%Y-%m-%d', '%d-%m-%Y', '%d/%m/%Y', '%Y/%m/%d') 72 | for fmt in FORMATS_DATE_FIELD: 73 | try: 74 | return datetime.strptime(value, fmt).date() 75 | except ValueError: 76 | pass 77 | mesage = f"Non-valid date format found: '{value}'" 78 | raise ValidationError(mesage) 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Calculation of tax data in México 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/sutsantiago/pyfiscal/blob/master/LICENSE.txt) 4 | [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/release/python-380/) 5 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 6 | 7 | ## Installation 8 | 9 | Install pyfiscal. 10 | ```python 11 | pip install pyfiscal 12 | ``` 13 | 14 | CURP 15 | ---- 16 | The Clave Única de Registro de Población (CURP) is a unique 18 character alphanumeric identity code for both Mexican residents and citizens. 17 | 18 | ![alt picture](https://github.com/roottsantiago/pyfiscal/blob/master/img/CURP.jpg) 19 | 20 | 21 | RFC 22 | --- 23 | The Federal Taxpayer Registry is a code used in Mexico to distinguish each individual or company required to pay taxes. The people or organizations that have their RFC are called contributors. 24 | 25 | 1.- Physical person: 26 | 27 | ![alt picture](https://github.com/roottsantiago/pyfiscal/blob/master/img/RFC.jpg) 28 | 29 | This homoclave will be designated by the SAT, reviewing the request through already designated official paper. 30 | 31 | 32 | NSS 33 | --- 34 | The Social Security Number (NSS) is unique, permanent and nontransferable and is assigned to keep a record of workers and insured. 35 | 36 | ![alt picture](https://github.com/roottsantiago/pyfiscal/blob/master/img/NSS.png) 37 | 38 | Validation: 39 | * Only 11 digits will be validated. 40 | * Validation by the Luhn algorithm. 41 | * Calculate the last digit. 42 | 43 | 44 | ## Getting Started with Docker 45 | If you want to install the dependencies and work using Docker, you can simply follow this steps. 46 | 47 | Clone the project repository 48 | ```bash 49 | git clone https://github.com/roottsantiago/pyfiscal.git 50 | cd pyfiscal 51 | ``` 52 | 53 | ### Usage 54 | There are several ways to use the project because there are those using `docker-compose.yml` and `Dockerfile`. Here's how to use it: 55 | 56 | > This is for the install part with docker-compose 57 | ```compose 58 | # Build 59 | docker-compose build 60 | # Run 61 | docker-compose up -d 62 | ```` 63 | 64 | ## Unit Tests 65 | ```python 66 | python -m unittest tests/data_fiscal_test.py 67 | python -m unittest tests/validator_test.py 68 | ``` 69 | > Testing with docker 70 | ```python 71 | docker exec pyfiscal python -m unittest tests/data_fiscal_test.py 72 | docker exec pyfiscal python -m unittest tests/validator_test.py 73 | ``` 74 | ## License 75 | 76 | See LICENSE for more details (The MIT License). 77 | 78 | 79 | ## References 80 | 81 | https://es.wikipedia.org/wiki/Algoritmo_de_Luhn 82 | -------------------------------------------------------------------------------- /pyfiscal/helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Script that manages tax data helpers 3 | """ 4 | from .constants import CHECKERS, TABLE3 5 | from .utils import get_current_year 6 | 7 | 8 | class DataFiscalValidator: 9 | """ 10 | Data fiscal validator class 11 | """ 12 | @staticmethod 13 | def check_digit_curp(curp: str) -> str: 14 | """ 15 | Method get check digit 16 | """ 17 | value = 0 18 | summary = 0 19 | count = 18 20 | len_curp = len(curp) 21 | 22 | for index in range(len_curp): 23 | for key, val in CHECKERS.items(): 24 | value = val if curp[index] == key else value 25 | summary = summary + value * count 26 | count -= 1 27 | 28 | # Get residue and returns the absolute value in case it is negative. 29 | digit = abs(10 - (summary % 10)) 30 | digit = 0 if digit == 10 else digit 31 | return str(digit) 32 | 33 | @staticmethod 34 | def check_digit_rfc(rfc: str) -> str: 35 | """ 36 | Anexo 3 - Tabla de valores para la generación del código verificador 37 | del registro federal de contribuyentes. 38 | """ 39 | num = 0 40 | sumparcial = 0 41 | digit = None 42 | 43 | # 2.- Una vez asignados los valores se aplicará la siguiente forma 44 | # tomando como base el factor 13 45 | # en orden descendente a cada letra y número del R.F.C. 46 | # para su multiplicación, de acuerdo a la siguiente formula: 47 | # (Vi * (Pi + 1)) + (Vi * (Pi + 1)) + ..............+ (Vi * (Pi + 1)) 48 | # MOD 11 49 | rfc3 = dict((x, y) for x, y in TABLE3) 50 | 51 | lenrfc = len(rfc) 52 | for count in range(lenrfc): 53 | letra = rfc[count] 54 | 55 | if rfc3.get(letra): 56 | num = rfc3.get(letra) 57 | sumparcial += (int(num) * (14 - (count + 1))) 58 | 59 | # 3.- El resultado de la suma se divide entre el factor 11. 60 | 61 | # Si el residuo es igual a cero, este será el valor que se le asignará 62 | # al dígito verificador. 63 | # Si el residuo es mayor a cero se restará este al factor 11: 11-3 =8 64 | # Si el residuo es igual a 10 el dígito verificador será “ A”. 65 | # Si el residuo es igual a cero el dígito verificador será cero. 66 | # Por lo tanto “8“ 67 | # es el dígito verificador de este ejemplo: GODE561231GR8. 68 | 69 | residue = sumparcial % 11 70 | digit = '0' if residue == 0 else residue 71 | 72 | if int(digit) > 0: 73 | digit = 11 - residue 74 | digit = 'A' if digit == 10 else digit 75 | return str(digit) 76 | 77 | @staticmethod 78 | def check_nss_registration_date(nss: str) -> bool: 79 | """Compare years except you don't have birth year 80 | 81 | 11 digits and valid subdelegation 82 | """ 83 | subdelegation = int(nss[0:2]) 84 | year = get_current_year() % 100 85 | high_date = int(nss[2:4]) 86 | birth_date = int(nss[4:6]) 87 | 88 | if subdelegation != 97: 89 | if high_date <= year: 90 | high_date += 100 91 | if birth_date <= year: 92 | birth_date += 100 93 | if birth_date > high_date: 94 | # He was discharged before he was born 95 | return False 96 | return True 97 | 98 | @staticmethod 99 | def check_digit_nss(nss): 100 | """ 101 | Validate an entry with a check digit. 102 | Example 4896889802135 103 | """ 104 | num = list(map(int, str(nss))) 105 | data = (sum(num[::-2] + [sum(divmod(d * 2, 10)) 106 | for d in num[-2::-2]]) % 10 == 0) 107 | return data 108 | -------------------------------------------------------------------------------- /pyfiscal/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Script manages constants 4 | """ 5 | VOWELS = ('A', 'E', 'I', 'O', 'U', 'Á', 'É', 'Í', 'Ó', 'Ú') 6 | 7 | 8 | CONSONANTS = ( 9 | 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 10 | 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' 11 | ) 12 | 13 | 14 | DISADVANTAGES_WORDS = ( 15 | ('BUEI', 'BUEX'), ('BUEY', 'BUEX'), 16 | ('CACA', 'CACX'), ('CACO', 'CACX'), 17 | ('CAGA', 'CAGX'), ('CAGO', 'CAGX'), 18 | ('CAKA', 'CAKX'), ('CAKO', 'CAKX'), 19 | ('COGE', 'COGX'), ('COJA', 'COJX'), 20 | ('COJE', 'COJX'), ('COJI', 'COJX'), 21 | ('COJO', 'COJX'), ('CULO', 'CULX'), 22 | ('FETO', 'FETX'), ('GUEY', 'GUEX'), 23 | ('JOTO', 'JOTX'), ('KACA', 'KACX'), 24 | ('KACO', 'KACX'), ('KAGA', 'KAGX'), 25 | ('KAGO', 'KAGX'), ('KOGE', 'KOGX'), 26 | ('KOJO', 'KOJX'), ('KAKA', 'KAKX'), 27 | ('KULO', 'KULX'), ('MAME', 'MAMX'), 28 | ('MAMO', 'MAMX'), ('MEAR', 'MEAX'), 29 | ('MEAS', 'MEAX'), ('MEON', 'MEOX'), 30 | ('MION', 'MIOX'), ('MOCO', 'MOCX'), 31 | ('MULA', 'MULX'), ('PEDA', 'PEDX'), 32 | ('PEDO', 'PEDX'), ('PENE', 'PENX'), 33 | ('PUTA', 'PUTX'), ('PUTO', 'PUTX'), 34 | ('QULO', 'QULX'), ('RATA', 'RATX'), 35 | ('RUIN', 'RUIX') 36 | ) 37 | 38 | ENTITIES = { 39 | 'AGUASCALIENTES': 'AS', 40 | 'BAJA CALIFORNIA': 'BC', 41 | 'BAJA CALIFORNIA SUR': 'BS', 42 | 'CAMPECHE': 'CC', 43 | 'CHIAPAS': 'CS', 44 | 'CHIHUAHUA': 'CH', 45 | 'COAHUILA': 'CL', 46 | 'COLIMA': 'CM', 47 | 'DISTRITO FEDERAL': 'DF', 48 | 'DURANGO': 'DG', 49 | 'GUANAJUATO': 'GT', 50 | 'GUERRERO': 'GR', 51 | 'HIDALGO': 'HG', 52 | 'JALISCO': 'JC', 53 | 'MEXICO': 'MC', 54 | 'MICHOACAN': 'MN', 55 | 'MORELOS': 'MS', 56 | 'NAYARIT': 'NT', 57 | 'NUEVO LEON': 'NL', 58 | 'OAXACA': 'OC', 59 | 'PUEBLA': 'PL', 60 | 'QUERETARO': 'QT', 61 | 'QUINTANA ROO': 'QR', 62 | 'SAN LUIS POTOSI': 'SP', 63 | 'SINALOA': 'SL', 64 | 'SONORA': 'SR', 65 | 'TABASCO': 'TC', 66 | 'TAMAULIPAS': 'TS', 67 | 'TLAXCALA': 'TL', 68 | 'VERACRUZ': 'VZ', 69 | 'YUCATÁN': 'YN', 70 | 'ZACATECAS': 'ZS', 71 | 'NACIDO EXTRANJERO': 'NE' 72 | } 73 | 74 | TABLE1 = ( 75 | (' ', '00'), ('B', '12'), ('O', '26'), 76 | ('0', '00'), ('C', '13'), ('P', '27'), 77 | ('1', '01'), ('D', '14'), ('Q', '28'), 78 | ('2', '02'), ('E', '15'), ('R', '29'), 79 | ('3', '03'), ('F', '16'), ('S', '32'), 80 | ('4', '04'), ('G', '17'), ('T', '33'), 81 | ('5', '05'), ('H', '18'), ('U', '34'), 82 | ('6', '06'), ('I', '19'), ('V', '35'), 83 | ('7', '07'), ('J', '21'), ('W', '36'), 84 | ('8', '08'), ('K', '22'), ('X', '37'), 85 | ('9', '09'), ('L', '23'), ('Y', '38'), 86 | ('&', '10'), ('M', '24'), ('Z', '39'), 87 | ('A', '11'), ('N', '25'), ('Ñ', '40'), 88 | ) 89 | 90 | TABLE2 = ( 91 | (0, '1'), (17, 'I'), 92 | (1, '2'), (18, 'J'), 93 | (2, '3'), (19, 'K'), 94 | (3, '4'), (20, 'L'), 95 | (4, '5'), (21, 'M'), 96 | (5, '6'), (22, 'N'), 97 | (6, '7'), (23, 'P'), 98 | (7, '8'), (24, 'Q'), 99 | (8, '9'), (25, 'R'), 100 | (9, 'A'), (26, 'S'), 101 | (10, 'B'), (27, 'T'), 102 | (11, 'C'), (28, 'U'), 103 | (12, 'D'), (29, 'V'), 104 | (13, 'E'), (30, 'W'), 105 | (14, 'F'), (31, 'X'), 106 | (15, 'G'), (32, 'Y'), 107 | (16, 'H'), (33, 'Z') 108 | ) 109 | 110 | TABLE3 = ( 111 | ('0', '00'), ('D', '13'), ('P', '26'), 112 | ('1', '01'), ('E', '14'), ('Q', '27'), 113 | ('2', '02'), ('F', '15'), ('R', '28'), 114 | ('3', '03'), ('G', '16'), ('S', '29'), 115 | ('4', '04'), ('H', '17'), ('T', '30'), 116 | ('5', '05'), ('I', '18'), ('U', '31'), 117 | ('6', '06'), ('J', '19'), ('V', '32'), 118 | ('7', '07'), ('K', '20'), ('W', '33'), 119 | ('8', '08'), ('L', '21'), ('X', '34'), 120 | ('9', '09'), ('M', '22'), ('Y', '35'), 121 | ('A', '10'), ('N', '23'), ('Z', '36'), 122 | ('B', '11'), ('&', '24'), (' ', '37'), 123 | ('C', '12'), ('O', '25'), ('Ñ', '38') 124 | ) 125 | 126 | CHECKERS = { 127 | '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, 128 | '8': 8, '9': 9, 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 129 | 'F': 15, 'G': 16, 'H': 17, 'I': 18, 'J': 19, 'K': 20, 'L': 21, 130 | 'M': 22, 'N': 23, 'Ñ': 24, 'O': 25, 'P': 26, 'Q': 27, 'R': 28, 131 | 'S': 29, 'T': 30, 'U': 31, 'V': 32, 'W': 33, 'X': 34, 'Y': 35, 132 | 'Z': 36 133 | } -------------------------------------------------------------------------------- /pyfiscal/generate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Base file in the generation and calculation of fiscal data. 4 | """ 5 | from .base import BaseGenerator 6 | from .helpers import DataFiscalValidator 7 | 8 | 9 | from .constants import TABLE1, TABLE2 10 | from .utils import GenderEnum 11 | 12 | 13 | # pylint: disable=W0223 14 | class GenerateRFC(BaseGenerator): 15 | """ 16 | Base class that generate RFC 17 | """ 18 | partial_data = None 19 | key_value = 'rfc' 20 | DATA_REQUIRED = ( 21 | 'name', 22 | 'last_name', 23 | 'mother_last_name', 24 | 'birth_date' 25 | ) 26 | 27 | def __init__(self, **kwargs): 28 | self.name = kwargs.get('name') 29 | self.last_name = kwargs.get('last_name') 30 | self.mother_last_name = kwargs.get('mother_last_name') 31 | self.birth_date = kwargs.get('birth_date') 32 | 33 | self.parse(name=self.name, last_name=self.last_name, 34 | mother_last_name=self.mother_last_name) 35 | 36 | self.partial_data = self.data_fiscal( 37 | name=self.name, 38 | last_name=self.last_name, 39 | mother_last_name=self.mother_last_name, 40 | birth_date=self.birth_date 41 | ) 42 | 43 | def calculate(self): 44 | """ 45 | Calculation method 46 | """ 47 | validator = DataFiscalValidator() 48 | rfc = self.partial_data 49 | rfc += self.homoclave(self.full_name) 50 | rfc += validator.check_digit_rfc(rfc) 51 | return rfc 52 | 53 | @staticmethod 54 | def homoclave(full_name): 55 | """ 56 | Method that calculates the homoclave 57 | """ 58 | num = '0' 59 | summary = 0 60 | div = 0 61 | mod = 0 62 | 63 | # 1.- Values will be assigned to the letters of the name or 64 | # business name according to the table1 65 | # 2.- The values are ordered as follows: 66 | # G O M E Z D I A Z E M M A 67 | # 017 26 24 15 39 00 14 19 11 39 00 15 24 24 11 68 | # A zero is added to the value of the first letter to standardize 69 | # the criteria of the numbers to be taken two by two. 70 | len_full_name = len(full_name) 71 | for index in range(len_full_name): 72 | rfc1 = dict((x, y) for x, y in TABLE1) 73 | num += rfc1.get(full_name[index]) 74 | 75 | # 3. The multiplications of the numbers taken two by two for 76 | # the position of the couple will be carried out: 77 | # La formula es: 78 | # El caracter actual multiplicado por diez mas el valor del caracter 79 | # siguiente y lo anterior multiplicado por el valor del caracter 80 | # siguiente. 81 | count = 0 82 | for index in range(len(num) - 1): 83 | count += 1 84 | summary += ((int(num[index]) * 10) + int(num[count]))\ 85 | * int(num[count]) 86 | 87 | # 4.- The result of the multiplications is added and the result 88 | # obtained, the last three figures will be taken and these are divided 89 | # by the factor 34. 90 | div = summary % 1000 91 | 92 | # mod = div % 34 93 | # div = (div-mod)/34 94 | div, mod = divmod(div, 34) 95 | # 5. With the quotient and the remainder, the table 2 is consulted 96 | # and the homonymy is assigned. 97 | rfc2 = dict((x, y) for x, y in TABLE2) 98 | hom = '' 99 | hom += rfc2.get(int(div)) 100 | hom += rfc2.get(int(mod)) 101 | return hom 102 | 103 | @property 104 | def data(self): 105 | """ 106 | Property method 107 | """ 108 | return self.calculate() 109 | 110 | 111 | # pylint: disable=W0223 112 | class GenerateCURP(BaseGenerator): 113 | """ 114 | Generate CURP 115 | """ 116 | partial_data = None 117 | key_value = 'curp' 118 | DATA_REQUIRED = ( 119 | 'name', 120 | 'last_name', 121 | 'mother_last_name', 122 | 'birth_date', 123 | 'gender', 124 | 'state' 125 | ) 126 | 127 | def __init__(self, **kwargs): 128 | self.name = kwargs.get('name') 129 | self.last_name = kwargs.get('last_name') 130 | self.mother_last_name = kwargs.get('mother_last_name', None) 131 | self.birth_date = kwargs.get('birth_date') 132 | self.gender = kwargs.get('gender') 133 | self.state = kwargs.get('state') 134 | 135 | self.parse(name=self.name, last_name=self.last_name, 136 | mother_last_name=self.mother_last_name, state=self.state) 137 | 138 | self.partial_data = self.data_fiscal( 139 | name=self.name, 140 | last_name=self.last_name, 141 | mother_last_name=self.mother_last_name, 142 | birth_date=self.birth_date 143 | ) 144 | 145 | def calculate(self): 146 | """ 147 | Method that calculate the CURP 148 | """ 149 | validator = DataFiscalValidator 150 | 151 | if not self.state: 152 | raise AttributeError("No such attribute: state") 153 | 154 | if not self.gender: 155 | raise AttributeError("No such attribute: gender") 156 | 157 | gender = self.get_gender(self.gender) 158 | state_code = (self.get_federative_entity(self.state) 159 | if self.state else None) 160 | last_name = self.get_consonant(self.last_name) 161 | mother_last_name = self.get_consonant(self.mother_last_name) 162 | name = self.get_consonant(self.complete_name) 163 | 164 | homoclave = self.homoclave(self.get_year(self.birth_date)) 165 | 166 | curp = self.partial_data 167 | curp += f'{gender}{state_code}{last_name}{mother_last_name}'\ 168 | f'{name}{homoclave}' 169 | curp += validator.check_digit_curp(curp) 170 | return curp 171 | 172 | @staticmethod 173 | def get_gender(gender: str): 174 | """ 175 | Get gender of enum 176 | """ 177 | value = None 178 | try: 179 | gender = gender.upper() 180 | value = GenderEnum[gender].value 181 | except KeyError as exc: 182 | print('Value not found in gender enum', exc) 183 | return value 184 | 185 | @staticmethod 186 | def homoclave(year): 187 | """ 188 | Method that obtain the homoclave 189 | """ 190 | hcv = '' 191 | if year < 2000: 192 | hcv = '0' 193 | elif year >= 2000: 194 | hcv = 'A' 195 | return hcv 196 | 197 | @property 198 | def data(self): 199 | """ 200 | Property method 201 | """ 202 | return self.calculate() 203 | 204 | 205 | # pylint: disable=W0223 206 | class GenerateNSS(BaseGenerator): 207 | """ 208 | Base class that calculates the social security number 209 | """ 210 | def __init__(self, nss): 211 | self.nss = nss 212 | 213 | def _calculate_luhn(self): 214 | """ 215 | Calculation of said digit. 216 | """ 217 | num = list(map(int, str(self.nss))) 218 | check_digit = (10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) 219 | for d in num[::-2]]) % 10) 220 | return 0 if check_digit == 10 else check_digit 221 | 222 | @property 223 | def data(self): 224 | """ 225 | Property method 226 | """ 227 | return self._calculate_luhn() 228 | 229 | 230 | class GenericGeneration: 231 | """ 232 | Class Generic Generation 233 | """ 234 | _data = {} 235 | generators = () 236 | 237 | def __init__(self, **kwargs): 238 | self._kwargs = kwargs 239 | 240 | @property 241 | def data(self): 242 | """ 243 | Property method 244 | """ 245 | for cls in self.generators: 246 | data = cls.DATA_REQUIRED 247 | kwargs = {key: self._kwargs[key] for key in data} 248 | gen = cls(**kwargs) 249 | gen.calculate() 250 | self._data[gen.key_value] = gen.data 251 | 252 | return self._data 253 | 254 | 255 | class GenerateDataFiscal(GenericGeneration): 256 | """ 257 | RFC and CURP generation 258 | """ 259 | generators = (GenerateCURP, GenerateRFC) 260 | -------------------------------------------------------------------------------- /.code_quality/bandit.yaml: -------------------------------------------------------------------------------- 1 | ### Bandit config file: 2 | 3 | ### This config may optionally select a subset of tests to run or skip by 4 | ### filling out the 'tests' and 'skips' lists given below. If no tests are 5 | ### specified for inclusion then it is assumed all tests are desired. The skips 6 | ### set will remove specific tests from the include set. This can be controlled 7 | ### using the -t/-s CLI options. Note that the same test.json ID should not appear 8 | ### in both 'tests' and 'skips', this would be nonsensical and is detected by 9 | ### Bandit at runtime. 10 | 11 | # Available tests: 12 | # B101 : assert_used 13 | # B102 : exec_used 14 | # B103 : set_bad_file_permissions 15 | # B104 : hardcoded_bind_all_interfaces 16 | # B105 : hardcoded_password_string 17 | # B106 : hardcoded_password_funcarg 18 | # B107 : hardcoded_password_default 19 | # B108 : hardcoded_tmp_directory 20 | # B110 : try_except_pass 21 | # B112 : try_except_continue 22 | # B201 : flask_debug_true 23 | # B301 : pickle 24 | # B302 : marshal 25 | # B303 : md5 26 | # B304 : ciphers 27 | # B305 : cipher_modes 28 | # B306 : mktemp_q 29 | # B307 : eval 30 | # B308 : mark_safe 31 | # B309 : httpsconnection 32 | # B310 : urllib_urlopen 33 | # B311 : random 34 | # B312 : telnetlib 35 | # B313 : xml_bad_cElementTree 36 | # B314 : xml_bad_ElementTree 37 | # B315 : xml_bad_expatreader 38 | # B316 : xml_bad_expatbuilder 39 | # B317 : xml_bad_sax 40 | # B318 : xml_bad_minidom 41 | # B319 : xml_bad_pulldom 42 | # B320 : xml_bad_etree 43 | # B321 : ftplib 44 | # B322 : input 45 | # B323 : unverified_context 46 | # B324 : hashlib_new_insecure_functions 47 | # B325 : tempnam 48 | # B401 : import_telnetlib 49 | # B402 : import_ftplib 50 | # B403 : import_pickle 51 | # B404 : import_subprocess 52 | # B405 : import_xml_etree 53 | # B406 : import_xml_sax 54 | # B407 : import_xml_expat 55 | # B408 : import_xml_minidom 56 | # B409 : import_xml_pulldom 57 | # B410 : import_lxml 58 | # B411 : import_xmlrpclib 59 | # B412 : import_httpoxy 60 | # B413 : import_pycrypto 61 | # B501 : request_with_no_cert_validation 62 | # B502 : ssl_with_bad_version 63 | # B503 : ssl_with_bad_defaults 64 | # B504 : ssl_with_no_version 65 | # B505 : weak_cryptographic_key 66 | # B506 : yaml_load 67 | # B507 : ssh_no_host_key_verification 68 | # B601 : paramiko_calls 69 | # B602 : subprocess_popen_with_shell_equals_true 70 | # B603 : subprocess_without_shell_equals_true 71 | # B604 : any_other_function_with_shell_equals_true 72 | # B605 : start_process_with_a_shell 73 | # B606 : start_process_with_no_shell 74 | # B607 : start_process_with_partial_path 75 | # B608 : hardcoded_sql_expressions 76 | # B609 : linux_commands_wildcard_injection 77 | # B610 : django_extra_used 78 | # B611 : django_rawsql_used 79 | # B701 : jinja2_autoescape_false 80 | # B702 : use_of_mako_templates 81 | # B703 : django_mark_safe 82 | 83 | exclude_dirs: 84 | - 'venv' 85 | - 'env' 86 | - 'build' 87 | - 'dist' 88 | - 'tests' 89 | 90 | # (optional) list included test.json IDs here, eg '[B101, B406]': 91 | tests: 92 | 93 | # (optional) list skipped test.json IDs here, eg '[B101, B406]': 94 | skips: [B311] 95 | 96 | ### (optional) plugin settings - some test.json plugins require configuration data 97 | ### that may be given here, per-plugin. All bandit test.json plugins have a built in 98 | ### set of sensible defaults and these will be used if no configuration is 99 | ### provided. It is not necessary to provide settings for every (or any) plugin 100 | ### if the defaults are acceptable. 101 | assert_used: 102 | skips: [ '*test_*.py' ] 103 | 104 | any_other_function_with_shell_equals_true: 105 | no_shell: 106 | - os.execl 107 | - os.execle 108 | - os.execlp 109 | - os.execlpe 110 | - os.execv 111 | - os.execve 112 | - os.execvp 113 | - os.execvpe 114 | - os.spawnl 115 | - os.spawnle 116 | - os.spawnlp 117 | - os.spawnlpe 118 | - os.spawnv 119 | - os.spawnve 120 | - os.spawnvp 121 | - os.spawnvpe 122 | - os.startfile 123 | shell: 124 | - os.system 125 | - os.popen 126 | - os.popen2 127 | - os.popen3 128 | - os.popen4 129 | - popen2.popen2 130 | - popen2.popen3 131 | - popen2.popen4 132 | - popen2.Popen3 133 | - popen2.Popen4 134 | - commands.getoutput 135 | - commands.getstatusoutput 136 | subprocess: 137 | - subprocess.Popen 138 | - subprocess.call 139 | - subprocess.check_call 140 | - subprocess.check_output 141 | - subprocess.run 142 | hardcoded_tmp_directory: 143 | tmp_dirs: 144 | - /tmp 145 | - /var/tmp 146 | - /dev/shm 147 | linux_commands_wildcard_injection: 148 | no_shell: 149 | - os.execl 150 | - os.execle 151 | - os.execlp 152 | - os.execlpe 153 | - os.execv 154 | - os.execve 155 | - os.execvp 156 | - os.execvpe 157 | - os.spawnl 158 | - os.spawnle 159 | - os.spawnlp 160 | - os.spawnlpe 161 | - os.spawnv 162 | - os.spawnve 163 | - os.spawnvp 164 | - os.spawnvpe 165 | - os.startfile 166 | shell: 167 | - os.system 168 | - os.popen 169 | - os.popen2 170 | - os.popen3 171 | - os.popen4 172 | - popen2.popen2 173 | - popen2.popen3 174 | - popen2.popen4 175 | - popen2.Popen3 176 | - popen2.Popen4 177 | - commands.getoutput 178 | - commands.getstatusoutput 179 | subprocess: 180 | - subprocess.Popen 181 | - subprocess.call 182 | - subprocess.check_call 183 | - subprocess.check_output 184 | - subprocess.run 185 | ssl_with_bad_defaults: 186 | bad_protocol_versions: 187 | - PROTOCOL_SSLv2 188 | - SSLv2_METHOD 189 | - SSLv23_METHOD 190 | - PROTOCOL_SSLv3 191 | - PROTOCOL_TLSv1 192 | - SSLv3_METHOD 193 | - TLSv1_METHOD 194 | ssl_with_bad_version: 195 | bad_protocol_versions: 196 | - PROTOCOL_SSLv2 197 | - SSLv2_METHOD 198 | - SSLv23_METHOD 199 | - PROTOCOL_SSLv3 200 | - PROTOCOL_TLSv1 201 | - SSLv3_METHOD 202 | - TLSv1_METHOD 203 | start_process_with_a_shell: 204 | no_shell: 205 | - os.execl 206 | - os.execle 207 | - os.execlp 208 | - os.execlpe 209 | - os.execv 210 | - os.execve 211 | - os.execvp 212 | - os.execvpe 213 | - os.spawnl 214 | - os.spawnle 215 | - os.spawnlp 216 | - os.spawnlpe 217 | - os.spawnv 218 | - os.spawnve 219 | - os.spawnvp 220 | - os.spawnvpe 221 | - os.startfile 222 | shell: 223 | - os.system 224 | - os.popen 225 | - os.popen2 226 | - os.popen3 227 | - os.popen4 228 | - popen2.popen2 229 | - popen2.popen3 230 | - popen2.popen4 231 | - popen2.Popen3 232 | - popen2.Popen4 233 | - commands.getoutput 234 | - commands.getstatusoutput 235 | subprocess: 236 | - subprocess.Popen 237 | - subprocess.call 238 | - subprocess.check_call 239 | - subprocess.check_output 240 | - subprocess.run 241 | start_process_with_no_shell: 242 | no_shell: 243 | - os.execl 244 | - os.execle 245 | - os.execlp 246 | - os.execlpe 247 | - os.execv 248 | - os.execve 249 | - os.execvp 250 | - os.execvpe 251 | - os.spawnl 252 | - os.spawnle 253 | - os.spawnlp 254 | - os.spawnlpe 255 | - os.spawnv 256 | - os.spawnve 257 | - os.spawnvp 258 | - os.spawnvpe 259 | - os.startfile 260 | shell: 261 | - os.system 262 | - os.popen 263 | - os.popen2 264 | - os.popen3 265 | - os.popen4 266 | - popen2.popen2 267 | - popen2.popen3 268 | - popen2.popen4 269 | - popen2.Popen3 270 | - popen2.Popen4 271 | - commands.getoutput 272 | - commands.getstatusoutput 273 | subprocess: 274 | - subprocess.Popen 275 | - subprocess.call 276 | - subprocess.check_call 277 | - subprocess.check_output 278 | - subprocess.run 279 | start_process_with_partial_path: 280 | no_shell: 281 | - os.execl 282 | - os.execle 283 | - os.execlp 284 | - os.execlpe 285 | - os.execv 286 | - os.execve 287 | - os.execvp 288 | - os.execvpe 289 | - os.spawnl 290 | - os.spawnle 291 | - os.spawnlp 292 | - os.spawnlpe 293 | - os.spawnv 294 | - os.spawnve 295 | - os.spawnvp 296 | - os.spawnvpe 297 | - os.startfile 298 | shell: 299 | - os.system 300 | - os.popen 301 | - os.popen2 302 | - os.popen3 303 | - os.popen4 304 | - popen2.popen2 305 | - popen2.popen3 306 | - popen2.popen4 307 | - popen2.Popen3 308 | - popen2.Popen4 309 | - commands.getoutput 310 | - commands.getstatusoutput 311 | subprocess: 312 | - subprocess.Popen 313 | - subprocess.call 314 | - subprocess.check_call 315 | - subprocess.check_output 316 | - subprocess.run 317 | subprocess_popen_with_shell_equals_true: 318 | no_shell: 319 | - os.execl 320 | - os.execle 321 | - os.execlp 322 | - os.execlpe 323 | - os.execv 324 | - os.execve 325 | - os.execvp 326 | - os.execvpe 327 | - os.spawnl 328 | - os.spawnle 329 | - os.spawnlp 330 | - os.spawnlpe 331 | - os.spawnv 332 | - os.spawnve 333 | - os.spawnvp 334 | - os.spawnvpe 335 | - os.startfile 336 | shell: 337 | - os.system 338 | - os.popen 339 | - os.popen2 340 | - os.popen3 341 | - os.popen4 342 | - popen2.popen2 343 | - popen2.popen3 344 | - popen2.popen4 345 | - popen2.Popen3 346 | - popen2.Popen4 347 | - commands.getoutput 348 | - commands.getstatusoutput 349 | subprocess: 350 | - subprocess.Popen 351 | - subprocess.call 352 | - subprocess.check_call 353 | - subprocess.check_output 354 | - subprocess.run 355 | subprocess_without_shell_equals_true: 356 | no_shell: 357 | - os.execl 358 | - os.execle 359 | - os.execlp 360 | - os.execlpe 361 | - os.execv 362 | - os.execve 363 | - os.execvp 364 | - os.execvpe 365 | - os.spawnl 366 | - os.spawnle 367 | - os.spawnlp 368 | - os.spawnlpe 369 | - os.spawnv 370 | - os.spawnve 371 | - os.spawnvp 372 | - os.spawnvpe 373 | - os.startfile 374 | shell: 375 | - os.system 376 | - os.popen 377 | - os.popen2 378 | - os.popen3 379 | - os.popen4 380 | - popen2.popen2 381 | - popen2.popen3 382 | - popen2.popen4 383 | - popen2.Popen3 384 | - popen2.Popen4 385 | - commands.getoutput 386 | - commands.getstatusoutput 387 | subprocess: 388 | - subprocess.Popen 389 | - subprocess.call 390 | - subprocess.check_call 391 | - subprocess.check_output 392 | - subprocess.run 393 | try_except_continue: 394 | check_typed_exception: false 395 | try_except_pass: 396 | check_typed_exception: false 397 | weak_cryptographic_key: 398 | weak_key_size_dsa_high: 1024 399 | weak_key_size_dsa_medium: 2048 400 | weak_key_size_ec_high: 160 401 | weak_key_size_ec_medium: 224 402 | weak_key_size_rsa_high: 1024 403 | weak_key_size_rsa_medium: 2048 404 | -------------------------------------------------------------------------------- /pyfiscal/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Script manages base classes for calculating fiscal data. 4 | """ 5 | from datetime import date 6 | import unicodedata 7 | from .validators import validate_date 8 | from .utils import ( 9 | to_upper, 10 | search_vowel, 11 | search_consonant 12 | ) 13 | from .constants import ENTITIES, DISADVANTAGES_WORDS 14 | 15 | 16 | class BaseGenerator: 17 | """ 18 | Base Generator Class 19 | """ 20 | full_name = None 21 | first_name_master = None 22 | last_name_master = None 23 | mothers_last_name_master = None 24 | state = None 25 | last_name = None 26 | mother_last_name = None 27 | complete_name = None 28 | 29 | def generate(self): 30 | """ 31 | Generation method 32 | """ 33 | raise NotImplementedError('No implement.') 34 | 35 | def parse(self, name, last_name, 36 | mother_last_name=None, state=None): 37 | """ 38 | Method in charge of parsing data. 39 | """ 40 | self.state = to_upper(state) if state else None 41 | 42 | if mother_last_name: 43 | mother_last_name = self.remove_accents(to_upper(mother_last_name)) 44 | self.mothers_last_name_master = mother_last_name 45 | mother_last_name = self.remove_articles(mother_last_name) 46 | mother_last_name = self.remove_precisions(mother_last_name) 47 | self.mother_last_name = mother_last_name 48 | 49 | first_name = self.remove_accents(to_upper(name)) 50 | self.first_name_master = first_name 51 | first_name = self.remove_names(first_name) 52 | first_name = self.remove_articles(first_name) 53 | first_name = self.remove_precisions(first_name) 54 | self.complete_name = first_name 55 | 56 | last_name = self.remove_accents(to_upper(last_name)) 57 | self.last_name_master = last_name 58 | last_name = self.remove_articles(last_name) 59 | last_name = self.remove_precisions(last_name) 60 | self.last_name = last_name 61 | 62 | self.full_name = f'{self.last_name_master} '\ 63 | f'{self.mothers_last_name_master} {self.first_name_master}' 64 | 65 | def data_fiscal(self, name, last_name, 66 | mother_last_name, birth_date): 67 | """ 68 | method tax data 69 | """ 70 | birth_date = self.parse_date(birth_date) 71 | 72 | if len(last_name) == 1 or len(last_name) == 2: 73 | initials = self.initials_name_comp(name, last_name, 74 | mother_last_name) 75 | elif mother_last_name is None or mother_last_name == '': 76 | # Rule 7 77 | initials = self.initials_single_last_name(name, last_name) 78 | else: 79 | initials = self.initials_name(name, 80 | last_name, mother_last_name) 81 | # Rule 9 82 | full_name_initials = self.verify_initials(initials) 83 | return f'{full_name_initials}{birth_date}' 84 | 85 | def initials_name(self, first_name, last_name, mother_last_name): 86 | """Rule 1 - The key is integrated with the following data: 87 | 88 | 1.- The first letter of the father's last name and 89 | the next first vowel of the same. 90 | 2.- The first letter of the mother's last name. 91 | 3.- The first letter of the name. 92 | """ 93 | 94 | ini_last_name = last_name[0:1] 95 | last_name_vowel = search_vowel(last_name) 96 | ini_mothlast_name = self.get_ini_mothlast_name(mother_last_name) 97 | ini_first_name = first_name[0:1] 98 | 99 | # Rule 5 100 | # When the paternal or maternal surname are composed, 101 | # the first word that corresponds 102 | # to any of them will be taken for the classification. 103 | # Dolores San Martín Dávalos SADD-180812 104 | # Mario Sánchez de la Barquera Gómez SAGM-190224 105 | # Antonio Jiménez Ponce de León JIPA-170808 106 | 107 | initials = f'{ini_last_name}{last_name_vowel}'\ 108 | f'{ini_mothlast_name}{ini_first_name}' 109 | return initials 110 | 111 | @staticmethod 112 | def remove_precisions(phrase): 113 | """ Rule 3 - When the initial letter of any of the surnames 114 | or first names is composed, only its initial will be noted. 115 | In Ch la C and in Ll la L. 116 | 117 | For example: 118 | Manuel Chávez González CAGM-240618 119 | Felipe Camargo Llamas CALF-450228 120 | Charles Kennedy Truman KETC-511012 121 | """ 122 | letters = phrase[0:2] 123 | data = phrase[2:len(phrase)] 124 | 125 | if letters == 'CH': 126 | phrase = f'C{data}' 127 | elif letters == 'LL': 128 | phrase = f'L{data}' 129 | return phrase 130 | 131 | @staticmethod 132 | def remove_articles(phrase): 133 | """ 134 | Replace all the occurrences of string in list. 135 | 136 | Rule 8 - When articles, prepositions, conjunctions or contractions 137 | appear in the name of natural persons, they will not be taken aselements 138 | of integration of the code, examples: 139 | Carmen de la Peña Ramírez PERC-631201 140 | Mario Sánchez de los Cobos SACM-701110 141 | Roberto González and Durán GODR-600101 142 | Juan del Valle Martínez VAMJ-691001 143 | """ 144 | data = [ 145 | 'DE LA ', 146 | 'DE LOS ', 147 | 'DEL ', 'DE ', 148 | 'LAS ', 149 | 'LA ', 150 | 'LOS ', 151 | 'Y ', 152 | 'MC ', 153 | 'MAC ', 154 | 'VON ', 155 | 'VAN ' 156 | ] 157 | # Iterate over the strings to be replaced 158 | for elem in data: 159 | # Check if string is in the main string 160 | if elem in phrase: 161 | # Replace the string 162 | phrase = phrase.replace(elem, '').strip() 163 | return phrase 164 | 165 | @staticmethod 166 | def remove_names(first_name): 167 | """ Rule 6 - When the name is composed, that is, 168 | it is made up of two or more words, the initial letter of the first 169 | will be taken for the conformation, provided it is not MARIA or JOSE 170 | given its frequent use, in which case the first letter will be 171 | taken of the second word. 172 | 173 | For example: 174 | Luz María Fernández Juárez FEJL-200205 175 | José Antonio Camargo Hernández CAHA-211218 176 | María Luisa Ramírez Sánchez RASL-251112 177 | """ 178 | data = ['JOSE ', 'MARIA '] 179 | 180 | # Iterate over the strings to be replaced 181 | for item in data: 182 | # Check if string is in the main string 183 | if item in first_name: 184 | # Replace the string 185 | first_name = first_name.replace(item, '').strip() 186 | return first_name 187 | 188 | @staticmethod 189 | def get_ini_mothlast_name(mother_last_name): 190 | """ 191 | The first letter of the mother's last name. 192 | """ 193 | result = mother_last_name[0:1] if mother_last_name else '' 194 | return result 195 | 196 | def initials_name_comp(self, first_name, last_name, mother_last_name): 197 | """Rule 4 - In cases where the paternal surname of the natural person 198 | is made up of one or two letters, the password will be 199 | formed as follows: 200 | 201 | 1.- The first letter of the paternal surname. 202 | 2.- The first letter of the mother's last name. 203 | 3.- The first and second letters of the name. 204 | 205 | For example: 206 | Alvaro de la O Lozano OLAL-401201 207 | Ernesto Ek Rivera ERER-071120 208 | """ 209 | ini_last_name = last_name[0:1] 210 | ini_mthlast_name = self.get_ini_mothlast_name(mother_last_name) 211 | data = f"{ini_last_name}{ini_mthlast_name}{first_name[0:2]}" 212 | return data 213 | 214 | @staticmethod 215 | def initials_single_last_name(first_name, last_name): 216 | """Rule 7 - In the cases in which the natural person has only one 217 | surname, he will comply with the first and second letters of the 218 | paternal or maternal surname, as it appears on the birth certificate, 219 | plus the first and second letters of the name. 220 | 221 | For example: 222 | Juan Martínez MAJU-420116 223 | Gerarda Zafra ZAGE-251115 224 | """ 225 | result = f'{last_name[0:2]}{first_name[0:2]}' 226 | return result 227 | 228 | @staticmethod 229 | def verify_initials(initials): 230 | """ 231 | Rule 9 - When an inconvenient word appears from the four letters 232 | that make up the alphabetical expression, the last letter will be 233 | replaced by an "X". 234 | """ 235 | words = dict((x, y) for x, y in DISADVANTAGES_WORDS) 236 | words = words.get(initials) if words.get(initials) else initials 237 | return words 238 | 239 | @staticmethod 240 | def remove_accents(text): 241 | """ Normalise (normalize) unicode data in Python 242 | to remove umlauts, accents etc. 243 | 244 | Rule 10 - When special characters appear as part of the name, 245 | paternal surname and maternal surname, 246 | they must be excluded for the calculation of the homonym 247 | and the verification digit. 248 | The characters will be interpreted, yes and only if, 249 | they are individually within the name, paternal surname 250 | and maternal surname. 251 | Examples: 252 | 253 | Roberto O’farril Carballo OACR-661121 254 | Rubén D’angelo Fargo DAFR-710108 255 | Luz Ma. Fernández Juárez FEJL-830120 256 | """ 257 | try: 258 | text = unicode(text, 'utf-8') 259 | except (TypeError, NameError): 260 | pass 261 | text = unicodedata.normalize('NFD', text) 262 | text = text.encode('ascii', 'ignore') 263 | text = text.decode("utf-8") 264 | return str(text) 265 | 266 | @staticmethod 267 | def parse_date(birthdate: str): 268 | """Rule 2 - The taxpayer's date of birth will be noted below, 269 | in the following order: 270 | 271 | 1. Year: The last two figures will be taken, 272 | writing them in Arabic numerals. 273 | 2.- Month: The month of birth will be taken in its order number, 274 | in a calendar year, writing it with Arabic numbers. 275 | 3.- Day: It will be written in Arabic numerals. 276 | 277 | Args: 278 | birthdate: The first parameter. 279 | 280 | Returns: 281 | As a result we will have the numerical expression: 070401 282 | """ 283 | 284 | try: 285 | _date = (validate_date(birthdate) if birthdate else date.today()) 286 | year = str(_date.year)[2:4] 287 | month = str(_date.month).zfill(2) 288 | day = str(_date.day).zfill(2) 289 | # When in the year, month or day, of the date of birth, 290 | # only one figure appears, a ZERO will be put before it. 291 | return f"{year}{month}{day}" 292 | except ValueError: 293 | raise Exception("Incorrect date format") 294 | 295 | @staticmethod 296 | def get_federative_entity(state: str): 297 | """ 298 | Method get states 299 | """ 300 | data = [value for key, value in ENTITIES.items() if key == state] 301 | status_code = data[0] if data else '' 302 | return status_code 303 | 304 | @staticmethod 305 | def get_consonant(word: str): 306 | """ 307 | Method get consonant 308 | """ 309 | return search_consonant(word) 310 | 311 | @staticmethod 312 | def get_year(date_str: str): 313 | """ 314 | Get year of birth date. 315 | """ 316 | try: 317 | # formatting the date using strptime() function 318 | _date = validate_date(date_str) if date_str else date.today() 319 | return _date.year 320 | except ValueError: 321 | raise Exception("Incorrect date format") 322 | -------------------------------------------------------------------------------- /.code_quality/.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Specify a score threshold to be exceeded before program exits with error. 9 | fail-under=10.0 10 | 11 | # Add files or directories to the blacklist. They should be base names, not 12 | # paths. 13 | ignore=CVS, pip-wheel-metadata, docs, htmlcov, .git, __pycache__, old, build, dist, venv, env, .env, .venv 14 | 15 | # Add files or directories matching the regex patterns to the blacklist. The 16 | # regex matches against base names, not paths. 17 | ignore-patterns= 18 | 19 | # Python code to execute, usually for sys.path manipulation such as 20 | # pygtk.require(). 21 | #init-hook= 22 | 23 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 24 | # number of processors available to use. 25 | jobs=1 26 | 27 | # Control the amount of potential inferred values when inferring a single 28 | # object. This can help the performance when dealing with large functions or 29 | # complex, nested conditions. 30 | limit-inference-results=100 31 | 32 | # List of plugins (as comma separated values of python module names) to load, 33 | # usually to register additional checkers. 34 | load-plugins= 35 | 36 | # Pickle collected data for later comparisons. 37 | persistent=yes 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=print-statement, 64 | parameter-unpacking, 65 | unpacking-in-except, 66 | old-raise-syntax, 67 | backtick, 68 | long-suffix, 69 | old-ne-operator, 70 | old-octal-literal, 71 | import-star-module-level, 72 | non-ascii-bytes-literal, 73 | raw-checker-failed, 74 | bad-inline-option, 75 | locally-disabled, 76 | file-ignored, 77 | suppressed-message, 78 | useless-suppression, 79 | deprecated-pragma, 80 | use-symbolic-message-instead, 81 | apply-builtin, 82 | basestring-builtin, 83 | buffer-builtin, 84 | cmp-builtin, 85 | coerce-builtin, 86 | execfile-builtin, 87 | file-builtin, 88 | long-builtin, 89 | raw_input-builtin, 90 | reduce-builtin, 91 | standarderror-builtin, 92 | unicode-builtin, 93 | xrange-builtin, 94 | coerce-method, 95 | delslice-method, 96 | getslice-method, 97 | setslice-method, 98 | no-absolute-import, 99 | old-division, 100 | dict-iter-method, 101 | dict-view-method, 102 | next-method-called, 103 | metaclass-assignment, 104 | indexing-exception, 105 | raising-string, 106 | reload-builtin, 107 | oct-method, 108 | hex-method, 109 | nonzero-method, 110 | cmp-method, 111 | input-builtin, 112 | round-builtin, 113 | intern-builtin, 114 | unichr-builtin, 115 | map-builtin-not-iterating, 116 | zip-builtin-not-iterating, 117 | range-builtin-not-iterating, 118 | filter-builtin-not-iterating, 119 | using-cmp-argument, 120 | eq-without-hash, 121 | div-method, 122 | idiv-method, 123 | rdiv-method, 124 | exception-message-attribute, 125 | invalid-str-codec, 126 | sys-max-int, 127 | bad-python3-import, 128 | deprecated-string-function, 129 | deprecated-str-translate-call, 130 | deprecated-itertools-function, 131 | deprecated-types-field, 132 | next-method-defined, 133 | dict-items-not-iterating, 134 | dict-keys-not-iterating, 135 | dict-values-not-iterating, 136 | deprecated-operator-function, 137 | deprecated-urllib-function, 138 | xreadlines-attribute, 139 | deprecated-sys-function, 140 | exception-escape, 141 | comprehension-escape, 142 | bad-indentation, # W0311 143 | misplaced-future, # W0410 144 | line-too-long, # C0301 145 | too-many-lines, # C0302 146 | trailing-whitespace, # C0303 147 | missing-final-newline, # C0304 148 | trailing-newlines, # C0305 149 | multiple-imports, # C0410 150 | import-error, # E0401 151 | too-few-public-methods, # R0903 152 | duplicate-code, #R0801 153 | cell-var-from-loop, #W0640 154 | protected-access, #W0212 155 | raise-missing-from #W0707 156 | 157 | # Enable the message, report, category or checker with the given id(s). You can 158 | # either give multiple identifier separated by comma (,) or put this option 159 | # multiple time (only on the command line, not in the configuration file where 160 | # it should appear only once). See also the "--disable" option for examples. 161 | enable=c-extension-no-member 162 | 163 | 164 | [REPORTS] 165 | 166 | # Python expression which should return a score less than or equal to 10. You 167 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 168 | # which contain the number of messages in each category, as well as 'statement' 169 | # which is the total number of statements analyzed. This score is used by the 170 | # global evaluation report (RP0004). 171 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 172 | 173 | # Template used to display messages. This is a python new-style format string 174 | # used to format the message information. See doc for all details. 175 | #msg-template= 176 | 177 | # Set the output format. Available formats are text, parseable, colorized, json 178 | # and msvs (visual studio). You can also give a reporter class, e.g. 179 | # mypackage.mymodule.MyReporterClass. 180 | output-format=text 181 | 182 | # Tells whether to display a full report or only the messages. 183 | reports=no 184 | 185 | # Activate the evaluation score. 186 | score=yes 187 | 188 | 189 | [REFACTORING] 190 | 191 | # Maximum number of nested blocks for function / method body 192 | max-nested-blocks=5 193 | 194 | # Complete name of functions that never returns. When checking for 195 | # inconsistent-return-statements if a never returning function is called then 196 | # it will be considered as an explicit return statement and no message will be 197 | # printed. 198 | never-returning-functions=sys.exit 199 | 200 | 201 | [LOGGING] 202 | 203 | # The type of string formatting that logging methods do. `old` means using % 204 | # formatting, `new` is for `{}` formatting. 205 | logging-format-style=old 206 | 207 | # Logging modules to check that the string format arguments are in logging 208 | # function parameter format. 209 | logging-modules=logging 210 | 211 | 212 | [SPELLING] 213 | 214 | # Limits count of emitted suggestions for spelling mistakes. 215 | max-spelling-suggestions=4 216 | 217 | # Spelling dictionary name. Available dictionaries: none. To make it work, 218 | # install the python-enchant package. 219 | spelling-dict= 220 | 221 | # List of comma separated words that should not be checked. 222 | spelling-ignore-words= 223 | 224 | # A path to a file that contains the private dictionary; one word per line. 225 | spelling-private-dict-file= 226 | 227 | # Tells whether to store unknown words to the private dictionary (see the 228 | # --spelling-private-dict-file option) instead of raising a message. 229 | spelling-store-unknown-words=no 230 | 231 | 232 | [MISCELLANEOUS] 233 | 234 | # List of note tags to take in consideration, separated by a comma. 235 | notes=FIXME, 236 | XXX, 237 | TODO 238 | 239 | # Regular expression of note tags to take in consideration. 240 | #notes-rgx= 241 | 242 | 243 | [TYPECHECK] 244 | 245 | # List of decorators that produce context managers, such as 246 | # contextlib.contextmanager. Add to this list to register other decorators that 247 | # produce valid context managers. 248 | contextmanager-decorators=contextlib.contextmanager 249 | 250 | # List of members which are set dynamically and missed by pylint inference 251 | # system, and so shouldn't trigger E1101 when accessed. Python regular 252 | # expressions are accepted. 253 | generated-members= 254 | 255 | # Tells whether missing members accessed in mixin class should be ignored. A 256 | # mixin class is detected if its name ends with "mixin" (case insensitive). 257 | ignore-mixin-members=yes 258 | 259 | # Tells whether to warn about missing members when the owner of the attribute 260 | # is inferred to be None. 261 | ignore-none=yes 262 | 263 | # This flag controls whether pylint should warn about no-member and similar 264 | # checks whenever an opaque object is returned when inferring. The inference 265 | # can return multiple potential results while evaluating a Python object, but 266 | # some branches might not be evaluated, which results in partial inference. In 267 | # that case, it might be useful to still emit no-member and other checks for 268 | # the rest of the inferred objects. 269 | ignore-on-opaque-inference=yes 270 | 271 | # List of class names for which member attributes should not be checked (useful 272 | # for classes with dynamically set attributes). This supports the use of 273 | # qualified names. 274 | ignored-classes=optparse.Values,thread._local,_thread._local 275 | 276 | # List of module names for which member attributes should not be checked 277 | # (useful for modules/projects where namespaces are manipulated during runtime 278 | # and thus existing member attributes cannot be deduced by static analysis). It 279 | # supports qualified module names, as well as Unix pattern matching. 280 | ignored-modules= 281 | 282 | # Show a hint with possible names when a member name was not found. The aspect 283 | # of finding the hint is based on edit distance. 284 | missing-member-hint=yes 285 | 286 | # The minimum edit distance a name should have in order to be considered a 287 | # similar match for a missing member name. 288 | missing-member-hint-distance=1 289 | 290 | # The total number of similar names that should be taken in consideration when 291 | # showing a hint for a missing member. 292 | missing-member-max-choices=1 293 | 294 | # List of decorators that change the signature of a decorated function. 295 | signature-mutators= 296 | 297 | 298 | [VARIABLES] 299 | 300 | # List of additional names supposed to be defined in builtins. Remember that 301 | # you should avoid defining new builtins when possible. 302 | additional-builtins= 303 | 304 | # Tells whether unused global variables should be treated as a violation. 305 | allow-global-unused-variables=yes 306 | 307 | # List of strings which can identify a callback function by name. A callback 308 | # name must start or end with one of those strings. 309 | callbacks=cb_, 310 | _cb 311 | 312 | # A regular expression matching the name of dummy variables (i.e. expected to 313 | # not be used). 314 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 315 | 316 | # Argument names that match this expression will be ignored. Default to name 317 | # with leading underscore. 318 | ignored-argument-names=_.*|^ignored_|^unused_ 319 | 320 | # Tells whether we should check for unused import in __init__ files. 321 | init-import=no 322 | 323 | # List of qualified module names which can have objects that can redefine 324 | # builtins. 325 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 326 | 327 | 328 | [FORMAT] 329 | 330 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 331 | expected-line-ending-format= 332 | 333 | # Regexp for a line that is allowed to be longer than the limit. 334 | ignore-long-lines=^\s*(# )??$ 335 | 336 | # Number of spaces of indent required inside a hanging or continued line. 337 | indent-after-paren=4 338 | 339 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 340 | # tab). 341 | indent-string=' ' 342 | 343 | # Maximum number of characters on a single line. 344 | # This check is disabled 345 | #max-line-length=100 346 | 347 | # Maximum number of lines in a module. 348 | max-module-lines=1000 349 | 350 | # Allow the body of a class to be on the same line as the declaration if body 351 | # contains single statement. 352 | single-line-class-stmt=no 353 | 354 | # Allow the body of an if to be on the same line as the test if there is no 355 | # else. 356 | single-line-if-stmt=no 357 | 358 | 359 | [SIMILARITIES] 360 | 361 | # Ignore comments when computing similarities. 362 | ignore-comments=yes 363 | 364 | # Ignore docstrings when computing similarities. 365 | ignore-docstrings=yes 366 | 367 | # Ignore imports when computing similarities. 368 | ignore-imports=no 369 | 370 | # Minimum lines number of a similarity. 371 | min-similarity-lines=4 372 | 373 | 374 | [BASIC] 375 | 376 | # Naming style matching correct argument names. 377 | argument-naming-style=snake_case 378 | 379 | # Regular expression matching correct argument names. Overrides argument- 380 | # naming-style. 381 | #argument-rgx= 382 | 383 | # Naming style matching correct attribute names. 384 | attr-naming-style=snake_case 385 | 386 | # Regular expression matching correct attribute names. Overrides attr-naming- 387 | # style. 388 | #attr-rgx= 389 | 390 | # Bad variable names which should always be refused, separated by a comma. 391 | bad-names=foo, 392 | bar, 393 | baz, 394 | toto, 395 | tutu, 396 | tata 397 | 398 | # Bad variable names regexes, separated by a comma. If names match any regex, 399 | # they will always be refused 400 | bad-names-rgxs= 401 | 402 | # Naming style matching correct class attribute names. 403 | class-attribute-naming-style=any 404 | 405 | # Regular expression matching correct class attribute names. Overrides class- 406 | # attribute-naming-style. 407 | #class-attribute-rgx= 408 | 409 | # Naming style matching correct class names. 410 | class-naming-style=PascalCase 411 | 412 | # Regular expression matching correct class names. Overrides class-naming- 413 | # style. 414 | #class-rgx= 415 | 416 | # Naming style matching correct constant names. 417 | const-naming-style=UPPER_CASE 418 | 419 | # Regular expression matching correct constant names. Overrides const-naming- 420 | # style. 421 | #const-rgx= 422 | 423 | # Minimum line length for functions/classes that require docstrings, shorter 424 | # ones are exempt. 425 | docstring-min-length=-1 426 | 427 | # Naming style matching correct function names. 428 | function-naming-style=snake_case 429 | 430 | # Regular expression matching correct function names. Overrides function- 431 | # naming-style. 432 | #function-rgx= 433 | 434 | # Good variable names which should always be accepted, separated by a comma. 435 | good-names=e, 436 | qs, 437 | k, 438 | v, 439 | id, 440 | i, 441 | j, 442 | k, 443 | ex, 444 | Run, 445 | _, 446 | pk, 447 | sk 448 | 449 | # Good variable names regexes, separated by a comma. If names match any regex, 450 | # they will always be accepted 451 | good-names-rgxs= 452 | 453 | # Include a hint for the correct naming format with invalid-name. 454 | include-naming-hint=no 455 | 456 | # Naming style matching correct inline iteration names. 457 | inlinevar-naming-style=any 458 | 459 | # Regular expression matching correct inline iteration names. Overrides 460 | # inlinevar-naming-style. 461 | #inlinevar-rgx= 462 | 463 | # Naming style matching correct method names. 464 | method-naming-style=snake_case 465 | 466 | # Regular expression matching correct method names. Overrides method-naming- 467 | # style. 468 | #method-rgx= 469 | 470 | # Naming style matching correct module names. 471 | module-naming-style=snake_case 472 | 473 | # Regular expression matching correct module names. Overrides module-naming- 474 | # style. 475 | #module-rgx= 476 | 477 | # Colon-delimited sets of names that determine each other's naming style when 478 | # the name regexes allow several styles. 479 | name-group= 480 | 481 | # Regular expression which should only match function or class names that do 482 | # not require a docstring. 483 | no-docstring-rgx=^_ 484 | 485 | # List of decorators that produce properties, such as abc.abstractproperty. Add 486 | # to this list to register other decorators that produce valid properties. 487 | # These decorators are taken in consideration only for invalid-name. 488 | property-classes=abc.abstractproperty 489 | 490 | # Naming style matching correct variable names. 491 | variable-naming-style=snake_case 492 | 493 | # Regular expression matching correct variable names. Overrides variable- 494 | # naming-style. 495 | #variable-rgx= 496 | 497 | 498 | [STRING] 499 | 500 | # This flag controls whether inconsistent-quotes generates a warning when the 501 | # character used as a quote delimiter is used inconsistently within a module. 502 | check-quote-consistency=no 503 | 504 | # This flag controls whether the implicit-str-concat should generate a warning 505 | # on implicit string concatenation in sequences defined over several lines. 506 | check-str-concat-over-line-jumps=no 507 | 508 | 509 | [IMPORTS] 510 | 511 | # List of modules that can be imported at any level, not just the top level 512 | # one. 513 | allow-any-import-level= 514 | 515 | # Allow wildcard imports from modules that define __all__. 516 | allow-wildcard-with-all=no 517 | 518 | # Analyse import fallback blocks. This can be used to support both Python 2 and 519 | # 3 compatible code, which means that the block might have code that exists 520 | # only in one or another interpreter, leading to false positives when analysed. 521 | analyse-fallback-blocks=no 522 | 523 | # Deprecated modules which should not be used, separated by a comma. 524 | deprecated-modules=optparse,tkinter.tix 525 | 526 | # Create a graph of external dependencies in the given file (report RP0402 must 527 | # not be disabled). 528 | ext-import-graph= 529 | 530 | # Create a graph of every (i.e. internal and external) dependencies in the 531 | # given file (report RP0402 must not be disabled). 532 | import-graph= 533 | 534 | # Create a graph of internal dependencies in the given file (report RP0402 must 535 | # not be disabled). 536 | int-import-graph= 537 | 538 | # Force import order to recognize a module as part of the standard 539 | # compatibility libraries. 540 | known-standard-library= 541 | 542 | # Force import order to recognize a module as part of a third party library. 543 | known-third-party=enchant 544 | 545 | # Couples of modules and preferred modules, separated by a comma. 546 | preferred-modules= 547 | 548 | 549 | [CLASSES] 550 | 551 | # List of method names used to declare (i.e. assign) instance attributes. 552 | defining-attr-methods=__init__, 553 | __new__, 554 | setUp, 555 | __post_init__ 556 | 557 | # List of member names, which should be excluded from the protected access 558 | # warning. 559 | exclude-protected=_asdict, 560 | _fields, 561 | _replace, 562 | _source, 563 | _make 564 | 565 | # List of valid names for the first argument in a class method. 566 | valid-classmethod-first-arg=cls 567 | 568 | # List of valid names for the first argument in a metaclass class method. 569 | valid-metaclass-classmethod-first-arg=cls 570 | 571 | 572 | [DESIGN] 573 | 574 | # Maximum number of arguments for function / method. 575 | max-args=10 576 | 577 | # Maximum number of attributes for a class (see R0902). 578 | max-attributes=15 579 | 580 | # Maximum number of boolean expressions in an if statement (see R0916). 581 | max-bool-expr=5 582 | 583 | # Maximum number of branch for function / method body. 584 | max-branches=38 585 | 586 | # Maximum number of locals for function / method body. 587 | max-locals=25 588 | 589 | # Maximum number of parents for a class (see R0901). 590 | max-parents=7 591 | 592 | # Maximum number of public methods for a class (see R0904). 593 | max-public-methods=20 594 | 595 | # Maximum number of return / yield for function / method body. 596 | max-returns=7 597 | 598 | # Maximum number of statements in function / method body. 599 | max-statements=90 600 | 601 | # Minimum number of public methods for a class (see R0903). 602 | min-public-methods=2 603 | 604 | 605 | [EXCEPTIONS] 606 | 607 | # Exceptions that will emit a warning when being caught. Defaults to 608 | # "BaseException, Exception". 609 | overgeneral-exceptions=BaseException, 610 | Exception 611 | --------------------------------------------------------------------------------