summaryrefslogtreecommitdiffstats
path: root/README.md
blob: d08b0bd252279e61ec83f3cbf934ae766c430a5f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# Hot functions API

A small HTTP service that registers typed Python functions and calls them with
JSON. It derives input and output schemas from type annotations; there is no
separate schema registration step or UI.

## Run locally

Python 3.10 or newer is needed for `int | str` annotations in the examples.
The `curl` examples use Bash's `$'...'` quoting for multiline source.

```sh
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
python server.py
```

The service listens on port 8000. Functions live in memory and disappear when
the server stops.

## Register and call a function

Send a complete, synchronous function definition. The function name must match
the URL, and every parameter and the return value must have a type annotation.

```sh
curl -X PATCH http://localhost:8000/double \
  --data-binary $'def double(x: int) -> int:\n    return x * 2'
```

With one parameter, POST either a bare JSON value or a named argument:

```sh
curl -X POST http://localhost:8000/double \
  -H 'Content-Type: application/json' -d '4'
# 8

curl -X POST http://localhost:8000/double \
  -H 'Content-Type: application/json' -d '{"x":4}'
# 8
```

The response is the function's JSON result, without a `result` wrapper. Input
and output are validated against the annotations in Pydantic's strict mode.

### Multiple arguments and union types

```sh
curl -X PATCH http://localhost:8000/label \
  --data-binary $'def label(x: int | str, prefix: str = "item") -> str:\n    return f"{prefix}:{x}"'

curl -X POST http://localhost:8000/label \
  -H 'Content-Type: application/json' -d '{"x":4,"prefix":"order"}'
# "order:4"

curl -X POST http://localhost:8000/label \
  -H 'Content-Type: application/json' -d '{"x":"abc"}'
# "item:abc"
```

For multiple parameters, POST a JSON object with parameter names as keys.
Defaults may be omitted. Unrecognized keys are rejected.

A single parameter may accept any JSON value its annotation permits: an object,
array, string, number, boolean, or `null`. For example, a function annotated
`x: dict[str, int]` can receive `{"count":2}` directly or as
`{"x":{"count":2}}`. If an object also looks like valid named arguments, the
named-argument form takes precedence. Structured types can use Pydantic models
defined in the uploaded source.

## Discover functions and schemas

```sh
curl http://localhost:8000/double
curl http://localhost:8000/
```

`GET /double` returns an object containing `name`, `source`, and `schema`.
`GET /` returns `{"functions": [...]}` with the same objects for all registered
functions. For `double`, the input schema describes both accepted POST forms:

```json
{
  "name": "double",
  "source": "def double(x: int) -> int:\n    return x * 2",
  "schema": {
    "input": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "anyOf": [
        {
          "type": "object",
          "properties": {"x": {"title": "X", "type": "integer"}},
          "required": ["x"],
          "additionalProperties": false
        },
        {"type": "integer"}
      ]
    },
    "output": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "integer"
    }
  }
}
```

The input and output schemas are JSON Schema Draft 2020-12 documents. Replacing
a function with `PATCH` also regenerates its schemas.

## API responses

| Request | Result |
| --- | --- |
| `PATCH /name` | `200` on registration; `400` for an invalid definition |
| `POST /name` | `200` with the JSON result; `400` for invalid input; `404` if undefined; `500` if execution or output validation fails |
| `GET /name` | `200` with the function description; `404` if undefined |
| `GET /` | `200` with all defined functions |

Run the tests with `python -m unittest -v test_server`.

## Trust boundary

Uploaded definitions execute as Python in the server process, including at
registration time. The server has no authentication, isolation, request-size
limit, or execution timeout and binds to `0.0.0.0`. Only run it where every
caller is fully trusted; it is not safe to expose to untrusted clients.