seamless-database 2.1.0__tar.gz → 2.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.
- seamless_database-2.3.0/LICENSE.txt +22 -0
- {seamless_database-2.1.0 → seamless_database-2.3.0}/PKG-INFO +55 -6
- {seamless_database-2.1.0 → seamless_database-2.3.0}/README.md +52 -5
- {seamless_database-2.1.0 → seamless_database-2.3.0}/database.py +145 -129
- {seamless_database-2.1.0 → seamless_database-2.3.0}/database_models.py +69 -10
- {seamless_database-2.1.0 → seamless_database-2.3.0}/pyproject.toml +1 -1
- {seamless_database-2.1.0 → seamless_database-2.3.0}/seamless_database.egg-info/PKG-INFO +55 -6
- {seamless_database-2.1.0 → seamless_database-2.3.0}/seamless_database.egg-info/SOURCES.txt +5 -0
- seamless_database-2.3.0/tests/test_celljoin_records.py +72 -0
- seamless_database-2.3.0/tests/test_contract_hashtype_db.py +154 -0
- seamless_database-2.3.0/tests/test_contract_reference_lifecycle_db.py +65 -0
- {seamless_database-2.1.0 → seamless_database-2.3.0}/tests/test_execution_records.py +212 -1
- seamless_database-2.3.0/tests/test_hash_type_tightening.py +159 -0
- {seamless_database-2.1.0 → seamless_database-2.3.0}/seamless_database.egg-info/dependency_links.txt +0 -0
- {seamless_database-2.1.0 → seamless_database-2.3.0}/seamless_database.egg-info/entry_points.txt +0 -0
- {seamless_database-2.1.0 → seamless_database-2.3.0}/seamless_database.egg-info/requires.txt +0 -0
- {seamless_database-2.1.0 → seamless_database-2.3.0}/seamless_database.egg-info/top_level.txt +0 -0
- {seamless_database-2.1.0 → seamless_database-2.3.0}/setup.cfg +0 -0
- {seamless_database-2.1.0 → seamless_database-2.3.0}/tests/test_random_port.py +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Author: Sjoerd de Vries, MBI platform.
|
|
4
|
+
Copyright (c) 2023-2026 CNRS.
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: seamless-database
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: SQLite-backed metadata database service for Seamless
|
|
5
5
|
Author: Sjoerd de Vries
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,8 +18,10 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE.txt
|
|
21
22
|
Requires-Dist: aiohttp
|
|
22
23
|
Requires-Dist: peewee
|
|
24
|
+
Dynamic: license-file
|
|
23
25
|
|
|
24
26
|
# seamless-database
|
|
25
27
|
|
|
@@ -35,13 +37,60 @@ The database stores the following kinds of records:
|
|
|
35
37
|
|-------|---------|
|
|
36
38
|
| **Transformation** | Maps a transformation checksum to its result checksum |
|
|
37
39
|
| **RevTransformation** | Reverse lookup: finds which transformations produced a given result |
|
|
38
|
-
| **
|
|
40
|
+
| **HashType** | Stores the packed checksum classification word used by new HashType validation and conversion checks |
|
|
39
41
|
| **SyntacticToSemantic** | Maps between syntactic and semantic checksums per celltype |
|
|
40
|
-
| **Expression** | Caches expression evaluation results
|
|
41
|
-
| **MetaData** | Stores execution
|
|
42
|
-
| **IrreproducibleTransformation** | Records transformations whose results are not reproducible |
|
|
42
|
+
| **Expression** | Caches expression evaluation results keyed by `(input_checksum, path, input_celltype, celltype)` |
|
|
43
|
+
| **MetaData** | Stores a canonical execution record for each successful, non-probe transformation |
|
|
44
|
+
| **IrreproducibleTransformation** | Records transformations whose results are not reproducible; metadata is preserved on migration |
|
|
43
45
|
|
|
44
|
-
All data is persisted in a single SQLite file (typically `seamless.db`).
|
|
46
|
+
All data is persisted in a single SQLite file (typically `seamless.db`). The current protocol version is **2.3**.
|
|
47
|
+
|
|
48
|
+
## Expression cache schema
|
|
49
|
+
|
|
50
|
+
Expression `input_celltype` describes the input checksum's interpretation;
|
|
51
|
+
`celltype` describes the produced result. Both are part of the composite cache
|
|
52
|
+
key. Database, jobserver, remote client, and Dask payloads use those same names.
|
|
53
|
+
The unreleased old `celltype`/`target_celltype` keys are not accepted aliases.
|
|
54
|
+
Existing development caches need these statements, in order, or a recreated
|
|
55
|
+
expression cache table:
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
ALTER TABLE expression RENAME COLUMN celltype TO input_celltype;
|
|
59
|
+
ALTER TABLE expression RENAME COLUMN target_celltype TO celltype;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Execution records
|
|
63
|
+
|
|
64
|
+
`MetaData` stores one canonical execution record per successful, non-probe transformation, keyed by `tf_checksum`. Records are **write-once**: subsequent calls to a recorded transformation hit the normal `Transformation` cache and do not re-enter the record path.
|
|
65
|
+
|
|
66
|
+
Two record sizes coexist under the same schema:
|
|
67
|
+
|
|
68
|
+
- **Minimal record (default)** — `schema_version`, `tf_checksum`, `result_checksum`, `seamless_version`, `execution_mode`, `remote_target`, `wall_time_seconds`, `cpu_time_user_seconds`, `cpu_time_system_seconds`, `memory_peak_bytes`, `gpu_memory_peak_bytes`. The hot path pays only timing/memory capture and one write.
|
|
69
|
+
- **Full record (`record: true` in `seamless.profile.yaml`)** — adds environment fingerprints (content-addressed `node`, `environment`, `queue` sub-buffer checksums), compilation context, validation snapshots, contract-violation lists, execution envelope (requested cluster/queue/node, scratch/fingertip flags, resolved `__env__`), and per-job freshness/retry/worker fields.
|
|
70
|
+
|
|
71
|
+
The validator on `PUT metadata` checks identity only — record syntax (integer `schema_version`, body `tf_checksum`/`result_checksum` matching the request, sane `checksum_fields` if present) — not the full payload schema. Identical duplicates are idempotent successes; differing bodies are rejected. Once `IrreproducibleTransformation` rows exist for a `tf_checksum`, `PUT metadata` for that checksum is rejected to avoid silently migrating it back into the normal cache.
|
|
72
|
+
|
|
73
|
+
When a normal entry moves to `IrreproducibleTransformation`, its metadata body travels with it unchanged.
|
|
74
|
+
|
|
75
|
+
### Schema upgrade from legacy
|
|
76
|
+
|
|
77
|
+
The legacy `meta_data` table had two columns (`checksum PRIMARY KEY`, `metadata JSON`). On startup, `seamless-database`:
|
|
78
|
+
|
|
79
|
+
- creates the upgraded table fresh if absent,
|
|
80
|
+
- drops and recreates the legacy table if it is empty,
|
|
81
|
+
- preserves an already-upgraded table on subsequent starts,
|
|
82
|
+
- and **fails loudly** if a non-empty legacy table is present (it must be migrated explicitly).
|
|
83
|
+
|
|
84
|
+
### Endpoints (request types)
|
|
85
|
+
|
|
86
|
+
- `PUT metadata` — atomically creates missing `Transformation` and `RevTransformation` rows alongside the `MetaData` row. Body: `{type: "metadata", checksum: <tf>, result: <result>, value: <record>}`.
|
|
87
|
+
- `GET metadata` — return the canonical record body for a `tf_checksum`.
|
|
88
|
+
- `GET irreproducible` — return all rows for a `tf_checksum`, optionally filtered by `result`. Each row includes `checksum`, `result`, and `metadata`.
|
|
89
|
+
- `PUT irreproducible` — move a normal entry into `IrreproducibleTransformation`, preserving metadata.
|
|
90
|
+
|
|
91
|
+
The remote-client equivalents are `set_execution_record`, `get_execution_record`, and `get_irreproducible_records` in `seamless-remote`.
|
|
92
|
+
|
|
93
|
+
See the [agent contract](https://github.com/sjdv1982/seamless/blob/main/docs/agent/contracts/execution-records.md) for the full behavioral spec.
|
|
45
94
|
|
|
46
95
|
## Role in the Seamless ecosystem
|
|
47
96
|
|
|
@@ -12,13 +12,60 @@ The database stores the following kinds of records:
|
|
|
12
12
|
|-------|---------|
|
|
13
13
|
| **Transformation** | Maps a transformation checksum to its result checksum |
|
|
14
14
|
| **RevTransformation** | Reverse lookup: finds which transformations produced a given result |
|
|
15
|
-
| **
|
|
15
|
+
| **HashType** | Stores the packed checksum classification word used by new HashType validation and conversion checks |
|
|
16
16
|
| **SyntacticToSemantic** | Maps between syntactic and semantic checksums per celltype |
|
|
17
|
-
| **Expression** | Caches expression evaluation results
|
|
18
|
-
| **MetaData** | Stores execution
|
|
19
|
-
| **IrreproducibleTransformation** | Records transformations whose results are not reproducible |
|
|
17
|
+
| **Expression** | Caches expression evaluation results keyed by `(input_checksum, path, input_celltype, celltype)` |
|
|
18
|
+
| **MetaData** | Stores a canonical execution record for each successful, non-probe transformation |
|
|
19
|
+
| **IrreproducibleTransformation** | Records transformations whose results are not reproducible; metadata is preserved on migration |
|
|
20
20
|
|
|
21
|
-
All data is persisted in a single SQLite file (typically `seamless.db`).
|
|
21
|
+
All data is persisted in a single SQLite file (typically `seamless.db`). The current protocol version is **2.3**.
|
|
22
|
+
|
|
23
|
+
## Expression cache schema
|
|
24
|
+
|
|
25
|
+
Expression `input_celltype` describes the input checksum's interpretation;
|
|
26
|
+
`celltype` describes the produced result. Both are part of the composite cache
|
|
27
|
+
key. Database, jobserver, remote client, and Dask payloads use those same names.
|
|
28
|
+
The unreleased old `celltype`/`target_celltype` keys are not accepted aliases.
|
|
29
|
+
Existing development caches need these statements, in order, or a recreated
|
|
30
|
+
expression cache table:
|
|
31
|
+
|
|
32
|
+
```sql
|
|
33
|
+
ALTER TABLE expression RENAME COLUMN celltype TO input_celltype;
|
|
34
|
+
ALTER TABLE expression RENAME COLUMN target_celltype TO celltype;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Execution records
|
|
38
|
+
|
|
39
|
+
`MetaData` stores one canonical execution record per successful, non-probe transformation, keyed by `tf_checksum`. Records are **write-once**: subsequent calls to a recorded transformation hit the normal `Transformation` cache and do not re-enter the record path.
|
|
40
|
+
|
|
41
|
+
Two record sizes coexist under the same schema:
|
|
42
|
+
|
|
43
|
+
- **Minimal record (default)** — `schema_version`, `tf_checksum`, `result_checksum`, `seamless_version`, `execution_mode`, `remote_target`, `wall_time_seconds`, `cpu_time_user_seconds`, `cpu_time_system_seconds`, `memory_peak_bytes`, `gpu_memory_peak_bytes`. The hot path pays only timing/memory capture and one write.
|
|
44
|
+
- **Full record (`record: true` in `seamless.profile.yaml`)** — adds environment fingerprints (content-addressed `node`, `environment`, `queue` sub-buffer checksums), compilation context, validation snapshots, contract-violation lists, execution envelope (requested cluster/queue/node, scratch/fingertip flags, resolved `__env__`), and per-job freshness/retry/worker fields.
|
|
45
|
+
|
|
46
|
+
The validator on `PUT metadata` checks identity only — record syntax (integer `schema_version`, body `tf_checksum`/`result_checksum` matching the request, sane `checksum_fields` if present) — not the full payload schema. Identical duplicates are idempotent successes; differing bodies are rejected. Once `IrreproducibleTransformation` rows exist for a `tf_checksum`, `PUT metadata` for that checksum is rejected to avoid silently migrating it back into the normal cache.
|
|
47
|
+
|
|
48
|
+
When a normal entry moves to `IrreproducibleTransformation`, its metadata body travels with it unchanged.
|
|
49
|
+
|
|
50
|
+
### Schema upgrade from legacy
|
|
51
|
+
|
|
52
|
+
The legacy `meta_data` table had two columns (`checksum PRIMARY KEY`, `metadata JSON`). On startup, `seamless-database`:
|
|
53
|
+
|
|
54
|
+
- creates the upgraded table fresh if absent,
|
|
55
|
+
- drops and recreates the legacy table if it is empty,
|
|
56
|
+
- preserves an already-upgraded table on subsequent starts,
|
|
57
|
+
- and **fails loudly** if a non-empty legacy table is present (it must be migrated explicitly).
|
|
58
|
+
|
|
59
|
+
### Endpoints (request types)
|
|
60
|
+
|
|
61
|
+
- `PUT metadata` — atomically creates missing `Transformation` and `RevTransformation` rows alongside the `MetaData` row. Body: `{type: "metadata", checksum: <tf>, result: <result>, value: <record>}`.
|
|
62
|
+
- `GET metadata` — return the canonical record body for a `tf_checksum`.
|
|
63
|
+
- `GET irreproducible` — return all rows for a `tf_checksum`, optionally filtered by `result`. Each row includes `checksum`, `result`, and `metadata`.
|
|
64
|
+
- `PUT irreproducible` — move a normal entry into `IrreproducibleTransformation`, preserving metadata.
|
|
65
|
+
|
|
66
|
+
The remote-client equivalents are `set_execution_record`, `get_execution_record`, and `get_irreproducible_records` in `seamless-remote`.
|
|
67
|
+
|
|
68
|
+
See the [agent contract](https://github.com/sjdv1982/seamless/blob/main/docs/agent/contracts/execution-records.md) for the full behavioral spec.
|
|
22
69
|
|
|
23
70
|
## Role in the Seamless ecosystem
|
|
24
71
|
|
|
@@ -9,14 +9,16 @@ import socket
|
|
|
9
9
|
import sys
|
|
10
10
|
import time
|
|
11
11
|
from urllib.parse import quote
|
|
12
|
-
from peewee import DoesNotExist
|
|
12
|
+
from peewee import DoesNotExist, IntegrityError
|
|
13
13
|
|
|
14
14
|
from database_models import (
|
|
15
15
|
db_init,
|
|
16
16
|
db_atomic,
|
|
17
17
|
Transformation,
|
|
18
18
|
RevTransformation,
|
|
19
|
-
|
|
19
|
+
CellJoin,
|
|
20
|
+
RevCellJoin,
|
|
21
|
+
HashType,
|
|
20
22
|
SyntacticToSemantic,
|
|
21
23
|
Expression,
|
|
22
24
|
MetaData,
|
|
@@ -54,80 +56,14 @@ def parse_checksum(checksum, as_bytes=False):
|
|
|
54
56
|
raise TypeError(type(checksum))
|
|
55
57
|
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"is_json_numeric_array",
|
|
66
|
-
"is_json_numeric_scalar",
|
|
67
|
-
"is_numpy",
|
|
68
|
-
"dtype",
|
|
69
|
-
"shape",
|
|
70
|
-
"is_seamless_mixed",
|
|
71
|
-
"str2text",
|
|
72
|
-
"text2str",
|
|
73
|
-
"binary2bytes",
|
|
74
|
-
"bytes2binary",
|
|
75
|
-
"binary2json",
|
|
76
|
-
"json2binary",
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
def __init__(self, checksum, params: dict = {}):
|
|
80
|
-
for slot in self.__slots__:
|
|
81
|
-
setattr(self, slot, params.get(slot))
|
|
82
|
-
if isinstance(checksum, str):
|
|
83
|
-
checksum = parse_checksum(checksum)
|
|
84
|
-
self.checksum = checksum
|
|
85
|
-
|
|
86
|
-
def __setattr__(self, attr, value):
|
|
87
|
-
if value is not None:
|
|
88
|
-
if attr == "length":
|
|
89
|
-
if not isinstance(value, int):
|
|
90
|
-
raise TypeError(type(value))
|
|
91
|
-
if not value >= 0:
|
|
92
|
-
raise ValueError
|
|
93
|
-
if attr.startswith("is_"):
|
|
94
|
-
if not isinstance(value, bool):
|
|
95
|
-
raise TypeError(type(value))
|
|
96
|
-
if attr.find("2") > -1 and value is not None:
|
|
97
|
-
if isinstance(value, bytes):
|
|
98
|
-
value = value.hex()
|
|
99
|
-
super().__setattr__(attr, value)
|
|
100
|
-
|
|
101
|
-
def __setitem__(self, item, value):
|
|
102
|
-
return setattr(self, item, value)
|
|
103
|
-
|
|
104
|
-
def __getitem__(self, item):
|
|
105
|
-
return getattr(self, item)
|
|
106
|
-
|
|
107
|
-
def update(self, other):
|
|
108
|
-
if not isinstance(other, SeamlessBufferInfo):
|
|
109
|
-
raise TypeError
|
|
110
|
-
for attr in self.__slots__:
|
|
111
|
-
v = getattr(other, attr)
|
|
112
|
-
if v is not None:
|
|
113
|
-
setattr(self, attr, v)
|
|
114
|
-
|
|
115
|
-
def get(self, attr, default=None):
|
|
116
|
-
value = getattr(self, attr)
|
|
117
|
-
if value is None:
|
|
118
|
-
return default
|
|
119
|
-
else:
|
|
120
|
-
return value
|
|
121
|
-
|
|
122
|
-
def as_dict(self):
|
|
123
|
-
result = {}
|
|
124
|
-
for attr in self.__slots__:
|
|
125
|
-
if attr == "checksum":
|
|
126
|
-
continue
|
|
127
|
-
v = getattr(self, attr)
|
|
128
|
-
if v is not None:
|
|
129
|
-
result[attr] = v
|
|
130
|
-
return result
|
|
59
|
+
def _valid_hash_type_word(value):
|
|
60
|
+
if not isinstance(value, int):
|
|
61
|
+
return False
|
|
62
|
+
try:
|
|
63
|
+
from seamless.checksum.hash_type import HashType as CoreHashType
|
|
64
|
+
except ImportError:
|
|
65
|
+
return False
|
|
66
|
+
return CoreHashType.is_valid_word(value)
|
|
131
67
|
|
|
132
68
|
|
|
133
69
|
def err(*args, **kwargs):
|
|
@@ -153,6 +89,12 @@ def _normalize_metadata_value(value):
|
|
|
153
89
|
raise TypeError(type(value))
|
|
154
90
|
|
|
155
91
|
|
|
92
|
+
def _normalize_expression_path_payload(path):
|
|
93
|
+
if not isinstance(path, str):
|
|
94
|
+
raise TypeError(type(path))
|
|
95
|
+
return json.dumps(path)
|
|
96
|
+
|
|
97
|
+
|
|
156
98
|
def _normalize_freshness_tokens(value):
|
|
157
99
|
if isinstance(value, dict):
|
|
158
100
|
return value
|
|
@@ -223,6 +165,17 @@ def _ensure_rev_transformation_row(checksum: str, result: str) -> None:
|
|
|
223
165
|
RevTransformation.create(checksum=checksum, result=result)
|
|
224
166
|
|
|
225
167
|
|
|
168
|
+
def _ensure_rev_celljoin_row(checksum: str, celltype: str, result: str) -> None:
|
|
169
|
+
query = RevCellJoin.select().where(
|
|
170
|
+
RevCellJoin.checksum == checksum,
|
|
171
|
+
RevCellJoin.celltype == celltype,
|
|
172
|
+
RevCellJoin.result == result,
|
|
173
|
+
)
|
|
174
|
+
if query.exists():
|
|
175
|
+
return
|
|
176
|
+
RevCellJoin.create(checksum=checksum, celltype=celltype, result=result)
|
|
177
|
+
|
|
178
|
+
|
|
226
179
|
def _delete_rev_transformation_rows(checksum: str, result: str) -> None:
|
|
227
180
|
(
|
|
228
181
|
RevTransformation.delete()
|
|
@@ -342,16 +295,18 @@ def build_sqlite_readonly_uri(path: str) -> str:
|
|
|
342
295
|
|
|
343
296
|
types = (
|
|
344
297
|
"protocol",
|
|
345
|
-
"
|
|
298
|
+
"hash_type",
|
|
346
299
|
"syntactic_to_semantic",
|
|
347
300
|
"semantic_to_syntactic",
|
|
348
301
|
"transformation",
|
|
349
302
|
"metadata",
|
|
350
303
|
"bucket_probe",
|
|
351
304
|
"expression",
|
|
305
|
+
"celljoin",
|
|
352
306
|
"irreproducible",
|
|
353
307
|
"rev_expression", # only GET
|
|
354
308
|
"rev_transformations", # only GET
|
|
309
|
+
"rev_celljoins", # only GET
|
|
355
310
|
)
|
|
356
311
|
|
|
357
312
|
|
|
@@ -364,7 +319,7 @@ def format_response(response, *, none_as_404=False):
|
|
|
364
319
|
else:
|
|
365
320
|
status = 404
|
|
366
321
|
response = "ERROR: Unknown key"
|
|
367
|
-
elif isinstance(response, (bool, dict, list)):
|
|
322
|
+
elif isinstance(response, (bool, dict, list, int)):
|
|
368
323
|
response = json.dumps(response)
|
|
369
324
|
elif not isinstance(response, (str, bytes)):
|
|
370
325
|
status = 400
|
|
@@ -377,7 +332,7 @@ def format_response(response, *, none_as_404=False):
|
|
|
377
332
|
|
|
378
333
|
class DatabaseServer:
|
|
379
334
|
future = None
|
|
380
|
-
PROTOCOL = ("seamless", "database", "2.
|
|
335
|
+
PROTOCOL = ("seamless", "database", "2.4")
|
|
381
336
|
|
|
382
337
|
def __init__(
|
|
383
338
|
self,
|
|
@@ -582,11 +537,11 @@ class DatabaseServer:
|
|
|
582
537
|
pass
|
|
583
538
|
|
|
584
539
|
async def _get(self, type_, checksum, request):
|
|
585
|
-
if type_ == "
|
|
540
|
+
if type_ == "hash_type":
|
|
586
541
|
try:
|
|
587
|
-
return
|
|
542
|
+
return HashType[checksum].hash_type
|
|
588
543
|
except DoesNotExist:
|
|
589
|
-
|
|
544
|
+
return None
|
|
590
545
|
|
|
591
546
|
elif type_ == "semantic_to_syntactic":
|
|
592
547
|
try:
|
|
@@ -686,18 +641,18 @@ class DatabaseServer:
|
|
|
686
641
|
|
|
687
642
|
elif type_ == "expression":
|
|
688
643
|
try:
|
|
644
|
+
input_celltype = request["input_celltype"]
|
|
645
|
+
path = _normalize_expression_path_payload(request["path"])
|
|
689
646
|
celltype = request["celltype"]
|
|
690
|
-
|
|
691
|
-
target_celltype = request["target_celltype"]
|
|
692
|
-
except KeyError:
|
|
647
|
+
except (KeyError, TypeError):
|
|
693
648
|
raise DatabaseError("Malformed expression request")
|
|
694
649
|
result = (
|
|
695
650
|
Expression.select()
|
|
696
651
|
.where(
|
|
697
652
|
Expression.input_checksum == checksum,
|
|
698
653
|
Expression.path == path,
|
|
654
|
+
Expression.input_celltype == input_celltype,
|
|
699
655
|
Expression.celltype == celltype,
|
|
700
|
-
Expression.target_celltype == target_celltype,
|
|
701
656
|
)
|
|
702
657
|
.execute()
|
|
703
658
|
)
|
|
@@ -705,6 +660,20 @@ class DatabaseServer:
|
|
|
705
660
|
return None
|
|
706
661
|
return parse_checksum(result[0].result)
|
|
707
662
|
|
|
663
|
+
elif type_ == "celljoin":
|
|
664
|
+
try:
|
|
665
|
+
celltype = request["celltype"]
|
|
666
|
+
except (KeyError, TypeError):
|
|
667
|
+
raise DatabaseError("Malformed celljoin request") from None
|
|
668
|
+
if celltype not in ("mixed", "plain", "deepcell", "deepfolder"):
|
|
669
|
+
raise DatabaseError("Malformed celljoin request")
|
|
670
|
+
row = CellJoin.get_or_none(
|
|
671
|
+
(CellJoin.checksum == checksum) & (CellJoin.celltype == celltype)
|
|
672
|
+
)
|
|
673
|
+
if row is None:
|
|
674
|
+
return None
|
|
675
|
+
return parse_checksum(row.result)
|
|
676
|
+
|
|
708
677
|
elif type_ == "rev_expression":
|
|
709
678
|
expressions = (
|
|
710
679
|
Expression.select()
|
|
@@ -720,8 +689,8 @@ class DatabaseServer:
|
|
|
720
689
|
expr = {
|
|
721
690
|
"checksum": expression.input_checksum,
|
|
722
691
|
"path": json.loads(expression.path),
|
|
692
|
+
"input_celltype": expression.input_celltype,
|
|
723
693
|
"celltype": expression.celltype,
|
|
724
|
-
"target_celltype": expression.target_celltype,
|
|
725
694
|
"result": checksum,
|
|
726
695
|
}
|
|
727
696
|
result.append(expr)
|
|
@@ -740,27 +709,35 @@ class DatabaseServer:
|
|
|
740
709
|
result = [transformation.checksum for transformation in transformations]
|
|
741
710
|
return result
|
|
742
711
|
|
|
712
|
+
elif type_ == "rev_celljoins":
|
|
713
|
+
rows = (
|
|
714
|
+
RevCellJoin.select()
|
|
715
|
+
.where(RevCellJoin.result == checksum)
|
|
716
|
+
.execute()
|
|
717
|
+
)
|
|
718
|
+
if not rows:
|
|
719
|
+
return None
|
|
720
|
+
return [
|
|
721
|
+
{"checksum": row.checksum, "celltype": row.celltype}
|
|
722
|
+
for row in rows
|
|
723
|
+
]
|
|
724
|
+
|
|
743
725
|
else:
|
|
744
726
|
raise DatabaseError("Unknown request type")
|
|
745
727
|
|
|
746
728
|
async def _put(self, type_, checksum, request):
|
|
747
729
|
|
|
748
|
-
if type_ == "
|
|
730
|
+
if type_ == "hash_type":
|
|
749
731
|
try:
|
|
750
732
|
value = request["value"]
|
|
751
|
-
if not
|
|
733
|
+
if not _valid_hash_type_word(value):
|
|
752
734
|
raise TypeError
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
pass
|
|
760
|
-
value = json.dumps(value, sort_keys=True, indent=2)
|
|
761
|
-
except Exception:
|
|
762
|
-
raise DatabaseError("Malformed PUT buffer info request") from None
|
|
763
|
-
BufferInfo.create(checksum=checksum, buffer_info=value)
|
|
735
|
+
except (KeyError, TypeError):
|
|
736
|
+
raise DatabaseError("Malformed PUT hash_type request") from None
|
|
737
|
+
try:
|
|
738
|
+
HashType.create(checksum=checksum, hash_type=value)
|
|
739
|
+
except IntegrityError:
|
|
740
|
+
return _conflict_response("HashType already exists with different value")
|
|
764
741
|
|
|
765
742
|
elif type_ == "semantic_to_syntactic":
|
|
766
743
|
try:
|
|
@@ -771,9 +748,7 @@ class DatabaseServer:
|
|
|
771
748
|
try:
|
|
772
749
|
celltype, subcelltype = request["celltype"], request["subcelltype"]
|
|
773
750
|
except KeyError:
|
|
774
|
-
raise DatabaseError(
|
|
775
|
-
"Malformed PUT semantic-to-syntactic request"
|
|
776
|
-
) from None
|
|
751
|
+
raise DatabaseError("Malformed PUT semantic-to-syntactic request") from None
|
|
777
752
|
for syntactic_checksum0 in value:
|
|
778
753
|
syntactic_checksum = parse_checksum(syntactic_checksum0, as_bytes=False)
|
|
779
754
|
with db_atomic():
|
|
@@ -791,35 +766,65 @@ class DatabaseServer:
|
|
|
791
766
|
raise DatabaseError(
|
|
792
767
|
"Malformed PUT transformation result request: value must be a checksum"
|
|
793
768
|
) from None
|
|
794
|
-
|
|
795
|
-
|
|
769
|
+
with db_atomic():
|
|
770
|
+
row = _get_transformation_row(checksum)
|
|
771
|
+
if row is not None and row.result != value:
|
|
772
|
+
return _conflict_response(
|
|
773
|
+
"Transformation already exists with different result"
|
|
774
|
+
)
|
|
775
|
+
if row is None:
|
|
776
|
+
Transformation.create(checksum=checksum, result=value)
|
|
777
|
+
_ensure_rev_transformation_row(checksum, value)
|
|
778
|
+
|
|
779
|
+
elif type_ == "celljoin":
|
|
780
|
+
try:
|
|
781
|
+
value = parse_checksum(request["value"], as_bytes=False)
|
|
782
|
+
celltype = request["celltype"]
|
|
783
|
+
except (KeyError, TypeError, ValueError):
|
|
784
|
+
raise DatabaseError(
|
|
785
|
+
"Malformed PUT celljoin result request"
|
|
786
|
+
) from None
|
|
787
|
+
if celltype not in ("mixed", "plain", "deepcell", "deepfolder"):
|
|
788
|
+
raise DatabaseError("Malformed PUT celljoin result request")
|
|
789
|
+
with db_atomic():
|
|
790
|
+
row = CellJoin.get_or_none(
|
|
791
|
+
(CellJoin.checksum == checksum)
|
|
792
|
+
& (CellJoin.celltype == celltype)
|
|
793
|
+
)
|
|
794
|
+
if row is not None and row.result != value:
|
|
795
|
+
return _conflict_response(
|
|
796
|
+
"CellJoin already exists with different result"
|
|
797
|
+
)
|
|
798
|
+
if row is None:
|
|
799
|
+
CellJoin.create(
|
|
800
|
+
checksum=checksum, celltype=celltype, result=value
|
|
801
|
+
)
|
|
802
|
+
_ensure_rev_celljoin_row(checksum, celltype, value)
|
|
796
803
|
|
|
797
804
|
elif type_ == "expression":
|
|
798
805
|
try:
|
|
799
806
|
value = parse_checksum(request["value"], as_bytes=False)
|
|
807
|
+
input_celltype = request["input_celltype"]
|
|
808
|
+
path = _normalize_expression_path_payload(request["path"])
|
|
800
809
|
celltype = request["celltype"]
|
|
801
|
-
|
|
802
|
-
target_celltype = request["target_celltype"]
|
|
803
|
-
except KeyError:
|
|
810
|
+
except (KeyError, TypeError):
|
|
804
811
|
raise DatabaseError("Malformed expression request")
|
|
805
812
|
try:
|
|
806
|
-
# assert
|
|
807
|
-
assert len(
|
|
808
|
-
if len(request["path"]):
|
|
809
|
-
assert celltype in ("mixed", "plain", "binary")
|
|
813
|
+
# assert input_celltype in celltypes TODO? also for celltype
|
|
814
|
+
assert len(input_celltype) <= 20
|
|
810
815
|
assert len(celltype) <= 20
|
|
811
|
-
assert len(target_celltype) <= 20
|
|
812
816
|
except AssertionError:
|
|
813
|
-
raise DatabaseError(
|
|
814
|
-
|
|
817
|
+
raise DatabaseError("Malformed expression request (constraint violation)")
|
|
818
|
+
try:
|
|
819
|
+
Expression.create(
|
|
820
|
+
input_checksum=checksum,
|
|
821
|
+
path=path,
|
|
822
|
+
input_celltype=input_celltype,
|
|
823
|
+
celltype=celltype,
|
|
824
|
+
result=value,
|
|
815
825
|
)
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
path=path,
|
|
819
|
-
celltype=celltype,
|
|
820
|
-
target_celltype=target_celltype,
|
|
821
|
-
result=value,
|
|
822
|
-
)
|
|
826
|
+
except IntegrityError:
|
|
827
|
+
return _conflict_response("Expression already exists with different result")
|
|
823
828
|
|
|
824
829
|
elif type_ == "metadata":
|
|
825
830
|
try:
|
|
@@ -867,12 +872,8 @@ class DatabaseServer:
|
|
|
867
872
|
try:
|
|
868
873
|
bucket_kind = request["bucket_kind"]
|
|
869
874
|
label = request["label"]
|
|
870
|
-
bucket_checksum = parse_checksum(
|
|
871
|
-
|
|
872
|
-
)
|
|
873
|
-
freshness_tokens = _normalize_freshness_tokens(
|
|
874
|
-
request["freshness_tokens"]
|
|
875
|
-
)
|
|
875
|
+
bucket_checksum = parse_checksum(request["bucket_checksum"], as_bytes=False)
|
|
876
|
+
freshness_tokens = _normalize_freshness_tokens(request["freshness_tokens"])
|
|
876
877
|
captured_at = request["captured_at"]
|
|
877
878
|
_validate_bucket_probe_request(
|
|
878
879
|
bucket_kind,
|
|
@@ -908,8 +909,24 @@ class DatabaseServer:
|
|
|
908
909
|
result = parse_checksum(request["result"], as_bytes=False)
|
|
909
910
|
except (KeyError, ValueError):
|
|
910
911
|
raise DatabaseError("Malformed 'irreproducible' request") from None
|
|
912
|
+
mode = request.get("mode", "manual")
|
|
913
|
+
if mode not in ("manual", "automatic"):
|
|
914
|
+
raise DatabaseError("Malformed irreproducible mode")
|
|
911
915
|
with db_atomic():
|
|
912
916
|
tf_row = _get_transformation_row(checksum)
|
|
917
|
+
if mode == "automatic":
|
|
918
|
+
if tf_row is None:
|
|
919
|
+
return web.Response(status=404, text="Transformation not found")
|
|
920
|
+
if parse_checksum(tf_row.result, as_bytes=False) == result:
|
|
921
|
+
return _conflict_response("Reported result is the recorded result")
|
|
922
|
+
if not IrreproducibleTransformation.select().where(
|
|
923
|
+
IrreproducibleTransformation.checksum == checksum,
|
|
924
|
+
IrreproducibleTransformation.result == result,
|
|
925
|
+
).exists():
|
|
926
|
+
IrreproducibleTransformation.create(
|
|
927
|
+
checksum=checksum, result=result, metadata=""
|
|
928
|
+
)
|
|
929
|
+
return "OK"
|
|
913
930
|
if tf_row is not None:
|
|
914
931
|
tf_result = parse_checksum(tf_row.result, as_bytes=False)
|
|
915
932
|
if tf_result != result:
|
|
@@ -1064,4 +1081,3 @@ If it doesn't exist, a new file is created.""",
|
|
|
1064
1081
|
BUCKET_KINDS = frozenset(
|
|
1065
1082
|
("node", "environment", "node_env", "queue", "queue_node")
|
|
1066
1083
|
)
|
|
1067
|
-
|