Skip to content

Commit df2bd2c

Browse files
committed
review / fix issues with updated inputs_v2
1 parent 2b7253c commit df2bd2c

28 files changed

Lines changed: 1380 additions & 499 deletions

docs/inputs-v2-direct-port-strategy.md

Lines changed: 223 additions & 0 deletions
Large diffs are not rendered by default.

docs/inputs-v2-legacy-adapter-inventory.md

Lines changed: 228 additions & 0 deletions
Large diffs are not rendered by default.

docs/inputs-v2-legacy-adapter-mapping.md

Lines changed: 203 additions & 0 deletions
Large diffs are not rendered by default.

pypath/inputs_v2/_measurements.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Source-neutral parsing of finite numeric observations and comparison bounds."""
2+
3+
from __future__ import annotations
4+
5+
import math
6+
import re
7+
8+
from biolink_model.datamodel.model import QuantityValue
9+
from omnipath_core.measurements import Measurement
10+
11+
_NUMBER = re.compile(
12+
r'\s*(<=|>=|<|>|=|~|≈)?\s*'
13+
r'([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)\s*'
14+
)
15+
_COMPARATORS = {None, '<', '>', '=', '<=', '>=', '~', '≈'}
16+
17+
18+
def measurement(
19+
value: object,
20+
unit: str | None = None,
21+
source_field: str | None = None,
22+
comparator: str | None = None,
23+
) -> Measurement | None:
24+
"""Parse a scalar without inferring its endpoint, units or biological meaning.
25+
26+
Invalid, non-finite and contradictory values omit only the measurement.
27+
Preserve inclusive/approximate bounds without replacing them by strict ones.
28+
"""
29+
if value is None:
30+
return None
31+
match = _NUMBER.fullmatch(str(value))
32+
if match is None:
33+
return None
34+
explicit = (
35+
(str(comparator).strip() or None) if comparator is not None else None
36+
)
37+
embedded = match.group(1)
38+
if explicit not in _COMPARATORS:
39+
return None
40+
if explicit and embedded and explicit != embedded:
41+
return None
42+
comparison = explicit or embedded
43+
number = float(match.group(2))
44+
if not math.isfinite(number):
45+
return None
46+
return Measurement(
47+
quantity=QuantityValue(
48+
has_numeric_value=number,
49+
has_unit=unit or None,
50+
has_binary_relation={
51+
'<': 'less_than',
52+
'>': 'greater_than',
53+
'=': 'equal_to',
54+
}.get(comparison),
55+
),
56+
source_field=source_field,
57+
comparator=comparison,
58+
)

pypath/inputs_v2/base.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,19 @@ def __call__(
238238
yield self.mapper(record)
239239

240240

241+
def _ontology_identifier_namespace(identifier, default):
242+
"""Keep imported CURIE namespaces; a document can contain foreign terms."""
243+
prefix, separator, _ = str(identifier).partition(':')
244+
if not separator:
245+
return default
246+
return {
247+
'GO': Namespace.GO, 'HP': Namespace.HPO, 'MONDO': Namespace.MONDO,
248+
'CHEMONTID': Namespace.CHEMONT, 'MI': Namespace.MI,
249+
'EC': Namespace.EC, 'OM': Namespace.OM,
250+
'UniProtKB-KW': Namespace.UNIPROT_KEYWORD,
251+
}.get(prefix, prefix.lower())
252+
253+
241254
def ontology_term_to_entity(
242255
term: OntologyTerm,
243256
*,
@@ -254,7 +267,7 @@ def ontology_term_to_entity(
254267
if not term.id or term.is_obsolete:
255268
return None
256269
identifiers = [
257-
Identifier(type=identifier_type, value=value)
270+
Identifier(type=_ontology_identifier_namespace(value, identifier_type), value=value)
258271
for value in dict.fromkeys([term.id, *(term.alt_ids or [])])
259272
if value
260273
]
@@ -293,7 +306,7 @@ def ontology_term_to_entity(
293306
relations.append(
294307
OntologyRelation(
295308
predicate=predicate,
296-
object=EntityRef(entity_type, identifier_type, target),
309+
object=EntityRef(entity_type, _ontology_identifier_namespace(target, identifier_type), target),
297310
ontology_id=ontology_id,
298311
)
299312
)

pypath/inputs_v2/bindingdb.py

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,15 @@
77

88
from __future__ import annotations
99

10-
import math
11-
import re
12-
1310
from biolink_model.datamodel.model import (
1411
ChemicalEntity,
1512
MacromolecularComplex,
1613
Protein,
17-
QuantityValue,
1814
slots,
1915
)
20-
from omnipath_core.measurements import Measurement
2116
from omnipath_core.naming import Namespace
2217

18+
from pypath.inputs_v2._measurements import measurement as _measurement
2319
from pypath.inputs_v2.base import Dataset, Download, Resource, ResourceConfig
2420
from pypath.inputs_v2.parsers.bindingdb import _raw
2521
from pypath.internals.cv_terms import LicenseCV, ResourceCv, UpdateCategoryCV
@@ -38,37 +34,6 @@
3834
)
3935

4036

41-
def _measurement(value, unit=None, source_field=None, comparator=None):
42-
"""Keep numeric source observations, units and comparison bounds together."""
43-
if value is None:
44-
return None
45-
match = re.fullmatch(
46-
'\\s*(<=|>=|<|>|=|~)?\\s*([+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?)\\s*',
47-
str(value),
48-
)
49-
if match is None:
50-
return None
51-
original_comparator = comparator or match.group(1)
52-
if original_comparator not in {None, '<', '>', '=', '<=', '>=', '~', '≈'}:
53-
return None
54-
relation = {'<': 'less_than', '>': 'greater_than', '=': 'equal_to'}.get(
55-
original_comparator
56-
)
57-
number = float(match.group(2))
58-
if not math.isfinite(number):
59-
return None
60-
quantity = QuantityValue(
61-
has_numeric_value=number,
62-
has_unit=unit or None,
63-
has_binary_relation=relation,
64-
)
65-
return Measurement(
66-
quantity=quantity,
67-
source_field=source_field,
68-
comparator=original_comparator,
69-
)
70-
71-
7237
def _bindingdb_url(dataset: str = 'All', **_kwargs: object) -> str:
7338
return f'https://bindingdb.org/rwd/bind/downloads/BindingDB_{dataset}_202605_tsv.zip'
7439

@@ -214,7 +179,7 @@ def _target(row):
214179
Identifier(type=Namespace.NAME, value=row.get('Target Name'))
215180
],
216181
membership=members,
217-
annotations=[],
182+
annotations=target_builder.annotations.build(row),
218183
)
219184

220185

@@ -300,7 +265,9 @@ def _target(row):
300265
),
301266
),
302267
),
303-
identifiers=IdentifiersBuilder(),
268+
identifiers=IdentifiersBuilder(
269+
CV(term=Namespace.BINDINGDB, value=f('BindingDB Reactant_set_id')),
270+
),
304271
)
305272
resource = Resource(
306273
config,

pypath/inputs_v2/cellphonedb.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,9 @@ def _type_selector(row: dict[str, Any]) -> type:
226226
),
227227
)
228228
),
229-
annotations=AnnotationsBuilder(),
229+
annotations=AnnotationsBuilder(
230+
CV(term=slots.in_taxon, value=f'NCBITaxon:{HUMAN_TAXON_ID}'),
231+
),
230232
)
231233

232234

pypath/inputs_v2/chembl.py

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from __future__ import annotations
99

1010
from functools import partial
11-
import math
1211
from pathlib import Path
1312
import re
1413

@@ -19,21 +18,20 @@
1918
ChemicalEntity,
2019
DirectionQualifierEnum,
2120
Gene,
22-
GeneFamily,
21+
ProteinFamily,
2322
MacromolecularComplex,
2423
MolecularActivity,
2524
MolecularEntity,
2625
NamedThing,
2726
NucleicAcidEntity,
2827
OrganismTaxon,
2928
Protein,
30-
QuantityValue,
3129
RNAProduct,
3230
slots,
3331
)
34-
from omnipath_core.measurements import Measurement
3532
from omnipath_core.naming import Namespace
3633

34+
from pypath.inputs_v2._measurements import measurement as _measurement
3735
from pypath.inputs_v2.base import Dataset, Download, Resource, ResourceConfig
3836
from pypath.inputs_v2.parsers.chembl import (
3937
activities_parser,
@@ -54,37 +52,6 @@
5452
from pypath.share import cache
5553

5654

57-
def _measurement(value, unit=None, source_field=None, comparator=None):
58-
"""Keep numeric source observations, units and comparison bounds together."""
59-
if value is None:
60-
return None
61-
match = re.fullmatch(
62-
'\\s*(<=|>=|<|>|=|~)?\\s*([+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?)\\s*',
63-
str(value),
64-
)
65-
if match is None:
66-
return None
67-
original_comparator = comparator or match.group(1)
68-
if original_comparator not in {None, '<', '>', '=', '<=', '>=', '~', '≈'}:
69-
return None
70-
relation = {'<': 'less_than', '>': 'greater_than', '=': 'equal_to'}.get(
71-
original_comparator
72-
)
73-
number = float(match.group(2))
74-
if not math.isfinite(number):
75-
return None
76-
quantity = QuantityValue(
77-
has_numeric_value=number,
78-
has_unit=unit or None,
79-
has_binary_relation=relation,
80-
)
81-
return Measurement(
82-
quantity=quantity,
83-
source_field=source_field,
84-
comparator=original_comparator,
85-
)
86-
87-
8855
VERSION = 36
8956
DB_REL_PATH = f'chembl_{VERSION}/chembl_{VERSION}_sqlite/chembl_{VERSION}.db'
9057
SQLITE_PATH = Path(cache.get_cachedir()) / f'ChEMBL_SQLite_{VERSION}.sqlite'
@@ -133,7 +100,7 @@ def _files_needed(version: int = VERSION, **_kwargs: object) -> list[str]:
133100
TARGET_TYPE_MAP = {
134101
'SINGLE PROTEIN': Protein,
135102
'PROTEIN COMPLEX': MacromolecularComplex,
136-
'PROTEIN FAMILY': GeneFamily,
103+
'PROTEIN FAMILY': ProteinFamily,
137104
'PROTEIN-PROTEIN INTERACTION': MolecularActivity,
138105
'SELECTIVITY GROUP': NamedThing,
139106
'NUCLEIC-ACID': NucleicAcidEntity,
@@ -333,6 +300,8 @@ def chembl_predicate(row):
333300
predicate=chembl_predicate,
334301
object=target_builder,
335302
annotations=AnnotationsBuilder(
303+
CV(term=slots.source_record_urls, value=f('assay_chembl_id', transform=lambda v: f'https://www.ebi.ac.uk/chembl/explore/assay/{v}')),
304+
CV(term=slots.source_record_urls, value=f('document_chembl_id', transform=lambda v: f'https://www.ebi.ac.uk/chembl/explore/document/{v}')),
336305
CV(
337306
term=slots.has_quantitative_value,
338307
value=f(
@@ -388,7 +357,10 @@ def chembl_predicate(row):
388357
),
389358
CV(term=slots.chembl_confidence_score, value=f('confidence_score')),
390359
),
391-
identifiers=IdentifiersBuilder(),
360+
identifiers=IdentifiersBuilder(
361+
CV(term=Namespace.CHEMBL_ACTIVITY, value=f('activity_id')),
362+
CV(term=Namespace.CHEMBL_MECHANISM, value=f('mec_id')),
363+
),
392364
)
393365
resource = Resource(
394366
config=config,

pypath/inputs_v2/drugcentral.py

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@
33
from __future__ import annotations
44

55
import csv
6-
import math
7-
import re
86

97
from biolink_model.datamodel.model import (
108
ChemicalEntity,
119
DirectionQualifierEnum,
1210
NamedThing,
1311
Protein,
14-
QuantityValue,
1512
slots,
1613
)
17-
from omnipath_core.measurements import Measurement
1814
from omnipath_core.naming import Namespace
1915

16+
from pypath.inputs_v2._measurements import measurement as _measurement
2017
from pypath.inputs_v2.base import Dataset, Download, Resource, ResourceConfig
2118
from pypath.inputs_v2.parsers.base import iter_tsv
2219
from pypath.internals.cv_terms import LicenseCV, ResourceCv, UpdateCategoryCV
@@ -35,38 +32,6 @@
3532
)
3633
from pypath.share.downloads import download_and_open
3734

38-
39-
def _measurement(value, unit=None, source_field=None, comparator=None):
40-
"""Keep numeric source observations, units and comparison bounds together."""
41-
if value is None:
42-
return None
43-
match = re.fullmatch(
44-
'\\s*(<=|>=|<|>|=|~)?\\s*([+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?)\\s*',
45-
str(value),
46-
)
47-
if match is None:
48-
return None
49-
original_comparator = comparator or match.group(1)
50-
if original_comparator not in {None, '<', '>', '=', '<=', '>=', '~', '≈'}:
51-
return None
52-
relation = {'<': 'less_than', '>': 'greater_than', '=': 'equal_to'}.get(
53-
original_comparator
54-
)
55-
number = float(match.group(2))
56-
if not math.isfinite(number):
57-
return None
58-
quantity = QuantityValue(
59-
has_numeric_value=number,
60-
has_unit=unit or None,
61-
has_binary_relation=relation,
62-
)
63-
return Measurement(
64-
quantity=quantity,
65-
source_field=source_field,
66-
comparator=original_comparator,
67-
)
68-
69-
7035
DRUGCENTRAL_STRUCTURES_URL = 'https://unmtid-shinyapps.net/download/DrugCentral/2021_09_01/structures.smiles.tsv'
7136
config = ResourceConfig(
7237
id=ResourceCv.DRUGCENTRAL,
@@ -101,7 +66,8 @@ def _clean(value):
10166

10267

10368
def _values(value):
104-
return [v.strip() for v in _clean(value).split('|') if v.strip()]
69+
# These lists are positionally aligned; dropping blanks changes identity.
70+
return [v.strip() for v in _clean(value).split('|')]
10571

10672

10773
def _taxon(row):
@@ -165,7 +131,7 @@ def _target_entity(row):
165131
type=Namespace.NAME, value=_clean(row.get('TARGET_NAME'))
166132
)
167133
],
168-
annotations=[],
134+
annotations=protein_builder.annotations.build(row),
169135
membership=[
170136
Membership(member=p, predicate=slots.has_member) for p in proteins
171137
],
@@ -204,6 +170,10 @@ def _predicate(row):
204170
),
205171
),
206172
CV(term=slots.description, value=f('ACT_COMMENT')),
173+
CV(term=slots.supporting_data_source, value=f('ACT_SOURCE')),
174+
CV(term=slots.supporting_data_source, value=f('MOA_SOURCE')),
175+
CV(term=slots.source_record_urls, value=f('ACT_SOURCE_URL')),
176+
CV(term=slots.source_record_urls, value=f('MOA_SOURCE_URL')),
207177
),
208178
identifiers=IdentifiersBuilder(),
209179
)

0 commit comments

Comments
 (0)