api-dock 0.7.1__tar.gz → 0.8.1__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.7.1 → api_dock-0.8.1}/PKG-INFO +312 -31
- {api_dock-0.7.1 → api_dock-0.8.1}/README.md +311 -30
- api_dock-0.8.1/api_dock/database_config.py +1101 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/example_api_dock_config/config.yaml +8 -0
- api_dock-0.8.1/api_dock/example_api_dock_config/databases/config.yaml +94 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/fast_api.py +33 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/flask_api.py +31 -0
- api_dock-0.8.1/api_dock/listings.py +346 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/route_mapper.py +66 -15
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/sql_builder.py +341 -17
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/storage_auth.py +133 -36
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/types.py +82 -2
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock.egg-info/PKG-INFO +312 -31
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock.egg-info/SOURCES.txt +5 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/pyproject.toml +1 -1
- api_dock-0.8.1/tests/test_listings.py +221 -0
- api_dock-0.8.1/tests/test_schema_unions.py +308 -0
- api_dock-0.8.1/tests/test_shared_database_config.py +835 -0
- api_dock-0.7.1/api_dock/database_config.py +0 -441
- {api_dock-0.7.1 → api_dock-0.8.1}/LICENSE.md +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/__init__.py +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/auth.py +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/cli.py +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/config.py +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/config_discovery.py +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/encryption.py +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/example_api_dock_config/databases/example_db.yaml +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock/example_api_dock_config/remotes/example_remote.yaml +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock.egg-info/dependency_links.txt +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock.egg-info/entry_points.txt +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock.egg-info/requires.txt +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/api_dock.egg-info/top_level.txt +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/setup.cfg +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/tests/test_inject_cookies.py +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/tests/test_proxy_pipeline.py +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/tests/test_sql_builder.py +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/tests/test_sql_selector.py +0 -0
- {api_dock-0.7.1 → api_dock-0.8.1}/tests/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: api_dock
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: A flexible API gateway that allows you to proxy requests to multiple remote APIs and Databases
|
|
5
5
|
Author-email: Brookie Guzder-Williams <bguzder-williams@berkeley.edu>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -95,6 +95,7 @@ Here is an example:
|
|
|
95
95
|
api_dock_config
|
|
96
96
|
├── config.yaml # The default main-config file
|
|
97
97
|
├── databases
|
|
98
|
+
│ ├── config.yaml # (optional) shared tables/schemas for all databases
|
|
98
99
|
│ ├── unversioned_db.yaml # Database config without versioning
|
|
99
100
|
│ └── versioned_db # Folder containing database configs for different versions
|
|
100
101
|
│ ├── 0.1.yaml
|
|
@@ -231,6 +232,58 @@ The optional `settings` section controls HTTP behavior:
|
|
|
231
232
|
|
|
232
233
|
- **`timeout`** (default: `10`): Upstream request timeout in seconds, applied to both the streaming and buffered proxy paths. Raise it for slow upstreams (e.g. large aggregation queries) that would otherwise return a 502 on timeout. Set to `null` or `false` to disable the timeout entirely (not recommended — a stalled upstream can hold the connection open indefinitely).
|
|
233
234
|
|
|
235
|
+
### Catalog Endpoints (`expose`)
|
|
236
|
+
|
|
237
|
+
The optional `expose` section adds read-only endpoints that list the models and versions of your configured databases, remotes, or both ("sources"). Listings are **opt-in** — with no `expose` key nothing is added.
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
# Enable all three defaults: /databases, /remotes, /sources
|
|
241
|
+
expose: true
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```yaml
|
|
245
|
+
GET /databases
|
|
246
|
+
# [{"model": "birdnet", "version": "2.4"},
|
|
247
|
+
# {"model": "birdnet", "version": "3.0"},
|
|
248
|
+
# {"model": "owl", "version": "0.5"}]
|
|
249
|
+
|
|
250
|
+
GET /sources # databases + remotes, combined
|
|
251
|
+
# [{"model": "birdnet", "version": "2.4"}, ..., {"model": "core", "version": "0.5.0"}]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Versions are the config filename stems, so semver like `0.5.0` is preserved; unversioned sources report `version: null`. Enable only what you want, and control the output shape with `dict`:
|
|
255
|
+
|
|
256
|
+
```yaml
|
|
257
|
+
expose:
|
|
258
|
+
dict: false # return "model/version" strings instead of {model, version} dicts
|
|
259
|
+
databases: true # add /databases
|
|
260
|
+
remotes: true # add /remotes
|
|
261
|
+
# sources omitted → not added
|
|
262
|
+
|
|
263
|
+
# GET /databases -> ["birdnet/2.4", "birdnet/3.0", "owl/0.5"]
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Each of `databases` / `remotes` / `sources` accepts several forms:
|
|
267
|
+
|
|
268
|
+
```yaml
|
|
269
|
+
expose:
|
|
270
|
+
databases: false # do not add the endpoint
|
|
271
|
+
|
|
272
|
+
remotes: "list/remotes" # custom route path (same as true, but at /list/remotes)
|
|
273
|
+
|
|
274
|
+
databases: # explicit list of models (optionally version-filtered)
|
|
275
|
+
- birdnet
|
|
276
|
+
- owl:
|
|
277
|
+
versions: [4.0, 5.0] # ints/floats/strings all match the "4.0"/"5.0" stems
|
|
278
|
+
|
|
279
|
+
sources: # most explicit form
|
|
280
|
+
route: "list/sources"
|
|
281
|
+
include: [birdnet, core] # true (default) | false | list of models
|
|
282
|
+
dict: true # per-endpoint override of the top-level `dict`
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Each listing route is served both with and without a trailing slash (e.g. `/sources` and `/sources/` both work), so it doesn't matter which convention your clients use. Custom multi-segment routes (e.g. `list/databases`) take precedence over the `/{remote}/{path}` proxy. If a listing route would shadow a configured remote/database, or an `include` names something that doesn't exist, API Dock emits a startup warning. The exposed routes are also reflected in the root (`/`) metadata's `endpoints`.
|
|
286
|
+
|
|
234
287
|
---
|
|
235
288
|
|
|
236
289
|
## Remote Configurations
|
|
@@ -356,6 +409,7 @@ Database configurations are stored in `api_dock_config/databases/` directory. Ea
|
|
|
356
409
|
- **tables**: Mapping of table names to file paths (supports S3, GCS, HTTPS, local paths)
|
|
357
410
|
- **queries**: Named SQL queries for reuse
|
|
358
411
|
- **routes**: REST endpoints mapped to SQL queries
|
|
412
|
+
- **schema** (optional): the shared schema (from `databases/config.yaml`) this config's `[[table]]` references fall back to. See [Shared Tables and Schemas](#shared-tables-and-schemas-databasesconfigyaml)
|
|
359
413
|
|
|
360
414
|
### Syntax
|
|
361
415
|
|
|
@@ -433,6 +487,225 @@ routes:
|
|
|
433
487
|
sql: "[[get_permissions]]"
|
|
434
488
|
```
|
|
435
489
|
|
|
490
|
+
### Shared Tables and Schemas (`databases/config.yaml`)
|
|
491
|
+
|
|
492
|
+
When several databases or versions read the same tables, define them once in the optional `api_dock_config/databases/config.yaml`. Everything lives under a `database` key: `meta` holds default table metadata, `schema` holds named groups of tables, and every other key is a global table.
|
|
493
|
+
|
|
494
|
+
```yaml
|
|
495
|
+
# api_dock_config/databases/config.yaml
|
|
496
|
+
database:
|
|
497
|
+
# global tables, available as [[table1]] in any database config
|
|
498
|
+
table1:
|
|
499
|
+
uri: s3://your-bucket/table1.parquet
|
|
500
|
+
table3:
|
|
501
|
+
uri: s3://your-other-bucket/table3.parquet
|
|
502
|
+
region: us-west-1 # a table's own keys override `meta`
|
|
503
|
+
public: false
|
|
504
|
+
|
|
505
|
+
# defaults applied to every table (shared tables and the tables in each version config)
|
|
506
|
+
meta:
|
|
507
|
+
region: us-west-2
|
|
508
|
+
public: true
|
|
509
|
+
|
|
510
|
+
# schemas, available as [[birdnet_2p4.detections]] in any route
|
|
511
|
+
schema:
|
|
512
|
+
birdnet_2p4:
|
|
513
|
+
detections:
|
|
514
|
+
uri: s3://your-bucket/birdnet/2.4/detections.parquet
|
|
515
|
+
birdnet_3p0:
|
|
516
|
+
detections:
|
|
517
|
+
uri: s3://your-bucket/birdnet/3.0/detections.parquet
|
|
518
|
+
public: false
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
A version config can name the schema it uses with `schema:`. An unqualified `[[name]]` is then looked up in order, first match wins:
|
|
522
|
+
|
|
523
|
+
1. the version config's own `tables`
|
|
524
|
+
2. its `schema:` in the shared config
|
|
525
|
+
3. the shared config's global tables
|
|
526
|
+
|
|
527
|
+
```yaml
|
|
528
|
+
# api_dock_config/databases/birdnet/2.4.yaml
|
|
529
|
+
name: birdnet
|
|
530
|
+
schema: birdnet_2p4
|
|
531
|
+
tables:
|
|
532
|
+
revisions: s3://your-bucket/birdnet/2.4/revisions.parquet # local to this version
|
|
533
|
+
|
|
534
|
+
routes:
|
|
535
|
+
# [[detections]] isn't in `tables`, so it comes from the birdnet_2p4 schema
|
|
536
|
+
- route: recordings/{{recording_id}}/detections
|
|
537
|
+
sql: SELECT [[detections]].* FROM [[detections]] WHERE [[detections]].recording_id = {{recording_id}}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Any route can reference any schema directly as `[[schema.table]]`, so a different database (e.g. `owl/5.0`) can query `[[birdnet_2p4.detections]]`. To keep a table private to one database/version, define it in that version's `tables` instead. Qualified references are exposed to DuckDB as real views, so you can also use the full name or your own alias in plain SQL:
|
|
541
|
+
|
|
542
|
+
```yaml
|
|
543
|
+
- route: detections/
|
|
544
|
+
sql: >
|
|
545
|
+
SELECT detections.common_name, COUNT(revisions.id) AS revcount
|
|
546
|
+
FROM [[birdnet_2p4.detections]]
|
|
547
|
+
LEFT JOIN [[revisions]] ON revisions.observation_id = birdnet_2p4.detections.id
|
|
548
|
+
GROUP BY birdnet_2p4.detections.common_name
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
expands to
|
|
552
|
+
|
|
553
|
+
```sql
|
|
554
|
+
SELECT detections.common_name, COUNT(revisions.id) AS revcount
|
|
555
|
+
FROM birdnet_2p4.detections
|
|
556
|
+
LEFT JOIN 's3://your-bucket/birdnet/2.4/revisions.parquet' AS revisions ON revisions.observation_id = birdnet_2p4.detections.id
|
|
557
|
+
GROUP BY birdnet_2p4.detections.common_name
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Notes:
|
|
561
|
+
- After `FROM`/`JOIN`, `[[schema.table]]` becomes `schema.table` with no alias, so `FROM [[birdnet_2p4.detections]] o` works. Elsewhere it becomes the bare table name (`detections`), because DuckDB doesn't accept `schema.table.*`.
|
|
562
|
+
- Schema and table names used as `[[schema.table]]` must be plain identifiers (letters, digits, underscores).
|
|
563
|
+
- Storage credentials are set per table. Tables whose `region`/`public` differ from the rest get their own S3 secret scoped to their path, so one query can mix regions and public/private buckets.
|
|
564
|
+
- Views are created only for the `[[schema.table]]` tables a query actually references.
|
|
565
|
+
|
|
566
|
+
#### Querying across schemas (`[[*.table]]`, schema groups)
|
|
567
|
+
|
|
568
|
+
Union references read the same table from several schemas at once:
|
|
569
|
+
|
|
570
|
+
| Reference | Reads |
|
|
571
|
+
|---|---|
|
|
572
|
+
| `[[*.detections]]` | every shared schema that has a `detections` table, including the current one |
|
|
573
|
+
| `[[*!.detections]]` | the same, minus the current database/version's `schema:` |
|
|
574
|
+
| `[[group1.detections]]` | the schemas listed in `schema_groups.group1` |
|
|
575
|
+
| `[[group1!.detections]]` | that group, minus the current schema |
|
|
576
|
+
|
|
577
|
+
```yaml
|
|
578
|
+
# api_dock_config/databases/config.yaml
|
|
579
|
+
schema_groups: # named lists of shared schemas
|
|
580
|
+
birdnet_models:
|
|
581
|
+
- birdnet_2p4
|
|
582
|
+
- birdnet_bullfrog_2p4v0p5
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
- A union expands, after `FROM`/`JOIN` only, to a parenthesized `UNION ALL BY NAME` over the member schemas, so give it an alias: `FROM [[*.detections]] detections`. Columns missing from some members come back as `NULL`.
|
|
586
|
+
- `*` skips schemas without the table. A group whose member lacks the table, a group naming an unknown schema, and a group sharing a name with a schema are all errors. `!` applies only to `*` and groups; with no `schema:`, it removes nothing.
|
|
587
|
+
- Every member gets its own S3 credentials (see above), so a union can mix regions and public/private buckets.
|
|
588
|
+
|
|
589
|
+
**Source columns.** Union rows carry only the tables' real columns unless the route asks for more with `source_columns`. The available facts are `schema` (the member schema), and `name` and `version` (the database/version whose `schema:` is that schema, or `NULL` if none or several use it):
|
|
590
|
+
|
|
591
|
+
```yaml
|
|
592
|
+
source_columns: [schema, name, version] # adds schema_name, name, version
|
|
593
|
+
source_columns: {schema: _schema, name: model} # pick a subset and rename
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
A source column that clashes with a real column raises an error. To use one for filtering without returning it, use DuckDB's `EXCLUDE`: `SELECT detections.* EXCLUDE (schema_name) ...`.
|
|
597
|
+
|
|
598
|
+
**`{{self.*}}` placeholders.** `{{self.schema}}`, `{{self.name}}` and `{{self.version}}` are the current database/version's schema, name and version, as SQL literals (`NULL` when unknown).
|
|
599
|
+
|
|
600
|
+
Together they make an "overlaps" route that every database/version can share. It returns every detection overlapping the given one, across all schemas, except that detection itself; other overlapping rows in the same schema are kept:
|
|
601
|
+
|
|
602
|
+
```yaml
|
|
603
|
+
routes:
|
|
604
|
+
- route: detections/{{id}}/overlaps
|
|
605
|
+
source_columns: [schema, name, version]
|
|
606
|
+
sql: |
|
|
607
|
+
WITH src AS (
|
|
608
|
+
SELECT recording_id, start_time, end_time FROM [[detections]] WHERE id = {{id}}
|
|
609
|
+
)
|
|
610
|
+
SELECT detections.*
|
|
611
|
+
FROM [[*.detections]] detections
|
|
612
|
+
JOIN src ON detections.recording_id = src.recording_id
|
|
613
|
+
AND detections.start_time < src.end_time
|
|
614
|
+
AND detections.end_time > src.start_time
|
|
615
|
+
WHERE NOT (detections.schema_name = {{self.schema}} AND detections.id = {{id}})
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Aliasing the union as `detections` also lets shared filters such as `[[detections]].confidence >= {{confidence}}` apply to the overlapping rows.
|
|
619
|
+
|
|
620
|
+
#### Inline database configs (`slugs`)
|
|
621
|
+
|
|
622
|
+
Simple database/version configs (often just a description and a `schema`) can live in the shared file instead of in their own files. Config files keep working, and the two can be mixed, even for the same database:
|
|
623
|
+
|
|
624
|
+
```yaml
|
|
625
|
+
# api_dock_config/databases/config.yaml
|
|
626
|
+
slugs:
|
|
627
|
+
- name: birdnet-bullfrog # the database slug in the URL
|
|
628
|
+
version: "2.5" # one version...
|
|
629
|
+
description: American Bullfrog Classifier from Birdnet 2.4
|
|
630
|
+
schema: birdnet_bullfrog_2p5v0p5
|
|
631
|
+
- name: birdnet-apple
|
|
632
|
+
authors: [API Team] # ...or several; keys here are defaults for each version
|
|
633
|
+
versions:
|
|
634
|
+
- version: "1.0"
|
|
635
|
+
description: Apple Classifier 1.0
|
|
636
|
+
schema: birdnet_apple_1p0
|
|
637
|
+
- version: "12.0"
|
|
638
|
+
description: Apple Classifier 12.0
|
|
639
|
+
schema: birdnet_apple_12p0
|
|
640
|
+
- name: notes # no version/versions = an unversioned database
|
|
641
|
+
tables:
|
|
642
|
+
notes: s3://your-bucket/notes.parquet
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
- Each entry (or each `versions` item) takes the same keys as a database config file: `description`, `authors`, `schema`, `tables`, `routes`, `query_params`, and so on. Shared `routes`/`query_params` (below), including `include`/`exclude`, apply to them like any other database/version.
|
|
646
|
+
- Like file-based databases, a slug is only served if it's listed under `databases:` in the main `config.yaml`.
|
|
647
|
+
- A database's versions are the union of its version files and its `slugs` versions, so `latest`, the `/{database}` versions listing, and the `expose` catalog endpoints all see both. If a file and a slug define the same database/version, the file wins.
|
|
648
|
+
- Quote versions (`version: "2.10"`). Unquoted YAML numbers are floats, so `2.10` would become `"2.1"`.
|
|
649
|
+
- A malformed `slugs` section (missing `name`, both `version` and `versions`, a duplicate version, or a mix of versioned and unversioned entries for one name) returns a 500 "Shared database configuration error".
|
|
650
|
+
|
|
651
|
+
#### Shared routes and query params
|
|
652
|
+
|
|
653
|
+
The shared file can also define top-level `routes` and `query_params`. These are added to **every** database/version, which is handy when each model/version serves the same endpoints over its own `schema`:
|
|
654
|
+
|
|
655
|
+
```yaml
|
|
656
|
+
# api_dock_config/databases/config.yaml
|
|
657
|
+
database:
|
|
658
|
+
...
|
|
659
|
+
|
|
660
|
+
routes:
|
|
661
|
+
- route: recordings/{{recording_id}}/detections/
|
|
662
|
+
sql: SELECT [[detections]].* FROM [[detections]] WHERE [[detections]].recording_id = {{recording_id}}
|
|
663
|
+
- route: detections/{{id}}
|
|
664
|
+
sql: SELECT [[detections]].* FROM [[detections]] WHERE [[detections]].id = {{id}}
|
|
665
|
+
- route: not_for_everyone/{{id}}
|
|
666
|
+
sql: SELECT [[other]].* FROM [[other]] WHERE [[other]].id = {{id}}
|
|
667
|
+
exclude: # don't add this route to these slug/versions
|
|
668
|
+
- 'slug1/3.0'
|
|
669
|
+
- slug: slug2
|
|
670
|
+
version: 2.3
|
|
671
|
+
- slug: slug3
|
|
672
|
+
version: '*' # '*' = every version
|
|
673
|
+
|
|
674
|
+
# the same route defined twice: one for everything except birdnet/2.4, one only for it
|
|
675
|
+
- route: detections/
|
|
676
|
+
exclude: ['birdnet/2.4']
|
|
677
|
+
sql: SELECT [[detections]].* FROM [[detections]]
|
|
678
|
+
- route: detections/
|
|
679
|
+
include: ['birdnet/2.4'] # ONLY add this route to these slug/versions
|
|
680
|
+
sql: SELECT [[detections]].*, [[revisions]].id AS revision_id FROM [[detections]] LEFT JOIN [[revisions]] ON [[revisions]].observation_id = [[detections]].id
|
|
681
|
+
|
|
682
|
+
query_params:
|
|
683
|
+
- confidence:
|
|
684
|
+
sql: "[[detections]].confidence >= {{confidence}}"
|
|
685
|
+
- start_time:
|
|
686
|
+
sql: "[[detections]].start_time >= {{start_time}}"
|
|
687
|
+
exclude: ['slug1/3.9']
|
|
688
|
+
- limit:
|
|
689
|
+
sql_append: LIMIT {{limit}}
|
|
690
|
+
|
|
691
|
+
# limit ALL shared routes / query params to these slug/versions
|
|
692
|
+
route_inclusions: ['birdnet', 'owl/5.0']
|
|
693
|
+
query_inclusions: [] # empty or missing = no restriction
|
|
694
|
+
|
|
695
|
+
# opt slug/versions out of ALL shared routes / query params
|
|
696
|
+
route_exclusions: ['legacy_db']
|
|
697
|
+
query_exclusions:
|
|
698
|
+
- slug: slug4
|
|
699
|
+
version: 1.0
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
Rules:
|
|
703
|
+
- **The version config wins.** Its own routes come first and replace any shared route with the same shape. Shape means the same path segments; `{{param}}` names and leading/trailing slashes are ignored, so `detections/{{id}}` and `/detections/{{detection_id}}/` are the same route. Routes the version config adds on top are kept.
|
|
704
|
+
- Shared `query_params` behave like a version config's top-level `query_params`. They apply to every route, and a param with the same name in the version config (or on a route) overrides the shared one.
|
|
705
|
+
- `include` and `route_inclusions`/`query_inclusions` are the opposite of `exclude` and `route_exclusions`/`query_exclusions`. When given (non-empty), the route or query param is added **only** to the listed slug/versions. A shared item is added only if it passes both the top-level lists and its own `include`/`exclude`.
|
|
706
|
+
- The same route (by shape) or query param (by name) can appear more than once in the shared file. Each database/version gets the first one whose `include`/`exclude` select it, so complementary `include`/`exclude` lists give different databases different versions of an endpoint.
|
|
707
|
+
- `include`, `exclude` and the four top-level lists take a list of `'<slug>/<version>'` strings or `{slug: <slug>, version: <version>}` mappings. `'<slug>'`, `'<slug>/*'` or a missing/`'*'` version match every version, including unversioned databases. Versions compare numerically when possible (`2.3`, `"2.3"`), and `latest` is resolved before matching.
|
|
708
|
+
|
|
436
709
|
**For more details**, see the [SQL Database Support Wiki](https://github.com/SchmidtDSE/api_dock/wiki/SQL-Database-Support).
|
|
437
710
|
|
|
438
711
|
---
|
|
@@ -1324,47 +1597,55 @@ pixi run python scripts/hello_world.py
|
|
|
1324
1597
|
|
|
1325
1598
|
## Publishing a Release
|
|
1326
1599
|
|
|
1600
|
+
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.
|
|
1601
|
+
|
|
1327
1602
|
```bash
|
|
1328
|
-
# 0.
|
|
1603
|
+
# 0. Start from a clean, up-to-date main
|
|
1604
|
+
export VERSION=0.8.1 # the NEW version, no leading "v"
|
|
1605
|
+
git checkout main
|
|
1606
|
+
git pull origin main
|
|
1607
|
+
git status
|
|
1608
|
+
|
|
1609
|
+
# 1. Set `version` in pyproject.toml to $VERSION
|
|
1329
1610
|
|
|
1330
|
-
#
|
|
1611
|
+
# 2. Run the tests
|
|
1612
|
+
pixi run -e dev pytest -q
|
|
1331
1613
|
|
|
1332
|
-
#
|
|
1614
|
+
# 3. Commit, tag, push (the commit command adds the "v$VERSION: " prefix)
|
|
1615
|
+
export COMMIT_MESSAGE='cross-schema unions, schema groups, source columns'
|
|
1333
1616
|
git add -A
|
|
1334
|
-
git commit -m "
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
find . -name "*.pyc" -delete
|
|
1344
|
-
pixi run -e dev python -m build --wheel
|
|
1345
|
-
ls dist/*.whl
|
|
1346
|
-
|
|
1347
|
-
# 5. Create GitHub release with the wheel attached
|
|
1348
|
-
gh release create v0.6.1 dist/api_dock-0.6.1-py3-none-any.whl \
|
|
1349
|
-
--title "v0.6.1" --notes "$(cat <<'EOF'
|
|
1617
|
+
git commit -m "v$VERSION: $COMMIT_MESSAGE"
|
|
1618
|
+
git tag "v$VERSION"
|
|
1619
|
+
git push origin main "v$VERSION"
|
|
1620
|
+
|
|
1621
|
+
# 4. Publish the GitHub Release; this triggers the PyPI upload.
|
|
1622
|
+
# Don't use --draft (the workflow only runs on a published release); no wheel needs attaching.
|
|
1623
|
+
gh release create "v$VERSION" \
|
|
1624
|
+
--title "v$VERSION" \
|
|
1625
|
+
--notes "$(cat <<'EOF'
|
|
1350
1626
|
* new features
|
|
1351
|
-
-
|
|
1352
|
-
-
|
|
1627
|
+
- Cross-schema unions: `[[*.table]]` reads a table from every shared schema that has it, and `[[*!.table]]` does the same minus the current database/version's schema
|
|
1628
|
+
- Named `schema_groups` in `databases/config.yaml`, used as `[[group.table]]` / `[[group!.table]]` (validated: known schemas only, no group/schema name clashes)
|
|
1629
|
+
- Route `source_columns` adds where each union row came from (`schema`, `name`, `version`), with default names `schema_name`/`name`/`version` or your own; nothing is added by default
|
|
1630
|
+
- `{{self.schema}}`, `{{self.name}}`, and `{{self.version}}` placeholders for the database/version being queried
|
|
1631
|
+
- Together these support an "overlaps" route shared by every database/version (every detection overlapping a given one across all schemas, except that detection itself); shared query params such as `confidence`, `sort`, and `limit` apply to its rows
|
|
1353
1632
|
* bug fixes
|
|
1354
|
-
-
|
|
1355
|
-
- `Content-Encoding` (gzip/br/deflate) is now preserved on compressed responses — raw bytes are streamed via `aiter_raw()` so the header stays valid and the client can decompress
|
|
1356
|
-
- Slow upstreams (e.g. large aggregation queries) no longer 502 at httpx's hardcoded 5s default — the timeout is now configurable via the `timeout` setting
|
|
1633
|
+
- A `!` union that removes every member returns no rows (with the right columns) instead of failing, and only the schemas a union actually reads get views and storage credentials
|
|
1357
1634
|
* cleanup / other improvements
|
|
1358
|
-
- Added `
|
|
1359
|
-
- `
|
|
1360
|
-
-
|
|
1635
|
+
- Added `SqlContext`; `build_sql_query()` / `build_sql_query_with_tables()` accept an optional `context`
|
|
1636
|
+
- README: new "Querying across schemas" section with an overlaps example; example `databases/config.yaml` shows `schema_groups` and a union route
|
|
1637
|
+
- Test suite grew from 198 to 227 tests (`test_schema_unions.py`, including a real DuckDB end-to-end overlaps test)
|
|
1361
1638
|
EOF
|
|
1362
1639
|
)"
|
|
1363
1640
|
|
|
1364
|
-
#
|
|
1365
|
-
|
|
1366
|
-
|
|
1641
|
+
# 5. Watch the publish workflow, then confirm PyPI has the new version
|
|
1642
|
+
gh run watch "$(gh run list --workflow=publish_to_pypi.yml -L1 --json databaseId -q '.[0].databaseId')" --repo SchmidtDSE/api_dock
|
|
1643
|
+
curl -s https://pypi.org/pypi/api-dock/json | python3 -c "import sys,json; print('PyPI latest:', json.load(sys.stdin)['info']['version'])"
|
|
1367
1644
|
|
|
1645
|
+
# 6. conda-forge: once the bot opens the v$VERSION PR (usually within hours), check that the recipe's
|
|
1646
|
+
# run requirements match pyproject.toml dependencies (the bot only bumps version + sha256), then merge it
|
|
1647
|
+
gh pr list --repo conda-forge/api_dock-feedstock --state open
|
|
1648
|
+
```
|
|
1368
1649
|
|
|
1369
1650
|
---
|
|
1370
1651
|
|