test-data-service 0.2.0__tar.gz → 0.3.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.2.0/src/test_data_service.egg-info → test_data_service-0.3.0}/PKG-INFO +78 -49
- {test_data_service-0.2.0 → test_data_service-0.3.0}/pyproject.toml +2 -2
- {test_data_service-0.2.0 → test_data_service-0.3.0}/readme.md +76 -47
- {test_data_service-0.2.0 → test_data_service-0.3.0/src/test_data_service.egg-info}/PKG-INFO +78 -49
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/test_data_service.egg-info/SOURCES.txt +1 -0
- test_data_service-0.3.0/src/testdataservice/__init__.py +36 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/app.py +187 -90
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/config.py +12 -0
- test_data_service-0.3.0/src/testdataservice/sqlite_storage.py +215 -0
- test_data_service-0.3.0/src/testdataservice/storage.py +336 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/web/static/app.js +249 -107
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/web/static/style.css +46 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/web/templates/index.html +47 -21
- test_data_service-0.3.0/tds.example.yml +8 -0
- test_data_service-0.3.0/tests/test_service.py +393 -0
- test_data_service-0.2.0/src/testdataservice/__init__.py +0 -26
- test_data_service-0.2.0/src/testdataservice/storage.py +0 -219
- test_data_service-0.2.0/tds.example.yml +0 -6
- test_data_service-0.2.0/tests/test_service.py +0 -449
- {test_data_service-0.2.0 → test_data_service-0.3.0}/LICENSE +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/MANIFEST.in +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/requirements-dev.txt +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/requirements.txt +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/setup.cfg +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/setup.py +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/test_data_service.egg-info/dependency_links.txt +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/test_data_service.egg-info/entry_points.txt +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/test_data_service.egg-info/requires.txt +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/test_data_service.egg-info/top_level.txt +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/__main__.py +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/accounts.py +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/auth.py +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/cli.py +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/keystore.py +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/passwords.py +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/sessions.py +0 -0
- {test_data_service-0.2.0 → test_data_service-0.3.0}/src/testdataservice/types.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: test-data-service
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A disk-backed test data service with a REST API and web interface
|
|
5
|
-
Author-email: Roy de Kleijn <
|
|
5
|
+
Author-email: Roy de Kleijn <info@testsmith.io>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/testsmith-io/test-data-service
|
|
8
8
|
Project-URL: Documentation, https://github.com/testsmith-io/test-data-service#readme
|
|
@@ -81,46 +81,68 @@ port: 8000
|
|
|
81
81
|
|
|
82
82
|
### Data model
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
Three levels: **namespace → table → row**.
|
|
85
|
+
|
|
86
|
+
* A **namespace** is the unit of access control.
|
|
87
|
+
* A namespace holds any number of **tables**.
|
|
88
|
+
* A table holds **rows**, and each row is a record with an **arbitrary set of
|
|
89
|
+
fields**. Field values can be any JSON (string, number, boolean, array,
|
|
90
|
+
object, null).
|
|
91
|
+
* A table can also have **designed columns** — a persisted column list you set
|
|
92
|
+
up front (the "schema"), which shows even before any rows exist. The
|
|
93
|
+
*effective* columns of a table are its designed columns plus any extra fields
|
|
94
|
+
that appear in its rows, so the layout stays dynamic.
|
|
87
95
|
|
|
88
96
|
### REST API
|
|
89
97
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
`
|
|
93
|
-
|
|
94
|
-
|
|
|
95
|
-
|
|
96
|
-
| `
|
|
97
|
-
| `GET` | `/api/
|
|
98
|
-
| `
|
|
99
|
-
| `GET` | `/api/{ns}`
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
98
|
+
| Method | Path | Permission | Description |
|
|
99
|
+
|----------|-----------------------------------|------------|---------------------------------|
|
|
100
|
+
| `GET` | `/api/health` | — | Health + auth status |
|
|
101
|
+
| `GET` | `/api/whoami` | — | Caller's access summary |
|
|
102
|
+
| `GET` | `/api/namespaces` | any key | List accessible namespaces |
|
|
103
|
+
| `POST` | `/api/namespaces` | admin | Create a namespace |
|
|
104
|
+
| `DELETE` | `/api/{ns}` | write `ns` | Delete a namespace |
|
|
105
|
+
| `GET` | `/api/{ns}` | read `ns` | List tables |
|
|
106
|
+
| `POST` | `/api/{ns}` | write `ns` | Create a table (`{"name": …}`) |
|
|
107
|
+
| `GET` | `/api/{ns}/{table}` | read `ns` | Get a table's designed columns |
|
|
108
|
+
| `PUT` | `/api/{ns}/{table}` | write `ns` | Design columns (`{"columns":[…]}`) |
|
|
109
|
+
| `DELETE` | `/api/{ns}/{table}` | write `ns` | Delete a table |
|
|
110
|
+
| `GET` | `/api/{ns}/{table}/rows` | read `ns` | List rows (filter with `?col=value`) |
|
|
111
|
+
| `POST` | `/api/{ns}/{table}/rows` | write `ns` | Create a row (body = its fields)|
|
|
112
|
+
| `GET` | `/api/{ns}/{table}/rows/{id}` | read `ns` | Get one row |
|
|
113
|
+
| `PUT` | `/api/{ns}/{table}/rows/{id}` | write `ns` | Replace a row's fields |
|
|
114
|
+
| `DELETE` | `/api/{ns}/{table}/rows/{id}` | write `ns` | Delete a row |
|
|
115
|
+
|
|
116
|
+
In the web UI, open a table and use the **Columns** designer to add/remove
|
|
117
|
+
columns; they persist and appear in the grid and the add-row form even with no
|
|
118
|
+
rows yet.
|
|
105
119
|
|
|
106
120
|
```bash
|
|
107
|
-
|
|
108
|
-
curl -X POST localhost:8000/api/demo
|
|
109
|
-
-H 'Content-Type: application/json' \
|
|
110
|
-
-d '{"key":"greeting","value":"hello","type":"string"}'
|
|
121
|
+
curl -X POST localhost:8000/api/namespaces -H 'Content-Type: application/json' -d '{"name":"demo"}'
|
|
122
|
+
curl -X POST localhost:8000/api/demo -H 'Content-Type: application/json' -d '{"name":"users"}'
|
|
111
123
|
|
|
112
|
-
#
|
|
113
|
-
curl -X
|
|
124
|
+
# a row IS its fields — any shape you like
|
|
125
|
+
curl -X POST localhost:8000/api/demo/users/rows \
|
|
114
126
|
-H 'Content-Type: application/json' \
|
|
115
|
-
-d '{"
|
|
127
|
+
-d '{"name":"alice","age":30,"active":true}'
|
|
128
|
+
# -> {"id":"<row-id>","updated_at":"...","fields":{...}}
|
|
116
129
|
|
|
117
|
-
curl localhost:8000/api/demo
|
|
130
|
+
curl localhost:8000/api/demo/users/rows
|
|
131
|
+
# -> {"columns":["name","age","active", ...], "rows":[{"id":..., "fields":{...}}, ...]}
|
|
132
|
+
|
|
133
|
+
# filter by column conditions — each query param is a column=value test,
|
|
134
|
+
# and multiple are AND-ed together:
|
|
135
|
+
curl "localhost:8000/api/demo/users/rows?status=ok&ean=123"
|
|
118
136
|
```
|
|
119
137
|
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
Filter values are matched leniently: `ean=123` matches a numeric `123` or the
|
|
139
|
+
string `"123"`, `active=true` matches the boolean. The web UI has a matching
|
|
140
|
+
**filter** box on each table (type `status=ok&ean=123`).
|
|
141
|
+
|
|
142
|
+
(The names `namespaces`, `health`, `whoami`, `keys`, `accounts`, `login`,
|
|
143
|
+
`logout`, `password`, and `me` are reserved and can't be used as namespaces.)
|
|
122
144
|
|
|
123
|
-
The API is self-documenting via OpenAPI
|
|
145
|
+
The API is self-documenting via OpenAPI:
|
|
124
146
|
|
|
125
147
|
- **`/docs`** — Swagger UI (also linked as **API docs** in the web interface header)
|
|
126
148
|
- **`/redoc`** — ReDoc
|
|
@@ -129,24 +151,20 @@ The API is self-documenting via OpenAPI. Interactive docs are available at:
|
|
|
129
151
|
When auth is enabled, each operation in Swagger exposes an `X-API-Key` header
|
|
130
152
|
field you can fill in to try requests.
|
|
131
153
|
|
|
132
|
-
### CSV import / export (web UI only)
|
|
154
|
+
### CSV import / export (web UI only, per table)
|
|
133
155
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
156
|
+
Each table in the web UI has **Download CSV** / **Import CSV** buttons. A table
|
|
157
|
+
maps naturally to CSV: the header row is the column names, each line is a row.
|
|
158
|
+
These are built on the rows API (no dedicated CSV endpoints). On import, each
|
|
159
|
+
cell is parsed leniently — `30` → number, `true` → boolean, `[1,2]` → array,
|
|
160
|
+
anything else → string.
|
|
139
161
|
|
|
140
162
|
```csv
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
tags,array,"[""x"", ""y""]"
|
|
163
|
+
name,age,active
|
|
164
|
+
alice,30,true
|
|
165
|
+
bob,25,false
|
|
145
166
|
```
|
|
146
167
|
|
|
147
|
-
On import the `type` column is optional — if omitted, each value's type is
|
|
148
|
-
inferred from its text.
|
|
149
|
-
|
|
150
168
|
### Access control (accounts + API keys)
|
|
151
169
|
|
|
152
170
|
Off by default. **Turn it on** with `auth: true` in `tds.yml`, `--auth`, or
|
|
@@ -182,9 +200,9 @@ Each signed-in user can mint a personal **API key** (web UI → *My API keys*, o
|
|
|
182
200
|
|
|
183
201
|
```bash
|
|
184
202
|
# the token is returned once; then use it as a header
|
|
185
|
-
curl -X POST localhost:8000/api/projects \
|
|
203
|
+
curl -X POST localhost:8000/api/projects/people/rows \
|
|
186
204
|
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
|
|
187
|
-
-d '{"
|
|
205
|
+
-d '{"name":"alice","role":"qa"}'
|
|
188
206
|
```
|
|
189
207
|
|
|
190
208
|
#### Auth & admin endpoints (summary)
|
|
@@ -211,7 +229,9 @@ environment variables:
|
|
|
211
229
|
|
|
212
230
|
| YAML key | Env var | Default | Purpose |
|
|
213
231
|
|------------|--------------------|--------------|------------------------------------------|
|
|
214
|
-
| `data_dir` | `TDS_DATA_DIR` | `./tds-data` |
|
|
232
|
+
| `data_dir` | `TDS_DATA_DIR` | `./tds-data` | Data + `.auth/` live here |
|
|
233
|
+
| `storage` | `TDS_STORAGE` | `sqlite` | Storage backend: `sqlite` or `file` |
|
|
234
|
+
| `db_path` | `TDS_DB_PATH` | `<data_dir>/tds.db` | SQLite DB path (sqlite backend) |
|
|
215
235
|
| `auth` | `TDS_AUTH_ENABLED` | `false` | Turn access control on/off |
|
|
216
236
|
| `host` | — | `127.0.0.1` | Bind host |
|
|
217
237
|
| `port` | — | `8000` | Bind port |
|
|
@@ -220,8 +240,17 @@ environment variables:
|
|
|
220
240
|
| `api_keys` | `TDS_API_KEYS` | *(empty)* | Seed machine keys + grants |
|
|
221
241
|
| — | `TDS_KEY_PEPPER` | *(empty)* | Optional pepper for API-key hashing |
|
|
222
242
|
|
|
223
|
-
|
|
224
|
-
|
|
243
|
+
#### Storage backend
|
|
244
|
+
|
|
245
|
+
Data is kept in **SQLite** by default — a single `tds.db` file the app creates
|
|
246
|
+
in `data_dir` on first start (no setup, no server). Rows store their dynamic
|
|
247
|
+
fields in a JSON column, so the schema is fixed while the data stays free-form.
|
|
248
|
+
Set `storage: file` to use one JSON file per namespace instead (human-readable
|
|
249
|
+
and dependency-free, but it rewrites a whole file per change and isn't safe
|
|
250
|
+
across processes). Either way, point `data_dir` at a persistent volume.
|
|
251
|
+
|
|
252
|
+
Credentials always live under `<data_dir>/.auth/` (`accounts.json`,
|
|
253
|
+
`keys.json`, `secret`) — hashed/secret material, never plaintext.
|
|
225
254
|
|
|
226
255
|
### Tests
|
|
227
256
|
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "test-data-service"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "A disk-backed test data service with a REST API and web interface"
|
|
9
9
|
readme = "readme.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = { text = "MIT" }
|
|
12
12
|
authors = [
|
|
13
|
-
{ name = "Roy de Kleijn", email = "
|
|
13
|
+
{ name = "Roy de Kleijn", email = "info@testsmith.io" },
|
|
14
14
|
]
|
|
15
15
|
keywords = ["test-data", "rest", "api", "fastapi", "key-value", "testing", "mock-data"]
|
|
16
16
|
classifiers = [
|
|
@@ -43,46 +43,68 @@ port: 8000
|
|
|
43
43
|
|
|
44
44
|
### Data model
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
Three levels: **namespace → table → row**.
|
|
47
|
+
|
|
48
|
+
* A **namespace** is the unit of access control.
|
|
49
|
+
* A namespace holds any number of **tables**.
|
|
50
|
+
* A table holds **rows**, and each row is a record with an **arbitrary set of
|
|
51
|
+
fields**. Field values can be any JSON (string, number, boolean, array,
|
|
52
|
+
object, null).
|
|
53
|
+
* A table can also have **designed columns** — a persisted column list you set
|
|
54
|
+
up front (the "schema"), which shows even before any rows exist. The
|
|
55
|
+
*effective* columns of a table are its designed columns plus any extra fields
|
|
56
|
+
that appear in its rows, so the layout stays dynamic.
|
|
49
57
|
|
|
50
58
|
### REST API
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`
|
|
55
|
-
|
|
56
|
-
|
|
|
57
|
-
|
|
58
|
-
| `
|
|
59
|
-
| `GET` | `/api/
|
|
60
|
-
| `
|
|
61
|
-
| `GET` | `/api/{ns}`
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
60
|
+
| Method | Path | Permission | Description |
|
|
61
|
+
|----------|-----------------------------------|------------|---------------------------------|
|
|
62
|
+
| `GET` | `/api/health` | — | Health + auth status |
|
|
63
|
+
| `GET` | `/api/whoami` | — | Caller's access summary |
|
|
64
|
+
| `GET` | `/api/namespaces` | any key | List accessible namespaces |
|
|
65
|
+
| `POST` | `/api/namespaces` | admin | Create a namespace |
|
|
66
|
+
| `DELETE` | `/api/{ns}` | write `ns` | Delete a namespace |
|
|
67
|
+
| `GET` | `/api/{ns}` | read `ns` | List tables |
|
|
68
|
+
| `POST` | `/api/{ns}` | write `ns` | Create a table (`{"name": …}`) |
|
|
69
|
+
| `GET` | `/api/{ns}/{table}` | read `ns` | Get a table's designed columns |
|
|
70
|
+
| `PUT` | `/api/{ns}/{table}` | write `ns` | Design columns (`{"columns":[…]}`) |
|
|
71
|
+
| `DELETE` | `/api/{ns}/{table}` | write `ns` | Delete a table |
|
|
72
|
+
| `GET` | `/api/{ns}/{table}/rows` | read `ns` | List rows (filter with `?col=value`) |
|
|
73
|
+
| `POST` | `/api/{ns}/{table}/rows` | write `ns` | Create a row (body = its fields)|
|
|
74
|
+
| `GET` | `/api/{ns}/{table}/rows/{id}` | read `ns` | Get one row |
|
|
75
|
+
| `PUT` | `/api/{ns}/{table}/rows/{id}` | write `ns` | Replace a row's fields |
|
|
76
|
+
| `DELETE` | `/api/{ns}/{table}/rows/{id}` | write `ns` | Delete a row |
|
|
77
|
+
|
|
78
|
+
In the web UI, open a table and use the **Columns** designer to add/remove
|
|
79
|
+
columns; they persist and appear in the grid and the add-row form even with no
|
|
80
|
+
rows yet.
|
|
67
81
|
|
|
68
82
|
```bash
|
|
69
|
-
|
|
70
|
-
curl -X POST localhost:8000/api/demo
|
|
71
|
-
-H 'Content-Type: application/json' \
|
|
72
|
-
-d '{"key":"greeting","value":"hello","type":"string"}'
|
|
83
|
+
curl -X POST localhost:8000/api/namespaces -H 'Content-Type: application/json' -d '{"name":"demo"}'
|
|
84
|
+
curl -X POST localhost:8000/api/demo -H 'Content-Type: application/json' -d '{"name":"users"}'
|
|
73
85
|
|
|
74
|
-
#
|
|
75
|
-
curl -X
|
|
86
|
+
# a row IS its fields — any shape you like
|
|
87
|
+
curl -X POST localhost:8000/api/demo/users/rows \
|
|
76
88
|
-H 'Content-Type: application/json' \
|
|
77
|
-
-d '{"
|
|
89
|
+
-d '{"name":"alice","age":30,"active":true}'
|
|
90
|
+
# -> {"id":"<row-id>","updated_at":"...","fields":{...}}
|
|
91
|
+
|
|
92
|
+
curl localhost:8000/api/demo/users/rows
|
|
93
|
+
# -> {"columns":["name","age","active", ...], "rows":[{"id":..., "fields":{...}}, ...]}
|
|
78
94
|
|
|
79
|
-
|
|
95
|
+
# filter by column conditions — each query param is a column=value test,
|
|
96
|
+
# and multiple are AND-ed together:
|
|
97
|
+
curl "localhost:8000/api/demo/users/rows?status=ok&ean=123"
|
|
80
98
|
```
|
|
81
99
|
|
|
82
|
-
|
|
83
|
-
|
|
100
|
+
Filter values are matched leniently: `ean=123` matches a numeric `123` or the
|
|
101
|
+
string `"123"`, `active=true` matches the boolean. The web UI has a matching
|
|
102
|
+
**filter** box on each table (type `status=ok&ean=123`).
|
|
103
|
+
|
|
104
|
+
(The names `namespaces`, `health`, `whoami`, `keys`, `accounts`, `login`,
|
|
105
|
+
`logout`, `password`, and `me` are reserved and can't be used as namespaces.)
|
|
84
106
|
|
|
85
|
-
The API is self-documenting via OpenAPI
|
|
107
|
+
The API is self-documenting via OpenAPI:
|
|
86
108
|
|
|
87
109
|
- **`/docs`** — Swagger UI (also linked as **API docs** in the web interface header)
|
|
88
110
|
- **`/redoc`** — ReDoc
|
|
@@ -91,24 +113,20 @@ The API is self-documenting via OpenAPI. Interactive docs are available at:
|
|
|
91
113
|
When auth is enabled, each operation in Swagger exposes an `X-API-Key` header
|
|
92
114
|
field you can fill in to try requests.
|
|
93
115
|
|
|
94
|
-
### CSV import / export (web UI only)
|
|
116
|
+
### CSV import / export (web UI only, per table)
|
|
95
117
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
118
|
+
Each table in the web UI has **Download CSV** / **Import CSV** buttons. A table
|
|
119
|
+
maps naturally to CSV: the header row is the column names, each line is a row.
|
|
120
|
+
These are built on the rows API (no dedicated CSV endpoints). On import, each
|
|
121
|
+
cell is parsed leniently — `30` → number, `true` → boolean, `[1,2]` → array,
|
|
122
|
+
anything else → string.
|
|
101
123
|
|
|
102
124
|
```csv
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
tags,array,"[""x"", ""y""]"
|
|
125
|
+
name,age,active
|
|
126
|
+
alice,30,true
|
|
127
|
+
bob,25,false
|
|
107
128
|
```
|
|
108
129
|
|
|
109
|
-
On import the `type` column is optional — if omitted, each value's type is
|
|
110
|
-
inferred from its text.
|
|
111
|
-
|
|
112
130
|
### Access control (accounts + API keys)
|
|
113
131
|
|
|
114
132
|
Off by default. **Turn it on** with `auth: true` in `tds.yml`, `--auth`, or
|
|
@@ -144,9 +162,9 @@ Each signed-in user can mint a personal **API key** (web UI → *My API keys*, o
|
|
|
144
162
|
|
|
145
163
|
```bash
|
|
146
164
|
# the token is returned once; then use it as a header
|
|
147
|
-
curl -X POST localhost:8000/api/projects \
|
|
165
|
+
curl -X POST localhost:8000/api/projects/people/rows \
|
|
148
166
|
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
|
|
149
|
-
-d '{"
|
|
167
|
+
-d '{"name":"alice","role":"qa"}'
|
|
150
168
|
```
|
|
151
169
|
|
|
152
170
|
#### Auth & admin endpoints (summary)
|
|
@@ -173,7 +191,9 @@ environment variables:
|
|
|
173
191
|
|
|
174
192
|
| YAML key | Env var | Default | Purpose |
|
|
175
193
|
|------------|--------------------|--------------|------------------------------------------|
|
|
176
|
-
| `data_dir` | `TDS_DATA_DIR` | `./tds-data` |
|
|
194
|
+
| `data_dir` | `TDS_DATA_DIR` | `./tds-data` | Data + `.auth/` live here |
|
|
195
|
+
| `storage` | `TDS_STORAGE` | `sqlite` | Storage backend: `sqlite` or `file` |
|
|
196
|
+
| `db_path` | `TDS_DB_PATH` | `<data_dir>/tds.db` | SQLite DB path (sqlite backend) |
|
|
177
197
|
| `auth` | `TDS_AUTH_ENABLED` | `false` | Turn access control on/off |
|
|
178
198
|
| `host` | — | `127.0.0.1` | Bind host |
|
|
179
199
|
| `port` | — | `8000` | Bind port |
|
|
@@ -182,8 +202,17 @@ environment variables:
|
|
|
182
202
|
| `api_keys` | `TDS_API_KEYS` | *(empty)* | Seed machine keys + grants |
|
|
183
203
|
| — | `TDS_KEY_PEPPER` | *(empty)* | Optional pepper for API-key hashing |
|
|
184
204
|
|
|
185
|
-
|
|
186
|
-
|
|
205
|
+
#### Storage backend
|
|
206
|
+
|
|
207
|
+
Data is kept in **SQLite** by default — a single `tds.db` file the app creates
|
|
208
|
+
in `data_dir` on first start (no setup, no server). Rows store their dynamic
|
|
209
|
+
fields in a JSON column, so the schema is fixed while the data stays free-form.
|
|
210
|
+
Set `storage: file` to use one JSON file per namespace instead (human-readable
|
|
211
|
+
and dependency-free, but it rewrites a whole file per change and isn't safe
|
|
212
|
+
across processes). Either way, point `data_dir` at a persistent volume.
|
|
213
|
+
|
|
214
|
+
Credentials always live under `<data_dir>/.auth/` (`accounts.json`,
|
|
215
|
+
`keys.json`, `secret`) — hashed/secret material, never plaintext.
|
|
187
216
|
|
|
188
217
|
### Tests
|
|
189
218
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: test-data-service
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A disk-backed test data service with a REST API and web interface
|
|
5
|
-
Author-email: Roy de Kleijn <
|
|
5
|
+
Author-email: Roy de Kleijn <info@testsmith.io>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/testsmith-io/test-data-service
|
|
8
8
|
Project-URL: Documentation, https://github.com/testsmith-io/test-data-service#readme
|
|
@@ -81,46 +81,68 @@ port: 8000
|
|
|
81
81
|
|
|
82
82
|
### Data model
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
Three levels: **namespace → table → row**.
|
|
85
|
+
|
|
86
|
+
* A **namespace** is the unit of access control.
|
|
87
|
+
* A namespace holds any number of **tables**.
|
|
88
|
+
* A table holds **rows**, and each row is a record with an **arbitrary set of
|
|
89
|
+
fields**. Field values can be any JSON (string, number, boolean, array,
|
|
90
|
+
object, null).
|
|
91
|
+
* A table can also have **designed columns** — a persisted column list you set
|
|
92
|
+
up front (the "schema"), which shows even before any rows exist. The
|
|
93
|
+
*effective* columns of a table are its designed columns plus any extra fields
|
|
94
|
+
that appear in its rows, so the layout stays dynamic.
|
|
87
95
|
|
|
88
96
|
### REST API
|
|
89
97
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
`
|
|
93
|
-
|
|
94
|
-
|
|
|
95
|
-
|
|
96
|
-
| `
|
|
97
|
-
| `GET` | `/api/
|
|
98
|
-
| `
|
|
99
|
-
| `GET` | `/api/{ns}`
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
98
|
+
| Method | Path | Permission | Description |
|
|
99
|
+
|----------|-----------------------------------|------------|---------------------------------|
|
|
100
|
+
| `GET` | `/api/health` | — | Health + auth status |
|
|
101
|
+
| `GET` | `/api/whoami` | — | Caller's access summary |
|
|
102
|
+
| `GET` | `/api/namespaces` | any key | List accessible namespaces |
|
|
103
|
+
| `POST` | `/api/namespaces` | admin | Create a namespace |
|
|
104
|
+
| `DELETE` | `/api/{ns}` | write `ns` | Delete a namespace |
|
|
105
|
+
| `GET` | `/api/{ns}` | read `ns` | List tables |
|
|
106
|
+
| `POST` | `/api/{ns}` | write `ns` | Create a table (`{"name": …}`) |
|
|
107
|
+
| `GET` | `/api/{ns}/{table}` | read `ns` | Get a table's designed columns |
|
|
108
|
+
| `PUT` | `/api/{ns}/{table}` | write `ns` | Design columns (`{"columns":[…]}`) |
|
|
109
|
+
| `DELETE` | `/api/{ns}/{table}` | write `ns` | Delete a table |
|
|
110
|
+
| `GET` | `/api/{ns}/{table}/rows` | read `ns` | List rows (filter with `?col=value`) |
|
|
111
|
+
| `POST` | `/api/{ns}/{table}/rows` | write `ns` | Create a row (body = its fields)|
|
|
112
|
+
| `GET` | `/api/{ns}/{table}/rows/{id}` | read `ns` | Get one row |
|
|
113
|
+
| `PUT` | `/api/{ns}/{table}/rows/{id}` | write `ns` | Replace a row's fields |
|
|
114
|
+
| `DELETE` | `/api/{ns}/{table}/rows/{id}` | write `ns` | Delete a row |
|
|
115
|
+
|
|
116
|
+
In the web UI, open a table and use the **Columns** designer to add/remove
|
|
117
|
+
columns; they persist and appear in the grid and the add-row form even with no
|
|
118
|
+
rows yet.
|
|
105
119
|
|
|
106
120
|
```bash
|
|
107
|
-
|
|
108
|
-
curl -X POST localhost:8000/api/demo
|
|
109
|
-
-H 'Content-Type: application/json' \
|
|
110
|
-
-d '{"key":"greeting","value":"hello","type":"string"}'
|
|
121
|
+
curl -X POST localhost:8000/api/namespaces -H 'Content-Type: application/json' -d '{"name":"demo"}'
|
|
122
|
+
curl -X POST localhost:8000/api/demo -H 'Content-Type: application/json' -d '{"name":"users"}'
|
|
111
123
|
|
|
112
|
-
#
|
|
113
|
-
curl -X
|
|
124
|
+
# a row IS its fields — any shape you like
|
|
125
|
+
curl -X POST localhost:8000/api/demo/users/rows \
|
|
114
126
|
-H 'Content-Type: application/json' \
|
|
115
|
-
-d '{"
|
|
127
|
+
-d '{"name":"alice","age":30,"active":true}'
|
|
128
|
+
# -> {"id":"<row-id>","updated_at":"...","fields":{...}}
|
|
116
129
|
|
|
117
|
-
curl localhost:8000/api/demo
|
|
130
|
+
curl localhost:8000/api/demo/users/rows
|
|
131
|
+
# -> {"columns":["name","age","active", ...], "rows":[{"id":..., "fields":{...}}, ...]}
|
|
132
|
+
|
|
133
|
+
# filter by column conditions — each query param is a column=value test,
|
|
134
|
+
# and multiple are AND-ed together:
|
|
135
|
+
curl "localhost:8000/api/demo/users/rows?status=ok&ean=123"
|
|
118
136
|
```
|
|
119
137
|
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
Filter values are matched leniently: `ean=123` matches a numeric `123` or the
|
|
139
|
+
string `"123"`, `active=true` matches the boolean. The web UI has a matching
|
|
140
|
+
**filter** box on each table (type `status=ok&ean=123`).
|
|
141
|
+
|
|
142
|
+
(The names `namespaces`, `health`, `whoami`, `keys`, `accounts`, `login`,
|
|
143
|
+
`logout`, `password`, and `me` are reserved and can't be used as namespaces.)
|
|
122
144
|
|
|
123
|
-
The API is self-documenting via OpenAPI
|
|
145
|
+
The API is self-documenting via OpenAPI:
|
|
124
146
|
|
|
125
147
|
- **`/docs`** — Swagger UI (also linked as **API docs** in the web interface header)
|
|
126
148
|
- **`/redoc`** — ReDoc
|
|
@@ -129,24 +151,20 @@ The API is self-documenting via OpenAPI. Interactive docs are available at:
|
|
|
129
151
|
When auth is enabled, each operation in Swagger exposes an `X-API-Key` header
|
|
130
152
|
field you can fill in to try requests.
|
|
131
153
|
|
|
132
|
-
### CSV import / export (web UI only)
|
|
154
|
+
### CSV import / export (web UI only, per table)
|
|
133
155
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
156
|
+
Each table in the web UI has **Download CSV** / **Import CSV** buttons. A table
|
|
157
|
+
maps naturally to CSV: the header row is the column names, each line is a row.
|
|
158
|
+
These are built on the rows API (no dedicated CSV endpoints). On import, each
|
|
159
|
+
cell is parsed leniently — `30` → number, `true` → boolean, `[1,2]` → array,
|
|
160
|
+
anything else → string.
|
|
139
161
|
|
|
140
162
|
```csv
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
tags,array,"[""x"", ""y""]"
|
|
163
|
+
name,age,active
|
|
164
|
+
alice,30,true
|
|
165
|
+
bob,25,false
|
|
145
166
|
```
|
|
146
167
|
|
|
147
|
-
On import the `type` column is optional — if omitted, each value's type is
|
|
148
|
-
inferred from its text.
|
|
149
|
-
|
|
150
168
|
### Access control (accounts + API keys)
|
|
151
169
|
|
|
152
170
|
Off by default. **Turn it on** with `auth: true` in `tds.yml`, `--auth`, or
|
|
@@ -182,9 +200,9 @@ Each signed-in user can mint a personal **API key** (web UI → *My API keys*, o
|
|
|
182
200
|
|
|
183
201
|
```bash
|
|
184
202
|
# the token is returned once; then use it as a header
|
|
185
|
-
curl -X POST localhost:8000/api/projects \
|
|
203
|
+
curl -X POST localhost:8000/api/projects/people/rows \
|
|
186
204
|
-H "X-API-Key: $TOKEN" -H "Content-Type: application/json" \
|
|
187
|
-
-d '{"
|
|
205
|
+
-d '{"name":"alice","role":"qa"}'
|
|
188
206
|
```
|
|
189
207
|
|
|
190
208
|
#### Auth & admin endpoints (summary)
|
|
@@ -211,7 +229,9 @@ environment variables:
|
|
|
211
229
|
|
|
212
230
|
| YAML key | Env var | Default | Purpose |
|
|
213
231
|
|------------|--------------------|--------------|------------------------------------------|
|
|
214
|
-
| `data_dir` | `TDS_DATA_DIR` | `./tds-data` |
|
|
232
|
+
| `data_dir` | `TDS_DATA_DIR` | `./tds-data` | Data + `.auth/` live here |
|
|
233
|
+
| `storage` | `TDS_STORAGE` | `sqlite` | Storage backend: `sqlite` or `file` |
|
|
234
|
+
| `db_path` | `TDS_DB_PATH` | `<data_dir>/tds.db` | SQLite DB path (sqlite backend) |
|
|
215
235
|
| `auth` | `TDS_AUTH_ENABLED` | `false` | Turn access control on/off |
|
|
216
236
|
| `host` | — | `127.0.0.1` | Bind host |
|
|
217
237
|
| `port` | — | `8000` | Bind port |
|
|
@@ -220,8 +240,17 @@ environment variables:
|
|
|
220
240
|
| `api_keys` | `TDS_API_KEYS` | *(empty)* | Seed machine keys + grants |
|
|
221
241
|
| — | `TDS_KEY_PEPPER` | *(empty)* | Optional pepper for API-key hashing |
|
|
222
242
|
|
|
223
|
-
|
|
224
|
-
|
|
243
|
+
#### Storage backend
|
|
244
|
+
|
|
245
|
+
Data is kept in **SQLite** by default — a single `tds.db` file the app creates
|
|
246
|
+
in `data_dir` on first start (no setup, no server). Rows store their dynamic
|
|
247
|
+
fields in a JSON column, so the schema is fixed while the data stays free-form.
|
|
248
|
+
Set `storage: file` to use one JSON file per namespace instead (human-readable
|
|
249
|
+
and dependency-free, but it rewrites a whole file per change and isn't safe
|
|
250
|
+
across processes). Either way, point `data_dir` at a persistent volume.
|
|
251
|
+
|
|
252
|
+
Credentials always live under `<data_dir>/.auth/` (`accounts.json`,
|
|
253
|
+
`keys.json`, `secret`) — hashed/secret material, never plaintext.
|
|
225
254
|
|
|
226
255
|
### Tests
|
|
227
256
|
|
{test_data_service-0.2.0 → test_data_service-0.3.0}/src/test_data_service.egg-info/SOURCES.txt
RENAMED
|
@@ -22,6 +22,7 @@ src/testdataservice/config.py
|
|
|
22
22
|
src/testdataservice/keystore.py
|
|
23
23
|
src/testdataservice/passwords.py
|
|
24
24
|
src/testdataservice/sessions.py
|
|
25
|
+
src/testdataservice/sqlite_storage.py
|
|
25
26
|
src/testdataservice/storage.py
|
|
26
27
|
src/testdataservice/types.py
|
|
27
28
|
src/testdataservice/web/static/app.js
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Test Data Service — a disk-backed namespace/table/row store with an API and web UI."""
|
|
2
|
+
|
|
3
|
+
from .app import create_app
|
|
4
|
+
from .config import Config
|
|
5
|
+
from .storage import (
|
|
6
|
+
Storage,
|
|
7
|
+
FileStorage,
|
|
8
|
+
StorageBackend,
|
|
9
|
+
open_storage,
|
|
10
|
+
Row,
|
|
11
|
+
NamespaceNotFoundError,
|
|
12
|
+
TableNotFoundError,
|
|
13
|
+
TableExistsError,
|
|
14
|
+
RowNotFoundError,
|
|
15
|
+
InvalidNamespaceError,
|
|
16
|
+
InvalidTableError,
|
|
17
|
+
)
|
|
18
|
+
from .sqlite_storage import SqliteStorage
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
21
|
+
__all__ = [
|
|
22
|
+
"create_app",
|
|
23
|
+
"Config",
|
|
24
|
+
"Storage",
|
|
25
|
+
"FileStorage",
|
|
26
|
+
"SqliteStorage",
|
|
27
|
+
"StorageBackend",
|
|
28
|
+
"open_storage",
|
|
29
|
+
"Row",
|
|
30
|
+
"NamespaceNotFoundError",
|
|
31
|
+
"TableNotFoundError",
|
|
32
|
+
"TableExistsError",
|
|
33
|
+
"RowNotFoundError",
|
|
34
|
+
"InvalidNamespaceError",
|
|
35
|
+
"InvalidTableError",
|
|
36
|
+
]
|