Skip to content

Extension tables missing proper table-category routing break omop-alchemy maintenance in any shared process #42

Description

@nicoloesch

Summary

omop_graph/extensions/omop_alchemy.py declares its extension tables with @cdm_table on the shared orm_loader.helpers.Base, but never sets __omop_table_category__. omop-alchemy's maintenance scan walks Base.registry.mappers and hard-fails on any mapped class missing that attribute, so importing both packages into one process makes omop-alchemy's vocabulary loading, table creation, and schema reconciliation impossible.

Confirmed this is not limited to vocab loading: every maintenance CLI command that touches table collection (cli_schema_summary, cli_schema_tables, cli_schema_info, cli_schema_reconcile, cli_tables, cli_foreign_keys, cli_vocab, cli_indexes) funnels through the same collection function and crashes identically.

Reproduction

import omop_graph.extensions.omop_alchemy  # noqa: F401  (any groundworkers import does this)
import sqlalchemy as sa
from omop_alchemy.maintenance.cli_vocab import load_vocab_source

engine = sa.create_engine("sqlite+pysqlite:///scratch.sqlite")
load_vocab_source(engine, source_path="./athena_files")
# raises RuntimeError: RelationshipClass is missing __omop_table_category__

The trigger is broader than the scoped code above, since RelationshipClass and RelationshipMapping of omop_graph/extensions/omop_alchemy.py are loaded eagerly via omop_graph/__init__.py, api.py, and
graph/kg.py. As a result, the error fires on any bare import omop_graph, not only deliberate use of the relationship tables.

Cause

omop_alchemy/cdm/base/decorators.py's cdm_table() unconditionally sets __omop_is_cdm_table__ = True and __omop_table_category__ = _infer_table_category(cls). _infer_table_category only returns a value if cls.__module__ starts with the hardcoded omop_alchemy.cdm.model. prefix, and otherwise returns None.
omop_alchemy/maintenance/tables.py's _table_category() raises RuntimeError when that attribute is None, and the registry-wide scan in collect_maintenance_tables() means one such class breaks every command listed above, regardless of which table is actually being touched.

Suggested fix

The actual mechanism belongs in omop-alchemy, not here, since _infer_table_category's closed-world assumption and TableCategory's fixed 8-member enum are both omop-alchemy's own design. Four approaches were considered, tracked in the linked omop-alchemy feature request rather than resolved here:

  • Hardcode a category value directly on omop-graph's two classes: Fast, but does not generalise to any future third-party table, here or elsewhere.
  • Add an explicit, optional category override to cdm_table itself, with no other change. Root-causes it for any third party, but risks letting an in-tree omop-alchemy model silently override its own inferred category.
  • A fully separate decorator for third-party tables, with its own independent copy of cdm_table's structural checks. Cleanest separation in principle, but duplicates logic that already exists at this stage.
  • A single cdm_table with an explicit origin parameter (internal versus extension) that routes to the right resolution logic internally, with a thin, separately named alias for the extension path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions