From a08811def919cd704ddff6ebdbc548c37b5e0294 Mon Sep 17 00:00:00 2001 From: Nico Loesch Date: Mon, 6 Jul 2026 23:35:01 +0000 Subject: [PATCH 1/8] Changes that come with the KnowledgeResource changes in oa-configurator --- docs/getting-started/configuration.md | 5 +++-- omop_alchemy/config.py | 13 ++++++++----- omop_alchemy/maintenance/cli_schema_info.py | 2 +- tests/test_indexes.py | 6 +++--- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index 51cc229..b69de50 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -29,8 +29,9 @@ password = "changeme" database_name = "omop_cdm" [resources.cdm_db] -database = "cdm" -cdm_schema = "omop" +resource_kind = "cdm" +database = "cdm" +cdm_schema = "omop" ``` You can also write or edit this file manually. diff --git a/omop_alchemy/config.py b/omop_alchemy/config.py index 1f8f0c9..0ee04f7 100644 --- a/omop_alchemy/config.py +++ b/omop_alchemy/config.py @@ -7,9 +7,9 @@ from oa_configurator import ( DatabaseConfig, PackageConfigBase, + ResolvedCDMResource, ResourceSpec, Resolver, - ResolvedResource, load_stack_config, ) @@ -58,7 +58,6 @@ class OmopAlchemyConfig(PackageConfigBase): "Tests drop and recreate the entire public schema on every run." ), connection_name_hint="pg_test", - cdm_schema_default="public", connection_defaults=DatabaseConfig( dialect="postgresql+psycopg", host="localhost", @@ -81,7 +80,7 @@ class OmopAlchemyConfig(PackageConfigBase): ) -def get_cdm_context() -> tuple[OmopAlchemyConfig, ResolvedResource]: +def get_cdm_context() -> tuple[OmopAlchemyConfig, ResolvedCDMResource]: """Return (pkg_config, resolved_cdm_resource), loading config once. The resource is taken from tools.omop_alchemy.default_resource when set; @@ -92,15 +91,19 @@ def get_cdm_context() -> tuple[OmopAlchemyConfig, ResolvedResource]: tool = stack.tools.get(OmopAlchemyConfig.tool_name) resource_name = (tool.default_resource if tool else None) or OmopAlchemyConfig.CDM_DB.semantic_name resolved = Resolver(stack).resolve_resource(resource_name) + if not isinstance(resolved, ResolvedCDMResource): + raise TypeError( + f"Resource {resource_name!r} resolved to {type(resolved).__name__}, expected ResolvedCDMResource." + ) return pkg_config, resolved -def create_cdm_engine(resolved: ResolvedResource) -> sa.Engine: +def create_cdm_engine(resolved: ResolvedCDMResource) -> sa.Engine: """Create the CDM SQLAlchemy engine with helpful PostgreSQL driver error messages.""" try: return resolved.create_engine() except ModuleNotFoundError as exc: - msg = _missing_driver_message(resolved.database.url, exc) + msg = _missing_driver_message(resolved.database.build_url(), exc) if msg is not None: raise RuntimeError(msg) from exc raise diff --git a/omop_alchemy/maintenance/cli_schema_info.py b/omop_alchemy/maintenance/cli_schema_info.py index f27daa1..d94ba6c 100644 --- a/omop_alchemy/maintenance/cli_schema_info.py +++ b/omop_alchemy/maintenance/cli_schema_info.py @@ -344,7 +344,7 @@ def collect_maintenance_info( resolver = Resolver(stack) resolved = resolver.resolve_resource(resource_name) db_schema = resolved.cdm_schema - raw_url = sa.engine.make_url(resolved.database.url) + raw_url = sa.engine.make_url(resolved.database.build_url()) engine_url = raw_url.render_as_string(hide_password=True) backend = raw_url.get_backend_name() from omop_alchemy.config import create_cdm_engine diff --git a/tests/test_indexes.py b/tests/test_indexes.py index 7a2be4d..64e09b5 100644 --- a/tests/test_indexes.py +++ b/tests/test_indexes.py @@ -1,7 +1,7 @@ import pytest import sqlalchemy as sa from typer.testing import CliRunner -from oa_configurator import StackConfig, DatabaseConfig, ResourceConfig +from oa_configurator import StackConfig, DatabaseConfig, CDMResourceConfig from omop_alchemy.backends.sqlite import SQLiteBackend from omop_alchemy.cdm.base.indexing import OMOP_CLUSTER_INDEX_INFO_KEY, omop_index_name @@ -328,7 +328,7 @@ def test_disable_indexes_cli_invokes_management(monkeypatch): cfg = StackConfig.for_session( databases={"db": DatabaseConfig(dialect="sqlite", database_name=":memory:")}, - resources={"cdm_db": ResourceConfig(database="db", cdm_schema="main")}, + resources={"cdm_db": CDMResourceConfig(database="db", cdm_schema="main")}, ) monkeypatch.setattr( "omop_alchemy.config.load_stack_config", @@ -392,7 +392,7 @@ def test_enable_indexes_cli_no_cluster_flag_passes_through(monkeypatch): cfg = StackConfig.for_session( databases={"db": DatabaseConfig(dialect="sqlite", database_name=":memory:")}, - resources={"cdm_db": ResourceConfig(database="db", cdm_schema="main")}, + resources={"cdm_db": CDMResourceConfig(database="db", cdm_schema="main")}, ) monkeypatch.setattr( "omop_alchemy.config.load_stack_config", From bc5dec4e41aa3b7118a751f2ed8e2e1b13d5dde3 Mon Sep 17 00:00:00 2001 From: Nico Loesch Date: Tue, 7 Jul 2026 03:52:48 +0000 Subject: [PATCH 2/8] Implement cava-devops changes for new CI/CD --- .github/CONTRIBUTING.md | 27 ++++++++ .github/ISSUE_TEMPLATE/bug_report.yml | 66 +++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 32 ++++++++++ .github/ISSUE_TEMPLATE/not_working.yml | 65 +++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 9 +++ .github/release-drafter.yml | 33 ++++++++++ .github/workflows/ci.yml | 14 +++++ .github/workflows/docs.yml | 4 +- .github/workflows/merge.yml | 13 ++++ .github/workflows/publish.yml | 19 ++++++ .github/workflows/python-publish.yml | 31 --------- .github/workflows/tests.yml | 73 ---------------------- CHANGELOG.md | 3 + pyproject.toml | 12 +++- 15 files changed, 297 insertions(+), 109 deletions(-) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/not_working.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/merge.yml create mode 100644 .github/workflows/publish.yml delete mode 100644 .github/workflows/python-publish.yml delete mode 100644 .github/workflows/tests.yml diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..e4c513d --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing + +## Development setup + +```bash +uv sync --all-extras --dev +uv run pytest -q +uv run ruff check . +``` + +## Opening a pull request + +1. Apply **exactly one** label before merging: + + | Label | When to use | + |---|---| + | `breaking` | Public API change, backward-incompatible | + | `feature` | New functionality, backward-compatible | + | `fix` | Bug fix | + | `dependencies` | Dependency version update | + | `chore` | CI changes, refactoring, test additions, docs — anything that does not affect the public-facing package. Bypasses the label gate; excluded from the changelog and does not bump the version. | + +2. When merging (squash), write a clear extended description in the merge dialog. That text — not the PR's opening description — becomes the changelog entry for this change. Leave it blank for `chore` PRs. + +## Versioning and releases + +Versions are derived from git tags; there is no version string in any source file. Releases are triggered by a maintainer publishing the standing draft release on the repository's Releases page. There is no automated commit-back to `main`. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..46ef5eb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,66 @@ +name: "🐞 Bug Report" +description: Report a reproducible bug. +type: "Bug" +body: + - type: checkboxes + attributes: + label: Pre-flight + options: + - label: I searched existing issues and this is not a duplicate. + required: true + + - type: textarea + id: summary + attributes: + label: Summary + description: One or two sentences describing the bug. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Reproduction + description: Steps and/or a minimal code example that reproduces the issue. Wrap code in triple backticks. + placeholder: | + 1. Import X and call Y with Z + 2. Observe error + + ```python + # minimal example + ``` + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual behaviour + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behaviour + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Error output + description: Full traceback if applicable. Formatted automatically. + render: python-traceback + + - type: textarea + id: system + attributes: + label: System info + description: | + Run this in your environment and paste the output: + + ```shell + python <(curl -s https://raw.githubusercontent.com/AustralianCancerDataNetwork/cava-devops/main/scripts/cava_system_info.py) + ``` + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..a6cc5b1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Discussions + url: https://github.com/orgs/AustralianCancerDataNetwork/discussions + about: Questions and general discussion about the CAVA stack. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..7b1d3ad --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,32 @@ +name: "🚀 Feature Request" +description: Propose a new feature or enhancement. +type: "Feature" +body: + - type: checkboxes + attributes: + label: Pre-flight + options: + - label: I searched existing issues and this has not been requested before. + required: true + + - type: textarea + id: problem + attributes: + label: Problem or motivation + description: What are you trying to do that you currently cannot? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: How would you like this to work? + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other approaches you considered and why you ruled them out. diff --git a/.github/ISSUE_TEMPLATE/not_working.yml b/.github/ISSUE_TEMPLATE/not_working.yml new file mode 100644 index 0000000..176a64e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/not_working.yml @@ -0,0 +1,65 @@ +name: "❗ Something is not working" +description: Something behaves unexpectedly but you are not sure if it is a bug. +body: + - type: checkboxes + attributes: + label: Pre-flight + options: + - label: I searched existing issues for this problem. + required: true + + - type: textarea + id: summary + attributes: + label: Problem summary + description: 1-2 sentences describing what is not working. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Reproduction + description: Steps and/or a minimal code example. Wrap code in triple backticks. + placeholder: | + 1. Import X and call Y with Z + 2. Observe unexpected behaviour + + ```python + # minimal example + ``` + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual outcome + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected outcome + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Error messages + description: Full traceback if applicable. Formatted automatically. + render: python-traceback + + - type: textarea + id: system + attributes: + label: System info + description: | + Run this in your environment and paste the output: + + ```shell + python <(curl -s https://raw.githubusercontent.com/AustralianCancerDataNetwork/cava-devops/main/scripts/cava_system_info.py) + ``` + render: shell diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..159dda2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +## Summary + + + +## Checklist + +- [ ] Applied exactly one label (`breaking`, `feature`, `fix`, `dependencies`, or `chore`) +- [ ] Tests pass locally (`uv run pytest -q`) +- [ ] Lint passes (`uv run ruff check .`) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..e2d05a1 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,33 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' +commitish: main + +categories: + - title: Breaking Changes + labels: ['breaking'] + - title: Features + labels: ['feature'] + - title: Fixes + labels: ['fix'] + - title: Dependencies + labels: ['dependencies'] + +template: | + $CHANGES + +change-template: | + - **$TITLE** (#$NUMBER) by @$AUTHOR + $BODY + +version-resolver: + major: + labels: ['breaking'] + minor: + labels: ['feature'] + patch: + labels: ['fix', 'dependencies'] + default: patch + +exclude-labels: ['chore'] + +autolabeler: [] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7744128 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,14 @@ +name: CI +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, labeled, unlabeled] +jobs: + label-gate: + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/label-gate.yml@main + build-test-sqlite: + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/build-test.yml@main + build-test-postgres: + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/build-test-postgres.yml@main + with: + postgres-db: test_db diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0dfbfb1..0854e63 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,8 +2,8 @@ name: Deploy Docs on: push: - branches: - - main + tags: ['v*'] + workflow_dispatch: permissions: contents: write diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml new file mode 100644 index 0000000..4844c62 --- /dev/null +++ b/.github/workflows/merge.yml @@ -0,0 +1,13 @@ +name: Release Update +on: + pull_request: + types: [closed] +jobs: + sync-description: + if: github.event.pull_request.merged == true + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/sync-pr-description.yml@main + draft: + needs: [sync-description] + if: needs.sync-description.result == 'success' + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/release-drafter.yml@main + secrets: inherit diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..e6426e4 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,19 @@ +name: Publish +on: + push: + tags: ['v*'] +jobs: + build: + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/publish.yml@main + publish: + needs: build + runs-on: ubuntu-latest + permissions: + id-token: write + environment: + name: pypi + url: https://pypi.org/p/omop-alchemy + steps: + - uses: actions/download-artifact@v4 + with: { name: dist, path: dist/ } + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index dc4c693..0000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Publish to PyPI - -on: - release: - types: [published] - - -jobs: - publish: - runs-on: ubuntu-latest - permissions: - id-token: write # REQUIRED for trusted publishing - contents: read - environment: - name: pypi - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install build tools - run: python -m pip install --upgrade build - - - name: Build package - run: python -m build - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index e06b472..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Tests - -on: - push: - branches: [main] - pull_request: - -jobs: - sqlite-tests: - name: SQLite tests (Python ${{ matrix.python-version }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.12", "3.13"] - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: pip install -e ".[dev]" - - - name: Run tests - run: pytest -q - - postgres-tests: - name: PostgreSQL tests (Python ${{ matrix.python-version }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.12", "3.13"] - - services: - postgres: - image: postgres:16 - env: - POSTGRES_USER: test - POSTGRES_PASSWORD: test - POSTGRES_DB: test_db - ports: - - 55432:5432 - options: >- - --health-cmd "pg_isready -U test -d test_db" - --health-interval 2s - --health-timeout 5s - --health-retries 10 - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies (including postgres extra) - run: pip install -e ".[dev,postgres]" - - - name: Provision test_cdm_db - run: | - omop-config configure omop_alchemy \ - --test-dialect postgresql+psycopg --test-database test_cdm \ - --test-host localhost --test-port 55432 --test-cdm-schema public \ - --test-user test --test-password test --test-database-name test_db - - - name: Run tests - run: pytest -v diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fd51f8..e40171c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +> [!NOTE] +> This file is no longer maintained. Release history from this point forward is in [GitHub Releases](https://github.com/AustralianCancerDataNetwork/OMOP_Alchemy/releases). + ## 0.2.0 - Initial public release - SQLAlchemy 2.0 typed OMOP CDM models diff --git a/pyproject.toml b/pyproject.toml index 4c05b25..746da85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "omop-alchemy" -version = "0.8.0" +dynamic = ["version"] description = "SQLAlchemy-based models, validation, and utilities for the OHDSI OMOP Common Data Model" readme = "README.md" requires-python = ">=3.12" @@ -50,7 +50,6 @@ dev = [ "requests>=2.33.0", "pytest>=9.0.3", "pytest-cov>=4.0", - "mypy>=1.8", "ruff>=0.4", "mkdocs-material>=9.7.1", "mkdocstrings-python>=2.0.1", @@ -76,10 +75,17 @@ omop_alchemy = "omop_alchemy.config:OmopAlchemyConfig" [build-system] -requires = ["hatchling"] +requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" +[tool.hatch.version] +source = "vcs" +raw-options = { tag_regex = '^v?(?P[0-9]+\.[0-9]+\.[0-9]+)$' } + [tool.hatch.build.targets.wheel] packages = ["omop_alchemy"] +[tool.uv] +cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }] + [tool.pytest.ini_options] From bdca3187443a643692dc7de5e2e2c3f8e03ab1a4 Mon Sep 17 00:00:00 2001 From: Nico Loesch Date: Sun, 12 Jul 2026 23:13:52 +0000 Subject: [PATCH 3/8] Run merge.yml only on main --- .github/workflows/merge.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 4844c62..cb9b208 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -2,6 +2,7 @@ name: Release Update on: pull_request: types: [closed] + branches: [main] jobs: sync-description: if: github.event.pull_request.merged == true From a39ca3c0889cc3eb279f42710941e400d937b275 Mon Sep 17 00:00:00 2001 From: Nico Loesch Date: Sun, 12 Jul 2026 23:27:08 +0000 Subject: [PATCH 4/8] Revert falsely commited KnowledgeResource commit --- docs/getting-started/configuration.md | 5 ++--- omop_alchemy/config.py | 13 +++++-------- omop_alchemy/maintenance/cli_schema_info.py | 2 +- tests/test_indexes.py | 6 +++--- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index b69de50..51cc229 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -29,9 +29,8 @@ password = "changeme" database_name = "omop_cdm" [resources.cdm_db] -resource_kind = "cdm" -database = "cdm" -cdm_schema = "omop" +database = "cdm" +cdm_schema = "omop" ``` You can also write or edit this file manually. diff --git a/omop_alchemy/config.py b/omop_alchemy/config.py index 0ee04f7..1f8f0c9 100644 --- a/omop_alchemy/config.py +++ b/omop_alchemy/config.py @@ -7,9 +7,9 @@ from oa_configurator import ( DatabaseConfig, PackageConfigBase, - ResolvedCDMResource, ResourceSpec, Resolver, + ResolvedResource, load_stack_config, ) @@ -58,6 +58,7 @@ class OmopAlchemyConfig(PackageConfigBase): "Tests drop and recreate the entire public schema on every run." ), connection_name_hint="pg_test", + cdm_schema_default="public", connection_defaults=DatabaseConfig( dialect="postgresql+psycopg", host="localhost", @@ -80,7 +81,7 @@ class OmopAlchemyConfig(PackageConfigBase): ) -def get_cdm_context() -> tuple[OmopAlchemyConfig, ResolvedCDMResource]: +def get_cdm_context() -> tuple[OmopAlchemyConfig, ResolvedResource]: """Return (pkg_config, resolved_cdm_resource), loading config once. The resource is taken from tools.omop_alchemy.default_resource when set; @@ -91,19 +92,15 @@ def get_cdm_context() -> tuple[OmopAlchemyConfig, ResolvedCDMResource]: tool = stack.tools.get(OmopAlchemyConfig.tool_name) resource_name = (tool.default_resource if tool else None) or OmopAlchemyConfig.CDM_DB.semantic_name resolved = Resolver(stack).resolve_resource(resource_name) - if not isinstance(resolved, ResolvedCDMResource): - raise TypeError( - f"Resource {resource_name!r} resolved to {type(resolved).__name__}, expected ResolvedCDMResource." - ) return pkg_config, resolved -def create_cdm_engine(resolved: ResolvedCDMResource) -> sa.Engine: +def create_cdm_engine(resolved: ResolvedResource) -> sa.Engine: """Create the CDM SQLAlchemy engine with helpful PostgreSQL driver error messages.""" try: return resolved.create_engine() except ModuleNotFoundError as exc: - msg = _missing_driver_message(resolved.database.build_url(), exc) + msg = _missing_driver_message(resolved.database.url, exc) if msg is not None: raise RuntimeError(msg) from exc raise diff --git a/omop_alchemy/maintenance/cli_schema_info.py b/omop_alchemy/maintenance/cli_schema_info.py index d94ba6c..f27daa1 100644 --- a/omop_alchemy/maintenance/cli_schema_info.py +++ b/omop_alchemy/maintenance/cli_schema_info.py @@ -344,7 +344,7 @@ def collect_maintenance_info( resolver = Resolver(stack) resolved = resolver.resolve_resource(resource_name) db_schema = resolved.cdm_schema - raw_url = sa.engine.make_url(resolved.database.build_url()) + raw_url = sa.engine.make_url(resolved.database.url) engine_url = raw_url.render_as_string(hide_password=True) backend = raw_url.get_backend_name() from omop_alchemy.config import create_cdm_engine diff --git a/tests/test_indexes.py b/tests/test_indexes.py index 64e09b5..7a2be4d 100644 --- a/tests/test_indexes.py +++ b/tests/test_indexes.py @@ -1,7 +1,7 @@ import pytest import sqlalchemy as sa from typer.testing import CliRunner -from oa_configurator import StackConfig, DatabaseConfig, CDMResourceConfig +from oa_configurator import StackConfig, DatabaseConfig, ResourceConfig from omop_alchemy.backends.sqlite import SQLiteBackend from omop_alchemy.cdm.base.indexing import OMOP_CLUSTER_INDEX_INFO_KEY, omop_index_name @@ -328,7 +328,7 @@ def test_disable_indexes_cli_invokes_management(monkeypatch): cfg = StackConfig.for_session( databases={"db": DatabaseConfig(dialect="sqlite", database_name=":memory:")}, - resources={"cdm_db": CDMResourceConfig(database="db", cdm_schema="main")}, + resources={"cdm_db": ResourceConfig(database="db", cdm_schema="main")}, ) monkeypatch.setattr( "omop_alchemy.config.load_stack_config", @@ -392,7 +392,7 @@ def test_enable_indexes_cli_no_cluster_flag_passes_through(monkeypatch): cfg = StackConfig.for_session( databases={"db": DatabaseConfig(dialect="sqlite", database_name=":memory:")}, - resources={"cdm_db": CDMResourceConfig(database="db", cdm_schema="main")}, + resources={"cdm_db": ResourceConfig(database="db", cdm_schema="main")}, ) monkeypatch.setattr( "omop_alchemy.config.load_stack_config", From f388c2d41cfef1b8fcb063c1f8916f9538adf267 Mon Sep 17 00:00:00 2001 From: Nico Loesch Date: Wed, 15 Jul 2026 07:11:51 +0000 Subject: [PATCH 5/8] Remove stale docs extra, fix release-drafter permissions --- .github/release-drafter.yml | 4 +--- .github/workflows/ci.yml | 10 ++++++++++ .github/workflows/docs.yml | 20 +------------------- .github/workflows/merge.yml | 8 +++----- .github/workflows/publish.yml | 2 ++ pyproject.toml | 4 ---- 6 files changed, 17 insertions(+), 31 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index e2d05a1..259b28c 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -15,9 +15,7 @@ categories: template: | $CHANGES -change-template: | - - **$TITLE** (#$NUMBER) by @$AUTHOR - $BODY +change-template: '- **$TITLE** (#$NUMBER) @$AUTHOR' version-resolver: major: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7744128..dba4e6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,3 +12,13 @@ jobs: uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/build-test-postgres.yml@main with: postgres-db: test_db + setup-commands: | + uv run omop-config configure omop_alchemy \ + --test-dialect postgresql+psycopg \ + --test-database pg_test \ + --test-host localhost \ + --test-port 5432 \ + --test-cdm-schema public \ + --test-user test \ + --test-password test \ + --test-database-name test_db diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0854e63..09bfde3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,28 +1,10 @@ name: Deploy Docs - on: push: tags: ['v*'] workflow_dispatch: - permissions: contents: write - jobs: deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install MkDocs - run: | - pip install mkdocs mkdocs-material mkdocstrings-python mkdocs-mermaid2-plugin - pip install -e . - - - name: Deploy to GitHub Pages - run: | - mkdocs gh-deploy --force \ No newline at end of file + uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/deploy-docs.yml@main diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index cb9b208..0c7cb55 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -3,12 +3,10 @@ on: pull_request: types: [closed] branches: [main] +permissions: + contents: write jobs: - sync-description: - if: github.event.pull_request.merged == true - uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/sync-pr-description.yml@main draft: - needs: [sync-description] - if: needs.sync-description.result == 'success' + if: github.event.pull_request.merged == true uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/release-drafter.yml@main secrets: inherit diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e6426e4..d68d946 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,6 +6,8 @@ jobs: build: uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/publish.yml@main publish: + # Inline: PyPI OIDC checks job_workflow_ref, which must point to this file. + # Moving pypa/gh-action-pypi-publish into cava-devops would break the trusted publisher. needs: build runs-on: ubuntu-latest permissions: diff --git a/pyproject.toml b/pyproject.toml index 746da85..093a951 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,10 +57,6 @@ dev = [ "mkdocs-mermaid2-plugin" ] -docs = [ - "sphinx", - "myst-parser", -] [project.urls] Homepage = "https://australiancancerdatanetwork.github.io/OMOP_Alchemy/" From 5ddcf4e5338fdf7f22733f6e047e08fe9934c6cf Mon Sep 17 00:00:00 2001 From: Nico Loesch Date: Thu, 16 Jul 2026 00:24:23 +0000 Subject: [PATCH 6/8] Support ty as static type checker --- .github/workflows/ci.yml | 3 +++ omop_alchemy/cdm/base/domain_validation.py | 4 ++-- omop_alchemy/cdm/base/indexing.py | 2 +- omop_alchemy/cdm/base/modifier_interface.py | 4 ++-- omop_alchemy/cdm/handlers/timeline/event_timeline.py | 4 ++-- omop_alchemy/cdm/model/clinical/person.py | 2 +- omop_alchemy/cdm/model/structural/episode_event.py | 4 ++-- omop_alchemy/maintenance/_cli_utils.py | 10 +++++----- omop_alchemy/maintenance/cli_vocab.py | 4 ++-- omop_alchemy/maintenance/help.py | 2 +- omop_alchemy/maintenance/tables.py | 4 ++-- pyproject.toml | 1 + 12 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dba4e6c..5261635 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,9 +8,12 @@ jobs: uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/label-gate.yml@main build-test-sqlite: uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/build-test.yml@main + with: + ty-src: omop_alchemy build-test-postgres: uses: AustralianCancerDataNetwork/cava-devops/.github/workflows/build-test-postgres.yml@main with: + ty-src: omop_alchemy postgres-db: test_db setup-commands: | uv run omop-config configure omop_alchemy \ diff --git a/omop_alchemy/cdm/base/domain_validation.py b/omop_alchemy/cdm/base/domain_validation.py index d88cf12..d49606b 100644 --- a/omop_alchemy/cdm/base/domain_validation.py +++ b/omop_alchemy/cdm/base/domain_validation.py @@ -121,7 +121,7 @@ def collect_domain_rules(cls) -> list[DomainRule]: for field, spec in cls.__expected_domains__.items(): rules.append( DomainRule( - table=cls.__tablename__, # type: ignore[attr-defined] + table=cls.__tablename__, # ty: ignore[invalid-argument-type] field=field, allowed_domains=spec.domains, ) @@ -153,7 +153,7 @@ def _check_domain(self, field: str) -> bool: ConceptCls = get_model_by_tablename("Concept") if ConceptCls is None: return False - concept = session.get(ConceptCls, concept_id) # type: ignore + concept = session.get(ConceptCls, concept_id) return concept.domain_id in expected.domains if concept else False # type: ignore[union-attr] diff --git a/omop_alchemy/cdm/base/indexing.py b/omop_alchemy/cdm/base/indexing.py index 7cc5220..0414926 100644 --- a/omop_alchemy/cdm/base/indexing.py +++ b/omop_alchemy/cdm/base/indexing.py @@ -225,7 +225,7 @@ def consume(part: TableArg) -> None: if isinstance(part, tuple): for item in part: - consume(item) + consume(item) # ty: ignore[invalid-argument-type] return items.append(part) diff --git a/omop_alchemy/cdm/base/modifier_interface.py b/omop_alchemy/cdm/base/modifier_interface.py index f18f6f4..0fcf2ef 100644 --- a/omop_alchemy/cdm/base/modifier_interface.py +++ b/omop_alchemy/cdm/base/modifier_interface.py @@ -22,10 +22,10 @@ def modifier_field_concept_id(cls) -> int: @classmethod def modifier_target_table(cls) -> str: - return cls.__tablename__ # type: ignore[attr-defined] + return cls.__tablename__ # ty: ignore[unresolved-attribute] @hybrid_property - def event_id(self) -> int: # type: ignore + def event_id(self) -> int: return getattr(self, self.__event_id_col__) @event_id.expression diff --git a/omop_alchemy/cdm/handlers/timeline/event_timeline.py b/omop_alchemy/cdm/handlers/timeline/event_timeline.py index ed6f3b1..e597389 100644 --- a/omop_alchemy/cdm/handlers/timeline/event_timeline.py +++ b/omop_alchemy/cdm/handlers/timeline/event_timeline.py @@ -269,5 +269,5 @@ def timeline(self) -> list[ClinicalEvent]: key=lambda e: e.event_time.start, ) - def to_json(self) -> list[str]: # type: ignore[override] - return [e.to_json() for e in self.timeline] # type: ignore[return-value] \ No newline at end of file + def to_json(self) -> list[str]: # ty: ignore[invalid-method-override] + return [e.to_json() for e in self.timeline] # ty: ignore[invalid-argument-type] \ No newline at end of file diff --git a/omop_alchemy/cdm/model/clinical/person.py b/omop_alchemy/cdm/model/clinical/person.py index df87335..a22b05b 100644 --- a/omop_alchemy/cdm/model/clinical/person.py +++ b/omop_alchemy/cdm/model/clinical/person.py @@ -113,7 +113,7 @@ class PersonView(Person, PersonContext, DomainValidationMixin): } @hybrid_method - def age_at(self, on_date: date) -> Optional[int]: # type: ignore + def age_at(self, on_date: date) -> Optional[int]: if not self.year_of_birth: return None return on_date.year - self.year_of_birth diff --git a/omop_alchemy/cdm/model/structural/episode_event.py b/omop_alchemy/cdm/model/structural/episode_event.py index 2627e42..ae3efb5 100644 --- a/omop_alchemy/cdm/model/structural/episode_event.py +++ b/omop_alchemy/cdm/model/structural/episode_event.py @@ -2,7 +2,7 @@ import sqlalchemy.orm as so from typing import TYPE_CHECKING, Any, Type, cast from functools import cached_property -from orm_loader.helpers import Base, get_model_by_tablename # type: ignore +from orm_loader.helpers import Base, get_model_by_tablename from omop_alchemy.cdm.base import ( cdm_table, CDMTableBase, @@ -71,7 +71,7 @@ def resolved_event(self) -> Any | None: cls = cast(Type[Any] | None, get_model_by_tablename(table_name)) if cls is not None: - return session.get(cls, self.event_id) # type: ignore + return session.get(cls, self.event_id) return None def __repr__(self): diff --git a/omop_alchemy/maintenance/_cli_utils.py b/omop_alchemy/maintenance/_cli_utils.py index 09ca063..2abe24b 100644 --- a/omop_alchemy/maintenance/_cli_utils.py +++ b/omop_alchemy/maintenance/_cli_utils.py @@ -70,8 +70,8 @@ def wrapper(**kwargs: Any) -> Any: ) try: if dry_run: - return func(conn, engine, dry_run=_dry_run, **kwargs) # type: ignore[arg-type] - return func(conn, engine, **kwargs) # type: ignore[arg-type] + return func(conn, engine, dry_run=_dry_run, **kwargs) + return func(conn, engine, **kwargs) finally: engine.dispose() except Exception as exc: @@ -99,10 +99,10 @@ def wrapper(**kwargs: Any) -> Any: annotation=bool, ) ) - wrapper.__signature__ = inspect.signature(func).replace(parameters=new_params) # type: ignore[attr-defined] + wrapper.__signature__ = inspect.signature(func).replace(parameters=new_params) # ty: ignore[unresolved-attribute] - return wrapper # type: ignore[return-value] - return decorator # type: ignore[return-value] + return wrapper # ty: ignore[invalid-return-type] + return decorator # ── Helpers ─────────────────────────────────────────────────────────────────── diff --git a/omop_alchemy/maintenance/cli_vocab.py b/omop_alchemy/maintenance/cli_vocab.py index ca5a711..72327dd 100644 --- a/omop_alchemy/maintenance/cli_vocab.py +++ b/omop_alchemy/maintenance/cli_vocab.py @@ -173,14 +173,14 @@ def _load_vocab_model_csv( load_kwargs["chunksize"] = chunksize try: - return int(model.load_csv(session, csv_path, **load_kwargs)) # type: ignore[arg-type] + return int(model.load_csv(session, csv_path, **load_kwargs)) # ty: ignore[invalid-argument-type] except Exception as exc: if not _is_missing_staging_table_error(exc, model=model): raise session.rollback() model.create_staging_table(session) - return int(model.load_csv(session, csv_path, **load_kwargs)) # type: ignore[arg-type] + return int(model.load_csv(session, csv_path, **load_kwargs)) # ty: ignore[invalid-argument-type] def _find_vocab_csv_path(source_path: Path, table_name: str) -> Path | None: diff --git a/omop_alchemy/maintenance/help.py b/omop_alchemy/maintenance/help.py index 1f81d8c..13c9209 100644 --- a/omop_alchemy/maintenance/help.py +++ b/omop_alchemy/maintenance/help.py @@ -164,4 +164,4 @@ def _print_commands_panel_with_backend_grouping( def install_help_customizations() -> None: - typer_rich_utils._print_commands_panel = _print_commands_panel_with_backend_grouping + typer_rich_utils._print_commands_panel = _print_commands_panel_with_backend_grouping # ty: ignore[invalid-assignment] diff --git a/omop_alchemy/maintenance/tables.py b/omop_alchemy/maintenance/tables.py index c1fe156..5ff90f1 100644 --- a/omop_alchemy/maintenance/tables.py +++ b/omop_alchemy/maintenance/tables.py @@ -105,7 +105,7 @@ def collect_maintenance_tables() -> list[MaintenanceTable]: _mapped_cdm_table_classes(), key=lambda cls: cls.__table__.name, ): - table = mapped_class.__table__ + table = mapped_class.__table__ # ty: ignore[unresolved-attribute] tables.append( MaintenanceTable( table_name=table.name, @@ -270,7 +270,7 @@ def schema_adjusted_metadata( for maintenance_table in tables: adjusted_tables[maintenance_table.table_name] = maintenance_table.table.to_metadata( metadata, - schema=db_schema, # type: ignore[arg-type] + schema=db_schema, # ty: ignore[invalid-argument-type] referred_schema_fn=( lambda _table, to_schema, _constraint, _referred_schema: to_schema ), diff --git a/pyproject.toml b/pyproject.toml index 093a951..25f2ed3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ dev = [ "requests>=2.33.0", "pytest>=9.0.3", "pytest-cov>=4.0", + "ty>=0.0.59", "ruff>=0.4", "mkdocs-material>=9.7.1", "mkdocstrings-python>=2.0.1", From e0e1246f2d0aee8ee87447d30b1d426dde1004db Mon Sep 17 00:00:00 2001 From: Nico Loesch Date: Mon, 20 Jul 2026 04:59:28 +0000 Subject: [PATCH 7/8] Fix ty static typing --- omop_alchemy/cdm/handlers/timeline/event_timeline.py | 2 +- omop_alchemy/maintenance/tables.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/omop_alchemy/cdm/handlers/timeline/event_timeline.py b/omop_alchemy/cdm/handlers/timeline/event_timeline.py index e597389..a558d48 100644 --- a/omop_alchemy/cdm/handlers/timeline/event_timeline.py +++ b/omop_alchemy/cdm/handlers/timeline/event_timeline.py @@ -142,7 +142,7 @@ def event_metadata(self) -> Mapping[str, Any]: return {} - def __repr__(self: ClinicalEventProtocol) -> str: + def __repr__(self: ClinicalEventProtocol) -> str: # ty: ignore[invalid-method-override] et = self.event_time ev = self.event_value() diff --git a/omop_alchemy/maintenance/tables.py b/omop_alchemy/maintenance/tables.py index 5ff90f1..53b34a3 100644 --- a/omop_alchemy/maintenance/tables.py +++ b/omop_alchemy/maintenance/tables.py @@ -103,7 +103,7 @@ def collect_maintenance_tables() -> list[MaintenanceTable]: for mapped_class in sorted( _mapped_cdm_table_classes(), - key=lambda cls: cls.__table__.name, + key=lambda cls: cls.__table__.name, # ty: ignore[unresolved-attribute] ): table = mapped_class.__table__ # ty: ignore[unresolved-attribute] tables.append( From 79f3ddf81c2c8aaaaa98dc92485ce3ffca6d3b29 Mon Sep 17 00:00:00 2001 From: Nico Loesch Date: Mon, 20 Jul 2026 05:42:48 +0000 Subject: [PATCH 8/8] Grant correct CI permission --- .github/workflows/merge.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 0c7cb55..c43dab6 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -5,6 +5,7 @@ on: branches: [main] permissions: contents: write + pull-requests: read jobs: draft: if: github.event.pull_request.merged == true