Repository navigation
Developer Guide
How to work on API Dock itself: setup, tests, conventions, how the code is laid out, and how releases are made.
curl -fsSL https://pixi.sh/install.sh | bash # once: install pixi
git clone https://github.com/SchmidtDSE/api_dock.git
cd api_dock
pixi install -e dev # dev tools, psycopg, a local PostgreSQL
pixi run -e dev pytest -q # run the testsThe dev environment includes the optional postgres extra (psycopg and psycopg_pool) and conda-forge postgresql. The postgres_server fixture in tests/conftest.py starts a throwaway PostgreSQL server for the test session. PostgreSQL tests are skipped when PostgreSQL or psycopg isn't installed, so the rest of the suite runs anywhere.
- Branch from
main. - Make the change, with tests for any new behaviour.
- Run
pixi run -e dev pytest -q; it must pass. - Commit file by file. Start each message with the file name, prefixed by a short task label when several files change for one reason:
(task label) path/to/file.py: what changed. - Open a pull request against
main.
- PEP 8, 100-character lines, type hints on every function, Google-style docstrings.
- Each
.pyfile's module docstring ends withLicense: BSD 3-Clause. - Modules are laid out in four sections, each headed by a three-line comment with two blank lines above and none below:
IMPORTS,CONSTANTS,PUBLIC(names without a leading underscore) andINTERNAL(names with one). - Names imported from
typingare listed alphabetically. - Two blank lines between module-level functions, one between methods, and no blank line after a docstring or section comment.
- Prefer clear, small helpers over abstraction for its own sake.
api_dock/
cli.py api-dock commands: init, start, describe, encrypt, decrypt, generate-key
config.py main and remote config loading, settings, route allow/deny, cookies
config_discovery.py finds configs by name; `init` copies the example config
route_mapper.py RouteMapper: request handling for remotes and databases, startup checks,
start()/aclose() for PostgreSQL pools
fast_api.py FastAPI app: routes, streaming proxy, lifespan, base_path middleware
flask_api.py Flask app (buffered; refuses PostgreSQL configs)
database_config.py database configs, the shared databases/config.yaml (tables, meta,
schemas, connections, slugs, shared routes, include/exclude),
version discovery, route matching, config checks
sql_builder.py builds SQL: conditional selection, [[table]] and union expansion,
bound values, query-param fragments, engine choice per route
sql_template_check.py refuses quoted/commented variables and templates ending in a comment
database_backends.py DatabaseBackend interface; DuckDBBackend (worker thread, duckdb
settings, storage credentials, schema views, attached PostgreSQL)
postgres_config.py PostgreSQL connection checks, env: resolution, connection strings
postgres_backend.py PostgresBackend and PostgresPools (psycopg pools, one per connection)
storage_auth.py DuckDB credentials for S3 (scoped per table), GCS, Azure, HTTP
listings.py the `expose` catalog endpoints
lookups.py lookups: definitions, templates and safety checks, SQL/HTTP runners,
LookupStore (rows, refresh), refresh endpoint settings
auth.py, encryption.py authentication providers for database routes; value encryption
types.py ProxyResponse, PreparedRequest, ListingSpec, TableReference, SqlContext
example_api_dock_config/ copied by `api-dock init`
tests/ one file per feature area; conftest.py has the PostgreSQL fixture
RouteMapper.map_database_route does the following:
- Resolves the version (
latest, version files and slugs). - Loads the version config and merges the main config and the shared routes/query params that apply to it.
- Checks authentication.
- Matches the route and merges top-level query params.
- Handles early responses (
response:,required). - Chooses the engine (
route_engine). - Builds the SQL with
?(DuckDB) or%s(psycopg) markers and the bound values (build_sql_query_with_tables). - Runs it on the chosen backend and converts the rows to JSON.
Remote requests go through prepare_remote_request, which validates and builds the upstream request. The FastAPI app streams it with httpx.
Publishing a GitHub release triggers .github/workflows/publish_to_pypi.yml, which builds the package and uploads it to PyPI with trusted publishing. Nothing is built or uploaded locally.
export VERSION=0.9.1 # the new version, no leading "v"
# set version = "$VERSION" in pyproject.toml, then:
pixi run -e dev pytest -q
export COMMIT_MESSAGE='short summary' # the commit adds the "v$VERSION: " prefix
git add -A && git commit -m "v$VERSION: $COMMIT_MESSAGE"
git tag "v$VERSION" && git push origin main "v$VERSION"
gh release create "v$VERSION" --title "v$VERSION" --notes "..." # not --draftCheck PyPI with https://pypi.org/pypi/api-dock/$VERSION/json; the main JSON page is cached for a few minutes. conda-forge follows through its bot's pull request on conda-forge/api_dock-feedstock, which a maintainer merges after checking the recipe's dependencies match pyproject.toml.
- The README is a short overview; details live in this wiki.
- In wiki pages, keep API Dock's
[[table]]syntax inside backticks or code blocks: GitHub wikis turn bare double brackets into wiki links.
Getting started
Remote APIs
Databases
- SQL Database Support
- Query Parameters
- Conditional SQL
- Shared Database Config
- Cross-Schema Queries
- PostgreSQL
- Lookups
Serving
Developing