pysec2pri 0.2.1__tar.gz → 0.2.3__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.
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/PKG-INFO +35 -13
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/README.md +33 -11
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/pyproject.toml +3 -3
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/api.py +6 -14
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/cli.py +19 -22
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/config/ensembl.yaml +1 -9
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/config/hgnc.yaml +1 -2
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/config/ncbi.yaml +1 -10
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/config/vgnc.yaml +0 -16
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/consolidate.py +67 -68
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/downloads/ensembl.py +12 -4
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/parsers/chebi.py +3 -4
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/parsers/hmdb.py +21 -36
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/parsers/vgnc.py +2 -3
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/version.py +1 -1
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/LICENSE +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/__init__.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/__main__.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/config/__init__.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/config/chebi.yaml +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/config/hmdb_metabolites.yaml +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/config/hmdb_proteins.yaml +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/config/uniprot.yaml +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/config/wikidata.yaml +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/constants.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/download.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/downloads/__init__.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/downloads/chebi.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/downloads/hgnc.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/downloads/hmdb.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/downloads/ncbi.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/downloads/uniprot.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/exports.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/logging.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/parsers/__init__.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/parsers/base.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/parsers/ensembl.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/parsers/hgnc.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/parsers/ncbi.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/parsers/uniprot.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/parsers/wikidata.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/py.typed +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/queries/__init__.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/queries/chemical_redirects.rq +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/queries/chemical_redirects_test.rq +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/queries/gene_redirects.rq +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/queries/gene_redirects_test.rq +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/queries/protein_redirects.rq +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/queries/protein_redirects_test.rq +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/update_ids.py +0 -0
- {pysec2pri-0.2.1 → pysec2pri-0.2.3}/src/pysec2pri/xref.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pysec2pri
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Secondary to primary identifier mapping
|
|
5
5
|
Keywords: snekpack,cookiecutter
|
|
6
6
|
Author: Javier Millán Acosta
|
|
@@ -40,7 +40,7 @@ Requires-Dist: numpy
|
|
|
40
40
|
Requires-Dist: rdkit
|
|
41
41
|
Requires-Dist: polars-runtime-32
|
|
42
42
|
Requires-Dist: sssom-schema==1.1.0a5
|
|
43
|
-
Requires-Dist: mapkgsutils
|
|
43
|
+
Requires-Dist: mapkgsutils==0.1.1
|
|
44
44
|
Maintainer: Javier Millán Acosta
|
|
45
45
|
Maintainer-email: Javier Millán Acosta <javier.millan.acosta@gmail.com>
|
|
46
46
|
Requires-Python: >=3.10
|
|
@@ -96,20 +96,33 @@ uv pip install git+https://github.com/jmillanacosta/pysec2pri.git
|
|
|
96
96
|
|
|
97
97
|
### Generating mapping sets
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Mapping sets can be generated from bash:
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
|
|
102
|
+
# ID mapping sets
|
|
103
|
+
pysec2pri chebi ids (--help)
|
|
104
|
+
pysec2pri ensembl ids (--help)
|
|
105
|
+
pysec2pri hgnc ids (--help)
|
|
106
|
+
pysec2pri vgnc ids (--help)
|
|
107
|
+
pysec2pri ncbi ids (--help)
|
|
108
|
+
pysec2pri hmdb-protein ids (--help)
|
|
109
|
+
pysec2pri hmdb-gene ids (--help)
|
|
110
|
+
pysec2pri uniprot ids (--help)
|
|
111
|
+
# Label mapping sets
|
|
112
|
+
pysec2pri chebi labels (--help)
|
|
113
|
+
pysec2pri hgnc labels (--help)
|
|
114
|
+
pysec2pri ensembl labels (--help)
|
|
115
|
+
pysec2pri vgnc labels (--help)
|
|
103
116
|
```
|
|
104
117
|
|
|
105
|
-
|
|
106
|
-
mapping file in your current directory.
|
|
118
|
+
Or python:
|
|
107
119
|
|
|
108
|
-
To process local files and specify the output:
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
pysec2pri chebi ids ChEBI_complete_3star.sdf --output my_mappings.sssom.tsv
|
|
112
120
|
```
|
|
121
|
+
from pysec2pri import generate_ensembl_labels, generate_ensembl
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Replacing Ensembl by the supported database. These functions return either a
|
|
125
|
+
`IdMappingSet` or `LabelMappingSet`, SSSOM `MappingSet`s.
|
|
113
126
|
|
|
114
127
|
For more options and help on any command:
|
|
115
128
|
|
|
@@ -122,14 +135,23 @@ The default output is in [SSSOM](https://mapping-commons.github.io/sssom/)
|
|
|
122
135
|
|
|
123
136
|
### Updating IDs and labels
|
|
124
137
|
|
|
125
|
-
A generated mapping set can be used to update IDs and labels in Python:
|
|
138
|
+
A generated mapping set can be used to update IDs and labels in Python: ChEBI
|
|
139
|
+
synonyms:
|
|
126
140
|
|
|
127
141
|
```python
|
|
128
142
|
from pysec2pri import generate_chebi_synonyms, resolve_labels
|
|
129
|
-
|
|
143
|
+
chebi_ms = generate_chebi_synonyms()
|
|
130
144
|
resolve_labels(["Glucose", "ATP", "Guanine"], cs)
|
|
131
145
|
```
|
|
132
146
|
|
|
147
|
+
Ensembl gene identifiers in a dataframe:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from pysec2pri import update_ids, generate_ensembl
|
|
151
|
+
ens_ms = generate_ensembl(version="115", species="9606")
|
|
152
|
+
df_with_new_column = update_ids(mapping_set=ens_ms, ids = df, at="Ensembl_id") # `at` is the name of the column
|
|
153
|
+
```
|
|
154
|
+
|
|
133
155
|
Or from the command line, given a TSV file `gene_ex.tsv`:
|
|
134
156
|
|
|
135
157
|
```
|
|
@@ -212,7 +234,7 @@ mappings.
|
|
|
212
234
|
### Aliases / synonyms
|
|
213
235
|
|
|
214
236
|
Alias mappings use `oboInOwl:hasExactSynonym`. The alias is the `subject_label`
|
|
215
|
-
and the
|
|
237
|
+
and the primary name is the `object_label`/`object_id`.
|
|
216
238
|
|
|
217
239
|
```mermaid
|
|
218
240
|
flowchart LR
|
|
@@ -43,20 +43,33 @@ uv pip install git+https://github.com/jmillanacosta/pysec2pri.git
|
|
|
43
43
|
|
|
44
44
|
### Generating mapping sets
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Mapping sets can be generated from bash:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
|
|
49
|
+
# ID mapping sets
|
|
50
|
+
pysec2pri chebi ids (--help)
|
|
51
|
+
pysec2pri ensembl ids (--help)
|
|
52
|
+
pysec2pri hgnc ids (--help)
|
|
53
|
+
pysec2pri vgnc ids (--help)
|
|
54
|
+
pysec2pri ncbi ids (--help)
|
|
55
|
+
pysec2pri hmdb-protein ids (--help)
|
|
56
|
+
pysec2pri hmdb-gene ids (--help)
|
|
57
|
+
pysec2pri uniprot ids (--help)
|
|
58
|
+
# Label mapping sets
|
|
59
|
+
pysec2pri chebi labels (--help)
|
|
60
|
+
pysec2pri hgnc labels (--help)
|
|
61
|
+
pysec2pri ensembl labels (--help)
|
|
62
|
+
pysec2pri vgnc labels (--help)
|
|
50
63
|
```
|
|
51
64
|
|
|
52
|
-
|
|
53
|
-
mapping file in your current directory.
|
|
65
|
+
Or python:
|
|
54
66
|
|
|
55
|
-
To process local files and specify the output:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
pysec2pri chebi ids ChEBI_complete_3star.sdf --output my_mappings.sssom.tsv
|
|
59
67
|
```
|
|
68
|
+
from pysec2pri import generate_ensembl_labels, generate_ensembl
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Replacing Ensembl by the supported database. These functions return either a
|
|
72
|
+
`IdMappingSet` or `LabelMappingSet`, SSSOM `MappingSet`s.
|
|
60
73
|
|
|
61
74
|
For more options and help on any command:
|
|
62
75
|
|
|
@@ -69,14 +82,23 @@ The default output is in [SSSOM](https://mapping-commons.github.io/sssom/)
|
|
|
69
82
|
|
|
70
83
|
### Updating IDs and labels
|
|
71
84
|
|
|
72
|
-
A generated mapping set can be used to update IDs and labels in Python:
|
|
85
|
+
A generated mapping set can be used to update IDs and labels in Python: ChEBI
|
|
86
|
+
synonyms:
|
|
73
87
|
|
|
74
88
|
```python
|
|
75
89
|
from pysec2pri import generate_chebi_synonyms, resolve_labels
|
|
76
|
-
|
|
90
|
+
chebi_ms = generate_chebi_synonyms()
|
|
77
91
|
resolve_labels(["Glucose", "ATP", "Guanine"], cs)
|
|
78
92
|
```
|
|
79
93
|
|
|
94
|
+
Ensembl gene identifiers in a dataframe:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from pysec2pri import update_ids, generate_ensembl
|
|
98
|
+
ens_ms = generate_ensembl(version="115", species="9606")
|
|
99
|
+
df_with_new_column = update_ids(mapping_set=ens_ms, ids = df, at="Ensembl_id") # `at` is the name of the column
|
|
100
|
+
```
|
|
101
|
+
|
|
80
102
|
Or from the command line, given a TSV file `gene_ex.tsv`:
|
|
81
103
|
|
|
82
104
|
```
|
|
@@ -159,7 +181,7 @@ mappings.
|
|
|
159
181
|
### Aliases / synonyms
|
|
160
182
|
|
|
161
183
|
Alias mappings use `oboInOwl:hasExactSynonym`. The alias is the `subject_label`
|
|
162
|
-
and the
|
|
184
|
+
and the primary name is the `object_label`/`object_id`.
|
|
163
185
|
|
|
164
186
|
```mermaid
|
|
165
187
|
flowchart LR
|
|
@@ -7,7 +7,7 @@ build-backend = "uv_build"
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "pysec2pri"
|
|
10
|
-
version = "0.2.
|
|
10
|
+
version = "0.2.3"
|
|
11
11
|
description = "Secondary to primary identifier mapping"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
authors = [
|
|
@@ -71,7 +71,7 @@ dependencies = [
|
|
|
71
71
|
"rdkit", # Chemistry toolkit for SDF parsing
|
|
72
72
|
"polars-runtime-32", # Polars runtime for 32-bit systems
|
|
73
73
|
"sssom-schema==1.1.0a5", # SSSOM schema for validation
|
|
74
|
-
"mapkgsutils
|
|
74
|
+
"mapkgsutils==0.1.1",
|
|
75
75
|
]
|
|
76
76
|
|
|
77
77
|
# see https://peps.python.org/pep-0735/ and https://docs.astral.sh/uv/concepts/dependencies/#dependency-groups
|
|
@@ -255,7 +255,7 @@ known-first-party = [
|
|
|
255
255
|
docstring-code-format = true
|
|
256
256
|
|
|
257
257
|
[tool.bumpversion]
|
|
258
|
-
current_version = "0.2.
|
|
258
|
+
current_version = "0.2.3"
|
|
259
259
|
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-]+)*))?"
|
|
260
260
|
serialize = [
|
|
261
261
|
"{major}.{minor}.{patch}-{release}+{build}",
|
|
@@ -244,8 +244,7 @@ def generate_hgnc_primary_ids(
|
|
|
244
244
|
Only the HGNC complete set file is downloaded/read. The returned mapping
|
|
245
245
|
set has an empty ``mappings`` list; its ``_primary_ids`` store is
|
|
246
246
|
populated with every current HGNC ID so that ``to_pri_ids()`` produces
|
|
247
|
-
the
|
|
248
|
-
happen to have an associated secondary.
|
|
247
|
+
the complete list.
|
|
249
248
|
|
|
250
249
|
Args:
|
|
251
250
|
input_path: Local HGNC complete set TSV. Auto-downloaded if ``None``.
|
|
@@ -1001,7 +1000,7 @@ def generate_ensembl_labels(
|
|
|
1001
1000
|
species: Canonical NCBI taxon ID, or ``"all"`` (default) to process
|
|
1002
1001
|
every species Ensembl publishes and combine them into one
|
|
1003
1002
|
mapping set -- see :func:`_generate_ensembl_all_species`. A
|
|
1004
|
-
symbol shared by two species becomes
|
|
1003
|
+
symbol shared by two species becomes ambiguous in
|
|
1005
1004
|
that combined output (there's no longer a per-species scope to
|
|
1006
1005
|
disambiguate it). This is network-heavy (~276 species); pass
|
|
1007
1006
|
an explicit taxon ID for a fast, single-species run.
|
|
@@ -1112,9 +1111,7 @@ def generate_ensembl_label_history(
|
|
|
1112
1111
|
previous-symbol -> current-symbol transitions are derived by diffing
|
|
1113
1112
|
each release's primary-label snapshot per stable ID. Delegates to
|
|
1114
1113
|
:func:`pysec2pri.consolidate.build_label_history`, which walks every
|
|
1115
|
-
historical release (or a bounded range) and resumes on re-run.
|
|
1116
|
-
a network-heavy, run-on-demand operation, not part of normal
|
|
1117
|
-
single-release generation.
|
|
1114
|
+
historical release (or a bounded range) and resumes on re-run.
|
|
1118
1115
|
|
|
1119
1116
|
Args:
|
|
1120
1117
|
species: Canonical NCBI taxon ID (default ``9606`` for human).
|
|
@@ -2015,15 +2012,10 @@ def crosswalk(
|
|
|
2015
2012
|
) -> dict[str, str] | pd.DataFrame:
|
|
2016
2013
|
r"""Map a gene identifier from one vocabulary to another, via HGNC.
|
|
2017
2014
|
|
|
2018
|
-
|
|
2019
|
-
resolves through :func:`generate_hgnc_labels` +
|
|
2015
|
+
Wrapper: ``frm="symbol"`` resolves through :func:`generate_hgnc_labels` +
|
|
2020
2016
|
:func:`~pysec2pri.update_ids.update_labels`, so a *previous* HGNC symbol
|
|
2021
|
-
still resolves to its current identity (the temporal aspect) and
|
|
2022
|
-
|
|
2023
|
-
guessed. ``frm`` in ``"ensembl"``, ``"entrez"``, ``"refseq"``, or
|
|
2024
|
-
``"uniprot"`` resolves through HGNC's own cross-reference crosswalk
|
|
2025
|
-
table, downloaded from the ``xref_sources`` declared in
|
|
2026
|
-
``config/hgnc.yaml`` unless *xref_mapping* is supplied explicitly.
|
|
2017
|
+
still resolves to its current identity (the temporal aspect) and an ambiguous
|
|
2018
|
+
label is left blank and reported rather than guessed.
|
|
2027
2019
|
|
|
2028
2020
|
Args:
|
|
2029
2021
|
input_data: A single identifier string, a list of identifier
|
|
@@ -464,7 +464,7 @@ def ensembl_label_history_cmd(
|
|
|
464
464
|
Ensembl's core schema has no previous-gene-symbol table, so this walks
|
|
465
465
|
every historical release (or a bounded --from-version/--to-version
|
|
466
466
|
range) and diffs each release's primary-label snapshot to recover
|
|
467
|
-
|
|
467
|
+
true previous -> current symbol transitions. Network-heavy and
|
|
468
468
|
resumable; run on demand, not part of normal mapping generation.
|
|
469
469
|
"""
|
|
470
470
|
from pysec2pri.api import generate_ensembl_label_history
|
|
@@ -638,11 +638,11 @@ def _make_consolidate_cmd(cfg_id: str, extra_opts: list[Callable[..., Any]]) ->
|
|
|
638
638
|
"""Return a decorated (but not yet click.command-wrapped) ``consolidate`` callable."""
|
|
639
639
|
|
|
640
640
|
def _cmd(
|
|
641
|
-
mode: str,
|
|
642
641
|
mapping_sets: str,
|
|
643
642
|
cache_dir: Path | None,
|
|
644
643
|
force: bool,
|
|
645
644
|
no_progress: bool,
|
|
645
|
+
output: Path | None,
|
|
646
646
|
**extra_kwargs: Any,
|
|
647
647
|
) -> None:
|
|
648
648
|
from pysec2pri.consolidate import _sssom_output_path, consolidate_mapping_dates
|
|
@@ -650,34 +650,23 @@ def _make_consolidate_cmd(cfg_id: str, extra_opts: list[Callable[..., Any]]) ->
|
|
|
650
650
|
extras = ", ".join(f"{k}={v}" for k, v in extra_kwargs.items() if v is not None)
|
|
651
651
|
click.echo(
|
|
652
652
|
f"Consolidating {cfg_id.upper()} mapping dates "
|
|
653
|
-
f"(
|
|
653
|
+
f"({extras + ', ' if extras else ''}{mapping_sets})..."
|
|
654
654
|
)
|
|
655
655
|
try:
|
|
656
656
|
path, _ = consolidate_mapping_dates(
|
|
657
657
|
cfg_id,
|
|
658
|
-
mode=mode,
|
|
659
658
|
cache_dir=cache_dir,
|
|
660
659
|
mapping_sets=mapping_sets,
|
|
661
660
|
show_progress=not no_progress,
|
|
662
661
|
force=force,
|
|
662
|
+
output=output,
|
|
663
663
|
**extra_kwargs,
|
|
664
664
|
)
|
|
665
665
|
except ValueError as exc:
|
|
666
666
|
raise click.ClickException(str(exc)) from exc
|
|
667
|
-
click.echo(f"Wrote consolidated mapping set -> {_sssom_output_path(path)}")
|
|
667
|
+
click.echo(f"Wrote consolidated mapping set -> {output or _sssom_output_path(path)}")
|
|
668
668
|
|
|
669
669
|
decorators: list[Callable[..., Any]] = [
|
|
670
|
-
click.option(
|
|
671
|
-
"--mode",
|
|
672
|
-
default="release",
|
|
673
|
-
show_default=True,
|
|
674
|
-
type=click.Choice(["release", "date"]),
|
|
675
|
-
help=(
|
|
676
|
-
"'release': walk every historical release for the first-seen version/date. "
|
|
677
|
-
"'date': single pass using the source's own per-row date (falls back to "
|
|
678
|
-
"'release' with a warning if the source has none)."
|
|
679
|
-
),
|
|
680
|
-
),
|
|
681
670
|
*extra_opts,
|
|
682
671
|
click.option(
|
|
683
672
|
"--mapping-sets",
|
|
@@ -693,6 +682,13 @@ def _make_consolidate_cmd(cfg_id: str, extra_opts: list[Callable[..., Any]]) ->
|
|
|
693
682
|
default=False,
|
|
694
683
|
help="Re-scan every release, ignoring resume state.",
|
|
695
684
|
),
|
|
685
|
+
click.option(
|
|
686
|
+
"-o",
|
|
687
|
+
"--output",
|
|
688
|
+
type=PathType,
|
|
689
|
+
default=None,
|
|
690
|
+
help="Also write the consolidated SSSOM mapping set to this path.",
|
|
691
|
+
),
|
|
696
692
|
_opt_no_progress,
|
|
697
693
|
]
|
|
698
694
|
for dec in reversed(decorators):
|
|
@@ -718,12 +714,13 @@ def _register_consolidate_commands(parent: click.Group) -> None:
|
|
|
718
714
|
"Build/update the per-mapping first-seen-date index, written as a real "
|
|
719
715
|
"SSSOM mapping set whose mapping_date is each mapping's date of first "
|
|
720
716
|
"appearance.\n\n"
|
|
721
|
-
"
|
|
722
|
-
"
|
|
723
|
-
"
|
|
724
|
-
"manual/one-off operation, not
|
|
725
|
-
"
|
|
726
|
-
"
|
|
717
|
+
"For datasources with a versioned archive (ChEBI, Ensembl, HGNC, "
|
|
718
|
+
"UniProt), walks every historical release once to discover the earliest "
|
|
719
|
+
"release each mapping appeared in. This is slow and network-heavy (~250 "
|
|
720
|
+
"releases for ChEBI); meant to be run as a manual/one-off operation, not "
|
|
721
|
+
"as part of normal mapping generation. For datasources without one "
|
|
722
|
+
"(NCBI, VGNC), a single current-release pass captures the source's own "
|
|
723
|
+
"per-row date directly (fast)."
|
|
727
724
|
),
|
|
728
725
|
)(raw_cmd)
|
|
729
726
|
)
|
|
@@ -7,13 +7,6 @@ prefix: "ENSEMBL"
|
|
|
7
7
|
curie_base_url: "http://identifiers.org/ensembl/"
|
|
8
8
|
entity_types: ["genes"]
|
|
9
9
|
|
|
10
|
-
# Canonical key = NCBI taxon ID. `token` is Ensembl's own species directory
|
|
11
|
-
# component (e.g. https://ftp.ensembl.org/pub/release-115/mysql/homo_sapiens_core_115_38/).
|
|
12
|
-
# `default: "all"` processes every species Ensembl publishes for the
|
|
13
|
-
# release and combines them into one mapping set (see
|
|
14
|
-
# pysec2pri.api._generate_ensembl_all_species) -- network-heavy (~276
|
|
15
|
-
# species, one download each); pass an explicit taxon ID for a fast,
|
|
16
|
-
# single-species run.
|
|
17
10
|
species:
|
|
18
11
|
default: "all"
|
|
19
12
|
available:
|
|
@@ -511,5 +504,4 @@ xref_sources:
|
|
|
511
504
|
refseq: "RefSeq IDs"
|
|
512
505
|
uniprot: "UniProt ID(supplied by UniProt)"
|
|
513
506
|
note:
|
|
514
|
-
"Source of truth for cross-references varies; this is a suggested default
|
|
515
|
-
not authoritative."
|
|
507
|
+
"Source of truth for cross-references varies; this is a suggested default."
|
|
@@ -142,8 +142,7 @@ xref_sources:
|
|
|
142
142
|
refseq: "RefSeq IDs"
|
|
143
143
|
uniprot: "UniProt ID(supplied by UniProt)"
|
|
144
144
|
note:
|
|
145
|
-
"Source of truth for cross-references varies; this is a suggested default
|
|
146
|
-
not authoritative."
|
|
145
|
+
"Source of truth for cross-references varies; this is a suggested default"
|
|
147
146
|
|
|
148
147
|
mapping_sets:
|
|
149
148
|
ids:
|
|
@@ -9,14 +9,6 @@ prefix: "NCBIGene"
|
|
|
9
9
|
curie_base_url: "http://identifiers.org/ncbigene/"
|
|
10
10
|
entity_types: ["genes"]
|
|
11
11
|
|
|
12
|
-
# NCBI's own token IS the canonical taxon ID (no translation needed, unlike
|
|
13
|
-
# Ensembl) -- gene_history/gene_info are filtered directly on #tax_id, which
|
|
14
|
-
# accepts ANY taxon ID, not just the ones listed here. `available` exists
|
|
15
|
-
# only to drive CLI help/choices for common species; NCBIParser never calls
|
|
16
|
-
# species_token(), so it never restricts which taxon IDs are accepted.
|
|
17
|
-
# `default: "all"` processes every organism in the file instead of
|
|
18
|
-
# filtering to one (see NCBIParser.ALL_SPECIES) -- cheap here since
|
|
19
|
-
# gene_info/gene_history are single global files already being downloaded.
|
|
20
12
|
species:
|
|
21
13
|
default: "all"
|
|
22
14
|
available:
|
|
@@ -173,5 +165,4 @@ xref_sources:
|
|
|
173
165
|
refseq: "RefSeq IDs"
|
|
174
166
|
uniprot: "UniProt ID(supplied by UniProt)"
|
|
175
167
|
note:
|
|
176
|
-
"Source of truth for cross-references varies; this is a suggested default
|
|
177
|
-
not authoritative."
|
|
168
|
+
"Source of truth for cross-references varies; this is a suggested default"
|
|
@@ -7,22 +7,6 @@ prefix: "VGNC"
|
|
|
7
7
|
curie_base_url: "http://identifiers.org/vgnc/"
|
|
8
8
|
entity_types: ["genes"]
|
|
9
9
|
|
|
10
|
-
# VGNC (Vertebrate Gene Nomenclature Committee) names genes for vertebrate
|
|
11
|
-
# species that lack their own nomenclature committee (human/mouse/rat are
|
|
12
|
-
# deliberately out of scope -- HGNC/MGI/RGD cover those). Unlike Ensembl
|
|
13
|
-
# (one download URL per species), VGNC publishes a single global TSV per
|
|
14
|
-
# file with a `taxon_id` column, filtered the same way NCBI's
|
|
15
|
-
# gene_info/gene_history are filtered by `#tax_id` -- see VGNCParser.
|
|
16
|
-
# `default: "all"` processes every species together (see
|
|
17
|
-
# VGNCParser.ALL_SPECIES) -- cheap for ids (the global withdrawn set is
|
|
18
|
-
# always parsed in full regardless), heavier for labels (a symbol shared
|
|
19
|
-
# by two species becomes genuinely ambiguous with no per-species scope to
|
|
20
|
-
# disambiguate it). VGNCParser never calls species_token(), so `available`
|
|
21
|
-
# exists only to drive CLI help/choices and never restricts which taxon IDs
|
|
22
|
-
# are accepted. Taxon IDs below were verified against the live data (each
|
|
23
|
-
# species' own vgnc/tsv/<species>/*_vgnc_gene_set_All.tsv, cross-referenced
|
|
24
|
-
# by VGNC ID against vgnc/tsv/all/all_vgnc_gene_set_All.tsv's taxon_id
|
|
25
|
-
# column).
|
|
26
10
|
species:
|
|
27
11
|
default: "all"
|
|
28
12
|
available:
|
|
@@ -40,7 +40,6 @@ import json
|
|
|
40
40
|
import os
|
|
41
41
|
import shutil
|
|
42
42
|
import tempfile
|
|
43
|
-
import warnings
|
|
44
43
|
from collections.abc import Callable, Iterable
|
|
45
44
|
from pathlib import Path
|
|
46
45
|
from typing import Any
|
|
@@ -60,7 +59,7 @@ __all__ = [
|
|
|
60
59
|
]
|
|
61
60
|
|
|
62
61
|
# Datasources this module knows how to download+parse per release.
|
|
63
|
-
# NCBI and VGNC have no versioned archive, so
|
|
62
|
+
# NCBI and VGNC have no versioned archive, so they take the single-parse path.
|
|
64
63
|
SUPPORTED_DATASOURCES = ("chebi", "ensembl", "hgnc", "ncbi", "uniprot", "vgnc")
|
|
65
64
|
|
|
66
65
|
# mapping_sets kinds each datasource's parser actually supports.
|
|
@@ -100,7 +99,8 @@ def _uniprot_versions(**kwargs: Any) -> list[str]:
|
|
|
100
99
|
return UniProtDownloader().list_versions()
|
|
101
100
|
|
|
102
101
|
|
|
103
|
-
# Datasources with a versioned archive that can be walked
|
|
102
|
+
# Datasources with a versioned archive that can be walked release by release.
|
|
103
|
+
# Datasources absent here (NCBI, VGNC) take the single-parse path instead.
|
|
104
104
|
_LIST_VERSIONS_FNS: dict[str, Callable[..., list[str]]] = {
|
|
105
105
|
"chebi": _chebi_versions,
|
|
106
106
|
"ensembl": _ensembl_versions,
|
|
@@ -161,6 +161,12 @@ _read_meta = _consolidate.read_meta
|
|
|
161
161
|
_write_meta = _consolidate.write_meta
|
|
162
162
|
|
|
163
163
|
|
|
164
|
+
def _save_optional_output(mapping_set: BaseMappingSet, output: Path | None) -> None:
|
|
165
|
+
"""Write *mapping_set* to *output* as SSSOM when an output path is given."""
|
|
166
|
+
if output is not None:
|
|
167
|
+
mapping_set.save("sssom", output)
|
|
168
|
+
|
|
169
|
+
|
|
164
170
|
def load_mapping_dates(
|
|
165
171
|
datasource: str,
|
|
166
172
|
cache_dir: Path | None = None,
|
|
@@ -382,48 +388,48 @@ def _write_consolidated_sssom(
|
|
|
382
388
|
def consolidate_mapping_dates(
|
|
383
389
|
datasource: str,
|
|
384
390
|
*,
|
|
385
|
-
mode: str = "release",
|
|
386
391
|
cache_dir: Path | None = None,
|
|
387
392
|
mapping_sets: str = "ids",
|
|
388
393
|
show_progress: bool = True,
|
|
389
394
|
force: bool = False,
|
|
395
|
+
output: Path | None = None,
|
|
390
396
|
**kwargs: Any,
|
|
391
397
|
) -> tuple[Path, BaseMappingSet]:
|
|
392
398
|
"""Build/update the first-seen-date index for *datasource*.
|
|
393
399
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
companion real SSSOM mapping set (see :func:`_sssom_output_path`) where
|
|
414
|
-
every row's ``mapping_date`` is its true first-seen date, rather than
|
|
415
|
-
whichever release happened to be parsed last.
|
|
400
|
+
Collects every mapping using all available provenance. The walk shape is
|
|
401
|
+
chosen automatically by data availability, not by a requested mode:
|
|
402
|
+
|
|
403
|
+
- Datasources **with** a versioned archive (ChEBI, Ensembl, HGNC,
|
|
404
|
+
UniProt): walk every historical release once (oldest first, resuming
|
|
405
|
+
from the last completed version unless *force*). For every mapping
|
|
406
|
+
seen, records the version/date it first appeared and keeps bumping the
|
|
407
|
+
version/date it was last seen. Slow and network-heavy (~250 releases
|
|
408
|
+
for ChEBI); meant to be run manually/as a one-off.
|
|
409
|
+
- Datasources **without** one (NCBI, VGNC): a single current parse
|
|
410
|
+
caching the *full* mapping set — every mapping kept, stamped with its
|
|
411
|
+
own per-row ``mapping_date`` when the parser provides one and left
|
|
412
|
+
undated otherwise. Fast, no historical walk.
|
|
413
|
+
|
|
414
|
+
Alongside the internal cache file this also writes a companion real SSSOM
|
|
415
|
+
mapping set (see :func:`_sssom_output_path`) where each row's
|
|
416
|
+
``mapping_date`` is its per-row date when present, else its first-seen
|
|
417
|
+
release date — distinct from the first-seen release version carried in
|
|
418
|
+
``subject_source_version``/``object_source_version``.
|
|
416
419
|
|
|
417
420
|
Args:
|
|
418
421
|
datasource: One of :data:`SUPPORTED_DATASOURCES`.
|
|
419
|
-
mode: ``"release"`` or ``"date"``.
|
|
420
422
|
cache_dir: Directory to write the cache file. Defaults to
|
|
421
423
|
:func:`default_cache_dir`.
|
|
422
424
|
mapping_sets: ``"ids"`` or ``"labels"``.
|
|
423
425
|
show_progress: Whether to show a progress bar over releases
|
|
424
|
-
(
|
|
426
|
+
(versioned-archive datasources only).
|
|
425
427
|
force: Re-scan every release from scratch, ignoring any existing
|
|
426
|
-
cache/resume state (
|
|
428
|
+
cache/resume state (versioned-archive datasources only).
|
|
429
|
+
output: Optional path to also write the consolidated SSSOM mapping
|
|
430
|
+
set to. The internal cache-adjacent copy (see
|
|
431
|
+
:func:`_sssom_output_path`) is always written; when *output* is
|
|
432
|
+
given, the same full mapping set is additionally saved there.
|
|
427
433
|
**kwargs: Datasource-specific knobs (``subset`` for ChEBI, ``species``
|
|
428
434
|
for NCBI/Ensembl); ignored for datasources with no such config
|
|
429
435
|
block.
|
|
@@ -434,13 +440,9 @@ def consolidate_mapping_dates(
|
|
|
434
440
|
written alongside it) and the in-memory consolidated mapping set.
|
|
435
441
|
|
|
436
442
|
Raises:
|
|
437
|
-
ValueError: For an
|
|
438
|
-
|
|
439
|
-
``mode="release"`` against a datasource with no versioned
|
|
440
|
-
archive.
|
|
443
|
+
ValueError: For an unsupported *datasource* or an unsupported
|
|
444
|
+
*mapping_sets* for *datasource*.
|
|
441
445
|
"""
|
|
442
|
-
if mode not in ("release", "date"):
|
|
443
|
-
raise ValueError(f"mode must be 'release' or 'date', got {mode!r}")
|
|
444
446
|
if datasource not in SUPPORTED_DATASOURCES:
|
|
445
447
|
raise ValueError(
|
|
446
448
|
f"Unsupported datasource: {datasource!r}. Supported: {SUPPORTED_DATASOURCES}"
|
|
@@ -465,46 +467,43 @@ def consolidate_mapping_dates(
|
|
|
465
467
|
cache_path = _cache_path(cache_dir, datasource, mapping_sets, **kwargs)
|
|
466
468
|
meta_path = _meta_path(cache_dir, datasource, mapping_sets, **kwargs)
|
|
467
469
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
cache_path,
|
|
471
|
-
meta_path,
|
|
472
|
-
run_one_version=lambda: _run_one_version(datasource, None, mapping_sets, **kwargs),
|
|
473
|
-
)
|
|
474
|
-
if got_dates:
|
|
475
|
-
_, mapping_set = _write_consolidated_sssom(
|
|
476
|
-
datasource, mapping_sets, cache_path, meta_path
|
|
477
|
-
)
|
|
478
|
-
return cache_path, mapping_set
|
|
479
|
-
msg = (
|
|
480
|
-
f"{datasource!r} has no parseable per-row mapping dates; "
|
|
481
|
-
"falling back to mode='release'."
|
|
482
|
-
)
|
|
483
|
-
logger.warning(msg)
|
|
484
|
-
warnings.warn(msg, UserWarning, stacklevel=2)
|
|
470
|
+
def _run(version: str | None) -> Any:
|
|
471
|
+
return _run_one_version(datasource, version, mapping_sets, **kwargs)
|
|
485
472
|
|
|
486
473
|
list_versions_fn = _LIST_VERSIONS_FNS.get(datasource)
|
|
487
474
|
if list_versions_fn is None:
|
|
488
|
-
|
|
475
|
+
# No versioned archive (NCBI, VGNC): single current parse, full set.
|
|
476
|
+
_consolidate.consolidate(
|
|
477
|
+
cache_path,
|
|
478
|
+
meta_path,
|
|
479
|
+
label=datasource,
|
|
480
|
+
run_one_version=_run,
|
|
481
|
+
show_progress=show_progress,
|
|
482
|
+
force=force,
|
|
483
|
+
)
|
|
484
|
+
else:
|
|
485
|
+
# Versioned archive: walk every historical release for first-seen dates.
|
|
486
|
+
from pysec2pri.download import resolve_release_date
|
|
489
487
|
|
|
490
|
-
|
|
488
|
+
fn = list_versions_fn
|
|
489
|
+
_consolidate.consolidate(
|
|
490
|
+
cache_path,
|
|
491
|
+
meta_path,
|
|
492
|
+
label=datasource,
|
|
493
|
+
run_one_version=_run,
|
|
494
|
+
list_versions=lambda: fn(**kwargs),
|
|
495
|
+
resolve_release_date=lambda v: resolve_release_date(datasource, v, **kwargs),
|
|
496
|
+
show_progress=show_progress,
|
|
497
|
+
force=force,
|
|
498
|
+
)
|
|
491
499
|
|
|
492
|
-
_consolidate.consolidate_by_release(
|
|
493
|
-
cache_path,
|
|
494
|
-
meta_path,
|
|
495
|
-
label=datasource,
|
|
496
|
-
list_versions=lambda: list_versions_fn(**kwargs),
|
|
497
|
-
run_one_version=lambda v: _run_one_version(datasource, v, mapping_sets, **kwargs),
|
|
498
|
-
resolve_release_date=lambda v: resolve_release_date(datasource, v, **kwargs),
|
|
499
|
-
show_progress=show_progress,
|
|
500
|
-
force=force,
|
|
501
|
-
)
|
|
502
500
|
_, mapping_set = _write_consolidated_sssom(datasource, mapping_sets, cache_path, meta_path)
|
|
501
|
+
_save_optional_output(mapping_set, output)
|
|
503
502
|
return cache_path, mapping_set
|
|
504
503
|
|
|
505
504
|
|
|
506
505
|
# Cross-release label history (e.g. Ensembl, whose core schema has no
|
|
507
|
-
# previous-gene-symbol table
|
|
506
|
+
# previous-gene-symbol table: previous->current symbol transitions
|
|
508
507
|
# are recovered by diffing each release's current-label snapshot).
|
|
509
508
|
|
|
510
509
|
|
|
@@ -571,8 +570,8 @@ def build_label_history(
|
|
|
571
570
|
previous release's, oldest to newest. Network-heavy and resumable --
|
|
572
571
|
meant to be run on demand, not as part of normal mapping generation
|
|
573
572
|
(mirrors the release-walk pattern in
|
|
574
|
-
:func:`mapkgsutils.consolidate.
|
|
575
|
-
|
|
573
|
+
:func:`mapkgsutils.consolidate.consolidate`, but tracks a carried label
|
|
574
|
+
map instead of first/last-seen dates).
|
|
576
575
|
|
|
577
576
|
Args:
|
|
578
577
|
datasource: Currently only ``"ensembl"`` is supported.
|
|
@@ -648,7 +647,7 @@ def build_label_history(
|
|
|
648
647
|
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
649
648
|
|
|
650
649
|
release_date = resolve_release_date(datasource, v, species=species)
|
|
651
|
-
# Unlike
|
|
650
|
+
# Unlike the consolidate walk's plain-dict cache, these rows become
|
|
652
651
|
# real Mapping objects -- mapping_date must be a valid date or None,
|
|
653
652
|
# never the raw (non-date-shaped) version string.
|
|
654
653
|
date_str = release_date.date().isoformat() if release_date else None
|
|
@@ -375,8 +375,16 @@ def urls_and_date(
|
|
|
375
375
|
urls = _get_ensembl_urls_for_version(version, species=species)
|
|
376
376
|
logger.info("Ensembl version %s (species %s): %s", version, species, urls)
|
|
377
377
|
else:
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
378
|
+
# The release number is global across species, but the URLs (and the
|
|
379
|
+
# resolved release date) are species-specific, so resolve the latest
|
|
380
|
+
# version and build URLs for the requested species rather than reusing
|
|
381
|
+
# check_ensembl_release()'s human-only URLs.
|
|
382
|
+
versions = EnsemblDownloader(show_progress=False).list_versions()
|
|
383
|
+
if not versions:
|
|
384
|
+
raise ValueError("Could not find Ensembl releases on the FTP server")
|
|
385
|
+
version = versions[-1]
|
|
386
|
+
urls = _get_ensembl_urls_for_version(version, species=species)
|
|
387
|
+
check_url = urls.get("stable_id_event")
|
|
388
|
+
release_date = get_file_last_modified(check_url) if check_url else None
|
|
389
|
+
logger.info("Ensembl release %s (species %s): %s", version, species, urls)
|
|
382
390
|
return urls, release_date
|
|
@@ -190,8 +190,7 @@ class ChEBIParser(BaseParser):
|
|
|
190
190
|
Reads ``compounds.tsv`` (TSV releases >= 245) to extract every current
|
|
191
191
|
ChEBI compound ID. The returned mapping set has an empty ``mappings``
|
|
192
192
|
list; its ``_primary_ids`` store is populated with every current ChEBI
|
|
193
|
-
ID (CHEBI: prefixed) so that ``to_pri_ids()`` produces the
|
|
194
|
-
complete list.
|
|
193
|
+
ID (CHEBI: prefixed) so that ``to_pri_ids()`` produces the complete list.
|
|
195
194
|
|
|
196
195
|
Args:
|
|
197
196
|
input_path: Path to a directory containing ``compounds.tsv``, or
|
|
@@ -496,7 +495,7 @@ def _parse_names_tsv(
|
|
|
496
495
|
|
|
497
496
|
Format: id|compound_id|name|type|status_id|adapted|language_code|ascii_name
|
|
498
497
|
|
|
499
|
-
The
|
|
498
|
+
The true primary (canonical) name for each compound is taken from
|
|
500
499
|
the ``name`` column of ``compounds.tsv``: the same value displayed on the
|
|
501
500
|
ChEBI website and stored as the top-level label in the ChEBI ontology.
|
|
502
501
|
Every entry in ``names.tsv`` that differs from that canonical name is
|
|
@@ -526,7 +525,7 @@ def _parse_names_tsv(
|
|
|
526
525
|
"Pass the path to compounds.tsv."
|
|
527
526
|
)
|
|
528
527
|
|
|
529
|
-
# Load canonical names from compounds.tsv (ChEBI
|
|
528
|
+
# Load canonical names from compounds.tsv (ChEBI primary label)
|
|
530
529
|
cpd_cols = ["id", "name", "stars"] if subset == "3star" else ["id", "name"]
|
|
531
530
|
cpd_df = pl.read_csv(
|
|
532
531
|
compounds_path,
|
|
@@ -17,7 +17,6 @@ import gzip
|
|
|
17
17
|
import io
|
|
18
18
|
import re
|
|
19
19
|
import zipfile
|
|
20
|
-
from collections.abc import Callable
|
|
21
20
|
from pathlib import Path
|
|
22
21
|
from typing import IO, TYPE_CHECKING
|
|
23
22
|
|
|
@@ -85,7 +84,6 @@ class HMDBParser(BaseParser):
|
|
|
85
84
|
element_tag: str,
|
|
86
85
|
prefix: str,
|
|
87
86
|
desc: str,
|
|
88
|
-
secondary_normaliser: Callable[[str], str] | None = None,
|
|
89
87
|
) -> BaseMappingSet:
|
|
90
88
|
"""Parse an HMDB XML file and return a mapping set.
|
|
91
89
|
|
|
@@ -95,8 +93,6 @@ class HMDBParser(BaseParser):
|
|
|
95
93
|
``"protein"``).
|
|
96
94
|
prefix: CURIE prefix (``"HMDB"`` for both record types).
|
|
97
95
|
desc: Progress-bar description string.
|
|
98
|
-
secondary_normaliser: Optional callable ``(raw_str) -> str``
|
|
99
|
-
applied to each raw secondary accession before prefixing.
|
|
100
96
|
|
|
101
97
|
Returns:
|
|
102
98
|
:class:`BaseMappingSet` with computed cardinalities and
|
|
@@ -130,10 +126,10 @@ class HMDBParser(BaseParser):
|
|
|
130
126
|
accession_elem = elem.find("accession")
|
|
131
127
|
if accession_elem is not None and accession_elem.text:
|
|
132
128
|
primary_ids_found.add(f"{prefix}:{accession_elem.text.strip()}")
|
|
133
|
-
rows_data.extend(self._process_record(elem, prefix
|
|
129
|
+
rows_data.extend(self._process_record(elem, prefix))
|
|
134
130
|
elem.clear()
|
|
135
131
|
except DefusedET.ParseError:
|
|
136
|
-
rows_data = self._parse_simple_xml(file_path, element_tag, prefix
|
|
132
|
+
rows_data = self._parse_simple_xml(file_path, element_tag, prefix)
|
|
137
133
|
|
|
138
134
|
mappings = self._build_mappings(
|
|
139
135
|
rows_data, fixed, desc="Building HMDB mappings", total=len(rows_data)
|
|
@@ -147,9 +143,14 @@ class HMDBParser(BaseParser):
|
|
|
147
143
|
self,
|
|
148
144
|
elem: Element,
|
|
149
145
|
prefix: str,
|
|
150
|
-
secondary_normaliser: Callable[[str], str] | None,
|
|
151
146
|
) -> list[dict[str, str]]:
|
|
152
|
-
"""Extract row dicts from a single record element.
|
|
147
|
+
"""Extract row dicts from a single record element.
|
|
148
|
+
|
|
149
|
+
Bare-numeric ``secondary_accessions`` entries are skipped: in the
|
|
150
|
+
proteins file these are HMDB's internal database row ids (not legacy
|
|
151
|
+
accessions), and a bare integer is never a valid HMDB accession for
|
|
152
|
+
either record type. See issue #44.
|
|
153
|
+
"""
|
|
153
154
|
# Primary accession
|
|
154
155
|
accession_elem = elem.find("hmdb:accession", HMDB_NS)
|
|
155
156
|
if accession_elem is None:
|
|
@@ -177,8 +178,8 @@ class HMDBParser(BaseParser):
|
|
|
177
178
|
if not sec_elem.text:
|
|
178
179
|
continue
|
|
179
180
|
raw_sec = sec_elem.text.strip()
|
|
180
|
-
if
|
|
181
|
-
|
|
181
|
+
if _BARE_NUM_RE.match(raw_sec):
|
|
182
|
+
continue
|
|
182
183
|
secondary_id = f"{prefix}:{raw_sec}"
|
|
183
184
|
rows.append(
|
|
184
185
|
{
|
|
@@ -200,7 +201,6 @@ class HMDBParser(BaseParser):
|
|
|
200
201
|
file_path: Path,
|
|
201
202
|
element_tag: str,
|
|
202
203
|
prefix: str,
|
|
203
|
-
secondary_normaliser: Callable[[str], str] | None,
|
|
204
204
|
) -> list[dict[str, str]]:
|
|
205
205
|
"""Fallback parser for test files without a namespace declaration."""
|
|
206
206
|
rows_data: list[dict[str, str]] = []
|
|
@@ -208,7 +208,7 @@ class HMDBParser(BaseParser):
|
|
|
208
208
|
tree = DefusedET.parse(file_path)
|
|
209
209
|
root = tree.getroot()
|
|
210
210
|
for record in root.findall(f".//{element_tag}"):
|
|
211
|
-
rows_data.extend(self._process_record(record, prefix
|
|
211
|
+
rows_data.extend(self._process_record(record, prefix))
|
|
212
212
|
except Exception as e:
|
|
213
213
|
logger.warning("Failed to parse HMDB XML: %s", e)
|
|
214
214
|
return rows_data
|
|
@@ -329,9 +329,15 @@ class HMDBProteinParser(HMDBParser):
|
|
|
329
329
|
|
|
330
330
|
Reads configuration from ``hmdb_proteins.yaml``.
|
|
331
331
|
Primary accessions have the form ``HMDBP…`` (e.g. ``HMDBP00001``).
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
:
|
|
332
|
+
|
|
333
|
+
A record's ``<secondary_accessions>`` block holds two distinct kinds of
|
|
334
|
+
value. Only the first is a real identifier:
|
|
335
|
+
|
|
336
|
+
* **``HMDBP``-prefixed accessions** (e.g. ``HMDBP05261``): genuine retired
|
|
337
|
+
accessions that map to the record's primary accession.
|
|
338
|
+
* **Bare numeric values** (e.g. ``5229``): HMDB's internal database row id,
|
|
339
|
+
*not* a legacy accession. These are dropped by the shared
|
|
340
|
+
:meth:`~HMDBParser._process_record` (see issue #44).
|
|
335
341
|
"""
|
|
336
342
|
|
|
337
343
|
datasource_name = "hmdb_proteins"
|
|
@@ -341,26 +347,6 @@ class HMDBProteinParser(HMDBParser):
|
|
|
341
347
|
"""Proteins download URL from ``hmdb_proteins.yaml``."""
|
|
342
348
|
return self.get_download_url("proteins") or ""
|
|
343
349
|
|
|
344
|
-
@staticmethod
|
|
345
|
-
def _normalise_protein_secondary(raw: str) -> str:
|
|
346
|
-
"""Normalise a raw secondary protein accession to a ``HMDBP``-prefixed form.
|
|
347
|
-
|
|
348
|
-
Bare numeric values (legacy IDs) are zero-padded to five digits and
|
|
349
|
-
prefixed with ``HMDBP`` to match the primary accession pattern.
|
|
350
|
-
Already-prefixed values are returned unchanged.
|
|
351
|
-
|
|
352
|
-
Args:
|
|
353
|
-
raw: Raw secondary accession string (e.g. ``"5229"`` or
|
|
354
|
-
``"HMDBP05229"``).
|
|
355
|
-
|
|
356
|
-
Returns:
|
|
357
|
-
Normalised accession string (e.g. ``"HMDBP05229"``).
|
|
358
|
-
"""
|
|
359
|
-
raw = raw.strip()
|
|
360
|
-
if _BARE_NUM_RE.match(raw):
|
|
361
|
-
return f"HMDBP{int(raw):05d}"
|
|
362
|
-
return raw
|
|
363
|
-
|
|
364
350
|
def parse(self, input_path: Path | str | None) -> BaseMappingSet:
|
|
365
351
|
"""Parse ``hmdb_proteins.xml`` (or ``.zip`` / ``.gz``).
|
|
366
352
|
|
|
@@ -377,7 +363,6 @@ class HMDBProteinParser(HMDBParser):
|
|
|
377
363
|
element_tag="protein",
|
|
378
364
|
prefix="HMDBP",
|
|
379
365
|
desc="Parsing HMDB proteins XML",
|
|
380
|
-
secondary_normaliser=self._normalise_protein_secondary,
|
|
381
366
|
)
|
|
382
367
|
|
|
383
368
|
|
|
@@ -25,9 +25,8 @@ This parser extracts:
|
|
|
25
25
|
detection and ``to_pri_labels()`` stay scoped to one species' namespace
|
|
26
26
|
instead of flagging cross-species homonyms as ambiguous. Passing
|
|
27
27
|
:data:`ALL_SPECIES` processes every species together instead, in which
|
|
28
|
-
case a shared symbol
|
|
29
|
-
to tell the species apart) and gets flagged accordingly
|
|
30
|
-
``_annotate_ambiguous_mappings`` doing its job, not a bug.
|
|
28
|
+
case a shared symbol is really ambiguous (there's no other context
|
|
29
|
+
to tell the species apart) and gets flagged accordingly.
|
|
31
30
|
|
|
32
31
|
Uses SSSOM-compliant MappingSet classes with cardinality computation.
|
|
33
32
|
"""
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|