modulector-sdk 2.3.0__py3-none-any.whl
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.
- modulector_sdk/__init__.py +69 -0
- modulector_sdk/py.typed +1 -0
- modulector_sdk/services.py +758 -0
- modulector_sdk/utils.py +237 -0
- modulector_sdk-2.3.0.dist-info/METADATA +74 -0
- modulector_sdk-2.3.0.dist-info/RECORD +9 -0
- modulector_sdk-2.3.0.dist-info/WHEEL +5 -0
- modulector_sdk-2.3.0.dist-info/licenses/LICENSE +21 -0
- modulector_sdk-2.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Convenience SDK helpers for Modulector API clients."""
|
|
2
|
+
|
|
3
|
+
from .services import (
|
|
4
|
+
API_BASE_URL,
|
|
5
|
+
MODULECTOR_API_BASE_URL,
|
|
6
|
+
MethylationDetailsResponse,
|
|
7
|
+
MirnaAlias,
|
|
8
|
+
MirnaDetailsResponse,
|
|
9
|
+
MirnaDisease,
|
|
10
|
+
MirnaDrug,
|
|
11
|
+
MirnaTargetInteraction,
|
|
12
|
+
SourceLink,
|
|
13
|
+
SubscribePubmedsResponse,
|
|
14
|
+
UcscCpgIsland,
|
|
15
|
+
find_methylation_sites,
|
|
16
|
+
find_mirna_codes,
|
|
17
|
+
get_diseases,
|
|
18
|
+
get_drugs,
|
|
19
|
+
get_methylation_details,
|
|
20
|
+
get_methylation_site_genes,
|
|
21
|
+
get_methylation_sites,
|
|
22
|
+
get_mirna_aliases,
|
|
23
|
+
get_mirna_codes,
|
|
24
|
+
get_mirna_details,
|
|
25
|
+
get_mirna_target_interactions,
|
|
26
|
+
subscribe_pubmeds,
|
|
27
|
+
unsubscribe_pubmeds,
|
|
28
|
+
)
|
|
29
|
+
from .utils import (
|
|
30
|
+
PaginatedResponse,
|
|
31
|
+
get_all_paginated_results,
|
|
32
|
+
get_paginated_response,
|
|
33
|
+
get_simple_response,
|
|
34
|
+
iter_paginated_results,
|
|
35
|
+
request_url,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"API_BASE_URL",
|
|
40
|
+
"MODULECTOR_API_BASE_URL",
|
|
41
|
+
"MethylationDetailsResponse",
|
|
42
|
+
"MirnaAlias",
|
|
43
|
+
"MirnaDetailsResponse",
|
|
44
|
+
"MirnaDisease",
|
|
45
|
+
"MirnaDrug",
|
|
46
|
+
"MirnaTargetInteraction",
|
|
47
|
+
"PaginatedResponse",
|
|
48
|
+
"SourceLink",
|
|
49
|
+
"SubscribePubmedsResponse",
|
|
50
|
+
"UcscCpgIsland",
|
|
51
|
+
"find_methylation_sites",
|
|
52
|
+
"find_mirna_codes",
|
|
53
|
+
"get_all_paginated_results",
|
|
54
|
+
"get_diseases",
|
|
55
|
+
"get_drugs",
|
|
56
|
+
"get_methylation_details",
|
|
57
|
+
"get_methylation_site_genes",
|
|
58
|
+
"get_methylation_sites",
|
|
59
|
+
"get_mirna_aliases",
|
|
60
|
+
"get_mirna_codes",
|
|
61
|
+
"get_mirna_details",
|
|
62
|
+
"get_mirna_target_interactions",
|
|
63
|
+
"get_paginated_response",
|
|
64
|
+
"get_simple_response",
|
|
65
|
+
"iter_paginated_results",
|
|
66
|
+
"request_url",
|
|
67
|
+
"subscribe_pubmeds",
|
|
68
|
+
"unsubscribe_pubmeds",
|
|
69
|
+
]
|
modulector_sdk/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
"""Typed service functions for Modulector API endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import Any, Final, Literal, TypedDict, cast
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from .utils import (
|
|
12
|
+
Headers,
|
|
13
|
+
PaginatedResponse,
|
|
14
|
+
get_paginated_response,
|
|
15
|
+
get_simple_response,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
MODULECTOR_API_BASE_URL: Final[str] = os.getenv(
|
|
19
|
+
"MODULECTOR_API_BASE_URL",
|
|
20
|
+
"https://modulector.multiomix.org",
|
|
21
|
+
)
|
|
22
|
+
API_BASE_URL: Final[str] = MODULECTOR_API_BASE_URL
|
|
23
|
+
FINDER_LIMIT_MAX: Final[int] = 3000
|
|
24
|
+
|
|
25
|
+
Ordering = str | Sequence[str]
|
|
26
|
+
ScoreClass = Literal["V", "H", "M", "L"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SourceLink(TypedDict):
|
|
30
|
+
"""External source link returned by detail endpoints."""
|
|
31
|
+
|
|
32
|
+
source: str
|
|
33
|
+
"""Name of the external database that contains related information."""
|
|
34
|
+
|
|
35
|
+
url: str
|
|
36
|
+
"""URL to access the external database entry."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MirnaDetailsResponse(TypedDict):
|
|
40
|
+
"""Response returned by the miRNA details service."""
|
|
41
|
+
|
|
42
|
+
aliases: list[str]
|
|
43
|
+
"""miRNA aliases, including previous IDs according to miRBase."""
|
|
44
|
+
|
|
45
|
+
mirna_sequence: str | None
|
|
46
|
+
"""miRNA nucleotide sequence."""
|
|
47
|
+
|
|
48
|
+
mirbase_accession_id: str
|
|
49
|
+
"""miRNA accession ID according to miRBase."""
|
|
50
|
+
|
|
51
|
+
links: list[SourceLink]
|
|
52
|
+
"""External database links for the miRNA of interest."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class MirnaAlias(TypedDict):
|
|
56
|
+
"""One miRBase accession to mature miRNA alias record."""
|
|
57
|
+
|
|
58
|
+
mirbase_accession_id: str
|
|
59
|
+
"""miRBase accession ID for the miRNA."""
|
|
60
|
+
|
|
61
|
+
mature_mirna: str
|
|
62
|
+
"""Mature miRNA ID in the miRBase database."""
|
|
63
|
+
|
|
64
|
+
previous_mature_mirna: str | None
|
|
65
|
+
"""Previous mature miRNA identifier associated with the same accession ID."""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class MirnaTargetInteraction(TypedDict):
|
|
69
|
+
"""One miRNA target interaction record."""
|
|
70
|
+
|
|
71
|
+
id: int
|
|
72
|
+
"""Record identifier in MirDIP."""
|
|
73
|
+
|
|
74
|
+
mirna: str
|
|
75
|
+
"""Standardized miRNA ID used by the interaction record."""
|
|
76
|
+
|
|
77
|
+
mirna_aliases: list[str]
|
|
78
|
+
"""Aliases and identifiers used to resolve the searched miRNA."""
|
|
79
|
+
|
|
80
|
+
gene: str
|
|
81
|
+
"""Target gene symbol."""
|
|
82
|
+
|
|
83
|
+
gene_aliases: list[str]
|
|
84
|
+
"""Aliases and symbols associated with the searched gene."""
|
|
85
|
+
|
|
86
|
+
score: str
|
|
87
|
+
"""MirDIP interaction score, with values between 0 and 1."""
|
|
88
|
+
|
|
89
|
+
source_name: str
|
|
90
|
+
"""Database from which the interaction was extracted."""
|
|
91
|
+
|
|
92
|
+
pubmeds: list[str]
|
|
93
|
+
"""PubMed URLs for the miRNA-gene interaction."""
|
|
94
|
+
|
|
95
|
+
sources: list[str]
|
|
96
|
+
"""Source database names used to calculate the MirDIP interaction score."""
|
|
97
|
+
|
|
98
|
+
score_class: ScoreClass | None
|
|
99
|
+
"""MirDIP score class for the interaction."""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class MirnaDisease(TypedDict):
|
|
103
|
+
"""One miRNA disease association record."""
|
|
104
|
+
|
|
105
|
+
id: int
|
|
106
|
+
"""Internal ID of the record in the HMDD database."""
|
|
107
|
+
|
|
108
|
+
category: str
|
|
109
|
+
"""HMDD category code assigned to classify the disease association."""
|
|
110
|
+
|
|
111
|
+
disease: str
|
|
112
|
+
"""Name of the disease associated with the miRNA."""
|
|
113
|
+
|
|
114
|
+
pubmed: str
|
|
115
|
+
"""PubMed URL for the scientific article supporting the association."""
|
|
116
|
+
|
|
117
|
+
description: str
|
|
118
|
+
"""Short description of why the miRNA is related to the disease."""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class MirnaDrug(TypedDict):
|
|
122
|
+
"""One miRNA drug association record."""
|
|
123
|
+
|
|
124
|
+
id: int
|
|
125
|
+
"""Internal ID of the record in the SM2miR database."""
|
|
126
|
+
|
|
127
|
+
small_molecule: str
|
|
128
|
+
"""Small molecule or drug name."""
|
|
129
|
+
|
|
130
|
+
fda_approved: bool
|
|
131
|
+
"""Whether the small molecule or drug is FDA approved."""
|
|
132
|
+
|
|
133
|
+
detection_method: str
|
|
134
|
+
"""Experimental method used to detect the miRNA expression effect."""
|
|
135
|
+
|
|
136
|
+
condition: str
|
|
137
|
+
"""Tissue or condition used for detection."""
|
|
138
|
+
|
|
139
|
+
pubmed: str
|
|
140
|
+
"""PubMed URL for the scientific article supporting the relationship."""
|
|
141
|
+
|
|
142
|
+
reference: str
|
|
143
|
+
"""Title of the scientific article supporting the relationship."""
|
|
144
|
+
|
|
145
|
+
expression_pattern: str
|
|
146
|
+
"""Expression pattern of the miRNA in the drug relationship."""
|
|
147
|
+
|
|
148
|
+
support: str
|
|
149
|
+
"""Supporting text for the drug-miRNA relationship."""
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class UcscCpgIsland(TypedDict):
|
|
153
|
+
"""UCSC CpG island relation for a methylation site."""
|
|
154
|
+
|
|
155
|
+
cpg_island: str
|
|
156
|
+
"""Chromosomal coordinates of the CpG island."""
|
|
157
|
+
|
|
158
|
+
relation: str
|
|
159
|
+
"""Relation of the methylation site to the CpG island."""
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class MethylationDetailsResponse(TypedDict):
|
|
163
|
+
"""Response returned by the methylation site details service."""
|
|
164
|
+
|
|
165
|
+
name: str
|
|
166
|
+
"""Methylation site name according to the Illumina Infinium MethylationEPIC
|
|
167
|
+
2.0 array.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
aliases: list[str]
|
|
171
|
+
"""Other names for the same methylation site on Illumina arrays."""
|
|
172
|
+
|
|
173
|
+
chromosome_position: str
|
|
174
|
+
"""Chromosome, position, and strand where the methylation site is located."""
|
|
175
|
+
|
|
176
|
+
ucsc_cpg_islands: list[UcscCpgIsland]
|
|
177
|
+
"""CpG islands related to the methylation site according to UCSC."""
|
|
178
|
+
|
|
179
|
+
genes: dict[str, list[str]]
|
|
180
|
+
"""Genes related to the methylation site and their affected regions."""
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class SubscribePubmedsResponse(TypedDict):
|
|
184
|
+
"""Response returned after creating a PubMed news subscription."""
|
|
185
|
+
|
|
186
|
+
token: str
|
|
187
|
+
"""Subscription token."""
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def get_mirna_target_interactions(
|
|
191
|
+
*,
|
|
192
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
193
|
+
mirna: str | None = None,
|
|
194
|
+
gene: str | None = None,
|
|
195
|
+
score: float | None = None,
|
|
196
|
+
include_pubmeds: bool | None = None,
|
|
197
|
+
ordering: Ordering | None = None,
|
|
198
|
+
search: str | None = None,
|
|
199
|
+
page: int | None = None,
|
|
200
|
+
page_size: int | None = None,
|
|
201
|
+
headers: Headers = None,
|
|
202
|
+
timeout: float = 30.0,
|
|
203
|
+
session: requests.Session | None = None,
|
|
204
|
+
) -> PaginatedResponse[MirnaTargetInteraction]:
|
|
205
|
+
"""Return miRNA-gene target interactions.
|
|
206
|
+
|
|
207
|
+
:param base_url: Base URL of the Modulector API.
|
|
208
|
+
:param mirna: miRNA accession ID or miRBase name.
|
|
209
|
+
:param gene: Gene symbol.
|
|
210
|
+
:param score: Minimum mirDIP interaction score. Valid values are between
|
|
211
|
+
``0`` and ``1``.
|
|
212
|
+
:param include_pubmeds: Whether PubMed links should be included.
|
|
213
|
+
:param ordering: Ordering field or comma-separated fields. Prefix a field
|
|
214
|
+
with ``-`` for descending order.
|
|
215
|
+
:param search: Search term for supported server-side search fields.
|
|
216
|
+
:param page: Page number to request.
|
|
217
|
+
:param page_size: Number of records per page.
|
|
218
|
+
:param headers: Optional HTTP headers.
|
|
219
|
+
:param timeout: Request timeout in seconds.
|
|
220
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
221
|
+
:raises ValueError: If neither ``mirna`` nor ``gene`` is provided, or if
|
|
222
|
+
``score`` is outside the accepted range.
|
|
223
|
+
:return: Paginated miRNA target interaction records.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
if mirna is None and gene is None:
|
|
227
|
+
raise ValueError("mirna or gene is required")
|
|
228
|
+
if score is not None and not 0 <= score <= 1:
|
|
229
|
+
raise ValueError("score must be between 0 and 1")
|
|
230
|
+
|
|
231
|
+
params = _request_params(
|
|
232
|
+
mirna=mirna,
|
|
233
|
+
gene=gene,
|
|
234
|
+
score=score,
|
|
235
|
+
include_pubmeds=_bool_param(include_pubmeds),
|
|
236
|
+
ordering=_format_ordering(ordering),
|
|
237
|
+
search=search,
|
|
238
|
+
)
|
|
239
|
+
response = get_paginated_response(
|
|
240
|
+
_build_url(base_url, "mirna-target-interactions"),
|
|
241
|
+
params=params,
|
|
242
|
+
page=page,
|
|
243
|
+
page_size=page_size,
|
|
244
|
+
headers=headers,
|
|
245
|
+
timeout=timeout,
|
|
246
|
+
session=session,
|
|
247
|
+
)
|
|
248
|
+
return cast(PaginatedResponse[MirnaTargetInteraction], response)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def get_mirna_details(
|
|
252
|
+
mirna: str,
|
|
253
|
+
*,
|
|
254
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
255
|
+
headers: Headers = None,
|
|
256
|
+
timeout: float = 30.0,
|
|
257
|
+
session: requests.Session | None = None,
|
|
258
|
+
) -> MirnaDetailsResponse:
|
|
259
|
+
"""Return details for one miRNA.
|
|
260
|
+
|
|
261
|
+
:param mirna: miRNA identifier, such as a miRNA code or accession ID.
|
|
262
|
+
:param base_url: Base URL of the Modulector API.
|
|
263
|
+
:param headers: Optional HTTP headers.
|
|
264
|
+
:param timeout: Request timeout in seconds.
|
|
265
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
266
|
+
:return: miRNA aliases, sequence, accession ID, and source links.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
payload = get_simple_response(
|
|
270
|
+
_build_url(base_url, "mirna"),
|
|
271
|
+
params={"mirna": mirna},
|
|
272
|
+
headers=headers,
|
|
273
|
+
timeout=timeout,
|
|
274
|
+
session=session,
|
|
275
|
+
)
|
|
276
|
+
return cast(MirnaDetailsResponse, payload)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def get_mirna_aliases(
|
|
280
|
+
*,
|
|
281
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
282
|
+
mature_mirna: str | None = None,
|
|
283
|
+
mirbase_accession_id: str | None = None,
|
|
284
|
+
previous_mature_mirna: str | None = None,
|
|
285
|
+
search: str | None = None,
|
|
286
|
+
ordering: Ordering | None = None,
|
|
287
|
+
page: int | None = None,
|
|
288
|
+
page_size: int | None = None,
|
|
289
|
+
headers: Headers = None,
|
|
290
|
+
timeout: float = 30.0,
|
|
291
|
+
session: requests.Session | None = None,
|
|
292
|
+
) -> PaginatedResponse[MirnaAlias]:
|
|
293
|
+
"""Return miRNA accession and mature ID alias records.
|
|
294
|
+
|
|
295
|
+
:param base_url: Base URL of the Modulector API.
|
|
296
|
+
:param mature_mirna: Exact mature miRNA filter.
|
|
297
|
+
:param mirbase_accession_id: Exact miRBase accession ID filter.
|
|
298
|
+
:param previous_mature_mirna: Exact previous mature miRNA filter.
|
|
299
|
+
:param search: Case-insensitive search across identifier fields.
|
|
300
|
+
:param ordering: Ordering field or comma-separated fields. Prefix a field
|
|
301
|
+
with ``-`` for descending order.
|
|
302
|
+
:param page: Page number to request.
|
|
303
|
+
:param page_size: Number of records per page.
|
|
304
|
+
:param headers: Optional HTTP headers.
|
|
305
|
+
:param timeout: Request timeout in seconds.
|
|
306
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
307
|
+
:return: Paginated miRNA alias records.
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
params = _request_params(
|
|
311
|
+
mature_mirna=mature_mirna,
|
|
312
|
+
mirbase_accession_id=mirbase_accession_id,
|
|
313
|
+
previous_mature_mirna=previous_mature_mirna,
|
|
314
|
+
search=search,
|
|
315
|
+
ordering=_format_ordering(ordering),
|
|
316
|
+
)
|
|
317
|
+
response = get_paginated_response(
|
|
318
|
+
_build_url(base_url, "mirna-aliases"),
|
|
319
|
+
params=params,
|
|
320
|
+
page=page,
|
|
321
|
+
page_size=page_size,
|
|
322
|
+
headers=headers,
|
|
323
|
+
timeout=timeout,
|
|
324
|
+
session=session,
|
|
325
|
+
)
|
|
326
|
+
return cast(PaginatedResponse[MirnaAlias], response)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def find_mirna_codes(
|
|
330
|
+
query: str,
|
|
331
|
+
*,
|
|
332
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
333
|
+
limit: int | None = None,
|
|
334
|
+
headers: Headers = None,
|
|
335
|
+
timeout: float = 30.0,
|
|
336
|
+
session: requests.Session | None = None,
|
|
337
|
+
) -> list[str]:
|
|
338
|
+
"""Return miRNA identifiers matching a search string.
|
|
339
|
+
|
|
340
|
+
:param query: miRNA search string.
|
|
341
|
+
:param base_url: Base URL of the Modulector API.
|
|
342
|
+
:param limit: Maximum number of returned values. The API accepts values up
|
|
343
|
+
to ``3000``.
|
|
344
|
+
:param headers: Optional HTTP headers.
|
|
345
|
+
:param timeout: Request timeout in seconds.
|
|
346
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
347
|
+
:raises ValueError: If ``limit`` is less than ``1`` or greater than
|
|
348
|
+
``3000``.
|
|
349
|
+
:return: Matching miRNA IDs or accession IDs.
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
_validate_limit(limit)
|
|
353
|
+
payload = get_simple_response(
|
|
354
|
+
_build_url(base_url, "mirna-codes-finder"),
|
|
355
|
+
params=_request_params(query=query, limit=limit),
|
|
356
|
+
headers=headers,
|
|
357
|
+
timeout=timeout,
|
|
358
|
+
session=session,
|
|
359
|
+
)
|
|
360
|
+
return cast(list[str], payload)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def get_mirna_codes(
|
|
364
|
+
mirna_codes: Sequence[str],
|
|
365
|
+
*,
|
|
366
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
367
|
+
headers: Headers = None,
|
|
368
|
+
timeout: float = 30.0,
|
|
369
|
+
session: requests.Session | None = None,
|
|
370
|
+
) -> dict[str, str | None]:
|
|
371
|
+
"""Return approved miRBase accessions for miRNA identifiers.
|
|
372
|
+
|
|
373
|
+
:param mirna_codes: miRNA identifiers to resolve.
|
|
374
|
+
:param base_url: Base URL of the Modulector API.
|
|
375
|
+
:param headers: Optional HTTP headers.
|
|
376
|
+
:param timeout: Request timeout in seconds.
|
|
377
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
378
|
+
:return: Mapping from each requested identifier to its accession ID, or
|
|
379
|
+
``None`` when no accession ID is found.
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
payload = get_simple_response(
|
|
383
|
+
_build_url(base_url, "mirna-codes"),
|
|
384
|
+
method="POST",
|
|
385
|
+
json={"mirna_codes": list(mirna_codes)},
|
|
386
|
+
headers=headers,
|
|
387
|
+
timeout=timeout,
|
|
388
|
+
session=session,
|
|
389
|
+
)
|
|
390
|
+
return cast(dict[str, str | None], payload)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def find_methylation_sites(
|
|
394
|
+
query: str,
|
|
395
|
+
*,
|
|
396
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
397
|
+
limit: int | None = None,
|
|
398
|
+
headers: Headers = None,
|
|
399
|
+
timeout: float = 30.0,
|
|
400
|
+
session: requests.Session | None = None,
|
|
401
|
+
) -> list[str]:
|
|
402
|
+
"""Return methylation site names matching a search string.
|
|
403
|
+
|
|
404
|
+
:param query: Methylation site search string.
|
|
405
|
+
:param base_url: Base URL of the Modulector API.
|
|
406
|
+
:param limit: Maximum number of returned values. The API accepts values up
|
|
407
|
+
to ``3000``.
|
|
408
|
+
:param headers: Optional HTTP headers.
|
|
409
|
+
:param timeout: Request timeout in seconds.
|
|
410
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
411
|
+
:raises ValueError: If ``limit`` is less than ``1`` or greater than
|
|
412
|
+
``3000``.
|
|
413
|
+
:return: Matching methylation site names.
|
|
414
|
+
"""
|
|
415
|
+
|
|
416
|
+
_validate_limit(limit)
|
|
417
|
+
payload = get_simple_response(
|
|
418
|
+
_build_url(base_url, "methylation-sites-finder"),
|
|
419
|
+
params=_request_params(query=query, limit=limit),
|
|
420
|
+
headers=headers,
|
|
421
|
+
timeout=timeout,
|
|
422
|
+
session=session,
|
|
423
|
+
)
|
|
424
|
+
return cast(list[str], payload)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def get_methylation_sites(
|
|
428
|
+
methylation_sites: Sequence[str],
|
|
429
|
+
*,
|
|
430
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
431
|
+
headers: Headers = None,
|
|
432
|
+
timeout: float = 30.0,
|
|
433
|
+
session: requests.Session | None = None,
|
|
434
|
+
) -> dict[str, list[str]]:
|
|
435
|
+
"""Return current EPIC 2.0 names for methylation site identifiers.
|
|
436
|
+
|
|
437
|
+
:param methylation_sites: Illumina methylation site names or identifiers.
|
|
438
|
+
:param base_url: Base URL of the Modulector API.
|
|
439
|
+
:param headers: Optional HTTP headers.
|
|
440
|
+
:param timeout: Request timeout in seconds.
|
|
441
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
442
|
+
:return: Mapping from each requested identifier to matching EPIC 2.0 site
|
|
443
|
+
names.
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
payload = get_simple_response(
|
|
447
|
+
_build_url(base_url, "methylation-sites"),
|
|
448
|
+
method="POST",
|
|
449
|
+
json={"methylation_sites": list(methylation_sites)},
|
|
450
|
+
headers=headers,
|
|
451
|
+
timeout=timeout,
|
|
452
|
+
session=session,
|
|
453
|
+
)
|
|
454
|
+
return cast(dict[str, list[str]], payload)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def get_methylation_site_genes(
|
|
458
|
+
methylation_sites: Sequence[str],
|
|
459
|
+
*,
|
|
460
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
461
|
+
headers: Headers = None,
|
|
462
|
+
timeout: float = 30.0,
|
|
463
|
+
session: requests.Session | None = None,
|
|
464
|
+
) -> dict[str, list[str]]:
|
|
465
|
+
"""Return genes associated with methylation site identifiers.
|
|
466
|
+
|
|
467
|
+
:param methylation_sites: Illumina methylation site names or identifiers.
|
|
468
|
+
:param base_url: Base URL of the Modulector API.
|
|
469
|
+
:param headers: Optional HTTP headers.
|
|
470
|
+
:param timeout: Request timeout in seconds.
|
|
471
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
472
|
+
:return: Mapping from each requested identifier to associated genes.
|
|
473
|
+
"""
|
|
474
|
+
|
|
475
|
+
payload = get_simple_response(
|
|
476
|
+
_build_url(base_url, "methylation-sites-genes"),
|
|
477
|
+
method="POST",
|
|
478
|
+
json={"methylation_sites": list(methylation_sites)},
|
|
479
|
+
headers=headers,
|
|
480
|
+
timeout=timeout,
|
|
481
|
+
session=session,
|
|
482
|
+
)
|
|
483
|
+
return cast(dict[str, list[str]], payload)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def get_methylation_details(
|
|
487
|
+
methylation_site: str,
|
|
488
|
+
*,
|
|
489
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
490
|
+
headers: Headers = None,
|
|
491
|
+
timeout: float = 30.0,
|
|
492
|
+
session: requests.Session | None = None,
|
|
493
|
+
) -> MethylationDetailsResponse:
|
|
494
|
+
"""Return details for one methylation site.
|
|
495
|
+
|
|
496
|
+
:param methylation_site: Methylation site name from the Infinium
|
|
497
|
+
MethylationEPIC 2.0 array.
|
|
498
|
+
:param base_url: Base URL of the Modulector API.
|
|
499
|
+
:param headers: Optional HTTP headers.
|
|
500
|
+
:param timeout: Request timeout in seconds.
|
|
501
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
502
|
+
:return: Methylation site details, aliases, CpG island relations, and genes.
|
|
503
|
+
"""
|
|
504
|
+
|
|
505
|
+
payload = get_simple_response(
|
|
506
|
+
_build_url(base_url, "methylation"),
|
|
507
|
+
params={"methylation_site": methylation_site},
|
|
508
|
+
headers=headers,
|
|
509
|
+
timeout=timeout,
|
|
510
|
+
session=session,
|
|
511
|
+
)
|
|
512
|
+
return cast(MethylationDetailsResponse, payload)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def get_diseases(
|
|
516
|
+
*,
|
|
517
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
518
|
+
mirna: str | None = None,
|
|
519
|
+
search: str | None = None,
|
|
520
|
+
ordering: Ordering | None = None,
|
|
521
|
+
page: int | None = None,
|
|
522
|
+
page_size: int | None = None,
|
|
523
|
+
headers: Headers = None,
|
|
524
|
+
timeout: float = 30.0,
|
|
525
|
+
session: requests.Session | None = None,
|
|
526
|
+
) -> PaginatedResponse[MirnaDisease]:
|
|
527
|
+
"""Return miRNA and human disease association records.
|
|
528
|
+
|
|
529
|
+
:param base_url: Base URL of the Modulector API.
|
|
530
|
+
:param mirna: miRNA code or accession ID. If omitted, all records are
|
|
531
|
+
returned page by page.
|
|
532
|
+
:param search: Case-insensitive search term for disease names.
|
|
533
|
+
:param ordering: Ordering field or comma-separated fields. Prefix a field
|
|
534
|
+
with ``-`` for descending order.
|
|
535
|
+
:param page: Page number to request.
|
|
536
|
+
:param page_size: Number of records per page.
|
|
537
|
+
:param headers: Optional HTTP headers.
|
|
538
|
+
:param timeout: Request timeout in seconds.
|
|
539
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
540
|
+
:return: Paginated miRNA disease association records.
|
|
541
|
+
"""
|
|
542
|
+
|
|
543
|
+
params = _request_params(
|
|
544
|
+
mirna=mirna,
|
|
545
|
+
search=search,
|
|
546
|
+
ordering=_format_ordering(ordering),
|
|
547
|
+
)
|
|
548
|
+
response = get_paginated_response(
|
|
549
|
+
_build_url(base_url, "diseases"),
|
|
550
|
+
params=params,
|
|
551
|
+
page=page,
|
|
552
|
+
page_size=page_size,
|
|
553
|
+
headers=headers,
|
|
554
|
+
timeout=timeout,
|
|
555
|
+
session=session,
|
|
556
|
+
)
|
|
557
|
+
return cast(PaginatedResponse[MirnaDisease], response)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def get_drugs(
|
|
561
|
+
*,
|
|
562
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
563
|
+
mirna: str | None = None,
|
|
564
|
+
fda_approved: bool | None = None,
|
|
565
|
+
search: str | None = None,
|
|
566
|
+
ordering: Ordering | None = None,
|
|
567
|
+
page: int | None = None,
|
|
568
|
+
page_size: int | None = None,
|
|
569
|
+
headers: Headers = None,
|
|
570
|
+
timeout: float = 30.0,
|
|
571
|
+
session: requests.Session | None = None,
|
|
572
|
+
) -> PaginatedResponse[MirnaDrug]:
|
|
573
|
+
"""Return drug or small molecule records affecting miRNA expression.
|
|
574
|
+
|
|
575
|
+
:param base_url: Base URL of the Modulector API.
|
|
576
|
+
:param mirna: miRNA code or accession ID. If omitted, all records are
|
|
577
|
+
returned page by page.
|
|
578
|
+
:param fda_approved: Optional FDA approval filter.
|
|
579
|
+
:param search: Case-insensitive search term for condition, small molecule,
|
|
580
|
+
and expression pattern fields.
|
|
581
|
+
:param ordering: Ordering field or comma-separated fields. Prefix a field
|
|
582
|
+
with ``-`` for descending order.
|
|
583
|
+
:param page: Page number to request.
|
|
584
|
+
:param page_size: Number of records per page.
|
|
585
|
+
:param headers: Optional HTTP headers.
|
|
586
|
+
:param timeout: Request timeout in seconds.
|
|
587
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
588
|
+
:return: Paginated miRNA drug association records.
|
|
589
|
+
"""
|
|
590
|
+
|
|
591
|
+
params = _request_params(
|
|
592
|
+
mirna=mirna,
|
|
593
|
+
fda_approved=_bool_param(fda_approved),
|
|
594
|
+
search=search,
|
|
595
|
+
ordering=_format_ordering(ordering),
|
|
596
|
+
)
|
|
597
|
+
response = get_paginated_response(
|
|
598
|
+
_build_url(base_url, "drugs"),
|
|
599
|
+
params=params,
|
|
600
|
+
page=page,
|
|
601
|
+
page_size=page_size,
|
|
602
|
+
headers=headers,
|
|
603
|
+
timeout=timeout,
|
|
604
|
+
session=session,
|
|
605
|
+
)
|
|
606
|
+
return cast(PaginatedResponse[MirnaDrug], response)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def subscribe_pubmeds(
|
|
610
|
+
mirna: str,
|
|
611
|
+
email: str,
|
|
612
|
+
*,
|
|
613
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
614
|
+
gene: str | None = None,
|
|
615
|
+
headers: Headers = None,
|
|
616
|
+
timeout: float = 30.0,
|
|
617
|
+
session: requests.Session | None = None,
|
|
618
|
+
) -> SubscribePubmedsResponse:
|
|
619
|
+
"""Subscribe an email address to PubMed news for a miRNA.
|
|
620
|
+
|
|
621
|
+
:param mirna: miRNA code or accession ID.
|
|
622
|
+
:param email: Email address to subscribe.
|
|
623
|
+
:param base_url: Base URL of the Modulector API.
|
|
624
|
+
:param gene: Optional gene symbol filter for the subscription.
|
|
625
|
+
:param headers: Optional HTTP headers.
|
|
626
|
+
:param timeout: Request timeout in seconds.
|
|
627
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
628
|
+
:return: Subscription token response.
|
|
629
|
+
"""
|
|
630
|
+
|
|
631
|
+
payload = get_simple_response(
|
|
632
|
+
_build_url(base_url, "subscribe-pubmeds"),
|
|
633
|
+
params=_request_params(mirna=mirna, email=email, gene=gene),
|
|
634
|
+
headers=headers,
|
|
635
|
+
timeout=timeout,
|
|
636
|
+
session=session,
|
|
637
|
+
)
|
|
638
|
+
return cast(SubscribePubmedsResponse, payload)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def unsubscribe_pubmeds(
|
|
642
|
+
token: str,
|
|
643
|
+
*,
|
|
644
|
+
base_url: str = MODULECTOR_API_BASE_URL,
|
|
645
|
+
headers: Headers = None,
|
|
646
|
+
timeout: float = 30.0,
|
|
647
|
+
session: requests.Session | None = None,
|
|
648
|
+
) -> str:
|
|
649
|
+
"""Unsubscribe from PubMed news.
|
|
650
|
+
|
|
651
|
+
:param token: Subscription token returned by ``subscribe_pubmeds``.
|
|
652
|
+
:param base_url: Base URL of the Modulector API.
|
|
653
|
+
:param headers: Optional HTTP headers.
|
|
654
|
+
:param timeout: Request timeout in seconds.
|
|
655
|
+
:param session: Optional ``requests.Session`` to use for the request.
|
|
656
|
+
:return: API confirmation message.
|
|
657
|
+
"""
|
|
658
|
+
|
|
659
|
+
payload = get_simple_response(
|
|
660
|
+
_build_url(base_url, "unsubscribe-pubmeds"),
|
|
661
|
+
params={"token": token},
|
|
662
|
+
headers=headers,
|
|
663
|
+
timeout=timeout,
|
|
664
|
+
session=session,
|
|
665
|
+
)
|
|
666
|
+
return cast(str, payload)
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def _build_url(base_url: str, endpoint: str) -> str:
|
|
670
|
+
"""Build an absolute endpoint URL with a trailing slash.
|
|
671
|
+
|
|
672
|
+
:param base_url: Base URL of the Modulector API.
|
|
673
|
+
:param endpoint: Endpoint path with or without leading or trailing slashes.
|
|
674
|
+
:return: Absolute URL for the endpoint.
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
return f"{base_url.rstrip('/')}/{endpoint.strip('/')}/"
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def _request_params(**params: Any) -> dict[str, Any]:
|
|
681
|
+
"""Return request parameters without ``None`` values.
|
|
682
|
+
|
|
683
|
+
:param params: Query parameters keyed by API parameter name.
|
|
684
|
+
:return: A dictionary containing only parameters with non-``None`` values.
|
|
685
|
+
"""
|
|
686
|
+
|
|
687
|
+
return {key: value for key, value in params.items() if value is not None}
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def _bool_param(value: bool | None) -> str | None:
|
|
691
|
+
"""Convert an optional boolean to the API's lowercase string form.
|
|
692
|
+
|
|
693
|
+
:param value: Boolean value to convert.
|
|
694
|
+
:return: ``"true"``, ``"false"``, or ``None``.
|
|
695
|
+
"""
|
|
696
|
+
|
|
697
|
+
if value is None:
|
|
698
|
+
return None
|
|
699
|
+
return "true" if value else "false"
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
def _format_ordering(ordering: Ordering | None) -> str | None:
|
|
703
|
+
"""Format an ordering value for Django REST Framework query parameters.
|
|
704
|
+
|
|
705
|
+
:param ordering: A comma-separated ordering string or a sequence of ordering
|
|
706
|
+
fields.
|
|
707
|
+
:return: A comma-separated ordering string, or ``None``.
|
|
708
|
+
"""
|
|
709
|
+
|
|
710
|
+
if ordering is None:
|
|
711
|
+
return None
|
|
712
|
+
if isinstance(ordering, str):
|
|
713
|
+
return ordering
|
|
714
|
+
|
|
715
|
+
ordering_fields = [field for field in ordering if field]
|
|
716
|
+
if not ordering_fields:
|
|
717
|
+
return None
|
|
718
|
+
return ",".join(ordering_fields)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def _validate_limit(limit: int | None) -> None:
|
|
722
|
+
"""Validate finder service limit parameters.
|
|
723
|
+
|
|
724
|
+
:param limit: Limit value to validate.
|
|
725
|
+
:raises ValueError: If ``limit`` is outside the API's accepted range.
|
|
726
|
+
:return: ``None``.
|
|
727
|
+
"""
|
|
728
|
+
|
|
729
|
+
if limit is not None and not 1 <= limit <= FINDER_LIMIT_MAX:
|
|
730
|
+
raise ValueError(f"limit must be between 1 and {FINDER_LIMIT_MAX}")
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
__all__ = [
|
|
734
|
+
"API_BASE_URL",
|
|
735
|
+
"MODULECTOR_API_BASE_URL",
|
|
736
|
+
"MethylationDetailsResponse",
|
|
737
|
+
"MirnaAlias",
|
|
738
|
+
"MirnaDetailsResponse",
|
|
739
|
+
"MirnaDisease",
|
|
740
|
+
"MirnaDrug",
|
|
741
|
+
"MirnaTargetInteraction",
|
|
742
|
+
"SourceLink",
|
|
743
|
+
"SubscribePubmedsResponse",
|
|
744
|
+
"UcscCpgIsland",
|
|
745
|
+
"find_methylation_sites",
|
|
746
|
+
"find_mirna_codes",
|
|
747
|
+
"get_diseases",
|
|
748
|
+
"get_drugs",
|
|
749
|
+
"get_methylation_details",
|
|
750
|
+
"get_methylation_site_genes",
|
|
751
|
+
"get_methylation_sites",
|
|
752
|
+
"get_mirna_aliases",
|
|
753
|
+
"get_mirna_codes",
|
|
754
|
+
"get_mirna_details",
|
|
755
|
+
"get_mirna_target_interactions",
|
|
756
|
+
"subscribe_pubmeds",
|
|
757
|
+
"unsubscribe_pubmeds",
|
|
758
|
+
]
|
modulector_sdk/utils.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Utilities for calling Modulector API endpoints.
|
|
2
|
+
|
|
3
|
+
The public API returns either a plain JSON payload or a paginated payload with
|
|
4
|
+
``count``, ``next``, ``previous``, and ``results`` fields.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Iterator, Mapping
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Generic, TypeVar
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
JSON = dict[str, Any] | list[Any] | str | int | float | bool | None
|
|
14
|
+
Params = Mapping[str, Any] | None
|
|
15
|
+
Headers = Mapping[str, str] | None
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class PaginatedResponse(Generic[T]):
|
|
21
|
+
"""Representation of a Modulector paginated response."""
|
|
22
|
+
|
|
23
|
+
count: int
|
|
24
|
+
next: str | None
|
|
25
|
+
previous: str | None
|
|
26
|
+
results: list[T]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def request_url(
|
|
30
|
+
url: str,
|
|
31
|
+
*,
|
|
32
|
+
method: str = "GET",
|
|
33
|
+
params: Params = None,
|
|
34
|
+
json: Any = None,
|
|
35
|
+
data: Any = None,
|
|
36
|
+
headers: Headers = None,
|
|
37
|
+
timeout: float = 30.0,
|
|
38
|
+
session: requests.Session | None = None,
|
|
39
|
+
) -> JSON:
|
|
40
|
+
"""Make an HTTP request and return the decoded JSON response.
|
|
41
|
+
|
|
42
|
+
HTTP errors are raised with ``requests.Response.raise_for_status()``.
|
|
43
|
+
JSON decoding errors are propagated from ``requests``.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
client = session or requests
|
|
47
|
+
response = client.request(
|
|
48
|
+
method.upper(),
|
|
49
|
+
url,
|
|
50
|
+
params=params,
|
|
51
|
+
json=json,
|
|
52
|
+
data=data,
|
|
53
|
+
headers=headers,
|
|
54
|
+
timeout=timeout,
|
|
55
|
+
)
|
|
56
|
+
response.raise_for_status()
|
|
57
|
+
return response.json()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_simple_response(
|
|
61
|
+
url: str,
|
|
62
|
+
*,
|
|
63
|
+
method: str = "GET",
|
|
64
|
+
params: Params = None,
|
|
65
|
+
json: Any = None,
|
|
66
|
+
data: Any = None,
|
|
67
|
+
headers: Headers = None,
|
|
68
|
+
timeout: float = 30.0,
|
|
69
|
+
session: requests.Session | None = None,
|
|
70
|
+
) -> JSON:
|
|
71
|
+
"""Return a non-paginated JSON response from an API endpoint."""
|
|
72
|
+
|
|
73
|
+
return request_url(
|
|
74
|
+
url,
|
|
75
|
+
method=method,
|
|
76
|
+
params=params,
|
|
77
|
+
json=json,
|
|
78
|
+
data=data,
|
|
79
|
+
headers=headers,
|
|
80
|
+
timeout=timeout,
|
|
81
|
+
session=session,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_paginated_response(
|
|
86
|
+
url: str,
|
|
87
|
+
*,
|
|
88
|
+
method: str = "GET",
|
|
89
|
+
params: Params = None,
|
|
90
|
+
page: int | None = None,
|
|
91
|
+
page_size: int | None = None,
|
|
92
|
+
json: Any = None,
|
|
93
|
+
data: Any = None,
|
|
94
|
+
headers: Headers = None,
|
|
95
|
+
timeout: float = 30.0,
|
|
96
|
+
session: requests.Session | None = None,
|
|
97
|
+
) -> PaginatedResponse[Any]:
|
|
98
|
+
"""Return one page from an endpoint using Modulector pagination."""
|
|
99
|
+
|
|
100
|
+
request_params = _with_pagination_params(params, page=page, page_size=page_size)
|
|
101
|
+
payload = request_url(
|
|
102
|
+
url,
|
|
103
|
+
method=method,
|
|
104
|
+
params=request_params,
|
|
105
|
+
json=json,
|
|
106
|
+
data=data,
|
|
107
|
+
headers=headers,
|
|
108
|
+
timeout=timeout,
|
|
109
|
+
session=session,
|
|
110
|
+
)
|
|
111
|
+
return _parse_paginated_response(payload)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def iter_paginated_results(
|
|
115
|
+
url: str,
|
|
116
|
+
*,
|
|
117
|
+
method: str = "GET",
|
|
118
|
+
params: Params = None,
|
|
119
|
+
page_size: int | None = None,
|
|
120
|
+
max_pages: int | None = None,
|
|
121
|
+
json: Any = None,
|
|
122
|
+
data: Any = None,
|
|
123
|
+
headers: Headers = None,
|
|
124
|
+
timeout: float = 30.0,
|
|
125
|
+
session: requests.Session | None = None,
|
|
126
|
+
) -> Iterator[Any]:
|
|
127
|
+
"""Yield every result by following each paginated response's ``next`` URL."""
|
|
128
|
+
|
|
129
|
+
if max_pages is not None and max_pages < 1:
|
|
130
|
+
raise ValueError("max_pages must be greater than 0")
|
|
131
|
+
|
|
132
|
+
next_url: str | None = url
|
|
133
|
+
request_params = _with_pagination_params(params, page=None, page_size=page_size)
|
|
134
|
+
pages_read = 0
|
|
135
|
+
|
|
136
|
+
while next_url is not None:
|
|
137
|
+
page = get_paginated_response(
|
|
138
|
+
next_url,
|
|
139
|
+
method=method,
|
|
140
|
+
params=request_params,
|
|
141
|
+
json=json,
|
|
142
|
+
data=data,
|
|
143
|
+
headers=headers,
|
|
144
|
+
timeout=timeout,
|
|
145
|
+
session=session,
|
|
146
|
+
)
|
|
147
|
+
yield from page.results
|
|
148
|
+
|
|
149
|
+
pages_read += 1
|
|
150
|
+
if max_pages is not None and pages_read >= max_pages:
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
next_url = page.next
|
|
154
|
+
request_params = None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_all_paginated_results(
|
|
158
|
+
url: str,
|
|
159
|
+
*,
|
|
160
|
+
method: str = "GET",
|
|
161
|
+
params: Params = None,
|
|
162
|
+
page_size: int | None = None,
|
|
163
|
+
max_pages: int | None = None,
|
|
164
|
+
json: Any = None,
|
|
165
|
+
data: Any = None,
|
|
166
|
+
headers: Headers = None,
|
|
167
|
+
timeout: float = 30.0,
|
|
168
|
+
session: requests.Session | None = None,
|
|
169
|
+
) -> list[Any]:
|
|
170
|
+
"""Return all results from a paginated endpoint as a list."""
|
|
171
|
+
|
|
172
|
+
return list(
|
|
173
|
+
iter_paginated_results(
|
|
174
|
+
url,
|
|
175
|
+
method=method,
|
|
176
|
+
params=params,
|
|
177
|
+
page_size=page_size,
|
|
178
|
+
max_pages=max_pages,
|
|
179
|
+
json=json,
|
|
180
|
+
data=data,
|
|
181
|
+
headers=headers,
|
|
182
|
+
timeout=timeout,
|
|
183
|
+
session=session,
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _with_pagination_params(
|
|
189
|
+
params: Params,
|
|
190
|
+
*,
|
|
191
|
+
page: int | None,
|
|
192
|
+
page_size: int | None,
|
|
193
|
+
) -> dict[str, Any] | None:
|
|
194
|
+
if page is not None and page < 1:
|
|
195
|
+
raise ValueError("page must be greater than 0")
|
|
196
|
+
if page_size is not None and not 1 <= page_size <= 1000:
|
|
197
|
+
raise ValueError("page_size must be between 1 and 1000")
|
|
198
|
+
|
|
199
|
+
if params is None and page is None and page_size is None:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
request_params = dict(params or {})
|
|
203
|
+
if page is not None:
|
|
204
|
+
request_params["page"] = page
|
|
205
|
+
if page_size is not None:
|
|
206
|
+
request_params["page_size"] = page_size
|
|
207
|
+
return request_params
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _parse_paginated_response(payload: JSON) -> PaginatedResponse[Any]:
|
|
211
|
+
if not isinstance(payload, Mapping):
|
|
212
|
+
raise ValueError("paginated response must be a JSON object")
|
|
213
|
+
|
|
214
|
+
missing_fields = {"count", "next", "previous", "results"} - payload.keys()
|
|
215
|
+
if missing_fields:
|
|
216
|
+
missing = ", ".join(sorted(missing_fields))
|
|
217
|
+
raise ValueError(f"paginated response is missing fields: {missing}")
|
|
218
|
+
|
|
219
|
+
results = payload["results"]
|
|
220
|
+
if not isinstance(results, list):
|
|
221
|
+
raise ValueError("paginated response field 'results' must be a list")
|
|
222
|
+
|
|
223
|
+
next_url = payload["next"]
|
|
224
|
+
previous_url = payload["previous"]
|
|
225
|
+
if next_url is not None and not isinstance(next_url, str):
|
|
226
|
+
raise ValueError("paginated response field 'next' must be a string or null")
|
|
227
|
+
if previous_url is not None and not isinstance(previous_url, str):
|
|
228
|
+
raise ValueError(
|
|
229
|
+
"paginated response field 'previous' must be a string or null"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return PaginatedResponse(
|
|
233
|
+
count=int(payload["count"]),
|
|
234
|
+
next=next_url,
|
|
235
|
+
previous=previous_url,
|
|
236
|
+
results=results,
|
|
237
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modulector-sdk
|
|
3
|
+
Version: 2.3.0
|
|
4
|
+
Summary: Typed Python SDK for the Modulector API.
|
|
5
|
+
Author: omicsdatascience
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: API, https://modulector.multiomix.org/
|
|
8
|
+
Project-URL: Homepage, https://modulector.multiomix.org/
|
|
9
|
+
Project-URL: Issues, https://github.com/omics-datascience/modulector/issues
|
|
10
|
+
Project-URL: Repository, https://github.com/omics-datascience/modulector
|
|
11
|
+
Keywords: api-client,bioinformatics,methylation,mirna,sdk
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: requests==2.31.0
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# Modulector SDK
|
|
32
|
+
|
|
33
|
+
Typed Python SDK for the Modulector API. The package contains client helpers for
|
|
34
|
+
querying miRNA target interactions, miRNA aliases, methylation sites, disease
|
|
35
|
+
associations, drug associations, and PubMed subscriptions without installing the
|
|
36
|
+
Django backend dependencies.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install modulector-sdk
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from modulector_sdk import get_mirna_details, get_mirna_target_interactions
|
|
48
|
+
|
|
49
|
+
details = get_mirna_details("hsa-miR-21-5p")
|
|
50
|
+
interactions = get_mirna_target_interactions(
|
|
51
|
+
mirna="hsa-miR-21-5p",
|
|
52
|
+
gene="PTEN",
|
|
53
|
+
include_pubmeds=True,
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Set `MODULECTOR_API_BASE_URL` to target a different Modulector deployment:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
MODULECTOR_API_BASE_URL=https://your-modulector.example.org python script.py
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Every service function also accepts a `base_url` keyword argument for
|
|
64
|
+
per-request overrides.
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
Build this SDK from the `sdk/` directory:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uv build
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The generated source distribution and wheel are written to `sdk/dist/`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
modulector_sdk/__init__.py,sha256=Wd7CZv_8iYI5az_YoJlVsi2O5y_6SdvCSuPfQPHlpaI,1692
|
|
2
|
+
modulector_sdk/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
3
|
+
modulector_sdk/services.py,sha256=XQus8xrRUv6NSjIIm28QGAA7foqCJA2c8c1t6VTWplg,24516
|
|
4
|
+
modulector_sdk/utils.py,sha256=0R8L_zXcdx9canOQj30SBDOQUyMIELWz0ksJn2S1Og0,6614
|
|
5
|
+
modulector_sdk-2.3.0.dist-info/licenses/LICENSE,sha256=rXNUFK1gna2amPRzSGZWLgKN7iM1pGsFBHJFT09MkOE,1094
|
|
6
|
+
modulector_sdk-2.3.0.dist-info/METADATA,sha256=mCuaZ8QQY61eLfLed66sH1ryNOk-3e4B3DjK7i4kais,2331
|
|
7
|
+
modulector_sdk-2.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
modulector_sdk-2.3.0.dist-info/top_level.txt,sha256=F-TA89bFrqt1LGOVZZHuVMknvKWvAU70aTzN-Vj1pcA,15
|
|
9
|
+
modulector_sdk-2.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 omicsdatascience
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
modulector_sdk
|