Metadata-Version: 2.4
Name: rawintent
Version: 5.0.0
Summary: Build APIs with simple endpoints, persistent API keys, and sync or async request clients
Author: RawIntent contributors
License-Expression: MIT
Keywords: api,http,endpoints,api-key,fastapi,client
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi<1,>=0.115
Requires-Dist: uvicorn<1,>=0.30
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2.7
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: build>=1; extra == "dev"
Requires-Dist: twine>=6; extra == "dev"
Dynamic: license-file

# RawIntent 5

Build your own API: define endpoints, create API keys, and send requests.

Version 5 replaces the English-language engine and neural compiler completely.
It is a breaking redesign, not a compatible update to RawIntent 4. Pin
`rawintent==4.1.0` if you still need the old language engine.

## Install

```sh
pip install rawintent
```

For an unpublished local checkout, use `python -m pip install .`.
Python 3.10 or later is required.

## Create a server

Save this as `app.py`:

```python
from rawintent import API

api = API("My API")

@api.get("/hello")
def hello(name: str = "world"):
    return {"message": f"Hello, {name}!"}

@api.post("/add")
def add(a: float, b: float):
    return {"result": a + b}

@api.get("/health", public=True)
def health():
    return {"status": "ok"}

if __name__ == "__main__":
    api.run()
```

Run `python app.py`. It listens at `http://127.0.0.1:8000`.
Open `http://127.0.0.1:8000/docs` to try endpoints in your browser.

You can also generate the starter and launch it with:

```sh
rawintent init app.py
rawintent serve app:api --reload
```

`python -m rawintent` works anywhere the console command is unavailable.
`init` will not overwrite an existing file.

## Create a key and send requests

Run this in another terminal from the same working directory as your server:

```sh
rawintent keys create --name my-client
```

Copy the returned `ri_...` token. It is displayed only at creation and cannot be
recovered from the database. Put it in an environment variable or a secret store.
In PowerShell you can capture the key directly:

```powershell
$env:RAWINTENT_API_KEY = python -m rawintent keys create --name my-client
python examples/client.py
```

In your client script:

```python
import os
from rawintent import Client

with Client("http://127.0.0.1:8000", api_key=os.environ["RAWINTENT_API_KEY"]) as client:
    print(client.get("/hello", params={"name": "Vincent"}))
    print(client.post("/add", json={"a": 2, "b": 3}))
```

Output:

```text
{'message': 'Hello, Vincent!'}
{'result': 5.0}
```

Any HTTP client can call your API. Send the key in the `X-API-Key` header.
The interactive docs have an **Authorize** button for the same key.
Keys belong to the server/database that issued them; they are not PyPI tokens
or credentials for a hosted RawIntent service.

## Endpoint rules

- `@api.get`, `@api.post`, `@api.put`, `@api.patch`, and `@api.delete` register routes.
- Endpoints require a valid key unless `public=True` is explicit.
- GET and DELETE scalar arguments come from query parameters.
- POST, PUT, and PATCH arguments become fields of a JSON object.
- `{item_id}` in a route is a path parameter and takes precedence over the body.
- Python type annotations validate inputs. Missing or invalid input returns HTTP 422.
- Both regular `def` and `async def` handlers work.
- Dictionary and list results become JSON. Return `Response` for other formats.
- `@api.route("/status", methods=["GET", "HEAD"])` supports other HTTP methods.
- Register body-based and query-based methods separately.

```python
@api.put("/items/{item_id}")
def update_item(item_id: int, title: str, count: int = 1):
    return {"id": item_id, "title": title, "count": count}
```

Call with `client.put("/items/42", json={"title": "Notebook", "count": 3})`.

For a raw JSON body or explicit query arguments, use exported FastAPI helpers:

```python
from rawintent import Body, Query

@api.post("/echo")
def echo(data: dict = Body(...), verbose: bool = Query(False)):
    return {"data": data, "verbose": verbose}
```

`Body(...)` uses the entire JSON document. Ordinary parameters instead use named
fields, so `def echo(data: dict)` expects `{"data": {...}}`.
`BaseModel`, `Field`, `Depends`, `Request`, `Response`, and `HTTPException` are
also exported. See [FastAPI parameter documentation](https://fastapi.tiangolo.com/tutorial/body/)
for explicit parameter declarations and nested Pydantic models.

## Key management

The default SQLite registry is `.rawintent/keys.sqlite3`, relative to the process
working directory. Use an absolute path for servers launched from different directories:

```python
api = API("My API", key_store="C:/my-api/keys.sqlite3")
```

Use the same file with CLI commands:

```sh
rawintent keys create --store C:/my-api/keys.sqlite3 --name reader --scope items:read --expires-in 86400
rawintent keys list --store C:/my-api/keys.sqlite3
rawintent keys revoke KEY_ID --store C:/my-api/keys.sqlite3
```

`keys list` shows IDs, names, prefixes, scopes, expiration, and active status;
it never prints secret tokens. Revocation takes effect on the next authentication
check, including across server processes sharing the same SQLite file.

Create keys programmatically with `api.create_key("client-name")`, or use
`KeyStore(path).create(...)`. Create them during provisioning, not each time
an application imports or a development server reloads.

```python
@api.get("/items", scopes=["items:read"])
def list_items():
    return []
```

A key created with `scopes=["items:read"]` can access that endpoint. Missing
required scopes return 403. Keys with `scopes=None` (the default) have all scopes;
`scopes=[]` allows ordinary protected endpoints but no named scopes. Scope names
are exact strings, with no wildcard expansion. Expired or revoked keys return 401.
`request.state.api_key` contains authenticated `KeyInfo` inside a protected handler.

## Async clients and errors

```python
import asyncio
import os
from rawintent import AsyncClient, APIError

async def main():
    async with AsyncClient("http://127.0.0.1:8000", os.environ["RAWINTENT_API_KEY"]) as client:
        try:
            print(await client.post("/add", json={"a": 2, "b": 3}))
        except APIError as error:
            print(error.status_code, error.detail)

asyncio.run(main())
```

Clients parse JSON responses, return text for non-JSON responses, and return `None`
for empty responses. HTTP errors and redirects raise `APIError`. Network errors
and timeouts retain their HTTPX exception types. The default timeout is 30 seconds;
set `timeout=10` when constructing a client to change it. Writes are not retried.
Client requests accept paths on their configured server, not arbitrary external
URLs. Redirects are not followed. A base URL ending in `/v1` retains that prefix
for calls such as `client.get("/items")`.

## Hosting

RawIntent builds on [FastAPI](https://fastapi.tiangolo.com/),
[Uvicorn](https://www.uvicorn.org/), and [HTTPX](https://www.python-httpx.org/).
`api` is an ASGI application; run `rawintent serve app:api --host 0.0.0.0 --port 8000`
or `uvicorn app:api` on your host. Use a TLS reverse proxy for HTTPS outside local
development. Disable interactive docs with `API(docs=False)` if desired.
Docs and the OpenAPI schema are public when enabled, but protected endpoint calls
still require a key. `api.app` exposes the underlying FastAPI app for advanced
middleware and lifespan configuration. Routes added directly to `api.app` use
FastAPI rules and do not automatically get RawIntent key authentication.

The SQLite registry must live on persistent storage. Keep it and your tokens out
of source control. The library does not provide hosting, billing, user accounts,
rate limiting, or a public key-signup portal. Key creation is a local administrative
operation; there is no unauthenticated web endpoint that issues credentials.

## Development

```sh
python -m pip install -e ".[dev]"
python -m pytest -q
python -m build
python -m twine check dist/*
```
