Metadata-Version: 2.4
Name: apizit-linking
Version: 0.5.0
Summary: Declarative HTTP binding for plain Python functions without decorators
Author: APIZIT
License-Expression: Apache-2.0
Project-URL: Homepage, https://chipsi44.github.io/apizit-linking-examples/
Project-URL: Documentation, https://chipsi44.github.io/apizit-linking-examples/quickstart/
Project-URL: Examples, https://github.com/chipsi44/apizit-linking-examples
Project-URL: Issues, https://github.com/chipsi44/apizit-linking-examples/issues
Project-URL: Changelog, https://github.com/chipsi44/apizit-linking-examples/blob/main/CHANGELOG.md
Project-URL: Release notes, https://chipsi44.github.io/apizit-linking-examples/releases/
Project-URL: Migration guide, https://chipsi44.github.io/apizit-linking-examples/migrations/
Project-URL: Security, https://chipsi44.github.io/apizit-linking-examples/security/
Keywords: api,apizit,apizit-linking,declarative,fastapi,http,python,routing,yaml
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: PyYAML>=6.0.3
Provides-Extra: fastapi
Requires-Dist: fastapi<1,>=0.115.0; extra == "fastapi"
Requires-Dist: python-multipart<1,>=0.0.18; extra == "fastapi"
Provides-Extra: preview
Requires-Dist: fastapi<1,>=0.115.0; extra == "preview"
Requires-Dist: python-multipart<1,>=0.0.18; extra == "preview"
Requires-Dist: uvicorn<1,>=0.30.0; extra == "preview"
Dynamic: license-file

# APIZIT Linking

**Keep the code. Add the API.**

`apizit-linking` is a standalone declarative Python-to-HTTP binding engine. It
turns a versioned configuration file into validated HTTP routes while keeping
customer Python code unchanged. APIZIT is one platform that consumes it, not a
dependency of the package.

[Documentation](https://chipsi44.github.io/apizit-linking-examples/) ·
[Forkable examples](https://github.com/chipsi44/apizit-linking-examples) ·
[Public support and issues](https://github.com/chipsi44/apizit-linking-examples/issues)

```python
def create_customer(customer_name: str, age: int):
    return {"name": customer_name, "age": age}
```

```yaml
version: 1

routes:
  - path: /customers
    method: POST
    function: customer_service:create_customer
    parameters:
      customer_name:
        source:
          location: body
          name: name
      age:
        source:
          location: body
          name: age
```

There are no APIZIT decorators, imports, handlers, or web routes in customer
code.

## Install 0.5

The core validation and runtime engine requires only PyYAML:

```text
pip install "apizit-linking==0.5.0"
```

Install the local FastAPI preview as an optional extra:

```text
pip install "apizit-linking[preview]==0.5.0"
apizit-linking validate .
apizit-linking preview . --port 8080
```

Version 0.5.0 is a public beta. Pin it exactly for reproducible builds. The
public
[compatibility policy](https://chipsi44.github.io/apizit-linking-examples/reference/compatibility/)
separates package, Python API, manifest schema, diagnostics, and runtime-artifact
guarantees.

## Validate locally

The command searches the target directory for `apizit_linking.yaml`,
`apizit_linking.yml`, then `apizit_linking.json`:

```text
apizit-linking validate .
apizit-linking validate . --json
apizit-linking validate path/to/apizit_linking.yaml --project-root .
```

Validation is static. It parses the customer module with the Python AST and does
not import or execute customer code.

## Preview locally

```text
apizit-linking preview . --port 8080
```

Preview binds to `127.0.0.1` by default. It validates before importing customer
modules, then executes them in the server process. Binding to a non-loopback
interface requires `--allow-network`.

The development server exposes interactive Swagger documentation at `/docs`,
ReDoc at `/redoc`, and its OpenAPI 3.1 document at `/openapi.json`. The document
describes explicit path/query/header/body/form/file bindings, repeated values,
the stable Linking `400` error, and a minimal `200` response inferred from the
Python return annotation.

The preview server is a development tool, not an authentication layer, a
production deployment boundary, or a sandbox. Linked customer modules are
ordinary trusted Python code and may execute arbitrary code when imported or
called.

## Forkable example projects

The independent public
[APIZIT Linking examples repository](https://github.com/chipsi44/apizit-linking-examples)
contains a runnable Hello World starter and five additional projects covering
path/query parameters, JSON bodies, errors, packages, and a CRUD-style Task
API. It is the single source of truth for user-facing examples.

```text
git clone https://github.com/chipsi44/apizit-linking-examples.git
cd apizit-linking-examples
python -m pip install -r requirements.txt
apizit-linking validate .
apizit-linking preview . --port 8080
```

Every business module in that repository imports neither APIZIT Linking nor a
web framework. Fork it as a starter or pin one of its commits as an APIZIT
integration fixture.

## V1 configuration reference

The root document is a closed, versioned object:

```yaml
version: 1
runtime:
  language: python
  version: "3.12"
routes:
  - path: /customers/{customer_id}
    method: PATCH
    function: customer_service:update_customer
```

Editor completion and structural validation use the
[canonical JSON Schema](https://chipsi44.github.io/apizit-linking-examples/schema/apizit-linking-v1.schema.json).

`function` uses the canonical dotted `module:function` form. `path` must be an
absolute HTTP path and route placeholders use `{parameter_name}`. Canonical
methods are uppercase and must be one of `GET`, `POST`, `PUT`, `PATCH`,
`DELETE`, `OPTIONS`, or `HEAD`; the compiler normalizes case-insensitive
migration input.

The compiler verifies module and function existence, top-level callability,
supported signatures, parameter bindings, request-source compatibility, and
route collisions. A static sibling such as `/customers/search` is registered
before `/customers/{customer_id}` regardless of declaration order. Cross-shaped
patterns that overlap without a safe specificity order are rejected as
`ROUTE_AMBIGUITY`.

### Explicit parameter sources

The canonical source syntax is a structured object:

```yaml
parameters:
  customer_name:
    source:
      location: body
      name: name
```

This binds HTTP body field `name` to Python parameter `customer_name`. When a
source is explicit, only that source is inspected. There is no fallback to
another source.

V1 supports:

- `path`
- `query`
- `header`
- `body`
- `form`
- `file`

Header names are case-insensitive; other external names are exact. A route
cannot require both a JSON `body` value and a `form` or `file` value because
those request encodings are incompatible.

### Automatic parameter resolution

If a parameter has no explicit source, V1 searches its Python name in this
stable order:

```text
path > query > header > body > form > file
```

The first present source wins. A required absent parameter produces
`PARAMETER_NOT_FOUND`; explicit mode produces
`PARAMETER_NOT_FOUND_IN_SOURCE`. Parameters with Python defaults are omitted
from the generated keyword arguments so Python applies their defaults.

Missing values, JSON `null`, and empty strings are distinct. `null` is accepted
only by a nullable annotation. V1 converts primitive `str`, `int`, `float`,
`bool`, nullable unions, lists, and dictionaries. If one annotation contains an
unknown forward reference, other resolvable annotations are still converted.

### Stable error shape

Request errors use a JSON object:

```json
{
  "error": {
    "code": "PARAMETER_NOT_FOUND_IN_SOURCE",
    "message": "Required field 'name' was not found in request source 'body'.",
    "parameter": "customer_name",
    "external_name": "name",
    "source": "body"
  }
}
```

Configuration diagnostics include a stable code, severity, field, route index,
parameter, and relevant source metadata. The beta does not yet guarantee that
new minor releases will never add diagnostic codes or metadata fields.

## Python API

Platform adapters compile with `compile_linking_file`, serialize the complete
validated `CompilationResult` with `to_runtime_dict()`, then consume that
closed, versioned artifact through
`create_app_from_runtime_artifact`. Compilation is the static boundary;
customer imports happen only through the runtime loader, which verifies that
the packaged function signatures, sync/async callable kind, and return
annotation still match the compiled contract.

The optional ASGI adapter is available without changing customer code:

```python
from apizit_linking.fastapi import create_app

app = create_app(".")
```

Platforms may wrap that app with their own server, authentication, metering, or
deployment layer. Reusable FastAPI factories keep documentation routes disabled
by default so deployment adapters do not expose them accidentally; pass
`docs=True` to enable `/docs`, `/redoc`, and `/openapi.json`. If one of those
paths (including Swagger's OAuth redirect) could be handled by a linked route,
the adapter moves the documentation endpoints under a safe generated prefix and
exposes the selected URLs on the FastAPI app.

OpenAPI metadata is generated statically from the same compiled route contract
stored in the versioned runtime artifact. It does not invoke customer functions
or delegate request validation to FastAPI, so Linking keeps its documented
`400` errors instead of introducing FastAPI `422` errors. Primitive, nullable,
list, and dictionary annotations produce JSON schemas. Missing or custom
annotations remain unconstrained. Return schemas are documentary: the adapter
performs no annotation-based validation or transformation of customer return
values. FastAPI still performs its normal response serialization.

Automatic parameters are shown through a portable query representation and an
`x-apizit-automatic-binding` extension that preserves the real resolution order
and alternative sources. See
[`docs/adr/0001-documentary-openapi-and-minimal-responses.md`](docs/adr/0001-documentary-openapi-and-minimal-responses.md)
for the complete contract and its intentionally deferred features.

## Package scope

The package owns discovery, YAML/YML/JSON parsing, closed V1 validation, safe
static `module:function` resolution, AST signature inspection, compilation,
structured diagnostics, request-context resolution, primitive conversion,
sync/async invocation, and the optional FastAPI preview.

It intentionally does not own Mangum, API Gateway, Lambda, deployment, metering,
or APIZIT scan UI/reporting. The core installation remains independent of
FastAPI.

## Development

Maintainers working from an authorized source checkout can run:

```text
uv sync --all-extras --all-groups
uv run python -m unittest discover -s tests -p "test_*.py"
uv run ruff check src tests
uv run black --check src tests
uv build
```

The complete V1 reference, release procedure, remaining work, and test support
are included in the source distribution. The public example gallery is
maintained separately. APIZIT Linking is licensed under Apache-2.0.
