Metadata-Version: 2.4
Name: apiup
Version: 0.7.0
Summary: Start a local mock REST API server from an OpenAPI 3.x spec. Convention: reads ~/.openapi/spec.yaml by default.
Project-URL: Homepage, https://github.com/roebi/apiup
Project-URL: Repository, https://github.com/roebi/apiup
Project-URL: Issues, https://github.com/roebi/apiup/issues
Project-URL: Changelog, https://github.com/roebi/apiup/blob/main/CHANGELOG.md
Author-email: "roebi (Robert Halter)" <roebi@users.noreply.github.com>
License: MIT
License-File: LICENSE
Keywords: api,cli,litestar,mock,openapi,rest,server
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Topic :: Software Development :: Testing :: Mocking
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: litestar[standard]>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: uvicorn>=0.29
Provides-Extra: dev
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: types-pyyaml; extra == 'dev'
Provides-Extra: validate
Requires-Dist: openapi-spec-validator>=0.7; extra == 'validate'
Description-Content-Type: text/markdown

# apiup

> ⚡ Start a local mock REST API server from an OpenAPI 3.x spec — one command.

[![CI](https://github.com/roebi/apiup/actions/workflows/ci.yml/badge.svg)](https://github.com/roebi/apiup/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/apiup)](https://pypi.org/project/apiup/)
[![Python](https://img.shields.io/pypi/pyversions/apiup)](https://pypi.org/project/apiup/)
[![License: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC_BY--NC--SA_4.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/)

## Install

```bash
pip install apiup
```

With [uv](https://docs.astral.sh/uv/):

```bash
uv tool install apiup
```

With validation support:

```bash
pip install 'apiup[validate]'
```

## Convention: `~/.openapi/`

`apiup` follows an XDG-style user convention — place your default spec at:

```
~/.openapi/spec.json      ← preferred
~/.openapi/spec.yaml      ← fallback
```

Optional config:

```yaml
# ~/.openapi/config.yaml
port: 8080
host: 127.0.0.1
mode: mock
spec: ~/.openapi/spec.json
```

## Usage

```bash
apiup                          # reads ~/.openapi/spec.json, starts on :8080
apiup --spec ./my-api.yaml     # custom spec path
apiup --port 9000              # custom port
apiup --host 0.0.0.0           # bind all interfaces
apiup --list                   # list routes without starting server
apiup --validate               # validate spec against OpenAPI 3.x schema
apiup --version
apiup --help
```

## Example session

```
$ apiup --validate
   Validating: /home/roebi/.openapi/spec.json
   OpenAPI   : 3.0.3
   Title     : My API v1.0.0
   ✓ spec valid  (7 route(s))

$ apiup --list
Routes in: /home/roebi/.openapi/spec.json

  GET      /skills        # List all skills
  POST     /skills        # Create a new skill
  GET      /skills/{id}   # Get skill details
  DELETE   /skills/{id}   # Delete a skill

4 route(s) found.

$ apiup
⚡ apiup 0.6.0 — My API v1.0.0
   Spec  : /home/roebi/.openapi/spec.json
   Mode  : mock
   Listen: http://127.0.0.1:8080
   Docs  : http://127.0.0.1:8080/docs
   Spec  : http://127.0.0.1:8080/spec.json
   Routes: 4

$ curl http://localhost:8080/skills
[{"id": "create-agent-skill-en", "name": "Create Agent Skill", "version": "1.0.0"}]
```

## Built-in endpoints

Every `apiup` instance exposes two additional routes:

| Endpoint | Description |
|---|---|
| `GET /docs` | Swagger UI (no extra dependencies) |
| `GET /spec.json` | Raw OpenAPI spec served directly |

## Mock behaviour

In mock mode `apiup`:

- Registers one route handler per `[METHOD] /path` in the spec
- Returns the first `example` value found in the spec responses
- Falls back to `{"_mock": true, "note": "no example in spec"}` if none present
- Returns `204 No Content` with no body for routes that declare it
- Resolves local `$ref` pointers in spec components

Add `example:` fields to your spec responses to get real data back:

```yaml
/skills:
  get:
    responses:
      "200":
        content:
          application/json:
            example:
              - id: create-agent-skill-en
                name: Create Agent Skill
                version: "1.0.0"
```

## Server modes

| Mode | Status | Description |
|---|---|---|
| `mock` | ✓ available | returns spec examples |
| `proxy` | planned | forwards requests to a real backend |
| `record` | planned | proxy + saves real responses as spec examples |

## Validation

```bash
pip install 'apiup[validate]'
apiup --validate
apiup --validate --spec ./my-api.yaml

# use in CI — exits 1 if spec is invalid
apiup --validate || exit 1
```

Requires `openapi-spec-validator`. If not installed, `--validate` warns but does not block startup.

## Python API

```python
from apiup.spec import load_spec, extract_routes
from apiup.mock import extract_mock_response
from apiup.server import build_mock_app, serve
from apiup.config import load_config

cfg    = load_config(spec="./my-api.yaml", port=9000)
spec   = load_spec(cfg.spec)
routes = extract_routes(spec)
app    = build_mock_app(routes, spec, cfg.spec)
serve(app, host=cfg.host, port=cfg.port)
```

## Development

```bash
uv sync --all-extras
uv run pytest
uv run ruff check .
uv run ruff format .
```

## License

CC BY-NC-SA 4.0 — see [LICENSE](LICENSE).

Part of the [roebi agent-skills](https://github.com/roebi/agent-skills) ecosystem.
