From cc67f1e36833beae32bc3e6274f0c871f6403dd6 Mon Sep 17 00:00:00 2001 From: Matheus Oliveira Cruz Date: Thu, 9 Jul 2026 14:54:56 -0300 Subject: [PATCH] feat: add awesomeapi network provider --- README.md | 2 +- pyproject.toml | 1 + src/cepx/providers/__init__.py | 4 +++ src/cepx/providers/awesomeapi.py | 43 ++++++++++++++++++++++++++++++++ tests/conftest.py | 19 ++++++++++++++ tests/unit/test_core.py | 5 +++- tests/unit/test_providers.py | 28 +++++++++++++++++++++ tests/unit/test_validation.py | 3 ++- 8 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 src/cepx/providers/awesomeapi.py diff --git a/README.md b/README.md index 900f56f..bf4de79 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ network on genuine misses. ## Network lookups Without the extra (or when you don't request `local`), cepx queries the live -providers (Correios, ViaCEP, WideNet, BrasilAPI, and OpenCEP) **concurrently**, and the +providers (Correios, ViaCEP, WideNet, BrasilAPI, OpenCEP, and AwesomeAPI) **concurrently**, and the **first successful** response wins. The lookup only fails once **every** provider fails, aggregating each error. diff --git a/pyproject.toml b/pyproject.toml index e6464d5..98e189b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ keywords = [ "widenet", "apicep", "opencep", + "awesomeapi", ] classifiers = [ "Development Status :: 4 - Beta", diff --git a/src/cepx/providers/__init__.py b/src/cepx/providers/__init__.py index 75b4512..bfe3078 100644 --- a/src/cepx/providers/__init__.py +++ b/src/cepx/providers/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations +from cepx.providers.awesomeapi import AwesomeAPI from cepx.providers.base import DEFAULT_TIMEOUT, HttpProvider, Provider from cepx.providers.brasilapi import BrasilAPI from cepx.providers.correios import Correios @@ -19,6 +20,7 @@ "widenet", "brasilapi", "opencep", + "awesomeapi", ) @@ -30,6 +32,7 @@ def get_available_providers() -> dict[str, Provider]: "widenet": WideNet(), "brasilapi": BrasilAPI(), "opencep": OpenCep(), + "awesomeapi": AwesomeAPI(), "local": LocalProvider(), } @@ -37,6 +40,7 @@ def get_available_providers() -> dict[str, Provider]: __all__ = ( "DEFAULT_PROVIDER_NAMES", "DEFAULT_TIMEOUT", + "AwesomeAPI", "BrasilAPI", "Correios", "CorreiosAlt", diff --git a/src/cepx/providers/awesomeapi.py b/src/cepx/providers/awesomeapi.py new file mode 100644 index 0000000..96fcf8c --- /dev/null +++ b/src/cepx/providers/awesomeapi.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import json + +from cepx._types import Address, RequestSpec +from cepx.errors import ProviderError +from cepx.providers.base import HttpProvider + + +class AwesomeAPI(HttpProvider): + name = "awesomeapi" + connection_error_message = "Failed to connect to the AwesomeAPI provider." + + def build_request(self, cep: str) -> RequestSpec: + headers = { + "content-type": "application/json;charset=utf-8", + } + + return RequestSpec( + method="GET", + url=f"https://cep.awesomeapi.com.br/json/{cep}", + headers=headers, + ) + + def parse(self, status_code: int, body: str) -> Address: + if status_code == 404: + raise ProviderError( + "CEP not found in the AwesomeAPI database.", + self.name, + ) + + if not 200 <= status_code < 300: + raise ProviderError(self.connection_error_message, self.name) + + data = json.loads(body) + + return self.build_address( + cep=data.get("cep"), + state=data.get("state"), + city=data.get("city"), + neighborhood=data.get("district"), + street=data.get("address"), + ) diff --git a/tests/conftest.py b/tests/conftest.py index 44bc271..7b254fc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ VIACEP_URL = "https://viacep.com.br/ws/05010000/json/" WIDENET_URL = "https://cdn.apicep.com/file/apicep/05010-000.json" OPENCEP_URL = "https://opencep.com/v1/05010000" +AWESOMEAPI_URL = "https://cep.awesomeapi.com.br/json/05010000" CORREIOS_URL = ( "https://apps.correios.com.br" @@ -41,6 +42,7 @@ "widenet", "brasilapi", "opencep", + "awesomeapi", ] @@ -87,6 +89,18 @@ def stub_found(router, name): "uf": "SP", } ) + elif name == "awesomeapi": + router.get(AWESOMEAPI_URL).respond( + json={ + "cep": "05010000", + "address_type": "Rua", + "address_name": "Caiubi", + "address": "Rua Caiubi", + "state": "SP", + "district": "Perdizes", + "city": "São Paulo", + } + ) elif name == "correios": router.post(CORREIOS_URL).respond(text=CORREIOS_XML_FOUND) elif name == "correios-alt": @@ -118,6 +132,10 @@ def stub_failed(router, name): ) elif name == "opencep": router.get(OPENCEP_URL).respond(status_code=404, json={}) + elif name == "awesomeapi": + router.get(AWESOMEAPI_URL).respond( + status_code=404, json={"code": "not_found"} + ) elif name == "correios": router.post(CORREIOS_URL).respond( status_code=500, text=CORREIOS_XML_FAULT @@ -165,6 +183,7 @@ def _clear_bundled_db_cache(): __all__ = ( + "AWESOMEAPI_URL", "BRASILAPI_URL", "CORREIOS_ALT_URL", "CORREIOS_URL", diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py index 80319ff..078ede5 100644 --- a/tests/unit/test_core.py +++ b/tests/unit/test_core.py @@ -28,6 +28,7 @@ def test_first_success_wins_sync(mock_all_found, expected_address): "widenet", "brasilapi", "opencep", + "awesomeapi", } @@ -86,6 +87,7 @@ def test_all_failed_sync(mock_all_failed): "widenet", "brasilapi", "opencep", + "awesomeapi", } @@ -93,7 +95,7 @@ async def test_all_failed_async(mock_all_failed): with pytest.raises(cepx.CepxError) as info: await cepx.acep("05010000") assert info.value.type == "provider_error" - assert len(info.value.errors) == 6 + assert len(info.value.errors) == 7 def test_aggregated_errors_preserve_provider_order(mock_all_failed): @@ -106,6 +108,7 @@ def test_aggregated_errors_preserve_provider_order(mock_all_failed): "widenet", "brasilapi", "opencep", + "awesomeapi", ] diff --git a/tests/unit/test_providers.py b/tests/unit/test_providers.py index 166c4eb..0516641 100644 --- a/tests/unit/test_providers.py +++ b/tests/unit/test_providers.py @@ -6,6 +6,7 @@ import cepx from tests.conftest import ( + AWESOMEAPI_URL, BRASILAPI_URL, CORREIOS_ALT_URL, CORREIOS_URL, @@ -43,6 +44,11 @@ def test_opencep(mock_all_found, expected_address): _assert_matches(address, "opencep", expected_address) +def test_awesomeapi(mock_all_found, expected_address): + address = cepx.cep("05010000", providers=["awesomeapi"]) + _assert_matches(address, "awesomeapi", expected_address) + + def test_correios(mock_all_found, expected_address): address = cepx.cep("05010000", providers=["correios"]) _assert_matches(address, "correios", expected_address) @@ -103,6 +109,28 @@ def test_opencep_non_2xx_is_connection_error(): ) +@respx.mock +def test_awesomeapi_not_found(): + respx.get(AWESOMEAPI_URL).respond( + status_code=404, json={"code": "not_found"} + ) + with pytest.raises(cepx.CepxError) as info: + cepx.cep("05010000", providers=["awesomeapi"]) + assert info.value.errors[0].message == ( + "CEP not found in the AwesomeAPI database." + ) + + +@respx.mock +def test_awesomeapi_non_2xx_is_connection_error(): + respx.get(AWESOMEAPI_URL).respond(status_code=500) + with pytest.raises(cepx.CepxError) as info: + cepx.cep("05010000", providers=["awesomeapi"]) + assert info.value.errors[0].message == ( + "Failed to connect to the AwesomeAPI provider." + ) + + @respx.mock def test_correios_fault_message(): respx.post(CORREIOS_URL).respond(status_code=500, text=CORREIOS_XML_FAULT) diff --git a/tests/unit/test_validation.py b/tests/unit/test_validation.py index 049b3db..3e24539 100644 --- a/tests/unit/test_validation.py +++ b/tests/unit/test_validation.py @@ -27,12 +27,13 @@ def test_none_providers_uses_all(): "widenet", "brasilapi", "opencep", + "awesomeapi", ] def test_empty_providers_uses_all(): _, providers = prepare("05010000", []) - assert len(providers) == 6 + assert len(providers) == 7 def test_selected_providers_preserve_order():