Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: CI

on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master, develop]
workflow_dispatch: # Allow manual triggering

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ruff mypy
pip install -e ".[dev]"
- name: Run Ruff linter
run: ruff check src/
- name: Run Ruff formatter check
run: ruff format --check src/
- name: Run MyPy
run: mypy src/omophub

test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run unit tests with coverage
run: |
pytest tests/unit --cov=omophub --cov-report=xml --cov-report=term-missing
- name: Upload coverage to Codecov
if: matrix.python-version == '3.12'
uses: codecov/codecov-action@v5
with:
files: ./coverage.xml
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}

integration:
# Only run on push to main/develop, not on PRs (saves ~7-8 min per PR)
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The integration job condition does not restrict push branches, so it also runs on master pushes despite the comment saying main/develop only.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/ci.yml, line 57:

<comment>The integration job condition does not restrict push branches, so it also runs on `master` pushes despite the comment saying main/develop only.</comment>

<file context>
@@ -0,0 +1,72 @@
+      
+  integration:
+    # Only run on push to main/develop, not on PRs (saves ~7-8 min per PR)
+    if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
+    runs-on: ubuntu-latest
+    steps:
</file context>
Suggested change
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'))
Fix with Cubic

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run integration tests
env:
TEST_API_KEY: ${{ secrets.TEST_API_KEY }}
run: |
pytest tests/integration -v
47 changes: 47 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Publish to PyPI

on:
release:
types: [published]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Required for hatch-vcs to get version from tags
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install build tools
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Upload distributions
uses: actions/upload-artifact@v5
with:
name: release-dists
path: dist/

publish:
runs-on: ubuntu-latest
needs: build
environment:
name: pypi
url: https://pypi.org/project/omophub/
permissions:
id-token: write # Required for trusted publishing
steps:
- name: Download distributions
uses: actions/download-artifact@v5
with:
name: release-dists
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
122 changes: 121 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,118 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.6.0] - 2026-04-10

### Added

- **FHIR-to-OMOP Concept Resolver** (`client.fhir`): Translate FHIR coded values into OMOP standard concepts, CDM target tables, and optional Phoebe recommendations in a single API call.
- `resolve()`: Resolve a single FHIR `Coding` (system URI + code) or text-only input via semantic search fallback. Returns the standard concept, target CDM table, domain alignment check, and optional mapping quality signal.
- `resolve_batch()`: Batch-resolve up to 100 FHIR codings per request with inline per-item error reporting. Failed items do not fail the batch.
- `resolve_codeable_concept()`: Resolve a FHIR `CodeableConcept` with multiple codings. Automatically picks the best match per OHDSI vocabulary preference (SNOMED > RxNorm > LOINC > CVX > ICD-10). Falls back to the `text` field via semantic search when no coding resolves.
- New TypedDict types for FHIR resolver: `FhirResolveResult`, `FhirResolution`, `FhirBatchResult`, `FhirBatchSummary`, `FhirCodeableConceptResult`, `ResolvedConcept`, `RecommendedConceptOutput`.
- Both sync (`OMOPHub`) and async (`AsyncOMOPHub`) clients support FHIR resolver methods via `client.fhir.*`.

### Changed

- **Extracted shared response parsing** (`_request.py`): The duplicated JSON decode / error-handling / rate-limit-retry logic across `Request._parse_response`, `Request._parse_response_raw`, `AsyncRequest._parse_response`, and `AsyncRequest._parse_response_raw` (4 copies of ~50 lines each) is now a single `_parse_and_raise()` module-level function. All four methods delegate to it, eliminating the risk of divergence bugs.
- **Fixed `paginate_async` signature** (`_pagination.py`): The type hint now correctly declares `Callable[[int, int], Awaitable[tuple[...]]]` instead of `Callable[[int, int], tuple[...]]`, and the runtime `hasattr(__await__)` duck-typing hack has been replaced with a clean `await`.
- **`AsyncSearch.semantic_iter`** now delegates to `paginate_async` instead of manually reimplementing the pagination loop, matching the sync `semantic_iter` which already uses `paginate_sync`.

### Fixed

- Python prerequisite in CONTRIBUTING.md corrected from `3.9+` to `3.10+` (matching `pyproject.toml`).
- `__all__` in `types/__init__.py` sorted per RUF022.

## [1.5.1] - 2026-04-08

### Fixed

- **Rate-limit handling**: HTTP client now respects the `Retry-After` header on `429 Too Many Requests` responses and applies exponential backoff with jitter on retries. Previous versions retried only on `502/503/504` with a fixed `2^attempt * 0.5s` schedule and did not back off on `429` at all, so a client that hit the server's rate limit at high volume could burn through thousands of failed requests in a tight loop. The client now honors `Retry-After`, uses exponential backoff with jitter, respects the configured `max_retries`, and caps backoff at 30 seconds.
- Updated `examples/search_concepts.py` to reflect current API.

## [1.5.0] - 2026-03-26

### Added

- **Bulk lexical search** (`search.bulk_basic()`): Execute up to 50 keyword searches in a single API call. Supports shared defaults for vocabulary, domain, and other filters. Each search is identified by a unique `search_id` for result matching. Maps to `POST /v1/search/bulk`.
- **Bulk semantic search** (`search.bulk_semantic()`): Execute up to 25 natural-language searches using neural embeddings in a single call. Supports per-search similarity thresholds and shared defaults. Includes query enhancement data (abbreviation expansion, misspelling correction). Maps to `POST /v1/search/semantic-bulk`.
- New TypedDict types for bulk search: `BulkSearchInput`, `BulkSearchDefaults`, `BulkSearchResponse`, `BulkSearchResultItem`, `BulkSemanticSearchInput`, `BulkSemanticSearchDefaults`, `BulkSemanticSearchResponse`, `BulkSemanticSearchResultItem`, `QueryEnhancement`.
- Both sync (`OMOPHub`) and async (`AsyncOMOPHub`) clients support bulk search methods.

### Changed

- Updated `__all__` exports to alphabetical order (ruff RUF022 compliance).
- `BulkSearchInput` and `BulkSemanticSearchInput` now use `Required[str]` for `search_id` and `query` fields for proper type checking.

## [1.4.1] - 2026-02-28

### Fixed

- User-Agent header now reports actual SDK version (e.g., `OMOPHub-SDK-Python/1.4.1`) instead of hardcoded `0.1.0`. Version is resolved at runtime via `importlib.metadata`.

## [1.4.0] - 2026-02-23

### Added

- **Semantic search** (`search.semantic()`, `search.semantic_iter()`): Natural language concept search using neural embeddings. Search for clinical intent like "high blood sugar levels" to find diabetes-related concepts. Supports filtering by vocabulary, domain, standard concept, concept class, and minimum similarity threshold. `semantic_iter()` provides automatic pagination.
- **Similarity search** (`search.similar()`): Find concepts similar to a reference concept ID, concept name, or natural language query. Three algorithm options: `'semantic'` (neural embeddings), `'lexical'` (string matching), and `'hybrid'` (combined). Configurable similarity threshold with optional detailed scores and explanations.

## [1.3.1] - 2026-01-24

### Fixed

- Fixed `search.basic_iter()` pagination bug that caused only the first page of results to be returned. The iterator now correctly fetches all pages when iterating through search results.

### Changed

- Added `get_raw()` method to internal request classes for retrieving full API responses with pagination metadata.
- Expanded `search.basic_iter()` method signature to explicitly list all filter parameters instead of using `**kwargs`.

## [1.3.0] - 2026-01-06

### Changes

**Parameter Renames (for API consistency):**
- `search.autocomplete()`: `max_suggestions` → `page_size`
- `concepts.suggest()`: `vocabulary` → `vocabulary_ids`, `domain` → `domain_ids`, `limit` → `page_size`
- `concepts.related()`: `relatedness_types` → `relationship_types`
- `concepts.relationships()`: `relationship_type` → `relationship_ids`
- `relationships.get()`: `relationship_type` → `relationship_ids`, `target_vocabulary` → `vocabulary_ids`
- `hierarchy.ancestors()`: `vocabulary_id` → `vocabulary_ids`, `include_deprecated` → `include_invalid`
- `hierarchy.descendants()`: `vocabulary_id` → `vocabulary_ids`, `include_deprecated` → `include_invalid`

**Simplified APIs (removed parameters):**
- `vocabularies.get()`: Removed `include_stats`, `include_domains` (use `stats()` method instead)
- `vocabularies.domains()`: Removed pagination parameters, now returns all domains
- `domains.list()`: Simplified to single `include_stats` parameter
- `domains.concepts()`: Removed `concept_class_ids`, added `include_invalid`
- `mappings.get()`: Simplified to `target_vocabulary`, `include_invalid`, `vocab_release`
- `relationships.types()`: Removed all filtering parameters

**Default Changes:**
- `vocabularies.list()`: Default `page_size` changed from 100 to 20
- `concepts.batch()`: Default `standard_only` changed from `False` to `True`

### Added

- `vocabularies.domain_stats(vocabulary_id, domain_id)` - Get statistics for a specific domain within a vocabulary
- `vocabularies.concept_classes()` - Get all concept classes
- `hierarchy.get(concept_id)` - Get complete hierarchy (ancestors and descendants) in one call
- `vocab_release` parameter to `concepts.get()`, `concepts.get_by_code()`, `mappings.get()`, `mappings.map()`
- `include_hierarchy` parameter to `concepts.get()` and `concepts.get_by_code()`
- Pagination support to `concepts.suggest()`
- `domain_ids`, `standard_only`, `include_reverse` parameters to `relationships.get()`

## [1.2.0] - 2025-12-09

### Added

- `include_synonyms` and `include_relationships` parameters to `concepts.get_by_code()` method for retrieving concept synonyms and relationships in a single request.

### Changed

- User-Agent header updated to `OMOPHub-SDK-Python/{version}`.

## [0.1.0] - 2025-12-01

### Added
Expand All @@ -27,5 +139,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Full type hints and PEP 561 compliance
- HTTP/2 support via httpx

[Unreleased]: https://github.com/omopHub/omophub-python/compare/v0.1.0...HEAD
[Unreleased]: https://github.com/omopHub/omophub-python/compare/v1.6.0...HEAD
[1.6.0]: https://github.com/omopHub/omophub-python/compare/v1.5.1...v1.6.0
[1.5.1]: https://github.com/omopHub/omophub-python/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/omopHub/omophub-python/compare/v1.4.1...v1.5.0
[1.4.1]: https://github.com/omopHub/omophub-python/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/omopHub/omophub-python/compare/v1.3.1...v1.4.0
[1.3.1]: https://github.com/omopHub/omophub-python/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/omopHub/omophub-python/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/omopHub/omophub-python/compare/v0.1.0...v1.2.0
[0.1.0]: https://github.com/omopHub/omophub-python/releases/tag/v0.1.0
Loading
Loading