brutils-2.5.0/0000775000000000000000000000000015221040537011701 5ustar rootrootbrutils-2.5.0/CODE_OF_CONDUCT_EN.md0000664000000000000000000001262115221040537015064 0ustar rootroot # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement via email to cmaiacd@gmail.com or mdeazevedomaia@gmail.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations brutils-2.5.0/.flake80000664000000000000000000000003415221040537013051 0ustar rootroot[flake8] max-line-length=80 brutils-2.5.0/.github/0000775000000000000000000000000015221040537013241 5ustar rootrootbrutils-2.5.0/.github/ISSUE_TEMPLATE/0000775000000000000000000000000015221040537015424 5ustar rootrootbrutils-2.5.0/.github/ISSUE_TEMPLATE/nova-funcionalidade-🌟.md0000664000000000000000000000304615221040537023226 0ustar rootroot--- name: "Nova Funcionalidade \U0001F31F" about: Sugestão de uma nova funcionalidade title: '' labels: feature assignees: '' --- **Seu pedido de recurso está relacionado a um problema? Por favor, descreva.** Uma descrição clara e concisa do que é o problema. Por exemplo: "Fico sempre frustrado quando [...]" **Descreva a solução que você gostaria** Uma descrição clara e concisa do que você deseja que aconteça. **Descreva alternativas que você considerou** Uma descrição clara e concisa de quaisquer soluções ou recursos alternativos que você tenha considerado. **Contexto adicional** Adicione qualquer outro contexto ou capturas de tela sobre o pedido de recurso aqui. **💌 Quer contribuir, mas não se sente à vontade?** Você tem vontade de contribuir, mas não se sente à vontade em abrir issues, PRs ou fazer perguntas publicamente? Nós sabemos como pode ser difícil dar o primeiro passo em um espaço aberto. A insegurança, o medo de errar ou até a sensação de “será que minha dúvida é boba?” podem pesar bastante. E tá tudo bem sentir isso. 💜 Queremos que você saiba que aqui ninguém precisa enfrentar esse caminho sem apoio. Se preferir um espaço mais reservado, você pode mandar um e-mail para cumbucadev@gmail.com e teremos o maior prazer em ajudar. Seja para tirar dúvidas, pedir orientação ou simplesmente ter alguém para conversar sobre como começar. O importante é que você saiba: sua participação é muito bem-vinda, e cada contribuição, por menor que pareça, faz uma grande diferença. ✨ brutils-2.5.0/.github/ISSUE_TEMPLATE/reportar-um-bug-🐛.md0000664000000000000000000000301415221040537022343 0ustar rootroot--- name: "Reportar um bug \U0001F41B" about: Reporte um bug, nos ajude a melhorar! title: '' labels: bug assignees: '' --- **Descrição do problema** Uma descrição clara e concisa do que é o erro. **Para Reproduzir** Passos para reproduzir o comportamento: 1. Chamar o utilitário '...' 2. Passando o parâmetro '....' 3. Ver o erro **Comportamento esperado** Uma descrição clara e concisa do que você esperava que acontecesse. **Desktop (por favor, forneça as seguintes informações):** - Sistema Operacional: [por exemplo, iOS] - Versão do brutils: [por exemplo, 2.0.0] **Contexto adicional** Adicione qualquer outro contexto sobre o problema aqui. **💌 Quer contribuir, mas não se sente à vontade?** Você tem vontade de contribuir, mas não se sente à vontade em abrir issues, PRs ou fazer perguntas publicamente? Nós sabemos como pode ser difícil dar o primeiro passo em um espaço aberto. A insegurança, o medo de errar ou até a sensação de “será que minha dúvida é boba?” podem pesar bastante. E tá tudo bem sentir isso. 💜 Queremos que você saiba que aqui ninguém precisa enfrentar esse caminho sem apoio. Se preferir um espaço mais reservado, você pode mandar um e-mail para cumbucadev@gmail.com e teremos o maior prazer em ajudar. Seja para tirar dúvidas, pedir orientação ou simplesmente ter alguém para conversar sobre como começar. O importante é que você saiba: sua participação é muito bem-vinda, e cada contribuição, por menor que pareça, faz uma grande diferença. ✨ brutils-2.5.0/.github/workflows/0000775000000000000000000000000015221040537015276 5ustar rootrootbrutils-2.5.0/.github/workflows/autoassign-issue-v1.yml0000664000000000000000000000045615221040537021655 0ustar rootrootname: Autoassign issue on: issue_comment: types: - created permissions: contents: read issues: write pull-requests: write jobs: issue_assign: name: "Call `Autoassign issue` shared workflow" uses: cumbucadev/shared-workflows/.github/workflows/autoassign-issue-v1.yml@main brutils-2.5.0/.github/workflows/close-stale-issues-v1.yml0000664000000000000000000000045715221040537022077 0ustar rootrootname: "Close stale issues and PRs" on: schedule: - cron: '00 4 * * *' workflow_dispatch: permissions: actions: write issues: write pull-requests: write jobs: stale: name: Stale uses: cumbucadev/shared-workflows/.github/workflows/close-stale-issues-v1.yml@main brutils-2.5.0/.github/workflows/check-lint.yml0000664000000000000000000000146515221040537020050 0ustar rootrootname: Check Lint on: pull_request: push: branches: - main jobs: lint: name: Check Lint runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v4 - name: Poetry Setup uses: snok/install-poetry@v1 - name: Install Dependencies run: poetry install - name: Run Lint Check id: lint run: | set -o pipefail make check 2>&1 | tee lint_output.log continue-on-error: true - name: Lint Failed if: steps.lint.outcome != 'success' run: | echo -e "\033[0;31m Linting failed. See the errors below:\n" cat lint_output.log echo -e "\n\033[0;33m Please, run \`make format\` and push the changes to fix this error." exit 1 brutils-2.5.0/.github/workflows/run-tests.yml0000664000000000000000000000152215221040537017765 0ustar rootrootname: run-tests on: pull_request: push: branches: - main jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v4 - name: Set Up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Poetry Setup uses: snok/install-poetry@v1 - name: Install Dependencies run: poetry install - name: Generate Report run: | poetry run coverage run -m unittest discover -s tests poetry run coverage xml - name: Upload Coverage to Codecov uses: codecov/codecov-action@v7 with: files: ./coverage.xml token: ${{ secrets.CODECOV_TOKEN }} brutils-2.5.0/.github/workflows/validate-pr-title-v1.yml0000664000000000000000000000044015221040537021672 0ustar rootrootname: "Validate PR title" on: pull_request_target: types: - opened - edited - reopened permissions: pull-requests: write jobs: lint_pr: name: Validate uses: cumbucadev/shared-workflows/.github/workflows/validate-pr-title-v1.yml@main brutils-2.5.0/.github/workflows/publish-to-production.yml0000664000000000000000000000076415221040537022302 0ustar rootrootname: Publish to Production on: release: types: [published] jobs: build-n-publish: name: Publish to PyPI. Build and publish Python 🐍 distributions 📦 runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v4 - name: Poetry Setup uses: snok/install-poetry@v1 - name: Build and publish to pypi run: | poetry build poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }} poetry publish brutils-2.5.0/.github/dependabot.yml0000664000000000000000000000127315221040537016074 0ustar rootroot# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: weekly time: "03:00" open-pull-requests-limit: 10 allow: - dependency-type: direct - dependency-type: indirect - package-ecosystem: github-actions directory: / schedule: interval: weekly time: "03:00" open-pull-requests-limit: 10 brutils-2.5.0/.github/FUNDING.yml0000664000000000000000000000004115221040537015051 0ustar rootrootgithub: [cumbucadev, camilamaia] brutils-2.5.0/.github/pull_request_template.md0000664000000000000000000000420515221040537020203 0ustar rootroot## Descrição ## Mudanças Propostas ## Checklist de Revisão - [ ] Eu li o [Contributing.md](https://github.com/brazilian-utils/brutils-python/blob/main/CONTRIBUTING.md) - [ ] Os testes foram adicionados ou atualizados para refletir as mudanças (se aplicável). - [ ] Foi adicionada uma entrada no changelog / Meu PR não necessita de uma nova entrada no changelog. - [ ] A [documentação](https://github.com/brazilian-utils/brutils-python/blob/main/README.md) em português foi atualizada ou criada, se necessário. - [ ] Se feita a documentação, a atualização do [arquivo em inglês](https://github.com/brazilian-utils/brutils-python/blob/main/README_EN.md). - [ ] Eu documentei as minhas mudanças no código, adicionando docstrings e comentários. [Instruções](https://github.com/brazilian-utils/brutils-python/blob/main/CONTRIBUTING.md#8-fa%C3%A7a-as-suas-altera%C3%A7%C3%B5es) - [ ] O código segue as diretrizes de estilo e padrões de codificação do projeto. - [ ] Todos os testes passam. [Instruções](https://github.com/brazilian-utils/brutils-python/blob/main/CONTRIBUTING.md#testes) - [ ] O Pull Request foi testado localmente. [Instruções](https://github.com/brazilian-utils/brutils-python/blob/main/CONTRIBUTING.md#7-execute-o-brutils-localmente) - [ ] Não há conflitos de mesclagem. ## Comentários Adicionais (opcional) ## Issue Relacionada Closes # brutils-2.5.0/.github/CODEOWNERS0000664000000000000000000000011015221040537014624 0ustar rootroot* @brazilian-utils/python-maintainers @brazilian-utils/python-core-team brutils-2.5.0/LICENSE0000664000000000000000000000206015221040537012704 0ustar rootrootMIT License Copyright (c) 2023 Brazilian Utils Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. brutils-2.5.0/CONTRIBUTING_EN.md0000664000000000000000000006137715221040537014532 0ustar rootroot# Contributing Thanks for taking the time to contribute! 🙇‍♀️🙇‍♂️ Every little bit of help counts! - [First Contribution](#first-contribution) - [Release a New Version](#release-a-new-version) ### 💌 Want to contribute, but don’t feel comfortable? Do you feel like contributing but feel uncomfortable opening issues, PRs, or asking questions publicly? We know how hard it can be to take the first step in an open space. Insecurity, the fear of making mistakes, or even wondering “is my question silly?” can weigh heavily. And it’s okay to feel that way. 💜 We want you to know that no one has to face this journey alone. If you’d prefer a more private space, you can send an email to cumbucadev@gmail.com, and we’ll be happy to help. Whether it’s to clear up doubts, ask for guidance, or simply have someone to talk to about how to get started. What matters is that you know: your participation is very welcome, and every contribution, no matter how small it may seem, makes a big difference. ✨ ## First Contribution How to make your first contribution: - [1. Create a GitHub Account](#1-create-a-github-account) - [2. Find an Issue to Work On](#2-find-an-issue-to-work-on) - [3. Install Git](#3-install-git) - [4. Fork the Project](#4-fork-the-project) - [5. Clone your Fork](#5-clone-your-fork) - [6. Create a New Branch](#6-create-a-new-branch) - [7. Run brutils Locally](#7-run-brutils-locally) - [8. Make your Changes](#8-make-your-changes) - [9. Test your Changes](#9-test-your-changes) - [10. Update READMEs](#10-update-readmes) - [11. Commit and push your Changes](#11-commit-and-push-your-changes) - [12. Add changelog Entries](#12-add-changelog-entries) - [13. Create a GitHub PR](#13-create-a-github-pr) - [14. Update your branch if Needed.](#14-update-your-branch-if-needed) ### 1. Create a GitHub Account Ensure you have a [GitHub account][github-join] and you are logged in. ### 2. Find an Issue to Work With Visit the [brutils issues page][brutils-issues] and find an issue that interests you and hasn't been assigned yet. Leave a comment in the issue with "bora!" (in Brazilian Portuguese, that means "let's go!"). A bot will assign the issue to you. Once assigned, proceed to the next step. Feel free to ask any questions in the issue's page before or during the development process. When starting to contribute, it is advised to handle one issue at a time. This ensures others have the chance to collaborate and avoids inactive resources. ### 3. Install Git Make sure you have [Git installed][install-git]. ### 4. Fork the Project [Fork the brutils repository][github-forking]. ### 5. Clone your Fork [Clone your fork][github-cloning] locally. ### 6. Create a New Branch Go into the brutils folder: ```bash $ cd brutils-python > ``` And create a new branch with the issue number you’re working on: ```bash $ git checkout -b > ``` Example: ```bash $ git checkout -b 386 Switched to a new branch '386' > ``` ### 7. Run brutils locally #### Installation with poetry ##### Requirements - [Python 3.10+][python] - [Poetry][poetry] Create a [virtualenv][virtualenv] for brutils and activate it: ```shell $ make shell Spawning shell within ...-py3.x emulate bash -c '. .../bin/activate' ``` To test if the virtual environment is correctly activated, run: ```sh $ poetry env inf Virtualenv Python: 3.x.y Implementation: CPython ... ``` **Note: You need to run `make shell` every time you open a new terminal window/tab.** Install the dependencies: ```shell $ make install git config --local core.hooksPath .githooks/ chmod -R +x .githooks Installing dependencies from lock file ... ``` #### Installation with pip If you prefer to use pip, you can install the project in development mode as follows: ##### Requirements - [Python 3.10+][python] - [pip][pip] Create a [virtualenv][virtualenv] for brutils and activate it using the following command: ```sh python -m venv venv source venv/bin/activate # On Windows use: venv\Scripts\activate ``` Use the pip command to install the dev and test dependencies through the requirements-dev.txt file: ```sh pip install -r requirements-dev.txt ``` #### Using locally Now, you can use it [in the same way described in the README.md file](/README_EN.md#usage). ## Tests Run tests using the following command: ```shell $ make test make test test__is_valid_mercosul (license_plate.test_is_valid.TestIsValidMercosul.test__is_valid_mercosul) ... ok test__is_valid_old_format (license_plate.test_is_valid.TestIsValidOldFormat.test__is_valid_old_format) ... ok .... ---------------------------------------------------------------------- Ran XX tests in 0.000s OK ``` ### 8. Make your changes Now it’s time to implement your changes. Check the issue description for instructions or ideas in the section “Describe alternatives you considered.” Make sure your changes resolve everything mentioned in the issue. We document our code using [docstrings][docstring-definition]. All modules, classes, functions, and methods should have docstrings. Your changes should reflect updated docstrings, especially if any parameters were changed. All docstrings must be written in English. You can use tools like Google Translate or ChatGPT for help. We’ll suggest changes to the translation if needed. We follow this pattern for docstring consistency: ```python class Example: """ Explain the purpose of the class Attributes: x[dict]: Short explanation here y[type, optional]: Short explanation here """ def __init__(self, x, y=None): self.x = x self.y = y def foobar(self, w): """ Purpose of the function Args: name[type]: Short explanation here Returns: type: Short explanation here Example: >>> command 1 output 1 >>> command 2 output 2 """ ... return value ``` Example: ```python def format_cep(cep): # type: (str) -> str | None """ Formats a Brazilian CEP (Postal Code) into a standard format. This function takes a CEP (Postal Code) as input and, if it is a valid 8-digit CEP, formats it into the standard "12345-678" format. Args: cep (str): The input CEP (Postal Code) to be formatted. Returns: str: The formatted CEP in the "12345-678" format if it's valid, None if it's not valid. Example: >>> format_cep("12345678") "12345-678" >>> format_cep("12345") None """ return f"{cep[:5]}-{cep[5:8]}" if is_valid(cep) else None ``` One last thing to keep in mind while self-documenting code with docstrings that you can ignore docstrings in property decorators and magic methods. ### 9. Test your changes #### Write new tests Make sure you have created the necessary tests for every new change you made. #### Make sure all tests passed Run all the tests with `make test` and make sure that they all passed. **PRs will not be merged if there is any test missing or failing.** #### Test it manually Open an interactive environment to manually test your changes: ```sh $ python Python 3.x.y ... Type "help", "copyright", "credits" or "license" for more information. >>> # Test your changes here! ### 10. Update READMEs Update the `brutils-python/README.md` and `brutils-python/README_EN.md` files with your changes. These files are essential for the library’s documentation, helping users understand how to use the provided features. Therefore, it’s important to always keep them up to date. • The brutils-python/README_EN.md file contains the documentation in English, and you can use the content already described in the docstring directly. • The brutils-python/README.md file contains the documentation in Portuguese. For this one, simply translate the function’s docstring. If you need help translating into Portuguese, tools like Google Translate or ChatGPT can assist. Don’t worry about potential translation mistakes, as we will suggest adjustments when necessary. Example in Portuguese (README.md): ````md ### format_cep Esta função recebe um CEP como entrada e, se for um CEP válido de 8 dígitos, formata-o no padrão "12345-678". Argumentos: - cep (str): O CEP a ser formatado. Retorna: - str: O CEP formatado no padrão "12345-678" se for válido, ou None se não for válido. Exemplo: ```python >>> from brutils import format_cep >>> format_cep('01310200') '01310-200' >>> format_cep("12345678") "12345-678" >>> format_cep("12345") None ``` ```` Example in English (README_EN.md): ````md ### format_cep This function takes a CEP (Postal Code) as input and, if it is a valid 8-digit CEP, formats it into the standard "12345-678" format. Args: - cep (str): The input CEP (Postal Code) to be formatted. Returns: - str: The formatted CEP in the "12345-678" format if it's valid, None if it's not valid. Example: ```python >>> from brutils import format_cep >>> format_cep('01310200') '01310-200' >>> format_cep("12345678") "12345-678" >>> format_cep("12345") None ``` ```` ### 11. Commit and push your changes Format your code by running the command: ```bash $ make format ... ``` Exemple: ```bash $ make format 31 files left unchanged All checks passed! ``` Add your changes to the staging area: ```bash $ git add --all ... ``` Commit your changes: ```bash $ git commit -a -m "" ... ``` Example: ```bash $ git push --set-upstream origin 386 Running pre-push hook checks All checks passed! Enumerating objects: 7, done. Counting objects: 100% (7/7), done. Delta compression using up to 10 threads Compressing objects: 100% (4/4), done. Writing objects: 100% (4/4), 2.36 KiB | 2.36 MiB/s, done. Total 4 (delta 3), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (3/3), completed with 3 local objects. remote: remote: Create a pull request for '386' on GitHub by visiting: remote: https://github.com/brazilian-utils/brutils-python/pull/new/386 remote: To github.com:brazilian-utils/brutils-python.git * [new branch] 386 -> 386 ``` Make the necessary changes and commits and push them when ready. ### 12. Add changelog entries #### What is a changelog? A changelog is a file that contains a chronologically organized list of notable changes for each version of a project. #### Why maintain a changelog? To make it easier for users and contributors to see exactly what notable changes have been made between each release (or version) of the project. #### Who needs a changelog? People. Whether they are consumers or developers, the end users of software are human beings who care about what’s in the software. When the software changes, people want to know why and how. #### Where is the brutils changelog? The brutils changelog is available at [CHANGELOG.md][changelog]. #### Guiding principles - Changelogs are for humans, not machines. - There should be an entry for every version. - The same types of changes should be grouped together. - Versions and sections should be linkable. - The most recent version comes first. - The release date of each version is displayed. #### What justifies an entry in the changelog? - Security fixes: These should be documented with the type labeled as “security” to alert users to resolved security issues. Example: “Fixed a critical vulnerability that allowed remote code execution.” - User-facing changes: Changes that directly affect how users interact with the software, including new features, changes to existing features, or UI improvements. Example: “Added a new filter option on the results page to make searching easier.” - Significant performance improvements: Should be recorded when they result in noticeable improvements in speed or efficiency that impact the user experience. Example: “The home page loading time was reduced by 40% after backend optimization.” - Changes affecting compatibility: Adjustments to maintain compatibility with other tools, systems, or versions. Example: “Updated library X to version 2.0 to support the new version of Python.” - Changes to the public API: Changes that affect how developers interact with the public API, such as adding new routes or modifying existing ones. Example: “Added a new /api/v1/users route for user management.” - Dependency changes: Updates or changes to the project’s dependencies that may affect software behavior or compatibility. Example: “Updated dependency package Y to version 3.1.4, which includes important security fixes.” #### What should NOT go in the changelog Although the changelog is a valuable tool for documenting changes, some information should not be included. Here are some examples of what should not appear in the changelog: - Internal Code Changes: Changes that do not affect the end-user experience, such as internal refactoring that does not alter functionality, need not be documented. Example: “Refactored internal functions” or “Fixed inconsistent tests.” - Non-notable performance improvements: Performance improvements that do not result in noticeable changes or clear benefits for the end user do not need to be included. Example: “Optimized internal algorithms.” - Minor bug fixes: Bug fixes that do not impact the general use or end-user experience can be omitted. Example: “Fixed a small typo in the code.” - Documentation-only changes: Changes that affect only the documentation, without modifying the software behavior, usually don’t need to be included. Example: “Updated README.md to reflect new dependencies.” - Excessive technical details: Overly technical information that is irrelevant to the end user or does not provide context on the impact of the change should be avoided. Example: “Changed memory management in class X.” - Maintainer entries: Changes related only to the development or internal maintenance process, such as CI/CD tool configuration adjustments, are generally not relevant for the changelog. Example: “Updated GitHub Actions configuration.” - A bug introduced and fixed in the same release does not need a changelog entry. Avoid including this information to keep the changelog focused and useful for project users and contributors. #### Writing good changelog entries A good changelog entry should be both descriptive and concise. It should explain the change to a reader with no prior context about the change. If it’s hard to be both concise and descriptive, lean toward being more descriptive. - **Bad** : Go to the project order. - **Good**: Display the user’s starred projects at the top of the “Go to project” dropdown. The first example provides no context on where the change was made, why it was made, or how it benefits the user. - **Bad**: Copy (some text) to the clipboard. - **Good**: Update the tooltip of “Copy to clipboard” to indicate what is being copied. Again, the first example is too vague and provides no context. - **Bad**: Fix and improve CSS and HTML issues in the mini pipeline graph and builds dropdown. - **Good**: Fix tooltips and hover states in the mini pipeline graph and builds dropdown. The first example is too focused on implementation details. Users don’t care that we changed CSS and HTML, they care about the final result of those changes. - **Bad**: Remove null values in the Commit object array returned by find_commits_by_message_with_elastic. - **Good**: Fix 500 errors caused by Elasticsearch results referencing commits already collected by the garbage collector. The first example focuses on how we fixed something, not on what was fixed. The rewritten version clearly describes the final benefit to the user (fewer 500 errors) and when it happens (when searching for commits with Elasticsearch). Use your best judgment and try to put yourself in the position of someone reading the compiled changelog. Does this entry add value? Does it provide context on where and why the change was made? #### How to add an entry to the changelog The changelog is available in the [CHANGELOG.md][changelog] file. First, you need to identify the type of your change. Types of changes: - `Added` for new features. - `Changed` for changes to existing features. - `Deprecated` for features that will soon be removed. - `Fixed` for any bug fixes. - `Removed` for features that were removed. - `Security` in case of vulnerabilities. You should always add new entries to the changelog in the `Unreleased` section. At the time of release, we will move the changes from the `Unreleased` section to a new version section. So, within the `Unreleased` section, you should add your entry to the appropriate section by type. If there is no section yet for the type of your change, you should add one. Let’s see some examples. Suppose you have a new `Fixed` change to add, and the current CHANGELOG.md file looks like this: ```md ## [Unreleased] ### Added - Utility `get_address_from_cep` [#358](https://github.com/brazilian-utils/brutils-python/pull/358) ### Changed - Utility `fmt_voter_id` renamed to `format_voter_id` [#221](https://github.com/brazilian-utils/brutils-python/issues/221) ``` You would need to add a new `Fixed` section and include your new entry there: ```md ## [Unreleased] ### Added - Utility `get_address_from_cep` [#358](https://github.com/brazilian-utils/brutils-python/pull/358) ### Changed - Utility `fmt_voter_id` renamed to `format_voter_id` [#221](https://github.com/brazilian-utils/brutils-python/issues/221) ### Fixed - My changelog message here. [#]() ``` Note that the order of sections by type matters. We have a lint that checks this, so the sections must be ordered alphabetically. First `Added`, then `Changed`, third `Deprecated`, and so on. Now, let’s say you have another entry to add, and its type is `Added`. Since we already have a section for that, you should just add a new line: ```md ## [Unreleased] ### Added - Utility `get_address_from_cep` [#358](https://github.com/brazilian-utils/brutils-python/pull/358) - My other changelog message here. [#]() ### Changed - Utility `fmt_voter_id` renamed to `format_voter_id` [#221](https://github.com/brazilian-utils/brutils-python/issues/221) ### Fixed - My changelog message here. [#]() ``` This content is based on the [Keep a Changelog][keep-a-changelog] site, as we follow its guidelines. ### 13. Create a GitHub PR [Create a GitHub PR][github-creating-a-pr] to submit your changes for review. To ensure your Pull Request (PR) is clear, effective, and reviewed quickly, follow these best practices: #### Write a Descriptive PR Title - Use clear and specific titles to describe the purpose of your changes. A good title helps maintainers understand the PR’s intent at a glance and improves project traceability. - **Example**: Instead of “Fix issue,” use “Add utility `convert_uf_to_text` to handle Brazilian state codes.” - **Benefits**: - Clear titles make it easier for reviewers to prioritize and understand the PR. - They improve the project’s organization and searchability. #### Provide a Detailed PR Description - Include a comprehensive description in your PR to explain: - **What** was done (e.g., added a new function, fixed a bug). - **Why** it was done (e.g., to address a specific issue or improve performance). - **What issues** were resolved or improvements applied (e.g., link to the issue or describe the enhancement). - **Example**: This PR adds the convert_uf_to_text utility to convert Brazilian state codes (e.g., "SP") to full state names (e.g., "São Paulo"). It addresses issue #474 by improving code reusability for address formatting. The function includes input validation and updated tests. - **Benefits**: - Detailed descriptions speed up the review process by providing context. - They help future maintainers understand the code’s purpose and history. #### Link the PR to the Related Issue - Reference the issue your PR addresses using keywords like `Closes #474` or `Fixes #474` in the PR description. This automatically closes the issue when the PR is merged. - **Example**: `Closes #474` - **Benefits**: - Linking issues keeps the repository organized and ensures tasks are tracked. - It automates issue closure, reducing manual work for maintainers. - For more details, see the [GitHub documentation on closing issues automatically](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). #### Verify the PR Description Template - Ensure your PR follows the repository’s PR description template (if provided). Check all required items, such as test coverage, documentation updates, or changelog entries. - **Example Checklist**: (showing how it looks when completed): - [x] Code changes are tested. - [x] Documentation (READMEs) is updated. - [ ] Changelog entry is added. - **Syntax Note**: - Use [x] to mark completed items and [ ] for incomplete ones, with no spaces inside the brackets (e.g., [ x ] or [x ] will not render correctly on GitHub). - **Benefits**: - Adhering to the template ensures your PR is complete and ready for review. - It reduces back-and-forth with reviewers, speeding up the merge process. ### 14. Update your branch if needed. [Ensure your branch is up to date with main][github-sync-pr]. ## Release a New Version Here you will find how to deploy a new production version of brutils: - [1. Create a Release Issue](#1-create-a-release-issue) - [2. Create a Release PR](#2-create-a-release-pr) - [3. Deploy via GitHub](#3-deploy-via-github) ### 1. Create a Release Issue #### Create the Issue To create the issue, you can use the feature template, naming the issue Release v. [Example](https://github.com/brazilian-utils/brutils-python/issues/322) #### Create a Branch The name of the branch created for the release is related to the Issue number, as shown in [this example](https://github.com/brazilian-utils/brutils-python/pull/326) ### 2. Create a Release PR #### Update the Library Version Increment the version number, following the [Semantic Versioning][semantic-versioning], in the `pyproject.toml` file: https://github.com/brazilian-utils/brutils-python/blob/main/pyproject.toml#L3 #### Update the CHANGELOG.md Add a title for the new version with the new number and the current date, as seen in [this example](https://github.com/brazilian-utils/brutils-python/blob/main/CHANGELOG.md?plain=1#L9). And add the version links, like [this example](https://github.com/antoniamaia/brutils-python/blob/eac770e8b213532d2bb5948d117f6f4684f65be2/CHANGELOG.md?plain=1#L76) #### Create the PR Create a PR named `Release v` containing the two changes mentioned above. In the description of the Pull Request, add the modified section of the changelog. [Example of Release PR](https://github.com/brazilian-utils/brutils-python/pull/128) #### Merge the PR Once the PR is accepted and passes all checks, merge it. ### 2. Deploy via GitHub The new version release in production is done automatically when a [new release is created][creating-releases] on GitHub. - Fill in the `tag version` should be `v` (e.g `v2.0.0`). - Fill in the `release title` should be the same as tag version (e.g `v2.0.0`). - Fill in the `release description` should be the content copied from the CHANGELOG.md file from the corresponding version section. Real examples are available at: https://github.com/brazilian-utils/brutils-python/releases When the GitHub deployment is completed, the new version will also be automatically released on [PyPI][brutils-on-pypi]. Download the new brutils version from PyPI and test to ensure everything is working as expected. [brutils-issues]: https://github.com/brazilian-utils/brutils-python/issues [brutils-on-pypi]: https://pypi.org/project/brutils/ [brutils-releases]: https://github.com/brazilian-utils/brutils-python/releases [changelog]: https://github.com/brazilian-utils/brutils-python/blob/main/CHANGELOG.md [creating-releases]: https://docs.github.com/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release [docstring-definition]: https://www.python.org/dev/peps/pep-0257/#what-is-a-docstring [github-cloning]: https://docs.github.com/repositories/creating-and-managing-repositories/cloning-a-repository [github-creating-a-pr]: https://docs.github.com/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request [github-forking]: https://docs.github.com/get-started/quickstart/contributing-to-projects [github-join]: https://github.com/join [github-sync-pr]: https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/keeping-your-pull-request-in-sync-with-the-base-branch [keep-a-changelog]: https://keepachangelog.com/en/1.0.0/ [poetry]: https://python-poetry.org/docs/#installation [python]: https://www.python.org/downloads/ [release-pr-example]: https://github.com/brazilian-utils/brutils-python/pull/326 [semantic-versioning]: https://semver.org [virtualenv]: https://virtualenv.pypa.io/en/latest/ brutils-2.5.0/pyproject.toml0000664000000000000000000000260015221040537014613 0ustar rootroot[tool.poetry] name = "brutils" version = "2.5.0" description = "Utils library for specific Brazilian businesses" authors = ["The Brazilian Utils Organization"] license = "MIT" readme = "README.md" repository = "https://github.com/brazilian-utils/python" keywords = ["cpf", "cnpj", "cep", "document", "validation", "brazil", "brazilian"] classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Office/Business", "Topic :: Software Development :: Internationalization", "Topic :: Software Development :: Libraries :: Python Modules", "Natural Language :: English", "Natural Language :: Portuguese", "Natural Language :: Portuguese (Brazilian)" ] [tool.poetry.dependencies] python = "^3.10" holidays = ">=0.58,<0.99" num2words = "0.5.14" coverage = "^7.2.7" [tool.poetry.group.test.dependencies] coverage = "^7.2.7" [tool.poetry.group.dev.dependencies] ruff = "0.15.17" [tool.ruff] line-length = 80 lint.extend-select = ["I"] [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" brutils-2.5.0/CORE_TEAM_EN.md0000664000000000000000000000361515221040537014150 0ustar rootroot

🇧🇷 Core Team brutils-python

The _Core Team_ of `brazilian-utils/brutils-python` is comprised of a group of contributors who have demonstrated remarkable enthusiasm for the project and the community. This team holds administrative privileges on GitHub specific to this repository. ## Responsibilities The _Core Team_ of `brutils-python` has the following responsibilities: * Be available to address strategic inquiries regarding the vision and future prospects of Brazilian Utils Python. * Commit to reviewing pull requests that have been submitted for some time or have been overlooked. * Periodically inspect open issues in Brazilian Utils Python, provide constructive suggestions, and categorize them using specific labels on GitHub. * Identify promising individuals in the Brazilian Utils Python community who may express interest in joining the team and contributing significantly. Similar to all contributors at `brutils-python`, members of the Core Team also operate as volunteers in the open-source realm; being part of the team is not an obligation. This team is recognized as leaders in this community, and while they are a reliable reference for obtaining answers to questions, it's important to note that they contribute voluntarily, dedicating their time, which may result in non-immediate availability. ## Members - [@camilamaia](https://github.com/camilamaia) - [@antoniamaia](https://github.com/antoniamaia) ## Adding New Members The process for incorporating new members into the _Core Team_ `brutils-python` is as follows: * An existing team member privately contacts the individual to gauge their interest. If there is interest, a pull request is opened, adding the new member to the list. * Other team members review the pull request. The person responsible for merging the PR is also tasked with adding the new member to the Core Team group on GitHub. brutils-2.5.0/.githooks/0000775000000000000000000000000015221040537013606 5ustar rootrootbrutils-2.5.0/.githooks/pre-push0000775000000000000000000000035215221040537015277 0ustar rootrootprintf "Running pre-push hook checks\n\n" make check bash -c "if [[ $? != 0 ]];then printf '\nPlease run the following command before push:\n\n make format\n\n\ Remember to commit any changes made on the process.\n'; exit 1; fi" brutils-2.5.0/README_EN.md0000664000000000000000000011605015221040537013545 0ustar rootroot![Brazilian Utils Logo](https://github.com/brazilian-utils/brand/raw/main/github-hero/github-hero-python.png)

Utils library for Brazilian-specific businesses.

[![codecov](https://codecov.io/gh/brazilian-utils/python/branch/main/graph/badge.svg)](https://codecov.io/gh/brazilian-utils/python) [![Check Lint](https://github.com/brazilian-utils/python/actions/workflows/check-lint.yml/badge.svg?branch=main)](https://github.com/brazilian-utils/python/actions/workflows/check-lint.yml?query=branch%3Amain) [![Downloads per Month](https://shields.io/pypi/dm/brutils)](https://pypistats.org/packages/brutils) [![Package version](https://shields.io/pypi/v/brutils)](https://pypi.org/project/brutils/) ### [Procurando pela versão em português?](README.md)
# Getting Started Brazilian Utils is a library focused on solving problems that we face daily in the development of applications for the Brazilian business. - [Installation](#installation) - [Usage](#usage) - [Utilities](#utilities) - [Feature Request and Bug Report](#feature-request-and-bug-report) - [Questions? Ideas?](#questions-ideas) - [Code Contribution](#code-contribution) # Installation ```bash pip install brutils ``` # Usage To use one of our utilities you just need to import the required function as in the example below: ```python >>> from brutils import is_valid_cpf >>> is_valid_cpf('00011122233') False ``` # Utilities - [CPF](#cpf) - [is\_valid\_cpf](#is_valid_cpf) - [format\_cpf](#format_cpf) - [remove\_symbols\_cpf](#remove_symbols_cpf) - [generate\_cpf](#generate_cpf) - [CNPJ](#cnpj) - [is\_valid\_cnpj](#is_valid_cnpj) - [format\_cnpj](#format_cnpj) - [remove\_symbols\_cnpj](#remove_symbols_cnpj) - [generate\_cnpj](#generate_cnpj) - [CEP](#cep) - [is\_valid\_cep](#is_valid_cep) - [format\_cep](#format_cep) - [remove\_symbols\_cep](#remove_symbols_cep) - [generate\_cep](#generate_cep) - [get\_address\_from\_cep](#get_address_from_cep) - [get\_cep\_information\_from\_address](#get_cep_information_from_address) - [Date](#date) - [convert\_date\_to_text](#convert_date_to_text) - [Phone](#phone) - [is\_valid\_phone](#is_valid_phone) - [format\_phone](#format_phone) - [remove\_symbols\_phone](#remove_symbols_phone) - [remove\_international\_dialing\_code](#remove_international_dialing_code) - [generate\_phone](#generate_phone) - [Email](#email) - [is\_valid\_email](#is_valid_email) - [CNH](#cnh) - [is\_valid\_cnh](#is_valid_cnh) - [License Plate](#license-plate) - [is\_valid\_license\_plate](#is_valid_license_plate) - [format\_license\_plate](#format_license_plate) - [remove\_symbols\_license\_plate](#remove_symbols_license_plate) - [generate\_license\_plate](#generate_license_plate) - [convert\_license\_plate\_to\_mercosul](#convert_license_plate_to_mercosul) - [get\_format\_license\_plate](#get_format_license_plate) - [PIS](#pis) - [is\_valid\_pis](#is_valid_pis) - [format\_pis](#format_pis) - [remove\_symbols\_pis](#remove_symbols_pis) - [generate\_pis](#generate_pis) - [RENAVAM](#renavam) - [is_valid_renavam](#is_valid_renavam) - [Legal Process](#legal-process) - [is\_valid\_legal\_process](#is_valid_legal_process) - [format\_legal\_process](#format_legal_process) - [remove\_symbols\_legal\_process](#remove_symbols_legal_process) - [generate\_legal\_process](#generate_legal_process) - [Voter ID](#voter-id) - [is_valid_voter_id](#is_valid_voter_id) - [format_voter_id](#format_voter_id) - [generate_voter_id](#generate_voter_id) - [IBGE](#ibge) - [convert_code_to_uf](#convert_code_to_uf) - [convert_uf_to_name](#convert_uf_to_name) - [convert_name_to_uf](#convert_name_to_uf) - [get_code_by_municipality_name](#get_code_by_municipality_name) - [get\_municipality\_by\_code](#get_municipality_by_code) - [Holidays](#holidays) - [is_holiday](#is_holiday) - [Monetary](#monetary) - [format_currency](#format_currency) - [convert\_real\_to\_text](#convert_real_to_text) - [Legal Nature](#legal-nature) - [is_valid_legal_nature](#is_valid_legal_nature) - [get_legal_nature_description](#get_legal_nature_description) - [list_all_legal_nature](#list_all_legal_nature) - [Passport](#passport) - [is_valid_passport](#is_valid_passport) - [format_passport](#format_passport) - [remove_symbols_passport](#remove_symbols_passport) - [generate_passport](#generate_passport) ## CPF ### is_valid_cpf Returns whether or not the verifying checksum digits of the given CPF (Brazilian Individual Taxpayer Number) match its base number. This function does not verify the existence of the CPF; it only validates the format of the string. Args: - cpf (str): The CPF to be validated, a 11-digit string Returns: - bool: True if the checksum digits match the base number, False otherwise. Example: ```python >>> from brutils import is_valid_cpf >>> is_valid_cpf("82178537464") True >>> is_valid_cpf('00011122233') False ``` ### format_cpf Format a CPF (Brazilian Individual Taxpayer Number) for display with visual aid symbols. This function takes a numbers-only CPF string as input and adds standard formatting visual aid symbols for display. Args: - cpf (str): A numbers-only CPF string. Returns: - str: A formatted CPF string with standard visual aid symbols or None if the input is invalid. Example: ```python >>> from brutils import format_cpf >>> format_cpf('82178537464') '821.785.374-64' >>> format_cpf("55550207753") '555.502.077-53' ``` ### remove_symbols_cpf Removes specific symbols from a CPF (Brazilian Individual Taxpayer Number) string. This function takes a CPF string as input and removes all occurrences of the '.', '-' characters from it. Args: - cpf (str): The CPF string containing symbols to be removed. Returns: - str: A new string with the specified symbols removed. Example: ```python >>> from brutils import remove_symbols_cpf >>> remove_symbols_cpf('000.111.222-33') '00011122233' ``` ### generate_cpf Generate a random valid CPF (Brazilian Individual Taxpayer Number) digit string. Returns: - str: A random valid CPF string. Example: ```python >>> from brutils import generate_cpf >>> generate_cpf() '17433964657' >>> generate_cpf() "10895948109" ``` ## CNPJ ### is_valid_cnpj Returns whether or not the verifying checksum digits of the given CNPJ (Brazilian Company Registration Number) match its base number. Input should be a 14-character string, allowing digits and uppercase letters in the first 12 positions and digits in the last 2 positions. This function does not verify the existence of the CNPJ; it only validates the format of the string. Args: - cnpj (str): The CNPJ to be validated. Returns: - bool: True if the checksum digits match the base number, False otherwise. Example: ```python >>> from brutils import is_valid_cnpj >>> is_valid_cnpj('03560714000142') True >>> is_valid_cnpj('00111222000133') False ``` ### format_cnpj Formats a CNPJ (Brazilian Company Registration Number) string for visual display. This function takes a CNPJ string as input, validates its format, and formats it with standard visual aid symbols for display purposes. Args: - cnpj (str): The CNPJ string to be formatted for display. Returns: - str: The formatted CNPJ with visual aid symbols if it's valid, None if it's not valid. Example: ```python >>> from brutils import format_cnpj >>> format_cnpj("03560714000142") '03.560.714/0001-42' >>> format_cnpj("98765432100100") None ``` ### remove_symbols_cnpj Removes specific symbols from a CNPJ (Brazilian Company Registration Number) string. This function takes a CNPJ string as input and removes all occurrences of the '.', '/' and '-' characters from it. Args: - cnpj (str): The CNPJ string containing symbols to be removed. Returns: - str: A new string with the specified symbols removed. Example: ```python >>> from brutils import remove_symbols_cnpj >>> remove_symbols_cnpj('00.111.222/0001-00') '00111222000100' ``` ### generate_cnpj Generates a random valid CNPJ (Brazilian Company Registration Number) string. An optional branch number parameter can be given; it defaults to 1. Use `alphanumeric=True` to generate a CNPJ whose first 12 positions may contain digits and uppercase letters. Args: - branch (int | str): An optional branch number to be included in the CNPJ. Alphanumeric branch values are accepted only with `alphanumeric=True`. - alphanumeric (bool): Whether the generated CNPJ should be alphanumeric. Returns: - str: A randomly generated valid CNPJ string. Example: ```python >>> from brutils import generate_cnpj >>> generate_cnpj() '34665388000161' >>> generate_cnpj(1234) "01745284123455" >>> generate_cnpj(alphanumeric=True) "9359QAG9000184" >>> generate_cnpj(branch="AB12", alphanumeric=True) "BR2026UTAB1290" >>> generate_cnpj(branch="CD34", alphanumeric=True) # Invalid CNPJ for validation examples: "NX9K79E2CD3400" ``` ## CEP ### is_valid_cep Checks if a Brazilian CEP (Postal Code) is valid. To be considered valid, the input must be a string containing exactly 8 digits. This function does not verify if the CEP is a real postal code; it only validates the format of the string. Args: - cep (str): The string containing the CEP to be checked. Returns: - bool: True if the CEP is valid (8 digits), False otherwise. Example: ```python >>> from brutils import is_valid_cep >>> is_valid_cep('01310200') True >>> is_valid_cep("12345") False >>> is_valid_cep("abcdefgh") False ``` ### format_cep This function takes a CEP (Postal Code) as input and, if it is a valid 8-digit CEP, formats it into the standard "12345-678" format. Args: - cep (str): The input CEP (Postal Code) to be formatted. Returns: - str: The formatted CEP in the "12345-678" format if it's valid, None if it's not valid. Example: ```python >>> from brutils import format_cep >>> format_cep('01310200') '01310-200' >>> format_cep("12345678") "12345-678" >>> format_cep("12345") None ``` ### remove_symbols_cep This function takes a CEP (Postal Code) as input and removes all occurrences of the '.' and '-' characters from it. Args: - cep (str): The input CEP (Postal Code) containing symbols to be removed. Returns: - str: A new string with the specified symbols removed. Example: ```python >>> from brutils import remove_symbols_cep >>> remove_symbols_cep('01310-200') '01310200' >>> remove_symbols_cep("123-45.678.9") "123456789" >>> remove_symbols_cep("abc.xyz") "abcxyz" ``` ### generate_cep Generates a random 8-digit CEP (Postal Code) number as a string. Returns: - str: A randomly generated 8-digit number. Example: ```python >>> from brutils import generate_cep >>> generate_cep() '77520503' ``` ### get_address_from_cep Fetches address information from a given CEP (Postal Code) using the ViaCEP API. Args: - cep (str): The CEP (Postal Code) to be used in the search. - raise_exceptions (bool, optional): Whether to raise exceptions when the CEP is invalid or not found. Defaults to False. Returns: - Address | None: An Address object (TypedDict) containing the address information if the CEP is found, None otherwise. Example: ```python >>> from brutils import get_address_from_cep >>> get_address_from_cep("12345678") { "cep": "12345-678", "logradouro": "Rua Example", "complemento": "", "bairro": "Example", "localidade": "Example", "uf": "EX", "ibge": "1234567", "gia": "1234", "ddd": "12", "siafi": "1234" } ``` ### get_cep_information_from_address Fetches CEP (Postal Code) options from a given address using the ViaCEP API. Args: - federal_unit (str): The two-letter abbreviation of the Brazilian state. - city (str): The name of the city. - street (str): The name (or substring) of the street. - raise_exceptions (bool, optional): Whether to raise exceptions when the address is invalid or not found. Defaults to False. Returns: - list[Address] | None: A list of Address objects (TypedDict) containing the address information if the address is found, None otherwise. Example: ```python >>> from brutils import get_cep_information_from_address >>> get_cep_information_from_address("EX", "Example", "Rua Example") [ { "cep": "12345-678", "logradouro": "Rua Example", "complemento": "", "bairro": "Example", "localidade": "Example", "uf": "EX", "ibge": "1234567", "gia": "1234", "ddd": "12", "siafi": "1234" } ] ``` ## Date ### convert_date_to_text Convert a brazilian date (dd/mm/yyyy) format in their portuguese textual representation. Args: - date (str): A date in a string format dd/mm/yyyy. Return: - (str) | None: A portuguese textual representation of the date or None case a date is invalid. Example: ````python >>> from brutils import convert_date_to_text >>> convert_date_to_text("25/12/2000") "Vinte e cinco de dezembro de dois mil" >>> convert_date_to_text("31/02/2000") None >>> convert_date_to_text("29/02/2024") "Vinte e nove de fevereiro de dois mil e vinte e quatro" >>> convert_date_to_text("1/08/2024") "Primeiro de agosto de dois mil e vinte e quatro" ```` ## Phone ### is_valid_phone Return whether a Brazilian phone number is valid. It does not verify if the number actually exists. ``` is_valid_phone(phone_number, type) ``` Args: - phone_number: - the phone number to be validated - digits only, no symbols - without the country code - should include the area code (DDD) with two digits - example: '+55 48 9999 9999' should be used as '4899999999' - mandatory - type: - 'mobile' to validate only mobile numbers - 'landline' to validate only landline phone numbers - if not specified, it validates for either. - optional Returns: - bool: True if the phone number is valid. False otherwise. Example: ```python >>> from brutils import is_valid_phone >>> is_valid_phone('11994029275') True >>> is_valid_mobile_phone('11994029275', 'mobile') True >>> is_valid_landline_phone('1938814933', 'landline') True ``` ### format_phone Format a phone number for visual display. This function takes a string representing a phone number containing only numbers as input and adds standard formatting symbols for display. Args: - phone (str): A string representing a phone number. Returns: - str: The formatted phone number for display or None if it is not valid. Example: ```python >>> from brutils import format_phone >>> format_phone("11994029275") '(11)99402-9275' >>> format_phone("1635014415") '(16)3501-4415' >>> format_phone("333333") None ``` ### remove_symbols_phone Remove symbols from the phone number. This function takes a phone number as input and removes all symbols, such as parentheses '()', dashes '-', and spaces ' '. Args: - phone (str): The input phone number containing the symbols to be removed. Returns: - str: A new string with the specified symbols removed. Example: ```python >>> from brutils import remove_symbols_phone >>> remove_symbols_phone('(21)2569-6969') '2125696969' >>> remove_symbols_phone('11 9999-8888') '1199998888' >>> remove_symbols_phone('333333') '333333' ``` ### remove_international_dialing_code Remove the international code (+55) from a string containing a Brazilian phone number, preserving other special characters. Args: - phone (str): The input phone number that may contain the international code. Returns: - str: A new string without the international code, preserving other special characters. Example: ```python >>> from brutils import remove_international_dialing_code >>> remove_international_dialing_code("5521994029275") "21994029275" >>> remove_international_dialing_code("+5521994029275") "+21994029275" >>> remove_international_dialing_code("5555994029275") "55994029275" >>> remove_international_dialing_code("21994029275") "21994029275" >>> remove_international_dialing_code("(+55)21994029275") "(+)21994029275" ``` ### generate_phone Generates a valid random phone number. Args: - type (str): It can be "landline" or "mobile". If not specified, the function generates a random number of any type. Returns: - str: A randomly generated valid phone number. Example: ```python >>> from brutils import generate_phone >>> generate_phone() "5929797740" >>> generate_phone("mobile") "1899115895" >>> generate_phone("landline") "5535317900" ``` ## Email ### is_valid_email Check if a string corresponds to a valid email address. Args: - email (str): The input string to be checked. Returns: - bool: True if email is a valid email address, False otherwise. Example: ```python from brutils import is_valid_email >>> is_valid_email("joao.ninguem@gmail.com") True >>> is_valid_email(".joao.ninguem@gmail.com") False >>> is_valid_email("joao.ninguem@gmail.") False >>> is_valid_email("joao ninguem@gmail.com") False ``` ## CNH ### is_valid_cnh Checks if the registration number of a brazilian CNH (Carteira de Habilitação Nacional or Driver's License in En.) is valid. To be considered valid, the input must be a string containing exactly 11 digits. This function does not verify if the registration number of the CNH is a real one, it only validates it's verification numbers. Argumentos: - cnh (str): A string containing the registration nunber of the CNH to be checked. Retorno: - bool: True if the CNH number is valid (11 digits), False otherwise. Exemplo: ```python >>> from brutils import is_valid_cnh >>> is_valid_cnh("123456789") False >>> is_valid_cnh("A2C45678901") False >>> is_valid_cnh("98765432100") True >>> is_valid_cnh("987654321-00") True ``` ## License Plate ### is_valid_license_plate Check if a license plate is valid. This function does not verify if the license plate is a real license plate; it only validates the format of the string. Args: - license_plate (str): A license plate string. - type (str): "old_format" or "mercosul". If not specified, checks for one or another. Returns: - bool: True if the license plate is valid, False otherwise. Example: ```python >>> from brutils import is_valid_license_plate >>> is_valid_license_plate('ABC1234') True >>> is_valid_license_plate('def5678', type="old_format") True >>> is_valid_license_plate('ABC4E67') True >>> is_valid_license_plate('ABC4E67', type="mercosul") True >>> is_valid_license_plate('GHI-4567') False ``` ### format_license_plate Formats a license plate into the correct pattern. This function receives a license plate in any pattern (LLLNNNN or LLLNLNN) and returns a formatted version. Args: - license_plate (str): A license plate string. Returns: - str: The formatted license plate string or 'None' if the input is invalid. Example: ```python >>> from brutils import format_license_plate >>> format_license_plate("ABC1234") "ABC-1234" # old format (contains a dash) >>> format_license_plate("abc1234") "ABC-1234" # old format (contains a dash) >>> format_license_plate("ABC1D23") "ABC1D23" # mercosul format >>> format_license_plate("abc1d23") "ABC1D23" # mercosul format >>> format_license_plate("ABCD123") None ``` ### remove_symbols_license_plate Removes the dash (-) symbol from a license plate string. Args: - license_plate_number (str): A license plate number containing symbols to be removed. Returns: - str: The license plate number with the specified symbols removed. Example: ```python from brutils import remove_symbols_license_plate >>> remove_symbols_license_plate("ABC-123") "ABC123" >>> remove_symbols_license_plate("abc123") "abc123" >>> remove_symbols_license_plate("ABCD123") "ABCD123" >>> remove_symbols_license_plate("@abc#-#123@") "@abc##123@" ``` ### generate_license_plate Generate a valid license plate in the given format. In case no format is provided, it will return a license plate in the Mercosul format. Args: - format (str): The desired format for the license plate. 'LLLNNNN' for the old pattern or 'LLLNLNN' for the Mercosul one. Default is 'LLLNLNN' Returns: - str: A randomly generated license plate number or 'None' if the format is invalid. Example: ```python from brutils import generate_license_plate >>> generate_license_plate() "ABC1D23" >>> generate_license_plate(format="LLLNLNN") "ABC4D56" >>> generate_license_plate(format="LLLNNNN") "ABC123" >>> generate_license_plate(format="invalid") None ``` ### convert_license_plate_to_mercosul Converts an old pattern license plate (LLLNNNN) to a Mercosul format (LLLNLNN). Args: - license_plate (str): A string of proper length representing the old pattern license plate. Returns: - str: The converted Mercosul license plate (LLLNLNN) or 'None' if the input is invalid. Example: ```python >>> from brutils import convert_license_plate_to_mercosul >>> convert_license_plate_to_mercosul("ABC123") "ABC1C34" >>> convert_license_plate_to_mercosul("abc123") "ABC1C34" >>> convert_license_plate_to_mercosul("ABC1D23") None ``` ### get_format_license_plate Return the format of a license plate. 'LLLNNNN' for the old pattern and 'LLLNLNN' for the Mercosul one. Args: - license_plate (str): A license plate string without symbols. Returns: - str: The format of the license plate (LLLNNNN, LLLNLNN) or 'None' if the format is invalid. Example: ```python from brutils import get_format_license_plate >>> get_format_license_plate("ABC123") "LLLNNNN" >>> get_format_license_plate("abc123") "LLLNNNN" >>> get_format_license_plate("ABC1D23") "LLLNLNN" >>> get_format_license_plate("abc1d23") "LLLNLNN" >>> get_format_license_plate("ABCD123") None ``` ## PIS ### is_valid_pis Verifies if the PIS/PASEP number is valid. Only numbers, formatted as a string. It does not check if the PIS/PASEP actually exists. References: - . Args: - pis (str): PIS number as a string with the proper length. Returns: - bool: True if the PIS is valid, False otherwise. Example: ```python from brutils import is_valid_pis >>> is_valid_pis("82178537464") False >>> is_valid_pis("12082043519") True ``` ### format_pis Formats a valid PIS (Programa de Integração Social) string with symbols and adds standard formatting symbols for display. Args: - pis (str): A valid string of PIS containing only numbers. Returns: - str: A formatted PIS string with standard visual aid symbols or None if the input is invalid. Example: ```python from brutils import format_pis >>> format_pis("17033259504") '170.33259.50-4' >>> format_pis("12013128292") '120.13128.29-2' ``` ### remove_symbols_pis This function takes a string of PIS (Programa de Integração Social) with formatting symbols and returns a clean version without certain symbols. It intentionally removes only the symbols "-" and ".", leaving other symbols untouched. Args: - pis (str): A string of PIS that may contain formatting symbols. Returns: - str: A clean string of PIS without formatting symbols. Example: ```python from brutils import remove_symbols_pis >>> remove_symbols_pis('170.33259.50-4') '17033259504' >>> remove_symbols_pis("123.456.789-09") '12345678909' >>> remove_symbols_pis('/._') '/_' ``` ### generate_pis Generates a string of digits containing a random valid Brazilian PIS number. Returns: - str: A randomly generated valid PIS number as a string. Example: ```python from brutils import generate_pis >>> generate_pis() '61352489741' >>> generate_pis() '73453349671' ``` ## Legal Process ## is_valid_legal_process Check if a legal process ID is valid. This function does not verify if the legal process ID is a real legal process ID; it only validates the format of the string. Args: - legal_process_id (str): A digit-only string representing the legal process ID. Returns: - bool: True if the legal process ID is valid, False otherwise. Example: ```python >>> from brutils import is_valid_legal_process >>> is_valid_legal_process('10188748220234018200') True >>> is_valid_legal_process('45532346920234025107') True >>> is_valid_legal_process('00000000000000000000') False >>> is_valid_legal_process('455323423QQWEQWSsasd&*(()') False >>> ``` ### format_legal_process Format a legal process ID into a standard format. Args: - legal_process_id (str): A 20-digits string representing the legal process ID. Returns: - str: The formatted legal process ID or None if the input is invalid. Example: ```python >>> from brutils import format_legal_process >>> format_legal_process('23141945820055070079') '2314194-58.2005.5.07.0079' >>> format_legal_process('00000000000000000000') '0000000-00.0000.0.00.0000' >>> format_legal_process("123") None ``` ### remove_symbols_legal_process Removes specific symbols from a given legal process. This function takes a legal process as input and removes all occurrences of the '.' and '-' characters from it. Args: - legal_process (str): A legal process containing symbols to be removed. Returns: - str: The legal process string with the specified symbols removed. Example: ```python from brutils import remove_symbols_legal_process >>> remove_symbols_legal_process("6439067-89.2023.4.04.5902") "64390678920234045902" >>> remove_symbols_legal_process("4976023-82.2012.7.00.2263") "49760238220127002263" >>> remove_symbols_legal_process("4976023-82.2012.7.00.2263*!*&#") "49760238220127002263*!*&#" ``` ### generate_legal_process Generate a random legal process ID number. Args: - year (int): The year for the legal process ID (default is the current year). The year should not be in the past - orgao (int): The organization code (1-9) for the legal process ID (default is random). Returns: - str: A randomly generated legal process ID. None if one of the arguments is invalid. Example: ```python >>> from brutils import generate_legal_process >>> generate_legal_process() "45676401020238170592" >>> generate_legal_process(year=2025) "32110268020258121130" >>> generate_legal_process(orgao=5) "37573041520235090313" >>> generate_legal_process(year=2024, orgao=4) "33158248820244017105" ``` ## Voter ID ### is_valid_voter_id Check if a Brazilian voter id number is valid. It does not verify if the voter id actually exists. References: - , - Args: - voter_id(str): string representing the voter id to be verified. Returns: - bool: True if the voter id is valid. False otherwise. Example: ```python >>> from brutils import is_valid_voter_id >>> is_valid_voter_id('123456789011') False >>> is_valid_voter_id('427503840213') True ``` ### format_voter_id Formats a voter ID number for visual display. This function takes a voter ID string containing only numbers as input and adds the standard formatting spaces for display. Arguments: * voter_id (str): A voter ID string containing only numbers. Returns: * str: The formatted voter ID with spaces if valid. Returns None if not valid. Example: ```python >>> from brutils import format_voter_id >>> format_voter_id("246593980493") '2465 9398 04 93' >>> format_voter_id("202715292895") '2027 1529 28 95' >>> format_voter_id("739035552205") >>> ``` ### generate_voter_id Generate a valid random Voter ID string of digits from an informed Brazilian federation union. Args: * federative_union(str): federative union for the voter id that will be generated. The default value "ZZ" is used for voter IDs issued to foreigners. Returns: * str: A randomly generated valid voter ID. Example: ```python >>> from brutils import generate_voter_id >>> generate_voter_id() '183475722801' >>> generate_voter_id(federative_union ="MG") '950125640248' ``` ## IBGE ### convert_code_to_uf Converts a given IBGE code (2-digit string) to its corresponding UF (state abbreviation). Args: * code (str): The 2-digit IBGE code to be converted. Retorna: * str or None: The UF code corresponding to the IBGE code, or None if the IBGE code is invalid. Exemplo: ```python >>> from brutils.ibge.uf import convert_code_to_uf >>> convert_code_to_uf("12") 'AC' >>> convert_code_to_uf("33") 'RJ' >>> convert_code_to_uf("99") >>> ``` ### get_code_by_municipality_name Returns the IBGE code for a given municipality name and uf code. This function takes a string representing a municipality's name and uf's code and returns the corresponding IBGE code (string). The function will handle names by ignoring differences in case, accents, and treating the character ç as c and ignoring case differences for the uf code. Args: * municipality_name (str): The name of the municipality. * uf (str): The uf code of the state. Returns: * str: The IBGE code of the municipality. Returns None if the name is not valid or does not exist. Example: ```python >>> from brutils import get_code_by_municipality_name >>> get_code_by_municipality_name("São Paulo", "SP") "3550308" >>> get_code_by_municipality_name("goiania", "go") "5208707" >>> get_code_by_municipality_name("Conceição do Coité", "BA") "2908408" >>> get_code_by_municipality_name("conceicao do Coite", "Ba") "2908408" >>> get_code_by_municipality_name("Municipio Inexistente", "") None >>> get_code_by_municipality_name("Municipio Inexistente", "RS") None ``` ### get_municipality_by_code Returns the municipality name and UF for a given IBGE code. Args: * code (str): The IBGE code of the municipality. Returns: * tuple: Returns a tuple formatted as ("Município", "UF"). * None: Returns None if the code is not valid. Example: ```python >>> from brutils import get_municipality_by_code >>> get_municipality_by_code(3550308) ("São Paulo", "SP") ``` ### convert_uf_to_name Converts a Brazilian UF code (e.g., 'SP') to the state's full name ('São Paulo'). The lookup is case-insensitive and ignores surrounding whitespace. Args: * uf (str): Two-letter UF code. Returns: * str | None: The full state name if found, or ``None`` if the code is invalid. Example: ```python >>> from brutils.ibge.uf import convert_uf_to_name >>> convert_uf_to_name('SP') 'São Paulo' >>> convert_uf_to_name('rj') 'Rio de Janeiro' ``` ### convert_name_to_uf Converts a Brazilian state name to its UF code. This function takes the full name of a Brazilian state and returns its corresponding two-letter UF code. The comparison is case-insensitive and ignores accents. Args: * state_name (str): The full name of the state (e.g., 'São Paulo', 'sao paulo'). Returns: * str | None: The UF code if found, or None if the state name is invalid. Example: ```python >>> from brutils.ibge.uf import convert_name_to_uf >>> convert_name_to_uf('São Paulo') 'SP' >>> convert_name_to_uf('sao paulo') 'SP' >>> convert_name_to_uf('Rio de Janeiro') 'RJ' >>> convert_name_to_uf('rio de janeiro') 'RJ' >>> convert_name_to_uf('Invalid State') >>> ``` ## Holidays ### is_holiday Checks if a given date is a national or state holiday in Brazil. This function takes a `datetime` object as the date and an optional state abbreviation (UF) to specify state holidays. It returns `True` if the date is a holiday, `False` if it’s not, or `None` if the date or UF are invalid. Note that the function does not cover municipal holidays. Args: - `date (datetime)`: The date to be checked. - `uf (str, optional)`: The state abbreviation (UF) to check for state holidays. If not provided, only national holidays are considered. Returns: - `bool | None`: `True` if the date is a holiday, `False` if it’s not, or `None` if the date or UF are invalid. Example: ```python >>> from datetime import datetime >>> from brutils import is_holiday >>> is_holiday(datetime(2024, 1, 1)) True >>> is_holiday(datetime(2024, 1, 2)) False >>> is_holiday(datetime(2024, 3, 2), uf="SP") False >>> is_holiday(datetime(2024, 12, 25), uf="RJ") True ``` ## Monetary ### format_currency Formats a number following the Brazilian monetary standard. The number will be formatted by adding the R$ symbol as a prefix, a comma as a decimal separator, and a period as a thousands grouper. Args: * float or Decimal: A number with or without decimal places. Returns: * str or None: The number formatted following the Brazilian standard. Example: ```python >>> from brutils.currency import format_currency >>> format_currency(1259.03) 'R$ 1.259,03' >>> format_currency(0) 'R$ 0,00' >>> format_currency("not a number") None ``` ### convert_real_to_text Converts a given monetary value in Brazilian Reais to its textual representation. It takes a decimal number representing a monetary value in Reais and converts it to a string with the amount written out in Brazilian Portuguese. It handles both the integer part (Reais) and the fractional part (centavos), respecting the correct grammar for singular and plural cases, as well as special cases like zero and negative values. Args: - amount (decimal): The monetary value to be converted into text. - The integer part represents Reais. - The decimal part represents centavos. - 2 decimal places Returns: - str: A string with the monetary value written out in Brazilian Portuguese. - Returns "Zero reais" for a value of 0.00. - Returns None if the amount is invalid or absolutely greater than 1 quadrillion. - Handles negative values, adding "Menos" at the beginning of the string. Limitations: - This function may lose precision by ±1 cent for cases where the absolute value is beyond trillions due to floating-point rounding errors. Example: ```python >>> from brutils.currency import convert_real_to_text >>> convert_real_to_text(1523.45) 'Mil, quinhentos e vinte e três reais e quarenta e cinco centavos' >>> convert_real_to_text(0.01) 'Um centavo' >>> convert_real_to_text(0.00) 'Zero reais' >>> convert_real_to_text(-50.25) 'Menos cinquenta reais e vinte e cinco centavos' >>> convert_real_to_text("invalid") None ``` ## Legal Nature ### is_valid_legal_nature Validates if the given code exists in the official table. Accepts `NNNN` or `NNN-N`. The value is **normalized** before checking: removes spaces, keeps only digits, and accepts a hyphen between the 3rd and 4th digits. **Arguments** - `code (str)`: 4-digit code (e.g.: `"2062"` or `"206-2"`) **Returns** - `bool`: `True` if it exists in the table, `False` otherwise. **Example** ```python >>> from brutils import legal_nature >>> legal_nature.is_valid("2062") True >>> legal_nature.is_valid("206-2") True >>> legal_nature.is_valid("9999") False ``` ### get_legal_nature_description Returns the **official description** for the Legal Nature code. Accepts `NNNN` or `NNN-N`. Applies the same normalization as `is_valid`. **Arguments** * `code (str)`: 4-digit code **Returns** * `str | None`: The corresponding description or `None` if the code is invalid or non-existent. **Example** ```python >>> from brutils import legal_nature >>> legal_nature.get_description("2062") 'Sociedade Empresária Limitada' >>> legal_nature.get_description("101-5") 'Órgão Público do Poder Executivo Federal' >>> legal_nature.get_description("0000") None ``` ### list_all_legal_nature Returns a copy of the complete dictionary `{code: description}`. **Returns** * `dict[str, str]`: Mapping of all codes to their descriptions. **Example** ```python >>> from brutils import legal_nature >>> data = legal_nature.list_all() >>> len(data) > 0 True >>> data["2062"] 'Sociedade Empresária Limitada' ``` ## RENAVAM ### is_valid_renavam Validates whether the verification digit of the given RENAVAM matches its first 10 digits. This function does not check if the vehicle exists; it only validates the string format and the verification digit. Arguments: - renavam (str): The RENAVAM to be validated, a string of 11 digits. Returns: - bool: True if RENAVAM is valid, False otherwise. Example: ```python >>> from brutils import is_valid_renavam >>> is_valid_renavam("86769597308") True >>> is_valid_renavam("12345678901") False ``` ## Passport ### is_valid_passport Checks if a Brazilian passport number is valid. To be considered valid, the input must be a string containing exactly two alphabetical characters followed by exactly six numerical digits. This function does not verify is the input is a real passport number, as there are no checksums for the Brazilian passport. Args: - passport (str): The string containing the passport number to be checked. Returns: - bool: True if the passport number is valid (2 letters followed by 6 digits). False otherwise. Example: ```python >>> from brutils import is_valid_passport >>> is_valid_passport("Ab123456") True >>> is_valid_passport("12345678") False >>> is_valid_passport("DC-221345") False ``` ### format_passport Formats a Brazilian passport number for display. This function takes a string representing a valid passport number and returns it formatted (uppercase, without symbols). Args: - passport (str | None): A Brazilian passport number (lower or uppercase, possibly including symbols) Returns: - str: The formatted passport number (uppercase, without symbols) or None if the input is invalid Example: ```python >>> format_passport("Ab123456") AB123456 >>> format_passport("Ab-123456") AB123456 >>> format_passport("111111") None ``` ### remove_symbols_passport Removes symbols ('-', '.', and whitespaces) from a passport number. This function takes a passport number string as input and removes all occurrences of the '.', '-', and whitespace characters from it. Args: - passport (str): The string containing a passport number Returns: - str: The passport numbers with dashes (-), dots (.), and whitespaces ( ) removed. Example: ```python >>> remove_symbols_passport("Ab123456") Ab123456 >>> remove_symbols_passport("Ab-123456") Ab123456 >>> remove_symbols_passport("Ab -. 123456") Ab123456 ``` ### generate_passport Generate a random valid Brazilian passport number string. This function generates a random Brazilian passport number string. Returns: - str: A random valid passport number string. Example: ```python >>> generate() "RY393097" >>> generate() "ZS840088" ``` # Feature Request and Bug Report If you want to suggest new features or report bugs, simply create a new [issue][github-issues], and we will respond to you there! (To learn more about GitHub issues, check out the [official GitHub documentation][github-issues-doc]). # Questions? Ideas? Questions on how to use the library? New ideas for the project? Want to share something with us? Feel free to start a thread in our [Discussions][github-discussions], and we'll interact with you there! (To learn more about GitHub discussions, refer to the [official GitHub documentation][github-discussions-doc]). # Code Contribution Your collaboration is always very welcome! We've prepared the [CONTRIBUTING.md][contributing] file to assist you in getting started. There, you'll find all the information you need to contribute to the project. Please, don't hesitate to ask us using [GitHub Discussions][github-discussions] if you encounter any difficulties or have any questions. Every bit of help counts! Let's build it together 🚀🚀 [contributing]: CONTRIBUTING_EN.md [github-discussions-doc]: https://docs.github.com/en/discussions [github-discussions]: https://github.com/brazilian-utils/brutils-python/discussions [github-issues-doc]: https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue [github-issues]: https://github.com/brazilian-utils/brutils-python/issues ## ❤️ Contributors

_Made with [contrib.rocks](https://contrib.rocks)._ brutils-2.5.0/CHANGELOG.md0000664000000000000000000001764315221040537013525 0ustar rootroot# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [2.5.0] - 2026-06-30 ### Added - Utilitário `generate_cnpj` com suporte a CNPJ alfanumérico [#741](https://github.com/brazilian-utils/python/pull/741) ## [2.4.0] - 2026-04-20 ### Added - Utilitário `convert_name_to_uf` [#606](https://github.com/brazilian-utils/python/pull/606) - Utilitário `is_valid_legal_nature` [#653](https://github.com/brazilian-utils/python/pull/653) - Utilitário `get_legal_nature_description` [#653](https://github.com/brazilian-utils/python/pull/653) - Utilitário `list_all_legal_nature` [#653](https://github.com/brazilian-utils/python/pull/653) - Utilitário `is_valid_cnh` [#651](https://github.com/brazilian-utils/brutils-python/pull/651) - Utilitário `is_valid_renavam` [#652](https://github.com/brazilian-utils/brutils-python/pull/652) - Utilitário `is_valid_passport` [#699](https://github.com/brazilian-utils/python/pull/699) - Utilitário `generate_passport` [#699](https://github.com/brazilian-utils/python/pull/699) - Utilitário `format_passport` [#699](https://github.com/brazilian-utils/python/pull/699) - Utilitário `remove_symbols_passport` [#699](https://github.com/brazilian-utils/python/pull/699) ### Fixed - Utilitário `brutils/cep.py` [#637](https://github.com/brazilian-utils/python/pull/637) - Utilitário `legal_process` [#634](https://github.com/brazilian-utils/python/pull/634) - Utilitário `voter_id` [#638](https://github.com/brazilian-utils/python/pull/638) ## [2.3.0] - 2025-10-07 ### Added - Utilitário `convert_code_to_uf` [#410](https://github.com/brazilian-utils/brutils-python/pull/410) - Utilitário `is_holiday` [#446](https://github.com/brazilian-utils/brutils-python/pull/446) - Utilitário `convert_date_to_text`[#415](https://github.com/brazilian-utils/brutils-python/pull/415) - Utilitário `get_municipality_by_code` [412](https://github.com/brazilian-utils/brutils-python/pull/412) - Utilitário `get_code_by_municipality_name` [#411](https://github.com/brazilian-utils/brutils-python/pull/411) - Utilitário `format_currency` [#434](https://github.com/brazilian-utils/brutils-python/pull/434) - Utilitário `convert_real_to_text` [#525](https://github.com/brazilian-utils/brutils-python/pull/525) - Utilitário `convert_uf_to_name` [#554](https://github.com/brazilian-utils/brutils-python/pull/554) ### Deprecated - **BREAKING CHANGES** Suporte ao Python 3.8 [#236](https://github.com/brazilian-utils/brutils-python/pull/561) - **BREAKING CHANGES** Suporte ao Python 3.9 [#236](https://github.com/brazilian-utils/brutils-python/pull/561) ## [2.2.0] - 2024-09-12 ### Added - Utilitário `get_address_from_cep` [#358](https://github.com/brazilian-utils/brutils-python/pull/358) - Utilitário `get_cep_information_from_address` [#358](https://github.com/brazilian-utils/brutils-python/pull/358) - Utilitário `format_voter_id` [#363](https://github.com/brazilian-utils/brutils-python/pull/363) - Utilitário `generate_voter_id` [#220](https://github.com/brazilian-utils/brutils-python/pull/220) ## [2.1.1] - 2024-01-06 ### Fixed - `generate_legal_process` [#325](https://github.com/brazilian-utils/brutils-python/pull/325) - `is_valid_legal_process` [#325](https://github.com/brazilian-utils/brutils-python/pull/325) - Import do utilitário `convert_license_plate_to_mercosul` [#324](https://github.com/brazilian-utils/brutils-python/pull/324) - Import do utilitário `generate_license_plate` [#324](https://github.com/brazilian-utils/brutils-python/pull/324) - Import do utilitário `get_format_license_plate` [#324](https://github.com/brazilian-utils/brutils-python/pull/324) ## [2.1.0] - 2024-01-05 ### Added - Suporte ao Python 3.12 [#245](https://github.com/brazilian-utils/brutils-python/pull/245) - Utilitário `convert_license_plate_to_mercosul` [#226](https://github.com/brazilian-utils/brutils-python/pull/226) - Utilitário `format_license_plate` [#230](https://github.com/brazilian-utils/brutils-python/pull/230) - Utilitário `format_phone` [#231](https://github.com/brazilian-utils/brutils-python/pull/231) - Utilitário `format_pis` [#224](https://github.com/brazilian-utils/brutils-python/pull/224) - Utilitário `format_legal_process` [#210](https://github.com/brazilian-utils/brutils-python/pull/210) - Utilitário `generate_license_plate` [#241](https://github.com/brazilian-utils/brutils-python/pull/241) - Utilitário `generate_phone` [#295](https://github.com/brazilian-utils/brutils-python/pull/295) - Utilitário `generate_pis` [#218](https://github.com/brazilian-utils/brutils-python/pull/218) - Utilitário `generate_legal_process` [#208](https://github.com/brazilian-utils/brutils-python/pull/208) - Utilitário `get_format_license_plate` [#243](https://github.com/brazilian-utils/brutils-python/pull/243) - Utilitário `is_valid_email` [#213](https://github.com/brazilian-utils/brutils-python/pull/213) - Utilitário `is_valid_license_plate` [#237](https://github.com/brazilian-utils/brutils-python/pull/237) - Utilitário `is_valid_phone` [#147](https://github.com/brazilian-utils/brutils-python/pull/147) - Utilitário `is_valid_pis` [#216](https://github.com/brazilian-utils/brutils-python/pull/216) - Utilitário `is_valid_legal_process` [#207](https://github.com/brazilian-utils/brutils-python/pull/207) - Utilitário `is_valid_voter_id` [#235](https://github.com/brazilian-utils/brutils-python/pull/235) - Utilitário `remove_international_dialing_code` [192](https://github.com/brazilian-utils/brutils-python/pull/192) - Utilitário `remove_symbols_license_plate` [#182](https://github.com/brazilian-utils/brutils-python/pull/182) - Utilitário `remove_symbols_phone` [#188](https://github.com/brazilian-utils/brutils-python/pull/188) - Utilitário `remove_symbols_pis` [#236](https://github.com/brazilian-utils/brutils-python/pull/236) - Utilitário `remove_symbols_legal_process` [#209](https://github.com/brazilian-utils/brutils-python/pull/209) ### Removed - **BREAKING CHANGE** Suporte ao Python 3.7 [#236](https://github.com/brazilian-utils/brutils-python/pull/236) ## [2.0.0] - 2023-07-23 ### Added - Utilitário `is_valid_cep` [123](https://github.com/brazilian-utils/brutils-python/pull/123) - Utilitário `format_cep` [125](https://github.com/brazilian-utils/brutils-python/pull/125) - Utilitário `remove_symbols_cep` [126](https://github.com/brazilian-utils/brutils-python/pull/126) - Utilitário `generate_cep` [124](https://github.com/brazilian-utils/brutils-python/pull/124) - Utilitário `is_valid_cpf` [34](https://github.com/brazilian-utils/brutils-python/pull/34) - Utilitário `format_cpf` [54](https://github.com/brazilian-utils/brutils-python/pull/54) - Utilitário `remove_symbols_cpf` [57](https://github.com/brazilian-utils/brutils-python/pull/57) - Utilitário `is_valid_cnpj` [36](https://github.com/brazilian-utils/brutils-python/pull/36) - Utilitário `format_cnpj` [52](https://github.com/brazilian-utils/brutils-python/pull/52) - Utilitário `remove_symbols_cnpj` [58](https://github.com/brazilian-utils/brutils-python/pull/58) ### Deprecated - Utilitário `cpf.sieve` - Utilitário `cpf.display` - Utilitário `cpf.validate` - Utilitário `cnpj.sieve` - Utilitário `cnpj.display` - Utilitário `cnpj.validate` [Unreleased]: https://github.com/brazilian-utils/brutils-python/compare/v2.5.0...HEAD [2.5.0]: https://github.com/brazilian-utils/brutils-python/releases/tag/v2.5.0 [2.4.0]: https://github.com/brazilian-utils/brutils-python/releases/tag/v2.4.0 [2.3.0]: https://github.com/brazilian-utils/brutils-python/releases/tag/v2.3.0 [2.2.0]: https://github.com/brazilian-utils/brutils-python/releases/tag/v2.2.0 [2.1.1]: https://github.com/brazilian-utils/brutils-python/releases/tag/v2.1.1 [2.1.0]: https://github.com/brazilian-utils/brutils-python/releases/tag/v2.1.0 [2.0.0]: https://github.com/brazilian-utils/brutils-python/releases/tag/v2.0.0 brutils-2.5.0/Makefile0000664000000000000000000000127715221040537013350 0ustar rootrootinstall: @git config --local core.hooksPath .githooks/ # This must be indented like this, otherwise it will not work on Windows # see: https://stackoverflow.com/questions/77974076/how-do-i-fix-this-error-when-checking-os-in-makefile ifneq ($(OS),Windows_NT) @chmod -R +x .githooks endif @poetry install shell: @poetry shell run-python: @poetry run python format: @poetry run ruff format . @poetry run ruff check --fix . check: @poetry run ruff format --check . @poetry run ruff check . test: ifeq ($(OS),Windows_NT) @set PYTHONDONTWRITEBYTECODE=1 && poetry run python -m unittest discover tests/ -v else @PYTHONDONTWRITEBYTECODE=1 poetry run python3 -m unittest discover tests/ -v endifbrutils-2.5.0/old_versions_documentation/0000775000000000000000000000000015221040537017340 5ustar rootrootbrutils-2.5.0/old_versions_documentation/v1.0.1/0000775000000000000000000000000015221040537020163 5ustar rootrootbrutils-2.5.0/old_versions_documentation/v1.0.1/PORTUGUESE_VERSION.md0000664000000000000000000000315015221040537023413 0ustar rootroot# brutils ### [Looking for the english version?](ENGLISH_VERSION.md) _Compatível com Python 2.7 e 3.x_ `brutils` é uma biblioteca para tratar de validações de documentos brasileiros, e que eventualmente pode evoluir para tratar de outras coisas dentro do escopo de validações relacionadas a burocracias brasileiras. Sua principal funcionalidade é a validação de CPFs e CNPJs, mas sugestões sobre outras coisas a se validar (preferencialmente de maneira determinística) são bem vindas. ## Instalação ``` pip install brutils ``` ## Utilização ### Importando a Biblioteca: ``` >>> from brutils import cpf, cnpj ``` ### Como faço para validar um CPF ou CNPJ? ``` # somente numeros, em formato string >>> cpf.validate('00011122233') False >>> cnpj.validate('00111222000133') False ``` ### E se a minha string estiver formatada com simbolos? ``` >>> cpf.sieve('000.111.222-33') '00011122233' >>> cnpj.sieve('00.111.222/0001-00') '00111222000100' # A função `sieve` limpa apenas os simbolos de formatação de CPF ou CNPJ, e de # whitespace nas pontas. Ela não remove outros caractéres propositalmente, pois # estes seriam indicativos de uma possível corrupção no dado ou de uma falta de # filtros de input. ``` ### E se eu quiser formatar uma string numérica? ``` >>> cpf.display('00011122233') '000.111.222-33' >>> cnpj.display('00111222000100') '00.111.222/0001-00' ``` ### E se eu quiser gerar CPFs ou CNPJs validos aleatórios? ``` >>> cpf.generate() '17433964657' >>> cnpj.generate() '34665388000161' ``` ## Testes ``` python2.7 -m unittest discover tests/ python3 -m unittest discover tests/ ``` brutils-2.5.0/old_versions_documentation/v1.0.1/ENGLISH_VERSION.md0000664000000000000000000000276015221040537023010 0ustar rootroot# brutils ### [Procurando pela versão em português?](PORTUGUESE_VERSION.md) _Compatible with Python 2.7 and 3.x_ `brutils` is a library for validating brazilian document numbers, and might eventually evolve to deal with other validations related to brazilian bureaucracy. It's main functionality is the validation of CPF and CNPJ numbers, but suggestions for other (preferrably deterministic) things to validate are welcome. ## Installation ``` pip install brutils ``` ## Utilization ### Importing: ``` >>> from brutils import cpf, cnpj ``` ### How do I validate a CPF or CNPJ? ``` # numbers only, formatted as strings >>> cpf.validate('00011122233') False >>> cnpj.validate('00111222000133') False ``` ### What if my string has formatting symbols in it? ``` >>> cpf.sieve('000.111.222-33') '00011122233' >>> cnpj.sieve('00.111.222/0001-00') '00111222000100' # The `sieve` function only filters out the symbols used for CPF or CNPJ validation. # It purposefully doesn't remove other symbols, as those may be indicators of data # corruption, or a possible lack of input filters. ``` ### What if I want to format a numbers only string? ``` >>> cpf.display('00011122233') '000.111.222-33' >>> cnpj.display('00111222000100') '00.111.222/0001-00' ``` ### What if I want to generate random, but numerically valid CPF or CNPJ numbers? ``` >>> cpf.generate() '17433964657' >>> cnpj.generate() '34665388000161' ``` ## Testing ``` python2.7 -m unittest discover tests/ python3 -m unittest discover tests/ ``` brutils-2.5.0/old_versions_documentation/README.md0000664000000000000000000000061715221040537020623 0ustar rootroot# Documentação Antiga Aqui você vai encontrar a documentação de versões já descontinuadas. A documentação das versões que ainda estão sendo mantidas está disponível nos arquivos README.md/README_EN.md. # Old Documentation Here you will find documentation for discontinued versions. Documentation for versions that are still being maintained is available on README.md/README_EN.md. brutils-2.5.0/.gitignore0000664000000000000000000002655615221040537013707 0ustar rootroot # Created by https://www.toptal.com/developers/gitignore/api/python,vim,pycharm,visualstudio # Edit at https://www.toptal.com/developers/gitignore?templates=python,vim,pycharm,visualstudio ### PyCharm ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf # AWS User-specific .idea/**/aws.xml # Generated files .idea/**/contentModel.xml # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml # Gradle .idea/**/gradle.xml .idea/**/libraries # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, # since they will be recreated, and may cause churn. Uncomment if using # auto-import. # .idea/artifacts # .idea/compiler.xml # .idea/jarRepositories.xml # .idea/modules.xml # .idea/*.iml # .idea/modules # *.iml # *.ipr # CMake cmake-build-*/ # Mongo Explorer plugin .idea/**/mongoSettings.xml # File-based project format *.iws # IntelliJ out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties # Editor-based Rest Client .idea/httpRequests # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser ### PyCharm Patch ### # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 # *.iml # modules.xml # .idea/misc.xml # *.ipr # Sonarlint plugin # https://plugins.jetbrains.com/plugin/7973-sonarlint .idea/**/sonarlint/ # SonarQube Plugin # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin .idea/**/sonarIssues.xml # Markdown Navigator plugin # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced .idea/**/markdown-navigator.xml .idea/**/markdown-navigator-enh.xml .idea/**/markdown-navigator/ # Cache file creation bug # See https://youtrack.jetbrains.com/issue/JBR-2257 .idea/$CACHE_FILE$ # CodeStream plugin # https://plugins.jetbrains.com/plugin/12206-codestream .idea/codestream.xml ### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ ### Vim ### # Swap [._]*.s[a-v][a-z] !*.svg # comment out if you don't need vector files [._]*.sw[a-p] [._]s[a-rt-v][a-z] [._]ss[a-gi-z] [._]sw[a-p] # Session Session.vim Sessionx.vim # Temporary .netrwhist *~ # Auto-generated tag files tags # Persistent undo [._]*.un~ ### VisualStudio ### ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # ASP.NET Scaffolding ScaffoldingReadMe.txt # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.tlog *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Nuget personal access tokens and Credentials nuget.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd # VS Code files for those working on multiple tools .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json *.code-workspace # Local History for Visual Studio Code .history/ # Windows Installer files from build outputs *.cab *.msi *.msix *.msm *.msp # JetBrains Rider .idea/ *.sln.iml ### VisualStudio Patch ### # Additional files built by Visual Studio # End of https://www.toptal.com/developers/gitignore/api/python,vim,pycharm,visualstudio brutils-2.5.0/tests/0000775000000000000000000000000015221040537013043 5ustar rootrootbrutils-2.5.0/tests/test_legal_process.py0000664000000000000000000000572115221040537017303 0ustar rootrootfrom datetime import datetime from unittest import TestCase, main from brutils.legal_process import ( _checksum, format_legal_process, generate, is_valid, remove_symbols, ) class TestLegalProcess(TestCase): def test_format_legal_process(self): self.assertEqual( format_legal_process("23141945820055070079"), ("2314194-58.2005.5.07.0079"), ) self.assertEqual( format_legal_process("00000000000000000000"), ("0000000-00.0000.0.00.0000"), ) self.assertIsInstance( format_legal_process("00000000000000000000"), str, ) self.assertIsNone(format_legal_process("2314194582005507")) self.assertIsNone(format_legal_process("0000000000000000000000000")) self.assertIsNone(format_legal_process("0000000000000000000asdasd")) def test_remove_symbols(self): self.assertEqual( remove_symbols("6439067-89.2023.4.04.5902"), "64390678920234045902" ) self.assertEqual( remove_symbols("4976023-82.2012.7.00.2263"), "49760238220127002263" ) self.assertEqual( remove_symbols("4976...-02382-.-2012.-7002--263"), "49760238220127002263", ) self.assertEqual( remove_symbols("4976023-82.2012.7.00.2263*!*&#"), "49760238220127002263*!*&#", ) self.assertEqual( remove_symbols("4976..#.-0@2382-.#-2012.#-7002--263@"), "4976#0@2382#2012#7002263@", ) self.assertEqual(remove_symbols("@...---...#"), "@#") self.assertEqual(remove_symbols("...---..."), "") self.assertIsInstance(remove_symbols("...---..."), str) def test_generate(self): self.assertEqual(generate()[9:13], str(datetime.now().year)) self.assertEqual(generate(year=3000)[9:13], "3000") self.assertEqual(generate(orgao=4)[13:14], "4") self.assertEqual(generate(year=3000, orgao=4)[9:13], "3000") self.assertIsInstance(generate(year=3000, orgao=4)[9:13], str) self.assertIsNone(generate(year=1000, orgao=4)) self.assertIsNone(generate(orgao=0)) def test_check_sum(self): self.assertEqual(_checksum(546611720238150014), "77") self.assertEqual(_checksum(403818720238230498), "50") self.assertIsInstance(_checksum(403818720238230498), str) def test_is_valid(self): self.assertIs(is_valid("10188748220234018200"), True) self.assertIs(is_valid("45532346920234025107"), True) self.assertIs(is_valid("10188748220239918200"), False) self.assertIs(is_valid("00000000000000000000"), False) self.assertIs(is_valid("455323469202340251"), False) self.assertIs(is_valid("455323469202340257123123123"), False) self.assertIs(is_valid("455323423QQWEQWSsasd&*(()"), False) self.assertIsInstance(is_valid("455323423QQWEQWSsasd&*(()"), bool) if __name__ == "__main__": main() brutils-2.5.0/tests/test_voter_id.py0000664000000000000000000001141015221040537016264 0ustar rootrootfrom unittest import TestCase, main from brutils.voter_id import ( _calculate_vd1, _calculate_vd2, _get_federative_union, _get_sequential_number, _get_verifying_digits, _is_length_valid, format_voter_id, generate, is_valid, ) class TestIsValid(TestCase): def test_valid_voter_id(self): # test a valid voter id number voter_id = "217633460930" self.assertIs(is_valid(voter_id), True) self.assertIsInstance(is_valid(voter_id), bool) def test_invalid_voter_id(self): # test an invalid voter id number (dv1 & UF fail) voter_id = "123456789011" self.assertIs(is_valid(voter_id), False) def test_invalid_length(self): # Test an invalid length for voter id invalid_length_short = "12345678901" invalid_length_long = "1234567890123" self.assertIs(is_valid(invalid_length_short), False) self.assertIs(is_valid(invalid_length_long), False) def test_invalid_characters(self): # Test voter id with non-numeric characters invalid_characters = "ABCD56789012" invalid_characters_space = "217633 460 930" self.assertIs(is_valid(invalid_characters), False) self.assertIs(is_valid(invalid_characters_space), False) def test_valid_special_case(self): # Test a valid edge case (SP & MG with 13 digits) valid_special = "3244567800167" self.assertIs(is_valid(valid_special), True) def test_invalid_vd1(self): voter_id = "427503840223" self.assertIs(is_valid(voter_id), False) def test_invalid_vd2(self): voter_id = "427503840214" self.assertIs(is_valid(voter_id), False) def test_get_voter_id_parts(self): voter_id = "12345678AB12" sequential_number = _get_sequential_number(voter_id) federative_union = _get_federative_union(voter_id) verifying_digits = _get_verifying_digits(voter_id) self.assertEqual(sequential_number, "12345678") self.assertIsInstance(sequential_number, str) self.assertEqual(federative_union, "AB") self.assertIsInstance(federative_union, str) self.assertEqual(verifying_digits, "12") self.assertIsInstance(verifying_digits, str) def test_valid_length_verify(self): voter_id = "123456789012" self.assertIs(_is_length_valid(voter_id), True) self.assertIsInstance(_is_length_valid(voter_id), bool) def test_invalid_length_verify(self): voter_id = "12345678AB123" # Invalid length self.assertIs(_is_length_valid(voter_id), False) def test_calculate_vd1(self): self.assertIs(_calculate_vd1("07881476", "03"), 6) # test edge case: when federative union is SP and rest is 0, declare vd1 as 1 self.assertIs(_calculate_vd1("73146499", "01"), 1) # test edge case: when federative union is MG and rest is 0, declare vd1 as 1 self.assertIs(_calculate_vd1("42750359", "02"), 1) # test edge case: rest is 10, declare vd1 as 0 self.assertIs(_calculate_vd1("73146415", "03"), 0) self.assertIsInstance(_calculate_vd1("73146415", "03"), int) def test_calculate_vd2(self): self.assertIs(_calculate_vd2("02", 7), 2) # edge case: if rest == 10, declare vd2 as zero self.assertIs(_calculate_vd2("03", 7), 0) # edge case: if UF is "01" (for SP) and rest == 0 # declare dv2 as 1 instead self.assertIs(_calculate_vd2("01", 4), 1) # edge case: if UF is "02" (for MG) and rest == 0 # declare dv2 as 1 instead self.assertIs(_calculate_vd2("02", 8), 1) self.assertIsInstance(_calculate_vd2("02", 8), int) def test_generate_voter_id(self): # test if is_valid a voter id from MG voter_id = generate(federative_union="MG") self.assertIs(is_valid(voter_id), True) # test if is_valid a voter id from AC voter_id = generate(federative_union="AC") self.assertIs(is_valid(voter_id), True) # test if is_valid a voter id from foreigner voter_id = generate() self.assertIs(is_valid(voter_id), True) self.assertIsInstance(voter_id, str) # test if UF is not valid voter_id = generate(federative_union="XX") self.assertIs(is_valid(voter_id), False) self.assertIsNone(voter_id) def test_format_voter_id(self): self.assertEqual(format_voter_id("277627122852"), "2776 2712 28 52") self.assertIsInstance(format_voter_id("277627122852"), str) self.assertIsNone(format_voter_id("00000000000")) self.assertIsNone(format_voter_id("0000000000a")) self.assertIsNone(format_voter_id("000000000000")) self.assertIsNone(format_voter_id("800911840197")) if __name__ == "__main__": main() brutils-2.5.0/tests/test_renavam.py0000664000000000000000000000140215221040537016102 0ustar rootrootfrom unittest import TestCase from brutils.renavam import is_valid_renavam class TestRENAVAM(TestCase): def test_is_valid_renavam(self): self.assertTrue(is_valid_renavam("86769597308")) self.assertFalse(is_valid_renavam("12345678901")) self.assertFalse(is_valid_renavam("1234567890a")) self.assertFalse(is_valid_renavam("12345678 901")) self.assertFalse(is_valid_renavam("12345678")) self.assertFalse(is_valid_renavam("")) self.assertFalse(is_valid_renavam("123456789012")) self.assertFalse(is_valid_renavam("abcdefghijk")) self.assertFalse(is_valid_renavam("12345678901!")) self.assertFalse(is_valid_renavam("00000000000")) self.assertFalse(is_valid_renavam("11111111111")) brutils-2.5.0/tests/test_passport.py0000664000000000000000000000425615221040537016336 0ustar rootrootfrom unittest import TestCase, main from unittest.mock import patch from brutils.passport import format_passport, generate, is_valid, remove_symbols class TestCPF(TestCase): def test_is_valid(self): # When passport is not string, returns False self.assertIs(is_valid(1), False) # type: ignore # When passport's len is different of 8, returns False self.assertIs(is_valid("1"), False) # When passport does not contain only digits, returns False self.assertIs(is_valid("1112223334-"), False) # When cpf is valid self.assertIs(is_valid("AA111111"), True) self.assertIs(is_valid("CL125167"), True) def test_generate(self): for _ in range(10_000): self.assertIs(is_valid(generate()), True) def test_remove_symbols(self): # When there are no symbols, returns the same string self.assertEqual(remove_symbols("Ab123456"), "Ab123456") # When there are spaces, returns the string without them self.assertEqual(remove_symbols(" AB 123 456 "), "AB123456") # When there are dashes, returns the string without them self.assertEqual(remove_symbols("-AB1-23-4-56-"), "AB123456") # When there are dots, returns the string without them self.assertEqual(remove_symbols(".AB.1.23.456."), "AB123456") # When there are multiple symbols, returns the string without any of them self.assertEqual(remove_symbols(".A B.1.2-3.45 -. 6."), "AB123456") @patch("brutils.passport.is_valid") class TestIsValidToFormat(TestCase): def test_when_passport_is_valid_returns_true_to_format(self, mock_is_valid): mock_is_valid.return_value = True # When passport is_valid, returns formatted passport self.assertEqual(format_passport("yz 987654"), "YZ987654") # Checks if function is_valid_passport is called mock_is_valid.assert_called_once_with("YZ987654") def test_when_cpf_is_not_valid_returns_none(self, mock_is_valid): mock_is_valid.return_value = False # When passport isn't valid, returns None self.assertIsNone(format_passport("acd12736")) if __name__ == "__main__": main() brutils-2.5.0/tests/test_pis.py0000664000000000000000000000524515221040537015255 0ustar rootrootfrom unittest import TestCase, main from unittest.mock import patch from brutils.pis import ( _checksum, format_pis, generate, is_valid, remove_symbols, ) class TestPIS(TestCase): def test_is_valid(self): # When PIS is not string, returns False self.assertIs(is_valid(1), False) self.assertIs(is_valid([]), False) self.assertIs(is_valid({}), False) self.assertIs(is_valid(None), False) # When PIS's len is different of 11, returns False self.assertIs(is_valid("123456789"), False) # When PIS does not contain only digits, returns False self.assertIs(is_valid("123pis"), False) self.assertIs(is_valid("123456789ab"), False) # When checksum digit doesn't match last digit, returns False self.assertIs(is_valid("11111111111"), False) self.assertIs(is_valid("11111111215"), False) self.assertIs(is_valid("12038619493"), False) # When PIS is valid self.assertIs(is_valid("12038619494"), True) self.assertIs(is_valid("12016784018"), True) self.assertIs(is_valid("12083210826"), True) def test_checksum(self): # Checksum digit is 0 when the subtracted number is 10 or 11 self.assertEqual(_checksum("1204152015"), 0) self.assertEqual(_checksum("1204433157"), 0) # Checksum digit is equal the subtracted number self.assertEqual(_checksum("1204917738"), 2) self.assertEqual(_checksum("1203861949"), 4) self.assertEqual(_checksum("1208321082"), 6) def test_generate(self): for _ in range(10_000): self.assertIs(is_valid(generate()), True) def test_remove_symbols(self): self.assertEqual(remove_symbols("00000000000"), "00000000000") self.assertEqual(remove_symbols("170.33259.50-4"), "17033259504") self.assertEqual(remove_symbols("134..2435/.-1892.-"), "1342435/1892") self.assertEqual(remove_symbols("abc1230916*!*&#"), "abc1230916*!*&#") self.assertEqual(remove_symbols("...---..."), "") @patch("brutils.pis.is_valid") def test_format_valid_pis(self, mock_is_valid): mock_is_valid.return_value = True # When PIS is_valid, returns formatted PIS self.assertEqual(format_pis("14372195539"), "143.72195.53-9") # Checks if function is_valid_pis is called mock_is_valid.assert_called_once_with("14372195539") @patch("brutils.pis.is_valid") def test_format_invalid_pis(self, mock_is_valid): mock_is_valid.return_value = False # When PIS isn't valid, returns None self.assertIsNone(format_pis("14372195539")) if __name__ == "__main__": main() brutils-2.5.0/tests/test_cep.py0000664000000000000000000002013515221040537015224 0ustar rootrootfrom unittest import TestCase, main from unittest.mock import MagicMock, patch from brutils.cep import ( CEPNotFound, InvalidCEP, format_cep, generate, get_address_from_cep, get_cep_information_from_address, is_valid, remove_symbols, ) class TestCEP(TestCase): def test_remove_symbols(self): self.assertEqual(remove_symbols("00000000"), "00000000") self.assertEqual(remove_symbols("01310-200"), "01310200") self.assertEqual(remove_symbols("01..310.-200.-"), "01310200") self.assertEqual(remove_symbols("abc01310200*!*&#"), "abc01310200*!*&#") self.assertEqual( remove_symbols("ab.c1.--.3-102.-0-.0-.*.-!*&#"), "abc1310200*!*&#" ) self.assertEqual(remove_symbols("...---..."), "") def test_is_valid(self): # When CEP is not string, returns False self.assertIs(is_valid(1), False) # When CEP's len is different of 8, returns False self.assertIs(is_valid("1"), False) # When CEP does not contain only digits, returns False self.assertIs(is_valid("1234567-"), False) # When CEP is valid self.assertIs(is_valid("99999999"), True) self.assertIs(is_valid("88390000"), True) def test_generate(self): for _ in range(10_000): self.assertIs(is_valid(generate()), True) @patch("brutils.cep.is_valid") class TestIsValidToFormat(TestCase): def test_when_cep_is_valid_returns_True_to_format(self, mock_is_valid): mock_is_valid.return_value = True self.assertEqual(format_cep("01310200"), "01310-200") # Checks if function is_valid_cnpj is called mock_is_valid.assert_called_once_with("01310200") def test_when_cep_is_not_valid_returns_none(self, mock_is_valid): mock_is_valid.return_value = False # When cep isn't valid, returns None self.assertIsNone(format_cep("013102009")) @patch("brutils.cep.urlopen") class TestCEPAPICalls(TestCase): @patch("brutils.cep.loads") def test_get_address_from_cep_success(self, mock_loads, mock_urlopen): mock_loads.return_value = {"cep": "01310-200"} self.assertEqual( get_address_from_cep("01310200", True), {"cep": "01310-200"} ) def test_get_address_from_cep_raise_exception_invalid_cep( self, mock_urlopen ): mock_data = MagicMock() mock_data.read.return_value = {"erro": True} mock_urlopen.return_value = mock_data self.assertIsNone(get_address_from_cep("013102009")) def test_get_address_from_cep_invalid_cep_raise_exception_invalid_cep( self, mock_urlopen ): with self.assertRaises(InvalidCEP): get_address_from_cep("abcdef", True) def test_get_address_from_cep_invalid_cep_raise_exception_cep_not_found( self, mock_urlopen ): mock_data = MagicMock() mock_data.read.return_value = {"erro": True} mock_urlopen.return_value = mock_data with self.assertRaises(CEPNotFound): get_address_from_cep("01310209", True) @patch("brutils.cep.loads") def test_get_cep_information_from_address_success( self, mock_loads, mock_urlopen ): mock_loads.return_value = [{"cep": "01310-200"}] self.assertDictEqual( get_cep_information_from_address( "SP", "Example", "Rua Example", True )[0], {"cep": "01310-200"}, ) @patch("brutils.cep.loads") def test_get_cep_information_from_address_success_with_uf_conversion( self, mock_loads, mock_urlopen ): mock_loads.return_value = [{"cep": "01310-200"}] self.assertDictEqual( get_cep_information_from_address( "São Paulo", "Example", "Rua Example", True )[0], {"cep": "01310-200"}, ) @patch("brutils.cep.loads") def test_get_cep_information_from_address_empty_response( self, mock_loads, mock_urlopen ): mock_loads.return_value = [] self.assertIsNone( get_cep_information_from_address("SP", "Example", "Rua Example") ) @patch("brutils.cep.loads") def test_get_cep_information_from_address_raise_exception_invalid_cep( self, mock_loads, mock_urlopen ): mock_loads.return_value = {"erro": True} self.assertIsNone( get_cep_information_from_address("SP", "Example", "Rua Example") ) def test_get_cep_information_from_address_invalid_cep_dont_raise_exception_invalid_uf( self, mock_urlopen ): self.assertIsNone( get_cep_information_from_address("ABC", "Example", "Rua Example") ) def test_get_cep_information_from_address_invalid_cep_raise_exception_invalid_uf( self, mock_urlopen ): with self.assertRaises(ValueError): get_cep_information_from_address( "ABC", "Example", "Rua Example", True ) def test_get_cep_information_from_address_invalid_cep_raise_exception_cep_not_found( self, mock_urlopen ): mock_response = MagicMock() mock_response.read.return_value = {"erro": True} mock_urlopen.return_value = mock_response with self.assertRaises(CEPNotFound): get_cep_information_from_address( "SP", "Example", "Rua Example", True ) @patch("brutils.cep.urlopen") class TestGetAddressFromCEP_MC_DC(TestCase): def test_ct1_cep_invalido_com_excecao(self, mock_urlopen): """CT1: cep inválido e raise_exceptions=True → lança InvalidCEP""" with self.assertRaises(InvalidCEP): get_address_from_cep("abcdefg", raise_exceptions=True) def test_ct2_cep_invalido_sem_excecao(self, mock_urlopen): """CT2: cep inválido e raise_exceptions=False → retorna None""" result = get_address_from_cep("abcdefg", raise_exceptions=False) self.assertIsNone(result) @patch("brutils.cep.loads") def test_ct3_cep_valido(self, mock_loads, mock_urlopen): """CT3: cep válido → retorna dicionário com endereço""" # A função get_address_from_cep faz dois loads() no mesmo JSON mock_loads.side_effect = [ {"cep": "01001-000"}, # Primeiro loads para verificar "erro" {"cep": "01001-000"}, # Segundo loads para retornar Address ] # Simula a resposta da API ViaCEP mock_response = MagicMock() mock_response.read.return_value = b'{"cep": "01001-000"}' # Corrige o mock para suportar o contexto "with urlopen(...) as f" mock_urlopen.return_value.__enter__.return_value = mock_response # Executa a chamada result = get_address_from_cep("01001000", raise_exceptions=False) # Valida o resultado self.assertIsInstance(result, dict) self.assertEqual(result.get("cep"), "01001-000") def test_ct4_erro_na_api_com_excecao(self, mock_urlopen): """CT4: cep válido mas inexistente + raise_exceptions=True → lança CEPNotFound""" mock_response = MagicMock() mock_response.read.return_value = b'{"erro": true}' mock_urlopen.return_value = mock_response with self.assertRaises(CEPNotFound): get_address_from_cep("99999999", raise_exceptions=True) def test_ct5_erro_na_api_sem_excecao(self, mock_urlopen): """CT5: cep válido mas inexistente + raise_exceptions=False → retorna None""" mock_response = mock_urlopen.return_value mock_response.read.return_value = b'{"erro": true}' result = get_address_from_cep("99999999", raise_exceptions=False) self.assertIsNone(result) def test_ct6_viacep_responde_com_erro_e_raise_exceptions_true( self, mock_urlopen ): """ CT6: cobre a linha 172 e a decisão CD2 (data.get("erro", True)) """ mock_response = MagicMock() mock_response.read.return_value = b'{"erro": true}' mock_urlopen.return_value = mock_response with self.assertRaises(CEPNotFound): get_address_from_cep("01001000", raise_exceptions=True) if __name__ == "__main__": main() brutils-2.5.0/tests/license_plate/0000775000000000000000000000000015221040537015652 5ustar rootrootbrutils-2.5.0/tests/license_plate/test_license_plate.py0000664000000000000000000001153415221040537022076 0ustar rootrootfrom unittest import TestCase, main from unittest.mock import patch from brutils.license_plate import ( _is_valid_mercosul, _is_valid_old_format, convert_to_mercosul, format_license_plate, generate, get_format, remove_symbols, ) class TestLicensePlate(TestCase): def test_remove_symbols(self): self.assertEqual(remove_symbols("ABC-123"), "ABC123") self.assertEqual(remove_symbols("abc123"), "abc123") self.assertEqual(remove_symbols("ABCD123"), "ABCD123") self.assertEqual(remove_symbols("A-B-C-1-2-3"), "ABC123") self.assertEqual(remove_symbols("@abc#-#123@"), "@abc##123@") self.assertEqual(remove_symbols("@---#"), "@#") self.assertEqual(remove_symbols("---"), "") def test_convert_license_plate_to_mercosul(self): # when license plate is not an instance of string, returns None self.assertIsNone(convert_to_mercosul(1234567)) # when license plate has a length different from 7, returns None self.assertIsNone(convert_to_mercosul("ABC123")) self.assertIsNone(convert_to_mercosul("ABC12356")) # when then license plate's 5th character is not a number, return None self.assertIsNone(convert_to_mercosul("ABC1A34")) self.assertIsNone(convert_to_mercosul("ABC1-34")) self.assertIsNone(convert_to_mercosul("ABC1*34")) self.assertIsNone(convert_to_mercosul("ABC1_34")) self.assertIsNone(convert_to_mercosul("ABC1%34")) self.assertIsNone(convert_to_mercosul("ABC1 34")) # when then license plate's 5th character is 0, return with a letter A self.assertEqual(convert_to_mercosul("AAA1011"), "AAA1A11") # when then license plate's 5th character is 1, return with a letter B self.assertEqual(convert_to_mercosul("AAA1111"), "AAA1B11") # when then license plate's 5th character is 2, return with a letter C self.assertEqual(convert_to_mercosul("AAA1211"), "AAA1C11") # when then license plate's 5th character is 3, return with a letter D self.assertEqual(convert_to_mercosul("AAA1311"), "AAA1D11") # when then license plate's 5th character is 4, return with a letter E self.assertEqual(convert_to_mercosul("AAA1411"), "AAA1E11") # when then license plate's 5th character is 5, return with a letter F self.assertEqual(convert_to_mercosul("AAA1511"), "AAA1F11") # when then license plate's 5th character is 6, return with a letter G self.assertEqual(convert_to_mercosul("AAA1611"), "AAA1G11") # when then license plate's 5th character is 7, return with a letter H self.assertEqual(convert_to_mercosul("AAA1711"), "AAA1H11") # when then license plate's 5th character is 8, return with a letter I self.assertEqual(convert_to_mercosul("AAA1811"), "AAA1I11") # when then license plate's 5th character is 9, return with a letter J self.assertEqual(convert_to_mercosul("AAA1911"), "AAA1J11") # when then license is provided in lowercase, it's correctly converted # and then returned value is in uppercase self.assertEqual(convert_to_mercosul("abc1234"), "ABC1C34") def test_format_license_plate(self): self.assertEqual(format_license_plate("ABC1234"), "ABC-1234") self.assertEqual(format_license_plate("abc1234"), "ABC-1234") self.assertEqual(format_license_plate("ABC1D23"), "ABC1D23") self.assertEqual(format_license_plate("abc1d23"), "ABC1D23") self.assertIsNone(format_license_plate("ABCD123")) def test_get_format(self): # Old format self.assertEqual(get_format("ABC1234"), "LLLNNNN") self.assertEqual(get_format("abc1234"), "LLLNNNN") # Mercosul self.assertEqual(get_format("ABC4E67"), "LLLNLNN") self.assertEqual(get_format("XXX9X99"), "LLLNLNN") # Invalid self.assertIsNone(get_format(None)) self.assertIsNone(get_format("")) self.assertIsNone(get_format("ABC-1D23")) self.assertIsNone(get_format("invalid plate")) def test_generate_license_plate(self): with patch("brutils.license_plate.choice", return_value="X"): with patch("brutils.license_plate.randint", return_value=9): self.assertEqual(generate(format="LLLNNNN"), "XXX9999") self.assertEqual(generate(format="LLLNLNN"), "XXX9X99") for _ in range(10_000): self.assertTrue(_is_valid_mercosul(generate(format="LLLNLNN"))) for _ in range(10_000): self.assertTrue(_is_valid_old_format(generate(format="LLLNNNN"))) # When no format is provided, returns a valid Mercosul license plate self.assertTrue(_is_valid_mercosul(generate())) # When invalid format is provided, returns None self.assertIsNone(generate("LNLNLNL")) if __name__ == "__main__": main() brutils-2.5.0/tests/license_plate/__init__.py0000664000000000000000000000000015221040537017751 0ustar rootrootbrutils-2.5.0/tests/license_plate/test_is_valid.py0000664000000000000000000001406015221040537021056 0ustar rootrootfrom unittest import TestCase, main from unittest.mock import patch from brutils.license_plate import ( _is_valid_mercosul, _is_valid_old_format, is_valid, ) @patch("brutils.license_plate._is_valid_mercosul") @patch("brutils.license_plate._is_valid_old_format") class TestIsValidWithTypeOldFormat(TestCase): def test_when_old_format_is_valid_returns_true( self, mock__is_valid_old_format, mock__is_valid_mercosul ): mock__is_valid_old_format.return_value = True self.assertIs(is_valid("ABC1234", "old_format"), True) mock__is_valid_old_format.assert_called_once_with("ABC1234") mock__is_valid_mercosul.assert_not_called() def test_when_old_format_is_not_valid_returns_false( self, mock__is_valid_old_format, mock__is_valid_mercosul ): mock__is_valid_old_format.return_value = False self.assertIs(is_valid("123456", "old_format"), False) mock__is_valid_old_format.assert_called_once_with("123456") mock__is_valid_mercosul.assert_not_called() @patch("brutils.license_plate._is_valid_mercosul") @patch("brutils.license_plate._is_valid_old_format") class TestIsValidWithTypeMercosul(TestCase): def test_when_mercosul_is_valid_returns_true( self, mock__is_valid_old_format, mock__is_valid_mercosul ): mock__is_valid_mercosul.return_value = True self.assertIs(is_valid("ABC4E67", "mercosul"), True) mock__is_valid_mercosul.assert_called_once_with("ABC4E67") mock__is_valid_old_format.assert_not_called() def test_when_mercosul_is_not_valid_returns_false( self, mock__is_valid_old_format, mock__is_valid_mercosul ): mock__is_valid_mercosul.return_value = False self.assertIs(is_valid("11994029275", "mercosul"), False) mock__is_valid_mercosul.assert_called_once_with("11994029275") mock__is_valid_old_format.assert_not_called() @patch("brutils.license_plate._is_valid_mercosul") @patch("brutils.license_plate._is_valid_old_format") class TestIsValidWithTypeNone(TestCase): def test_when_mercosul_valid_old_format_invalid( self, mock__is_valid_old_format, mock__is_valid_mercosul ): mock__is_valid_mercosul.return_value = True mock__is_valid_old_format.return_value = False self.assertIs(is_valid("ABC4E67"), True) mock__is_valid_old_format.assert_called_once_with("ABC4E67") mock__is_valid_mercosul.assert_called_once_with("ABC4E67") def test_when_mercosul_and_old_format_are_valids( self, mock__is_valid_old_format, mock__is_valid_mercosul ): mock__is_valid_mercosul.return_value = True mock__is_valid_old_format.return_value = True self.assertIs(is_valid("ABC1234"), True) mock__is_valid_mercosul.assert_not_called() mock__is_valid_old_format.assert_called_once_with("ABC1234") def test_when_mercosul_invalid_old_format_valid( self, mock__is_valid_old_format, mock__is_valid_mercosul ): mock__is_valid_mercosul.return_value = False mock__is_valid_old_format.return_value = True self.assertIs(is_valid("ABC1234"), True) mock__is_valid_old_format.assert_called_once_with("ABC1234") mock__is_valid_mercosul.assert_not_called() def test_when_mercosul_and_old_format_are_invalid( self, mock__is_valid_old_format, mock__is_valid_mercosul ): mock__is_valid_old_format.return_value = False mock__is_valid_mercosul.return_value = False self.assertIs(is_valid("ABC1234"), False) mock__is_valid_old_format.assert_called_once_with("ABC1234") mock__is_valid_mercosul.assert_called_once_with("ABC1234") class TestIsValidOldFormat(TestCase): def test__is_valid_old_format(self): # When license plate is valid, returns True self.assertIs(_is_valid_old_format("ABC1234"), True) self.assertIs(_is_valid_old_format("abc1234"), True) # When license plate is valid with whitespaces, returns True self.assertIs(_is_valid_old_format(" ABC1234 "), True) # When license plate is not string, returns False self.assertIs(_is_valid_old_format(123456), False) # When license plate is invalid with special characters, # returns False self.assertIs(_is_valid_old_format("ABC-1234"), False) # When license plate is invalid with numbers and letters out of # order, returns False self.assertIs(_is_valid_old_format("A1CA23W"), False) # When license plate is invalid with new format, returns False self.assertIs(_is_valid_old_format("ABC1D23"), False) self.assertIs(_is_valid_old_format("abcd123"), False) class TestIsValidMercosul(TestCase): def test__is_valid_mercosul(self): # When license plate is not string, returns False self.assertIs(_is_valid_mercosul(1234567), False) # When license plate doesn't match the pattern LLLNLNN, # returns False self.assertIs(_is_valid_mercosul("ABCDEFG"), False) self.assertIs(_is_valid_mercosul("1234567"), False) self.assertIs(_is_valid_mercosul("ABC4567"), False) self.assertIs(_is_valid_mercosul("ABCD567"), False) self.assertIs(_is_valid_mercosul("ABC45F7"), False) self.assertIs(_is_valid_mercosul("ABC456G"), False) self.assertIs(_is_valid_mercosul("ABC123"), False) # When license plate is an empty string, returns False self.assertIs(_is_valid_mercosul(""), False) # When license plate's length is different of 7, returns False self.assertIs(_is_valid_mercosul("ABC4E678"), False) # When license plate has separator, returns false self.assertIs(_is_valid_mercosul("ABC-1D23"), False) # When license plate is valid self.assertIs(_is_valid_mercosul("ABC4E67"), True) self.assertIs(_is_valid_mercosul("AAA1A11"), True) self.assertIs(_is_valid_mercosul("XXX9X99"), True) # Check if function is case insensitive self.assertIs(_is_valid_mercosul("abc4e67"), True) if __name__ == "__main__": main() brutils-2.5.0/tests/test_cnpj.py0000664000000000000000000001232215221040537015406 0ustar rootrootfrom unittest import TestCase, main from unittest.mock import patch from brutils.cnpj import ( _checksum, _hashdigit, _is_alphanumeric, display, format_cnpj, generate, is_valid, remove_symbols, sieve, validate, ) class TestCNPJ(TestCase): def test_sieve(self): self.assertEqual(sieve("00000000000"), "00000000000") self.assertEqual(sieve("12.345.678/0001-90"), "12345678000190") self.assertEqual(sieve("134..2435/.-1892.-"), "13424351892") self.assertEqual(sieve("abc1230916*!*&#"), "abc1230916*!*&#") self.assertEqual( sieve("ab.c1.--.2-3/09.-1-./6/-.*.-!*&#"), "abc1230916*!*&#" ) self.assertEqual(sieve("/...---.../"), "") def test_display(self): self.assertEqual(display("00000000000109"), "00.000.000/0001-09") self.assertEqual(display("12ABC34501DE35"), "12.ABC.345/01DE-35") self.assertIsNone(display("12ABC34501DEAA")) self.assertIsNone(display("00000000000000")) self.assertIsNone(display("0000000000000")) self.assertIsNone(display("0000000000000a")) def test__is_alphanumeric(self): self.assertIs(_is_alphanumeric("12ABC34501DE35"), True) self.assertIs(_is_alphanumeric("12345678910111"), True) self.assertIs(_is_alphanumeric("123456a78b10C1"), False) self.assertIs(_is_alphanumeric("12.ABC.345/01DE-35"), False) def test_validate(self): self.assertIs(validate("34665388000161"), True) self.assertIs(validate("12ABC34501DE35"), True) self.assertIs(validate("Z46ABC88000164"), True) self.assertIs(validate("12ABC34501DEAA"), False) self.assertIs(validate("52599927000100"), False) self.assertIs(validate("00000000000"), False) def test_is_valid(self): # When CNPJ is not string, returns False self.assertIs(is_valid(1), False) # When CNPJ's len is different of 14, returns False self.assertIs(is_valid("1"), False) # When CNPJ does not contain only digits, returns False self.assertIs(is_valid("1112223334445-"), False) # When CNPJ has only the same digit, returns false self.assertIs(is_valid("11111111111111"), False) # When rest_1 is lt 2 and the 13th digit is not 0, returns False self.assertIs(is_valid("1111111111315"), False) # When rest_1 is gte 2 and the 13th digit is not (11 - rest), returns # False self.assertIs(is_valid("1111111111115"), False) # When rest_2 is lt 2 and the 14th digit is not 0, returns False self.assertIs(is_valid("11111111121205"), False) # When rest_2 is gte 2 and the 14th digit is not (11 - rest), returns # False self.assertIs(is_valid("11111111113105"), False) # When CNPJ is valid self.assertIs(is_valid("34665388000161"), True) self.assertIs(is_valid("01838723000127"), True) def test_generate(self): for _ in range(10_000): self.assertIs(validate(generate()), True) self.assertIsNotNone(display(generate())) self.assertIs(validate(generate(branch=1234)), True) def test_generate_alphanumeric(self): for _ in range(10_000): generated = generate(alphanumeric=True) self.assertIs(validate(generated), True) self.assertIsNotNone(display(generated)) self.assertIs( validate(generate(branch="1234", alphanumeric=True)), True ) self.assertIs( validate(generate(branch="AB12", alphanumeric=True)), True ) def test__hashdigit(self): self.assertEqual(_hashdigit("00000000000000", 13), 0) self.assertEqual(_hashdigit("00000000000000", 14), 0) self.assertEqual(_hashdigit("52513127000292", 13), 9) self.assertEqual(_hashdigit("52513127000292", 14), 9) def test__checksum(self): self.assertEqual(_checksum("00000000000000"), "00") self.assertEqual(_checksum("52513127000299"), "99") @patch("brutils.cnpj.sieve") class TestRemoveSymbols(TestCase): def test_remove_symbols(self, mock_sieve): # When call remove_symbols, it calls sieve remove_symbols("12.345.678/0001-90") mock_sieve.assert_called() @patch("brutils.cnpj.is_valid") class TestIsValidToFormat(TestCase): def test_when_cnpj_is_valid_returns_true_to_format(self, mock_is_valid): mock_is_valid.return_value = True # When cnpj is_valid, returns formatted cnpj self.assertEqual(format_cnpj("01838723000127"), "01.838.723/0001-27") # Checks if function is_valid_cnpj is called mock_is_valid.assert_called_once_with("01838723000127") def test_when_cnpj_is_not_valid_returns_none(self, mock_is_valid): mock_is_valid.return_value = False # When cnpj isn't valid, returns None self.assertIsNone(format_cnpj("01838723000127")) class TestFormatCnpj(TestCase): def test_when_cnpj_is_alphanumeric_valid_returns_formatted_cnpj(self): self.assertEqual(format_cnpj("12ABC34501DE35"), "12.ABC.345/01DE-35") def test_when_cnpj_has_alphanumeric_check_digits_returns_none(self): self.assertIsNone(format_cnpj("12ABC34501DEAA")) if __name__ == "__main__": main() brutils-2.5.0/tests/test_email.py0000664000000000000000000000320615221040537015544 0ustar rootrootfrom unittest import TestCase, main from brutils import is_valid_email class TestEmailValidation(TestCase): def test_valid_email(self): # Valid email addresses valid_emails = [ "joao.ninguem@gmail.com", "user123@gmail.com", "test.email@mydomain.co.uk", "johndoe@sub.domain.example", "f99999999@place.university-campus.ac.in", ] for email in valid_emails: try: self.assertTrue(is_valid_email(email)) except: # noqa: E722 print(f"AssertionError for email: {email}") raise AssertionError def test_invalid_email(self): # Invalid email addresses invalid_emails = [ ".joao.ninguem@gmail.com", "joao ninguem@gmail.com", "not_an_email", "@missing_username.com", "user@incomplete.", "user@.incomplete", "user@inva!id.com", "user@missing-tld.", ] for email in invalid_emails: try: self.assertFalse(is_valid_email(email)) except: # noqa: E722 print(f"AssertionError for email: {email}") raise AssertionError def test_non_string_input(self): # Non-string input should return False non_strings = [None, 123, True, ["test@example.com"]] for value in non_strings: self.assertFalse(is_valid_email(value)) def test_empty_string(self): # Empty string should return False self.assertFalse(is_valid_email("")) if __name__ == "__main__": main() brutils-2.5.0/tests/phone/0000775000000000000000000000000015221040537014154 5ustar rootrootbrutils-2.5.0/tests/phone/test_phone.py0000664000000000000000000001027115221040537016677 0ustar rootrootfrom unittest import TestCase, main from brutils.phone import ( _is_valid_landline, _is_valid_mobile, format_phone, generate, is_valid, remove_international_dialing_code, remove_symbols_phone, ) class TestPhone(TestCase): def test_remove_symbols_phone(self): # When the string empty, it returns an empty string self.assertEqual(remove_symbols_phone(""), "") # When there are no symbols to remove, it returns the same string self.assertEqual(remove_symbols_phone("21994029275"), "21994029275") # When there are symbols to remove, it returns the string without # symbols self.assertEqual(remove_symbols_phone("(21)99402-9275"), "21994029275") self.assertEqual(remove_symbols_phone("(21)2569-6969"), "2125696969") # When there are extra symbols, it only removes the specified symbols self.assertEqual( remove_symbols_phone("(21) 99402-9275!"), "21994029275!" ) # When the string contains non-numeric characters, it returns the # string without the specified symbols self.assertEqual(remove_symbols_phone("(21)ABC-DEF"), "21ABCDEF") # When the phone number contains a plus symbol and spaces, they are # removed self.assertEqual( remove_symbols_phone("+55 21 99402-9275"), "5521994029275" ) # When the phone number contains multiple spaces, all are removed self.assertEqual( remove_symbols_phone("55 21 99402 9275"), "5521994029275" ) # When the phone number contains a mixture of all specified symbols, # all are removed self.assertEqual( remove_symbols_phone("+55 (21) 99402-9275"), "5521994029275" ) def test_format_phone_number(self): # When is a invalid number self.assertEqual(format_phone("333333"), None) # When is a mobile number self.assertEqual(format_phone("21994029275"), "(21)99402-9275") self.assertEqual(format_phone("21994029275"), "(21)99402-9275") self.assertEqual(format_phone("21994029275"), "(21)99402-9275") self.assertEqual(format_phone("11994029275"), "(11)99402-9275") # When is a landline number self.assertEqual(format_phone("1928814933"), "(19)2881-4933") self.assertEqual(format_phone("1938814933"), "(19)3881-4933") self.assertEqual(format_phone("1948814933"), "(19)4881-4933") self.assertEqual(format_phone("1958814933"), "(19)5881-4933") self.assertEqual(format_phone("3333333333"), "(33)3333-3333") def test_generate(self): for _ in range(25): with self.subTest(): no_type_phone_generated = generate() self.assertIs(is_valid(no_type_phone_generated), True) mobile_phone_generated = generate("mobile") self.assertIs(_is_valid_mobile(mobile_phone_generated), True) landline_phone_generated = generate("landline") self.assertIs( _is_valid_landline(landline_phone_generated), True ) def test_remove_international_dialing_code(self): # When the phone number does not have the international code, # return the same phone number self.assertEqual( remove_international_dialing_code("21994029275"), "21994029275" ) self.assertEqual( remove_international_dialing_code("55994024478"), "55994024478" ) self.assertEqual( remove_international_dialing_code("994024478"), "994024478" ) # When the phone number has the international code, # return phone number without international code self.assertEqual( remove_international_dialing_code("5521994029275"), "21994029275" ) self.assertEqual( remove_international_dialing_code("+5521994029275"), "+21994029275" ) self.assertEqual( remove_international_dialing_code("+5555994029275"), "+55994029275" ) self.assertEqual( remove_international_dialing_code("5511994029275"), "11994029275" ) if __name__ == "__main__": main() brutils-2.5.0/tests/phone/__init__.py0000664000000000000000000000000015221040537016253 0ustar rootrootbrutils-2.5.0/tests/phone/test_is_valid.py0000664000000000000000000001271215221040537017362 0ustar rootrootfrom unittest import TestCase, main from unittest.mock import patch from brutils.phone import ( _is_valid_landline, _is_valid_mobile, is_valid, ) @patch("brutils.phone._is_valid_landline") @patch("brutils.phone._is_valid_mobile") class TestIsValidWithTypeMobile(TestCase): def test_when_mobile_is_valid_returns_true( self, mock__is_valid_mobile, mock__is_valid_landline ): mock__is_valid_mobile.return_value = True self.assertIs(is_valid("11994029275", "mobile"), True) mock__is_valid_mobile.assert_called_once_with("11994029275") mock__is_valid_landline.assert_not_called() def test_when_mobile_is_not_valid_returns_false( self, mock__is_valid_mobile, mock__is_valid_landline ): mock__is_valid_mobile.return_value = False self.assertIs(is_valid("119940", "mobile"), False) mock__is_valid_mobile.assert_called_once_with("119940") mock__is_valid_landline.assert_not_called() @patch("brutils.phone._is_valid_mobile") @patch("brutils.phone._is_valid_landline") class TestIsValidWithTypeLandline(TestCase): def test_when_landline_is_valid_returns_true( self, mock__is_valid_landline, mock__is_valid_mobile ): mock__is_valid_landline.return_value = True self.assertIs(is_valid("11994029275", "landline"), True) mock__is_valid_landline.assert_called_once_with("11994029275") mock__is_valid_mobile.assert_not_called() def test_when_landline_is_not_valid_returns_false( self, mock__is_valid_landline, mock__is_valid_mobile ): mock__is_valid_landline.return_value = False self.assertIs(is_valid("11994029275", "landline"), False) mock__is_valid_landline.assert_called_once_with("11994029275") mock__is_valid_mobile.assert_not_called() @patch("brutils.phone._is_valid_landline") @patch("brutils.phone._is_valid_mobile") class TestIsValidWithTypeNone(TestCase): def test_when_landline_is_valid( self, mock__is_valid_mobile, mock__is_valid_landline ): mock__is_valid_landline.return_value = True self.assertIs(is_valid("1958814933"), True) mock__is_valid_landline.assert_called_once_with("1958814933") mock__is_valid_mobile.assert_not_called() def test_when_landline_invalid_mobile_valid( self, mock__is_valid_mobile, mock__is_valid_landline ): mock__is_valid_landline.return_value = False mock__is_valid_mobile.return_value = True self.assertIs(is_valid("11994029275"), True) mock__is_valid_landline.assert_called_once_with("11994029275") mock__is_valid_mobile.assert_called_once_with("11994029275") def test_when_landline_and_mobile_are_invalid( self, mock__is_valid_mobile, mock__is_valid_landline ): mock__is_valid_landline.return_value = False mock__is_valid_mobile.return_value = False self.assertIs(is_valid("11994029275"), False) mock__is_valid_landline.assert_called_once_with("11994029275") mock__is_valid_mobile.assert_called_once_with("11994029275") class TestIsValidLandline(TestCase): def test__is_valid_landline(self): # When landline phone is not string, returns False self.assertIs(_is_valid_landline(1938814933), False) # When landline phone doesn't contain only digits, returns False self.assertIs(_is_valid_landline("(19)388149"), False) # When landline phone is an empty string, returns False self.assertIs(_is_valid_landline(""), False) # When landline phone's len is different of 10, returns False self.assertIs(_is_valid_landline("193881"), False) # When landline phone's first digit is 0, returns False self.assertIs(_is_valid_landline("0938814933"), False) # When landline phone's second digit is 0, returns False self.assertIs(_is_valid_landline("1038814933"), False) # When landline phone's third digit is different of 2,3,4 or 5, # returns False self.assertIs(_is_valid_landline("1998814933"), False) # When landline phone is valid self.assertIs(_is_valid_landline("1928814933"), True) self.assertIs(_is_valid_landline("1938814933"), True) self.assertIs(_is_valid_landline("1948814933"), True) self.assertIs(_is_valid_landline("1958814933"), True) self.assertIs(_is_valid_landline("3333333333"), True) class TestIsValidMobile(TestCase): def test__is_valid_mobile(self): # When mobile is not string, returns False self.assertIs(_is_valid_mobile(1), False) # When mobile doesn't contain only digits, returns False self.assertIs(_is_valid_mobile(119940 - 2927), False) # When mobile is an empty string, returns False self.assertIs(_is_valid_mobile(""), False) # When mobile's len is different of 11, returns False self.assertIs(_is_valid_mobile("119940"), False) # When mobile's first digit is 0, returns False self.assertIs(_is_valid_mobile("01994029275"), False) # When mobile's second digit is 0, returns False self.assertIs(_is_valid_mobile("90994029275"), False) # When mobile's third digit is different of 9, returns False self.assertIs(_is_valid_mobile("11594029275"), False) # When mobile is valid self.assertIs(_is_valid_mobile("99999999999"), True) self.assertIs(_is_valid_mobile("11994029275"), True) if __name__ == "__main__": main() brutils-2.5.0/tests/test_cpf.py0000664000000000000000000000727715221040537015241 0ustar rootrootfrom unittest import TestCase, main from unittest.mock import patch from brutils.cpf import ( _checksum, _hashdigit, display, format_cpf, generate, is_valid, remove_symbols, sieve, validate, ) class TestCPF(TestCase): def test_sieve(self): self.assertEqual(sieve("00000000000"), "00000000000") self.assertEqual(sieve("123.456.789-10"), "12345678910") self.assertEqual(sieve("134..2435.-1892.-"), "13424351892") self.assertEqual(sieve("abc1230916*!*&#"), "abc1230916*!*&#") self.assertEqual( sieve("ab.c1.--.2-309.-1-.6-.*.-!*&#"), "abc1230916*!*&#" ) self.assertEqual(sieve("...---..."), "") def test_display(self): self.assertEqual(display("00000000011"), "000.000.000-11") self.assertIsNone(display("00000000000")) self.assertIsNone(display("0000000000a")) self.assertIsNone(display("000000000000")) def test_validate(self): self.assertIs(validate("52513127765"), True) self.assertIs(validate("52599927765"), True) self.assertIs(validate("00000000000"), False) def test_is_valid(self): # When cpf is not string, returns False self.assertIs(is_valid(1), False) # When cpf's len is different of 11, returns False self.assertIs(is_valid("1"), False) # When cpf does not contain only digits, returns False self.assertIs(is_valid("1112223334-"), False) # When CPF has only the same digit, returns false self.assertIs(is_valid("11111111111"), False) # When rest_1 is lt 2 and the 10th digit is not 0, returns False self.assertIs(is_valid("11111111215"), False) # When rest_1 is gte 2 and the 10th digit is not (11 - rest), returns # False self.assertIs(is_valid("11144477705"), False) # When rest_2 is lt 2 and the 11th digit is not 0, returns False self.assertIs(is_valid("11111111204"), False) # When rest_2 is gte 2 and the 11th digit is not (11 - rest), returns # False self.assertIs(is_valid("11144477732"), False) # When cpf is valid self.assertIs(is_valid("11144477735"), True) self.assertIs(is_valid("11111111200"), True) def test_generate(self): for _ in range(10_000): self.assertIs(validate(generate()), True) self.assertIsNotNone(display(generate())) def test__hashdigit(self): self.assertEqual(_hashdigit("000000000", 10), 0) self.assertEqual(_hashdigit("0000000000", 11), 0) self.assertEqual(_hashdigit("52513127765", 10), 6) self.assertEqual(_hashdigit("52513127765", 11), 5) def test_checksum(self): self.assertEqual(_checksum("000000000"), "00") self.assertEqual(_checksum("525131277"), "65") @patch("brutils.cpf.sieve") class TestRemoveSymbols(TestCase): def test_remove_symbols(self, mock_sieve): # When call remove_symbols, it calls sieve remove_symbols("123.456.789-10") mock_sieve.assert_called() @patch("brutils.cpf.is_valid") class TestIsValidToFormat(TestCase): def test_when_cpf_is_valid_returns_true_to_format(self, mock_is_valid): mock_is_valid.return_value = True # When cpf is_valid, returns formatted cpf self.assertEqual(format_cpf("11144477735"), "111.444.777-35") # Checks if function is_valid_cpf is called mock_is_valid.assert_called_once_with("11144477735") def test_when_cpf_is_not_valid_returns_none(self, mock_is_valid): mock_is_valid.return_value = False # When cpf isn't valid, returns None self.assertIsNone(format_cpf("11144477735")) if __name__ == "__main__": main() brutils-2.5.0/tests/test_legal_nature.py0000664000000000000000000000547715221040537017133 0ustar rootrootfrom unittest import TestCase, main from brutils.legal_nature import ( LEGAL_NATURE, get_description, is_valid, list_all, ) class TestNaturezaJuridica(TestCase): def test_is_valid_non_string(self): # When input is not string, returns False self.assertIs(is_valid(1234), False) self.assertIs(is_valid(None), False) self.assertIs(is_valid([]), False) self.assertIs(is_valid({}), False) def test_is_valid_invalid_patterns(self): # Wrong length after normalization or contains no digits self.assertIs(is_valid(""), False) self.assertIs(is_valid("20"), False) self.assertIs(is_valid("20623"), False) self.assertIs(is_valid("abcd"), False) self.assertIs(is_valid("---"), False) def test_is_valid_formats_hyphen_and_plain(self): # Accept both "NNNN" and "NNN-N" formats self.assertTrue(is_valid("2062")) self.assertTrue(is_valid("206-2")) self.assertTrue(is_valid("101-5")) self.assertTrue(is_valid("1015")) def test_is_valid_known_codes_true(self): # A few known valid codes from different sections for code in ( "1015", "2062", "2143", "2305", "3034", "3131", "3212", "4014", "5002", ): self.assertTrue(is_valid(code)) def test_get_description_known(self): self.assertEqual( get_description("2062"), "Sociedade Empresária Limitada" ) self.assertEqual( get_description("101-5"), "Órgão Público do Poder Executivo Federal" ) self.assertEqual(get_description("2143"), "Cooperativa") self.assertEqual( get_description("5002"), "Organização Internacional e Outras Instituições Extraterritoriais", ) def test_get_description_invalid(self): self.assertIsNone(get_description("9999")) self.assertIsNone(get_description("0000")) self.assertIsNone(get_description("20A2")) self.assertIsNone(get_description(None)) # type: ignore[arg-type] def test_table_integrity(self): # Dictionary should contain only 4-digit string keys and string descriptions for k, v in LEGAL_NATURE.items(): self.assertIsInstance(k, str) self.assertTrue( k.isdigit() and len(k) == 4, msg=f"Invalid key: {k}" ) self.assertIsInstance(v, str) self.assertTrue(len(v) > 0) def test_list_all_returns_copy(self): data = list_all() self.assertEqual(data["2062"], "Sociedade Empresária Limitada") data["2062"] = "X" self.assertEqual(LEGAL_NATURE["2062"], "Sociedade Empresária Limitada") if __name__ == "__main__": main() brutils-2.5.0/tests/ibge/0000775000000000000000000000000015221040537013751 5ustar rootrootbrutils-2.5.0/tests/ibge/test_uf.py0000664000000000000000000001204515221040537015776 0ustar rootrootfrom unittest import TestCase from brutils.ibge.uf import ( convert_code_to_uf, convert_name_to_uf, convert_uf_to_name, ) class TestUF(TestCase): def test_convert_code_to_uf_valid(self): test_cases = [ ("12", "AC"), ("27", "AL"), ("16", "AP"), ("13", "AM"), ("29", "BA"), ("23", "CE"), ("53", "DF"), ("32", "ES"), ("52", "GO"), ("21", "MA"), ("51", "MT"), ("50", "MS"), ("31", "MG"), ("15", "PA"), ("25", "PB"), ("41", "PR"), ("26", "PE"), ("22", "PI"), ("33", "RJ"), ("24", "RN"), ("43", "RS"), ("11", "RO"), ("14", "RR"), ("42", "SC"), ("35", "SP"), ("28", "SE"), ("17", "TO"), ] for code, expected_uf in test_cases: with self.subTest(code=code): self.assertEqual(convert_code_to_uf(code), expected_uf) def test_convert_code_to_uf_invalid(self): invalid_codes = ["99", "00", "", "AB", "1", "123"] for invalid_code in invalid_codes: with self.subTest(code=invalid_code): self.assertIsNone(convert_code_to_uf(invalid_code)) def test_convert_uf_to_name_valid(self): test_cases = [ ("SP", "São Paulo"), ("RJ", "Rio de Janeiro"), ("MG", "Minas Gerais"), ("DF", "Distrito Federal"), ("BA", "Bahia"), ("RS", "Rio Grande do Sul"), ] for uf, expected_name in test_cases: with self.subTest(uf=uf): self.assertEqual(convert_uf_to_name(uf), expected_name) def test_convert_uf_to_name_case_insensitive(self): test_cases = [ ("sp", "São Paulo"), ("df", "Distrito Federal"), ("Rj", "Rio de Janeiro"), ] for uf, expected_name in test_cases: with self.subTest(uf=uf): self.assertEqual(convert_uf_to_name(uf), expected_name) def test_convert_uf_to_name_invalid(self): invalid_ufs = ["XX", "XXX", "", "A", "123", " "] for invalid_uf in invalid_ufs: with self.subTest(uf=invalid_uf): self.assertIsNone(convert_uf_to_name(invalid_uf)) def test_convert_name_to_uf_all_states(self): test_cases = [ ("Acre", "AC"), ("Alagoas", "AL"), ("Amapá", "AP"), ("Amazonas", "AM"), ("Bahia", "BA"), ("Ceará", "CE"), ("Distrito Federal", "DF"), ("Espírito Santo", "ES"), ("Goiás", "GO"), ("Maranhão", "MA"), ("Mato Grosso", "MT"), ("Mato Grosso do Sul", "MS"), ("Minas Gerais", "MG"), ("Pará", "PA"), ("Paraíba", "PB"), ("Paraná", "PR"), ("Pernambuco", "PE"), ("Piauí", "PI"), ("Rio de Janeiro", "RJ"), ("Rio Grande do Norte", "RN"), ("Rio Grande do Sul", "RS"), ("Rondônia", "RO"), ("Roraima", "RR"), ("Santa Catarina", "SC"), ("São Paulo", "SP"), ("Sergipe", "SE"), ("Tocantins", "TO"), ] for state_name, expected_uf in test_cases: with self.subTest(state_name=state_name): self.assertEqual(convert_name_to_uf(state_name), expected_uf) def test_convert_name_to_uf_normalization(self): test_cases = [ ("sao paulo", "SP"), ("SAO PAULO", "SP"), ("SãO pAuLo", "SP"), (" Rio de Janeiro ", "RJ"), ("ceara", "CE"), ("PARANÁ", "PR"), ("parana", "PR"), ] for state_name, expected_uf in test_cases: with self.subTest(state_name=state_name): self.assertEqual(convert_name_to_uf(state_name), expected_uf) def test_convert_name_to_uf_wrong_spacing(self): test_cases = [ ("Sao Paulo", "SP"), ("Rio de Janeiro", "RJ"), ("Mato Grosso", "MT"), ("Rio Grande do Sul", "RS"), ("Mato Grosso do Sul", "MS"), ("Espírito Santo", "ES"), ("Rio Grande do Norte", "RN"), ] for state_name, expected_uf in test_cases: with self.subTest(state_name=state_name): self.assertEqual(convert_name_to_uf(state_name), expected_uf) def test_convert_name_to_uf_invalid_input(self): invalid_inputs = [ None, "", " ", "Estado Inválido", "São Pauloo", "Rio", "Brasil", "XYZ", 123, [], {}, ] for invalid_input in invalid_inputs: with self.subTest(input=repr(invalid_input)): self.assertIsNone(convert_name_to_uf(invalid_input)) brutils-2.5.0/tests/ibge/__init__.py0000664000000000000000000000000015221040537016050 0ustar rootrootbrutils-2.5.0/tests/ibge/test_municipality.py0000664000000000000000000003621515221040537020100 0ustar rootrootimport gzip from json import JSONDecodeError, dumps from unittest import TestCase, main from unittest.mock import MagicMock, patch from urllib.error import HTTPError from brutils.ibge.municipality import ( _get_values, _is_empty, _transform_text, get_code_by_municipality_name, get_municipality_by_code, ) IBGE_BASE_URL = "https://servicodados.ibge.gov.br/api/v1/localidades" VALID_MUNICIPALITY_DATA = { "SAO_PAULO": { "nome": "São Paulo", "microrregiao": {"mesorregiao": {"UF": {"sigla": "SP"}}}, "code": "3550308", }, "RIO_DE_JANEIRO": { "nome": "Rio de Janeiro", "microrregiao": {"mesorregiao": {"UF": {"sigla": "RJ"}}}, "code": "3304557", }, "GOIANIA": { "nome": "Goiânia", "microrregiao": {"mesorregiao": {"UF": {"sigla": "GO"}}}, "code": "5208707", }, } class TestIBGE(TestCase): """Test suite for IBGE municipality functions.""" def _create_mock_response(self, data, encoding=None): """Helper method to create mock HTTP responses.""" mock_file = MagicMock() mock_file.__enter__.return_value = mock_file mock_file.read.return_value = data mock_file.info.return_value = {"Content-Encoding": encoding} return mock_file @patch("brutils.ibge.municipality.urlopen") def test_get_municipality_by_code_success(self, mock): """Test successful municipality retrieval by code.""" def mock_response(url): responses = { f"{IBGE_BASE_URL}/municipios/{VALID_MUNICIPALITY_DATA['SAO_PAULO']['code']}": dumps( VALID_MUNICIPALITY_DATA["SAO_PAULO"] ).encode("utf-8"), f"{IBGE_BASE_URL}/municipios/{VALID_MUNICIPALITY_DATA['RIO_DE_JANEIRO']['code']}": dumps( VALID_MUNICIPALITY_DATA["RIO_DE_JANEIRO"] ).encode("utf-8"), f"{IBGE_BASE_URL}/municipios/{VALID_MUNICIPALITY_DATA['GOIANIA']['code']}": dumps( VALID_MUNICIPALITY_DATA["GOIANIA"] ).encode("utf-8"), f"{IBGE_BASE_URL}/municipios/1234567": b"[]", } return self._create_mock_response(responses.get(url, b"[]")) mock.side_effect = mock_response self.assertEqual( get_municipality_by_code( VALID_MUNICIPALITY_DATA["SAO_PAULO"]["code"] ), ("São Paulo", "SP"), ) self.assertEqual( get_municipality_by_code( VALID_MUNICIPALITY_DATA["RIO_DE_JANEIRO"]["code"] ), ("Rio de Janeiro", "RJ"), ) self.assertEqual( get_municipality_by_code( VALID_MUNICIPALITY_DATA["GOIANIA"]["code"] ), ("Goiânia", "GO"), ) self.assertIsNone(get_municipality_by_code("1234567")) @patch("brutils.ibge.municipality.urlopen") def test_get_municipality_by_code_http_error_404(self, mock): """Test HTTP 404 error handling.""" mock.side_effect = HTTPError( "http://fakeurl.com", 404, "Not Found", None, None ) result = get_municipality_by_code("342432") self.assertIsNone(result) @patch("brutils.ibge.municipality.urlopen") def test_get_municipality_by_code_http_error_401(self, mock): """Test HTTP 401 error handling.""" mock.side_effect = HTTPError( "http://fakeurl.com", 401, "Denied", None, None ) result = get_municipality_by_code("342432") self.assertIsNone(result) @patch("brutils.ibge.municipality.urlopen") def test_get_municipality_by_code_generic_exception(self, mock): """Test generic exception handling.""" mock.side_effect = Exception("Erro desconhecido") result = get_municipality_by_code("342432") self.assertIsNone(result) @patch("brutils.ibge.municipality.urlopen") def test_get_municipality_by_code_gzip_success(self, mock_urlopen): """Test successful gzip decompression.""" valid_json = dumps(VALID_MUNICIPALITY_DATA["SAO_PAULO"]) compressed_data = gzip.compress(valid_json.encode("utf-8")) mock_response = self._create_mock_response(compressed_data, "gzip") mock_urlopen.return_value = mock_response result = get_municipality_by_code("3550308") self.assertEqual(result, ("São Paulo", "SP")) @patch("brutils.ibge.municipality.urlopen") def test_get_municipality_by_code_no_compression(self, mock_urlopen): """Test successful JSON parsing without compression.""" valid_json = dumps(VALID_MUNICIPALITY_DATA["SAO_PAULO"]) mock_response = self._create_mock_response(valid_json.encode("utf-8")) mock_urlopen.return_value = mock_response result = get_municipality_by_code("3550308") self.assertEqual(result, ("São Paulo", "SP")) @patch("gzip.GzipFile.read", side_effect=OSError("Erro na descompressão")) def test_get_municipality_by_code_gzip_error(self, mock_gzip_read): """Test gzip decompression error handling.""" result = get_municipality_by_code("3550308") self.assertIsNone(result) @patch( "gzip.GzipFile.read", side_effect=Exception("Erro desconhecido na descompressão"), ) def test_get_municipality_by_code_gzip_generic_error(self, mock_gzip_read): """Test generic gzip error handling.""" result = get_municipality_by_code("3550308") self.assertIsNone(result) @patch("json.loads", side_effect=JSONDecodeError("error", "city.json", 1)) def test_get_municipality_by_code_json_decode_error(self, mock_json_loads): """Test JSON decode error handling.""" result = get_municipality_by_code("3550308") self.assertIsNone(result) @patch("json.loads", side_effect=KeyError) def test_get_municipality_by_code_json_key_error(self, mock_json_loads): """Test JSON key error handling.""" result = get_municipality_by_code("3550308") self.assertIsNone(result) @patch("brutils.ibge.municipality.urlopen") def test_get_code_by_municipality_name_success(self, mock_urlopen): """Test successful municipality code retrieval by name and UF.""" def mock_response(url): mock_responses = { f"{IBGE_BASE_URL}/estados/SC/municipios": [ {"nome": "Florianópolis", "id": 4205407}, {"nome": "Aurora", "id": 4201901}, {"nome": "Lauro Müller", "id": 4209607}, ], f"{IBGE_BASE_URL}/estados/SP/municipios": [ {"nome": "São Paulo", "id": 3550308}, ], f"{IBGE_BASE_URL}/estados/RJ/municipios": [ {"nome": "Rio de Janeiro", "id": 3304557}, ], f"{IBGE_BASE_URL}/estados/GO/municipios": [ {"nome": "Goiânia", "id": 5208707}, ], f"{IBGE_BASE_URL}/estados/BA/municipios": [ {"nome": "Conceição do Coité", "id": 2908408}, ], f"{IBGE_BASE_URL}/estados/RS/municipios": [ {"nome": "Tôrres", "id": 4321501}, ], f"{IBGE_BASE_URL}/estados/CE/municipios": [ {"nome": "Aurora", "id": 2301703}, ], } response_data = mock_responses.get(url, []) json_data = dumps(response_data).encode("utf-8") return self._create_mock_response(json_data) mock_urlopen.side_effect = mock_response self.assertEqual( get_code_by_municipality_name("Florianópolis", "sc"), "4205407" ) self.assertEqual( get_code_by_municipality_name("São Paulo", "sp"), "3550308" ) self.assertEqual( get_code_by_municipality_name("rio de janeiro", "rj"), "3304557" ) self.assertEqual( get_code_by_municipality_name("GOIANIA", "GO"), "5208707" ) self.assertEqual( get_code_by_municipality_name("Conceição do Coité", "BA"), "2908408" ) self.assertEqual( get_code_by_municipality_name("conceicao do Coite", "Ba"), "2908408" ) self.assertEqual( get_code_by_municipality_name("Lauro Müller", "sc"), "4209607" ) self.assertEqual( get_code_by_municipality_name("Tôrres", "rs"), "4321501" ) self.assertEqual( get_code_by_municipality_name("aurora", "ce"), "2301703" ) self.assertEqual( get_code_by_municipality_name("aurora", "sc"), "4201901" ) def test_get_code_by_municipality_name_not_found(self): """Test cases where municipality is not found.""" self.assertIsNone( get_code_by_municipality_name("Municipio Inexistente", "RS") ) self.assertIsNone(get_code_by_municipality_name("São Paulo", "XX")) @patch("brutils.ibge.municipality.urlopen") def test_get_code_by_municipality_name_http_error_404(self, mock): """Test HTTP 404 error handling for municipality name search.""" mock.side_effect = HTTPError( "http://fakeurl.com", 404, "Not Found", None, None ) result = get_code_by_municipality_name("São Paulo", "XX") self.assertIsNone(result) @patch("brutils.ibge.municipality.urlopen") def test_get_code_by_municipality_name_http_error_500(self, mock): """Test HTTP 500 error handling for municipality name search.""" mock.side_effect = HTTPError( "http://fakeurl.com", 500, "Internal Server Error", None, None ) result = get_code_by_municipality_name("São Paulo", "SP") self.assertIsNone(result) @patch("brutils.ibge.municipality.urlopen") def test_get_code_by_municipality_name_generic_exception(self, mock): """Test generic exception handling for municipality name search.""" mock.side_effect = Exception("Network error") result = get_code_by_municipality_name("São Paulo", "SP") self.assertIsNone(result) @patch("brutils.ibge.municipality.urlopen") def test_get_code_by_municipality_name_empty_response(self, mock): """Test empty API response handling.""" mock_response = self._create_mock_response(b"[]") mock.return_value = mock_response result = get_code_by_municipality_name("São Paulo", "SP") self.assertIsNone(result) @patch("brutils.ibge.municipality.urlopen") def test_get_code_by_municipality_name_json_decode_error(self, mock): """Test JSON decode error handling.""" mock_response = self._create_mock_response(b"invalid json") mock.return_value = mock_response result = get_code_by_municipality_name("São Paulo", "SP") self.assertIsNone(result) @patch("brutils.ibge.municipality.urlopen") def test_get_code_by_municipality_name_missing_fields(self, mock): """Test response with missing required fields.""" invalid_data = dumps([{"invalid": "data"}]).encode("utf-8") mock_response = self._create_mock_response(invalid_data) mock.return_value = mock_response result = get_code_by_municipality_name("São Paulo", "SP") self.assertIsNone(result) @patch("brutils.ibge.municipality.urlopen") def test_get_code_by_municipality_name_gzip_success(self, mock): """Test successful gzip decompression for municipality name search.""" municipalities_data = [ {"nome": "São Paulo", "id": 3550308}, {"nome": "Campinas", "id": 3509502}, ] json_data = dumps(municipalities_data) compressed_data = gzip.compress(json_data.encode("utf-8")) mock_response = self._create_mock_response(compressed_data, "gzip") mock.return_value = mock_response result = get_code_by_municipality_name("São Paulo", "SP") self.assertEqual(result, "3550308") @patch("brutils.ibge.municipality.urlopen") def test_get_code_by_municipality_name_gzip_error(self, mock): """Test gzip decompression error for municipality name search.""" mock_response = self._create_mock_response(b"invalid gzip data", "gzip") mock.return_value = mock_response result = get_code_by_municipality_name("São Paulo", "SP") self.assertIsNone(result) def test_transform_text_normalization(self): """Test text normalization with accent removal and case conversion.""" self.assertEqual(_transform_text("São Paulo"), "sao paulo") self.assertEqual(_transform_text("Goiânia"), "goiania") self.assertEqual( _transform_text("Conceição do Coité"), "conceicao do coite" ) self.assertEqual(_transform_text("BRASÍLIA"), "brasilia") self.assertEqual(_transform_text("rio de janeiro"), "rio de janeiro") self.assertEqual(_transform_text("Müller"), "muller") self.assertEqual(_transform_text("Tôrres"), "torres") self.assertEqual(_transform_text("José de Freitas"), "jose de freitas") def test_transform_text_edge_cases(self): """Test edge cases for text transformation.""" self.assertEqual(_transform_text(""), "") self.assertEqual(_transform_text("A"), "a") self.assertEqual(_transform_text(" "), " ") def test_is_empty_response_detection(self): """Test detection of empty API responses.""" self.assertTrue(_is_empty(b"[]")) self.assertTrue(_is_empty(b"")) self.assertFalse(_is_empty(b"[{}]")) self.assertFalse(_is_empty(b'{"name": "test"}')) self.assertFalse(_is_empty(b"some data")) def test_get_values_success(self): """Test successful data extraction from municipality JSON.""" result = _get_values(VALID_MUNICIPALITY_DATA["SAO_PAULO"]) self.assertEqual(result, ("São Paulo", "SP")) result = _get_values(VALID_MUNICIPALITY_DATA["RIO_DE_JANEIRO"]) self.assertEqual(result, ("Rio de Janeiro", "RJ")) def test_get_values_key_errors(self): """Test KeyError handling for malformed data structures.""" with self.assertRaises(KeyError): _get_values({"invalid": "structure"}) with self.assertRaises(KeyError): _get_values({"nome": "Test"}) with self.assertRaises(KeyError): _get_values( {"nome": "Test", "microrregiao": {"invalid": "structure"}} ) def test_empty_string_parameters(self): """Test handling of empty string parameters.""" result = get_code_by_municipality_name("", "SP") self.assertIsNone(result) result = get_code_by_municipality_name("São Paulo", "") self.assertIsNone(result) def test_case_insensitive_uf_handling(self): """Test that UF parameter is case insensitive.""" result1 = get_code_by_municipality_name("São Paulo", "sp") result2 = get_code_by_municipality_name("São Paulo", "SP") result3 = get_code_by_municipality_name("São Paulo", "Sp") self.assertEqual(result1, result2) self.assertEqual(result2, result3) if __name__ == "__main__": main() brutils-2.5.0/tests/test_date_utils.py0000664000000000000000000001467615221040537016627 0ustar rootrootfrom datetime import datetime from unittest import TestCase from unittest.mock import patch from num2words import num2words from brutils import convert_date_to_text from brutils.data.enums.months import MonthsEnum from brutils.date_utils import is_holiday class TestIsHoliday(TestCase): @patch("brutils.date_utils.is_holiday") def test_feriados_validos(self, mock_is_holiday): # Testes com feriados válidos mock_is_holiday.return_value = True self.assertTrue(is_holiday(datetime(2024, 1, 1))) # Ano Novo self.assertTrue( is_holiday(datetime(2024, 7, 9), uf="SP") ) # Revolução Constitucionalista (SP) self.assertTrue( is_holiday(datetime(2024, 9, 7)) ) # Independência do Brasil self.assertTrue(is_holiday(datetime(2025, 1, 1))) # Ano Novo @patch("brutils.date_utils.is_holiday") def test_dias_normais(self, mock_is_holiday): # Testes com dias normais mock_is_holiday.return_value = False self.assertFalse(is_holiday(datetime(2024, 1, 2))) # Dia normal self.assertFalse( is_holiday(datetime(2024, 7, 9), uf="RJ") ) # Dia normal no RJ @patch("brutils.date_utils.is_holiday") def test_data_invalida(self, mock_is_holiday): # Testes com data inválida mock_is_holiday.return_value = None self.assertIsNone(is_holiday("2024-01-01")) # Formato incorreto self.assertIsNone(is_holiday(None)) # Data None @patch("brutils.date_utils.is_holiday") def test_uf_invalida(self, mock_is_holiday): # Testes com UF inválida mock_is_holiday.return_value = None self.assertIsNone( is_holiday(datetime(2024, 1, 1), uf="XX") ) # UF inválida self.assertIsNone( is_holiday(datetime(2024, 1, 1), uf="SS") ) # UF inválida @patch("brutils.date_utils.is_holiday") def test_limite_de_datas(self, mock_is_holiday): # Testes com limite de datas mock_is_holiday.return_value = True self.assertTrue(is_holiday(datetime(2024, 12, 25))) # Natal self.assertTrue( is_holiday(datetime(2024, 11, 15)) ) # Proclamação da República @patch("brutils.date_utils.is_holiday") def test_datas_depois_de_feriados(self, mock_is_holiday): # Test data after holidays mock_is_holiday.return_value = False self.assertFalse(is_holiday(datetime(2024, 12, 26))) # Não é feriado self.assertFalse(is_holiday(datetime(2025, 1, 2))) # Não é feriado @patch("brutils.date_utils.is_holiday") def test_ano_bissexto(self, mock_is_holiday): # Teste ano bissexto mock_is_holiday.return_value = False self.assertFalse( is_holiday(datetime(2024, 2, 29)) ) # Não é feriado, mas data válida # Uncomment to test non-leap year invalid date # self.assertIsNone(is_holiday(datetime(1900, 2, 29))) # Ano não bissexto, data inválida @patch("brutils.date_utils.is_holiday") def test_data_passada_futura(self, mock_is_holiday): # Teste de data passada e futura mock_is_holiday.return_value = True self.assertTrue(is_holiday(datetime(2023, 1, 1))) # Ano anterior self.assertTrue(is_holiday(datetime(2100, 12, 25))) # Ano futuro self.assertFalse( is_holiday(datetime(2100, 1, 2)) ) # Dia normal em ano futuro @patch("brutils.date_utils.is_holiday") def test_data_sem_uf(self, mock_is_holiday): # Teste feriado nacional sem UF mock_is_holiday.return_value = True self.assertTrue( is_holiday(datetime(2024, 12, 25)) ) # Natal, feriado nacional self.assertFalse( is_holiday(datetime(2024, 7, 9)) ) # Data estadual de SP, sem UF class TestNum2Words(TestCase): def test_num_conversion(self) -> None: """ Smoke test of the num2words library. This test is used to guarantee that our dependency still works. """ self.assertEqual(num2words(30, lang="pt-br"), "trinta") self.assertEqual(num2words(42, lang="pt-br"), "quarenta e dois") self.assertEqual( num2words(2024, lang="pt-br"), "dois mil e vinte e quatro" ) self.assertEqual(num2words(0, lang="pt-br"), "zero") self.assertEqual(num2words(-1, lang="pt-br"), "menos um") class TestConvertDateToText(TestCase): def test_convert_date_to_text(self): self.assertEqual( convert_date_to_text("15/08/2024"), "Quinze de agosto de dois mil e vinte e quatro", ) self.assertEqual( convert_date_to_text("01/01/2000"), "Primeiro de janeiro de dois mil", ) self.assertEqual( convert_date_to_text("31/12/1999"), "Trinta e um de dezembro de mil novecentos e noventa e nove", ) self.assertIsNone(convert_date_to_text("30/02/2020"), None) self.assertIsNone(convert_date_to_text("30/00/2020"), None) self.assertIsNone(convert_date_to_text("30/02/2000"), None) self.assertIsNone(convert_date_to_text("50/09/2000"), None) self.assertIsNone(convert_date_to_text("25/15/2000"), None) self.assertIsNone(convert_date_to_text("29/02/2019"), None) # Invalid date pattern. self.assertIsNone(convert_date_to_text("Invalid")) self.assertIsNone(convert_date_to_text("25/1/2020")) self.assertIsNone(convert_date_to_text("1924/08/20")) self.assertIsNone(convert_date_to_text("5/09/2020")) self.assertIsNone(convert_date_to_text("00/09/2020")) self.assertIsNone(convert_date_to_text("32/09/2020")) self.assertEqual( convert_date_to_text("29/02/2020"), "Vinte e nove de fevereiro de dois mil e vinte", ) self.assertEqual( convert_date_to_text("01/01/1900"), "Primeiro de janeiro de mil e novecentos", ) months_year = [ (1, "janeiro"), (2, "fevereiro"), (3, "marco"), (4, "abril"), (5, "maio"), (6, "junho"), (7, "julho"), (8, "agosto"), (9, "setembro"), (10, "outubro"), (11, "novembro"), (12, "dezembro"), ] def testMonthEnum(self): for number_month, name_month in self.months_year: month = MonthsEnum(number_month) self.assertEqual(month.month_name, name_month) brutils-2.5.0/tests/__init__.py0000664000000000000000000000000015221040537015142 0ustar rootrootbrutils-2.5.0/tests/test_imports.py0000664000000000000000000000764115221040537016161 0ustar rootrootimport importlib import inspect import pkgutil import unittest def get_public_functions(module): """Get all public functions and methods in the module.""" return [ name for name, obj in inspect.getmembers(module, inspect.isfunction) if not is_private_function(name) and inspect.getmodule(obj) == module ] + [ name for name, obj in inspect.getmembers(module, inspect.ismethod) if obj.__module__ == module.__name__ and not name.startswith("_") ] def get_imported_methods(module): """Get all names in the module's namespace.""" return [ name for name in dir(module) if not is_private_function(name) and not is_standard_function(name) ] def is_private_function(name): """Check if a function is private.""" return name.startswith("_") def is_standard_function(name): """Check if a function name is a standard or built-in function.""" return name in dir(__builtins__) or ( name.startswith("__") and name.endswith("__") ) class TestImports(unittest.TestCase): @classmethod def setUpClass(cls): """Set up the package and its __init__.py module.""" cls.package_name = "brutils" cls.package = importlib.import_module(cls.package_name) cls.init_module = importlib.import_module( f"{cls.package_name}.__init__" ) cls.all_public_methods = [] cls.imported_methods = [] # Iterate over all submodules and collect their public methods for _, module_name, _ in pkgutil.walk_packages( cls.package.__path__, cls.package.__name__ + "." ): module = importlib.import_module(module_name) cls.all_public_methods.extend(get_public_functions(module)) # Collect imported methods from __init__.py cls.imported_methods = get_imported_methods(cls.init_module) # Filter out standard or built-in functions cls.filtered_public_methods = [ method for method in cls.all_public_methods if not is_standard_function(method) ] cls.filtered_imported_methods = [ method for method in cls.imported_methods if not is_standard_function(method) ] # Remove specific old methods cls.filtered_public_methods = [ method for method in cls.filtered_public_methods if method not in {"sieve", "display", "validate"} ] # Ensure all public methods are included in __all__ cls.all_defined_names = dir(cls.init_module) cls.public_methods_in_all = getattr(cls.init_module, "__all__", []) cls.missing_imports = [ method for method in cls.filtered_public_methods if method not in cls.filtered_imported_methods ] def test_public_methods_in_imports(self): """Test that all public methods are imported or aliased.""" aliases_imports = [ method for method in self.filtered_imported_methods if method not in self.filtered_public_methods ] diff = len(self.missing_imports) - len(aliases_imports) if diff != 0: self.fail( f"{diff} public method(s) missing from imports at __init__.py. You need to import the new brutils features methods inside the brutils/__init__.py file" ) def test_public_methods_in_all(self): """Test that all public methods are included in __all__.""" missing_in_all = set(self.filtered_imported_methods) - set( self.public_methods_in_all ) if missing_in_all: self.fail( f"Public method(s) missing from __all__: {missing_in_all}. You need to add the new brutils features methods names to the list __all__ in the brutils/__init__.py file" ) if __name__ == "__main__": unittest.main() brutils-2.5.0/tests/test_cnh.py0000664000000000000000000000061715221040537015230 0ustar rootrootfrom unittest import TestCase from brutils.cnh import is_valid_cnh class TestCNH(TestCase): def test_is_valid_cnh(self): self.assertFalse(is_valid_cnh("22222222222")) self.assertFalse(is_valid_cnh("ABC70304734")) self.assertFalse(is_valid_cnh("6619558737912")) self.assertTrue(is_valid_cnh("097703047-34")) self.assertTrue(is_valid_cnh("09770304734")) brutils-2.5.0/tests/test_currency.py0000664000000000000000000001204415221040537016307 0ustar rootrootfrom decimal import Decimal from unittest import TestCase from brutils.currency import convert_real_to_text, format_currency class TestFormatCurrency(TestCase): def test_when_value_is_a_decimal_value(self): assert format_currency(Decimal("123236.70")) == "R$ 123.236,70" def test_when_value_is_a_float_value(self): assert format_currency(123236.70) == "R$ 123.236,70" def test_when_value_is_negative(self): assert format_currency(-123236.70) == "R$ -123.236,70" def test_when_value_is_zero(self): assert format_currency(0) == "R$ 0,00" def test_value_decimal_replace_rounding(self): assert format_currency(-123236.7676) == "R$ -123.236,77" def test_when_value_is_not_a_valid_currency(self): assert format_currency("not a number") is None assert format_currency("09809,87") is None assert format_currency("897L") is None class TestConvertRealToText(TestCase): def test_convert_real_to_text(self): self.assertEqual(convert_real_to_text(0.00), "Zero reais") self.assertEqual(convert_real_to_text(0.01), "Um centavo") self.assertEqual(convert_real_to_text(0.50), "Cinquenta centavos") self.assertEqual(convert_real_to_text(1.00), "Um real") self.assertEqual( convert_real_to_text(-50.25), "Menos cinquenta reais e vinte e cinco centavos", ) self.assertEqual( convert_real_to_text(1523.45), "Mil, quinhentos e vinte e três reais e quarenta e cinco centavos", ) self.assertEqual(convert_real_to_text(1000000.00), "Um milhão de reais") self.assertEqual( convert_real_to_text(2000000.00), "Dois milhões de reais" ) self.assertEqual( convert_real_to_text(1000000000.00), "Um bilhão de reais" ) self.assertEqual( convert_real_to_text(2000000000.00), "Dois bilhões de reais" ) self.assertEqual( convert_real_to_text(1000000000000.00), "Um trilhão de reais" ) self.assertEqual( convert_real_to_text(2000000000000.00), "Dois trilhões de reais" ) self.assertEqual( convert_real_to_text(1000000.45), "Um milhão de reais e quarenta e cinco centavos", ) self.assertEqual( convert_real_to_text(2000000000.99), "Dois bilhões de reais e noventa e nove centavos", ) self.assertEqual( convert_real_to_text(1234567890.50), "Um bilhão, duzentos e trinta e quatro milhões, quinhentos e sessenta e sete mil, oitocentos e noventa reais e cinquenta centavos", ) # Almost zero values self.assertEqual(convert_real_to_text(0.001), "Zero reais") self.assertEqual(convert_real_to_text(0.009), "Zero reais") # Negative milions self.assertEqual( convert_real_to_text(-1000000.00), "Menos um milhão de reais" ) self.assertEqual( convert_real_to_text(-2000000.50), "Menos dois milhões de reais e cinquenta centavos", ) # billions with cents self.assertEqual( convert_real_to_text(1000000000.01), "Um bilhão de reais e um centavo", ) self.assertEqual( convert_real_to_text(1000000000.99), "Um bilhão de reais e noventa e nove centavos", ) self.assertEqual( convert_real_to_text(999999999999.99), "Novecentos e noventa e nove bilhões, novecentos e noventa e nove milhões, novecentos e noventa e nove mil, novecentos e noventa e nove reais e noventa e nove centavos", ) # trillions with cents self.assertEqual( convert_real_to_text(1000000000000.01), "Um trilhão de reais e um centavo", ) self.assertEqual( convert_real_to_text(1000000000000.99), "Um trilhão de reais e noventa e nove centavos", ) self.assertEqual( convert_real_to_text(9999999999999.99), "Nove trilhões, novecentos e noventa e nove bilhões, novecentos e noventa e nove milhões, novecentos e noventa e nove mil, novecentos e noventa e nove reais e noventa e nove centavos", ) # 1 quadrillion self.assertEqual( convert_real_to_text(1000000000000000.00), "Um quatrilhão de reais", ) # Edge cases should return None self.assertIsNone( convert_real_to_text("invalid_value") ) # invalid value self.assertIsNone(convert_real_to_text(None)) # None value self.assertIsNone( convert_real_to_text(-1000000000000001.00) ) # less than -1 quadrillion self.assertIsNone( convert_real_to_text(-1000000000000001.00) ) # more than 1 quadrillion self.assertIsNone(convert_real_to_text(float("inf"))) # Infinity self.assertIsNone( convert_real_to_text(float("nan")) ) # Not a number (NaN) brutils-2.5.0/poetry.lock0000664000000000000000000005422615221040537014106 0ustar rootroot# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. [[package]] name = "coverage" version = "7.14.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["main", "test"] files = [ {file = "coverage-7.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3e3680291c4a1d0dadfa84a2c459576a4af5133abb617905714339a0c73138cf"}, {file = "coverage-7.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5274669f37f2343635a347b91a60777621341ab3378e9c6ac9335eee704bddf"}, {file = "coverage-7.14.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfe5a5fec635799ef33428f1e5e61bafa45a92a96190ba731561ba558ccc214d"}, {file = "coverage-7.14.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:62a9f70b52e0b5a95cfef4a5c5641b06983cadc5e538a3feeb5c00211f523ac2"}, {file = "coverage-7.14.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c18ebc343e15be53049b3a2dce38fe82d58f37e20ab9094b3a39c0aa4f6bb47"}, {file = "coverage-7.14.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b84ffdf877644e7096aa936991efeed873f7f3df57b9cd001312b7668ab08550"}, {file = "coverage-7.14.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e854312c4103f2ad4c0dc023b69b77ebfd2c89db5f86c4c94dc2353f9a92167e"}, {file = "coverage-7.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c643734307300234fafa36bf2a040a7235f8f177ea1fd6ec1423aea6fb7b929f"}, {file = "coverage-7.14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84ac9499e48700399a5dd0ea7085b5091961fec52c68d66b4ec0d3cf7f4441b1"}, {file = "coverage-7.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7f02d09f70776579b926d889a4c9c235070a1f47c40458aeaca563fae5acfdb5"}, {file = "coverage-7.14.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ce66d8e46da2bb5ee313a745cbd2e391d319176c1f7a9451bfcd3a2fb920859b"}, {file = "coverage-7.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c912c259304cfb5ee584481cfb7ce1ff932b4d61e6c9140b8f19cb7b5ed82332"}, {file = "coverage-7.14.1-cp310-cp310-win32.whl", hash = "sha256:1238cb94638e610e972c60dac68e813f868dc7d6e982535270558443058d9d59"}, {file = "coverage-7.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:fc459e5d73be2d6332fcfe8dbf3d8994671fe33c700f4565988ecfa511547253"}, {file = "coverage-7.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f"}, {file = "coverage-7.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4"}, {file = "coverage-7.14.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1"}, {file = "coverage-7.14.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f"}, {file = "coverage-7.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129"}, {file = "coverage-7.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860"}, {file = "coverage-7.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c"}, {file = "coverage-7.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7"}, {file = "coverage-7.14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec"}, {file = "coverage-7.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef"}, {file = "coverage-7.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df"}, {file = "coverage-7.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9"}, {file = "coverage-7.14.1-cp311-cp311-win32.whl", hash = "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548"}, {file = "coverage-7.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e"}, {file = "coverage-7.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3"}, {file = "coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c"}, {file = "coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c"}, {file = "coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b"}, {file = "coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6"}, {file = "coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37"}, {file = "coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad"}, {file = "coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84"}, {file = "coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54"}, {file = "coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7"}, {file = "coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9"}, {file = "coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02"}, {file = "coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a"}, {file = "coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1"}, {file = "coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e"}, {file = "coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a"}, {file = "coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793"}, {file = "coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d"}, {file = "coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247"}, {file = "coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d"}, {file = "coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b"}, {file = "coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be"}, {file = "coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43"}, {file = "coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901"}, {file = "coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff"}, {file = "coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4"}, {file = "coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d"}, {file = "coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33"}, {file = "coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c"}, {file = "coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416"}, {file = "coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42"}, {file = "coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d"}, {file = "coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5"}, {file = "coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52"}, {file = "coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a"}, {file = "coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a"}, {file = "coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2"}, {file = "coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e"}, {file = "coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d"}, {file = "coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb"}, {file = "coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d"}, {file = "coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69"}, {file = "coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54"}, {file = "coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1"}, {file = "coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce"}, {file = "coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1"}, {file = "coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee"}, {file = "coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500"}, {file = "coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906"}, {file = "coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42"}, {file = "coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8"}, {file = "coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851"}, {file = "coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034"}, {file = "coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c"}, {file = "coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36"}, {file = "coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5"}, {file = "coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4"}, {file = "coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d"}, {file = "coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee"}, {file = "coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7"}, {file = "coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343"}, {file = "coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1"}, {file = "coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b"}, {file = "coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474"}, {file = "coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86"}, {file = "coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e"}, {file = "coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65"}, {file = "coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e"}, {file = "coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8"}, {file = "coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07"}, {file = "coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de"}, {file = "coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890"}, {file = "coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd"}, {file = "coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e"}, {file = "coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c"}, {file = "coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af"}, {file = "coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2"}, {file = "coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be"}, ] [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" optional = false python-versions = "*" groups = ["main"] files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] [[package]] name = "holidays" version = "0.98" description = "Open World Holidays Framework" optional = false python-versions = ">=3.10" groups = ["main"] files = [ {file = "holidays-0.98-py3-none-any.whl", hash = "sha256:65c9ba3402f1d10cc715af0510cdc847e60d8854df640515d546748ca4a24e60"}, {file = "holidays-0.98.tar.gz", hash = "sha256:09b078a4695d53a35ea01517b331942f3232b3b7e3877f74f6d5227adbedc76e"}, ] [package.dependencies] python-dateutil = ">=2.9.0.post0,<3" [[package]] name = "num2words" version = "0.5.14" description = "Modules to convert numbers to words. Easily extensible." optional = false python-versions = "*" groups = ["main"] files = [ {file = "num2words-0.5.14-py3-none-any.whl", hash = "sha256:1c8e5b00142fc2966fd8d685001e36c4a9911e070d1b120e1beb721fa1edb33d"}, {file = "num2words-0.5.14.tar.gz", hash = "sha256:b066ec18e56b6616a3b38086b5747daafbaa8868b226a36127e0451c0cf379c6"}, ] [package.dependencies] docopt = ">=0.6.2" [[package]] name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] six = ">=1.5" [[package]] name = "ruff" version = "0.15.17" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f"}, {file = "ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7"}, {file = "ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d"}, {file = "ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed"}, {file = "ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e"}, {file = "ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d"}, {file = "ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c"}, {file = "ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978"}, {file = "ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719"}, {file = "ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4"}, {file = "ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7"}, {file = "ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f"}, {file = "ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f"}, {file = "ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8"}, {file = "ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392"}, {file = "ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084"}, {file = "ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456"}, {file = "ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219"}, ] [[package]] name = "six" version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [metadata] lock-version = "2.1" python-versions = "^3.10" content-hash = "c12318b582f7c2faa3c477e586b50d834f5b88bd83599845a340145970e7af12" brutils-2.5.0/CODE_OF_CONDUCT.md0000664000000000000000000001414115221040537014501 0ustar rootroot # Código de Conduta de Colaboração ## Nosso compromisso Como participantes, colaboradoras e líderes, nós nos comprometemos a fazer com que a participação em nossa comunidade seja uma experiência livre de assédio para todas as pessoas, independentemente de idade, tamanho do corpo, deficiência aparente ou não aparente, etnia, características sexuais, identidade ou expressão de gênero, nível de experiência, educação, situação sócio-econômica, nacionalidade, aparência pessoal, raça, casta, religião ou identidade e orientação sexuais. Comprometemo-nos a agir e interagir de maneiras que contribuam para uma comunidade aberta, acolhedora, diversificada, inclusiva e saudável. ## Nossos padrões Exemplos de comportamentos que contribuem para criar um ambiente positivo para a nossa comunidade incluem: * Demonstrar empatia e bondade com as outras pessoas * Respeitar opiniões, pontos de vista e experiências contrárias * Dar e receber feedbacks construtivos de maneira respeitosa * Assumir responsabilidade, pedir desculpas às pessoas afetadas por nossos erros e aprender com a experiência * Focar no que é melhor não só para nós individualmente, mas para a comunidade em geral Exemplos de comportamentos inaceitáveis incluem: * Uso de linguagem ou imagens sexualizadas, bem como o assédio sexual ou de qualquer natureza * Comentários insultuosos/depreciativos e ataques pessoais ou políticos (Trolling) * Assédio público ou privado * Publicar informações particulares de outras pessoas, como um endereço de e-mail ou endereço físico, sem a permissão explícita delas * Outras condutas que são normalmente consideradas inapropriadas em um ambiente profissional ## Aplicação das nossas responsabilidades A liderança da comunidade é responsável por esclarecer e aplicar nossos padrões de comportamento aceitáveis e tomará ações corretivas apropriadas e justas em resposta a qualquer comportamento que considerar impróprio, ameaçador, ofensivo ou problemático. A liderança da comunidade tem o direito e a responsabilidade de remover, editar ou rejeitar comentários, commits, códigos, edições na wiki, erros e outras contribuições que não estão alinhadas com este Código de Conduta e irá comunicar as razões por trás das decisões da moderação quando for apropriado. ## Escopo Este Código de Conduta se aplica dentro de todos os espaços da comunidade e também se aplica quando uma pessoa estiver representando oficialmente a comunidade em espaços públicos. Exemplos de representação da nossa comunidade incluem usar um endereço de e-mail oficial, postar em contas oficiais de mídias sociais ou atuar como uma pessoa indicada como representante em um evento online ou offline. ## Aplicação Ocorrências de comportamentos abusivos, de assédio ou que sejam inaceitáveis por qualquer outro motivo poderão ser reportadas para a liderança da comunidade, responsável pela aplicação, via contato cmaiacd@gmail.com ou mdeazevedomaia@gmail.com. Todas as reclamações serão revisadas e investigadas imediatamente e de maneira justa. A liderança da comunidade tem a obrigação de respeitar a privacidade e a segurança de quem reportar qualquer incidente. ## Diretrizes de aplicação A liderança da comunidade seguirá estas Diretrizes de Impacto na Comunidade para determinar as consequências de qualquer ação que considerar violadora deste Código de Conduta: ### 1. Ação Corretiva **Impacto na comunidade**: Uso de linguagem imprópria ou outro comportamento considerado anti-profissional ou repudiado pela comunidade. **Consequência**: Aviso escrito e privado da liderança da comunidade, esclarecendo a natureza da violação e com a explicação do motivo pelo qual o comportamento era impróprio. Um pedido de desculpas público poderá ser solicitado. ### 2. Advertência **Impacto na comunidade**: Violação por meio de um incidente único ou atitudes repetidas. **Consequência**: Advertência com consequências para comportamento repetido. Não poderá haver interações com as pessoas envolvidas, incluindo interações não solicitadas com as pessoas que estiverem aplicando o Código de Conduta, por um período determinado. Isto inclui evitar interações em espaços da comunidade, bem como canais externos como as mídias sociais. A violação destes termos pode levar a um banimento temporário ou permanente. ### 3. Banimento Temporário **Impacto na comunidade**: Violação grave dos padrões da comunidade, incluindo a persistência do comportamento impróprio. **Consequência**: Banimento temporário de qualquer tipo de interação ou comunicação pública com a comunidade por um determinado período. Estarão proibidas as interações públicas ou privadas com as pessoas envolvidas, incluindo interações não solicitadas com as pessoas que estiverem aplicando o Código de Conduta. A violação destes termos pode resultar em um banimento permanente. ### 4. Banimento Permanente **Impacto na comunidade**: Demonstrar um padrão na violação das normas da comunidade, incluindo a persistência do comportamento impróprio, assédio a uma pessoa ou agressão ou depreciação a classes de pessoas. **Consequência**: Banimento permanente de qualquer tipo de interação pública dentro da comunidade. ## Atribuição Este Código de Conduta é adaptado do [Contributor Covenant][homepage], versão 2.1, disponível em [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. As Diretrizes de Impacto na Comunidade foram inspiradas pela [Aplicação do código de conduta Mozilla][Mozilla CoC]. Para obter respostas a perguntas comuns sobre este código de conduta, veja a página de Perguntas Frequentes (FAQ) em [https://www.contributor-covenant.org/faq][FAQ]. Traduções estão disponíveis em [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations brutils-2.5.0/MAINTAINING.md0000664000000000000000000001710315221040537013763 0ustar rootroot# Prompt ChatGPT para Criar Issues ## Exemplo: formatar moeda brasileira - Criar uma issue para o repositório brutils-python. - Issue: formatar moeda brasileira - nome da função: format_brl - entrada: float - saída: string formatada - caso entrada seja inválida, retornar None - Não implementar a lógica da função. Apenas deixar a docstring e um comentário `# implementar a lógica da função aqui` - Considerar o maior número de edge cases possíveis - Criar testes unitários para todos os edge cases - Estes são os utilitários já existentes na lib: ```md - [CPF](#cpf) - [is\_valid\_cpf](#is_valid_cpf) - [format\_cpf](#format_cpf) - [remove\_symbols\_cpf](#remove_symbols_cpf) - [generate\_cpf](#generate_cpf) - [CNPJ](#cnpj) - [is\_valid\_cnpj](#is_valid_cnpj) - [format\_cnpj](#format_cnpj) - [remove\_symbols\_cnpj](#remove_symbols_cnpj) - [generate\_cnpj](#generate_cnpj) - [CEP](#cep) - [is\_valid\_cep](#is_valid_cep) - [format\_cep](#format_cep) - [remove\_symbols\_cep](#remove_symbols_cep) - [generate\_cep](#generate_cep) - [get\_address\_from\_cep](#get_address_from_cep) - [get\_cep\_information\_from\_address](#get_cep_information_from_address) - [Telefone](#telefone) - [is\_valid\_phone](#is_valid_phone) - [format\_phone](#format_phone) - [remove\_symbols\_phone](#remove_symbols_phone) - [remove\_international\_dialing\_code](#remove_international_dialing_code) - [generate\_phone](#generate_phone) - [Email](#email) - [is\_valid\_email](#is_valid_email) - [Placa de Carro](#placa-de-carro) - [is\_valid\_license\_plate](#is_valid_license_plate) - [format\_license\_plate](#format_license_plate) - [remove\_symbols\_license\_plate](#remove_symbols_license_plate) - [generate\_license\_plate](#generate_license_plate) - [convert\_license\_plate\_to\_mercosul](#convert_license_plate_to_mercosul) - [get\_format\_license\_plate](#get_format_license_plate) - [PIS](#pis) - [is\_valid\_pis](#is_valid_pis) - [format\_pis](#format_pis) - [remove\_symbols\_pis](#remove_symbols_pis) - [generate\_pis](#generate_pis) - [Processo Jurídico](#processo-jurídico) - [is\_valid\_legal\_process](#is_valid_legal_process) - [format\_legal\_process](#format_legal_process) - [remove\_symbols\_legal\_process](#remove_symbols_legal_process) - [generate\_legal\_process](#generate_legal_process) - [Título Eleitoral](#titulo-eleitoral) - [is_valid_voter_id](#is_valid_voter_id) - [format_voter_id](#format_voter_id) - [generate_voter_id](#generate_voter_id) - [IBGE](#ibge) - [convert_code_to_uf](#convert_code_to_uf) ``` - Seguindo exatamento o mesmo modelo dessa issue: ```md Título da Issue: Conversão de Nome de Estado para UF **Seu pedido de recurso está relacionado a um problema? Por favor, descreva.** Dado o nome completo de um estado brasileiro, quero obter o código de Unidade Federativa (UF) correspondente. Isso é útil para conversão de nomes completos de estados em siglas utilizadas em sistemas e documentos. Por exemplo, converter `"São Paulo"` para `"SP"`. **Descreva a solução que você gostaria** * Uma função `convert_text_to_uf`, que recebe o nome completo do estado (string) e retorna o código UF correspondente. * A função deve ignorar maiúsculas e minúsculas, e também deve desconsiderar acentos e o caractere especial ç (considerando c também). * A função deve verificar se o nome completo é válido e retornar o código UF correspondente. * Se o nome completo não for válido, a função deve retornar `None`. * A função deve lidar com todos os estados e o Distrito Federal do Brasil. * A lista das UFs e seus nomes completos já existe no arquivo `brutils/data/enums/uf.py`. Ela deve ser reutilizada. **Descreva alternativas que você considerou** 1. Seguir até o passo 8 do [guia de contribuição](https://github.com/brazilian-utils/brutils-python/blob/main/CONTRIBUTING.md#primeira-contribui%C3%A7%C3%A3o). 2. Como parte do passo 8, criar o arquivo: `brutils-python/brutils/ibge/uf.py`. ```python def convert_text_to_uf(state_name): # type: (str) -> str | None """ Converts a given Brazilian state full name to its corresponding UF code. This function takes the full name of a Brazilian state and returns the corresponding 2-letter UF code. It handles all Brazilian states and the Federal District. Args: state_name (str): The full name of the state to be converted. Returns: str or None: The UF code corresponding to the full state name, or None if the full state name is invalid. Example: >>> convert_text_to_uf('São Paulo') "SP" >>> convert_text_to_uf('Rio de Janeiro') "RJ" >>> convert_text_to_uf('Minas Gerais') "MG" >>> convert_text_to_uf('Distrito Federal') "DF" >>> convert_text_to_uf('Estado Inexistente') None """ # implementar a lógica da função aqui ``` Importar a nova função no arquivo `brutils-python/brutils/__init__.py`: ```python # UF Imports from brutils.ibge.uf import ( convert_text_to_uf, ) ``` E adicionar o nome da nova função na lista `__all__` do mesmo arquivo `brutils-python/brutils/__init__.py`: ```python __all__ = [ ... # UF 'convert_text_to_uf', ] ``` 3. Como parte do passo 9, criar o arquivo de teste: `brutils-python/tests/test_uf.py`. ```python from unittest import TestCase from brutils.ibge.uf import convert_text_to_uf class TestUF(TestCase): def test_convert_text_to_uf(self): # Testes para nomes válidos self.assertEqual(convert_text_to_uf('São Paulo'), "SP") self.assertEqual(convert_text_to_uf('Rio de Janeiro'), "RJ") self.assertEqual(convert_text_to_uf('Minas Gerais'), "MG") self.assertEqual(convert_text_to_uf('Distrito Federal'), "DF") self.assertEqual(convert_text_to_uf('são paulo'), "SP") # Teste com minúsculas self.assertEqual(convert_text_to_uf('riO de janeiRo'), "RJ") # Teste com misturas de maiúsculas e minúsculas self.assertEqual(convert_text_to_uf('minas gerais'), "MG") # Teste com minúsculas self.assertEqual(convert_text_to_uf('sao paulo'), "SP") # Teste sem acento # Testes para nomes inválidos self.assertIsNone(convert_text_to_uf('Estado Inexistente')) # Nome não existe self.assertIsNone(convert_text_to_uf('')) # Nome vazio self.assertIsNone(convert_text_to_uf('123')) # Nome com números self.assertIsNone(convert_text_to_uf('São Paulo SP')) # Nome com sigla incluída self.assertIsNone(convert_text_to_uf('A')) # Nome com letra não mapeada self.assertIsNone(convert_text_to_uf('ZZZ')) # Nome com mais de 2 letras # implementar mais casos de teste aqui se necessário ``` 4. Seguir os passos seguintes do [guia de contribuição](https://github.com/brazilian-utils/brutils-python/blob/main/CONTRIBUTING.md#primeira-contribui%C3%A7%C3%A3o). **Contexto adicional** * A lista de estados e suas siglas é definida pelo Instituto Brasileiro de Geografia e Estatística (IBGE). Para mais detalhes, consulte o [site do IBGE](https://atendimento.tecnospeed.com.br/hc/pt-br/articles/360021494734-Tabela-de-C%C3%B3digo-de-UF-do-IBGE). * A função deve lidar com a normalização de texto, incluindo a remoção de acentos e a conversão para minúsculas para garantir que o texto seja comparado de forma consistente. ``` brutils-2.5.0/README.md0000664000000000000000000012274015221040537013166 0ustar rootroot![Logo do Brazilian Utils](https://github.com/brazilian-utils/brand/raw/main/github-hero/github-hero-python.png)

Biblioteca de utilitários projetada para validar, gerar e manipular dados de acordo com as particularidades do Brasil

[![codecov](https://codecov.io/gh/brazilian-utils/python/branch/main/graph/badge.svg)](https://codecov.io/gh/brazilian-utils/python) [![Check Lint](https://github.com/brazilian-utils/python/actions/workflows/check-lint.yml/badge.svg?branch=main)](https://github.com/brazilian-utils/python/actions/workflows/check-lint.yml?query=branch%3Amain) [![Downloads per Month](https://shields.io/pypi/dm/brutils)](https://pypistats.org/packages/brutils) [![Package version](https://shields.io/pypi/v/brutils)](https://pypi.org/project/brutils/) ### [Looking for the english version?](README_EN.md)
# Introdução Brazilian Utils é uma biblioteca com foco na resolução de problemas que enfrentamos diariamente no desenvolvimento de aplicações para o business Brasileiro. - [Instalação](#instalação) - [Utilização](#utilização) - [Utilitários](#utilitários) - [Novos Utilitários e Reportar Bugs](#novos-utilitários-e-reportar-bugs) - [Dúvidas? Ideias?](#dúvidas-ideias) - [Contribuindo com o Código do Projeto](#contribuindo-com-o-código-do-projeto) # Instalação ```bash pip install brutils ``` # Utilização Para usar um de nossos utilitários, basta importar a função necessária, como no exemplo abaixo: ```python >>> from brutils import is_valid_cpf >>> is_valid_cpf('00011122233') False ``` # Utilitários - [CPF](#cpf) - [is\_valid\_cpf](#is_valid_cpf) - [format\_cpf](#format_cpf) - [remove\_symbols\_cpf](#remove_symbols_cpf) - [generate\_cpf](#generate_cpf) - [CNPJ](#cnpj) - [is\_valid\_cnpj](#is_valid_cnpj) - [format\_cnpj](#format_cnpj) - [remove\_symbols\_cnpj](#remove_symbols_cnpj) - [generate\_cnpj](#generate_cnpj) - [CEP](#cep) - [is\_valid\_cep](#is_valid_cep) - [format\_cep](#format_cep) - [remove\_symbols\_cep](#remove_symbols_cep) - [generate\_cep](#generate_cep) - [get\_address\_from\_cep](#get_address_from_cep) - [get\_cep\_information\_from\_address](#get_cep_information_from_address) - [Telefone](#telefone) - [is\_valid\_phone](#is_valid_phone) - [format\_phone](#format_phone) - [remove\_symbols\_phone](#remove_symbols_phone) - [remove\_international\_dialing\_code](#remove_international_dialing_code) - [generate\_phone](#generate_phone) - [Email](#email) - [is\_valid\_email](#is_valid_email) - [Data](#date) - [convert\_date\_to_text](#convert_date_to_text) - [CNH](#cnh) - [is\_valid\_cnh](#is_valid_cnh) - [Placa de Carro](#placa-de-carro) - [is\_valid\_license\_plate](#is_valid_license_plate) - [format\_license\_plate](#format_license_plate) - [remove\_symbols\_license\_plate](#remove_symbols_license_plate) - [generate\_license\_plate](#generate_license_plate) - [convert\_license\_plate\_to\_mercosul](#convert_license_plate_to_mercosul) - [get\_format\_license\_plate](#get_format_license_plate) - [PIS](#pis) - [is\_valid\_pis](#is_valid_pis) - [format\_pis](#format_pis) - [remove\_symbols\_pis](#remove_symbols_pis) - [generate\_pis](#generate_pis) - [Processo Jurídico](#processo-jurídico) - [is\_valid\_legal\_process](#is_valid_legal_process) - [format\_legal\_process](#format_legal_process) - [remove\_symbols\_legal\_process](#remove_symbols_legal_process) - [generate\_legal\_process](#generate_legal_process) - [RENAVAM](#renavam) - [is_valid_renavam](#is_valid_renavam) - [Titulo Eleitoral](#titulo-eleitoral) - [is\_valid\_voter\_id](#is_valid_voter_id) - [format\_voter\_id](#format_voter_id) - [generate\_voter\_id](#generate_voter_id) - [IBGE](#ibge) - [convert_code_to_uf](#convert_code_to_uf) - [convert_uf_to_name](#convert_uf_to_name) - [convert_name_to_uf](#convert_name_to_uf) - [get_code_by_municipality_name](#get_code_by_municipality_name) - [get\_municipality\_by\_code](#get_municipality_by_code) - [Feriados](#feriados) - [is_holiday](#is_holiday) - [Monetário](#monetário) - [format\_currency](#format_currency) - [convert\_real\_to\_text](#convert_real_to_text) - [Natureza Jurídica](#natureza-jurídica) - [is_valid_legal_nature](#is_valid_legal_nature) - [get_legal_nature_description](#get_legal_nature_description) - [list_all_legal_nature](#list_all_legal_nature) - [Passaporte](#passaporte) - [is_valid_passport](#is_valid_passport) - [format_passport](#format_passport) - [remove_symbols_passport](#remove_symbols_passport) - [generate_passport](#generate_passport) ## CPF ### is_valid_cpf Retorna se os dígitos de verificação do CPF fornecido correspondem ao seu número base. Esta função não verifica a existência do CPF; ela apenas valida o formato da string. Argumentos: - cpf (str): O CPF a ser validado, uma string de 11 dígitos Retorna: - bool: Verdadeiro se os dígitos de verificação corresponderem ao número base, Falso caso contrário. Exemplo: ```python >>> from brutils import is_valid_cpf >>> is_valid_cpf("82178537464") True >>> is_valid_cpf('00011122233') False ``` ### format_cpf Formata um CPF (Cadastro de Pessoa Física brasileiro) para exibição visual. Esta função recebe uma string de CPF contendo apenas números como entrada e adiciona símbolos de formatação padrão para exibição. Argumentos: - cpf (str): Uma string de CPF contendo apenas números. Retorna: - str: O CPF formatado com símbolos visuais se for válido, None se não for válido. Exemplo: ```python >>> from brutils import format_cpf >>> format_cpf('82178537464') '821.785.374-64' >>> format_cpf("55550207753") '555.502.077-53' ``` ### remove_symbols_cpf Remove símbolos específicos de uma string de CPF (Cadastro de Pessoa Física brasileiro). Esta função recebe como entrada uma string de CPF e remove todas as ocorrências dos caracteres '.', '-' dela. Argumentos: - cpf (str): A string de CPF contendo os símbolos a serem removidos. Retorna: - str: Uma nova string com os símbolos especificados removidos. Exemplo: ```python >>> from brutils import remove_symbols_cpf >>> remove_symbols_cpf('000.111.222-33') '00011122233' ``` ### generate_cpf Gerar uma string de dígitos de CPF válida aleatória. Retorna: - str: Um CPF válido gerado aleatoriamente. Exemplo: ```python >>> from brutils import generate_cpf >>> generate_cpf() '17433964657' >>> generate_cpf() "10895948109" ``` ## CNPJ ### is_valid_cnpj Verifica se os dígitos de verificação do CNPJ (Cadastro Nacional da Pessoa Jurídica) fornecido correspondem ao seu número base. A entrada deve ser uma string de 14 caracteres, permitindo dígitos e letras maiúsculas nas 12 primeiras posições e dígitos nas 2 últimas. Esta função não verifica a existência do CNPJ; ela só valida o formato da string. Argumentos: - cnpj (str): O CNPJ a ser validado. Retorna: - bool: True se os dígitos de verificação corresponderem ao número base, False caso contrário. Exemplo: ```python >>> from brutils import is_valid_cnpj >>> is_valid_cnpj('03560714000142') True >>> is_valid_cnpj('00111222000133') False ``` ### format_cnpj Formata uma string de CNPJ (Cadastro Nacional da Pessoa Jurídica) para exibição visual. Esta função recebe uma string de CNPJ como entrada, valida seu formato e a formata com símbolos visuais padrão para fins de exibição. Argumentos: - cnpj (str): A string de CNPJ a ser formatada para exibição. Retorna: - str: O CNPJ formatado com símbolos visuais se for válido, None se não for válido. Exemplo: ```python >>> from brutils import format_cnpj >>> format_cnpj("03560714000142") '03.560.714/0001-42' >>> format_cnpj("98765432100100") None ``` ### remove_symbols_cnpj Remove símbolos específicos de uma string de CNPJ (Cadastro Nacional da Pessoa Jurídica). Esta função recebe uma string de CNPJ como entrada e remove todas as ocorrências dos caracteres '.', '/' e '-' dela. Argumentos: - cnpj (str): A string de CNPJ que contém os símbolos a serem removidos. Retorna: - str: Uma nova string com os símbolos especificados removidos. Exemplo: ```python >>> from brutils import remove_symbols_cnpj >>> remove_symbols_cnpj('00.111.222/0001-00') '00111222000100' ``` ### generate_cnpj Gera uma string de CNPJ válida aleatória. Um número de filial opcional pode ser fornecido; o padrão é 1. Use `alphanumeric=True` para gerar um CNPJ cujas 12 primeiras posições podem conter dígitos e letras maiúsculas. Argumentos: - branch (int | str): Um número de filial opcional a ser incluído no CNPJ. Valores de filial alfanuméricos são aceitos apenas com `alphanumeric=True`. - alphanumeric (bool): Define se o CNPJ gerado deve ser alfanumérico. Retorna: - str: Um CNPJ válido gerado aleatoriamente. Exemplo: ```python >>> from brutils import generate_cnpj >>> generate_cnpj() '34665388000161' >>> generate_cnpj(1234) "01745284123455" >>> generate_cnpj(alphanumeric=True) "9359QAG9000184" >>> generate_cnpj(branch="AB12", alphanumeric=True) "BR2026UTAB1290" >>> generate_cnpj(branch="CD34", alphanumeric=True) # CNPJ inválido para exemplos de validação: "NX9K79E2CD3400" ``` ## CEP ### is_valid_cep Verifica se um CEP (Código de Endereçamento Postal) brasileiro é válido. Para que um CEP seja considerado válido, a entrada deve ser uma string contendo exatamente 8 dígitos. Esta função não verifica se o CEP é um CEP real, pois valida apenas o formato da string. Argumentos: - cep (str): A string contendo o CEP a ser verificado. Retorno: - bool: True se o CEP for válido (8 dígitos), False caso contrário. Exemplo: ```python >>> from brutils import is_valid_cep >>> is_valid_cep('01310200') True >>> is_valid_cep("12345") False >>> is_valid_cep("abcdefgh") False ``` ### format_cep Formata um CEP (Código de Endereçamento Postal) brasileiro em um formato padrão. Esta função recebe um CEP como entrada e, se for um CEP válido com 8 dígitos, o formata no padrão "12345-678". Argumentos: - cep (str): O CEP (Código de Endereçamento Postal) de entrada a ser formatado. Retorna: - str: O CEP formatado no formato "12345-678" se for válido, None se não for válido. Example: ```python >>> from brutils import format_cep >>> format_cep('01310200') '01310-200' >>> format_cep("12345678") "12345-678" >>> format_cep("12345") None ``` ### remove_symbols_cep Remove símbolos específicos de um CEP (Código de Endereçamento Postal) fornecido. Esta função recebe um CEP como entrada e remove todas as ocorrências dos caracteres '.' e '-' dele. Argumentos: - cep (str): O CEP (Código de Endereçamento Postal) de entrada que contém os símbolos a serem removidos. Retorna: - str: Uma nova string com os símbolos especificados removidos. Exemplo: ```python >>> from brutils import remove_symbols_cep >>> remove_symbols_cep('01310-200') '01310200' >>> remove_symbols_cep("123-45.678.9") "123456789" >>> remove_symbols_cep("abc.xyz") "abcxyz" ``` ### generate_cep Gera um número de CEP (Código de Endereçamento Postal) aleatório de 8 dígitos como uma string. Retorna: - str: Um número de 8 dígitos gerado aleatoriamente. Exemplo: ```python >>> from brutils import generate_cep >>> generate_cep() '77520503' >>> generate_cep() '29641407' ``` ### get_address_from_cep Busca as informações de endereço a partir de um CEP (Código de Endereçamento Postal) utilizando a API da ViaCEP. Argumentos: - cep (str): O CEP a ser utilizado na busca. - raise_exceptions (bool, opcional): Se deve gerar exceções quando o CEP for inválido ou não for encontrado. O padrão é False. Retorna: - Address | None: Um objeto Address (TypedDict) contendo as informações de endereço se o CEP for encontrado, caso contrário, None. Exemplo: ```python >>> from brutils import get_address_from_cep >>> get_address_from_cep("12345678") { "cep": "12345-678", "logradouro": "Rua Example", "complemento": "", "bairro": "Example", "localidade": "Example", "uf": "EX", "ibge": "1234567", "gia": "1234", "ddd": "12", "siafi": "1234" } ``` ### get_cep_information_from_address Busca o CEP e outras informações a partir de um endereço utilizando a API da ViaCEP. Argumentos: - federal_unit (str): Abreviação de duas letras do estado brasileiro. - city (str): Nome da cidade. - street (str): Nome (ou substring) da rua. - raise_exceptions (bool, opcional): Se deve gerar exceções quando o endereço é inválido ou não foi encontrado. O padrão é False. Retorna: - list[Address] | None: Uma lista de objetos Address (TypedDict) contendo as informações de endereço se o endereço for encontrado, None caso contrário. Exemplo: ```python >>> from brutils import get_cep_information_from_address >>> get_cep_information_from_address("EX", "Example", "Rua Example") [ { "cep": "12345-678", "logradouro": "Rua Example", "complemento": "", "bairro": "Example", "localidade": "Example", "uf": "EX", "ibge": "1234567", "gia": "1234", "ddd": "12", "siafi": "1234" } ] ``` ## Telefone ### is_valid_phone Retorna se um número de telefone brasileiro é válido conforme o formato da string. Não verifica se o número realmente existe. ``` is_valid_phone(phone_number, type) ``` Argumentos: - phone_number (str): - o número de telefone a ser validado - apenas dígitos, sem símbolos - sem o código do país - deve incluir o número de DDD com dois dígitos - exemplo: '+55 48 9999 9999' deve ser utilizado como '4899999999' - obrigatório - type (str): - 'mobile' para validar apenas números de celular - 'landline' para validar apenas números de telefone fixo - caso não especificado, valida para um para o outro. - opcional Retorna: - bool: True se o número é válido, False caso contrário. Exemplo: ```python >>> from brutils import is_valid_phone >>> is_valid_phone('11994029275') True >>> is_valid_phone('11994029275', 'mobile') True >>> is_valid_phone('1938814933', 'landline') True ``` ### format_phone Formata um número de telefone para exibição visual. Esta função recebe uma string representando um número de telefone contendo apenas números como entrada e adiciona símbolos de formatação padrão para exibição. Argumentos: - phone (str): Uma string representando um número de telefone. Retorna: - str: O número de telefone formatado para exibição ou None se não for válido. Exemplo: ```python >>> from brutils import format_phone >>> format_phone("11994029275") '(11)99402-9275' >>> format_phone("1635014415") '(16)3501-4415' >>> format_phone("333333") None ``` ### remove_symbols_phone Remove símbolos do número de telefone. Esta função recebe um número de telefone como entrada e remove todos os símbolos, como parênteses '()', traços '-' e espaços ' '. Argumentos: - phone (str): O número de telefone de entrada que contém os símbolos a serem removidos. Retorna: - str: Uma nova string com os símbolos especificados removidos. Exemplo: ```python >>> from brutils import remove_symbols_phone >>> remove_symbols_phone('(21)2569-6969') '2125696969' >>> remove_symbols_phone('11 9999-8888') '1199998888' >>> remove_symbols_phone('333333') '333333' ``` ### remove_international_dialing_code Remove o código internacional (+55) de uma string que contém um número de telefone brasileiro, mantendo outros caracteres especiais. Argumentos: - phone (str): O número de telefone de entrada que pode conter o código internacional. Retorna: - str: Uma nova string sem o código internacional, preservando outros caracteres especiais. Exemplo: ```python >>> from brutils import remove_international_dialing_code >>> remove_international_dialing_code("5521994029275") "21994029275" >>> remove_international_dialing_code("+5521994029275") "+21994029275" >>> remove_international_dialing_code("5555994029275") "55994029275" >>> remove_international_dialing_code("21994029275") "21994029275" >>> remove_international_dialing_code("(+55)21994029275") "(+)21994029275" ``` ### generate_phone Gera um número de telefone aleatório válido. Argumentos: - type (str): Pode ser "landline" ou "mobile". Se não for especificado, a função gera um número aleatório de qualquer tipo. Retorna: - str: Um número de telefone válido gerado aleatoriamente. Exemplo: ```python >>> from brutils import generate_phone >>> generate_phone() "5929797740" >>> generate_phone("mobile") "1899115895" >>> generate_phone("landline") "5535317900" ``` ## Email ### is_valid_email Verificar se uma string corresponde a um endereço de e-mail válido. Argumentos: - email (str): A string de entrada a ser verificada. Retorna: - bool: Verdadeiro se o email for um endereço de e-mail válido, Falso caso contrário. Exemplo: ```python from brutils import is_valid_email >>> is_valid_email("joao.ninguem@gmail.com") True >>> is_valid_email(".joao.ninguem@gmail.com") False >>> is_valid_email("joao.ninguem@gmail.") False >>> is_valid_email("joao ninguem@gmail.com") False ``` ## Data ## convert_date_to_text Converte uma data em sua representação textual. Argumentos: - date (str): Uma string no formato dd/mm/aaaa Retorna: - A represetação textual da data ou None caso a data esteja mal formatada ou a data seja inválida. Exemplo: ````python >>> from brutils import convert_date_to_text >>> convert_date_to_text("25/12/2000") "Vinte e cinco de dezembro de dois mil" >>> convert_date_to_text("31/02/2000") None >>> convert_date_to_text("29/02/2024") "Vinte e nove de fevereiro de dois mil e vinte e quatro" >>> convert_date_to_text("1/08/2024") "Primeiro de agosto de dois mil e vinte e quatro" ```` ## CNH ### is_valid_cnh Verifica se o número de registro de CNH (Carteira de Habilitação Nacional) brasileiro é válido. Para que um número de CNH seja considerado válido, a entrada deve ser uma string contendo exatamente 11 dígitos numéricos. Esta função não verifica se o número da CNH é real, apenas valida os dígitos verificadores. Argumentos: - cnh (str): A string contendo o número de registro de CNH a ser verificado. Retorno: - bool: True se o número de registro da CNHN for válido (11 dígitos), False caso contrário. Exemplo: ```python >>> from brutils import is_valid_cnh >>> is_valid_cnh("12345678901") False >>> is_valid_cnh("A2C45678901") False >>> is_valid_cnh("98765432100") True >>> is_valid_cnh("987654321-00") True ``` ## Placa de Carro ### is_valid_license_plate Verifica se uma placa de carro é válida. Esta função não verifica se a placa de carro é uma placa de carro real, apenas valida o formato da string. Argumentos: - license_plate (str): Uma string representando uma placa de carro. - type (str): "old_format" ou "mercosul". Se não especificado, verifica um ou outro. Retorna: - bool: True se a placa de carro for válida, False caso contrário. Exemplo: ```python >>> from brutils import is_valid_license_plate >>> is_valid_license_plate('ABC1234') True >>> is_valid_license_plate('def5678', type="old_format") True >>> is_valid_license_plate('ABC4E67') True >>> is_valid_license_plate('ABC4E67', type="mercosul") True >>> is_valid_license_plate('GHI-4567') False ``` ### format_license_plate Formata uma placa de carro no padrão correto. Esta função recebe uma placa de carro em qualquer formato (LLLNNNN ou LLLNLNN) e retorna uma versão formatada. Argumentos: - license_plate (str): Uma string representando uma placa de carro. Retorna: - str: A string da placa de carro formatada ou 'None' se a entrada for inválida. Exemplo: ```python >>> from brutils import format_license_plate >>> format_license_plate("ABC1234") "ABC-1234" # formato antigo (contém traço) >>> format_license_plate("abc1234") "ABC-1234" # formato antigo (contém traço) >>> format_license_plate("ABC1D23") "ABC1D23" # formato mercosul >>> format_license_plate("abc1d23") "ABC1D23" # formato mercosul >>> format_license_plate("ABCD123") None ``` ### remove_symbols_license_plate Remove o símbolo de hífen (-) de uma string de placa de carro. Argumentos: - license_plate_number (str): Uma string de placa de carro contendo símbolos a serem removidos. Retorna: - str: A string da placa de carro com os símbolos especificados removidos. Exemplo: ```python from brutils import remove_symbols_license_plate >>> remove_symbols_license_plate("ABC-123") "ABC123" >>> remove_symbols_license_plate("abc123") "abc123" >>> remove_symbols_license_plate("ABCD123") "ABCD123" >>> remove_symbols_license_plate("@abc#-#123@") "@abc##123@" ``` ### generate_license_plate Gera uma placa de carro válida no formato especificado. Caso nenhum formato seja fornecido, ele retornará uma placa de carro no formato Mercosul. Argumentos: - format (str): O formato desejado para a placa de carro. 'LLLNNNN' para o formato antigo ou 'LLLNLNN' para o formato Mercosul. O padrão é 'LLLNLNN'. Retorna: - str: Um número de placa de carro gerado aleatoriamente ou None se o formato for inválido. Exemplo: ```python from brutils import generate_license_plate >>> generate_license_plate() "ABC1D23" >>> generate_license_plate(format="LLLNLNN") "ABC4D56" >>> generate_license_plate(format="LLLNNNN") "ABC123" >>> generate_license_plate(format="invalid") None ``` ### convert_license_plate_to_mercosul Converte uma placa de carro no formato antigo (LLLNNNN) para o formato Mercosul (LLLNLNN). Argumentos: - license_plate(str): Uma string com o tamanho adequado que representa a placa no formato antigo. Retorna: - str: A placa Mercosul convertida (LLLNLNN) ou None se a entrada for inválida. Exemplo: ```python >>> from brutils import convert_license_plate_to_mercosul >>> convert_license_plate_to_mercosul("ABC123") "ABC1C34" >>> convert_license_plate_to_mercosul("abc123") "ABC1C34" >>> convert_license_plate_to_mercosul("ABC1D23") None ``` ### get_format_license_plate Retorna o formato de uma placa de carro. 'LLLNNNN' para o formato antigo e 'LLLNLNN' para o formato Mercosul. Argumentos: - license_plate (str): Uma string de placa de carro sem símbolos. Retorna: - str: O formato da placa de carro (LLLNNNN, LLLNLNN) ou 'None' se o formato for inválido. Exemplo: ```python from brutils import get_format_license_plate >>> get_format_license_plate("ABC123") "LLLNNNN" >>> get_format_license_plate("abc123") "LLLNNNN" >>> get_format_license_plate("ABC1D23") "LLLNLNN" >>> get_format_license_plate("abc1d23") "LLLNLNN" >>> get_format_license_plate("ABCD123") None ``` ## PIS ### is_valid_pis Verifica se o número PIS/PASEP é valido. Apenas números, formatados como string. Não verifica se o PIS/PASEP realmente existe. Referências: - . Argumentos: - pis (str): Número PIS como uma string com o comprimento apropriado. Retorna: - bool: True se o PIS for válido, False caso contrário. Exemplo: ```python from brutils import is_valid_pis >>> is_valid_pis("82178537464") False >>> is_valid_pis("12082043519") True ``` ### format_pis Formata uma string de PIS (Programa de Integração Social) válida com símbolos e adiciona símbolos de formatação padrão para exibição. Argumentos: - pis (str): Uma string válida de PIS contendo apenas números. Retorna: - str: Uma string de PIS formatada com símbolos visuais padrão ou None se a entrada for inválida. Exemplo: ```python from brutils import format_pis >>> format_pis("17033259504") '170.33259.50-4' >>> format_pis("12013128292") '120.13128.29-2' ``` ### remove_symbols_pis Esta função recebe uma string de PIS (Programa de Integração Social) com símbolos de formatação e retorna uma versão limpa sem símbolos. Remove apenas os símbolos "-" e "." , propositalmente não remove outros símbolos. Argumentos: - pis (str): Uma string de PIS que pode conter símbolos de formatação. Retorna: - str: Uma string de PIS limpa, sem símbolos de formatação. Exemplo: ```python from brutils import remove_symbols_pis >>> remove_symbols_pis('170.33259.50-4') '17033259504' >>> remove_symbols_pis("123.456.789-09") '12345678909' >>> remove_symbols_pis('/._') '/_' ``` ### generate_pis Gera uma string de dígitos contendo um número de um PIS brasileiro válido aleatório. Retorna: - str: Um número PIS válido gerado aleatoriamente como string. Exemplo: ```python from brutils import generate_pis >>> generate_pis() '61352489741' >>> generate_pis() '73453349671' ``` ## Processo Jurídico ## is_valid_legal_process Verifica se um ID de processo jurídico é válido, não verifica se o ID de processo jurídico é um ID de processo jurídico real; ela apenas valida o formato da string. Argumentos: - legal_process_id (str): Uma string contendo apenas dígitos que representa o ID do processo jurídico. Retorna: - bool: True se o ID do processo jurídico for válido, False caso contrário. Examplo: ```python >>> from brutils import is_valid_legal_process >>> is_valid_legal_process('10188748220234018200') True >>> is_valid_legal_process('45532346920234025107') True >>> is_valid_legal_process('00000000000000000000') False >>> is_valid_legal_process('455323423QQWEQWSsasd&*(()') False >>> ``` ### format_legal_process Formata um ID de processo jurídico em um formato padrão. Argumentos: - legal_process_id (str): Uma string de 20 dígitos que representa o ID do processo jurídico. Retorna: - str: O ID do processo jurídico formatado ou None se a entrada for inválida. Exemplo: ```python >>> from brutils import format_legal_process >>> format_legal_process('23141945820055070079') '2314194-58.2005.5.07.0079' >>> format_legal_process('00000000000000000000') '0000000-00.0000.0.00.0000' >>> ``` ### remove_symbols_legal_process Remove símbolos específicos de um processo jurídico fornecido. Esta função recebe um processo jurídico como entrada e remove todas as ocorrências dos caracteres '.' e '-' dele. Argumentos: - legal_process (str): Um processo jurídico contendo símbolos a serem removidos. Retorna: - str: A string do processo jurídico com os símbolos especificados removidos. Exemplo: ```python from brutils import remove_symbols_legal_process >>> remove_symbols_legal_process("6439067-89.2023.4.04.5902") "64390678920234045902" >>> remove_symbols_legal_process("4976023-82.2012.7.00.2263") "49760238220127002263" >>> remove_symbols_legal_process("4976023-82.2012.7.00.2263*!*&#") "49760238220127002263*!*&#" ``` ### generate_legal_process Gera um número válido aleatório de ID de processo jurídico. Argumentos: - year (int): O ano para o ID do processo jurídico (o padrão é o ano atual). Não pode ser um ano do passado. - orgao (int): O órgão (1-9) para o ID do processo jurídico (o padrão é aleatório). Retorna: - str: Um ID de processo jurídico gerado aleatoriamente. None caso algum dos argumento seja inválido. Exemplo: ```python >>> from brutils import generate_legal_process >>> generate_legal_process() "45676401020238170592" >>> generate_legal_process(ano=2025) "32110268020258121130" >>> generate_legal_process(orgao=5) "37573041520235090313" >>> generate_legal_process(ano=2024, orgao=4) "33158248820244017105" ``` ## Titulo Eleitoral ### is_valid_voter_id Verifica se um número de Título de Eleitor brasileiro é válido. Não verifica se realmente existe. Referências: - - Argumentos: - voter_id (str): string representando o número do título de eleitor a ser verificado. Retorna: - bool: True se o número do título de eleitor for válido. False, caso contrário. Exemplo: ```python >>> from brutils import is_valid_voter_id >>> is_valid_voter_id('123456789011') False >>> is_valid_voter_id('427503840213') True ``` ### format_voter_id Formata um número de Título de Eleitor para exibição visual. Esta função recebe uma string de Título de Eleitor contendo apenas números como entrada e adiciona os espaços de formatação padrão para exibição. Argumentos: * voter_id (str): Uma string de Título de Eleitor contendo apenas números. Retorna: * str: O Título de Eleitor formatado com os espaços, se for válido. Retorna None se não for válido. Exemplo: ```python >>> from brutils import format_voter_id >>> format_voter_id("246593980493") '2465 9398 04 93' >>> format_voter_id("202715292895") '2027 1529 28 95' >>> format_voter_id("739035552205") >>> ``` ### generate_voter_id Gera uma string de dígitos de Título de Eleitor válida aleatória a partir de um estado brasileiro informado. Args: * federative_union (str): Unidade Federativa para o título de eleitor que será gerado. O valor padrão "ZZ" é usado para Títulos de Eleitor emitidos para estrangeiros. Retorna: * str: Um Título de Eleitor válido gerado aleatoriamente. Exemplo: ```python >>> from brutils import generate_voter_id >>> generate_voter_id() '183475722801' >>> generate_voter_id(federative_union ="MG") '950125640248' ``` ## IBGE ### convert_code_to_uf Converte um determinado código do IBGE (string de 2 dígitos) para sua UF (abreviatura estadual) correspondente. Args: * code (str): O código IBGE de 2 dígitos a ser convertido. Retorna: * str or None: O código UF correspondente ao código IBGE, ou None se o código IBGE for inválido. Exemplo: ```python >>> from brutils.ibge.uf import convert_code_to_uf >>> convert_code_to_uf("12") 'AC' >>> convert_code_to_uf("33") 'RJ' >>> convert_code_to_uf("99") >>> ``` ### get_code_by_municipality_name Retorna o código IBGE para um dado nome de município e código de UF. Essa função recebe uma string representando o nome de um município e o código da UF, e retorna o código IBGE correspondente (string). A função lida com os nomes ignorando diferenças de maiúsculas, acentos, tratando o caractere "ç" como "c", e ignorando diferenças de maiúsculas para o código da UF. Argumentos: * municipality_name (str): O nome do município. * uf (str): O código UF do estado. Retorna: * str: O código IBGE do município. Retorna None se o nome não for válido ou não existir. Exemplo: ```python >>> from brutils import get_code_by_municipality_name >>> get_code_by_municipality_name("São Paulo", "SP") "3550308" >>> get_code_by_municipality_name("goiania", "go") "5208707" >>> get_code_by_municipality_name("Conceição do Coité", "BA") "2908408" >>> get_code_by_municipality_name("conceicao do Coite", "Ba") "2908408" >>> get_code_by_municipality_name("Municipio Inexistente", "") None >>> get_code_by_municipality_name("Municipio Inexistente", "RS") None ``` ### get_municipality_by_code Retorna o nome do município e a UF para um código do IBGE. Args: * code (str): O código do IBGE para o município. Returns: * tuple: Retorna uma Tupla formatado como ("Município", "UF"). * None: Retorna None se o código for inválido. Example: ```python >>> from brutils import get_municipality_by_code >>> get_municipality_by_code(3550308) ("São Paulo", "SP") ``` ### convert_uf_to_name Converte um código de UF brasileiro (por exemplo, 'SP') no nome completo do estado ('São Paulo'). A busca é case-insensitive (não diferencia maiúsculas de minúsculas) e ignora espaços em branco ao redor. Argumentos: * uf (str): Código de UF com duas letras. Retorna: * str | None: O nome completo do estado, ou ``None`` se o código for inválido. Exemplo: ```python >>> from brutils.ibge.uf import convert_uf_to_name >>> convert_uf_to_name('SP') 'São Paulo' >>> convert_uf_to_name('rj') 'Rio de Janeiro' ``` ### convert_name_to_uf Converte o nome completo de um estado brasileiro para seu código UF. Esta função recebe o nome completo de um estado brasileiro e retorna o código UF de duas letras correspondente. A comparação ignora maiúsculas/minúsculas e acentos. Argumentos: * state_name (str): O nome completo do estado (por exemplo, 'São Paulo', 'sao paulo'). Retorna: * str | None: O código UF se encontrado, ou None se o nome do estado for inválido. Exemplo: ```python >>> from brutils.ibge.uf import convert_name_to_uf >>> convert_name_to_uf('São Paulo') 'SP' >>> convert_name_to_uf('sao paulo') 'SP' >>> convert_name_to_uf('Rio de Janeiro') 'RJ' >>> convert_name_to_uf('rio de janeiro') 'RJ' >>> convert_name_to_uf('Estado Inválido') >>> ``` ## Feriados ### is_holiday Verifica se uma determinada data é um feriado nacional ou estadual no Brasil. Esta função recebe um objeto `datetime` como a data e uma UF opcional (Unidade Federativa) para especificar feriados estaduais. Retorna `True` se a data for um feriado, `False` se não for, ou `None` se a data ou UF forem inválidas. Nota: a função não abrange feriados municipais. Argumentos: - `date (datetime)`: A data a ser verificada. - `uf (str, opcional)`: A abreviação do estado (UF) para verificar feriados estaduais. Se não fornecido, apenas feriados nacionais são considerados. Retorna: - `bool | None`: `True` se a data for um feriado, `False` se não for, ou `None` se a data ou UF forem inválidas. Exemplo: ```python >>> from datetime import datetime >>> from brutils import is_holiday >>> is_holiday(datetime(2024, 1, 1)) True >>> is_holiday(datetime(2024, 1, 2)) False >>> is_holiday(datetime(2024, 3, 2), uf="SP") False >>> is_holiday(datetime(2024, 12, 25), uf="RJ") True ``` ## Monetário ### format_currency Formata um número seguindo o padrão monetário brasileiro. O número será formatado adicionando o símbolo R$ como prefixo, vírgula como separador decimal, e ponto como agrupador de milhar. Argumentos: * float ou Decimal: Um número com ou sem casas decimais. Retorna: * str ou None: O número formatado seguindo o padrão brasileiro. Exemplo: ```python >>> from brutils.currency import format_currency >>> format_currency(1259.03) 'R$ 1.259,03' >>> format_currency(0) 'R$ 0,00' >>> format_currency("not a number") None ``` ### convert_real_to_text Converte um valor monetário em reais para sua representação por extenso. Esta função recebe um número decimal representando um valor monetário em reais e o converte para uma string com o valor escrito por extenso em português do Brasil. Ela trata tanto a parte inteira (reais) quanto a parte fracionária (centavos), respeitando a gramática correta para os casos de singular e plural, bem como casos especiais como zero e valores negativos. Argumentos: - amount (decimal): O valor monetário a ser convertido por extenso. - A parte inteira representa os reais. - A parte decimal representa os centavos. - 2 casas decimais. Retorna: - str: Uma string com o valor monetário escrito por extenso em português do Brasil. - Retorna "Zero reais" para o valor 0,00. - Retorna None se o valor for inválido ou absolutamente maior que 1 quatrilhão. - Trata valores negativos, adicionando "Menos" no início da string. Limitações: - Esta função pode perder precisão em ±1 centavo para casos em que o valor absoluto ultrapasse trilhões devido a erros de arredondamento de ponto flutuante. Exemplo: ```python >>> from brutils.currency import convert_real_to_text >>> convert_real_to_text(1523.45) 'Mil, quinhentos e vinte e três reais e quarenta e cinco centavos' >>> convert_real_to_text(0.01) 'Um centavo' >>> convert_real_to_text(0.00) 'Zero reais' >>> convert_real_to_text(-50.25) 'Menos cinquenta reais e vinte e cinco centavos' >>> convert_real_to_text("invalid") None ``` ## Natureza Jurídica ### is_valid_legal_nature Valida se o código informado existe na tabela oficial. Aceita `NNNN` ou `NNN-N`. O valor é **normalizado** antes da checagem: remove espaços, mantém apenas dígitos e aceita hífen entre o 3º e 4º dígitos. **Argumentos** - `code (str)`: Código de 4 dígitos (ex.: `"2062"` ou `"206-2"`) **Retorna** - `bool`: `True` se existir na tabela, `False` caso contrário. **Exemplo** ```python >>> from brutils import legal_nature >>> legal_nature.is_valid("2062") True >>> legal_nature.is_valid("206-2") True >>> legal_nature.is_valid("9999") False ``` ### get_legal_nature_description Retorna a **descrição oficial** do código de Natureza Jurídica. Aceita `NNNN` ou `NNN-N`. Aplica a mesma normalização do `is_valid`. **Argumentos** * `code (str)`: Código de 4 dígitos **Retorna** * `str | None`: Descrição correspondente ou `None` se o código for inválido ou inexistente. **Exemplo** ```python >>> from brutils import legal_nature >>> legal_nature.get_description("2062") 'Sociedade Empresária Limitada' >>> legal_nature.get_description("101-5") 'Órgão Público do Poder Executivo Federal' >>> legal_nature.get_description("0000") None ``` ### list_all_legal_nature Retorna uma cópia do dicionário completo `{codigo: descricao}`. **Retorna** * `dict[str, str]`: Mapeamento de todos os códigos para suas descrições. **Exemplo** ```python >>> from brutils import legal_nature >>> data = legal_nature.list_all() >>> len(data) > 0 True >>> data["2062"] 'Sociedade Empresária Limitada' ``` ## RENAVAM ### is_valid_renavam Valida se os dígitos de verificação do RENAVAM fornecido correspondem aos seus 10 dígitos iniciais. Esta função não verifica a existência do veículo; ela apenas valida o formato da string e o dígito verificador. Argumentos: - renavam (str): O RENAVAM a ser validado, uma string de 11 dígitos. Retorna: - bool: Verdadeiro se o RENAVAM for válido Falso caso contrário. Exemplo: ```python >>> from brutils import is_valid_renavam >>> is_valid_renavam("86769597308") True >>> is_valid_renavam("12345678901") False ``` ## Passaporte ### is_valid_passport Verifica se um número de passaporte brasileiro é válido. Para ser considerado válido, a entrada deve ser uma string contendo exatamente dois caracteres alfabéticos seguidos de exatamente seis dígitos numéricos. Esta função não verifica se a entrada é um número de passaporte real, pois não existem dígitos verificadores para o passaporte brasileiro. Argumentos: - passport (str): A string contendo o número do passaporte a ser verificado. Retorna: - bool: True se o número do passaporte for válido (2 letras seguidas de 6 dígitos). False caso contrário. Exemplo: ```python >>> from brutils import is_valid_passport >>> is_valid_passport("Ab123456") True >>> is_valid_passport("12345678") False >>> is_valid_passport("DC-221345") False ``` ### format_passport Formata um número de passaporte brasileiro para exibição. Esta função recebe uma string representando um número de passaporte válido e o retorna formatado (maiúsculas, sem símbolos). Argumentos: - passport (str | None): Um número de passaporte brasileiro (minúsculas ou maiúsculas, possivelmente incluindo símbolos) Retorna: - str: O número do passaporte formatado (maiúsculas, sem símbolos) ou None se a entrada for inválida Exemplo: ```python >>> from brutils import format_passport >>> format_passport("Ab123456") AB123456 >>> format_passport("Ab-123456") AB123456 >>> format_passport("111111") None ``` ### remove_symbols_passport Remove símbolos ('-', '.' e espaços em branco) de um número de passaporte. Esta função recebe uma string com um número de passaporte como entrada e remove todas as ocorrências dos caracteres '.', '-' e espaço em branco. Argumentos: - passport (str): A string contendo um número de passaporte Retorna: - str: O número do passaporte com hífens (-), pontos (.) e espaços em branco ( ) removidos. Exemplo: ```python >>> from brutils import remove_symbols_passport >>> remove_symbols_passport("Ab123456") Ab123456 >>> remove_symbols_passport("Ab-123456") Ab123456 >>> remove_symbols_passport("Ab -. 123456") Ab123456 ``` ### generate_passport Gera uma string com um número de passaporte brasileiro válido aleatório. Esta função gera uma string com um número de passaporte brasileiro aleatório. Retorna: - str: Uma string com um número de passaporte válido aleatório. Exemplo: ```python >>> from brutils import generate_passport >>> generate_passport() "RY393097" >>> generate_passport() "ZS840088" ``` # Novos Utilitários e Reportar Bugs Caso queira sugerir novas funcionalidades ou reportar bugs, basta criar uma nova [issue][github-issues] e iremos lhe responder por lá! (Para saber mais sobre github issues, confira a [documentação oficial do GitHub][github-issues-doc]). # Dúvidas? Ideias? Dúvidas de como utilizar a biblioteca? Novas ideias para o projeto? Quer compartilhar algo com a gente? Fique à vontade para criar um tópico no nosso [Discussions][github-discussions] que iremos interagir por lá! (Para saber mais sobre github discussions, confira a [documentação oficial do GitHub][github-discussions-doc]). # Contribuindo com o Código do Projeto Sua colaboração é sempre muito bem-vinda! Preparamos o arquivo [CONTRIBUTING.md][contributing] para te ajudar nos primeiros passos. Lá você encontrará toda a informação necessária para contribuir com o projeto. Não hesite em nos perguntar utilizando o [GitHub Discussions][github-discussions] caso haja qualquer dificuldade ou dúvida. Toda ajuda conta! Vamos construir juntos! 🚀🚀 [contributing]: CONTRIBUTING.md [github-discussions-doc]: https://docs.github.com/pt/discussions [github-discussions]: https://github.com/brazilian-utils/brutils-python/discussions [github-issues-doc]: https://docs.github.com/pt/issues/tracking-your-work-with-issues/creating-an-issue [github-issues]: https://github.com/brazilian-utils/brutils-python/issues ## ❤️ Quem já Contribuiu

_Feito por [contrib.rocks](https://contrib.rocks)._ brutils-2.5.0/CORE_TEAM.md0000664000000000000000000000412515221040537013563 0ustar rootroot

🇧🇷 Core Team brutils-python

O _Core Team_ do `brazilian-utils/brutils-python` é composto por um conjunto de colaboradores que manifestaram um entusiasmo notável pelo projeto e pela comunidade. Essa equipe possui privilégios administrativos no GitHub específicos para o repositório. ## Responsabilidades O _Core Team_ do `brutils-python` possui as seguintes responsabilidades: * Estar prontamente disponível para abordar questionamentos de natureza estratégica acerca da visão e perspectivas futuras do Brazilian Utils Python. * Comprometer-se a revisar pull requests que tenham sido submetidos há algum tempo ou que tenham sido negligenciados. * Periodicamente examinar as questões abertas no Brazilian Utils Python, fornecer sugestões construtivas e categorizá-las utilizando rótulos específicos no GitHub. * Identificar indivíduos promissores na comunidade do Brazilian Utils Python que possam expressar interesse em integrar a equipe e contribuir de maneira significativa. Da mesma forma que todos os colaboradores do `brutils-python`, os membros do Core Team também atuam como voluntários no âmbito de código aberto; fazer parte da equipe não é uma obrigação. Essa equipe é reconhecida como líder nesta comunidade e, embora seja uma referência confiável para obter respostas a perguntas, é importante salientar que eles contribuem de forma voluntária, dedicando seu tempo, o que pode resultar em disponibilidade não imediata. ## Membros - [@camilamaia](https://github.com/camilamaia) - [@antoniamaia](https://github.com/antoniamaia) ## Adição de Novos Membros O procedimento para incorporar novos membros ao _Core Team_ `brutils-python` é o seguinte: * Um integrante já existente da equipe entra em contato de forma privada para averiguar o interesse da pessoa. Se houver interesse, é aberto um pull request adicionando o novo membro à lista. * Os outros integrantes da equipe revisam o pull request. A pessoa responsável por efetuar o merge no PR é também encarregada de incluir o novo membro no grupo Core Team no GitHub. brutils-2.5.0/CONTRIBUTING.md0000664000000000000000000007533015221040537014142 0ustar rootroot# Contribuindo Obrigado por dedicar o seu tempo para contribuir! 🙇‍♀️🙇‍♂️ Toda ajuda é bem-vinda! - [Primeira Contribuição](#primeira-contribuição) - [Lançar uma Nova Versão](#lançar-uma-nova-versão) ## 💌 Quer contribuir, mas não se sente à vontade? Você tem vontade de contribuir, mas não se sente à vontade em abrir issues, PRs ou fazer perguntas publicamente? Nós sabemos como pode ser difícil dar o primeiro passo em um espaço aberto. A insegurança, o medo de errar ou até a sensação de “será que minha dúvida é boba?” podem pesar bastante. E tá tudo bem sentir isso. 💜 Queremos que você saiba que aqui ninguém precisa enfrentar esse caminho sem apoio. Se preferir um espaço mais reservado, você pode mandar um e-mail para cumbucadev@gmail.com e teremos o maior prazer em ajudar. Seja para tirar dúvidas, pedir orientação ou simplesmente ter alguém para conversar sobre como começar. O importante é que você saiba: sua participação é muito bem-vinda, e cada contribuição, por menor que pareça, faz uma grande diferença. ✨ ## Primeira Contribuição Como fazer a sua primeira contribuição: - [1. Crie uma Conta no GitHub](#1-crie-uma-conta-no-github) - [2. Encontre uma Issue para Trabalhar](#2-encontre-uma-issue-para-trabalhar) - [3. Instale o Git](#3-instale-o-git) - [4. Faça um Fork do Projeto](#4-faça-um-fork-do-projeto) - [5. Clone o Seu Fork](#5-clone-o-seu-fork) - [6. Crie um Novo Branch](#6-crie-um-novo-branch) - [7. Execute o brutils Localmente](#7-execute-o-brutils-localmente) - [8. Faça as Suas Alterações](#8-faça-as-suas-alterações) - [9. Teste as Suas Alterações](#9-teste-as-suas-alterações) - [10. Atualizar READMEs](#10-atualizar-readmes) - [11. Faça o Commit e Envie as Suas Alterações](#11-faça-o-commit-e-envie-as-suas-alterações) - [12. Adicione Entradas no CHANGELOG.md](#12-adicione-entradas-no-changelogmd) - [13. Crie um PR no GitHub](#13-crie-um-pr-no-github) - [14. Atualizar a Sua Branch se Necessário](#14-atualizar-a-sua-branch-se-necessário) ### 1. Crie uma Conta no GitHub Certifique-se de ter uma [conta no GitHub][github-join] e de estar com a sessão iniciada. Caso não tenha uma conta, siga os passos de [como criar de uma conta pessoal no GitHub][github-essentials-criar-conta]. ### 2. Encontre uma Issue para Trabalhar Visite a [página de issues do brutils][brutils-issues] e encontre uma issue com a qual você gostaria de trabalhar e que ainda não tenha sido atribuída a ninguém. Deixe um comentário na issue com conteúdo "bora!" Em seguida, um bot vai atribuir a issue a você. Uma vez atribuída, você pode prosseguir para a próxima etapa. Sinta-se à vontade para fazer qualquer pergunta na página da issue antes ou durante o processo de desenvolvimento. Ao começar a contribuir para o projeto, é recomendável que você pegue uma issue por vez. Isso ajuda a garantir que outras pessoas também tenham a oportunidade de colaborar e evita que recursos fiquem inativos por muito tempo. ### 3. Instale o Git Certifique-se de ter o git instalado, seguindo os passos do [tutorial de instalação do git][github-essentials-instalando-o-git]. ### 4. Faça um Fork do Projeto [Faça um fork do repositório brutils][github-forking]. ### 5. Clone o Seu Fork [Clone][github-cloning] o seu fork localmente. ### 6. Crie um Novo Branch Entre na pasta do brutils: ```bash $ cd brutils-python > ``` E crie uma nova branch com o nome da issue em que você irá trabalhar através do comando: ```bash $ git checkout -b > ``` Exemplo: ```bash $ git checkout -b 386 Switched to a new branch '386' > ``` ### 7. Execute o brutils Localmente #### Instalação com poetry ##### Requisitos - [Python 3.10+][python] - [Poetry][poetry] Crie um [virtualenv][virtualenv] para o brutils e o ative através do comando: ```shell $ make shell Spawning shell within ...-py3.x emulate bash -c '. .../bin/activate' ``` Para testar se o ambiente virtual está ativo corretamente, execute o comando e verifique se a resposta é algo parecido com a seguinte: ```sh $ poetry env info Virtualenv Python: 3.x.y Implementation: CPython ... ``` **Observação: Você precisa executar `make shell` toda vez que abrir uma nova janela ou aba do terminal.** Instale as dependências: ```shell $ make install git config --local core.hooksPath .githooks/ chmod -R +x .githooks Installing dependencies from lock file ... ``` #### Instalação com pip Se preferir usar pip, você pode instalar o projeto em modo de desenvolvimento da seguinte forma: ##### Requisitos - [Python 3.10+][python] - [pip][pip] Crie um [virtualenv][virtualenv] para o brutils e o ative através do comando: ```sh python -m venv venv source venv/bin/activate # No Windows use: venv\Scripts\activate ``` Utilize o comando pip para instalar as dependencias de dev e testes através do arquivo requirements-dev.txt ```sh pip install -r requirements-dev.txt ``` #### Utilizando Localmente Agora você pode usá-lo [da mesma forma descrita no arquivo README.md](/README.md#utilização). ## Testes Execute os testes através do seguinte comando: ```shell $ make test make test test__is_valid_mercosul (license_plate.test_is_valid.TestIsValidMercosul.test__is_valid_mercosul) ... ok test__is_valid_old_format (license_plate.test_is_valid.TestIsValidOldFormat.test__is_valid_old_format) ... ok .... ---------------------------------------------------------------------- Ran XX tests in 0.000s OK ``` Certifique-se de que o retorno é `OK`, o quê indica todos os testes estão passando e que não tem nenhum falhando. --- ## Testes no Windows Caso os testes não funcionem ou se você precisar configurar o ambiente do zero, siga as etapas abaixo para preparar o ambiente no Windows. ### Instalar o Chocolatey Primeiro, abra o PowerShell como administrador e execute o seguinte comando para instalar o **Chocolatey**, que é um gerenciador de pacotes para o Windows: ```powershell Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) ``` ### Instalar o `make` Após a instalação do Chocolatey, instale o **make** com o seguinte comando: ```powershell choco install make ``` Verifique se o `make` foi instalado corretamente com: ```powershell where.exe make ``` ### Instalar o Poetry Instale o **Poetry**, que é uma ferramenta de gerenciamento de dependências e ambientes Python, usando o comando: ```powershell choco install poetry ``` ### Instalar as dependências do projeto Dentro do diretório do seu projeto, abra o terminal e instale as dependências do projeto usando o **Poetry**: ```powershell poetry install ``` ### Verificar e configurar o `Makefile` No arquivo `Makefile` do seu projeto, verifique se a seção de testes está configurada corretamente. Se estiver no Windows, use a seguinte configuração para o alvo `test`: ```makefile test: ifeq ($(OS),Windows_NT) @set PYTHONDONTWRITEBYTECODE=1 && poetry run python -m unittest discover tests/ -v else @PYTHONDONTWRITEBYTECODE=1 poetry run python3 -m unittest discover tests/ -v endif ``` ### 8. Faça as Suas Alterações Agora é a etapa em que você pode implementar as suas alterações no código. Normalmente existem instruções/ideias de como você pode implementar a solução diretamente na descrição da issue, na seção "Descreva alternativas que você considerou". Leia atentamente tudo que está escrito na issue para garantir que suas modificações resolvem tudo que está sendo solicitado. É importante notar que documentamos o nosso código usando [docstrings][docstring-definition]. Módulos, classes, funções e métodos devem ser documentados. Suas alterações também devem ser bem documentadas e refletir docstrings atualizadas, caso algum dos parâmetros tenha sido alterado para um classe/atributo ou mesmo funções. Todas as docstring devem estar em Inglês. Fique à vontade para utilizar ferramentas como Google Tradutor ou ChatGPT caso precise. Iremos sugerir mudanças na tradução se necessário, então não se preocupe com possíveis erros de inglês. Seguimos o padrão abaixo para manter consistência nas docstrings: ```python class Example: """ Explain the purpose of the class Attributes: x[dict]: Short explanation here y[type, optional]: Short explanation here """ def __init__(self, x, y=None): self.x = x self.y = y def foobar(self, w): """ Purpose of the function Args: name[type]: Short explanation here Returns: type: Short explanation here Example: >>> command 1 output 1 >>> command 2 output 2 """ ... return value ``` Exemplo: ```python def format_cep(cep): # type: (str) -> str | None """ Formats a Brazilian CEP (Postal Code) into a standard format. This function takes a CEP (Postal Code) as input and, if it is a valid 8-digit CEP, formats it into the standard "12345-678" format. Args: cep (str): The input CEP (Postal Code) to be formatted. Returns: str: The formatted CEP in the "12345-678" format if it's valid, None if it's not valid. Example: >>> format_cep("12345678") "12345-678" >>> format_cep("12345") None """ return f"{cep[:5]}-{cep[5:8]}" if is_valid(cep) else None ``` Algo a se ter em mente ao documentar o código com docstrings é que você pode ignorar docstrings em decoradores de propriedade e métodos mágicos. ### 9. Teste as Suas Alterações #### Escreva Novos Testes Certifique-se de ter criado os testes necessários para cada nova alteração que você fez. #### Certifique-se de que Todos os Testes Passam Execute todos os testes com o comando `make test` e certifique-se de que todos passam. **Os PRs não serão mesclados se houver algum teste faltando ou falhando.** #### Teste manualmente Abra um ambiente interativo para testar manualmente as suas mudanças: ```sh $ python Python 3.x.y ... Type "help", "copyright", "credits" or "license" for more information. >>> # Teste as suas mudanças aqui! ``` Exemplo: ```sh $ python Python 3.12.5 (main, Aug 6 2024, 19:08:49) [Clang 15.0.0 (clang-1500.3.9.4)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import brutils >>> from brutils import cpf >>> cpf.generate() '13403202232' >>> from brutils import generate_cpf >>> generate_cpf() '64590379228' ``` ### 10. Atualizar READMEs Atualize os arquivos `brutils-python/README.md` e `brutils-python/README_EN.md` com suas alterações. Esses arquivos são essenciais para a documentação da biblioteca, ajudando os usuários a entender como utilizar os recursos oferecidos. Portanto, é importante mantê-los sempre atualizados. - O arquivo brutils-python/README_EN.md contém a documentação em Inglês, e pode usar o conteúdo já descrito na docstring diretamente. - O arquivo brutils-python/README.md contém a documentação em Português. Para este, basta traduzir a docstring da função. Se precisar de assistência na tradução para o Português, ferramentas como Google Tradutor ou ChatGPT podem ajudar. Não se preocupe com possíveis erros de tradução, pois sugeriremos ajustes quando necessário. Exemplo em Inglês (README_EN.md): ````md ### format_cep This function takes a CEP (Postal Code) as input and, if it is a valid 8-digit CEP, formats it into the standard "12345-678" format. Args: - cep (str): The input CEP (Postal Code) to be formatted. Returns: - str: The formatted CEP in the "12345-678" format if it's valid, None if it's not valid. Example: ```python >>> from brutils import format_cep >>> format_cep('01310200') '01310-200' >>> format_cep("12345678") "12345-678" >>> format_cep("12345") None ``` ```` Exemplo em Português (README.md): ````md ### format_cep Formata um CEP (Código de Endereçamento Postal) brasileiro em um formato padrão. Esta função recebe um CEP como entrada e, se for um CEP válido com 8 dígitos, o formata no padrão "12345-678". Argumentos: - cep (str): O CEP (Código de Endereçamento Postal) de entrada a ser formatado. Retorna: - str: O CEP formatado no formato "12345-678" se for válido, None se não for válido. Example: ```python >>> from brutils import format_cep >>> format_cep('01310200') '01310-200' >>> format_cep("12345678") "12345-678" >>> format_cep("12345") None ``` ```` ### 11. Faça o Commit e Envie as Suas Alterações Fomarte o seu código executando o comando: ```bash $ make format ... ``` Exemplo: ```bash $ make format 31 files left unchanged All checks passed! ``` Adicione suas mudanças para staging area: ```bash $ git add --all ... ``` Faça o commit das alterações: ```bash $ git commit -a -m "" ... ``` Exemplo: ```bash $ git commit -m 'Adicionando mais info aos arquivos de contribuição' [386 173b7e6] Adicionando mais info aos arquivos de contribuição 2 files changed, 144 insertions(+), 34 deletions(-) ``` Push o seu commit para o GitHub: ```bash $ git push --set-upstream origin ... ``` Exemplo: ```bash $ git push --set-upstream origin 386 Running pre-push hook checks All checks passed! Enumerating objects: 7, done. Counting objects: 100% (7/7), done. Delta compression using up to 10 threads Compressing objects: 100% (4/4), done. Writing objects: 100% (4/4), 2.36 KiB | 2.36 MiB/s, done. Total 4 (delta 3), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (3/3), completed with 3 local objects. remote: remote: Create a pull request for '386' on GitHub by visiting: remote: https://github.com/brazilian-utils/brutils-python/pull/new/386 remote: To github.com:brazilian-utils/brutils-python.git * [new branch] 386 -> 386 ``` Faça as alterações e commits necessários e envie-os quando estiverem prontos. ### 12. Adicione Entradas no CHANGELOG.md #### O que é um changelog? Um changelog é um arquivo que contém uma lista organizada cronologicamente de mudanças notáveis para cada versão de um projeto. #### Por que manter um changelog? Para facilitar para usuários e contribuintes verem exatamente quais mudanças notáveis foram feitas entre cada release (ou versão) do projeto. #### Quem precisa de um changelog? Pessoas. Sejam consumidores ou desenvolvedores, os usuários finais de software são seres humanos que se importam com o que está no software. Quando o software muda, as pessoas querem saber por que e como. #### Onde está o changelog do brutils? O changelog do brutils está disponível em [CHANGELOG.md][changelog] #### Princípios orientadores - Changelogs são para humanos, não máquinas. - Deve haver uma entrada para cada versão. - Os mesmos tipos de mudanças devem ser agrupados. - Versões e seções devem ser linkáveis. - A versão mais recente vem primeiro. - A data de lançamento de cada versão é exibida. #### O que justifica uma entrada no changelog? - Correções de segurança: Devem ser documentadas com o tipo definido como "segurança" para alertar os usuários sobre questões de segurança resolvidas. Exemplo: “Corrigido um vulnerabilidade crítica que permitia a execução remota de código.” - Mudanças voltadas ao usuário: Alterações que afetam diretamente a forma como os usuários interagem com o software, incluindo novas funcionalidades, alterações em funcionalidades existentes ou melhorias na interface. Exemplo: “Adicionada uma nova opção de filtro na página de resultados para facilitar a busca.” - Melhorias significativas de desempenho: Devem ser registradas quando resultam em melhorias notáveis na velocidade ou na eficiência do software que impactam a experiência do usuário. Exemplo: “O tempo de carregamento da página inicial foi reduzido em 40% após a otimização do backend.” - Alterações que afetam a compatibilidade: Mudanças que ajustam o software para manter a compatibilidade com outras ferramentas, sistemas ou versões. Exemplo: “Atualizada a biblioteca X para a versão 2.0 para suportar a nova versão do Python.” - Mudanças na API pública: Alterações que afetam como os desenvolvedores interagem com a API pública do software, seja adicionando novas rotas ou alterando as existentes. Exemplo: “Adicionada uma nova rota /api/v1/users para gerenciamento de usuários.” - Alterações nas dependências: Atualizações ou mudanças nas dependências do projeto que podem afetar o comportamento ou a compatibilidade do software. Exemplo: “Atualizado o pacote de dependência Y para a versão 3.1.4, que inclui correções importantes de segurança.” #### O quê NÃO deve ir no changelog Embora o changelog seja uma ferramenta valiosa para documentar mudanças, algumas informações não devem ser incluídas. Aqui estão alguns exemplos do que não deve aparecer no changelog: - Mudanças Internas de Código: Alterações que não afetam o comportamento do usuário final, como refatorações de código interno que não alteram a funcionalidade, não precisam ser documentadas no changelog. Exemplo: “Refatoração de funções internas” ou “Correção testes inconsistentes.” - Melhorias de Desempenho Não Notáveis: Melhorias de desempenho que não resultam em mudanças perceptíveis ou benefícios claros para o usuário final não precisam de uma entrada específica. Exemplo: “Otimização de algoritmos internos.” - Correções de Bugs Menores: Correções para bugs que não afetam o uso geral ou a experiência do usuário final podem ser omitidas. Exemplo: “Correção de um pequeno erro de digitação no código.” - Mudanças Somente de Documentação: Alterações que afetam apenas a documentação, sem modificar o comportamento do software, geralmente não precisam ser incluídas no changelog. Exemplo: “Atualização do README.md para refletir novas dependências.” - Detalhes Técnicos Excessivos: Informações excessivamente técnicas que não são relevantes para o usuário final ou não oferecem contexto sobre o impacto da mudança devem ser evitadas. Exemplo: “Mudança no gerenciamento de memória na classe X.” - Entradas de Mantenedor: Mudanças que são relacionadas apenas ao processo de desenvolvimento ou manutenção interna, como ajustes de configuração de ferramentas de CI/CD, geralmente não são relevantes para o changelog. Exemplo: “Atualização na configuração do GitHub Actions.” - Uma correção de bug introduzida e corrigida na mesma release não precisa de uma entrada no changelog. Evite incluir essas informações no changelog para manter o documento focado e útil para os usuários e contribuintes do projeto. #### Escrevendo boas entradas no changelog Uma boa entrada no changelog deve ser descritiva e concisa. Deve explicar a mudança a um leitor que não tem nenhum contexto sobre a mudança. Se for difícil ser ao mesmo tempo conciso e descritivo, opte por ser mais descritivo. - **Ruim**: Ir para a ordem do projeto. - **Bom**: Mostrar os projetos estrelados do usuário no topo do dropdown “Ir para o projeto”. O primeiro exemplo não dá contexto sobre onde a mudança foi feita, nem por que, nem como beneficia o usuário. - **Ruim**: Copiar (algum texto) para a área de transferência. - **Bom**: Atualizar o tooltip de “Copiar para a área de transferência” para indicar o que está sendo copiado. Novamente, o primeiro exemplo é muito vago e não fornece contexto. - **Ruim**: Corrige e melhora problemas de CSS e HTML no gráfico de mini pipeline e dropdown de builds. - **Bom**: Corrigir tooltips e estados de hover no gráfico de mini pipeline e dropdown de builds. O primeiro exemplo está muito focado nos detalhes de implementação. O usuário não se importa que mudamos CSS e HTML, ele se importa com o resultado final dessas mudanças. - **Ruim**: Remover valores nulos no Array de objetos Commit retornados por find_commits_by_message_with_elastic - **Bom**: Corrigir erros 500 causados por resultados do Elasticsearch referenciando commits já recolhidos pelo garbage collector. O primeiro exemplo foca em como corrigimos algo, não no que foi corrigido. A versão reescrita descreve claramente o benefício final para o usuário (menos erros 500) e quando isso acontece (ao buscar commits com Elasticsearch). Use seu melhor julgamento e tente se colocar na posição de alguém lendo o changelog compilado. Essa entrada agrega valor? Oferece contexto sobre onde e por que a mudança foi feita? ### Como adicionar uma entrada no changelog O changelog está disponível no arquivo [CHANGELOG.md][changelog]. Primeiro, você precisa identificar o tipo da sua mudança. Tipos de mudanças: - `Added` para novas funcionalidades. - `Changed` para mudanças em funcionalidades existentes. - `Deprecated` para funcionalidades que serão removidas em breve. - `Fixed` para qualquer correção de bugs. - `Removed` para funcionalidades que foram removidas. - `Security` em caso de vulnerabilidades. Você deve sempre adicionar novas entradas no changelog na seção `Unreleased`. No momento do release, moveremos as mudanças da seção `Unreleased` para uma nova seção de versão. Portanto, dentro da seção `Unreleased`, você deve adicionar sua entrada na seção apropriada por tipo. Se ainda não houver uma seção para o tipo da sua mudança, você deve adicioná-la. Vamos ver alguns exemplos. Suponhamos que você tenha uma nova mudança `Fixed` para adicionar, e o arquivo atual do CHANGELOG.md está assim: ```md ## [Unreleased] ### Added - Utilitário `get_address_from_cep` [#358](https://github.com/brazilian-utils/brutils-python/pull/358) ### Changed - Utilitário `fmt_voter_id` renomeado para `format_voter_id` [#221](https://github.com/brazilian-utils/brutils-python/issues/221) ``` Você precisará adicionar uma nova seção `Fixed` e incluir a nova entrada lá: ```md ## [Unreleased] ### Added - Utilitário `get_address_from_cep` [#358](https://github.com/brazilian-utils/brutils-python/pull/358) ### Changed - Utilitário `fmt_voter_id` renomeado para `format_voter_id` [#221](https://github.com/brazilian-utils/brutils-python/issues/221) ### Fixed - Minha mensagem de changelog aqui. [#]() ``` Note que a ordem das seções por tipo importa. Temos um lint que verifica isso, então as seções devem ser ordenadas alfabeticamente. Primeiro `Added`, depois `Changed`, terceiro `Deprecated` e assim por diante. Agora, digamos que você tem mais uma entrada para adicionar e o tipo dela é `Added`. Como já temos uma seção para isso, você devve apenas adicionar uma nova linha: ```md ## [Unreleased] ### Added - Utilitário `get_address_from_cep` [#358](https://github.com/brazilian-utils/brutils-python/pull/358) - Minha outra mensagem de changelog aqui. [#]() ### Changed - Utilitário `fmt_voter_id` renomeado para `format_voter_id` [#221](https://github.com/brazilian-utils/brutils-python/issues/221) ### Fixed - Minha mensagem de changelog aqui. [#]() ``` _Este conteúdo é baseado no [site do keep a changelog][keep-a-changelog], já que seguimos suas diretrizes._ ### 13. Crie um PR no GitHub [Crie um PR no GitHub][github-creating-a-pr] para enviar suas alterações para revisão. Para garantir que seu Pull Request (PR) seja claro, eficaz e revisado rapidamente, siga estas boas práticas: #### Escreva um Título Descritivo para o PR - Use títulos claros e específicos para descrever o propósito das suas alterações. Um bom título ajuda às pessoas mantenedoras a entender a intenção do PR rapidamente e melhora a rastreabilidade do projeto. - **Exemplo**: Em vez de “Corrigir problema”, use “Adiciona utilitário `convert_uf_to_text` para lidar com códigos de estados brasileiros.” - **Benefícios**: - Títulos claros facilitam a priorização e o entendimento pelos revisores. - Melhoram a organização e a busca no projeto. #### Forneça uma Descrição Detalhada do PR - Inclua uma descrição completa no seu PR para explicar: - **O que** foi feito (ex.: adicionou uma nova função, corrigiu um bug). - **Por que** foi feito (ex.: para resolver uma issue específica ou melhorar o desempenho). - **Quais problemas** foram resolvidos ou melhorias aplicadas (ex.: referencie a issue ou descreva a melhoria). - **Exemplo**: Este PR adiciona o utilitário convert_uf_to_text para converter códigos de estados brasileiros (ex.: “SP”) em nomes completos (ex.: “São Paulo”). Resolve a issue #474, melhorando a reutilização de código para formatação de endereços. A função inclui validação de entrada e testes atualizados. - **Benefícios**: - Descrições detalhadas agilizam o processo de revisão ao fornecer contexto. - Ajudam futuros mantenedores a entender o propósito e o histórico do código. #### Vincule o PR à Issue Relacionada - Referencie a issue que seu PR resolve usando palavras-chave como `Closes #474` ou `Fixes #474` na descrição do PR. Isso fecha a issue automaticamente quando o PR for mesclado. - **Exemplo**: `Closes #474` - **Benefícios**: - Vincular issues mantém o repositório organizado e garante o rastreamento de tarefas. - Automatiza o fechamento de issues, reduzindo trabalho manual para mantenedores. - Para mais detalhes, consulte a [documentação do GitHub sobre fechamento automático de issues](https://docs.github.com/pt/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). #### Verifique o Template de Descrição do PR - Certifique-se de que seu PR segue o template de descrição do repositório. Verifique todos os itens obrigatórios, como cobertura de testes, atualizações de documentação ou entradas no changelog. - **Exemplo de Checklist**: (mostrando como fica quando preenchido): - [x] Alterações no código foram testadas. - [x] Documentação (READMEs) foi atualizada. - [ ] Entrada no changelog foi adicionada (marque apenas se aplicável). - **Nota sobre a Sintaxe**: - Use [x] para marcar itens concluídos e [ ] para itens não concluídos, sem espaços dentro dos colchetes (ex.: [ x ] ou [x ] não será renderizado corretamente no GitHub). - **Benefícios**: - Seguir o template garante que o PR esteja completo e pronto para revisão. - Reduz a necessidade de idas e vindas com revisores, acelerando o processo de mesclagem. ### 14. Atualizar a Sua Branch se Necessário [Certifique-se de que sua branch esteja atualizado com o main][github-sync-pr] ## Lançar uma Nova Versão Aqui você encontrará como lançar uma nova versão em produção do brutils: - [1. Criar uma Issue de Release](#1-criar-uma-issue-de-release) - [2. Criar um Release PR](#2-criar-um-release-pr) - [3. Deploy via GitHub](#3-deploy-via-github) ### 1. Criar uma Issue de Release #### Crie a Issue Para a criação da issue, pode ser utilizado o template de feature, sendo o nome da issue `Release v`. [Exemplo](https://github.com/brazilian-utils/brutils-python/issues/322) #### Crie uma Branch O nome da branch criada para o release é relacionado ao número da Issue, como mostra [este exemplo](https://github.com/brazilian-utils/brutils-python/pull/326) #### Faça o Commit A mensagem do commit deve seguir a estrutura `chore: release ` Exemplo: `chore: release 2.3.0` ### 2. Criar um Release PR #### Atualizar a Versão da Biblioteca Incremente o número da versão, seguindo o [Versionamento Semântico][semantic-versioning], no arquivo `pyproject.toml`: - [https://github.com/brazilian-utils/brutils-python/blob/main/pyproject.toml#L3]([https://github.com/brazilian-utils/brutils-python/blob/main/pyproject.toml#L3]) #### Atualizar o CHANGELOG.md Adicione um título para a nova versão com o novo número e a data atual, como [neste exemplo](https://github.com/brazilian-utils/brutils-python/blob/main/CHANGELOG.md?plain=1#L9). E adicione os links da versão, como [neste exemplo](https://github.com/antoniamaia/brutils-python/blob/eac770e8b213532d2bb5948d117f6f4684f65be2/CHANGELOG.md?plain=1#L76) #### Crie o PR Crie um PR com o nome `chore: release ` contendo as duas alterações acima. Na descrição da Pull Request, adicione o trecho do changelog alterado. Exemplo de Release PR: https://github.com/brazilian-utils/brutils-python/pull/596 #### Faça o Merge do PR Assim que o PR for aceito e passar em todas as verificações, faça o merge. ### 3. Deploy via GitHub O lançamento da nova versão em produção é feita automaticamente quando uma [nova release é criada][creating-releases] no GitHub. - Preencha o campo `tag version` com: `v` (por exemplo, `v2.0.0`). - Preencha o campo `release title` com o mesmo valor que a versão da tag (por exemplo, `v2.0.0`). - Preencha o campo `release description` com o conteúdo copiado do arquivo CHANGELOG.md da seção de versão correspondente. Exemplos reais estão disponíveis em: [https://github.com/brazilian-utils/brutils-python/releases][brutils-releases] Quando o Deploy via GitHub for concluído, a nova versão também será lançada automaticamente no [PyPI][brutils-on-pypi]. Baixe a nova versão do brutils do PyPI e teste se tudo está funcionando conforme o esperado. [brutils-issues]: https://github.com/brazilian-utils/brutils-python/issues [brutils-on-pypi]: https://pypi.org/project/brutils/ [brutils-releases]: https://github.com/brazilian-utils/brutils-python/releases [changelog]: https://github.com/brazilian-utils/brutils-python/blob/main/CHANGELOG.md [creating-releases]: https://docs.github.com/pt/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release [docstring-definition]: https://www.python.org/dev/peps/pep-0257/#what-is-a-docstring [github-cloning]: https://docs.github.com/pt/repositories/creating-and-managing-repositories/cloning-a-repository [github-creating-a-pr]: https://docs.github.com/pt/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request [github-essentials-criar-conta]: https://github-essentials.cumbuca.dev/dia-5-contas-e-planos/criacao-de-uma-conta-pessoal-no-github [github-essentials-instalando-o-git]: https://github-essentials.cumbuca.dev/dia-2-controle-de-versao-basico-com-git/git/instalando-o-git [github-forking]: https://docs.github.com/pt/get-started/quickstart/contributing-to-projects [github-join]: https://github.com/join [github-sync-pr]: https://docs.github.com/pt/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/keeping-your-pull-request-in-sync-with-the-base-branch [keep-a-changelog]: https://keepachangelog.com/en/1.0.0/ [poetry]: https://python-poetry.org/docs/#installation [python]: https://www.python.org/downloads/ [release-pr-example]: https://github.com/brazilian-utils/brutils-python/pull/326 [semantic-versioning]: https://semver.org/lang/pt-BR/ [virtualenv]: https://virtualenv.pypa.io/en/latest/ brutils-2.5.0/codecov.yml0000664000000000000000000000002415221040537014042 0ustar rootrootignore: - "tests" brutils-2.5.0/brutils/0000775000000000000000000000000015221040537013365 5ustar rootrootbrutils-2.5.0/brutils/cnpj.py0000664000000000000000000002045415221040537014676 0ustar rootrootfrom itertools import chain from random import choices, randint from string import ascii_uppercase, digits # FORMATTING ############ def sieve(dirty: str) -> str: """ Removes specific symbols from a CNPJ (Brazilian Company Registration Number) string. This function takes a CNPJ string as input and removes all occurrences of the '.', '/' and '-' characters from it. Args: cnpj (str): The CNPJ string containing symbols to be removed. Returns: str: A new string with the specified symbols removed. Example: >>> sieve("12.345/6789-01") "12345678901" >>> sieve("98/76.543-2101") "98765432101" .. note:: This method should not be used in new code and is only provided for backward compatibility. """ return "".join(filter(lambda char: char not in "./-", dirty)) def remove_symbols(dirty: str) -> str: """ This function is an alias for the `sieve` function, offering a more descriptive name. Args: dirty (str): The dirty string containing symbols to be removed. Returns: str: A new string with the specified symbols removed. Example: >>> remove_symbols("12.345/6789-01") "12345678901" >>> remove_symbols("98/76.543-2101") "98765432101" """ return sieve(dirty) def display(cnpj: str) -> str | None: """ Will format an adequately formatted numbers-only CNPJ string, adding in standard formatting visual aid symbols for display. Formats a CNPJ (Brazilian Company Registration Number) string for visual display. This function takes a CNPJ string as input, validates its format, and formats it with standard visual aid symbols for display purposes. Args: cnpj (str): The CNPJ string to be formatted for display. Returns: str: The formatted CNPJ with visual aid symbols if it's valid, None if it's not valid. Example: >>> display("12345678901234") "12.345.678/9012-34" >>> display("98765432100100") "98.765.432/1001-00" .. note:: This method should not be used in new code and is only provided for backward compatibility. """ if ( len(cnpj) != 14 or not _is_alphanumeric(cnpj[:12]) or not cnpj[12:].isdigit() or len(set(cnpj)) == 1 ): return None return "{}.{}.{}/{}-{}".format( cnpj[:2], cnpj[2:5], cnpj[5:8], cnpj[8:12], cnpj[12:] ) def format_cnpj(cnpj: str) -> str | None: """ Formats a CNPJ (Brazilian Company Registration Number) string for visual display. This function takes a CNPJ string as input, validates its format, and formats it with standard visual aid symbols for display purposes. Args: cnpj (str): The CNPJ string to be formatted for display. Returns: str: The formatted CNPJ with visual aid symbols if it's valid, None if it's not valid. Example: >>> format_cnpj("03560714000142") '03.560.714/0001-42' >>> format_cnpj("98765432100100") None """ if not is_valid(cnpj): return None return "{}.{}.{}/{}-{}".format( cnpj[:2], cnpj[2:5], cnpj[5:8], cnpj[8:12], cnpj[12:14] ) # OPERATIONS ############ def _is_alphanumeric(cnpj: str) -> bool: """ Checks whether all characters are digits or uppercase letters. Args: cnpj (str): The CNPJ string to be validated. Returns: bool: True if all characters are either digits or uppercase letters, False otherwise. Example: >>> _is_alphanumeric("035ABC1400Z142") True >>> _is_alphanumeric("0011-22200013!") False """ return all(char in (digits + ascii_uppercase) for char in cnpj) def validate(cnpj: str) -> bool: """ Validates a CNPJ (Brazilian Company Registration Number) by comparing its verifying checksum digits to its base number. This function checks the validity of a CNPJ by comparing its verifying checksum digits to its base number. The input should be a string of digits with the appropriate length. Args: cnpj (str): The CNPJ to be validated. Returns: bool: True if the checksum digits match the base number, False otherwise. Example: >>> validate("03560714000142") True >>> validate("00111222000133") False .. note:: This method should not be used in new code and is only provided for backward compatibility. """ if ( len(cnpj) != 14 or not _is_alphanumeric(cnpj[:12]) or not cnpj[12:].isdigit() or len(set(cnpj)) == 1 ): return False return all( _hashdigit(cnpj, i + 13) == int(v) for i, v in enumerate(cnpj[12:]) ) def is_valid(cnpj: str) -> bool: """ Returns whether or not the verifying checksum digits of the given `cnpj` match its base number. This function does not verify the existence of the CNPJ; it only validates the format of the string. Args: cnpj (str): The CNPJ to be validated, a 14-digit string Returns: bool: True if the checksum digits match the base number, False otherwise. Example: >>> is_valid("03560714000142") True >>> is_valid("00111222000133") False """ return isinstance(cnpj, str) and validate(cnpj) def generate(branch: int | str = 1, alphanumeric: bool = False) -> str: """ Generates a random valid CNPJ string. An optional branch number parameter can be given; it defaults to 1. Use alphanumeric=True to generate a CNPJ whose first 12 characters may contain digits and uppercase letters. Args: branch (int | str): An optional branch number to be included in the CNPJ. Alphanumeric branch values are accepted only with alphanumeric=True. alphanumeric (bool): Whether the generated CNPJ should be alphanumeric. Returns: str: A randomly generated valid CNPJ string. Example: >>> generate() "30180536000105" >>> generate(1234) "01745284123455" >>> generate(branch="AB12", alphanumeric=True) "NX9K79E2AB1200" """ if alphanumeric: branch = str(branch) branch = branch[:4] if len(branch) >= 4 else branch.zfill(4) branch = ( "0001" if branch == "0000" or not _is_alphanumeric(branch) else branch ) base = "".join(choices(digits * 3 + ascii_uppercase, k=8)) + branch return base + _checksum(base) branch = int(branch) branch %= 10000 branch += int(branch == 0) branch = str(branch).zfill(4) base = str(randint(0, 99999999)).zfill(8) + branch return base + _checksum(base) def _hashdigit(cnpj: str, position: int) -> int: """ Calculates the checksum digit at the given `position` for the provided `cnpj`. The input must contain all elements before `position`. Args: cnpj (str): The CNPJ for which the checksum digit is calculated. position (int): The position of the checksum digit to be calculated. Returns: int: The calculated checksum digit. Example: >>> _hashdigit("12345678901234", 13) 3 >>> _hashdigit("98765432100100", 14) 9 """ weightgen = chain(range(position - 8, 1, -1), range(9, 1, -1)) val = ( sum( (ord(digit) - 48) * weight for digit, weight in zip(cnpj, weightgen) ) % 11 ) return 0 if val < 2 else 11 - val def _checksum(basenum: str) -> str: """ Calculates the verifying checksum digits for a given CNPJ base number. This function computes the verifying checksum digits for a provided CNPJ base number. The `basenum` should be a digit-string of the appropriate length. Args: basenum (str): The base number of the CNPJ for which verifying checksum digits are calculated. Returns: str: The verifying checksum digits. Example: >>> _checksum("123456789012") "30" >>> _checksum("987654321001") "41" """ verifying_digits = str(_hashdigit(basenum, 13)) verifying_digits += str(_hashdigit(basenum + verifying_digits, 14)) return verifying_digits brutils-2.5.0/brutils/pis.py0000664000000000000000000000567415221040537014546 0ustar rootrootfrom random import randint WEIGHTS = [3, 2, 9, 8, 7, 6, 5, 4, 3, 2] # FORMATTING ############ def remove_symbols(pis: str) -> str: """ Remove formatting symbols from a PIS. This function takes a PIS (Programa de Integração Social) string with formatting symbols and returns a cleaned version with no symbols. Args: pis (str): A PIS string that may contain formatting symbols. Returns: str: A cleaned PIS string with no formatting symbols. Example: >>> remove_symbols("123.456.789-09") '12345678909' >>> remove_symbols("98765432100") '98765432100' """ return pis.replace(".", "").replace("-", "") def format_pis(pis: str) -> str: """ Format a valid PIS (Programa de Integração Social) string with standard visual aid symbols. This function takes a valid numbers-only PIS string as input and adds standard formatting visual aid symbols for display. Args: pis (str): A valid numbers-only PIS string. Returns: str: A formatted PIS string with standard visual aid symbols or None if the input is invalid. Example: >>> format_pis("12345678909") '123.45678.90-9' >>> format_pis("98765432100") '987.65432.10-0' """ if not is_valid(pis): return None return "{}.{}.{}-{}".format(pis[:3], pis[3:8], pis[8:10], pis[10:11]) # OPERATIONS ############ def is_valid(pis: str) -> bool: """ Returns whether or not the verifying checksum digit of the given `PIS` match its base number. Args: pis (str): PIS number as a string of proper length. Returns: bool: True if PIS is valid, False otherwise. Example: >>> is_valid_pis("82178537464") True >>> is_valid_pis("55550207753") True """ return ( isinstance(pis, str) and len(pis) == 11 and pis.isdigit() and pis[-1] == str(_checksum(pis[:-1])) ) def generate() -> str: """ Generate a random valid Brazilian PIS number. This function generates a random PIS number with the following characteristics: - It has 11 digits - It passes the weight calculation check Args: None Returns: str: A randomly generated valid PIS number as a string. Example: >>> generate() '12345678909' >>> generate() '98765432100' """ base = str(randint(0, 9999999999)).zfill(10) return base + str(_checksum(base)) def _checksum(base_pis: str) -> int: """ Calculate the checksum digit of the given `base_pis` string. Args: base_pis (str): The first 10 digits of a PIS number as a string. Returns: int: The checksum digit. """ pis_digits = list(map(int, base_pis)) pis_sum = sum(digit * weight for digit, weight in zip(pis_digits, WEIGHTS)) check_digit = 11 - (pis_sum % 11) return 0 if check_digit in [10, 11] else check_digit brutils-2.5.0/brutils/cep.py0000664000000000000000000001635015221040537014513 0ustar rootrootfrom json import loads from random import randint from unicodedata import normalize from urllib.request import urlopen from brutils.data.enums import UF from brutils.exceptions import CEPNotFound, InvalidCEP from brutils.schemas import Address # FORMATTING ############ def remove_symbols(dirty: str) -> str: """ Removes specific symbols from a given CEP (Postal Code). This function takes a CEP (Postal Code) as input and removes all occurrences of the '.' and '-' characters from it. Args: cep (str): The input CEP (Postal Code) containing symbols to be removed. Returns: str: A new string with the specified symbols removed. Example: >>> remove_symbols("123-45.678.9") "123456789" >>> remove_symbols("abc.xyz") "abcxyz" """ return "".join(filter(lambda char: char not in ".-", dirty)) def format_cep(cep: str) -> str | None: """ Formats a Brazilian CEP (Postal Code) into a standard format. This function takes a CEP (Postal Code) as input and, if it is a valid 8-digit CEP, formats it into the standard "12345-678" format. Args: cep (str): The input CEP (Postal Code) to be formatted. Returns: str: The formatted CEP in the "12345-678" format if it's valid, None if it's not valid. Example: >>> format_cep("12345678") "12345-678" >>> format_cep("12345") None """ return f"{cep[:5]}-{cep[5:8]}" if is_valid(cep) else None # OPERATIONS ############ def is_valid(cep: str) -> bool: """ Checks if a CEP (Postal Code) is valid. To be considered valid, the input must be a string containing exactly 8 digits. This function does not verify if the CEP is a real postal code; it only validates the format of the string. Args: cep (str): The string containing the CEP to be checked. Returns: bool: True if the CEP is valid (8 digits), False otherwise. Example: >>> is_valid("12345678") True >>> is_valid("12345") False >>> is_valid("abcdefgh") False Source: https://en.wikipedia.org/wiki/Código_de_Endereçamento_Postal """ return isinstance(cep, str) and len(cep) == 8 and cep.isdigit() def generate() -> str: """ Generates a random 8-digit CEP (Postal Code) number as a string. Returns: str: A randomly generated 8-digit number. Example: >>> generate() "12345678" """ generated_number = "" for _ in range(8): generated_number = generated_number + str(randint(0, 9)) return generated_number # Reference: https://viacep.com.br/ def get_address_from_cep( cep: str, raise_exceptions: bool = False ) -> Address | None: """ Fetches address information from a given CEP (Postal Code) using the ViaCEP API. Args: cep (str): The CEP (Postal Code) to be used in the search. raise_exceptions (bool, optional): Whether to raise exceptions when the CEP is invalid or not found. Defaults to False. Raises: InvalidCEP: When the input CEP is invalid. CEPNotFound: When the input CEP is not found. Returns: Address | None: An Address object (TypedDict) containing the address information if the CEP is found, None otherwise. Example: >>> get_address_from_cep("12345678") { "cep": "12345-678", "logradouro": "Rua Example", "complemento": "", "bairro": "Example", "localidade": "Example", "uf": "EX", "ibge": "1234567", "gia": "1234", "ddd": "12", "siafi": "1234" } >>> get_address_from_cep("abcdefg") None >>> get_address_from_cep("abcdefg", True) InvalidCEP: CEP 'abcdefg' is invalid. >>> get_address_from_cep("00000000", True) CEPNotFound: 00000000 """ base_api_url = "https://viacep.com.br/ws/{}/json/" clean_cep = remove_symbols(cep) cep_is_valid = is_valid(clean_cep) if not cep_is_valid: if raise_exceptions: raise InvalidCEP(cep) return None try: with urlopen(base_api_url.format(clean_cep)) as f: response = f.read() data = loads(response) if data.get("erro", False): raise CEPNotFound(cep) return Address(**loads(response)) except Exception as e: if raise_exceptions: raise CEPNotFound(cep) from e return None def get_cep_information_from_address( federal_unit: str, city: str, street: str, raise_exceptions: bool = False ) -> list[Address] | None: """ Fetches CEP (Postal Code) options from a given address using the ViaCEP API. Args: federal_unit (str): The two-letter abbreviation of the Brazilian state. city (str): The name of the city. street (str): The name (or substring) of the street. raise_exceptions (bool, optional): Whether to raise exceptions when the address is invalid or not found. Defaults to False. Raises: ValueError: When the input UF is invalid. CEPNotFound: When the input address is not found. Returns: list[Address] | None: A list of Address objects (TypedDict) containing the address information if the address is found, None otherwise. Example: >>> get_cep_information_from_address("EX", "Example", "Rua Example") [ { "cep": "12345-678", "logradouro": "Rua Example", "complemento": "", "bairro": "Example", "localidade": "Example", "uf": "EX", "ibge": "1234567", "gia": "1234", "ddd": "12", "siafi": "1234" } ] >>> get_cep_information_from_address("A", "Example", "Rua Example") None >>> get_cep_information_from_address("XX", "Example", "Example", True) ValueError: Invalid UF: XX >>> get_cep_information_from_address("SP", "Example", "Example", True) CEPNotFound: SP - Example - Example """ if federal_unit in UF.values: federal_unit = UF(federal_unit).name if federal_unit not in UF.names: if raise_exceptions: raise ValueError(f"Invalid UF: {federal_unit}") return None base_api_url = "https://viacep.com.br/ws/{}/{}/{}/json/" parsed_city = ( normalize("NFD", city) .encode("ascii", "ignore") .decode("utf-8") .replace(" ", "%20") ) parsed_street = ( normalize("NFD", street) .encode("ascii", "ignore") .decode("utf-8") .replace(" ", "%20") ) try: with urlopen( base_api_url.format(federal_unit, parsed_city, parsed_street) ) as f: response = f.read() response = loads(response) if len(response) == 0: raise CEPNotFound(f"{federal_unit} - {city} - {street}") return [Address(**address) for address in response] except Exception as e: if raise_exceptions: raise CEPNotFound(f"{federal_unit} - {city} - {street}") from e return None brutils-2.5.0/brutils/exceptions/0000775000000000000000000000000015221040537015546 5ustar rootrootbrutils-2.5.0/brutils/exceptions/cep.py0000664000000000000000000000025715221040537016673 0ustar rootrootclass InvalidCEP(Exception): def __init__(self, cep): self.cep = cep super().__init__(f"CEP '{cep}' is invalid.") class CEPNotFound(Exception): pass brutils-2.5.0/brutils/exceptions/__init__.py0000664000000000000000000000005115221040537017653 0ustar rootrootfrom .cep import CEPNotFound, InvalidCEP brutils-2.5.0/brutils/data/0000775000000000000000000000000015221040537014276 5ustar rootrootbrutils-2.5.0/brutils/data/legal_process_ids.json0000664000000000000000000002525015221040537020656 0ustar rootroot{ "orgao_1": { "id_tribunal": [1], "id_foro": [0] }, "orgao_2": { "id_tribunal": [2], "id_foro": [0] }, "orgao_3": { "id_tribunal": [3], "id_foro": [0] }, "orgao_4": { "id_tribunal": [1, 2, 3, 4, 5], "id_foro": [ 5120, 4100, 4101, 8200, 8201, 8202, 3600, 3601, 3602, 3603, 3100, 7200, 7201, 7202, 7203, 6181, 6182, 6183, 7205, 7206, 7207, 7208, 7209, 7211, 7213, 7214, 7215, 7216, 6201, 5190, 9810, 9305, 6126, 9830, 4200, 6127, 8300, 8302, 8303, 8304, 9330, 3700, 3701, 3702, 8308, 3200, 3201, 7310, 6301, 6302, 6303, 6304, 6305, 6306, 6307, 9380, 6308, 6309, 6310, 6311, 6312, 6313, 6314, 6315, 6316, 6317, 6318, 6319, 6320, 4300, 8400, 8401, 8402, 9710, 3800, 3801, 3802, 3803, 3804, 3805, 3806, 3807, 3808, 3809, 3810, 3811, 3300, 3301, 3302, 3303, 3304, 3305, 3306, 3307, 3308, 3309, 3310, 3311, 3812, 3813, 3814, 3815, 6401, 9720, 8500, 8501, 8502, 3900, 3901, 3902, 3903, 3904, 9700, 8000, 8001, 8002, 3400, 7000, 7001, 7002, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7011, 7012, 6501, 7013, 7014, 7015, 7016, 6001, 6002, 6003, 6004, 6005, 6006, 6007, 5001, 5002, 5003, 5004, 5005, 9621, 4000, 4001, 8100, 8101, 8102, 8103, 3500, 3501, 3502, 3503, 3504, 3000, 7100, 9661, 7101, 7102, 7103, 7104, 7105, 7106, 7107, 7108, 7109, 7110, 7111, 7112, 7113, 7115, 7116, 7117, 7118, 7119, 7120, 6100, 6102, 6103, 6104, 6105, 6106, 6107, 6108, 6109, 6110, 6111, 6112, 6113, 6114, 6115, 6116, 6117, 6118, 6119, 6120, 6121, 6122, 6123, 6124, 5101, 5102, 5103, 5104, 6125, 5106, 5107, 5108, 5109, 5110, 5111, 5112, 5113, 5114, 5115, 5116, 5117, 5118, 5119 ] }, "orgao_5": { "id_tribunal": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 ], "id_foro": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 161, 171, 172, 181, 182, 191, 192, 193, 194, 195, 201, 202, 203, 204, 205, 206, 207, 211, 221, 222, 223, 224, 225, 226, 231, 232, 233, 241, 242, 243, 244, 245, 246, 247, 251, 252, 253, 254, 255, 261, 262, 263, 264, 271, 281, 282, 291, 292, 301, 302, 303, 304, 305, 311, 312, 313, 314, 315, 316, 317, 318, 319, 321, 322, 325, 331, 332, 333, 341, 342, 343, 351, 352, 361, 371, 372, 373, 381, 382, 383, 384, 391, 401, 402, 403, 404, 411, 412, 413, 416, 421, 425, 426, 431, 432, 433, 434, 436, 441, 442, 443, 444, 445, 446, 447, 451, 459, 461, 462, 463, 464, 465, 466, 471, 472, 481, 482, 491, 492, 493, 501, 511, 512, 513, 521, 522, 531, 541, 551, 561, 562, 567, 571, 581, 585, 594, 601, 611, 612, 621, 631, 641, 643, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 668, 669, 670, 671, 672, 673, 678, 701, 702, 721, 731, 732, 733, 741, 749, 751, 761, 771, 781, 791, 801, 802, 9000, 811, 812, 821, 831, 841, 851, 861, 871, 872, 892, 895, 896, 897, 898, 899, 901, 902, 903, 904, 905, 965, 999 ] }, "orgao_6": { "id_tribunal": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 ], "id_foro": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422 ] }, "orgao_7": { "id_tribunal": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "id_foro": [ 4, 101, 102, 103, 5, 201, 202, 203, 6, 301, 7, 303, 8, 401, 9, 10, 11, 12 ] }, "orgao_8": { "id_tribunal": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 ], "id_foro": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 6200, 62, 63, 64, 65, 66, 67, 68, 70, 69, 71, 72, 73, 74, 75, 76, 77, 78, 79, 81, 80, 82, 83, 84, 85, 86, 87, 88, 89, 90, 92, 91, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 4200, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 132, 133, 134, 136, 137, 138, 131, 135, 141, 142, 139, 144, 145, 146, 140, 148, 4100, 149, 150, 2200, 151, 152, 153, 6300, 154, 155, 156, 157, 158, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 172, 173, 174, 175, 176, 177, 178, 171, 180, 181, 182, 179, 183, 184, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 4300, 204, 205, 206, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 2300, 252, 253, 254, 6400, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 280, 281, 283, 284, 279, 282, 287, 286, 288, 290, 291, 292, 294, 295, 296, 297, 299, 300, 301, 302, 303, 4400, 306, 309, 310, 311, 312, 313, 315, 317, 318, 319, 320, 321, 322, 323, 324, 325, 2100, 327, 326, 329, 330, 331, 332, 333, 334, 335, 337, 338, 340, 341, 342, 343, 344, 346, 347, 348, 349, 350, 351, 2400, 352, 355, 6500, 356, 358, 357, 360, 361, 362, 363, 366, 368, 369, 370, 371, 372, 374, 377, 378, 380, 381, 382, 383, 384, 386, 388, 390, 391, 392, 393, 394, 395, 396, 397, 398, 400, 401, 4500, 405, 404, 407, 408, 410, 411, 412, 414, 415, 416, 417, 418, 420, 421, 422, 424, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 438, 439, 440, 441, 443, 444, 445, 446, 447, 449, 450, 451, 2500, 452, 453, 6600, 456, 457, 459, 460, 461, 458, 462, 464, 466, 467, 470, 471, 472, 473, 474, 476, 477, 479, 480, 481, 482, 483, 484, 486, 487, 488, 490, 491, 493, 495, 498, 499, 500, 501, 4600, 505, 506, 508, 510, 511, 512, 514, 515, 516, 517, 518, 520, 521, 522, 523, 525, 526, 527, 528, 529, 530, 531, 533, 534, 538, 539, 540, 541, 542, 543, 547, 549, 550, 551, 2600, 553, 554, 555, 6700, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 567, 568, 566, 570, 571, 572, 575, 576, 577, 579, 580, 581, 582, 584, 586, 587, 588, 589, 590, 592, 595, 596, 597, 598, 600, 601, 602, 603, 4700, 604, 606, 607, 609, 610, 611, 614, 615, 619, 620, 621, 624, 625, 627, 628, 629, 630, 631, 634, 637, 638, 640, 642, 643, 646, 647, 648, 650, 651, 2700, 653, 654, 655, 6800, 657, 659, 660, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 4800, 704, 705, 707, 708, 706, 710, 709, 713, 718, 143, 720, 730, 731, 147, 741, 740, 750, 751, 2800, 6900, 760, 761, 770, 775, 778, 780, 781, 790, 159, 800, 4900, 160, 9000, 9001, 9002, 9003, 9004, 9005, 9006, 9007, 9008, 9009, 810, 9010, 820, 830, 831, 840, 850, 2900, 7000, 860, 870, 878, 879, 880, 881, 890, 900, 902, 903, 5000, 904, 905, 906, 907, 908, 910, 911, 909, 920, 930, 185, 940, 941, 950, 951, 3000, 7100, 960, 970, 980, 981, 990, 1000, 5100, 1010, 1020, 1030, 1040, 207, 1050, 3100, 7200, 1060, 1070, 1071, 1080, 1090, 1100, 5200, 1110, 1111, 1120, 1130, 1140, 1150, 3200, 7300, 1160, 1161, 1170, 1171, 1180, 1190, 1200, 1201, 5300, 1210, 1211, 1220, 1230, 1240, 1250, 3300, 7400, 1260, 1270, 1280, 1290, 1300, 5400, 1310, 1320, 1330, 1340, 1350, 3400, 7500, 1360, 1370, 1380, 1390, 1400, 5500, 1410, 1420, 1430, 1440, 1450, 3500, 7600, 1460, 1470, 1480, 1490, 1500, 5600, 1510, 1520, 1530, 1540, 1550, 3600, 1560, 1570, 1580, 1590, 1600, 5700, 1610, 1620, 1630, 3700, 7800, 5800, 3800, 5900, 9999, 3900, 6000, 4000, 2000, 2001, 2002, 2003, 6100, 2004, 2005 ] }, "orgao_9": { "id_tribunal": [21, 26, 13], "id_foro": [0, 1, 2, 3, 4, 40, 10, 20, 21, 90, 91, 30] } } brutils-2.5.0/brutils/data/enums/0000775000000000000000000000000015221040537015425 5ustar rootrootbrutils-2.5.0/brutils/data/enums/better_enum.py0000664000000000000000000000046615221040537020316 0ustar rootrootfrom enum import Enum, EnumMeta class __MetaEnum(EnumMeta): @property def names(cls): return sorted(cls._member_names_) @property def values(cls): return sorted(list(map(lambda x: x.value, cls._member_map_.values()))) class BetterEnum(Enum, metaclass=__MetaEnum): pass brutils-2.5.0/brutils/data/enums/months.py0000664000000000000000000000273515221040537017316 0ustar rootrootfrom brutils.data.enums.better_enum import BetterEnum class MonthsEnum(BetterEnum): JANEIRO = 1 FEVEREIRO = 2 MARCO = 3 ABRIL = 4 MAIO = 5 JUNHO = 6 JULHO = 7 AGOSTO = 8 SETEMBRO = 9 OUTUBRO = 10 NOVEMBRO = 11 DEZEMBRO = 12 @property def month_name(self) -> str: if self == MonthsEnum.JANEIRO: return "janeiro" elif self == MonthsEnum.FEVEREIRO: return "fevereiro" elif self == MonthsEnum.MARCO: return "marco" elif self == MonthsEnum.ABRIL: return "abril" elif self == MonthsEnum.MAIO: return "maio" elif self == MonthsEnum.JUNHO: return "junho" elif self == MonthsEnum.JULHO: return "julho" elif self == MonthsEnum.AGOSTO: return "agosto" elif self == MonthsEnum.SETEMBRO: return "setembro" elif self == MonthsEnum.OUTUBRO: return "outubro" elif self == MonthsEnum.NOVEMBRO: return "novembro" else: return "dezembro" @classmethod def is_valid_month(cls, month: int) -> bool: """ Checks if the given month value is valid. Args: month (int): The month to check. Returns: True if the month is valid, False otherwise. """ return ( True if month in set(month.value for month in MonthsEnum) else False ) brutils-2.5.0/brutils/data/enums/uf.py0000664000000000000000000000206215221040537016411 0ustar rootrootfrom brutils.data.enums.better_enum import BetterEnum class UF(BetterEnum): AC = "Acre" AL = "Alagoas" AP = "Amapá" AM = "Amazonas" BA = "Bahia" CE = "Ceará" DF = "Distrito Federal" ES = "Espírito Santo" GO = "Goiás" MA = "Maranhão" MT = "Mato Grosso" MS = "Mato Grosso do Sul" MG = "Minas Gerais" PA = "Pará" PB = "Paraíba" PR = "Paraná" PE = "Pernambuco" PI = "Piauí" RJ = "Rio de Janeiro" RN = "Rio Grande do Norte" RS = "Rio Grande do Sul" RO = "Rondônia" RR = "Roraima" SC = "Santa Catarina" SP = "São Paulo" SE = "Sergipe" TO = "Tocantins" class UF_CODE(BetterEnum): AC = "12" AL = "27" AP = "16" AM = "13" BA = "29" CE = "23" DF = "53" ES = "32" GO = "52" MA = "21" MT = "51" MS = "50" MG = "31" PA = "15" PB = "25" PR = "41" PE = "26" PI = "22" RJ = "33" RN = "24" RS = "43" RO = "11" RR = "14" SC = "42" SP = "35" SE = "28" TO = "17" brutils-2.5.0/brutils/data/enums/__init__.py0000664000000000000000000000005615221040537017537 0ustar rootrootfrom brutils.data.enums.uf import UF, UF_CODE brutils-2.5.0/brutils/data/cities_code.json0000664000000000000000000057134515221040537017462 0ustar rootroot{ "RO": { "alta floresta d'oeste": "1100015", "alto alegre dos parecis": "1100379", "alto paraiso": "1100403", "alvorada d'oeste": "1100346", "ariquemes": "1100023", "buritis": "1100452", "cabixi": "1100031", "cacaulandia": "1100601", "cacoal": "1100049", "campo novo de rondonia": "1100700", "candeias do jamari": "1100809", "castanheiras": "1100908", "cerejeiras": "1100056", "chupinguaia": "1100924", "colorado do oeste": "1100064", "corumbiara": "1100072", "costa marques": "1100080", "cujubim": "1100940", "espigao d'oeste": "1100098", "governador jorge teixeira": "1101005", "guajara-mirim": "1100106", "itapua do oeste": "1101104", "jaru": "1100114", "ji-parana": "1100122", "machadinho d'oeste": "1100130", "ministro andreazza": "1101203", "mirante da serra": "1101302", "monte negro": "1101401", "nova brasilandia d'oeste": "1100148", "nova mamore": "1100338", "nova uniao": "1101435", "novo horizonte do oeste": "1100502", "ouro preto do oeste": "1100155", "parecis": "1101450", "pimenta bueno": "1100189", "pimenteiras do oeste": "1101468", "porto velho": "1100205", "presidente medici": "1100254", "primavera de rondonia": "1101476", "rio crespo": "1100262", "rolim de moura": "1100288", "santa luzia d'oeste": "1100296", "sao felipe d'oeste": "1101484", "sao francisco do guapore": "1101492", "sao miguel do guapore": "1100320", "seringueiras": "1101500", "teixeiropolis": "1101559", "theobroma": "1101609", "urupa": "1101708", "vale do anari": "1101757", "vale do paraiso": "1101807", "vilhena": "1100304" }, "AC": { "acrelandia": "1200013", "assis brasil": "1200054", "brasileia": "1200104", "bujari": "1200138", "capixaba": "1200179", "cruzeiro do sul": "1200203", "epitaciolandia": "1200252", "feijo": "1200302", "jordao": "1200328", "mancio lima": "1200336", "manoel urbano": "1200344", "marechal thaumaturgo": "1200351", "placido de castro": "1200385", "porto acre": "1200807", "porto walter": "1200393", "rio branco": "1200401", "rodrigues alves": "1200427", "santa rosa do purus": "1200435", "sena madureira": "1200500", "senador guiomard": "1200450", "tarauaca": "1200609", "xapuri": "1200708" }, "AM": { "alvaraes": "1300029", "amatura": "1300060", "anama": "1300086", "anori": "1300102", "apui": "1300144", "atalaia do norte": "1300201", "autazes": "1300300", "barcelos": "1300409", "barreirinha": "1300508", "benjamin constant": "1300607", "beruri": "1300631", "boa vista do ramos": "1300680", "boca do acre": "1300706", "borba": "1300805", "caapiranga": "1300839", "canutama": "1300904", "carauari": "1301001", "careiro": "1301100", "careiro da varzea": "1301159", "coari": "1301209", "codajas": "1301308", "eirunepe": "1301407", "envira": "1301506", "fonte boa": "1301605", "guajara": "1301654", "humaita": "1301704", "ipixuna": "1301803", "iranduba": "1301852", "itacoatiara": "1301902", "itamarati": "1301951", "itapiranga": "1302009", "japura": "1302108", "jurua": "1302207", "jutai": "1302306", "labrea": "1302405", "manacapuru": "1302504", "manaquiri": "1302553", "manaus": "1302603", "manicore": "1302702", "maraa": "1302801", "maues": "1302900", "nhamunda": "1303007", "nova olinda do norte": "1303106", "novo airao": "1303205", "novo aripuana": "1303304", "parintins": "1303403", "pauini": "1303502", "presidente figueiredo": "1303536", "rio preto da eva": "1303569", "santa isabel do rio negro": "1303601", "santo antonio do ica": "1303700", "sao gabriel da cachoeira": "1303809", "sao paulo de olivenca": "1303908", "sao sebastiao do uatuma": "1303957", "silves": "1304005", "tabatinga": "1304062", "tapaua": "1304104", "tefe": "1304203", "tonantins": "1304237", "uarini": "1304260", "urucara": "1304302", "urucurituba": "1304401" }, "RR": { "alto alegre": "1400050", "amajari": "1400027", "boa vista": "1400100", "bonfim": "1400159", "canta": "1400175", "caracarai": "1400209", "caroebe": "1400233", "iracema": "1400282", "mucajai": "1400308", "normandia": "1400407", "pacaraima": "1400456", "rorainopolis": "1400472", "sao joao da baliza": "1400506", "sao luiz": "1400605", "uiramuta": "1400704" }, "PA": { "abaetetuba": "1500107", "abel figueiredo": "1500131", "acara": "1500206", "afua": "1500305", "agua azul do norte": "1500347", "alenquer": "1500404", "almeirim": "1500503", "altamira": "1500602", "anajas": "1500701", "ananindeua": "1500800", "anapu": "1500859", "augusto correa": "1500909", "aurora do para": "1500958", "aveiro": "1501006", "bagre": "1501105", "baiao": "1501204", "bannach": "1501253", "barcarena": "1501303", "belem": "1501402", "belterra": "1501451", "benevides": "1501501", "bom jesus do tocantins": "1501576", "bonito": "1501600", "braganca": "1501709", "brasil novo": "1501725", "brejo grande do araguaia": "1501758", "breu branco": "1501782", "breves": "1501808", "bujaru": "1501907", "cachoeira do arari": "1502004", "cachoeira do piria": "1501956", "cameta": "1502103", "canaa dos carajas": "1502152", "capanema": "1502202", "capitao poco": "1502301", "castanhal": "1502400", "chaves": "1502509", "colares": "1502608", "conceicao do araguaia": "1502707", "concordia do para": "1502756", "cumaru do norte": "1502764", "curionopolis": "1502772", "curralinho": "1502806", "curua": "1502855", "curuca": "1502905", "dom eliseu": "1502939", "eldorado do carajas": "1502954", "faro": "1503002", "floresta do araguaia": "1503044", "garrafao do norte": "1503077", "goianesia do para": "1503093", "gurupa": "1503101", "igarape-acu": "1503200", "igarape-miri": "1503309", "inhangapi": "1503408", "ipixuna do para": "1503457", "irituia": "1503507", "itaituba": "1503606", "itupiranga": "1503705", "jacareacanga": "1503754", "jacunda": "1503804", "juruti": "1503903", "limoeiro do ajuru": "1504000", "mae do rio": "1504059", "magalhaes barata": "1504109", "maraba": "1504208", "maracana": "1504307", "marapanim": "1504406", "marituba": "1504422", "medicilandia": "1504455", "melgaco": "1504505", "mocajuba": "1504604", "moju": "1504703", "mojui dos campos": "1504752", "monte alegre": "1504802", "muana": "1504901", "nova esperanca do piria": "1504950", "nova ipixuna": "1504976", "nova timboteua": "1505007", "novo progresso": "1505031", "novo repartimento": "1505064", "obidos": "1505106", "oeiras do para": "1505205", "oriximina": "1505304", "ourem": "1505403", "ourilandia do norte": "1505437", "pacaja": "1505486", "palestina do para": "1505494", "paragominas": "1505502", "parauapebas": "1505536", "pau d'arco": "1505551", "peixe-boi": "1505601", "picarra": "1505635", "placas": "1505650", "ponta de pedras": "1505700", "portel": "1505809", "porto de moz": "1505908", "prainha": "1506005", "primavera": "1506104", "quatipuru": "1506112", "redencao": "1506138", "rio maria": "1506161", "rondon do para": "1506187", "ruropolis": "1506195", "salinopolis": "1506203", "salvaterra": "1506302", "santa barbara do para": "1506351", "santa cruz do arari": "1506401", "santa izabel do para": "1506500", "santa luzia do para": "1506559", "santa maria das barreiras": "1506583", "santa maria do para": "1506609", "santana do araguaia": "1506708", "santarem": "1506807", "santarem novo": "1506906", "santo antonio do taua": "1507003", "sao caetano de odivelas": "1507102", "sao domingos do araguaia": "1507151", "sao domingos do capim": "1507201", "sao felix do xingu": "1507300", "sao francisco do para": "1507409", "sao geraldo do araguaia": "1507458", "sao joao da ponta": "1507466", "sao joao de pirabas": "1507474", "sao joao do araguaia": "1507508", "sao miguel do guama": "1507607", "sao sebastiao da boa vista": "1507706", "sapucaia": "1507755", "senador jose porfirio": "1507805", "soure": "1507904", "tailandia": "1507953", "terra alta": "1507961", "terra santa": "1507979", "tome-acu": "1508001", "tracuateua": "1508035", "trairao": "1508050", "tucuma": "1508084", "tucurui": "1508100", "ulianopolis": "1508126", "uruara": "1508159", "vigia": "1508209", "viseu": "1508308", "vitoria do xingu": "1508357", "xinguara": "1508407" }, "AP": { "amapa": "1600105", "calcoene": "1600204", "cutias": "1600212", "ferreira gomes": "1600238", "itaubal": "1600253", "laranjal do jari": "1600279", "macapa": "1600303", "mazagao": "1600402", "oiapoque": "1600501", "pedra branca do amapari": "1600154", "porto grande": "1600535", "pracuuba": "1600550", "santana": "1600600", "serra do navio": "1600055", "tartarugalzinho": "1600709", "vitoria do jari": "1600808" }, "TO": { "abreulandia": "1700251", "aguiarnopolis": "1700301", "alianca do tocantins": "1700350", "almas": "1700400", "alvorada": "1700707", "ananas": "1701002", "angico": "1701051", "aparecida do rio negro": "1701101", "aragominas": "1701309", "araguacema": "1701903", "araguacu": "1702000", "araguaina": "1702109", "araguana": "1702158", "araguatins": "1702208", "arapoema": "1702307", "arraias": "1702406", "augustinopolis": "1702554", "aurora do tocantins": "1702703", "axixa do tocantins": "1702901", "babaculandia": "1703008", "bandeirantes do tocantins": "1703057", "barra do ouro": "1703073", "barrolandia": "1703107", "bernardo sayao": "1703206", "bom jesus do tocantins": "1703305", "brasilandia do tocantins": "1703602", "brejinho de nazare": "1703701", "buriti do tocantins": "1703800", "cachoeirinha": "1703826", "campos lindos": "1703842", "cariri do tocantins": "1703867", "carmolandia": "1703883", "carrasco bonito": "1703891", "caseara": "1703909", "centenario": "1704105", "chapada da natividade": "1705102", "chapada de areia": "1704600", "colinas do tocantins": "1705508", "colmeia": "1716703", "combinado": "1705557", "conceicao do tocantins": "1705607", "couto magalhaes": "1706001", "cristalandia": "1706100", "crixas do tocantins": "1706258", "darcinopolis": "1706506", "dianopolis": "1707009", "divinopolis do tocantins": "1707108", "dois irmaos do tocantins": "1707207", "duere": "1707306", "esperantina": "1707405", "fatima": "1707553", "figueiropolis": "1707652", "filadelfia": "1707702", "formoso do araguaia": "1708205", "goianorte": "1708304", "goiatins": "1709005", "guarai": "1709302", "gurupi": "1709500", "ipueiras": "1709807", "itacaja": "1710508", "itaguatins": "1710706", "itapiratins": "1710904", "itapora do tocantins": "1711100", "jau do tocantins": "1711506", "juarina": "1711803", "lagoa da confusao": "1711902", "lagoa do tocantins": "1711951", "lajeado": "1712009", "lavandeira": "1712157", "lizarda": "1712405", "luzinopolis": "1712454", "marianopolis do tocantins": "1712504", "mateiros": "1712702", "maurilandia do tocantins": "1712801", "miracema do tocantins": "1713205", "miranorte": "1713304", "monte do carmo": "1713601", "monte santo do tocantins": "1713700", "muricilandia": "1713957", "natividade": "1714203", "nazare": "1714302", "nova olinda": "1714880", "nova rosalandia": "1715002", "novo acordo": "1715101", "novo alegre": "1715150", "novo jardim": "1715259", "oliveira de fatima": "1715507", "palmas": "1721000", "palmeirante": "1715705", "palmeiras do tocantins": "1713809", "palmeiropolis": "1715754", "paraiso do tocantins": "1716109", "parana": "1716208", "pau d'arco": "1716307", "pedro afonso": "1716505", "peixe": "1716604", "pequizeiro": "1716653", "pindorama do tocantins": "1717008", "piraque": "1717206", "pium": "1717503", "ponte alta do bom jesus": "1717800", "ponte alta do tocantins": "1717909", "porto alegre do tocantins": "1718006", "porto nacional": "1718204", "praia norte": "1718303", "presidente kennedy": "1718402", "pugmil": "1718451", "recursolandia": "1718501", "riachinho": "1718550", "rio da conceicao": "1718659", "rio dos bois": "1718709", "rio sono": "1718758", "sampaio": "1718808", "sandolandia": "1718840", "santa fe do araguaia": "1718865", "santa maria do tocantins": "1718881", "santa rita do tocantins": "1718899", "santa rosa do tocantins": "1718907", "santa tereza do tocantins": "1719004", "santa terezinha do tocantins": "1720002", "sao bento do tocantins": "1720101", "sao felix do tocantins": "1720150", "sao miguel do tocantins": "1720200", "sao salvador do tocantins": "1720259", "sao sebastiao do tocantins": "1720309", "sao valerio": "1720499", "silvanopolis": "1720655", "sitio novo do tocantins": "1720804", "sucupira": "1720853", "tabocao": "1708254", "taguatinga": "1720903", "taipas do tocantins": "1720937", "talisma": "1720978", "tocantinia": "1721109", "tocantinopolis": "1721208", "tupirama": "1721257", "tupiratins": "1721307", "wanderlandia": "1722081", "xambioa": "1722107" }, "MA": { "acailandia": "2100055", "afonso cunha": "2100105", "agua doce do maranhao": "2100154", "alcantara": "2100204", "aldeias altas": "2100303", "altamira do maranhao": "2100402", "alto alegre do maranhao": "2100436", "alto alegre do pindare": "2100477", "alto parnaiba": "2100501", "amapa do maranhao": "2100550", "amarante do maranhao": "2100600", "anajatuba": "2100709", "anapurus": "2100808", "apicum-acu": "2100832", "araguana": "2100873", "araioses": "2100907", "arame": "2100956", "arari": "2101004", "axixa": "2101103", "bacabal": "2101202", "bacabeira": "2101251", "bacuri": "2101301", "bacurituba": "2101350", "balsas": "2101400", "barao de grajau": "2101509", "barra do corda": "2101608", "barreirinhas": "2101707", "bela vista do maranhao": "2101772", "belagua": "2101731", "benedito leite": "2101806", "bequimao": "2101905", "bernardo do mearim": "2101939", "boa vista do gurupi": "2101970", "bom jardim": "2102002", "bom jesus das selvas": "2102036", "bom lugar": "2102077", "brejo": "2102101", "brejo de areia": "2102150", "buriti": "2102200", "buriti bravo": "2102309", "buriticupu": "2102325", "buritirana": "2102358", "cachoeira grande": "2102374", "cajapio": "2102408", "cajari": "2102507", "campestre do maranhao": "2102556", "candido mendes": "2102606", "cantanhede": "2102705", "capinzal do norte": "2102754", "carolina": "2102804", "carutapera": "2102903", "caxias": "2103000", "cedral": "2103109", "central do maranhao": "2103125", "centro do guilherme": "2103158", "centro novo do maranhao": "2103174", "chapadinha": "2103208", "cidelandia": "2103257", "codo": "2103307", "coelho neto": "2103406", "colinas": "2103505", "conceicao do lago-acu": "2103554", "coroata": "2103604", "cururupu": "2103703", "davinopolis": "2103752", "dom pedro": "2103802", "duque bacelar": "2103901", "esperantinopolis": "2104008", "estreito": "2104057", "feira nova do maranhao": "2104073", "fernando falcao": "2104081", "formosa da serra negra": "2104099", "fortaleza dos nogueiras": "2104107", "fortuna": "2104206", "godofredo viana": "2104305", "goncalves dias": "2104404", "governador archer": "2104503", "governador edison lobao": "2104552", "governador eugenio barros": "2104602", "governador luiz rocha": "2104628", "governador newton bello": "2104651", "governador nunes freire": "2104677", "graca aranha": "2104701", "grajau": "2104800", "guimaraes": "2104909", "humberto de campos": "2105005", "icatu": "2105104", "igarape do meio": "2105153", "igarape grande": "2105203", "imperatriz": "2105302", "itaipava do grajau": "2105351", "itapecuru mirim": "2105401", "itinga do maranhao": "2105427", "jatoba": "2105450", "jenipapo dos vieiras": "2105476", "joao lisboa": "2105500", "joselandia": "2105609", "junco do maranhao": "2105658", "lago da pedra": "2105708", "lago do junco": "2105807", "lago dos rodrigues": "2105948", "lago verde": "2105906", "lagoa do mato": "2105922", "lagoa grande do maranhao": "2105963", "lajeado novo": "2105989", "lima campos": "2106003", "loreto": "2106102", "luis domingues": "2106201", "magalhaes de almeida": "2106300", "maracacume": "2106326", "maraja do sena": "2106359", "maranhaozinho": "2106375", "mata roma": "2106409", "matinha": "2106508", "matoes": "2106607", "matoes do norte": "2106631", "milagres do maranhao": "2106672", "mirador": "2106706", "miranda do norte": "2106755", "mirinzal": "2106805", "moncao": "2106904", "montes altos": "2107001", "morros": "2107100", "nina rodrigues": "2107209", "nova colinas": "2107258", "nova iorque": "2107308", "nova olinda do maranhao": "2107357", "olho d'agua das cunhas": "2107407", "olinda nova do maranhao": "2107456", "paco do lumiar": "2107506", "palmeirandia": "2107605", "paraibano": "2107704", "parnarama": "2107803", "passagem franca": "2107902", "pastos bons": "2108009", "paulino neves": "2108058", "paulo ramos": "2108108", "pedreiras": "2108207", "pedro do rosario": "2108256", "penalva": "2108306", "peri mirim": "2108405", "peritoro": "2108454", "pindare-mirim": "2108504", "pinheiro": "2108603", "pio xii": "2108702", "pirapemas": "2108801", "pocao de pedras": "2108900", "porto franco": "2109007", "porto rico do maranhao": "2109056", "presidente dutra": "2109106", "presidente juscelino": "2109205", "presidente medici": "2109239", "presidente sarney": "2109270", "presidente vargas": "2109304", "primeira cruz": "2109403", "raposa": "2109452", "riachao": "2109502", "ribamar fiquene": "2109551", "rosario": "2109601", "sambaiba": "2109700", "santa filomena do maranhao": "2109759", "santa helena": "2109809", "santa ines": "2109908", "santa luzia": "2110005", "santa luzia do parua": "2110039", "santa quiteria do maranhao": "2110104", "santa rita": "2110203", "santana do maranhao": "2110237", "santo amaro do maranhao": "2110278", "santo antonio dos lopes": "2110302", "sao benedito do rio preto": "2110401", "sao bento": "2110500", "sao bernardo": "2110609", "sao domingos do azeitao": "2110658", "sao domingos do maranhao": "2110708", "sao felix de balsas": "2110807", "sao francisco do brejao": "2110856", "sao francisco do maranhao": "2110906", "sao joao batista": "2111003", "sao joao do caru": "2111029", "sao joao do paraiso": "2111052", "sao joao do soter": "2111078", "sao joao dos patos": "2111102", "sao jose de ribamar": "2111201", "sao jose dos basilios": "2111250", "sao luis": "2111300", "sao luis gonzaga do maranhao": "2111409", "sao mateus do maranhao": "2111508", "sao pedro da agua branca": "2111532", "sao pedro dos crentes": "2111573", "sao raimundo das mangabeiras": "2111607", "sao raimundo do doca bezerra": "2111631", "sao roberto": "2111672", "sao vicente ferrer": "2111706", "satubinha": "2111722", "senador alexandre costa": "2111748", "senador la rocque": "2111763", "serrano do maranhao": "2111789", "sitio novo": "2111805", "sucupira do norte": "2111904", "sucupira do riachao": "2111953", "tasso fragoso": "2112001", "timbiras": "2112100", "timon": "2112209", "trizidela do vale": "2112233", "tufilandia": "2112274", "tuntum": "2112308", "turiacu": "2112407", "turilandia": "2112456", "tutoia": "2112506", "urbano santos": "2112605", "vargem grande": "2112704", "viana": "2112803", "vila nova dos martirios": "2112852", "vitoria do mearim": "2112902", "vitorino freire": "2113009", "ze doca": "2114007" }, "PI": { "acaua": "2200053", "agricolandia": "2200103", "agua branca": "2200202", "alagoinha do piaui": "2200251", "alegrete do piaui": "2200277", "alto longa": "2200301", "altos": "2200400", "alvorada do gurgueia": "2200459", "amarante": "2200509", "angical do piaui": "2200608", "anisio de abreu": "2200707", "antonio almeida": "2200806", "aroazes": "2200905", "aroeiras do itaim": "2200954", "arraial": "2201002", "assuncao do piaui": "2201051", "avelino lopes": "2201101", "baixa grande do ribeiro": "2201150", "barra d'alcantara": "2201176", "barras": "2201200", "barreiras do piaui": "2201309", "barro duro": "2201408", "batalha": "2201507", "bela vista do piaui": "2201556", "belem do piaui": "2201572", "beneditinos": "2201606", "bertolinia": "2201705", "betania do piaui": "2201739", "boa hora": "2201770", "bocaina": "2201804", "bom jesus": "2201903", "bom principio do piaui": "2201919", "bonfim do piaui": "2201929", "boqueirao do piaui": "2201945", "brasileira": "2201960", "brejo do piaui": "2201988", "buriti dos lopes": "2202000", "buriti dos montes": "2202026", "cabeceiras do piaui": "2202059", "cajazeiras do piaui": "2202075", "cajueiro da praia": "2202083", "caldeirao grande do piaui": "2202091", "campinas do piaui": "2202109", "campo alegre do fidalgo": "2202117", "campo grande do piaui": "2202133", "campo largo do piaui": "2202174", "campo maior": "2202208", "canavieira": "2202251", "canto do buriti": "2202307", "capitao de campos": "2202406", "capitao gervasio oliveira": "2202455", "caracol": "2202505", "caraubas do piaui": "2202539", "caridade do piaui": "2202554", "castelo do piaui": "2202604", "caxingo": "2202653", "cocal": "2202703", "cocal de telha": "2202711", "cocal dos alves": "2202729", "coivaras": "2202737", "colonia do gurgueia": "2202752", "colonia do piaui": "2202778", "conceicao do caninde": "2202802", "coronel jose dias": "2202851", "corrente": "2202901", "cristalandia do piaui": "2203008", "cristino castro": "2203107", "curimata": "2203206", "currais": "2203230", "curral novo do piaui": "2203271", "curralinhos": "2203255", "demerval lobao": "2203305", "dirceu arcoverde": "2203354", "dom expedito lopes": "2203404", "dom inocencio": "2203453", "domingos mourao": "2203420", "elesbao veloso": "2203503", "eliseu martins": "2203602", "esperantina": "2203701", "fartura do piaui": "2203750", "flores do piaui": "2203800", "floresta do piaui": "2203859", "floriano": "2203909", "francinopolis": "2204006", "francisco ayres": "2204105", "francisco macedo": "2204154", "francisco santos": "2204204", "fronteiras": "2204303", "geminiano": "2204352", "gilbues": "2204402", "guadalupe": "2204501", "guaribas": "2204550", "hugo napoleao": "2204600", "ilha grande": "2204659", "inhuma": "2204709", "ipiranga do piaui": "2204808", "isaias coelho": "2204907", "itainopolis": "2205003", "itaueira": "2205102", "jacobina do piaui": "2205151", "jaicos": "2205201", "jardim do mulato": "2205250", "jatoba do piaui": "2205276", "jerumenha": "2205300", "joao costa": "2205359", "joaquim pires": "2205409", "joca marques": "2205458", "jose de freitas": "2205508", "juazeiro do piaui": "2205516", "julio borges": "2205524", "jurema": "2205532", "lagoa alegre": "2205557", "lagoa de sao francisco": "2205573", "lagoa do barro do piaui": "2205565", "lagoa do piaui": "2205581", "lagoa do sitio": "2205599", "lagoinha do piaui": "2205540", "landri sales": "2205607", "luis correia": "2205706", "luzilandia": "2205805", "madeiro": "2205854", "manoel emidio": "2205904", "marcolandia": "2205953", "marcos parente": "2206001", "massape do piaui": "2206050", "matias olimpio": "2206100", "miguel alves": "2206209", "miguel leao": "2206308", "milton brandao": "2206357", "monsenhor gil": "2206407", "monsenhor hipolito": "2206506", "monte alegre do piaui": "2206605", "morro cabeca no tempo": "2206654", "morro do chapeu do piaui": "2206670", "murici dos portelas": "2206696", "nazare do piaui": "2206704", "nazaria": "2206720", "nossa senhora de nazare": "2206753", "nossa senhora dos remedios": "2206803", "nova santa rita": "2207959", "novo oriente do piaui": "2206902", "novo santo antonio": "2206951", "oeiras": "2207009", "olho d'agua do piaui": "2207108", "padre marcos": "2207207", "paes landim": "2207306", "pajeu do piaui": "2207355", "palmeira do piaui": "2207405", "palmeirais": "2207504", "paqueta": "2207553", "parnagua": "2207603", "parnaiba": "2207702", "passagem franca do piaui": "2207751", "patos do piaui": "2207777", "pau d'arco do piaui": "2207793", "paulistana": "2207801", "pavussu": "2207850", "pedro ii": "2207900", "pedro laurentino": "2207934", "picos": "2208007", "pimenteiras": "2208106", "pio ix": "2208205", "piracuruca": "2208304", "piripiri": "2208403", "porto": "2208502", "porto alegre do piaui": "2208551", "prata do piaui": "2208601", "queimada nova": "2208650", "redencao do gurgueia": "2208700", "regeneracao": "2208809", "riacho frio": "2208858", "ribeira do piaui": "2208874", "ribeiro goncalves": "2208908", "rio grande do piaui": "2209005", "santa cruz do piaui": "2209104", "santa cruz dos milagres": "2209153", "santa filomena": "2209203", "santa luz": "2209302", "santa rosa do piaui": "2209377", "santana do piaui": "2209351", "santo antonio de lisboa": "2209401", "santo antonio dos milagres": "2209450", "santo inacio do piaui": "2209500", "sao braz do piaui": "2209559", "sao felix do piaui": "2209609", "sao francisco de assis do piaui": "2209658", "sao francisco do piaui": "2209708", "sao goncalo do gurgueia": "2209757", "sao goncalo do piaui": "2209807", "sao joao da canabrava": "2209856", "sao joao da fronteira": "2209872", "sao joao da serra": "2209906", "sao joao da varjota": "2209955", "sao joao do arraial": "2209971", "sao joao do piaui": "2210003", "sao jose do divino": "2210052", "sao jose do peixe": "2210102", "sao jose do piaui": "2210201", "sao juliao": "2210300", "sao lourenco do piaui": "2210359", "sao luis do piaui": "2210375", "sao miguel da baixa grande": "2210383", "sao miguel do fidalgo": "2210391", "sao miguel do tapuio": "2210409", "sao pedro do piaui": "2210508", "sao raimundo nonato": "2210607", "sebastiao barros": "2210623", "sebastiao leal": "2210631", "sigefredo pacheco": "2210656", "simoes": "2210706", "simplicio mendes": "2210805", "socorro do piaui": "2210904", "sussuapara": "2210938", "tamboril do piaui": "2210953", "tanque do piaui": "2210979", "teresina": "2211001", "uniao": "2211100", "urucui": "2211209", "valenca do piaui": "2211308", "varzea branca": "2211357", "varzea grande": "2211407", "vera mendes": "2211506", "vila nova do piaui": "2211605", "wall ferraz": "2211704" }, "CE": { "abaiara": "2300101", "acarape": "2300150", "acarau": "2300200", "acopiara": "2300309", "aiuaba": "2300408", "alcantaras": "2300507", "altaneira": "2300606", "alto santo": "2300705", "amontada": "2300754", "antonina do norte": "2300804", "apuiares": "2300903", "aquiraz": "2301000", "aracati": "2301109", "aracoiaba": "2301208", "ararenda": "2301257", "araripe": "2301307", "aratuba": "2301406", "arneiroz": "2301505", "assare": "2301604", "aurora": "2301703", "baixio": "2301802", "banabuiu": "2301851", "barbalha": "2301901", "barreira": "2301950", "barro": "2302008", "barroquinha": "2302057", "baturite": "2302107", "beberibe": "2302206", "bela cruz": "2302305", "boa viagem": "2302404", "brejo santo": "2302503", "camocim": "2302602", "campos sales": "2302701", "caninde": "2302800", "capistrano": "2302909", "caridade": "2303006", "carire": "2303105", "caririacu": "2303204", "carius": "2303303", "carnaubal": "2303402", "cascavel": "2303501", "catarina": "2303600", "catunda": "2303659", "caucaia": "2303709", "cedro": "2303808", "chaval": "2303907", "choro": "2303931", "chorozinho": "2303956", "coreau": "2304004", "crateus": "2304103", "crato": "2304202", "croata": "2304236", "cruz": "2304251", "deputado irapuan pinheiro": "2304269", "erere": "2304277", "eusebio": "2304285", "farias brito": "2304301", "forquilha": "2304350", "fortaleza": "2304400", "fortim": "2304459", "frecheirinha": "2304509", "general sampaio": "2304608", "graca": "2304657", "granja": "2304707", "granjeiro": "2304806", "groairas": "2304905", "guaiuba": "2304954", "guaraciaba do norte": "2305001", "guaramiranga": "2305100", "hidrolandia": "2305209", "horizonte": "2305233", "ibaretama": "2305266", "ibiapina": "2305308", "ibicuitinga": "2305332", "icapui": "2305357", "ico": "2305407", "iguatu": "2305506", "independencia": "2305605", "ipaporanga": "2305654", "ipaumirim": "2305704", "ipu": "2305803", "ipueiras": "2305902", "iracema": "2306009", "iraucuba": "2306108", "itaicaba": "2306207", "itaitinga": "2306256", "itapaje": "2306306", "itapipoca": "2306405", "itapiuna": "2306504", "itarema": "2306553", "itatira": "2306603", "jaguaretama": "2306702", "jaguaribara": "2306801", "jaguaribe": "2306900", "jaguaruana": "2307007", "jardim": "2307106", "jati": "2307205", "jijoca de jericoacoara": "2307254", "juazeiro do norte": "2307304", "jucas": "2307403", "lavras da mangabeira": "2307502", "limoeiro do norte": "2307601", "madalena": "2307635", "maracanau": "2307650", "maranguape": "2307700", "marco": "2307809", "martinopole": "2307908", "massape": "2308005", "mauriti": "2308104", "meruoca": "2308203", "milagres": "2308302", "milha": "2308351", "miraima": "2308377", "missao velha": "2308401", "mombaca": "2308500", "monsenhor tabosa": "2308609", "morada nova": "2308708", "moraujo": "2308807", "morrinhos": "2308906", "mucambo": "2309003", "mulungu": "2309102", "nova olinda": "2309201", "nova russas": "2309300", "novo oriente": "2309409", "ocara": "2309458", "oros": "2309508", "pacajus": "2309607", "pacatuba": "2309706", "pacoti": "2309805", "pacuja": "2309904", "palhano": "2310001", "palmacia": "2310100", "paracuru": "2310209", "paraipaba": "2310258", "parambu": "2310308", "paramoti": "2310407", "pedra branca": "2310506", "penaforte": "2310605", "pentecoste": "2310704", "pereiro": "2310803", "pindoretama": "2310852", "piquet carneiro": "2310902", "pires ferreira": "2310951", "poranga": "2311009", "porteiras": "2311108", "potengi": "2311207", "potiretama": "2311231", "quiterianopolis": "2311264", "quixada": "2311306", "quixelo": "2311355", "quixeramobim": "2311405", "quixere": "2311504", "redencao": "2311603", "reriutaba": "2311702", "russas": "2311801", "saboeiro": "2311900", "salitre": "2311959", "santa quiteria": "2312205", "santana do acarau": "2312007", "santana do cariri": "2312106", "sao benedito": "2312304", "sao goncalo do amarante": "2312403", "sao joao do jaguaribe": "2312502", "sao luis do curu": "2312601", "senador pompeu": "2312700", "senador sa": "2312809", "sobral": "2312908", "solonopole": "2313005", "tabuleiro do norte": "2313104", "tamboril": "2313203", "tarrafas": "2313252", "taua": "2313302", "tejucuoca": "2313351", "tiangua": "2313401", "trairi": "2313500", "tururu": "2313559", "ubajara": "2313609", "umari": "2313708", "umirim": "2313757", "uruburetama": "2313807", "uruoca": "2313906", "varjota": "2313955", "varzea alegre": "2314003", "vicosa do ceara": "2314102" }, "RN": { "acari": "2400109", "acu": "2400208", "afonso bezerra": "2400307", "agua nova": "2400406", "alexandria": "2400505", "almino afonso": "2400604", "alto do rodrigues": "2400703", "angicos": "2400802", "antonio martins": "2400901", "apodi": "2401008", "areia branca": "2401107", "ares": "2401206", "baia formosa": "2401404", "barauna": "2401453", "barcelona": "2401503", "bento fernandes": "2401602", "bodo": "2401651", "bom jesus": "2401701", "brejinho": "2401800", "caicara do norte": "2401859", "caicara do rio do vento": "2401909", "caico": "2402006", "campo grande": "2401305", "campo redondo": "2402105", "canguaretama": "2402204", "caraubas": "2402303", "carnauba dos dantas": "2402402", "carnaubais": "2402501", "ceara-mirim": "2402600", "cerro cora": "2402709", "coronel ezequiel": "2402808", "coronel joao pessoa": "2402907", "cruzeta": "2403004", "currais novos": "2403103", "doutor severiano": "2403202", "encanto": "2403301", "equador": "2403400", "espirito santo": "2403509", "extremoz": "2403608", "felipe guerra": "2403707", "fernando pedroza": "2403756", "florania": "2403806", "francisco dantas": "2403905", "frutuoso gomes": "2404002", "galinhos": "2404101", "goianinha": "2404200", "governador dix-sept rosado": "2404309", "grossos": "2404408", "guamare": "2404507", "ielmo marinho": "2404606", "ipanguacu": "2404705", "ipueira": "2404804", "itaja": "2404853", "itau": "2404903", "jacana": "2405009", "jandaira": "2405108", "janduis": "2405207", "januario cicco": "2405306", "japi": "2405405", "jardim de angicos": "2405504", "jardim de piranhas": "2405603", "jardim do serido": "2405702", "joao camara": "2405801", "joao dias": "2405900", "jose da penha": "2406007", "jucurutu": "2406106", "jundia": "2406155", "lagoa d'anta": "2406205", "lagoa de pedras": "2406304", "lagoa de velhos": "2406403", "lagoa nova": "2406502", "lagoa salgada": "2406601", "lajes": "2406700", "lajes pintadas": "2406809", "lucrecia": "2406908", "luis gomes": "2407005", "macaiba": "2407104", "macau": "2407203", "major sales": "2407252", "marcelino vieira": "2407302", "martins": "2407401", "maxaranguape": "2407500", "messias targino": "2407609", "montanhas": "2407708", "monte alegre": "2407807", "monte das gameleiras": "2407906", "mossoro": "2408003", "natal": "2408102", "nisia floresta": "2408201", "nova cruz": "2408300", "olho d'agua do borges": "2408409", "ouro branco": "2408508", "parana": "2408607", "parau": "2408706", "parazinho": "2408805", "parelhas": "2408904", "parnamirim": "2403251", "passa e fica": "2409100", "passagem": "2409209", "patu": "2409308", "pau dos ferros": "2409407", "pedra grande": "2409506", "pedra preta": "2409605", "pedro avelino": "2409704", "pedro velho": "2409803", "pendencias": "2409902", "piloes": "2410009", "poco branco": "2410108", "portalegre": "2410207", "porto do mangue": "2410256", "pureza": "2410405", "rafael fernandes": "2410504", "rafael godeiro": "2410603", "riacho da cruz": "2410702", "riacho de santana": "2410801", "riachuelo": "2410900", "rio do fogo": "2408953", "rodolfo fernandes": "2411007", "ruy barbosa": "2411106", "santa cruz": "2411205", "santa maria": "2409332", "santana do matos": "2411403", "santana do serido": "2411429", "santo antonio": "2411502", "sao bento do norte": "2411601", "sao bento do trairi": "2411700", "sao fernando": "2411809", "sao francisco do oeste": "2411908", "sao goncalo do amarante": "2412005", "sao joao do sabugi": "2412104", "sao jose de mipibu": "2412203", "sao jose do campestre": "2412302", "sao jose do serido": "2412401", "sao miguel": "2412500", "sao miguel do gostoso": "2412559", "sao paulo do potengi": "2412609", "sao pedro": "2412708", "sao rafael": "2412807", "sao tome": "2412906", "sao vicente": "2413003", "senador eloi de souza": "2413102", "senador georgino avelino": "2413201", "serra caiada": "2410306", "serra de sao bento": "2413300", "serra do mel": "2413359", "serra negra do norte": "2413409", "serrinha": "2413508", "serrinha dos pintos": "2413557", "severiano melo": "2413607", "sitio novo": "2413706", "taboleiro grande": "2413805", "taipu": "2413904", "tangara": "2414001", "tenente ananias": "2414100", "tenente laurentino cruz": "2414159", "tibau": "2411056", "tibau do sul": "2414209", "timbauba dos batistas": "2414308", "touros": "2414407", "triunfo potiguar": "2414456", "umarizal": "2414506", "upanema": "2414605", "varzea": "2414704", "venha-ver": "2414753", "vera cruz": "2414803", "vicosa": "2414902", "vila flor": "2415008" }, "PB": { "agua branca": "2500106", "aguiar": "2500205", "alagoa grande": "2500304", "alagoa nova": "2500403", "alagoinha": "2500502", "alcantil": "2500536", "algodao de jandaira": "2500577", "alhandra": "2500601", "amparo": "2500734", "aparecida": "2500775", "aracagi": "2500809", "arara": "2500908", "araruna": "2501005", "areia": "2501104", "areia de baraunas": "2501153", "areial": "2501203", "aroeiras": "2501302", "assuncao": "2501351", "baia da traicao": "2501401", "bananeiras": "2501500", "barauna": "2501534", "barra de santa rosa": "2501609", "barra de santana": "2501575", "barra de sao miguel": "2501708", "bayeux": "2501807", "belem": "2501906", "belem do brejo do cruz": "2502003", "bernardino batista": "2502052", "boa ventura": "2502102", "boa vista": "2502151", "bom jesus": "2502201", "bom sucesso": "2502300", "bonito de santa fe": "2502409", "boqueirao": "2502508", "borborema": "2502706", "brejo do cruz": "2502805", "brejo dos santos": "2502904", "caapora": "2503001", "cabaceiras": "2503100", "cabedelo": "2503209", "cachoeira dos indios": "2503308", "cacimba de areia": "2503407", "cacimba de dentro": "2503506", "cacimbas": "2503555", "caicara": "2503605", "cajazeiras": "2503704", "cajazeirinhas": "2503753", "caldas brandao": "2503803", "camalau": "2503902", "campina grande": "2504009", "capim": "2504033", "caraubas": "2504074", "carrapateira": "2504108", "casserengue": "2504157", "catingueira": "2504207", "catole do rocha": "2504306", "caturite": "2504355", "conceicao": "2504405", "condado": "2504504", "conde": "2504603", "congo": "2504702", "coremas": "2504801", "coxixola": "2504850", "cruz do espirito santo": "2504900", "cubati": "2505006", "cuite": "2505105", "cuite de mamanguape": "2505238", "cuitegi": "2505204", "curral de cima": "2505279", "curral velho": "2505303", "damiao": "2505352", "desterro": "2505402", "diamante": "2505600", "dona ines": "2505709", "duas estradas": "2505808", "emas": "2505907", "esperanca": "2506004", "fagundes": "2506103", "frei martinho": "2506202", "gado bravo": "2506251", "guarabira": "2506301", "gurinhem": "2506400", "gurjao": "2506509", "ibiara": "2506608", "igaracy": "2502607", "imaculada": "2506707", "inga": "2506806", "itabaiana": "2506905", "itaporanga": "2507002", "itapororoca": "2507101", "itatuba": "2507200", "jacarau": "2507309", "jerico": "2507408", "joao pessoa": "2507507", "joca claudino": "2513653", "juarez tavora": "2507606", "juazeirinho": "2507705", "junco do serido": "2507804", "juripiranga": "2507903", "juru": "2508000", "lagoa": "2508109", "lagoa de dentro": "2508208", "lagoa seca": "2508307", "lastro": "2508406", "livramento": "2508505", "logradouro": "2508554", "lucena": "2508604", "mae d'agua": "2508703", "malta": "2508802", "mamanguape": "2508901", "manaira": "2509008", "marcacao": "2509057", "mari": "2509107", "marizopolis": "2509156", "massaranduba": "2509206", "mataraca": "2509305", "matinhas": "2509339", "mato grosso": "2509370", "matureia": "2509396", "mogeiro": "2509404", "montadas": "2509503", "monte horebe": "2509602", "monteiro": "2509701", "mulungu": "2509800", "natuba": "2509909", "nazarezinho": "2510006", "nova floresta": "2510105", "nova olinda": "2510204", "nova palmeira": "2510303", "olho d'agua": "2510402", "olivedos": "2510501", "ouro velho": "2510600", "parari": "2510659", "passagem": "2510709", "patos": "2510808", "paulista": "2510907", "pedra branca": "2511004", "pedra lavrada": "2511103", "pedras de fogo": "2511202", "pedro regis": "2512721", "pianco": "2511301", "picui": "2511400", "pilar": "2511509", "piloes": "2511608", "piloezinhos": "2511707", "pirpirituba": "2511806", "pitimbu": "2511905", "pocinhos": "2512002", "poco dantas": "2512036", "poco de jose de moura": "2512077", "pombal": "2512101", "prata": "2512200", "princesa isabel": "2512309", "puxinana": "2512408", "queimadas": "2512507", "quixaba": "2512606", "remigio": "2512705", "riachao": "2512747", "riachao do bacamarte": "2512754", "riachao do poco": "2512762", "riacho de santo antonio": "2512788", "riacho dos cavalos": "2512804", "rio tinto": "2512903", "salgadinho": "2513000", "salgado de sao felix": "2513109", "santa cecilia": "2513158", "santa cruz": "2513208", "santa helena": "2513307", "santa ines": "2513356", "santa luzia": "2513406", "santa rita": "2513703", "santa teresinha": "2513802", "santana de mangueira": "2513505", "santana dos garrotes": "2513604", "santo andre": "2513851", "sao bentinho": "2513927", "sao bento": "2513901", "sao domingos": "2513968", "sao domingos do cariri": "2513943", "sao francisco": "2513984", "sao joao do cariri": "2514008", "sao joao do rio do peixe": "2500700", "sao joao do tigre": "2514107", "sao jose da lagoa tapada": "2514206", "sao jose de caiana": "2514305", "sao jose de espinharas": "2514404", "sao jose de piranhas": "2514503", "sao jose de princesa": "2514552", "sao jose do bonfim": "2514602", "sao jose do brejo do cruz": "2514651", "sao jose do sabugi": "2514701", "sao jose dos cordeiros": "2514800", "sao jose dos ramos": "2514453", "sao mamede": "2514909", "sao miguel de taipu": "2515005", "sao sebastiao de lagoa de roca": "2515104", "sao sebastiao do umbuzeiro": "2515203", "sao vicente do serido": "2515401", "sape": "2515302", "serra branca": "2515500", "serra da raiz": "2515609", "serra grande": "2515708", "serra redonda": "2515807", "serraria": "2515906", "sertaozinho": "2515930", "sobrado": "2515971", "solanea": "2516003", "soledade": "2516102", "sossego": "2516151", "sousa": "2516201", "sume": "2516300", "tacima": "2516409", "taperoa": "2516508", "tavares": "2516607", "teixeira": "2516706", "tenorio": "2516755", "triunfo": "2516805", "uirauna": "2516904", "umbuzeiro": "2517001", "varzea": "2517100", "vieiropolis": "2517209", "vista serrana": "2505501", "zabele": "2517407" }, "PE": { "abreu e lima": "2600054", "afogados da ingazeira": "2600104", "afranio": "2600203", "agrestina": "2600302", "agua preta": "2600401", "aguas belas": "2600500", "alagoinha": "2600609", "alianca": "2600708", "altinho": "2600807", "amaraji": "2600906", "angelim": "2601003", "aracoiaba": "2601052", "araripina": "2601102", "arcoverde": "2601201", "barra de guabiraba": "2601300", "barreiros": "2601409", "belem de maria": "2601508", "belem do sao francisco": "2601607", "belo jardim": "2601706", "betania": "2601805", "bezerros": "2601904", "bodoco": "2602001", "bom conselho": "2602100", "bom jardim": "2602209", "bonito": "2602308", "brejao": "2602407", "brejinho": "2602506", "brejo da madre de deus": "2602605", "buenos aires": "2602704", "buique": "2602803", "cabo de santo agostinho": "2602902", "cabrobo": "2603009", "cachoeirinha": "2603108", "caetes": "2603207", "calcado": "2603306", "calumbi": "2603405", "camaragibe": "2603454", "camocim de sao felix": "2603504", "camutanga": "2603603", "canhotinho": "2603702", "capoeiras": "2603801", "carnaiba": "2603900", "carnaubeira da penha": "2603926", "carpina": "2604007", "caruaru": "2604106", "casinhas": "2604155", "catende": "2604205", "cedro": "2604304", "cha de alegria": "2604403", "cha grande": "2604502", "condado": "2604601", "correntes": "2604700", "cortes": "2604809", "cumaru": "2604908", "cupira": "2605004", "custodia": "2605103", "dormentes": "2605152", "escada": "2605202", "exu": "2605301", "feira nova": "2605400", "fernando de noronha": "2605459", "ferreiros": "2605509", "flores": "2605608", "floresta": "2605707", "frei miguelinho": "2605806", "gameleira": "2605905", "garanhuns": "2606002", "gloria do goita": "2606101", "goiana": "2606200", "granito": "2606309", "gravata": "2606408", "iati": "2606507", "ibimirim": "2606606", "ibirajuba": "2606705", "igarassu": "2606804", "iguaracy": "2606903", "ilha de itamaraca": "2607604", "inaja": "2607000", "ingazeira": "2607109", "ipojuca": "2607208", "ipubi": "2607307", "itacuruba": "2607406", "itaiba": "2607505", "itambe": "2607653", "itapetim": "2607703", "itapissuma": "2607752", "itaquitinga": "2607802", "jaboatao dos guararapes": "2607901", "jaqueira": "2607950", "jatauba": "2608008", "jatoba": "2608057", "joao alfredo": "2608107", "joaquim nabuco": "2608206", "jucati": "2608255", "jupi": "2608305", "jurema": "2608404", "lagoa de itaenga": "2608503", "lagoa do carro": "2608453", "lagoa do ouro": "2608602", "lagoa dos gatos": "2608701", "lagoa grande": "2608750", "lajedo": "2608800", "limoeiro": "2608909", "macaparana": "2609006", "machados": "2609105", "manari": "2609154", "maraial": "2609204", "mirandiba": "2609303", "moreilandia": "2614303", "moreno": "2609402", "nazare da mata": "2609501", "olinda": "2609600", "orobo": "2609709", "oroco": "2609808", "ouricuri": "2609907", "palmares": "2610004", "palmeirina": "2610103", "panelas": "2610202", "paranatama": "2610301", "parnamirim": "2610400", "passira": "2610509", "paudalho": "2610608", "paulista": "2610707", "pedra": "2610806", "pesqueira": "2610905", "petrolandia": "2611002", "petrolina": "2611101", "pocao": "2611200", "pombos": "2611309", "primavera": "2611408", "quipapa": "2611507", "quixaba": "2611533", "recife": "2611606", "riacho das almas": "2611705", "ribeirao": "2611804", "rio formoso": "2611903", "saire": "2612000", "salgadinho": "2612109", "salgueiro": "2612208", "saloa": "2612307", "sanharo": "2612406", "santa cruz": "2612455", "santa cruz da baixa verde": "2612471", "santa cruz do capibaribe": "2612505", "santa filomena": "2612554", "santa maria da boa vista": "2612604", "santa maria do cambuca": "2612703", "santa terezinha": "2612802", "sao benedito do sul": "2612901", "sao bento do una": "2613008", "sao caitano": "2613107", "sao joao": "2613206", "sao joaquim do monte": "2613305", "sao jose da coroa grande": "2613404", "sao jose do belmonte": "2613503", "sao jose do egito": "2613602", "sao lourenco da mata": "2613701", "sao vicente ferrer": "2613800", "serra talhada": "2613909", "serrita": "2614006", "sertania": "2614105", "sirinhaem": "2614204", "solidao": "2614402", "surubim": "2614501", "tabira": "2614600", "tacaimbo": "2614709", "tacaratu": "2614808", "tamandare": "2614857", "taquaritinga do norte": "2615003", "terezinha": "2615102", "terra nova": "2615201", "timbauba": "2615300", "toritama": "2615409", "tracunhaem": "2615508", "trindade": "2615607", "triunfo": "2615706", "tupanatinga": "2615805", "tuparetama": "2615904", "venturosa": "2616001", "verdejante": "2616100", "vertente do lerio": "2616183", "vertentes": "2616209", "vicencia": "2616308", "vitoria de santo antao": "2616407", "xexeu": "2616506" }, "AL": { "agua branca": "2700102", "anadia": "2700201", "arapiraca": "2700300", "atalaia": "2700409", "barra de santo antonio": "2700508", "barra de sao miguel": "2700607", "batalha": "2700706", "belem": "2700805", "belo monte": "2700904", "boca da mata": "2701001", "branquinha": "2701100", "cacimbinhas": "2701209", "cajueiro": "2701308", "campestre": "2701357", "campo alegre": "2701407", "campo grande": "2701506", "canapi": "2701605", "capela": "2701704", "carneiros": "2701803", "cha preta": "2701902", "coite do noia": "2702009", "colonia leopoldina": "2702108", "coqueiro seco": "2702207", "coruripe": "2702306", "craibas": "2702355", "delmiro gouveia": "2702405", "dois riachos": "2702504", "estrela de alagoas": "2702553", "feira grande": "2702603", "feliz deserto": "2702702", "flexeiras": "2702801", "girau do ponciano": "2702900", "ibateguara": "2703007", "igaci": "2703106", "igreja nova": "2703205", "inhapi": "2703304", "jacare dos homens": "2703403", "jacuipe": "2703502", "japaratinga": "2703601", "jaramataia": "2703700", "jequia da praia": "2703759", "joaquim gomes": "2703809", "jundia": "2703908", "junqueiro": "2704005", "lagoa da canoa": "2704104", "limoeiro de anadia": "2704203", "maceio": "2704302", "major isidoro": "2704401", "mar vermelho": "2704906", "maragogi": "2704500", "maravilha": "2704609", "marechal deodoro": "2704708", "maribondo": "2704807", "mata grande": "2705002", "matriz de camaragibe": "2705101", "messias": "2705200", "minador do negrao": "2705309", "monteiropolis": "2705408", "murici": "2705507", "novo lino": "2705606", "olho d'agua das flores": "2705705", "olho d'agua do casado": "2705804", "olho d'agua grande": "2705903", "olivenca": "2706000", "ouro branco": "2706109", "palestina": "2706208", "palmeira dos indios": "2706307", "pao de acucar": "2706406", "pariconha": "2706422", "paripueira": "2706448", "passo de camaragibe": "2706505", "paulo jacinto": "2706604", "penedo": "2706703", "piacabucu": "2706802", "pilar": "2706901", "pindoba": "2707008", "piranhas": "2707107", "poco das trincheiras": "2707206", "porto calvo": "2707305", "porto de pedras": "2707404", "porto real do colegio": "2707503", "quebrangulo": "2707602", "rio largo": "2707701", "roteiro": "2707800", "santa luzia do norte": "2707909", "santana do ipanema": "2708006", "santana do mundau": "2708105", "sao bras": "2708204", "sao jose da laje": "2708303", "sao jose da tapera": "2708402", "sao luis do quitunde": "2708501", "sao miguel dos campos": "2708600", "sao miguel dos milagres": "2708709", "sao sebastiao": "2708808", "satuba": "2708907", "senador rui palmeira": "2708956", "tanque d'arca": "2709004", "taquarana": "2709103", "teotonio vilela": "2709152", "traipu": "2709202", "uniao dos palmares": "2709301", "vicosa": "2709400" }, "SE": { "amparo do sao francisco": "2800100", "aquidaba": "2800209", "aracaju": "2800308", "araua": "2800407", "areia branca": "2800506", "barra dos coqueiros": "2800605", "boquim": "2800670", "brejo grande": "2800704", "campo do brito": "2801009", "canhoba": "2801108", "caninde de sao francisco": "2801207", "capela": "2801306", "carira": "2801405", "carmopolis": "2801504", "cedro de sao joao": "2801603", "cristinapolis": "2801702", "cumbe": "2801900", "divina pastora": "2802007", "estancia": "2802106", "feira nova": "2802205", "frei paulo": "2802304", "gararu": "2802403", "general maynard": "2802502", "gracho cardoso": "2802601", "ilha das flores": "2802700", "indiaroba": "2802809", "itabaiana": "2802908", "itabaianinha": "2803005", "itabi": "2803104", "itaporanga d'ajuda": "2803203", "japaratuba": "2803302", "japoata": "2803401", "lagarto": "2803500", "laranjeiras": "2803609", "macambira": "2803708", "malhada dos bois": "2803807", "malhador": "2803906", "maruim": "2804003", "moita bonita": "2804102", "monte alegre de sergipe": "2804201", "muribeca": "2804300", "neopolis": "2804409", "nossa senhora aparecida": "2804458", "nossa senhora da gloria": "2804508", "nossa senhora das dores": "2804607", "nossa senhora de lourdes": "2804706", "nossa senhora do socorro": "2804805", "pacatuba": "2804904", "pedra mole": "2805000", "pedrinhas": "2805109", "pinhao": "2805208", "pirambu": "2805307", "poco redondo": "2805406", "poco verde": "2805505", "porto da folha": "2805604", "propria": "2805703", "riachao do dantas": "2805802", "riachuelo": "2805901", "ribeiropolis": "2806008", "rosario do catete": "2806107", "salgado": "2806206", "santa luzia do itanhy": "2806305", "santa rosa de lima": "2806503", "santana do sao francisco": "2806404", "santo amaro das brotas": "2806602", "sao cristovao": "2806701", "sao domingos": "2806800", "sao francisco": "2806909", "sao miguel do aleixo": "2807006", "simao dias": "2807105", "siriri": "2807204", "telha": "2807303", "tobias barreto": "2807402", "tomar do geru": "2807501", "umbauba": "2807600" }, "BA": { "abaira": "2900108", "abare": "2900207", "acajutiba": "2900306", "adustina": "2900355", "agua fria": "2900405", "aiquara": "2900603", "alagoinhas": "2900702", "alcobaca": "2900801", "almadina": "2900900", "amargosa": "2901007", "amelia rodrigues": "2901106", "america dourada": "2901155", "anage": "2901205", "andarai": "2901304", "andorinha": "2901353", "angical": "2901403", "anguera": "2901502", "antas": "2901601", "antonio cardoso": "2901700", "antonio goncalves": "2901809", "apora": "2901908", "apuarema": "2901957", "aracas": "2902054", "aracatu": "2902005", "araci": "2902104", "aramari": "2902203", "arataca": "2902252", "aratuipe": "2902302", "aurelino leal": "2902401", "baianopolis": "2902500", "baixa grande": "2902609", "banzae": "2902658", "barra": "2902708", "barra da estiva": "2902807", "barra do choca": "2902906", "barra do mendes": "2903003", "barra do rocha": "2903102", "barreiras": "2903201", "barro alto": "2903235", "barro preto": "2903300", "barrocas": "2903276", "belmonte": "2903409", "belo campo": "2903508", "biritinga": "2903607", "boa nova": "2903706", "boa vista do tupim": "2903805", "bom jesus da lapa": "2903904", "bom jesus da serra": "2903953", "boninal": "2904001", "bonito": "2904050", "boquira": "2904100", "botupora": "2904209", "brejoes": "2904308", "brejolandia": "2904407", "brotas de macaubas": "2904506", "brumado": "2904605", "buerarema": "2904704", "buritirama": "2904753", "caatiba": "2904803", "cabaceiras do paraguacu": "2904852", "cachoeira": "2904902", "cacule": "2905008", "caem": "2905107", "caetanos": "2905156", "caetite": "2905206", "cafarnaum": "2905305", "cairu": "2905404", "caldeirao grande": "2905503", "camacan": "2905602", "camacari": "2905701", "camamu": "2905800", "campo alegre de lourdes": "2905909", "campo formoso": "2906006", "canapolis": "2906105", "canarana": "2906204", "canavieiras": "2906303", "candeal": "2906402", "candeias": "2906501", "candiba": "2906600", "candido sales": "2906709", "cansancao": "2906808", "canudos": "2906824", "capela do alto alegre": "2906857", "capim grosso": "2906873", "caraibas": "2906899", "caravelas": "2906907", "cardeal da silva": "2907004", "carinhanha": "2907103", "casa nova": "2907202", "castro alves": "2907301", "catolandia": "2907400", "catu": "2907509", "caturama": "2907558", "central": "2907608", "chorrocho": "2907707", "cicero dantas": "2907806", "cipo": "2907905", "coaraci": "2908002", "cocos": "2908101", "conceicao da feira": "2908200", "conceicao do almeida": "2908309", "conceicao do coite": "2908408", "conceicao do jacuipe": "2908507", "conde": "2908606", "condeuba": "2908705", "contendas do sincora": "2908804", "coracao de maria": "2908903", "cordeiros": "2909000", "coribe": "2909109", "coronel joao sa": "2909208", "correntina": "2909307", "cotegipe": "2909406", "cravolandia": "2909505", "crisopolis": "2909604", "cristopolis": "2909703", "cruz das almas": "2909802", "curaca": "2909901", "dario meira": "2910008", "dias d'avila": "2910057", "dom basilio": "2910107", "dom macedo costa": "2910206", "elisio medrado": "2910305", "encruzilhada": "2910404", "entre rios": "2910503", "erico cardoso": "2900504", "esplanada": "2910602", "euclides da cunha": "2910701", "eunapolis": "2910727", "fatima": "2910750", "feira da mata": "2910776", "feira de santana": "2910800", "filadelfia": "2910859", "firmino alves": "2910909", "floresta azul": "2911006", "formosa do rio preto": "2911105", "gandu": "2911204", "gaviao": "2911253", "gentio do ouro": "2911303", "gloria": "2911402", "gongogi": "2911501", "governador mangabeira": "2911600", "guajeru": "2911659", "guanambi": "2911709", "guaratinga": "2911808", "heliopolis": "2911857", "iacu": "2911907", "ibiassuce": "2912004", "ibicarai": "2912103", "ibicoara": "2912202", "ibicui": "2912301", "ibipeba": "2912400", "ibipitanga": "2912509", "ibiquera": "2912608", "ibirapitanga": "2912707", "ibirapua": "2912806", "ibirataia": "2912905", "ibitiara": "2913002", "ibitita": "2913101", "ibotirama": "2913200", "ichu": "2913309", "igapora": "2913408", "igrapiuna": "2913457", "iguai": "2913507", "ilheus": "2913606", "inhambupe": "2913705", "ipecaeta": "2913804", "ipiau": "2913903", "ipira": "2914000", "ipupiara": "2914109", "irajuba": "2914208", "iramaia": "2914307", "iraquara": "2914406", "irara": "2914505", "irece": "2914604", "itabela": "2914653", "itaberaba": "2914703", "itabuna": "2914802", "itacare": "2914901", "itaete": "2915007", "itagi": "2915106", "itagiba": "2915205", "itagimirim": "2915304", "itaguacu da bahia": "2915353", "itaju do colonia": "2915403", "itajuipe": "2915502", "itamaraju": "2915601", "itamari": "2915700", "itambe": "2915809", "itanagra": "2915908", "itanhem": "2916005", "itaparica": "2916104", "itape": "2916203", "itapebi": "2916302", "itapetinga": "2916401", "itapicuru": "2916500", "itapitanga": "2916609", "itaquara": "2916708", "itarantim": "2916807", "itatim": "2916856", "itirucu": "2916906", "itiuba": "2917003", "itororo": "2917102", "ituacu": "2917201", "itubera": "2917300", "iuiu": "2917334", "jaborandi": "2917359", "jacaraci": "2917409", "jacobina": "2917508", "jaguaquara": "2917607", "jaguarari": "2917706", "jaguaripe": "2917805", "jandaira": "2917904", "jequie": "2918001", "jeremoabo": "2918100", "jiquirica": "2918209", "jitauna": "2918308", "joao dourado": "2918357", "juazeiro": "2918407", "jucurucu": "2918456", "jussara": "2918506", "jussari": "2918555", "jussiape": "2918605", "lafaiete coutinho": "2918704", "lagoa real": "2918753", "laje": "2918803", "lajedao": "2918902", "lajedinho": "2919009", "lajedo do tabocal": "2919058", "lamarao": "2919108", "lapao": "2919157", "lauro de freitas": "2919207", "lencois": "2919306", "licinio de almeida": "2919405", "livramento de nossa senhora": "2919504", "luis eduardo magalhaes": "2919553", "macajuba": "2919603", "macarani": "2919702", "macaubas": "2919801", "macurure": "2919900", "madre de deus": "2919926", "maetinga": "2919959", "maiquinique": "2920007", "mairi": "2920106", "malhada": "2920205", "malhada de pedras": "2920304", "manoel vitorino": "2920403", "mansidao": "2920452", "maracas": "2920502", "maragogipe": "2920601", "marau": "2920700", "marcionilio souza": "2920809", "mascote": "2920908", "mata de sao joao": "2921005", "matina": "2921054", "medeiros neto": "2921104", "miguel calmon": "2921203", "milagres": "2921302", "mirangaba": "2921401", "mirante": "2921450", "monte santo": "2921500", "morpara": "2921609", "morro do chapeu": "2921708", "mortugaba": "2921807", "mucuge": "2921906", "mucuri": "2922003", "mulungu do morro": "2922052", "mundo novo": "2922102", "muniz ferreira": "2922201", "muquem do sao francisco": "2922250", "muritiba": "2922300", "mutuipe": "2922409", "nazare": "2922508", "nilo pecanha": "2922607", "nordestina": "2922656", "nova canaa": "2922706", "nova fatima": "2922730", "nova ibia": "2922755", "nova itarana": "2922805", "nova redencao": "2922854", "nova soure": "2922904", "nova vicosa": "2923001", "novo horizonte": "2923035", "novo triunfo": "2923050", "olindina": "2923100", "oliveira dos brejinhos": "2923209", "ouricangas": "2923308", "ourolandia": "2923357", "palmas de monte alto": "2923407", "palmeiras": "2923506", "paramirim": "2923605", "paratinga": "2923704", "paripiranga": "2923803", "pau brasil": "2923902", "paulo afonso": "2924009", "pe de serra": "2924058", "pedrao": "2924108", "pedro alexandre": "2924207", "piata": "2924306", "pilao arcado": "2924405", "pindai": "2924504", "pindobacu": "2924603", "pintadas": "2924652", "pirai do norte": "2924678", "piripa": "2924702", "piritiba": "2924801", "planaltino": "2924900", "planalto": "2925006", "pocoes": "2925105", "pojuca": "2925204", "ponto novo": "2925253", "porto seguro": "2925303", "potiragua": "2925402", "prado": "2925501", "presidente dutra": "2925600", "presidente janio quadros": "2925709", "presidente tancredo neves": "2925758", "queimadas": "2925808", "quijingue": "2925907", "quixabeira": "2925931", "rafael jambeiro": "2925956", "remanso": "2926004", "retirolandia": "2926103", "riachao das neves": "2926202", "riachao do jacuipe": "2926301", "riacho de santana": "2926400", "ribeira do amparo": "2926509", "ribeira do pombal": "2926608", "ribeirao do largo": "2926657", "rio de contas": "2926707", "rio do antonio": "2926806", "rio do pires": "2926905", "rio real": "2927002", "rodelas": "2927101", "ruy barbosa": "2927200", "salinas da margarida": "2927309", "salvador": "2927408", "santa barbara": "2927507", "santa brigida": "2927606", "santa cruz cabralia": "2927705", "santa cruz da vitoria": "2927804", "santa ines": "2927903", "santa luzia": "2928059", "santa maria da vitoria": "2928109", "santa rita de cassia": "2928406", "santa terezinha": "2928505", "santaluz": "2928000", "santana": "2928208", "santanopolis": "2928307", "santo amaro": "2928604", "santo antonio de jesus": "2928703", "santo estevao": "2928802", "sao desiderio": "2928901", "sao domingos": "2928950", "sao felipe": "2929107", "sao felix": "2929008", "sao felix do coribe": "2929057", "sao francisco do conde": "2929206", "sao gabriel": "2929255", "sao goncalo dos campos": "2929305", "sao jose da vitoria": "2929354", "sao jose do jacuipe": "2929370", "sao miguel das matas": "2929404", "sao sebastiao do passe": "2929503", "sapeacu": "2929602", "satiro dias": "2929701", "saubara": "2929750", "saude": "2929800", "seabra": "2929909", "sebastiao laranjeiras": "2930006", "senhor do bonfim": "2930105", "sento se": "2930204", "serra do ramalho": "2930154", "serra dourada": "2930303", "serra preta": "2930402", "serrinha": "2930501", "serrolandia": "2930600", "simoes filho": "2930709", "sitio do mato": "2930758", "sitio do quinto": "2930766", "sobradinho": "2930774", "souto soares": "2930808", "tabocas do brejo velho": "2930907", "tanhacu": "2931004", "tanque novo": "2931053", "tanquinho": "2931103", "taperoa": "2931202", "tapiramuta": "2931301", "teixeira de freitas": "2931350", "teodoro sampaio": "2931400", "teofilandia": "2931509", "teolandia": "2931608", "terra nova": "2931707", "tremedal": "2931806", "tucano": "2931905", "uaua": "2932002", "ubaira": "2932101", "ubaitaba": "2932200", "ubata": "2932309", "uibai": "2932408", "umburanas": "2932457", "una": "2932507", "urandi": "2932606", "urucuca": "2932705", "utinga": "2932804", "valenca": "2932903", "valente": "2933000", "varzea da roca": "2933059", "varzea do poco": "2933109", "varzea nova": "2933158", "varzedo": "2933174", "vera cruz": "2933208", "vereda": "2933257", "vitoria da conquista": "2933307", "wagner": "2933406", "wanderley": "2933455", "wenceslau guimaraes": "2933505", "xique-xique": "2933604" }, "MG": { "abadia dos dourados": "3100104", "abaete": "3100203", "abre campo": "3100302", "acaiaca": "3100401", "acucena": "3100500", "agua boa": "3100609", "agua comprida": "3100708", "aguanil": "3100807", "aguas formosas": "3100906", "aguas vermelhas": "3101003", "aimores": "3101102", "aiuruoca": "3101201", "alagoa": "3101300", "albertina": "3101409", "alem paraiba": "3101508", "alfenas": "3101607", "alfredo vasconcelos": "3101631", "almenara": "3101706", "alpercata": "3101805", "alpinopolis": "3101904", "alterosa": "3102001", "alto caparao": "3102050", "alto jequitiba": "3153509", "alto rio doce": "3102100", "alvarenga": "3102209", "alvinopolis": "3102308", "alvorada de minas": "3102407", "amparo do serra": "3102506", "andradas": "3102605", "andrelandia": "3102803", "angelandia": "3102852", "antonio carlos": "3102902", "antonio dias": "3103009", "antonio prado de minas": "3103108", "aracai": "3103207", "aracitaba": "3103306", "aracuai": "3103405", "araguari": "3103504", "arantina": "3103603", "araponga": "3103702", "arapora": "3103751", "arapua": "3103801", "araujos": "3103900", "araxa": "3104007", "arceburgo": "3104106", "arcos": "3104205", "areado": "3104304", "argirita": "3104403", "aricanduva": "3104452", "arinos": "3104502", "astolfo dutra": "3104601", "ataleia": "3104700", "augusto de lima": "3104809", "baependi": "3104908", "baldim": "3105004", "bambui": "3105103", "bandeira": "3105202", "bandeira do sul": "3105301", "barao de cocais": "3105400", "barao de monte alto": "3105509", "barbacena": "3105608", "barra longa": "3105707", "barroso": "3105905", "bela vista de minas": "3106002", "belmiro braga": "3106101", "belo horizonte": "3106200", "belo oriente": "3106309", "belo vale": "3106408", "berilo": "3106507", "berizal": "3106655", "bertopolis": "3106606", "betim": "3106705", "bias fortes": "3106804", "bicas": "3106903", "biquinhas": "3107000", "boa esperanca": "3107109", "bocaina de minas": "3107208", "bocaiuva": "3107307", "bom despacho": "3107406", "bom jardim de minas": "3107505", "bom jesus da penha": "3107604", "bom jesus do amparo": "3107703", "bom jesus do galho": "3107802", "bom repouso": "3107901", "bom sucesso": "3108008", "bonfim": "3108107", "bonfinopolis de minas": "3108206", "bonito de minas": "3108255", "borda da mata": "3108305", "botelhos": "3108404", "botumirim": "3108503", "bras pires": "3108701", "brasilandia de minas": "3108552", "brasilia de minas": "3108602", "braunas": "3108800", "brazopolis": "3108909", "brumadinho": "3109006", "bueno brandao": "3109105", "buenopolis": "3109204", "bugre": "3109253", "buritis": "3109303", "buritizeiro": "3109402", "cabeceira grande": "3109451", "cabo verde": "3109501", "cachoeira da prata": "3109600", "cachoeira de minas": "3109709", "cachoeira de pajeu": "3102704", "cachoeira dourada": "3109808", "caetanopolis": "3109907", "caete": "3110004", "caiana": "3110103", "cajuri": "3110202", "caldas": "3110301", "camacho": "3110400", "camanducaia": "3110509", "cambui": "3110608", "cambuquira": "3110707", "campanario": "3110806", "campanha": "3110905", "campestre": "3111002", "campina verde": "3111101", "campo azul": "3111150", "campo belo": "3111200", "campo do meio": "3111309", "campo florido": "3111408", "campos altos": "3111507", "campos gerais": "3111606", "cana verde": "3111903", "canaa": "3111705", "canapolis": "3111804", "candeias": "3112000", "cantagalo": "3112059", "caparao": "3112109", "capela nova": "3112208", "capelinha": "3112307", "capetinga": "3112406", "capim branco": "3112505", "capinopolis": "3112604", "capitao andrade": "3112653", "capitao eneas": "3112703", "capitolio": "3112802", "caputira": "3112901", "carai": "3113008", "caranaiba": "3113107", "carandai": "3113206", "carangola": "3113305", "caratinga": "3113404", "carbonita": "3113503", "careacu": "3113602", "carlos chagas": "3113701", "carmesia": "3113800", "carmo da cachoeira": "3113909", "carmo da mata": "3114006", "carmo de minas": "3114105", "carmo do cajuru": "3114204", "carmo do paranaiba": "3114303", "carmo do rio claro": "3114402", "carmopolis de minas": "3114501", "carneirinho": "3114550", "carrancas": "3114600", "carvalhopolis": "3114709", "carvalhos": "3114808", "casa grande": "3114907", "cascalho rico": "3115003", "cassia": "3115102", "cataguases": "3115300", "catas altas": "3115359", "catas altas da noruega": "3115409", "catuji": "3115458", "catuti": "3115474", "caxambu": "3115508", "cedro do abaete": "3115607", "central de minas": "3115706", "centralina": "3115805", "chacara": "3115904", "chale": "3116001", "chapada do norte": "3116100", "chapada gaucha": "3116159", "chiador": "3116209", "cipotanea": "3116308", "claraval": "3116407", "claro dos pocoes": "3116506", "claudio": "3116605", "coimbra": "3116704", "coluna": "3116803", "comendador gomes": "3116902", "comercinho": "3117009", "conceicao da aparecida": "3117108", "conceicao da barra de minas": "3115201", "conceicao das alagoas": "3117306", "conceicao das pedras": "3117207", "conceicao de ipanema": "3117405", "conceicao do mato dentro": "3117504", "conceicao do para": "3117603", "conceicao do rio verde": "3117702", "conceicao dos ouros": "3117801", "conego marinho": "3117836", "confins": "3117876", "congonhal": "3117900", "congonhas": "3118007", "congonhas do norte": "3118106", "conquista": "3118205", "conselheiro lafaiete": "3118304", "conselheiro pena": "3118403", "consolacao": "3118502", "contagem": "3118601", "coqueiral": "3118700", "coracao de jesus": "3118809", "cordisburgo": "3118908", "cordislandia": "3119005", "corinto": "3119104", "coroaci": "3119203", "coromandel": "3119302", "coronel fabriciano": "3119401", "coronel murta": "3119500", "coronel pacheco": "3119609", "coronel xavier chaves": "3119708", "corrego danta": "3119807", "corrego do bom jesus": "3119906", "corrego fundo": "3119955", "corrego novo": "3120003", "couto de magalhaes de minas": "3120102", "crisolita": "3120151", "cristais": "3120201", "cristalia": "3120300", "cristiano otoni": "3120409", "cristina": "3120508", "crucilandia": "3120607", "cruzeiro da fortaleza": "3120706", "cruzilia": "3120805", "cuparaque": "3120839", "curral de dentro": "3120870", "curvelo": "3120904", "datas": "3121001", "delfim moreira": "3121100", "delfinopolis": "3121209", "delta": "3121258", "descoberto": "3121308", "desterro de entre rios": "3121407", "desterro do melo": "3121506", "diamantina": "3121605", "diogo de vasconcelos": "3121704", "dionisio": "3121803", "divinesia": "3121902", "divino": "3122009", "divino das laranjeiras": "3122108", "divinolandia de minas": "3122207", "divinopolis": "3122306", "divisa alegre": "3122355", "divisa nova": "3122405", "divisopolis": "3122454", "dom bosco": "3122470", "dom cavati": "3122504", "dom joaquim": "3122603", "dom silverio": "3122702", "dom vicoso": "3122801", "dona euzebia": "3122900", "dores de campos": "3123007", "dores de guanhaes": "3123106", "dores do indaia": "3123205", "dores do turvo": "3123304", "doresopolis": "3123403", "douradoquara": "3123502", "durande": "3123528", "eloi mendes": "3123601", "engenheiro caldas": "3123700", "engenheiro navarro": "3123809", "entre folhas": "3123858", "entre rios de minas": "3123908", "ervalia": "3124005", "esmeraldas": "3124104", "espera feliz": "3124203", "espinosa": "3124302", "espirito santo do dourado": "3124401", "estiva": "3124500", "estrela dalva": "3124609", "estrela do indaia": "3124708", "estrela do sul": "3124807", "eugenopolis": "3124906", "ewbank da camara": "3125002", "extrema": "3125101", "fama": "3125200", "faria lemos": "3125309", "felicio dos santos": "3125408", "felisburgo": "3125606", "felixlandia": "3125705", "fernandes tourinho": "3125804", "ferros": "3125903", "fervedouro": "3125952", "florestal": "3126000", "formiga": "3126109", "formoso": "3126208", "fortaleza de minas": "3126307", "fortuna de minas": "3126406", "francisco badaro": "3126505", "francisco dumont": "3126604", "francisco sa": "3126703", "franciscopolis": "3126752", "frei gaspar": "3126802", "frei inocencio": "3126901", "frei lagonegro": "3126950", "fronteira": "3127008", "fronteira dos vales": "3127057", "fruta de leite": "3127073", "frutal": "3127107", "funilandia": "3127206", "galileia": "3127305", "gameleiras": "3127339", "glaucilandia": "3127354", "goiabeira": "3127370", "goiana": "3127388", "goncalves": "3127404", "gonzaga": "3127503", "gouveia": "3127602", "governador valadares": "3127701", "grao mogol": "3127800", "grupiara": "3127909", "guanhaes": "3128006", "guape": "3128105", "guaraciaba": "3128204", "guaraciama": "3128253", "guaranesia": "3128303", "guarani": "3128402", "guarara": "3128501", "guarda-mor": "3128600", "guaxupe": "3128709", "guidoval": "3128808", "guimarania": "3128907", "guiricema": "3129004", "gurinhata": "3129103", "heliodora": "3129202", "iapu": "3129301", "ibertioga": "3129400", "ibia": "3129509", "ibiai": "3129608", "ibiracatu": "3129657", "ibiraci": "3129707", "ibirite": "3129806", "ibitiura de minas": "3129905", "ibituruna": "3130002", "icarai de minas": "3130051", "igarape": "3130101", "igaratinga": "3130200", "iguatama": "3130309", "ijaci": "3130408", "ilicinea": "3130507", "imbe de minas": "3130556", "inconfidentes": "3130606", "indaiabira": "3130655", "indianopolis": "3130705", "ingai": "3130804", "inhapim": "3130903", "inhauma": "3131000", "inimutaba": "3131109", "ipaba": "3131158", "ipanema": "3131208", "ipatinga": "3131307", "ipiacu": "3131406", "ipuiuna": "3131505", "irai de minas": "3131604", "itabira": "3131703", "itabirinha": "3131802", "itabirito": "3131901", "itacambira": "3132008", "itacarambi": "3132107", "itaguara": "3132206", "itaipe": "3132305", "itajuba": "3132404", "itamarandiba": "3132503", "itamarati de minas": "3132602", "itambacuri": "3132701", "itambe do mato dentro": "3132800", "itamogi": "3132909", "itamonte": "3133006", "itanhandu": "3133105", "itanhomi": "3133204", "itaobim": "3133303", "itapagipe": "3133402", "itapecerica": "3133501", "itapeva": "3133600", "itatiaiucu": "3133709", "itau de minas": "3133758", "itauna": "3133808", "itaverava": "3133907", "itinga": "3134004", "itueta": "3134103", "ituiutaba": "3134202", "itumirim": "3134301", "iturama": "3134400", "itutinga": "3134509", "jaboticatubas": "3134608", "jacinto": "3134707", "jacui": "3134806", "jacutinga": "3134905", "jaguaracu": "3135001", "jaiba": "3135050", "jampruca": "3135076", "janauba": "3135100", "januaria": "3135209", "japaraiba": "3135308", "japonvar": "3135357", "jeceaba": "3135407", "jenipapo de minas": "3135456", "jequeri": "3135506", "jequitai": "3135605", "jequitiba": "3135704", "jequitinhonha": "3135803", "jesuania": "3135902", "joaima": "3136009", "joanesia": "3136108", "joao monlevade": "3136207", "joao pinheiro": "3136306", "joaquim felicio": "3136405", "jordania": "3136504", "jose goncalves de minas": "3136520", "jose raydan": "3136553", "josenopolis": "3136579", "juatuba": "3136652", "juiz de fora": "3136702", "juramento": "3136801", "juruaia": "3136900", "juvenilia": "3136959", "ladainha": "3137007", "lagamar": "3137106", "lagoa da prata": "3137205", "lagoa dos patos": "3137304", "lagoa dourada": "3137403", "lagoa formosa": "3137502", "lagoa grande": "3137536", "lagoa santa": "3137601", "lajinha": "3137700", "lambari": "3137809", "lamim": "3137908", "laranjal": "3138005", "lassance": "3138104", "lavras": "3138203", "leandro ferreira": "3138302", "leme do prado": "3138351", "leopoldina": "3138401", "liberdade": "3138500", "lima duarte": "3138609", "limeira do oeste": "3138625", "lontra": "3138658", "luisburgo": "3138674", "luislandia": "3138682", "luminarias": "3138708", "luz": "3138807", "machacalis": "3138906", "machado": "3139003", "madre de deus de minas": "3139102", "malacacheta": "3139201", "mamonas": "3139250", "manga": "3139300", "manhuacu": "3139409", "manhumirim": "3139508", "mantena": "3139607", "mar de espanha": "3139805", "maravilhas": "3139706", "maria da fe": "3139904", "mariana": "3140001", "marilac": "3140100", "mario campos": "3140159", "maripa de minas": "3140209", "marlieria": "3140308", "marmelopolis": "3140407", "martinho campos": "3140506", "martins soares": "3140530", "mata verde": "3140555", "materlandia": "3140605", "mateus leme": "3140704", "mathias lobato": "3171501", "matias barbosa": "3140803", "matias cardoso": "3140852", "matipo": "3140902", "mato verde": "3141009", "matozinhos": "3141108", "matutina": "3141207", "medeiros": "3141306", "medina": "3141405", "mendes pimentel": "3141504", "merces": "3141603", "mesquita": "3141702", "minas novas": "3141801", "minduri": "3141900", "mirabela": "3142007", "miradouro": "3142106", "mirai": "3142205", "miravania": "3142254", "moeda": "3142304", "moema": "3142403", "monjolos": "3142502", "monsenhor paulo": "3142601", "montalvania": "3142700", "monte alegre de minas": "3142809", "monte azul": "3142908", "monte belo": "3143005", "monte carmelo": "3143104", "monte formoso": "3143153", "monte santo de minas": "3143203", "monte siao": "3143401", "montes claros": "3143302", "montezuma": "3143450", "morada nova de minas": "3143500", "morro da garca": "3143609", "morro do pilar": "3143708", "munhoz": "3143807", "muriae": "3143906", "mutum": "3144003", "muzambinho": "3144102", "nacip raydan": "3144201", "nanuque": "3144300", "naque": "3144359", "natalandia": "3144375", "natercia": "3144409", "nazareno": "3144508", "nepomuceno": "3144607", "ninheira": "3144656", "nova belem": "3144672", "nova era": "3144706", "nova lima": "3144805", "nova modica": "3144904", "nova ponte": "3145000", "nova porteirinha": "3145059", "nova resende": "3145109", "nova serrana": "3145208", "nova uniao": "3136603", "novo cruzeiro": "3145307", "novo oriente de minas": "3145356", "novorizonte": "3145372", "olaria": "3145406", "olhos-d'agua": "3145455", "olimpio noronha": "3145505", "oliveira": "3145604", "oliveira fortes": "3145703", "onca de pitangui": "3145802", "oratorios": "3145851", "orizania": "3145877", "ouro branco": "3145901", "ouro fino": "3146008", "ouro preto": "3146107", "ouro verde de minas": "3146206", "padre carvalho": "3146255", "padre paraiso": "3146305", "pai pedro": "3146552", "paineiras": "3146404", "pains": "3146503", "paiva": "3146602", "palma": "3146701", "palmopolis": "3146750", "papagaios": "3146909", "para de minas": "3147105", "paracatu": "3147006", "paraguacu": "3147204", "paraisopolis": "3147303", "paraopeba": "3147402", "passa quatro": "3147600", "passa tempo": "3147709", "passa vinte": "3147808", "passabem": "3147501", "passos": "3147907", "patis": "3147956", "patos de minas": "3148004", "patrocinio": "3148103", "patrocinio do muriae": "3148202", "paula candido": "3148301", "paulistas": "3148400", "pavao": "3148509", "pecanha": "3148608", "pedra azul": "3148707", "pedra bonita": "3148756", "pedra do anta": "3148806", "pedra do indaia": "3148905", "pedra dourada": "3149002", "pedralva": "3149101", "pedras de maria da cruz": "3149150", "pedrinopolis": "3149200", "pedro leopoldo": "3149309", "pedro teixeira": "3149408", "pequeri": "3149507", "pequi": "3149606", "perdigao": "3149705", "perdizes": "3149804", "perdoes": "3149903", "periquito": "3149952", "pescador": "3150000", "piau": "3150109", "piedade de caratinga": "3150158", "piedade de ponte nova": "3150208", "piedade do rio grande": "3150307", "piedade dos gerais": "3150406", "pimenta": "3150505", "pingo-d'agua": "3150539", "pintopolis": "3150570", "piracema": "3150604", "pirajuba": "3150703", "piranga": "3150802", "pirangucu": "3150901", "piranguinho": "3151008", "pirapetinga": "3151107", "pirapora": "3151206", "pirauba": "3151305", "pitangui": "3151404", "piumhi": "3151503", "planura": "3151602", "poco fundo": "3151701", "pocos de caldas": "3151800", "pocrane": "3151909", "pompeu": "3152006", "ponte nova": "3152105", "ponto chique": "3152131", "ponto dos volantes": "3152170", "porteirinha": "3152204", "porto firme": "3152303", "pote": "3152402", "pouso alegre": "3152501", "pouso alto": "3152600", "prados": "3152709", "prata": "3152808", "pratapolis": "3152907", "pratinha": "3153004", "presidente bernardes": "3153103", "presidente juscelino": "3153202", "presidente kubitschek": "3153301", "presidente olegario": "3153400", "prudente de morais": "3153608", "quartel geral": "3153707", "queluzito": "3153806", "raposos": "3153905", "raul soares": "3154002", "recreio": "3154101", "reduto": "3154150", "resende costa": "3154200", "resplendor": "3154309", "ressaquinha": "3154408", "riachinho": "3154457", "riacho dos machados": "3154507", "ribeirao das neves": "3154606", "ribeirao vermelho": "3154705", "rio acima": "3154804", "rio casca": "3154903", "rio do prado": "3155108", "rio doce": "3155009", "rio espera": "3155207", "rio manso": "3155306", "rio novo": "3155405", "rio paranaiba": "3155504", "rio pardo de minas": "3155603", "rio piracicaba": "3155702", "rio pomba": "3155801", "rio preto": "3155900", "rio vermelho": "3156007", "ritapolis": "3156106", "rochedo de minas": "3156205", "rodeiro": "3156304", "romaria": "3156403", "rosario da limeira": "3156452", "rubelita": "3156502", "rubim": "3156601", "sabara": "3156700", "sabinopolis": "3156809", "sacramento": "3156908", "salinas": "3157005", "salto da divisa": "3157104", "santa barbara": "3157203", "santa barbara do leste": "3157252", "santa barbara do monte verde": "3157278", "santa barbara do tugurio": "3157302", "santa cruz de minas": "3157336", "santa cruz de salinas": "3157377", "santa cruz do escalvado": "3157401", "santa efigenia de minas": "3157500", "santa fe de minas": "3157609", "santa helena de minas": "3157658", "santa juliana": "3157708", "santa luzia": "3157807", "santa margarida": "3157906", "santa maria de itabira": "3158003", "santa maria do salto": "3158102", "santa maria do suacui": "3158201", "santa rita de caldas": "3159209", "santa rita de ibitipoca": "3159407", "santa rita de jacutinga": "3159308", "santa rita de minas": "3159357", "santa rita do itueto": "3159506", "santa rita do sapucai": "3159605", "santa rosa da serra": "3159704", "santa vitoria": "3159803", "santana da vargem": "3158300", "santana de cataguases": "3158409", "santana de pirapama": "3158508", "santana do deserto": "3158607", "santana do garambeu": "3158706", "santana do jacare": "3158805", "santana do manhuacu": "3158904", "santana do paraiso": "3158953", "santana do riacho": "3159001", "santana dos montes": "3159100", "santo antonio do amparo": "3159902", "santo antonio do aventureiro": "3160009", "santo antonio do grama": "3160108", "santo antonio do itambe": "3160207", "santo antonio do jacinto": "3160306", "santo antonio do monte": "3160405", "santo antonio do retiro": "3160454", "santo antonio do rio abaixo": "3160504", "santo hipolito": "3160603", "santos dumont": "3160702", "sao bento abade": "3160801", "sao bras do suacui": "3160900", "sao domingos das dores": "3160959", "sao domingos do prata": "3161007", "sao felix de minas": "3161056", "sao francisco": "3161106", "sao francisco de paula": "3161205", "sao francisco de sales": "3161304", "sao francisco do gloria": "3161403", "sao geraldo": "3161502", "sao geraldo da piedade": "3161601", "sao geraldo do baixio": "3161650", "sao goncalo do abaete": "3161700", "sao goncalo do para": "3161809", "sao goncalo do rio abaixo": "3161908", "sao goncalo do rio preto": "3125507", "sao goncalo do sapucai": "3162005", "sao gotardo": "3162104", "sao joao batista do gloria": "3162203", "sao joao da lagoa": "3162252", "sao joao da mata": "3162302", "sao joao da ponte": "3162401", "sao joao das missoes": "3162450", "sao joao del rei": "3162500", "sao joao do manhuacu": "3162559", "sao joao do manteninha": "3162575", "sao joao do oriente": "3162609", "sao joao do pacui": "3162658", "sao joao do paraiso": "3162708", "sao joao evangelista": "3162807", "sao joao nepomuceno": "3162906", "sao joaquim de bicas": "3162922", "sao jose da barra": "3162948", "sao jose da lapa": "3162955", "sao jose da safira": "3163003", "sao jose da varginha": "3163102", "sao jose do alegre": "3163201", "sao jose do divino": "3163300", "sao jose do goiabal": "3163409", "sao jose do jacuri": "3163508", "sao jose do mantimento": "3163607", "sao lourenco": "3163706", "sao miguel do anta": "3163805", "sao pedro da uniao": "3163904", "sao pedro do suacui": "3164100", "sao pedro dos ferros": "3164001", "sao romao": "3164209", "sao roque de minas": "3164308", "sao sebastiao da bela vista": "3164407", "sao sebastiao da vargem alegre": "3164431", "sao sebastiao do anta": "3164472", "sao sebastiao do maranhao": "3164506", "sao sebastiao do oeste": "3164605", "sao sebastiao do paraiso": "3164704", "sao sebastiao do rio preto": "3164803", "sao sebastiao do rio verde": "3164902", "sao tiago": "3165008", "sao tomas de aquino": "3165107", "sao tome das letras": "3165206", "sao vicente de minas": "3165305", "sapucai-mirim": "3165404", "sardoa": "3165503", "sarzedo": "3165537", "sem-peixe": "3165560", "senador amaral": "3165578", "senador cortes": "3165602", "senador firmino": "3165701", "senador jose bento": "3165800", "senador modestino goncalves": "3165909", "senhora de oliveira": "3166006", "senhora do porto": "3166105", "senhora dos remedios": "3166204", "sericita": "3166303", "seritinga": "3166402", "serra azul de minas": "3166501", "serra da saudade": "3166600", "serra do salitre": "3166808", "serra dos aimores": "3166709", "serrania": "3166907", "serranopolis de minas": "3166956", "serranos": "3167004", "serro": "3167103", "sete lagoas": "3167202", "setubinha": "3165552", "silveirania": "3167301", "silvianopolis": "3167400", "simao pereira": "3167509", "simonesia": "3167608", "sobralia": "3167707", "soledade de minas": "3167806", "tabuleiro": "3167905", "taiobeiras": "3168002", "taparuba": "3168051", "tapira": "3168101", "tapirai": "3168200", "taquaracu de minas": "3168309", "tarumirim": "3168408", "teixeiras": "3168507", "teofilo otoni": "3168606", "timoteo": "3168705", "tiradentes": "3168804", "tiros": "3168903", "tocantins": "3169000", "tocos do moji": "3169059", "toledo": "3169109", "tombos": "3169208", "tres coracoes": "3169307", "tres marias": "3169356", "tres pontas": "3169406", "tumiritinga": "3169505", "tupaciguara": "3169604", "turmalina": "3169703", "turvolandia": "3169802", "uba": "3169901", "ubai": "3170008", "ubaporanga": "3170057", "uberaba": "3170107", "uberlandia": "3170206", "umburatiba": "3170305", "unai": "3170404", "uniao de minas": "3170438", "uruana de minas": "3170479", "urucania": "3170503", "urucuia": "3170529", "vargem alegre": "3170578", "vargem bonita": "3170602", "vargem grande do rio pardo": "3170651", "varginha": "3170701", "varjao de minas": "3170750", "varzea da palma": "3170800", "varzelandia": "3170909", "vazante": "3171006", "verdelandia": "3171030", "veredinha": "3171071", "verissimo": "3171105", "vermelho novo": "3171154", "vespasiano": "3171204", "vicosa": "3171303", "vieiras": "3171402", "virgem da lapa": "3171600", "virginia": "3171709", "virginopolis": "3171808", "virgolandia": "3171907", "visconde do rio branco": "3172004", "volta grande": "3172103", "wenceslau braz": "3172202" }, "ES": { "afonso claudio": "3200102", "agua doce do norte": "3200169", "aguia branca": "3200136", "alegre": "3200201", "alfredo chaves": "3200300", "alto rio novo": "3200359", "anchieta": "3200409", "apiaca": "3200508", "aracruz": "3200607", "atilio vivacqua": "3200706", "baixo guandu": "3200805", "barra de sao francisco": "3200904", "boa esperanca": "3201001", "bom jesus do norte": "3201100", "brejetuba": "3201159", "cachoeiro de itapemirim": "3201209", "cariacica": "3201308", "castelo": "3201407", "colatina": "3201506", "conceicao da barra": "3201605", "conceicao do castelo": "3201704", "divino de sao lourenco": "3201803", "domingos martins": "3201902", "dores do rio preto": "3202009", "ecoporanga": "3202108", "fundao": "3202207", "governador lindenberg": "3202256", "guacui": "3202306", "guarapari": "3202405", "ibatiba": "3202454", "ibiracu": "3202504", "ibitirama": "3202553", "iconha": "3202603", "irupi": "3202652", "itaguacu": "3202702", "itapemirim": "3202801", "itarana": "3202900", "iuna": "3203007", "jaguare": "3203056", "jeronimo monteiro": "3203106", "joao neiva": "3203130", "laranja da terra": "3203163", "linhares": "3203205", "mantenopolis": "3203304", "marataizes": "3203320", "marechal floriano": "3203346", "marilandia": "3203353", "mimoso do sul": "3203403", "montanha": "3203502", "mucurici": "3203601", "muniz freire": "3203700", "muqui": "3203809", "nova venecia": "3203908", "pancas": "3204005", "pedro canario": "3204054", "pinheiros": "3204104", "piuma": "3204203", "ponto belo": "3204252", "presidente kennedy": "3204302", "rio bananal": "3204351", "rio novo do sul": "3204401", "santa leopoldina": "3204500", "santa maria de jetiba": "3204559", "santa teresa": "3204609", "sao domingos do norte": "3204658", "sao gabriel da palha": "3204708", "sao jose do calcado": "3204807", "sao mateus": "3204906", "sao roque do canaa": "3204955", "serra": "3205002", "sooretama": "3205010", "vargem alta": "3205036", "venda nova do imigrante": "3205069", "viana": "3205101", "vila pavao": "3205150", "vila valerio": "3205176", "vila velha": "3205200", "vitoria": "3205309" }, "RJ": { "angra dos reis": "3300100", "aperibe": "3300159", "araruama": "3300209", "areal": "3300225", "armacao dos buzios": "3300233", "arraial do cabo": "3300258", "barra do pirai": "3300308", "barra mansa": "3300407", "belford roxo": "3300456", "bom jardim": "3300506", "bom jesus do itabapoana": "3300605", "cabo frio": "3300704", "cachoeiras de macacu": "3300803", "cambuci": "3300902", "campos dos goytacazes": "3301009", "cantagalo": "3301108", "carapebus": "3300936", "cardoso moreira": "3301157", "carmo": "3301207", "casimiro de abreu": "3301306", "comendador levy gasparian": "3300951", "conceicao de macabu": "3301405", "cordeiro": "3301504", "duas barras": "3301603", "duque de caxias": "3301702", "engenheiro paulo de frontin": "3301801", "guapimirim": "3301850", "iguaba grande": "3301876", "itaborai": "3301900", "itaguai": "3302007", "italva": "3302056", "itaocara": "3302106", "itaperuna": "3302205", "itatiaia": "3302254", "japeri": "3302270", "laje do muriae": "3302304", "macae": "3302403", "macuco": "3302452", "mage": "3302502", "mangaratiba": "3302601", "marica": "3302700", "mendes": "3302809", "mesquita": "3302858", "miguel pereira": "3302908", "miracema": "3303005", "natividade": "3303104", "nilopolis": "3303203", "niteroi": "3303302", "nova friburgo": "3303401", "nova iguacu": "3303500", "paracambi": "3303609", "paraiba do sul": "3303708", "paraty": "3303807", "paty do alferes": "3303856", "petropolis": "3303906", "pinheiral": "3303955", "pirai": "3304003", "porciuncula": "3304102", "porto real": "3304110", "quatis": "3304128", "queimados": "3304144", "quissama": "3304151", "resende": "3304201", "rio bonito": "3304300", "rio claro": "3304409", "rio das flores": "3304508", "rio das ostras": "3304524", "rio de janeiro": "3304557", "santa maria madalena": "3304607", "santo antonio de padua": "3304706", "sao fidelis": "3304805", "sao francisco de itabapoana": "3304755", "sao goncalo": "3304904", "sao joao da barra": "3305000", "sao joao de meriti": "3305109", "sao jose de uba": "3305133", "sao jose do vale do rio preto": "3305158", "sao pedro da aldeia": "3305208", "sao sebastiao do alto": "3305307", "sapucaia": "3305406", "saquarema": "3305505", "seropedica": "3305554", "silva jardim": "3305604", "sumidouro": "3305703", "tangua": "3305752", "teresopolis": "3305802", "trajano de moraes": "3305901", "tres rios": "3306008", "valenca": "3306107", "varre-sai": "3306156", "vassouras": "3306206", "volta redonda": "3306305" }, "SP": { "adamantina": "3500105", "adolfo": "3500204", "aguai": "3500303", "aguas da prata": "3500402", "aguas de lindoia": "3500501", "aguas de santa barbara": "3500550", "aguas de sao pedro": "3500600", "agudos": "3500709", "alambari": "3500758", "alfredo marcondes": "3500808", "altair": "3500907", "altinopolis": "3501004", "alto alegre": "3501103", "aluminio": "3501152", "alvares florence": "3501202", "alvares machado": "3501301", "alvaro de carvalho": "3501400", "alvinlandia": "3501509", "americana": "3501608", "americo brasiliense": "3501707", "americo de campos": "3501806", "amparo": "3501905", "analandia": "3502002", "andradina": "3502101", "angatuba": "3502200", "anhembi": "3502309", "anhumas": "3502408", "aparecida": "3502507", "aparecida d'oeste": "3502606", "apiai": "3502705", "aracariguama": "3502754", "aracatuba": "3502804", "aracoiaba da serra": "3502903", "aramina": "3503000", "arandu": "3503109", "arapei": "3503158", "araraquara": "3503208", "araras": "3503307", "arco-iris": "3503356", "arealva": "3503406", "areias": "3503505", "areiopolis": "3503604", "ariranha": "3503703", "artur nogueira": "3503802", "aruja": "3503901", "aspasia": "3503950", "assis": "3504008", "atibaia": "3504107", "auriflama": "3504206", "avai": "3504305", "avanhandava": "3504404", "avare": "3504503", "bady bassitt": "3504602", "balbinos": "3504701", "balsamo": "3504800", "bananal": "3504909", "barao de antonina": "3505005", "barbosa": "3505104", "bariri": "3505203", "barra bonita": "3505302", "barra do chapeu": "3505351", "barra do turvo": "3505401", "barretos": "3505500", "barrinha": "3505609", "barueri": "3505708", "bastos": "3505807", "batatais": "3505906", "bauru": "3506003", "bebedouro": "3506102", "bento de abreu": "3506201", "bernardino de campos": "3506300", "bertioga": "3506359", "bilac": "3506409", "birigui": "3506508", "biritiba mirim": "3506607", "boa esperanca do sul": "3506706", "bocaina": "3506805", "bofete": "3506904", "boituva": "3507001", "bom jesus dos perdoes": "3507100", "bom sucesso de itarare": "3507159", "bora": "3507209", "boraceia": "3507308", "borborema": "3507407", "borebi": "3507456", "botucatu": "3507506", "braganca paulista": "3507605", "brauna": "3507704", "brejo alegre": "3507753", "brodowski": "3507803", "brotas": "3507902", "buri": "3508009", "buritama": "3508108", "buritizal": "3508207", "cabralia paulista": "3508306", "cabreuva": "3508405", "cacapava": "3508504", "cachoeira paulista": "3508603", "caconde": "3508702", "cafelandia": "3508801", "caiabu": "3508900", "caieiras": "3509007", "caiua": "3509106", "cajamar": "3509205", "cajati": "3509254", "cajobi": "3509304", "cajuru": "3509403", "campina do monte alegre": "3509452", "campinas": "3509502", "campo limpo paulista": "3509601", "campos do jordao": "3509700", "campos novos paulista": "3509809", "cananeia": "3509908", "canas": "3509957", "candido mota": "3510005", "candido rodrigues": "3510104", "canitar": "3510153", "capao bonito": "3510203", "capela do alto": "3510302", "capivari": "3510401", "caraguatatuba": "3510500", "carapicuiba": "3510609", "cardoso": "3510708", "casa branca": "3510807", "cassia dos coqueiros": "3510906", "castilho": "3511003", "catanduva": "3511102", "catigua": "3511201", "cedral": "3511300", "cerqueira cesar": "3511409", "cerquilho": "3511508", "cesario lange": "3511607", "charqueada": "3511706", "chavantes": "3557204", "clementina": "3511904", "colina": "3512001", "colombia": "3512100", "conchal": "3512209", "conchas": "3512308", "cordeiropolis": "3512407", "coroados": "3512506", "coronel macedo": "3512605", "corumbatai": "3512704", "cosmopolis": "3512803", "cosmorama": "3512902", "cotia": "3513009", "cravinhos": "3513108", "cristais paulista": "3513207", "cruzalia": "3513306", "cruzeiro": "3513405", "cubatao": "3513504", "cunha": "3513603", "descalvado": "3513702", "diadema": "3513801", "dirce reis": "3513850", "divinolandia": "3513900", "dobrada": "3514007", "dois corregos": "3514106", "dolcinopolis": "3514205", "dourado": "3514304", "dracena": "3514403", "duartina": "3514502", "dumont": "3514601", "echapora": "3514700", "eldorado": "3514809", "elias fausto": "3514908", "elisiario": "3514924", "embauba": "3514957", "embu das artes": "3515004", "embu-guacu": "3515103", "emilianopolis": "3515129", "engenheiro coelho": "3515152", "espirito santo do pinhal": "3515186", "espirito santo do turvo": "3515194", "estiva gerbi": "3557303", "estrela do norte": "3515301", "estrela d'oeste": "3515202", "euclides da cunha paulista": "3515350", "fartura": "3515400", "fernando prestes": "3515608", "fernandopolis": "3515509", "fernao": "3515657", "ferraz de vasconcelos": "3515707", "flora rica": "3515806", "floreal": "3515905", "florida paulista": "3516002", "florinea": "3516101", "franca": "3516200", "francisco morato": "3516309", "franco da rocha": "3516408", "gabriel monteiro": "3516507", "galia": "3516606", "garca": "3516705", "gastao vidigal": "3516804", "gaviao peixoto": "3516853", "general salgado": "3516903", "getulina": "3517000", "glicerio": "3517109", "guaicara": "3517208", "guaimbe": "3517307", "guaira": "3517406", "guapiacu": "3517505", "guapiara": "3517604", "guara": "3517703", "guaracai": "3517802", "guaraci": "3517901", "guarani d'oeste": "3518008", "guaranta": "3518107", "guararapes": "3518206", "guararema": "3518305", "guaratingueta": "3518404", "guarei": "3518503", "guariba": "3518602", "guaruja": "3518701", "guarulhos": "3518800", "guatapara": "3518859", "guzolandia": "3518909", "herculandia": "3519006", "holambra": "3519055", "hortolandia": "3519071", "iacanga": "3519105", "iacri": "3519204", "iaras": "3519253", "ibate": "3519303", "ibira": "3519402", "ibirarema": "3519501", "ibitinga": "3519600", "ibiuna": "3519709", "icem": "3519808", "iepe": "3519907", "igaracu do tiete": "3520004", "igarapava": "3520103", "igarata": "3520202", "iguape": "3520301", "ilha comprida": "3520426", "ilha solteira": "3520442", "ilhabela": "3520400", "indaiatuba": "3520509", "indiana": "3520608", "indiapora": "3520707", "inubia paulista": "3520806", "ipaussu": "3520905", "ipero": "3521002", "ipeuna": "3521101", "ipigua": "3521150", "iporanga": "3521200", "ipua": "3521309", "iracemapolis": "3521408", "irapua": "3521507", "irapuru": "3521606", "itabera": "3521705", "itai": "3521804", "itajobi": "3521903", "itaju": "3522000", "itanhaem": "3522109", "itaoca": "3522158", "itapecerica da serra": "3522208", "itapetininga": "3522307", "itapeva": "3522406", "itapevi": "3522505", "itapira": "3522604", "itapirapua paulista": "3522653", "itapolis": "3522703", "itaporanga": "3522802", "itapui": "3522901", "itapura": "3523008", "itaquaquecetuba": "3523107", "itarare": "3523206", "itariri": "3523305", "itatiba": "3523404", "itatinga": "3523503", "itirapina": "3523602", "itirapua": "3523701", "itobi": "3523800", "itu": "3523909", "itupeva": "3524006", "ituverava": "3524105", "jaborandi": "3524204", "jaboticabal": "3524303", "jacarei": "3524402", "jaci": "3524501", "jacupiranga": "3524600", "jaguariuna": "3524709", "jales": "3524808", "jambeiro": "3524907", "jandira": "3525003", "jardinopolis": "3525102", "jarinu": "3525201", "jau": "3525300", "jeriquara": "3525409", "joanopolis": "3525508", "joao ramalho": "3525607", "jose bonifacio": "3525706", "julio mesquita": "3525805", "jumirim": "3525854", "jundiai": "3525904", "junqueiropolis": "3526001", "juquia": "3526100", "juquitiba": "3526209", "lagoinha": "3526308", "laranjal paulista": "3526407", "lavinia": "3526506", "lavrinhas": "3526605", "leme": "3526704", "lencois paulista": "3526803", "limeira": "3526902", "lindoia": "3527009", "lins": "3527108", "lorena": "3527207", "lourdes": "3527256", "louveira": "3527306", "lucelia": "3527405", "lucianopolis": "3527504", "luis antonio": "3527603", "luiziania": "3527702", "lupercio": "3527801", "lutecia": "3527900", "macatuba": "3528007", "macaubal": "3528106", "macedonia": "3528205", "magda": "3528304", "mairinque": "3528403", "mairipora": "3528502", "manduri": "3528601", "maraba paulista": "3528700", "maracai": "3528809", "marapoama": "3528858", "mariapolis": "3528908", "marilia": "3529005", "marinopolis": "3529104", "martinopolis": "3529203", "matao": "3529302", "maua": "3529401", "mendonca": "3529500", "meridiano": "3529609", "mesopolis": "3529658", "miguelopolis": "3529708", "mineiros do tiete": "3529807", "mira estrela": "3530003", "miracatu": "3529906", "mirandopolis": "3530102", "mirante do paranapanema": "3530201", "mirassol": "3530300", "mirassolandia": "3530409", "mococa": "3530508", "mogi das cruzes": "3530607", "mogi guacu": "3530706", "mogi mirim": "3530805", "mombuca": "3530904", "moncoes": "3531001", "mongagua": "3531100", "monte alegre do sul": "3531209", "monte alto": "3531308", "monte aprazivel": "3531407", "monte azul paulista": "3531506", "monte castelo": "3531605", "monte mor": "3531803", "monteiro lobato": "3531704", "morro agudo": "3531902", "morungaba": "3532009", "motuca": "3532058", "murutinga do sul": "3532108", "nantes": "3532157", "narandiba": "3532207", "natividade da serra": "3532306", "nazare paulista": "3532405", "neves paulista": "3532504", "nhandeara": "3532603", "nipoa": "3532702", "nova alianca": "3532801", "nova campina": "3532827", "nova canaa paulista": "3532843", "nova castilho": "3532868", "nova europa": "3532900", "nova granada": "3533007", "nova guataporanga": "3533106", "nova independencia": "3533205", "nova luzitania": "3533304", "nova odessa": "3533403", "novais": "3533254", "novo horizonte": "3533502", "nuporanga": "3533601", "ocaucu": "3533700", "oleo": "3533809", "olimpia": "3533908", "onda verde": "3534005", "oriente": "3534104", "orindiuva": "3534203", "orlandia": "3534302", "osasco": "3534401", "oscar bressane": "3534500", "osvaldo cruz": "3534609", "ourinhos": "3534708", "ouro verde": "3534807", "ouroeste": "3534757", "pacaembu": "3534906", "palestina": "3535002", "palmares paulista": "3535101", "palmeira d'oeste": "3535200", "palmital": "3535309", "panorama": "3535408", "paraguacu paulista": "3535507", "paraibuna": "3535606", "paraiso": "3535705", "paranapanema": "3535804", "paranapua": "3535903", "parapua": "3536000", "pardinho": "3536109", "pariquera-acu": "3536208", "parisi": "3536257", "patrocinio paulista": "3536307", "pauliceia": "3536406", "paulinia": "3536505", "paulistania": "3536570", "paulo de faria": "3536604", "pederneiras": "3536703", "pedra bela": "3536802", "pedranopolis": "3536901", "pedregulho": "3537008", "pedreira": "3537107", "pedrinhas paulista": "3537156", "pedro de toledo": "3537206", "penapolis": "3537305", "pereira barreto": "3537404", "pereiras": "3537503", "peruibe": "3537602", "piacatu": "3537701", "piedade": "3537800", "pilar do sul": "3537909", "pindamonhangaba": "3538006", "pindorama": "3538105", "pinhalzinho": "3538204", "piquerobi": "3538303", "piquete": "3538501", "piracaia": "3538600", "piracicaba": "3538709", "piraju": "3538808", "pirajui": "3538907", "pirangi": "3539004", "pirapora do bom jesus": "3539103", "pirapozinho": "3539202", "pirassununga": "3539301", "piratininga": "3539400", "pitangueiras": "3539509", "planalto": "3539608", "platina": "3539707", "poa": "3539806", "poloni": "3539905", "pompeia": "3540002", "pongai": "3540101", "pontal": "3540200", "pontalinda": "3540259", "pontes gestal": "3540309", "populina": "3540408", "porangaba": "3540507", "porto feliz": "3540606", "porto ferreira": "3540705", "potim": "3540754", "potirendaba": "3540804", "pracinha": "3540853", "pradopolis": "3540903", "praia grande": "3541000", "pratania": "3541059", "presidente alves": "3541109", "presidente bernardes": "3541208", "presidente epitacio": "3541307", "presidente prudente": "3541406", "presidente venceslau": "3541505", "promissao": "3541604", "quadra": "3541653", "quata": "3541703", "queiroz": "3541802", "queluz": "3541901", "quintana": "3542008", "rafard": "3542107", "rancharia": "3542206", "redencao da serra": "3542305", "regente feijo": "3542404", "reginopolis": "3542503", "registro": "3542602", "restinga": "3542701", "ribeira": "3542800", "ribeirao bonito": "3542909", "ribeirao branco": "3543006", "ribeirao corrente": "3543105", "ribeirao do sul": "3543204", "ribeirao dos indios": "3543238", "ribeirao grande": "3543253", "ribeirao pires": "3543303", "ribeirao preto": "3543402", "rifaina": "3543600", "rincao": "3543709", "rinopolis": "3543808", "rio claro": "3543907", "rio das pedras": "3544004", "rio grande da serra": "3544103", "riolandia": "3544202", "riversul": "3543501", "rosana": "3544251", "roseira": "3544301", "rubiacea": "3544400", "rubineia": "3544509", "sabino": "3544608", "sagres": "3544707", "sales": "3544806", "sales oliveira": "3544905", "salesopolis": "3545001", "salmourao": "3545100", "saltinho": "3545159", "salto": "3545209", "salto de pirapora": "3545308", "salto grande": "3545407", "sandovalina": "3545506", "santa adelia": "3545605", "santa albertina": "3545704", "santa barbara d'oeste": "3545803", "santa branca": "3546009", "santa clara d'oeste": "3546108", "santa cruz da conceicao": "3546207", "santa cruz da esperanca": "3546256", "santa cruz das palmeiras": "3546306", "santa cruz do rio pardo": "3546405", "santa ernestina": "3546504", "santa fe do sul": "3546603", "santa gertrudes": "3546702", "santa isabel": "3546801", "santa lucia": "3546900", "santa maria da serra": "3547007", "santa mercedes": "3547106", "santa rita do passa quatro": "3547502", "santa rita d'oeste": "3547403", "santa rosa de viterbo": "3547601", "santa salete": "3547650", "santana da ponte pensa": "3547205", "santana de parnaiba": "3547304", "santo anastacio": "3547700", "santo andre": "3547809", "santo antonio da alegria": "3547908", "santo antonio de posse": "3548005", "santo antonio do aracangua": "3548054", "santo antonio do jardim": "3548104", "santo antonio do pinhal": "3548203", "santo expedito": "3548302", "santopolis do aguapei": "3548401", "santos": "3548500", "sao bento do sapucai": "3548609", "sao bernardo do campo": "3548708", "sao caetano do sul": "3548807", "sao carlos": "3548906", "sao francisco": "3549003", "sao joao da boa vista": "3549102", "sao joao das duas pontes": "3549201", "sao joao de iracema": "3549250", "sao joao do pau d'alho": "3549300", "sao joaquim da barra": "3549409", "sao jose da bela vista": "3549508", "sao jose do barreiro": "3549607", "sao jose do rio pardo": "3549706", "sao jose do rio preto": "3549805", "sao jose dos campos": "3549904", "sao lourenco da serra": "3549953", "sao luiz do paraitinga": "3550001", "sao manuel": "3550100", "sao miguel arcanjo": "3550209", "sao paulo": "3550308", "sao pedro": "3550407", "sao pedro do turvo": "3550506", "sao roque": "3550605", "sao sebastiao": "3550704", "sao sebastiao da grama": "3550803", "sao simao": "3550902", "sao vicente": "3551009", "sarapui": "3551108", "sarutaia": "3551207", "sebastianopolis do sul": "3551306", "serra azul": "3551405", "serra negra": "3551603", "serrana": "3551504", "sertaozinho": "3551702", "sete barras": "3551801", "severinia": "3551900", "silveiras": "3552007", "socorro": "3552106", "sorocaba": "3552205", "sud mennucci": "3552304", "sumare": "3552403", "suzanapolis": "3552551", "suzano": "3552502", "tabapua": "3552601", "tabatinga": "3552700", "taboao da serra": "3552809", "taciba": "3552908", "taguai": "3553005", "taiacu": "3553104", "taiuva": "3553203", "tambau": "3553302", "tanabi": "3553401", "tapirai": "3553500", "tapiratiba": "3553609", "taquaral": "3553658", "taquaritinga": "3553708", "taquarituba": "3553807", "taquarivai": "3553856", "tarabai": "3553906", "taruma": "3553955", "tatui": "3554003", "taubate": "3554102", "tejupa": "3554201", "teodoro sampaio": "3554300", "terra roxa": "3554409", "tiete": "3554508", "timburi": "3554607", "torre de pedra": "3554656", "torrinha": "3554706", "trabiju": "3554755", "tremembe": "3554805", "tres fronteiras": "3554904", "tuiuti": "3554953", "tupa": "3555000", "tupi paulista": "3555109", "turiuba": "3555208", "turmalina": "3555307", "ubarana": "3555356", "ubatuba": "3555406", "ubirajara": "3555505", "uchoa": "3555604", "uniao paulista": "3555703", "urania": "3555802", "uru": "3555901", "urupes": "3556008", "valentim gentil": "3556107", "valinhos": "3556206", "valparaiso": "3556305", "vargem": "3556354", "vargem grande do sul": "3556404", "vargem grande paulista": "3556453", "varzea paulista": "3556503", "vera cruz": "3556602", "vinhedo": "3556701", "viradouro": "3556800", "vista alegre do alto": "3556909", "vitoria brasil": "3556958", "votorantim": "3557006", "votuporanga": "3557105", "zacarias": "3557154" }, "PR": { "abatia": "4100103", "adrianopolis": "4100202", "agudos do sul": "4100301", "almirante tamandare": "4100400", "altamira do parana": "4100459", "alto paraiso": "4128625", "alto parana": "4100608", "alto piquiri": "4100707", "altonia": "4100509", "alvorada do sul": "4100806", "amapora": "4100905", "ampere": "4101002", "anahy": "4101051", "andira": "4101101", "angulo": "4101150", "antonina": "4101200", "antonio olinto": "4101309", "apucarana": "4101408", "arapongas": "4101507", "arapoti": "4101606", "arapua": "4101655", "araruna": "4101705", "araucaria": "4101804", "ariranha do ivai": "4101853", "assai": "4101903", "assis chateaubriand": "4102000", "astorga": "4102109", "atalaia": "4102208", "balsa nova": "4102307", "bandeirantes": "4102406", "barbosa ferraz": "4102505", "barra do jacare": "4102703", "barracao": "4102604", "bela vista da caroba": "4102752", "bela vista do paraiso": "4102802", "bituruna": "4102901", "boa esperanca": "4103008", "boa esperanca do iguacu": "4103024", "boa ventura de sao roque": "4103040", "boa vista da aparecida": "4103057", "bocaiuva do sul": "4103107", "bom jesus do sul": "4103156", "bom sucesso": "4103206", "bom sucesso do sul": "4103222", "borrazopolis": "4103305", "braganey": "4103354", "brasilandia do sul": "4103370", "cafeara": "4103404", "cafelandia": "4103453", "cafezal do sul": "4103479", "california": "4103503", "cambara": "4103602", "cambe": "4103701", "cambira": "4103800", "campina da lagoa": "4103909", "campina do simao": "4103958", "campina grande do sul": "4104006", "campo bonito": "4104055", "campo do tenente": "4104105", "campo largo": "4104204", "campo magro": "4104253", "campo mourao": "4104303", "candido de abreu": "4104402", "candoi": "4104428", "cantagalo": "4104451", "capanema": "4104501", "capitao leonidas marques": "4104600", "carambei": "4104659", "carlopolis": "4104709", "cascavel": "4104808", "castro": "4104907", "catanduvas": "4105003", "centenario do sul": "4105102", "cerro azul": "4105201", "ceu azul": "4105300", "chopinzinho": "4105409", "cianorte": "4105508", "cidade gaucha": "4105607", "clevelandia": "4105706", "colombo": "4105805", "colorado": "4105904", "congonhinhas": "4106001", "conselheiro mairinck": "4106100", "contenda": "4106209", "corbelia": "4106308", "cornelio procopio": "4106407", "coronel domingos soares": "4106456", "coronel vivida": "4106506", "corumbatai do sul": "4106555", "cruz machado": "4106803", "cruzeiro do iguacu": "4106571", "cruzeiro do oeste": "4106605", "cruzeiro do sul": "4106704", "cruzmaltina": "4106852", "curitiba": "4106902", "curiuva": "4107009", "diamante do norte": "4107108", "diamante do sul": "4107124", "diamante d'oeste": "4107157", "dois vizinhos": "4107207", "douradina": "4107256", "doutor camargo": "4107306", "doutor ulysses": "4128633", "eneas marques": "4107405", "engenheiro beltrao": "4107504", "entre rios do oeste": "4107538", "esperanca nova": "4107520", "espigao alto do iguacu": "4107546", "farol": "4107553", "faxinal": "4107603", "fazenda rio grande": "4107652", "fenix": "4107702", "fernandes pinheiro": "4107736", "figueira": "4107751", "flor da serra do sul": "4107850", "florai": "4107801", "floresta": "4107900", "florestopolis": "4108007", "florida": "4108106", "formosa do oeste": "4108205", "foz do iguacu": "4108304", "foz do jordao": "4108452", "francisco alves": "4108320", "francisco beltrao": "4108403", "general carneiro": "4108502", "godoy moreira": "4108551", "goioere": "4108601", "goioxim": "4108650", "grandes rios": "4108700", "guaira": "4108809", "guairaca": "4108908", "guamiranga": "4108957", "guapirama": "4109005", "guaporema": "4109104", "guaraci": "4109203", "guaraniacu": "4109302", "guarapuava": "4109401", "guaraquecaba": "4109500", "guaratuba": "4109609", "honorio serpa": "4109658", "ibaiti": "4109708", "ibema": "4109757", "ibipora": "4109807", "icaraima": "4109906", "iguaracu": "4110003", "iguatu": "4110052", "imbau": "4110078", "imbituva": "4110102", "inacio martins": "4110201", "inaja": "4110300", "indianopolis": "4110409", "ipiranga": "4110508", "ipora": "4110607", "iracema do oeste": "4110656", "irati": "4110706", "iretama": "4110805", "itaguaje": "4110904", "itaipulandia": "4110953", "itambaraca": "4111001", "itambe": "4111100", "itapejara d'oeste": "4111209", "itaperucu": "4111258", "itauna do sul": "4111308", "ivai": "4111407", "ivaipora": "4111506", "ivate": "4111555", "ivatuba": "4111605", "jaboti": "4111704", "jacarezinho": "4111803", "jaguapita": "4111902", "jaguariaiva": "4112009", "jandaia do sul": "4112108", "janiopolis": "4112207", "japira": "4112306", "japura": "4112405", "jardim alegre": "4112504", "jardim olinda": "4112603", "jataizinho": "4112702", "jesuitas": "4112751", "joaquim tavora": "4112801", "jundiai do sul": "4112900", "juranda": "4112959", "jussara": "4113007", "kalore": "4113106", "lapa": "4113205", "laranjal": "4113254", "laranjeiras do sul": "4113304", "leopolis": "4113403", "lidianopolis": "4113429", "lindoeste": "4113452", "loanda": "4113502", "lobato": "4113601", "londrina": "4113700", "luiziana": "4113734", "lunardelli": "4113759", "lupionopolis": "4113809", "mallet": "4113908", "mambore": "4114005", "mandaguacu": "4114104", "mandaguari": "4114203", "mandirituba": "4114302", "manfrinopolis": "4114351", "mangueirinha": "4114401", "manoel ribas": "4114500", "marechal candido rondon": "4114609", "maria helena": "4114708", "marialva": "4114807", "marilandia do sul": "4114906", "marilena": "4115002", "mariluz": "4115101", "maringa": "4115200", "mariopolis": "4115309", "maripa": "4115358", "marmeleiro": "4115408", "marquinho": "4115457", "marumbi": "4115507", "matelandia": "4115606", "matinhos": "4115705", "mato rico": "4115739", "maua da serra": "4115754", "medianeira": "4115804", "mercedes": "4115853", "mirador": "4115903", "miraselva": "4116000", "missal": "4116059", "moreira sales": "4116109", "morretes": "4116208", "munhoz de melo": "4116307", "nossa senhora das gracas": "4116406", "nova alianca do ivai": "4116505", "nova america da colina": "4116604", "nova aurora": "4116703", "nova cantu": "4116802", "nova esperanca": "4116901", "nova esperanca do sudoeste": "4116950", "nova fatima": "4117008", "nova laranjeiras": "4117057", "nova londrina": "4117107", "nova olimpia": "4117206", "nova prata do iguacu": "4117255", "nova santa barbara": "4117214", "nova santa rosa": "4117222", "nova tebas": "4117271", "novo itacolomi": "4117297", "ortigueira": "4117305", "ourizona": "4117404", "ouro verde do oeste": "4117453", "paicandu": "4117503", "palmas": "4117602", "palmeira": "4117701", "palmital": "4117800", "palotina": "4117909", "paraiso do norte": "4118006", "paranacity": "4118105", "paranagua": "4118204", "paranapoema": "4118303", "paranavai": "4118402", "pato bragado": "4118451", "pato branco": "4118501", "paula freitas": "4118600", "paulo frontin": "4118709", "peabiru": "4118808", "perobal": "4118857", "perola": "4118907", "perola d'oeste": "4119004", "pien": "4119103", "pinhais": "4119152", "pinhal de sao bento": "4119251", "pinhalao": "4119202", "pinhao": "4119301", "pirai do sul": "4119400", "piraquara": "4119509", "pitanga": "4119608", "pitangueiras": "4119657", "planaltina do parana": "4119707", "planalto": "4119806", "ponta grossa": "4119905", "pontal do parana": "4119954", "porecatu": "4120002", "porto amazonas": "4120101", "porto barreiro": "4120150", "porto rico": "4120200", "porto vitoria": "4120309", "prado ferreira": "4120333", "pranchita": "4120358", "presidente castelo branco": "4120408", "primeiro de maio": "4120507", "prudentopolis": "4120606", "quarto centenario": "4120655", "quatigua": "4120705", "quatro barras": "4120804", "quatro pontes": "4120853", "quedas do iguacu": "4120903", "querencia do norte": "4121000", "quinta do sol": "4121109", "quitandinha": "4121208", "ramilandia": "4121257", "rancho alegre": "4121307", "rancho alegre d'oeste": "4121356", "realeza": "4121406", "reboucas": "4121505", "renascenca": "4121604", "reserva": "4121703", "reserva do iguacu": "4121752", "ribeirao claro": "4121802", "ribeirao do pinhal": "4121901", "rio azul": "4122008", "rio bom": "4122107", "rio bonito do iguacu": "4122156", "rio branco do ivai": "4122172", "rio branco do sul": "4122206", "rio negro": "4122305", "rolandia": "4122404", "roncador": "4122503", "rondon": "4122602", "rosario do ivai": "4122651", "sabaudia": "4122701", "salgado filho": "4122800", "salto do itarare": "4122909", "salto do lontra": "4123006", "santa amelia": "4123105", "santa cecilia do pavao": "4123204", "santa cruz de monte castelo": "4123303", "santa fe": "4123402", "santa helena": "4123501", "santa ines": "4123600", "santa isabel do ivai": "4123709", "santa izabel do oeste": "4123808", "santa lucia": "4123824", "santa maria do oeste": "4123857", "santa mariana": "4123907", "santa monica": "4123956", "santa tereza do oeste": "4124020", "santa terezinha de itaipu": "4124053", "santana do itarare": "4124004", "santo antonio da platina": "4124103", "santo antonio do caiua": "4124202", "santo antonio do paraiso": "4124301", "santo antonio do sudoeste": "4124400", "santo inacio": "4124509", "sao carlos do ivai": "4124608", "sao jeronimo da serra": "4124707", "sao joao": "4124806", "sao joao do caiua": "4124905", "sao joao do ivai": "4125001", "sao joao do triunfo": "4125100", "sao jorge do ivai": "4125308", "sao jorge do patrocinio": "4125357", "sao jorge d'oeste": "4125209", "sao jose da boa vista": "4125407", "sao jose das palmeiras": "4125456", "sao jose dos pinhais": "4125506", "sao manoel do parana": "4125555", "sao mateus do sul": "4125605", "sao miguel do iguacu": "4125704", "sao pedro do iguacu": "4125753", "sao pedro do ivai": "4125803", "sao pedro do parana": "4125902", "sao sebastiao da amoreira": "4126009", "sao tome": "4126108", "sapopema": "4126207", "sarandi": "4126256", "saudade do iguacu": "4126272", "senges": "4126306", "serranopolis do iguacu": "4126355", "sertaneja": "4126405", "sertanopolis": "4126504", "siqueira campos": "4126603", "sulina": "4126652", "tamarana": "4126678", "tamboara": "4126702", "tapejara": "4126801", "tapira": "4126900", "teixeira soares": "4127007", "telemaco borba": "4127106", "terra boa": "4127205", "terra rica": "4127304", "terra roxa": "4127403", "tibagi": "4127502", "tijucas do sul": "4127601", "toledo": "4127700", "tomazina": "4127809", "tres barras do parana": "4127858", "tunas do parana": "4127882", "tuneiras do oeste": "4127908", "tupassi": "4127957", "turvo": "4127965", "ubirata": "4128005", "umuarama": "4128104", "uniao da vitoria": "4128203", "uniflor": "4128302", "urai": "4128401", "ventania": "4128534", "vera cruz do oeste": "4128559", "vere": "4128609", "virmond": "4128658", "vitorino": "4128708", "wenceslau braz": "4128500", "xambre": "4128807" }, "SC": { "abdon batista": "4200051", "abelardo luz": "4200101", "agrolandia": "4200200", "agronomica": "4200309", "agua doce": "4200408", "aguas de chapeco": "4200507", "aguas frias": "4200556", "aguas mornas": "4200606", "alfredo wagner": "4200705", "alto bela vista": "4200754", "anchieta": "4200804", "angelina": "4200903", "anita garibaldi": "4201000", "anitapolis": "4201109", "antonio carlos": "4201208", "apiuna": "4201257", "arabuta": "4201273", "araquari": "4201307", "ararangua": "4201406", "armazem": "4201505", "arroio trinta": "4201604", "arvoredo": "4201653", "ascurra": "4201703", "atalanta": "4201802", "aurora": "4201901", "balneario arroio do silva": "4201950", "balneario barra do sul": "4202057", "balneario camboriu": "4202008", "balneario gaivota": "4202073", "balneario picarras": "4212809", "balneario rincao": "4220000", "bandeirante": "4202081", "barra bonita": "4202099", "barra velha": "4202107", "bela vista do toldo": "4202131", "belmonte": "4202156", "benedito novo": "4202206", "biguacu": "4202305", "blumenau": "4202404", "bocaina do sul": "4202438", "bom jardim da serra": "4202503", "bom jesus": "4202537", "bom jesus do oeste": "4202578", "bom retiro": "4202602", "bombinhas": "4202453", "botuvera": "4202701", "braco do norte": "4202800", "braco do trombudo": "4202859", "brunopolis": "4202875", "brusque": "4202909", "cacador": "4203006", "caibi": "4203105", "calmon": "4203154", "camboriu": "4203204", "campo alegre": "4203303", "campo belo do sul": "4203402", "campo ere": "4203501", "campos novos": "4203600", "canelinha": "4203709", "canoinhas": "4203808", "capao alto": "4203253", "capinzal": "4203907", "capivari de baixo": "4203956", "catanduvas": "4204004", "caxambu do sul": "4204103", "celso ramos": "4204152", "cerro negro": "4204178", "chapadao do lageado": "4204194", "chapeco": "4204202", "cocal do sul": "4204251", "concordia": "4204301", "cordilheira alta": "4204350", "coronel freitas": "4204400", "coronel martins": "4204459", "correia pinto": "4204558", "corupa": "4204509", "criciuma": "4204608", "cunha pora": "4204707", "cunhatai": "4204756", "curitibanos": "4204806", "descanso": "4204905", "dionisio cerqueira": "4205001", "dona emma": "4205100", "doutor pedrinho": "4205159", "entre rios": "4205175", "ermo": "4205191", "erval velho": "4205209", "faxinal dos guedes": "4205308", "flor do sertao": "4205357", "florianopolis": "4205407", "formosa do sul": "4205431", "forquilhinha": "4205456", "fraiburgo": "4205506", "frei rogerio": "4205555", "galvao": "4205605", "garopaba": "4205704", "garuva": "4205803", "gaspar": "4205902", "governador celso ramos": "4206009", "grao-para": "4206108", "gravatal": "4206207", "guabiruba": "4206306", "guaraciaba": "4206405", "guaramirim": "4206504", "guaruja do sul": "4206603", "guatambu": "4206652", "herval d'oeste": "4206702", "ibiam": "4206751", "ibicare": "4206801", "ibirama": "4206900", "icara": "4207007", "ilhota": "4207106", "imarui": "4207205", "imbituba": "4207304", "imbuia": "4207403", "indaial": "4207502", "iomere": "4207577", "ipira": "4207601", "ipora do oeste": "4207650", "ipuacu": "4207684", "ipumirim": "4207700", "iraceminha": "4207759", "irani": "4207809", "irati": "4207858", "irineopolis": "4207908", "ita": "4208005", "itaiopolis": "4208104", "itajai": "4208203", "itapema": "4208302", "itapiranga": "4208401", "itapoa": "4208450", "ituporanga": "4208500", "jabora": "4208609", "jacinto machado": "4208708", "jaguaruna": "4208807", "jaragua do sul": "4208906", "jardinopolis": "4208955", "joacaba": "4209003", "joinville": "4209102", "jose boiteux": "4209151", "jupia": "4209177", "lacerdopolis": "4209201", "lages": "4209300", "laguna": "4209409", "lajeado grande": "4209458", "laurentino": "4209508", "lauro muller": "4209607", "lebon regis": "4209706", "leoberto leal": "4209805", "lindoia do sul": "4209854", "lontras": "4209904", "luiz alves": "4210001", "luzerna": "4210035", "macieira": "4210050", "mafra": "4210100", "major gercino": "4210209", "major vieira": "4210308", "maracaja": "4210407", "maravilha": "4210506", "marema": "4210555", "massaranduba": "4210605", "matos costa": "4210704", "meleiro": "4210803", "mirim doce": "4210852", "modelo": "4210902", "mondai": "4211009", "monte carlo": "4211058", "monte castelo": "4211108", "morro da fumaca": "4211207", "morro grande": "4211256", "navegantes": "4211306", "nova erechim": "4211405", "nova itaberaba": "4211454", "nova trento": "4211504", "nova veneza": "4211603", "novo horizonte": "4211652", "orleans": "4211702", "otacilio costa": "4211751", "ouro": "4211801", "ouro verde": "4211850", "paial": "4211876", "painel": "4211892", "palhoca": "4211900", "palma sola": "4212007", "palmeira": "4212056", "palmitos": "4212106", "papanduva": "4212205", "paraiso": "4212239", "passo de torres": "4212254", "passos maia": "4212270", "paulo lopes": "4212304", "pedras grandes": "4212403", "penha": "4212502", "peritiba": "4212601", "pescaria brava": "4212650", "petrolandia": "4212700", "pinhalzinho": "4212908", "pinheiro preto": "4213005", "piratuba": "4213104", "planalto alegre": "4213153", "pomerode": "4213203", "ponte alta": "4213302", "ponte alta do norte": "4213351", "ponte serrada": "4213401", "porto belo": "4213500", "porto uniao": "4213609", "pouso redondo": "4213708", "praia grande": "4213807", "presidente castello branco": "4213906", "presidente getulio": "4214003", "presidente nereu": "4214102", "princesa": "4214151", "quilombo": "4214201", "rancho queimado": "4214300", "rio das antas": "4214409", "rio do campo": "4214508", "rio do oeste": "4214607", "rio do sul": "4214805", "rio dos cedros": "4214706", "rio fortuna": "4214904", "rio negrinho": "4215000", "rio rufino": "4215059", "riqueza": "4215075", "rodeio": "4215109", "romelandia": "4215208", "salete": "4215307", "saltinho": "4215356", "salto veloso": "4215406", "sangao": "4215455", "santa cecilia": "4215505", "santa helena": "4215554", "santa rosa de lima": "4215604", "santa rosa do sul": "4215653", "santa terezinha": "4215679", "santa terezinha do progresso": "4215687", "santiago do sul": "4215695", "santo amaro da imperatriz": "4215703", "sao bento do sul": "4215802", "sao bernardino": "4215752", "sao bonifacio": "4215901", "sao carlos": "4216008", "sao cristovao do sul": "4216057", "sao domingos": "4216107", "sao francisco do sul": "4216206", "sao joao batista": "4216305", "sao joao do itaperiu": "4216354", "sao joao do oeste": "4216255", "sao joao do sul": "4216404", "sao joaquim": "4216503", "sao jose": "4216602", "sao jose do cedro": "4216701", "sao jose do cerrito": "4216800", "sao lourenco do oeste": "4216909", "sao ludgero": "4217006", "sao martinho": "4217105", "sao miguel da boa vista": "4217154", "sao miguel do oeste": "4217204", "sao pedro de alcantara": "4217253", "saudades": "4217303", "schroeder": "4217402", "seara": "4217501", "serra alta": "4217550", "sideropolis": "4217600", "sombrio": "4217709", "sul brasil": "4217758", "taio": "4217808", "tangara": "4217907", "tigrinhos": "4217956", "tijucas": "4218004", "timbe do sul": "4218103", "timbo": "4218202", "timbo grande": "4218251", "tres barras": "4218301", "treviso": "4218350", "treze de maio": "4218400", "treze tilias": "4218509", "trombudo central": "4218608", "tubarao": "4218707", "tunapolis": "4218756", "turvo": "4218806", "uniao do oeste": "4218855", "urubici": "4218905", "urupema": "4218954", "urussanga": "4219002", "vargeao": "4219101", "vargem": "4219150", "vargem bonita": "4219176", "vidal ramos": "4219200", "videira": "4219309", "vitor meireles": "4219358", "witmarsum": "4219408", "xanxere": "4219507", "xavantina": "4219606", "xaxim": "4219705", "zortea": "4219853" }, "RS": { "acegua": "4300034", "agua santa": "4300059", "agudo": "4300109", "ajuricaba": "4300208", "alecrim": "4300307", "alegrete": "4300406", "alegria": "4300455", "almirante tamandare do sul": "4300471", "alpestre": "4300505", "alto alegre": "4300554", "alto feliz": "4300570", "alvorada": "4300604", "amaral ferrador": "4300638", "ametista do sul": "4300646", "andre da rocha": "4300661", "anta gorda": "4300703", "antonio prado": "4300802", "arambare": "4300851", "ararica": "4300877", "aratiba": "4300901", "arroio do meio": "4301008", "arroio do padre": "4301073", "arroio do sal": "4301057", "arroio do tigre": "4301206", "arroio dos ratos": "4301107", "arroio grande": "4301305", "arvorezinha": "4301404", "augusto pestana": "4301503", "aurea": "4301552", "bage": "4301602", "balneario pinhal": "4301636", "barao": "4301651", "barao de cotegipe": "4301701", "barao do triunfo": "4301750", "barra do guarita": "4301859", "barra do quarai": "4301875", "barra do ribeiro": "4301909", "barra do rio azul": "4301925", "barra funda": "4301958", "barracao": "4301800", "barros cassal": "4302006", "benjamin constant do sul": "4302055", "bento goncalves": "4302105", "boa vista das missoes": "4302154", "boa vista do burica": "4302204", "boa vista do cadeado": "4302220", "boa vista do incra": "4302238", "boa vista do sul": "4302253", "bom jesus": "4302303", "bom principio": "4302352", "bom progresso": "4302378", "bom retiro do sul": "4302402", "boqueirao do leao": "4302451", "bossoroca": "4302501", "bozano": "4302584", "braga": "4302600", "brochier": "4302659", "butia": "4302709", "cacapava do sul": "4302808", "cacequi": "4302907", "cachoeira do sul": "4303004", "cachoeirinha": "4303103", "cacique doble": "4303202", "caibate": "4303301", "caicara": "4303400", "camaqua": "4303509", "camargo": "4303558", "cambara do sul": "4303608", "campestre da serra": "4303673", "campina das missoes": "4303707", "campinas do sul": "4303806", "campo bom": "4303905", "campo novo": "4304002", "campos borges": "4304101", "candelaria": "4304200", "candido godoi": "4304309", "candiota": "4304358", "canela": "4304408", "cangucu": "4304507", "canoas": "4304606", "canudos do vale": "4304614", "capao bonito do sul": "4304622", "capao da canoa": "4304630", "capao do cipo": "4304655", "capao do leao": "4304663", "capela de santana": "4304689", "capitao": "4304697", "capivari do sul": "4304671", "caraa": "4304713", "carazinho": "4304705", "carlos barbosa": "4304804", "carlos gomes": "4304853", "casca": "4304903", "caseiros": "4304952", "catuipe": "4305009", "caxias do sul": "4305108", "centenario": "4305116", "cerrito": "4305124", "cerro branco": "4305132", "cerro grande": "4305157", "cerro grande do sul": "4305173", "cerro largo": "4305207", "chapada": "4305306", "charqueadas": "4305355", "charrua": "4305371", "chiapetta": "4305405", "chui": "4305439", "chuvisca": "4305447", "cidreira": "4305454", "ciriaco": "4305504", "colinas": "4305587", "colorado": "4305603", "condor": "4305702", "constantina": "4305801", "coqueiro baixo": "4305835", "coqueiros do sul": "4305850", "coronel barros": "4305871", "coronel bicaco": "4305900", "coronel pilar": "4305934", "cotipora": "4305959", "coxilha": "4305975", "crissiumal": "4306007", "cristal": "4306056", "cristal do sul": "4306072", "cruz alta": "4306106", "cruzaltense": "4306130", "cruzeiro do sul": "4306205", "david canabarro": "4306304", "derrubadas": "4306320", "dezesseis de novembro": "4306353", "dilermando de aguiar": "4306379", "dois irmaos": "4306403", "dois irmaos das missoes": "4306429", "dois lajeados": "4306452", "dom feliciano": "4306502", "dom pedrito": "4306601", "dom pedro de alcantara": "4306551", "dona francisca": "4306700", "doutor mauricio cardoso": "4306734", "doutor ricardo": "4306759", "eldorado do sul": "4306767", "encantado": "4306809", "encruzilhada do sul": "4306908", "engenho velho": "4306924", "entre rios do sul": "4306957", "entre-ijuis": "4306932", "erebango": "4306973", "erechim": "4307005", "ernestina": "4307054", "erval grande": "4307203", "erval seco": "4307302", "esmeralda": "4307401", "esperanca do sul": "4307450", "espumoso": "4307500", "estacao": "4307559", "estancia velha": "4307609", "esteio": "4307708", "estrela": "4307807", "estrela velha": "4307815", "eugenio de castro": "4307831", "fagundes varela": "4307864", "farroupilha": "4307906", "faxinal do soturno": "4308003", "faxinalzinho": "4308052", "fazenda vilanova": "4308078", "feliz": "4308102", "flores da cunha": "4308201", "floriano peixoto": "4308250", "fontoura xavier": "4308300", "formigueiro": "4308409", "forquetinha": "4308433", "fortaleza dos valos": "4308458", "frederico westphalen": "4308508", "garibaldi": "4308607", "garruchos": "4308656", "gaurama": "4308706", "general camara": "4308805", "gentil": "4308854", "getulio vargas": "4308904", "girua": "4309001", "glorinha": "4309050", "gramado": "4309100", "gramado dos loureiros": "4309126", "gramado xavier": "4309159", "gravatai": "4309209", "guabiju": "4309258", "guaiba": "4309308", "guapore": "4309407", "guarani das missoes": "4309506", "harmonia": "4309555", "herval": "4307104", "herveiras": "4309571", "horizontina": "4309605", "hulha negra": "4309654", "humaita": "4309704", "ibarama": "4309753", "ibiaca": "4309803", "ibiraiaras": "4309902", "ibirapuita": "4309951", "ibiruba": "4310009", "igrejinha": "4310108", "ijui": "4310207", "ilopolis": "4310306", "imbe": "4310330", "imigrante": "4310363", "independencia": "4310405", "inhacora": "4310413", "ipe": "4310439", "ipiranga do sul": "4310462", "irai": "4310504", "itaara": "4310538", "itacurubi": "4310553", "itapuca": "4310579", "itaqui": "4310603", "itati": "4310652", "itatiba do sul": "4310702", "ivora": "4310751", "ivoti": "4310801", "jaboticaba": "4310850", "jacuizinho": "4310876", "jacutinga": "4310900", "jaguarao": "4311007", "jaguari": "4311106", "jaquirana": "4311122", "jari": "4311130", "joia": "4311155", "julio de castilhos": "4311205", "lagoa bonita do sul": "4311239", "lagoa dos tres cantos": "4311270", "lagoa vermelha": "4311304", "lagoao": "4311254", "lajeado": "4311403", "lajeado do bugre": "4311429", "lavras do sul": "4311502", "liberato salzano": "4311601", "lindolfo collor": "4311627", "linha nova": "4311643", "macambara": "4311718", "machadinho": "4311700", "mampituba": "4311734", "manoel viana": "4311759", "maquine": "4311775", "marata": "4311791", "marau": "4311809", "marcelino ramos": "4311908", "mariana pimentel": "4311981", "mariano moro": "4312005", "marques de souza": "4312054", "mata": "4312104", "mato castelhano": "4312138", "mato leitao": "4312153", "mato queimado": "4312179", "maximiliano de almeida": "4312203", "minas do leao": "4312252", "miraguai": "4312302", "montauri": "4312351", "monte alegre dos campos": "4312377", "monte belo do sul": "4312385", "montenegro": "4312401", "mormaco": "4312427", "morrinhos do sul": "4312443", "morro redondo": "4312450", "morro reuter": "4312476", "mostardas": "4312500", "mucum": "4312609", "muitos capoes": "4312617", "muliterno": "4312625", "nao-me-toque": "4312658", "nicolau vergueiro": "4312674", "nonoai": "4312708", "nova alvorada": "4312757", "nova araca": "4312807", "nova bassano": "4312906", "nova boa vista": "4312955", "nova brescia": "4313003", "nova candelaria": "4313011", "nova esperanca do sul": "4313037", "nova hartz": "4313060", "nova padua": "4313086", "nova palma": "4313102", "nova petropolis": "4313201", "nova prata": "4313300", "nova ramada": "4313334", "nova roma do sul": "4313359", "nova santa rita": "4313375", "novo barreiro": "4313490", "novo cabrais": "4313391", "novo hamburgo": "4313409", "novo machado": "4313425", "novo tiradentes": "4313441", "novo xingu": "4313466", "osorio": "4313508", "paim filho": "4313607", "palmares do sul": "4313656", "palmeira das missoes": "4313706", "palmitinho": "4313805", "panambi": "4313904", "pantano grande": "4313953", "parai": "4314001", "paraiso do sul": "4314027", "pareci novo": "4314035", "parobe": "4314050", "passa sete": "4314068", "passo do sobrado": "4314076", "passo fundo": "4314100", "paulo bento": "4314134", "paverama": "4314159", "pedras altas": "4314175", "pedro osorio": "4314209", "pejucara": "4314308", "pelotas": "4314407", "picada cafe": "4314423", "pinhal": "4314456", "pinhal da serra": "4314464", "pinhal grande": "4314472", "pinheirinho do vale": "4314498", "pinheiro machado": "4314506", "pinto bandeira": "4314548", "pirapo": "4314555", "piratini": "4314605", "planalto": "4314704", "poco das antas": "4314753", "pontao": "4314779", "ponte preta": "4314787", "portao": "4314803", "porto alegre": "4314902", "porto lucena": "4315008", "porto maua": "4315057", "porto vera cruz": "4315073", "porto xavier": "4315107", "pouso novo": "4315131", "presidente lucena": "4315149", "progresso": "4315156", "protasio alves": "4315172", "putinga": "4315206", "quarai": "4315305", "quatro irmaos": "4315313", "quevedos": "4315321", "quinze de novembro": "4315354", "redentora": "4315404", "relvado": "4315453", "restinga seca": "4315503", "rio dos indios": "4315552", "rio grande": "4315602", "rio pardo": "4315701", "riozinho": "4315750", "roca sales": "4315800", "rodeio bonito": "4315909", "rolador": "4315958", "rolante": "4316006", "ronda alta": "4316105", "rondinha": "4316204", "roque gonzales": "4316303", "rosario do sul": "4316402", "sagrada familia": "4316428", "saldanha marinho": "4316436", "salto do jacui": "4316451", "salvador das missoes": "4316477", "salvador do sul": "4316501", "sananduva": "4316600", "santa barbara do sul": "4316709", "santa cecilia do sul": "4316733", "santa clara do sul": "4316758", "santa cruz do sul": "4316808", "santa margarida do sul": "4316972", "santa maria": "4316907", "santa maria do herval": "4316956", "santa rosa": "4317202", "santa tereza": "4317251", "santa vitoria do palmar": "4317301", "santana da boa vista": "4317004", "sant'ana do livramento": "4317103", "santiago": "4317400", "santo angelo": "4317509", "santo antonio da patrulha": "4317608", "santo antonio das missoes": "4317707", "santo antonio do palma": "4317558", "santo antonio do planalto": "4317756", "santo augusto": "4317806", "santo cristo": "4317905", "santo expedito do sul": "4317954", "sao borja": "4318002", "sao domingos do sul": "4318051", "sao francisco de assis": "4318101", "sao francisco de paula": "4318200", "sao gabriel": "4318309", "sao jeronimo": "4318408", "sao joao da urtiga": "4318424", "sao joao do polesine": "4318432", "sao jorge": "4318440", "sao jose das missoes": "4318457", "sao jose do herval": "4318465", "sao jose do hortencio": "4318481", "sao jose do inhacora": "4318499", "sao jose do norte": "4318507", "sao jose do ouro": "4318606", "sao jose do sul": "4318614", "sao jose dos ausentes": "4318622", "sao leopoldo": "4318705", "sao lourenco do sul": "4318804", "sao luiz gonzaga": "4318903", "sao marcos": "4319000", "sao martinho": "4319109", "sao martinho da serra": "4319125", "sao miguel das missoes": "4319158", "sao nicolau": "4319208", "sao paulo das missoes": "4319307", "sao pedro da serra": "4319356", "sao pedro das missoes": "4319364", "sao pedro do butia": "4319372", "sao pedro do sul": "4319406", "sao sebastiao do cai": "4319505", "sao sepe": "4319604", "sao valentim": "4319703", "sao valentim do sul": "4319711", "sao valerio do sul": "4319737", "sao vendelino": "4319752", "sao vicente do sul": "4319802", "sapiranga": "4319901", "sapucaia do sul": "4320008", "sarandi": "4320107", "seberi": "4320206", "sede nova": "4320230", "segredo": "4320263", "selbach": "4320305", "senador salgado filho": "4320321", "sentinela do sul": "4320354", "serafina correa": "4320404", "serio": "4320453", "sertao": "4320503", "sertao santana": "4320552", "sete de setembro": "4320578", "severiano de almeida": "4320602", "silveira martins": "4320651", "sinimbu": "4320677", "sobradinho": "4320701", "soledade": "4320800", "tabai": "4320859", "tapejara": "4320909", "tapera": "4321006", "tapes": "4321105", "taquara": "4321204", "taquari": "4321303", "taquarucu do sul": "4321329", "tavares": "4321352", "tenente portela": "4321402", "terra de areia": "4321436", "teutonia": "4321451", "tio hugo": "4321469", "tiradentes do sul": "4321477", "toropi": "4321493", "torres": "4321501", "tramandai": "4321600", "travesseiro": "4321626", "tres arroios": "4321634", "tres cachoeiras": "4321667", "tres coroas": "4321709", "tres de maio": "4321808", "tres forquilhas": "4321832", "tres palmeiras": "4321857", "tres passos": "4321907", "trindade do sul": "4321956", "triunfo": "4322004", "tucunduva": "4322103", "tunas": "4322152", "tupanci do sul": "4322186", "tupancireta": "4322202", "tupandi": "4322251", "tuparendi": "4322301", "turucu": "4322327", "ubiretama": "4322343", "uniao da serra": "4322350", "unistalda": "4322376", "uruguaiana": "4322400", "vacaria": "4322509", "vale do sol": "4322533", "vale real": "4322541", "vale verde": "4322525", "vanini": "4322558", "venancio aires": "4322608", "vera cruz": "4322707", "veranopolis": "4322806", "vespasiano correa": "4322855", "viadutos": "4322905", "viamao": "4323002", "vicente dutra": "4323101", "victor graeff": "4323200", "vila flores": "4323309", "vila langaro": "4323358", "vila maria": "4323408", "vila nova do sul": "4323457", "vista alegre": "4323507", "vista alegre do prata": "4323606", "vista gaucha": "4323705", "vitoria das missoes": "4323754", "westfalia": "4323770", "xangri-la": "4323804" }, "MS": { "agua clara": "5000203", "alcinopolis": "5000252", "amambai": "5000609", "anastacio": "5000708", "anaurilandia": "5000807", "angelica": "5000856", "antonio joao": "5000906", "aparecida do taboado": "5001003", "aquidauana": "5001102", "aral moreira": "5001243", "bandeirantes": "5001508", "bataguassu": "5001904", "bataypora": "5002001", "bela vista": "5002100", "bodoquena": "5002159", "bonito": "5002209", "brasilandia": "5002308", "caarapo": "5002407", "camapua": "5002605", "campo grande": "5002704", "caracol": "5002803", "cassilandia": "5002902", "chapadao do sul": "5002951", "corguinho": "5003108", "coronel sapucaia": "5003157", "corumba": "5003207", "costa rica": "5003256", "coxim": "5003306", "deodapolis": "5003454", "dois irmaos do buriti": "5003488", "douradina": "5003504", "dourados": "5003702", "eldorado": "5003751", "fatima do sul": "5003801", "figueirao": "5003900", "gloria de dourados": "5004007", "guia lopes da laguna": "5004106", "iguatemi": "5004304", "inocencia": "5004403", "itapora": "5004502", "itaquirai": "5004601", "ivinhema": "5004700", "japora": "5004809", "jaraguari": "5004908", "jardim": "5005004", "jatei": "5005103", "juti": "5005152", "ladario": "5005202", "laguna carapa": "5005251", "maracaju": "5005400", "miranda": "5005608", "mundo novo": "5005681", "navirai": "5005707", "nioaque": "5005806", "nova alvorada do sul": "5006002", "nova andradina": "5006200", "novo horizonte do sul": "5006259", "paraiso das aguas": "5006275", "paranaiba": "5006309", "paranhos": "5006358", "pedro gomes": "5006408", "ponta pora": "5006606", "porto murtinho": "5006903", "ribas do rio pardo": "5007109", "rio brilhante": "5007208", "rio negro": "5007307", "rio verde de mato grosso": "5007406", "rochedo": "5007505", "santa rita do pardo": "5007554", "sao gabriel do oeste": "5007695", "selviria": "5007802", "sete quedas": "5007703", "sidrolandia": "5007901", "sonora": "5007935", "tacuru": "5007950", "taquarussu": "5007976", "terenos": "5008008", "tres lagoas": "5008305", "vicentina": "5008404" }, "MT": { "acorizal": "5100102", "agua boa": "5100201", "alta floresta": "5100250", "alto araguaia": "5100300", "alto boa vista": "5100359", "alto garcas": "5100409", "alto paraguai": "5100508", "alto taquari": "5100607", "apiacas": "5100805", "araguaiana": "5101001", "araguainha": "5101209", "araputanga": "5101258", "arenapolis": "5101308", "aripuana": "5101407", "barao de melgaco": "5101605", "barra do bugres": "5101704", "barra do garcas": "5101803", "bom jesus do araguaia": "5101852", "brasnorte": "5101902", "caceres": "5102504", "campinapolis": "5102603", "campo novo do parecis": "5102637", "campo verde": "5102678", "campos de julio": "5102686", "canabrava do norte": "5102694", "canarana": "5102702", "carlinda": "5102793", "castanheira": "5102850", "chapada dos guimaraes": "5103007", "claudia": "5103056", "cocalinho": "5103106", "colider": "5103205", "colniza": "5103254", "comodoro": "5103304", "confresa": "5103353", "conquista d'oeste": "5103361", "cotriguacu": "5103379", "cuiaba": "5103403", "curvelandia": "5103437", "denise": "5103452", "diamantino": "5103502", "dom aquino": "5103601", "feliz natal": "5103700", "figueiropolis d'oeste": "5103809", "gaucha do norte": "5103858", "general carneiro": "5103908", "gloria d'oeste": "5103957", "guaranta do norte": "5104104", "guiratinga": "5104203", "indiavai": "5104500", "ipiranga do norte": "5104526", "itanhanga": "5104542", "itauba": "5104559", "itiquira": "5104609", "jaciara": "5104807", "jangada": "5104906", "jauru": "5105002", "juara": "5105101", "juina": "5105150", "juruena": "5105176", "juscimeira": "5105200", "lambari d'oeste": "5105234", "lucas do rio verde": "5105259", "luciara": "5105309", "marcelandia": "5105580", "matupa": "5105606", "mirassol d'oeste": "5105622", "nobres": "5105903", "nortelandia": "5106000", "nossa senhora do livramento": "5106109", "nova bandeirantes": "5106158", "nova brasilandia": "5106208", "nova canaa do norte": "5106216", "nova guarita": "5108808", "nova lacerda": "5106182", "nova marilandia": "5108857", "nova maringa": "5108907", "nova monte verde": "5108956", "nova mutum": "5106224", "nova nazare": "5106174", "nova olimpia": "5106232", "nova santa helena": "5106190", "nova ubirata": "5106240", "nova xavantina": "5106257", "novo horizonte do norte": "5106273", "novo mundo": "5106265", "novo santo antonio": "5106315", "novo sao joaquim": "5106281", "paranaita": "5106299", "paranatinga": "5106307", "pedra preta": "5106372", "peixoto de azevedo": "5106422", "planalto da serra": "5106455", "pocone": "5106505", "pontal do araguaia": "5106653", "ponte branca": "5106703", "pontes e lacerda": "5106752", "porto alegre do norte": "5106778", "porto dos gauchos": "5106802", "porto esperidiao": "5106828", "porto estrela": "5106851", "poxoreu": "5107008", "primavera do leste": "5107040", "querencia": "5107065", "reserva do cabacal": "5107156", "ribeirao cascalheira": "5107180", "ribeiraozinho": "5107198", "rio branco": "5107206", "rondolandia": "5107578", "rondonopolis": "5107602", "rosario oeste": "5107701", "salto do ceu": "5107750", "santa carmem": "5107248", "santa cruz do xingu": "5107743", "santa rita do trivelato": "5107768", "santa terezinha": "5107776", "santo afonso": "5107263", "santo antonio de leverger": "5107800", "santo antonio do leste": "5107792", "sao felix do araguaia": "5107859", "sao jose do povo": "5107297", "sao jose do rio claro": "5107305", "sao jose do xingu": "5107354", "sao jose dos quatro marcos": "5107107", "sao pedro da cipa": "5107404", "sapezal": "5107875", "serra nova dourada": "5107883", "sinop": "5107909", "sorriso": "5107925", "tabapora": "5107941", "tangara da serra": "5107958", "tapurah": "5108006", "terra nova do norte": "5108055", "tesouro": "5108105", "torixoreu": "5108204", "uniao do sul": "5108303", "vale de sao domingos": "5108352", "varzea grande": "5108402", "vera": "5108501", "vila bela da santissima trindade": "5105507", "vila rica": "5108600" }, "GO": { "abadia de goias": "5200050", "abadiania": "5200100", "acreuna": "5200134", "adelandia": "5200159", "agua fria de goias": "5200175", "agua limpa": "5200209", "aguas lindas de goias": "5200258", "alexania": "5200308", "aloandia": "5200506", "alto horizonte": "5200555", "alto paraiso de goias": "5200605", "alvorada do norte": "5200803", "amaralina": "5200829", "americano do brasil": "5200852", "amorinopolis": "5200902", "anapolis": "5201108", "anhanguera": "5201207", "anicuns": "5201306", "aparecida de goiania": "5201405", "aparecida do rio doce": "5201454", "apore": "5201504", "aracu": "5201603", "aragarcas": "5201702", "aragoiania": "5201801", "araguapaz": "5202155", "arenopolis": "5202353", "aruana": "5202502", "aurilandia": "5202601", "avelinopolis": "5202809", "baliza": "5203104", "barro alto": "5203203", "bela vista de goias": "5203302", "bom jardim de goias": "5203401", "bom jesus de goias": "5203500", "bonfinopolis": "5203559", "bonopolis": "5203575", "brazabrantes": "5203609", "britania": "5203807", "buriti alegre": "5203906", "buriti de goias": "5203939", "buritinopolis": "5203962", "cabeceiras": "5204003", "cachoeira alta": "5204102", "cachoeira de goias": "5204201", "cachoeira dourada": "5204250", "cacu": "5204300", "caiaponia": "5204409", "caldas novas": "5204508", "caldazinha": "5204557", "campestre de goias": "5204607", "campinacu": "5204656", "campinorte": "5204706", "campo alegre de goias": "5204805", "campo limpo de goias": "5204854", "campos belos": "5204904", "campos verdes": "5204953", "carmo do rio verde": "5205000", "castelandia": "5205059", "catalao": "5205109", "caturai": "5205208", "cavalcante": "5205307", "ceres": "5205406", "cezarina": "5205455", "chapadao do ceu": "5205471", "cidade ocidental": "5205497", "cocalzinho de goias": "5205513", "colinas do sul": "5205521", "corrego do ouro": "5205703", "corumba de goias": "5205802", "corumbaiba": "5205901", "cristalina": "5206206", "cristianopolis": "5206305", "crixas": "5206404", "crominia": "5206503", "cumari": "5206602", "damianopolis": "5206701", "damolandia": "5206800", "davinopolis": "5206909", "diorama": "5207105", "divinopolis de goias": "5208301", "doverlandia": "5207253", "edealina": "5207352", "edeia": "5207402", "estrela do norte": "5207501", "faina": "5207535", "fazenda nova": "5207600", "firminopolis": "5207808", "flores de goias": "5207907", "formosa": "5208004", "formoso": "5208103", "gameleira de goias": "5208152", "goianapolis": "5208400", "goiandira": "5208509", "goianesia": "5208608", "goiania": "5208707", "goianira": "5208806", "goias": "5208905", "goiatuba": "5209101", "gouvelandia": "5209150", "guapo": "5209200", "guaraita": "5209291", "guarani de goias": "5209408", "guarinos": "5209457", "heitorai": "5209606", "hidrolandia": "5209705", "hidrolina": "5209804", "iaciara": "5209903", "inaciolandia": "5209937", "indiara": "5209952", "inhumas": "5210000", "ipameri": "5210109", "ipiranga de goias": "5210158", "ipora": "5210208", "israelandia": "5210307", "itaberai": "5210406", "itaguari": "5210562", "itaguaru": "5210604", "itaja": "5210802", "itapaci": "5210901", "itapirapua": "5211008", "itapuranga": "5211206", "itaruma": "5211305", "itaucu": "5211404", "itumbiara": "5211503", "ivolandia": "5211602", "jandaia": "5211701", "jaragua": "5211800", "jatai": "5211909", "jaupaci": "5212006", "jesupolis": "5212055", "joviania": "5212105", "jussara": "5212204", "lagoa santa": "5212253", "leopoldo de bulhoes": "5212303", "luziania": "5212501", "mairipotaba": "5212600", "mambai": "5212709", "mara rosa": "5212808", "marzagao": "5212907", "matrincha": "5212956", "maurilandia": "5213004", "mimoso de goias": "5213053", "minacu": "5213087", "mineiros": "5213103", "moipora": "5213400", "monte alegre de goias": "5213509", "montes claros de goias": "5213707", "montividiu": "5213756", "montividiu do norte": "5213772", "morrinhos": "5213806", "morro agudo de goias": "5213855", "mossamedes": "5213905", "mozarlandia": "5214002", "mundo novo": "5214051", "mutunopolis": "5214101", "nazario": "5214408", "neropolis": "5214507", "niquelandia": "5214606", "nova america": "5214705", "nova aurora": "5214804", "nova crixas": "5214838", "nova gloria": "5214861", "nova iguacu de goias": "5214879", "nova roma": "5214903", "nova veneza": "5215009", "novo brasil": "5215207", "novo gama": "5215231", "novo planalto": "5215256", "orizona": "5215306", "ouro verde de goias": "5215405", "ouvidor": "5215504", "padre bernardo": "5215603", "palestina de goias": "5215652", "palmeiras de goias": "5215702", "palmelo": "5215801", "palminopolis": "5215900", "panama": "5216007", "paranaiguara": "5216304", "parauna": "5216403", "perolandia": "5216452", "petrolina de goias": "5216809", "pilar de goias": "5216908", "piracanjuba": "5217104", "piranhas": "5217203", "pirenopolis": "5217302", "pires do rio": "5217401", "planaltina": "5217609", "pontalina": "5217708", "porangatu": "5218003", "porteirao": "5218052", "portelandia": "5218102", "posse": "5218300", "professor jamil": "5218391", "quirinopolis": "5218508", "rialma": "5218607", "rianapolis": "5218706", "rio quente": "5218789", "rio verde": "5218805", "rubiataba": "5218904", "sanclerlandia": "5219001", "santa barbara de goias": "5219100", "santa cruz de goias": "5219209", "santa fe de goias": "5219258", "santa helena de goias": "5219308", "santa isabel": "5219357", "santa rita do araguaia": "5219407", "santa rita do novo destino": "5219456", "santa rosa de goias": "5219506", "santa tereza de goias": "5219605", "santa terezinha de goias": "5219704", "santo antonio da barra": "5219712", "santo antonio de goias": "5219738", "santo antonio do descoberto": "5219753", "sao domingos": "5219803", "sao francisco de goias": "5219902", "sao joao da parauna": "5220058", "sao joao d'alianca": "5220009", "sao luis de montes belos": "5220108", "sao luiz do norte": "5220157", "sao miguel do araguaia": "5220207", "sao miguel do passa quatro": "5220264", "sao patricio": "5220280", "sao simao": "5220405", "senador canedo": "5220454", "serranopolis": "5220504", "silvania": "5220603", "simolandia": "5220686", "sitio d'abadia": "5220702", "taquaral de goias": "5221007", "teresina de goias": "5221080", "terezopolis de goias": "5221197", "tres ranchos": "5221304", "trindade": "5221403", "trombas": "5221452", "turvania": "5221502", "turvelandia": "5221551", "uirapuru": "5221577", "uruacu": "5221601", "uruana": "5221700", "urutai": "5221809", "valparaiso de goias": "5221858", "varjao": "5221908", "vianopolis": "5222005", "vicentinopolis": "5222054", "vila boa": "5222203", "vila propicio": "5222302" }, "DF": { "brasilia": "5300108" } }brutils-2.5.0/brutils/cpf.py0000664000000000000000000001334715221040537014517 0ustar rootrootfrom random import randint # FORMATTING ############ def sieve(dirty: str) -> str: """ Removes specific symbols from a CPF (Brazilian Individual Taxpayer Number) string. This function takes a CPF string as input and removes all occurrences of the '.', '-' characters from it. Args: cpf (str): The CPF string containing symbols to be removed. Returns: str: A new string with the specified symbols removed. Example: >>> sieve("123.456.789-01") '12345678901' >>> sieve("987-654-321.01") '98765432101' .. note:: This method should not be used in new code and is only provided for backward compatibility. """ return "".join(filter(lambda char: char not in ".-", dirty)) def remove_symbols(dirty: str) -> str: """ Alias for the `sieve` function. Better naming. Args: cpf (str): The CPF string containing symbols to be removed. Returns: str: A new string with the specified symbols removed. """ return sieve(dirty) def display(cpf: str) -> str: """ Format a CPF for display with visual aid symbols. This function takes a numbers-only CPF string as input and adds standard formatting visual aid symbols for display. Args: cpf (str): A numbers-only CPF string. Returns: str: A formatted CPF string with standard visual aid symbols or None if the input is invalid. Example: >>> display("12345678901") "123.456.789-01" >>> display("98765432101") "987.654.321-01" .. note:: This method should not be used in new code and is only provided for backward compatibility. """ if not cpf.isdigit() or len(cpf) != 11 or len(set(cpf)) == 1: return None return "{}.{}.{}-{}".format(cpf[:3], cpf[3:6], cpf[6:9], cpf[9:]) def format_cpf(cpf: str) -> str: """ Format a CPF for display with visual aid symbols. This function takes a numbers-only CPF string as input and adds standard formatting visual aid symbols for display. Args: cpf (str): A numbers-only CPF string. Returns: str: A formatted CPF string with standard visual aid symbols or None if the input is invalid. Example: >>> format_cpf("82178537464") '821.785.374-64' >>> format_cpf("55550207753") '555.502.077-53' """ if not is_valid(cpf): return None return "{}.{}.{}-{}".format(cpf[:3], cpf[3:6], cpf[6:9], cpf[9:11]) # OPERATIONS ############ def validate(cpf: str) -> bool: """ Validate the checksum digits of a CPF. This function checks whether the verifying checksum digits of the given CPF match its base number. The input should be a digit string of the proper length. Args: cpf (str): A numbers-only CPF string. Returns: bool: True if the checksum digits are valid, False otherwise. Example: >>> validate("82178537464") True >>> validate("55550207753") True .. note:: This method should not be used in new code and is only provided for backward compatibility. """ if not cpf.isdigit() or len(cpf) != 11 or len(set(cpf)) == 1: return False return all(_hashdigit(cpf, i + 10) == int(v) for i, v in enumerate(cpf[9:])) def is_valid(cpf: str) -> bool: """ Returns whether or not the verifying checksum digits of the given `˜CPF` match its base number. This function does not verify the existence of the CPF; it only validates the format of the string. Args: cpf (str): The CPF to be validated, a 11-digit string Returns: bool: True if the checksum digits match the base number, False otherwise. Example: >>> is_valid("82178537464") True >>> is_valid("55550207753") True """ return isinstance(cpf, str) and validate(cpf) def generate() -> str: """ Generate a random valid CPF digit string. This function generates a random valid CPF string. Returns: str: A random valid CPF string. Example: >>> generate() "10895948109" >>> generate() "52837606502" """ base = str(randint(1, 999999998)).zfill(9) return base + _checksum(base) def _hashdigit(cpf: str, position: int) -> int: """ Compute the given position checksum digit for a CPF. This function computes the specified position checksum digit for the CPF input. The input needs to contain all elements previous to the position, or the computation will yield the wrong result. Args: cpf (str): A CPF string. position (int): The position to calculate the checksum digit for. Returns: int: The calculated checksum digit. Example: >>> _hashdigit("52599927765", 11) 5 >>> _hashdigit("52599927765", 10) 6 """ val = ( sum( int(digit) * weight for digit, weight in zip(cpf, range(position, 1, -1)) ) % 11 ) return 0 if val < 2 else 11 - val def _checksum(basenum: str) -> str: """ Compute the checksum digits for a given CPF base number. This function calculates the checksum digits for a given CPF base number. The base number should be a digit string of adequate length. Args: basenum (str): A digit string of adequate length. Returns: str: The calculated checksum digits. Example: >>> _checksum("335451269") '51' >>> _checksum("382916331") '26' """ verifying_digits = str(_hashdigit(basenum, 10)) verifying_digits += str(_hashdigit(basenum + verifying_digits, 11)) return verifying_digits brutils-2.5.0/brutils/phone.py0000664000000000000000000001242615221040537015055 0ustar rootrootimport re from random import choice, randint # FORMATTING ############ def format_phone(phone: str) -> str | None: """ Function responsible for formatting a telephone number Args: phone_number (str): The phone number to format. Returns: str: The formatted phone number, or None if the number is not valid. >>> format_phone("11994029275") '(11)99402-9275' >>> format_phone("1635014415") '(16)3501-4415' >>> format_phone("333333") """ if not is_valid(phone): return None ddd = phone[:2] phone_number = phone[2:] return f"({ddd}){phone_number[:-4]}-{phone_number[-4:]}" # OPERATIONS ############ def is_valid(phone_number: str, type: str = None) -> bool: """ Returns if a Brazilian phone number is valid. It does not verify if the number actually exists. Args: phone_number (str): The phone number to validate. Only digits, without country code. It should include two digits DDD. type (str): "mobile" or "landline". If not specified, checks for one or another. Returns: bool: True if the phone number is valid. False otherwise. """ if type == "landline": return _is_valid_landline(phone_number) if type == "mobile": return _is_valid_mobile(phone_number) return _is_valid_landline(phone_number) or _is_valid_mobile(phone_number) def remove_symbols_phone(phone_number: str) -> str: """ Removes common symbols from a Brazilian phone number string. Args: phone_number (str): The phone number to remove symbols. Can include two digits DDD. Returns: str: A new string with the specified symbols removed. """ cleaned_phone = ( phone_number.replace("(", "") .replace(")", "") .replace("-", "") .replace("+", "") .replace(" ", "") ) return cleaned_phone def generate(type: str = None) -> str: """ Generate a valid and random phone number. Args: type (str): "landline" or "mobile". If not specified, checks for one or another. Returns: str: A randomly generated valid phone number. Example: >>> generate() "2234451215" >>> generate("mobile") "1899115895" >>> generate("landline") "5535317900" """ if type == "mobile": return _generate_mobile_phone() if type == "landline": return _generate_landline_phone() generate_functions = [_generate_landline_phone, _generate_mobile_phone] return choice(generate_functions)() def remove_international_dialing_code(phone_number: str) -> str: """ Function responsible for remove a international code phone Args: phone_number (str): The phone number with international code phone. Returns: str: The phone number without international code or the same phone number. Example: >>> remove_international_dialing_code("5511994029275") '11994029275' >>> remove_international_dialing_code("1635014415") '1635014415' >>> remove_international_dialing_code("+5511994029275") '+11994029275' """ pattern = r"\+?55" if ( re.search(pattern, phone_number) and len(phone_number.replace(" ", "")) > 11 ): return phone_number.replace("55", "", 1) else: return phone_number def _is_valid_mobile(phone_number: str) -> bool: """ Returns if a Brazilian mobile number is valid. It does not verify if the number actually exists. Args: phone_number (str): The mobile number to validate. Only digits, without country code. It should include two digits DDD. Returns: bool: True if the phone number is valid. False otherwise. """ pattern = re.compile(r"^[1-9][1-9][9]\d{8}$") return ( isinstance(phone_number, str) and re.match(pattern, phone_number) is not None ) def _is_valid_landline(phone_number: str) -> bool: """ Returns if a Brazilian landline number is valid. It does not verify if the number actually exists. Args: phone_number (str): The landline number to validate. Only digits, without country code. It should include two digits DDD. Returns: bool: True if the phone number is valid. False otherwise. """ pattern = re.compile(r"^[1-9][1-9][2-5]\d{7}$") return ( isinstance(phone_number, str) and re.match(pattern, phone_number) is not None ) def _generate_ddd_number() -> str: """ Generate a valid DDD number. """ return f"{''.join([str(randint(1, 9)) for i in range(2)])}" def _generate_mobile_phone() -> str: """ Generate a valid and random mobile phone number """ ddd = _generate_ddd_number() client_number = [str(randint(0, 9)) for i in range(8)] phone_number = f"{ddd}9{''.join(client_number)}" return phone_number def _generate_landline_phone() -> str: """ Generate a valid and random landline phone number. """ ddd = _generate_ddd_number() return f"{ddd}{randint(2, 5)}{str(randint(0, 9999999)).zfill(7)}" brutils-2.5.0/brutils/schemas/0000775000000000000000000000000015221040537015010 5ustar rootrootbrutils-2.5.0/brutils/schemas/address.py0000664000000000000000000000032615221040537017010 0ustar rootrootfrom typing import TypedDict class Address(TypedDict): cep: str logradouro: str complemento: str bairro: str localidade: str uf: str ibge: str gia: str ddd: str siafi: str brutils-2.5.0/brutils/schemas/__init__.py0000664000000000000000000000003515221040537017117 0ustar rootrootfrom .address import Address brutils-2.5.0/brutils/email.py0000664000000000000000000000144515221040537015032 0ustar rootrootimport re def is_valid(email: str) -> bool: """ Check if a string corresponds to a valid email address. Args: email (str): The input string to be checked. Returns: bool: True if email is a valid email address, False otherwise. Example: >>> is_valid("brutils@brutils.com") True >>> is_valid("invalid-email@brutils") False .. note:: The rules for validating an email address generally follow the specifications defined by RFC 5322 (updated by RFC 5322bis), which is the widely accepted standard for email address formats. """ pattern = re.compile( r"^(?![.])[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" ) return isinstance(email, str) and re.match(pattern, email) is not None brutils-2.5.0/brutils/currency.py0000664000000000000000000000636415221040537015602 0ustar rootrootfrom decimal import ROUND_DOWN, Decimal, InvalidOperation from num2words import num2words def format_currency(value: float | int | str | Decimal) -> str | None: """ Format a numeric value as Brazilian currency (R$). Args: value: The numeric value to format. Accepts float, int, str, or Decimal. Returns: Formatted currency string (e.g., "R$ 1.234,56") or None if invalid. Example: >>> format_currency(1234.56) 'R$ 1.234,56' >>> format_currency(0) 'R$ 0,00' >>> format_currency(-9876.54) 'R$ -9.876,54' >>> format_currency("invalid") None """ try: decimal_value = Decimal(value) formatted_value = ( f"R$ {decimal_value:,.2f}".replace(",", "_") .replace(".", ",") .replace("_", ".") ) return formatted_value except (InvalidOperation, TypeError, ValueError): return None def convert_real_to_text(amount: Decimal | float | int | str) -> str | None: """ Convert a monetary value in Brazilian Reais to textual representation. Args: amount: Monetary value to convert. Accepts Decimal, float, int, or str. Returns: Textual representation in Brazilian Portuguese, or None if invalid. Note: - Values are rounded down to 2 decimal places - Maximum supported value is 1 quadrillion reais - Negative values are prefixed with "Menos" Example: >>> convert_real_to_text(1523.45) 'Mil, quinhentos e vinte e três reais e quarenta e cinco centavos' >>> convert_real_to_text(1.00) 'Um real' >>> convert_real_to_text(0.50) 'Cinquenta centavos' >>> convert_real_to_text(0.00) 'Zero reais' """ try: amount = Decimal(str(amount)).quantize( Decimal("0.01"), rounding=ROUND_DOWN ) except (InvalidOperation, TypeError, ValueError): return None if amount.is_nan() or amount.is_infinite(): return None if abs(amount) > Decimal("1000000000000000.00"): # 1 quadrillion return None negative = amount < 0 amount = abs(amount) reais = int(amount) centavos = int((amount - reais) * 100) parts = [] if reais > 0: """ Note: Although the `num2words` library provides a "to='currency'" feature, it has known issues with the representation of "zero reais" and "zero centavos". Therefore, this implementation uses only the traditional number-to-text conversion for better accuracy. """ reais_text = num2words(reais, lang="pt_BR") currency_text = "real" if reais == 1 else "reais" conector = "de " if reais_text.endswith(("lhão", "lhões")) else "" parts.append(f"{reais_text} {conector}{currency_text}") if centavos > 0: centavos_text = f"{num2words(centavos, lang='pt_BR')} {'centavo' if centavos == 1 else 'centavos'}" if reais > 0: parts.append(f"e {centavos_text}") else: parts.append(centavos_text) if reais == 0 and centavos == 0: parts.append("Zero reais") result = " ".join(parts) if negative: result = f"Menos {result}" return result.capitalize() brutils-2.5.0/brutils/cnh.py0000664000000000000000000000435515221040537014516 0ustar rootrootdef is_valid_cnh(cnh: str) -> bool: """ Validates the registration number for the Brazilian CNH (Carteira Nacional de Habilitação) that was created in 2022. Previous versions of the CNH are not supported in this version. This function checks if the given CNH is valid based on the format and allowed characters, verifying the verification digits. Args: cnh (str): CNH string (symbols will be ignored). Returns: bool: True if CNH has a valid format. Examples: >>> is_valid_cnh("12345678901") False >>> is_valid_cnh("A2C45678901") False >>> is_valid_cnh("98765432100") True >>> is_valid_cnh("987654321-00") True """ cnh = "".join( filter(str.isdigit, cnh) ) # clean the input and check for numbers only if not cnh: return False if len(cnh) != 11: return False # Reject sequences as "00000000000", "11111111111", etc. if cnh == cnh[0] * 11: return False # cast digits to list of integers digits: list[int] = [int(ch) for ch in cnh] first_verificator = digits[9] second_verificator = digits[10] if not _check_first_verificator( digits, first_verificator ): # checking the 10th digit return False return _check_second_verificator( digits, second_verificator, first_verificator ) # checking the 11th digit def _check_first_verificator(digits: list[int], first_verificator: int) -> bool: """ Generates the first verification digit and uses it to verify the 10th digit of the CNH """ sum = 0 for i in range(9): sum += digits[i] * (9 - i) sum = sum % 11 result = 0 if sum > 9 else sum return result == first_verificator def _check_second_verificator( digits: list[int], second_verificator: int, first_verificator: int ) -> bool: """ Generates the second verification and uses it to verify the 11th digit of the CNH """ sum = 0 for i in range(9): sum += digits[i] * (i + 1) result = sum % 11 if first_verificator > 9: result = result + 9 if (result - 2) < 0 else result - 2 if result > 9: result = 0 return result == second_verificator brutils-2.5.0/brutils/passport.py0000664000000000000000000000555715221040537015626 0ustar rootrootimport random import re import string def is_valid(passport: str) -> bool: """ Checks if a Brazilian passport number is valid. To be considered valid, the input must be a string containing exactly two alphabetical characters followed by exactly six numerical digits. This function does not verify is the input is a real passport number, as there are no checksums for the Brazilian passport. Args: passport (str): The string containing the passport number to be checked. Returns: bool: True if the passport number is valid (2 letters followed by 6 digits). False otherwise. Example: >>> is_valid("Ab123456") True >>> is_valid("12345678") False >>> is_valid("DC-221345") False """ if not isinstance(passport, str): return False pattern = re.compile("^[A-Z]{2}[0-9]{6}$") match = re.match(pattern, passport) return match is not None def remove_symbols(passport: str) -> str: """ Removes symbols ('-', '.', and whitespaces) from a passport number. This function takes a passport number string as input and removes all occurrences of the '.', '-', and whitespace characters from it. Args: passport (str): The string containing a passport number Returns: str: The passport numbers with dashes (-), dots (.), and whitespaces ( ) removed. Example: >>> remove_symbols("Ab123456") Ab123456 >>> remove_symbols("Ab-123456") Ab123456 >>> remove_symbols("Ab -. 123456") Ab123456 """ return "".join(filter(lambda c: c not in ".- ", passport)) def format_passport(passport: str) -> str | None: """ Formats a Brazilian passport number for display. This function takes a string representing a valid passport number and returns it formatted (uppercase, without symbols). Args: passport (str | None): A Brazilian passport number (lower or uppercase, possibly including symbols) Returns: str: The formatted passport number (uppercase, without symbols) or None if the input is invalid Example: >>> format_passport("Ab123456") AB123456 >>> format_passport("Ab-123456") AB123456 >>> format_passport("111111") None """ passport = remove_symbols(passport.upper()) return passport if is_valid(passport) else None def generate() -> str: """ Generate a random valid Brazilian passport number string. This function generates a random Brazilian passport number string. Returns: str: A random valid passport number string. Example: >>> generate() "RY393097" >>> generate() "ZS840088" """ letters = "".join(random.choices(string.ascii_uppercase, k=2)) digits = "".join(random.choices(string.digits, k=6)) return f"{letters}{digits}" brutils-2.5.0/brutils/ibge/0000775000000000000000000000000015221040537014273 5ustar rootrootbrutils-2.5.0/brutils/ibge/uf.py0000664000000000000000000000622315221040537015262 0ustar rootrootimport unicodedata from brutils.data.enums.uf import UF, UF_CODE def convert_code_to_uf(code: str) -> str | None: """ Converts a given IBGE code (2-digit string) to its corresponding UF (state abbreviation). This function takes a 2-digit IBGE code and returns the corresponding UF code. It handles all Brazilian states and the Federal District. Args: code (str): The 2-digit IBGE code to be converted. Returns: str or None: The UF code corresponding to the IBGE code, or None if the IBGE code is invalid. Example: >>> convert_code_to_uf('12') 'AC' >>> convert_code_to_uf('33') 'RJ' >>> convert_code_to_uf('99') >>> """ if code not in UF_CODE.values: return None return UF_CODE(code).name def convert_uf_to_name(uf: str) -> str | None: """ Convert a Brazilian UF code (e.g., 'SP') to its full state name ('São Paulo'). - The lookup is case-insensitive and ignores surrounding whitespace. - Returns ``None`` if the input is invalid or the UF code is not recognized. Args: uf (str): A two-letter UF code (e.g., 'RJ', 'sp'). Returns: str | None: The full state name if found, or ``None`` if the code is invalid. Examples: >>> convert_uf_to_name('SP') 'São Paulo' >>> convert_uf_to_name('rj') 'Rio de Janeiro' """ if not uf or not isinstance(uf, str) or len(uf.strip()) != 2: return None federal_unit = uf.strip().upper() if federal_unit not in UF.names: return None result = UF[federal_unit].value return result def _normalize_text(text: str) -> str: """ Normalize text by removing accents and normalizing whitespace. Args: text (str): The text to normalize. Returns: str: The normalized text in uppercase. """ nfd = unicodedata.normalize("NFD", text) without_accents = "".join( char for char in nfd if unicodedata.category(char) != "Mn" ) normalized_spaces = " ".join(without_accents.split()) return normalized_spaces.upper() def convert_name_to_uf(state_name: str) -> str | None: """ Convert a Brazilian state name to its UF code. This function takes the full name of a Brazilian state and returns its corresponding two-letter UF code. The comparison is case-insensitive and ignores accents. Args: state_name (str): The full name of the state (e.g., 'São Paulo', 'sao paulo'). Returns: str | None: The UF code if found, or None if the state name is invalid. Examples: >>> convert_name_to_uf('São Paulo') 'SP' >>> convert_name_to_uf('sao paulo') 'SP' >>> convert_name_to_uf('Rio de Janeiro') 'RJ' >>> convert_name_to_uf('rio de janeiro') 'RJ' >>> convert_name_to_uf('Estado Inválido') >>> """ if not state_name or not isinstance(state_name, str): return None normalized_input = _normalize_text(state_name.strip()) for uf in UF: if _normalize_text(uf.value) == normalized_input: return uf.name return None brutils-2.5.0/brutils/ibge/__init__.py0000664000000000000000000000000015221040537016372 0ustar rootrootbrutils-2.5.0/brutils/ibge/municipality.py0000664000000000000000000001207715221040537017363 0ustar rootrootimport gzip import io import json import unicodedata from urllib.error import HTTPError from urllib.request import urlopen IBGE_MUNICIPALITY_BY_CODE_URL = ( "https://servicodados.ibge.gov.br/api/v1/localidades/municipios/{code}" ) IBGE_MUNICIPALITIES_BY_UF_URL = "https://servicodados.ibge.gov.br/api/v1/localidades/estados/{uf}/municipios" def get_municipality_by_code(code: str) -> tuple[str, str] | None: """ Returns the municipality name and UF for a given IBGE code. This function takes a string representing an IBGE municipality code and returns a tuple with the municipality's name and its corresponding UF. Args: code (str): The IBGE code of the municipality. Returns: tuple: A tuple formatted as ("Município", "UF") or None if the code is not valid. Example: >>> get_municipality_by_code("3550308") ("São Paulo", "SP") >>> get_municipality_by_code("3304557") ("Rio de Janeiro", "RJ") >>> get_municipality_by_code("1234567") None """ base_url = IBGE_MUNICIPALITY_BY_CODE_URL.format(code=code) decompressed_data = _fetch_ibge_data(base_url) if decompressed_data is None: return None try: json_data = json.loads(decompressed_data) return _get_values(json_data) except (json.JSONDecodeError, KeyError): return None def get_code_by_municipality_name( municipality_name: str, uf: str ) -> str | None: """ Returns the IBGE code for a given municipality name and uf code. This function takes a string representing a municipality's name and uf's code and returns the corresponding IBGE code (string). The function will handle names by ignoring differences in case, accents, and treating the character ç as c and ignoring case differences for the uf code. Args: municipality_name (str): The name of the municipality. uf (str): The uf code of the state. Returns: str: The IBGE code of the municipality or None if the name is not valid or does not exist. Example: >>> get_code_by_municipality_name("São Paulo", "SP") "3550308" >>> get_code_by_municipality_name("Conceição do Coité", "Ba") "2908408" >>> get_code_by_municipality_name("Municipio Inexistente", "RS") None """ uf = uf.upper() base_url = IBGE_MUNICIPALITIES_BY_UF_URL.format(uf=uf) decompressed_data = _fetch_ibge_data(base_url) if decompressed_data is None: return None try: json_data = json.loads(decompressed_data) normalized_municipality_name = _transform_text(municipality_name) for municipality in json_data: municipality_name_from_api = municipality.get("nome", "") normalized_name_from_api = _transform_text( municipality_name_from_api ) if normalized_name_from_api == normalized_municipality_name: return str(municipality.get("id")) return None except (json.JSONDecodeError, KeyError): return None def _fetch_ibge_data(url: str) -> bytes | None: """ Fetch data from IBGE API with gzip decompression support. Args: url (str): The URL to fetch data from. Returns: bytes | None: The decompressed data or None if failed. """ try: with urlopen(url) as f: compressed_data = f.read() if f.info().get("Content-Encoding") == "gzip": try: with gzip.GzipFile( fileobj=io.BytesIO(compressed_data) ) as gzip_file: decompressed_data = gzip_file.read() except (OSError, Exception): return None else: decompressed_data = compressed_data if _is_empty(decompressed_data): return None return decompressed_data except HTTPError: return None except Exception: return None def _get_values(data: dict) -> tuple[str, str]: """Extract municipality name and UF from IBGE API response.""" municipio = data["nome"] estado = data["microrregiao"]["mesorregiao"]["UF"]["sigla"] return (municipio, estado) def _is_empty(data: bytes) -> bool: """Check if the response data is empty.""" return data == b"[]" or len(data) == 0 def _transform_text(municipality_name: str) -> str: """ Normalize municipality name and returns the normalized string. Args: municipality_name (str): The name of the municipality. Returns: str: The normalized string Example: >>> _transform_text("São Paulo") 'sao paulo' >>> _transform_text("Goiânia") 'goiania' >>> _transform_text("Conceição do Coité") 'conceicao do coite' """ normalized_string = ( unicodedata.normalize("NFKD", municipality_name) .encode("ascii", "ignore") .decode("ascii") ) case_fold_string = normalized_string.casefold() return case_fold_string brutils-2.5.0/brutils/legal_process.py0000664000000000000000000001170115221040537016561 0ustar rootrootimport json import os import re from datetime import datetime from random import randint from typing import Final ROOT_DIR: Final[str] = os.path.dirname(os.path.abspath(__file__)) DATA_DIR: Final[str] = f"{ROOT_DIR}/data" VALID_IDS_FILE: Final[str] = f"{DATA_DIR}/legal_process_ids.json" # FORMATTING ############ def remove_symbols(legal_process: str) -> str: """ Removes specific symbols from a given legal process. This function takes a legal process as input and removes all occurrences of the '.' and '-' characters from it. Args: legal_process (str): A legal process containing symbols to be removed. Returns: str: The legal process string with the specified symbols removed. Example: >>> remove_symbols("123.45-678.901.234-56.7890") '12345678901234567890' >>> remove_symbols("9876543-21.0987.6.54.3210") '98765432109876543210' """ return legal_process.replace(".", "").replace("-", "") def format_legal_process(legal_process_id: str) -> str | None: """ Format a legal process ID into a standard format. Args: legal_process_id (str): A 20-digits string representing the legal process ID. Returns: str: The formatted legal process ID or None if the input is invalid. Example: >>> format_legal_process("12345678901234567890") '1234567-89.0123.4.56.7890' >>> format_legal_process("98765432109876543210") '9876543-21.0987.6.54.3210' >>> format_legal_process("123") None """ if legal_process_id.isdigit() and len(legal_process_id) == 20: capture_fields = r"(\d{7})(\d{2})(\d{4})(\d)(\d{2})(\d{4})" include_chars = r"\1-\2.\3.\4.\5.\6" return re.sub(capture_fields, include_chars, legal_process_id) return None # OPERATIONS ############ def is_valid(legal_process_id: str) -> bool: """ Check if a legal process ID is valid. This function does not verify if the legal process ID is a real legal process ID; it only validates the format of the string. Args: legal_process_id (str): A digit-only string representing the legal process ID. Returns: bool: True if the legal process ID is valid, False otherwise. Example: >>> is_valid("68476506020233030000") True >>> is_valid("51808233620233030000") True >>> is_valid("123") False """ clean_legal_process_id = remove_symbols(legal_process_id) DD = clean_legal_process_id[7:9] J = clean_legal_process_id[13:14] TR = clean_legal_process_id[14:16] OOOO = clean_legal_process_id[16:] with open(VALID_IDS_FILE) as file: legal_process_ids = json.load(file) process = legal_process_ids.get(f"orgao_{J}") if not process: return False valid_process = int(TR) in process.get("id_tribunal") and int( OOOO ) in process.get("id_foro") return ( _checksum(int(clean_legal_process_id[0:7] + clean_legal_process_id[9:])) == DD ) and valid_process def generate( year: int = datetime.now().year, orgao: int = randint(1, 9) ) -> str | None: """ Generate a random legal process ID number. Args: year (int): The year for the legal process ID (default is the current year). The year should not be in the past orgao (int): The organization code (1-9) for the legal process ID (default is random). Returns: str: A randomly generated legal process ID. None if one of the arguments is invalid. Example: >>> generate(2023, 5) '51659517020235080562' >>> generate() '88031888120233030000' >>> generate(2022, 10) None """ if year < datetime.now().year or orgao not in range(1, 10): return None # Getting possible legal process ids from 'legal_process_ids.json' asset with open(VALID_IDS_FILE) as file: legal_process_ids = json.load(file) _ = legal_process_ids[f"orgao_{orgao}"] TR = str( _["id_tribunal"][randint(0, (len(_["id_tribunal"]) - 1))] ).zfill(2) OOOO = str(_["id_foro"][randint(0, (len(_["id_foro"])) - 1)]).zfill(4) NNNNNNN = str(randint(0, 9999999)).zfill(7) DD = _checksum(f"{NNNNNNN}{year}{orgao}{TR}{OOOO}") return f"{NNNNNNN}{DD}{year}{orgao}{TR}{OOOO}" def _checksum(basenum: int) -> str: """ Checksum to compute the verification digit for a Legal Process ID number. `basenum` needs to be a digit without the verification id. Args: basenum (int): The base number for checksum calculation. Returns: str: The checksum value as a string. Example: >>> _checksum(1234567) '50' >>> _checksum(9876543) '88' """ return str(97 - ((int(basenum) * 100) % 97)).zfill(2) brutils-2.5.0/brutils/__init__.py0000664000000000000000000001146115221040537015501 0ustar rootroot# CEP Imports from brutils.cep import ( format_cep, get_address_from_cep, get_cep_information_from_address, ) from brutils.cep import generate as generate_cep from brutils.cep import is_valid as is_valid_cep from brutils.cep import remove_symbols as remove_symbols_cep # CNH Imports from brutils.cnh import is_valid_cnh as is_valid_cnh # CNPJ Imports from brutils.cnpj import format_cnpj from brutils.cnpj import generate as generate_cnpj from brutils.cnpj import is_valid as is_valid_cnpj from brutils.cnpj import remove_symbols as remove_symbols_cnpj # CPF Imports from brutils.cpf import format_cpf from brutils.cpf import generate as generate_cpf from brutils.cpf import is_valid as is_valid_cpf from brutils.cpf import remove_symbols as remove_symbols_cpf # Currency from brutils.currency import convert_real_to_text, format_currency # Date Utils Import from brutils.date_utils import convert_date_to_text, is_holiday # Email Import from brutils.email import is_valid as is_valid_email # IBGE Imports from brutils.ibge.municipality import ( get_code_by_municipality_name, get_municipality_by_code, ) from brutils.ibge.uf import ( convert_code_to_uf, convert_name_to_uf, convert_uf_to_name, ) # Legal Nature imports from brutils.legal_nature import get_description as get_natureza_legal_nature from brutils.legal_nature import is_valid as is_valid_legal_nature from brutils.legal_nature import list_all as list_all_legal_nature # Legal Process Imports from brutils.legal_process import format_legal_process from brutils.legal_process import generate as generate_legal_process from brutils.legal_process import is_valid as is_valid_legal_process from brutils.legal_process import remove_symbols as remove_symbols_legal_process # License Plate Imports from brutils.license_plate import ( convert_to_mercosul as convert_license_plate_to_mercosul, ) from brutils.license_plate import format_license_plate from brutils.license_plate import generate as generate_license_plate from brutils.license_plate import get_format as get_format_license_plate from brutils.license_plate import is_valid as is_valid_license_plate from brutils.license_plate import remove_symbols as remove_symbols_license_plate # Passport Imports from brutils.passport import format_passport from brutils.passport import generate as generate_passport from brutils.passport import is_valid as is_valid_passport from brutils.passport import remove_symbols as remove_symbols_passport # Phone Imports from brutils.phone import ( format_phone, remove_international_dialing_code, remove_symbols_phone, ) from brutils.phone import generate as generate_phone from brutils.phone import is_valid as is_valid_phone # PIS Imports from brutils.pis import format_pis from brutils.pis import generate as generate_pis from brutils.pis import is_valid as is_valid_pis from brutils.pis import remove_symbols as remove_symbols_pis # RENAVAM Imports from brutils.renavam import is_valid_renavam # Voter ID Imports from brutils.voter_id import format_voter_id from brutils.voter_id import generate as generate_voter_id from brutils.voter_id import is_valid as is_valid_voter_id # Defining __all__ to expose the public methods __all__ = [ # CEP "format_cep", "get_address_from_cep", "get_cep_information_from_address", "generate_cep", "is_valid_cep", "remove_symbols_cep", # CNPJ "format_cnpj", "generate_cnpj", "is_valid_cnpj", "remove_symbols_cnpj", # CPF "format_cpf", "generate_cpf", "is_valid_cpf", "remove_symbols_cpf", # CNH "is_valid_cnh", # Email "is_valid_email", # Legal Process "format_legal_process", "generate_legal_process", "is_valid_legal_process", "remove_symbols_legal_process", # License Plate "convert_license_plate_to_mercosul", "format_license_plate", "generate_license_plate", "get_format_license_plate", "is_valid_license_plate", "remove_symbols_license_plate", # Phone "format_phone", "remove_international_dialing_code", "remove_symbols_phone", "generate_phone", "is_valid_phone", # PIS "format_pis", "generate_pis", "is_valid_pis", "remove_symbols_pis", # RENAVAM "is_valid_renavam", # Voter ID "format_voter_id", "generate_voter_id", "is_valid_voter_id", # IBGE "convert_code_to_uf", "convert_name_to_uf", "convert_uf_to_name", "get_code_by_municipality_name", "get_municipality_by_code", # Date Utils "is_holiday", "convert_date_to_text", # Currency "format_currency", "convert_real_to_text", # Legal Nature "is_valid_legal_nature", "get_natureza_legal_nature", "list_all_legal_nature", # Passport "is_valid_passport", "remove_symbols_passport", "format_passport", "generate_passport", ] brutils-2.5.0/brutils/date_utils.py0000664000000000000000000000562515221040537016104 0ustar rootrootimport re from datetime import datetime import holidays from num2words import num2words from brutils.data.enums.months import MonthsEnum DATE_REGEX = re.compile(r"^\d{2}/\d{2}/\d{4}$") def is_holiday(target_date: datetime, uf: str = None) -> bool | None: """ Checks if the given date is a national or state holiday in Brazil. This function takes a date as a `datetime` object and an optional UF (Unidade Federativa), returning a boolean value indicating whether the date is a holiday or `None` if the date or UF are invalid. The method does not handle municipal holidays. Args: target_date (datetime): The date to be checked. uf (str, optional): The state abbreviation (UF) to check for state holidays. If not provided, only national holidays will be considered. Returns: bool | None: Returns `True` if the date is a holiday, `False` if it is not, or `None` if the date or UF are invalid. Note: The function logic should be implemented using the `holidays` library. For more information, refer to the documentation at: https://pypi.org/project/holidays/ Usage Examples: >>> from datetime import datetime >>> is_holiday(datetime(2024, 1, 1)) True >>> is_holiday(datetime(2024, 1, 2)) False >>> is_holiday(datetime(2024, 3, 2), uf="SP") False >>> is_holiday(datetime(2024, 12, 25), uf="RJ") True """ if not isinstance(target_date, datetime): return None national_holidays = holidays.Brazil(years=target_date.year) valid_ufs = national_holidays.subdivisions if uf is not None and uf not in valid_ufs: return None if uf is None: return target_date in national_holidays state_holidays = holidays.Brazil(prov=uf, years=target_date.year) return target_date in state_holidays def convert_date_to_text(date: str) -> str | None: """ Converts a given date in Brazilian format (dd/mm/yyyy) to its textual representation. This function takes a date as a string in the format dd/mm/yyyy and converts it to a string with the date written out in Brazilian Portuguese, including the full month name and the year. Args: date (str): The date to be converted into text. Expected format: dd/mm/yyyy. Returns: str | None: A string with the date written out in Brazilian Portuguese, or None if the date is invalid. """ if not DATE_REGEX.match(date): return None try: dt = datetime.strptime(date, "%d/%m/%Y") except ValueError: return None day, month, year = dt.day, dt.month, dt.year day_str = "Primeiro" if day == 1 else num2words(day, lang="pt") month_enum = MonthsEnum(month) year_str = num2words(year, lang="pt") return f"{day_str.capitalize()} de {month_enum.month_name} de {year_str}" brutils-2.5.0/brutils/voter_id.py0000664000000000000000000002000215221040537015544 0ustar rootrootfrom random import randint def is_valid(voter_id: str) -> bool: """ Check if a Brazilian voter id number is valid. It does not verify if the voter id actually exists. References: - https://pt.wikipedia.org/wiki/T%C3%ADtulo_de_eleitor, - http://clubes.obmep.org.br/blog/a-matematica-nos-documentos-titulo-de-eleitor/ Args: voter_id(str): string representing the voter id to be verified. Returns: bool: True if the voter id is valid. False otherwise. """ # ensure voter_id only contains numerical characters and has a valid length if ( not isinstance(voter_id, str) or not voter_id.isdigit() or not _is_length_valid(voter_id) ): return False # the voter id is composed of 3 parts, in this order: # - sequential_number (8 or 9 digits) # - federative_union (2 digits) # - verifying digits (2 digits) sequential_number = _get_sequential_number(voter_id) federative_union = _get_federative_union(voter_id) verifying_digits = _get_verifying_digits(voter_id) # ensure federative union is valid if not _is_federative_union_valid(federative_union): return False # ensure the first verifying digit is valid vd1 = _calculate_vd1(sequential_number, federative_union) if vd1 != int(verifying_digits[0]): return False # ensure the second verifying digit is valid vd2 = _calculate_vd2(federative_union, vd1) if vd2 != int(verifying_digits[1]): return False return True def _is_length_valid(voter_id: str) -> bool: """ Check if the length of the provided voter id is valid. Typically, the length should be 12, but there are cases for SP and MG where the length might be 13. This occurs as an edge case when the sequential number has 9 digits instead of 8. Args: voter_id (str): string representing the voter id to be verified Returns: bool: True if the length is valid. False otherwise. """ federative_union = _get_federative_union(voter_id) if len(voter_id) == 12: return True # edge case: for SP and MG with 9 digit long 'sequential number' return len(voter_id) == 13 and federative_union in ["01", "02"] def _get_sequential_number(voter_id: str) -> str: """ Some voter ids in São Paulo and Minas Gerais may have nine digits in their sequential number instead of eight. This fact does not compromise the calculation of the check digits, as it is done based on the first eight sequential numbers. Args: voter_id (str): string representing the voter id to be verified. Returns: str: eight-digit string representing the sequential number for the given voter id """ return voter_id[:8] def _get_federative_union(voter_id: str) -> str: """ Returns the two digits that represent the federative union for the given voter id. Indexing it backwards, as the sequential_number can have eight or nine digits. Args: voter_id (str): string representing the voter id to be verified Returns: str: two-digits string representing the federative union for the given voter id """ return voter_id[-4:-2] def _get_verifying_digits(voter_id: str) -> str: """ Returns the two verifying digits for the given voter id. Indexing it backwards, as the sequential_number can have eight or nine digits. Args: voter_id (str): string representing the voter id to be verified Returns: str: the two verifying digits for the given voter id """ return voter_id[-2:] def _is_federative_union_valid(federative_union: str) -> bool: """ Check if the federative union is valid. Args: federative_union(str): federative union for the given voter id Returns: bool: True if the federative union is valid. False otherwise. """ # valid federative unions: from '01' to '28' return federative_union in ["{:02d}".format(i) for i in range(1, 29)] def _calculate_vd1(sequential_number: str, federative_union: str) -> int: """ Calculate the first verifying digit. Args: sequential_number(str): sequential number sliced from the voter_id federative_union(str): federative union for the given voter id Returns: int: the expected value for the first verifying digit for the given voter id """ # 2, 3, 4, 5, 6, 7, 8, 9 x1, x2, x3, x4, x5, x6, x7, x8 = range(2, 10) sum = ( (int(sequential_number[0]) * x1) + (int(sequential_number[1]) * x2) + (int(sequential_number[2]) * x3) + (int(sequential_number[3]) * x4) + (int(sequential_number[4]) * x5) + (int(sequential_number[5]) * x6) + (int(sequential_number[6]) * x7) + (int(sequential_number[7]) * x8) ) rest = sum % 11 vd1 = rest # edge case: rest == 0 and federative_union is SP or MG if rest == 0 and federative_union in ["01", "02"]: vd1 = 1 # edge case: rest == 10, declare vd1 as zero if rest == 10: vd1 = 0 return vd1 def _calculate_vd2(federative_union: str, vd1: int) -> int: """ Calculate the second verifying digit. Args: federative_union(str): federative union for the given voter id vd1(int): first verifying digit calculated for the given voter id Returns: int: the expected value for the second verifying digit for the given voter id """ x9, x10, x11 = 7, 8, 9 sum = ( (int(federative_union[0]) * x9) + (int(federative_union[1]) * x10) + (vd1 * x11) ) rest = sum % 11 vd2 = rest # edge case: if federative_union is "01" or "02" (for SP and MG) AND # rest == 0, declare vd2 as 1 if federative_union in ["01", "02"] and rest == 0: vd2 = 1 # edge case: rest == 10, declare vd2 as zero if rest == 10: vd2 = 0 return vd2 def generate(federative_union: str = "ZZ") -> str | None: """ Generates a random valid Brazilian voter registration. Args: federative_union(str): federative union for the voter id that will be generated. The default value "ZZ" is used for voter IDs issued to foreigners. Returns: str: A randomly generated valid voter ID for the given federative union """ UFs = { "SP": "01", "MG": "02", "RJ": "03", "RS": "04", "BA": "05", "PR": "06", "CE": "07", "PE": "08", "SC": "09", "GO": "10", "MA": "11", "PB": "12", "PA": "13", "ES": "14", "PI": "15", "RN": "16", "AL": "17", "MT": "18", "MS": "19", "DF": "20", "SE": "21", "AM": "22", "RO": "23", "AC": "24", "AP": "25", "RR": "26", "TO": "27", "ZZ": "28", } federative_union = federative_union.upper() if federative_union in (UFs): sequential_number = str(randint(0, 99999999)).zfill(8) uf_number = UFs[federative_union] if _is_federative_union_valid(uf_number): vd1 = _calculate_vd1(sequential_number, uf_number) vd2 = _calculate_vd2(uf_number, vd1) return f"{sequential_number}{uf_number}{vd1}{vd2}" return None def format_voter_id(voter_id: str) -> str | None: """ Format a voter ID for display with visual spaces. This function takes a numeric voter ID string as input and adds standard formatting for display purposes. Args: voter_id (str): A numeric voter ID string. Returns: str: A formatted voter ID string with standard visual spacing, or None if the input is invalid. Example: >>> format_voter_id("690847092828") '6908 4709 28 28' >>> format_voter_id("163204010922") '1632 0401 09 22' """ if not is_valid(voter_id): return None return "{} {} {} {}".format( voter_id[:4], voter_id[4:8], voter_id[8:10], voter_id[10:12] ) brutils-2.5.0/brutils/renavam.py0000664000000000000000000000306715221040537015376 0ustar rootrootRENAVAM_DV_WEIGHTS = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3] def _validate_renavam_format(renavam: str): if not isinstance(renavam, str): return False if len(renavam) != 11 or not renavam.isdigit(): return False if len(set(renavam)) == 1: return False return True def _sum_weighted_digits(renavam: str) -> int: base_digits = [int(d) for d in renavam[:-1][::-1]] return sum(x * y for x, y in zip(base_digits, RENAVAM_DV_WEIGHTS)) def _calculate_renavam_dv(renavam: str): weighted_sum = _sum_weighted_digits(renavam) dv = 11 - (weighted_sum % 11) return 0 if dv >= 10 else dv def is_valid_renavam(renavam: str) -> bool: """ Validates the Brazilian vehicle registration number (RENAVAM). This function takes a RENAVAM string and checks if it is valid. A valid RENAVAM consists of exactly 11 digits, with the last digit as a verification digit calculated from the previous 10 digits. Args: renavam (str): The RENAVAM string to be validated. Returns: bool: True if the RENAVAM is valid, False otherwise. Example: >>> is_valid_renavam('86769597308') True >>> is_valid_renavam('12345678901') False >>> is_valid_renavam('1234567890a') False >>> is_valid_renavam('12345678 901') False >>> is_valid_renavam('12345678') False >>> is_valid_renavam('') False """ if not _validate_renavam_format(renavam): return False return _calculate_renavam_dv(renavam) == int(renavam[-1]) brutils-2.5.0/brutils/legal_nature.py0000664000000000000000000001367715221040537016417 0ustar rootroot""" brutils.legal_nature ~~~~~~~~~~~~~~~~~~~~~~~~~ Utilities for consulting and validating the official *Natureza Jurídica* (Legal Nature) codes defined by the Receita Federal do Brasil (RFB). .. note:: The codes and descriptions in this module are sourced from the official **Tabela de Natureza Jurídica** (RFB), as provided in the document used by the Cadastro Nacional (e.g., FCN). This module offers simple lookups and validation helpers based on the official table. It does not infer the current legal/registration status of any entity. Source: https://www.gov.br/empresas-e-negocios/pt-br/drei/links-e-downloads/arquivos/TABELADENATUREZAJURDICA.pdf """ from typing import Dict, Optional # FORMATTING ############ # Helper to normalize inputs like "101-5" => "1015" def _normalize(code: str) -> Optional[str]: if not isinstance(code, str): return None digits = "".join(ch for ch in code.strip() if ch.isdigit()) return digits if len(digits) == 4 else None LEGAL_NATURE: Dict[str, str] = { # 1. ADMINISTRAÇÃO PÚBLICA "1015": "Órgão Público do Poder Executivo Federal", "1023": "Órgão Público do Poder Executivo Estadual ou do Distrito Federal", "1031": "Órgão Público do Poder Executivo Municipal", "1040": "Órgão Público do Poder Legislativo Federal", "1058": "Órgão Público do Poder Legislativo Estadual ou do Distrito Federal", "1066": "Órgão Público do Poder Legislativo Municipal", "1074": "Órgão Público do Poder Judiciário Federal", "1082": "Órgão Público do Poder Judiciário Estadual", "1104": "Autarquia Federal", "1112": "Autarquia Estadual ou do Distrito Federal", "1120": "Autarquia Municipal", "1139": "Fundação Federal", "1147": "Fundação Estadual ou do Distrito Federal", "1155": "Fundação Municipal", "1163": "Órgão Público Autônomo da União", "1171": "Órgão Público Autônomo Estadual ou do Distrito Federal", "1180": "Órgão Público Autônomo Municipal", # 2. ENTIDADES EMPRESARIAIS "2011": "Empresa Pública", "2038": "Sociedade de Economia Mista", "2046": "Sociedade Anônima Aberta", "2054": "Sociedade Anônima Fechada", "2062": "Sociedade Empresária Limitada", "2076": "Sociedade Empresária em Nome Coletivo", "2089": "Sociedade Empresária em Comandita Simples", "2097": "Sociedade Empresária em Comandita por Ações", "2100": "Sociedade Mercantil de Capital e Indústria (extinta pelo NCC/2002)", "2127": "Sociedade Empresária em Conta de Participação", "2135": "Empresário (Individual)", "2143": "Cooperativa", "2151": "Consórcio de Sociedades", "2160": "Grupo de Sociedades", "2178": "Estabelecimento, no Brasil, de Sociedade Estrangeira", "2194": "Estabelecimento, no Brasil, de Empresa Binacional Argentino-Brasileira", "2208": "Entidade Binacional Itaipu", "2216": "Empresa Domiciliada no Exterior", "2224": "Clube/Fundo de Investimento", "2232": "Sociedade Simples Pura", "2240": "Sociedade Simples Limitada", "2259": "Sociedade em Nome Coletivo", "2267": "Sociedade em Comandita Simples", "2275": "Sociedade Simples em Conta de Participação", "2305": "Empresa Individual de Responsabilidade Limitada", # 3. ENTIDADES SEM FINS LUCRATIVOS "3034": "Serviço Notarial e Registral (Cartório)", "3042": "Organização Social", "3050": "Organização da Sociedade Civil de Interesse Público (Oscip)", "3069": "Outras Formas de Fundações Mantidas com Recursos Privados", "3077": "Serviço Social Autônomo", "3085": "Condomínio Edilícios", "3093": "Unidade Executora (Programa Dinheiro Direto na Escola)", "3107": "Comissão de Conciliação Prévia", "3115": "Entidade de Mediação e Arbitragem", "3123": "Partido Político", "3131": "Entidade Sindical", "3204": "Estabelecimento, no Brasil, de Fundação ou Associação Estrangeiras", "3212": "Fundação ou Associação Domiciliada no Exterior", "3999": "Outras Formas de Associação", # 4. PESSOAS FÍSICAS "4014": "Empresa Individual Imobiliária", "4022": "Segurado Especial", "4081": "Contribuinte individual", # 5. ORGANIZAÇÕES INTERNACIONAIS E OUTRAS INSTITUIÇÕES EXTRATERRITORIAIS "5002": "Organização Internacional e Outras Instituições Extraterritoriais", } # OPERATIONS ############ def is_valid(code: str) -> bool: """ Check if a string corresponds to a valid *Natureza Jurídica* code. Args: code (str): The code to be validated. Accepts either "NNNN" or "NNN-N". Returns: bool: True if the normalized code exists in the official table, False otherwise. Example: >>> is_valid("2062") True >>> is_valid("206-2") True >>> is_valid("9999") False .. note:: Validation is based solely on the presence of the code in the official RFB table. It does not verify the current legal status or registration of the entity. """ normalized = _normalize(code) return normalized in LEGAL_NATURE if normalized else False def get_description(code: str) -> Optional[str]: """ Retrieve the description of a *Natureza Jurídica* code. Args: code (str): The code to look up. Accepts either "NNNN" or "NNN-N". Returns: str | None: The full description if the code is valid, otherwise None. Example: >>> get_description("2062") 'Sociedade Empresária Limitada' >>> get_description("101-5") 'Órgão Público do Poder Executivo Federal' >>> get_description("0000") None """ normalized = _normalize(code) return LEGAL_NATURE.get(normalized) if normalized else None def list_all() -> Dict[str, str]: """ Return a copy of the full *Natureza Jurídica* table. Returns: dict[str, str]: Mapping from "NNNN" codes to descriptions. """ return dict(LEGAL_NATURE) brutils-2.5.0/brutils/license_plate.py0000664000000000000000000001373515221040537016557 0ustar rootrootimport re from random import choice, randint from string import ascii_uppercase from typing import Literal # FORMATTING ############ def convert_to_mercosul(license_plate: str) -> str | None: """ Converts an old pattern license plate (LLLNNNN) to a Mercosul format (LLLNLNN). Args: license_plate (str): A string of proper length representing the old pattern license plate. Returns: str | None: The converted Mercosul license plate (LLLNLNN) or 'None' if the input is invalid. Example: >>> convert_to_mercosul("ABC4567") 'ABC4F67' >>> convert_to_mercosul("ABC4*67") None """ if not _is_valid_old_format(license_plate): return None digits = [letter for letter in license_plate.upper()] digits[4] = chr(ord("A") + int(digits[4])) return "".join(digits) def format_license_plate(license_plate: str) -> str | None: """ Formats a license plate into the correct pattern. This function receives a license plate in any pattern (LLLNNNN or LLLNLNN) and returns a formatted version. Args: license_plate (str): A license plate string. Returns: str | None: The formatted license plate string or 'None' if the input is invalid. Example: >>> format("ABC1234") # old format (contains a dash) 'ABC-1234' >>> format("abc1e34") # mercosul format 'ABC1E34' >>> format("ABC123") None """ license_plate = license_plate.upper() if _is_valid_old_format(license_plate): return license_plate[0:3] + "-" + license_plate[3:] elif _is_valid_mercosul(license_plate): return license_plate.upper() return None # OPERATIONS ############ def is_valid( license_plate: str, type: Literal["old_format", "mercosul"] | None = None ) -> bool: """ Returns if a Brazilian license plate number is valid. It does not verify if the plate actually exists. Args: license_plate (str): The license plate number to be validated. type (Literal["old_format", "mercosul"] | None): "old_format" or "mercosul". If not specified, checks for one or another. Returns: bool: True if the plate number is valid. False otherwise. """ if type == "old_format": return _is_valid_old_format(license_plate) if type == "mercosul": return _is_valid_mercosul(license_plate) return _is_valid_old_format(license_plate) or _is_valid_mercosul( license_plate ) def remove_symbols(license_plate_number: str) -> str: """ Removes the dash (-) symbol from a license plate string. Args: license_plate_number (str): A license plate number containing symbols to be removed. Returns: str | None: The license plate number with the specified symbols removed. Example: >>> remove_symbols("ABC-123") "ABC123" >>> remove_symbols("abc123") "abc123" >>> remove_symbols("ABCD123") "ABCD123" """ return license_plate_number.replace("-", "") def get_format(license_plate: str) -> str | None: """ Return the format of a license plate. 'LLLNNNN' for the old pattern and 'LLLNLNN' for the Mercosul one. Args: license_plate (str): A license plate string without symbols. Returns: str | None: The format of the license plate (LLLNNNN, LLLNLNN) or 'None' if the format is invalid. Example: >>> get_format("abc123") "LLLNNNN" >>> get_format("abc1d23") "LLLNLNN" >>> get_format("ABCD123") None """ if _is_valid_old_format(license_plate): return "LLLNNNN" if _is_valid_mercosul(license_plate): return "LLLNLNN" return None def generate(format: str = "LLLNLNN") -> str | None: """ Generate a valid license plate in the given format. In case no format is provided, it will return a license plate in the Mercosul format. Args: format (str): The desired format for the license plate. 'LLLNNNN' for the old pattern or 'LLLNLNN' for the Mercosul one. Default is 'LLLNLNN' Returns: str | None: A randomly generated license plate number or 'None' if the format is invalid. Example: >>> generate() "ABC1D23" >>> generate(format="LLLNLNN") "ABC4D56" >>> generate(format="LLLNNNN") "ABC123" >>> generate(format="invalid") None """ generated = "" format = format.upper() if format not in ("LLLNLNN", "LLLNNNN"): return None for char in format: if char == "L": generated += choice(ascii_uppercase) else: generated += str(randint(0, 9)) return generated def _is_valid_old_format(license_plate: str) -> bool: """ Checks whether a string matches the old format of Brazilian license plate. Args: license_plate (str): The desired format for the license plate. pattern:'LLLNNNN' Returns: bool: True if the plate number is valid. False otherwise. """ pattern = re.compile(r"^[A-Za-z]{3}[0-9]{4}$") return ( isinstance(license_plate, str) and re.match(pattern, license_plate.strip()) is not None ) def _is_valid_mercosul(license_plate: str) -> bool: """ Checks whether a string matches the old format of Brazilian license plate. Args: license_plate (str): The desired format for the license plate. pattern:'LLLNLNN' Returns: bool: True if the plate number is valid. False otherwise. """ if not isinstance(license_plate, str): return False license_plate = license_plate.upper().strip() pattern = re.compile(r"^[A-Z]{3}\d[A-Z]\d{2}$") return re.match(pattern, license_plate) is not None brutils-2.5.0/requirements-dev.txt0000664000000000000000000000007715221040537015745 0ustar rootrootholidays>=0.58 num2words==0.5.13 coverage>=7.2.7 ruff==0.15.12