medterm4ds 0.0.1__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.
- medterm4ds/__init__.py +333 -0
- medterm4ds/apps/__init__.py +1 -0
- medterm4ds/apps/_asyncutil.py +24 -0
- medterm4ds/apps/api.py +413 -0
- medterm4ds/apps/cli.py +1668 -0
- medterm4ds/apps/fhir_api.py +3745 -0
- medterm4ds/apps/mcp.py +959 -0
- medterm4ds/client.py +733 -0
- medterm4ds/core/__init__.py +55 -0
- medterm4ds/core/config.py +101 -0
- medterm4ds/core/display.py +143 -0
- medterm4ds/core/http.py +46 -0
- medterm4ds/core/models.py +557 -0
- medterm4ds/core/normalize.py +75 -0
- medterm4ds/core/provision.py +401 -0
- medterm4ds/core/schemas.py +222 -0
- medterm4ds/core/sql.py +47 -0
- medterm4ds/domains/__init__.py +47 -0
- medterm4ds/domains/evidence.py +488 -0
- medterm4ds/domains/terminology.py +420 -0
- medterm4ds/ds.py +221 -0
- medterm4ds/engines/__init__.py +21 -0
- medterm4ds/engines/api/__init__.py +5 -0
- medterm4ds/engines/api/engine.py +420 -0
- medterm4ds/engines/base.py +186 -0
- medterm4ds/engines/duckdb/__init__.py +7 -0
- medterm4ds/engines/duckdb/_engine_base.py +748 -0
- medterm4ds/engines/duckdb/_mixins/_DiscoveryOps.py +253 -0
- medterm4ds/engines/duckdb/_mixins/_EngineState.py +334 -0
- medterm4ds/engines/duckdb/_mixins/_HierarchyOps.py +114 -0
- medterm4ds/engines/duckdb/_mixins/_IndicationsOps.py +50 -0
- medterm4ds/engines/duckdb/_mixins/_LookupOps.py +255 -0
- medterm4ds/engines/duckdb/_mixins/_MappingOps.py +301 -0
- medterm4ds/engines/duckdb/_mixins/_OptimizeOps.py +206 -0
- medterm4ds/engines/duckdb/_mixins/_PatientFriendlyOps.py +191 -0
- medterm4ds/engines/duckdb/_mixins/_ResolutionOps.py +126 -0
- medterm4ds/engines/duckdb/_mixins/__init__.py +6 -0
- medterm4ds/engines/duckdb/engine.py +59 -0
- medterm4ds/engines/duckdb/hierarchy.py +357 -0
- medterm4ds/engines/duckdb/indications.py +390 -0
- medterm4ds/engines/duckdb/mappings.py +1278 -0
- medterm4ds/engines/duckdb/patient_friendly.py +1329 -0
- medterm4ds/engines/duckdb/prepared.py +1572 -0
- medterm4ds/engines/duckdb/resolution.py +518 -0
- medterm4ds/engines/fhir/__init__.py +225 -0
- medterm4ds/engines/fhir/closure.py +291 -0
- medterm4ds/engines/fhir/equivalence.py +155 -0
- medterm4ds/engines/fhir/responses.py +574 -0
- medterm4ds/engines/fhir/semantic.py +213 -0
- medterm4ds/engines/fhir/xml.py +153 -0
- medterm4ds/outputs/__init__.py +55 -0
- medterm4ds/outputs/checkpoint.py +257 -0
- medterm4ds/outputs/fhir.py +183 -0
- medterm4ds/outputs/records.py +124 -0
- medterm4ds/outputs/render.py +174 -0
- medterm4ds/services/__init__.py +133 -0
- medterm4ds/services/bulk.py +125 -0
- medterm4ds/services/conceptmap.py +111 -0
- medterm4ds/services/crosswalk.py +112 -0
- medterm4ds/services/crosswalk_prepared.py +452 -0
- medterm4ds/services/crosswalk_walk.py +553 -0
- medterm4ds/services/data_setup.py +711 -0
- medterm4ds/services/discovery.py +82 -0
- medterm4ds/services/extraction.py +931 -0
- medterm4ds/services/hierarchy.py +192 -0
- medterm4ds/services/indications.py +30 -0
- medterm4ds/services/inventory.py +165 -0
- medterm4ds/services/loinc_parts.py +391 -0
- medterm4ds/services/lookup.py +71 -0
- medterm4ds/services/lookup_builder.py +150 -0
- medterm4ds/services/mapping.py +48 -0
- medterm4ds/services/optimize.py +32 -0
- medterm4ds/services/patient_friendly.py +48 -0
- medterm4ds/services/patient_friendly_prepared.py +1454 -0
- medterm4ds/services/prepared_primitives.py +124 -0
- medterm4ds/services/resolution.py +95 -0
- medterm4ds/services/rxnorm_tty_walk.py +320 -0
- medterm4ds/services/schema_reporting.py +74 -0
- medterm4ds/services/search.py +1094 -0
- medterm4ds/services/selection.py +273 -0
- medterm4ds/services/walk.py +399 -0
- medterm4ds/setup.py +104 -0
- medterm4ds/sources/__init__.py +98 -0
- medterm4ds/sources/base.py +193 -0
- medterm4ds/sources/cpt_hcpcs.py +126 -0
- medterm4ds/sources/cvx.py +50 -0
- medterm4ds/sources/generic.py +29 -0
- medterm4ds/sources/icd.py +43 -0
- medterm4ds/sources/loinc.py +144 -0
- medterm4ds/sources/rxnorm.py +236 -0
- medterm4ds/sources/snomed.py +187 -0
- medterm4ds-0.0.1.dist-info/METADATA +1000 -0
- medterm4ds-0.0.1.dist-info/RECORD +96 -0
- medterm4ds-0.0.1.dist-info/WHEEL +4 -0
- medterm4ds-0.0.1.dist-info/entry_points.txt +3 -0
- medterm4ds-0.0.1.dist-info/licenses/LICENSE +674 -0
medterm4ds/__init__.py
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"""Medical Terminology for Data Science.
|
|
2
|
+
|
|
3
|
+
# Recommended API (start here)
|
|
4
|
+
|
|
5
|
+
import medterm4ds as mt
|
|
6
|
+
|
|
7
|
+
# Connect to a UMLS DuckDB file (built via mt.build_umls_duckdb or the
|
|
8
|
+
# medterm4ds-build package).
|
|
9
|
+
terms = mt.connect("/path/to/umls.duckdb")
|
|
10
|
+
|
|
11
|
+
# Lookup, hierarchy, mapping, patient-friendly names.
|
|
12
|
+
info = terms.lookup("SNOMEDCT_US", "44054006")
|
|
13
|
+
parents = terms.parents([("SNOMEDCT_US", "44054006")])
|
|
14
|
+
friendly = terms.patient_friendly("SNOMEDCT_US", "44054006")
|
|
15
|
+
|
|
16
|
+
# Text-to-code search (BM25 + SapBERT).
|
|
17
|
+
results = mt.search("diabetes", mode="hybrid")
|
|
18
|
+
|
|
19
|
+
# Text extraction (GLiNER + medspaCy + search).
|
|
20
|
+
concepts = mt.extract("Patient has T2DM. No CKD.", format="codes")
|
|
21
|
+
|
|
22
|
+
For a remote HTTP backend instead of a local DuckDB:
|
|
23
|
+
|
|
24
|
+
terms = mt.connect_remote("http://localhost:8000")
|
|
25
|
+
|
|
26
|
+
# Surfaces
|
|
27
|
+
|
|
28
|
+
Same engine, four deliverables:
|
|
29
|
+
|
|
30
|
+
- **Python library** (this module)
|
|
31
|
+
- **CLI**: `pip install medterm4ds[api]` then `medterm4ds --help`
|
|
32
|
+
- **MCP server**: `pip install medterm4ds[mcp]` then `medterm4ds-mcp`
|
|
33
|
+
- **FHIR R4 server**: `pip install medterm4ds[fhir]` then `python -m medterm4ds.apps.fhir_api`
|
|
34
|
+
|
|
35
|
+
# Module layout
|
|
36
|
+
|
|
37
|
+
- `medterm4ds` — recommended API (this module)
|
|
38
|
+
- `medterm4ds.services.*` — service-layer functions (lookup, hierarchy, mapping,
|
|
39
|
+
search, extraction, etc.). Accept an `engine=` arg.
|
|
40
|
+
- `medterm4ds.domains.*` — domain-specific helpers (diagnosis_codes, lab_codes,
|
|
41
|
+
drugs_for_indication). Wrap services with source-list presets.
|
|
42
|
+
- `medterm4ds.engines.*` — engine implementations (LocalDuckDBEngine,
|
|
43
|
+
LocalLiteEngine, RemoteApiEngine).
|
|
44
|
+
- `medterm4ds.apps.*` — CLI, FastAPI, MCP, FHIR server entry points.
|
|
45
|
+
- `medterm4ds.ds` — dataframe helpers for pandas/polars workflows.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
__version__ = "0.0.1"
|
|
49
|
+
|
|
50
|
+
# ============================================================================
|
|
51
|
+
# Primary API — what most users need
|
|
52
|
+
# ============================================================================
|
|
53
|
+
|
|
54
|
+
from .client import Terminology, connect, connect_remote
|
|
55
|
+
|
|
56
|
+
# Intelligent text-to-code search (optional — requires BM25/SapBERT indexes)
|
|
57
|
+
# Log ImportError at WARNING so real bugs (typos, broken transitive imports)
|
|
58
|
+
# surface instead of silently producing AttributeError at the user's call site.
|
|
59
|
+
# Optional-deps users who expectedly lack BM25/SapBERT see one warning per
|
|
60
|
+
# missing surface, which they can silence via logging config if desired.
|
|
61
|
+
import logging as _logging
|
|
62
|
+
|
|
63
|
+
_logger = _logging.getLogger(__name__)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
from .services.search import search as _search_func
|
|
67
|
+
|
|
68
|
+
def search(query, *, mode="lexical", sources=None, count=20):
|
|
69
|
+
"""Text-to-code search (lexical/semantic/hybrid).
|
|
70
|
+
|
|
71
|
+
Requires BM25 indexes at MEDTERM4DS_SEARCH_INDEX_DIR and/or SapBERT
|
|
72
|
+
model at MEDTERM4DS_EMBEDDING_MODEL_DIR.
|
|
73
|
+
"""
|
|
74
|
+
return _search_func(query, mode=mode, sources=sources, count=count)
|
|
75
|
+
except ImportError as _exc:
|
|
76
|
+
_logger.warning("medterm4ds.search unavailable (install medterm4ds[search]?): %s", _exc)
|
|
77
|
+
|
|
78
|
+
# Text extraction (optional — requires medterm4ds[extraction])
|
|
79
|
+
try:
|
|
80
|
+
from .services.extraction import extract as _extract_func
|
|
81
|
+
from .services.extraction import find_terms as _find_terms_func
|
|
82
|
+
from .services.extraction import resolve_spans as _resolve_spans_func
|
|
83
|
+
|
|
84
|
+
def extract(text, *, format="codes", **kwargs):
|
|
85
|
+
"""Extract medical concepts from free text.
|
|
86
|
+
|
|
87
|
+
Requires medterm4ds[extraction] (medspaCy + transformers).
|
|
88
|
+
"""
|
|
89
|
+
return _extract_func(text, format=format, **kwargs)
|
|
90
|
+
|
|
91
|
+
def find_terms(text, **kwargs):
|
|
92
|
+
"""Extract medical terms from text (NLP only, no code resolution)."""
|
|
93
|
+
return _find_terms_func(text, **kwargs)
|
|
94
|
+
|
|
95
|
+
def resolve_spans(spans, **kwargs):
|
|
96
|
+
"""Resolve filtered spans to coded concepts via search."""
|
|
97
|
+
return _resolve_spans_func(spans, **kwargs)
|
|
98
|
+
except ImportError as _exc:
|
|
99
|
+
_logger.warning("medterm4ds.extract unavailable (install medterm4ds[extraction]?): %s", _exc)
|
|
100
|
+
|
|
101
|
+
# FHIR server (optional — requires medterm4ds[fhir])
|
|
102
|
+
try:
|
|
103
|
+
from .apps.fhir_api import create_fhir_app
|
|
104
|
+
except ImportError as _exc:
|
|
105
|
+
_logger.warning("medterm4ds.create_fhir_app unavailable (install medterm4ds[fhir]?): %s", _exc)
|
|
106
|
+
|
|
107
|
+
# Cache management — inspect and clean the ~/.medterm4ds/ cache
|
|
108
|
+
from .core.provision import cache_clear, cache_info, cache_versions
|
|
109
|
+
|
|
110
|
+
# ============================================================================
|
|
111
|
+
# Types — what most call sites need
|
|
112
|
+
# ============================================================================
|
|
113
|
+
|
|
114
|
+
from .core.models import (
|
|
115
|
+
CodeInfo,
|
|
116
|
+
CodeMapping,
|
|
117
|
+
CodeRef,
|
|
118
|
+
CodeRelation,
|
|
119
|
+
CodeResolution,
|
|
120
|
+
ConceptMapRow,
|
|
121
|
+
FriendlyNameResult,
|
|
122
|
+
NameSearchResult,
|
|
123
|
+
OptimizeResult,
|
|
124
|
+
OptimizeRule,
|
|
125
|
+
Provenance,
|
|
126
|
+
ProvenanceStep,
|
|
127
|
+
SourceStats,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# ============================================================================
|
|
131
|
+
# Advanced — engine implementations, service functions, domain helpers
|
|
132
|
+
#
|
|
133
|
+
# These are re-exported at the top level for legacy callers and for users who
|
|
134
|
+
# need direct access without going through the Terminology facade. Most new
|
|
135
|
+
# code should import from the submodule directly (e.g.
|
|
136
|
+
# `from medterm4ds.services.hierarchy import get_descendants`) for clarity.
|
|
137
|
+
# ============================================================================
|
|
138
|
+
|
|
139
|
+
from .core.config import (
|
|
140
|
+
LOCAL_DUCKDB_MEMORY_PROFILES,
|
|
141
|
+
LOCAL_LITE_MEMORY_PROFILES,
|
|
142
|
+
LocalDuckDBConfig,
|
|
143
|
+
LocalLiteConfig,
|
|
144
|
+
local_duckdb_config,
|
|
145
|
+
local_lite_config,
|
|
146
|
+
)
|
|
147
|
+
from .core.schemas import (
|
|
148
|
+
OUTPUT_SCHEMA_VERSION,
|
|
149
|
+
OutputField,
|
|
150
|
+
OutputSchema,
|
|
151
|
+
get_output_schema,
|
|
152
|
+
list_output_schemas,
|
|
153
|
+
)
|
|
154
|
+
from .domains import (
|
|
155
|
+
cross_reference,
|
|
156
|
+
diagnosis_codes,
|
|
157
|
+
discover,
|
|
158
|
+
drugs_by_class,
|
|
159
|
+
drugs_for_indication,
|
|
160
|
+
fda_label_by_rxcui,
|
|
161
|
+
guideline_fulltext,
|
|
162
|
+
guideline_recommendations,
|
|
163
|
+
guideline_search,
|
|
164
|
+
guidelines_for_code,
|
|
165
|
+
hcpcs_drugs,
|
|
166
|
+
indication_search,
|
|
167
|
+
lab_codes,
|
|
168
|
+
lab_value_codes,
|
|
169
|
+
procedure_codes,
|
|
170
|
+
search_drug,
|
|
171
|
+
vaccine_codes,
|
|
172
|
+
)
|
|
173
|
+
from .ds import (
|
|
174
|
+
code_ttys_dataframe,
|
|
175
|
+
conceptmap_dataframe,
|
|
176
|
+
hierarchy_dataframe,
|
|
177
|
+
lookup_dataframe,
|
|
178
|
+
map_dataframe,
|
|
179
|
+
mapping_conceptmap_dataframe,
|
|
180
|
+
patient_friendly_dataframe,
|
|
181
|
+
sample_codes_dataframe,
|
|
182
|
+
search_names_dataframe,
|
|
183
|
+
source_stats_dataframe,
|
|
184
|
+
)
|
|
185
|
+
from .engines.api import RemoteApiEngine
|
|
186
|
+
from .engines.duckdb import LocalDuckDBEngine, LocalLiteEngine
|
|
187
|
+
from .services.bulk import (
|
|
188
|
+
iter_batches,
|
|
189
|
+
iter_hierarchy_bulk,
|
|
190
|
+
iter_lookup_bulk,
|
|
191
|
+
iter_mapping_bulk,
|
|
192
|
+
iter_patient_friendly_bulk,
|
|
193
|
+
)
|
|
194
|
+
from .services.conceptmap import (
|
|
195
|
+
get_concept_map,
|
|
196
|
+
get_mapping_concept_map,
|
|
197
|
+
iter_concept_map,
|
|
198
|
+
iter_mapping_concept_map,
|
|
199
|
+
)
|
|
200
|
+
from .services.data_setup import (
|
|
201
|
+
DEFAULT_UMLS_RELEASE_TYPE,
|
|
202
|
+
DEFAULT_UMLS_VERIFY_SOURCES,
|
|
203
|
+
annotate_umls_duckdb,
|
|
204
|
+
build_umls_duckdb,
|
|
205
|
+
download_umls_release,
|
|
206
|
+
prepare_umls_duckdb,
|
|
207
|
+
verify_umls_duckdb,
|
|
208
|
+
)
|
|
209
|
+
from .services.discovery import get_code_ttys, get_source_stats, sample_source_codes, search_names
|
|
210
|
+
from .services.hierarchy import (
|
|
211
|
+
get_ancestors,
|
|
212
|
+
get_children,
|
|
213
|
+
get_code_relations,
|
|
214
|
+
get_descendants,
|
|
215
|
+
get_descendants_bfs,
|
|
216
|
+
get_parents,
|
|
217
|
+
is_descendant,
|
|
218
|
+
)
|
|
219
|
+
from .services.lookup import get_code_info, get_code_infos
|
|
220
|
+
from .services.mapping import get_code_mappings
|
|
221
|
+
from .services.optimize import optimize_codes
|
|
222
|
+
from .services.patient_friendly import get_patient_friendly_names
|
|
223
|
+
from .services.resolution import resolve_codes
|
|
224
|
+
|
|
225
|
+
__all__ = [
|
|
226
|
+
"__version__",
|
|
227
|
+
# Primary API
|
|
228
|
+
"Terminology",
|
|
229
|
+
"connect",
|
|
230
|
+
"connect_remote",
|
|
231
|
+
"search",
|
|
232
|
+
"extract",
|
|
233
|
+
"find_terms",
|
|
234
|
+
"resolve_spans",
|
|
235
|
+
"create_fhir_app",
|
|
236
|
+
"cache_clear",
|
|
237
|
+
"cache_info",
|
|
238
|
+
"cache_versions",
|
|
239
|
+
# Types
|
|
240
|
+
"CodeRef",
|
|
241
|
+
"CodeInfo",
|
|
242
|
+
"CodeMapping",
|
|
243
|
+
"CodeRelation",
|
|
244
|
+
"CodeResolution",
|
|
245
|
+
"ConceptMapRow",
|
|
246
|
+
"FriendlyNameResult",
|
|
247
|
+
"NameSearchResult",
|
|
248
|
+
"OptimizeResult",
|
|
249
|
+
"OptimizeRule",
|
|
250
|
+
"Provenance",
|
|
251
|
+
"ProvenanceStep",
|
|
252
|
+
"SourceStats",
|
|
253
|
+
# Advanced — engines
|
|
254
|
+
"LocalDuckDBEngine",
|
|
255
|
+
"LocalLiteEngine",
|
|
256
|
+
"RemoteApiEngine",
|
|
257
|
+
"LocalDuckDBConfig",
|
|
258
|
+
"LocalLiteConfig",
|
|
259
|
+
"LOCAL_DUCKDB_MEMORY_PROFILES",
|
|
260
|
+
"LOCAL_LITE_MEMORY_PROFILES",
|
|
261
|
+
"local_duckdb_config",
|
|
262
|
+
"local_lite_config",
|
|
263
|
+
# Advanced — service functions
|
|
264
|
+
"get_ancestors",
|
|
265
|
+
"get_children",
|
|
266
|
+
"get_code_relations",
|
|
267
|
+
"get_descendants",
|
|
268
|
+
"get_descendants_bfs",
|
|
269
|
+
"get_parents",
|
|
270
|
+
"is_descendant",
|
|
271
|
+
"get_code_info",
|
|
272
|
+
"get_code_infos",
|
|
273
|
+
"get_code_mappings",
|
|
274
|
+
"get_code_ttys",
|
|
275
|
+
"get_source_stats",
|
|
276
|
+
"get_patient_friendly_names",
|
|
277
|
+
"optimize_codes",
|
|
278
|
+
"resolve_codes",
|
|
279
|
+
"sample_source_codes",
|
|
280
|
+
"search_names",
|
|
281
|
+
"get_concept_map",
|
|
282
|
+
"get_mapping_concept_map",
|
|
283
|
+
"iter_concept_map",
|
|
284
|
+
"iter_mapping_concept_map",
|
|
285
|
+
"iter_batches",
|
|
286
|
+
"iter_hierarchy_bulk",
|
|
287
|
+
"iter_lookup_bulk",
|
|
288
|
+
"iter_mapping_bulk",
|
|
289
|
+
"iter_patient_friendly_bulk",
|
|
290
|
+
# Advanced — domain helpers
|
|
291
|
+
"cross_reference",
|
|
292
|
+
"diagnosis_codes",
|
|
293
|
+
"discover",
|
|
294
|
+
"drugs_by_class",
|
|
295
|
+
"drugs_for_indication",
|
|
296
|
+
"fda_label_by_rxcui",
|
|
297
|
+
"guideline_fulltext",
|
|
298
|
+
"guideline_recommendations",
|
|
299
|
+
"guideline_search",
|
|
300
|
+
"guidelines_for_code",
|
|
301
|
+
"hcpcs_drugs",
|
|
302
|
+
"indication_search",
|
|
303
|
+
"lab_codes",
|
|
304
|
+
"lab_value_codes",
|
|
305
|
+
"procedure_codes",
|
|
306
|
+
"search_drug",
|
|
307
|
+
"vaccine_codes",
|
|
308
|
+
# Advanced — dataframe helpers
|
|
309
|
+
"code_ttys_dataframe",
|
|
310
|
+
"conceptmap_dataframe",
|
|
311
|
+
"hierarchy_dataframe",
|
|
312
|
+
"lookup_dataframe",
|
|
313
|
+
"map_dataframe",
|
|
314
|
+
"mapping_conceptmap_dataframe",
|
|
315
|
+
"patient_friendly_dataframe",
|
|
316
|
+
"sample_codes_dataframe",
|
|
317
|
+
"search_names_dataframe",
|
|
318
|
+
"source_stats_dataframe",
|
|
319
|
+
# Advanced — data setup
|
|
320
|
+
"DEFAULT_UMLS_RELEASE_TYPE",
|
|
321
|
+
"DEFAULT_UMLS_VERIFY_SOURCES",
|
|
322
|
+
"annotate_umls_duckdb",
|
|
323
|
+
"build_umls_duckdb",
|
|
324
|
+
"download_umls_release",
|
|
325
|
+
"prepare_umls_duckdb",
|
|
326
|
+
"verify_umls_duckdb",
|
|
327
|
+
# Advanced — schemas
|
|
328
|
+
"OUTPUT_SCHEMA_VERSION",
|
|
329
|
+
"OutputField",
|
|
330
|
+
"OutputSchema",
|
|
331
|
+
"get_output_schema",
|
|
332
|
+
"list_output_schemas",
|
|
333
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Application entry points."""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Shared async helpers for the medterm4ds app layer.
|
|
2
|
+
|
|
3
|
+
Both `mcp.py` and `fhir_api.py` need to offload synchronous DuckDB-touching
|
|
4
|
+
work to a single-worker ThreadPoolExecutor (DuckDB Python connections are
|
|
5
|
+
not thread-safe under concurrent use). This module centralizes that helper
|
|
6
|
+
so the two apps don't drift apart.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
13
|
+
from functools import partial
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def run_db(executor: ThreadPoolExecutor, func, *args, **kwargs):
|
|
17
|
+
"""Offload a sync call to the given single-worker executor.
|
|
18
|
+
|
|
19
|
+
`executor` is per-app (lives on McpRuntime.db_executor or
|
|
20
|
+
app.state.db_executor) so multiple servers in one process get
|
|
21
|
+
independent workers and clean teardown.
|
|
22
|
+
"""
|
|
23
|
+
loop = asyncio.get_running_loop()
|
|
24
|
+
return await loop.run_in_executor(executor, partial(func, *args, **kwargs))
|