Skip to content

Commit d4078d5

Browse files
committed
resources: a resource description can declare which namespaces it mints
ResourceConfig gains a mints field: the identifier namespaces a resource is the minting authority for, distinct from namespaces its schema merely cross-references. Declared on ChEBI, HMDB, SwissLipids, LIPID MAPS, KEGG and ChEMBL -- the resources whose own accessions anchor a chemical identifier's key space. KEGG mints kegg_compound and kegg_reaction but not pubchem_compound: its conv/pubchem endpoint returns PubChem substance IDs, not compound IDs, even though the ingestion schema still tags that cross-reference as PUBCHEM_COMPOUND. The mints declaration states the intended authority independently of that extraction bug, which a separate fix corrects.
1 parent bb0ec11 commit d4078d5

8 files changed

Lines changed: 88 additions & 0 deletions

File tree

pypath/inputs_v2/base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ class ResourceConfig:
9797
short: str | None = None
9898
full: str | None = None
9999
synonyms: tuple[str, ...] = ()
100+
# The identifier namespaces this resource is the minting authority for —
101+
# not every namespace its schema tags data as. A resource can carry a
102+
# cross-reference to a namespace it does not mint (KEGG cites PubChem
103+
# identifiers). ChEBI is the one that mints `chebi`. Empty for a pure
104+
# consumer, which is the default and the common case.
105+
mints: tuple[IdentifierNamespaceCv, ...] = ()
100106

101107
def names(self) -> 'ResourceNames':
102108
"""Resolve this resource's (slug, short, full, synonyms).

pypath/inputs_v2/chebi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
pubmed='41312627',
4141
primary_category='small_molecules',
4242
annotation_ontologies=(OntologyCv.CHEBI,),
43+
mints=(IdentifierNamespaceCv.CHEBI,),
4344
description=(
4445
'ChEBI is a manually curated database and ontology of small molecular '
4546
'entities. This inputs_v2 module emits searchable small-molecule '

pypath/inputs_v2/chembl.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def _files_needed(version: int = VERSION, **_kwargs: object) -> list[str]:
7272
update_category=UpdateCategoryCV.REGULAR,
7373
pubmed='21948594',
7474
primary_category='interactions',
75+
mints=(IdentifierNamespaceCv.CHEMBL_COMPOUND,),
7576
description=(
7677
"ChEMBL is a manually curated chemical database of bioactive molecules "
7778
"with drug-like properties."

pypath/inputs_v2/hmdb.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
pubmed='34986597',
4747
primary_category='small_molecules',
4848
annotation_ontologies=(OntologyCv.CHEMONT,),
49+
mints=(IdentifierNamespaceCv.HMDB,),
4950
description=(
5051
'The Human Metabolome Database (HMDB) is a comprehensive database '
5152
'containing detailed information about small molecule metabolites '

pypath/inputs_v2/kegg.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ def _kegg_organism_codes(
124124
'compound participants parsed from reaction equations and enriched '
125125
'with EC, Rhea, KO, RCLASS, ChEBI, and PubChem identifiers.'
126126
),
127+
# KEGG mints its own compound and reaction accessions. It cites
128+
# PubChem identifiers via `conv/pubchem` but does not mint them.
129+
# That endpoint returns PubChem substance IDs, not compound IDs, so
130+
# `pubchem_compound` deliberately stays off this list. The schema
131+
# below still tags the cross-reference as PUBCHEM_COMPOUND. A
132+
# separate fix corrects that extraction, not this declaration.
133+
mints=(
134+
IdentifierNamespaceCv.KEGG_COMPOUND,
135+
IdentifierNamespaceCv.KEGG_REACTION,
136+
),
127137
)
128138

129139
f = FieldConfig(

pypath/inputs_v2/lipidmaps.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
update_category=UpdateCategoryCV.REGULAR,
3737
pubmed='37855672',
3838
primary_category='lipids',
39+
mints=(IdentifierNamespaceCv.LIPIDMAPS,),
3940
description=(
4041
'The LIPID MAPS Structure Database (LMSD) is a comprehensive database '
4142
'of lipid structures, annotations, and cross-references. It contains '

pypath/inputs_v2/swisslipids.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
pubmed='25943471',
4141
primary_category='lipids',
4242
annotation_ontologies=(OntologyCv.SWISSLIPIDS,),
43+
mints=(IdentifierNamespaceCv.SWISSLIPIDS,),
4344
description=(
4445
'SwissLipids is a curated resource providing a framework for the '
4546
'annotation of mass spectrometry data. It provides over 750,000 lipid '

tests/test_resource_config.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Tests for the ``mints`` declaration on ``ResourceConfig``.
2+
3+
A resource description states which identifier namespaces it is the minting
4+
authority for. The authority relation (``identifier_authority`` in
5+
omnipath-build) is populated from this declaration, not inferred from what a
6+
resource's schema happens to tag data as — a resource can carry cross-
7+
references to a namespace without minting it.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
from pypath.inputs_v2.base import ResourceConfig
13+
from pypath.internals.cv_terms import (
14+
IdentifierNamespaceCv,
15+
LicenseCV,
16+
ResourceCv,
17+
UpdateCategoryCV,
18+
)
19+
20+
21+
def _config(**overrides) -> ResourceConfig:
22+
defaults = dict(
23+
id=ResourceCv.CHEBI,
24+
name='Test Resource',
25+
url='https://example.org',
26+
license=LicenseCV.CC_BY_4_0,
27+
update_category=UpdateCategoryCV.REGULAR,
28+
description='A resource used only to exercise the mints field.',
29+
)
30+
defaults.update(overrides)
31+
return ResourceConfig(**defaults)
32+
33+
34+
def test_mints_defaults_to_empty():
35+
"""A resource that declares nothing mints nothing — a pure consumer."""
36+
config = _config()
37+
assert config.mints == ()
38+
39+
40+
def test_mints_declares_the_namespaces_a_resource_is_authoritative_for():
41+
config = _config(mints=(IdentifierNamespaceCv.CHEBI,))
42+
assert config.mints == (IdentifierNamespaceCv.CHEBI,)
43+
44+
45+
def test_mints_holds_more_than_one_namespace():
46+
config = _config(
47+
mints=(
48+
IdentifierNamespaceCv.KEGG_COMPOUND,
49+
IdentifierNamespaceCv.KEGG_REACTION,
50+
),
51+
)
52+
assert IdentifierNamespaceCv.KEGG_COMPOUND in config.mints
53+
assert IdentifierNamespaceCv.KEGG_REACTION in config.mints
54+
55+
56+
def test_mints_does_not_claim_a_namespace_a_resource_only_cites():
57+
"""The regression this field exists to catch: KEGG cites PubChem
58+
59+
identifiers it does not mint. Declaring ``mints`` explicitly is what lets
60+
the build tell "this resource is the authority" apart from "this resource
61+
also carries a cross-reference".
62+
"""
63+
kegg = _config(
64+
id=ResourceCv.KEGG_METABOLIC,
65+
mints=(IdentifierNamespaceCv.KEGG_COMPOUND,),
66+
)
67+
assert IdentifierNamespaceCv.PUBCHEM_COMPOUND not in kegg.mints

0 commit comments

Comments
 (0)