├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── coverage.yml │ ├── lint.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CHANGELOG ├── CONTRIBUTING ├── LICENSE ├── Makefile ├── README.md ├── brazilcep ├── __init__.py ├── __version__.py ├── apicep.py ├── client.py ├── exceptions.py ├── opencep.py ├── py.typed ├── utils.py └── viacep.py ├── docs └── source │ ├── _static │ ├── favicon.ico │ └── logo.png │ ├── _templates │ ├── sidebar-intro.html │ └── sidebar-links.html │ ├── api.rst │ ├── changes.rst │ ├── conf.py │ ├── contributing.rst │ ├── index.rst │ └── user │ ├── install.rst │ └── quickstart.rst ├── pyproject.toml ├── tests ├── __init__.py ├── test_apicep.py ├── test_client.py ├── test_opencep.py ├── test_utils.py └── test_viacep.py └── tox.ini /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: mstuttgart 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: Bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ## Expected Result 13 | 14 | 15 | 16 | ## Actual Result 17 | 18 | 19 | 20 | ## Reproduction Steps 21 | 22 | ```python 23 | import brazilcep 24 | 25 | ``` 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: Question 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: New feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ## Proposed change 6 | 7 | 11 | 12 | Your PR description goes here. 13 | 14 | ## Type of change 15 | 16 | 21 | 22 | - [ ] New CEP API support (thank you!) 23 | - [ ] Existing code/documentation/test/process quality improvement (best practice, cleanup, refactoring, optimization) 24 | - [ ] Dependency update (version deprecation/pin/upgrade) 25 | - [ ] Bugfix (non-breaking change which fixes an issue) 26 | - [ ] Breaking change (a code change causing existing functionality to break) 27 | - [ ] New feature (new `brazilcep` functionality in general) 28 | 29 | ## Checklist 30 | 31 | 34 | 35 | - [ ] I've followed the [contributing guidelines][contributing-guidelines] 36 | - [ ] I've successfully run `make check`, all checks and tests are green 37 | 38 | 41 | 42 | [contributing-guidelines]: https://github.com/mstuttgart/brazilcep/blob/dev/CONTRIBUTING.md 43 | [docs]: https://brazilcep.readthedocs.io 44 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Github CI Coverage 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | - develop 9 | 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-python@v4 20 | with: 21 | python-version: "3.12" 22 | 23 | - name: Install coverage dependencies 24 | run: | 25 | pip install ".[dev]" 26 | pip install ".[coverage]" 27 | 28 | - name: Run coverage tests 29 | run: make coverage 30 | 31 | - name: Upload coverage to Codecov 32 | uses: codecov/codecov-action@v5 33 | with: 34 | fail_ci_if_error: false 35 | token: ${{ secrets.CODECOV_TOKEN }} 36 | 37 | - name: Upload test results to Codecov 38 | if: ${{ !cancelled() }} 39 | uses: codecov/test-results-action@v1 40 | with: 41 | token: ${{ secrets.CODECOV_TOKEN }} 42 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Github CI Lint 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | - develop 9 | 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-python@v4 20 | with: 21 | python-version: "3.12" 22 | 23 | - name: Install test dependencies 24 | run: | 25 | pip install ".[dev]" 26 | 27 | - name: Lint with ruff 28 | run: make lint 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 Distribution 📦 to PyPI and TestPyPI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "*" 9 | 10 | jobs: 11 | build: 12 | name: Build Distribution 📦 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout Code 17 | uses: actions/checkout@v4 18 | 19 | - name: Set Up Python 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: "3.*" 23 | 24 | - name: Install Build Dependencies 25 | run: pip install ".[build]" 26 | 27 | - name: Build Distribution Packages 28 | run: python3 -m build 29 | 30 | - name: Upload Distribution Packages 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: python-package-distributions 34 | path: dist/ 35 | 36 | publish-to-pypi: 37 | name: Publish Distribution 📦 to PyPI 38 | if: startsWith(github.ref, 'refs/tags/') # Only publish on tag pushes 39 | needs: build 40 | runs-on: ubuntu-latest 41 | 42 | environment: 43 | name: pypi 44 | url: https://pypi.org/p/brazilcep 45 | 46 | permissions: 47 | id-token: write 48 | 49 | steps: 50 | - name: Download Distribution Packages 51 | uses: actions/download-artifact@v4 52 | with: 53 | name: python-package-distributions 54 | path: dist/ 55 | 56 | - name: Publish to PyPI 57 | uses: pypa/gh-action-pypi-publish@release/v1 58 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | --- 4 | name: Github CI PyTest 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | - develop 11 | 12 | pull_request: 13 | 14 | jobs: 15 | ci: 16 | strategy: 17 | fail-fast: false 18 | 19 | matrix: 20 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 21 | os: [ubuntu-latest] 22 | 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: actions/setup-python@v4 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | 30 | - name: Install test dependencies 31 | run: pip install ".[dev]" 32 | 33 | - name: Run tests 34 | run: make test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | .python-version 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | testreport.junit.xml 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | main.py 62 | .idea 63 | /docs/_build_html/ 64 | 65 | # vscode 66 | .vscode 67 | 68 | # virtualenv 69 | .venv 70 | .env 71 | env 72 | 73 | # mkdocs 74 | site 75 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Exclude specific directories from pre-commit checks 2 | exclude: "docs/" 3 | 4 | repos: 5 | # Pre-commit hooks for general code quality checks 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v5.0.0 8 | hooks: 9 | - id: check-yaml # Validate YAML files 10 | - id: debug-statements # Detect debug statements (e.g., print, pdb) 11 | - id: end-of-file-fixer # Ensure files end with a newline 12 | - id: trailing-whitespace # Remove trailing whitespace 13 | - id: check-builtin-literals # Check for unnecessary use of built-in literals 14 | - id: mixed-line-ending # Fix mixed line endings 15 | args: 16 | - --fix=lf # Convert to LF line endings 17 | 18 | # Sort imports automatically 19 | - repo: https://github.com/PyCQA/isort 20 | rev: 6.0.1 21 | hooks: 22 | - id: isort 23 | 24 | # Format Python code with Black 25 | - repo: https://github.com/psf/black 26 | rev: 25.1.0 27 | hooks: 28 | - id: black 29 | 30 | # Lint Python code with Ruff 31 | - repo: https://github.com/astral-sh/ruff-pre-commit 32 | rev: v0.11.2 33 | hooks: 34 | - id: ruff 35 | 36 | # Format tox.ini files 37 | - repo: https://github.com/tox-dev/tox-ini-fmt 38 | rev: "1.5.0" 39 | hooks: 40 | - id: tox-ini-fmt 41 | 42 | # Upgrade Python syntax to modern versions 43 | - repo: https://github.com/asottile/pyupgrade 44 | rev: v3.19.1 45 | hooks: 46 | - id: pyupgrade 47 | args: [--py39-plus] # Target Python 3.9+ syntax 48 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # .readthedocs.yaml 3 | # Read the Docs configuration file 4 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 5 | 6 | # Required 7 | version: 2 8 | 9 | # Set the version of Python and other tools you might need 10 | build: 11 | os: ubuntu-22.04 12 | tools: 13 | python: "3.11" 14 | 15 | python: 16 | install: 17 | - method: pip 18 | path: . 19 | extra_requirements: 20 | - docs 21 | 22 | sphinx: 23 | configuration: docs/source/conf.py 24 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 7.0.0 (2025-03-27) 5 | ------------------ 6 | 7 | - Introduced async request functionality. Special thanks to [Iago Borba de Almeida](https://github.com/iagobalmeida) for the contribution. 8 | - Refined pre-commit hooks for better code consistency. 9 | - Updated documentation to fix syntax errors, add async request usage details, and enhance overall clarity. 10 | - Improved test coverage to ensure higher reliability. 11 | - Enhanced both documentation and code quality for better maintainability. 12 | - Optimized Makefile for streamlined usage. 13 | - Removed the deprecated Correios webservice to simplify the codebase. 14 | 15 | 6.7.0 (2024-11-25) 16 | ------------------ 17 | 18 | - Added new exceptions to encapsulate request errors 19 | - Improved BrazilCEP API and documentation 20 | 21 | 6.6.1 (2024-11-23) 22 | ------------------ 23 | 24 | - Updated PyPI links 25 | 26 | 6.6.0 (2024-11-23) 27 | ------------------ 28 | 29 | - Dropped Python 3.8 support 30 | - Added Python 3.13 support 31 | - Added `help`, `test`, `setup`, `coverage`, and `docs` commands to Makefile 32 | - Replaced Nox with Tox 33 | - Added GitHub issue and PR templates 34 | - Introduced pre-commit hooks 35 | - Migrated documentation from MkDocs to Sphinx 36 | 37 | 6.5.0 (2024-08-18) 38 | ------------------ 39 | 40 | - Removed API Correios from the website 41 | 42 | 6.4.1 (2024-08-18) 43 | ------------------ 44 | 45 | - Fixed `beautifulsoup4` version 46 | 47 | 6.4.0 (2024-08-14) 48 | ------------------ 49 | 50 | - Added API Correios (via Correios site) support 51 | - Added OpenCEP support 52 | 53 | 6.3.1 (2024-05-05) 54 | ------------------ 55 | 56 | - Updated copyright and Python version 57 | 58 | 6.3.0 (2024-05-05) 59 | ------------------ 60 | 61 | - Updated `pyproject.toml` to align with Python documentation standards 62 | - Improved Nox tests and dependency environments 63 | - Added support for Python 3.12 64 | 65 | 6.2.0 (2023-10-01) 66 | ------------------ 67 | 68 | - Added timeout settings 69 | - Added proxy settings (https://github.com/mstuttgart/brazilcep/issues/40) 70 | - Added real tests for APICEP and ViaCEP 71 | - Updated documentation and README 72 | - Deprecated Correios webservice support 73 | 74 | 6.1.0 (2023-10-01) 75 | ------------------ 76 | 77 | - Added timeout parameters for requests 78 | - Fixed typos in README 79 | 80 | 6.0.0 (2023-04-30) 81 | ------------------ 82 | 83 | - Renamed library to BrazilCEP 84 | - Created a new pip package 85 | - Dropped support for Python 3.5, 3.6, and 3.7 86 | - Added support for Python 3.9, 3.10, and 3.11 87 | - Introduced Poetry for dependency management 88 | - Added matrix testing for Python versions using Nox 89 | - Improved documentation and code quality 90 | - Fixed GitHub Actions lint and coverage issues 91 | - Enhanced lint and coverage tests 92 | - Created a documentation site (translated to English) 93 | - Updated contribution guide to use Poetry 94 | 95 | 5.1.0 (2022-10-12) 96 | ------------------ 97 | 98 | - Fixed `AttributeError: 'NoneType' object has no attribute 'split'` when querying CEP `99999-999` 99 | - Added new exception `BlockedByFlood` for excessive queries 100 | - Improved tests by adding mocks for ViaCEP and ApiCEP queries 101 | 102 | 5.0.0 (2021-01-07) 103 | ------------------ 104 | 105 | - Added support for Python 3.8 106 | - Integrated Correios and APICEP.com webservices for CEP queries 107 | - Introduced new exceptions for better error handling 108 | - Removed deprecated methods and exceptions 109 | - Removed unused `get_cep_from_address` function 110 | - Centralized usage examples in README 111 | - Adopted Tox for testing across Python versions 112 | - Added new tests and improved coverage 113 | 114 | 4.0.4 (2020-08-18) 115 | ------------------ 116 | 117 | - Fixed `AttributeError: 'ConnectionError' object has no attribute 'message'` 118 | 119 | 4.0.3 (2020-06-08) 120 | ------------------ 121 | 122 | - Fixed `get_cep_from_address` error key - Thanks to Hendrix Costa (https://github.com/hendrixcosta) 123 | - Updated documentation 124 | 125 | 4.0.2 (2020-05-31) 126 | ------------------ 127 | 128 | - Fixed usage example for `get_cep_from_address` 129 | - Fixed `KeyError` exception when address not found - Thanks to Patrick Ferraz (https://github.com/patricksferraz) 130 | - Added error handling for `status_code=200` - Thanks to Bruno Mello (https://github.com/bgmello) 131 | 132 | 4.0.1 (2020-02-22) 133 | ------------------ 134 | 135 | - Fixed `travis.yml` deployment issues 136 | 137 | 4.0.0 (2020-02-22) 138 | ------------------ 139 | 140 | - Added support for ViaCEP API (https://viacep.com.br/) 141 | - Introduced CEP range queries 142 | - Added `format_cep` and `validate_cep` functions 143 | - Added support for Python 3.7 144 | - Improved code and documentation 145 | 146 | Milestone: https://github.com/mstuttgart/pycep-correios/milestone/4 147 | 148 | 3.2.0 (2019-08-18) 149 | ------------------ 150 | 151 | - Dropped support for Python 3.4 152 | - Added support for responses with missing attributes 153 | - Added mocks to tests 154 | 155 | 3.1.0 (2018-11-11) 156 | ------------------ 157 | 158 | - Fixed return type of `consultar_cep` method (now returns a dictionary) 159 | - Updated documentation to reflect changes in Correios API 160 | - Updated README for PyPI compatibility 161 | 162 | 3.0.0 (2018-10-21) 163 | ------------------ 164 | 165 | - Migrated API to use [python-zeep](https://pypi.org/project/zeep/) 166 | - Deprecated old exceptions 167 | - Updated documentation 168 | - Dropped support for Python 2.7 169 | 170 | 2.3.1 (2018-05-03) 171 | ------------------ 172 | 173 | - Fixed `README.rst` 174 | - Added validation command to `setup.py` in Makefile 175 | - Fixed repository and download links 176 | 177 | 2.3.0 (2018-05-03) 178 | ------------------ 179 | 180 | - Added logging for CEP queries 181 | - Added English documentation 182 | - Limited versions of `requests` and `Jinja2` for compatibility 183 | - Dropped support for Python 3.3 184 | 185 | 2.2.0 (2017-07-16) 186 | ------------------ 187 | 188 | - Improved `formatar_cep` method with regex validation 189 | - Introduced new exceptions for better error handling 190 | - Added environments for testing and production 191 | - Improved API organization 192 | 193 | 2.1.1 (2017-06-30) 194 | ------------------ 195 | 196 | - Fixed Unicode errors in Python 2.7 197 | 198 | 2.1.0 (2017-06-29) 199 | ------------------ 200 | 201 | - Added support for Python 2.7+ 202 | - Updated documentation 203 | 204 | 2.0.0 (2017-06-20) 205 | ------------------ 206 | 207 | - Refactored PyCEPCorreios for easier usage 208 | - Removed old exceptions and classes 209 | - Simplified imports 210 | - Added Sphinx documentation 211 | - Added Tox testing 212 | - Introduced CEP validation and formatting methods 213 | 214 | 1.1.7 (2017-05-09) 215 | ------------------ 216 | 217 | - Fixed `jinja2.exceptions.TemplateNotFound: consultacep.xml` 218 | - Resolved installation issues with PyCEPCorreios via pip 219 | - Updated README and documentation examples 220 | 221 | 1.1.6 (2017-05-08) 222 | ------------------ 223 | 224 | - Fixed installation bugs 225 | - Improved code and resolved bugs 226 | 227 | 1.1.1 (2017-02-08) 228 | ------------------ 229 | 230 | - General code improvements 231 | - Added XML schema using Jinja2 232 | 233 | 1.0.1 (2016-08-03) 234 | ------------------ 235 | 236 | - Simplified exception classes 237 | - Organized test code 238 | - Used mocks for testing 239 | 240 | 1.0.0 (2016-07-31) 241 | ------------------ 242 | 243 | - Migrated API to Python 3 (dropped Python 2.7 support) 244 | - Replaced `suds` with `requests` for HTTP requests 245 | 246 | 0.0.2 (2016-05-09) 247 | ------------------ 248 | 249 | - Updated `setup.py` version and dependencies 250 | 251 | 0.0.1 (2016-05-05) 252 | ------------------ 253 | 254 | - Initial release 255 | - Enabled CEP lookup via Correios webservice 256 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | .. _prs: https://github.com/mstuttgart/brazilcep/pulls 6 | .. _`main branch`: https://github.com/mstuttgart/brazilcep/tree/main 7 | 8 | Thank you for considering contributing! This document outlines the various ways you can contribute to this project and how to get started. 9 | 10 | Bug Reports and Feature Requests 11 | ================================ 12 | 13 | Found a Bug? 14 | ------------ 15 | 16 | Before reporting an issue, please perform `a quick search `_ to check if it has already been reported. If it exists, feel free to comment on the existing issue. Otherwise, open `a new GitHub issue `_. 17 | 18 | When reporting a bug, ensure you include: 19 | 20 | * A clear and concise title. 21 | * A detailed description with relevant information. 22 | * Steps to reproduce the issue and the expected behavior. 23 | * If possible, a code sample or an executable test case demonstrating the issue. 24 | 25 | Have a Suggestion for an Enhancement or New Feature? 26 | ---------------------------------------------------- 27 | 28 | We use GitHub issues to track feature requests. Before creating a feature request: 29 | 30 | * Ensure you have a clear idea of the enhancement. If unsure, consider discussing it first in a GitHub issue. 31 | * Check the documentation to confirm the feature does not already exist. 32 | * Perform `a quick search `_ to see if the feature has already been suggested. 33 | 34 | When submitting a feature request, please: 35 | 36 | * Provide a clear and descriptive title. 37 | * Explain why the enhancement would be useful, possibly referencing similar features in other libraries. 38 | * Include code examples to demonstrate how the feature would be used. 39 | 40 | Contributions are always welcome and greatly appreciated! 41 | 42 | Contributing Fixes and New Features 43 | =================================== 44 | 45 | When contributing fixes or new features, start by forking or branching from the `main branch`_ to ensure you work on the latest code and minimize merge conflicts. 46 | 47 | All contributed PRs_ must include valid test coverage to be considered for merging. If you need help with writing tests, don't hesitate to ask. 48 | 49 | Thank you for your support! 50 | 51 | Running Tests 52 | ------------- 53 | 54 | To set up the development environment and install all required dependencies, run: 55 | 56 | .. code-block:: shell 57 | 58 | $ virtualenv -p python3 .venv 59 | $ source .venv/bin/activate 60 | $ make setup 61 | 62 | The project provides automated style checks, tests, and coverage reports: 63 | 64 | .. code-block:: shell 65 | 66 | $ make check 67 | 68 | You can also run them individually: 69 | 70 | .. code-block:: shell 71 | 72 | $ make pre-commit 73 | $ make test 74 | 75 | To retrieve uncovered lines, use: 76 | 77 | .. code-block:: shell 78 | 79 | $ make coverage 80 | 81 | To run specific tests, use the ``pytest`` command: 82 | 83 | .. code-block:: shell 84 | 85 | $ pytest tests/test_apicep.py 86 | 87 | For more granular testing: 88 | 89 | .. code-block:: shell 90 | 91 | $ pytest tests/test_apicep.py::test_fetch_address_success 92 | 93 | To run tests against the real API, create a `.env` file in the project's root directory with the following content: 94 | 95 | .. code-block:: ini 96 | 97 | SKIP_REAL_TEST=False 98 | 99 | This will enable tests that interact with the live API. Ensure you have the necessary permissions and understand the implications of running tests against the real API. 100 | 101 | Code Style 102 | ---------- 103 | 104 | BrazilCEP uses a collection of tools to maintain consistent code style. These tools are orchestrated using `pre-commit`_, which can be installed locally to check your changes before opening a PR. The CI process will also run these checks before merging. 105 | 106 | To run pre-commit checks locally: 107 | 108 | .. code-block:: shell 109 | 110 | $ make pre-commit 111 | 112 | The full list of formatting requirements is specified in the `.pre-commit-config.yaml`_ file located in the project's root directory. 113 | 114 | .. _pre-commit: https://pre-commit.com/ 115 | .. _.pre-commit-config.yaml: https://github.com/mstuttgart/brazilcep/blob/main/.pre-commit-config.yaml 116 | 117 | Building Sphinx Documentation 118 | ----------------------------- 119 | 120 | .. _readthedocs.io: https://brazilcep.readthedocs.io/ 121 | 122 | The project includes Sphinx documentation sources under ``./docs/source``, which are published online at `readthedocs.io`_. 123 | 124 | High-quality documentation is essential for any project. If you're not familiar with reStructuredText for Sphinx, you can read a primer `here`__. 125 | 126 | __ https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html 127 | 128 | Thank you for your contribution! 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2016-2024, Michell Stuttgart 5 | 6 | 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: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | 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. 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help check clean build coverage lint docs pre-commit setup test tox 2 | 3 | # Help target to display available commands 4 | help: 5 | @echo "Usage: make " 6 | @echo "" 7 | @echo "Available targets:" 8 | @echo " check Run pre-commit, docs, tests, and build" 9 | @echo " clean Clean up generated files and caches" 10 | @echo " build Build package distribution" 11 | @echo " coverage Identify code not covered with tests" 12 | @echo " lint Run lint checks on the module" 13 | @echo " docs Build documentation" 14 | @echo " pre-commit Run pre-commit against all files" 15 | @echo " setup Set up the development environment" 16 | @echo " test Run tests (in parallel)" 17 | @echo " tox Run tox (in parallel)" 18 | @echo " help Show this summary of available commands" 19 | 20 | # Run all checks 21 | check: 22 | $(MAKE) pre-commit 23 | $(MAKE) docs 24 | $(MAKE) test 25 | $(MAKE) build 26 | 27 | # Clean up generated files and caches 28 | clean: 29 | find . -name "*.mo" -delete 30 | find . -name "*.pyc" -delete 31 | rm -rf .mypy_cache/ .pytest_cache/ .ruff_cache/ dist/ tox/ 32 | rm -rf docs/build/ docs/source/_autosummary/ 33 | rm -f .coverage .coverage.xml 34 | 35 | # Build package distribution 36 | build: 37 | rm -rf *.egg-info/ 38 | python3 -m build 39 | 40 | # Run coverage analysis 41 | coverage: 42 | pytest --cov=brazilcep --cov-config=pyproject.toml --cov-report=term-missing --no-cov-on-fail --cov-report=html --junitxml=junit.xml -o junit_family=legacy 43 | 44 | # Run lint checks 45 | lint: 46 | isort --check brazilcep tests 47 | black --check brazilcep tests 48 | ruff check brazilcep tests 49 | mypy brazilcep 50 | 51 | # Build documentation 52 | docs: 53 | rm -rf docs/build/ 54 | sphinx-autobuild -b html --watch brazilcep/ docs/source/ docs/build/ 55 | 56 | # Run pre-commit hooks 57 | pre-commit: 58 | pre-commit run --all-files 59 | 60 | # Set up the development environment 61 | setup: 62 | pip install -e ".[dev]" 63 | pip install -e ".[coverage]" 64 | pip install -e ".[docs]" 65 | pip install -e ".[build]" 66 | pre-commit install --hook-type pre-commit 67 | pre-commit install --hook-type pre-push 68 | pre-commit autoupdate 69 | mypy --install-types 70 | 71 | # Run tests 72 | test: 73 | pytest -v 74 | 75 | # Run tox 76 | tox: 77 | tox --parallel auto 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | BrazilCEP Logo 4 | 5 |
6 | BrazilCEP 7 |

8 | 9 |

10 | 11 | 12 | GitHub Workflow Status 13 | 14 | 15 | 16 | Codecov 17 | 18 | 19 | 20 | Read the Docs 21 | 22 | 23 | 24 | Downloads 25 | 26 | 27 | 28 | PyPI Version 29 | 30 | 31 | 32 | Python Versions 33 | 34 | 35 |

36 | 37 |

38 | About | 39 | Install | 40 | Quick Start | 41 | Documentation | 42 | Contribute | 43 | Credits 44 |

45 | 46 | ## About 47 | 48 | **BrazilCEP** is a minimalist and easy-to-use Python library designed to query CEP (Postal Address Code) data. 49 | 50 | Its goal is to provide a unified query interface for multiple search services, simplifying the integration of Python applications with these services. 51 | 52 | Currently, it supports several CEP APIs: 53 | 54 | - [ViaCEP](https://viacep.com.br) 55 | - [ApiCEP (WideNet)](https://apicep.com) 56 | - [OpenCEP](https://opencep.com) 57 | 58 | > [!NOTE] 59 | > **BrazilCEP** is the new name of the former **PyCEPCorreio** Python library. 60 | > To migrate your old code to the new version, refer to the [migration guide](https://brazilcep.readthedocs.io/api.html#migrate-from-pycepcorreios). 61 | 62 | > [!TIP] 63 | > **CEP** or **Código de Endereçamento Postal** (_Postal Address Code_) is a system of numeric codes created, maintained, and organized by _Correios do Brasil_ to streamline address organization and delivery of letters and parcels. 64 | 65 | ## Install 66 | 67 | To install the latest stable release of BrazilCEP, use [pip](http://pip-installer.org): 68 | 69 | ```sh 70 | pip install brazilcep 71 | ``` 72 | 73 | ## Quick Start 74 | 75 | Making a request is straightforward. Start by importing the BrazilCEP module: 76 | 77 | ```python 78 | >>> import brazilcep 79 | ``` 80 | 81 | Next, use the `get_address_from_cep` function to query any CEP: 82 | 83 | ```python 84 | >>> address = brazilcep.get_address_from_cep('37503-130') 85 | ``` 86 | 87 | The result is a dictionary containing the address details: 88 | 89 | ```python 90 | >>> address 91 | { 92 | 'district': 'rua abc', 93 | 'cep': '37503130', 94 | 'city': 'city ABC', 95 | 'street': 'str', 96 | 'uf': 'str', 97 | 'complement': 'str', 98 | } 99 | ``` 100 | 101 | The CEP must always be provided as a string. 102 | 103 | > [!TIP] 104 | > BrazilCEP is designed for on-demand queries in web applications. Bulk querying through scripts or other means is discouraged. 105 | 106 | > [!IMPORTANT] 107 | > BrazilCEP does not guarantee the availability or support of any third-party query APIs. This library serves as a convenient interface for accessing these services. 108 | 109 | #### Asynchronous Requests with BrazilCEP 110 | 111 | BrazilCEP (version >= 7.0.0) also supports asynchronous operations , allowing you to retrieve address information for a given CEP without blocking your application. This is particularly useful for web applications or services that require high responsiveness. 112 | 113 | To perform an asynchronous request, use the `async_get_address_from_cep` function: 114 | 115 | ```python 116 | import asyncio 117 | import brazilcep 118 | 119 | async def main(): 120 | address = await brazilcep.async_get_address_from_cep('37503-130') 121 | print(address) 122 | 123 | asyncio.run(main()) 124 | ``` 125 | > [!NOTE] 126 | > This function is asynchronous and must be awaited when called. 127 | > Ensure that your environment supports asynchronous programming before using this function. 128 | 129 | ## Documentation 130 | 131 | Comprehensive documentation for BrazilCEP is available on [ReadTheDocs](https://brazilcep.readthedocs.io/). 132 | 133 | ## Contribute 134 | 135 | To contribute, follow the guidelines outlined [here](https://brazilcep.readthedocs.io/contributing.html). 136 | 137 | ## Credits 138 | 139 | Copyright (C) 2016-2024 by Michell Stuttgart 140 | -------------------------------------------------------------------------------- /brazilcep/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | BrazilCEP 3 | ~~~~~~~~~ 4 | 5 | BrazilCEP is a lightweight and user-friendly Python library for querying 6 | CEP (Brazilian postal codes) data. 7 | 8 | The library provides a unified and consistent interface for accessing 9 | multiple CEP lookup services, making it easier to integrate Python 10 | applications with these services. 11 | 12 | Features: 13 | - Simple and intuitive API. 14 | - Support for both synchronous and asynchronous queries. 15 | - Compatible with multiple CEP web services. 16 | 17 | :copyright: (c) 2023 by Michell Stuttgart. 18 | :license: MIT, see LICENSE for more details. 19 | """ 20 | 21 | from .__version__ import __version__ 22 | from .client import WebService, async_get_address_from_cep, get_address_from_cep 23 | 24 | __all__ = [ 25 | "get_address_from_cep", 26 | "async_get_address_from_cep", 27 | "WebService", 28 | "__version__", 29 | ] 30 | -------------------------------------------------------------------------------- /brazilcep/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "7.0.0" 2 | -------------------------------------------------------------------------------- /brazilcep/apicep.py: -------------------------------------------------------------------------------- 1 | """ 2 | brazilcep.apicep 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | This module provides an adapter for the BrazilCEP library to interact with the ApiCEP API. 6 | 7 | The ApiCEP API is a RESTful web service that allows querying Brazilian postal codes (CEPs) 8 | to retrieve address information. This module includes both synchronous and asynchronous 9 | functions for fetching address data, handling API responses, and formatting the results. 10 | 11 | Features: 12 | - Fetch address data using a CEP (postal code). 13 | - Handle API responses and errors gracefully. 14 | - Support for both synchronous and asynchronous operations. 15 | 16 | :copyright: (c) 2023 by Michell Stuttgart. 17 | :license: MIT, see LICENSE for more details. 18 | """ 19 | 20 | import json 21 | from typing import Union 22 | 23 | from brazilcep import exceptions, utils 24 | 25 | URL = "https://ws.apicep.com/cep/{}.json" 26 | 27 | 28 | def __format_response(response: dict) -> dict: 29 | """ 30 | Formats the response from the ApiCEP API into a standardized address dictionary. 31 | 32 | This function processes the raw JSON response from the ApiCEP API and extracts 33 | relevant address information, ensuring that all fields are properly formatted 34 | and stripped of unnecessary whitespace. 35 | 36 | Args: 37 | response (dict): The raw JSON response from the ApiCEP API 38 | 39 | Returns: 40 | A dictionary containing the formatted address data. 41 | 42 | dict: A dictionary containing the formatted address data with the following keys: 43 | - district (str): The neighborhood or district name. 44 | - cep (str): The postal code (CEP). 45 | - city (str): The name of the city. 46 | - street (str): The street name, excluding any additional information. 47 | - uf (str): The state abbreviation (e.g., "SP" for São Paulo). 48 | - complement (str): Additional address details, if available. 49 | """ 50 | 51 | return { 52 | "district": response.get("district") or "", 53 | "cep": response.get("code") or "", 54 | "city": response.get("city") or "", 55 | "street": (response.get("address") or "").split(" - até")[0], 56 | "uf": response.get("state") or "", 57 | "complement": "", 58 | } 59 | 60 | 61 | def __handle_response(status_code: int, text: str): 62 | """ 63 | Handles the API response based on the HTTP status code and response content. 64 | 65 | Args: 66 | status_code (int): The HTTP status code returned by the API. 67 | text (str): The raw response content as a string. 68 | 69 | Raises: 70 | exceptions.BrazilCEPException: If the JSON response cannot be parsed or an unexpected error occurs. 71 | exceptions.InvalidCEP: If the API indicates the provided CEP (postal code) is invalid. 72 | exceptions.BlockedByFlood: If the API blocks the request due to excessive usage (rate limiting). 73 | exceptions.CEPNotFound: If the API indicates the requested CEP (postal code) was not found. 74 | 75 | Returns: 76 | dict: A formatted dictionary containing the parsed and processed response data if the request is successful. 77 | """ 78 | 79 | if status_code == 200: 80 | try: 81 | response_json = json.loads(text) 82 | except json.JSONDecodeError as e: 83 | raise exceptions.BrazilCEPException(f"Failed to parse JSON response: {e}") 84 | 85 | status = response_json.get("status") 86 | message = response_json.get("message") 87 | 88 | if status == 400: 89 | if message == "CEP informado é inválido": 90 | raise exceptions.InvalidCEP() 91 | 92 | if message == "Blocked by flood": 93 | raise exceptions.BlockedByFlood() 94 | 95 | raise exceptions.BrazilCEPException( 96 | f"Unexpected error. Status: {status}, Message: {message}" 97 | ) 98 | 99 | elif status == 404: 100 | raise exceptions.CEPNotFound() 101 | 102 | else: 103 | return __format_response(response_json) 104 | 105 | elif status_code == 429: 106 | raise exceptions.BlockedByFlood() 107 | 108 | raise exceptions.BrazilCEPException( 109 | f"Unexpected error. Status code: {status_code}, Response: {text}" 110 | ) 111 | 112 | 113 | def fetch_address( 114 | cep: str, timeout: Union[None, int] = None, proxies: Union[None, dict] = None 115 | ) -> dict: 116 | """ 117 | Fetch address data from the ApiCEP web service using a given CEP. 118 | 119 | This function sends a synchronous HTTP request to the ApiCEP API to retrieve 120 | address information for the provided CEP (Brazilian postal code). 121 | 122 | Args: 123 | cep (str): The CEP to be searched. 124 | timeout (Union[None, int], optional): The number of seconds to wait for the server to respond. Defaults to None. 125 | proxies (Union[None, dict], optional): A dictionary mapping protocol to the URL of the proxy. Defaults to None. 126 | 127 | Raises: 128 | exceptions.ConnectionError: Raised when a connection error occurs. 129 | exceptions.HTTPError: Raised for HTTP errors. 130 | exceptions.URLRequired: Raised when an invalid URL is used for the request. 131 | exceptions.TooManyRedirects: Raised when too many redirects occur. 132 | exceptions.Timeout: Raised when the request times out. 133 | exceptions.InvalidCEP: Raised for invalid CEP requests. 134 | exceptions.BlockedByFlood: Raised when the API blocks the request due to excessive usage. 135 | exceptions.CEPNotFound: Raised when the requested CEP is not found. 136 | exceptions.BrazilCEPException: Base class for other exceptions. 137 | 138 | Returns: 139 | dict: A dictionary containing the formatted address data. 140 | """ 141 | status_code, text = utils.requests_get(url=URL.format(cep), timeout=timeout, proxies=proxies) 142 | return __handle_response(status_code=status_code, text=text) 143 | 144 | 145 | async def async_fetch_address( 146 | cep: str, timeout: Union[None, int] = None, proxies: Union[None, dict] = None 147 | ) -> dict: 148 | """ 149 | Fetch address data asynchronously from the ApiCEP web service using a given CEP. 150 | 151 | This function sends an asynchronous HTTP request to the ApiCEP API to retrieve 152 | address information for the provided CEP (Brazilian postal code). 153 | 154 | Args: 155 | cep (str): The CEP to be searched. 156 | timeout (Union[None, int], optional): The number of seconds to wait for the server to respond. Defaults to None. 157 | proxies (Union[None, dict], optional): A dictionary mapping protocol to the URL of the proxy. Defaults to None. 158 | 159 | Raises: 160 | exceptions.ConnectionError: Raised when a connection error occurs. 161 | exceptions.HTTPError: Raised for HTTP errors. 162 | exceptions.URLRequired: Raised when an invalid URL is used for the request. 163 | exceptions.TooManyRedirects: Raised when too many redirects occur. 164 | exceptions.Timeout: Raised when the request times out. 165 | exceptions.InvalidCEP: Raised for invalid CEP requests. 166 | exceptions.BlockedByFlood: Raised when the API blocks the request due to excessive usage. 167 | exceptions.CEPNotFound: Raised when the requested CEP is not found. 168 | exceptions.BrazilCEPException: Base class for other exceptions. 169 | 170 | Returns: 171 | dict: A dictionary containing the formatted address data. 172 | """ 173 | 174 | status_code, text = await utils.aiohttp_get(url=URL.format(cep), timeout=timeout) 175 | return __handle_response(status_code=status_code, text=text) 176 | -------------------------------------------------------------------------------- /brazilcep/client.py: -------------------------------------------------------------------------------- 1 | """ 2 | brazilcep.client 3 | ~~~~~~~~~~~~~~~~~ 4 | 5 | This module implements the BrazilCEP client, which provides functionality 6 | to query address information based on Brazilian postal codes (CEP) using 7 | various web services. 8 | 9 | Main Features: 10 | - Synchronous and asynchronous methods to fetch address data. 11 | - Support for multiple web services: ViaCEP, ApiCEP, and OpenCEP. 12 | - Automatic formatting of CEP input to ensure valid queries. 13 | - Customizable timeout and proxy support for HTTP requests. 14 | 15 | :copyright: (c) 2023 by Michell Stuttgart. 16 | :license: MIT, see LICENSE for more details. 17 | """ 18 | 19 | import enum 20 | import re 21 | from typing import Callable, Optional 22 | 23 | from . import apicep, opencep, viacep 24 | 25 | NUMBERS = re.compile(r"[^0-9]") 26 | DEFAULT_TIMEOUT: int = 5 # in seconds 27 | 28 | 29 | class WebService(enum.Enum): 30 | """Enum representing the available web services for CEP queries. 31 | 32 | Attributes: 33 | VIACEP: ViaCEP web service. 34 | APICEP: ApiCEP web service. 35 | OPENCEP: OpenCEP web service. 36 | """ 37 | 38 | VIACEP = 1 39 | APICEP = 2 40 | OPENCEP = 3 41 | 42 | 43 | services: dict[WebService, Callable] = { 44 | WebService.VIACEP: viacep.fetch_address, 45 | WebService.APICEP: apicep.fetch_address, 46 | WebService.OPENCEP: opencep.fetch_address, 47 | } 48 | 49 | async_services: dict[WebService, Callable] = { 50 | WebService.VIACEP: viacep.async_fetch_address, 51 | WebService.APICEP: apicep.async_fetch_address, 52 | WebService.OPENCEP: opencep.async_fetch_address, 53 | } 54 | 55 | 56 | def get_address_from_cep( 57 | cep: str, 58 | webservice: WebService = WebService.OPENCEP, 59 | timeout: Optional[int] = DEFAULT_TIMEOUT, 60 | proxies: Optional[dict] = None, 61 | ) -> dict: 62 | """ 63 | This function queries a specified web service to retrieve address information 64 | based on the provided CEP. It supports multiple web services and allows customization 65 | of request parameters such as timeout and proxy settings. 66 | 67 | Args: 68 | cep (str): The CEP (Brazilian postal code) to query. Must be a valid 8-digit string. 69 | webservice (WebService, optional): The web service to use for the query. Defaults to WebService.OPENCEP. 70 | Supported values are: 71 | - WebService.OPENCEP 72 | - WebService.VIACEP 73 | - WebService.APICEP timeout (Optional[int]): Timeout in seconds for the request. Defaults to 5 seconds. 74 | timeout (Optional[int], optional): The maximum time, in seconds, to wait for a response. Defaults to 5 seconds. 75 | proxies (Optional[dict], optional): A dictionary mapping protocols (e.g., "http", "https") to proxy URLs. 76 | 77 | Raises: 78 | ValueError: If the provided CEP is invalid (e.g., not properly formatted or not 8 digits). 79 | KeyError: If the specified webservice is not a valid WebService enum value. 80 | 81 | Returns: 82 | dict: A dictionary containing the address data corresponding to the queried CEP. 83 | 84 | Examples: 85 | >>> address = get_address_from_cep("01001000", webservice=WebService.VIACEP) 86 | >>> print(address) 87 | { 88 | "cep": "01001000", 89 | "street": "Praça da Sé", 90 | "complement": "lado ímpar", 91 | "disctric": "Sé", 92 | "city": "São Paulo", 93 | "uf": "SP", 94 | } 95 | """ 96 | return services[webservice](_format_cep(cep), timeout=timeout, proxies=proxies) 97 | 98 | 99 | async def async_get_address_from_cep( 100 | cep: str, 101 | webservice: WebService = WebService.OPENCEP, 102 | timeout: Optional[int] = DEFAULT_TIMEOUT, 103 | proxies: Optional[dict] = None, 104 | ) -> dict: 105 | """ 106 | Asynchronously fetch the address associated with a given CEP (Brazilian postal code). 107 | 108 | This function queries a specified web service to retrieve address information 109 | based on the provided CEP. It supports multiple web services and allows customization 110 | of request parameters such as timeout and proxy settings. 111 | 112 | Args: 113 | cep (str): The CEP (Brazilian postal code) to query. Must be a valid 8-digit string. 114 | webservice (WebService, optional): The web service to use for the query. Defaults to WebService.OPENCEP. 115 | Supported values are: 116 | - WebService.OPENCEP 117 | - WebService.VIACEP 118 | - WebService.APICEP 119 | timeout (Optional[int], optional): The maximum time, in seconds, to wait for a response. Defaults to 5 seconds. 120 | proxies (Optional[dict], optional): A dictionary mapping protocols (e.g., "http", "https") to proxy URLs. 121 | 122 | Raises: 123 | ValueError: If the provided CEP is invalid (e.g., not properly formatted or not 8 digits). 124 | KeyError: If the specified webservice is not a valid WebService enum value. 125 | 126 | Returns: 127 | dict: A dictionary containing the address data corresponding to the queried CEP. 128 | 129 | Examples: 130 | >>> address = await async_get_address_from_cep("01001000", webservice=WebService.VIACEP) 131 | >>> print(address) 132 | { 133 | "cep": "01001000", 134 | "street": "Praça da Sé", 135 | "complement": "lado ímpar", 136 | "disctric": "Sé", 137 | "city": "São Paulo", 138 | "uf": "SP", 139 | } 140 | """ 141 | return await async_services[webservice](_format_cep(cep), timeout=timeout, proxies=proxies) 142 | 143 | 144 | def _format_cep(cep: str) -> str: 145 | """ 146 | Format a Brazilian postal code (CEP) by removing non-numeric characters and validating its structure. 147 | 148 | This function ensures that the input is a valid CEP by stripping out any characters that are not digits. 149 | 150 | Args: 151 | cep (str): CEP to be formatted. 152 | 153 | Raises: 154 | ValueError: If the input is not a string or is empty. 155 | 156 | Returns: 157 | str: A string containing the formatted CEP. 158 | """ 159 | if not isinstance(cep, str) or not cep.strip(): 160 | raise ValueError("CEP must be a non-empty string.") 161 | 162 | return NUMBERS.sub("", cep) 163 | -------------------------------------------------------------------------------- /brazilcep/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | brazilcep.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~ 4 | 5 | This module defines custom exceptions used in the BrazilCEP library. 6 | 7 | These exceptions are designed to handle various error scenarios that may occur 8 | when interacting with the BrazilCEP library, such as invalid input, connection 9 | issues, or HTTP-related errors. 10 | 11 | Classes: 12 | - BrazilCEPException: Base class for all exceptions in the BrazilCEP library. 13 | - InvalidCEP: Raised when an invalid CEP (postal code) is provided. 14 | - CEPNotFound: Raised when a requested CEP cannot be found. 15 | - BlockedByFlood: Raised when requests are blocked due to excessive traffic. 16 | - ConnectionError: Raised when a connection error occurs. 17 | - HTTPError: Raised when an HTTP error is encountered. 18 | - URLRequired: Raised when an invalid or missing URL is used for a request. 19 | - TooManyRedirects: Raised when too many redirects occur during a request. 20 | - Timeout: Raised when a request exceeds the allowed time limit. 21 | - JSONLoadError: Raised when a JSON parsing operation fails. 22 | 23 | :copyright: (c) 2023 by Michell Stuttgart. 24 | :license: MIT, see LICENSE for more details. 25 | """ 26 | 27 | 28 | class BrazilCEPException(Exception): 29 | """ 30 | Base class for exceptions 31 | """ 32 | 33 | 34 | class InvalidCEP(BrazilCEPException): 35 | """ 36 | Exception raised for invalid CEP requests 37 | """ 38 | 39 | 40 | class CEPNotFound(BrazilCEPException): 41 | """ 42 | Exception raised when a CEP is not found 43 | """ 44 | 45 | 46 | class BlockedByFlood(BrazilCEPException): 47 | """ 48 | Exception raised due to a flood of requests being blocked 49 | """ 50 | 51 | 52 | class ConnectionError(BrazilCEPException): 53 | """ 54 | Exception raised by a BrazilCEP connection error 55 | """ 56 | 57 | 58 | class HTTPError(BrazilCEPException): 59 | """ 60 | Exception raised by HTTP error 61 | """ 62 | 63 | 64 | class URLRequired(BrazilCEPException): 65 | """ 66 | Exception raised for using an invalid URL to make a request 67 | """ 68 | 69 | 70 | class TooManyRedirects(BrazilCEPException): 71 | """ 72 | Exception raised by too many redirects 73 | """ 74 | 75 | 76 | class Timeout(BrazilCEPException): 77 | """ 78 | Exception raised when a request times out 79 | """ 80 | 81 | 82 | class JSONLoadError(BrazilCEPException): 83 | """ 84 | Exception raised when a JSON loading operation fails 85 | """ 86 | -------------------------------------------------------------------------------- /brazilcep/opencep.py: -------------------------------------------------------------------------------- 1 | """ 2 | brazilcep.opencep 3 | ~~~~~~~~~~~~~~~~~ 4 | 5 | This module provides an adapter for the BrazilCEP library to interact with the OpenCEP API. 6 | 7 | The OpenCEP API is a RESTful web service that allows querying Brazilian postal codes (CEPs) 8 | to retrieve address information. This module includes both synchronous and asynchronous 9 | functions for fetching address data, handling API responses, and formatting the results. 10 | 11 | Features: 12 | - Fetch address data using a CEP (postal code). 13 | - Handle API responses and errors gracefully. 14 | - Support for both synchronous and asynchronous operations. 15 | 16 | :copyright: (c) 2023 by Michell Stuttgart. 17 | :license: MIT, see LICENSE for more details. 18 | """ 19 | 20 | import json 21 | from typing import Union 22 | 23 | from . import exceptions, utils 24 | 25 | URL = "https://opencep.com/v1/{}" 26 | 27 | 28 | def __format_response(response: dict) -> dict: 29 | """ 30 | Formats the response from the OpenCEP API into a standardized address dictionary. 31 | 32 | This function processes the raw JSON response from the OpenCEP API and extracts 33 | relevant address fields, ensuring that all string values are stripped of leading 34 | and trailing whitespace. If a field is missing in the response, it defaults to 35 | an empty string. 36 | 37 | Args: 38 | response (dict): The raw JSON response from the OpenCEP API. 39 | 40 | Returns: 41 | 42 | dict: A dictionary containing the following standardized address fields: 43 | - district (str): The neighborhood or district name (from "bairro"). 44 | - cep (str): The postal code (from "cep"). 45 | - city (str): The city name (from "localidade"). 46 | - street (str): The street name (from "logradouro"). 47 | - uf (str): The state abbreviation (from "uf"). 48 | - complement (str): Additional address details (from "complemento"). 49 | """ 50 | 51 | return { 52 | "district": response.get("bairro") or "", 53 | "cep": response.get("cep") or "", 54 | "city": response.get("localidade") or "", 55 | "street": response.get("logradouro") or "", 56 | "uf": response.get("uf") or "", 57 | "complement": "", 58 | } 59 | 60 | 61 | def __handle_response(status_code: int, text: str) -> dict: 62 | """ 63 | Handles the API response based on the status code and response content. 64 | 65 | Args: 66 | status_code (int): The HTTP status code returned by the API. 67 | text (str): The raw response content from the API. 68 | 69 | Raises: 70 | exceptions.CEPNotFound: If the CEP is not found (404 status code). 71 | exceptions.BrazilCEPException: For any other non-successful status codes. 72 | 73 | Returns: 74 | dict: A formatted dictionary containing address information if the request is successful. 75 | """ 76 | try: 77 | if status_code == 200: 78 | response_json = json.loads(text) 79 | return __format_response(response_json) 80 | 81 | if status_code == 404: 82 | raise exceptions.CEPNotFound() 83 | 84 | raise exceptions.BrazilCEPException(f"Unexpected error. Status code: {status_code}") 85 | 86 | except json.JSONDecodeError as e: 87 | raise exceptions.BrazilCEPException(f"Failed to parse JSON response: {e}") 88 | 89 | 90 | def fetch_address( 91 | cep: str, timeout: Union[None, int] = None, proxies: Union[None, dict] = None 92 | ) -> dict: 93 | """ 94 | Fetch address data from the OpenCEP API using a given CEP. 95 | 96 | This function queries the OpenCEP REST API to retrieve address information 97 | for a given Brazilian postal code (CEP). It handles various exceptions and 98 | returns a standardized address dictionary. 99 | 100 | Args: 101 | cep (str): The CEP to be searched. 102 | timeout (Union[None, int], optional): The number of seconds to wait for the server to respond. Defaults to None. 103 | proxies (Union[None, dict], optional): A dictionary mapping protocol to the URL of the proxy. Defaults to None. 104 | 105 | Raises: 106 | exceptions.ConnectionError: Raised for connection errors. 107 | exceptions.HTTPError: Raised for HTTP errors. 108 | exceptions.URLRequired: Raised for invalid URLs. 109 | exceptions.TooManyRedirects: Raised for too many redirects. 110 | exceptions.Timeout: Raised when the request times out. 111 | exceptions.InvalidCEP: Raised for invalid CEP requests. 112 | exceptions.CEPNotFound: Raised when the CEP is not found. 113 | exceptions.BrazilCEPException: Base class for other exceptions. 114 | 115 | Returns: 116 | dict: A dictionary containing standardized address data. 117 | """ 118 | status_code, text = utils.requests_get(url=URL.format(cep), timeout=timeout, proxies=proxies) 119 | return __handle_response(status_code=status_code, text=text) 120 | 121 | 122 | async def async_fetch_address( 123 | cep: str, timeout: Union[None, int] = None, proxies: Union[None, dict] = None 124 | ) -> dict: 125 | """ 126 | Fetch address data asynchronously from the OpenCEP API using a given CEP. 127 | 128 | This function queries the OpenCEP REST API asynchronously to retrieve address information 129 | for a given Brazilian postal code (CEP). It handles various exceptions and 130 | returns a standardized address dictionary. 131 | 132 | Args: 133 | cep (str): The CEP to be searched. 134 | timeout (Union[None, int], optional): The number of seconds to wait for the server to respond. Defaults to None. 135 | proxies (Union[None, dict], optional): A dictionary mapping protocol to the URL of the proxy. Defaults to None. 136 | 137 | Raises: 138 | exceptions.ConnectionError: Raised for connection errors. 139 | exceptions.HTTPError: Raised for HTTP errors. 140 | exceptions.URLRequired: Raised for invalid URLs. 141 | exceptions.TooManyRedirects: Raised for too many redirects. 142 | exceptions.Timeout: Raised when the request times out. 143 | exceptions.InvalidCEP: Raised for invalid CEP requests. 144 | exceptions.CEPNotFound: Raised when the CEP is not found. 145 | exceptions.BrazilCEPException: Base class for other exceptions. 146 | 147 | Returns: 148 | dict: A dictionary containing standardized address data. 149 | """ 150 | status_code, text = await utils.aiohttp_get(url=URL.format(cep), timeout=timeout) 151 | return __handle_response(status_code=status_code, text=text) 152 | -------------------------------------------------------------------------------- /brazilcep/py.typed: -------------------------------------------------------------------------------- 1 | # Required by PEP 561 to show that we have static types 2 | # See https://www.python.org/dev/peps/pep-0561/#packaging-type-information. 3 | # Enables mypy to discover type hints. 4 | -------------------------------------------------------------------------------- /brazilcep/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import aiohttp 4 | import requests 5 | 6 | from . import exceptions 7 | 8 | 9 | async def aiohttp_get( 10 | url: str, timeout: Optional[int] = None, raise_for_status: bool = False 11 | ) -> tuple[int, str]: 12 | """ 13 | Perform an asynchronous GET request using aiohttp. 14 | 15 | Args: 16 | url (str): The URL to request. 17 | timeout (Optional[int]): Timeout in seconds for the request. Defaults to None. 18 | raise_for_status (bool): Whether to raise an exception for HTTP errors. Deafults to False. 19 | 20 | Returns: 21 | tuple[int, str]: The HTTP status code and response text. 22 | 23 | Raises: 24 | exceptions.Timeout: Raised when the request times out. 25 | exceptions.ConnectionError: Raised for connection-related errors. 26 | exceptions.BrazilCEPException: Raised for any general exception during the request. 27 | """ 28 | try: 29 | async with aiohttp.ClientSession() as session: 30 | client_timeout = aiohttp.ClientTimeout(total=timeout or 30) 31 | 32 | async with session.get(url, timeout=client_timeout) as response: 33 | if raise_for_status: 34 | response.raise_for_status() 35 | 36 | response_text = await response.text() 37 | return response.status, response_text 38 | 39 | except aiohttp.ConnectionTimeoutError as exc: 40 | raise exceptions.Timeout(exc) 41 | 42 | except aiohttp.ClientConnectionError as exc: 43 | raise exceptions.ConnectionError(exc) 44 | 45 | except (aiohttp.ClientError, Exception) as exc: 46 | raise exceptions.BrazilCEPException(exc) 47 | 48 | 49 | def requests_get( 50 | url: str, timeout: Optional[int] = None, proxies: Optional[dict] = None 51 | ) -> tuple[int, str]: 52 | """ 53 | Perform a synchronous GET request using requests. 54 | 55 | Args: 56 | url (str): The URL to request. 57 | timeout (Optional[int]): Timeout in seconds for the request. Defaults to None. 58 | proxies (Optional[dict]): Proxy configuration for the request. Defaults to None. 59 | 60 | Returns: 61 | tuple[int, str]: The HTTP status code and response text. 62 | 63 | Raises: 64 | exceptions.ConnectionError: Raised for connection errors. 65 | exceptions.HTTPError: Raised for HTTP errors. 66 | exceptions.URLRequired: Raised for invalid URLs. 67 | exceptions.TooManyRedirects: Raised for too many redirects. 68 | exceptions.Timeout: Raised for request timeouts. 69 | exceptions.BrazilCEPException: Raised for any general exception during the request. 70 | """ 71 | try: 72 | response = requests.get(url, timeout=timeout, proxies=proxies) 73 | return response.status_code, response.text 74 | 75 | except requests.exceptions.ConnectionError as exc: 76 | raise exceptions.ConnectionError(exc) 77 | 78 | except requests.exceptions.HTTPError as exc: 79 | raise exceptions.HTTPError(exc) 80 | 81 | except requests.exceptions.URLRequired as exc: 82 | raise exceptions.URLRequired(exc) 83 | 84 | except requests.exceptions.TooManyRedirects as exc: 85 | raise exceptions.TooManyRedirects(exc) 86 | 87 | except requests.exceptions.Timeout as exc: 88 | raise exceptions.Timeout(exc) 89 | 90 | except Exception as exc: 91 | raise exceptions.BrazilCEPException(exc) 92 | -------------------------------------------------------------------------------- /brazilcep/viacep.py: -------------------------------------------------------------------------------- 1 | """ 2 | brazilcep.viacep 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | This module provides an adapter for the BrazilCEP library to interact with the ViaCEP API. 6 | 7 | The ViaCEP API is a RESTful web service that allows querying Brazilian postal codes (CEPs) 8 | to retrieve address information. This module includes both synchronous and asynchronous 9 | functions for fetching address data, handling API responses, and formatting the results. 10 | 11 | Features: 12 | - Fetch address data using a CEP (postal code). 13 | - Handle API responses and errors gracefully. 14 | - Support for both synchronous and asynchronous operations. 15 | 16 | :copyright: (c) 2023 by Michell Stuttgart. 17 | :license: MIT, see LICENSE for more details. 18 | """ 19 | 20 | import json 21 | from typing import Union 22 | 23 | from . import exceptions, utils 24 | 25 | URL = "http://www.viacep.com.br/ws/{}/json" 26 | 27 | 28 | def __format_response(response: dict) -> dict: 29 | """ 30 | Transforms the raw ViaCEP API response into a standardized address dictionary. 31 | 32 | This function extracts and formats specific fields from the API response, 33 | ensuring that all string values are stripped of leading and trailing whitespace. 34 | 35 | Args: 36 | response (dict): The raw JSON response from the ViaCEP API. 37 | 38 | Returns: 39 | dict: A dictionary containing the following standardized address fields: 40 | - district (str): The neighborhood or district name. 41 | - cep (str): The postal code (CEP). 42 | - city (str): The city name. 43 | - street (str): The street name. 44 | - uf (str): The state abbreviation (UF). 45 | - complement (str): Additional address information, if available. 46 | """ 47 | 48 | return { 49 | "district": response.get("bairro") or "", 50 | "cep": response.get("cep") or "", 51 | "city": response.get("localidade") or "", 52 | "street": response.get("logradouro") or "", 53 | "uf": response.get("uf") or "", 54 | "complement": response.get("complemento") or "", 55 | } 56 | 57 | 58 | def __handle_response(status_code: int, text: str) -> dict: 59 | """ 60 | Handles the API response based on the status code and response content. 61 | 62 | Args: 63 | status_code (int): The HTTP status code returned by the API. 64 | text (str): The raw response content from the API. 65 | 66 | Raises: 67 | exceptions.CEPNotFound: If the CEP is not found in the API response. 68 | exceptions.InvalidCEP: If the provided CEP is invalid. 69 | exceptions.BrazilCEPException: For any other errors with the API response. 70 | 71 | Returns: 72 | dict: A formatted dictionary containing address information. 73 | """ 74 | if status_code == 200: 75 | try: 76 | response_json = json.loads(text) 77 | except json.JSONDecodeError as e: 78 | raise exceptions.BrazilCEPException(f"Invalid JSON response: {e}") 79 | 80 | if response_json.get("erro"): 81 | raise exceptions.CEPNotFound("CEP not found in the API response.") 82 | 83 | return __format_response(response_json) 84 | 85 | if status_code == 400: 86 | raise exceptions.InvalidCEP("The provided CEP is invalid.") 87 | 88 | raise exceptions.BrazilCEPException(f"Unexpected error. Status code: {status_code}") 89 | 90 | 91 | def fetch_address( 92 | cep: str, timeout: Union[None, int] = None, proxies: Union[None, dict] = None 93 | ) -> dict: 94 | """ 95 | Fetch address information for a given CEP using the ViaCEP API. 96 | 97 | This function sends a synchronous HTTP request to the ViaCEP API to retrieve 98 | address information for the specified CEP (postal code). 99 | 100 | Args: 101 | cep (str): The CEP to be searched. 102 | timeout (Union[None, int], optional): The number of seconds to wait for the server to respond. Defaults to None. 103 | proxies (Union[None, dict], optional): A dictionary mapping protocol to the URL of the proxy. Defaults to None. 104 | 105 | Raises: 106 | exceptions.ConnectionError: Raised for connection errors. 107 | exceptions.HTTPError: Raised for HTTP errors. 108 | exceptions.URLRequired: Raised if an invalid URL is used for the request. 109 | exceptions.TooManyRedirects: Raised if too many redirects occur. 110 | exceptions.Timeout: Raised if the request times out. 111 | exceptions.InvalidCEP: Raised if the provided CEP is invalid. 112 | exceptions.CEPNotFound: Raised if the CEP is not found in the API response. 113 | exceptions.BrazilCEPException: Base class for other exceptions. 114 | 115 | Returns: 116 | dict: A dictionary containing standardized address information. 117 | """ 118 | status_code, text = utils.requests_get(url=URL.format(cep), timeout=timeout, proxies=proxies) 119 | return __handle_response(status_code=status_code, text=text) 120 | 121 | 122 | async def async_fetch_address( 123 | cep: str, timeout: Union[None, int] = None, proxies: Union[None, dict] = None 124 | ) -> dict: 125 | """ 126 | Fetch address information for a given CEP asynchronously using the ViaCEP API. 127 | 128 | This function sends an asynchronous HTTP request to the ViaCEP API to retrieve 129 | address information for the specified CEP (postal code). 130 | 131 | Args: 132 | cep (str): The CEP to be searched. 133 | timeout (Union[None, int], optional): The number of seconds to wait for the server to respond. Defaults to None. 134 | proxies (Union[None, dict], optional): A dictionary mapping protocol to the URL of the proxy. Defaults to None. 135 | 136 | Raises: 137 | exceptions.ConnectionError: Raised for connection errors. 138 | exceptions.HTTPError: Raised for HTTP errors. 139 | exceptions.URLRequired: Raised if an invalid URL is used for the request. 140 | exceptions.TooManyRedirects: Raised if too many redirects occur. 141 | exceptions.Timeout: Raised if the request times out. 142 | exceptions.InvalidCEP: Raised if the provided CEP is invalid. 143 | exceptions.CEPNotFound: Raised if the CEP is not found in the API response. 144 | exceptions.BrazilCEPException: Base class for other exceptions. 145 | 146 | Returns: 147 | dict: A dictionary containing standardized address information. 148 | """ 149 | status_code, text = await utils.aiohttp_get(url=URL.format(cep), timeout=timeout) 150 | return __handle_response(status_code=status_code, text=text) 151 | -------------------------------------------------------------------------------- /docs/source/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstuttgart/brazilcep/6f7361307a1347731ecbac6aa38f7f31c3be5625/docs/source/_static/favicon.ico -------------------------------------------------------------------------------- /docs/source/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstuttgart/brazilcep/6f7361307a1347731ecbac6aa38f7f31c3be5625/docs/source/_static/logo.png -------------------------------------------------------------------------------- /docs/source/_templates/sidebar-intro.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

6 | BrazilCEP is Python library designed to query CEP (brazilian zip codes) data 7 |

8 | 9 |

10 | 18 |

19 | -------------------------------------------------------------------------------- /docs/source/_templates/sidebar-links.html: -------------------------------------------------------------------------------- 1 |

Useful Links

2 | 11 |
12 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | ============= 4 | API Reference 5 | ============= 6 | 7 | .. module:: brazilcep 8 | 9 | This section of the documentation provides details about all the interfaces of BrazilCEP. 10 | 11 | Client 12 | ------ 13 | 14 | The primary functionality of BrazilCEP can be accessed through the following function: 15 | 16 | .. autofunction:: get_address_from_cep 17 | .. autofunction:: async_get_address_from_cep 18 | 19 | Exceptions 20 | ---------- 21 | 22 | The following exceptions are available in BrazilCEP: 23 | 24 | .. autoexception:: brazilcep.exceptions.BrazilCEPException 25 | .. autoexception:: brazilcep.exceptions.ConnectionError 26 | .. autoexception:: brazilcep.exceptions.HTTPError 27 | .. autoexception:: brazilcep.exceptions.URLRequired 28 | .. autoexception:: brazilcep.exceptions.TooManyRedirects 29 | .. autoexception:: brazilcep.exceptions.Timeout 30 | .. autoexception:: brazilcep.exceptions.InvalidCEP 31 | .. autoexception:: brazilcep.exceptions.CEPNotFound 32 | .. autoexception:: brazilcep.exceptions.BlockedByFlood 33 | 34 | Webservices 35 | ----------- 36 | 37 | BrazilCEP supports the following webservice integrations: 38 | 39 | .. autofunction:: brazilcep.apicep.fetch_address 40 | .. autofunction:: brazilcep.opencep.fetch_address 41 | .. autofunction:: brazilcep.viacep.fetch_address 42 | 43 | Migration from PyCEPCorreios 44 | ---------------------------- 45 | 46 | **BrazilCEP** is the new name for the former **PyCEPCorreios**. Migrating your code is simple and requires minimal changes. 47 | 48 | 1. Update the `import` statements from: 49 | 50 | .. code-block:: python 51 | 52 | import pycepcorreios 53 | 54 | to: 55 | 56 | .. code-block:: python 57 | 58 | import brazilcep 59 | 60 | 2. Adjust the *keys* in the query results returned by the `get_address_from_cep` function. 61 | 62 | The keys have been translated to English. Below is an example of the old and new formats: 63 | 64 | Old format: 65 | 66 | .. code-block:: python 67 | 68 | get_address_from_cep('37503-130') 69 | { 70 | 'bairro': 'rua abc', 71 | 'cep': '37503130', 72 | 'cidade': 'city ABC', 73 | 'logradouro': 'str', 74 | 'uf': 'str', 75 | 'complemento': 'str', 76 | } 77 | 78 | New format: 79 | 80 | .. code-block:: python 81 | 82 | get_address_from_cep('37503-130') 83 | { 84 | 'district': 'rua abc', 85 | 'cep': '37503130', 86 | 'city': 'city ABC', 87 | 'street': 'str', 88 | 'uf': 'str', 89 | 'complement': 'str', 90 | } 91 | 92 | 3. Note that the following exception has been removed: 93 | 94 | * `BaseException` 95 | -------------------------------------------------------------------------------- /docs/source/changes.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../CHANGELOG 2 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | import logging 8 | import os 9 | import sys 10 | from datetime import datetime 11 | 12 | # -- Path setup -------------------------------------------------------------- 13 | 14 | # If extensions (or modules to document with autodoc) are in another directory, 15 | # add these directories to sys.path here. If the directory is relative to the 16 | # documentation root, use os.path.abspath to make it absolute, like shown here. 17 | # 18 | 19 | sys.path.insert(0, os.path.abspath("../../")) 20 | 21 | import brazilcep 22 | 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = "BrazilCEP" 26 | copyright = f"{datetime.today().year}, Michell Stuttgart" 27 | author = "Michell Stuttgart" 28 | version = brazilcep.__version__ 29 | release = brazilcep.__version__ 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = [ 38 | "sphinx.ext.autodoc", 39 | "sphinx.ext.napoleon", 40 | "sphinx.ext.intersphinx", 41 | "sphinx.ext.viewcode", 42 | "sphinx.ext.doctest", 43 | "sphinx_copybutton", 44 | "sphinx_autodoc_typehints", 45 | ] 46 | 47 | # Tell myst-parser to assign header anchors for h1-h3. 48 | myst_heading_anchors = 3 49 | 50 | # suppress_warnings = ["myst.header"] 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ["_templates"] 54 | 55 | # List of patterns, relative to source directory, that match files and 56 | # directories to ignore when looking for source files. 57 | # This pattern also affects html_static_path and html_extra_path. 58 | exclude_patterns = ["_build"] 59 | 60 | source_suffix = [".rst", ".md"] 61 | 62 | intersphinx_mapping = { 63 | "python": ("https://docs.python.org/3", None), 64 | # Uncomment these if you use them in your codebase: 65 | # "torch": ("https://pytorch.org/docs/stable", None), 66 | # "datasets": ("https://huggingface.co/docs/datasets/master/en", None), 67 | # "transformers": ("https://huggingface.co/docs/transformers/master/en", None), 68 | } 69 | 70 | # By default, sort documented members by type within classes and modules. 71 | autodoc_member_order = "groupwise" 72 | 73 | # Include default values when documenting parameter types. 74 | typehints_defaults = "comma" 75 | 76 | 77 | # -- Options for HTML output ------------------------------------------------- 78 | 79 | # The theme to use for HTML and HTML Help pages. See the documentation for 80 | # a list of builtin themes. 81 | # 82 | html_theme = "alabaster" 83 | 84 | html_title = f"brazilcep v{brazilcep.__version__}" 85 | 86 | # Add any paths that contain custom static files (such as style sheets) here, 87 | # relative to this directory. They are copied after the builtin static files, 88 | # so a file named "default.css" will overwrite the builtin "default.css". 89 | html_static_path = ["_static"] 90 | 91 | # html_css_files = ["css/custom.css"] 92 | 93 | html_favicon = "_static/favicon.ico" 94 | 95 | html_theme_options = { 96 | "logo": "logo.png", 97 | "show_powered_by": False, 98 | "github_user": "mstuttgart", 99 | "github_repo": "brazilcep", 100 | "github_banner": True, 101 | "show_related": False, 102 | "note_bg": "#FFF59C", 103 | } 104 | 105 | # Custom sidebar templates, maps document names to template names. 106 | html_sidebars = { 107 | # "index": ["sidebar-intro.html"], 108 | "**": [ 109 | 'sidebar-intro.html', 110 | 'globaltoc.html', 111 | 'sidebar-links.html', 112 | 'searchbox.html', 113 | ], 114 | } 115 | 116 | # -- Hack to get rid of stupid warnings from sphinx_autodoc_typehints -------- 117 | 118 | 119 | class ShutupSphinxAutodocTypehintsFilter(logging.Filter): 120 | def filter(self, record: logging.LogRecord) -> bool: 121 | if "Cannot resolve forward reference" in record.msg: 122 | return False 123 | return True 124 | 125 | 126 | logging.getLogger("sphinx.sphinx_autodoc_typehints").addFilter(ShutupSphinxAutodocTypehintsFilter()) 127 | -------------------------------------------------------------------------------- /docs/source/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../CONTRIBUTING 2 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | 2 | BrazilCEP: Query CEP Easily™ 3 | ============================ 4 | 5 | .. image:: https://static.pepy.tech/badge/brazilcep/month 6 | :target: https://pepy.tech/project/brazilcep/ 7 | :alt: BrazilCEP Downloads Per Month Badge 8 | 9 | .. image:: https://img.shields.io/pypi/l/brazilcep.svg 10 | :target: https://pypi.org/project/brazilcep/ 11 | :alt: License Badge 12 | 13 | .. image:: https://img.shields.io/pypi/v/brazilcep.svg 14 | :target: https://pypi.org/project/brazilcep/ 15 | :alt: BrazilCEP Version Badge 16 | 17 | .. image:: https://img.shields.io/pypi/pyversions/brazilcep.svg 18 | :target: https://pypi.org/project/brazilcep/ 19 | :alt: Python Version Support Badge 20 | 21 | **BrazilCEP** is a minimalist and easy-to-use Python library designed to query CEP (Postal Address Code) data. 22 | 23 | --- 24 | 25 | Quick Example 26 | ------------- 27 | 28 | Here’s a simple usage example:: 29 | 30 | >>> import brazilcep 31 | >>> brazilcep.get_address_from_cep('37503-130') 32 | { 33 | 'district': 'rua abc', 34 | 'cep': '37503130', 35 | 'city': 'city ABC', 36 | 'street': 'str', 37 | 'uf': 'str', 38 | 'complement': 'str', 39 | } 40 | 41 | Its goal is to provide a unified query interface for multiple search services, simplifying the integration of Python applications with these services. 42 | 43 | Starting from version 7.0.0, **BrazilCEP** introduces support for asynchronous operations. 44 | 45 | Supported CEP APIs 46 | ------------------ 47 | 48 | Currently, BrazilCEP supports the following CEP APIs: 49 | 50 | - `ViaCEP `_ 51 | - `ApiCEP (WideNet) `_ 52 | - `OpenCEP `_ 53 | 54 | What is CEP? 55 | ------------ 56 | 57 | **CEP** or **Código de Endereçamento Postal** (*Postal Address Code*) is a system of numeric codes created, maintained, and organized by *Correios do Brasil* to streamline address organization and the delivery of letters and parcels. 58 | 59 | Documentation Overview 60 | ---------------------- 61 | 62 | User Guide 63 | ~~~~~~~~~~ 64 | 65 | This section provides background information and step-by-step instructions for using BrazilCEP. 66 | 67 | .. toctree:: 68 | :maxdepth: 2 69 | 70 | user/install 71 | user/quickstart 72 | 73 | API Documentation 74 | ~~~~~~~~~~~~~~~~~ 75 | 76 | For details on specific functions, classes, or methods, refer to this section. 77 | 78 | .. toctree:: 79 | :maxdepth: 2 80 | 81 | api 82 | 83 | Contributor Guide 84 | ~~~~~~~~~~~~~~~~~ 85 | 86 | If you want to contribute to the project, this section provides all the necessary guidelines. 87 | 88 | .. toctree:: 89 | :maxdepth: 3 90 | 91 | contributing 92 | 93 | Release History 94 | ~~~~~~~~~~~~~~~ 95 | 96 | For a detailed changelog of the project, refer to this section. 97 | 98 | .. toctree:: 99 | :maxdepth: 1 100 | 101 | changes 102 | 103 | -------------------------------------------------------------------------------- /docs/source/user/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installation 4 | ============ 5 | 6 | This section of the documentation explains how to install **BrazilCEP**. 7 | 8 | Supported Python Versions 9 | ------------------------- 10 | 11 | **BrazilCEP** supports the following Python versions: 12 | 13 | .. image:: https://img.shields.io/pypi/pyversions/brazilcep.svg 14 | :target: https://pypi.org/project/brazilcep/ 15 | :alt: Python Version Support Badge 16 | 17 | Install from PyPI 18 | ----------------- 19 | 20 | To install **BrazilCEP** from PyPI, run the following command in your terminal:: 21 | 22 | pip install brazilcep 23 | 24 | Install from Source Code 25 | ------------------------ 26 | 27 | The source code for **BrazilCEP** is hosted on GitHub and is always available for download. 28 | 29 | 1. Clone the public repository:: 30 | 31 | git clone https://github.com/mstuttgart/brazilcep.git 32 | 33 | Or download the `tarball `_:: 34 | 35 | curl -OL https://github.com/mstuttgart/brazilcep/tarball/main 36 | 37 | 2. Navigate to the project directory:: 38 | 39 | cd brazilcep 40 | 41 | 3. Install the package in editable mode:: 42 | 43 | pip install -e . 44 | 45 | You can now use **BrazilCEP** in your Python projects. 46 | -------------------------------------------------------------------------------- /docs/source/user/quickstart.rst: -------------------------------------------------------------------------------- 1 | .. _quickstart: 2 | 3 | Quickstart 4 | ========== 5 | 6 | .. module:: brazilcep.client 7 | 8 | Eager to get started? This page provides a quick introduction to using BrazilCEP. 9 | 10 | First, ensure that: 11 | 12 | * BrazilCEP is :ref:`installed ` 13 | 14 | Let's dive into some simple examples. 15 | 16 | Make a CEP Request 17 | ------------------ 18 | 19 | Making a request with BrazilCEP is straightforward. 20 | 21 | Start by importing the BrazilCEP module:: 22 | 23 | >>> import brazilcep 24 | 25 | Next, use the `get_address_from_cep` function to query any CEP:: 26 | 27 | >>> address = brazilcep.get_address_from_cep('37503-130') 28 | 29 | The result is a *dict* object called ``address``. You can retrieve all the address information you need from this object:: 30 | 31 | >>> address 32 | { 33 | 'district': 'rua abc', 34 | 'cep': '37503130', 35 | 'city': 'city ABC', 36 | 'street': 'str', 37 | 'uf': 'str', 38 | 'complement': 'str', 39 | } 40 | 41 | **Note:** The CEP must always be a string. 42 | 43 | Asynchronous Requests with BrazilCEP 44 | ------------------------------------ 45 | 46 | BrazilCEP (version >= 7.0.0) also supports asynchronous operations , allowing you to retrieve address information for a given CEP without blocking your application. This is particularly useful for web applications or services that require high responsiveness. 47 | 48 | To perform an asynchronous request, use the `async_get_address_from_cep` function: 49 | 50 | .. code-block:: python 51 | 52 | import asyncio 53 | import brazilcep 54 | 55 | async def main(): 56 | address = await brazilcep.async_get_address_from_cep('37503-130') 57 | print(address) 58 | 59 | asyncio.run(main()) 60 | 61 | .. note:: 62 | 63 | - This function is asynchronous and must be awaited when called. 64 | - Ensure that your environment supports asynchronous programming before using this function. 65 | 66 | By leveraging asynchronous requests, you can improve the performance and scalability of your applications when working with CEP data. 67 | 68 | Timeouts 69 | -------- 70 | 71 | You can specify a timeout for BrazilCEP requests using the ``timeout`` parameter. This is highly recommended for production code to prevent your program from hanging indefinitely. 72 | 73 | Proxy 74 | ----- 75 | 76 | BrazilCEP supports proxy settings following the *requests* library pattern:: 77 | 78 | from brazilcep import get_address_from_cep 79 | 80 | proxies = { 81 | 'https': "00.00.000.000", 82 | 'http': '00.00.000.000', 83 | } 84 | 85 | # Set proxies 86 | get_address_from_cep('37503-130', proxies=proxies) 87 | 88 | For more details, refer to the official `requests documentation `_. 89 | 90 | Using Different APIs 91 | --------------------- 92 | 93 | .. note:: 94 | 95 | BrazilCEP is designed for on-demand queries, such as integration into web pages. Bulk querying of CEPs through scripts or other means is not recommended. 96 | 97 | .. note:: 98 | 99 | BrazilCEP is not responsible for the functionality, availability, or support of any third-party query APIs. This library simply provides a centralized way to search for CEPs using these services. 100 | 101 | By default, BrazilCEP uses the API provided by the `OpenCEP `_ service. 102 | 103 | To use other services, specify the desired service when calling the `get_address_from_cep` function. 104 | 105 | Start by importing the BrazilCEP `WebService` class:: 106 | 107 | >>> from brazilcep import get_address_from_cep, WebService 108 | 109 | Then, call the `get_address_from_cep` method with the `webservice` parameter:: 110 | 111 | >>> get_address_from_cep('37503-130', webservice=WebService.APICEP) 112 | { 113 | 'district': 'rua abc', 114 | 'cep': '37503130', 115 | 'city': 'city ABC', 116 | 'street': 'str', 117 | 'uf': 'str', 118 | 'complement': 'str', 119 | } 120 | 121 | The possible values for the `webservice` parameter are: 122 | 123 | * `WebService.APICEP` 124 | * `WebService.VIACEP` 125 | * `WebService.OPENCEP` 126 | 127 | Errors and Exceptions 128 | --------------------- 129 | 130 | BrazilCEP provides a set of exceptions to handle errors during the query process: 131 | 132 | - :exc:`~brazilcep.exceptions.InvalidCEP`: Raised when an invalid CEP is provided. 133 | - :exc:`~brazilcep.exceptions.CEPNotFound`: Raised when the CEP is not found in the selected API. 134 | - :exc:`~brazilcep.exceptions.BlockedByFlood`: Raised when too many CEP requests are made in a short period. 135 | - :exc:`~brazilcep.exceptions.ConnectionError`: Raised when a connection error occurs. 136 | - :exc:`~brazilcep.exceptions.HTTPError`: Raised when an HTTP error occurs. 137 | - :exc:`~brazilcep.exceptions.URLRequired`: Raised when an invalid URL is used for a CEP request. 138 | - :exc:`~brazilcep.exceptions.TooManyRedirects`: Raised when too many redirects occur. 139 | - :exc:`~brazilcep.exceptions.Timeout`: Raised when a request times out. 140 | 141 | All exceptions explicitly raised by BrazilCEP inherit from :exc:`brazilcep.exceptions.BrazilCEPException`. 142 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "brazilcep" 7 | description = "Minimalist and easy-to-use Python library designed to query CEP (Postal Address Code) data" 8 | readme = "README.md" 9 | license = { file = "LICENSE" } 10 | authors = [{ name = "Michell Stuttgart", email = "michellstut@gmail.com" }] 11 | requires-python = ">=3.9" 12 | dynamic = ["version"] 13 | classifiers = [ 14 | "Development Status :: 5 - Production/Stable", 15 | "Intended Audience :: Developers", 16 | "License :: OSI Approved :: MIT License", 17 | "Operating System :: OS Independent", 18 | "Programming Language :: Python", 19 | "Programming Language :: Python :: 3", 20 | "Programming Language :: Python :: 3 :: Only", 21 | "Programming Language :: Python :: 3.9", 22 | "Programming Language :: Python :: 3.10", 23 | "Programming Language :: Python :: 3.11", 24 | "Programming Language :: Python :: 3.12", 25 | "Programming Language :: Python :: 3.13", 26 | "Topic :: Software Development :: Build Tools", 27 | "Topic :: Software Development :: Libraries", 28 | "Topic :: Software Development :: Libraries :: Python Modules", 29 | "Natural Language :: English", 30 | "Natural Language :: Portuguese (Brazilian)", 31 | ] 32 | dependencies = [ 33 | "requests>=2.28.2", 34 | "aiohttp>=3.8.1", 35 | ] 36 | 37 | [project.optional-dependencies] 38 | dev = [ 39 | "tox>=4.23.2", 40 | "pytest>=7.3.1", 41 | "pytest-asyncio", 42 | "mypy>=1.0,<1.5", 43 | "types-requests", 44 | "requests-mock>=1.10.0", 45 | "black>=23.0,<24.0", 46 | "isort>=5.12,<5.13", 47 | "pre-commit>=4.0.1", 48 | "python-dotenv", 49 | "ruff", 50 | "ipdb", 51 | ] 52 | coverage = [ 53 | "coveralls>=3.3.1", 54 | "pytest-cov>=4.0.0", 55 | "requests-mock>=1.10.0", 56 | "codecov-cli>=2.0.15", 57 | ] 58 | docs = [ 59 | "Sphinx>=6,<8", 60 | "readme-renderer==43", 61 | "myst-parser>=1.0,<2.1", 62 | "sphinx-copybutton==0.5.2", 63 | "sphinx-autobuild==2021.3.14", 64 | "sphinx-autodoc-typehints==1.23.3", 65 | ] 66 | build = ["build==1.2.2.post1"] 67 | 68 | [project.urls] 69 | Documentation = "https://brazilcep.readthedocs.io/index.html" 70 | Repository = "https://github.com/mstuttgart/brazilcep" 71 | Tracker = "https://github.com/mstuttgart/brazilcep/issues" 72 | Changelog = "https://github.com/mstuttgart/brazilcep/blob/main/CHANGELOG" 73 | 74 | [tool.check-manifest] 75 | ignore = [ 76 | ".*", 77 | "tests/**", 78 | "docs/**", 79 | "bin/**", 80 | "*.yml", 81 | ".tox.ini", 82 | ".pylintrc", 83 | ] 84 | 85 | [tool.setuptools] 86 | include-package-data = true 87 | 88 | [tool.setuptools.packages.find] 89 | exclude = [ 90 | "*.tests", 91 | "*.tests.*", 92 | "tests.*", 93 | "tests", 94 | "docs*", 95 | "scripts*", 96 | ] 97 | 98 | [tool.setuptools.package-data] 99 | brazilcep = ["py.typed"] 100 | 101 | [tool.setuptools.dynamic] 102 | version = { attr = "brazilcep.__version__.__version__" } 103 | 104 | [tool.black] 105 | line-length = 100 106 | include = '\.pyi?$' 107 | exclude = ''' 108 | ( 109 | __pycache__ 110 | | \.git 111 | | \.mypy_cache 112 | | \.pytest_cache 113 | | \.vscode 114 | | \.venv 115 | | \bdist\b 116 | | \bdoc\b 117 | | \.nox 118 | | \.github 119 | ) 120 | ''' 121 | 122 | [tool.isort] 123 | profile = "black" 124 | multi_line_output = 3 125 | skip = ["docs"] 126 | 127 | [tool.pyright] 128 | reportPrivateImportUsage = false 129 | 130 | [tool.mypy] 131 | strict = false 132 | 133 | [[tool.mypy.overrides]] 134 | module = "tests.*" 135 | strict_optional = false 136 | 137 | [tool.coverage.run] 138 | branch = true 139 | omit = ["tests/*"] 140 | 141 | [tool.pytest.ini_options] 142 | asyncio_default_fixture_loop_scope = "function" 143 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstuttgart/brazilcep/6f7361307a1347731ecbac6aa38f7f31c3be5625/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_apicep.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from unittest.mock import patch 4 | 5 | import dotenv 6 | import pytest 7 | 8 | from brazilcep import ( 9 | WebService, 10 | async_get_address_from_cep, 11 | exceptions, 12 | get_address_from_cep, 13 | ) 14 | 15 | dotenv.load_dotenv() 16 | 17 | IN_GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true" 18 | SKIP_REAL_TEST = os.getenv("SKIP_REAL_TEST", True) 19 | 20 | # Constants for mock responses 21 | RESPONSE_MOCK_TEXT_SUCCESS = """{ 22 | "status":200, 23 | "ok":true, 24 | "code":"37503-130", 25 | "state":"MG", 26 | "city":"Itajubá", 27 | "district":"Santo Antônio", 28 | "address":"Rua Geraldino Campista - até 214/215", 29 | "statusText":"ok" 30 | }""" 31 | 32 | API_URL = "https://ws.apicep.com/cep" 33 | 34 | 35 | @pytest.mark.skipif( 36 | SKIP_REAL_TEST or IN_GITHUB_ACTIONS, reason="Skip real API tests in certain environments." 37 | ) 38 | def test_fetch_address_success_real(): 39 | """ 40 | Test successful address fetch with real API. 41 | """ 42 | address = get_address_from_cep("37.503-130", webservice=WebService.APICEP) 43 | 44 | assert address["district"] == "Santo Antônio" 45 | assert address["cep"] == "37503-130" 46 | assert address["city"] == "Itajubá" 47 | assert address["complement"] == "" 48 | assert address["street"] == "Rua Geraldino Campista" 49 | assert address["uf"] == "MG" 50 | 51 | 52 | @pytest.mark.skipif( 53 | SKIP_REAL_TEST or IN_GITHUB_ACTIONS, reason="Skip real API tests in certain environments." 54 | ) 55 | def test_fetch_address_cep_not_found_real(): 56 | """ 57 | Test invalid CEP with real API. 58 | """ 59 | with pytest.raises(exceptions.InvalidCEP): 60 | get_address_from_cep("37.503-13", webservice=WebService.APICEP) 61 | 62 | 63 | def test_fetch_address_success(requests_mock): 64 | """ 65 | Test successful address fetch with mocked API. 66 | """ 67 | 68 | requests_mock.get(f"{API_URL}/37503130.json", text=RESPONSE_MOCK_TEXT_SUCCESS) 69 | 70 | address = get_address_from_cep("37.503-130", webservice=WebService.APICEP, timeout=5) 71 | 72 | assert address["district"] == "Santo Antônio" 73 | assert address["cep"] == "37503-130" 74 | assert address["city"] == "Itajubá" 75 | assert address["complement"] == "" 76 | assert address["street"] == "Rua Geraldino Campista" 77 | assert address["uf"] == "MG" 78 | 79 | # Test another mocked response 80 | req_mock_text = """{ 81 | "status":200, 82 | "ok":true, 83 | "code":"99999-999", 84 | "state":"PR", 85 | "city":"Sarandi", 86 | "district":null, 87 | "address":null, 88 | "statusText":"ok" 89 | }""" 90 | 91 | requests_mock.get(f"{API_URL}/99999999.json", text=req_mock_text) 92 | 93 | proxies = {"https": "00.00.000.000", "http": "00.00.000.000"} 94 | 95 | address = get_address_from_cep( 96 | "99999-999", webservice=WebService.APICEP, timeout=5, proxies=proxies 97 | ) 98 | 99 | assert address["district"] == "" 100 | assert address["cep"] == "99999-999" 101 | assert address["city"] == "Sarandi" 102 | assert address["complement"] == "" 103 | assert address["street"] == "" 104 | assert address["uf"] == "PR" 105 | 106 | 107 | def test_fetch_address_cep_not_found(requests_mock): 108 | """ 109 | Test CEP not found error. 110 | """ 111 | requests_mock.get(f"{API_URL}/00000000.json", text='{"status":404}') 112 | 113 | with pytest.raises(exceptions.CEPNotFound): 114 | get_address_from_cep("00000-000", webservice=WebService.APICEP) 115 | 116 | 117 | def test_fetch_address_invalid_cep(requests_mock): 118 | """ 119 | Test invalid CEP error. 120 | """ 121 | requests_mock.get( 122 | f"{API_URL}/3750313.json", text='{"status":400, "message": "CEP informado é inválido"}' 123 | ) 124 | 125 | with pytest.raises(exceptions.InvalidCEP): 126 | get_address_from_cep("37503-13", webservice=WebService.APICEP) 127 | 128 | 129 | def test_fetch_address_blocked_by_flood(requests_mock): 130 | """ 131 | Test blocked by flood error. 132 | """ 133 | requests_mock.get( 134 | f"{API_URL}/37503130.json", text='{"status":400, "message": "Blocked by flood"}' 135 | ) 136 | 137 | with pytest.raises(exceptions.BlockedByFlood): 138 | get_address_from_cep("37503-130", webservice=WebService.APICEP) 139 | 140 | 141 | def test_fetch_address_other_error_code_400(requests_mock): 142 | """ 143 | Test status 400 code error. 144 | """ 145 | requests_mock.get(f"{API_URL}/37503130.json", text='{"status":400, "message": "Unknown error"}') 146 | 147 | with pytest.raises(exceptions.BrazilCEPException): 148 | get_address_from_cep("37503-130", webservice=WebService.APICEP) 149 | 150 | 151 | def test_fetch_address_429(requests_mock): 152 | """ 153 | Test too many requests error. 154 | """ 155 | requests_mock.get(f"{API_URL}/37503130.json", status_code=429) 156 | 157 | with pytest.raises(exceptions.BlockedByFlood): 158 | get_address_from_cep("37503-130", webservice=WebService.APICEP) 159 | 160 | 161 | def test_fetch_address_404(requests_mock): 162 | """ 163 | Test generic 404 error. 164 | """ 165 | requests_mock.get(f"{API_URL}/37503130.json", status_code=404) 166 | 167 | with pytest.raises(exceptions.BrazilCEPException): 168 | get_address_from_cep("37503-130", webservice=WebService.APICEP) 169 | 170 | 171 | def test_json_decode_error(requests_mock): 172 | """ 173 | Test json decode error. 174 | """ 175 | 176 | requests_mock.get(f"{API_URL}/37503130.json", text=RESPONSE_MOCK_TEXT_SUCCESS) 177 | 178 | with patch("brazilcep.apicep.json.loads", side_effect=json.JSONDecodeError("", "", 0)): 179 | with pytest.raises(exceptions.BrazilCEPException): 180 | get_address_from_cep("37503-130", webservice=WebService.APICEP) 181 | 182 | 183 | @pytest.mark.skipif( 184 | SKIP_REAL_TEST or IN_GITHUB_ACTIONS, reason="Skip real API tests in certain environments." 185 | ) 186 | @pytest.mark.asyncio 187 | async def test_async_fetch_address_success_real(): 188 | """ 189 | Test fetching an address asynchronously using real API. 190 | """ 191 | address = await async_get_address_from_cep("37503-130", webservice=WebService.APICEP) 192 | 193 | assert address["district"] == "Santo Antônio" 194 | assert address["cep"] == "37503-130" 195 | assert address["city"] == "Itajubá" 196 | assert address["complement"] == "" 197 | assert address["street"] == "Rua Geraldino Campista" 198 | assert address["uf"] == "MG" 199 | 200 | 201 | @pytest.mark.asyncio 202 | async def test_async_get_address_from_cep_success(): 203 | """ 204 | Test async address fetch. 205 | """ 206 | 207 | async def __mock_aiohttp_get(*args, **kwargs): 208 | return 200, RESPONSE_MOCK_TEXT_SUCCESS 209 | 210 | with patch("brazilcep.apicep.utils.aiohttp_get", side_effect=__mock_aiohttp_get): 211 | result = await async_get_address_from_cep("37503-130", webservice=WebService.APICEP) 212 | assert isinstance(result, dict) 213 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from brazilcep import get_address_from_cep 4 | from brazilcep.client import _format_cep 5 | 6 | 7 | def test_search_error(): 8 | """ 9 | Test that invalid webservice values raise a KeyError. 10 | """ 11 | invalid_webservices = [5, "VIACEP"] 12 | 13 | for webservice in invalid_webservices: 14 | with pytest.raises(KeyError): 15 | get_address_from_cep("37.503-130", webservice=webservice) 16 | 17 | 18 | def test_format_cep_success(): 19 | """ 20 | Test that _format_cep correctly formats valid CEP inputs. 21 | """ 22 | test_cases = [ 23 | ("37.503-003", "37503003"), 24 | (" 37.503-003", "37503003"), 25 | ("37 503-003", "37503003"), 26 | ("37.503&003saasd", "37503003"), 27 | ("\n \r 37.503-003", "37503003"), 28 | ("37.503-003;", "37503003"), # Semicolon 29 | ("37.503-003;", "37503003"), # Unicode Greek Question Mark 30 | ] 31 | 32 | for input_cep, expected_output in test_cases: 33 | assert _format_cep(input_cep) == expected_output 34 | 35 | 36 | def test_format_cep_fail(): 37 | """ 38 | Test that _format_cep raises a ValueError for invalid inputs. 39 | """ 40 | invalid_inputs = [37503003, "", None, False, True] 41 | for invalid_input in invalid_inputs: 42 | with pytest.raises(ValueError): 43 | _format_cep(invalid_input) 44 | -------------------------------------------------------------------------------- /tests/test_opencep.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from unittest.mock import patch 4 | 5 | import dotenv 6 | import pytest 7 | 8 | from brazilcep import ( 9 | WebService, 10 | async_get_address_from_cep, 11 | exceptions, 12 | get_address_from_cep, 13 | ) 14 | 15 | dotenv.load_dotenv() 16 | 17 | # Constants 18 | IN_GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true" 19 | SKIP_REAL_TEST = os.getenv("SKIP_REAL_TEST", True) 20 | BASE_URL = "https://opencep.com/v1/" 21 | 22 | RESPONSE_MOCK_TEXT_SUCCESS = """{ 23 | "cep": "37503-130", 24 | "logradouro": "Rua Geraldino Campista", 25 | "complemento": "até 214/215", 26 | "bairro": "Santo Antônio", 27 | "localidade": "Itajubá", 28 | "uf": "MG", 29 | "ibge": "3132404" 30 | }""" 31 | 32 | 33 | @pytest.mark.skipif( 34 | SKIP_REAL_TEST or IN_GITHUB_ACTIONS, reason="Skip real API tests in certain environments." 35 | ) 36 | def test_fetch_address_success_real(): 37 | """ 38 | Test fetching a valid address from the real API. 39 | """ 40 | address = get_address_from_cep("37.503-130", webservice=WebService.OPENCEP) 41 | 42 | assert address["district"] == "Santo Antônio" 43 | assert address["cep"] == "37503-130" 44 | assert address["city"] == "Itajubá" 45 | assert address["complement"] == "" 46 | assert address["street"] == "Rua Geraldino Campista" 47 | assert address["uf"] == "MG" 48 | 49 | 50 | @pytest.mark.skipif( 51 | SKIP_REAL_TEST or IN_GITHUB_ACTIONS, reason="Skip real API tests in certain environments." 52 | ) 53 | def test_fetch_address_cep_not_found_real(): 54 | """ 55 | Test fetching an invalid CEP from the real API. 56 | """ 57 | with pytest.raises(exceptions.CEPNotFound): 58 | get_address_from_cep("00000-000", webservice=WebService.OPENCEP) 59 | 60 | 61 | def test_fetch_address_success(requests_mock): 62 | """ 63 | Test fetching a valid address using mocked requests. 64 | """ 65 | requests_mock.get(f"{BASE_URL}37503130", text=RESPONSE_MOCK_TEXT_SUCCESS, status_code=200) 66 | 67 | address = get_address_from_cep("37.503-130", webservice=WebService.OPENCEP, timeout=5) 68 | 69 | assert address["district"] == "Santo Antônio" 70 | assert address["cep"] == "37503-130" 71 | assert address["street"] == "Rua Geraldino Campista" 72 | assert address["uf"] == "MG" 73 | 74 | req_mock_text = """{ 75 | "cep": "99999-999", 76 | "logradouro": "Avenida das Torres", 77 | "complemento": "até 99999999 - lado ímpar", 78 | "unidade": "", 79 | "bairro": "Jardim Centro Cívico", 80 | "localidade": "Sarandi", 81 | "uf": "PR", 82 | "ibge": "4126256" 83 | }""" 84 | 85 | requests_mock.get(f"{BASE_URL}99999999", text=req_mock_text, status_code=200) 86 | 87 | proxies = {"https": "00.00.000.000", "http": "00.00.000.000"} 88 | 89 | address = get_address_from_cep( 90 | "99999-999", webservice=WebService.OPENCEP, timeout=6, proxies=proxies 91 | ) 92 | 93 | assert address["district"] == "Jardim Centro Cívico" 94 | assert address["cep"] == "99999-999" 95 | assert address["city"] == "Sarandi" 96 | assert address["complement"] == "" 97 | assert address["street"] == "Avenida das Torres" 98 | assert address["uf"] == "PR" 99 | 100 | 101 | def test_fetch_address_cep_not_found(requests_mock): 102 | """ 103 | Test fetching an invalid CEP using mocked requests. 104 | """ 105 | requests_mock.get(f"{BASE_URL}00000000", status_code=404) 106 | 107 | with pytest.raises(exceptions.CEPNotFound): 108 | get_address_from_cep("00000-000", webservice=WebService.OPENCEP) 109 | 110 | 111 | def test_fetch_address_500(requests_mock): 112 | """ 113 | Test handling a 500 Internal Server Error response. 114 | """ 115 | requests_mock.get(f"{BASE_URL}37503130", status_code=500) 116 | 117 | with pytest.raises(exceptions.BrazilCEPException): 118 | get_address_from_cep("37503-130", webservice=WebService.OPENCEP) 119 | 120 | 121 | def test_json_decode_error(requests_mock): 122 | """Test json decode error.""" 123 | 124 | requests_mock.get(f"{BASE_URL}37503130", text=RESPONSE_MOCK_TEXT_SUCCESS, status_code=200) 125 | 126 | with patch("brazilcep.opencep.json.loads", side_effect=json.JSONDecodeError("", "", 0)): 127 | with pytest.raises(exceptions.BrazilCEPException): 128 | get_address_from_cep("37503-130", webservice=WebService.OPENCEP) 129 | 130 | 131 | @pytest.mark.skipif( 132 | SKIP_REAL_TEST or IN_GITHUB_ACTIONS, reason="Skip real API tests in certain environments." 133 | ) 134 | @pytest.mark.asyncio 135 | async def test_async_fetch_address_success_real(): 136 | """ 137 | Test fetching an address asynchronously using real API. 138 | """ 139 | address = await async_get_address_from_cep("37503-130", webservice=WebService.OPENCEP) 140 | 141 | assert address["district"] == "Santo Antônio" 142 | assert address["cep"] == "37503-130" 143 | assert address["city"] == "Itajubá" 144 | assert address["complement"] == "" 145 | assert address["street"] == "Rua Geraldino Campista" 146 | assert address["uf"] == "MG" 147 | 148 | 149 | @pytest.mark.asyncio 150 | async def test_async_get_address_from_cep_success(): 151 | """ 152 | Test fetching an address asynchronously using mocked aiohttp. 153 | """ 154 | 155 | async def __mock_aiohttp_get(*args, **kwargs): 156 | return 200, RESPONSE_MOCK_TEXT_SUCCESS 157 | 158 | with patch("brazilcep.opencep.utils.aiohttp_get", side_effect=__mock_aiohttp_get): 159 | result = await async_get_address_from_cep("37503-130", webservice=WebService.OPENCEP) 160 | assert isinstance(result, dict) 161 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import AsyncMock, MagicMock, patch 2 | 3 | import aiohttp 4 | import pytest 5 | import requests 6 | 7 | from brazilcep import exceptions, utils 8 | 9 | URL = "https://api.com/99999999" 10 | 11 | 12 | # sync tests 13 | 14 | 15 | def test_requests_get_timeout_error(requests_mock): 16 | """ 17 | Test requests_get with a timeout error. 18 | """ 19 | 20 | requests_mock.get(URL, exc=requests.exceptions.Timeout) 21 | 22 | with pytest.raises(exceptions.Timeout): 23 | utils.requests_get(URL) 24 | 25 | 26 | def test_connection_error(requests_mock): 27 | """ 28 | Test requests_get with a connection error. 29 | """ 30 | requests_mock.get(URL, exc=requests.exceptions.ConnectionError) 31 | 32 | with pytest.raises(exceptions.ConnectionError): 33 | utils.requests_get(URL) 34 | 35 | 36 | def test_http_error(requests_mock): 37 | """ 38 | Test requests_get with a HTTP error. 39 | """ 40 | requests_mock.get(URL, exc=requests.exceptions.HTTPError) 41 | 42 | with pytest.raises(exceptions.HTTPError): 43 | utils.requests_get(URL) 44 | 45 | 46 | def test_url_required_error(requests_mock): 47 | """ 48 | Test requests_get with a URL required error. 49 | """ 50 | requests_mock.get(URL, exc=requests.exceptions.URLRequired) 51 | 52 | with pytest.raises(exceptions.URLRequired): 53 | utils.requests_get(URL) 54 | 55 | 56 | def test_too_many_redirects_error(requests_mock): 57 | """ 58 | Test requests_get with a too many redirects error. 59 | """ 60 | requests_mock.get(URL, exc=requests.exceptions.TooManyRedirects) 61 | 62 | with pytest.raises(exceptions.TooManyRedirects): 63 | utils.requests_get(URL) 64 | 65 | 66 | def test_general_exception_error(requests_mock): 67 | """ 68 | Test requests_get with a general_exception error. 69 | """ 70 | requests_mock.get(URL, exc=Exception) 71 | 72 | with pytest.raises(exceptions.BrazilCEPException): 73 | utils.requests_get(URL) 74 | 75 | 76 | # async tests 77 | 78 | 79 | @pytest.mark.asyncio 80 | async def test_aiohttp_get_success(): 81 | """ 82 | Test aiohttp_get with a successful response. 83 | """ 84 | 85 | mock_response = MagicMock() 86 | mock_response.status = 200 87 | mock_response.text = AsyncMock(return_value="Success") 88 | mock_response.__aenter__.return_value = mock_response 89 | 90 | with patch("brazilcep.utils.aiohttp.ClientSession.get", return_value=mock_response): 91 | status, text = await utils.aiohttp_get(URL) 92 | assert status == 200 93 | assert text == "Success" 94 | 95 | 96 | @pytest.mark.asyncio 97 | async def test_aiohttp_get_connection_error(): 98 | """ 99 | Test aiohttp_get with a connection error. 100 | """ 101 | 102 | with patch( 103 | "brazilcep.utils.aiohttp.ClientSession.get", side_effect=aiohttp.ClientConnectionError 104 | ): 105 | with pytest.raises(exceptions.ConnectionError): 106 | await utils.aiohttp_get(URL) 107 | 108 | 109 | @pytest.mark.asyncio 110 | async def test_aiohttp_get_timeout(): 111 | """ 112 | Test aiohttp_get with a timeout error. 113 | """ 114 | 115 | with patch( 116 | "brazilcep.utils.aiohttp.ClientSession.get", side_effect=aiohttp.ConnectionTimeoutError 117 | ): 118 | with pytest.raises(exceptions.Timeout): 119 | await utils.aiohttp_get(URL, timeout=1) 120 | 121 | 122 | @pytest.mark.asyncio 123 | async def test_aiohttp_get_client_error(): 124 | """ 125 | Test aiohttp_get with a client error. 126 | """ 127 | 128 | with patch("brazilcep.utils.aiohttp.ClientSession.get", side_effect=aiohttp.ClientError): 129 | with pytest.raises(exceptions.BrazilCEPException): 130 | await utils.aiohttp_get(URL) 131 | 132 | 133 | @pytest.mark.asyncio 134 | async def test_aiohttp_get_general_exception(): 135 | """ 136 | Test aiohttp_get with a general exception. 137 | """ 138 | 139 | with patch("brazilcep.utils.aiohttp.ClientSession.get", side_effect=Exception): 140 | with pytest.raises(exceptions.BrazilCEPException): 141 | await utils.aiohttp_get(URL) 142 | 143 | 144 | @pytest.mark.asyncio 145 | async def test_raise_for_status(): 146 | """ 147 | Test aiohttp_get with raise_for_status response. 148 | """ 149 | 150 | mock_response = MagicMock() 151 | mock_response.raise_for_status = MagicMock(side_effect=aiohttp.ClientResponseError) 152 | mock_response.__aenter__.return_value = mock_response 153 | 154 | with patch("brazilcep.utils.aiohttp.ClientSession.get", return_value=mock_response): 155 | with pytest.raises(exceptions.BrazilCEPException): 156 | await utils.aiohttp_get(URL, raise_for_status=True) 157 | -------------------------------------------------------------------------------- /tests/test_viacep.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from unittest.mock import patch 4 | 5 | import dotenv 6 | import pytest 7 | 8 | from brazilcep import ( 9 | WebService, 10 | async_get_address_from_cep, 11 | exceptions, 12 | get_address_from_cep, 13 | ) 14 | 15 | dotenv.load_dotenv() 16 | 17 | IN_GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true" 18 | SKIP_REAL_TEST = os.getenv("SKIP_REAL_TEST", False) 19 | 20 | BASE_URL = "http://www.viacep.com.br/ws" 21 | RESPONSE_MOCK_TEXT_SUCCESS = """{ 22 | "cep": "37503-130", 23 | "logradouro": "Rua Geraldino Campista", 24 | "complemento": "até 214/215", 25 | "bairro": "Santo Antônio", 26 | "localidade": "Itajubá", 27 | "uf": "MG", 28 | "ibge": "3132404", 29 | "gia": "", 30 | "ddd": "35", 31 | "siafi": "4647" 32 | }""" 33 | 34 | 35 | @pytest.mark.skipif( 36 | SKIP_REAL_TEST or IN_GITHUB_ACTIONS, 37 | reason="Skip real API tests in certain environments.", 38 | ) 39 | def test_get_address_from_cep_success_real(): 40 | """ 41 | Test successful address retrieval from real API. 42 | """ 43 | address = get_address_from_cep("37.503-130", webservice=WebService.VIACEP) 44 | 45 | assert address["district"] == "Santo Antônio" 46 | assert address["cep"] == "37503-130" 47 | assert address["city"] == "Itajubá" 48 | assert address["complement"] == "até 214/215" 49 | assert address["street"] == "Rua Geraldino Campista" 50 | assert address["uf"] == "MG" 51 | 52 | 53 | @pytest.mark.skipif( 54 | SKIP_REAL_TEST or IN_GITHUB_ACTIONS, 55 | reason="Skip real API tests in certain environments.", 56 | ) 57 | def test_get_address_from_cep_not_found_real(): 58 | """ 59 | Test address not found scenario with real API. 60 | """ 61 | with pytest.raises(exceptions.CEPNotFound): 62 | get_address_from_cep("00000-000", webservice=WebService.VIACEP) 63 | 64 | 65 | def test_get_address_from_cep_success(requests_mock): 66 | """ 67 | Test successful address retrieval with mocked API. 68 | """ 69 | requests_mock.get(f"{BASE_URL}/37503130/json", text=RESPONSE_MOCK_TEXT_SUCCESS) 70 | 71 | proxies = {"https": "00.00.000.000", "http": "00.00.000.000"} 72 | 73 | address = get_address_from_cep( 74 | "37.503-130", webservice=WebService.VIACEP, timeout=10, proxies=proxies 75 | ) 76 | 77 | assert address["district"] == "Santo Antônio" 78 | assert address["cep"] == "37503-130" 79 | assert address["city"] == "Itajubá" 80 | assert address["complement"] == "até 214/215" 81 | assert address["street"] == "Rua Geraldino Campista" 82 | assert address["uf"] == "MG" 83 | 84 | 85 | def test_get_address_from_cep_not_found(requests_mock): 86 | """ 87 | Test address not found scenario with mocked API. 88 | """ 89 | mock_response = '{"erro": "true"}' 90 | 91 | requests_mock.get(f"{BASE_URL}/00000000/json", text=mock_response) 92 | with pytest.raises(exceptions.CEPNotFound): 93 | get_address_from_cep("00000-000", webservice=WebService.VIACEP) 94 | 95 | requests_mock.get(f"{BASE_URL}/99999999/json", text=mock_response) 96 | with pytest.raises(exceptions.CEPNotFound): 97 | get_address_from_cep("99999-999", webservice=WebService.VIACEP) 98 | 99 | 100 | def test_get_address_invalid_cep(requests_mock): 101 | """ 102 | Test invalid CEP scenario. 103 | """ 104 | requests_mock.get(f"{BASE_URL}/3750313/json", status_code=400) 105 | 106 | with pytest.raises(exceptions.InvalidCEP): 107 | get_address_from_cep("37503-13", webservice=WebService.VIACEP) 108 | 109 | 110 | def test_fetch_address_404(requests_mock): 111 | """ 112 | Test 404 error scenario. 113 | """ 114 | requests_mock.get(f"{BASE_URL}/37503130/json", status_code=404) 115 | 116 | with pytest.raises(exceptions.BrazilCEPException): 117 | get_address_from_cep("37503-130", webservice=WebService.VIACEP) 118 | 119 | 120 | def test_json_decode_error(requests_mock): 121 | """ 122 | Test json decode error. 123 | """ 124 | 125 | requests_mock.get(f"{BASE_URL}/37503130/json", text=RESPONSE_MOCK_TEXT_SUCCESS) 126 | 127 | with patch("brazilcep.viacep.json.loads", side_effect=json.JSONDecodeError("", "", 0)): 128 | with pytest.raises(exceptions.BrazilCEPException): 129 | get_address_from_cep("37503-130", webservice=WebService.VIACEP) 130 | 131 | 132 | @pytest.mark.skipif( 133 | SKIP_REAL_TEST or IN_GITHUB_ACTIONS, reason="Skip real API tests in certain environments." 134 | ) 135 | @pytest.mark.asyncio 136 | async def test_async_fetch_address_success_real(): 137 | """ 138 | Test fetching an address asynchronously using real API. 139 | """ 140 | address = await async_get_address_from_cep("37503-130", webservice=WebService.VIACEP) 141 | 142 | assert address["district"] == "Santo Antônio" 143 | assert address["cep"] == "37503-130" 144 | assert address["city"] == "Itajubá" 145 | assert address["complement"] == "até 214/215" 146 | assert address["street"] == "Rua Geraldino Campista" 147 | assert address["uf"] == "MG" 148 | 149 | 150 | @pytest.mark.asyncio 151 | async def test_async_get_address_from_cep_success(): 152 | """ 153 | Test asynchronous address retrieval. 154 | """ 155 | 156 | async def __mock_aiohttp_get(*args, **kwargs): 157 | return 200, RESPONSE_MOCK_TEXT_SUCCESS 158 | 159 | with patch("brazilcep.opencep.utils.aiohttp_get", side_effect=__mock_aiohttp_get): 160 | result = await async_get_address_from_cep("37503-130", webservice=WebService.VIACEP) 161 | assert isinstance(result, dict) 162 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | requires = 3 | tox>=4.2 4 | env_list = 5 | python3.{13, 12, 11, 10, 9} 6 | skip_missing_interpreters = true 7 | 8 | [testenv] 9 | deps = 10 | -e ".[dev]" 11 | commands = 12 | pytest 13 | --------------------------------------------------------------------------------