Metadata-Version: 2.4
Name: apilize-protocol
Version: 1.1.0
Summary: Python SDK for the Apilize Protocol v1 — author financial models that speak the protocol over CLI stdio.
Author: Apilize
License: MIT
Project-URL: Homepage, https://github.com/Apilize/apilize-protocol
Project-URL: Repository, https://github.com/Apilize/apilize-protocol
Project-URL: Documentation, https://github.com/Apilize/apilize-protocol/tree/main/sdk/python
Keywords: apilize,protocol,financial-modeling,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: jsonschema>=4.20
Requires-Dist: PyYAML>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"

# apilize-protocol — Python SDK for the Apilize Protocol v1

Author financial-modeling containers that speak the [Apilize Protocol v1](https://github.com/Apilize/apilize-protocol) over CLI stdio. Subclass `Model`, override the handlers you need, ship a Docker container — the SDK takes care of the session loop, JSON-Lines framing, error envelopes, and exit codes.

## Install

```bash
pip install apilize-protocol
```

## Hello, Apilize — a complete model in ~30 lines

```python
# model/handlers.py
from apilize_protocol import Model

class HelloValuation(Model):
    def _compute(self, inputs):
        """Pure calculation shared by handle_run and handle_export."""
        years = inputs["assumptions"]["holding_period_years"]
        annual = sum(u["monthly_rent_eur"] for u in inputs["units"]) * 12
        return {
            "cash_flows": [annual] * years,
            "npv": annual * years,
            "irr": 0.0,
            "terminal_value": 0.0,
        }

    def handle_run(self, msg):
        yield {"type": "progress", "stage": "summing", "percent": 50}
        yield {"type": "result", "data": self._compute(msg["input"])}

    def handle_export(self, msg):
        import json
        from pathlib import Path
        out = Path(msg["outputPath"])
        out.write_text(json.dumps(self._compute(msg["input"])))
        return {
            "format": msg["format"],
            "outputPath": str(out),
            "byteSize": out.stat().st_size,
        }
```

```python
# model/__main__.py
import sys
from .handlers import HelloValuation

if __name__ == "__main__":
    sys.exit(HelloValuation.main())
```

Then add a `data/spec.yaml`, `data/ui.yaml`, `data/i18n/en.yaml`, copy the `Dockerfile.template` from this SDK as `Dockerfile`, and `docker build` — that's a conformant Apilize Protocol model.

## What you get from `Model`

| Method | Default behavior | Override when |
|---|---|---|
| `handle_spec` | Returns the contents of `data/spec.yaml` | You compute spec dynamically |
| `handle_ui` | Returns `data/ui.yaml` | You build UI from inputs |
| `handle_i18n` | Returns `data/i18n/{locale}.yaml`; raises `UnsupportedLocaleError` if missing | You source translations elsewhere |
| `handle_validate` | Runs `jsonschema` validation against `spec.inputSchema` | Domain-specific validation rules |
| `handle_run` | Raises `NotImplementedError` | **Always** — this is your model |
| `handle_export` | Raises `NotImplementedError` | If your spec declares export formats |

The session loop, SIGTERM handling, exit-code semantics, JSON-Lines framing, per-message error wrapping, and validation are handled by `Model.serve()` (invoked via `Model.main()`).

## Container packaging

Copy `Dockerfile.template` from this SDK into your model repo as `Dockerfile`. The template:

- Uses `python:3.12-slim` as base.
- Sets `PYTHONUNBUFFERED=1` — required for the session-mode transport to work correctly. Without this, the orchestrator's `readline()` deadlocks.
- Installs your `pyproject.toml` (which declares `apilize-protocol` as a dep).
- Sets `ENTRYPOINT ["python", "-m", "model"]`.

There is no published base image in v1.x; the template is the contract.

> **Note on the in-repo example Dockerfiles.** The `Dockerfile`s under `examples/dcf-example/` and `examples/hello-valuation/` install the SDK from the repository's `sdk/python/` source via `COPY sdk/python /tmp/sdk` rather than from PyPI. This is a repo-local-CI convenience — the SDK isn't published to PyPI yet, and the examples need to build against the in-tree source. External authors should use the template above, which installs from `pip install .` against their `pyproject.toml` that declares `apilize-protocol` as a regular dependency. The in-repo example divergence is internal to this repository's CI.

## Conformance

The full conformance suite lives in [`apilize-protocol/conformance/`](https://github.com/Apilize/apilize-protocol/tree/main/conformance). Run it against your container:

```bash
docker build -t my-model .
DOCKER_IMAGE=my-model pytest conformance/tests/
```

If all tests pass, your container is a conformant Apilize Protocol v1 model.

## Optional: logging

The SDK does not own a logging convention beyond *"stderr is empty on clean session"* (per the protocol contract). If you want diagnostics in container logs during development:

```python
import logging, sys
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
```

Note that conformance asserts STDERR is empty on clean session exit — disable logging or redirect to a file before running conformance.

## Public API

```python
from apilize_protocol import (
    Model,                      # subclass this to write a model
    Kind,                       # Enum: VALUATION / SOURCE / TRANSFORM
    SpecResponse, UiResponse, I18nResponse,
    ValidateResponse, ValidationError,
    RunProgress, RunResult,
    ExportResponse,
    ProtocolError,
    ApilizeProtocolError,       # base exception
    UnsupportedLocaleError,
    UnsupportedFormatError,
    InputValidationError,
    MissingFieldError,
    DEFAULT_PROTOCOL_RANGE,     # "^1.0"
)
```

## License

MIT. See [LICENSE](https://github.com/Apilize/apilize-protocol/blob/main/LICENSE) in the repository root.

## Versioning

SDK versions track protocol minor versions: `1.1.0` SDK targets protocol `1.1.0`. SDK-internal additive changes bump the SDK patch version while keeping the protocol target. Breaking changes require a new protocol major (v2) and a corresponding SDK major.
