verdy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- verdy-0.1.0/LICENSE +21 -0
- verdy-0.1.0/PKG-INFO +154 -0
- verdy-0.1.0/README.md +134 -0
- verdy-0.1.0/pyproject.toml +29 -0
- verdy-0.1.0/setup.cfg +4 -0
- verdy-0.1.0/tests/test_match.py +194 -0
- verdy-0.1.0/verdy/__init__.py +34 -0
- verdy-0.1.0/verdy/__main__.py +60 -0
- verdy-0.1.0/verdy/match.py +197 -0
- verdy-0.1.0/verdy/output.py +87 -0
- verdy-0.1.0/verdy/resolve.py +85 -0
- verdy-0.1.0/verdy/sources.py +403 -0
- verdy-0.1.0/verdy.egg-info/PKG-INFO +154 -0
- verdy-0.1.0/verdy.egg-info/SOURCES.txt +16 -0
- verdy-0.1.0/verdy.egg-info/dependency_links.txt +1 -0
- verdy-0.1.0/verdy.egg-info/entry_points.txt +2 -0
- verdy-0.1.0/verdy.egg-info/requires.txt +8 -0
- verdy-0.1.0/verdy.egg-info/top_level.txt +1 -0
verdy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jovana Dobreva
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
verdy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: verdy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: VERDY: match your own terms (species, habitats, parameters, measurements) to the ontologies you choose: ENVO, NCBITaxon, GBIF, PATO, CF, Wikidata, any OLS4 ontology.
|
|
5
|
+
Author: Jovana Dobreva
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dobrevajovana/verdy
|
|
8
|
+
Project-URL: Source, https://github.com/dobrevajovana/verdy
|
|
9
|
+
Project-URL: Issues, https://github.com/dobrevajovana/verdy/issues
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: requests>=2.31
|
|
14
|
+
Provides-Extra: excel
|
|
15
|
+
Requires-Dist: pandas>=2.0; extra == "excel"
|
|
16
|
+
Requires-Dist: openpyxl>=3.1; extra == "excel"
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
<p align="center"><img src="https://raw.githubusercontent.com/dobrevajovana/verdy/main/logo.png" width="200" alt="VERDY logo: a green forest spirit with a flower crown, braided leafy hair and berry nodes linked by vines"></p>
|
|
22
|
+
|
|
23
|
+
# VERDY
|
|
24
|
+
|
|
25
|
+
[](https://github.com/dobrevajovana/verdy/actions/workflows/python-package.yml)
|
|
26
|
+
|
|
27
|
+
*Part of **VERDANT**: **V**ocabulary **E**nrichment & **R**esolution of **D**iversity **A**nnotations via **N**eural **T**echnology
|
|
28
|
+
(BioHackathon Europe 2026).*
|
|
29
|
+
|
|
30
|
+
**Meet VERDY, your warrior for ontology matching.** Give it your ENA IDs or free-text terms, choose the ontologies, and it predicts the matching terms in each.
|
|
31
|
+
|
|
32
|
+
1. **Input:** a file (or Python list) of ENA IDs and/or free text. The IDs can be samples, runs or projects. The free text can be species, habitats, parameters or measurement names.
|
|
33
|
+
2. **ENA lookup:** each ENA ID is looked up in [ENA](https://www.ebi.ac.uk/ena/portal/api) for its sample's organism and environment fields (`scientific_name`, `broad_scale_environmental_context`, `local_environmental_context`, `environmental_medium`, ...). Free text is used as it is.
|
|
34
|
+
3. **Search:** every name is searched in each ontology you choose (for example `natura2000`, `gold`, `envo`).
|
|
35
|
+
4. **Output:** a file with the exact match, or the most closely related terms, per name and ontology.
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import verdy
|
|
39
|
+
|
|
40
|
+
matches = verdy.match("my_input.csv", ontologies=["natura2000", "gold", "envo", "ncbitaxon"])
|
|
41
|
+
verdy.save(matches, "my_input.matches.csv") # .csv .tsv .sssom.tsv .json .xlsx
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
verdy my_input.csv -o natura2000,gold,envo --out my_input.matches.csv
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
pip install git+https://github.com/dobrevajovana/verdy.git
|
|
52
|
+
pip install "verdy[excel] @ git+https://github.com/dobrevajovana/verdy.git" # to read and write Excel
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Python 3.10 or newer; the only dependency is `requests`.
|
|
56
|
+
|
|
57
|
+
## Input
|
|
58
|
+
|
|
59
|
+
`verdy.match` takes a CSV / TSV / Excel file, a text file with one value per line, a Python list or a pandas DataFrame.
|
|
60
|
+
The column is found by its header (`name`, `term`, `parameter`, `species`, ..., or `id`, `accession`, `sample_accession`, `run_accession`)
|
|
61
|
+
or given with `column="..."`. One file can mix both kinds of value:
|
|
62
|
+
|
|
63
|
+
| value | what happens |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `SAMD00132352`, `SAMEA...`, `SAMN...`, `ERS...` | an ENA sample: its fields are looked up in ENA and each value is searched |
|
|
66
|
+
| `ERR9966617`, `SRR...`, `ERX...` | a run or experiment: its sample, as above |
|
|
67
|
+
| `PRJEB54740`, `PRJNA...` | a project: all its samples, each row under the sample's own accession |
|
|
68
|
+
| anything else | free text, searched as it is (units in brackets, underscores and a leading code are set aside) |
|
|
69
|
+
|
|
70
|
+
`fields=` chooses other ENA sample fields to search (see ENA's `returnFields?result=sample`).
|
|
71
|
+
After a run, `verdy.match.unresolved` lists the IDs ENA did not know.
|
|
72
|
+
|
|
73
|
+
## The ontologies
|
|
74
|
+
|
|
75
|
+
`verdy.ONTOLOGIES` (or `verdy --list`):
|
|
76
|
+
|
|
77
|
+
| ID | What | For |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `natura2000` | Natura 2000 habitat types and species (EEA Article 17 lists, fetched live) | habitats by name or code, protected species |
|
|
80
|
+
| `envo` | Environment Ontology | habitats, environments, materials, environmental measurements |
|
|
81
|
+
| `gold` | GOLD ecosystem classification | sample ecosystems |
|
|
82
|
+
| `ncbitaxon` | NCBI Taxonomy | organisms |
|
|
83
|
+
| `gbif` | GBIF Backbone Taxonomy | organisms |
|
|
84
|
+
| `pato` | Phenotype And Trait Ontology | qualities: temperature, salinity, humidity, ... |
|
|
85
|
+
| `oba` | Ontology of Biological Attributes | traits and measurable attributes |
|
|
86
|
+
| `cf` | CF Standard Names | climate, weather and ocean variables |
|
|
87
|
+
| `uo` | Units of measurement | units |
|
|
88
|
+
| `chebi` | Chemical Entities of Biological Interest | chemicals |
|
|
89
|
+
| `obi` | Ontology for Biomedical Investigations | assays, instruments, protocols |
|
|
90
|
+
| `uberon`, `po`, `go` | anatomy, plants, biological processes | |
|
|
91
|
+
| `wikidata` | Wikidata | anything with a name |
|
|
92
|
+
|
|
93
|
+
**Any other ontology in [OLS4](https://www.ebi.ac.uk/ols4/ontologies) works as well:** pass its ID
|
|
94
|
+
(for example `foodon`, `pco` or `ms`).
|
|
95
|
+
|
|
96
|
+
## What comes out
|
|
97
|
+
|
|
98
|
+
One row per match:
|
|
99
|
+
|
|
100
|
+
| column | |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `term_id`, `term`, `category` | your term |
|
|
103
|
+
| `match_type` | `skos:exactMatch`: a label or synonym is the term (plurals and `sp.` aside) · `skos:closeMatch`: most words shared · `skos:relatedMatch`: some words shared |
|
|
104
|
+
| `ontology`, `match_curie`, `match_id`, `match_label` | the matched term |
|
|
105
|
+
| `confidence` | 1.0 exact as written, 0.95 exact up to plurals, then by word overlap |
|
|
106
|
+
| `word_overlap`, `rank` | how many words they share (0..1), and the place among this term's matches |
|
|
107
|
+
| `definition` | the term's definition (units first, for CF) |
|
|
108
|
+
| `searched_for` | the term after cleaning |
|
|
109
|
+
|
|
110
|
+
How the search works:
|
|
111
|
+
* At most `per_ontology` matches (default 3) per term and ontology. When there is an exact match, only that one is given.
|
|
112
|
+
* In the taxonomies (NCBITaxon, GBIF), only exact names count, because a near name is another taxon.
|
|
113
|
+
* `verdy.unmatched(terms, matches, ontologies)` lists, per ontology, the terms it found nothing for.
|
|
114
|
+
|
|
115
|
+
Every answer (ENA, OLS4, GBIF, Wikidata, the CF table, the Natura 2000 lists) is cached in `~/.cache/verdy` (or `VERDY_CACHE`), so a second run makes no requests.
|
|
116
|
+
|
|
117
|
+
## Example
|
|
118
|
+
|
|
119
|
+
`python examples/predict.py` runs [examples/input.csv](examples/input.csv), which has three ENA accessions and four free-text terms, against
|
|
120
|
+
Natura 2000, GOLD, ENVO, NCBITaxon, PATO and CF. It writes [examples/input.matches.csv](examples/input.matches.csv). Part of the result:
|
|
121
|
+
|
|
122
|
+
| input | name searched (from) | ontology | match |
|
|
123
|
+
|---|---|---|---|
|
|
124
|
+
| SAMD00132352 | urban biome (broad_scale_environmental_context) | ENVO | exactMatch ENVO:01000249 urban biome |
|
|
125
|
+
| SAMD00132352 | air (local_environmental_context, environmental_medium) | ENVO · GOLD | exactMatch ENVO:00002005 air · GOLD:Air |
|
|
126
|
+
| SAMD00132352 | air metagenome (scientific_name) | NCBITaxon | exactMatch NCBITaxon:655179 |
|
|
127
|
+
| ERR9966617 | temperate forest | ENVO | exactMatch ENVO:01001805 temperate forest |
|
|
128
|
+
| ERR9966617 | mountain | ENVO | exactMatch ENVO:00000081 mountain |
|
|
129
|
+
| SAMEA110429224 | Human (host_scientific_name) | NCBITaxon | exactMatch NCBITaxon:9606 Homo sapiens |
|
|
130
|
+
| Lynx lynx | | Natura 2000 · NCBITaxon | exactMatch n2k:1361 · NCBITaxon:13125 |
|
|
131
|
+
| Atlantic salt meadows | | Natura 2000 | exactMatch n2k:1330 |
|
|
132
|
+
| air_temperature | | CF · ENVO | exactMatch CF:air_temperature · ENVO:09200001 temperature of air |
|
|
133
|
+
| salinity | | PATO | exactMatch PATO:0085001 |
|
|
134
|
+
|
|
135
|
+
## One term at a time
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
verdy.search("cloud liquid water content", "cf") # the source's best terms, best first
|
|
139
|
+
verdy.resolve("SAMD00132352") # what ENA says about an ID
|
|
140
|
+
verdy.read_terms("my_input.csv") # what VERDY read from your file
|
|
141
|
+
verdy.to_dataframe(matches) # pandas
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Tests
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
pip install -e ".[test]" && python -m pytest -q
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The tests stub every web service, so they run offline.
|
|
151
|
+
|
|
152
|
+
## Licence
|
|
153
|
+
|
|
154
|
+
MIT, see [LICENSE](LICENSE).
|
verdy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<p align="center"><img src="https://raw.githubusercontent.com/dobrevajovana/verdy/main/logo.png" width="200" alt="VERDY logo: a green forest spirit with a flower crown, braided leafy hair and berry nodes linked by vines"></p>
|
|
2
|
+
|
|
3
|
+
# VERDY
|
|
4
|
+
|
|
5
|
+
[](https://github.com/dobrevajovana/verdy/actions/workflows/python-package.yml)
|
|
6
|
+
|
|
7
|
+
*Part of **VERDANT**: **V**ocabulary **E**nrichment & **R**esolution of **D**iversity **A**nnotations via **N**eural **T**echnology
|
|
8
|
+
(BioHackathon Europe 2026).*
|
|
9
|
+
|
|
10
|
+
**Meet VERDY, your warrior for ontology matching.** Give it your ENA IDs or free-text terms, choose the ontologies, and it predicts the matching terms in each.
|
|
11
|
+
|
|
12
|
+
1. **Input:** a file (or Python list) of ENA IDs and/or free text. The IDs can be samples, runs or projects. The free text can be species, habitats, parameters or measurement names.
|
|
13
|
+
2. **ENA lookup:** each ENA ID is looked up in [ENA](https://www.ebi.ac.uk/ena/portal/api) for its sample's organism and environment fields (`scientific_name`, `broad_scale_environmental_context`, `local_environmental_context`, `environmental_medium`, ...). Free text is used as it is.
|
|
14
|
+
3. **Search:** every name is searched in each ontology you choose (for example `natura2000`, `gold`, `envo`).
|
|
15
|
+
4. **Output:** a file with the exact match, or the most closely related terms, per name and ontology.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import verdy
|
|
19
|
+
|
|
20
|
+
matches = verdy.match("my_input.csv", ontologies=["natura2000", "gold", "envo", "ncbitaxon"])
|
|
21
|
+
verdy.save(matches, "my_input.matches.csv") # .csv .tsv .sssom.tsv .json .xlsx
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
verdy my_input.csv -o natura2000,gold,envo --out my_input.matches.csv
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
pip install git+https://github.com/dobrevajovana/verdy.git
|
|
32
|
+
pip install "verdy[excel] @ git+https://github.com/dobrevajovana/verdy.git" # to read and write Excel
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Python 3.10 or newer; the only dependency is `requests`.
|
|
36
|
+
|
|
37
|
+
## Input
|
|
38
|
+
|
|
39
|
+
`verdy.match` takes a CSV / TSV / Excel file, a text file with one value per line, a Python list or a pandas DataFrame.
|
|
40
|
+
The column is found by its header (`name`, `term`, `parameter`, `species`, ..., or `id`, `accession`, `sample_accession`, `run_accession`)
|
|
41
|
+
or given with `column="..."`. One file can mix both kinds of value:
|
|
42
|
+
|
|
43
|
+
| value | what happens |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `SAMD00132352`, `SAMEA...`, `SAMN...`, `ERS...` | an ENA sample: its fields are looked up in ENA and each value is searched |
|
|
46
|
+
| `ERR9966617`, `SRR...`, `ERX...` | a run or experiment: its sample, as above |
|
|
47
|
+
| `PRJEB54740`, `PRJNA...` | a project: all its samples, each row under the sample's own accession |
|
|
48
|
+
| anything else | free text, searched as it is (units in brackets, underscores and a leading code are set aside) |
|
|
49
|
+
|
|
50
|
+
`fields=` chooses other ENA sample fields to search (see ENA's `returnFields?result=sample`).
|
|
51
|
+
After a run, `verdy.match.unresolved` lists the IDs ENA did not know.
|
|
52
|
+
|
|
53
|
+
## The ontologies
|
|
54
|
+
|
|
55
|
+
`verdy.ONTOLOGIES` (or `verdy --list`):
|
|
56
|
+
|
|
57
|
+
| ID | What | For |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `natura2000` | Natura 2000 habitat types and species (EEA Article 17 lists, fetched live) | habitats by name or code, protected species |
|
|
60
|
+
| `envo` | Environment Ontology | habitats, environments, materials, environmental measurements |
|
|
61
|
+
| `gold` | GOLD ecosystem classification | sample ecosystems |
|
|
62
|
+
| `ncbitaxon` | NCBI Taxonomy | organisms |
|
|
63
|
+
| `gbif` | GBIF Backbone Taxonomy | organisms |
|
|
64
|
+
| `pato` | Phenotype And Trait Ontology | qualities: temperature, salinity, humidity, ... |
|
|
65
|
+
| `oba` | Ontology of Biological Attributes | traits and measurable attributes |
|
|
66
|
+
| `cf` | CF Standard Names | climate, weather and ocean variables |
|
|
67
|
+
| `uo` | Units of measurement | units |
|
|
68
|
+
| `chebi` | Chemical Entities of Biological Interest | chemicals |
|
|
69
|
+
| `obi` | Ontology for Biomedical Investigations | assays, instruments, protocols |
|
|
70
|
+
| `uberon`, `po`, `go` | anatomy, plants, biological processes | |
|
|
71
|
+
| `wikidata` | Wikidata | anything with a name |
|
|
72
|
+
|
|
73
|
+
**Any other ontology in [OLS4](https://www.ebi.ac.uk/ols4/ontologies) works as well:** pass its ID
|
|
74
|
+
(for example `foodon`, `pco` or `ms`).
|
|
75
|
+
|
|
76
|
+
## What comes out
|
|
77
|
+
|
|
78
|
+
One row per match:
|
|
79
|
+
|
|
80
|
+
| column | |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `term_id`, `term`, `category` | your term |
|
|
83
|
+
| `match_type` | `skos:exactMatch`: a label or synonym is the term (plurals and `sp.` aside) · `skos:closeMatch`: most words shared · `skos:relatedMatch`: some words shared |
|
|
84
|
+
| `ontology`, `match_curie`, `match_id`, `match_label` | the matched term |
|
|
85
|
+
| `confidence` | 1.0 exact as written, 0.95 exact up to plurals, then by word overlap |
|
|
86
|
+
| `word_overlap`, `rank` | how many words they share (0..1), and the place among this term's matches |
|
|
87
|
+
| `definition` | the term's definition (units first, for CF) |
|
|
88
|
+
| `searched_for` | the term after cleaning |
|
|
89
|
+
|
|
90
|
+
How the search works:
|
|
91
|
+
* At most `per_ontology` matches (default 3) per term and ontology. When there is an exact match, only that one is given.
|
|
92
|
+
* In the taxonomies (NCBITaxon, GBIF), only exact names count, because a near name is another taxon.
|
|
93
|
+
* `verdy.unmatched(terms, matches, ontologies)` lists, per ontology, the terms it found nothing for.
|
|
94
|
+
|
|
95
|
+
Every answer (ENA, OLS4, GBIF, Wikidata, the CF table, the Natura 2000 lists) is cached in `~/.cache/verdy` (or `VERDY_CACHE`), so a second run makes no requests.
|
|
96
|
+
|
|
97
|
+
## Example
|
|
98
|
+
|
|
99
|
+
`python examples/predict.py` runs [examples/input.csv](examples/input.csv), which has three ENA accessions and four free-text terms, against
|
|
100
|
+
Natura 2000, GOLD, ENVO, NCBITaxon, PATO and CF. It writes [examples/input.matches.csv](examples/input.matches.csv). Part of the result:
|
|
101
|
+
|
|
102
|
+
| input | name searched (from) | ontology | match |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| SAMD00132352 | urban biome (broad_scale_environmental_context) | ENVO | exactMatch ENVO:01000249 urban biome |
|
|
105
|
+
| SAMD00132352 | air (local_environmental_context, environmental_medium) | ENVO · GOLD | exactMatch ENVO:00002005 air · GOLD:Air |
|
|
106
|
+
| SAMD00132352 | air metagenome (scientific_name) | NCBITaxon | exactMatch NCBITaxon:655179 |
|
|
107
|
+
| ERR9966617 | temperate forest | ENVO | exactMatch ENVO:01001805 temperate forest |
|
|
108
|
+
| ERR9966617 | mountain | ENVO | exactMatch ENVO:00000081 mountain |
|
|
109
|
+
| SAMEA110429224 | Human (host_scientific_name) | NCBITaxon | exactMatch NCBITaxon:9606 Homo sapiens |
|
|
110
|
+
| Lynx lynx | | Natura 2000 · NCBITaxon | exactMatch n2k:1361 · NCBITaxon:13125 |
|
|
111
|
+
| Atlantic salt meadows | | Natura 2000 | exactMatch n2k:1330 |
|
|
112
|
+
| air_temperature | | CF · ENVO | exactMatch CF:air_temperature · ENVO:09200001 temperature of air |
|
|
113
|
+
| salinity | | PATO | exactMatch PATO:0085001 |
|
|
114
|
+
|
|
115
|
+
## One term at a time
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
verdy.search("cloud liquid water content", "cf") # the source's best terms, best first
|
|
119
|
+
verdy.resolve("SAMD00132352") # what ENA says about an ID
|
|
120
|
+
verdy.read_terms("my_input.csv") # what VERDY read from your file
|
|
121
|
+
verdy.to_dataframe(matches) # pandas
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Tests
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
pip install -e ".[test]" && python -m pytest -q
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The tests stub every web service, so they run offline.
|
|
131
|
+
|
|
132
|
+
## Licence
|
|
133
|
+
|
|
134
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "verdy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "VERDY: match your own terms (species, habitats, parameters, measurements) to the ontologies you choose: ENVO, NCBITaxon, GBIF, PATO, CF, Wikidata, any OLS4 ontology."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = ["requests>=2.31"]
|
|
12
|
+
authors = [{ name = "Jovana Dobreva" }]
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://github.com/dobrevajovana/verdy"
|
|
18
|
+
Source = "https://github.com/dobrevajovana/verdy"
|
|
19
|
+
Issues = "https://github.com/dobrevajovana/verdy/issues"
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
excel = ["pandas>=2.0", "openpyxl>=3.1"]
|
|
23
|
+
test = ["pytest>=7"]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
verdy = "verdy.__main__:main"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools]
|
|
29
|
+
packages = ["verdy"]
|
verdy-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""verdy.match with every web service stubbed.
|
|
2
|
+
|
|
3
|
+
Run: python -m pytest -q
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import csv
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
import verdy
|
|
12
|
+
from verdy import sources
|
|
13
|
+
|
|
14
|
+
ENVO = "http://purl.obolibrary.org/obo/ENVO_"
|
|
15
|
+
TAXON = "http://purl.obolibrary.org/obo/NCBITaxon_"
|
|
16
|
+
PATO = "http://purl.obolibrary.org/obo/PATO_"
|
|
17
|
+
CF = b"""<?xml version="1.0"?><standard_name_table>
|
|
18
|
+
<entry id="air_temperature"><canonical_units>K</canonical_units><description>Air temperature is the bulk temperature of the air.</description></entry>
|
|
19
|
+
<entry id="wind_speed"><canonical_units>m s-1</canonical_units><description>Speed is the magnitude of velocity.</description></entry>
|
|
20
|
+
<alias id="surface_air_temperature"><entry_id>air_temperature</entry_id></alias>
|
|
21
|
+
</standard_name_table>"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def services(monkeypatch, tmp_path):
|
|
26
|
+
"""OLS4 (ENVO, NCBITaxon, PATO), GBIF, Wikidata and the CF table, answering from these tables."""
|
|
27
|
+
monkeypatch.setattr(sources, "CACHE", tmp_path / "cache")
|
|
28
|
+
monkeypatch.setattr(sources, "_cf_terms", None)
|
|
29
|
+
(tmp_path / "cache").mkdir()
|
|
30
|
+
(tmp_path / "cache" / "cf-standard-name-table.xml").write_bytes(CF)
|
|
31
|
+
ols = {("envo", "estuary"): [{"iri": ENVO + "00000045", "label": "estuary", "obo_id": "ENVO:00000045"}],
|
|
32
|
+
("envo", "lagoon"): [{"iri": ENVO + "00000038", "label": "lagoon", "obo_id": "ENVO:00000038"}],
|
|
33
|
+
("envo", "coastal"): [{"iri": ENVO + "1", "label": "coastal water", "obo_id": "ENVO:1"}],
|
|
34
|
+
("ncbitaxon", "lynx lynx"): [{"iri": TAXON + "13125", "label": "Lynx lynx", "obo_id": "NCBITaxon:13125"}],
|
|
35
|
+
("ncbitaxon", "estuaries"): [{"iri": TAXON + "1", "label": "estuaries bacterium"}],
|
|
36
|
+
("pato", "temperature"): [{"iri": PATO + "0000146", "label": "temperature", "obo_id": "PATO:0000146"}],
|
|
37
|
+
("envo", "urban biome"): [{"iri": ENVO + "01000249", "label": "urban biome", "obo_id": "ENVO:01000249",
|
|
38
|
+
"is_defining_ontology": True}],
|
|
39
|
+
("envo", "air"): [{"iri": ENVO + "00002005", "label": "air", "obo_id": "ENVO:00002005", "is_defining_ontology": True}],
|
|
40
|
+
("gold", "air"): [{"iri": "https://w3id.org/gold.path/3962", "label": "Environmental > Air > Outdoor Air",
|
|
41
|
+
"is_defining_ontology": False},
|
|
42
|
+
{"iri": "https://w3id.org/gold.vocab/Air", "label": "Air", "is_defining_ontology": False}]}
|
|
43
|
+
calls = []
|
|
44
|
+
|
|
45
|
+
def fake(url, **p):
|
|
46
|
+
calls.append(url)
|
|
47
|
+
if url.endswith("/portal/api/search"): # ENA samples
|
|
48
|
+
acc = p["query"].split('"')[1]
|
|
49
|
+
return [{"sample_accession": "SAMD1", "scientific_name": "air metagenome", "broad_scale_environmental_context":
|
|
50
|
+
"urban biome", "local_environmental_context": "air", "environmental_medium": "air",
|
|
51
|
+
"isolation_source": "missing"}] if acc in ("SAMD1", "ERS1") else []
|
|
52
|
+
if url.endswith("/portal/api/filereport"): # ENA runs and projects
|
|
53
|
+
return {"ERR1": [{"sample_accession": "SAMD1"}], "PRJEB1": [{"sample_accession": "SAMD1"},
|
|
54
|
+
{"sample_accession": "SAMD1"}]}.get(p["accession"], [])
|
|
55
|
+
if "/ontologies/" in url:
|
|
56
|
+
oid = url.rsplit("/", 1)[1]
|
|
57
|
+
return {"config": {"title": oid, "preferredPrefix": {"ncbitaxon": "NCBITaxon"}.get(oid, oid.upper())}} \
|
|
58
|
+
if oid in ("envo", "ncbitaxon", "pato", "gold") else {}
|
|
59
|
+
if url.endswith("/search"):
|
|
60
|
+
return {"response": {"docs": ols.get((p["ontology"], p["q"].lower()), [])}}
|
|
61
|
+
if "species/match" in url:
|
|
62
|
+
return {"usageKey": 2435240, "canonicalName": "Lynx lynx", "matchType": "EXACT"} if p["name"] == "Lynx lynx" \
|
|
63
|
+
else {"matchType": "NONE"}
|
|
64
|
+
if "MapServer" in url: # Natura 2000 (Article 17)
|
|
65
|
+
if "/5/" in url:
|
|
66
|
+
return {"features": [{"attributes": {"reference_habitatlist.habitat_code": "1130",
|
|
67
|
+
"reference_habitatlist.label": "Estuaries"}}]}
|
|
68
|
+
return {"features": [{"attributes": {"reference_specieslist.species_code": "1361",
|
|
69
|
+
"reference_specieslist.species_name": "Lynx lynx",
|
|
70
|
+
"reference_specieslist.taxonomical_group": "Mammals"}}]}
|
|
71
|
+
if "wikidata" in url:
|
|
72
|
+
return {"search": [{"id": "Q43375", "label": "Eurasian lynx", "aliases": ["Lynx lynx"]}]} if p["search"] == "Lynx lynx" \
|
|
73
|
+
else {"search": [{"id": "Q9", "label": "some moth", "aliases": ["coastal lagoons moth"]}]}
|
|
74
|
+
return {}
|
|
75
|
+
monkeypatch.setattr(sources, "_json", fake)
|
|
76
|
+
return calls
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_read_terms_from_many_inputs(tmp_path):
|
|
80
|
+
(tmp_path / "a.csv").write_text("id,Parameter,group\nx1,air temperature (°C),parameter\nx2,Estuaries,habitat\nx2,again,habitat\n")
|
|
81
|
+
(tmp_path / "b.txt").write_text("Lynx lynx\n\nEstuaries\n")
|
|
82
|
+
(tmp_path / "c.tsv").write_text("code\tlabel\n1130\tEstuaries\n")
|
|
83
|
+
assert verdy.read_terms(tmp_path / "a.csv") == [
|
|
84
|
+
{"id": "x1", "label": "air temperature (°C)", "category": "parameter"}, {"id": "x2", "label": "Estuaries", "category": "habitat"}]
|
|
85
|
+
assert [t["label"] for t in verdy.read_terms(tmp_path / "b.txt")] == ["Lynx lynx", "Estuaries"]
|
|
86
|
+
assert verdy.read_terms(tmp_path / "c.tsv")[0] == {"id": "1130", "label": "Estuaries", "category": None}
|
|
87
|
+
assert [t["id"] for t in verdy.read_terms(["Lynx lynx", {"name": "Coastal lagoons"}])] == ["lynx_lynx", "coastal_lagoons"]
|
|
88
|
+
with pytest.raises(ValueError):
|
|
89
|
+
verdy.read_terms(tmp_path / "a.csv", column="nope")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_only_the_chosen_ontologies(services):
|
|
93
|
+
ms = verdy.match(["Lynx lynx", "Estuaries", "Coastal lagoons"], ["envo"])
|
|
94
|
+
assert {m["ontology"] for m in ms} == {"ENVO"}
|
|
95
|
+
by = {(m["term"], m["match_label"]): m for m in ms}
|
|
96
|
+
assert by[("Estuaries", "estuary")]["match_type"] == "skos:exactMatch" # plural aside
|
|
97
|
+
assert by[("Coastal lagoons", "lagoon")]["match_type"] == "skos:relatedMatch"
|
|
98
|
+
assert not any(m["term"] == "Lynx lynx" for m in ms)
|
|
99
|
+
assert not any("wikidata" in c or "gbif" in c for c in services) # not chosen, not asked
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_taxonomies_take_exact_names_only(services):
|
|
103
|
+
ms = verdy.match(["Lynx lynx", "Estuaries"], "ncbitaxon, gbif, wikidata")
|
|
104
|
+
got = {(m["term"], m["ontology"]): m for m in ms}
|
|
105
|
+
assert got[("Lynx lynx", "NCBITaxon")]["match_curie"] == "NCBITaxon:13125"
|
|
106
|
+
assert got[("Lynx lynx", "GBIF")]["match_curie"] == "GBIF:2435240"
|
|
107
|
+
assert got[("Lynx lynx", "Wikidata")]["match_type"] == "skos:exactMatch" # by its alias
|
|
108
|
+
assert ("Estuaries", "NCBITaxon") not in got # 'estuaries bacterium' is not it
|
|
109
|
+
assert ("Estuaries", "Wikidata") not in got # an alias alone is no near match
|
|
110
|
+
assert verdy.unmatched(["Lynx lynx", "Estuaries"], ms, "ncbitaxon,gbif") == {"ncbitaxon": ["Estuaries"], "gbif": ["Estuaries"]}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_parameters_in_cf_and_pato(services):
|
|
114
|
+
ms = verdy.match(["air temperature [K]", "wind speed", "surface air temperature"], ["cf", "pato"])
|
|
115
|
+
got = {(m["term"], m["ontology"], m["match_curie"]): m["match_type"] for m in ms}
|
|
116
|
+
assert got[("air temperature [K]", "CF", "CF:air_temperature")] == "skos:exactMatch" # units in brackets aside
|
|
117
|
+
assert got[("surface air temperature", "CF", "CF:air_temperature")] == "skos:exactMatch" # a CF alias
|
|
118
|
+
assert got[("wind speed", "CF", "CF:wind_speed")] == "skos:exactMatch"
|
|
119
|
+
assert got[("air temperature [K]", "PATO", "PATO:0000146")] == "skos:relatedMatch"
|
|
120
|
+
assert next(m for m in ms if m["match_curie"] == "CF:air_temperature")["definition"].startswith("[K]")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_leading_codes_units_and_underscores():
|
|
124
|
+
from verdy.sources import query_text
|
|
125
|
+
assert query_text("1330 Atlantic salt meadows") == "Atlantic salt meadows"
|
|
126
|
+
assert query_text("air temperature [K]") == "air temperature" and query_text("air_temperature") == "air temperature"
|
|
127
|
+
assert query_text("16S rRNA gene") == "16S rRNA gene"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_unknown_or_no_ontology(services):
|
|
131
|
+
with pytest.raises(ValueError, match="nosuch"):
|
|
132
|
+
verdy.match(["Lynx lynx"], ["nosuch"])
|
|
133
|
+
with pytest.raises(ValueError):
|
|
134
|
+
verdy.match(["Lynx lynx"], [])
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_outputs(services, tmp_path):
|
|
138
|
+
ms = verdy.match(["Lynx lynx", "Estuaries"], ["envo", "ncbitaxon"])
|
|
139
|
+
for f in ("m.csv", "m.tsv", "m.json", "m.sssom.tsv"):
|
|
140
|
+
verdy.save(ms, tmp_path / f)
|
|
141
|
+
rows = list(csv.DictReader(open(tmp_path / "m.csv")))
|
|
142
|
+
assert {r["match_curie"] for r in rows} == {"ENVO:00000045", "NCBITaxon:13125"}
|
|
143
|
+
assert json.loads((tmp_path / "m.json").read_text()) == ms
|
|
144
|
+
sssom = (tmp_path / "m.sssom.tsv").read_text()
|
|
145
|
+
assert "semapv:LexicalMatching" in sssom and "# ENVO: http://purl.obolibrary.org/obo/ENVO_" in sssom
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_cache_makes_no_second_request(services):
|
|
149
|
+
verdy.match(["Estuaries"], ["envo"])
|
|
150
|
+
n = len(services)
|
|
151
|
+
verdy.match(["Estuaries"], ["envo"])
|
|
152
|
+
assert len(services) == n
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_command_line(services, tmp_path, capsys):
|
|
156
|
+
from verdy.__main__ import main
|
|
157
|
+
(tmp_path / "n.csv").write_text("name\nLynx lynx\nEstuaries\n")
|
|
158
|
+
assert main(["--list"]) == 0 and "ncbitaxon" in capsys.readouterr().out
|
|
159
|
+
assert main([str(tmp_path / "n.csv"), "-o", "envo,ncbitaxon", "--out", str(tmp_path / "o.csv")]) == 0
|
|
160
|
+
err = capsys.readouterr().err
|
|
161
|
+
assert "2 matches for 2 of 2 inputs" in err and "nothing in envo for: Lynx lynx" in err
|
|
162
|
+
assert (tmp_path / "o.csv").exists()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_ids_are_looked_up_in_ena_and_the_ontologies(services):
|
|
166
|
+
import importlib
|
|
167
|
+
r = importlib.import_module("verdy.resolve")
|
|
168
|
+
assert [r.kind(x) for x in ("SAMD1", "ERS1", "ERR1", "PRJEB1", "ENVO:00000045", "air")] == \
|
|
169
|
+
["sample", "sample", "run", "project", "name", "name"]
|
|
170
|
+
assert r.resolve("SAMD1") == [("scientific_name", "air metagenome"), ("broad_scale_environmental_context", "urban biome"),
|
|
171
|
+
("local_environmental_context", "air"), ("environmental_medium", "air")] # 'missing' left out
|
|
172
|
+
assert r.resolve("ERR1") == r.resolve("SAMD1")
|
|
173
|
+
assert r.resolve("air temperature") == [("name", "air temperature")]
|
|
174
|
+
assert r.resolve("SAMD999") == []
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_ids_in_predictions_out(services, tmp_path):
|
|
178
|
+
(tmp_path / "ids.csv").write_text("sample_accession\nSAMD1\nEstuaries\nPRJEB1\nSAMD999\n")
|
|
179
|
+
ms = verdy.match(tmp_path / "ids.csv", ["envo", "gold", "natura2000"])
|
|
180
|
+
rows = {(m["term_id"], m["term"], m["ontology"], m["match_curie"] or m["match_id"]): m for m in ms}
|
|
181
|
+
air = rows[("SAMD1", "air", "ENVO", "ENVO:00002005")]
|
|
182
|
+
assert air["category"] == "local_environmental_context|environmental_medium" and air["match_type"] == "skos:exactMatch"
|
|
183
|
+
assert ("SAMD1", "urban biome", "ENVO", "ENVO:01000249") in rows
|
|
184
|
+
assert any(k[0] == "SAMD1" and k[2] == "GOLD" and "Air" in rows[k]["match_label"] for k in rows) # GOLD kept
|
|
185
|
+
assert rows[("estuaries", "Estuaries", "Natura2000", "n2k:1130")]["match_type"] == "skos:exactMatch"
|
|
186
|
+
assert verdy.match.unresolved == ["SAMD999"]
|
|
187
|
+
assert not any(m["term_id"] == "PRJEB1" for m in ms) # a project's rows carry its samples' IDs
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_free_text_and_ena_ids_together(services):
|
|
191
|
+
ms = verdy.match(["SAMD1", "Estuaries"], ["envo"])
|
|
192
|
+
assert {(m["term_id"], m["term"]) for m in ms} >= {("SAMD1", "air"), ("SAMD1", "urban biome"), ("estuaries", "Estuaries")}
|
|
193
|
+
sssom = verdy.to_sssom(ms)
|
|
194
|
+
assert "biosample:SAMD1\t" in sssom and "term:estuaries" in sssom
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""VERDY: predict matches for your ENA IDs or free-text terms in the ontologies you choose.
|
|
2
|
+
|
|
3
|
+
Part of VERDANT: Vocabulary Enrichment & Resolution of Diversity Annotations via Neural Technology.
|
|
4
|
+
|
|
5
|
+
Input (a CSV / TSV / Excel file, a text file or a Python list) holds ENA IDs (samples,
|
|
6
|
+
runs, projects) and/or free text (species, habitats, parameters, measurements). ENA IDs
|
|
7
|
+
are looked up in ENA for their organism and environment fields; every name is searched in
|
|
8
|
+
each ontology you choose; out come its exact and most related terms.
|
|
9
|
+
|
|
10
|
+
import verdy
|
|
11
|
+
|
|
12
|
+
matches = verdy.match("my_input.csv", ontologies=["natura2000", "gold", "envo", "ncbitaxon"])
|
|
13
|
+
verdy.save(matches, "my_input.matches.csv") # or .tsv / .sssom.tsv / .json / .xlsx
|
|
14
|
+
verdy.match.unresolved # ENA IDs ENA did not know
|
|
15
|
+
verdy.unmatched("my_input.csv", matches, ["envo"]) # names an ontology found nothing for
|
|
16
|
+
|
|
17
|
+
verdy.ONTOLOGIES # the ready ones; any OLS4 ontology ID works too
|
|
18
|
+
verdy.search("air temperature", "cf") # one name in one ontology, its best terms
|
|
19
|
+
verdy.resolve("SAMD00132352") # what ENA says about an ID
|
|
20
|
+
|
|
21
|
+
From the shell: verdy my_input.csv -o natura2000,gold,envo --out my_input.matches.csv
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from verdy.match import MATCH_TYPES, match, read_terms, resolve_terms, unmatched
|
|
25
|
+
from verdy.resolve import SAMPLE_FIELDS, resolve
|
|
26
|
+
from verdy.output import save, to_dataframe, to_sssom, to_table
|
|
27
|
+
from verdy.sources import ONTOLOGIES, ontology_info, search
|
|
28
|
+
|
|
29
|
+
__version__ = "0.1.0"
|
|
30
|
+
|
|
31
|
+
predict = match # the same: IDs or names in, predicted matches out
|
|
32
|
+
|
|
33
|
+
__all__ = ["match", "predict", "resolve", "resolve_terms", "SAMPLE_FIELDS", "search", "read_terms", "unmatched", "save", "to_table", "to_sssom", "to_dataframe",
|
|
34
|
+
"ONTOLOGIES", "MATCH_TYPES", "ontology_info", "__version__"]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Command line: verdy TERMS -o envo,pato,cf [--column NAME] [--out FILE]
|
|
2
|
+
|
|
3
|
+
verdy my_terms.csv -o envo,ncbitaxon,gbif --out my_terms.matches.csv
|
|
4
|
+
verdy my_terms.csv -o pato,cf,uo # print a table instead
|
|
5
|
+
verdy --list # the ready ontologies
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from verdy.match import match, read_terms, resolve_terms, unmatched
|
|
15
|
+
from verdy.output import save, to_table
|
|
16
|
+
from verdy.sources import ONTOLOGIES
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv=None) -> int:
|
|
20
|
+
ap = argparse.ArgumentParser(prog="verdy", description="Match your terms to the ontologies you choose.")
|
|
21
|
+
ap.add_argument("terms", nargs="?", help="CSV / TSV / Excel with an ID or name column, or a text file with one per line")
|
|
22
|
+
ap.add_argument("-o", "--ontologies", help="comma-separated IDs: see --list; any OLS4 ontology ID works too")
|
|
23
|
+
ap.add_argument("--column", help="the column holding the terms (found by its header otherwise)")
|
|
24
|
+
ap.add_argument("--out", type=Path, help="write here: .csv, .tsv, .sssom.tsv, .json or .xlsx (default: print)")
|
|
25
|
+
ap.add_argument("--per-ontology", type=int, default=3, help="at most this many matches per term and ontology (3)")
|
|
26
|
+
ap.add_argument("--min-score", type=float, default=0.34, help="least word overlap for a related match (0.34)")
|
|
27
|
+
ap.add_argument("--list", action="store_true", help="list the ready ontologies")
|
|
28
|
+
args = ap.parse_args(argv)
|
|
29
|
+
if args.list:
|
|
30
|
+
for k, v in ONTOLOGIES.items():
|
|
31
|
+
print(f"{k:11} {v}")
|
|
32
|
+
return 0
|
|
33
|
+
if not args.terms or not args.ontologies:
|
|
34
|
+
ap.error("give a TERMS file and --ontologies (or --list)")
|
|
35
|
+
try:
|
|
36
|
+
terms = read_terms(args.terms, args.column)
|
|
37
|
+
matches = match(terms, args.ontologies, per_ontology=args.per_ontology,
|
|
38
|
+
min_score=args.min_score,
|
|
39
|
+
progress=lambda d, t: print(f"\r{d} of {t} lookups", end="", file=sys.stderr))
|
|
40
|
+
except ValueError as exc:
|
|
41
|
+
ap.error(str(exc))
|
|
42
|
+
print(file=sys.stderr)
|
|
43
|
+
unresolved = match.unresolved
|
|
44
|
+
if unresolved:
|
|
45
|
+
print(f"nothing found for {len(unresolved)} IDs: {', '.join(unresolved[:20])}"
|
|
46
|
+
+ (" ..." if len(unresolved) > 20 else ""), file=sys.stderr)
|
|
47
|
+
if args.out:
|
|
48
|
+
save(matches, args.out)
|
|
49
|
+
print(f"{len(matches)} matches for {len({m['term_id'] for m in matches})} of {len(terms)} inputs -> {args.out}",
|
|
50
|
+
file=sys.stderr)
|
|
51
|
+
else:
|
|
52
|
+
sys.stdout.write(to_table(matches, "\t"))
|
|
53
|
+
for o, missing in unmatched(resolve_terms(terms)[0], matches, args.ontologies).items():
|
|
54
|
+
if missing:
|
|
55
|
+
print(f"nothing in {o} for: {', '.join(missing)}", file=sys.stderr)
|
|
56
|
+
return 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
sys.exit(main())
|