test-data-service 0.1.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.
- test_data_service-0.1.0/LICENSE +21 -0
- test_data_service-0.1.0/MANIFEST.in +7 -0
- test_data_service-0.1.0/PKG-INFO +229 -0
- test_data_service-0.1.0/pyproject.toml +69 -0
- test_data_service-0.1.0/readme.md +192 -0
- test_data_service-0.1.0/requirements-dev.txt +6 -0
- test_data_service-0.1.0/requirements.txt +5 -0
- test_data_service-0.1.0/setup.cfg +4 -0
- test_data_service-0.1.0/setup.py +9 -0
- test_data_service-0.1.0/src/test_data_service.egg-info/PKG-INFO +229 -0
- test_data_service-0.1.0/src/test_data_service.egg-info/SOURCES.txt +30 -0
- test_data_service-0.1.0/src/test_data_service.egg-info/dependency_links.txt +1 -0
- test_data_service-0.1.0/src/test_data_service.egg-info/entry_points.txt +2 -0
- test_data_service-0.1.0/src/test_data_service.egg-info/requires.txt +12 -0
- test_data_service-0.1.0/src/test_data_service.egg-info/top_level.txt +1 -0
- test_data_service-0.1.0/src/testdataservice/__init__.py +26 -0
- test_data_service-0.1.0/src/testdataservice/__main__.py +10 -0
- test_data_service-0.1.0/src/testdataservice/accounts.py +168 -0
- test_data_service-0.1.0/src/testdataservice/app.py +558 -0
- test_data_service-0.1.0/src/testdataservice/auth.py +141 -0
- test_data_service-0.1.0/src/testdataservice/cli.py +62 -0
- test_data_service-0.1.0/src/testdataservice/config.py +162 -0
- test_data_service-0.1.0/src/testdataservice/keystore.py +154 -0
- test_data_service-0.1.0/src/testdataservice/passwords.py +41 -0
- test_data_service-0.1.0/src/testdataservice/sessions.py +44 -0
- test_data_service-0.1.0/src/testdataservice/storage.py +219 -0
- test_data_service-0.1.0/src/testdataservice/types.py +57 -0
- test_data_service-0.1.0/src/testdataservice/web/static/app.js +509 -0
- test_data_service-0.1.0/src/testdataservice/web/static/style.css +136 -0
- test_data_service-0.1.0/src/testdataservice/web/templates/index.html +162 -0
- test_data_service-0.1.0/tds.example.yml +6 -0
- test_data_service-0.1.0/tests/test_service.py +449 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Roy de Kleijn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: test-data-service
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A disk-backed test data service with a REST API and web interface
|
|
5
|
+
Author-email: Roy de Kleijn <dekleijn.roy@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/testsmith-io/test-data-service
|
|
8
|
+
Project-URL: Documentation, https://github.com/testsmith-io/test-data-service#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/testsmith-io/test-data-service
|
|
10
|
+
Project-URL: Issues, https://github.com/testsmith-io/test-data-service/issues
|
|
11
|
+
Keywords: test-data,rest,api,fastapi,key-value,testing,mock-data
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Testing
|
|
21
|
+
Classifier: Framework :: FastAPI
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: fastapi>=0.110
|
|
26
|
+
Requires-Dist: uvicorn[standard]>=0.27
|
|
27
|
+
Requires-Dist: jinja2>=3.1
|
|
28
|
+
Requires-Dist: pydantic>=2.0
|
|
29
|
+
Requires-Dist: pyyaml>=6.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
32
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
34
|
+
Requires-Dist: flake8>=6.0; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Test Data Service
|
|
41
|
+
|
|
42
|
+
A working prototype of the above. Data is stored as JSON files on disk
|
|
43
|
+
(one file per namespace), exposed through a REST API and a web interface,
|
|
44
|
+
with optional role-based access control.
|
|
45
|
+
|
|
46
|
+
### Install & run
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# from PyPI (once published)
|
|
50
|
+
pip install test-data-service
|
|
51
|
+
|
|
52
|
+
# start the server (API + web UI) on http://127.0.0.1:8000
|
|
53
|
+
tds serve # or: python -m testdataservice serve
|
|
54
|
+
# open http://127.0.0.1:8000 in a browser
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
From source, for development:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
python3 -m venv .venv
|
|
61
|
+
.venv/bin/pip install -e ".[dev]"
|
|
62
|
+
.venv/bin/tds serve
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Packaging/release details (build, `twine`, GitHub Actions publish-on-release)
|
|
66
|
+
are in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
67
|
+
|
|
68
|
+
#### Configure with a YAML file
|
|
69
|
+
|
|
70
|
+
Drop a `tds.yml` (or `tds.yaml`) in your project root and just run `tds serve` —
|
|
71
|
+
no flags needed. Environment variables override the file.
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
# tds.yml
|
|
75
|
+
data_dir: ./data # where namespace files + .auth live
|
|
76
|
+
auth: true # turn on accounts / access control
|
|
77
|
+
host: 127.0.0.1
|
|
78
|
+
port: 8000
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Data model
|
|
82
|
+
|
|
83
|
+
Each entry is a `key` / `value` pair inside a **namespace**. A value is one
|
|
84
|
+
of four types: `string`, `boolean`, `number`, or `array`. The type is stored
|
|
85
|
+
alongside the value and validated on write.
|
|
86
|
+
|
|
87
|
+
### REST API
|
|
88
|
+
|
|
89
|
+
`POST` targets the namespace (collection) with the key **in the body**; `PUT`
|
|
90
|
+
targets a specific entry URI. They are not interchangeable — `POST` only creates,
|
|
91
|
+
`PUT` only updates.
|
|
92
|
+
|
|
93
|
+
| Method | Path | Permission | Description |
|
|
94
|
+
|----------|-------------------------|------------|--------------------------------------|
|
|
95
|
+
| `GET` | `/api/health` | — | Health + auth status |
|
|
96
|
+
| `GET` | `/api/whoami` | — | Caller's access summary |
|
|
97
|
+
| `GET` | `/api/namespaces` | any key | List accessible namespaces |
|
|
98
|
+
| `GET` | `/api/{ns}` | read `ns` | List entries in a namespace |
|
|
99
|
+
| `POST` | `/api/{ns}` | write `ns` | Create an entry (`409` if key exists) |
|
|
100
|
+
| `GET` | `/api/{ns}/{key}` | read `ns` | Get one entry |
|
|
101
|
+
| `PUT` | `/api/{ns}/{key}` | write `ns` | Update an entry (`404` if missing) |
|
|
102
|
+
| `DELETE` | `/api/{ns}/{key}` | write `ns` | Delete an entry |
|
|
103
|
+
| `DELETE` | `/api/{ns}` | write `ns` | Delete a namespace |
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# create — POST to the namespace, key in the body
|
|
107
|
+
curl -X POST localhost:8000/api/demo \
|
|
108
|
+
-H 'Content-Type: application/json' \
|
|
109
|
+
-d '{"key":"greeting","value":"hello","type":"string"}'
|
|
110
|
+
|
|
111
|
+
# update — PUT to the entry's URI
|
|
112
|
+
curl -X PUT localhost:8000/api/demo/greeting \
|
|
113
|
+
-H 'Content-Type: application/json' \
|
|
114
|
+
-d '{"value":"hi there","type":"string"}'
|
|
115
|
+
|
|
116
|
+
curl localhost:8000/api/demo # list entries
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
(The names `namespaces`, `health`, and `whoami` are reserved and can't be used
|
|
120
|
+
as namespaces, since they'd collide with the routes above.)
|
|
121
|
+
|
|
122
|
+
The API is self-documenting via OpenAPI. Interactive docs are available at:
|
|
123
|
+
|
|
124
|
+
- **`/docs`** — Swagger UI (also linked as **API docs** in the web interface header)
|
|
125
|
+
- **`/redoc`** — ReDoc
|
|
126
|
+
- **`/openapi.json`** — raw OpenAPI schema
|
|
127
|
+
|
|
128
|
+
When auth is enabled, each operation in Swagger exposes an `X-API-Key` header
|
|
129
|
+
field you can fill in to try requests.
|
|
130
|
+
|
|
131
|
+
### CSV import / export (web UI only)
|
|
132
|
+
|
|
133
|
+
The web interface has **Download CSV** / **Import CSV** buttons on each
|
|
134
|
+
namespace. These are a convenience of the web UI only — they are built on the
|
|
135
|
+
regular entry API (export reads the entries; import creates/updates each row),
|
|
136
|
+
so there are no dedicated CSV endpoints. The CSV has three columns —
|
|
137
|
+
`key`, `type`, `value`:
|
|
138
|
+
|
|
139
|
+
```csv
|
|
140
|
+
key,type,value
|
|
141
|
+
name,string,alice
|
|
142
|
+
count,number,7
|
|
143
|
+
tags,array,"[""x"", ""y""]"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
On import the `type` column is optional — if omitted, each value's type is
|
|
147
|
+
inferred from its text.
|
|
148
|
+
|
|
149
|
+
### Access control (accounts + API keys)
|
|
150
|
+
|
|
151
|
+
Off by default. **Turn it on** with `auth: true` in `tds.yml`, `--auth`, or
|
|
152
|
+
`TDS_AUTH_ENABLED=1`. There are two credential types, and you can use both:
|
|
153
|
+
|
|
154
|
+
* **User accounts** — username + password. Humans log into the **web UI**
|
|
155
|
+
(session cookie). Passwords are hashed with scrypt (a slow, salted KDF); the
|
|
156
|
+
plaintext is never stored.
|
|
157
|
+
* **API keys** — long random tokens for scripts/CI. Only a SHA-256 hash is
|
|
158
|
+
stored; the token is shown **once**, at creation.
|
|
159
|
+
|
|
160
|
+
Access is **scoped to each namespace**. A role of `admin` means full CRUD on
|
|
161
|
+
that namespace; `reader` means read-only. A **global admin** manages everything.
|
|
162
|
+
|
|
163
|
+
#### First run
|
|
164
|
+
|
|
165
|
+
On first start with auth on, a default admin account is created:
|
|
166
|
+
**username `admin`, password `admin`** — and the web UI **requires you to set a
|
|
167
|
+
new password** before anything else.
|
|
168
|
+
|
|
169
|
+
#### What the admin does (from the web UI)
|
|
170
|
+
|
|
171
|
+
1. **Create namespaces** (the admin-only form in the sidebar).
|
|
172
|
+
2. **Create user accounts** scoped to namespaces — e.g. `alice` with
|
|
173
|
+
`projects=admin` (full CRUD on `projects`). Users then log in and
|
|
174
|
+
create/read/update/delete data in their namespaces, from the web UI **and**
|
|
175
|
+
the API.
|
|
176
|
+
|
|
177
|
+
#### Using the API as a user
|
|
178
|
+
|
|
179
|
+
Each signed-in user can mint a personal **API key** (web UI → *My API keys*, or
|
|
180
|
+
`POST /api/me/keys`) that inherits their namespace permissions:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# the token is returned once; then use it as a header
|
|
184
|
+
curl -X POST localhost:8000/api/projects \
|
|
185
|
+
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
|
|
186
|
+
-d '{"key":"env","value":"staging","type":"string"}'
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Auth & admin endpoints (summary)
|
|
190
|
+
|
|
191
|
+
| Method | Path | Who | Purpose |
|
|
192
|
+
|----------|-------------------------|--------|--------------------------------------|
|
|
193
|
+
| `POST` | `/api/login` | anyone | Log in (sets session cookie) |
|
|
194
|
+
| `POST` | `/api/logout` | anyone | Clear session |
|
|
195
|
+
| `POST` | `/api/password` | user | Change your own password |
|
|
196
|
+
| `POST` | `/api/namespaces` | admin | Create a namespace |
|
|
197
|
+
| `GET`/`POST`/`DELETE` | `/api/accounts[/{user}]` | admin | Manage user accounts |
|
|
198
|
+
| `GET`/`POST`/`DELETE` | `/api/me/keys[/{id}]` | user | Manage your own API keys |
|
|
199
|
+
| `GET`/`POST`/`PUT`/`DELETE` | `/api/keys[/{id}]` | admin | Manage standalone (machine) keys |
|
|
200
|
+
|
|
201
|
+
Standalone machine-to-machine keys (not tied to an account) are still managed by
|
|
202
|
+
an admin at `/api/keys`. For bootstrapping you may seed keys via `TDS_API_KEYS`
|
|
203
|
+
or an admin token via `TDS_ADMIN_KEY` (both hashed in memory, never written to
|
|
204
|
+
disk).
|
|
205
|
+
|
|
206
|
+
### Configuration
|
|
207
|
+
|
|
208
|
+
Settings come from `tds.yml`/`tds.yaml` in the project root, overridden by
|
|
209
|
+
environment variables:
|
|
210
|
+
|
|
211
|
+
| YAML key | Env var | Default | Purpose |
|
|
212
|
+
|------------|--------------------|--------------|------------------------------------------|
|
|
213
|
+
| `data_dir` | `TDS_DATA_DIR` | `./tds-data` | Namespace files + `.auth/` live here |
|
|
214
|
+
| `auth` | `TDS_AUTH_ENABLED` | `false` | Turn access control on/off |
|
|
215
|
+
| `host` | — | `127.0.0.1` | Bind host |
|
|
216
|
+
| `port` | — | `8000` | Bind port |
|
|
217
|
+
| `secret` | `TDS_SECRET` | *(generated)*| Signs web-UI session cookies |
|
|
218
|
+
| `admin_key`| `TDS_ADMIN_KEY` | *(empty)* | Bootstrap machine admin token |
|
|
219
|
+
| `api_keys` | `TDS_API_KEYS` | *(empty)* | Seed machine keys + grants |
|
|
220
|
+
| — | `TDS_KEY_PEPPER` | *(empty)* | Optional pepper for API-key hashing |
|
|
221
|
+
|
|
222
|
+
Credentials live under `<data_dir>/.auth/` (`accounts.json`, `keys.json`,
|
|
223
|
+
`secret`) — all hashed/secret material, never plaintext passwords or tokens.
|
|
224
|
+
|
|
225
|
+
### Tests
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
.venv/bin/python -m pytest
|
|
229
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "test-data-service"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A disk-backed test data service with a REST API and web interface"
|
|
9
|
+
readme = "readme.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Roy de Kleijn", email = "dekleijn.roy@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["test-data", "rest", "api", "fastapi", "key-value", "testing", "mock-data"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: Testing",
|
|
26
|
+
"Framework :: FastAPI",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
dependencies = [
|
|
30
|
+
"fastapi>=0.110",
|
|
31
|
+
"uvicorn[standard]>=0.27",
|
|
32
|
+
"jinja2>=3.1",
|
|
33
|
+
"pydantic>=2.0",
|
|
34
|
+
"pyyaml>=6.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=7.4",
|
|
40
|
+
"httpx>=0.27",
|
|
41
|
+
"black>=23.0",
|
|
42
|
+
"flake8>=6.0",
|
|
43
|
+
"mypy>=1.0",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.scripts]
|
|
47
|
+
tds = "testdataservice.cli:main"
|
|
48
|
+
|
|
49
|
+
[project.urls]
|
|
50
|
+
Homepage = "https://github.com/testsmith-io/test-data-service"
|
|
51
|
+
Documentation = "https://github.com/testsmith-io/test-data-service#readme"
|
|
52
|
+
Repository = "https://github.com/testsmith-io/test-data-service"
|
|
53
|
+
Issues = "https://github.com/testsmith-io/test-data-service/issues"
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.packages.find]
|
|
56
|
+
where = ["src"]
|
|
57
|
+
|
|
58
|
+
[tool.setuptools.package-data]
|
|
59
|
+
testdataservice = ["web/templates/*.html", "web/static/*"]
|
|
60
|
+
|
|
61
|
+
[tool.black]
|
|
62
|
+
line-length = 100
|
|
63
|
+
target-version = ['py310', 'py311', 'py312']
|
|
64
|
+
|
|
65
|
+
[tool.mypy]
|
|
66
|
+
python_version = "3.10"
|
|
67
|
+
warn_return_any = true
|
|
68
|
+
warn_unused_configs = true
|
|
69
|
+
disallow_untyped_defs = false
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
## Test Data Service
|
|
4
|
+
|
|
5
|
+
A working prototype of the above. Data is stored as JSON files on disk
|
|
6
|
+
(one file per namespace), exposed through a REST API and a web interface,
|
|
7
|
+
with optional role-based access control.
|
|
8
|
+
|
|
9
|
+
### Install & run
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# from PyPI (once published)
|
|
13
|
+
pip install test-data-service
|
|
14
|
+
|
|
15
|
+
# start the server (API + web UI) on http://127.0.0.1:8000
|
|
16
|
+
tds serve # or: python -m testdataservice serve
|
|
17
|
+
# open http://127.0.0.1:8000 in a browser
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
From source, for development:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
python3 -m venv .venv
|
|
24
|
+
.venv/bin/pip install -e ".[dev]"
|
|
25
|
+
.venv/bin/tds serve
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Packaging/release details (build, `twine`, GitHub Actions publish-on-release)
|
|
29
|
+
are in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
30
|
+
|
|
31
|
+
#### Configure with a YAML file
|
|
32
|
+
|
|
33
|
+
Drop a `tds.yml` (or `tds.yaml`) in your project root and just run `tds serve` —
|
|
34
|
+
no flags needed. Environment variables override the file.
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
# tds.yml
|
|
38
|
+
data_dir: ./data # where namespace files + .auth live
|
|
39
|
+
auth: true # turn on accounts / access control
|
|
40
|
+
host: 127.0.0.1
|
|
41
|
+
port: 8000
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Data model
|
|
45
|
+
|
|
46
|
+
Each entry is a `key` / `value` pair inside a **namespace**. A value is one
|
|
47
|
+
of four types: `string`, `boolean`, `number`, or `array`. The type is stored
|
|
48
|
+
alongside the value and validated on write.
|
|
49
|
+
|
|
50
|
+
### REST API
|
|
51
|
+
|
|
52
|
+
`POST` targets the namespace (collection) with the key **in the body**; `PUT`
|
|
53
|
+
targets a specific entry URI. They are not interchangeable — `POST` only creates,
|
|
54
|
+
`PUT` only updates.
|
|
55
|
+
|
|
56
|
+
| Method | Path | Permission | Description |
|
|
57
|
+
|----------|-------------------------|------------|--------------------------------------|
|
|
58
|
+
| `GET` | `/api/health` | — | Health + auth status |
|
|
59
|
+
| `GET` | `/api/whoami` | — | Caller's access summary |
|
|
60
|
+
| `GET` | `/api/namespaces` | any key | List accessible namespaces |
|
|
61
|
+
| `GET` | `/api/{ns}` | read `ns` | List entries in a namespace |
|
|
62
|
+
| `POST` | `/api/{ns}` | write `ns` | Create an entry (`409` if key exists) |
|
|
63
|
+
| `GET` | `/api/{ns}/{key}` | read `ns` | Get one entry |
|
|
64
|
+
| `PUT` | `/api/{ns}/{key}` | write `ns` | Update an entry (`404` if missing) |
|
|
65
|
+
| `DELETE` | `/api/{ns}/{key}` | write `ns` | Delete an entry |
|
|
66
|
+
| `DELETE` | `/api/{ns}` | write `ns` | Delete a namespace |
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# create — POST to the namespace, key in the body
|
|
70
|
+
curl -X POST localhost:8000/api/demo \
|
|
71
|
+
-H 'Content-Type: application/json' \
|
|
72
|
+
-d '{"key":"greeting","value":"hello","type":"string"}'
|
|
73
|
+
|
|
74
|
+
# update — PUT to the entry's URI
|
|
75
|
+
curl -X PUT localhost:8000/api/demo/greeting \
|
|
76
|
+
-H 'Content-Type: application/json' \
|
|
77
|
+
-d '{"value":"hi there","type":"string"}'
|
|
78
|
+
|
|
79
|
+
curl localhost:8000/api/demo # list entries
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
(The names `namespaces`, `health`, and `whoami` are reserved and can't be used
|
|
83
|
+
as namespaces, since they'd collide with the routes above.)
|
|
84
|
+
|
|
85
|
+
The API is self-documenting via OpenAPI. Interactive docs are available at:
|
|
86
|
+
|
|
87
|
+
- **`/docs`** — Swagger UI (also linked as **API docs** in the web interface header)
|
|
88
|
+
- **`/redoc`** — ReDoc
|
|
89
|
+
- **`/openapi.json`** — raw OpenAPI schema
|
|
90
|
+
|
|
91
|
+
When auth is enabled, each operation in Swagger exposes an `X-API-Key` header
|
|
92
|
+
field you can fill in to try requests.
|
|
93
|
+
|
|
94
|
+
### CSV import / export (web UI only)
|
|
95
|
+
|
|
96
|
+
The web interface has **Download CSV** / **Import CSV** buttons on each
|
|
97
|
+
namespace. These are a convenience of the web UI only — they are built on the
|
|
98
|
+
regular entry API (export reads the entries; import creates/updates each row),
|
|
99
|
+
so there are no dedicated CSV endpoints. The CSV has three columns —
|
|
100
|
+
`key`, `type`, `value`:
|
|
101
|
+
|
|
102
|
+
```csv
|
|
103
|
+
key,type,value
|
|
104
|
+
name,string,alice
|
|
105
|
+
count,number,7
|
|
106
|
+
tags,array,"[""x"", ""y""]"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
On import the `type` column is optional — if omitted, each value's type is
|
|
110
|
+
inferred from its text.
|
|
111
|
+
|
|
112
|
+
### Access control (accounts + API keys)
|
|
113
|
+
|
|
114
|
+
Off by default. **Turn it on** with `auth: true` in `tds.yml`, `--auth`, or
|
|
115
|
+
`TDS_AUTH_ENABLED=1`. There are two credential types, and you can use both:
|
|
116
|
+
|
|
117
|
+
* **User accounts** — username + password. Humans log into the **web UI**
|
|
118
|
+
(session cookie). Passwords are hashed with scrypt (a slow, salted KDF); the
|
|
119
|
+
plaintext is never stored.
|
|
120
|
+
* **API keys** — long random tokens for scripts/CI. Only a SHA-256 hash is
|
|
121
|
+
stored; the token is shown **once**, at creation.
|
|
122
|
+
|
|
123
|
+
Access is **scoped to each namespace**. A role of `admin` means full CRUD on
|
|
124
|
+
that namespace; `reader` means read-only. A **global admin** manages everything.
|
|
125
|
+
|
|
126
|
+
#### First run
|
|
127
|
+
|
|
128
|
+
On first start with auth on, a default admin account is created:
|
|
129
|
+
**username `admin`, password `admin`** — and the web UI **requires you to set a
|
|
130
|
+
new password** before anything else.
|
|
131
|
+
|
|
132
|
+
#### What the admin does (from the web UI)
|
|
133
|
+
|
|
134
|
+
1. **Create namespaces** (the admin-only form in the sidebar).
|
|
135
|
+
2. **Create user accounts** scoped to namespaces — e.g. `alice` with
|
|
136
|
+
`projects=admin` (full CRUD on `projects`). Users then log in and
|
|
137
|
+
create/read/update/delete data in their namespaces, from the web UI **and**
|
|
138
|
+
the API.
|
|
139
|
+
|
|
140
|
+
#### Using the API as a user
|
|
141
|
+
|
|
142
|
+
Each signed-in user can mint a personal **API key** (web UI → *My API keys*, or
|
|
143
|
+
`POST /api/me/keys`) that inherits their namespace permissions:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# the token is returned once; then use it as a header
|
|
147
|
+
curl -X POST localhost:8000/api/projects \
|
|
148
|
+
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
|
|
149
|
+
-d '{"key":"env","value":"staging","type":"string"}'
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### Auth & admin endpoints (summary)
|
|
153
|
+
|
|
154
|
+
| Method | Path | Who | Purpose |
|
|
155
|
+
|----------|-------------------------|--------|--------------------------------------|
|
|
156
|
+
| `POST` | `/api/login` | anyone | Log in (sets session cookie) |
|
|
157
|
+
| `POST` | `/api/logout` | anyone | Clear session |
|
|
158
|
+
| `POST` | `/api/password` | user | Change your own password |
|
|
159
|
+
| `POST` | `/api/namespaces` | admin | Create a namespace |
|
|
160
|
+
| `GET`/`POST`/`DELETE` | `/api/accounts[/{user}]` | admin | Manage user accounts |
|
|
161
|
+
| `GET`/`POST`/`DELETE` | `/api/me/keys[/{id}]` | user | Manage your own API keys |
|
|
162
|
+
| `GET`/`POST`/`PUT`/`DELETE` | `/api/keys[/{id}]` | admin | Manage standalone (machine) keys |
|
|
163
|
+
|
|
164
|
+
Standalone machine-to-machine keys (not tied to an account) are still managed by
|
|
165
|
+
an admin at `/api/keys`. For bootstrapping you may seed keys via `TDS_API_KEYS`
|
|
166
|
+
or an admin token via `TDS_ADMIN_KEY` (both hashed in memory, never written to
|
|
167
|
+
disk).
|
|
168
|
+
|
|
169
|
+
### Configuration
|
|
170
|
+
|
|
171
|
+
Settings come from `tds.yml`/`tds.yaml` in the project root, overridden by
|
|
172
|
+
environment variables:
|
|
173
|
+
|
|
174
|
+
| YAML key | Env var | Default | Purpose |
|
|
175
|
+
|------------|--------------------|--------------|------------------------------------------|
|
|
176
|
+
| `data_dir` | `TDS_DATA_DIR` | `./tds-data` | Namespace files + `.auth/` live here |
|
|
177
|
+
| `auth` | `TDS_AUTH_ENABLED` | `false` | Turn access control on/off |
|
|
178
|
+
| `host` | — | `127.0.0.1` | Bind host |
|
|
179
|
+
| `port` | — | `8000` | Bind port |
|
|
180
|
+
| `secret` | `TDS_SECRET` | *(generated)*| Signs web-UI session cookies |
|
|
181
|
+
| `admin_key`| `TDS_ADMIN_KEY` | *(empty)* | Bootstrap machine admin token |
|
|
182
|
+
| `api_keys` | `TDS_API_KEYS` | *(empty)* | Seed machine keys + grants |
|
|
183
|
+
| — | `TDS_KEY_PEPPER` | *(empty)* | Optional pepper for API-key hashing |
|
|
184
|
+
|
|
185
|
+
Credentials live under `<data_dir>/.auth/` (`accounts.json`, `keys.json`,
|
|
186
|
+
`secret`) — all hashed/secret material, never plaintext passwords or tokens.
|
|
187
|
+
|
|
188
|
+
### Tests
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
.venv/bin/python -m pytest
|
|
192
|
+
```
|