orfdata 0.8.0__tar.gz → 0.9.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.
Files changed (109) hide show
  1. {orfdata-0.8.0/orfdata.egg-info → orfdata-0.9.0}/PKG-INFO +1 -1
  2. {orfdata-0.8.0 → orfdata-0.9.0}/docs/measurements.md +18 -0
  3. {orfdata-0.8.0 → orfdata-0.9.0}/docs/pfizer-protein.md +5 -2
  4. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/dataset.py +36 -5
  5. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/dataset_cli.py +5 -1
  6. orfdata-0.9.0/orfdata/version.py +1 -0
  7. {orfdata-0.8.0 → orfdata-0.9.0/orfdata.egg-info}/PKG-INFO +1 -1
  8. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata.egg-info/SOURCES.txt +1 -0
  9. orfdata-0.9.0/tests/test_selectors.py +130 -0
  10. orfdata-0.8.0/orfdata/version.py +0 -1
  11. {orfdata-0.8.0 → orfdata-0.9.0}/LICENSE +0 -0
  12. {orfdata-0.8.0 → orfdata-0.9.0}/MANIFEST.in +0 -0
  13. {orfdata-0.8.0 → orfdata-0.9.0}/README.md +0 -0
  14. {orfdata-0.8.0 → orfdata-0.9.0}/deploy.sh +0 -0
  15. {orfdata-0.8.0 → orfdata-0.9.0}/docs/copy-yeast.md +0 -0
  16. {orfdata-0.8.0 → orfdata-0.9.0}/docs/design.md +0 -0
  17. {orfdata-0.8.0 → orfdata-0.9.0}/docs/pairing.md +0 -0
  18. {orfdata-0.8.0 → orfdata-0.9.0}/docs/reference.md +0 -0
  19. {orfdata-0.8.0 → orfdata-0.9.0}/docs/source-references.md +0 -0
  20. {orfdata-0.8.0 → orfdata-0.9.0}/docs/species.md +0 -0
  21. {orfdata-0.8.0 → orfdata-0.9.0}/docs/storage.md +0 -0
  22. {orfdata-0.8.0 → orfdata-0.9.0}/lint.sh +0 -0
  23. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/__init__.py +0 -0
  24. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/build.py +0 -0
  25. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/catalog.py +0 -0
  26. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/cli.py +0 -0
  27. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/data/datasets.json +0 -0
  28. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/data/orf-evidence/AUTHOR-LICENSE.txt +0 -0
  29. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/data/orf-evidence/NOTICE.txt +0 -0
  30. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/data/orf-evidence/catalog.json.gz +0 -0
  31. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/data/orf-evidence/manifest.json +0 -0
  32. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/data/uorf-evidence/AUTHOR-LICENSE.txt +0 -0
  33. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/data/uorf-evidence/NOTICE.txt +0 -0
  34. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/data/uorf-evidence/catalog.json.gz +0 -0
  35. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/data/uorf-evidence/manifest.json +0 -0
  36. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/datasets.py +0 -0
  37. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/initiation_context.py +0 -0
  38. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/measurements.py +0 -0
  39. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/model.py +0 -0
  40. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/pairing.py +0 -0
  41. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/reference.py +0 -0
  42. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/reference_mapping.py +0 -0
  43. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/sequence.py +0 -0
  44. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/sources/__init__.py +0 -0
  45. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/sources/copy.py +0 -0
  46. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata/sources/pfizer.py +0 -0
  47. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata.egg-info/dependency_links.txt +0 -0
  48. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata.egg-info/entry_points.txt +0 -0
  49. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata.egg-info/requires.txt +0 -0
  50. {orfdata-0.8.0 → orfdata-0.9.0}/orfdata.egg-info/top_level.txt +0 -0
  51. {orfdata-0.8.0 → orfdata-0.9.0}/pyproject.toml +0 -0
  52. {orfdata-0.8.0 → orfdata-0.9.0}/setup.cfg +0 -0
  53. {orfdata-0.8.0 → orfdata-0.9.0}/test.sh +0 -0
  54. {orfdata-0.8.0 → orfdata-0.9.0}/tests/conftest.py +0 -0
  55. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/GSM1905474.soft +0 -0
  56. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/GSM1905474_KL9_fpkm.txt.gz +0 -0
  57. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/GSM1905476.soft +0 -0
  58. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/GSM1905476_KL10_fpkm.txt.gz +0 -0
  59. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/GSM1905478.soft +0 -0
  60. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/GSM1905478_KL11_fpkm.txt.gz +0 -0
  61. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/GSM1905479.soft +0 -0
  62. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/GSM1905479_KL12_fpkm.txt.gz +0 -0
  63. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/NOTICE.md +0 -0
  64. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/expected.json +0 -0
  65. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/geo-series.soft +0 -0
  66. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/manifest.json +0 -0
  67. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/methods.pdf +0 -0
  68. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/paper.xml +0 -0
  69. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/pmc-metadata.json +0 -0
  70. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/qconcat.fasta +0 -0
  71. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/copy/workbook.xlsx +0 -0
  72. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/NOTICE.md +0 -0
  73. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/dog/expected.json +0 -0
  74. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/dog/manifest.json +0 -0
  75. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/dog/protein.tsv +0 -0
  76. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/dog/search.fasta +0 -0
  77. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/human/expected.json +0 -0
  78. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/human/manifest.json +0 -0
  79. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/human/protein.tsv +0 -0
  80. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/human/search.fasta +0 -0
  81. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/mouse/expected.json +0 -0
  82. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/mouse/manifest.json +0 -0
  83. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/mouse/protein.tsv +0 -0
  84. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/pfizer/mouse/search.fasta +0 -0
  85. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/references/README.md +0 -0
  86. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/references/canis_lupus_familiaris.json +0 -0
  87. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/references/context-keys-v0.4.json +0 -0
  88. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/references/homo_sapiens.json +0 -0
  89. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/references/mus_musculus.json +0 -0
  90. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/references/saccharomyces_cerevisiae.json +0 -0
  91. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/storage/README.md +0 -0
  92. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/storage/generate.py +0 -0
  93. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/storage/v1.json +0 -0
  94. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/storage/v1.sqlite +0 -0
  95. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/storage/v2.json +0 -0
  96. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/storage/v2.sqlite +0 -0
  97. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/storage/v3.json +0 -0
  98. {orfdata-0.8.0 → orfdata-0.9.0}/tests/data/storage/v3.sqlite +0 -0
  99. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_copy.py +0 -0
  100. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_cross_reference_pairing.py +0 -0
  101. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_initiation_context.py +0 -0
  102. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_measurements.py +0 -0
  103. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_pairing.py +0 -0
  104. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_pfizer.py +0 -0
  105. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_reference_context.py +0 -0
  106. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_reference_mapping.py +0 -0
  107. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_reference_orf.py +0 -0
  108. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_storage.py +0 -0
  109. {orfdata-0.8.0 → orfdata-0.9.0}/tests/test_uorf_catalog.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orfdata
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: Reference open reading frames and experimental translation evidence on annotated transcripts
5
5
  Author: OpenVax
6
6
  License-Expression: Apache-2.0
@@ -51,6 +51,24 @@ For native sources with unreported genomic metadata, use explicit
51
51
  [source references and mapping views](source-references.md) to retain the original
52
52
  observation alongside its mapping to a PyEnsembl target.
53
53
 
54
+ Choose a native series explicitly when a source supplies several units:
55
+
56
+ ```python
57
+ values = dataset.measurements(quantity="protein_abundance", unit="iBAQ", scale="linear")
58
+ dataset.export_parquet("ibaq.parquet", unit="iBAQ", scale="linear")
59
+ ```
60
+
61
+ `unit`, `scale` and `log_base` also work with `mapped_measurements` and existing
62
+ storage v1-v4 releases. Units are exact and case-sensitive. Selecting
63
+ `scale="log", log_base=2` returns values already on that scale; nothing is transformed.
64
+ An unavailable series returns no observations. Contradictory linear/log requests
65
+ raise an error. Omitting selectors preserves all native series.
66
+
67
+ For example, Pfizer iBAQ and rIBAQ are two series from the same source rows.
68
+ Selecting or exporting both does not create additional donors, spectra or
69
+ independent detections. Source row/sample/assay lineage stays attached to each
70
+ measurement.
71
+
54
72
  The CLI uses the same operations:
55
73
 
56
74
  ```sh
@@ -20,7 +20,7 @@ include sex. Channels and technical runs do not count as independent donors.
20
20
  pip install 'orfdata[data]'
21
21
  orfdata datasets list --species dog
22
22
  orfdata datasets download pfizer-atlas-dog-protein
23
- orfdata datasets query pfizer-atlas-dog-protein --quantity protein_abundance
23
+ orfdata datasets query pfizer-atlas-dog-protein --quantity protein_abundance --unit iBAQ --scale linear
24
24
  ```
25
25
 
26
26
  ```python
@@ -30,10 +30,13 @@ registry = DatasetRegistry()
30
30
  dataset = registry.open("pfizer-atlas-dog-protein") # After explicit download.
31
31
  reference, = dataset.manifest.references
32
32
  group = ScopedTarget(reference, "protein_group", "A0A0A0MPB0", "MSV000099744:ProteinGroup")
33
- for value in dataset.measurements(target=group):
33
+ for value in dataset.measurements(target=group, unit="iBAQ", scale="linear"):
34
34
  print(value.sample_id, value.unit, value.value, value.status)
35
35
  ```
36
36
 
37
+ Omit the unit selector to return both native series. They retain the same source
38
+ row and sample identity; two units do not imply two independent measurements.
39
+
37
40
  Every target retains its species and the checksum of the deposited UniProt
38
41
  search FASTA. The original genomic assembly and UniProt release are unknown.
39
42
  They stay unknown; a future annotation mapping must be explicit and validated.
@@ -18,6 +18,8 @@ from .measurements import (
18
18
  MeasurementReference,
19
19
  Sample,
20
20
  TargetMapping,
21
+ _identifier,
22
+ _number,
21
23
  _plain,
22
24
  )
23
25
  from .pairing import (
@@ -138,6 +140,19 @@ def _measurement_record(observation):
138
140
  return result
139
141
 
140
142
 
143
+ def _validate_native_selectors(unit=None, scale=None, log_base=None):
144
+ if unit is not None:
145
+ _identifier(unit, "measurement unit selector")
146
+ if scale is not None and scale not in ("linear", "log"):
147
+ raise ValueError("Measurement scale selector must be linear or log")
148
+ if log_base is not None:
149
+ _number(log_base, "logarithm base selector")
150
+ if log_base <= 0 or log_base == 1:
151
+ raise ValueError("Logarithm base selector must be positive and not one")
152
+ if scale == "linear":
153
+ raise ValueError("A linear scale selector cannot have a logarithm base")
154
+
155
+
141
156
  class QuantitativeDataset:
142
157
  """A read-only release. Constructing or querying it never downloads data.
143
158
 
@@ -405,8 +420,9 @@ class QuantitativeDataset:
405
420
  raise ValueError("Choose explicit usable mapping statuses")
406
421
  if type(require_sequence) is not bool:
407
422
  raise TypeError("require_sequence must be a boolean")
408
- if set(observation_filters) - {"sample_id", "assay_id", "quantity", "status"}:
423
+ if set(observation_filters) - {"sample_id", "assay_id", "quantity", "status", "unit", "scale", "log_base"}:
409
424
  raise ValueError("Mapped observation filters cannot override source/target reference identity")
425
+ _validate_native_selectors(**{name: observation_filters.get(name) for name in ("unit", "scale", "log_base")})
410
426
  for mapping in self.reference_mappings(target=target):
411
427
  if mapping.status not in allowed or (require_sequence and not mapping.sequence_validated):
412
428
  continue
@@ -468,16 +484,19 @@ class QuantitativeDataset:
468
484
  tuple(by_target[1].get(relation.right.key, ())))
469
485
 
470
486
  def measurements(self, *, target=None, reference=None, sample_id=None,
471
- assay_id=None, quantity=None, status=None):
487
+ assay_id=None, quantity=None, status=None, unit=None, scale=None,
488
+ log_base=None):
472
489
  """Iterate native observations; a target match is exact, never orthology.
473
490
 
474
491
  Unqualified IDs across multiple references are rejected. To browse all
475
492
  species together, omit ID filters; each result still carries its scope.
493
+ Native unit/scale/base selectors are exact; no conversion or aggregation.
476
494
  """
477
495
  if quantity is not None and quantity not in QUANTITIES:
478
496
  raise ValueError("Unknown measurement quantity")
479
497
  if status is not None and status not in MEASUREMENT_STATUSES:
480
498
  raise ValueError("Unknown measurement status")
499
+ _validate_native_selectors(unit, scale, log_base)
481
500
  scope = self._scope(reference)
482
501
  if target is not None:
483
502
  if not isinstance(target, ScopedTarget):
@@ -495,11 +514,23 @@ class QuantitativeDataset:
495
514
  "m.id, m.sample_id, m.assay_id, m.quantity, m.status "
496
515
  "FROM measurement m JOIN target t ON m.reference_key=t.reference_key "
497
516
  "AND m.target_key=t.key")
498
- if selected:
499
- query += " WHERE " + " AND ".join(f"m.{key}=?" for key, _ in selected)
517
+ conditions = [f"m.{key}=?" for key, _ in selected]
518
+ parameters = [value for _, value in selected]
519
+ # JSON extraction applies to original v1-v3 records and compact v4
520
+ # residual records. SQLite filters before Python reconstructs objects.
521
+ for value, expression in (
522
+ (unit, "json_extract(m.record, '$.unit')"),
523
+ (scale, "coalesce(json_extract(m.record, '$.scale'), 'linear')"),
524
+ (log_base, "json_extract(m.record, '$.log_base')"),
525
+ ):
526
+ if value is not None:
527
+ conditions.append(expression + "=?")
528
+ parameters.append(value)
529
+ if conditions:
530
+ query += " WHERE " + " AND ".join(conditions)
500
531
  query += " ORDER BY m.reference_key, m.id"
501
532
  with closing(self._connect()) as connection:
502
- for row in connection.execute(query, [value for _, value in selected]):
533
+ for row in connection.execute(query, parameters):
503
534
  values = json.loads(row[0])
504
535
  if self._storage_version == 4:
505
536
  values = dict(_V4_DEFAULTS, **values)
@@ -39,6 +39,9 @@ def run(argv=None, prog=None):
39
39
  subparser.add_argument("--assay-id")
40
40
  subparser.add_argument("--quantity")
41
41
  subparser.add_argument("--status")
42
+ subparser.add_argument("--unit", help="Exact native unit, e.g. iBAQ, rIBAQ, TPM or counts")
43
+ subparser.add_argument("--scale", choices=("linear", "log"))
44
+ subparser.add_argument("--log-base", type=float, help="Exact native logarithm base; no transformation")
42
45
  subparser.add_argument("--target")
43
46
  subparser.add_argument("--kind")
44
47
  subparser.add_argument("--namespace")
@@ -111,7 +114,8 @@ def run(argv=None, prog=None):
111
114
  references = {r.key: r for r in dataset.manifest.references}
112
115
  target = ScopedTarget(references[args.reference], args.kind, args.target, args.namespace)
113
116
  filters = dict(sample_id=args.sample, assay_id=args.assay_id,
114
- quantity=args.quantity, status=args.status)
117
+ quantity=args.quantity, status=args.status,
118
+ unit=args.unit, scale=args.scale, log_base=args.log_base)
115
119
  if args.mapped:
116
120
  if target is None:
117
121
  parser.error("--mapped requires an exact target and reference")
@@ -0,0 +1 @@
1
+ __version__ = "0.9.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orfdata
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: Reference open reading frames and experimental translation evidence on annotated transcripts
5
5
  Author: OpenVax
6
6
  License-Expression: Apache-2.0
@@ -57,6 +57,7 @@ tests/test_pfizer.py
57
57
  tests/test_reference_context.py
58
58
  tests/test_reference_mapping.py
59
59
  tests/test_reference_orf.py
60
+ tests/test_selectors.py
60
61
  tests/test_storage.py
61
62
  tests/test_uorf_catalog.py
62
63
  tests/data/copy/GSM1905474.soft
@@ -0,0 +1,130 @@
1
+ """Native series selection preserves values, lineage and target resolution."""
2
+
3
+ import json
4
+ from dataclasses import replace
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from orfdata import DatasetManifest, DatasetRegistry, Measurement, QuantitativeDataset
10
+ from orfdata.cli import run
11
+ from orfdata.sources.pfizer import import_protein
12
+
13
+
14
+ @pytest.fixture(params=("human", "mouse", "dog"))
15
+ def native(request, tmp_path):
16
+ root = Path(__file__).parent / "data/pfizer" / request.param
17
+ manifest = DatasetManifest.from_dict(json.loads((root / "manifest.json").read_text()))
18
+ return import_protein({a.asset_id: root / a.asset_id for a in manifest.sources}, tmp_path / "native.sqlite", manifest)
19
+
20
+
21
+ def test_native_unit_values_and_source_identity(native):
22
+ original = list(native.measurements(quantity="protein_abundance"))
23
+ assert len(original) == 10
24
+ selected = {}
25
+ for unit in ("iBAQ", "rIBAQ"):
26
+ values = list(native.measurements(quantity="protein_abundance", unit=unit, scale="linear"))
27
+ assert len(values) == 5 and values == [m for m in original if m.unit == unit]
28
+ assert any(m.value == 0 and m.status == "measured" for m in values)
29
+ assert any(m.value is None and m.status == "missing" for m in values)
30
+ selected[unit] = {(m.sample_id, m.assay_id, m.provenance["line"]) for m in values}
31
+ assert selected["iBAQ"] == selected["rIBAQ"] # Same five source rows, two series.
32
+ for unit in ("copies/cell", "ibaq", "iBAQ' OR 1=1 --"):
33
+ assert list(native.measurements(unit=unit)) == []
34
+ assert list(native.measurements(unit="iBAQ", scale="log")) == []
35
+ mapping = next(m for m in native.reference_mappings() if m.targets)
36
+ target = mapping.targets[0]
37
+ assert list(native.mapped_measurements(target, unit="iBAQ")) == []
38
+ views = list(native.mapped_measurements(target, mapping_statuses=["unresolved"], unit="iBAQ", scale="linear"))
39
+ assert views and all(v.measurement.unit == "iBAQ" and v.attribution == "group_membership_context" for v in views)
40
+
41
+
42
+ def test_cli_native_and_mapped(native, tmp_path, capsys):
43
+ from test_measurements import definition
44
+
45
+ definitions = definition(native)
46
+ path = tmp_path / "registry.json"
47
+ path.write_text(json.dumps(definitions))
48
+ cache = tmp_path / "cache"
49
+ DatasetRegistry(definitions, cache_root=cache).download(native.manifest.dataset_id, show_progress=False)
50
+ prefix = ["datasets", "--registry", str(path), "--cache-root", str(cache), "query", native.manifest.dataset_id]
51
+ run(prefix + ["--unit", "iBAQ", "--scale", "linear"])
52
+ rows = [json.loads(line) for line in capsys.readouterr().out.splitlines()]
53
+ assert len(rows) == 5 and all(r["unit"] == "iBAQ" for r in rows)
54
+ target = next(m.targets[0] for m in native.reference_mappings() if m.targets)
55
+ run(prefix + ["--mapped", "--mapping-status", "unresolved", "--reference", target.reference.key,
56
+ "--target", target.identifier, "--kind", target.kind, "--namespace", target.namespace,
57
+ "--unit", "rIBAQ", "--scale", "linear"])
58
+ rows = [json.loads(line) for line in capsys.readouterr().out.splitlines()]
59
+ assert rows and all(r["measurement"]["unit"] == "rIBAQ" for r in rows)
60
+ run(prefix + ["--unit", "copies/cell"])
61
+ assert capsys.readouterr().out == ""
62
+
63
+
64
+ def test_parquet_uses_the_same_selection(native, tmp_path):
65
+ parquet = pytest.importorskip("pyarrow.parquet")
66
+ output = tmp_path / "selected.parquet"
67
+ native.export_parquet(output, unit="iBAQ", scale="linear")
68
+ rows = parquet.read_table(output).to_pylist()
69
+ assert len(rows) == 5 and all(r["unit"] == "iBAQ" for r in rows)
70
+ assert [Measurement.from_dict(json.loads(r["measurement_json"])) for r in rows] == list(native.measurements(unit="iBAQ"))
71
+ native.export_parquet(output, unit="unavailable")
72
+ assert parquet.read_table(output).num_rows == 0
73
+
74
+
75
+ @pytest.mark.parametrize("version", (1, 2, 3))
76
+ def test_legacy_exact_series_filters(version):
77
+ dataset = QuantitativeDataset(Path(__file__).parent / f"data/storage/v{version}.sqlite")
78
+ original = list(dataset.measurements())
79
+ for unit in ("TPM", "iBAQ", "fixture-units"):
80
+ assert list(dataset.measurements(unit=unit, scale="linear")) == [m for m in original if m.unit == unit]
81
+ assert list(dataset.measurements(scale="log")) == []
82
+ assert list(dataset.measurements(log_base=2)) == []
83
+
84
+
85
+ @pytest.fixture
86
+ def transformed(experiment, tmp_path):
87
+ manifest, samples, assays, values = experiment
88
+ value = values[0]
89
+ observations = [replace(value, measurement_id="linear", value=0),
90
+ replace(value, measurement_id="log2", value=-2, scale="log", log_base=2),
91
+ replace(value, measurement_id="log10", value=-1, scale="log", log_base=10)]
92
+ return QuantitativeDataset.create(tmp_path / "log.sqlite", manifest, samples=samples, assays=assays, measurements=observations)
93
+
94
+
95
+ def test_log_base_selection_never_transforms(transformed):
96
+ assert [m.value for m in transformed.measurements(unit="TPM", scale="linear")] == [0]
97
+ assert {m.value for m in transformed.measurements(scale="log")} == {-1, -2}
98
+ assert [m.value for m in transformed.measurements(unit="TPM", log_base=2)] == [-2]
99
+ assert [m.value for m in transformed.measurements(scale="log", log_base=10)] == [-1]
100
+ assert list(transformed.measurements(log_base=3)) == []
101
+
102
+
103
+ def test_cli_log_base_and_contradictory_selection(transformed, tmp_path, capsys):
104
+ from test_measurements import definition
105
+
106
+ definitions = definition(transformed)
107
+ path = tmp_path / "registry.json"
108
+ path.write_text(json.dumps(definitions))
109
+ cache = tmp_path / "cache"
110
+ DatasetRegistry(definitions, cache_root=cache).download("fixture", show_progress=False)
111
+ prefix = ["datasets", "--registry", str(path), "--cache-root", str(cache), "query", "fixture"]
112
+ run(prefix + ["--unit", "TPM", "--scale", "log", "--log-base", "2"])
113
+ rows = [json.loads(line) for line in capsys.readouterr().out.splitlines()]
114
+ assert len(rows) == 1 and rows[0]["value"] == -2 and rows[0]["log_base"] == 2
115
+ with pytest.raises(SystemExit):
116
+ run(prefix + ["--scale", "linear", "--log-base", "2"])
117
+ assert "cannot have a logarithm base" in capsys.readouterr().err
118
+
119
+
120
+ @pytest.mark.parametrize("filters", [
121
+ {"unit": ""}, {"unit": " "}, {"unit": 1}, {"scale": "log2"}, {"log_base": 0},
122
+ {"log_base": 1}, {"log_base": -2}, {"log_base": True}, {"log_base": float("inf")},
123
+ {"log_base": float("nan")}, {"scale": "linear", "log_base": 2},
124
+ ])
125
+ def test_invalid_selectors(transformed, filters):
126
+ with pytest.raises((ValueError, TypeError)):
127
+ list(transformed.measurements(**filters))
128
+ target = next(transformed.measurements()).target
129
+ with pytest.raises((ValueError, TypeError)):
130
+ list(transformed.mapped_measurements(target, **filters))
@@ -1 +0,0 @@
1
- __version__ = "0.8.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes