api-dock 0.9.0__tar.gz → 0.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api_dock-0.10.0/PKG-INFO +347 -0
- api_dock-0.10.0/README.md +304 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/auth.py +186 -58
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/cli.py +259 -98
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/config.py +419 -146
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/config_discovery.py +30 -47
- api_dock-0.10.0/api_dock/database_backends.py +247 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/database_config.py +401 -158
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/encryption.py +49 -23
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/example_api_dock_config/config.yaml +3 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/example_api_dock_config/databases/config.yaml +36 -0
- api_dock-0.10.0/api_dock/example_api_dock_config/remotes/config.yaml +49 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/example_api_dock_config/remotes/example_remote.yaml +3 -1
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/fast_api.py +67 -8
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/flask_api.py +42 -1
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/listings.py +7 -27
- api_dock-0.10.0/api_dock/lookups.py +1128 -0
- api_dock-0.10.0/api_dock/postgres_backend.py +385 -0
- api_dock-0.10.0/api_dock/postgres_config.py +358 -0
- api_dock-0.10.0/api_dock/route_mapper.py +1446 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/sql_builder.py +472 -111
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/storage_auth.py +37 -75
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/types.py +21 -2
- api_dock-0.10.0/api_dock.egg-info/PKG-INFO +347 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock.egg-info/SOURCES.txt +20 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock.egg-info/requires.txt +7 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/pyproject.toml +12 -5
- api_dock-0.10.0/tests/test_auth.py +249 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_bound_parameters.py +8 -8
- api_dock-0.10.0/tests/test_encryption_and_discovery.py +137 -0
- api_dock-0.10.0/tests/test_json_conversion.py +140 -0
- api_dock-0.10.0/tests/test_lookup_cli.py +96 -0
- api_dock-0.10.0/tests/test_lookup_requests.py +343 -0
- api_dock-0.10.0/tests/test_lookup_sources.py +189 -0
- api_dock-0.10.0/tests/test_lookups.py +486 -0
- api_dock-0.10.0/tests/test_postgres_config.py +256 -0
- api_dock-0.10.0/tests/test_postgres_mixed.py +247 -0
- api_dock-0.10.0/tests/test_postgres_requests.py +177 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_proxy_pipeline.py +4 -4
- api_dock-0.10.0/tests/test_remote_config.py +264 -0
- api_dock-0.10.0/tests/test_remote_cookies.py +151 -0
- api_dock-0.10.0/tests/test_review_fixes.py +209 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_runtime_settings.py +7 -7
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_sql_builder.py +3 -3
- api_dock-0.10.0/tests/test_sql_placement.py +176 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_sql_selector.py +2 -2
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_startup_check.py +48 -4
- api_dock-0.10.0/tests/test_storage_auth.py +77 -0
- api_dock-0.9.0/PKG-INFO +0 -1702
- api_dock-0.9.0/README.md +0 -1663
- api_dock-0.9.0/api_dock/route_mapper.py +0 -991
- api_dock-0.9.0/api_dock.egg-info/PKG-INFO +0 -1702
- {api_dock-0.9.0 → api_dock-0.10.0}/LICENSE.md +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/__init__.py +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/example_api_dock_config/databases/example_db.yaml +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock/sql_template_check.py +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock.egg-info/dependency_links.txt +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock.egg-info/entry_points.txt +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/api_dock.egg-info/top_level.txt +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/setup.cfg +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_database_requests.py +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_inject_cookies.py +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_listings.py +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_schema_unions.py +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_shared_database_config.py +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_sql_template_check.py +0 -0
- {api_dock-0.9.0 → api_dock-0.10.0}/tests/test_types.py +0 -0
api_dock-0.10.0/PKG-INFO
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: api_dock
|
|
3
|
+
Version: 0.10.0
|
|
4
|
+
Summary: A flexible API gateway that allows you to proxy requests to multiple remote APIs and Databases
|
|
5
|
+
Author-email: Brookie Guzder-Williams <bguzder-williams@berkeley.edu>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Classifier: Topic :: Database
|
|
15
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE.md
|
|
21
|
+
Requires-Dist: click
|
|
22
|
+
Requires-Dist: fastapi<0.118,>=0.117.1
|
|
23
|
+
Requires-Dist: uvicorn<0.38,>=0.37.0
|
|
24
|
+
Requires-Dist: pyyaml<7,>=6.0.3
|
|
25
|
+
Requires-Dist: httpx<0.29,>=0.28.1
|
|
26
|
+
Requires-Dist: duckdb<2,>=1.1.3
|
|
27
|
+
Requires-Dist: flask
|
|
28
|
+
Requires-Dist: cryptography<49.0.0,>=48.0.0
|
|
29
|
+
Requires-Dist: boto3<2,>=1.42.59
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pycodestyle<3,>=2.14.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ipykernel<7,>=6.30.1; extra == "dev"
|
|
33
|
+
Requires-Dist: jupyterlab<5,>=4.4.9; extra == "dev"
|
|
34
|
+
Requires-Dist: numpy<3,>=2.3.3; extra == "dev"
|
|
35
|
+
Requires-Dist: pandas<3,>=2.3.2; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest<10,>=9.0.2; extra == "dev"
|
|
37
|
+
Provides-Extra: postgres
|
|
38
|
+
Requires-Dist: psycopg[binary]<4,>=3.3.6; extra == "postgres"
|
|
39
|
+
Requires-Dist: psycopg_pool<4,>=3.3.2; extra == "postgres"
|
|
40
|
+
Provides-Extra: gcp
|
|
41
|
+
Requires-Dist: google-cloud-secret-manager<3,>=2; extra == "gcp"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# API Dock
|
|
45
|
+
|
|
46
|
+
API Dock builds an API from YAML files. Behind one endpoint it proxies requests to **remote HTTP
|
|
47
|
+
APIs** and serves **SQL routes** over Parquet/CSV files (local disk, S3, GCS, Azure or HTTPS) and
|
|
48
|
+
**PostgreSQL** tables. Run it with the `api-dock` CLI as a FastAPI or Flask app, or embed its
|
|
49
|
+
`RouteMapper` in an existing Python service.
|
|
50
|
+
|
|
51
|
+
| source | served at | how |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| remote HTTP API | `/<remote>/<path>` | forwarded upstream and streamed back, with optional allow-lists and restrictions |
|
|
54
|
+
| file tables (Parquet, CSV, ...) | `/<database>/<version>/<route>` | SQL run by DuckDB |
|
|
55
|
+
| PostgreSQL tables | `/<database>/<version>/<route>` | SQL run natively, or by DuckDB when a route mixes sources |
|
|
56
|
+
|
|
57
|
+
Request values reach the database as bound parameters, never pasted into SQL, and every database
|
|
58
|
+
config is checked when the server starts.
|
|
59
|
+
|
|
60
|
+
**Documentation lives in the [wiki](https://github.com/SchmidtDSE/api_dock/wiki/).** Start with [Getting Started](https://github.com/SchmidtDSE/api_dock/wiki/Getting-Started) and
|
|
61
|
+
[Concepts](https://github.com/SchmidtDSE/api_dock/wiki/Concepts).
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install api-dock # core
|
|
69
|
+
pip install 'api-dock[postgres]' # adds the PostgreSQL driver and pool
|
|
70
|
+
conda install -c conda-forge api_dock # or from conda-forge
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
API Dock needs Python 3.11 or later.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Quick start
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
api-dock init # creates api_dock_config/ with commented example files
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Replace the examples with a main config, one remote and one database:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
api_dock_config/
|
|
87
|
+
├── config.yaml
|
|
88
|
+
├── remotes/
|
|
89
|
+
│ └── httpbin.yaml
|
|
90
|
+
└── databases/
|
|
91
|
+
└── places.yaml
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
# api_dock_config/config.yaml
|
|
96
|
+
name: my-api
|
|
97
|
+
description: My first API Dock
|
|
98
|
+
remotes:
|
|
99
|
+
- httpbin
|
|
100
|
+
databases:
|
|
101
|
+
- places
|
|
102
|
+
settings:
|
|
103
|
+
add_trailing_slash: false # httpbin doesn't want /get/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
# api_dock_config/remotes/httpbin.yaml
|
|
108
|
+
name: httpbin
|
|
109
|
+
url: https://httpbin.org
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
# api_dock_config/databases/places.yaml
|
|
114
|
+
tables:
|
|
115
|
+
places: data/places.parquet # or s3://bucket/places/**/*.parquet, a PostgreSQL table, ...
|
|
116
|
+
routes:
|
|
117
|
+
- route: places
|
|
118
|
+
sql: SELECT * FROM [[places]]
|
|
119
|
+
query_params:
|
|
120
|
+
- country:
|
|
121
|
+
sql: "country = {{country}}"
|
|
122
|
+
- route: places/{{id}}
|
|
123
|
+
sql: SELECT * FROM [[places]] WHERE id = {{id}}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`[[places]]` is replaced by the table's source; `{{id}}` and `{{country}}` are request values,
|
|
127
|
+
sent to the database as bound parameters.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
api-dock start # FastAPI on port 8000
|
|
131
|
+
curl http://localhost:8000/httpbin/get # proxied to https://httpbin.org/get
|
|
132
|
+
curl http://localhost:8000/places/places?country=FR
|
|
133
|
+
# [{"id": 2, "name": "Lyon", "country": "FR"}]
|
|
134
|
+
curl http://localhost:8000/places/places/1
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
[Getting Started](https://github.com/SchmidtDSE/api_dock/wiki/Getting-Started) walks through this example, including making the Parquet
|
|
138
|
+
file.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## What you can configure
|
|
143
|
+
|
|
144
|
+
| topic | wiki page |
|
|
145
|
+
|---|---|
|
|
146
|
+
| main config, settings (`timeout`, `base_path`, `duckdb`, ...), multiple configs | [Configuration](https://github.com/SchmidtDSE/api_dock/wiki/Configuration) |
|
|
147
|
+
| versioned remotes and databases, inline versions, `latest` | [Versioning](https://github.com/SchmidtDSE/api_dock/wiki/Versioning) |
|
|
148
|
+
| remote configs (files or `remotes/config.yaml`), allow-lists, `restricted` patterns, route mapping | [Routing and Restrictions](https://github.com/SchmidtDSE/api_dock/wiki/Routing-and-Restrictions) |
|
|
149
|
+
| cookies, database authentication, encrypted values | [Authentication and Cookies](https://github.com/SchmidtDSE/api_dock/wiki/Authentication-and-Cookies) |
|
|
150
|
+
| tables, `[[table]]` references, routes, startup checks | [SQL Database Support](https://github.com/SchmidtDSE/api_dock/wiki/SQL-Database-Support) |
|
|
151
|
+
| filtering, sorting, pagination, required params | [Query Parameters](https://github.com/SchmidtDSE/api_dock/wiki/Query-Parameters) |
|
|
152
|
+
| picking SQL from the request | [Conditional SQL](https://github.com/SchmidtDSE/api_dock/wiki/Conditional-SQL) |
|
|
153
|
+
| shared tables, schemas, slugs, shared routes | [Shared Database Config](https://github.com/SchmidtDSE/api_dock/wiki/Shared-Database-Config) |
|
|
154
|
+
| unions across schemas (`[[*.table]]`, schema groups) | [Cross-Schema Queries](https://github.com/SchmidtDSE/api_dock/wiki/Cross-Schema-Queries) |
|
|
155
|
+
| PostgreSQL connections, engines, safety | [PostgreSQL](https://github.com/SchmidtDSE/api_dock/wiki/PostgreSQL) |
|
|
156
|
+
| databases, versions and values generated from a query, refreshed on a schedule | [Lookups](https://github.com/SchmidtDSE/api_dock/wiki/Lookups) |
|
|
157
|
+
| `/databases`, `/remotes`, `/sources` listings | [Catalog Endpoints](https://github.com/SchmidtDSE/api_dock/wiki/Catalog-Endpoints) |
|
|
158
|
+
| `RouteMapper` in your own app, production deployment | [Python API and Deployment](https://github.com/SchmidtDSE/api_dock/wiki/Python-API-and-Deployment) |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Example: versions from a catalog (lookups)
|
|
163
|
+
|
|
164
|
+
When the list of databases/versions lives somewhere else (a table of model runs, a
|
|
165
|
+
deployments API), a lookup turns its rows into config. api_dock runs it at startup, every
|
|
166
|
+
`refresh` and on demand:
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
# api_dock_config/databases/config.yaml
|
|
170
|
+
database:
|
|
171
|
+
connections:
|
|
172
|
+
core: {host: db.example.com, dbname: catalog, user: readonly, password: env:DB_PASSWORD}
|
|
173
|
+
runs_catalog: {connection: core, table: public.model_runs} # or {uri: s3://.../runs.parquet}
|
|
174
|
+
|
|
175
|
+
lookups:
|
|
176
|
+
model_runs:
|
|
177
|
+
sql: |
|
|
178
|
+
SELECT name, version, detections_uri,
|
|
179
|
+
replace(name, '-', '_') || '_' || replace(version, '.', 'p') AS schema
|
|
180
|
+
FROM [[runs_catalog]] WHERE published
|
|
181
|
+
refresh: 7d
|
|
182
|
+
allow: ["s3://my-bucket/runs/"] # URIs from rows must start with this
|
|
183
|
+
|
|
184
|
+
slugs:
|
|
185
|
+
- from: model_runs # one database/version per row
|
|
186
|
+
name: "{{row.name}}"
|
|
187
|
+
version: "{{row.version}}"
|
|
188
|
+
schema:
|
|
189
|
+
name: "{{row.schema}}"
|
|
190
|
+
tables:
|
|
191
|
+
detections: {uri: "{{row.detections_uri}}"}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
# api_dock_config/config.yaml
|
|
196
|
+
databases:
|
|
197
|
+
- from: model_runs # serve every database the lookup generates
|
|
198
|
+
settings:
|
|
199
|
+
lookups: # optional: GET status / POST refresh
|
|
200
|
+
refresh_route: /admin/lookups
|
|
201
|
+
token: env:API_DOCK_ADMIN_TOKEN
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Lookups can read PostgreSQL tables, Parquet/CSV files or an HTTP API, and can also
|
|
205
|
+
generate remote versions or fill single values. See [Lookups](https://github.com/SchmidtDSE/api_dock/wiki/Lookups).
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## CLI
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
api-dock # list configs and commands
|
|
213
|
+
api-dock init [--force] # create api_dock_config/
|
|
214
|
+
api-dock start [config_name] # serve api_dock_config/<config_name>.yaml (default: config)
|
|
215
|
+
api-dock start --backbone flask --host 127.0.0.1 --port 9000 --log-level debug
|
|
216
|
+
api-dock describe [config_name] # print the config
|
|
217
|
+
api-dock generate-key # local encryption key
|
|
218
|
+
api-dock encrypt "secret" # also --method env_key|aws_kms
|
|
219
|
+
api-dock decrypt "gAAAAA..."
|
|
220
|
+
api-dock lookups # run the config's lookups and print their rows
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Flask responses are buffered and Flask refuses configs with PostgreSQL connections; use the default
|
|
224
|
+
FastAPI backbone for those. Full reference: [Getting Started](https://github.com/SchmidtDSE/api_dock/wiki/Getting-Started#cli-reference).
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## How it works (in brief)
|
|
229
|
+
|
|
230
|
+
- **Configs.** The main `config.yaml` lists remotes and databases and is read at startup. Remote
|
|
231
|
+
files, database files and the shared `remotes/config.yaml` and `databases/config.yaml` are read
|
|
232
|
+
again on each request, so
|
|
233
|
+
route edits don't need a restart.
|
|
234
|
+
- **Remotes.** A request is checked against the remote's allow/block lists, then forwarded with
|
|
235
|
+
httpx. The FastAPI app streams the upstream response back.
|
|
236
|
+
- **Databases.** The version is resolved (`latest`, version files, `slugs`), the route matched and
|
|
237
|
+
its SQL built: `[[table]]` references expanded, query-param fragments appended, values bound.
|
|
238
|
+
- **Engines.** A route whose tables are all on one PostgreSQL connection runs natively through
|
|
239
|
+
that connection's pool. Anything else runs on an in-memory DuckDB in a worker thread, with
|
|
240
|
+
PostgreSQL attached read-only when needed.
|
|
241
|
+
- **Lookups.** Named queries (SQL over the configured tables, or an HTTP API) run at startup
|
|
242
|
+
and on a schedule; `from:` entries turn their rows into database versions or remote versions.
|
|
243
|
+
- **Startup checks.** Every database and version is checked as requests will see it (table
|
|
244
|
+
references, quoted variables, unions, connections, engines); a bad config stops startup with a
|
|
245
|
+
message naming the database, version and route.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Repo layout
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
api_dock/ the package
|
|
253
|
+
cli.py api-dock commands
|
|
254
|
+
config.py, config_discovery.py main/remote config loading, settings, route rules
|
|
255
|
+
route_mapper.py RouteMapper: request handling, startup checks, PostgreSQL lifecycle
|
|
256
|
+
fast_api.py, flask_api.py the two app backbones
|
|
257
|
+
database_config.py database configs, shared config, versions and slugs
|
|
258
|
+
lookups.py lookups: templates, SQL/HTTP runners, refresh
|
|
259
|
+
sql_builder.py SQL building, table references, unions, engine choice
|
|
260
|
+
database_backends.py DuckDB backend postgres_backend.py, postgres_config.py PostgreSQL
|
|
261
|
+
storage_auth.py, auth.py, encryption.py, listings.py, sql_template_check.py, types.py
|
|
262
|
+
example_api_dock_config/ copied by `api-dock init`
|
|
263
|
+
tests/ pixi run -e dev pytest -q
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
More in the [Developer Guide](https://github.com/SchmidtDSE/api_dock/wiki/Developer-Guide).
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
# Development
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
pixi install -e dev # includes psycopg and a local PostgreSQL for the tests
|
|
274
|
+
pixi run -e dev pytest -q # PostgreSQL tests skip if PostgreSQL/psycopg are missing
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Publishing a Release
|
|
278
|
+
|
|
279
|
+
Publishing a GitHub Release is what publishes to PyPI: `.github/workflows/publish_to_pypi.yml` runs on `release: published`, builds the sdist and wheel with `uv build`, and uploads them using PyPI trusted publishing (OIDC). There's no local build, no API token, and no `twine`. conda-forge follows automatically: its bot opens a version PR on [conda-forge/api_dock-feedstock](https://github.com/conda-forge/api_dock-feedstock), which a maintainer merges.
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# 0. Start from a clean, up-to-date main
|
|
283
|
+
export VERSION=0.10.0 # the NEW version, no leading "v"
|
|
284
|
+
git checkout main
|
|
285
|
+
git pull origin main
|
|
286
|
+
git status
|
|
287
|
+
|
|
288
|
+
# 1. Set `version` in pyproject.toml to $VERSION
|
|
289
|
+
|
|
290
|
+
# 2. Run the tests
|
|
291
|
+
pixi run -e dev pytest -q
|
|
292
|
+
|
|
293
|
+
# 3. Commit, tag, push (the commit command adds the "v$VERSION: " prefix)
|
|
294
|
+
export COMMIT_MESSAGE='code review fixes: remote cookies, safer SQL building, auth caching'
|
|
295
|
+
git add -A
|
|
296
|
+
git commit -m "v$VERSION: $COMMIT_MESSAGE"
|
|
297
|
+
git tag "v$VERSION"
|
|
298
|
+
git push origin main "v$VERSION"
|
|
299
|
+
|
|
300
|
+
# 4. Publish the GitHub Release; this triggers the PyPI upload.
|
|
301
|
+
# Don't use --draft (the workflow only runs on a published release); no wheel needs attaching.
|
|
302
|
+
gh release create "v$VERSION" \
|
|
303
|
+
--title "v$VERSION" \
|
|
304
|
+
--notes "$(cat <<'EOF'
|
|
305
|
+
**Upgrading:** a remote now receives only the cookies its `cookies:` setting allows (none without it). A remote that relied on the browser's cookies being passed through needs `cookies: [<name>]`, or `cookies: true` for all of them. Configs that set `action`, an unknown version `schema:`, non-boolean `add_trailing_slash`/`follow_redirects`, or a bad `timeout` now stop at startup.
|
|
306
|
+
|
|
307
|
+
* new features
|
|
308
|
+
- `api-dock describe` builds the API like `start` (lookups, startup checks) and prints every database/version with expanded route SQL, plus each remote's url
|
|
309
|
+
- `api-dock init --force` replaces existing files; the whole example tree is copied
|
|
310
|
+
- `gcp` extra (`pip install 'api_dock[gcp]'`) for GCP Secret Manager authentication
|
|
311
|
+
- Startup warning when an `authentication` block would be ignored by remote routes
|
|
312
|
+
* bug fixes
|
|
313
|
+
- Remote cookies: the client's raw `Cookie` header is no longer forwarded; the upstream `Cookie` header is built from the remote's `cookies` setting, so filtering and injected cookies work even when the client sends cookies
|
|
314
|
+
- Several upstream `Set-Cookie` headers reach the client separately instead of merged into one (new `ProxyResponse.set_cookies`)
|
|
315
|
+
- Query-param filters join the base query's top-level `WHERE` (strings, comments, CTEs and subqueries ignored), parenthesized and before a trailing `GROUP BY`/`ORDER BY`/`LIMIT`: fixes `WHERE a OR b` letting rows past filters, CTE-only `WHERE`s and base queries ending in `ORDER BY`
|
|
316
|
+
- `[[table]]` is a table source only right after `FROM`/`JOIN` or a `FROM`-list comma (was any `FROM`/`JOIN` in the previous 20 characters, which broke `ON [[a]].id = [[b]].id`)
|
|
317
|
+
- `sql_append` values are limited to integers and column names (with `ASC`/`DESC`, `NULLS FIRST/LAST`); the unimplemented `action` (which echoed request values, including cookies) is refused
|
|
318
|
+
- Authentication providers are built once per config instead of on every request (no secret-store/KMS call per request); `refresh_interval` now refreshes, keeping the last values if a refresh fails; tokens compared in constant time; `aws_tokens_file` works (`aws_key_id` optional)
|
|
319
|
+
- Remote route mapping fills `{{route_name}}` and `{{cookies.x}}` in `remote_route`, and keeps a query string written there
|
|
320
|
+
- Include/exclude and listing filters compare versions part by part (`1.10` no longer matches `1.1`)
|
|
321
|
+
- Values in DuckDB secret SQL (S3 region, GCS keys, HTTP headers) are quoted; a second GCS `service_account` no longer overwrites the process-wide one
|
|
322
|
+
- A main config that is missing (explicit path), invalid YAML or not a mapping stops startup instead of serving an empty API; settings are validated at startup
|
|
323
|
+
- An explicit encryption `key_file` must exist (only the default key file falls back to `API_DOCK_ENCRYPTION_KEY`); `env_key` reads only its variable
|
|
324
|
+
- Remote error responses no longer include internal error text (it's logged); `map_route_sync` no longer leaks event loops
|
|
325
|
+
* cleanup / other improvements
|
|
326
|
+
- `map_database_route` split into named steps; one version-resolution helper for remotes and databases
|
|
327
|
+
- A remote request reads only that remote's config file (was every remote file, twice)
|
|
328
|
+
- Removed unused internal functions; shared YAML-loading and version-matching helpers; `follow_protocol_downgrades` (never implemented) removed
|
|
329
|
+
- FastAPI app reports the package version; classifiers match Python 3.11+; style fixes
|
|
330
|
+
- Tests grew from 626 to 760, including the first tests for authentication, encryption, config discovery and storage credentials
|
|
331
|
+
EOF
|
|
332
|
+
)"
|
|
333
|
+
|
|
334
|
+
# 5. Watch the publish workflow, then confirm PyPI has the new version
|
|
335
|
+
gh run watch "$(gh run list --workflow=publish_to_pypi.yml -L1 --json databaseId -q '.[0].databaseId')" --repo SchmidtDSE/api_dock
|
|
336
|
+
curl -s https://pypi.org/pypi/api-dock/json | python3 -c "import sys,json; print('PyPI latest:', json.load(sys.stdin)['info']['version'])"
|
|
337
|
+
|
|
338
|
+
# 6. conda-forge: once the bot opens the v$VERSION PR (usually within hours), check that the recipe's
|
|
339
|
+
# run requirements match pyproject.toml dependencies (the bot only bumps version + sha256), then merge it
|
|
340
|
+
gh pr list --repo conda-forge/api_dock-feedstock --state open
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
# License
|
|
346
|
+
|
|
347
|
+
BSD 3-Clause
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# API Dock
|
|
2
|
+
|
|
3
|
+
API Dock builds an API from YAML files. Behind one endpoint it proxies requests to **remote HTTP
|
|
4
|
+
APIs** and serves **SQL routes** over Parquet/CSV files (local disk, S3, GCS, Azure or HTTPS) and
|
|
5
|
+
**PostgreSQL** tables. Run it with the `api-dock` CLI as a FastAPI or Flask app, or embed its
|
|
6
|
+
`RouteMapper` in an existing Python service.
|
|
7
|
+
|
|
8
|
+
| source | served at | how |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| remote HTTP API | `/<remote>/<path>` | forwarded upstream and streamed back, with optional allow-lists and restrictions |
|
|
11
|
+
| file tables (Parquet, CSV, ...) | `/<database>/<version>/<route>` | SQL run by DuckDB |
|
|
12
|
+
| PostgreSQL tables | `/<database>/<version>/<route>` | SQL run natively, or by DuckDB when a route mixes sources |
|
|
13
|
+
|
|
14
|
+
Request values reach the database as bound parameters, never pasted into SQL, and every database
|
|
15
|
+
config is checked when the server starts.
|
|
16
|
+
|
|
17
|
+
**Documentation lives in the [wiki](https://github.com/SchmidtDSE/api_dock/wiki/).** Start with [Getting Started](https://github.com/SchmidtDSE/api_dock/wiki/Getting-Started) and
|
|
18
|
+
[Concepts](https://github.com/SchmidtDSE/api_dock/wiki/Concepts).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install api-dock # core
|
|
26
|
+
pip install 'api-dock[postgres]' # adds the PostgreSQL driver and pool
|
|
27
|
+
conda install -c conda-forge api_dock # or from conda-forge
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
API Dock needs Python 3.11 or later.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
api-dock init # creates api_dock_config/ with commented example files
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Replace the examples with a main config, one remote and one database:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
api_dock_config/
|
|
44
|
+
├── config.yaml
|
|
45
|
+
├── remotes/
|
|
46
|
+
│ └── httpbin.yaml
|
|
47
|
+
└── databases/
|
|
48
|
+
└── places.yaml
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
# api_dock_config/config.yaml
|
|
53
|
+
name: my-api
|
|
54
|
+
description: My first API Dock
|
|
55
|
+
remotes:
|
|
56
|
+
- httpbin
|
|
57
|
+
databases:
|
|
58
|
+
- places
|
|
59
|
+
settings:
|
|
60
|
+
add_trailing_slash: false # httpbin doesn't want /get/
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
# api_dock_config/remotes/httpbin.yaml
|
|
65
|
+
name: httpbin
|
|
66
|
+
url: https://httpbin.org
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
# api_dock_config/databases/places.yaml
|
|
71
|
+
tables:
|
|
72
|
+
places: data/places.parquet # or s3://bucket/places/**/*.parquet, a PostgreSQL table, ...
|
|
73
|
+
routes:
|
|
74
|
+
- route: places
|
|
75
|
+
sql: SELECT * FROM [[places]]
|
|
76
|
+
query_params:
|
|
77
|
+
- country:
|
|
78
|
+
sql: "country = {{country}}"
|
|
79
|
+
- route: places/{{id}}
|
|
80
|
+
sql: SELECT * FROM [[places]] WHERE id = {{id}}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`[[places]]` is replaced by the table's source; `{{id}}` and `{{country}}` are request values,
|
|
84
|
+
sent to the database as bound parameters.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
api-dock start # FastAPI on port 8000
|
|
88
|
+
curl http://localhost:8000/httpbin/get # proxied to https://httpbin.org/get
|
|
89
|
+
curl http://localhost:8000/places/places?country=FR
|
|
90
|
+
# [{"id": 2, "name": "Lyon", "country": "FR"}]
|
|
91
|
+
curl http://localhost:8000/places/places/1
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
[Getting Started](https://github.com/SchmidtDSE/api_dock/wiki/Getting-Started) walks through this example, including making the Parquet
|
|
95
|
+
file.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## What you can configure
|
|
100
|
+
|
|
101
|
+
| topic | wiki page |
|
|
102
|
+
|---|---|
|
|
103
|
+
| main config, settings (`timeout`, `base_path`, `duckdb`, ...), multiple configs | [Configuration](https://github.com/SchmidtDSE/api_dock/wiki/Configuration) |
|
|
104
|
+
| versioned remotes and databases, inline versions, `latest` | [Versioning](https://github.com/SchmidtDSE/api_dock/wiki/Versioning) |
|
|
105
|
+
| remote configs (files or `remotes/config.yaml`), allow-lists, `restricted` patterns, route mapping | [Routing and Restrictions](https://github.com/SchmidtDSE/api_dock/wiki/Routing-and-Restrictions) |
|
|
106
|
+
| cookies, database authentication, encrypted values | [Authentication and Cookies](https://github.com/SchmidtDSE/api_dock/wiki/Authentication-and-Cookies) |
|
|
107
|
+
| tables, `[[table]]` references, routes, startup checks | [SQL Database Support](https://github.com/SchmidtDSE/api_dock/wiki/SQL-Database-Support) |
|
|
108
|
+
| filtering, sorting, pagination, required params | [Query Parameters](https://github.com/SchmidtDSE/api_dock/wiki/Query-Parameters) |
|
|
109
|
+
| picking SQL from the request | [Conditional SQL](https://github.com/SchmidtDSE/api_dock/wiki/Conditional-SQL) |
|
|
110
|
+
| shared tables, schemas, slugs, shared routes | [Shared Database Config](https://github.com/SchmidtDSE/api_dock/wiki/Shared-Database-Config) |
|
|
111
|
+
| unions across schemas (`[[*.table]]`, schema groups) | [Cross-Schema Queries](https://github.com/SchmidtDSE/api_dock/wiki/Cross-Schema-Queries) |
|
|
112
|
+
| PostgreSQL connections, engines, safety | [PostgreSQL](https://github.com/SchmidtDSE/api_dock/wiki/PostgreSQL) |
|
|
113
|
+
| databases, versions and values generated from a query, refreshed on a schedule | [Lookups](https://github.com/SchmidtDSE/api_dock/wiki/Lookups) |
|
|
114
|
+
| `/databases`, `/remotes`, `/sources` listings | [Catalog Endpoints](https://github.com/SchmidtDSE/api_dock/wiki/Catalog-Endpoints) |
|
|
115
|
+
| `RouteMapper` in your own app, production deployment | [Python API and Deployment](https://github.com/SchmidtDSE/api_dock/wiki/Python-API-and-Deployment) |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Example: versions from a catalog (lookups)
|
|
120
|
+
|
|
121
|
+
When the list of databases/versions lives somewhere else (a table of model runs, a
|
|
122
|
+
deployments API), a lookup turns its rows into config. api_dock runs it at startup, every
|
|
123
|
+
`refresh` and on demand:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
# api_dock_config/databases/config.yaml
|
|
127
|
+
database:
|
|
128
|
+
connections:
|
|
129
|
+
core: {host: db.example.com, dbname: catalog, user: readonly, password: env:DB_PASSWORD}
|
|
130
|
+
runs_catalog: {connection: core, table: public.model_runs} # or {uri: s3://.../runs.parquet}
|
|
131
|
+
|
|
132
|
+
lookups:
|
|
133
|
+
model_runs:
|
|
134
|
+
sql: |
|
|
135
|
+
SELECT name, version, detections_uri,
|
|
136
|
+
replace(name, '-', '_') || '_' || replace(version, '.', 'p') AS schema
|
|
137
|
+
FROM [[runs_catalog]] WHERE published
|
|
138
|
+
refresh: 7d
|
|
139
|
+
allow: ["s3://my-bucket/runs/"] # URIs from rows must start with this
|
|
140
|
+
|
|
141
|
+
slugs:
|
|
142
|
+
- from: model_runs # one database/version per row
|
|
143
|
+
name: "{{row.name}}"
|
|
144
|
+
version: "{{row.version}}"
|
|
145
|
+
schema:
|
|
146
|
+
name: "{{row.schema}}"
|
|
147
|
+
tables:
|
|
148
|
+
detections: {uri: "{{row.detections_uri}}"}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
# api_dock_config/config.yaml
|
|
153
|
+
databases:
|
|
154
|
+
- from: model_runs # serve every database the lookup generates
|
|
155
|
+
settings:
|
|
156
|
+
lookups: # optional: GET status / POST refresh
|
|
157
|
+
refresh_route: /admin/lookups
|
|
158
|
+
token: env:API_DOCK_ADMIN_TOKEN
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Lookups can read PostgreSQL tables, Parquet/CSV files or an HTTP API, and can also
|
|
162
|
+
generate remote versions or fill single values. See [Lookups](https://github.com/SchmidtDSE/api_dock/wiki/Lookups).
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## CLI
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
api-dock # list configs and commands
|
|
170
|
+
api-dock init [--force] # create api_dock_config/
|
|
171
|
+
api-dock start [config_name] # serve api_dock_config/<config_name>.yaml (default: config)
|
|
172
|
+
api-dock start --backbone flask --host 127.0.0.1 --port 9000 --log-level debug
|
|
173
|
+
api-dock describe [config_name] # print the config
|
|
174
|
+
api-dock generate-key # local encryption key
|
|
175
|
+
api-dock encrypt "secret" # also --method env_key|aws_kms
|
|
176
|
+
api-dock decrypt "gAAAAA..."
|
|
177
|
+
api-dock lookups # run the config's lookups and print their rows
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Flask responses are buffered and Flask refuses configs with PostgreSQL connections; use the default
|
|
181
|
+
FastAPI backbone for those. Full reference: [Getting Started](https://github.com/SchmidtDSE/api_dock/wiki/Getting-Started#cli-reference).
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## How it works (in brief)
|
|
186
|
+
|
|
187
|
+
- **Configs.** The main `config.yaml` lists remotes and databases and is read at startup. Remote
|
|
188
|
+
files, database files and the shared `remotes/config.yaml` and `databases/config.yaml` are read
|
|
189
|
+
again on each request, so
|
|
190
|
+
route edits don't need a restart.
|
|
191
|
+
- **Remotes.** A request is checked against the remote's allow/block lists, then forwarded with
|
|
192
|
+
httpx. The FastAPI app streams the upstream response back.
|
|
193
|
+
- **Databases.** The version is resolved (`latest`, version files, `slugs`), the route matched and
|
|
194
|
+
its SQL built: `[[table]]` references expanded, query-param fragments appended, values bound.
|
|
195
|
+
- **Engines.** A route whose tables are all on one PostgreSQL connection runs natively through
|
|
196
|
+
that connection's pool. Anything else runs on an in-memory DuckDB in a worker thread, with
|
|
197
|
+
PostgreSQL attached read-only when needed.
|
|
198
|
+
- **Lookups.** Named queries (SQL over the configured tables, or an HTTP API) run at startup
|
|
199
|
+
and on a schedule; `from:` entries turn their rows into database versions or remote versions.
|
|
200
|
+
- **Startup checks.** Every database and version is checked as requests will see it (table
|
|
201
|
+
references, quoted variables, unions, connections, engines); a bad config stops startup with a
|
|
202
|
+
message naming the database, version and route.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Repo layout
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
api_dock/ the package
|
|
210
|
+
cli.py api-dock commands
|
|
211
|
+
config.py, config_discovery.py main/remote config loading, settings, route rules
|
|
212
|
+
route_mapper.py RouteMapper: request handling, startup checks, PostgreSQL lifecycle
|
|
213
|
+
fast_api.py, flask_api.py the two app backbones
|
|
214
|
+
database_config.py database configs, shared config, versions and slugs
|
|
215
|
+
lookups.py lookups: templates, SQL/HTTP runners, refresh
|
|
216
|
+
sql_builder.py SQL building, table references, unions, engine choice
|
|
217
|
+
database_backends.py DuckDB backend postgres_backend.py, postgres_config.py PostgreSQL
|
|
218
|
+
storage_auth.py, auth.py, encryption.py, listings.py, sql_template_check.py, types.py
|
|
219
|
+
example_api_dock_config/ copied by `api-dock init`
|
|
220
|
+
tests/ pixi run -e dev pytest -q
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
More in the [Developer Guide](https://github.com/SchmidtDSE/api_dock/wiki/Developer-Guide).
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
# Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
pixi install -e dev # includes psycopg and a local PostgreSQL for the tests
|
|
231
|
+
pixi run -e dev pytest -q # PostgreSQL tests skip if PostgreSQL/psycopg are missing
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Publishing a Release
|
|
235
|
+
|
|
236
|
+
Publishing a GitHub Release is what publishes to PyPI: `.github/workflows/publish_to_pypi.yml` runs on `release: published`, builds the sdist and wheel with `uv build`, and uploads them using PyPI trusted publishing (OIDC). There's no local build, no API token, and no `twine`. conda-forge follows automatically: its bot opens a version PR on [conda-forge/api_dock-feedstock](https://github.com/conda-forge/api_dock-feedstock), which a maintainer merges.
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# 0. Start from a clean, up-to-date main
|
|
240
|
+
export VERSION=0.10.0 # the NEW version, no leading "v"
|
|
241
|
+
git checkout main
|
|
242
|
+
git pull origin main
|
|
243
|
+
git status
|
|
244
|
+
|
|
245
|
+
# 1. Set `version` in pyproject.toml to $VERSION
|
|
246
|
+
|
|
247
|
+
# 2. Run the tests
|
|
248
|
+
pixi run -e dev pytest -q
|
|
249
|
+
|
|
250
|
+
# 3. Commit, tag, push (the commit command adds the "v$VERSION: " prefix)
|
|
251
|
+
export COMMIT_MESSAGE='code review fixes: remote cookies, safer SQL building, auth caching'
|
|
252
|
+
git add -A
|
|
253
|
+
git commit -m "v$VERSION: $COMMIT_MESSAGE"
|
|
254
|
+
git tag "v$VERSION"
|
|
255
|
+
git push origin main "v$VERSION"
|
|
256
|
+
|
|
257
|
+
# 4. Publish the GitHub Release; this triggers the PyPI upload.
|
|
258
|
+
# Don't use --draft (the workflow only runs on a published release); no wheel needs attaching.
|
|
259
|
+
gh release create "v$VERSION" \
|
|
260
|
+
--title "v$VERSION" \
|
|
261
|
+
--notes "$(cat <<'EOF'
|
|
262
|
+
**Upgrading:** a remote now receives only the cookies its `cookies:` setting allows (none without it). A remote that relied on the browser's cookies being passed through needs `cookies: [<name>]`, or `cookies: true` for all of them. Configs that set `action`, an unknown version `schema:`, non-boolean `add_trailing_slash`/`follow_redirects`, or a bad `timeout` now stop at startup.
|
|
263
|
+
|
|
264
|
+
* new features
|
|
265
|
+
- `api-dock describe` builds the API like `start` (lookups, startup checks) and prints every database/version with expanded route SQL, plus each remote's url
|
|
266
|
+
- `api-dock init --force` replaces existing files; the whole example tree is copied
|
|
267
|
+
- `gcp` extra (`pip install 'api_dock[gcp]'`) for GCP Secret Manager authentication
|
|
268
|
+
- Startup warning when an `authentication` block would be ignored by remote routes
|
|
269
|
+
* bug fixes
|
|
270
|
+
- Remote cookies: the client's raw `Cookie` header is no longer forwarded; the upstream `Cookie` header is built from the remote's `cookies` setting, so filtering and injected cookies work even when the client sends cookies
|
|
271
|
+
- Several upstream `Set-Cookie` headers reach the client separately instead of merged into one (new `ProxyResponse.set_cookies`)
|
|
272
|
+
- Query-param filters join the base query's top-level `WHERE` (strings, comments, CTEs and subqueries ignored), parenthesized and before a trailing `GROUP BY`/`ORDER BY`/`LIMIT`: fixes `WHERE a OR b` letting rows past filters, CTE-only `WHERE`s and base queries ending in `ORDER BY`
|
|
273
|
+
- `[[table]]` is a table source only right after `FROM`/`JOIN` or a `FROM`-list comma (was any `FROM`/`JOIN` in the previous 20 characters, which broke `ON [[a]].id = [[b]].id`)
|
|
274
|
+
- `sql_append` values are limited to integers and column names (with `ASC`/`DESC`, `NULLS FIRST/LAST`); the unimplemented `action` (which echoed request values, including cookies) is refused
|
|
275
|
+
- Authentication providers are built once per config instead of on every request (no secret-store/KMS call per request); `refresh_interval` now refreshes, keeping the last values if a refresh fails; tokens compared in constant time; `aws_tokens_file` works (`aws_key_id` optional)
|
|
276
|
+
- Remote route mapping fills `{{route_name}}` and `{{cookies.x}}` in `remote_route`, and keeps a query string written there
|
|
277
|
+
- Include/exclude and listing filters compare versions part by part (`1.10` no longer matches `1.1`)
|
|
278
|
+
- Values in DuckDB secret SQL (S3 region, GCS keys, HTTP headers) are quoted; a second GCS `service_account` no longer overwrites the process-wide one
|
|
279
|
+
- A main config that is missing (explicit path), invalid YAML or not a mapping stops startup instead of serving an empty API; settings are validated at startup
|
|
280
|
+
- An explicit encryption `key_file` must exist (only the default key file falls back to `API_DOCK_ENCRYPTION_KEY`); `env_key` reads only its variable
|
|
281
|
+
- Remote error responses no longer include internal error text (it's logged); `map_route_sync` no longer leaks event loops
|
|
282
|
+
* cleanup / other improvements
|
|
283
|
+
- `map_database_route` split into named steps; one version-resolution helper for remotes and databases
|
|
284
|
+
- A remote request reads only that remote's config file (was every remote file, twice)
|
|
285
|
+
- Removed unused internal functions; shared YAML-loading and version-matching helpers; `follow_protocol_downgrades` (never implemented) removed
|
|
286
|
+
- FastAPI app reports the package version; classifiers match Python 3.11+; style fixes
|
|
287
|
+
- Tests grew from 626 to 760, including the first tests for authentication, encryption, config discovery and storage credentials
|
|
288
|
+
EOF
|
|
289
|
+
)"
|
|
290
|
+
|
|
291
|
+
# 5. Watch the publish workflow, then confirm PyPI has the new version
|
|
292
|
+
gh run watch "$(gh run list --workflow=publish_to_pypi.yml -L1 --json databaseId -q '.[0].databaseId')" --repo SchmidtDSE/api_dock
|
|
293
|
+
curl -s https://pypi.org/pypi/api-dock/json | python3 -c "import sys,json; print('PyPI latest:', json.load(sys.stdin)['info']['version'])"
|
|
294
|
+
|
|
295
|
+
# 6. conda-forge: once the bot opens the v$VERSION PR (usually within hours), check that the recipe's
|
|
296
|
+
# run requirements match pyproject.toml dependencies (the bot only bumps version + sha256), then merge it
|
|
297
|
+
gh pr list --repo conda-forge/api_dock-feedstock --state open
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
# License
|
|
303
|
+
|
|
304
|
+
BSD 3-Clause
|