├── .coveragerc ├── .githooks └── pre-push ├── .github └── workflows │ └── integration.yml ├── .gitignore ├── .lint └── .markdownlintrc ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── docker-compose.yml ├── docs ├── guia-de-uso.md ├── index.md ├── licenca.md └── sobre.md ├── mkdocs.yml ├── requirements.txt ├── setup.py ├── tests ├── __init__.py ├── test_CNH.py ├── test_CNPJ.py ├── test_CNS.py ├── test_CPF.py ├── test_Certidao.py ├── test_PIS.py ├── test_RENAVAM.py ├── test_TituloEleitoral.py └── test_generic.py └── validate_docbr ├── BaseDoc.py ├── CNH.py ├── CNPJ.py ├── CNS.py ├── CPF.py ├── Certidao.py ├── PIS.py ├── RENAVAM.py ├── TituloEleitoral.py ├── __init__.py └── generic.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = validate_docbr 3 | disable_warnings = no-data-collected 4 | 5 | [report] 6 | fail_under = 97.50 7 | precision = 2 8 | show_missing = True 9 | -------------------------------------------------------------------------------- /.githooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | warning() { 4 | # shellcheck disable=2028 5 | echo "\\e[1;33m$1" 6 | } 7 | 8 | alertMessages() { 9 | warning "One or more $1 are failing." 10 | warning "Please fix those $1 before pushing your branch" 11 | } 12 | 13 | if make lint; then 14 | if make test-coverage; then 15 | exit 0 16 | else 17 | alertMessages "unit tests" 18 | exit 1 19 | fi 20 | else 21 | alertMessages "linter checks" 22 | exit 1 23 | fi 24 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Integration 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | env: 10 | DOCKER_IMAGE: alvarofpp/app:${{ github.sha }} 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | container: 16 | image: alvarofpp/linter:latest 17 | volumes: 18 | - ./:/app 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | - run: git config --global --add safe.directory $GITHUB_WORKSPACE 24 | - run: lint-commit origin/main 25 | - run: lint-markdown 26 | - run: lint-dockerfile 27 | - run: lint-yaml 28 | - run: lint-shell-script 29 | - run: lint-python 30 | tests: 31 | needs: lint 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | with: 36 | fetch-depth: 0 37 | - run: docker build -t $DOCKER_IMAGE . 38 | - run: | 39 | docker run --rm -v $(pwd):/app $DOCKER_IMAGE /bin/bash -c \ 40 | "pytest --cov=validate_docbr/" 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | venv/* 3 | dist/* 4 | build/* 5 | */__pycache__/* 6 | site/* 7 | # Egg metadata 8 | *.egg-info 9 | .coverage 10 | -------------------------------------------------------------------------------- /.lint/.markdownlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "heading-style": { "style": "atx" }, 4 | "ul-style": { "style": "dash" }, 5 | "line-length": { "line_length": 100 }, 6 | "no-duplicate-heading": { "allow_different_nesting": true }, 7 | "no-trailing-punctuation": { "punctuation": ".,;:!" }, 8 | "no-blanks-blockquote": false, 9 | "no-inline-html": { "allowed_elements": ["p", "a", "img"] }, 10 | "hr-style": { "style": "---" } 11 | } 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribuindo 2 | 3 | Para contribuir com o pacote com a inserção de um novo documento: 4 | 5 | - Crie uma _issue_ dizendo sobre o documento que deseja inserir ao pacote; 6 | - Preferencialmente coloque links que ajudem a entender o algoritmo de geração e validação do documento. 7 | - Realize os procedimentos padrões, sendo que na hora de criar a sua _branch_, referencie a sua _issue_; 8 | - Realize o _pull request_ para a branch _master_. 9 | 10 | ## Sobre o código 11 | 12 | Para novos documentos: 13 | 14 | - Criar uma classe com as siglas do documento (herdando a classe pai `BaseDoc`); 15 | - Importar a classe no `__init__.py`; 16 | - Criar testes em `test/`. 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12.1-slim 2 | 3 | # Virtual environment 4 | RUN python3 -m venv /opt/venv 5 | ENV PATH="/opt/venv/bin:$PATH" 6 | 7 | # Install requirements 8 | # hadolint ignore=DL3013 9 | RUN pip3 install --no-cache-dir --upgrade pip 10 | COPY requirements.txt /tmp/requirements.txt 11 | RUN pip3 install --no-cache-dir --requirement /tmp/requirements.txt 12 | 13 | # Set environment variables 14 | ENV WORKDIR=/app 15 | WORKDIR ${WORKDIR} 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Álvaro Ferreira Pires de Paiva 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the “Software”), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Variables 2 | APP_NAME=app 3 | ROOT=$(shell pwd) 4 | 5 | ## Lint 6 | DOCKER_IMAGE_LINTER=alvarofpp/linter:latest 7 | LINT_COMMIT_TARGET_BRANCH=origin/main 8 | 9 | ## Test 10 | TEST_CONTAINER_NAME=${APP_NAME}_test 11 | 12 | # Commands 13 | .PHONY: install-hooks 14 | install-hooks: 15 | git config core.hooksPath .githooks 16 | 17 | .PHONY: build 18 | build: install-hooks 19 | @docker compose build --pull 20 | 21 | .PHONY: build-no-cache 22 | build-no-cache: install-hooks 23 | @docker compose build --no-cache --pull 24 | 25 | .PHONY: lint 26 | lint: 27 | @docker pull ${DOCKER_IMAGE_LINTER} 28 | @docker run --rm -v ${ROOT}:/app ${DOCKER_IMAGE_LINTER} " \ 29 | lint-commit ${LINT_COMMIT_TARGET_BRANCH} \ 30 | && lint-markdown \ 31 | && lint-dockerfile \ 32 | && lint-yaml \ 33 | && lint-shell-script \ 34 | && lint-python" 35 | 36 | .PHONY: test 37 | test: 38 | @docker compose run --rm -v ${ROOT}:/app \ 39 | --name ${TEST_CONTAINER_NAME} ${APP_NAME} \ 40 | pytest 41 | 42 | .PHONY: test-coverage 43 | test-coverage: 44 | @docker compose run --rm -v ${ROOT}:/app \ 45 | --name ${TEST_CONTAINER_NAME} ${APP_NAME} \ 46 | /bin/bash -c "pytest --cov=validate_docbr/" 47 | 48 | .PHONY: shell 49 | shell: 50 | @docker compose run --rm ${APP_NAME} bash 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # validate-docbr 2 | 3 | 4 | latest release 5 | 6 | 7 | > :warning: Estamos planejando a versão `2.0.0` do pacote. **Para saber as mudanças que estão por 8 | > vir, leia a [issue #67](https://github.com/alvarofpp/validate-docbr/issues/67)**. 9 | > Você também pode sugerir atualizações para o pacote na mesma issue, participe! :warning: 10 | 11 | Pacote Python para validação de documentos brasileiros. 12 | 13 | Para instalar o pacote: 14 | 15 | ```shell 16 | pip install validate-docbr 17 | ``` 18 | 19 | A documentação pode ser acessada [clicando aqui](https://alvarofpp.github.io/validate-docbr). 20 | 21 | ## Documentos 22 | 23 | Documentos que estão no pacote: 24 | 25 | - [CPF](validate_docbr/CPF.py): Cadastro de Pessoas Físicas; 26 | - [CNH](validate_docbr/CNH.py): Carteira Nacional de Habilitação; 27 | - [CNPJ](validate_docbr/CNPJ.py): Cadastro Nacional da Pessoa Jurídica; 28 | - [CNS](validate_docbr/CNS.py): Cartão Nacional de Saúde; 29 | - [PIS](validate_docbr/PIS.py): PIS/NIS/PASEP/NIT; 30 | - [Título eleitoral](validate_docbr/TituloEleitoral.py): Cadastro que permite cidadãos brasileiros votar; 31 | - [RENAVAM](validate_docbr/RENAVAM.py): Registro Nacional de Veículos Automotores. 32 | 33 | Para entender melhor os documentos e suas respectivas classes, basta acessar a [Wiki do projeto](https://github.com/alvarofpp/validate-docbr/wiki). 34 | 35 | ## Métodos 36 | 37 | Todos os documentos possuem os mesmos métodos e funcionam da mesma forma. 38 | 39 | ### validate 40 | 41 | Valida o documento passado como argumento. Retorna um `bool`, `True` caso seja válido, 42 | `False` caso contrário. Recebe os parâmetros: 43 | 44 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 45 | | --------- | ---- | ----------- | ------------ | --------- | 46 | | `doc` | `str`| `''` | X | O documento que se quer validar. | 47 | 48 | ```python 49 | from validate_docbr import CPF 50 | 51 | cpf = CPF() 52 | 53 | # Validar CPF 54 | cpf.validate("012.345.678-90") # True 55 | cpf.validate("012.345.678-91") # False 56 | ``` 57 | 58 | [Caso especial de CPF](https://alvarofpp.github.io/validate-docbr/guia-de-uso/#caso-especial-de-cpf). 59 | 60 | ### validate_list 61 | 62 | Valida uma lista de documentos passado como argumento. Retorna uma lista de `bool`, 63 | `True` caso seja válido, `False` caso contrário. Recebe os parâmetros: 64 | 65 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 66 | | --------- | ---- | ----------- | ------------ | --------- | 67 | | `docs` | `List[str]`| `[]` | X | A lista de documentos para validar. | 68 | 69 | ```python 70 | from validate_docbr import CPF 71 | 72 | cpf = CPF() 73 | 74 | # Validar CPFs 75 | cpf.validate_list(["012.345.678-90", "012.345.678-91"]) # [True, False] 76 | ``` 77 | 78 | ### validate_docs 79 | 80 | **Observação**: diferente dos outros métodos, esse método é do escopo global do pacote, 81 | não precisa-se instanciar uma classe para uso. 82 | 83 | Valida vários documentos difererentes. Retorna uma lista com valores `bool` para cada tupla da 84 | lista (na mesma ordem), `True` caso seja válido, `False` caso contrário. Recebe os parâmetros: 85 | 86 | 87 | 88 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 89 | | --------- | ---- | ----------- | ------------ | --------- | 90 | | `documents` | `List[Tuple[BaseDoc, str]]`| `[]` | X | Lista de tuplas, cada tupla possui como primeiro elemento o tipo de documento e o segundo elemento o valor que se deseja validar. | 91 | 92 | 93 | 94 | ```python 95 | import validate_docbr as docbr 96 | 97 | 98 | # Validar diferentes documentos 99 | docs = [(docbr.CPF, '90396100457'), (docbr.CNPJ, '49910753848365')] 100 | docbr.validate_docs(docs) # [True, False] 101 | ``` 102 | 103 | ### generate 104 | 105 | Gera um novo documento, retorna em formato de `str`. Recebe os parâmetros: 106 | 107 | 108 | 109 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 110 | | --------- | ---- | ----------- | ------------ | --------- | 111 | | `mask` | `bool` | `False` | - | Quando possui o valor `True`, o documento retornado estará formatado. | 112 | 113 | 114 | 115 | ```python 116 | from validate_docbr import CPF 117 | 118 | cpf = CPF() 119 | 120 | # Gerar novo CPF 121 | new_cpf_one = cpf.generate() # "01234567890" 122 | new_cpf_two = cpf.generate(mask=True) # "012.345.678-90" 123 | ``` 124 | 125 | ### generate_list 126 | 127 | Gera uma lista de documentos, retorna em formato de `list` com elementos do tipo `str`. 128 | Recebe os parâmetros: 129 | 130 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 131 | | --------- | ---- | ----------- | ------------ | --------- | 132 | | `n` | `int` | `1` | X | A quantidade desejada de documentos que serão gerados. | 133 | | `mask` | `bool` | `False` | - | Se os documentos gerados deverão ter ou não máscara. | 134 | | `repeat` | `bool` | `False` | - | Se aceita ou não documentos repetidos. | 135 | 136 | ```python 137 | from validate_docbr import CPF 138 | 139 | cpf = CPF() 140 | 141 | # Gerar lista de CPFs 142 | cpfs_one = cpf.generate_list(2) # [ "85215667438", "28293145811" ] 143 | cpfs_two = cpf.generate_list(2, mask=True) # [ "852.156.674-38", "282.931.458-11" ] 144 | ``` 145 | 146 | ### mask 147 | 148 | Mascara o documento passado como argumento. Retorna um `str` que é o documento mascarado. 149 | Recebe os parâmetros: 150 | 151 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 152 | | --------- | ---- | ----------- | ------------ | --------- | 153 | | `doc` | `str`| `''` | X | O documento que se quer mascarar. | 154 | 155 | ```python 156 | from validate_docbr import CPF 157 | 158 | cpf = CPF() 159 | 160 | cpf_me = "01234567890" 161 | 162 | # Mascara o CPF 163 | cpf.mask(cpf_me) # "012.345.678-90" 164 | ``` 165 | 166 | ## Testes 167 | 168 | Para realizar os testes basta executar o seguinte comando: 169 | 170 | ```shell 171 | make test 172 | ``` 173 | 174 | Para verificar a cobertura de testes: 175 | 176 | ```shell 177 | make test-coverage 178 | ``` 179 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | services: 3 | app: 4 | build: 5 | context: . 6 | image: alvarofpp/validate-docbr 7 | container_name: validate-docbr 8 | volumes: 9 | - .:/app 10 | -------------------------------------------------------------------------------- /docs/guia-de-uso.md: -------------------------------------------------------------------------------- 1 | # Guia de uso 2 | 3 | Todos os documentos possuem os mesmos métodos e funcionam da mesma forma. 4 | 5 | ## validate 6 | 7 | Valida o documento passado como argumento. Retorna um `bool`, `True` caso seja válido, 8 | `False` caso contrário. Recebe os parâmetros: 9 | 10 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 11 | | --------- | ---- | ----------- | ------------ | --------- | 12 | | `doc` | `str`| `''` | X | O documento que se quer validar. | 13 | 14 | ```python 15 | from validate_docbr import CPF 16 | 17 | cpf = CPF() 18 | 19 | # Validar CPF 20 | cpf.validate("012.345.678-90") # True 21 | cpf.validate("012.345.678-91") # False 22 | ``` 23 | 24 | ### Caso especial de CPF 25 | 26 | Os CPFs de 000.000.000-00 até 999.999.999-99 são considerados como válidos pois, em alguns casos, 27 | existem pessoas vinculadas a eles. Usei a base de dados da 28 | [Coleção de CNPJs e CPFs brasileiros do Brasil.IO][brasil.io] 29 | para verificar esses documentos: 30 | 31 | 32 | 33 | | CPF | Pessoa | Consulta | 34 | | --- | ------ | -------- | 35 | | 000.000.000-00 | - | `https://brasil.io/dataset/documentos-brasil/documents?search=00000000000&document_type=CPF&document=&name=&sources=` | 36 | | 111.111.111-11 | AKA CENTRAL PARK - NEW YORK | `https://brasil.io/dataset/documentos-brasil/documents?search=11111111111&document_type=CPF&document=&name=&sources=` | 37 | | 222.222.222-22 | - | `https://brasil.io/dataset/documentos-brasil/documents?search=22222222222&document_type=CPF&document=&name=&sources=` | 38 | | 333.333.333-33 | - | `https://brasil.io/dataset/documentos-brasil/documents?search=33333333333&document_type=CPF&document=&name=&sources=` | 39 | | 444.444.444-44 | - | `https://brasil.io/dataset/documentos-brasil/documents?search=44444444444&document_type=CPF&document=&name=&sources=` | 40 | | 555.555.555-55 | ISAEL HERMINIO DA SILVA | `https://brasil.io/dataset/documentos-brasil/documents?search=55555555555&document_type=CPF&document=&name=&sources=` | 41 | | 666.666.666-66 | - | `https://brasil.io/dataset/documentos-brasil/documents?search=66666666666&document_type=CPF&document=&name=&sources=` | 42 | | 777.777.777-77 | ANTONIO GONÇALO DA SILVA | `https://brasil.io/dataset/documentos-brasil/documents?search=77777777777&document_type=CPF&document=&name=&sources=` | 43 | | 888.888.888-88 | - | `https://brasil.io/dataset/documentos-brasil/documents?search=88888888888&document_type=CPF&document=&name=&sources=` | 44 | | 999.999.999-99 | JOAQUIM ROCHA MATOS | `https://brasil.io/dataset/documentos-brasil/documents?search=99999999999&document_type=CPF&document=&name=&sources=` | 45 | 46 | 47 | 48 | Porém, é comum optar por não validar esses CPFs. Para isso basta usar o parâmetro `repeated_digits` 49 | (por padrão é `False`) da classe `CPF` ou mudar a variável de mesmo nome no objeto criado. 50 | 51 | ```python 52 | from validate_docbr import CPF 53 | 54 | cpf = CPF(repeated_digits=True) 55 | 56 | # Validar CPF 57 | cpf.validate("111.111.111-11") # True 58 | 59 | # Não aceitando entradas de 000.000.000-00 até 999.999.999-99 60 | cpf.repeated_digits = False 61 | 62 | # Validar CPF 63 | cpf.validate("111.111.111-11") # False 64 | ``` 65 | 66 | ## validate_list 67 | 68 | Valida uma lista de documentos passado como argumento. Retorna uma lista de `bool`, 69 | `True` caso seja válido, `False` caso contrário. Recebe os parâmetros: 70 | 71 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 72 | | --------- | ---- | ----------- | ------------ | --------- | 73 | | `docs` | `List[str]`| `[]` | X | A lista de documentos para validar. | 74 | 75 | ```python 76 | from validate_docbr import CPF 77 | 78 | cpf = CPF() 79 | 80 | # Validar CPFs 81 | cpf.validate_list(["012.345.678-90", "012.345.678-91"]) # [True, False] 82 | ``` 83 | 84 | ## validate_docs 85 | 86 | **Observação**: diferente dos outros métodos, esse método é do escopo global do pacote, 87 | não precisa-se instanciar uma classe para uso. 88 | 89 | Valida vários documentos difererentes. Retorna uma lista com valores `bool` para cada tupla 90 | da lista (na mesma ordem), `True` caso seja válido, `False` caso contrário. Recebe os parâmetros: 91 | 92 | 93 | 94 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 95 | | --------- | ---- | ----------- | ------------ | --------- | 96 | | `documents` | `List[Tuple[BaseDoc, str]]`| `[]` | X | Lista de tuplas, cada tupla possui como primeiro elemento o tipo de documento e o segundo elemento o valor que se deseja validar. | 97 | 98 | 99 | 100 | ```python 101 | import validate_docbr as docbr 102 | 103 | 104 | # Validar diferentes documentos 105 | docs = [(docbr.CPF, '90396100457'), (docbr.CNPJ, '49910753848365')] 106 | docbr.validate_docs(docs) # [True, False] 107 | ``` 108 | 109 | ## generate 110 | 111 | Gera um novo documento, retorna em formato de `str`. Recebe os parâmetros: 112 | 113 | 114 | 115 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 116 | | --------- | ---- | ----------- | ------------ | --------- | 117 | | `mask` | `bool` | `False` | - | Quando possui o valor `True`, o documento retornado estará formatado. | 118 | 119 | 120 | 121 | ```python 122 | from validate_docbr import CPF 123 | 124 | cpf = CPF() 125 | 126 | # Gerar novo CPF 127 | new_cpf_one = cpf.generate() # "01234567890" 128 | new_cpf_two = cpf.generate(True) # "012.345.678-90" 129 | ``` 130 | 131 | ## generate_list 132 | 133 | Gera uma lista de documentos, retorna em formato de `list` com elementos do tipo `str`. Recebe os parâmetros: 134 | 135 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 136 | | --------- | ---- | ----------- | ------------ | --------- | 137 | | `n` | `int` | `1` | X | A quantidade desejada de documentos que serão gerados. | 138 | | `mask` | `bool` | `False` | - | Se os documentos gerados deverão ter ou não máscara. | 139 | | `repeat` | `bool` | `False` | - | Se aceita ou não documentos repetidos. | 140 | 141 | ```python 142 | from validate_docbr import CPF 143 | 144 | cpf = CPF() 145 | 146 | # Gerar lista de CPFs 147 | cpfs_one = cpf.generate_list(2) # [ "85215667438", "28293145811" ] 148 | cpfs_two = cpf.generate_list(2, True) # [ "852.156.674-38", "282.931.458-11" ] 149 | ``` 150 | 151 | ## mask 152 | 153 | Mascara o documento passado como argumento. Retorna um `str` que é o documento mascarado. Recebe os parâmetros: 154 | 155 | | Parâmetro | Tipo | Valor padrão | Obrigatório | Descrição | 156 | | --------- | ---- | ----------- | ------------ | --------- | 157 | | `doc` | `str`| `''` | X | O documento que se quer mascarar. | 158 | 159 | ```python 160 | from validate_docbr import CPF 161 | 162 | cpf = CPF() 163 | 164 | cpf_me = "01234567890" 165 | 166 | # Mascara o CPF 167 | cpf.mask(cpf_me) # "012.345.678-90" 168 | ``` 169 | 170 | [brasil.io]: https://brasil.io/dataset/documentos-brasil/documents 171 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # validate-docbr 2 | 3 | 4 | latest release 5 | 6 | 7 | Pacote Python para validação de documentos brasileiros. 8 | 9 | ## Instalação do validate-docbr 10 | 11 | Para instalar o pacote: 12 | 13 | ```bash 14 | pip install validate-docbr 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/licenca.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019, Álvaro Ferreira Pires de Paiva 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the “Software”), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /docs/sobre.md: -------------------------------------------------------------------------------- 1 | # Sobre 2 | 3 | Pacote Python para validação de documentos brasileiros. 4 | 5 | ## Documentos 6 | 7 | Documentos que estão no pacote: 8 | 9 | - CPF: Cadastro de Pessoas Físicas; 10 | - CNH: Carteira Nacional de Habilitação; 11 | - CNPJ: Cadastro Nacional da Pessoa Jurídica; 12 | - CNS: Cartão Nacional de Saúde; 13 | - PIS: PIS/NIS/PASEP/NIT; 14 | - Título Eleitoral: Cadastro que permite cidadãos brasileiros votar. 15 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | site_name: validate-docbr 3 | site_url: https://github.com/alvarofpp/validate-docbr 4 | site_description: Pacote Python para validação de documentos brasileiros. 5 | site_author: MkDocs Team 6 | 7 | nav: 8 | - Instalação: index.md 9 | - Guia de uso: guia-de-uso.md 10 | - Sobre: sobre.md 11 | - Licença: licenca.md 12 | 13 | repo_url: https://github.com/alvarofpp/validate-docbr 14 | 15 | theme: readthedocs 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==7.4.4 2 | pytest-cov==4.1.0 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="validate_docbr", 8 | version="1.11.0", 9 | author="Álvaro Ferreira Pires de Paiva", 10 | author_email="alvarofepipa@gmail.com", 11 | description="Validate brazilian documents.", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/alvarofpp/validate-docbr", 15 | packages=setuptools.find_packages(), 16 | classifiers=[ 17 | "Programming Language :: Python :: 3", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: OS Independent", 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarofpp/validate-docbr/b9c2e71f45dcc20c84c7b5ae129374e0343b8f11/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_CNH.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import validate_docbr as docbr 4 | 5 | 6 | class TestCnh(unittest.TestCase): 7 | """Testar a classe CNH.""" 8 | 9 | def setUp(self): 10 | """Inicia novo objeto em todo os testes.""" 11 | self.cnh = docbr.CNH() 12 | 13 | def test_generate_validate(self): 14 | """Verifica os métodos de geração e validação de documento.""" 15 | # generate_list 16 | cnhs = ( 17 | self.cnh.generate_list(1) 18 | + self.cnh.generate_list(1, mask=True) 19 | + self.cnh.generate_list(1, mask=True, repeat=True) 20 | ) 21 | self.assertIsInstance(cnhs, list) 22 | self.assertTrue(len(cnhs) == 3) 23 | 24 | # validate_list 25 | cnhs_validates = self.cnh.validate_list(cnhs) 26 | self.assertTrue(sum(cnhs_validates) == 3) 27 | 28 | def test_mask(self): 29 | """Verifica se o método mask funciona corretamente.""" 30 | masked_cpf = self.cnh.mask('11122233344') 31 | self.assertEqual(masked_cpf, '111 222 333 44') 32 | 33 | def test_special_case(self): 34 | """ Verifica os casos especiais de CNH """ 35 | cases = [ 36 | ('00000000000', False), 37 | ('AAAAAAAAAAA', False), 38 | ('78623161668', False), 39 | ('0123 456 789 10', False), 40 | ('65821310502', True), 41 | ('658 213 105 02', True), 42 | ('10764125809', True), 43 | ('77625261946', True) 44 | ] 45 | for cnh, is_valid in cases: 46 | self.assertEqual(self.cnh.validate(cnh), is_valid) 47 | -------------------------------------------------------------------------------- /tests/test_CNPJ.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import validate_docbr as docbr 4 | 5 | 6 | class TestCnpj(unittest.TestCase): 7 | """Testar a classe CNPJ.""" 8 | 9 | def setUp(self): 10 | """Inicia novo objeto em todo os testes.""" 11 | self.cnpj = docbr.CNPJ() 12 | 13 | def test_generate_validate(self): 14 | """Verifica os métodos de geração e validação de documento.""" 15 | # generate_list 16 | cnpjs = self.cnpj.generate_list(5000) \ 17 | + self.cnpj.generate_list(5000, mask=True) \ 18 | + self.cnpj.generate_list(5000, mask=True, repeat=True) 19 | self.assertIsInstance(cnpjs, list) 20 | self.assertTrue(len(cnpjs) == 15000) 21 | 22 | # validate_list 23 | cnpjs_validates = self.cnpj.validate_list(cnpjs) 24 | self.assertTrue(sum(cnpjs_validates) == 15000) 25 | 26 | def test_mask(self): 27 | """Verifica se o método mask funciona corretamente.""" 28 | masked_cnpj = self.cnpj.mask('11222333444455') 29 | self.assertEqual(masked_cnpj, '11.222.333/4444-55') 30 | 31 | def test_special_case(self): 32 | """Verifica os casos especiais de CNPJ.""" 33 | cases = [ 34 | ('00000-000/0000', False), 35 | ('AAAA0AAAAAAA2AAAAAA', False), 36 | ('74600269000145', True), 37 | ] 38 | for cnpj, is_valid in cases: 39 | self.assertEqual(self.cnpj.validate(cnpj), is_valid) 40 | 41 | def test_validate_success(self): 42 | """Testar o método validate do CNPJ.""" 43 | # GIVEN 44 | doc = '74600269000145' 45 | 46 | # WHEN 47 | validate_return = self.cnpj.validate(doc) 48 | 49 | # THEN 50 | self.assertTrue(validate_return) 51 | 52 | def test_validate_wrong_input(self): 53 | """Testar o método validate do CNPJ em caso de input errado.""" 54 | # GIVEN 55 | doc = '74600269000145_' 56 | 57 | # WHEN 58 | validate_return = self.cnpj.validate(doc) 59 | 60 | # THEN 61 | self.assertFalse(validate_return) 62 | 63 | def test_validate_wrong_length(self): 64 | """Testar o método validate do CNPJ em caso de tamanho inválido.""" 65 | # GIVEN 66 | doc = '746002690001450' 67 | 68 | # WHEN 69 | validate_return = self.cnpj.validate(doc) 70 | 71 | # THEN 72 | self.assertFalse(validate_return) 73 | 74 | def test_alphanumeric(self): 75 | """Testar o método validate do CNPJ em caso de caracteres alfanuméricos.""" 76 | # GIVEN 77 | cases = [ 78 | ('12.aBc.345/01dE-35', True), 79 | ('12ABC34501DE35', True), 80 | ('12.ABC.345/01DE-34', False), 81 | ] 82 | 83 | # WHEN 84 | results = [] 85 | for doc, is_valid in cases: 86 | results.append(self.cnpj.validate(doc) == is_valid) 87 | 88 | # THEN 89 | self.assertTrue(all(results)) 90 | -------------------------------------------------------------------------------- /tests/test_CNS.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import validate_docbr as docbr 4 | 5 | 6 | class TestCns(unittest.TestCase): 7 | """Testar a classe CNS.""" 8 | 9 | def setUp(self): 10 | """Inicia novo objeto em todo os testes.""" 11 | self.cns = docbr.CNS() 12 | 13 | def test_generate_validate(self): 14 | """Verifica os métodos de geração e validação de documento.""" 15 | # generate_list 16 | cnss = self.cns.generate_list(5000) \ 17 | + self.cns.generate_list(5000, mask=True) \ 18 | + self.cns.generate_list(5000, mask=True, repeat=True) 19 | self.assertIsInstance(cnss, list) 20 | self.assertTrue(len(cnss) == 15000) 21 | 22 | # validate_list 23 | cnss_validates = self.cns.validate_list(cnss) 24 | self.assertTrue(sum(cnss_validates) == 15000) 25 | 26 | def test_mask(self): 27 | """Verifica se o método mask funciona corretamente.""" 28 | masked_cns = self.cns.mask('111222233334444') 29 | self.assertEqual(masked_cns, '111 2222 3333 4444') 30 | 31 | def test_special_case(self): 32 | """ Verifica os casos especiais de CNS """ 33 | cases = [ 34 | ('AAAAAAAAAAA', False), 35 | ('', False), 36 | ] 37 | for cns, is_valid in cases: 38 | self.assertEqual(self.cns.validate(cns), is_valid) 39 | -------------------------------------------------------------------------------- /tests/test_CPF.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import validate_docbr as docbr 4 | 5 | 6 | class TestCpf(unittest.TestCase): 7 | """Testar a classe CPF.""" 8 | 9 | def setUp(self): 10 | """Inicia novo objeto em todo os testes.""" 11 | self.cpf = docbr.CPF() 12 | 13 | def test_generate_validate(self): 14 | """Verifica os métodos de geração e validação de documento.""" 15 | # generate_list 16 | cpfs = self.cpf.generate_list(5000) \ 17 | + self.cpf.generate_list(5000, mask=True) \ 18 | + self.cpf.generate_list(5000, mask=True, repeat=True) 19 | self.assertIsInstance(cpfs, list) 20 | self.assertTrue(len(cpfs) == 15000) 21 | 22 | # validate_list 23 | cpfs_validates = self.cpf.validate_list(cpfs) 24 | self.assertTrue(sum(cpfs_validates) == 15000) 25 | 26 | def test_mask(self): 27 | """Verifica se o método mask funciona corretamente.""" 28 | masked_cpf = self.cpf.mask('11122233344') 29 | self.assertEqual(masked_cpf, '111.222.333-44') 30 | 31 | def test_special_case(self): 32 | """ Verifica os casos especiais de CPF """ 33 | cpfs_repeated_digits = [ 34 | '000.000.000-00', 35 | '111.111.111-11', 36 | '222.222.222-22', 37 | '333.333.333-33', 38 | '444.444.444-44', 39 | '555.555.555-55', 40 | '666.666.666-66', 41 | '777.777.777-77', 42 | '888.888.888-88', 43 | '999.999.999-99', 44 | ] 45 | # Entrada consideradas invalidas 46 | for cpf in cpfs_repeated_digits: 47 | self.assertFalse(self.cpf.validate(cpf)) 48 | # Entrada consideradas validas 49 | self.cpf.repeated_digits = True 50 | for cpf in cpfs_repeated_digits: 51 | self.assertTrue(self.cpf.validate(cpf)) 52 | 53 | cases = [ 54 | ('AAA.AAA.AAA+AA', False), 55 | ('04255791000144', False), 56 | ] 57 | for cpf, is_valid in cases: 58 | self.assertEqual(self.cpf.validate(cpf), is_valid) 59 | 60 | def test_add_leading_zeros(self): 61 | """Verifica se o método de adicionar zeros à esquerda funciona corretamente.""" 62 | cases = [ 63 | ('123456789', False), # 9 digitos 64 | ('12345678901', False), # 11 digitos 65 | ('1234567', False), # 7 digitos 66 | ('9380826044', True) # cpf valido 67 | ] 68 | for cpf_input, is_valid in cases: 69 | self.assertEqual(self.cpf.validate(cpf_input), is_valid) 70 | -------------------------------------------------------------------------------- /tests/test_Certidao.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import validate_docbr as docbr 4 | 5 | 6 | class TestCertidao(unittest.TestCase): 7 | """Testar a classe Certidao.""" 8 | 9 | def setUp(self): 10 | """Inicia novo objeto em todo os testes.""" 11 | self.certidao = docbr.Certidao() 12 | 13 | def test_generate_validate(self): 14 | """Verifica os métodos de geração e validação de documento.""" 15 | # generate_list 16 | certidoes = self.certidao.generate_list(5000) \ 17 | + self.certidao.generate_list(5000, mask=True) 18 | self.assertIsInstance(certidoes, list) 19 | self.assertTrue(len(certidoes) == 10000) 20 | 21 | # validate_list 22 | certidoes_validates = self.certidao.validate_list(certidoes) 23 | self.assertTrue(sum(certidoes_validates) == 10000) 24 | 25 | def test_mask(self): 26 | """Verifica se o método mask funciona corretamente.""" 27 | 28 | masked_certidao = self.certidao.mask( 29 | '10453901552013100012021000012321') 30 | self.assertEqual( 31 | masked_certidao, '104539.01.55.2013.1.00012.021.0000123-21') 32 | 33 | def test_special_case(self): 34 | """ Verifica os casos especiais de Certidão """ 35 | cases = [ 36 | ('3467875434578764345789654', False), 37 | ('AAAAAAAAAAA', False), 38 | ('', False), 39 | ('27610201552018226521370659786633', True), 40 | ('27610201552018226521370659786630', False), 41 | ] 42 | for certidao, is_valid in cases: 43 | self.assertEqual(self.certidao.validate(certidao), is_valid) 44 | -------------------------------------------------------------------------------- /tests/test_PIS.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import validate_docbr as docbr 4 | 5 | 6 | class TestPis(unittest.TestCase): 7 | """Testar a classe PIS/NIS/PASEP/NIT.""" 8 | 9 | def setUp(self): 10 | """Inicia novo objeto em todo os testes.""" 11 | self.pis = docbr.PIS() 12 | 13 | def test_generate_validate(self): 14 | """Verifica os métodos de geração e validação de documento.""" 15 | # generate_list 16 | piss = self.pis.generate_list(5000) \ 17 | + self.pis.generate_list(5000, mask=True) \ 18 | + self.pis.generate_list(5000, mask=True, repeat=True) 19 | self.assertIsInstance(piss, list) 20 | self.assertTrue(len(piss) == 15000) 21 | 22 | # validate_list 23 | piss_validates = self.pis.validate_list(piss) 24 | self.assertTrue(sum(piss_validates) == 15000) 25 | 26 | def test_mask(self): 27 | """Verifica se o método mask funciona corretamente.""" 28 | masked_pis = self.pis.mask('23992734770') 29 | self.assertEqual(masked_pis, '239.92734.77-0') 30 | 31 | masked_pis = self.pis.mask('93999998770') 32 | self.assertEqual(masked_pis, '939.99998.77-0') 33 | 34 | masked_pis = self.pis.mask('03953333770') 35 | self.assertEqual(masked_pis, '039.53333.77-0') 36 | 37 | def test_special_case(self): 38 | """ Verifica os casos especiais de PIS """ 39 | cases = [ 40 | ('3467875434578764345789654', False), 41 | ('AAAAAAAAAAA', False), 42 | ('', False), 43 | ] 44 | for pis, is_valid in cases: 45 | self.assertEqual(self.pis.validate(pis), is_valid) 46 | -------------------------------------------------------------------------------- /tests/test_RENAVAM.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import validate_docbr as docbr 4 | 5 | 6 | class TestRenavam(unittest.TestCase): 7 | """Testar a classe RENAVAM.""" 8 | 9 | def setUp(self): 10 | """Inicia novo objeto em todo os testes.""" 11 | self.renavam = docbr.RENAVAM() 12 | 13 | def test_generate_validate(self): 14 | """Verifica os métodos de geração e validação de documento.""" 15 | # generate_list 16 | renavams = ( 17 | self.renavam.generate_list(1) 18 | + self.renavam.generate_list(1, mask=True) 19 | + self.renavam.generate_list(1, mask=True, repeat=True) 20 | ) 21 | self.assertIsInstance(renavams, list) 22 | self.assertTrue(len(renavams) == 3) 23 | 24 | # validate_list 25 | renavams_validates = self.renavam.validate_list(renavams) 26 | self.assertTrue(sum(renavams_validates) == 3) 27 | 28 | def test_mask(self): 29 | """Verifica se o método mask funciona corretamente.""" 30 | masked_renavam = self.renavam.mask('13824652268') 31 | self.assertEqual(masked_renavam, '1382465226-8') 32 | 33 | def test_special_case(self): 34 | """ Verifica os casos especiais de RENAVAM """ 35 | cases = [ 36 | ('3467875434578764345789654', False), 37 | ('', False), 38 | ('AAAAAAAAAAA', False), 39 | ('38872054170', False), 40 | ('40999838209', False), 41 | ('31789431480', False), 42 | ('38919643060', False), 43 | ('13824652268', True), 44 | ('08543317523', True), 45 | ('09769017014', True), 46 | ('01993520012', True), 47 | ('04598137389', True), 48 | ('05204907510', True), 49 | ] 50 | for renavam, is_valid in cases: 51 | self.assertEqual(self.renavam.validate(renavam), is_valid) 52 | -------------------------------------------------------------------------------- /tests/test_TituloEleitoral.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import validate_docbr as docbr 4 | 5 | 6 | class TestTituloEleitoral(unittest.TestCase): 7 | def setUp(self): 8 | """ Inicia novo objeto em todo os testes """ 9 | self.titulo_eleitoral = docbr.TituloEleitoral() 10 | 11 | def test_generate(self): 12 | """ Verifica se o método generate """ 13 | # generate, generate(mask=True) 14 | titulos = [self.titulo_eleitoral.generate() for i in range(10000)] \ 15 | + [self.titulo_eleitoral.generate(mask=True) for i in range(10000)] 16 | self.assertIsInstance(titulos, list) 17 | self.assertTrue(len(titulos) == 20000) 18 | 19 | def test_generate_list(self): 20 | """ Verifica se o método generate_list """ 21 | # generate_list 22 | titulo_eleitoral = self.titulo_eleitoral.generate_list(10000) \ 23 | + self.titulo_eleitoral.generate_list(10000, True) \ 24 | + self.titulo_eleitoral.generate_list(10000, True, True) 25 | self.assertIsInstance(titulo_eleitoral, list) 26 | self.assertTrue(len(titulo_eleitoral) == 30000) 27 | 28 | def test_validate(self): 29 | """ Verifica se o método validate """ 30 | # validate 31 | for titulo in self.titulo_eleitoral.generate_list(10000): 32 | self.assertTrue(self.titulo_eleitoral.validate(titulo)) 33 | 34 | def test_mask_returns_correctly_formatted_string(self): 35 | masked_titulo = self.titulo_eleitoral.mask('123123123123') 36 | 37 | self.assertEqual(masked_titulo, '1231 2312 3123') 38 | 39 | def test_special_case(self): 40 | """ Verifica os casos especiais de Titulo de Eleitor """ 41 | cases = [ 42 | ('3467875434578764345789654', False), 43 | ('AAAAAAAAAAA', False), 44 | ('', False), 45 | ] 46 | for titulo_eleitoral, is_valid in cases: 47 | self.assertEqual(self.titulo_eleitoral.validate(titulo_eleitoral), is_valid) 48 | -------------------------------------------------------------------------------- /tests/test_generic.py: -------------------------------------------------------------------------------- 1 | import random 2 | import unittest 3 | 4 | import validate_docbr as docbr 5 | 6 | 7 | def get_random_number_str(length): 8 | numbers = '0123456789' 9 | return ''.join(random.choice(numbers) for i in range(length)) 10 | 11 | 12 | class TestValidateDocs(unittest.TestCase): 13 | """Testa a função validate_docs""" 14 | 15 | def test_correct_argument(self): 16 | """Testa a função quando os argumentos estão corretos""" 17 | DocClasses = [ 18 | docbr.CPF, 19 | docbr.CNH, 20 | docbr.CNPJ, 21 | docbr.CNS, 22 | docbr.PIS, 23 | docbr.TituloEleitoral, 24 | ] 25 | 26 | documents = [] 27 | right_answers = [] 28 | 29 | for DocClass in DocClasses: 30 | # Documentos válidos 31 | tuples = [(DocClass, doc) for doc in DocClass().generate_list(200)] 32 | documents += tuples 33 | right_answers += [True] * len(tuples) 34 | 35 | # Documentos aleatórios 36 | len_doc = len(DocClass().generate()) 37 | for _ in range(200): 38 | random_doc = get_random_number_str(len_doc) 39 | documents += [(DocClass, random_doc)] 40 | right_answers += [DocClass().validate(random_doc)] 41 | 42 | self.assertEqual(docbr.validate_docs(documents), right_answers) 43 | 44 | def test_incorrect_argument(self): 45 | """Test a função quando os argumentos estão incorretos""" 46 | with self.assertRaises(TypeError): 47 | docbr.validate_docs([('cpf', docbr.CPF().generate())]) 48 | -------------------------------------------------------------------------------- /validate_docbr/BaseDoc.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List 3 | 4 | 5 | class BaseDoc(ABC): 6 | """Classe base para todas as classes referentes a documentos.""" 7 | 8 | @abstractmethod 9 | def validate(self, doc: str = "") -> bool: 10 | """Método para validar o documento desejado.""" 11 | pass 12 | 13 | def validate_list(self, docs: List[str]) -> List[bool]: 14 | """Método para validar uma lista de documentos desejado.""" 15 | return [self.validate(doc) for doc in docs] 16 | 17 | @abstractmethod 18 | def generate(self, mask: bool = False) -> str: 19 | """Método para gerar um documento válido.""" 20 | pass 21 | 22 | def generate_list( 23 | self, n: int = 1, mask: bool = False, repeat: bool = False 24 | ) -> list: 25 | """Gerar uma lista do mesmo documento.""" 26 | doc_list = [] 27 | 28 | if n <= 0: 29 | return doc_list 30 | 31 | for _ in range(n): 32 | doc_list.append(self.generate(mask)) 33 | 34 | while not repeat: 35 | doc_set = set(doc_list) 36 | unique_values = len(doc_set) 37 | 38 | if unique_values < n: 39 | doc_list = list(doc_set) + self.generate_list( 40 | (n - unique_values), mask, repeat 41 | ) 42 | else: 43 | repeat = True 44 | 45 | return doc_list 46 | 47 | @abstractmethod 48 | def mask(self, doc: str = "") -> str: 49 | """Mascara o documento enviado""" 50 | pass 51 | 52 | def _only_digits(self, doc: str = "") -> str: 53 | """Remove os outros caracteres que não sejam dígitos.""" 54 | return "".join([x for x in doc if x.isdigit()]) 55 | 56 | def _only_digits_and_letters(self, doc: str = "") -> str: 57 | """Remove os outros caracteres que não sejam dígitos ou letras.""" 58 | return "".join([x for x in doc if x.isdigit() or x.isalpha()]) 59 | 60 | def _validate_input( 61 | self, input: str, valid_characters: List = None, allow_letters: bool = False 62 | ) -> bool: 63 | """Validar input. 64 | Caso ele possua apenas dígitos (e, opcionalmente, letras) e caracteres 65 | válidos, retorna True. 66 | Caso possua algum caractere que não seja dígito ou caractere válido, 67 | retorna False.""" 68 | if valid_characters is None: 69 | valid_characters = [".", "-", "/", " "] 70 | 71 | if allow_letters: 72 | set_non_digit_characters = set( 73 | [x for x in input if not x.isdigit() and not x.isalpha()] 74 | ) 75 | else: 76 | set_non_digit_characters = set([x for x in input if not x.isdigit()]) 77 | set_valid_characters = set(valid_characters) 78 | 79 | return not (len(set_non_digit_characters.difference(set_valid_characters)) > 0) 80 | -------------------------------------------------------------------------------- /validate_docbr/CNH.py: -------------------------------------------------------------------------------- 1 | from random import sample 2 | from typing import Union 3 | 4 | from .BaseDoc import BaseDoc 5 | 6 | 7 | class CNH(BaseDoc): 8 | """Classe referente ao Carteira Nacional de Habilitação (CNH).""" 9 | 10 | def __init__(self): 11 | self.digits = list(range(10)) 12 | 13 | def validate(self, doc: str = '') -> bool: 14 | """Validar CNH.""" 15 | if not self._validate_input(doc, [' ']): 16 | return False 17 | 18 | doc = self._only_digits(doc) 19 | 20 | if len(doc) != 11 or self._is_repeated_digits(doc): 21 | return False 22 | 23 | first_digit = self._generate_first_digit(doc) 24 | second_digit = self._generate_second_digit(doc) 25 | 26 | return first_digit == doc[9] and second_digit == doc[10] 27 | 28 | def generate(self, mask: bool = False) -> str: 29 | """Gerar CNH.""" 30 | cnh = [str(sample(self.digits, 1)[0]) for i in range(9)] 31 | cnh.append(self._generate_first_digit(cnh)) 32 | cnh.append(self._generate_second_digit(cnh)) 33 | 34 | cnh = ''.join(cnh) 35 | return self.mask(cnh) if mask else cnh 36 | 37 | def mask(self, doc: str = '') -> str: 38 | """Coloca a máscara de CNH na variável doc.""" 39 | return f"{doc[:3]} {doc[3:6]} {doc[6:9]} {doc[9:]}" 40 | 41 | def _generate_first_digit(self, doc: Union[str, list]) -> str: 42 | """Gerar o primeiro dígito verificador da CNH.""" 43 | self.dsc = 0 44 | sum = 0 45 | 46 | for i in range(9, 0, -1): 47 | sum += int(doc[9 - i]) * i 48 | 49 | first_value = sum % 11 50 | if first_value >= 10: 51 | first_value, self.dsc = 0, 2 52 | return str(first_value) 53 | 54 | def _generate_second_digit(self, doc: Union[str, list]) -> str: 55 | """Gerar o segundo dígito verificador da CNH.""" 56 | sum = 0 57 | 58 | for i in range(1, 10): 59 | sum += int(doc[i-1]) * i 60 | 61 | rest = sum % 11 62 | 63 | second_value = rest - self.dsc 64 | if second_value < 0: 65 | second_value += 11 66 | if second_value >= 10: 67 | second_value = 0 68 | return str(second_value) 69 | 70 | def _is_repeated_digits(self, doc: str) -> bool: 71 | """Verifica se é uma CNH contém com números repetidos. 72 | Exemplo: 11111111111""" 73 | return len(set(doc)) == 1 74 | -------------------------------------------------------------------------------- /validate_docbr/CNPJ.py: -------------------------------------------------------------------------------- 1 | import string 2 | from random import sample 3 | from typing import Union 4 | 5 | from .BaseDoc import BaseDoc 6 | 7 | 8 | class CNPJ(BaseDoc): 9 | """Classe referente ao Cadastro Nacional da Pessoa Jurídica (CNPJ).""" 10 | 11 | def __init__(self): 12 | self.digits = list(range(10)) 13 | self.digits_and_letters = list(string.ascii_uppercase) + list(string.digits) 14 | self.weights_first = list(range(5, 1, -1)) + list(range(9, 1, -1)) 15 | self.weights_second = list(range(6, 1, -1)) + list(range(9, 1, -1)) 16 | 17 | def validate(self, doc: str = '') -> bool: 18 | """Validar CNPJ.""" 19 | if not self._validate_input(doc, ['.', '/', '-'], allow_letters=True): 20 | return False 21 | 22 | doc = doc.strip().upper() 23 | doc = self._only_digits_and_letters(doc) 24 | 25 | if len(doc) != 14: 26 | return False 27 | 28 | return self._generate_first_digit(doc) == doc[12] \ 29 | and self._generate_second_digit(doc) == doc[13] 30 | 31 | def generate(self, mask: bool = False, digits_only: bool = True) -> str: 32 | """Gerar CNPJ.""" 33 | # Os doze primeiros dígitos 34 | if digits_only: 35 | cnpj = [str(sample(self.digits, 1)[0]) for i in range(12)] 36 | else: 37 | cnpj = [sample(self.digits_and_letters, 1)[0] for i in range(12)] 38 | 39 | # Gerar os dígitos verificadores 40 | cnpj.append(self._generate_first_digit(cnpj)) 41 | cnpj.append(self._generate_second_digit(cnpj)) 42 | 43 | cnpj = "".join(cnpj) 44 | 45 | return self.mask(cnpj) if mask else cnpj 46 | 47 | def mask(self, doc: str = '') -> str: 48 | """Coloca a máscara de CNPJ na variável doc.""" 49 | return f"{doc[:2]}.{doc[2:5]}.{doc[5:8]}/{doc[8:12]}-{doc[-2:]}" 50 | 51 | def _generate_first_digit(self, doc: Union[str, list]) -> str: 52 | """Gerar o primeiro dígito verificador do CNPJ.""" 53 | sum = 0 54 | 55 | for i in range(12): 56 | sum += (ord(str(doc[i])) - 48) * self.weights_first[i] 57 | 58 | sum = sum % 11 59 | sum = 0 if sum < 2 else 11 - sum 60 | 61 | return str(sum) 62 | 63 | def _generate_second_digit(self, doc: Union[str, list]) -> str: 64 | """Gerar o segundo dígito verificador do CNPJ.""" 65 | sum = 0 66 | 67 | for i in range(13): 68 | sum += (ord(str(doc[i])) - 48) * self.weights_second[i] 69 | 70 | sum = sum % 11 71 | sum = 0 if sum < 2 else 11 - sum 72 | 73 | return str(sum) 74 | -------------------------------------------------------------------------------- /validate_docbr/CNS.py: -------------------------------------------------------------------------------- 1 | from random import sample 2 | 3 | from .BaseDoc import BaseDoc 4 | 5 | 6 | class CNS(BaseDoc): 7 | """Classe referente ao Cartão Nacional de Saúde (CNS).""" 8 | 9 | def __init__(self): 10 | self.digits = list(range(10)) 11 | self.first_digit = [1, 2, 7, 8, 9] 12 | 13 | def validate(self, doc: str = '') -> bool: 14 | """Validar CNS.""" 15 | if not self._validate_input(doc, [' ']): 16 | return False 17 | 18 | doc = list(self._only_digits(doc)) 19 | 20 | if len(doc) != 15 or int(doc[0]) not in self.first_digit: 21 | return False 22 | 23 | return self._check_cns_valid(doc) 24 | 25 | def _validate_first_case(self, doc: list) -> bool: 26 | """Validar CNSs que comecem com 1 ou 2.""" 27 | cns = self._generate_first_case(doc) 28 | 29 | return cns == doc 30 | 31 | def _validate_second_case(self, doc: list) -> bool: 32 | """Validar CNSs que comecem com 7, 8 ou 9.""" 33 | sum = self._sum_algorithm(doc) 34 | 35 | return sum % 11 == 0 36 | 37 | def generate(self, mask: bool = False) -> str: 38 | """Gerar CNS.""" 39 | # Primeiro dígito válido 40 | cns = [str(sample(self.first_digit, 1)[0])] 41 | 42 | # Geração irá depender do resultado do primeiro dígito 43 | if cns[0] in ['1', '2']: 44 | cns = self._generate_first_case(cns, True) 45 | else: 46 | cns = self._generate_second_case(cns) 47 | 48 | cns = ''.join(cns) 49 | 50 | return self.mask(cns) if mask else cns 51 | 52 | def mask(self, doc: str = '') -> str: 53 | """Coloca a máscara de CPF na variável doc.""" 54 | return f"{doc[:3]} {doc[3:7]} {doc[7:11]} {doc[-4:]}" 55 | 56 | def _generate_first_case(self, cns: list, generate_random=False) -> list: 57 | """Gera um CNS válido para os casos que se inicia com 1 ou 2.""" 58 | if generate_random: 59 | # Adiciona os próximos 10 dígitos 60 | cns = cns + [str(sample(self.digits, 1)[0]) for i in range(10)] 61 | else: 62 | # Pega apenas a parte que precisamos do CNS 63 | cns = cns[:11] 64 | 65 | # Processo de soma 66 | sum = self._sum_algorithm(cns, 11) 67 | 68 | dv = 11 - (sum % 11) 69 | 70 | if dv == 11: 71 | dv = 0 72 | 73 | if dv == 10: 74 | sum += 2 75 | dv = 11 - (sum % 11) 76 | cns = cns + ['0', '0', '1', str(dv)] 77 | else: 78 | cns = cns + ['0', '0', '0', str(dv)] 79 | 80 | return cns 81 | 82 | def _generate_second_case(self, cns: list) -> list: 83 | """Gera um CNS válido para os casos que se inicia com 7, 8 ou 9.""" 84 | # Gerar os próximos 14 dígitos 85 | cns = cns + [str(sample(list(range(10)), 1)[0]) for i in range(14)] 86 | sum = self._sum_algorithm(cns) 87 | rest = sum % 11 88 | 89 | if rest == 0: 90 | return cns 91 | 92 | # Resto para o próximo múltiplo de 11 93 | diff = 11 - rest 94 | 95 | # Verificar qual é o mais próximo 96 | return self._change_cns(cns, 15 - diff, diff) 97 | 98 | def _change_cns(self, cns: list, i: int, val: int) -> list: 99 | """Altera o CNS recursivamente para que atenda as especificações de 100 | validade dele.""" 101 | if val == 0: 102 | if self._check_cns_valid(cns): 103 | return cns 104 | else: 105 | sum = self._sum_algorithm(cns) 106 | diff = 15 - (sum % 11) 107 | return self._change_cns(cns, 15 - diff, diff) 108 | 109 | if 15 - i > val: 110 | i += 1 111 | return self._change_cns(cns, i, val) 112 | 113 | if cns[i] != '9': 114 | cns[i] = str(int(cns[i]) + 1) 115 | val -= (15 - i) 116 | else: 117 | val += (15 - i) 118 | cns[i] = str(int(cns[i]) - 1) 119 | i -= 1 120 | 121 | return self._change_cns(cns, i, val) 122 | 123 | def _sum_algorithm(self, cns: list, n: int = 15) -> int: 124 | """Realiza o processo de soma necessária para o CNS.""" 125 | sum = 0 126 | for i in range(n): 127 | sum += int(cns[i]) * (15 - i) 128 | 129 | return sum 130 | 131 | def _check_cns_valid(self, cns: list) -> bool: 132 | """Checa se o CNS é válido.""" 133 | if cns[0] in ['1', '2']: 134 | return self._validate_first_case(cns) 135 | else: 136 | return self._validate_second_case(cns) 137 | -------------------------------------------------------------------------------- /validate_docbr/CPF.py: -------------------------------------------------------------------------------- 1 | from random import sample 2 | from typing import List 3 | 4 | from .BaseDoc import BaseDoc 5 | 6 | 7 | class CPF(BaseDoc): 8 | """Classe referente ao Cadastro de Pessoas Físicas (CPF).""" 9 | 10 | def __init__(self, repeated_digits: bool = False): 11 | self.digits = list(range(10)) 12 | self.repeated_digits = repeated_digits 13 | 14 | def validate(self, doc: str = '') -> bool: 15 | """Validar CPF.""" 16 | if not self._validate_input(doc, ['.', '-']): 17 | return False 18 | 19 | doc = list(self._only_digits(doc)) 20 | 21 | if len(doc) < 11: 22 | doc = self._complete_with_zeros(doc) 23 | 24 | if not self.repeated_digits and self._check_repeated_digits(doc): 25 | return False 26 | 27 | return self._generate_first_digit(doc) == doc[9] \ 28 | and self._generate_second_digit(doc) == doc[10] 29 | 30 | def generate(self, mask: bool = False) -> str: 31 | """Gerar CPF.""" 32 | # Os nove primeiros dígitos 33 | cpf = [str(sample(self.digits, 1)[0]) for i in range(9)] 34 | 35 | # Gerar os dígitos verificadores 36 | cpf.append(self._generate_first_digit(cpf)) 37 | cpf.append(self._generate_second_digit(cpf)) 38 | 39 | cpf = "".join(cpf) 40 | 41 | return self.mask(cpf) if mask else cpf 42 | 43 | def mask(self, doc: str = '') -> str: 44 | """Coloca a máscara de CPF na variável doc.""" 45 | return f"{doc[:3]}.{doc[3:6]}.{doc[6:9]}-{doc[-2:]}" 46 | 47 | def _generate_first_digit(self, doc: list) -> str: 48 | """Gerar o primeiro dígito verificador do CPF.""" 49 | sum = 0 50 | 51 | for i in range(10, 1, -1): 52 | sum += int(doc[10 - i]) * i 53 | 54 | sum = (sum * 10) % 11 55 | 56 | if sum == 10: 57 | sum = 0 58 | 59 | return str(sum) 60 | 61 | def _generate_second_digit(self, doc: list) -> str: 62 | """Gerar o segundo dígito verificador do CPF.""" 63 | sum = 0 64 | 65 | for i in range(11, 1, -1): 66 | sum += int(doc[11 - i]) * i 67 | 68 | sum = (sum * 10) % 11 69 | 70 | if sum == 10: 71 | sum = 0 72 | 73 | return str(sum) 74 | 75 | def _check_repeated_digits(self, doc: List[str]) -> bool: 76 | """Verifica se é um CPF com números repetidos. 77 | Exemplo: 111.111.111-11""" 78 | return len(set(doc)) == 1 79 | 80 | def _complete_with_zeros(self, doc: str) -> list[str]: 81 | """Adiciona zeros à esquerda para completar o CPF.""" 82 | zeros_needed = 11 - len(doc) 83 | return ['0'] * zeros_needed + doc 84 | -------------------------------------------------------------------------------- /validate_docbr/Certidao.py: -------------------------------------------------------------------------------- 1 | from random import sample 2 | 3 | from .BaseDoc import BaseDoc 4 | 5 | 6 | class Certidao(BaseDoc): 7 | """Classe referente a Certidão de Nascimento/Casamento/Óbito.""" 8 | 9 | def __init__(self): 10 | self.digits = list(range(10)) 11 | 12 | def validate(self, doc: str = '') -> bool: 13 | """Método para validar a Certidão de Nascimento/Casamento/Óbito.""" 14 | if not self._validate_input(doc, ['.', '-']): 15 | return False 16 | 17 | doc = self._only_digits(doc) 18 | 19 | if len(doc) != 32: 20 | return False 21 | 22 | num = list(doc[:-2]) 23 | dv = doc[-2:] 24 | 25 | expected_dv = self._generate_verifying_digit(num) 26 | 27 | if dv == expected_dv: 28 | return True 29 | 30 | return False 31 | 32 | def _weighted_sum(self, value) -> int: 33 | sum = 0 34 | multiplier = 32 - len(value) 35 | 36 | for i in range(len(value)): 37 | sum += int(value[i]) * multiplier 38 | 39 | multiplier += 1 40 | multiplier = 0 if multiplier > 10 else multiplier 41 | 42 | return sum 43 | 44 | def generate(self, mask: bool = False) -> str: 45 | """Método para gerar a Certidão de Nascimento/Casamento/Óbito.""" 46 | # Os trinta primeiros dígitos 47 | certidao = [str(sample(self.digits, 1)[0]) for i in range(30)] 48 | 49 | # Gerar os dígitos verificadores 50 | certidao.append(self._generate_verifying_digit(certidao)) 51 | 52 | certidao = "".join(certidao) 53 | 54 | return self.mask(certidao) if mask else certidao 55 | 56 | def mask(self, doc: str = '') -> str: 57 | """Mascara para a Certidão de Nascimento/Casamento/Óbito.""" 58 | return "{}.{}.{}.{}.{}.{}.{}.{}-{}".format( 59 | doc[:6], doc[6:8], doc[8:10], doc[10:14], 60 | doc[14], doc[15:20], doc[20:23], doc[23:30], doc[-2:]) 61 | 62 | def _generate_verifying_digit(self, doc: list) -> str: 63 | """Gerar o dígito verificador da Certidao.""" 64 | dv1 = self._weighted_sum(doc) % 11 65 | dv1 = 1 if dv1 > 9 else dv1 66 | 67 | dv2 = self._weighted_sum(doc+[dv1]) % 11 68 | dv2 = 1 if dv2 > 9 else dv2 69 | 70 | return str(dv1)+str(dv2) 71 | -------------------------------------------------------------------------------- /validate_docbr/PIS.py: -------------------------------------------------------------------------------- 1 | from random import sample 2 | from typing import Union 3 | 4 | from .BaseDoc import BaseDoc 5 | 6 | 7 | class PIS(BaseDoc): 8 | """Classe referente ao PIS/NIS/PASEP/NIT.""" 9 | 10 | def __init__(self): 11 | self.digits = list(range(10)) 12 | 13 | def validate(self, doc: str = '') -> bool: 14 | """Validar PIS/NIS/PASEP/NIT.""" 15 | if not self._validate_input(doc, ['.', '-']): 16 | return False 17 | 18 | doc = self._only_digits(doc) 19 | 20 | if len(doc) != 11 or self._is_repeated_digits(doc): 21 | return False 22 | 23 | digit = self._generate_digit(doc) 24 | 25 | return digit == doc[10] 26 | 27 | def generate(self, mask: bool = False) -> str: 28 | """Gerar PIS/NIS/PASEP/NIT.""" 29 | pis = [str(sample(self.digits, 1)[0]) for i in range(10)] 30 | pis.append(self._generate_digit(pis)) 31 | 32 | pis = ''.join(pis) 33 | return self.mask(pis) if mask else pis 34 | 35 | def mask(self, doc: str = '') -> str: 36 | """Coloca a máscara de PIS/NIS/PASEP/NIT na variável doc.""" 37 | return f"{doc[:3]}.{doc[3:8]}.{doc[8:10]}-{doc[10:]}" 38 | 39 | def _generate_digit(self, doc: Union[str, list]) -> str: 40 | """Gerar o dígito verificador do PIS/NIS/PASEP/NIT.""" 41 | multipliers = [3, 2, 9, 8, 7, 6, 5, 4, 3, 2] 42 | summation = 0 43 | 44 | for position in range(0, 10): 45 | summation += int(doc[position]) * multipliers[position] 46 | 47 | module = summation % 11 48 | digit = 0 49 | 50 | if module >= 2: 51 | digit = 11 - module 52 | 53 | return str(digit) 54 | 55 | def _is_repeated_digits(self, doc: str) -> bool: 56 | """Verifica se o PIS/NIS/PASEP/NIT contém com números repetidos. 57 | Exemplo: 11111111111""" 58 | return len(set(doc)) == 1 59 | -------------------------------------------------------------------------------- /validate_docbr/RENAVAM.py: -------------------------------------------------------------------------------- 1 | from random import sample 2 | from typing import Union 3 | 4 | from .BaseDoc import BaseDoc 5 | 6 | 7 | class RENAVAM(BaseDoc): 8 | """Classe referente ao Registro Nacional de Veículos Automotores (RENAVAM).""" 9 | 10 | def __init__(self): 11 | self.digits = list(range(10)) 12 | 13 | def validate(self, doc: str = '') -> bool: 14 | """Validar RENAVAM.""" 15 | if not self._validate_input(doc, [' ']): 16 | return False 17 | 18 | doc = self._only_digits(doc) 19 | 20 | if len(doc) != 11: 21 | return False 22 | 23 | last_digit = self._generate_last_digit(doc) 24 | 25 | return last_digit == doc[10] 26 | 27 | def generate(self, mask: bool = False) -> str: 28 | """Gerar Renavam.""" 29 | renavam = [str(sample(self.digits, 1)[0]) for i in range(10)] 30 | renavam.append(self._generate_last_digit(renavam)) 31 | 32 | renavam = ''.join(renavam) 33 | return renavam 34 | 35 | def mask(self, doc: str = '') -> str: 36 | """Coloca a máscara de Renavam na variável doc.""" 37 | return f"{doc[:10]}-{doc[10]}" 38 | 39 | def _generate_last_digit(self, doc: Union[str, list]) -> str: 40 | """Gerar o dígito verificador do Renavam.""" 41 | sequence = '3298765432' 42 | sum = 0 43 | 44 | for i in range(0, 10): 45 | sum += int(doc[i]) * int(sequence[i]) 46 | 47 | rest = (sum * 10) % 11 48 | 49 | if rest == 10: 50 | rest = 0 51 | return str(rest) 52 | -------------------------------------------------------------------------------- /validate_docbr/TituloEleitoral.py: -------------------------------------------------------------------------------- 1 | from random import sample 2 | from typing import List 3 | 4 | from .BaseDoc import BaseDoc 5 | 6 | 7 | class TituloEleitoral(BaseDoc): 8 | """Classe referente ao Título eleitoral""" 9 | 10 | def __init__(self): 11 | self.digits = list(range(10)) 12 | self.first_check_digit_weights = list(range(2, 10)) 13 | self.second_check_digit_weights = list(range(7, 10)) 14 | self.first_check_digit_doc_slice = slice(0, 8) 15 | self.second_check_digit_doc_slice = slice(8, 10) 16 | 17 | def validate(self, doc: str = '') -> bool: 18 | """Método para validar o título eleitoral.""" 19 | if not self._validate_input(doc, [' ']): 20 | return False 21 | 22 | doc_digits = list(map(int, self._only_digits(doc=doc))) 23 | 24 | if len(doc_digits) != 12: 25 | return False 26 | 27 | first_check_digit = self._compute_first_check_digit(doc_digits=doc_digits) 28 | second_check_digit = self._compute_second_check_digit( 29 | doc_digits=doc_digits, 30 | first_check_digit=first_check_digit 31 | ) 32 | 33 | return first_check_digit == doc_digits[-2] \ 34 | and second_check_digit == doc_digits[-1] 35 | 36 | def generate(self, mask: bool = False) -> str: 37 | """Método para gerar um título eleitoral válido.""" 38 | document_digits = [sample(self.digits, 1)[0] for _ in range(8)] 39 | 40 | state_identifier = self._generate_valid_state_identifier() 41 | document_digits.extend(map(int, state_identifier)) 42 | 43 | first_check_digit = self._compute_first_check_digit(doc_digits=document_digits) 44 | second_check_digit = self._compute_second_check_digit( 45 | doc_digits=document_digits, 46 | first_check_digit=first_check_digit 47 | ) 48 | document_digits.extend([first_check_digit, second_check_digit]) 49 | 50 | document = ''.join(map(str, document_digits)) 51 | 52 | if mask: 53 | return self.mask(doc=document) 54 | 55 | return document 56 | 57 | def mask(self, doc: str = '') -> str: 58 | """Mascara o documento enviado""" 59 | return f'{doc[0:4]} {doc[4:8]} {doc[8:]}' 60 | 61 | def _compute_first_check_digit(self, doc_digits: List[int]) -> int: 62 | """Método que calcula o primeiro dígito verificador.""" 63 | doc_digits_to_consider = doc_digits[self.first_check_digit_doc_slice] 64 | terms = [ 65 | doc_digit * multiplier 66 | for doc_digit, multiplier in zip( 67 | doc_digits_to_consider, 68 | self.first_check_digit_weights 69 | ) 70 | ] 71 | 72 | total = sum(terms) 73 | 74 | if total % 11 == 10: 75 | return 0 76 | 77 | return total % 11 78 | 79 | def _compute_second_check_digit( 80 | self, 81 | doc_digits: List[int], 82 | first_check_digit: int 83 | ) -> int: 84 | """Método que calcula o segundo dígito verificador.""" 85 | doc_digits_to_consider = doc_digits[self.second_check_digit_doc_slice] \ 86 | + [first_check_digit] 87 | terms = [ 88 | doc_digit * multiplier 89 | for doc_digit, multiplier in zip( 90 | doc_digits_to_consider, 91 | self.second_check_digit_weights 92 | ) 93 | ] 94 | 95 | total = sum(terms) 96 | 97 | if total % 11 == 10: 98 | return 0 99 | 100 | return total % 11 101 | 102 | def _generate_valid_state_identifier(self) -> str: 103 | state_identifier = str(sample(range(1, 19), 1)[0]) 104 | return state_identifier.zfill(2) 105 | -------------------------------------------------------------------------------- /validate_docbr/__init__.py: -------------------------------------------------------------------------------- 1 | from .BaseDoc import BaseDoc 2 | from .Certidao import Certidao 3 | from .CNH import CNH 4 | from .CNPJ import CNPJ 5 | from .CNS import CNS 6 | from .CPF import CPF 7 | from .generic import validate_docs 8 | from .PIS import PIS 9 | from .RENAVAM import RENAVAM 10 | from .TituloEleitoral import TituloEleitoral 11 | 12 | __all__ = [ 13 | 'BaseDoc', 14 | 'CPF', 15 | 'CNPJ', 16 | 'CNH', 17 | 'CNS', 18 | 'PIS', 19 | 'TituloEleitoral', 20 | 'Certidao', 21 | 'RENAVAM', 22 | 'validate_docs', 23 | ] 24 | -------------------------------------------------------------------------------- /validate_docbr/generic.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from typing import List, Tuple, Type 3 | 4 | from .BaseDoc import BaseDoc 5 | 6 | 7 | def validate_docs(documents: List[Tuple[Type[BaseDoc], str]] = list): 8 | """Recebe uma lista de tuplas (classe, valor) e a valida""" 9 | validations = [] 10 | 11 | for doc in documents: 12 | if not inspect.isclass(doc[0]) or not issubclass(doc[0], BaseDoc): 13 | raise TypeError( 14 | "O primeiro índice da tupla deve ser uma classe de documento!" 15 | ) 16 | 17 | validations.append(doc[0]().validate(doc[1])) 18 | 19 | return validations 20 | --------------------------------------------------------------------------------