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.
@@ -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
+ ]
@@ -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
+ ]
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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