Skip to main content

Test Data Service

A working prototype of the above. Data is stored as JSON files on disk (one file per namespace), exposed through a REST API and a web interface, with optional role-based access control.

Install & run

# from PyPI (once published)
pip install test-data-service

# start the server (API + web UI) on http://127.0.0.1:8000
tds serve                       # or: python -m testdataservice serve
# open http://127.0.0.1:8000 in a browser

From source, for development:

python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/tds serve

Packaging/release details (build, twine, GitHub Actions publish-on-release) are in CONTRIBUTING.md.

Configure with a YAML file

Drop a tds.yml (or tds.yaml) in your project root and just run tds serve — no flags needed. Environment variables override the file.

# tds.yml
data_dir: ./data        # where namespace files + .auth live
auth: true              # turn on accounts / access control
host: 127.0.0.1
port: 8000

Data model

Each entry is a key / value pair inside a namespace. A value is one of four types: string, boolean, number, or array. The type is stored alongside the value and validated on write.

REST API

POST targets the namespace (collection) with the key in the body; PUT targets a specific entry URI. They are not interchangeable — POST only creates, PUT only updates.

Method Path Permission Description
GET /api/health — Health + auth status
GET /api/whoami — Caller's access summary
GET /api/namespaces any key List accessible namespaces
GET /api/{ns} read ns List entries in a namespace
POST /api/{ns} write ns Create an entry (409 if key exists)
GET /api/{ns}/{key} read ns Get one entry
PUT /api/{ns}/{key} write ns Update an entry (404 if missing)
DELETE /api/{ns}/{key} write ns Delete an entry
DELETE /api/{ns} write ns Delete a namespace
# create — POST to the namespace, key in the body
curl -X POST localhost:8000/api/demo \
  -H 'Content-Type: application/json' \
  -d '{"key":"greeting","value":"hello","type":"string"}'

# update — PUT to the entry's URI
curl -X PUT localhost:8000/api/demo/greeting \
  -H 'Content-Type: application/json' \
  -d '{"value":"hi there","type":"string"}'

curl localhost:8000/api/demo          # list entries

(The names namespaces, health, and whoami are reserved and can't be used as namespaces, since they'd collide with the routes above.)

The API is self-documenting via OpenAPI. Interactive docs are available at:

  • /docs — Swagger UI (also linked as API docs in the web interface header)
  • /redoc — ReDoc
  • /openapi.json — raw OpenAPI schema

When auth is enabled, each operation in Swagger exposes an X-API-Key header field you can fill in to try requests.

CSV import / export (web UI only)

The web interface has Download CSV / Import CSV buttons on each namespace. These are a convenience of the web UI only — they are built on the regular entry API (export reads the entries; import creates/updates each row), so there are no dedicated CSV endpoints. The CSV has three columns — key, type, value:

key,type,value
name,string,alice
count,number,7
tags,array,"[""x"", ""y""]"

On import the type column is optional — if omitted, each value's type is inferred from its text.

Access control (accounts + API keys)

Off by default. Turn it on with auth: true in tds.yml, --auth, or TDS_AUTH_ENABLED=1. There are two credential types, and you can use both:

  • User accounts — username + password. Humans log into the web UI (session cookie). Passwords are hashed with scrypt (a slow, salted KDF); the plaintext is never stored.
  • API keys — long random tokens for scripts/CI. Only a SHA-256 hash is stored; the token is shown once, at creation.

Access is scoped to each namespace. A role of admin means full CRUD on that namespace; reader means read-only. A global admin manages everything.

First run

On first start with auth on, a default admin account is created: username admin, password admin — and the web UI requires you to set a new password before anything else.

What the admin does (from the web UI)

  1. Create namespaces (the admin-only form in the sidebar).
  2. Create user accounts scoped to namespaces — e.g. alice with projects=admin (full CRUD on projects). Users then log in and create/read/update/delete data in their namespaces, from the web UI and the API.

Using the API as a user

Each signed-in user can mint a personal API key (web UI → My API keys, or POST /api/me/keys) that inherits their namespace permissions:

# the token is returned once; then use it as a header
curl -X POST localhost:8000/api/projects \
  -H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
  -d '{"key":"env","value":"staging","type":"string"}'

Auth & admin endpoints (summary)

Method Path Who Purpose
POST /api/login anyone Log in (sets session cookie)
POST /api/logout anyone Clear session
POST /api/password user Change your own password
POST /api/namespaces admin Create a namespace
GET/POST/DELETE /api/accounts[/{user}] admin Manage user accounts
GET/POST/DELETE /api/me/keys[/{id}] user Manage your own API keys
GET/POST/PUT/DELETE /api/keys[/{id}] admin Manage standalone (machine) keys

Standalone machine-to-machine keys (not tied to an account) are still managed by an admin at /api/keys. For bootstrapping you may seed keys via TDS_API_KEYS or an admin token via TDS_ADMIN_KEY (both hashed in memory, never written to disk).

Configuration

Settings come from tds.yml/tds.yaml in the project root, overridden by environment variables:

YAML key Env var Default Purpose
data_dir TDS_DATA_DIR ./tds-data Namespace files + .auth/ live here
auth TDS_AUTH_ENABLED false Turn access control on/off
host — 127.0.0.1 Bind host
port — 8000 Bind port
secret TDS_SECRET (generated) Signs web-UI session cookies
admin_key TDS_ADMIN_KEY (empty) Bootstrap machine admin token
api_keys TDS_API_KEYS (empty) Seed machine keys + grants
— TDS_KEY_PEPPER (empty) Optional pepper for API-key hashing

Credentials live under <data_dir>/.auth/ (accounts.json, keys.json, secret) — all hashed/secret material, never plaintext passwords or tokens.

Tests

.venv/bin/python -m pytest

Metadata

Release files for test-data-service 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for test-data-service 0.1.0
File Size Uploaded
test_data_service-0.1.0.tar.gz 33.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for test-data-service 0.1.0
File Interpreter ABI Platform
test_data_service-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 68.2 kB

Release files / test_data_service-0.1.0.tar.gz

Download URL test_data_service-0.1.0.tar.gz
Size 33.8 kB
Tags Source
SHA-256 checksum
How to use checksums
0f274411a43927383312c763955ded04ce0dc483b0b021cc29643cf89bd790a2
BLAKE2b-256 checksum
How to use checksums
0eb2f0894a402452304d53b6aa8808caf1541f921c9749c8ad6ea38fe3ad2b16
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Release files / test_data_service-0.1.0-py3-none-any.whl

Download URL test_data_service-0.1.0-py3-none-any.whl
Size 34.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
787f042e1823a3676da2413822a74e6b9cc36e02082b8a38a79eaa8875aa6aa4
BLAKE2b-256 checksum
How to use checksums
8bb6ae2ef674443521f2e0141adebda223ed2ce03a128bd4f538e2e5d3a33fed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page