Metadata-Version: 2.4
Name: ideal-postcodes
Version: 0.1.0
Summary: Explicit Python client for Ideal Postcodes
Keywords: address,postcode,address-lookup,address-autocomplete,uk
Author: Ideal Postcodes
Author-email: Ideal Postcodes <support@ideal-postcodes.co.uk>
License-Expression: LicenseRef-Proprietary
License-File: LICENSE
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Dist: httpx==0.28.1
Requires-Dist: pydantic==2.13.5
Requires-Python: >=3.12
Project-URL: Documentation, https://docs.ideal-postcodes.co.uk/docs/sdks/python
Project-URL: Homepage, https://ideal-postcodes.co.uk
Project-URL: Issues, https://github.com/ideal-postcodes/feedback/issues
Project-URL: Repository, https://github.com/ideal-postcodes/feedback
Description-Content-Type: text/markdown

# Ideal Postcodes Python SDK

Client for every current API operation: address search and lookup, place search, email and phone validation, and key, licensee and configuration management. Requires Python 3.12 or later; tested on 3.12, 3.13 and 3.14.

```sh
pip install ideal-postcodes
```

```python
from ideal_postcodes import IdealPostcodes

with IdealPostcodes("ak_test") as client:
    for address in client.postcodes("BR8 7RE"):
        print(address.line_1, address.post_town)
```

`ak_test` is the public test key. The client returns Pydantic models from the API's `result` field. Read fields as attributes and use `model_dump()` for a dictionary. Reuse one client and close it with a context manager. Production keys belong in environment variables.

## Search and resolve

```python
from ideal_postcodes import IdealPostcodes

with IdealPostcodes("ak_test") as client:
    hits = client.find_address(query="9 Malyons Road", limit=5).hits
    if hits:
        address = client.resolve_address(hits[0].id)
        print(address.postcode)
```

Resolve the suggestion the user chooses. An empty suggestion list is a normal result.

## Methods

| Method | Purpose |
| --- | --- |
| `postcodes(postcode, ...)` | Addresses at a postcode |
| `addresses(query=..., ...)` | Paginated address search; filters can be used without query text |
| `address_cleanse(query=..., ...)` | Cleanse a freeform address, with optional postcode and post town |
| `find_address(query=..., ...)` | Autocomplete suggestions |
| `resolve_address(address, ...)` | Resolve a suggestion using the spec's GBR endpoint |
| `find_place(query=..., ...)` | Place search |
| `resolve_place(place, ...)` | Resolve a place suggestion |
| `email_validation(query=..., ...)` | Validate an email address |
| `phone_number_validation(query=..., ...)` | Validate a phone number |
| `key_availability(key)` | Public availability and licensed contexts |
| `key_details(key, user_token=...)` | Private settings and remaining lookups |
| `update_key_details(key, user_token=..., ...)` | Update selected key settings |
| `key_usage(key, user_token=..., ...)` | Usage totals and daily breakdown |
| `key_logs(key, user_token=..., ...)` | Paid lookup logs as CSV text |
| `list_licensees`, `create_licensee`, `retrieve_licensee`, `update_licensee`, `delete_licensee`, `regenerate_licensee_key` | Licensee management |
| `list_configs`, `create_config`, `retrieve_config`, `update_config`, `delete_config` | Saved configurations |
Methods expose every supported input in their signatures. Required keyword arguments must be supplied. Optional `None` values are omitted, leaving defaults to the server. Dataset lists are comma-joined. String flags use `"true"` and `"false"`, as declared by the API. Parameter constraints appear in each method's docstring; scalar ranges, lengths and enums are checked before sending.

Use `help(IdealPostcodes.find_address)` or inspect `ideal_postcodes/client.py` for parameters. Response definitions live in `ideal_postcodes/types.py`. `examples/` has a runnable script for each method.

## Keys

```python
from ideal_postcodes import IdealPostcodes

with IdealPostcodes("ak_test") as client:
    availability = client.key_availability("ak_test")
    print(availability.available)
```

The `key` argument identifies the key to inspect or update. Availability and `retrieve_config` need no Management Key. Other key, licensee and configuration methods require your Management Key as the keyword `user_token`; the client sends it in the `Authorization` header, never the URL. Load it from your application's secret storage. Usage and logs accept `start` and `end` as UNIX timestamps in milliseconds. `key_logs` returns CSV text rather than a response model.

Update only the settings you want to change. Nested settings accept typed dictionaries from `ideal_postcodes.requests`. For example, `daily_limit={"limit": None}` disables the daily limit, `notifications={"enabled": False}` disables notifications and `allowed_urls=[]` clears the URL restriction. Omitted top-level settings remain unchanged. Nested `None` values are sent as JSON `null`.

## Errors and retries

All SDK request failures derive from `ClientError`:

- `APIError`: unsuccessful HTTP response, with `status`, `code`, `message`, `body` and raw `retry_after`
- `AuthenticationError`, `PaymentRequiredError`, `NotFoundError`, `RateLimitError` and `ServerError`: API error subclasses
- `TransportError`: connection or timeout failure before a response
- `ProtocolError`: unexpected success status, malformed envelope or invalid response fields. When response fields fail validation, `result` holds the unvalidated result, so `except ProtocolError as error: raw = error.result` can fall back to it

`NotFoundError.suggestions` contains postcode suggestions when supplied by the API. HTTP 429 and HTTP 503 with API code 5030 raise `RateLimitError`. Other 5xx responses raise `ServerError`. Exception strings include the API's `message`, for example `API request failed (HTTP 404, code 4040): Postcode not found`, and omit credentials and request URLs. `body` may contain caller input.

There are no automatic retries. A timeout may occur after a lookup was processed. Choose retry behaviour in the application and bound attempts; `Retry-After` can be seconds or an HTTP date. Inputs are checked against the spec before the network call: a missing argument, wrong type or unknown settings key raises `TypeError`, and an out-of-range number, string length or pattern raises `ValueError`. Option values the SDK does not list yet pass through to the API.

## Testing and limits

Pass `transport=httpx.MockTransport(handler)` to test without network access, or override `base_url`. The default timeout is 10 seconds.

Response models validate required fields, types, lists and unions from the checked-in OpenAPI contract, and accept normal API growth: extra fields survive in `model_dump()`, option fields are plain strings so new values come through, and a `native` record from a dataset the SDK does not know yet arrives as a dictionary. An explicit `filter` returns address dictionaries containing the requested fields; paginated address search returns a model whose `hits` are dictionaries. The client is synchronous; run it in a worker thread when calling from an async application.

Signup is not included.

## Development

Run `uv sync --frozen`, then `make ci` and `make build`. `make generate` rebuilds the client, transport and response types from the shared generator and this brand's spec. `make check-generated` detects drift without rewriting committed files. `make format` applies Ruff fixes and formatting; `make ci` checks both. Run commands from this package directory.

## Licence

SEE LICENSE IN LICENSE
