impc-api 1.0.3__tar.gz → 1.0.4__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.4/MANIFEST.in +1 -0
- {impc_api-1.0.3 → impc_api-1.0.4}/PKG-INFO +3 -2
- impc_api-1.0.4/README.md +194 -0
- impc_api-1.0.4/impc_api/__init__.py +6 -0
- impc_api-1.0.4/impc_api/batch_solr_request.py +266 -0
- impc_api-1.0.4/impc_api/solr_request.py +191 -0
- impc_api-1.0.4/impc_api/utils/__init__.py +0 -0
- impc_api-1.0.4/impc_api/utils/core_fields.json +15 -0
- impc_api-1.0.4/impc_api/utils/validators.py +117 -0
- impc_api-1.0.4/impc_api/utils/warnings.py +32 -0
- {impc_api-1.0.3 → impc_api-1.0.4}/impc_api.egg-info/PKG-INFO +3 -2
- impc_api-1.0.4/impc_api.egg-info/SOURCES.txt +20 -0
- impc_api-1.0.4/impc_api.egg-info/top_level.txt +1 -0
- impc_api-1.0.4/pyproject.toml +34 -0
- impc_api-1.0.4/setup.py +39 -0
- impc_api-1.0.4/tests/test_batch_solr_request.py +746 -0
- impc_api-1.0.4/tests/test_helpers.py +33 -0
- impc_api-1.0.4/tests/test_solr_request.py +318 -0
- impc_api-1.0.3/README.md +0 -84
- impc_api-1.0.3/impc_api.egg-info/SOURCES.txt +0 -7
- impc_api-1.0.3/impc_api.egg-info/top_level.txt +0 -1
- {impc_api-1.0.3 → impc_api-1.0.4}/LICENSE +0 -0
- {impc_api-1.0.3 → impc_api-1.0.4}/impc_api.egg-info/dependency_links.txt +0 -0
- {impc_api-1.0.3 → impc_api-1.0.4}/impc_api.egg-info/requires.txt +0 -0
- {impc_api-1.0.3 → impc_api-1.0.4}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include impc_api/utils/core_fields.json
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: impc_api
|
|
3
|
-
Version: 1.0.
|
|
4
|
-
Summary: A package to facilitate making API
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: A package to facilitate making API requests to the IMPC Solr API
|
|
5
5
|
Home-page: https://github.com/mpi2/impc-data-api-workshop
|
|
6
6
|
Author: MPI2, Marina Kan, Diego Pava
|
|
7
|
+
Project-URL: Homepage, https://github.com/mpi2/impc-data-api-workshop
|
|
7
8
|
Requires-Python: >=3.10
|
|
8
9
|
Description-Content-Type: text/markdown
|
|
9
10
|
License-File: LICENSE
|
impc_api-1.0.4/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# IMPC_API
|
|
2
|
+
|
|
3
|
+
`impc_api` is a Python package.
|
|
4
|
+
|
|
5
|
+
The functions in this package are intended for use in a Jupyter Notebook.
|
|
6
|
+
|
|
7
|
+
## Installation Instructions
|
|
8
|
+
|
|
9
|
+
1. **Create a virtual environment (optional but recommended)**:
|
|
10
|
+
On Mac:
|
|
11
|
+
```bash
|
|
12
|
+
python3 -m venv .venv
|
|
13
|
+
source .venv/bin/activate
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
2. **Install the package**: `pip install impc_api`
|
|
17
|
+
3. **Install Jupyter**: `pip install jupyter`
|
|
18
|
+
4. **Run the Jupyter Notebook**: `jupyter notebook`
|
|
19
|
+
|
|
20
|
+
After executing the command, the Jupyter interface should open in your browser. If it does not, follow the instructions provided in the terminal.
|
|
21
|
+
|
|
22
|
+
5. **Try it out**:
|
|
23
|
+
|
|
24
|
+
Create a [Jupyter Notebook](https://jupyter-notebook.readthedocs.io/en/latest/) and try some of the examples below:
|
|
25
|
+
|
|
26
|
+
## Available functions
|
|
27
|
+
|
|
28
|
+
The available functions can be imported as:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from impc_api import solr_request, batch_solr_request
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
# 1. Solr request
|
|
35
|
+
|
|
36
|
+
The most basic request to the IMPC solr API
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
num_found, df = solr_request(
|
|
40
|
+
core='genotype-phenotype',
|
|
41
|
+
params={
|
|
42
|
+
'q': '*:*',
|
|
43
|
+
'rows': 10,
|
|
44
|
+
'fl': 'marker_symbol,allele_symbol,parameter_stable_id'
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## a. Facet request
|
|
50
|
+
|
|
51
|
+
`solr_request` allows facet requests
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
num_found, df = solr_request(
|
|
55
|
+
core="genotype-phenotype",
|
|
56
|
+
params={
|
|
57
|
+
"q": "*:*",
|
|
58
|
+
"rows": 0,
|
|
59
|
+
"facet": "on",
|
|
60
|
+
"facet.field": "zygosity",
|
|
61
|
+
"facet.limit": 15,
|
|
62
|
+
"facet.mincount": 1,
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## b. Solr request validation
|
|
68
|
+
|
|
69
|
+
A common pitfall when writing a query is the misspelling of `core` and `fields` arguments. For this, we have included a `validate` argument that raises a warning when these values are not as expected. Note this does not prevent you from executing a query; it just alerts you to a potential issue.
|
|
70
|
+
|
|
71
|
+
### Core validation
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
num_found, df = solr_request(
|
|
75
|
+
core='invalid_core',
|
|
76
|
+
params={
|
|
77
|
+
'q': '*:*',
|
|
78
|
+
'rows': 10
|
|
79
|
+
},
|
|
80
|
+
validate=True
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
> InvalidCoreWarning: Invalid core: "genotype-phenotyp", select from the available cores:
|
|
84
|
+
> dict_keys(['experiment', 'genotype-phenotype', 'impc_images', 'phenodigm', 'statistical-result'])
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Field list validation
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
num_found, df = solr_request(
|
|
91
|
+
core='genotype-phenotype',
|
|
92
|
+
params={
|
|
93
|
+
'q': '*:*',
|
|
94
|
+
'rows': 10,
|
|
95
|
+
'fl': 'invalid_field,marker_symbol,allele_symbol'
|
|
96
|
+
},
|
|
97
|
+
validate=True
|
|
98
|
+
)
|
|
99
|
+
> InvalidFieldWarning: Unexpected field name: "invalid_field". Check the spelling of fields.
|
|
100
|
+
> To see expected fields check the documentation at: https://www.ebi.ac.uk/mi/impc/solrdoc/
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
# 2. Batch Solr Request
|
|
104
|
+
|
|
105
|
+
`batch_solr_request` is available for large queries. This solves issues where a request is too large to fit into memory or where it puts a lot of strain on the API.
|
|
106
|
+
|
|
107
|
+
Use `batch_solr_request` for:
|
|
108
|
+
|
|
109
|
+
- Large queries (>1,000,000)
|
|
110
|
+
- Querying multiple items in a list
|
|
111
|
+
- Downloading data in `json` or `csv` format.
|
|
112
|
+
|
|
113
|
+
## Large queries
|
|
114
|
+
|
|
115
|
+
For large queries you can choose between seeing them in a DataFrame or downloading them in `json` or `csv` format.
|
|
116
|
+
|
|
117
|
+
## a. Large query - see in DataFrame
|
|
118
|
+
|
|
119
|
+
This will fetch your data using the API responsibly and return a Pandas DataFrame
|
|
120
|
+
|
|
121
|
+
When your request is larger than recommended and you have not opted for downloading the data, a warning will be presented and you should follow the instructions to proceed.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
df = batch_solr_request(
|
|
125
|
+
core='genotype-phenotype',
|
|
126
|
+
params={
|
|
127
|
+
'q':'*:*'
|
|
128
|
+
},
|
|
129
|
+
download=False,
|
|
130
|
+
batch_size=30000
|
|
131
|
+
)
|
|
132
|
+
print(df.head())
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## b. Large query - Download
|
|
136
|
+
|
|
137
|
+
When using the `download=True` option, a file with the requested information will be saved as `filename`. The format is selected based on the `wt` parameter.
|
|
138
|
+
A DataFrame may be returned, provided it does not exceed the memory available on your laptop. If the DataFrame is too large, an error will be raised. For these cases, we recommend you read the downloaded file in batches/chunks.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
df = batch_solr_request(
|
|
142
|
+
core='genotype-phenotype',
|
|
143
|
+
params={
|
|
144
|
+
'q':'*:*',
|
|
145
|
+
'wt':'csv'
|
|
146
|
+
},
|
|
147
|
+
download=True,
|
|
148
|
+
filename='geno_pheno_query',
|
|
149
|
+
batch_size=100000
|
|
150
|
+
)
|
|
151
|
+
print(df.head())
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## c. Query by multiple values
|
|
155
|
+
|
|
156
|
+
`batch_solr_request` also allows to search multiple items in a list provided they belong to them same field.
|
|
157
|
+
Pass the list to the `field_list` param and specify the type of `fl` in `field_type`.
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
# List of gene symbols
|
|
161
|
+
genes = ["Zfp580", "Firrm", "Gpld1", "Mbip"]
|
|
162
|
+
|
|
163
|
+
df = batch_solr_request(
|
|
164
|
+
core='genotype-phenotype',
|
|
165
|
+
params={
|
|
166
|
+
'q':'*:*',
|
|
167
|
+
'fl': 'marker_symbol,mp_term_name,p_value',
|
|
168
|
+
'field_list': genes,
|
|
169
|
+
'field_type': 'marker_symbol'
|
|
170
|
+
},
|
|
171
|
+
download = False
|
|
172
|
+
)
|
|
173
|
+
print(df.head())
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
This can be downloaded too:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
# List of gene symbols
|
|
180
|
+
genes = ["Zfp580", "Firrm", "Gpld1", "Mbip"]
|
|
181
|
+
|
|
182
|
+
df = batch_solr_request(
|
|
183
|
+
core='genotype-phenotype',
|
|
184
|
+
params={
|
|
185
|
+
'q':'*:*',
|
|
186
|
+
'fl': 'marker_symbol,mp_term_name,p_value',
|
|
187
|
+
'field_list': genes,
|
|
188
|
+
'field_type': 'marker_symbol'
|
|
189
|
+
},
|
|
190
|
+
download = True,
|
|
191
|
+
filename='gene_list_query'
|
|
192
|
+
)
|
|
193
|
+
print(df.head())
|
|
194
|
+
```
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import warnings
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import requests
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
from IPython.display import display
|
|
9
|
+
|
|
10
|
+
from impc_api.utils.validators import DownloadFormatValidator
|
|
11
|
+
from impc_api.utils.warnings import (
|
|
12
|
+
warning_config,
|
|
13
|
+
RowsParamIgnored,
|
|
14
|
+
UnsupportedDownloadFormatError,
|
|
15
|
+
)
|
|
16
|
+
from .solr_request import solr_request
|
|
17
|
+
|
|
18
|
+
# Initialise warning config
|
|
19
|
+
warning_config()
|
|
20
|
+
|
|
21
|
+
def batch_solr_request(
|
|
22
|
+
core, params, download=False, batch_size=5000, filename="batch_request"
|
|
23
|
+
):
|
|
24
|
+
"""Function for large API requests (>1,000,000 results). Fetches the data in batches and
|
|
25
|
+
produces a Pandas DataFrame or downloads a file in json or csv formats.
|
|
26
|
+
|
|
27
|
+
Additionally, allows to search multiple items in a list provided they belong to them same field.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
core (str): name of IMPC solr core.
|
|
31
|
+
params (dict): dictionary containing the API call parameters.
|
|
32
|
+
download (bool, optional): True for download a local file, False to display results as a DataFrame. Defaults to False.
|
|
33
|
+
batch_size (int, optional): Size of batches to fetch the data. Defaults to 5000.
|
|
34
|
+
filename (str, optional): When download=True, select the name of the file. Defaults to 'batch_request'.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
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
|
+
"""
|
|
41
|
+
|
|
42
|
+
# If params["rows"] is passed, the user is warned about no effect
|
|
43
|
+
if params.get("rows") is not None:
|
|
44
|
+
warnings.warn(
|
|
45
|
+
message='The "rows" param will be ignored in batch_solr_request. To set a batch size, specify a "batch_size" argument.',
|
|
46
|
+
category=RowsParamIgnored,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Set params for batch request
|
|
50
|
+
params["start"] = 0 # Start at the first result
|
|
51
|
+
# Override param rows in case there was input. Defines batch size to 5000 by default
|
|
52
|
+
params["rows"] = batch_size
|
|
53
|
+
|
|
54
|
+
# If user did not specify format, defaults to json.
|
|
55
|
+
if params.get("wt") is None:
|
|
56
|
+
params["wt"] = "json"
|
|
57
|
+
|
|
58
|
+
# Check if it's multiple request
|
|
59
|
+
if params.get("field_list") is not None:
|
|
60
|
+
# Extract entities_list and entity_type from params
|
|
61
|
+
field_list = params.pop("field_list")
|
|
62
|
+
field_type = params.pop("field_type")
|
|
63
|
+
|
|
64
|
+
# Construct the filter query with grouped model IDs
|
|
65
|
+
fq = "{}:({})".format(
|
|
66
|
+
field_type, " OR ".join(["{}".format(id) for id in field_list])
|
|
67
|
+
)
|
|
68
|
+
# Show users the field and field values they passed to the function
|
|
69
|
+
print("Queried field:", fq)
|
|
70
|
+
# Set internal params the users should not change
|
|
71
|
+
params["fq"] = fq
|
|
72
|
+
|
|
73
|
+
# Determine the total number of rows. Note that we do not request any data (rows = 0).
|
|
74
|
+
num_results, _ = solr_request(
|
|
75
|
+
core=core, params={**params, "start": 0, "rows": 0, "wt": "json"}, silent=True
|
|
76
|
+
)
|
|
77
|
+
print(f"Number of found documents: {num_results}")
|
|
78
|
+
|
|
79
|
+
# # Download only logic
|
|
80
|
+
# If user decides to download, a generator is used to fetch data in batches without storing results in memory.
|
|
81
|
+
if download:
|
|
82
|
+
try:
|
|
83
|
+
# Check if the format is supported
|
|
84
|
+
DownloadFormatValidator(wt=params["wt"])
|
|
85
|
+
|
|
86
|
+
# Implement loop behaviour
|
|
87
|
+
print("Downloading file...")
|
|
88
|
+
filename_path = Path(f"{filename}.{params['wt']}")
|
|
89
|
+
gen = _batch_solr_generator(core, params, num_results)
|
|
90
|
+
_solr_downloader(params, filename_path, gen)
|
|
91
|
+
print(f"File saved as: {filename_path}")
|
|
92
|
+
except UnsupportedDownloadFormatError as e:
|
|
93
|
+
raise e
|
|
94
|
+
except Exception as e:
|
|
95
|
+
raise (f"An error ocurred while downloading the data:{e}")
|
|
96
|
+
|
|
97
|
+
# Try to read the downloaded file
|
|
98
|
+
try:
|
|
99
|
+
print("Reading downloaded file...")
|
|
100
|
+
return _read_downloaded_file(filename_path, params["wt"])
|
|
101
|
+
except Exception as e:
|
|
102
|
+
raise Exception(f"An unexpected error occured:{e}")
|
|
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)
|
|
107
|
+
|
|
108
|
+
# If it's too big, warn the user and ask if they want to proceed.
|
|
109
|
+
else:
|
|
110
|
+
print(
|
|
111
|
+
"Your request might exceed the available memory. We suggest setting 'download=True' and reading the file in batches"
|
|
112
|
+
)
|
|
113
|
+
prompt = input(
|
|
114
|
+
"Do you wish to proceed anyway? press ('y' or enter to proceed) / type('n' or 'exit' to cancel)"
|
|
115
|
+
)
|
|
116
|
+
match prompt:
|
|
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
|
+
|
|
124
|
+
|
|
125
|
+
# Helper batch_to_df
|
|
126
|
+
def _batch_to_df(core, params, num_results):
|
|
127
|
+
"""Helper function to fetch data in batches and display them in a DataFrame
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
core (str): name of IMPC solr core.
|
|
131
|
+
params (dict): dictionary containing the API call parameters.
|
|
132
|
+
num_results (int): Number of docs available
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
pd.DataFrame: DataFrame with the results.
|
|
136
|
+
"""
|
|
137
|
+
start = params["start"]
|
|
138
|
+
batch_size = params["rows"]
|
|
139
|
+
chunks = []
|
|
140
|
+
# If the 'wt' param was changed by error, we set it to 'json'
|
|
141
|
+
params["wt"] = "json"
|
|
142
|
+
|
|
143
|
+
# Request chunks until we have complete data.
|
|
144
|
+
with tqdm(total=num_results) as pbar:
|
|
145
|
+
while start < num_results:
|
|
146
|
+
# Request chunk. We don't need num_results anymore because it does not change.
|
|
147
|
+
_, df_chunk = solr_request(
|
|
148
|
+
core=core,
|
|
149
|
+
params={**params, "start": start, "rows": batch_size},
|
|
150
|
+
silent=True,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Update progress bar with the number of rows requested.
|
|
154
|
+
pbar.update(batch_size)
|
|
155
|
+
pbar.refresh()
|
|
156
|
+
# Record chunk.
|
|
157
|
+
chunks.append(df_chunk)
|
|
158
|
+
# Increment start.
|
|
159
|
+
start += batch_size
|
|
160
|
+
# Prepare final dataframe.
|
|
161
|
+
return pd.concat(chunks, ignore_index=True)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _batch_solr_generator(core, params, num_results):
|
|
165
|
+
"""Generator function to fetch results from the SOLR API in batches using pagination.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
core (str): name of IMPC solr core.
|
|
169
|
+
params (dict): dictionary containing the API call parameters.
|
|
170
|
+
num_results (int): Number of docs available
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
Exception: If a problem occurs during the download, an exception is raised.
|
|
174
|
+
|
|
175
|
+
Yields:
|
|
176
|
+
([dict, str]): A JSON object or plain text with the results.
|
|
177
|
+
"""
|
|
178
|
+
base_url = "https://www.ebi.ac.uk/mi/impc/solr/"
|
|
179
|
+
solr_url = base_url + core + "/select"
|
|
180
|
+
start = params["start"]
|
|
181
|
+
batch_size = params["rows"]
|
|
182
|
+
|
|
183
|
+
with tqdm(total=num_results) as pbar:
|
|
184
|
+
while start <= num_results:
|
|
185
|
+
params["start"] = start
|
|
186
|
+
response = requests.get(solr_url, params=params, timeout=10)
|
|
187
|
+
|
|
188
|
+
if response.status_code == 200:
|
|
189
|
+
if params.get("wt") == "json":
|
|
190
|
+
data = response.json()["response"]["docs"]
|
|
191
|
+
else:
|
|
192
|
+
data = response.text
|
|
193
|
+
|
|
194
|
+
# Update and refresh the progress bar after getting the data
|
|
195
|
+
pbar.update(batch_size)
|
|
196
|
+
pbar.refresh()
|
|
197
|
+
yield data
|
|
198
|
+
|
|
199
|
+
else:
|
|
200
|
+
raise Exception(f"Request failed. Status code: {response.status_code}")
|
|
201
|
+
|
|
202
|
+
# pbar.update(batch_size)
|
|
203
|
+
start += batch_size
|
|
204
|
+
print(f"Your request URL after the last call:{response.url}")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# File writer
|
|
208
|
+
def _solr_downloader(params, filename, solr_generator):
|
|
209
|
+
"""Function to write the data from the generator into the specified format.
|
|
210
|
+
Supports json and csv only.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
params (dict): dictionary containing the API call parameters.
|
|
214
|
+
filename (Path): name for the file to be downloaded. Defaults to "core.format" as passed by parent function.
|
|
215
|
+
solr_generator ([dict, str]): Generator object with the results.
|
|
216
|
+
"""
|
|
217
|
+
with open(filename, "w", encoding="UTF-8") as f:
|
|
218
|
+
if params.get("wt") == "json":
|
|
219
|
+
f.write("[\n")
|
|
220
|
+
first_chunk = True
|
|
221
|
+
|
|
222
|
+
for chunk in solr_generator:
|
|
223
|
+
for item in chunk:
|
|
224
|
+
if not first_chunk:
|
|
225
|
+
f.write(",\n")
|
|
226
|
+
json.dump(item, f, ensure_ascii=False)
|
|
227
|
+
first_chunk = False
|
|
228
|
+
f.write("\n]\n")
|
|
229
|
+
|
|
230
|
+
elif params.get("wt") == "csv":
|
|
231
|
+
first_chunk = True
|
|
232
|
+
for chunk in solr_generator:
|
|
233
|
+
lines = chunk.splitlines()
|
|
234
|
+
if first_chunk:
|
|
235
|
+
# Write all lines in the first chunk
|
|
236
|
+
f.write(chunk)
|
|
237
|
+
first_chunk = False
|
|
238
|
+
else:
|
|
239
|
+
# Skip the first line (header) in subsequent chunks
|
|
240
|
+
f.write("\n" + "\n".join(lines[1:]) + "\n")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# File reader
|
|
244
|
+
def _read_downloaded_file(filename: Path, request_format):
|
|
245
|
+
"""Wrapper for reading files into Pandas DataFrames
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
filename (Path): Name of the file to read
|
|
249
|
+
request_format (str): Format of the file to read. Only 'json' and 'csv' are supported.
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
MemoryError: When there is not enough memory to read the file.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
pd.DataFrame: Returns a pd.DataFrame with the data from the file.
|
|
256
|
+
"""
|
|
257
|
+
try:
|
|
258
|
+
match request_format:
|
|
259
|
+
case "json":
|
|
260
|
+
return pd.read_json(filename)
|
|
261
|
+
case "csv":
|
|
262
|
+
return pd.read_csv(filename)
|
|
263
|
+
except MemoryError as exc:
|
|
264
|
+
raise MemoryError(
|
|
265
|
+
"MemoryError: Insuficient memory to read the file. Consider reading file in batches using Pandas or Polars."
|
|
266
|
+
) from exc
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import requests
|
|
3
|
+
from IPython.display import display
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
|
|
6
|
+
from impc_api.utils.validators import CoreParamsValidator
|
|
7
|
+
|
|
8
|
+
# Display the whole dataframe <15
|
|
9
|
+
pd.set_option("display.max_rows", 15)
|
|
10
|
+
pd.set_option("display.max_columns", None)
|
|
11
|
+
|
|
12
|
+
# Create helper function
|
|
13
|
+
def solr_request(core, params, silent=False, validate=False):
|
|
14
|
+
"""Performs a single Solr request to the IMPC Solr API.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
core (str): name of IMPC solr core.
|
|
18
|
+
params (dict): dictionary containing the API call parameters.
|
|
19
|
+
silent (bool, optional): default False
|
|
20
|
+
If True, displays: URL of API call, the number of found docs
|
|
21
|
+
and a portion of the DataFrame.
|
|
22
|
+
validate (bool, optional): default False
|
|
23
|
+
If True, validates the parameters against the core schema and raises warnings
|
|
24
|
+
if any parameter seems invalid.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
num_found: Number of docs found.
|
|
29
|
+
pandas.DataFrame: Pandas.DataFrame object with the information requested.
|
|
30
|
+
|
|
31
|
+
Example query:
|
|
32
|
+
num_found, df = solr_request(
|
|
33
|
+
core='genotype-phenotype',
|
|
34
|
+
params={
|
|
35
|
+
'q': '*:*', # Your query, '*' retrieves all documents.
|
|
36
|
+
'rows': 10, # Number of rows to retrieve.
|
|
37
|
+
'fl': 'marker_symbol,allele_symbol,parameter_stable_id', # Fields to retrieve.
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
Faceting query provides a summary of data distribution across the specified fields.
|
|
42
|
+
Example faceting query:
|
|
43
|
+
num_found, df = solr_request(
|
|
44
|
+
core='genotype-phenotype',
|
|
45
|
+
params={
|
|
46
|
+
'q': '*:*', # Your query, '*' retrieves all documents.
|
|
47
|
+
'rows': 0, # Number of rows to retrieve.
|
|
48
|
+
'facet': 'on', # Enable facet counts in query response.
|
|
49
|
+
'facet.field': 'zygosity', # Identifies a field to be treated as a facet.
|
|
50
|
+
'facet.limit': 15, # Controls how many constraints should be returned for each facet.
|
|
51
|
+
'facet.mincount': 1 # Specifies the minimum counts required be included in response.
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
When querying the phenodigm core, pass 'q': 'type:...'
|
|
56
|
+
Example phenodigm query:
|
|
57
|
+
num_found, df = solr_request(
|
|
58
|
+
core='phenodigm',
|
|
59
|
+
params={
|
|
60
|
+
'q': 'type:disease_model_summary', # Pass the type within the core and filters.
|
|
61
|
+
'rows': 5
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
if validate:
|
|
67
|
+
CoreParamsValidator(core=core, params=params)
|
|
68
|
+
|
|
69
|
+
base_url = "https://www.ebi.ac.uk/mi/impc/solr/"
|
|
70
|
+
solr_url = base_url + core + "/select"
|
|
71
|
+
|
|
72
|
+
response = requests.get(solr_url, params=params)
|
|
73
|
+
if not silent:
|
|
74
|
+
print(f"\nYour request:\n{response.request.url}\n")
|
|
75
|
+
|
|
76
|
+
# Check if the request was successful (status code 200).
|
|
77
|
+
if response.status_code == 200:
|
|
78
|
+
# Parse the JSON response.
|
|
79
|
+
data = response.json()
|
|
80
|
+
num_found = data["response"]["numFound"]
|
|
81
|
+
if not silent:
|
|
82
|
+
print(f"Number of found documents: {num_found}\n")
|
|
83
|
+
|
|
84
|
+
# For faceting query.
|
|
85
|
+
if params.get("facet") == "on":
|
|
86
|
+
df = _process_faceting(data, params)
|
|
87
|
+
|
|
88
|
+
# For regular query.
|
|
89
|
+
else:
|
|
90
|
+
# Extract and add search results to the list.
|
|
91
|
+
search_results = []
|
|
92
|
+
for doc in data["response"]["docs"]:
|
|
93
|
+
search_results.append(doc)
|
|
94
|
+
|
|
95
|
+
# Convert the list of dictionaries into a DataFrame and print the DataFrame.
|
|
96
|
+
df = pd.DataFrame(search_results)
|
|
97
|
+
|
|
98
|
+
if not silent:
|
|
99
|
+
display(df)
|
|
100
|
+
return num_found, df
|
|
101
|
+
|
|
102
|
+
else:
|
|
103
|
+
print("Error:", response.status_code, response.text)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _process_faceting(data, params):
|
|
107
|
+
"""Processes the faceting data from an API response.
|
|
108
|
+
Note: This function should not be used alone but only as a helper function for solr_request().
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
data (dict): The JSON response from the API containing faceting information.
|
|
112
|
+
params (dict): Dictionary containing the API call parameters, including the facet field.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
pandas.DataFrame: A DataFrame with the facet field values and their corresponding counts.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
# Extract and add faceting query results to the list.
|
|
119
|
+
facet_counts = data["facet_counts"]["facet_fields"][params["facet.field"]]
|
|
120
|
+
# Initialize an empty dictionary.
|
|
121
|
+
faceting_dict = {}
|
|
122
|
+
# Iterate over the list, taking pairs of elements.
|
|
123
|
+
for i in range(0, len(facet_counts), 2):
|
|
124
|
+
# Assign label as key and count as value.
|
|
125
|
+
label = facet_counts[i]
|
|
126
|
+
count = facet_counts[i + 1]
|
|
127
|
+
faceting_dict[label] = [count]
|
|
128
|
+
|
|
129
|
+
# Convert the list of dictionaries into a DataFrame and print the DataFrame.
|
|
130
|
+
df = pd.DataFrame.from_dict(
|
|
131
|
+
faceting_dict, orient="index", columns=["counts"]
|
|
132
|
+
).reset_index()
|
|
133
|
+
# Rename the columns.
|
|
134
|
+
df.columns = [params["facet.field"], "count_per_category"]
|
|
135
|
+
return df
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# Batch request based on solr_request.
|
|
139
|
+
def batch_request(core, params, batch_size):
|
|
140
|
+
"""Calls `solr_request` multiple times with `params`
|
|
141
|
+
to retrieve results in chunk `batch_size` rows at a time.
|
|
142
|
+
|
|
143
|
+
Passing parameter `rows` is ignored and replaced with `batch_size`
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
core (str): name of IMPC solr core.
|
|
147
|
+
params (dict): dictionary containing the API call parameters.
|
|
148
|
+
batch_size (int): Size of batches (number of docs) per request.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
pandas.DataFrame: Pandas.DataFrame object with the information requested.
|
|
152
|
+
|
|
153
|
+
Example query:
|
|
154
|
+
df = batch_request(
|
|
155
|
+
core="genotype-phenotype",
|
|
156
|
+
params={
|
|
157
|
+
'q': 'top_level_mp_term_name:"cardiovascular system phenotype" AND effect_size:[* TO *] AND life_stage_name:"Late adult"',
|
|
158
|
+
'fl': 'allele_accession_id,life_stage_name,marker_symbol,mp_term_name,p_value,parameter_name,parameter_stable_id,phenotyping_center,statistical_method,top_level_mp_term_name,effect_size'
|
|
159
|
+
},
|
|
160
|
+
batch_size=100
|
|
161
|
+
)
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
if "rows" in "params":
|
|
165
|
+
print(
|
|
166
|
+
"WARN: You have specified the `params` -> `rows` value. It will be ignored, because the data is retrieved `batch_size` rows at a time."
|
|
167
|
+
)
|
|
168
|
+
# Determine the total number of rows. Note that we do not request any data (rows = 0).
|
|
169
|
+
num_results, _ = solr_request(
|
|
170
|
+
core=core, params={**params, "start": 0, "rows": 0}, silent=True
|
|
171
|
+
)
|
|
172
|
+
# Initialise everything for data retrieval.
|
|
173
|
+
start = 0
|
|
174
|
+
chunks = []
|
|
175
|
+
# Request chunks until we have complete data.
|
|
176
|
+
with tqdm(total=num_results) as pbar: # Initialize tqdm progress bar.
|
|
177
|
+
while start < num_results:
|
|
178
|
+
# Update progress bar with the number of rows requested.
|
|
179
|
+
pbar.update(batch_size)
|
|
180
|
+
# Request chunk. We don't need num_results anymore because it does not change.
|
|
181
|
+
_, df_chunk = solr_request(
|
|
182
|
+
core=core,
|
|
183
|
+
params={**params, "start": start, "rows": batch_size},
|
|
184
|
+
silent=True,
|
|
185
|
+
)
|
|
186
|
+
# Record chunk.
|
|
187
|
+
chunks.append(df_chunk)
|
|
188
|
+
# Increment start.
|
|
189
|
+
start += batch_size
|
|
190
|
+
# Prepare final dataframe.
|
|
191
|
+
return pd.concat(chunks, ignore_index=True)
|
|
File without changes
|