Resources¶
Physical connections, and the databases built on top of them. A database is one of two kinds, discriminated by a required kind field: a plain generic database, or a CDM database with its logical vocab/results role bundle.
ConnectionConfig¶
A concrete database endpoint: dialect, host, credentials, target database. Stored in [connections.<name>].
Bases: SecretSafeBaseModel
Complete specification of one physical database connection: server address, credentials, and target database.
Referenced by :attr:DatabaseConfig.connection and
:attr:DatabaseConfig.vocab_connection. Each entry under
[connections] in config.toml maps to one instance of this model.
Passwords are stored in plaintext for now; secret management support is planned for a future release.
dialect_name
property
¶
dialect_name: str
SQLAlchemy dialect name (e.g. "postgresql", "sqlite"), derived from
dialect alone. Unlike :attr:ResolvedConnection.dialect_name, this
needs no other field set (host, database_name, etc.), so it is
safe to call before the connection is otherwise complete.
build_url ¶
build_url() -> str
Build the full connection URL, including the plaintext password.
Returns:
| Type | Description |
|---|---|
str
|
SQLAlchemy-compatible connection URL. For SQLite, returns
|
physical_identity ¶
physical_identity() -> tuple[Any, ...]
Identity tuple (physical connection key, user) used to decide if this connection is the same physical connection as another one.
user is a separate element so callers comparing addresses alone
can drop it.
physical_key ¶
physical_key() -> str
Physical identity of the database this connection addresses.
See :func:connection_key, which this defers to so a config entry
and a live URL compare through one implementation.
resolve ¶
resolve(name: str) -> ResolvedConnection
Resolve this connection to a concrete, engine-ready target.
safe_url ¶
safe_url() -> str
Build the connection URL with the password redacted.
Safe for logging and display. Identical to build_url() for SQLite
connections, which carry no password.
Returns:
| Type | Description |
|---|---|
str
|
Connection URL with |
to_env_pairs ¶
to_env_pairs(prefix: str) -> list[str]
Return PREFIX_FIELD=value strings for each non-None field.
Used by :func:~oa_configurator.io.write_env_file to emit env vars for
Docker Compose env_file:. Field names are uppercased directly
(e.g. host → PREFIX_HOST), so adding a new field here
automatically appears in the export without touching io.py.
The test_only config-only flag is excluded, since it is not a
database connection parameter.
DatabaseKind¶
Bases: str, Enum
Discriminator for :class:DatabaseConfig subclasses.
DatabaseConfig¶
The shared base unifying kind and connection. Every [databases.<name>] entry is one of the two concrete kinds below, chosen by its own kind field. Each kind adds its own schema field on top
schema_nameonGenericDatabaseConfig,cdm_schemaonCDMDatabaseConfig.
Both default to None, meaning "no schema override, use the connection's own default" (Postgres's own search_path default, typically public), not a guarantee this library makes or encodes in code.
Bases: SecretSafeBaseModel
Shared interface for every named database: a connection, plus each concrete kind's own schema field(s).
Abstract in practice: kind has no default, so every concrete entry
must declare it explicitly via one of the subclasses below. Use this
class (not a subclass) for isinstance checks and RefTo targets
that accept any kind; use :data:DatabaseEntry for parsing raw config
data, which dispatches to the correct subclass based on kind.
Notes
schema dispatch is handled in subclasses. See :class:GenericDatabaseConfig
for schema_name and :class:CDMDatabaseConfig for cdm_schema,
vocab_schema, and results_schema.
connection_name_for_role ¶
connection_name_for_role(role: Role) -> str
Name of the connection entry to use for role.
Always self.connection here; overridden by CDMDatabaseConfig, whose vocab role may route to a separate connection.
GenericDatabaseConfig¶
kind = "generic". A database with no CDM-specific fields: just connection and schema_name. Used by anything that isn't the CDM itself, e.g. a vector store's own database (see Vector Stores).
Bases: DatabaseConfig
A connection plus one optional schema. No CDM role-splitting.
Used by consumers that need a single database with no vocab/results distinction, e.g. an embedding store or a metadata database.
resolve ¶
resolve(name: str, stack: StackConfig) -> ResolvedDatabase
Resolve this database to a concrete connection and effective schema.
stack must already have passed :meth:StackConfig.validate_references,
so self.connection is guaranteed to exist in stack.connections.
CDMDatabaseConfig¶
kind = "cdm". Maps the OMOP logical roles (CDM, vocab, results) to named connections and schema names.
Bases: DatabaseConfig
Maps the OMOP logical roles (CDM, vocab, results) to named connections and schema names.
This is what a CDM-consuming package treats as "its database": the
logical CDM/vocab/results bundle, as opposed to :class:ConnectionConfig
(the physical server address and credentials underneath it). Most
packages only need a single cdm_db database.
connection_name_for_role ¶
connection_name_for_role(role: Role) -> str
Name of the connection entry to use for role: vocab_connection for Role.VOCAB when explicitly configured, else the primary connection.
resolve ¶
resolve(
name: str, stack: StackConfig
) -> ResolvedCDMDatabase
Resolve this database to concrete connections and effective schema names.
The vocab connection falls back to the primary connection when not
explicitly configured; the vocab schema falls back to the CDM
schema under the same condition. vocab_schema/results_schema
resolve to None when their own connection's dialect has no real
multi-schema concept (e.g. SQLite), rather than carrying a
dialect-inappropriate string that a later fold would need to
correct. stack must already have passed
:meth:StackConfig.validate_references, so self.connection/
self.vocab_connection are guaranteed to exist in
stack.connections.
Role¶
Selects among a CDM database's several connections at resolve time. Not related to kind: kind decides which fields an entry has at config-authoring time, Role selects among one CDM entry's connections. A generic entry only ever has one connection, so Role has nothing to select there.
Bases: StrEnum
Pre-determined logical schema tags for consumers. Each tag corresponds to a configurable schema name (depending on the database's type [Database vs. CDMDatabase]): - Role.PRIMARY -> schema or cdm_schema: primary tables - Role.VOCAB -> vocab_schema: vocabulary tables - Role.RESULTS -> results_schema: results tables
Also disambiguates which physical connection to use on a CDMDatabase: Role.PRIMARY and Role.VOCAB each select a connection (connection_for_role, create_engine(role=...)); Role.RESULTS has no connection of its own and always resolves through Role.PRIMARY's.
Dialect¶
The SQLAlchemy backend names (Engine.dialect.name / get_backend_name()) this codebase recognizes: postgresql, sqlite, and duckdb. Distinct from ConnectionConfig.dialect, which stays a free-form string to carry a driver suffix such as postgresql+psycopg; Dialect is the plain backend-name axis every dialect-keyed dispatch in this codebase branches on. See Dialect support across the stack for the convention every consuming repo follows to add a new one.
Bases: StrEnum
SQLAlchemy backend names (Engine.dialect.name / get_backend_name())
this codebase recognizes, independent of the driver.
Distinct from ConnectionConfig.dialect, which stays a free-form
string to carry a driver suffix such as "postgresql+psycopg".
Resolved types¶
ConnectionConfig.resolve(), GenericDatabaseConfig.resolve(), and CDMDatabaseConfig.resolve() produce these. Resolver.resolve_connection()/resolve_database() are thin wrappers around the same methods; resolve_database() returns the resolved subtype matching the entry's own kind.
Concrete physical connection ready for engine creation.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Logical name of the connection as declared in the config. |
url |
str
|
Full database URL including credentials.
TODO: make this a private attribute to avoid accidental password exposure;
requires a factory method or |
safe_url |
str
|
Database URL with credentials redacted, safe for logging and display. |
_engine_url |
URL
|
SQLAlchemy URL object used for engine creation. Avoids the lossy string
round-trip through |
test_only |
bool
|
Whether it is a test-only connection. |
dialect_name
property
¶
dialect_name: str
SQLAlchemy dialect name (e.g. "postgresql", "sqlite"), read off the URL directly.
addresses_same_database_as ¶
addresses_same_database_as(
other: ResolvedConnection,
) -> bool
Do self and other address the same physical database?
Compares physical identity rather than dataclass equality, so a second config entry pointing at one database is recognised as the same database rather than as a separate one.
create_engine ¶
create_engine(**kwargs: Any) -> Engine
Create a SQLAlchemy engine for this connection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Any
|
Forwarded to |
{}
|
Notes
pool_pre_ping=True checks a pooled connection is still alive
before handing it out, avoiding stale-connection failures after
a long-idle period. It adds a small overhead to every checkout,
so it can be disabled when the database is known to be reliable
and the application is latency-sensitive using **kwargs.
Returns:
| Type | Description |
|---|---|
Engine
|
|
physical_key ¶
physical_key() -> str
Physical identity of the database this connection addresses.
See :func:connection_key. Two connection entries with different
names that address one database share a key, which is what decides
whether a role is genuinely hosted elsewhere.
Resolved generic database: one connection, one optional schema.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Logical name of the database as declared in the config. |
connection |
ResolvedConnection
|
Resolved connection for this database. |
schema_name |
str | None
|
Effective schema name for this database, or None for no override (use the connection's own default/search_path). |
configured_internal_schema_translate_map ¶
configured_internal_schema_translate_map() -> dict[
str, str | None
]
Schema translate map for this database.
Routing:
- "primary" → schema_name (or None if the dialect
has no real multi-schema concept, e.g. SQLite)
Notes
This is NOT the full schema_translate_map for an engine!
See :meth:create_engine for the full map, which merges the resolver's own
config-derived claims with any additional claims passed in by the caller.
connection_for_role ¶
connection_for_role(
role: Role = Role.PRIMARY,
) -> ResolvedConnection
Return the resolved connection for a given role. Only Role.PRIMARY is valid here as ResolvedDatabase has no vocab/results connection-splitting.
Raises:
| Type | Description |
|---|---|
ValueError
|
If role is not |
connection_for_schema_tag ¶
connection_for_schema_tag(
schema_tag: Role | str,
) -> ResolvedConnection
Return the connection hosting schema_tag's physical schema.
Mirrors :meth:route_for_schema_tag's routing rule, so a caller
asking where a tag lives gets the same answer as a caller routing a
statement to it.
create_engine ¶
create_engine(
*,
schema_claims: Iterable[SchemaClaim] = (),
extensions: Sequence[Callable[[Any, Any], None]] = (),
execution_options: dict[str, Any] | None = None,
owner: str | None = None,
register_claims: bool = True,
**kwargs: Any,
) -> Engine
Create a SQLAlchemy engine with the schema translate map applied.
Parameters are as for :meth:_build_engine, which this calls for
the primary connection.
foreign_key_can_span ¶
foreign_key_can_span(
from_tag: Role | str,
to_tag: Role | str,
*,
physical_schemas: Mapping[str, str | None]
| None = None,
) -> bool
Can a foreign key reference to_tag's tables from from_tag's?
False for: - physical databases where no dialect can express the constraint - physical schemas on a dialect that cannot express a cross-schema key at all, even within one physical database (e.g. DuckDB) - two distinct logical tags may still share one physical schema and need no cross-schema key
Ask this instead of deriving a split flag, so the answer stays correct if a deployment later colocates, federates, or re-schemas either tag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
physical_schemas
|
Mapping[str, str | None]
|
Physical schema names for tags beyond this database's own
configured ones (e.g. a caller-claimed tag from
|
None
|
Raises:
| Type | Description |
|---|---|
UnregisteredSchemaTagError
|
If from_tag or to_tag has no entry in this database's configured schema-translate map or in physical_schemas, on a dialect that cannot cross schemas. Failing loud here rather than guessing avoids silently treating two unresolved tags as the same schema. |
occupied_schemas ¶
occupied_schemas(connection: Connection) -> set[str]
Physical schema names this database currently claims that live on connection's own physical server.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connection
|
Connection
|
Open connection, used both to match this database's own roles against it and to read its live default schema (search_path-dependent, not a static per-dialect guess). The caller is expected to already have one in scope, not open a fresh one just for this. |
required |
resolved_physical_schemas ¶
resolved_physical_schemas(
*, probe_options: _SchemaProbeOptions | None = None
) -> dict[str, str | None]
Physical schema every internal tag resolves to, with unset ones filled in from their own connection's live default schema.
A tag left unset in config means "use the connection's own default schema", which only that connection can answer. Reading it here, per tag, keeps one answer for the whole database, so engines built for different roles agree on where every tag lives.
Stays None for a tag whose connection has no real schema concept,
where None is the correct and final answer.
Notes
Opens one connection per distinct connection that has an unset tag, reusing the supplied engine for its own connection and forwarding the same engine options to any remote connection. No connection is opened when config names every schema.
roles_on_connection ¶
roles_on_connection(
connection: Connection | Engine,
) -> tuple[Role, ...]
Role(s) this database entry has on connection's own physical
server (host, database, port), or () if none.
connection may be an already-open Connection or an Engine
directly (Engine.engine returns itself, so connection.engine.url
works either way).
Only Role.PRIMARY is possible here, since ResolvedDatabase
has no vocab/results connection-splitting.
route_for_schema_tag ¶
route_for_schema_tag(
schema_tag: Role | str, *, vocab: _T, primary: _T
) -> _T
Determines the right connection, engine, or session to use for a given schema_tag in a split-connection database.
Notes
Only Role.PRIMARY and any string are valid here, since ResolvedDatabase has no vocab/results connection-splitting.
Raises:
| Type | Description |
|---|---|
TypeError
|
If vocab and primary are not the same type, and aren't both members of Bindable. |
ValueError
|
If schema_tag is a Role other than Role.PRIMARY, which has no meaning for this database. |
schema_for_role ¶
schema_for_role(role: Role = Role.PRIMARY) -> str | None
Return the effective schema for a given role. Only PRIMARY is valid here as ResolvedDatabase has no vocab/results role-splitting.
Raises:
| Type | Description |
|---|---|
ValueError
|
If role is not |
schema_tags ¶
schema_tags() -> tuple[Role, ...]
Role tags whose schema provenance is worth tracking for this database.
tags_share_a_transaction ¶
tags_share_a_transaction(*schema_tags: Role | str) -> bool
Can one transaction cover statements against all of schema_tags?
True only when every tag resolves to the same physical database. Nothing provides atomicity across two engines, so a caller needing all-or-nothing behaviour must check this first. Vacuously true for no tags or one tag.
Bases: ResolvedDatabase
Resolved CDM database: adds vocab/results role-splitting on top of
:class:ResolvedDatabase.
Attributes:
| Name | Type | Description |
|---|---|---|
vocab_connection |
ResolvedConnection
|
Resolved vocabulary connection for this database. May be the same as connection if no separate vocab connection is configured. |
vocab_schema |
str or None
|
Effective vocabulary schema name for this database. May be the same as schema_name if no separate vocab schema is configured; None when vocab_connection's dialect has no real multi-schema concept (e.g. SQLite). |
results_schema |
str or None
|
Effective results schema name for this database. May be the same as schema_name if no separate results schema is configured; None when connection's dialect has no real multi-schema concept (e.g. SQLite). |
cdm_schema
property
¶
cdm_schema: str | None
Alias for schema_name, matching OHDSI's own CDM/VOCAB/RESULTS naming.
configured_internal_schema_translate_map ¶
configured_internal_schema_translate_map() -> dict[
str, str | None
]
SQLAlchemy schema translate map for OMOP ORM models.
Routing: "primary" → cdm_schema "vocab" → vocab_schema, falling back to cdm_schema if unset "results" → results_schema, falling back to cdm_schema if unset
Notes
This is NOT the full schema_translate_map for an engine!
See :meth:create_engine for the full map, which merges the resolver's own
config-derived claims with any additional claims passed in by the caller.
connection_for_role ¶
connection_for_role(
role: Role = Role.PRIMARY,
) -> ResolvedConnection
Return the resolved connection for a given role: only Role.PRIMARY
or Role.VOCAB, the only two physical connections that exist.
Raises:
| Type | Description |
|---|---|
ValueError
|
If role is neither |
connection_for_schema_tag ¶
connection_for_schema_tag(
schema_tag: Role | str,
) -> ResolvedConnection
Return the connection hosting schema_tag's physical schema.
Role.VOCAB resolves to vocab_connection; every other tag
resolves to the primary connection, matching
:meth:route_for_schema_tag.
create_engine ¶
create_engine(**kwargs: Any) -> Engine
Always raises: a CDM database has a primary and a vocabulary engine.
Raises:
| Type | Description |
|---|---|
TypeError
|
Always. Use :meth: |
create_engines ¶
create_engines(
*,
schema_claims: Iterable[SchemaClaim] = (),
extensions: Sequence[Callable[[Any, Any], None]] = (),
execution_options: dict[str, Any] | None = None,
owner: str | None = None,
register_claims: bool = True,
**kwargs: Any,
) -> tuple[Engine, Engine]
Return (primary_engine, vocab_engine), the only way to get CDM engines.
The vocabulary engine is the primary engine itself when
vocab_connection addresses the same physical database, so a
colocated deployment opens one pool. Every parameter applies to both
engines and is as for :meth:_build_engine.
occupied_schemas ¶
occupied_schemas(connection: Connection) -> set[str]
Physical schema names this database currently claims that live on connection's own physical server.
Scoped by :meth:roles_on_connection, so a schema belonging to a
role hosted on a genuinely different physical server (e.g. vocab
on its own connection) is never reported just because its name
happens to coincide. Unlike checking every role unconditionally,
this never needs to open a second engine to a different server.
roles_on_connection ¶
roles_on_connection(
connection: Connection | Engine,
) -> tuple[Role, ...]
Role(s) this database entry has on connection's own physical
server (host, database, port), or () if none.
Role.RESULTS is included whenever Role.PRIMARY is, since it
has no connection of its own and always resolves through
Role.PRIMARY's. When primary and vocab share one physical
server, all three roles are returned.
route_for_schema_tag ¶
route_for_schema_tag(
schema_tag: Role | str, *, vocab: _T, primary: _T
) -> _T
Determines the right connection, engine, or session to use for a given schema_tag in a split-connection database.
Role.VOCAB routes to vocab; Role.PRIMARY, Role.RESULTS, and any other string tag route to primary (Role.RESULTS has no connection of its own; it always resolves through Role.PRIMARY's).
Raises:
| Type | Description |
|---|---|
TypeError
|
If vocab and primary are not the same type, and aren't both members of Bindable. |
schema_for_role ¶
schema_for_role(role: Role = Role.PRIMARY) -> str | None
Return the effective schema for a given role.
See :meth:CDMDatabaseConfig.resolve for how vocab/results roles are handled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
role
|
Role
|
Which schema to return. Defaults to |
PRIMARY
|
Returns:
| Type | Description |
|---|---|
str or None
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If role is not recognised as a Role member. |
schema_tags ¶
schema_tags() -> tuple[Role, ...]
Role tags whose schema provenance is worth tracking: primary, vocab, and results.
Schema-aware SQL primitives¶
Physical-schema/schema-tag-aware helpers. See
Schema translate map for the
distinction between a schema tag (an abstract schema_translate_map key)
and a physical schema (an actual, literal database schema name).
Quoted, schema-qualified identifier, for raw SQL that's genuinely unavoidable. Utilises IdentifierPreparer.format_table to quote the name according to the dialect's rules.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bindable
|
Engine | Connection | Session | IdentifierPreparer
|
The SQLAlchemy object whose dialect is used to quote the name, or an already-resolved IdentifierPreparer directly, for a caller with no live bindable in hand (e.g. a dialect-only preparer built ahead of any connection). |
required |
name
|
str
|
Unqualified identifier to quote. |
required |
physical_schema
|
str or None
|
The already-resolved schema to prefix with, or None for an unqualified name. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The quoted identifier, schema-prefixed unless physical_schema is
|
Opens its own transaction for an Engine, or uses an already-open Connection or Session directly, participating in the caller's own transaction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bindable
|
sqlalchemy.engine.Engine, sqlalchemy.engine.Connection, or sqlalchemy.orm.Session
|
An Engine opens a new connection and transaction scoped to this context manager, committing on a clean exit. A Connection is forwarded as-is; its transaction is owned by the caller, and passing the same Connection into several calls groups them into one shared transaction. A Session reduces to its own live connection (via _as_bind), same ownership as a Connection: its transaction is the Session's own, not opened or committed here. |
required |
Yields:
| Type | Description |
|---|---|
Connection
|
|
Yield a Connection in AUTOCOMMIT isolation mode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bindable
|
Engine or Connection
|
Given an |
required |
Notes
Connectionhas no.connect()of its own, so the two bindable arg types need different handling rather than one blind call chain.- The connection must not already have an active transaction:
SQLAlchemy refuses to change
isolation_levelonce one has started.
Yields:
| Type | Description |
|---|---|
Connection
|
|
CREATE SCHEMA IF NOT EXISTS, for Postgres and SQLite; not validated for other dialects.
Given a Connection, executes directly on it (no nested transaction), so it participates in a caller's already-open transaction. Given an Engine, opens its own short-lived transaction.
No-ops on a dialect with no multi-schema concept (e.g. SQLite), when physical_schema is None, or when physical_schema is that dialect's own default schema (e.g. Postgres's "public").
Notes
Unlike the schema primitives above, this parameter is required, not inferred, since creating the wrong schema by silent inference would be far worse than a missing default.
Foreign-key feasibility and omission reporting¶
DuckDB can enforce a foreign key within one schema, but not between schemas. PostgreSQL supports both within one database. Neither supports foreign keys between separate physical databases.
For a worked table-creation example, see Creating tables with unenforced foreign keys.
| Task | API |
|---|---|
| Check whether two schema tags can be linked | resolved.foreign_key_can_span(from_tag, to_tag) |
| Find keys the database will not enforce | unenforced_foreign_keys(tables, resolved=resolved) |
| Prepare table copies for creation | without_cross_engine_foreign_keys(tables, resolved=resolved) |
The report is a tuple of UnenforcedForeignKey records: .constraint is the original constraint, .reason is one of:
| Reason | Meaning | What happens during creation |
|---|---|---|
cross_database |
Source and target are in separate databases | Removed from the table copies |
cross_schema |
The dialect cannot enforce this key between these schemas | Removed from the table copies |
deferred |
Cyclic, or declared with use_alter=True, on DuckDB |
Kept in the copies, but the ALTER TABLE DuckDB would need to add it afterwards is itself unsupported; the DuckDB compile shim no-ops it, so the key is never actually created |
Pass the complete set of tables being created together to the report and copying functions, since cycle classification depends on that set. The original tables and their ORM join information are never mutated; creating the originals directly does not apply the filtering.
All three APIs accept physical_schemas, a mapping from schema tags to physical schema names, for tags beyond the database's own configured ones (e.g. claimed via schema_claims). None for a tag means the dialect's own default schema. Unknown tags raise UnregisteredSchemaTagError rather than guessing. Comparisons on DuckDB ignore ASCII letter case, matching its own identifier folding. The check opens no connection.
An empty report is not a general table-creation compatibility check. DuckDB still rejects, for example, ON DELETE CASCADE/SET NULL/SET DEFAULT. These helpers also don't check existing rows: an omitted key still needs its own validation if your loader is to catch a dangling reference.
Bases: NamedTuple
An original FK constraint and why the database will not enforce it.
cross_database and cross_schema identify keys
:func:without_cross_engine_foreign_keys removes from DDL copies.
deferred means the constraint survives in the DDL as a cyclic or
use_alter=True dependency. It only ever applies on DuckDB. There,
the later ALTER TABLE ADD CONSTRAINT SQLAlchemy would use to add
it is itself unsupported, and the DuckDB compile shim no-ops that
statement instead of raising. The key is therefore never actually
created, the same practical outcome as
cross_database/cross_schema, just reached by a different
path. Inspecting this record does not change the original
constraint.
Report every FK in tables the database will not enforce, and why.
A read-only counterpart to :func:without_cross_engine_foreign_keys: it
classifies the same boundary cases plus deferred keys, without copying
or mutating anything. Cllers that need their own reporting (e.g. a
schema-reconciliation report) have one shared source of truth instead
of each maintaining an independent list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tables
|
Iterable[Table]
|
Tables to classify. Pass the complete set being created together: a cyclic dependency is only visible across the whole set. |
required |
resolved
|
ResolvedDatabase
|
Topology answering whether a foreign key can span two tags. |
required |
physical_schemas
|
Mapping[str, str | None]
|
Forwarded to :meth: |
None
|
Returns:
| Type | Description |
|---|---|
tuple[UnenforcedForeignKey, ...]
|
One entry per unenforced constraint, in table input order. |
Copies of tables with foreign keys crossing an unsupported boundary removed.
Derived from the real tables so a column or type added to a model cannot drift from its copy. Only constraints whose referred tag cannot be enforced from the owning tag (another physical database, or another physical schema on a dialect that cannot express cross-schema keys) are dropped; a composite key targeting an enforceable tag survives untouched.
This changes only the DDL emitted for a split deployment. The ORM models
keep their own ForeignKey declarations, and SQLAlchemy infers join
conditions from those in-Python, so relationship loading does not depend
on the physical constraint existing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tables
|
Iterable[Table]
|
Tables to copy, typically everything one engine creates. Each table's
own |
required |
resolved
|
ResolvedDatabase
|
Topology answering whether a foreign key can span two tags. |
required |
physical_schemas
|
Mapping[str, str | None]
|
Physical schema names for tags beyond resolved's own configured
ones, forwarded to :meth: |
None
|
Returns:
| Type | Description |
|---|---|
list[Table]
|
The copies, in input order, all in one fresh MetaData so a surviving foreign key resolves its target at DDL compilation. The originals and their MetaData are never mutated. |
Schema provenance¶
See Schema provenance guard for the full explanation of what this guards against and when it no-ops.
The schema-provenance guard scoped to schema_tag's own physical schema.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connection
|
Connection
|
Connection the guarded DDL runs on. Must have been built by
create_engine(): the entry name and test_only are read back off its
own execution_options, set there alongside schema_translate_map, so
no separate |
required |
schema_tag
|
Role or str
|
Schema tag being guarded. |
required |
Returns:
| Type | Description |
|---|---|
AbstractContextManager[None]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If connection wasn't built by create_engine() (its execution_options carry neither the entry name nor test_only). |
Look up schema_tag's physical schema in the bindable's schema_translate_map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bindable
|
Engine | Connection | Session
|
|
required |
schema_tag
|
str or None
|
The schema_translate_map key to look up. Defaults to Role.PRIMARY. |
PRIMARY
|
Returns:
| Type | Description |
|---|---|
str or None
|
None if schema_tag is None, or if the dialect has no schema concept. Otherwise the mapped physical schema. |
Raises:
| Type | Description |
|---|---|
UnregisteredSchemaTagError
|
If schema_tag has no schema_translate_map entry on a schema-capable dialect. There used to be a fallback here treating schema_tag as a literal, already-registered physical schema name; it was removed because it silently accepted a typo'd tag that happened to collide with some other tag's physical name. |
Schemas, other than physical_schema, that already have a table named table_name. Excludes the dialect's own system schemas.
Notes
- DuckDB uses catalog-qualified names in get_schema_names() (e.g. "mydb.main")
- Physical_schema is always bare (e.g. "main")
- The catalog prefix is only stripped for dialects whose profile declares schema_names_are_catalog_qualified; otherwise a schema name is compared as-is, so a quoted name that happens to contain a literal dot (e.g. Postgres's "a.b") is never mistaken for a catalog-qualified name.
- No code path in this codebase ever ATTACHes a second DuckDB catalog today, so a schema sharing a name across two attached catalogs is not disambiguated here. A future feature that does attach a second catalog would need to revisit this function.
Bases: RuntimeError
A database's physical schema no longer matches its recorded provenance.
Bases: RuntimeError
A schema tag or a reserved physical schema is already claimed by a different owner on the same connection.
Bases: RuntimeError
The schema_registry table on a connection predates its current layout.
Bases: RuntimeError
A schema_tag fell through to a literal physical schema that was never registered on this connection.