impc-api 1.0.1__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.
@@ -0,0 +1 @@
1
+ include impc_api/utils/core_fields.json
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.1
2
+ Name: impc_api
3
+ Version: 1.0.4
4
+ Summary: A package to facilitate making API requests to the IMPC Solr API
5
+ Home-page: https://github.com/mpi2/impc-data-api-workshop
6
+ Author: MPI2, Marina Kan, Diego Pava
7
+ Project-URL: Homepage, https://github.com/mpi2/impc-data-api-workshop
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: pandas>=2.2.0
12
+ Requires-Dist: requests>=2.31.0
13
+ Requires-Dist: tqdm>=4.66.4
14
+ Requires-Dist: pydantic>=2.9
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=8.2.2; extra == "dev"
17
+
18
+ # IMPC_API
19
+
20
+ `impc_api` is a Python package.
21
+
22
+ The functions in this package are intended for use in a Jupyter Notebook.
23
+
24
+ ## Installation Instructions
25
+
26
+ 1. **Create a virtual environment (optional but recommended)**:
27
+ On Mac:
28
+ ```bash
29
+ python3 -m venv .venv
30
+ source .venv/bin/activate
31
+ ```
32
+
33
+ 2. **Install the package**: `pip install impc_api`
34
+ 3. **Install Jupyter**: `pip install jupyter`
35
+ 4. **Run the Jupyter Notebook**: `jupyter notebook`
36
+
37
+ After executing the command, the Jupyter interface should open in your browser. If it does not, follow the instructions provided in the terminal.
38
+
39
+ 5. **Try it out**:
40
+
41
+ Create a [Jupyter Notebook](https://jupyter-notebook.readthedocs.io/en/latest/) and try some of the examples below:
42
+
43
+ ## Available functions
44
+
45
+ The available functions can be imported as:
46
+
47
+ ```python
48
+ from impc_api import solr_request, batch_solr_request
49
+ ```
50
+
51
+ # 1. Solr request
52
+
53
+ The most basic request to the IMPC solr API
54
+
55
+ ```python
56
+ num_found, df = solr_request(
57
+ core='genotype-phenotype',
58
+ params={
59
+ 'q': '*:*',
60
+ 'rows': 10,
61
+ 'fl': 'marker_symbol,allele_symbol,parameter_stable_id'
62
+ }
63
+ )
64
+ ```
65
+
66
+ ## a. Facet request
67
+
68
+ `solr_request` allows facet requests
69
+
70
+ ```python
71
+ num_found, df = solr_request(
72
+ core="genotype-phenotype",
73
+ params={
74
+ "q": "*:*",
75
+ "rows": 0,
76
+ "facet": "on",
77
+ "facet.field": "zygosity",
78
+ "facet.limit": 15,
79
+ "facet.mincount": 1,
80
+ }
81
+ )
82
+ ```
83
+
84
+ ## b. Solr request validation
85
+
86
+ 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.
87
+
88
+ ### Core validation
89
+
90
+ ```python
91
+ num_found, df = solr_request(
92
+ core='invalid_core',
93
+ params={
94
+ 'q': '*:*',
95
+ 'rows': 10
96
+ },
97
+ validate=True
98
+ )
99
+
100
+ > InvalidCoreWarning: Invalid core: "genotype-phenotyp", select from the available cores:
101
+ > dict_keys(['experiment', 'genotype-phenotype', 'impc_images', 'phenodigm', 'statistical-result'])
102
+ ```
103
+
104
+ ### Field list validation
105
+
106
+ ```python
107
+ num_found, df = solr_request(
108
+ core='genotype-phenotype',
109
+ params={
110
+ 'q': '*:*',
111
+ 'rows': 10,
112
+ 'fl': 'invalid_field,marker_symbol,allele_symbol'
113
+ },
114
+ validate=True
115
+ )
116
+ > InvalidFieldWarning: Unexpected field name: "invalid_field". Check the spelling of fields.
117
+ > To see expected fields check the documentation at: https://www.ebi.ac.uk/mi/impc/solrdoc/
118
+ ```
119
+
120
+ # 2. Batch Solr Request
121
+
122
+ `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.
123
+
124
+ Use `batch_solr_request` for:
125
+
126
+ - Large queries (>1,000,000)
127
+ - Querying multiple items in a list
128
+ - Downloading data in `json` or `csv` format.
129
+
130
+ ## Large queries
131
+
132
+ For large queries you can choose between seeing them in a DataFrame or downloading them in `json` or `csv` format.
133
+
134
+ ## a. Large query - see in DataFrame
135
+
136
+ This will fetch your data using the API responsibly and return a Pandas DataFrame
137
+
138
+ 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.
139
+
140
+ ```python
141
+ df = batch_solr_request(
142
+ core='genotype-phenotype',
143
+ params={
144
+ 'q':'*:*'
145
+ },
146
+ download=False,
147
+ batch_size=30000
148
+ )
149
+ print(df.head())
150
+ ```
151
+
152
+ ## b. Large query - Download
153
+
154
+ 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.
155
+ 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.
156
+
157
+ ```python
158
+ df = batch_solr_request(
159
+ core='genotype-phenotype',
160
+ params={
161
+ 'q':'*:*',
162
+ 'wt':'csv'
163
+ },
164
+ download=True,
165
+ filename='geno_pheno_query',
166
+ batch_size=100000
167
+ )
168
+ print(df.head())
169
+ ```
170
+
171
+ ## c. Query by multiple values
172
+
173
+ `batch_solr_request` also allows to search multiple items in a list provided they belong to them same field.
174
+ Pass the list to the `field_list` param and specify the type of `fl` in `field_type`.
175
+
176
+ ```python
177
+ # List of gene symbols
178
+ genes = ["Zfp580", "Firrm", "Gpld1", "Mbip"]
179
+
180
+ df = batch_solr_request(
181
+ core='genotype-phenotype',
182
+ params={
183
+ 'q':'*:*',
184
+ 'fl': 'marker_symbol,mp_term_name,p_value',
185
+ 'field_list': genes,
186
+ 'field_type': 'marker_symbol'
187
+ },
188
+ download = False
189
+ )
190
+ print(df.head())
191
+ ```
192
+
193
+ This can be downloaded too:
194
+
195
+ ```python
196
+ # List of gene symbols
197
+ genes = ["Zfp580", "Firrm", "Gpld1", "Mbip"]
198
+
199
+ df = batch_solr_request(
200
+ core='genotype-phenotype',
201
+ params={
202
+ 'q':'*:*',
203
+ 'fl': 'marker_symbol,mp_term_name,p_value',
204
+ 'field_list': genes,
205
+ 'field_type': 'marker_symbol'
206
+ },
207
+ download = True,
208
+ filename='gene_list_query'
209
+ )
210
+ print(df.head())
211
+ ```
@@ -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,6 @@
1
+ from .solr_request import solr_request
2
+ from .batch_solr_request import batch_solr_request
3
+ from .utils import validators, warnings
4
+
5
+ # Control what gets imported by client
6
+ __all__ = ["solr_request", "batch_solr_request"]
@@ -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