mapkgsutils 0.0.3__tar.gz → 0.0.4__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.
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: mapkgsutils
3
+ Version: 0.0.4
4
+ Summary: Utils shared by several mapping set generation tools for biomedical databases
5
+ Keywords: snekpack,cookiecutter
6
+ Author: Javier Millan Acosta
7
+ Author-email: Javier Millan Acosta <javier.millanacosta@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 1 - Planning
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Framework :: Pytest
15
+ Classifier: Framework :: Sphinx
16
+ Classifier: Natural Language :: English
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3 :: Only
24
+ Classifier: Typing :: Typed
25
+ Requires-Dist: more-itertools
26
+ Requires-Dist: tqdm
27
+ Requires-Dist: click
28
+ Requires-Dist: more-click
29
+ Requires-Dist: typing-extensions
30
+ Requires-Dist: pydantic>=2.0
31
+ Requires-Dist: pyyaml
32
+ Requires-Dist: sssom==0.4.21
33
+ Requires-Dist: sssom-schema==1.1.0a5
34
+ Requires-Dist: curies>=0.7.0
35
+ Requires-Dist: polars>=0.20.0
36
+ Requires-Dist: polars-runtime-32
37
+ Requires-Dist: pandas
38
+ Requires-Dist: numpy
39
+ Requires-Dist: httpx>=0.24.0
40
+ Maintainer: Javier Millan Acosta
41
+ Maintainer-email: Javier Millan Acosta <javier.millanacosta@gmail.com>
42
+ Requires-Python: >=3.10
43
+ Project-URL: Bug Tracker, https://github.com//mapkgsutils/issues
44
+ Project-URL: Homepage, https://github.com//mapkgsutils
45
+ Project-URL: Repository, https://github.com//mapkgsutils.git
46
+ Project-URL: Documentation, https://mapkgsutils.readthedocs.io
47
+ Project-URL: Funding, https://github.com/sponsors/jmillanacosta
48
+ Description-Content-Type: text/markdown
49
+
50
+ <!--
51
+ <p align="center">
52
+ <img src="https://github.com//mapkgsutils/raw/main/docs/source/logo.png" height="150">
53
+ </p>
54
+ -->
55
+
56
+ <h1 align="center">
57
+ MaPkgsUtilS
58
+ </h1>
59
+
60
+ <p align="center">
61
+ <a href="https://github.com//mapkgsutils/actions/workflows/tests.yml">
62
+ <img alt="Tests" src="https://github.com//mapkgsutils/actions/workflows/tests.yml/badge.svg" /></a>
63
+ <a href="https://pypi.org/project/mapkgsutils">
64
+ <img alt="PyPI" src="https://img.shields.io/pypi/v/mapkgsutils" /></a>
65
+ <a href="https://pypi.org/project/mapkgsutils">
66
+ <img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/mapkgsutils" /></a>
67
+ <a href="https://github.com//mapkgsutils/blob/main/LICENSE">
68
+ <img alt="PyPI - License" src="https://img.shields.io/pypi/l/mapkgsutils" /></a>
69
+ <a href='https://mapkgsutils.readthedocs.io/en/latest/?badge=latest'>
70
+ <img src='https://readthedocs.org/projects/mapkgsutils/badge/?version=latest' alt='Documentation Status' /></a>
71
+ <a href="https://codecov.io/gh//mapkgsutils/branch/main">
72
+ <img src="https://codecov.io/gh//mapkgsutils/branch/main/graph/badge.svg" alt="Codecov status" /></a>
73
+ <a href="https://github.com/cthoyt/cookiecutter-python-package">
74
+ <img alt="Cookiecutter template from @cthoyt" src="https://img.shields.io/badge/Cookiecutter-snekpack-blue" /></a>
75
+ <a href="https://github.com/astral-sh/ruff">
76
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
77
+ <a href="https://github.com//mapkgsutils/blob/main/.github/CODE_OF_CONDUCT.md">
78
+ <img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"/></a>
79
+ <!-- uncomment if you archive on zenodo
80
+ <a href="https://doi.org/10.5281/zenodo.XXXXXX">
81
+ <img src="https://zenodo.org/badge/DOI/10.5281/zenodo.XXXXXX.svg" alt="DOI"></a>
82
+ -->
83
+ </p>
84
+
85
+ Utils shared by several mapping set generation tools for biomedical databases.
86
+
87
+ ## 🚀 Installation
88
+
89
+ <!-- Uncomment this section after your first ``just finish``
90
+ The most recent release can be installed from
91
+ [PyPI](https://pypi.org/project/mapkgsutils/) with uv:
92
+
93
+ ```console
94
+ $ uv pip install mapkgsutils
95
+ ```
96
+
97
+ or with pip:
98
+
99
+ ```console
100
+ $ python3 -m pip install mapkgsutils
101
+ ```
102
+ -->
103
+
104
+ The most recent code and data can be installed directly from GitHub with uv:
105
+
106
+ ```console
107
+ $ uv pip install git+https://github.com//mapkgsutils.git
108
+ ```
109
+
110
+ or with pip:
111
+
112
+ ```console
113
+ $ python3 -m pip install git+https://github.com//mapkgsutils.git
114
+ ```
@@ -0,0 +1,65 @@
1
+ <!--
2
+ <p align="center">
3
+ <img src="https://github.com//mapkgsutils/raw/main/docs/source/logo.png" height="150">
4
+ </p>
5
+ -->
6
+
7
+ <h1 align="center">
8
+ MaPkgsUtilS
9
+ </h1>
10
+
11
+ <p align="center">
12
+ <a href="https://github.com//mapkgsutils/actions/workflows/tests.yml">
13
+ <img alt="Tests" src="https://github.com//mapkgsutils/actions/workflows/tests.yml/badge.svg" /></a>
14
+ <a href="https://pypi.org/project/mapkgsutils">
15
+ <img alt="PyPI" src="https://img.shields.io/pypi/v/mapkgsutils" /></a>
16
+ <a href="https://pypi.org/project/mapkgsutils">
17
+ <img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/mapkgsutils" /></a>
18
+ <a href="https://github.com//mapkgsutils/blob/main/LICENSE">
19
+ <img alt="PyPI - License" src="https://img.shields.io/pypi/l/mapkgsutils" /></a>
20
+ <a href='https://mapkgsutils.readthedocs.io/en/latest/?badge=latest'>
21
+ <img src='https://readthedocs.org/projects/mapkgsutils/badge/?version=latest' alt='Documentation Status' /></a>
22
+ <a href="https://codecov.io/gh//mapkgsutils/branch/main">
23
+ <img src="https://codecov.io/gh//mapkgsutils/branch/main/graph/badge.svg" alt="Codecov status" /></a>
24
+ <a href="https://github.com/cthoyt/cookiecutter-python-package">
25
+ <img alt="Cookiecutter template from @cthoyt" src="https://img.shields.io/badge/Cookiecutter-snekpack-blue" /></a>
26
+ <a href="https://github.com/astral-sh/ruff">
27
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
28
+ <a href="https://github.com//mapkgsutils/blob/main/.github/CODE_OF_CONDUCT.md">
29
+ <img src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg" alt="Contributor Covenant"/></a>
30
+ <!-- uncomment if you archive on zenodo
31
+ <a href="https://doi.org/10.5281/zenodo.XXXXXX">
32
+ <img src="https://zenodo.org/badge/DOI/10.5281/zenodo.XXXXXX.svg" alt="DOI"></a>
33
+ -->
34
+ </p>
35
+
36
+ Utils shared by several mapping set generation tools for biomedical databases.
37
+
38
+ ## 🚀 Installation
39
+
40
+ <!-- Uncomment this section after your first ``just finish``
41
+ The most recent release can be installed from
42
+ [PyPI](https://pypi.org/project/mapkgsutils/) with uv:
43
+
44
+ ```console
45
+ $ uv pip install mapkgsutils
46
+ ```
47
+
48
+ or with pip:
49
+
50
+ ```console
51
+ $ python3 -m pip install mapkgsutils
52
+ ```
53
+ -->
54
+
55
+ The most recent code and data can be installed directly from GitHub with uv:
56
+
57
+ ```console
58
+ $ uv pip install git+https://github.com//mapkgsutils.git
59
+ ```
60
+
61
+ or with pip:
62
+
63
+ ```console
64
+ $ python3 -m pip install git+https://github.com//mapkgsutils.git
65
+ ```
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "mapkgsutils"
7
- version = "0.0.3"
7
+ version = "0.0.4"
8
8
  description = "Utils shared by several mapping set generation tools for biomedical databases"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -236,7 +236,7 @@ known-first-party = [
236
236
  docstring-code-format = true
237
237
 
238
238
  [tool.bumpversion]
239
- current_version = "0.0.3"
239
+ current_version = "0.0.4"
240
240
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(?:-(?P<release>[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+(?P<build>[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?"
241
241
  serialize = [
242
242
  "{major}.{minor}.{patch}-{release}+{build}",
@@ -65,11 +65,11 @@ class DistributionEraSchema(BaseModel):
65
65
  class DatasourceConfigSchema(BaseModel):
66
66
  """Top-level schema for a ``config/<datasource>.yaml`` file.
67
67
 
68
- ``name``, ``prefix``, and ``curie_base_url`` are required -- they are the
69
- only fields :class:`~mapkgsutils.parsers.base.DatasourceConfig` declares
70
- without a default. Every other recognized key is optional and permissive
71
- in shape; unrecognized top-level keys are not an error here (see
72
- :func:`validate_config_dict`), only a warning.
68
+ ``name``, ``prefix``, and ``curie_base_url`` are the only fields
69
+ :class:`~mapkgsutils.parsers.base.DatasourceConfig` declares without a
70
+ default, so they're required here too. Every other recognized key is
71
+ optional and permissive in shape. Unrecognized top-level keys just get a
72
+ warning (see :func:`validate_config_dict`), not a hard error.
73
73
  """
74
74
 
75
75
  model_config = ConfigDict(extra="allow")
@@ -99,7 +99,6 @@ class DatasourceConfigSchema(BaseModel):
99
99
  distribution_eras: list[DistributionEraSchema] = Field(default_factory=list)
100
100
  xref_sources: list[XrefSourceSchema] = Field(default_factory=list)
101
101
  species: dict[str, Any] = Field(default_factory=dict)
102
- genome_build: dict[str, Any] = Field(default_factory=dict)
103
102
  subset: dict[str, Any] = Field(default_factory=dict)
104
103
  mappingset: dict[str, Any] = Field(default_factory=dict)
105
104
  mapping: dict[str, Any] = Field(default_factory=dict)
@@ -1,15 +1,12 @@
1
1
  """Generic mapping-date consolidation: cache I/O and the release/date walk skeleton.
2
2
 
3
- A versioned datasource's per-row deprecation/change date isn't always
4
- recorded anywhere in a single release -- recovering it means walking every
5
- historical release once and recording the first/last release each mapping
6
- (keyed by its version-independent pair hash) was seen in. This module is the
7
- datasource-agnostic half of that: cache I/O, the release-walk and
8
- single-pass-date loop shapes, and materializing the cache back into a real
9
- SSSOM mapping set. Per-datasource dispatch (which versions exist, how to
10
- download/parse one, which mapping-set class to build) is supplied by the
11
- caller as plain callables/values -- see :mod:`pysec2pri.consolidate` for a
12
- concrete binding.
3
+ A versioned datasource doesn't always record when a mapping first appeared.
4
+ Recovering that date means walking every historical release once and
5
+ recording the first/last release each mapping (keyed by its
6
+ version-independent pair hash) was seen in.
7
+
8
+ This module provides the cache I/O, the release-walk and single-pass-date
9
+ loop shapes, and rebuilding the cache back into a real SSSOM mapping set.
13
10
  """
14
11
 
15
12
  from __future__ import annotations
@@ -44,9 +41,9 @@ CACHE_COLUMNS = (
44
41
  "last_seen_version",
45
42
  "last_seen_date",
46
43
  # JSON-encoded snapshot of the mapping's own fields (subject_id,
47
- # object_id, predicate_id, ...) as last seen -- used to materialize the
48
- # consolidated index as a real SSSOM mapping set (see
49
- # build_consolidated_mapping_set). Empty for legacy/hand-built rows.
44
+ # object_id, predicate_id, ...) as last seen. Used to rebuild the cache
45
+ # into a real SSSOM mapping set later (see build_consolidated_mapping_set).
46
+ # Empty for legacy/hand-built rows.
50
47
  "fields_json",
51
48
  )
52
49
 
@@ -129,12 +126,12 @@ def _mapping_fields_json(m: Any) -> str:
129
126
  """JSON-encode a mapping's own fields (excluding mapping_date/record_id).
130
127
 
131
128
  Snapshots a mapping's current shape (subject_id, object_id,
132
- predicate_id, ...) so the consolidated index can later be materialized
133
- as a real SSSOM mapping set (see :func:`build_consolidated_mapping_set`).
134
- ``default=str`` handles enum-valued fields like ``mapping_cardinality``,
135
- which round-trip cleanly back through ``Mapping(**fields)``. Returns
136
- ``"{}"`` for non-dataclass stand-ins (e.g. test doubles) that don't carry
137
- a full field set -- callers skip materializing those rows.
129
+ predicate_id, ...) so :func:`build_consolidated_mapping_set` can rebuild
130
+ it into a real SSSOM mapping set later. ``default=str`` handles
131
+ enum-valued fields like ``mapping_cardinality``, which round-trip
132
+ cleanly back through ``Mapping(**fields)``. Returns ``"{}"`` for
133
+ non-dataclass stand-ins (e.g. test doubles); callers skip those rows
134
+ rather than materializing them.
138
135
  """
139
136
  from dataclasses import fields as dataclass_fields
140
137
  from dataclasses import is_dataclass
@@ -203,15 +200,18 @@ def build_consolidated_mapping_set(
203
200
  mapping_set_class: type[Any],
204
201
  record_namespace: str,
205
202
  mapping_set_metadata: Mapping[str, Any],
203
+ cardinality_on: str = "id",
206
204
  ) -> Any:
207
205
  """Materialize the consolidated index as a real SSSOM mapping set.
208
206
 
209
- Each record's stored field snapshot (from the most recent release where
210
- it was seen) is rebuilt into a ``Mapping``, with ``mapping_date``
211
- overridden to its ``first_seen_date`` -- the date of appearance, rather
212
- than whatever date the snapshot's own release happened to carry -- and
213
- ``record_id`` re-scoped with a trailing ``consolidate`` segment, marking
214
- the IRI as a derived, cross-release product.
207
+ Each row's ``mapping_date`` and ``subject_source_version``/
208
+ ``object_source_version`` are overridden from the record's
209
+ ``first_seen_date``/``first_seen_version``: the date of first
210
+ appearance and the release it first appeared in, rather than whatever
211
+ the last-seen snapshot happened to carry. The two stay in their own
212
+ fields. A release version (e.g. ChEBI's "183") is not a date and never
213
+ goes in ``mapping_date``, and a real date never goes in the version
214
+ fields.
215
215
 
216
216
  Args:
217
217
  records: ``record_id -> fields`` dict as read by :func:`read_cache`.
@@ -221,6 +221,8 @@ def build_consolidated_mapping_set(
221
221
  ``record_id``.
222
222
  mapping_set_metadata: ``mapping_set_id``/``curie_map``/``license``/...
223
223
  metadata for the resulting mapping set.
224
+ cardinality_on: ``"id"`` or ``"label"``, forwarded to
225
+ :meth:`~mapkgsutils.parsers.base.BaseMappingSet._compute_cardinalities`.
224
226
 
225
227
  Returns:
226
228
  A ``mapping_set_class`` instance with cardinalities computed.
@@ -243,7 +245,15 @@ def build_consolidated_mapping_set(
243
245
  # nothing to materialize into a real Mapping; date bookkeeping
244
246
  # for them still lives in the TSV cache, just not in the SSSOM.
245
247
  continue
248
+ # mapping_date and subject/object_source_version are semantically
249
+ # distinct SSSOM fields: a release version (e.g. ChEBI's "183") is
250
+ # never a date, and must never end up in mapping_date or vice versa.
251
+ # Override both from first_seen_*, not whatever the snapshot's own
252
+ # (last-seen) values were.
246
253
  row_fields["mapping_date"] = fields.get("first_seen_date") or None
254
+ first_seen_version = fields.get("first_seen_version") or None
255
+ row_fields["subject_source_version"] = first_seen_version
256
+ row_fields["object_source_version"] = first_seen_version
247
257
  row_fields["record_id"] = f"{record_namespace}{version_label}/consolidate/{pair_key}"
248
258
  mappings.append(SSSOMMapping(**row_fields))
249
259
 
@@ -257,7 +267,7 @@ def build_consolidated_mapping_set(
257
267
  mapping_set_description=mapping_set_metadata.get("mapping_set_description"),
258
268
  license=mapping_set_metadata.get("license"),
259
269
  )
260
- mapping_set.compute_cardinalities()
270
+ mapping_set._compute_cardinalities(on=cardinality_on)
261
271
  return mapping_set
262
272
 
263
273
 
@@ -268,6 +278,7 @@ def write_consolidated_sssom(
268
278
  mapping_set_class: type[Any],
269
279
  record_namespace: str,
270
280
  mapping_set_metadata: Mapping[str, Any],
281
+ cardinality_on: str = "id",
271
282
  ) -> Path:
272
283
  """Build and save the companion SSSOM mapping set next to the cache file.
273
284
 
@@ -278,6 +289,8 @@ def write_consolidated_sssom(
278
289
  record_namespace: Base IRI namespace for rebuilt ``record_id`` values.
279
290
  mapping_set_metadata: ``mapping_set_id``/``curie_map``/``license``/...
280
291
  metadata for the resulting mapping set.
292
+ cardinality_on: ``"id"`` or ``"label"``, see
293
+ :func:`build_consolidated_mapping_set`.
281
294
 
282
295
  Returns:
283
296
  Path to the written SSSOM TSV (see :func:`sssom_output_path`).
@@ -290,6 +303,7 @@ def write_consolidated_sssom(
290
303
  mapping_set_class=mapping_set_class,
291
304
  record_namespace=record_namespace,
292
305
  mapping_set_metadata=mapping_set_metadata,
306
+ cardinality_on=cardinality_on,
293
307
  )
294
308
  output_path = sssom_output_path(cache_path)
295
309
  mapping_set.save("sssom", output_path)
@@ -341,9 +355,9 @@ def consolidate_by_release(
341
355
  try:
342
356
  mapping_set = run_one_version(v)
343
357
  release_date = resolve_release_date(v)
344
- # Empty (not the version label v) when no real release date is
345
- # resolvable -- v is a version, not a date, and isn't always
346
- # date-shaped (e.g. ChEBI's plain release numbers).
358
+ # Empty when no real release date resolves. v is a version, not
359
+ # a date, and isn't always date-shaped (e.g. ChEBI's plain
360
+ # release numbers), so don't store it as one.
347
361
  date_str = release_date.date().isoformat() if release_date else ""
348
362
 
349
363
  for m in mapping_set.mappings or []:
@@ -1,18 +1,13 @@
1
- """Disambiguate ambiguous secondary with context: label, id, and xref evidence.
1
+ """Disambiguate ambiguous mappings with context: label, id, and xref evidence.
2
2
 
3
3
  :class:`ContextSpec` describes a per-row piece of evidence that can
4
4
  be used to decide which of those two entities a given ambiguous cell actually
5
5
  means:
6
6
 
7
- * ``label``: an alias/synonym string.
7
+ * ``label``: a label string.
8
8
  * ``id`` : a related/foreign identifier string.
9
9
  * ``xref`` : a cross-reference token (e.g. an Ensembl ID) resolved through
10
10
  an independent :class:`XrefMapping` crosswalk table.
11
-
12
- All three are resolved the same way: confirm *secondary usage* (the evidence
13
- points to the mapping's target), confirm *primary usage* (the evidence points
14
- to the token's own current identity), or leave the cell unresolved. Every
15
- attempt can be recorded as a :class:`DecisionRecord` for an auditable TSV log.
16
11
  """
17
12
 
18
13
  from __future__ import annotations
@@ -35,8 +30,8 @@ class XrefRecord:
35
30
 
36
31
  Args:
37
32
  subject_id: The cross-reference token, e.g. ``"ENSG00000197471"``.
38
- object_id: The target primary id, e.g. ``"HGNC:11249"``.
39
- object_label: The target primary label, e.g. ``"SPN"``.
33
+ object_id: The target id, e.g. ``"HGNC:11249"``.
34
+ object_label: The target label, e.g. ``"SPN"``.
40
35
  predicate_id: The equivalence predicate, e.g. ``"skos:exactMatch"``.
41
36
  ``None`` when the source table carries no predicate column.
42
37
  """
@@ -93,8 +88,8 @@ def load_xref_mapping(
93
88
  Args:
94
89
  path: Path to the crosswalk file.
95
90
  subject_col: Column with the cross-reference token.
96
- object_col: Column with the target primary id.
97
- object_label_col: Column with the target primary label (optional).
91
+ object_col: Column with the target id.
92
+ object_label_col: Column with the target label (optional).
98
93
  predicate_col: Column with the equivalence predicate (optional).
99
94
  sep: Field delimiter.
100
95
 
@@ -257,13 +252,12 @@ def resolve_ambiguous_with_xref(
257
252
  """Attempt to resolve an ambiguous token using a cross-reference token.
258
253
 
259
254
  Args:
260
- token: The ambiguous label or ID (appears both as a current primary
261
- entry and as a secondary one pointing elsewhere).
255
+ token: The ambiguous label or ID.
262
256
  xref_token: The row's cross-reference token, e.g. an Ensembl ID.
263
- lkp: ``{secondary_token: resolved_token}`` lookup.
257
+ lkp: ``{token: resolved_token}`` lookup.
264
258
  xref_index: ``{xref_token: [XrefRecord, ...]}`` (see
265
259
  :meth:`XrefMapping.by_subject`).
266
- token_to_id: ``{primary_token: primary_id}``. ``None`` when *token*
260
+ token_to_id: ``{token: id}``. ``None`` when *token*
267
261
  is already a CURIE (ID mode).
268
262
  accepted_predicates: Equivalence predicates to accept. ``None``
269
263
  accepts any predicate.
@@ -48,7 +48,7 @@ class DistributionEra:
48
48
  format: str | None = None
49
49
  from_version: str | None = None
50
50
  to_version: str | None = None
51
- wayback: bool = False # declarative only for now -- no resolver yet
51
+ wayback: bool = False # declarative only for now, no resolver yet
52
52
 
53
53
 
54
54
  @dataclass
@@ -124,8 +124,6 @@ class DatasourceConfig:
124
124
  xref_sources: list[XrefSource] = field(default_factory=list)
125
125
  # Species this datasource publishes
126
126
  species: dict[str, Any] = field(default_factory=dict)
127
- # Genome assembly/build metadata.
128
- genome_build: dict[str, Any] = field(default_factory=dict)
129
127
  # Compound/entry subset this datasource publishes (e.g. ChEBI's
130
128
  # 3star/complete). Generic, config-driven counterpart to `species`.
131
129
  subset: dict[str, Any] = field(default_factory=dict)
@@ -299,7 +297,6 @@ def get_datasource_config(datasource_name: str, *, config_package: str) -> Datas
299
297
  distribution_eras=eras,
300
298
  xref_sources=xref_sources,
301
299
  species=raw.get("species") or {},
302
- genome_build=raw.get("genome_build") or {},
303
300
  subset=raw.get("subset") or {},
304
301
  mappingset_metadata=raw.get("mappingset", {}),
305
302
  mapping_metadata=raw.get("mapping", {}),
@@ -507,16 +504,16 @@ def pair_hash(pri: str, sec: str) -> str:
507
504
  """Version-independent 16-hex-char digest for a (pri, sec) pair.
508
505
 
509
506
  The same pair always hashes identically, regardless of release/version
510
- or product (species/subset). This is the join key a cross-release
511
- consolidation layer uses to match a mapping across releases (to discover
512
- when it first/last appeared) -- *not* what ends up in the ``record_id``
513
- field (see :func:`mint_record_id`).
507
+ or product (species/subset). A cross-release consolidation layer uses
508
+ this as the join key to match a mapping across releases and discover
509
+ when it first/last appeared. It is not what ends up in the
510
+ ``record_id`` field; see :func:`mint_record_id` for that.
514
511
  """
515
512
  return hashlib.sha256(f"{pri}|{sec}".encode()).hexdigest()[:16]
516
513
 
517
514
 
518
515
  def mint_record_id(pri: str, sec: str, *, namespace: str) -> str:
519
- """Mint a row's ``record_id`` -- the row's OWL Axiom IRI in SSSOM's RDF/OWL output.
516
+ """Mint a row's ``record_id``, the row's OWL Axiom IRI in SSSOM's RDF/OWL output.
520
517
 
521
518
  Scoped to *namespace* (typically a release- and product-specific prefix,
522
519
  see :meth:`BaseParser._record_namespace`), so the same (pri, sec) pair
@@ -528,7 +525,7 @@ def mint_record_id(pri: str, sec: str, *, namespace: str) -> str:
528
525
  confidence, ...) under the same IRI.
529
526
 
530
527
  The trailing 16 hex characters are always :func:`pair_hash`'s
531
- version-independent digest -- use that function directly (not this one)
528
+ version-independent digest. Use that function directly, not this one,
532
529
  for cross-release matching/lookups.
533
530
  """
534
531
  return f"{namespace}{pair_hash(pri, sec)}"
@@ -1278,12 +1275,6 @@ class BaseParser(ABC):
1278
1275
  """
1279
1276
  self.version = version
1280
1277
  self.show_progress = show_progress
1281
- # Genome assembly/build for the SSSOM ``*_source_version`` fields.
1282
- # Default ``None`` -> resolved from config per species, or left to
1283
- # fall back to ``self.version``. Parsers that read a build straight
1284
- # from the data (e.g. an Ensembl-style ``mapping_session``) may set
1285
- # this, or override per mapping row.
1286
- self.genome_build: str | None = None
1287
1278
  # Release date of the source data, used for the SSSOM ``mapping_date``.
1288
1279
  # Set by the download layer to the upstream release date; falls back
1289
1280
  # to the version when that is an ISO date (e.g. quarterly
@@ -1370,7 +1361,7 @@ class BaseParser(ABC):
1370
1361
 
1371
1362
  ``None`` for most parsers (one release == one product). Override
1372
1363
  when a parser option selects a genuinely different dataset rather
1373
- than just a different output mode -- e.g. a species selector for a
1364
+ than just a different output mode, e.g. a species selector for a
1374
1365
  multi-species datasource, where the same release number produces a
1375
1366
  disjoint set of mappings per species. Folded into ``mapping_set_id``
1376
1367
  and :meth:`_record_namespace` so two runs that differ only in this
@@ -1384,8 +1375,8 @@ class BaseParser(ABC):
1384
1375
  Mirrors ``mapping_set_id``'s ``{base}/{version}/{slug}`` ordering
1385
1376
  (see :meth:`create_mapping_set`) so a mapping's ``record_id`` is
1386
1377
  scoped to the same release/product as the mapping *set* it's
1387
- asserted in -- use this (instead of reading
1388
- ``mapping_metadata()["record_id"]`` directly) when building
1378
+ asserted in. Use this, instead of reading
1379
+ ``mapping_metadata()["record_id"]`` directly, when building
1389
1380
  per-row ``record_id`` values.
1390
1381
  """
1391
1382
  base = str(self.get_mapping_metadata().get("record_id") or "")
@@ -1717,74 +1708,13 @@ class BaseParser(ABC):
1717
1708
  return str(self.version)
1718
1709
  return date.today().isoformat()
1719
1710
 
1720
- def _species_build(self) -> str | None:
1721
- """Return the configured genome build for this run's species, if any.
1722
-
1723
- Reads ``species.available[<species>].build`` from config.
1724
- ``self.species`` may be a canonical taxon ID (single-species runs)
1725
- or a datasource token (all-species runs), so both the ``available``
1726
- key and each entry's ``token`` are matched.
1727
-
1728
- Returns:
1729
- The per-species build string, or ``None`` when not configured.
1730
- """
1731
- cfg = self._config
1732
- species = getattr(self, "species", None)
1733
- if not cfg or species is None:
1734
- return None
1735
- available = (cfg.species or {}).get("available") or {}
1736
- entry = available.get(species) or available.get(str(species))
1737
- if entry is None:
1738
- for value in available.values():
1739
- if isinstance(value, dict) and str(value.get("token")) == str(species):
1740
- entry = value
1741
- break
1742
- if isinstance(entry, dict) and entry.get("build"):
1743
- return str(entry["build"])
1744
- return None
1745
-
1746
- def _genome_build(self) -> str | None:
1747
- """Resolve the genome assembly/build for the source-version fields.
1748
-
1749
- The mapping set's release is already explicit in
1750
- ``mapping_set_version`` / ``mapping_set_id``, so
1751
- ``subject_source_version`` / ``object_source_version`` carry the
1752
- genome build (e.g. ``"GRCh38"``) rather than repeating the release.
1753
- Resolution order:
1754
-
1755
- 1. an explicit :attr:`genome_build` override (e.g. a build a parser
1756
- discovered straight from the data);
1757
- 2. the per-species build from ``species.available[<species>].build``;
1758
- 3. the datasource-wide ``genome_build.default`` -- but *only* for
1759
- single-build datasources (those with no ``species.available`` map,
1760
- e.g. a human-only datasource). A multi-species datasource never
1761
- falls through to ``default``, so it can't stamp one build (say
1762
- GRCh38) onto a non-human species; an uncurated species is left to
1763
- fall back to the release instead.
1764
-
1765
- Returns:
1766
- The resolved build, or ``None`` when none is configured (e.g.
1767
- ChEBI), so callers fall back to :attr:`version`.
1768
- """
1769
- if self.genome_build:
1770
- return self.genome_build
1771
- species_build = self._species_build()
1772
- if species_build:
1773
- return species_build
1774
- cfg = self._config
1775
- if not cfg or (cfg.species or {}).get("available"):
1776
- return None
1777
- default = (cfg.genome_build or {}).get("default")
1778
- return str(default) if default else None
1779
-
1780
1711
  def _source_version(self) -> str | None:
1781
1712
  """Return the value for the set-level SSSOM ``*_source_version`` fields.
1782
1713
 
1783
- The genome build when one is resolvable (see :meth:`_genome_build`),
1784
- otherwise the release :attr:`version` (unchanged behaviour for
1785
- datasources without a genome build, e.g. ChEBI).
1714
+ Always the analyzed release, :attr:`version`. The same field means
1715
+ the same thing across every datasource's mapping set.
1786
1716
  """
1787
- return self._genome_build() or self.version
1717
+ return self.version
1788
1718
 
1789
1719
  def create_mapping_set(
1790
1720
  self,
@@ -12,7 +12,7 @@ __all__ = [
12
12
  "get_version",
13
13
  ]
14
14
 
15
- VERSION = "0.0.3"
15
+ VERSION = "0.0.4"
16
16
 
17
17
 
18
18
  def get_git_hash() -> str: