Version 1.0.1 · Stable release · Apache-2.0

Keep Python business logic independent from FastAPI

A web framework should not have to own your domain function. With APIZIT Linking, the Python module calculates a result using ordinary values, while YAML declares how HTTP path, query, header, or body fields become function arguments.

The boundary to preserve#

Framework coupling usually starts when a business function accepts a request object, raises HTTPException, returns a framework response, or carries a route decorator. Those choices make the same logic harder to call from a script, a worker, a test, or another adapter.

For this guide, the project remains only two files:

Text
pricing-api/
├── apizit_linking.yaml
└── pricing.py

APIZIT Linking uses FastAPI only in its optional preview adapter. Your business module does not import FastAPI or APIZIT Linking.

1. Write and test the business function#

Create pricing.py:

Python
def calculate_total(
    unit_price: float,
    quantity: int,
    tax_rate: float = 0.21,
) -> dict[str, float | int]:
    subtotal = round(unit_price * quantity, 2)
    tax = round(subtotal * tax_rate, 2)
    return {
        "quantity": quantity,
        "unit_price": unit_price,
        "subtotal": subtotal,
        "tax": tax,
        "total": round(subtotal + tax, 2),
    }

This function knows about prices, quantities, and tax. It does not know about HTTP. Test it directly before adding any route:

Text
python -c "from pricing import calculate_total; assert calculate_total(12.5, 2)['total'] == 30.25"

That same import remains valid in a unit test, command-line job, notebook, or queue worker. HTTP is a caller, not the center of the module.

2. Add HTTP binding outside the code#

Create apizit_linking.yaml. Each body field maps explicitly to one Python parameter:

YAML
version: 1

runtime:
  language: python
  version: "3.12"

routes:
  - path: /quotes
    method: POST
    function: pricing:calculate_total
    parameters:
      unit_price:
        source:
          location: body
          name: unit_price
      quantity:
        source:
          location: body
          name: quantity
      tax_rate:
        source:
          location: body
          name: tax_rate

unit_price and quantity are required because the Python signature has no defaults. tax_rate is optional: when the JSON field is absent, APIZIT Linking omits the keyword and Python applies 0.21.

Type hints drive the supported primitive conversions. For example, the JSON string "2" can become an integer, but a non-integer value produces a structured request error before the function is invoked.

3. Validate and run the API#

Install the 1.0.1 preview/models extras, validate the static contract, then start the local server:

Text
python -m pip install "apizit-linking[preview,models]==1.0.1"
apizit-linking validate .
apizit-linking preview . --port 8080

Validation should report one compiled route. In another terminal, send a JSON object:

Text
curl -X POST "http://127.0.0.1:8080/quotes" \
  -H "Content-Type: application/json" \
  -d '{"unit_price":12.5,"quantity":2}'

Expected response (200 OK):

JSON
{
  "quantity": 2,
  "unit_price": 12.5,
  "subtotal": 25.0,
  "tax": 5.25,
  "total": 30.25
}

Send {"unit_price":12.5,"quantity":"many"} and the adapter returns 400 Bad Request with error code INVALID_PARAMETER. The pricing function is not called.

Why the separation matters#

ConcernPython moduleLinking YAML / adapter
Business calculationOwns itDoes not duplicate it
Route and HTTP methodUnawareDeclares it
Request sourceReceives ordinary valuesMaps body, query, path, header, form, or file
Local serverNo server dependencyOptional preview extra
Unit testingDirect function callSeparate integration tests

This design also keeps deployment choices open. APIZIT can consume the compiled linking contract, while another platform can use the standalone package and optional FastAPI adapter. Neither choice requires a decorator in pricing.py.

Where V1 stops#

The example keeps legacy binding and a plain Python function. Its default successful response is 200; request conversion failures use 400 and unmapped domain exceptions use a generic JSON 500. The 1.0 release adds explicit response/errors policies, optional nested models, injected resources and guards. Add those policies deliberately and test their behavior. Preview imports trusted code and provides no production sandbox.

The persistent API exercises models, transactions and permissions. Current limits distinguish the supported contract from streaming, identity-provider and storage responsibilities.

Use this boundary with an AI assistant#

Ask your assistant to work on the Python behavior first: inputs, return value and domain cases. Review or run those function tests before adding an HTTP route. Then provide the function signature and the intended method, path and parameter sources as a separate task. This keeps the business decision and the HTTP mapping explicit for both the assistant and the reviewer.

Validate the proposed manifest with JSON diagnostics, inspect the affected module:function targets and preview the request cases. Static validation checks supported contracts; it does not prove business correctness or execute the function. Preview imports the project and must only run code you trust. The architecture makes these boundaries inspectable, but lower token usage, faster work and fewer mistakes would need measurement for your own workflow.

Follow the AI-assisted development guide for the complete sequence. For debugging, trace a route back to Python. Compare Flask and FastAPI when you need a different HTTP contract.

Download this page as Markdown