Skip to content

Commit 3312860

Browse files
author
User
committed
Release v1.0.0
1 parent c8d67db commit 3312860

62 files changed

Lines changed: 9935 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 908 additions & 0 deletions
Large diffs are not rendered by default.

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [0.1.0] - 2025-12-01
9+
10+
### Added
11+
12+
- Initial release of the OMOPHub Python SDK
13+
- Synchronous client (`OMOPHub`) and asynchronous client (`AsyncOMOPHub`)
14+
- Full support for all OMOPHub API endpoints:
15+
- Concepts: get, get_by_code, batch, suggest, related, relationships
16+
- Search: basic, advanced, autocomplete
17+
- Hierarchy: ancestors, descendants, paths
18+
- Relationships: get, types
19+
- Mappings: get, map, bulk
20+
- Vocabularies: list, get, stats, domains, concepts
21+
- Domains: list, get, concepts
22+
- TypedDict type definitions for all API responses
23+
- Automatic retry with exponential backoff
24+
- Rate limit handling with Retry-After support
25+
- Comprehensive exception hierarchy
26+
- Auto-pagination iterators for search results
27+
- Full type hints and PEP 561 compliance
28+
- HTTP/2 support via httpx
29+
30+
[Unreleased]: https://github.com/omopHub/omophub-python/compare/v0.1.0...HEAD
31+
[0.1.0]: https://github.com/omopHub/omophub-python/releases/tag/v0.1.0

README.md

Lines changed: 253 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,253 @@
1-
# omophub-python
2-
omophub's python sdk
1+
# OMOPHub Python SDK
2+
3+
[![PyPI version](https://badge.fury.io/py/omophub.svg)](https://badge.fury.io/py/omophub)
4+
[![Python Versions](https://img.shields.io/pypi/pyversions/omophub.svg)](https://pypi.org/project/omophub/)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6+
7+
The official Python SDK for [OMOPHub](https://omophub.com) - a medical vocabulary API providing access to OHDSI ATHENA standardized vocabularies including SNOMED CT, ICD-10, RxNorm, LOINC, and 90+ other medical terminologies.
8+
9+
## Installation
10+
11+
```bash
12+
pip install omophub
13+
```
14+
15+
## Quick Start
16+
17+
```python
18+
import omophub
19+
20+
# Initialize the client
21+
client = omophub.OMOPHub(api_key="oh_xxxxxxxxx")
22+
23+
# Get a concept by ID
24+
concept = client.concepts.get(201826)
25+
print(concept["concept_name"]) # "Type 2 diabetes mellitus"
26+
27+
# Search for concepts
28+
results = client.search.basic("diabetes", vocabulary_ids=["SNOMED", "ICD10CM"])
29+
for concept in results["concepts"]:
30+
print(f"{concept['concept_id']}: {concept['concept_name']}")
31+
32+
# Get concept ancestors
33+
ancestors = client.hierarchy.ancestors(201826, max_levels=3)
34+
35+
# Map concepts between vocabularies
36+
mappings = client.mappings.get(201826, target_vocabularies=["ICD10CM"])
37+
```
38+
39+
## Async Usage
40+
41+
```python
42+
import omophub
43+
import asyncio
44+
45+
async def main():
46+
async with omophub.AsyncOMOPHub(api_key="oh_xxx") as client:
47+
concept = await client.concepts.get(201826)
48+
print(concept["concept_name"])
49+
50+
asyncio.run(main())
51+
```
52+
53+
## Configuration
54+
55+
### API Key
56+
57+
Set your API key in one of three ways:
58+
59+
```python
60+
# 1. Pass directly to client
61+
client = omophub.OMOPHub(api_key="oh_xxxxxxxxx")
62+
63+
# 2. Set environment variable
64+
# export OMOPHUB_API_KEY=oh_xxxxxxxxx
65+
client = omophub.OMOPHub()
66+
67+
# 3. Set module-level variable
68+
import omophub
69+
omophub.api_key = "oh_xxxxxxxxx"
70+
client = omophub.OMOPHub()
71+
```
72+
73+
Get your API key from the [OMOPHub Dashboard](https://dashboard.omophub.com/api-keys).
74+
75+
### Additional Options
76+
77+
```python
78+
client = omophub.OMOPHub(
79+
api_key="oh_xxx",
80+
base_url="https://api.omophub.com/v1", # API base URL
81+
timeout=30.0, # Request timeout in seconds
82+
max_retries=3, # Retry attempts for failed requests
83+
vocab_version="2025.2", # Specific vocabulary version
84+
)
85+
```
86+
87+
## Resources
88+
89+
### Concepts
90+
91+
```python
92+
# Get concept by ID
93+
concept = client.concepts.get(201826)
94+
95+
# Get concept by vocabulary code
96+
concept = client.concepts.get_by_code("SNOMED", "73211009")
97+
98+
# Batch get concepts
99+
result = client.concepts.batch([201826, 4329847, 73211009])
100+
101+
# Get autocomplete suggestions
102+
suggestions = client.concepts.suggest("diab", vocabulary="SNOMED", limit=10)
103+
104+
# Get related concepts
105+
related = client.concepts.related(201826, relatedness_types=["hierarchical", "semantic"])
106+
107+
# Get concept relationships
108+
relationships = client.concepts.relationships(201826)
109+
```
110+
111+
### Search
112+
113+
```python
114+
# Basic search
115+
results = client.search.basic(
116+
"heart attack",
117+
vocabulary_ids=["SNOMED"],
118+
domain_ids=["Condition"],
119+
page=1,
120+
page_size=20,
121+
)
122+
123+
# Advanced search with facets
124+
results = client.search.advanced(
125+
"myocardial infarction",
126+
vocabularies=["SNOMED", "ICD10CM"],
127+
standard_concepts_only=True,
128+
)
129+
130+
# Semantic search
131+
results = client.search.semantic("chest pain with shortness of breath")
132+
133+
# Fuzzy search (typo-tolerant)
134+
results = client.search.fuzzy("diabetis") # finds "diabetes"
135+
136+
# Auto-pagination iterator
137+
for concept in client.search.basic_iter("diabetes", page_size=100):
138+
print(concept["concept_name"])
139+
```
140+
141+
### Hierarchy
142+
143+
```python
144+
# Get ancestors
145+
ancestors = client.hierarchy.ancestors(
146+
201826,
147+
max_levels=5,
148+
relationship_types=["Is a"],
149+
)
150+
151+
# Get descendants
152+
descendants = client.hierarchy.descendants(
153+
201826,
154+
max_levels=3,
155+
standard_only=True,
156+
)
157+
```
158+
159+
### Mappings
160+
161+
```python
162+
# Get mappings for a concept
163+
mappings = client.mappings.get(
164+
201826,
165+
target_vocabularies=["ICD10CM", "Read"],
166+
include_mapping_quality=True,
167+
)
168+
169+
# Map concepts to target vocabulary
170+
result = client.mappings.map(
171+
source_concepts=[201826, 4329847],
172+
target_vocabulary="ICD10CM",
173+
)
174+
```
175+
176+
### Vocabularies
177+
178+
```python
179+
# List all vocabularies
180+
vocabularies = client.vocabularies.list(include_stats=True)
181+
182+
# Get vocabulary details
183+
snomed = client.vocabularies.get("SNOMED", include_domains=True)
184+
185+
# Get vocabulary statistics
186+
stats = client.vocabularies.stats("SNOMED")
187+
```
188+
189+
### Domains
190+
191+
```python
192+
# List all domains
193+
domains = client.domains.list(include_statistics=True)
194+
195+
# Get domain details
196+
condition = client.domains.get("Condition")
197+
198+
# Get concepts in a domain
199+
concepts = client.domains.concepts("Drug", standard_only=True)
200+
```
201+
202+
## Error Handling
203+
204+
```python
205+
import omophub
206+
207+
try:
208+
client = omophub.OMOPHub(api_key="oh_xxx")
209+
concept = client.concepts.get(999999999)
210+
except omophub.NotFoundError as e:
211+
print(f"Concept not found: {e.message}")
212+
except omophub.AuthenticationError as e:
213+
print(f"Authentication failed: {e.message}")
214+
except omophub.RateLimitError as e:
215+
print(f"Rate limited. Retry after {e.retry_after} seconds")
216+
except omophub.ValidationError as e:
217+
print(f"Invalid request: {e.message}")
218+
except omophub.APIError as e:
219+
print(f"API error {e.status_code}: {e.message}")
220+
except omophub.OMOPHubError as e:
221+
print(f"SDK error: {e.message}")
222+
```
223+
224+
## Type Hints
225+
226+
The SDK is fully typed with TypedDict definitions for all API responses:
227+
228+
```python
229+
from omophub import OMOPHub, Concept
230+
231+
client = OMOPHub(api_key="oh_xxx")
232+
concept: Concept = client.concepts.get(201826)
233+
234+
# IDE autocomplete works for all fields
235+
print(concept["concept_id"])
236+
print(concept["concept_name"])
237+
print(concept["vocabulary_id"])
238+
```
239+
240+
## Documentation
241+
242+
- [Full Documentation](https://docs.omophub.com/sdks/python/overview)
243+
- [API Reference](https://docs.omophub.com/api-reference)
244+
- [Examples](https://github.com/omopHub/omophub-python/tree/main/examples)
245+
246+
## License
247+
248+
MIT License - see [LICENSE](LICENSE) for details.
249+
250+
## Support
251+
252+
- [GitHub Issues](https://github.com/omopHub/omophub-python/issues)
253+
- [Documentation](https://docs.omophub.com)

examples/async_usage.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/env python3
2+
"""Examples of async usage of the OMOPHub SDK."""
3+
4+
import asyncio
5+
6+
import omophub
7+
8+
9+
async def basic_async_usage() -> None:
10+
"""Demonstrate basic async client usage."""
11+
print("=== Basic Async Usage ===")
12+
13+
# Use async context manager
14+
async with omophub.AsyncOMOPHub(api_key="oh_your_api_key") as client:
15+
# Get a concept
16+
concept = await client.concepts.get(201826)
17+
print(f"Concept: {concept['concept_name']}")
18+
19+
# Search
20+
results = await client.search.basic("diabetes", page_size=5)
21+
concepts = results.get("concepts", [])
22+
print(f"Found {len(concepts)} concepts")
23+
24+
25+
async def concurrent_requests() -> None:
26+
"""Demonstrate concurrent API requests."""
27+
print("\n=== Concurrent Requests ===")
28+
29+
async with omophub.AsyncOMOPHub(api_key="oh_your_api_key") as client:
30+
# Fetch multiple concepts concurrently
31+
concept_ids = [201826, 4329847, 1112807, 40483312, 37311061]
32+
33+
tasks = [client.concepts.get(cid) for cid in concept_ids]
34+
concepts = await asyncio.gather(*tasks)
35+
36+
print(f"Fetched {len(concepts)} concepts concurrently:")
37+
for c in concepts:
38+
print(f" {c['concept_id']}: {c['concept_name']}")
39+
40+
41+
async def parallel_searches() -> None:
42+
"""Demonstrate parallel search operations."""
43+
print("\n=== Parallel Searches ===")
44+
45+
async with omophub.AsyncOMOPHub(api_key="oh_your_api_key") as client:
46+
# Run multiple searches in parallel
47+
search_terms = ["diabetes", "hypertension", "asthma", "depression"]
48+
49+
async def search_and_count(term: str) -> tuple[str, int]:
50+
results = await client.search.basic(term, page_size=1)
51+
# Get total from pagination metadata if available
52+
meta = results.get("meta", {}).get("pagination", {})
53+
total_items = meta.get("total_items")
54+
if total_items is not None:
55+
total = total_items
56+
else:
57+
concepts = results.get("concepts", [])
58+
total = len(concepts) if isinstance(concepts, list) else 0
59+
return term, total
60+
61+
tasks = [search_and_count(term) for term in search_terms]
62+
results = await asyncio.gather(*tasks)
63+
64+
print("Search results:")
65+
for term, count in results:
66+
print(f" '{term}': {count} concepts")
67+
68+
69+
async def main() -> None:
70+
"""Run all async examples."""
71+
await basic_async_usage()
72+
await concurrent_requests()
73+
await parallel_searches()
74+
75+
76+
if __name__ == "__main__":
77+
asyncio.run(main())

0 commit comments

Comments
 (0)