Metadata-Version: 2.4
Name: alt-stack-openapi-pydantic
Version: 1.7.0
Summary: Generate Pydantic models, typed route maps, and an asyncio client from OpenAPI
License-Expression: MIT
Project-URL: Homepage, https://github.com/anthonyaltieri/alt-stack
Project-URL: Repository, https://github.com/anthonyaltieri/alt-stack
Project-URL: Issues, https://github.com/anthonyaltieri/alt-stack/issues
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic[email]>=2.7.0
Provides-Extra: dev
Requires-Dist: alt-stack-http-client-httpx; extra == "dev"
Requires-Dist: pyrefly>=1.0.0; extra == "dev"
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: ruff>=0.6.0; extra == "dev"
Requires-Dist: ty>=0.0.78; extra == "dev"
Dynamic: license-file

# `alt-stack-openapi-pydantic`

Generate Python 3.11+ Pydantic 2 models, statically typed `Request`/`Response` route maps, and an asyncio `HttpxApiClient` from OpenAPI JSON. This is the Python counterpart of `@alt-stack/zod-openapi`; the generated client runs on `alt-stack-http-client-httpx`.

## Install

```bash
python -m pip install alt-stack-openapi-pydantic
```

Consumers of a generated SDK install the runtime package instead:

```bash
python -m pip install alt-stack-http-client-httpx
```

## Generate

```bash
alt-stack-openapi-pydantic ./openapi.json --output ./generated_types.py
```

`input` may be a local JSON path or HTTP(S) URL. Use `--registry` to execute custom type mappings and `--include` to insert imports/definitions into output.

```python
from generated_types import User

user = User.model_validate({"id": "u_1", "name": "Ada"})
```

The CLI enables route generation and emits `Request`/`Response` dictionaries whose leaves are Pydantic model classes. The dictionaries carry generated `TypedDict` annotations, so type checkers resolve literal lookups to the exact model and reject unknown paths, methods, request parts, and status codes:

```python
from generated_types import Request, Response

Request["/users"]["POST"]["body"]          # type[CreateUserBody]
Response["/users/{id}"]["GET"]["200"]      # type[User]
Response["/users/{id}"]["GET"]["999"]      # static error: unknown status
```

Generated modules also include an asyncio `HttpxApiClient` with one typed method per route, built on `alt_stack_http_client_httpx`:

```python
from alt_stack_http_client_httpx import ApiSuccess

from generated_types import GetUsersIdParams, HttpxApiClient, Request, Response

async with HttpxApiClient(
    "https://api.example.com", request_map=Request, response_map=Response
) as client:
    result = await client.get("/users/{id}", params=GetUsersIdParams(id="u_1"))

if isinstance(result, ApiSuccess):
    result.body  # User; unknown paths, missing params, and wrong models are static errors
```

Generated modules that use non-object intersections inline a small `all_of` validator, so they depend only on `pydantic` and `alt-stack-http-client-httpx` at runtime.

## Branded types

Annotate an identity schema in OpenAPI:

```json
{ "type": "string", "format": "uuid", "x-altstack-brand": "UserId" }
```

The generated module declares `UserId = NewType("UserId", UUID)` once and uses it
in field and parameter annotations. Pydantic still parses UUID strings into UUID
values and applies each use's existing constraints. Optional and nullable fields,
references, array items, and union members retain the nominal type.

Names must be ASCII identifiers (`[A-Za-z_][A-Za-z0-9_]*`) and must not be Python
keywords, imported helper names, or unrelated component names. Conflicting base
types produce a generation error. If a component and its brand have the same name,
the Pydantic wrapper is named `UserIdModel`, leaving `UserId` for the `NewType`.
Python requires a class as a `NewType` base. Literal brands use their primitive
base; composite unions use `object` with the original Pydantic validator retained
in `Annotated` metadata.

## Development

```bash
uv run --project packages/python-pydantic-openapi --extra dev pytest packages/python-pydantic-openapi/tests
uv run --project packages/python-pydantic-openapi --extra dev ruff check packages/python-pydantic-openapi
uv run --project packages/python-pydantic-openapi --extra dev ty check packages/python-pydantic-openapi
```

## Documentation

- [Code generation Quickstart](../../apps/docs/docs/codegen/quickstart.md)
- [Common Patterns](../../apps/docs/docs/codegen/common-patterns.md)
- [Python/Pydantic API Documentation](../../apps/docs/docs/codegen/api/pydantic-openapi.md)
