Skip to content

Commit 46f404b

Browse files
committed
API Server
1 parent 764b4db commit 46f404b

20 files changed

Lines changed: 1635 additions & 14 deletions

Dockerfile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ WORKDIR /app
1515
COPY pyproject.toml README.md LICENSE ./
1616
COPY sidemantic/ sidemantic/
1717
COPY examples/ examples/
18+
COPY scripts/ scripts/
1819

19-
RUN uv pip install --system --no-cache ".[serve,mcp,all-databases]"
20+
RUN uv pip install --system --no-cache ".[serve,mcp,api,all-databases]"
21+
RUN mkdir -p /app/models
22+
RUN cp -R /app/examples/multi_format_demo/. /app/models/
23+
RUN python /app/scripts/build_demo_duckdb.py
2024

2125
# --- Runtime stage (no build tools) ---
2226
FROM python:3.12-slim
@@ -33,9 +37,11 @@ COPY docker-entrypoint.sh /docker-entrypoint.sh
3337
RUN chmod +x /docker-entrypoint.sh
3438

3539
RUN mkdir -p /app/models
40+
COPY --from=builder /app/models/ /app/models/
3641
WORKDIR /app/models
3742

3843
EXPOSE 5433
44+
EXPOSE 4400
3945

4046
ENTRYPOINT ["/docker-entrypoint.sh"]
41-
# Mode is controlled by SIDEMANTIC_MODE env var (serve, mcp, both)
47+
# Mode is controlled by SIDEMANTIC_MODE env var (serve, mcp, api, both)

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ Malloy support (uv):
2323
uv add "sidemantic[malloy]"
2424
```
2525

26+
HTTP API server (uv):
27+
```bash
28+
uv add "sidemantic[api]"
29+
```
30+
2631
Notebook widget (uv):
2732
```bash
2833
uv add "sidemantic[widget]" jupyterlab
@@ -129,6 +134,9 @@ sidemantic workbench models/ --db data.duckdb
129134
# PostgreSQL server (connect Tableau, DBeaver, etc.)
130135
sidemantic serve models/ --port 5433
131136

137+
# HTTP API server (JSON or Arrow)
138+
sidemantic api-serve models/ --port 4400 --auth-token secret
139+
132140
# Validate definitions
133141
sidemantic validate models/
134142

@@ -154,6 +162,11 @@ uvx sidemantic workbench --demo
154162
uvx sidemantic serve --demo --port 5433
155163
```
156164

165+
**HTTP API server** (JSON or Arrow):
166+
```bash
167+
uvx --from "sidemantic[api]" sidemantic api-serve --demo --port 4400 --auth-token secret
168+
```
169+
157170
**Colab notebooks:**
158171

159172
[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/sidequery/sidemantic/blob/main/examples/notebooks/sidemantic_sql_duckdb_demo.ipynb) SQL + DuckDB
@@ -219,6 +232,7 @@ See `examples/` for more.
219232
- Segments and metric-level filters
220233
- Jinja2 templating for dynamic SQL
221234
- PostgreSQL wire protocol server for BI tools
235+
- HTTP API with JSON and Arrow IPC responses
222236

223237
## Multi-Format Support
224238

@@ -264,6 +278,54 @@ docker run -p 5433:5433 sidequery/sidemantic --demo
264278

265279
See [`examples/docker/`](examples/docker/) for MCP mode, env vars, building from source, and integration test services.
266280

281+
For Cloudflare Worker + Container deployment, see [`examples/cloudflare_containers/`](examples/cloudflare_containers/).
282+
283+
## HTTP API
284+
285+
Start the API server:
286+
287+
```bash
288+
sidemantic api-serve models/ --db data.duckdb --port 4400 --auth-token secret
289+
```
290+
291+
Compile a structured semantic query:
292+
293+
```bash
294+
curl -s http://localhost:4400/compile \
295+
-H "Authorization: Bearer secret" \
296+
-H "Content-Type: application/json" \
297+
-d '{"dimensions":["orders.status"],"metrics":["orders.total_amount"]}'
298+
```
299+
300+
Run a structured query as JSON:
301+
302+
```bash
303+
curl -s http://localhost:4400/query \
304+
-H "Authorization: Bearer secret" \
305+
-H "Content-Type: application/json" \
306+
-d '{"dimensions":["orders.status"],"metrics":["orders.total_amount","orders.order_count"]}'
307+
```
308+
309+
Run a structured query as Arrow IPC:
310+
311+
```bash
312+
curl -s http://localhost:4400/query \
313+
-H "Authorization: Bearer secret" \
314+
-H "Accept: application/vnd.apache.arrow.stream" \
315+
-H "Content-Type: application/json" \
316+
-d '{"metrics":["orders.order_count"]}' \
317+
> result.arrow
318+
```
319+
320+
Execute rewritten SQL over HTTP:
321+
322+
```bash
323+
curl -s http://localhost:4400/sql \
324+
-H "Authorization: Bearer secret" \
325+
-H "Content-Type: application/json" \
326+
-d '{"query":"SELECT status, total_amount FROM orders ORDER BY status"}'
327+
```
328+
267329
## Agent Skill
268330

269331
Sidemantic ships an [agent skill](skills/sidemantic-modeler/) that teaches Claude Code, Codex, and other `SKILL.md`-compatible agents to build, validate, and query semantic models.

docker-entrypoint.sh

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
#!/bin/sh
22
set -e
33

4-
# SIDEMANTIC_MODE: "serve" (default), "mcp", or "both"
4+
# SIDEMANTIC_MODE: "serve" (default), "mcp", "api", or "both"
55
MODE="${SIDEMANTIC_MODE:-serve}"
6+
DEMO_ARGS=""
7+
if [ -n "$SIDEMANTIC_DEMO" ]; then
8+
DEMO_ARGS="--demo"
9+
fi
610

711
# Build arg arrays for each command.
812
# serve accepts: --connection, --db, --host, --port, --username, --password
@@ -32,21 +36,50 @@ if [ -n "$SIDEMANTIC_DB" ]; then
3236
MCP_ARGS="$MCP_ARGS --db \"$SIDEMANTIC_DB\""
3337
fi
3438

39+
# HTTP API args
40+
API_ARGS="--host 0.0.0.0"
41+
if [ -n "$SIDEMANTIC_CONNECTION" ]; then
42+
API_ARGS="$API_ARGS --connection \"$SIDEMANTIC_CONNECTION\""
43+
fi
44+
if [ -n "$SIDEMANTIC_DB" ]; then
45+
API_ARGS="$API_ARGS --db \"$SIDEMANTIC_DB\""
46+
fi
47+
if [ -n "$SIDEMANTIC_API_TOKEN" ]; then
48+
API_ARGS="$API_ARGS --auth-token \"$SIDEMANTIC_API_TOKEN\""
49+
fi
50+
if [ -n "$SIDEMANTIC_API_PORT" ]; then
51+
API_ARGS="$API_ARGS --port \"$SIDEMANTIC_API_PORT\""
52+
fi
53+
if [ -n "$SIDEMANTIC_MAX_REQUEST_BODY_BYTES" ]; then
54+
API_ARGS="$API_ARGS --max-request-body-bytes \"$SIDEMANTIC_MAX_REQUEST_BODY_BYTES\""
55+
fi
56+
if [ -n "$SIDEMANTIC_CORS_ORIGINS" ]; then
57+
OLD_IFS="$IFS"
58+
IFS=','
59+
for ORIGIN in $SIDEMANTIC_CORS_ORIGINS; do
60+
API_ARGS="$API_ARGS --cors-origin \"$ORIGIN\""
61+
done
62+
IFS="$OLD_IFS"
63+
fi
64+
3565
case "$MODE" in
3666
serve)
37-
eval exec sidemantic serve $SERVE_ARGS "$@"
67+
eval exec sidemantic serve $SERVE_ARGS $DEMO_ARGS "$@"
3868
;;
3969
mcp)
40-
eval exec sidemantic mcp-serve $MCP_ARGS "$@"
70+
eval exec sidemantic mcp-serve $MCP_ARGS $DEMO_ARGS "$@"
71+
;;
72+
api)
73+
eval exec sidemantic api-serve $API_ARGS $DEMO_ARGS "$@"
4174
;;
4275
both)
43-
eval sidemantic serve $SERVE_ARGS &
76+
eval sidemantic serve $SERVE_ARGS $DEMO_ARGS &
4477
SERVE_PID=$!
4578
trap "kill $SERVE_PID 2>/dev/null" EXIT
46-
eval exec sidemantic mcp-serve $MCP_ARGS "$@"
79+
eval exec sidemantic mcp-serve $MCP_ARGS $DEMO_ARGS "$@"
4780
;;
4881
*)
49-
echo "Unknown SIDEMANTIC_MODE: $MODE (use serve, mcp, or both)" >&2
82+
echo "Unknown SIDEMANTIC_MODE: $MODE (use serve, mcp, api, or both)" >&2
5083
exit 1
5184
;;
5285
esac
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
.wrangler/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM sidequery/sidemantic:latest
2+
3+
ENV SIDEMANTIC_MODE=api
4+
ENV SIDEMANTIC_API_PORT=4400
5+
6+
WORKDIR /app/models
7+
8+
# Demo mode makes the example deployable without bundling local models.
9+
# For a real deployment, replace this with baked-in models and remove --demo.
10+
CMD ["--demo"]
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Cloudflare Containers
2+
3+
This example runs the Sidemantic HTTP API behind a Cloudflare Worker that proxies requests into Cloudflare Containers.
4+
5+
## Deployment shape
6+
7+
- HTTPS terminates at the Worker.
8+
- The Worker starts a Sidemantic container and forwards requests to port `4400`.
9+
- The container runs `SIDEMANTIC_MODE=api`.
10+
- The example image bakes demo models and a seeded `demo.duckdb` into `/app/models`.
11+
12+
For a real deployment, the clean shape is:
13+
14+
- bake your semantic models into the container image
15+
- point Sidemantic at an external database with `SIDEMANTIC_CONNECTION`
16+
- keep DuckDB files out of the container unless they are disposable, because Cloudflare container disks reset on stop
17+
18+
## Files
19+
20+
- `wrangler.jsonc`: Cloudflare Worker and container binding config
21+
- `src/index.ts`: Worker entrypoint and container proxy
22+
- `Dockerfile`: Sidemantic container image used by Cloudflare
23+
24+
## Prereqs
25+
26+
- Bun
27+
- Docker
28+
- A Cloudflare account with Containers enabled
29+
- Wrangler auth: `bunx wrangler whoami`
30+
31+
## Deploy the demo
32+
33+
```bash
34+
cd examples/cloudflare_containers
35+
bun install
36+
bunx wrangler secret put SIDEMANTIC_API_TOKEN
37+
bun run deploy
38+
```
39+
40+
After deploy, query the worker URL:
41+
42+
```bash
43+
curl -s https://YOUR-WORKER.workers.dev/query \
44+
-H "Authorization: Bearer YOUR_TOKEN" \
45+
-H "Content-Type: application/json" \
46+
-d '{"metrics":["customers.customer_count"]}'
47+
```
48+
49+
Arrow still works through the Worker:
50+
51+
```bash
52+
curl -s https://YOUR-WORKER.workers.dev/query \
53+
-H "Authorization: Bearer YOUR_TOKEN" \
54+
-H "Accept: application/vnd.apache.arrow.stream" \
55+
-H "Content-Type: application/json" \
56+
-d '{"metrics":["customers.customer_count"]}' \
57+
> result.arrow
58+
```
59+
60+
## Switching from demo to real models
61+
62+
Edit `Dockerfile` to bake in your models:
63+
64+
```dockerfile
65+
FROM sidequery/sidemantic:latest
66+
67+
ENV SIDEMANTIC_MODE=api
68+
ENV SIDEMANTIC_API_PORT=4400
69+
70+
COPY models/ /app/models/
71+
WORKDIR /app/models
72+
```
73+
74+
Then remove `SIDEMANTIC_DB` from the default container env in [src/index.ts](/Users/nico/Code/sidemantic/examples/cloudflare_containers/src/index.ts).
75+
76+
Then set your warehouse connection:
77+
78+
```bash
79+
bunx wrangler secret put SIDEMANTIC_CONNECTION
80+
```
81+
82+
The Worker passes `SIDEMANTIC_CONNECTION`, `SIDEMANTIC_API_TOKEN`, and `SIDEMANTIC_CORS_ORIGINS` into the container at startup.
83+
84+
## Auth
85+
86+
The container already supports bearer-token auth via `SIDEMANTIC_API_TOKEN`.
87+
88+
If you want proper edge auth, put the deployed hostname behind Cloudflare Access and use service tokens or your IdP there. Keep the app bearer token as a second gate if you want defense in depth.
89+
90+
## Notes
91+
92+
- Cloudflare Containers is beta.
93+
- Cold starts are materially slower than plain Workers.
94+
- The Worker is required. The container is not exposed directly on the public Internet.

0 commit comments

Comments
 (0)