impc-api 1.0.7__tar.gz → 2.0.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.
@@ -1,21 +1,19 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: impc_api
3
- Version: 1.0.7
3
+ Version: 2.0.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
- Project-URL: Homepage, https://github.com/mpi2/impc-api
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.31.0
11
+ Requires-Dist: requests>=2.33.0
14
12
  Requires-Dist: tqdm>=4.66.4
15
13
  Requires-Dist: pydantic>=2.9
16
- Provides-Extra: dev
17
- Requires-Dist: pytest>=8.2.2; extra == "dev"
18
- Dynamic: license-file
14
+ Requires-Python: >=3.10
15
+ Project-URL: Homepage, https://github.com/mpi2/impc-api
16
+ Description-Content-Type: text/markdown
19
17
 
20
18
  # IMPC_API
21
19
 
@@ -46,7 +44,7 @@ Create a [Jupyter Notebook](https://jupyter-notebook.readthedocs.io/en/latest/)
46
44
  The available functions can be imported as:
47
45
 
48
46
  ```python
49
- from impc_api import solr_request, batch_solr_request
47
+ from impc_api import batch_solr_request, get_core_fields, solr_request
50
48
  ```
51
49
 
52
50
  # 1. Solr request
@@ -64,6 +62,20 @@ num_found, df = solr_request(
64
62
  )
65
63
  ```
66
64
 
65
+ By default, `solr_request` uses a 10 second request timeout. To use a different timeout, pass the optional `timeout` argument:
66
+
67
+ ```python
68
+ num_found, df = solr_request(
69
+ core='genotype-phenotype',
70
+ params={
71
+ 'q': '*:*',
72
+ 'rows': 10,
73
+ 'fl': 'marker_symbol,allele_symbol,parameter_stable_id'
74
+ },
75
+ timeout=30
76
+ )
77
+ ```
78
+
67
79
  ## a. Facet request
68
80
 
69
81
  `solr_request` allows facet requests
@@ -144,7 +156,7 @@ print(url)
144
156
 
145
157
  Use `batch_solr_request` for:
146
158
 
147
- - Large queries (>100,000 rows)
159
+ - Large queries (>1,000,000 rows)
148
160
  - Querying multiple items in a list
149
161
  - Downloading data in `json` or `csv` format.
150
162
 
@@ -156,7 +168,7 @@ For large queries you can choose between seeing them in a DataFrame or downloadi
156
168
 
157
169
  This will fetch your data using the API responsibly and return a Pandas DataFrame
158
170
 
159
- 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.
171
+ 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
172
 
161
173
  ```python
162
174
  df = batch_solr_request(
@@ -230,3 +242,20 @@ df = batch_solr_request(
230
242
  )
231
243
  print(df.head())
232
244
  ```
245
+ # 3.Get Core Fields
246
+
247
+ ## Retrieve valid fields for a core
248
+
249
+ Use `get_core_fields` before constructing a request to discover the documented
250
+ fields available for a public IMPC Solr core. The function uses the same source
251
+ of truth as request validation and does not make a network request.
252
+
253
+ ```python
254
+ from impc_api import get_core_fields
255
+
256
+ get_core_fields("genotype-phenotype")
257
+ ```
258
+
259
+ The complete list is returned. An unknown core returns an empty list. Skills and
260
+ other integrations should call this function instead of maintaining their own
261
+ copies of core field names.
@@ -1,22 +1,3 @@
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.
@@ -46,7 +27,7 @@ Create a [Jupyter Notebook](https://jupyter-notebook.readthedocs.io/en/latest/)
46
27
  The available functions can be imported as:
47
28
 
48
29
  ```python
49
- from impc_api import solr_request, batch_solr_request
30
+ from impc_api import batch_solr_request, get_core_fields, solr_request
50
31
  ```
51
32
 
52
33
  # 1. Solr request
@@ -64,6 +45,20 @@ num_found, df = solr_request(
64
45
  )
65
46
  ```
66
47
 
48
+ By default, `solr_request` uses a 10 second request timeout. To use a different timeout, pass the optional `timeout` argument:
49
+
50
+ ```python
51
+ num_found, df = solr_request(
52
+ core='genotype-phenotype',
53
+ params={
54
+ 'q': '*:*',
55
+ 'rows': 10,
56
+ 'fl': 'marker_symbol,allele_symbol,parameter_stable_id'
57
+ },
58
+ timeout=30
59
+ )
60
+ ```
61
+
67
62
  ## a. Facet request
68
63
 
69
64
  `solr_request` allows facet requests
@@ -144,7 +139,7 @@ print(url)
144
139
 
145
140
  Use `batch_solr_request` for:
146
141
 
147
- - Large queries (>100,000 rows)
142
+ - Large queries (>1,000,000 rows)
148
143
  - Querying multiple items in a list
149
144
  - Downloading data in `json` or `csv` format.
150
145
 
@@ -156,7 +151,7 @@ For large queries you can choose between seeing them in a DataFrame or downloadi
156
151
 
157
152
  This will fetch your data using the API responsibly and return a Pandas DataFrame
158
153
 
159
- 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.
154
+ 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
155
 
161
156
  ```python
162
157
  df = batch_solr_request(
@@ -230,3 +225,20 @@ df = batch_solr_request(
230
225
  )
231
226
  print(df.head())
232
227
  ```
228
+ # 3.Get Core Fields
229
+
230
+ ## Retrieve valid fields for a core
231
+
232
+ Use `get_core_fields` before constructing a request to discover the documented
233
+ fields available for a public IMPC Solr core. The function uses the same source
234
+ of truth as request validation and does not make a network request.
235
+
236
+ ```python
237
+ from impc_api import get_core_fields
238
+
239
+ get_core_fields("genotype-phenotype")
240
+ ```
241
+
242
+ The complete list is returned. An unknown core returns an empty list. Skills and
243
+ other integrations should call this function instead of maintaining their own
244
+ 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,23 @@ from impc_api.utils.warnings import (
12
14
  warning_config,
13
15
  RowsParamIgnored,
14
16
  UnsupportedDownloadFormatError,
17
+ LargeRequestMemoryWarning
15
18
  )
16
19
  from .solr_request import 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, params, download=False, batch_size=5000, filename="batch_request"
23
- ):
28
+ core: str,
29
+ params: Dict[str, Any],
30
+ download: bool = False,
31
+ batch_size: int = 5000,
32
+ filename: str = "batch_request",
33
+ ) -> pd.DataFrame:
24
34
  """Function for large API requests (>1,000,000 results). Fetches the data in batches and
25
35
  produces a Pandas DataFrame or downloads a file in json or csv formats.
26
36
 
@@ -36,7 +46,6 @@ def batch_solr_request(
36
46
 
37
47
  Returns:
38
48
  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
49
  """
41
50
 
42
51
  # If params["rows"] is passed, the user is warned about no effect
@@ -92,38 +101,28 @@ def batch_solr_request(
92
101
  except UnsupportedDownloadFormatError as e:
93
102
  raise e
94
103
  except Exception as e:
95
- raise (f"An error ocurred while downloading the data:{e}")
104
+ raise Exception(f"An error occurred while downloading the data:{e}")
96
105
 
97
106
  # Try to read the downloaded file
98
107
  try:
99
108
  print("Reading downloaded file...")
100
109
  return _read_downloaded_file(filename_path, params["wt"])
101
110
  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)
111
+ raise Exception(f"An unexpected error occurred:{e}")
107
112
 
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)"
113
+
114
+ # If a request is too large, warn the user to use download=true.
115
+ if num_results > 1000000 and not download:
116
+ warnings.warn(
117
+ message="This request may exceed available memory. If the download fails, set 'download=True' and try again.",
118
+ category=LargeRequestMemoryWarning,
115
119
  )
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
-
120
+ return _batch_to_df(core, params, num_results)
124
121
 
125
122
  # Helper batch_to_df
126
- def _batch_to_df(core, params, num_results):
123
+ def _batch_to_df(
124
+ core: str, params: Dict[str, Any], num_results: int
125
+ ) -> pd.DataFrame:
127
126
  """Helper function to fetch data in batches and display them in a DataFrame
128
127
 
129
128
  Args:
@@ -161,7 +160,9 @@ def _batch_to_df(core, params, num_results):
161
160
  return pd.concat(chunks, ignore_index=True)
162
161
 
163
162
 
164
- def _batch_solr_generator(core, params, num_results):
163
+ def _batch_solr_generator(
164
+ core: str, params: Dict[str, Any], num_results: int
165
+ ) -> Iterator[BatchSolrChunk]:
165
166
  """Generator function to fetch results from the SOLR API in batches using pagination.
166
167
 
167
168
  Args:
@@ -205,7 +206,11 @@ def _batch_solr_generator(core, params, num_results):
205
206
 
206
207
 
207
208
  # File writer
208
- def _solr_downloader(params, filename, solr_generator):
209
+ def _solr_downloader(
210
+ params: Dict[str, Any],
211
+ filename: Path,
212
+ solr_generator: Iterable[BatchSolrChunk],
213
+ ) -> None:
209
214
  """Function to write the data from the generator into the specified format.
210
215
  Supports json and csv only.
211
216
 
@@ -230,6 +235,7 @@ def _solr_downloader(params, filename, solr_generator):
230
235
  elif params.get("wt") == "csv":
231
236
  first_chunk = True
232
237
  for chunk in solr_generator:
238
+ assert isinstance(chunk, str)
233
239
  lines = chunk.splitlines()
234
240
  if first_chunk:
235
241
  # Write all lines in the first chunk
@@ -241,7 +247,7 @@ def _solr_downloader(params, filename, solr_generator):
241
247
 
242
248
 
243
249
  # File reader
244
- def _read_downloaded_file(filename: Path, request_format):
250
+ def _read_downloaded_file(filename: Path, request_format: str) -> pd.DataFrame:
245
251
  """Wrapper for reading files into Pandas DataFrames
246
252
 
247
253
  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,26 @@ 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
+
8
17
  # Display the whole dataframe <15
9
18
  pd.set_option("display.max_rows", 15)
10
19
  pd.set_option("display.max_columns", None)
11
20
 
12
21
  # Create helper function
13
- def solr_request(core, params, silent=False, validate=False, url_only=False):
22
+ def solr_request(
23
+ core: str,
24
+ params: Dict[str, Any],
25
+ silent: bool = False,
26
+ validate: bool = False,
27
+ url_only: bool = False,
28
+ timeout: RequestTimeout = DEFAULT_REQUEST_TIMEOUT,
29
+ ) -> SolrRequestResult:
14
30
  """Performs a single Solr request to the IMPC Solr API.
15
31
 
16
32
  Args:
@@ -24,6 +40,8 @@ def solr_request(core, params, silent=False, validate=False, url_only=False):
24
40
  if any parameter seems invalid.
25
41
  url_only (bool, optional): default False
26
42
  If true, returns the request URL but no data.
43
+ timeout (int or float, optional): default 10
44
+ Number of seconds to wait for the API request before timing out.
27
45
 
28
46
 
29
47
  Returns:
@@ -80,7 +98,7 @@ def solr_request(core, params, silent=False, validate=False, url_only=False):
80
98
  base_url = "https://www.ebi.ac.uk/mi/impc/solr/"
81
99
  solr_url = base_url + core + "/select"
82
100
 
83
- response = requests.get(solr_url, params=params)
101
+ response = requests.get(solr_url, params=params, timeout=timeout)
84
102
 
85
103
  if url_only:
86
104
  if response.status_code == 200:
@@ -124,7 +142,7 @@ def solr_request(core, params, silent=False, validate=False, url_only=False):
124
142
  print("Error:", response.status_code, response.text)
125
143
 
126
144
 
127
- def _process_faceting(data, params):
145
+ def _process_faceting(data: Dict[str, Any], params: Dict[str, Any]) -> pd.DataFrame:
128
146
  """Processes the faceting data from an API response.
129
147
  Note: This function should not be used alone but only as a helper function for solr_request().
130
148
 
@@ -157,7 +175,9 @@ def _process_faceting(data, params):
157
175
 
158
176
 
159
177
  # Batch request based on solr_request.
160
- def batch_request(core, params, batch_size):
178
+ def batch_request(
179
+ core: str, params: Dict[str, Any], batch_size: int
180
+ ) -> pd.DataFrame:
161
181
  """Calls `solr_request` multiple times with `params`
162
182
  to retrieve results in chunk `batch_size` rows at a time.
163
183
 
@@ -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 List, Dict
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(cls, values):
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(message, category, filename, lineno, line=None):
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 = ["setuptools"]
3
- build-backend = "setuptools.build_meta"
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"
7
+ version = "2.0.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,7 +17,7 @@ dependencies = [
15
17
  "ipykernel>=6.29.5",
16
18
  "notebook>=7.2.2",
17
19
  "pandas>=2.2.0",
18
- "requests>=2.31.0",
20
+ "requests>=2.33.0",
19
21
  "tqdm>=4.66.4",
20
22
  "pydantic>=2.9"
21
23
  ]
@@ -23,14 +25,13 @@ dependencies = [
23
25
  readme = "README.md"
24
26
  requires-python = ">=3.10"
25
27
 
26
- [project.optional-dependencies]
28
+ [dependency-groups]
27
29
  dev = [
28
30
  "pytest>=8.2.2"
29
31
  ]
30
32
 
31
- [tool.setuptools.packages.find]
32
- include = ["impc_api", "impc_api.*"]
33
+ [tool.uv.build-backend]
34
+ module-root = ""
33
35
 
34
36
  [project.urls]
35
37
  "Homepage" = "https://github.com/mpi2/impc-api"
36
-
@@ -1 +0,0 @@
1
- include impc_api/utils/core_fields.json