Metadata-Version: 2.4
Name: cepx
Version: 0.1.4
Summary: Fast, offline-capable Brazilian CEP (postal code) lookup for Python, sync and async.
Project-URL: Homepage, https://github.com/moc-guardian/cepx
Project-URL: Repository, https://github.com/moc-guardian/cepx
Project-URL: Issues, https://github.com/moc-guardian/cepx/issues
Project-URL: Changelog, https://github.com/moc-guardian/cepx/blob/main/CHANGELOG.md
License: MIT
Keywords: apicep,awesomeapi,brasilapi,cep,correios,opencep,viacep,widenet,zipcode
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: Portuguese (Brazilian)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx[http2]>=0.27
Provides-Extra: local
Requires-Dist: cepx-data>=0.1.2; extra == 'local'
Description-Content-Type: text/markdown

# cepx

Fast, offline-capable Brazilian CEP (postal code) lookup for Python, sync and
async.

Most CEP libraries are thin HTTP clients: every lookup is a round-trip to
Correios/ViaCEP/BrasilAPI, so you inherit their latency, rate limits, and
downtime. **cepx can resolve CEPs fully offline** from a prebuilt database
bundled with the package; in **microseconds**, with no network at all. When you
do want the network, it still races the same public providers and returns the
first successful answer.

## Why cepx

|                       | `local` (offline)          | network providers                         |
| --------------------- | -------------------------- | ----------------------------------------- |
| Latency               | **~12 µs/lookup (~85k/s)** | one HTTP round-trip (tens–hundreds of ms) |
| Network               | none                       | required                                  |
| Rate limits / outages | none                       | subject to both                           |
| Works air-gapped      | ✅                         | ❌                                        |
| Coverage              | ~1.14M CEPs (bundled)      | whatever the live service returns         |

The offline path is **thousands of times faster** than any HTTP lookup and
depends on nothing but your own process. Numbers above are the full public API
(`cepx.cep(...)`) against the complete national database, reproducible with
`make bench-local`.

## Offline lookups (the `local` provider)

Install the `local` extra (it pulls in
[`cepx-data`](https://github.com/moc-guardian/cepx-data)), which ships a prebuilt
SQLite database of ~1.14M CEPs:

```bash
pip install "cepx[local]"
```

```python
import cepx

cepx.cep("05010000", providers=["local"])
# Address(cep='05010000', state='SP', city='São Paulo',
#         neighborhood='Perdizes', street='Rua Caiubi', provider='local')
```

- **Zero network.** No requests, no rate limits, no third-party availability to
  depend on. Ideal for batch jobs, air-gapped environments, and hot paths.
- **Auto-discovered.** Once `cepx-data` is installed, the `local` provider finds
  the database automatically; set `CEPX_DB` to point at your own SQLite file.
- **Opt-in.** `local` is not part of the default provider race; request it
  explicitly with `providers=["local"]`.

### The database

`cepx[local]` adds one dependency, `cepx-data`, which bundles the CEP database:

- **~17 MiB download** (the wheel), **~42 MiB on disk** once installed — a
  one-time cost, no runtime downloads.
- **~1.14 million CEPs**: every UF, ~5.4k municipalities, ~31k neighborhoods,
  ~611k streets.
- Stored as a normalized SQLite database (one row per CEP keyed on the CEP
  itself, with UF/city/neighborhood/street deduplicated into lookup tables), so
  a query is a single indexed primary-key join.
- Data is derived from [CEP Aberto](https://www.cepaberto.com/) under the Open
  Database License (ODbL); `cepx-data` ships it and carries the attribution.

A CEP that isn't in the dataset is a clean miss (`provider_error`); fall back to
the network providers for full coverage if you need it.

### Offline-first with network fallback

Prefer the local database and only touch the network when a CEP isn't covered:

```python
def lookup(cep):
    try:
        return cepx.cep(cep, providers=["local"])   # ~microseconds on a hit
    except cepx.CepxError:
        return cepx.cep(cep)                        # network only on a miss
```

Do this rather than passing `providers=["local", ...]` in a single call: mixing
`local` with network providers forces the concurrent race path, which spins up a
thread pool and fires the network requests on _every_ lookup, even the ones
`local` answers instantly (they're just cancelled once `local` wins). The
two-step form keeps hits fully offline at microsecond speed and only touches the
network on genuine misses.

## Network lookups

Without the extra (or when you don't request `local`), cepx queries the live
providers **concurrently** and the **first successful** response wins. The
lookup only fails once **every** provider fails, aggregating each error.

These are the built-in providers. The first column is the exact string to pass
in `providers=[...]`:

| `providers=`     | Service                                                                    | Notes                                                                                       |
| ---------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `"correios"`     | [Correios](https://www.correios.com.br) SIGEP (official postal authority)  | The official source; SOAP/XML endpoint, so usually the slowest to answer                    |
| `"correios-alt"` | Correios address search (the `buscacepinter` site backend)                 | Same official data through the public site's endpoint                                       |
| `"viacep"`       | [ViaCEP](https://viacep.com.br)                                            | Long-running, widely used free API                                                          |
| `"widenet"`      | [apicep.com](https://apicep.com) (formerly WideNet)                        | Serves CDN-cached JSON                                                                      |
| `"brasilapi"`    | [BrasilAPI](https://brasilapi.com.br)                                      | Itself an aggregator; fans out to several of the others, so it often wins the race          |
| `"opencep"`      | [OpenCEP](https://opencep.com)                                             | Open, community-maintained database                                                         |
| `"awesomeapi"`   | [AwesomeAPI](https://awesomeapi.com.br)                                    | Also returns lat/lng, DDD, and IBGE city code (cepx keeps the standard five address fields) |
| `"local"`        | Offline SQLite database ([see above](#offline-lookups-the-local-provider)) | **Opt-in**, not part of the default race; microsecond lookups with no network               |

Every provider except `local` is queried in the default race; pass a subset via
`providers=[...]` to restrict the lookup to just those services.

```python
import cepx

cepx.cep("05010000")
# Address(cep='05010000', ..., provider='brasilapi')

cepx.cep(5010000)                            # ints are left-padded
cepx.cep("05010000", providers=["viacep"])   # restrict providers
cepx.cep("05010000", timeout=5.0)            # per-provider timeout (s)
```

```python
import asyncio
import cepx

asyncio.run(cepx.acep("05010000"))
```

## Errors

`cepx.CepxError` is raised with a `type` of either `"validation_error"`
(bad input, before any request) or `"provider_error"` (all providers failed).
Its `errors` list holds the underlying `cepx.ProviderError` entries.

## Development

```bash
make setup         # create the venv, install deps, install git hooks
make check         # unit tests + coverage + pre-commit (lint, format, types)
make bench-local   # benchmark the local provider against cepx-data
```

Run `make` with no target to see everything available.

## Credits

cepx began as a Python port of
[`cep-promise`](https://github.com/BrasilAPI/cep-promise) and keeps its
first-successful-provider model for network lookups.
