orfdata 0.7.0__tar.gz → 0.8.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.
- {orfdata-0.7.0 → orfdata-0.8.0}/MANIFEST.in +1 -0
- {orfdata-0.7.0/orfdata.egg-info → orfdata-0.8.0}/PKG-INFO +1 -1
- {orfdata-0.7.0 → orfdata-0.8.0}/docs/measurements.md +2 -1
- orfdata-0.8.0/docs/storage.md +45 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/dataset.py +121 -54
- orfdata-0.8.0/orfdata/version.py +1 -0
- {orfdata-0.7.0 → orfdata-0.8.0/orfdata.egg-info}/PKG-INFO +1 -1
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata.egg-info/SOURCES.txt +11 -1
- orfdata-0.8.0/tests/data/storage/README.md +15 -0
- orfdata-0.8.0/tests/data/storage/generate.py +74 -0
- orfdata-0.8.0/tests/data/storage/v1.json +582 -0
- orfdata-0.8.0/tests/data/storage/v1.sqlite +0 -0
- orfdata-0.8.0/tests/data/storage/v2.json +584 -0
- orfdata-0.8.0/tests/data/storage/v2.sqlite +0 -0
- orfdata-0.8.0/tests/data/storage/v3.json +613 -0
- orfdata-0.8.0/tests/data/storage/v3.sqlite +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/test_pairing.py +4 -8
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/test_reference_mapping.py +2 -6
- orfdata-0.8.0/tests/test_storage.py +76 -0
- orfdata-0.7.0/orfdata/version.py +0 -1
- {orfdata-0.7.0 → orfdata-0.8.0}/LICENSE +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/README.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/deploy.sh +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/docs/copy-yeast.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/docs/design.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/docs/pairing.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/docs/pfizer-protein.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/docs/reference.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/docs/source-references.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/docs/species.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/lint.sh +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/__init__.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/build.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/catalog.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/cli.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/data/datasets.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/data/orf-evidence/AUTHOR-LICENSE.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/data/orf-evidence/NOTICE.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/data/orf-evidence/catalog.json.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/data/orf-evidence/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/data/uorf-evidence/AUTHOR-LICENSE.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/data/uorf-evidence/NOTICE.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/data/uorf-evidence/catalog.json.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/data/uorf-evidence/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/dataset_cli.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/datasets.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/initiation_context.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/measurements.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/model.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/pairing.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/reference.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/reference_mapping.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/sequence.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/sources/__init__.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/sources/copy.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata/sources/pfizer.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata.egg-info/dependency_links.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata.egg-info/entry_points.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata.egg-info/requires.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/orfdata.egg-info/top_level.txt +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/pyproject.toml +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/setup.cfg +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/test.sh +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/conftest.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/GSM1905474.soft +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/GSM1905474_KL9_fpkm.txt.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/GSM1905476.soft +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/GSM1905476_KL10_fpkm.txt.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/GSM1905478.soft +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/GSM1905478_KL11_fpkm.txt.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/GSM1905479.soft +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/GSM1905479_KL12_fpkm.txt.gz +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/NOTICE.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/expected.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/geo-series.soft +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/methods.pdf +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/paper.xml +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/pmc-metadata.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/qconcat.fasta +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/copy/workbook.xlsx +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/NOTICE.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/dog/expected.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/dog/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/dog/protein.tsv +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/dog/search.fasta +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/human/expected.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/human/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/human/protein.tsv +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/human/search.fasta +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/mouse/expected.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/mouse/manifest.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/mouse/protein.tsv +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/pfizer/mouse/search.fasta +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/references/README.md +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/references/canis_lupus_familiaris.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/references/context-keys-v0.4.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/references/homo_sapiens.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/references/mus_musculus.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/data/references/saccharomyces_cerevisiae.json +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/test_copy.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/test_cross_reference_pairing.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/test_initiation_context.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/test_measurements.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/test_pfizer.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/test_reference_context.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/test_reference_orf.py +0 -0
- {orfdata-0.7.0 → orfdata-0.8.0}/tests/test_uorf_catalog.py +0 -0
|
@@ -145,4 +145,5 @@ arbitrary other taxa use the same schema. The core API needs neither pandas nor
|
|
|
145
145
|
PyArrow. Optional `orfdata[columnar]` enables `dataset.export_parquet(path)`;
|
|
146
146
|
query columns accompany full measurement JSON, preserving exact values and
|
|
147
147
|
provenance even when a floating-point analysis column cannot represent an
|
|
148
|
-
integer exactly.
|
|
148
|
+
integer exactly. [Compact storage and compatibility](storage.md) describes
|
|
149
|
+
new SQLite v4 writes, legacy readers and actual-source size/query benchmarks.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Quantitative storage
|
|
2
|
+
|
|
3
|
+
ORFdata 0.8 writes compact SQLite storage version 4. `Measurement`,
|
|
4
|
+
`QuantitativeDataset`, the registry and CLI keep the same public records and
|
|
5
|
+
query semantics. Human, mouse, dog, yeast and arbitrary taxa retain separate
|
|
6
|
+
reference-scoped identities.
|
|
7
|
+
|
|
8
|
+
References, targets and assays are stored once and addressed by internal integer
|
|
9
|
+
keys. Measurements retain their values and metadata; repeated required fields
|
|
10
|
+
are reconstructed from indexed columns and the dataset manifest. Fields equal
|
|
11
|
+
to the fixed version-4 defaults are omitted from stored JSON and restored on read.
|
|
12
|
+
Zeros, nulls, censoring, uncertainty, nested provenance and derived inputs retain
|
|
13
|
+
their meaning. Foreign keys prevent cross-reference target/assay assignments.
|
|
14
|
+
Internal integer keys never appear as public biological identifiers.
|
|
15
|
+
|
|
16
|
+
Published storage versions 1, 2 and 3 remain readable. There is no automatic
|
|
17
|
+
migration or replacement of downloaded artifacts. A new source rebuild can use
|
|
18
|
+
a different storage layout and therefore a different file checksum while
|
|
19
|
+
returning the same records. Registry downloads always verify the exact published
|
|
20
|
+
artifact's hash; source builds retain their own DataCache receipts.
|
|
21
|
+
|
|
22
|
+
The implementation streams observations and bounds its target cache to 131,072
|
|
23
|
+
entries. SQLite index statistics are populated during creation so a narrow
|
|
24
|
+
target query can use the target index instead of scanning an entire reference.
|
|
25
|
+
Raw SQL table layouts are internal; use the Python/CLI query and export APIs.
|
|
26
|
+
|
|
27
|
+
## Actual-source benchmark
|
|
28
|
+
|
|
29
|
+
The first 100,000 rows of the native Pfizer/GEO RNA table contain four dog
|
|
30
|
+
libraries and 30,941 genes, including measured zeros. Source gzip SHA-256:
|
|
31
|
+
`a22780f92ffd0a028e30fe3392d73d01f787eb5a62b2043e8d26940191cbac70`.
|
|
32
|
+
This is a storage benchmark slice, not a newly released experimental dataset.
|
|
33
|
+
|
|
34
|
+
| Measurement | Storage v3 | Storage v4 |
|
|
35
|
+
|---|---:|---:|
|
|
36
|
+
| Installed size | 121.6 MB | 38.6 MB |
|
|
37
|
+
| Gzip size | 13.4 MB | 7.5 MB |
|
|
38
|
+
| Build time | 8.90 s | 4.28 s |
|
|
39
|
+
| One-gene query (four results) | 135 ms | 1.5 ms |
|
|
40
|
+
| Peak build RSS | 39.8 MB | 52.1 MB |
|
|
41
|
+
|
|
42
|
+
All 100,000 reconstructed observations were compared field for field. Timings
|
|
43
|
+
are local measurements on Python 3.12, not cross-machine guarantees; the bounded
|
|
44
|
+
target cache trades some memory for repeated-target lookup speed. Legacy tests
|
|
45
|
+
also use actual databases written by ORFdata 0.3.0, 0.4.0 and 0.5.0.
|
|
@@ -5,6 +5,8 @@ import json
|
|
|
5
5
|
import os
|
|
6
6
|
import sqlite3
|
|
7
7
|
from contextlib import closing
|
|
8
|
+
from dataclasses import MISSING, fields
|
|
9
|
+
from functools import lru_cache
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
|
|
10
12
|
from .measurements import (
|
|
@@ -16,6 +18,7 @@ from .measurements import (
|
|
|
16
18
|
MeasurementReference,
|
|
17
19
|
Sample,
|
|
18
20
|
TargetMapping,
|
|
21
|
+
_plain,
|
|
19
22
|
)
|
|
20
23
|
from .pairing import (
|
|
21
24
|
PAIRING_BASES,
|
|
@@ -30,38 +33,50 @@ from .reference_mapping import MappedMeasurement, ReferenceMapping
|
|
|
30
33
|
|
|
31
34
|
_SCHEMA = """
|
|
32
35
|
CREATE TABLE metadata (name TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
33
|
-
CREATE TABLE reference (
|
|
36
|
+
CREATE TABLE reference (i INTEGER PRIMARY KEY, key TEXT UNIQUE NOT NULL,
|
|
37
|
+
record TEXT NOT NULL, UNIQUE(key, i));
|
|
34
38
|
CREATE TABLE target (
|
|
35
|
-
|
|
39
|
+
i INTEGER PRIMARY KEY, reference_i INTEGER NOT NULL,
|
|
40
|
+
reference_key TEXT NOT NULL,
|
|
36
41
|
key TEXT NOT NULL, kind TEXT NOT NULL, namespace TEXT NOT NULL,
|
|
37
|
-
identifier TEXT NOT NULL,
|
|
42
|
+
identifier TEXT NOT NULL, UNIQUE(reference_key, key), UNIQUE(reference_i, i),
|
|
43
|
+
FOREIGN KEY(reference_key, reference_i) REFERENCES reference(key, i));
|
|
38
44
|
CREATE TABLE sample (
|
|
39
45
|
reference_key TEXT NOT NULL REFERENCES reference(key),
|
|
40
46
|
id TEXT NOT NULL, record TEXT NOT NULL, PRIMARY KEY(reference_key, id));
|
|
41
47
|
CREATE TABLE assay (
|
|
48
|
+
i INTEGER PRIMARY KEY, reference_i INTEGER NOT NULL,
|
|
42
49
|
reference_key TEXT NOT NULL, id TEXT NOT NULL, sample_id TEXT NOT NULL,
|
|
43
|
-
record TEXT NOT NULL,
|
|
50
|
+
record TEXT NOT NULL, UNIQUE(reference_key, id), UNIQUE(reference_i, i),
|
|
44
51
|
UNIQUE(reference_key, id, sample_id),
|
|
52
|
+
FOREIGN KEY(reference_key, reference_i) REFERENCES reference(key, i),
|
|
45
53
|
FOREIGN KEY(reference_key, sample_id) REFERENCES sample(reference_key, id));
|
|
46
54
|
CREATE TABLE mapping (
|
|
47
55
|
reference_key TEXT NOT NULL REFERENCES reference(key),
|
|
48
56
|
id TEXT NOT NULL, record TEXT NOT NULL, PRIMARY KEY(reference_key, id));
|
|
49
|
-
CREATE TABLE
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
FOREIGN KEY(
|
|
55
|
-
|
|
56
|
-
CREATE TABLE
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
CREATE TABLE observation (
|
|
58
|
+
i INTEGER PRIMARY KEY, reference_i INTEGER NOT NULL,
|
|
59
|
+
id TEXT NOT NULL, target_i INTEGER NOT NULL, assay_i INTEGER NOT NULL,
|
|
60
|
+
quantity TEXT NOT NULL, status TEXT NOT NULL, record TEXT NOT NULL,
|
|
61
|
+
UNIQUE(reference_i, id),
|
|
62
|
+
FOREIGN KEY(reference_i, target_i) REFERENCES target(reference_i, i),
|
|
63
|
+
FOREIGN KEY(reference_i, assay_i) REFERENCES assay(reference_i, i));
|
|
64
|
+
CREATE TABLE observation_input (
|
|
65
|
+
output_i INTEGER NOT NULL REFERENCES observation(i),
|
|
66
|
+
input_i INTEGER NOT NULL REFERENCES observation(i), PRIMARY KEY(output_i, input_i));
|
|
67
|
+
CREATE VIEW measurement AS
|
|
68
|
+
SELECT r.key reference_key, m.id, t.key target_key, a.sample_id,
|
|
69
|
+
a.id assay_id, m.quantity, m.status, m.record
|
|
70
|
+
FROM observation m JOIN reference r ON r.i=m.reference_i
|
|
71
|
+
JOIN target t ON t.i=m.target_i JOIN assay a ON a.i=m.assay_i;
|
|
72
|
+
CREATE VIEW input_measurement AS
|
|
73
|
+
SELECT r.key reference_key, o.id output_id, s.key input_reference_key, i.id input_id
|
|
74
|
+
FROM observation_input p JOIN observation o ON o.i=p.output_i
|
|
75
|
+
JOIN observation i ON i.i=p.input_i JOIN reference r ON r.i=o.reference_i
|
|
76
|
+
JOIN reference s ON s.i=i.reference_i;
|
|
62
77
|
CREATE INDEX target_lookup ON target(reference_key, namespace, kind, identifier);
|
|
63
|
-
CREATE INDEX observation_lookup ON
|
|
64
|
-
CREATE INDEX assay_lookup ON
|
|
78
|
+
CREATE INDEX observation_lookup ON observation(reference_i, target_i, assay_i);
|
|
79
|
+
CREATE INDEX assay_lookup ON observation(reference_i, assay_i, quantity, status);
|
|
65
80
|
CREATE TABLE assay_pair (
|
|
66
81
|
reference_key TEXT NOT NULL REFERENCES reference(key), id TEXT NOT NULL,
|
|
67
82
|
right_reference_key TEXT NOT NULL REFERENCES reference(key),
|
|
@@ -100,6 +115,29 @@ def _target_key(target):
|
|
|
100
115
|
return hashlib.sha256(_json(target.key).encode()).hexdigest()
|
|
101
116
|
|
|
102
117
|
|
|
118
|
+
_STORED_FIELDS = tuple(f for f in fields(Measurement) if f.name not in {
|
|
119
|
+
"measurement_id", "target", "sample_id", "assay_id", "quantity", "status",
|
|
120
|
+
"dataset_id", "dataset_version"})
|
|
121
|
+
_V4_DEFAULTS = {
|
|
122
|
+
"origin": "observed", "scale": "linear", "log_base": None,
|
|
123
|
+
"uncertainty": None, "uncertainty_type": None, "detection_limit": None,
|
|
124
|
+
"input_measurements": (), "input_references": (), "derivation": {}, "provenance": {},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _measurement_record(observation):
|
|
129
|
+
# Schema defaults are fixed here, independent of future model defaults.
|
|
130
|
+
# Do not serialize the target
|
|
131
|
+
# and then discard it: its full reference is already stored once.
|
|
132
|
+
result = {}
|
|
133
|
+
for field in _STORED_FIELDS:
|
|
134
|
+
value = getattr(observation, field.name)
|
|
135
|
+
default = _V4_DEFAULTS.get(field.name, MISSING)
|
|
136
|
+
if default is MISSING or value != default:
|
|
137
|
+
result[field.name] = _plain(value)
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
|
|
103
141
|
class QuantitativeDataset:
|
|
104
142
|
"""A read-only release. Constructing or querying it never downloads data.
|
|
105
143
|
|
|
@@ -112,7 +150,7 @@ class QuantitativeDataset:
|
|
|
112
150
|
self.path = Path(path).resolve()
|
|
113
151
|
with closing(self._connect()) as connection:
|
|
114
152
|
self._storage_version = connection.execute("PRAGMA user_version").fetchone()[0]
|
|
115
|
-
if self._storage_version not in (1, 2, 3):
|
|
153
|
+
if self._storage_version not in (1, 2, 3, 4):
|
|
116
154
|
raise ValueError("Unsupported quantitative database schema")
|
|
117
155
|
row = connection.execute("SELECT value FROM metadata WHERE name='manifest'").fetchone()
|
|
118
156
|
if row is None:
|
|
@@ -146,25 +184,46 @@ class QuantitativeDataset:
|
|
|
146
184
|
connection = sqlite3.connect(path)
|
|
147
185
|
connection.execute("PRAGMA foreign_keys=ON")
|
|
148
186
|
connection.executescript(_SCHEMA)
|
|
149
|
-
connection.execute("PRAGMA user_version=
|
|
187
|
+
connection.execute("PRAGMA user_version=4")
|
|
150
188
|
connection.execute("INSERT INTO metadata VALUES ('manifest', ?)",
|
|
151
189
|
(_json(manifest.to_dict()),))
|
|
190
|
+
reference_ids = {}
|
|
152
191
|
for reference in manifest.references:
|
|
153
|
-
connection.execute("INSERT INTO reference VALUES (?, ?)",
|
|
154
|
-
|
|
192
|
+
cursor = connection.execute("INSERT INTO reference (key, record) VALUES (?, ?)",
|
|
193
|
+
(reference.key, _json(reference.to_dict())))
|
|
194
|
+
reference_ids[reference.key] = cursor.lastrowid
|
|
195
|
+
|
|
196
|
+
@lru_cache(maxsize=131072)
|
|
197
|
+
def insert_target(target):
|
|
198
|
+
reference_key = target.reference.key
|
|
199
|
+
if reference_key not in reference_ids:
|
|
200
|
+
raise sqlite3.IntegrityError("Target reference absent from dataset")
|
|
201
|
+
reference_i = reference_ids[reference_key]
|
|
202
|
+
key = _target_key(target)
|
|
203
|
+
connection.execute("INSERT OR IGNORE INTO target "
|
|
204
|
+
"(reference_i, reference_key, key, kind, namespace, identifier) "
|
|
205
|
+
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
206
|
+
(reference_i, reference_key, key, target.kind, target.namespace, target.identifier))
|
|
207
|
+
target_i = connection.execute("SELECT i FROM target WHERE reference_key=? AND key=?",
|
|
208
|
+
(reference_key, key)).fetchone()[0]
|
|
209
|
+
return reference_i, target_i, key
|
|
155
210
|
for sample in samples:
|
|
156
211
|
if not isinstance(sample, Sample):
|
|
157
212
|
raise TypeError("samples must contain Samples")
|
|
158
213
|
connection.execute("INSERT INTO sample VALUES (?, ?, ?)",
|
|
159
214
|
(sample.reference.key, sample.sample_id, _json(sample.to_dict())))
|
|
215
|
+
assay_ids = {}
|
|
160
216
|
for assay in assays:
|
|
161
217
|
if not isinstance(assay, Assay):
|
|
162
218
|
raise TypeError("assays must contain Assays")
|
|
163
219
|
if assay.kind not in manifest.assay_kinds:
|
|
164
220
|
raise ValueError("Assay kind absent from manifest coverage")
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
221
|
+
reference_i = reference_ids.get(assay.reference.key)
|
|
222
|
+
cursor = connection.execute("INSERT INTO assay "
|
|
223
|
+
"(reference_i, reference_key, id, sample_id, record) VALUES (?, ?, ?, ?, ?)",
|
|
224
|
+
(reference_i, assay.reference.key, assay.assay_id, assay.sample_id,
|
|
225
|
+
_json(assay.to_dict())))
|
|
226
|
+
assay_ids[(reference_i, assay.assay_id, assay.sample_id)] = cursor.lastrowid
|
|
168
227
|
for pair in pairings:
|
|
169
228
|
if not isinstance(pair, AssayPair):
|
|
170
229
|
raise TypeError("pairings must contain AssayPairs")
|
|
@@ -194,9 +253,7 @@ class QuantitativeDataset:
|
|
|
194
253
|
if (relation.dataset_id, relation.dataset_version) != (manifest.dataset_id, manifest.version):
|
|
195
254
|
raise ValueError("Target relation belongs to a different dataset release")
|
|
196
255
|
for target in (relation.left, relation.right):
|
|
197
|
-
|
|
198
|
-
(target.reference.key, _target_key(target), target.kind,
|
|
199
|
-
target.namespace, target.identifier))
|
|
256
|
+
insert_target(target)
|
|
200
257
|
for index, (first, second) in enumerate(((relation.left, relation.right), (relation.right, relation.left))):
|
|
201
258
|
_check_one_to_one(relation_links[index],
|
|
202
259
|
(relation.relation, first.key, second.reference.key, second.kind, second.namespace), second.key,
|
|
@@ -218,9 +275,7 @@ class QuantitativeDataset:
|
|
|
218
275
|
if (mapping.dataset_id, mapping.dataset_version) != (manifest.dataset_id, manifest.version):
|
|
219
276
|
raise ValueError("Reference mapping belongs to a different dataset release")
|
|
220
277
|
for target in (mapping.source, *mapping.targets):
|
|
221
|
-
|
|
222
|
-
(target.reference.key, _target_key(target), target.kind,
|
|
223
|
-
target.namespace, target.identifier))
|
|
278
|
+
insert_target(target)
|
|
224
279
|
connection.execute("INSERT INTO reference_mapping VALUES (?, ?, ?, ?, ?)",
|
|
225
280
|
(mapping.source.reference.key, mapping.mapping_id, _target_key(mapping.source),
|
|
226
281
|
mapping.target_reference.key, _json(mapping.to_dict())))
|
|
@@ -238,33 +293,39 @@ class QuantitativeDataset:
|
|
|
238
293
|
raise ValueError("Measurement provenance names an unknown source asset")
|
|
239
294
|
if source is None and not (observation.input_measurements or observation.input_references):
|
|
240
295
|
raise ValueError("Measurement requires a source_asset or input measurements")
|
|
241
|
-
|
|
242
|
-
inputs = [(
|
|
243
|
-
inputs.extend((value.reference.key, value.measurement_id)
|
|
296
|
+
reference_i, target_i, _ = insert_target(observation.target)
|
|
297
|
+
inputs = [(reference_i, identifier) for identifier in observation.input_measurements]
|
|
298
|
+
inputs.extend((reference_ids.get(value.reference.key), value.measurement_id)
|
|
299
|
+
for value in observation.input_references)
|
|
300
|
+
input_ids = []
|
|
244
301
|
for input_reference, identifier in inputs:
|
|
245
|
-
|
|
246
|
-
"SELECT
|
|
302
|
+
found = connection.execute(
|
|
303
|
+
"SELECT i FROM observation WHERE reference_i=? AND id=?",
|
|
247
304
|
(input_reference, identifier),
|
|
248
|
-
).fetchone()
|
|
305
|
+
).fetchone()
|
|
306
|
+
if found is None:
|
|
249
307
|
raise ValueError("Derived inputs must precede outputs in their declared reference contexts")
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
308
|
+
input_ids.append(found[0])
|
|
309
|
+
assay_i = assay_ids.get((reference_i, observation.assay_id, observation.sample_id))
|
|
310
|
+
if assay_i is None:
|
|
311
|
+
raise sqlite3.IntegrityError("Observation assay/sample absent from its reference context")
|
|
312
|
+
cursor = connection.execute("INSERT INTO observation "
|
|
313
|
+
"(reference_i, id, target_i, assay_i, quantity, status, record) "
|
|
314
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
315
|
+
(reference_i, observation.measurement_id, target_i, assay_i,
|
|
316
|
+
observation.quantity, observation.status,
|
|
317
|
+
_json(_measurement_record(observation))))
|
|
318
|
+
connection.executemany("INSERT INTO observation_input VALUES (?, ?)",
|
|
319
|
+
((cursor.lastrowid, input_i) for input_i in input_ids))
|
|
320
|
+
insert_target.cache_clear()
|
|
321
|
+
counts = {table: connection.execute(
|
|
322
|
+
f"SELECT count(*) FROM {'observation' if table == 'measurement' else table}").fetchone()[0]
|
|
265
323
|
for table in ("sample", "assay", "mapping", "target", "measurement",
|
|
266
324
|
"assay_pair", "target_relation", "reference_mapping")}
|
|
267
325
|
connection.execute("INSERT INTO metadata VALUES ('counts', ?)", (_json(counts),))
|
|
326
|
+
# Real cardinalities prevent the planner from scanning a whole
|
|
327
|
+
# reference in ID order for a narrow target query.
|
|
328
|
+
connection.execute("ANALYZE")
|
|
268
329
|
connection.commit()
|
|
269
330
|
connection.close()
|
|
270
331
|
connection = None
|
|
@@ -430,7 +491,8 @@ class QuantitativeDataset:
|
|
|
430
491
|
filters = dict(reference_key=scope, target_key=_target_key(target) if target else None,
|
|
431
492
|
sample_id=sample_id, assay_id=assay_id, quantity=quantity, status=status)
|
|
432
493
|
selected = [(key, value) for key, value in filters.items() if value is not None]
|
|
433
|
-
query = ("SELECT m.record, m.reference_key, t.kind, t.namespace, t.identifier "
|
|
494
|
+
query = ("SELECT m.record, m.reference_key, t.kind, t.namespace, t.identifier, "
|
|
495
|
+
"m.id, m.sample_id, m.assay_id, m.quantity, m.status "
|
|
434
496
|
"FROM measurement m JOIN target t ON m.reference_key=t.reference_key "
|
|
435
497
|
"AND m.target_key=t.key")
|
|
436
498
|
if selected:
|
|
@@ -439,6 +501,11 @@ class QuantitativeDataset:
|
|
|
439
501
|
with closing(self._connect()) as connection:
|
|
440
502
|
for row in connection.execute(query, [value for _, value in selected]):
|
|
441
503
|
values = json.loads(row[0])
|
|
504
|
+
if self._storage_version == 4:
|
|
505
|
+
values = dict(_V4_DEFAULTS, **values)
|
|
506
|
+
values.update(measurement_id=row[5], sample_id=row[6], assay_id=row[7],
|
|
507
|
+
quantity=row[8], status=row[9], dataset_id=self.manifest.dataset_id,
|
|
508
|
+
dataset_version=self.manifest.version)
|
|
442
509
|
values["target"] = ScopedTarget(self._references[row[1]], row[2], row[4], row[3])
|
|
443
510
|
if "input_references" in values:
|
|
444
511
|
values["input_references"] = tuple(MeasurementReference.from_dict(value)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.0"
|
|
@@ -13,6 +13,7 @@ docs/pfizer-protein.md
|
|
|
13
13
|
docs/reference.md
|
|
14
14
|
docs/source-references.md
|
|
15
15
|
docs/species.md
|
|
16
|
+
docs/storage.md
|
|
16
17
|
orfdata/__init__.py
|
|
17
18
|
orfdata/build.py
|
|
18
19
|
orfdata/catalog.py
|
|
@@ -56,6 +57,7 @@ tests/test_pfizer.py
|
|
|
56
57
|
tests/test_reference_context.py
|
|
57
58
|
tests/test_reference_mapping.py
|
|
58
59
|
tests/test_reference_orf.py
|
|
60
|
+
tests/test_storage.py
|
|
59
61
|
tests/test_uorf_catalog.py
|
|
60
62
|
tests/data/copy/GSM1905474.soft
|
|
61
63
|
tests/data/copy/GSM1905474_KL9_fpkm.txt.gz
|
|
@@ -92,4 +94,12 @@ tests/data/references/canis_lupus_familiaris.json
|
|
|
92
94
|
tests/data/references/context-keys-v0.4.json
|
|
93
95
|
tests/data/references/homo_sapiens.json
|
|
94
96
|
tests/data/references/mus_musculus.json
|
|
95
|
-
tests/data/references/saccharomyces_cerevisiae.json
|
|
97
|
+
tests/data/references/saccharomyces_cerevisiae.json
|
|
98
|
+
tests/data/storage/README.md
|
|
99
|
+
tests/data/storage/generate.py
|
|
100
|
+
tests/data/storage/v1.json
|
|
101
|
+
tests/data/storage/v1.sqlite
|
|
102
|
+
tests/data/storage/v2.json
|
|
103
|
+
tests/data/storage/v2.sqlite
|
|
104
|
+
tests/data/storage/v3.json
|
|
105
|
+
tests/data/storage/v3.sqlite
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
These synthetic databases were generated by the **released** ORFdata versions
|
|
2
|
+
0.3.0 (storage 1), 0.4.0 (storage 2) and 0.5.0 (storage 3), using `generate.py`.
|
|
3
|
+
They test actual legacy layouts; changing a new database's `user_version` would
|
|
4
|
+
not produce a valid old-format fixture.
|
|
5
|
+
|
|
6
|
+
Each contains four species with colliding local sample/assay/target/measurement
|
|
7
|
+
IDs, measured zero, a censored value, uncertainty and derived input lineage.
|
|
8
|
+
Versions 2/3 include pairings; version 3 includes reference mappings. No records
|
|
9
|
+
are experimental evidence. The JSON files retain the original writer's records.
|
|
10
|
+
|
|
11
|
+
Example regeneration from a checkout of the appropriate release:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
PYTHONPATH=/path/to/orfdata-0.3.0 python generate.py /path/to/fixtures 1
|
|
15
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Run from released 0.3.0/0.4.0/0.5.0 to generate genuine legacy databases."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import orfdata
|
|
9
|
+
from orfdata import (
|
|
10
|
+
Assay,
|
|
11
|
+
DatasetManifest,
|
|
12
|
+
Measurement,
|
|
13
|
+
QuantitativeDataset,
|
|
14
|
+
ReferenceContext,
|
|
15
|
+
Sample,
|
|
16
|
+
ScopedTarget,
|
|
17
|
+
SourceAsset,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
output = Path(sys.argv[1])
|
|
21
|
+
version = int(sys.argv[2])
|
|
22
|
+
references = tuple(ReferenceContext(taxon, name, assembly, "ensembl", "110")
|
|
23
|
+
for taxon, name, assembly in ((9606, "human", "GRCh38"),
|
|
24
|
+
(10090, "mouse", "GRCm39"),
|
|
25
|
+
(9615, "dog", "ROS_Cfam_1.0"),
|
|
26
|
+
(4932, "yeast", "R64-1-1")))
|
|
27
|
+
source = SourceAsset("input.tsv", "https://example.invalid/storage-fixture.tsv",
|
|
28
|
+
hashlib.sha256(b"synthetic fixture\n").hexdigest(), 18,
|
|
29
|
+
"Synthetic test data; Apache-2.0", redistributable=True)
|
|
30
|
+
manifest = DatasetManifest("legacy-fixture", "1", "Synthetic legacy storage validation",
|
|
31
|
+
references, (source,), ("Synthetic, not experimental evidence",),
|
|
32
|
+
("rna_seq", "digest_ms"), {"scope": "four deliberately colliding species"},
|
|
33
|
+
{"writer": "orfdata", "writer_version": orfdata.__version__})
|
|
34
|
+
samples, assays, observations, pairs, relations, mappings = [], [], [], [], [], []
|
|
35
|
+
for index, reference in enumerate(references):
|
|
36
|
+
samples.append(Sample("sample", reference, biological_unit_id="culture"))
|
|
37
|
+
assays.extend((Assay("rna", "sample", "rna_seq", reference), Assay("protein", "sample", "digest_ms", reference)))
|
|
38
|
+
gene = ScopedTarget(reference, "gene", "same-id", "fixture")
|
|
39
|
+
protein = ScopedTarget(reference, "protein", "same-id", "fixture")
|
|
40
|
+
observations.extend((
|
|
41
|
+
Measurement("rna-value", gene, "sample", "rna", "rna_abundance", index, "TPM",
|
|
42
|
+
manifest.dataset_id, manifest.version, provenance={"source_asset": "input.tsv", "row": index}),
|
|
43
|
+
Measurement("protein-value", protein, "sample", "protein", "protein_abundance",
|
|
44
|
+
None if index == 1 else 10.25 + index, "iBAQ", manifest.dataset_id, manifest.version,
|
|
45
|
+
status="below_detection" if index == 1 else "measured", detection_limit=20 if index == 1 else None,
|
|
46
|
+
uncertainty=0.5, uncertainty_type="standard_deviation",
|
|
47
|
+
provenance={"source_asset": "input.tsv", "row": index, "native": "<20" if index == 1 else "observed"}),
|
|
48
|
+
Measurement("derived", gene, "sample", "rna", "rna_abundance", 1, "fixture-units",
|
|
49
|
+
manifest.dataset_id, manifest.version, origin="derived", input_measurements=("rna-value",),
|
|
50
|
+
derivation={"method": "synthetic lineage fixture"}),
|
|
51
|
+
))
|
|
52
|
+
if version >= 2:
|
|
53
|
+
from orfdata import AssayPair, TargetRelation
|
|
54
|
+
|
|
55
|
+
pairs.append(AssayPair("pair", reference, manifest.dataset_id, manifest.version,
|
|
56
|
+
"rna", "protein", "same_material", {"source_asset": "input.tsv"}, "concurrent"))
|
|
57
|
+
relations.append(TargetRelation("relation", manifest.dataset_id, manifest.version, gene, protein,
|
|
58
|
+
"canonical_translation", "one_to_one", {"source_asset": "input.tsv"}))
|
|
59
|
+
if version >= 3:
|
|
60
|
+
from orfdata import ReferenceMapping
|
|
61
|
+
|
|
62
|
+
mappings.append(ReferenceMapping("map", manifest.dataset_id, manifest.version,
|
|
63
|
+
protein, reference, (ScopedTarget(reference, "protein", "mapped-id", "fixture"),),
|
|
64
|
+
"unique", "synthetic identifier match", {"sequence": "not validated"},
|
|
65
|
+
provenance={"source_asset": "input.tsv"}))
|
|
66
|
+
kwargs = dict(samples=samples, assays=assays, measurements=observations)
|
|
67
|
+
if version >= 2:
|
|
68
|
+
kwargs.update(pairings=pairs, relations=relations)
|
|
69
|
+
if version >= 3:
|
|
70
|
+
kwargs.update(reference_mappings=mappings)
|
|
71
|
+
dataset = QuantitativeDataset.create(output / f"v{version}.sqlite", manifest, **kwargs)
|
|
72
|
+
expected = dict(writer_version=orfdata.__version__, manifest=manifest.to_dict(),
|
|
73
|
+
measurements=[m.to_dict() for m in observations], counts=dataset.counts)
|
|
74
|
+
(output / f"v{version}.json").write_text(json.dumps(expected, indent=2) + "\n")
|