Metadata-Version: 2.4
Name: e164-python-sdk
Version: 0.2.0
Summary: Python SDK for the e164.com phone number lookup API.
Author-email: Catalin Dragos <catalin.dragos@e164.com>
License-Expression: MIT
Project-URL: Homepage, https://e164.com
Project-URL: Source, https://github.com/e164-com/e164-python-sdk
Project-URL: Issues, https://github.com/e164-com/e164-python-sdk/issues
Keywords: e164,phone,telephony,msisdn,mccmnc,tadig,numbering-plan
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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: Topic :: Communications :: Telephony
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25.0
Requires-Dist: urllib3>=1.26.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: mypy>=1.8; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Requires-Dist: types-requests; extra == "dev"
Dynamic: license-file

# E164 Python SDK

A Python client for the [e164.com](https://e164.com) phone number lookup API. Given a phone
number it returns the matching numbering-plan record: country, operator, number type,
expected length, MCC/MNC and TADIG codes.

## Installation

```bash
pip install e164-python-sdk
```

Requires Python 3.9 or newer.

## Quickstart

```python
from e164_python import E164, E164Error

with E164() as client:
    try:
        record = client.lookup("+44 113 391 0781")
    except E164Error as exc:
        print(f"Lookup failed: {exc}")
    else:
        print(record.iso3)  # 'GBR'
        print(record.calling_code)  # 44
        print(record.operator_brand)  # 'BT'
        print(record.type)  # 'GEOGRAPHIC'
```

Input is normalised for you, so `"+44 113 391 0781"`, `"44-113-391-0781"` and
`"441133910781"` are all equivalent.

## Response fields

`lookup()` returns a `Response` dataclass. Every field is optional — the API omits what it
does not know.

| Field | Type | Description |
| --- | --- | --- |
| `prefix` | `str` | The numbering-plan prefix that matched |
| `calling_code` | `int` | Country calling code, e.g. `44` |
| `iso3` | `str` | ISO 3166-1 alpha-3 country code, e.g. `'GBR'` |
| `tadig` | `str` | TADIG code, e.g. `'GBRJT'` (mobile only) |
| `mccmnc` | `str` | Mobile country/network code, e.g. `'23450'` (mobile only) |
| `type` | `str` | `'GEOGRAPHIC'`, `'MOBILE'`, and so on |
| `location` | `str` | Human-readable location, when known |
| `operator_brand` | `str` | Operator trading name |
| `operator_company` | `str` | Operator legal entity |
| `total_length_min` | `int` | Minimum total number length, in digits |
| `total_length_max` | `int` | Maximum total number length, in digits |
| `weight` | `int` | Match weight |
| `source` | `str` | Data source |
| `extra` | `dict` | Any field the API returns that this SDK predates |

`record.to_dict()` returns the record in the API's own shape, omitting empty fields and
merging `extra` back in at the top level.

> **Note:** `calling_code`, `total_length_min`, `total_length_max` and `weight` are integers.
> Versions up to 0.1.6 annotated them as `str`, which never matched what the API sends.

## Multiple matches

`lookup()` returns the single best match and raises `NumberNotFoundError` when there is
none. Use `lookup_all()` to see every record, which returns an empty list instead of
raising:

```python
with E164() as client:
    for record in client.lookup_all("441133910781"):
        print(record.prefix, record.operator_brand)
```

## Error handling

Every exception inherits from `E164Error`, so a single `except E164Error` catches
everything this SDK raises.

| Exception | Raised when |
| --- | --- |
| `InvalidPhoneNumberError` | Input is empty, over 15 digits, or not ASCII digits. No request is made. |
| `NumberNotFoundError` | The API has no data for the number. |
| `E164TimeoutError` | The request exceeded the timeout. |
| `E164TransportError` | The request never reached the API (DNS, connection, TLS). |
| `E164HTTPError` | The API returned 4xx/5xx. Carries `.status_code`. |
| `E164ResponseError` | The API returned something other than the expected JSON. |

```python
from e164_python import E164, E164HTTPError, NumberNotFoundError

with E164() as client:
    try:
        record = client.lookup("441133910781")
    except NumberNotFoundError:
        record = None
    except E164HTTPError as exc:
        print(f"API error {exc.status_code}")
        raise
```

`E164Error` currently also inherits from `ValueError`, so code written against 0.1.6 and
earlier — which raised a bare `ValueError` for every failure — keeps working. That base
class is deprecated and will be removed in 1.0.

## Configuration

```python
client = E164(
    timeout=5.0,  # seconds; None waits forever. Default 10.
    retries=3,  # retries with backoff on 429/5xx. Default 2.
    base_url="https://e164.com",  # point at a staging environment
    user_agent="my-app/2.1",  # override the default UA
)
```

To reuse your own connection pool, proxy settings or auth, pass a `requests.Session`. It is
used exactly as given — the SDK will not touch its headers or adapters, and will not close
it, since it remains yours to manage:

```python
import requests

session = requests.Session()
session.proxies = {"https": "http://proxy.internal:3128"}

client = E164(client=session)
```

## Development

```bash
pip install -e ".[dev]"

pytest              # run the test suite
ruff check .        # lint
ruff format .       # format
mypy                # type check
```

## License

MIT — see [LICENSE](LICENSE).
