impc-api 1.0.7__tar.gz → 2.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.
- {impc_api-1.0.7/impc_api.egg-info → impc_api-2.1.0}/PKG-INFO +53 -12
- impc_api-1.0.7/PKG-INFO → impc_api-2.1.0/README.md +47 -23
- {impc_api-1.0.7 → impc_api-2.1.0}/impc_api/__init__.py +2 -1
- {impc_api-1.0.7 → impc_api-2.1.0}/impc_api/batch_solr_request.py +51 -33
- impc_api-2.1.0/impc_api/get_core_fields.py +22 -0
- {impc_api-1.0.7 → impc_api-2.1.0}/impc_api/solr_request.py +39 -8
- {impc_api-1.0.7 → impc_api-2.1.0}/impc_api/utils/validators.py +9 -6
- {impc_api-1.0.7 → impc_api-2.1.0}/impc_api/utils/warnings.py +13 -2
- {impc_api-1.0.7 → impc_api-2.1.0}/pyproject.toml +10 -9
- impc_api-1.0.7/MANIFEST.in +0 -1
- impc_api-1.0.7/README.md +0 -213
- impc_api-1.0.7/impc_api.egg-info/SOURCES.txt +0 -19
- impc_api-1.0.7/impc_api.egg-info/dependency_links.txt +0 -1
- impc_api-1.0.7/impc_api.egg-info/requires.txt +0 -9
- impc_api-1.0.7/impc_api.egg-info/top_level.txt +0 -1
- impc_api-1.0.7/setup.cfg +0 -4
- impc_api-1.0.7/tests/test_batch_solr_request.py +0 -746
- impc_api-1.0.7/tests/test_helpers.py +0 -33
- impc_api-1.0.7/tests/test_solr_request.py +0 -409
- {impc_api-1.0.7 → impc_api-2.1.0}/LICENSE +0 -0
- {impc_api-1.0.7 → impc_api-2.1.0}/impc_api/utils/__init__.py +0 -0
- {impc_api-1.0.7 → impc_api-2.1.0}/impc_api/utils/core_fields.json +0 -0
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: impc_api
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: A package to facilitate making API requests to the IMPC Solr API
|
|
5
5
|
Author: MPI2, Marina Kan, Diego Pava
|
|
6
|
-
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
6
|
+
License-Expression: Apache-2.0
|
|
9
7
|
License-File: LICENSE
|
|
10
8
|
Requires-Dist: ipykernel>=6.29.5
|
|
11
9
|
Requires-Dist: notebook>=7.2.2
|
|
12
10
|
Requires-Dist: pandas>=2.2.0
|
|
13
|
-
Requires-Dist: requests>=2.
|
|
11
|
+
Requires-Dist: requests>=2.33.0
|
|
14
12
|
Requires-Dist: tqdm>=4.66.4
|
|
15
13
|
Requires-Dist: pydantic>=2.9
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Project-URL: Homepage, https://github.com/mpi2/impc-api
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
19
17
|
|
|
20
18
|
# IMPC_API
|
|
21
19
|
|
|
@@ -23,7 +21,8 @@ Dynamic: license-file
|
|
|
23
21
|
|
|
24
22
|
## Installation Instructions
|
|
25
23
|
|
|
26
|
-
0. Ensure that Python is installed on your system. The minimum required version is 3.
|
|
24
|
+
0. Ensure that Python is installed on your system. The minimum required version is 3.12.
|
|
25
|
+
Python 3.10 will not be supported after version 2.0.0.
|
|
27
26
|
|
|
28
27
|
1. **Create a virtual environment (optional but recommended)**:
|
|
29
28
|
On Mac or Linux:
|
|
@@ -46,7 +45,7 @@ Create a [Jupyter Notebook](https://jupyter-notebook.readthedocs.io/en/latest/)
|
|
|
46
45
|
The available functions can be imported as:
|
|
47
46
|
|
|
48
47
|
```python
|
|
49
|
-
from impc_api import
|
|
48
|
+
from impc_api import batch_solr_request, get_core_fields, solr_request
|
|
50
49
|
```
|
|
51
50
|
|
|
52
51
|
# 1. Solr request
|
|
@@ -64,6 +63,31 @@ num_found, df = solr_request(
|
|
|
64
63
|
)
|
|
65
64
|
```
|
|
66
65
|
|
|
66
|
+
By default, `solr_request` uses a 10 second request timeout. To use a different timeout, pass the optional `timeout` argument:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
num_found, df = solr_request(
|
|
70
|
+
core='genotype-phenotype',
|
|
71
|
+
params={
|
|
72
|
+
'q': '*:*',
|
|
73
|
+
'rows': 10,
|
|
74
|
+
'fl': 'marker_symbol,allele_symbol,parameter_stable_id'
|
|
75
|
+
},
|
|
76
|
+
timeout=30
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
To use another compatible Solr deployment, override the base URL. The same
|
|
81
|
+
argument is available on `batch_solr_request`:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
num_found, df = solr_request(
|
|
85
|
+
core='genotype-phenotype',
|
|
86
|
+
params={'q': '*:*', 'rows': 10},
|
|
87
|
+
base_url='http://solr-host:8983/solr',
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
67
91
|
## a. Facet request
|
|
68
92
|
|
|
69
93
|
`solr_request` allows facet requests
|
|
@@ -144,7 +168,7 @@ print(url)
|
|
|
144
168
|
|
|
145
169
|
Use `batch_solr_request` for:
|
|
146
170
|
|
|
147
|
-
- Large queries (>
|
|
171
|
+
- Large queries (>1,000,000 rows)
|
|
148
172
|
- Querying multiple items in a list
|
|
149
173
|
- Downloading data in `json` or `csv` format.
|
|
150
174
|
|
|
@@ -156,7 +180,7 @@ For large queries you can choose between seeing them in a DataFrame or downloadi
|
|
|
156
180
|
|
|
157
181
|
This will fetch your data using the API responsibly and return a Pandas DataFrame
|
|
158
182
|
|
|
159
|
-
When your request is larger than recommended and you have not opted
|
|
183
|
+
When your request is larger than recommended and you have not opted to download the data, a warning will be presented suggesting that you retry your request and download the data.
|
|
160
184
|
|
|
161
185
|
```python
|
|
162
186
|
df = batch_solr_request(
|
|
@@ -230,3 +254,20 @@ df = batch_solr_request(
|
|
|
230
254
|
)
|
|
231
255
|
print(df.head())
|
|
232
256
|
```
|
|
257
|
+
# 3.Get Core Fields
|
|
258
|
+
|
|
259
|
+
## Retrieve valid fields for a core
|
|
260
|
+
|
|
261
|
+
Use `get_core_fields` before constructing a request to discover the documented
|
|
262
|
+
fields available for a public IMPC Solr core. The function uses the same source
|
|
263
|
+
of truth as request validation and does not make a network request.
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from impc_api import get_core_fields
|
|
267
|
+
|
|
268
|
+
get_core_fields("genotype-phenotype")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The complete list is returned. An unknown core returns an empty list. Skills and
|
|
272
|
+
other integrations should call this function instead of maintaining their own
|
|
273
|
+
copies of core field names.
|
|
@@ -1,29 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: impc_api
|
|
3
|
-
Version: 1.0.7
|
|
4
|
-
Summary: A package to facilitate making API requests to the IMPC Solr API
|
|
5
|
-
Author: MPI2, Marina Kan, Diego Pava
|
|
6
|
-
Project-URL: Homepage, https://github.com/mpi2/impc-api
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Requires-Dist: ipykernel>=6.29.5
|
|
11
|
-
Requires-Dist: notebook>=7.2.2
|
|
12
|
-
Requires-Dist: pandas>=2.2.0
|
|
13
|
-
Requires-Dist: requests>=2.31.0
|
|
14
|
-
Requires-Dist: tqdm>=4.66.4
|
|
15
|
-
Requires-Dist: pydantic>=2.9
|
|
16
|
-
Provides-Extra: dev
|
|
17
|
-
Requires-Dist: pytest>=8.2.2; extra == "dev"
|
|
18
|
-
Dynamic: license-file
|
|
19
|
-
|
|
20
1
|
# IMPC_API
|
|
21
2
|
|
|
22
3
|
`impc_api` is a Python package which provides several helper functions that wrap around the IMPC SOLR API. The functions in this package are intended for use in a Jupyter Notebook.
|
|
23
4
|
|
|
24
5
|
## Installation Instructions
|
|
25
6
|
|
|
26
|
-
0. Ensure that Python is installed on your system. The minimum required version is 3.
|
|
7
|
+
0. Ensure that Python is installed on your system. The minimum required version is 3.12.
|
|
8
|
+
Python 3.10 will not be supported after version 2.0.0.
|
|
27
9
|
|
|
28
10
|
1. **Create a virtual environment (optional but recommended)**:
|
|
29
11
|
On Mac or Linux:
|
|
@@ -46,7 +28,7 @@ Create a [Jupyter Notebook](https://jupyter-notebook.readthedocs.io/en/latest/)
|
|
|
46
28
|
The available functions can be imported as:
|
|
47
29
|
|
|
48
30
|
```python
|
|
49
|
-
from impc_api import
|
|
31
|
+
from impc_api import batch_solr_request, get_core_fields, solr_request
|
|
50
32
|
```
|
|
51
33
|
|
|
52
34
|
# 1. Solr request
|
|
@@ -64,6 +46,31 @@ num_found, df = solr_request(
|
|
|
64
46
|
)
|
|
65
47
|
```
|
|
66
48
|
|
|
49
|
+
By default, `solr_request` uses a 10 second request timeout. To use a different timeout, pass the optional `timeout` argument:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
num_found, df = solr_request(
|
|
53
|
+
core='genotype-phenotype',
|
|
54
|
+
params={
|
|
55
|
+
'q': '*:*',
|
|
56
|
+
'rows': 10,
|
|
57
|
+
'fl': 'marker_symbol,allele_symbol,parameter_stable_id'
|
|
58
|
+
},
|
|
59
|
+
timeout=30
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
To use another compatible Solr deployment, override the base URL. The same
|
|
64
|
+
argument is available on `batch_solr_request`:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
num_found, df = solr_request(
|
|
68
|
+
core='genotype-phenotype',
|
|
69
|
+
params={'q': '*:*', 'rows': 10},
|
|
70
|
+
base_url='http://solr-host:8983/solr',
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
67
74
|
## a. Facet request
|
|
68
75
|
|
|
69
76
|
`solr_request` allows facet requests
|
|
@@ -144,7 +151,7 @@ print(url)
|
|
|
144
151
|
|
|
145
152
|
Use `batch_solr_request` for:
|
|
146
153
|
|
|
147
|
-
- Large queries (>
|
|
154
|
+
- Large queries (>1,000,000 rows)
|
|
148
155
|
- Querying multiple items in a list
|
|
149
156
|
- Downloading data in `json` or `csv` format.
|
|
150
157
|
|
|
@@ -156,7 +163,7 @@ For large queries you can choose between seeing them in a DataFrame or downloadi
|
|
|
156
163
|
|
|
157
164
|
This will fetch your data using the API responsibly and return a Pandas DataFrame
|
|
158
165
|
|
|
159
|
-
When your request is larger than recommended and you have not opted
|
|
166
|
+
When your request is larger than recommended and you have not opted to download the data, a warning will be presented suggesting that you retry your request and download the data.
|
|
160
167
|
|
|
161
168
|
```python
|
|
162
169
|
df = batch_solr_request(
|
|
@@ -230,3 +237,20 @@ df = batch_solr_request(
|
|
|
230
237
|
)
|
|
231
238
|
print(df.head())
|
|
232
239
|
```
|
|
240
|
+
# 3.Get Core Fields
|
|
241
|
+
|
|
242
|
+
## Retrieve valid fields for a core
|
|
243
|
+
|
|
244
|
+
Use `get_core_fields` before constructing a request to discover the documented
|
|
245
|
+
fields available for a public IMPC Solr core. The function uses the same source
|
|
246
|
+
of truth as request validation and does not make a network request.
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from impc_api import get_core_fields
|
|
250
|
+
|
|
251
|
+
get_core_fields("genotype-phenotype")
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The complete list is returned. An unknown core returns an empty list. Skills and
|
|
255
|
+
other integrations should call this function instead of maintaining their own
|
|
256
|
+
copies of core field names.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .solr_request import solr_request
|
|
2
2
|
from .batch_solr_request import batch_solr_request
|
|
3
|
+
from .get_core_fields import get_core_fields
|
|
3
4
|
from .utils import validators, warnings
|
|
4
5
|
|
|
5
6
|
# Control what gets imported by client
|
|
6
|
-
__all__ = ["solr_request", "batch_solr_request"]
|
|
7
|
+
__all__ = ["solr_request", "batch_solr_request", "get_core_fields"]
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import warnings
|
|
3
|
+
from collections.abc import Iterator
|
|
3
4
|
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, Iterable, List, Union
|
|
4
6
|
|
|
5
7
|
import pandas as pd
|
|
6
8
|
import requests
|
|
@@ -12,15 +14,24 @@ from impc_api.utils.warnings import (
|
|
|
12
14
|
warning_config,
|
|
13
15
|
RowsParamIgnored,
|
|
14
16
|
UnsupportedDownloadFormatError,
|
|
17
|
+
LargeRequestMemoryWarning
|
|
15
18
|
)
|
|
16
|
-
from .solr_request import solr_request
|
|
19
|
+
from .solr_request import DEFAULT_SOLR_BASE_URL, solr_request
|
|
17
20
|
|
|
18
21
|
# Initialise warning config
|
|
19
22
|
warning_config()
|
|
20
23
|
|
|
24
|
+
BatchSolrChunk = Union[List[Dict[str, Any]], str]
|
|
25
|
+
|
|
26
|
+
|
|
21
27
|
def batch_solr_request(
|
|
22
|
-
core
|
|
23
|
-
|
|
28
|
+
core: str,
|
|
29
|
+
params: Dict[str, Any],
|
|
30
|
+
download: bool = False,
|
|
31
|
+
batch_size: int = 5000,
|
|
32
|
+
filename: str = "batch_request",
|
|
33
|
+
base_url: str = DEFAULT_SOLR_BASE_URL,
|
|
34
|
+
) -> pd.DataFrame:
|
|
24
35
|
"""Function for large API requests (>1,000,000 results). Fetches the data in batches and
|
|
25
36
|
produces a Pandas DataFrame or downloads a file in json or csv formats.
|
|
26
37
|
|
|
@@ -32,11 +43,12 @@ def batch_solr_request(
|
|
|
32
43
|
download (bool, optional): True for download a local file, False to display results as a DataFrame. Defaults to False.
|
|
33
44
|
batch_size (int, optional): Size of batches to fetch the data. Defaults to 5000.
|
|
34
45
|
filename (str, optional): When download=True, select the name of the file. Defaults to 'batch_request'.
|
|
46
|
+
base_url (str, optional): default public IMPC Solr endpoint.
|
|
47
|
+
Override this to use another compatible Solr deployment.
|
|
35
48
|
|
|
36
49
|
|
|
37
50
|
Returns:
|
|
38
51
|
pd.DataFrame: if download=False, displays a DataFrame with the results.
|
|
39
|
-
None: if download=True, displays a statement on the console and returns None.
|
|
40
52
|
"""
|
|
41
53
|
|
|
42
54
|
# If params["rows"] is passed, the user is warned about no effect
|
|
@@ -72,7 +84,10 @@ def batch_solr_request(
|
|
|
72
84
|
|
|
73
85
|
# Determine the total number of rows. Note that we do not request any data (rows = 0).
|
|
74
86
|
num_results, _ = solr_request(
|
|
75
|
-
core=core,
|
|
87
|
+
core=core,
|
|
88
|
+
params={**params, "start": 0, "rows": 0, "wt": "json"},
|
|
89
|
+
silent=True,
|
|
90
|
+
base_url=base_url,
|
|
76
91
|
)
|
|
77
92
|
print(f"Number of found documents: {num_results}")
|
|
78
93
|
|
|
@@ -86,44 +101,37 @@ def batch_solr_request(
|
|
|
86
101
|
# Implement loop behaviour
|
|
87
102
|
print("Downloading file...")
|
|
88
103
|
filename_path = Path(f"{filename}.{params['wt']}")
|
|
89
|
-
gen = _batch_solr_generator(core, params, num_results)
|
|
104
|
+
gen = _batch_solr_generator(core, params, num_results, base_url=base_url)
|
|
90
105
|
_solr_downloader(params, filename_path, gen)
|
|
91
106
|
print(f"File saved as: {filename_path}")
|
|
92
107
|
except UnsupportedDownloadFormatError as e:
|
|
93
108
|
raise e
|
|
94
109
|
except Exception as e:
|
|
95
|
-
raise (f"An error
|
|
110
|
+
raise Exception(f"An error occurred while downloading the data:{e}")
|
|
96
111
|
|
|
97
112
|
# Try to read the downloaded file
|
|
98
113
|
try:
|
|
99
114
|
print("Reading downloaded file...")
|
|
100
115
|
return _read_downloaded_file(filename_path, params["wt"])
|
|
101
116
|
except Exception as e:
|
|
102
|
-
raise Exception(f"An unexpected error
|
|
103
|
-
|
|
104
|
-
# If the number of results is small enough and download is off, it's okay to show as df
|
|
105
|
-
if num_results < 1000000 and not download:
|
|
106
|
-
return _batch_to_df(core, params, num_results)
|
|
117
|
+
raise Exception(f"An unexpected error occurred:{e}")
|
|
107
118
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"Do you wish to proceed anyway? press ('y' or enter to proceed) / type('n' or 'exit' to cancel)"
|
|
119
|
+
|
|
120
|
+
# If a request is too large, warn the user to use download=true.
|
|
121
|
+
if num_results > 1000000 and not download:
|
|
122
|
+
warnings.warn(
|
|
123
|
+
message="This request may exceed available memory. If the download fails, set 'download=True' and try again.",
|
|
124
|
+
category=LargeRequestMemoryWarning,
|
|
115
125
|
)
|
|
116
|
-
|
|
117
|
-
case "n" | "exit":
|
|
118
|
-
print("Exiting gracefully")
|
|
119
|
-
exit()
|
|
120
|
-
case "y" | "":
|
|
121
|
-
print("Fetching data...")
|
|
122
|
-
return _batch_to_df(core, params, num_results)
|
|
123
|
-
|
|
126
|
+
return _batch_to_df(core, params, num_results, base_url=base_url)
|
|
124
127
|
|
|
125
128
|
# Helper batch_to_df
|
|
126
|
-
def _batch_to_df(
|
|
129
|
+
def _batch_to_df(
|
|
130
|
+
core: str,
|
|
131
|
+
params: Dict[str, Any],
|
|
132
|
+
num_results: int,
|
|
133
|
+
base_url: str = DEFAULT_SOLR_BASE_URL,
|
|
134
|
+
) -> pd.DataFrame:
|
|
127
135
|
"""Helper function to fetch data in batches and display them in a DataFrame
|
|
128
136
|
|
|
129
137
|
Args:
|
|
@@ -148,6 +156,7 @@ def _batch_to_df(core, params, num_results):
|
|
|
148
156
|
core=core,
|
|
149
157
|
params={**params, "start": start, "rows": batch_size},
|
|
150
158
|
silent=True,
|
|
159
|
+
base_url=base_url,
|
|
151
160
|
)
|
|
152
161
|
|
|
153
162
|
# Update progress bar with the number of rows requested.
|
|
@@ -161,7 +170,12 @@ def _batch_to_df(core, params, num_results):
|
|
|
161
170
|
return pd.concat(chunks, ignore_index=True)
|
|
162
171
|
|
|
163
172
|
|
|
164
|
-
def _batch_solr_generator(
|
|
173
|
+
def _batch_solr_generator(
|
|
174
|
+
core: str,
|
|
175
|
+
params: Dict[str, Any],
|
|
176
|
+
num_results: int,
|
|
177
|
+
base_url: str = DEFAULT_SOLR_BASE_URL,
|
|
178
|
+
) -> Iterator[BatchSolrChunk]:
|
|
165
179
|
"""Generator function to fetch results from the SOLR API in batches using pagination.
|
|
166
180
|
|
|
167
181
|
Args:
|
|
@@ -175,8 +189,7 @@ def _batch_solr_generator(core, params, num_results):
|
|
|
175
189
|
Yields:
|
|
176
190
|
([dict, str]): A JSON object or plain text with the results.
|
|
177
191
|
"""
|
|
178
|
-
|
|
179
|
-
solr_url = base_url + core + "/select"
|
|
192
|
+
solr_url = f"{base_url.rstrip('/')}/{core}/select"
|
|
180
193
|
start = params["start"]
|
|
181
194
|
batch_size = params["rows"]
|
|
182
195
|
|
|
@@ -205,7 +218,11 @@ def _batch_solr_generator(core, params, num_results):
|
|
|
205
218
|
|
|
206
219
|
|
|
207
220
|
# File writer
|
|
208
|
-
def _solr_downloader(
|
|
221
|
+
def _solr_downloader(
|
|
222
|
+
params: Dict[str, Any],
|
|
223
|
+
filename: Path,
|
|
224
|
+
solr_generator: Iterable[BatchSolrChunk],
|
|
225
|
+
) -> None:
|
|
209
226
|
"""Function to write the data from the generator into the specified format.
|
|
210
227
|
Supports json and csv only.
|
|
211
228
|
|
|
@@ -230,6 +247,7 @@ def _solr_downloader(params, filename, solr_generator):
|
|
|
230
247
|
elif params.get("wt") == "csv":
|
|
231
248
|
first_chunk = True
|
|
232
249
|
for chunk in solr_generator:
|
|
250
|
+
assert isinstance(chunk, str)
|
|
233
251
|
lines = chunk.splitlines()
|
|
234
252
|
if first_chunk:
|
|
235
253
|
# Write all lines in the first chunk
|
|
@@ -241,7 +259,7 @@ def _solr_downloader(params, filename, solr_generator):
|
|
|
241
259
|
|
|
242
260
|
|
|
243
261
|
# File reader
|
|
244
|
-
def _read_downloaded_file(filename: Path, request_format):
|
|
262
|
+
def _read_downloaded_file(filename: Path, request_format: str) -> pd.DataFrame:
|
|
245
263
|
"""Wrapper for reading files into Pandas DataFrames
|
|
246
264
|
|
|
247
265
|
Args:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from .utils.validators import ValidationJson
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_core_fields(core: str) -> list[str]:
|
|
5
|
+
"""Return the documented fields available for an IMPC Solr core.
|
|
6
|
+
|
|
7
|
+
Fields are read from the same source of truth used to validate requests.
|
|
8
|
+
This function does not make a request to the IMPC Solr API.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
core: Name of a public IMPC Solr core.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
The documented field names, or an empty list when ``core`` is unknown.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
>>> from impc_api import get_core_fields
|
|
18
|
+
>>> fields = get_core_fields("genotype-phenotype")
|
|
19
|
+
>>> fields[:3]
|
|
20
|
+
['doc_id', 'ontology_db_id', 'assertion_type']
|
|
21
|
+
"""
|
|
22
|
+
return ValidationJson().valid_fields(core)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
|
2
|
+
|
|
1
3
|
import pandas as pd
|
|
2
4
|
import requests
|
|
3
5
|
from IPython.display import display
|
|
@@ -5,12 +7,28 @@ from tqdm import tqdm
|
|
|
5
7
|
|
|
6
8
|
from impc_api.utils.validators import CoreParamsValidator
|
|
7
9
|
|
|
10
|
+
RequestTimeout = Union[float, Tuple[float, float]]
|
|
11
|
+
SolrRequestResult = Optional[
|
|
12
|
+
Union[Tuple[int, pd.DataFrame], Tuple[Optional[str], None]]
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
DEFAULT_REQUEST_TIMEOUT: RequestTimeout = 10.0
|
|
16
|
+
DEFAULT_SOLR_BASE_URL = "https://www.ebi.ac.uk/mi/impc/solr/"
|
|
17
|
+
|
|
8
18
|
# Display the whole dataframe <15
|
|
9
19
|
pd.set_option("display.max_rows", 15)
|
|
10
20
|
pd.set_option("display.max_columns", None)
|
|
11
21
|
|
|
12
22
|
# Create helper function
|
|
13
|
-
def solr_request(
|
|
23
|
+
def solr_request(
|
|
24
|
+
core: str,
|
|
25
|
+
params: Dict[str, Any],
|
|
26
|
+
silent: bool = False,
|
|
27
|
+
validate: bool = False,
|
|
28
|
+
url_only: bool = False,
|
|
29
|
+
timeout: RequestTimeout = DEFAULT_REQUEST_TIMEOUT,
|
|
30
|
+
base_url: str = DEFAULT_SOLR_BASE_URL,
|
|
31
|
+
) -> SolrRequestResult:
|
|
14
32
|
"""Performs a single Solr request to the IMPC Solr API.
|
|
15
33
|
|
|
16
34
|
Args:
|
|
@@ -24,6 +42,10 @@ def solr_request(core, params, silent=False, validate=False, url_only=False):
|
|
|
24
42
|
if any parameter seems invalid.
|
|
25
43
|
url_only (bool, optional): default False
|
|
26
44
|
If true, returns the request URL but no data.
|
|
45
|
+
timeout (int or float, optional): default 10
|
|
46
|
+
Number of seconds to wait for the API request before timing out.
|
|
47
|
+
base_url (str, optional): default public IMPC Solr endpoint.
|
|
48
|
+
Override this to use another compatible Solr deployment.
|
|
27
49
|
|
|
28
50
|
|
|
29
51
|
Returns:
|
|
@@ -77,10 +99,9 @@ def solr_request(core, params, silent=False, validate=False, url_only=False):
|
|
|
77
99
|
if validate:
|
|
78
100
|
CoreParamsValidator(core=core, params=params)
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
solr_url = base_url + core + "/select"
|
|
102
|
+
solr_url = f"{base_url.rstrip('/')}/{core}/select"
|
|
82
103
|
|
|
83
|
-
response = requests.get(solr_url, params=params)
|
|
104
|
+
response = requests.get(solr_url, params=params, timeout=timeout)
|
|
84
105
|
|
|
85
106
|
if url_only:
|
|
86
107
|
if response.status_code == 200:
|
|
@@ -124,7 +145,7 @@ def solr_request(core, params, silent=False, validate=False, url_only=False):
|
|
|
124
145
|
print("Error:", response.status_code, response.text)
|
|
125
146
|
|
|
126
147
|
|
|
127
|
-
def _process_faceting(data, params):
|
|
148
|
+
def _process_faceting(data: Dict[str, Any], params: Dict[str, Any]) -> pd.DataFrame:
|
|
128
149
|
"""Processes the faceting data from an API response.
|
|
129
150
|
Note: This function should not be used alone but only as a helper function for solr_request().
|
|
130
151
|
|
|
@@ -157,7 +178,12 @@ def _process_faceting(data, params):
|
|
|
157
178
|
|
|
158
179
|
|
|
159
180
|
# Batch request based on solr_request.
|
|
160
|
-
def batch_request(
|
|
181
|
+
def batch_request(
|
|
182
|
+
core: str,
|
|
183
|
+
params: Dict[str, Any],
|
|
184
|
+
batch_size: int,
|
|
185
|
+
base_url: str = DEFAULT_SOLR_BASE_URL,
|
|
186
|
+
) -> pd.DataFrame:
|
|
161
187
|
"""Calls `solr_request` multiple times with `params`
|
|
162
188
|
to retrieve results in chunk `batch_size` rows at a time.
|
|
163
189
|
|
|
@@ -166,7 +192,8 @@ def batch_request(core, params, batch_size):
|
|
|
166
192
|
Args:
|
|
167
193
|
core (str): name of IMPC solr core.
|
|
168
194
|
params (dict): dictionary containing the API call parameters.
|
|
169
|
-
|
|
195
|
+
batch_size (int): Size of batches (number of docs) per request.
|
|
196
|
+
base_url (str, optional): default public IMPC Solr endpoint.
|
|
170
197
|
|
|
171
198
|
Returns:
|
|
172
199
|
pandas.DataFrame: Pandas.DataFrame object with the information requested.
|
|
@@ -188,7 +215,10 @@ def batch_request(core, params, batch_size):
|
|
|
188
215
|
)
|
|
189
216
|
# Determine the total number of rows. Note that we do not request any data (rows = 0).
|
|
190
217
|
num_results, _ = solr_request(
|
|
191
|
-
core=core,
|
|
218
|
+
core=core,
|
|
219
|
+
params={**params, "start": 0, "rows": 0},
|
|
220
|
+
silent=True,
|
|
221
|
+
base_url=base_url,
|
|
192
222
|
)
|
|
193
223
|
# Initialise everything for data retrieval.
|
|
194
224
|
start = 0
|
|
@@ -203,6 +233,7 @@ def batch_request(core, params, batch_size):
|
|
|
203
233
|
core=core,
|
|
204
234
|
params={**params, "start": start, "rows": batch_size},
|
|
205
235
|
silent=True,
|
|
236
|
+
base_url=base_url,
|
|
206
237
|
)
|
|
207
238
|
# Record chunk.
|
|
208
239
|
chunks.append(df_chunk)
|
|
@@ -19,7 +19,8 @@ import json
|
|
|
19
19
|
import warnings
|
|
20
20
|
from dataclasses import dataclass, field
|
|
21
21
|
from pathlib import Path
|
|
22
|
-
from typing import
|
|
22
|
+
from typing import Any, Collection, Dict, List
|
|
23
|
+
|
|
23
24
|
from pydantic import BaseModel, model_validator, field_validator
|
|
24
25
|
from impc_api.utils.warnings import (
|
|
25
26
|
warning_config,
|
|
@@ -38,14 +39,14 @@ class ValidationJson:
|
|
|
38
39
|
_validation_json: Dict[str, List[str]] = field(default_factory=dict, init=False)
|
|
39
40
|
|
|
40
41
|
# Eager initialisation
|
|
41
|
-
def __post_init__(self):
|
|
42
|
+
def __post_init__(self) -> None:
|
|
42
43
|
self._validation_json = self.load_core_fields(self.CORE_FILE)
|
|
43
44
|
|
|
44
45
|
def load_core_fields(self, filename: Path) -> Dict[str, List[str]]:
|
|
45
46
|
with open(filename, "r") as f:
|
|
46
47
|
return json.load(f)
|
|
47
48
|
|
|
48
|
-
def valid_cores(self):
|
|
49
|
+
def valid_cores(self) -> Collection[str]:
|
|
49
50
|
return self._validation_json.keys()
|
|
50
51
|
|
|
51
52
|
def valid_fields(self, core: str) -> List[str]:
|
|
@@ -59,11 +60,13 @@ def get_fields(fields: str) -> List[str]:
|
|
|
59
60
|
|
|
60
61
|
class CoreParamsValidator(BaseModel):
|
|
61
62
|
core: str
|
|
62
|
-
params: Dict
|
|
63
|
+
params: Dict[str, Any]
|
|
63
64
|
|
|
64
65
|
@model_validator(mode="before")
|
|
65
66
|
@classmethod
|
|
66
|
-
def validate_core_and_fields(
|
|
67
|
+
def validate_core_and_fields(
|
|
68
|
+
cls: type["CoreParamsValidator"], values: Dict[str, Any]
|
|
69
|
+
) -> Dict[str, Any]:
|
|
67
70
|
invalid_core: bool = False
|
|
68
71
|
core = values.get("core")
|
|
69
72
|
params = values.get("params")
|
|
@@ -108,7 +111,7 @@ class DownloadFormatValidator(BaseModel):
|
|
|
108
111
|
wt: str
|
|
109
112
|
|
|
110
113
|
@field_validator("wt")
|
|
111
|
-
def validate_wt(cls, value):
|
|
114
|
+
def validate_wt(cls: type["DownloadFormatValidator"], value: str) -> str:
|
|
112
115
|
supported_formats = {"json", "csv"}
|
|
113
116
|
if value not in supported_formats:
|
|
114
117
|
raise UnsupportedDownloadFormatError(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Module for warnings and excepton utils"""
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
+
from typing import Optional, Type, Union
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
# Custom warnings
|
|
@@ -16,16 +17,26 @@ class RowsParamIgnored(Warning):
|
|
|
16
17
|
"""Warning raised when the row param is ignored"""
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
class LargeRequestMemoryWarning(Warning):
|
|
21
|
+
"""Warning raised when user triggers a large request without downloading in batches"""
|
|
22
|
+
|
|
23
|
+
|
|
19
24
|
# custom exceptions
|
|
20
25
|
class UnsupportedDownloadFormatError(Exception):
|
|
21
26
|
"""Exception raised when the format is not supported for download"""
|
|
22
27
|
|
|
23
28
|
|
|
24
29
|
# Custom warning function
|
|
25
|
-
def warning_config():
|
|
30
|
+
def warning_config() -> None:
|
|
26
31
|
"""Customises formatting and filters for warnings"""
|
|
27
32
|
|
|
28
|
-
def custom_warning(
|
|
33
|
+
def custom_warning(
|
|
34
|
+
message: Union[str, Warning],
|
|
35
|
+
category: Type[Warning],
|
|
36
|
+
filename: str,
|
|
37
|
+
lineno: int,
|
|
38
|
+
line: Optional[str] = None,
|
|
39
|
+
) -> str:
|
|
29
40
|
return f"{category.__name__}: {message}\n"
|
|
30
41
|
|
|
31
42
|
warnings.formatwarning = custom_warning
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["
|
|
3
|
-
build-backend = "
|
|
2
|
+
requires = ["uv_build>=0.11.7,<0.12.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "impc_api"
|
|
7
|
-
version = "1.0
|
|
7
|
+
version = "2.1.0"
|
|
8
8
|
description = "A package to facilitate making API requests to the IMPC Solr API"
|
|
9
|
+
license = "Apache-2.0"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
9
11
|
authors = [
|
|
10
12
|
{ name = "MPI2" },
|
|
11
13
|
{ name = "Marina Kan" },
|
|
@@ -15,22 +17,21 @@ dependencies = [
|
|
|
15
17
|
"ipykernel>=6.29.5",
|
|
16
18
|
"notebook>=7.2.2",
|
|
17
19
|
"pandas>=2.2.0",
|
|
18
|
-
"requests>=2.
|
|
20
|
+
"requests>=2.33.0",
|
|
19
21
|
"tqdm>=4.66.4",
|
|
20
22
|
"pydantic>=2.9"
|
|
21
23
|
]
|
|
22
24
|
|
|
23
25
|
readme = "README.md"
|
|
24
|
-
requires-python = ">=3.
|
|
26
|
+
requires-python = ">=3.12"
|
|
25
27
|
|
|
26
|
-
[
|
|
28
|
+
[dependency-groups]
|
|
27
29
|
dev = [
|
|
28
30
|
"pytest>=8.2.2"
|
|
29
31
|
]
|
|
30
32
|
|
|
31
|
-
[tool.
|
|
32
|
-
|
|
33
|
+
[tool.uv.build-backend]
|
|
34
|
+
module-root = ""
|
|
33
35
|
|
|
34
36
|
[project.urls]
|
|
35
37
|
"Homepage" = "https://github.com/mpi2/impc-api"
|
|
36
|
-
|