From 028767ba85820b4f7c135b844ebcd309ee62c34f Mon Sep 17 00:00:00 2001 From: Nathaniel Ramm Date: Wed, 13 May 2026 19:33:44 +1000 Subject: [PATCH 1/6] docs(plan): add profile-spec migration plan Brief plan following the migration guide section of the upstream mountainash-settings 26.5.0 design spec. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-05-13-profile-spec-migration.md | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-13-profile-spec-migration.md diff --git a/docs/superpowers/plans/2026-05-13-profile-spec-migration.md b/docs/superpowers/plans/2026-05-13-profile-spec-migration.md new file mode 100644 index 0000000..dcb24b4 --- /dev/null +++ b/docs/superpowers/plans/2026-05-13-profile-spec-migration.md @@ -0,0 +1,236 @@ +# mountainash-data: profile-spec migration plan + +**Goal:** Migrate `mountainash-data` to the new `ProfileSpec` / `Profile` vocabulary introduced in `mountainash-settings 26.5.0`. Follow the [migration guide](../../../../mountainash-settings/docs/superpowers/specs/2026-05-13-profile-spec-rename-design.md#migration-guide-for-downstream-consumers) verbatim. + +**Architecture:** Mechanical search-and-replace across 21 backend files plus updates to `descriptor.py`, `registry.py`, `profile.py`, and test files. Add a PEP 562 `__getattr__` shim in `mountainash-data`'s own `descriptor.py` so any downstream consumer of `mountainash-data` gets the same one-release deprecation window. + +**Tech Stack:** Python 3.10+, pydantic 2.x, mountainash-settings ≥26.5.0 (via path-based dep), pytest, hatch. + +**Upstream spec:** `../../../../mountainash-settings/docs/superpowers/specs/2026-05-13-profile-spec-rename-design.md` + +**Out of scope:** +- Renaming `*AuthSettings` concrete class names (e.g. `PostgreSQLAuthSettings`) — explicitly deferred in the upstream spec. +- Adding a `mountainash-settings` version pin — `mountainash-data` uses a path-based dependency (`{root:uri}/../mountainash-settings`), so version coordination is implicit. + +--- + +## File survey (from `grep -ln`) + +**Source files using old names (21):** + +``` +src/mountainash_data/core/settings/registry.py +src/mountainash_data/core/settings/profile.py +src/mountainash_data/core/settings/descriptor.py +src/mountainash_data/core/settings/sqlite.py +src/mountainash_data/core/settings/duckdb.py (likely) +src/mountainash_data/core/settings/postgresql.py +src/mountainash_data/core/settings/mysql.py +src/mountainash_data/core/settings/mssql.py +src/mountainash_data/core/settings/snowflake.py +src/mountainash_data/core/settings/redshift.py +src/mountainash_data/core/settings/bigquery.py (likely) +src/mountainash_data/core/settings/databricks.py (likely) +src/mountainash_data/core/settings/motherduck.py +src/mountainash_data/core/settings/clickhouse.py (likely) +src/mountainash_data/core/settings/trino.py +src/mountainash_data/core/settings/singlestoredb.py +src/mountainash_data/core/settings/exasol.py +src/mountainash_data/core/settings/impala.py +src/mountainash_data/core/settings/materialize.py +src/mountainash_data/core/settings/risingwave.py +src/mountainash_data/core/settings/druid.py (likely) +src/mountainash_data/core/settings/pyspark.py +src/mountainash_data/core/settings/pyiceberg_rest.py +``` + +**Test files using old names (3):** + +``` +tests/test_unit/core/settings/test_descriptor.py +tests/test_unit/core/settings/test_profile.py +tests/test_unit/core/settings/test_descriptors_invariants.py +``` + +--- + +## Rename table + +Apply to every file touched: + +| Old | New | +|---|---| +| `from mountainash_settings.profiles import ProfileDescriptor` | `from mountainash_settings.profiles import ProfileSpec` | +| `from mountainash_settings.profiles.descriptor import _Missing` | `from mountainash_settings.profiles import Missing` | +| `class BackendDescriptor(ProfileDescriptor)` | `class BackendSpec(ProfileSpec)` | +| Any `BackendDescriptor` reference | `BackendSpec` | +| `*_DESCRIPTOR = BackendDescriptor(...)` | `*_SPEC = BackendSpec(...)` | +| Every reference to `POSTGRESQL_DESCRIPTOR` etc. | `POSTGRESQL_SPEC` etc. | +| `@register(POSTGRESQL_DESCRIPTOR)` | `@register` (argument-free) | +| `__descriptor__ = POSTGRESQL_DESCRIPTOR` | `__spec__ = POSTGRESQL_SPEC` | +| `Registry("databases")` | `Registry("databases", spec_type=BackendSpec, profile_type=ConnectionProfile)` | +| Local MRO walk in `to_driver_kwargs` | `lookup_class_var` import from `mountainash_settings` | +| `descriptor_invariants_for` | `spec_invariants_for` | +| `TestDescriptorInvariants_*` | `TestSpecInvariants_*` (in expected pytest output assertions) | + +--- + +## Tasks + +### Task A: `descriptor.py` rename + shim + +**Files:** +- Modify: `src/mountainash_data/core/settings/descriptor.py` +- Modify: `src/mountainash_data/core/settings/__init__.py` (if `BackendDescriptor` is re-exported there) + +**Required changes:** + +1. Rename `class BackendDescriptor` → `class BackendSpec`. +2. Replace `from mountainash_settings.profiles.descriptor import _Missing` with `from mountainash_settings.profiles import Missing`. +3. Replace `from mountainash_settings.profiles import ProfileDescriptor` with `from mountainash_settings.profiles import ProfileSpec`. Update `class BackendSpec(ProfileSpec)`. +4. Update `__all__` to use `BackendSpec` and `Missing`. +5. Add PEP 562 `__getattr__` shim at the bottom of `descriptor.py`: + +```python +import warnings + + +_DEPRECATED = { + "BackendDescriptor": ("BackendSpec", BackendSpec), + "_Missing": ("Missing", Missing), +} + + +def __getattr__(name): + if name in _DEPRECATED: + new_name, obj = _DEPRECATED[name] + warnings.warn( + f"{name!r} is renamed to {new_name!r} in mountainash-data. " + f"Update imports to use the new name.", + DeprecationWarning, stacklevel=2, + ) + return obj + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") +``` + +6. Update `__init__.py` re-exports if `BackendDescriptor` was previously exported — change to `BackendSpec` and add a top-level shim if downstream consumers may import directly from the package root. + +### Task B: `registry.py` constraints + `profile.py` lookup helper + +**Files:** +- Modify: `src/mountainash_data/core/settings/registry.py` +- Modify: `src/mountainash_data/core/settings/profile.py` + +**registry.py changes:** + +1. Update the `DATABASES_REGISTRY` construction: + +```python +# Before +DATABASES_REGISTRY = Registry("databases") + +# After +DATABASES_REGISTRY = Registry( + "databases", + spec_type=BackendSpec, + profile_type=ConnectionProfile, +) +``` + +2. Update any `descriptor_invariants_for` references to `spec_invariants_for`. + +**profile.py changes:** + +3. Replace local MRO walk in `to_driver_kwargs()`: + +```python +# Before — local MRO walk +adapter = type(self).__dict__.get("__adapter__") +if adapter is None: + for base in type(self).__mro__[1:]: + candidate = base.__dict__.get("__adapter__") + if candidate is not None: + adapter = candidate + break + +# After — public helper +from mountainash_settings import lookup_class_var +adapter = lookup_class_var(type(self), "__adapter__") +``` + +The import can go at the top of the file rather than inline. + +### Task C: 21 backend file sweep + +For each file in `src/mountainash_data/core/settings/` matching `^(?!__init__|descriptor|profile|registry).*\.py$`: + +**Replace:** +- `BackendDescriptor` → `BackendSpec` (imports and constructor calls) +- `_DESCRIPTOR = BackendDescriptor(` → `_SPEC = BackendSpec(` +- Every reference to `_DESCRIPTOR` → `_SPEC` +- `@register(_DESCRIPTOR)` → `@register` (drop the argument) +- `__descriptor__ = _DESCRIPTOR` (or whatever spec it points at) → `__spec__ = _SPEC` + +Each file is independent. Apply the same mechanical pattern. Verify after each that imports resolve. + +### Task D: Tests + +**Files:** +- Modify: `tests/test_unit/core/settings/test_profile.py` +- Modify: `tests/test_unit/core/settings/test_descriptor.py` (or rename to `test_spec.py` if desired — optional) +- Modify: `tests/test_unit/core/settings/test_descriptors_invariants.py` (consider renaming to `test_spec_invariants.py`) + +Apply the same rename table. Update any references to the old API names. + +### Task E: Version bump + verification + +**Files:** +- Modify: `src/mountainash_data/__version__.py` + +**Steps:** + +1. Bump version. Current is `2026.04.2`. Following `mountainash-data`'s CalVer pattern (`YYYY.MM.MICRO`), the next release is `2026.05.0` (since we're in May). + +2. Run full test suite: + ```bash + hatch run test:test + ``` + +3. Run with deprecation warnings escalated to errors: + ```bash + hatch run test:test -W "error::DeprecationWarning" -W "default::DeprecationWarning:mountainash_data.core.settings.descriptor" + ``` + The `-W default` filter explicitly allows warnings from `mountainash-data`'s own descriptor shim (which exists by design). Any warning from elsewhere fails the run — that's the migration completion check. + +4. Run lint: + ```bash + hatch run ruff:check + ``` + +5. Build: + ```bash + hatch build + ``` + +### Task F: Push and PR + +1. Push the feature branch. +2. Open PR targeting `develop` with the same level of detail as the upstream PR (description, test plan, removal commitment). + +### Restore stashed working tree + +After the PR is open: + +```bash +git stash pop # restore hatch.toml reorder + .claude/worktrees/settings-registry +``` + +(Or leave for the user to handle.) + +--- + +## Execution strategy + +The work is mechanical. Single-subagent dispatch can handle Tasks A-D as one batch since they're all in the same package and the transformations are templated. Task E and F can be done manually. + +This plan does not list individual TDD steps because the upstream contract guarantees behaviour: the new names resolve to the same objects as the old names (via deprecation aliases). Running the existing test suite is the verification. New tests are not added in this PR — the upstream PR added all the deprecation tests; this PR is purely a consumer migration. From 09169566418cc4ea2e8150ba47c04c007d0454fe Mon Sep 17 00:00:00 2001 From: Nathaniel Ramm Date: Wed, 13 May 2026 19:40:17 +1000 Subject: [PATCH 2/6] =?UTF-8?q?refactor(settings):=20rename=20BackendDescr?= =?UTF-8?q?iptor=E2=86=92BackendSpec;=20add=20Registry=20constraints;=20us?= =?UTF-8?q?e=20lookup=5Fclass=5Fvar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - descriptor.py: import ProfileSpec/Missing (new public names), rename BackendDescriptor to BackendSpec; add PEP 562 __getattr__ deprecation shim preserving BackendDescriptor and _Missing as DeprecationWarning aliases - __init__.py: export BackendSpec/Missing; add package-level shim - registry.py: pass spec_type=BackendSpec, profile_type=ConnectionProfile to Registry; update type hints (ProfileDescriptor → BackendSpec) - profile.py: replace manual MRO walk with public lookup_class_var helper Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../core/settings/__init__.py | 29 +++++++++++++--- .../core/settings/descriptor.py | 33 +++++++++++++++---- src/mountainash_data/core/settings/profile.py | 9 ++--- .../core/settings/registry.py | 20 +++++++---- 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src/mountainash_data/core/settings/__init__.py b/src/mountainash_data/core/settings/__init__.py index 5b53466..ca43405 100644 --- a/src/mountainash_data/core/settings/__init__.py +++ b/src/mountainash_data/core/settings/__init__.py @@ -1,13 +1,13 @@ -"""Backend settings — declarative descriptor + registry. +"""Backend settings — declarative spec + registry. The *AuthSettings classes below are stable import anchors; internally each -class body is a two-line shell (``__descriptor__`` + ``__adapter__``). +class body is a two-line shell (``__spec__`` + ``__adapter__``). """ from __future__ import annotations # Core primitives -from .descriptor import MISSING, BackendDescriptor, ParameterSpec +from .descriptor import MISSING, Missing, BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import ( DATABASES_REGISTRY, @@ -55,9 +55,30 @@ class body is a two-line shell (``__descriptor__`` + ``__adapter__``). from .druid import DruidAuthSettings from .pyiceberg_rest import PyIcebergRestAuthSettings +import warnings as _warnings + + +_DEPRECATED_PKG = { + "BackendDescriptor": ("BackendSpec", BackendSpec), + "_Missing": ("Missing", Missing), +} + + +def __getattr__(name: str): + if name in _DEPRECATED_PKG: + new_name, obj = _DEPRECATED_PKG[name] + _warnings.warn( + f"{name!r} is renamed to {new_name!r} in mountainash-data. " + f"Update imports to use the new name.", + DeprecationWarning, stacklevel=2, + ) + return obj + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + __all__ = [ # primitives - "MISSING", "BackendDescriptor", "ParameterSpec", "ConnectionProfile", + "MISSING", "Missing", "BackendSpec", "ParameterSpec", "ConnectionProfile", "DATABASES_REGISTRY", "REGISTRY", "get_descriptor", "get_settings_class", "register", # auth diff --git a/src/mountainash_data/core/settings/descriptor.py b/src/mountainash_data/core/settings/descriptor.py index d418ce7..e8d147f 100644 --- a/src/mountainash_data/core/settings/descriptor.py +++ b/src/mountainash_data/core/settings/descriptor.py @@ -1,4 +1,4 @@ -"""Database-flavored ProfileDescriptor with typed metadata fields. +"""Database-flavored ProfileSpec with typed metadata fields. Retained in mountainash-data (rather than lifted to mountainash-settings) because these fields are domain-specific: ``connection_string_scheme`` and @@ -11,17 +11,17 @@ from mountainash_settings.profiles import ( MISSING, + Missing, ParameterSpec, - ProfileDescriptor, + ProfileSpec, ) -from mountainash_settings.profiles.descriptor import _Missing -__all__ = ["MISSING", "_Missing", "BackendDescriptor", "ParameterSpec"] +__all__ = ["MISSING", "Missing", "ParameterSpec", "BackendSpec"] @dataclass(frozen=True, kw_only=True) -class BackendDescriptor(ProfileDescriptor): - """ProfileDescriptor with database-specific typed metadata. +class BackendSpec(ProfileSpec): + """ProfileSpec with database-specific typed metadata. Extra fields: default_port: Default TCP port if the backend listens on one. @@ -36,3 +36,24 @@ class BackendDescriptor(ProfileDescriptor): connection_string_scheme: str | None = None ibis_dialect: str | None = None rides_on: str | None = None + + +import warnings + + +_DEPRECATED = { + "BackendDescriptor": ("BackendSpec", BackendSpec), + "_Missing": ("Missing", Missing), +} + + +def __getattr__(name: str): + if name in _DEPRECATED: + new_name, obj = _DEPRECATED[name] + warnings.warn( + f"{name!r} is renamed to {new_name!r} in mountainash-data. " + f"Update imports to use the new name.", + DeprecationWarning, stacklevel=2, + ) + return obj + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/src/mountainash_data/core/settings/profile.py b/src/mountainash_data/core/settings/profile.py index 9613831..8e2f0fe 100644 --- a/src/mountainash_data/core/settings/profile.py +++ b/src/mountainash_data/core/settings/profile.py @@ -10,6 +10,7 @@ import typing as t from urllib.parse import quote +from mountainash_settings import lookup_class_var from mountainash_settings.profiles import DescriptorProfile __all__ = ["ConnectionProfile"] @@ -36,13 +37,7 @@ def to_driver_kwargs(self) -> dict[str, t.Any]: composite mappings on top. Otherwise defaults to descriptor ``driver_key`` mappings + default auth dispatch. """ - adapter = type(self).__dict__.get("__adapter__") - if adapter is None: - for base in type(self).__mro__[1:]: - candidate = base.__dict__.get("__adapter__") - if candidate is not None: - adapter = candidate - break + adapter = lookup_class_var(type(self), "__adapter__") if adapter is not None: return adapter(self) kwargs = self._default_kwargs() diff --git a/src/mountainash_data/core/settings/registry.py b/src/mountainash_data/core/settings/registry.py index 54e6f4f..db5c3b9 100644 --- a/src/mountainash_data/core/settings/registry.py +++ b/src/mountainash_data/core/settings/registry.py @@ -12,9 +12,11 @@ from mountainash_settings.profiles import Registry +from .descriptor import BackendSpec +from .profile import ConnectionProfile + if t.TYPE_CHECKING: - from mountainash_settings.profiles import ProfileDescriptor - from .profile import ConnectionProfile + from mountainash_settings.profiles import ProfileSpec __all__ = [ "DATABASES_REGISTRY", @@ -26,12 +28,16 @@ "register", ] -DATABASES_REGISTRY = Registry("databases") +DATABASES_REGISTRY = Registry( + "databases", + spec_type=BackendSpec, + profile_type=ConnectionProfile, +) register = DATABASES_REGISTRY.decorator() -def get_descriptor(name: str) -> "ProfileDescriptor": +def get_descriptor(name: str) -> BackendSpec: return DATABASES_REGISTRY.get_descriptor(name) @@ -47,7 +53,7 @@ class _RegistryDictView(Mapping): def __contains__(self, name: object) -> bool: return isinstance(name, str) and name in DATABASES_REGISTRY - def __getitem__(self, name: str) -> "ProfileDescriptor": + def __getitem__(self, name: str) -> BackendSpec: return DATABASES_REGISTRY.get_descriptor(name) def __iter__(self) -> t.Iterator[str]: @@ -56,13 +62,13 @@ def __iter__(self) -> t.Iterator[str]: def __len__(self) -> int: return len(DATABASES_REGISTRY) - def items(self) -> t.ItemsView[str, "ProfileDescriptor"]: + def items(self) -> t.ItemsView[str, BackendSpec]: return DATABASES_REGISTRY.descriptors.items() def keys(self) -> t.KeysView[str]: return DATABASES_REGISTRY.descriptors.keys() - def values(self) -> t.ValuesView["ProfileDescriptor"]: + def values(self) -> t.ValuesView[BackendSpec]: return DATABASES_REGISTRY.descriptors.values() From 46e17c61bf85fac9aa75b1532a538cf925203450 Mon Sep 17 00:00:00 2001 From: Nathaniel Ramm Date: Wed, 13 May 2026 19:41:57 +1000 Subject: [PATCH 3/6] refactor(settings): rename *_DESCRIPTOR constants to *_SPEC across backends; use bare @register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mechanical sweep of all 20 backend files: - Import: BackendDescriptor → BackendSpec - Constant: NAME_DESCRIPTOR → NAME_SPEC - Decorator: @register(NAME_SPEC) → @register (bare; reads __spec__ from class body) - Class attribute: __descriptor__ = NAME_SPEC → __spec__ = NAME_SPEC The bare @register form is canonical from mountainash-settings 26.5.0 and removes the DeprecationWarning previously fired by the with-spec form. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- src/mountainash_data/core/settings/bigquery.py | 10 +++++----- src/mountainash_data/core/settings/clickhouse.py | 8 ++++---- src/mountainash_data/core/settings/databricks.py | 8 ++++---- src/mountainash_data/core/settings/druid.py | 8 ++++---- src/mountainash_data/core/settings/duckdb.py | 10 +++++----- src/mountainash_data/core/settings/exasol.py | 8 ++++---- src/mountainash_data/core/settings/impala.py | 8 ++++---- src/mountainash_data/core/settings/materialize.py | 8 ++++---- src/mountainash_data/core/settings/motherduck.py | 10 +++++----- src/mountainash_data/core/settings/mssql.py | 8 ++++---- src/mountainash_data/core/settings/mysql.py | 8 ++++---- src/mountainash_data/core/settings/postgresql.py | 8 ++++---- src/mountainash_data/core/settings/pyiceberg_rest.py | 8 ++++---- src/mountainash_data/core/settings/pyspark.py | 10 +++++----- src/mountainash_data/core/settings/redshift.py | 8 ++++---- src/mountainash_data/core/settings/risingwave.py | 8 ++++---- src/mountainash_data/core/settings/singlestoredb.py | 8 ++++---- src/mountainash_data/core/settings/snowflake.py | 8 ++++---- src/mountainash_data/core/settings/sqlite.py | 10 +++++----- src/mountainash_data/core/settings/trino.py | 8 ++++---- 20 files changed, 85 insertions(+), 85 deletions(-) diff --git a/src/mountainash_data/core/settings/bigquery.py b/src/mountainash_data/core/settings/bigquery.py index 86225e4..e2582c3 100644 --- a/src/mountainash_data/core/settings/bigquery.py +++ b/src/mountainash_data/core/settings/bigquery.py @@ -14,11 +14,11 @@ from ..constants import CONST_DB_PROVIDER_TYPE from .adapters import bigquery as _adapter from mountainash_settings.auth import NoAuth, ServiceAccountAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -__all__ = ["BigQueryAuthSettings", "BIGQUERY_DESCRIPTOR"] +__all__ = ["BigQueryAuthSettings", "BIGQUERY_SPEC"] _PROJECT_ID_RE = re.compile(r"^[a-z][a-z0-9-]{4,28}[a-z0-9]$") @@ -32,7 +32,7 @@ def _validate_project_id(value: str) -> str: return value -BIGQUERY_DESCRIPTOR = BackendDescriptor( +BIGQUERY_SPEC = BackendSpec( name="bigquery", provider_type=CONST_DB_PROVIDER_TYPE.BIGQUERY, connection_string_scheme="bigquery://", @@ -60,9 +60,9 @@ def _validate_project_id(value: str) -> str: ) -@register(BIGQUERY_DESCRIPTOR) +@register class BigQueryAuthSettings(ConnectionProfile): - __descriptor__ = BIGQUERY_DESCRIPTOR + __spec__ = BIGQUERY_SPEC __adapter__ = staticmethod(_adapter.build_driver_kwargs) @field_validator("PROJECT_ID", check_fields=False) diff --git a/src/mountainash_data/core/settings/clickhouse.py b/src/mountainash_data/core/settings/clickhouse.py index 0200797..cf4d29b 100644 --- a/src/mountainash_data/core/settings/clickhouse.py +++ b/src/mountainash_data/core/settings/clickhouse.py @@ -11,12 +11,12 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import NoAuth, PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -CLICKHOUSE_DESCRIPTOR = BackendDescriptor( +CLICKHOUSE_SPEC = BackendSpec( name="clickhouse", provider_type=CONST_DB_PROVIDER_TYPE.CLICKHOUSE, default_port=9000, @@ -44,6 +44,6 @@ ) -@register(CLICKHOUSE_DESCRIPTOR) +@register class ClickHouseAuthSettings(ConnectionProfile): - __descriptor__ = CLICKHOUSE_DESCRIPTOR + __spec__ = CLICKHOUSE_SPEC diff --git a/src/mountainash_data/core/settings/databricks.py b/src/mountainash_data/core/settings/databricks.py index 3a6579b..697b25a 100644 --- a/src/mountainash_data/core/settings/databricks.py +++ b/src/mountainash_data/core/settings/databricks.py @@ -12,12 +12,12 @@ from ..constants import CONST_DB_PROVIDER_TYPE from .adapters import databricks as _adapter from mountainash_settings.auth import NoAuth, PasswordAuth, TokenAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -DATABRICKS_DESCRIPTOR = BackendDescriptor( +DATABRICKS_SPEC = BackendSpec( name="databricks", provider_type=CONST_DB_PROVIDER_TYPE.DATABRICKS, ibis_dialect="databricks", @@ -37,7 +37,7 @@ ) -@register(DATABRICKS_DESCRIPTOR) +@register class DatabricksAuthSettings(ConnectionProfile): - __descriptor__ = DATABRICKS_DESCRIPTOR + __spec__ = DATABRICKS_SPEC __adapter__ = staticmethod(_adapter.build_driver_kwargs) diff --git a/src/mountainash_data/core/settings/druid.py b/src/mountainash_data/core/settings/druid.py index 4ac1273..51c03dd 100644 --- a/src/mountainash_data/core/settings/druid.py +++ b/src/mountainash_data/core/settings/druid.py @@ -11,12 +11,12 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import NoAuth, PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -DRUID_DESCRIPTOR = BackendDescriptor( +DRUID_SPEC = BackendSpec( name="druid", provider_type=CONST_DB_PROVIDER_TYPE.DRUID, default_port=8082, @@ -35,6 +35,6 @@ ) -@register(DRUID_DESCRIPTOR) +@register class DruidAuthSettings(ConnectionProfile): - __descriptor__ = DRUID_DESCRIPTOR + __spec__ = DRUID_SPEC diff --git a/src/mountainash_data/core/settings/duckdb.py b/src/mountainash_data/core/settings/duckdb.py index 2ddb94f..eb74a45 100644 --- a/src/mountainash_data/core/settings/duckdb.py +++ b/src/mountainash_data/core/settings/duckdb.py @@ -15,11 +15,11 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import NoAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -__all__ = ["DuckDBAuthSettings", "DUCKDB_DESCRIPTOR"] +__all__ = ["DuckDBAuthSettings", "DUCKDB_SPEC"] _MEMORY_LIMIT_RE = re.compile(r"^(?:\d+(?:\.\d+)?\s*[KMG]i?B|\d+%)$", re.IGNORECASE) @@ -34,7 +34,7 @@ def _validate_memory_limit(value: t.Any) -> t.Any: return value -DUCKDB_DESCRIPTOR = BackendDescriptor( +DUCKDB_SPEC = BackendSpec( name="duckdb", provider_type=CONST_DB_PROVIDER_TYPE.DUCKDB, connection_string_scheme="duckdb://", @@ -84,9 +84,9 @@ def _validate_memory_limit(value: t.Any) -> t.Any: ) -@register(DUCKDB_DESCRIPTOR) +@register class DuckDBAuthSettings(ConnectionProfile): - __descriptor__ = DUCKDB_DESCRIPTOR + __spec__ = DUCKDB_SPEC @field_validator("MEMORY_LIMIT", check_fields=False) @classmethod diff --git a/src/mountainash_data/core/settings/exasol.py b/src/mountainash_data/core/settings/exasol.py index 5f29415..f5d4cd9 100644 --- a/src/mountainash_data/core/settings/exasol.py +++ b/src/mountainash_data/core/settings/exasol.py @@ -9,12 +9,12 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -EXASOL_DESCRIPTOR = BackendDescriptor( +EXASOL_SPEC = BackendSpec( name="exasol", provider_type=CONST_DB_PROVIDER_TYPE.EXASOL, default_port=8563, @@ -31,6 +31,6 @@ ) -@register(EXASOL_DESCRIPTOR) +@register class ExasolAuthSettings(ConnectionProfile): - __descriptor__ = EXASOL_DESCRIPTOR + __spec__ = EXASOL_SPEC diff --git a/src/mountainash_data/core/settings/impala.py b/src/mountainash_data/core/settings/impala.py index b7b0b29..c6b0691 100644 --- a/src/mountainash_data/core/settings/impala.py +++ b/src/mountainash_data/core/settings/impala.py @@ -13,7 +13,7 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import NoAuth, PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register @@ -25,7 +25,7 @@ class ImpalaAuthMechanism(StrEnum): LDAP = "LDAP" -IMPALA_DESCRIPTOR = BackendDescriptor( +IMPALA_SPEC = BackendSpec( name="impala", provider_type=CONST_DB_PROVIDER_TYPE.IMPALA, default_port=21050, @@ -54,6 +54,6 @@ class ImpalaAuthMechanism(StrEnum): ) -@register(IMPALA_DESCRIPTOR) +@register class ImpalaAuthSettings(ConnectionProfile): - __descriptor__ = IMPALA_DESCRIPTOR + __spec__ = IMPALA_SPEC diff --git a/src/mountainash_data/core/settings/materialize.py b/src/mountainash_data/core/settings/materialize.py index 50dd900..657f8ca 100644 --- a/src/mountainash_data/core/settings/materialize.py +++ b/src/mountainash_data/core/settings/materialize.py @@ -11,12 +11,12 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import NoAuth, PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -MATERIALIZE_DESCRIPTOR = BackendDescriptor( +MATERIALIZE_SPEC = BackendSpec( name="materialize", provider_type=CONST_DB_PROVIDER_TYPE.MATERIALIZE, default_port=6875, @@ -39,6 +39,6 @@ ) -@register(MATERIALIZE_DESCRIPTOR) +@register class MaterializeAuthSettings(ConnectionProfile): - __descriptor__ = MATERIALIZE_DESCRIPTOR + __spec__ = MATERIALIZE_SPEC diff --git a/src/mountainash_data/core/settings/motherduck.py b/src/mountainash_data/core/settings/motherduck.py index 607f401..1f96659 100644 --- a/src/mountainash_data/core/settings/motherduck.py +++ b/src/mountainash_data/core/settings/motherduck.py @@ -12,14 +12,14 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import TokenAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -__all__ = ["MotherDuckAuthSettings", "MOTHERDUCK_DESCRIPTOR"] +__all__ = ["MotherDuckAuthSettings", "MOTHERDUCK_SPEC"] -MOTHERDUCK_DESCRIPTOR = BackendDescriptor( +MOTHERDUCK_SPEC = BackendSpec( name="motherduck", provider_type=CONST_DB_PROVIDER_TYPE.MOTHERDUCK, connection_string_scheme="duckdb://md:", # md:?motherduck_token=... @@ -35,6 +35,6 @@ ) -@register(MOTHERDUCK_DESCRIPTOR) +@register class MotherDuckAuthSettings(ConnectionProfile): - __descriptor__ = MOTHERDUCK_DESCRIPTOR + __spec__ = MOTHERDUCK_SPEC diff --git a/src/mountainash_data/core/settings/mssql.py b/src/mountainash_data/core/settings/mssql.py index 9f3bb48..fa944ae 100644 --- a/src/mountainash_data/core/settings/mssql.py +++ b/src/mountainash_data/core/settings/mssql.py @@ -12,7 +12,7 @@ from ..constants import CONST_DB_PROVIDER_TYPE from .adapters import mssql as _adapter from mountainash_settings.auth import AzureADAuth, PasswordAuth, WindowsAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register @@ -29,7 +29,7 @@ class MSSQLEncryption(StrEnum): STRICT = "strict" -MSSQL_DESCRIPTOR = BackendDescriptor( +MSSQL_SPEC = BackendSpec( name="mssql", provider_type=CONST_DB_PROVIDER_TYPE.MSSQL, default_port=1433, @@ -106,7 +106,7 @@ class MSSQLEncryption(StrEnum): ) -@register(MSSQL_DESCRIPTOR) +@register class MSSQLAuthSettings(ConnectionProfile): - __descriptor__ = MSSQL_DESCRIPTOR + __spec__ = MSSQL_SPEC __adapter__ = staticmethod(_adapter.build_driver_kwargs) diff --git a/src/mountainash_data/core/settings/mysql.py b/src/mountainash_data/core/settings/mysql.py index b58807f..4e33ac8 100644 --- a/src/mountainash_data/core/settings/mysql.py +++ b/src/mountainash_data/core/settings/mysql.py @@ -15,7 +15,7 @@ from ..constants import CONST_DB_PROVIDER_TYPE from .adapters import mysql as _adapter from mountainash_settings.auth import PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register @@ -28,7 +28,7 @@ class MySQLSSLMode(StrEnum): VERIFY_IDENTITY = "VERIFY_IDENTITY" -MYSQL_DESCRIPTOR = BackendDescriptor( +MYSQL_SPEC = BackendSpec( name="mysql", provider_type=CONST_DB_PROVIDER_TYPE.MYSQL, default_port=3306, @@ -82,7 +82,7 @@ class MySQLSSLMode(StrEnum): ) -@register(MYSQL_DESCRIPTOR) +@register class MySQLAuthSettings(ConnectionProfile): - __descriptor__ = MYSQL_DESCRIPTOR + __spec__ = MYSQL_SPEC __adapter__ = staticmethod(_adapter.build_driver_kwargs) diff --git a/src/mountainash_data/core/settings/postgresql.py b/src/mountainash_data/core/settings/postgresql.py index 36e5433..afef815 100644 --- a/src/mountainash_data/core/settings/postgresql.py +++ b/src/mountainash_data/core/settings/postgresql.py @@ -16,7 +16,7 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import NoAuth, PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register @@ -63,7 +63,7 @@ def _join_require_auth(v: list[PostgresRequireAuthMethods]) -> str: return ",".join(m.value for m in v) -POSTGRESQL_DESCRIPTOR = BackendDescriptor( +POSTGRESQL_SPEC = BackendSpec( name="postgresql", provider_type=CONST_DB_PROVIDER_TYPE.POSTGRESQL, default_port=5432, @@ -156,6 +156,6 @@ def _join_require_auth(v: list[PostgresRequireAuthMethods]) -> str: ) -@register(POSTGRESQL_DESCRIPTOR) +@register class PostgreSQLAuthSettings(ConnectionProfile): - __descriptor__ = POSTGRESQL_DESCRIPTOR + __spec__ = POSTGRESQL_SPEC diff --git a/src/mountainash_data/core/settings/pyiceberg_rest.py b/src/mountainash_data/core/settings/pyiceberg_rest.py index 6239bab..169d2f8 100644 --- a/src/mountainash_data/core/settings/pyiceberg_rest.py +++ b/src/mountainash_data/core/settings/pyiceberg_rest.py @@ -13,12 +13,12 @@ from ..constants import CONST_DB_PROVIDER_TYPE from .adapters import pyiceberg_rest as _adapter from mountainash_settings.auth import OAuth2Auth, TokenAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -PYICEBERG_REST_DESCRIPTOR = BackendDescriptor( +PYICEBERG_REST_SPEC = BackendSpec( name="pyiceberg_rest", provider_type=CONST_DB_PROVIDER_TYPE.PYICEBERG_REST, connection_string_scheme=None, # uri= kwarg, not URL form @@ -57,7 +57,7 @@ ) -@register(PYICEBERG_REST_DESCRIPTOR) +@register class PyIcebergRestAuthSettings(ConnectionProfile): - __descriptor__ = PYICEBERG_REST_DESCRIPTOR + __spec__ = PYICEBERG_REST_SPEC __adapter__ = staticmethod(_adapter.build_driver_kwargs) diff --git a/src/mountainash_data/core/settings/pyspark.py b/src/mountainash_data/core/settings/pyspark.py index f5faa88..4afdc64 100644 --- a/src/mountainash_data/core/settings/pyspark.py +++ b/src/mountainash_data/core/settings/pyspark.py @@ -16,11 +16,11 @@ from ..constants import CONST_DB_PROVIDER_TYPE from .adapters import pyspark as _adapter from mountainash_settings.auth import NoAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -__all__ = ["PySparkAuthSettings", "PySparkMode", "PYSPARK_DESCRIPTOR"] +__all__ = ["PySparkAuthSettings", "PySparkMode", "PYSPARK_SPEC"] class PySparkMode(StrEnum): @@ -28,7 +28,7 @@ class PySparkMode(StrEnum): STREAMING = "streaming" -PYSPARK_DESCRIPTOR = BackendDescriptor( +PYSPARK_SPEC = BackendSpec( name="pyspark", provider_type=CONST_DB_PROVIDER_TYPE.PYSPARK, connection_string_scheme=None, # SparkSession, not URL @@ -51,7 +51,7 @@ class PySparkMode(StrEnum): ) -@register(PYSPARK_DESCRIPTOR) +@register class PySparkAuthSettings(ConnectionProfile): - __descriptor__ = PYSPARK_DESCRIPTOR + __spec__ = PYSPARK_SPEC __adapter__ = staticmethod(_adapter.build_driver_kwargs) diff --git a/src/mountainash_data/core/settings/redshift.py b/src/mountainash_data/core/settings/redshift.py index 34ad712..fd4363e 100644 --- a/src/mountainash_data/core/settings/redshift.py +++ b/src/mountainash_data/core/settings/redshift.py @@ -16,7 +16,7 @@ from ..constants import CONST_DB_PROVIDER_TYPE from .adapters import redshift as _adapter from mountainash_settings.auth import IAMAuth, PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register @@ -34,7 +34,7 @@ class RedshiftSSLMode(StrEnum): _ROLE_ARN_RE = re.compile(r"^arn:aws(?:-us-gov|-cn)?:iam::\d{12}:role/.+$") -REDSHIFT_DESCRIPTOR = BackendDescriptor( +REDSHIFT_SPEC = BackendSpec( name="redshift", provider_type=CONST_DB_PROVIDER_TYPE.REDSHIFT, default_port=5439, @@ -67,9 +67,9 @@ class RedshiftSSLMode(StrEnum): ) -@register(REDSHIFT_DESCRIPTOR) +@register class RedshiftAuthSettings(ConnectionProfile): - __descriptor__ = REDSHIFT_DESCRIPTOR + __spec__ = REDSHIFT_SPEC __adapter__ = staticmethod(_adapter.build_driver_kwargs) @field_validator("REGION", check_fields=False) diff --git a/src/mountainash_data/core/settings/risingwave.py b/src/mountainash_data/core/settings/risingwave.py index 00e0866..c3f30b0 100644 --- a/src/mountainash_data/core/settings/risingwave.py +++ b/src/mountainash_data/core/settings/risingwave.py @@ -11,12 +11,12 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import NoAuth, PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -RISINGWAVE_DESCRIPTOR = BackendDescriptor( +RISINGWAVE_SPEC = BackendSpec( name="risingwave", provider_type=CONST_DB_PROVIDER_TYPE.RISINGWAVE, default_port=5432, @@ -35,6 +35,6 @@ ) -@register(RISINGWAVE_DESCRIPTOR) +@register class RisingWaveAuthSettings(ConnectionProfile): - __descriptor__ = RISINGWAVE_DESCRIPTOR + __spec__ = RISINGWAVE_SPEC diff --git a/src/mountainash_data/core/settings/singlestoredb.py b/src/mountainash_data/core/settings/singlestoredb.py index e85d754..3bafde0 100644 --- a/src/mountainash_data/core/settings/singlestoredb.py +++ b/src/mountainash_data/core/settings/singlestoredb.py @@ -12,7 +12,7 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import NoAuth, PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register @@ -23,7 +23,7 @@ class SingleStoreDriver(StrEnum): HTTPS = "https" -SINGLESTOREDB_DESCRIPTOR = BackendDescriptor( +SINGLESTOREDB_SPEC = BackendSpec( name="singlestoredb", provider_type=CONST_DB_PROVIDER_TYPE.SINGLESTOREDB, default_port=3306, @@ -46,6 +46,6 @@ class SingleStoreDriver(StrEnum): ) -@register(SINGLESTOREDB_DESCRIPTOR) +@register class SingleStoreDBAuthSettings(ConnectionProfile): - __descriptor__ = SINGLESTOREDB_DESCRIPTOR + __spec__ = SINGLESTOREDB_SPEC diff --git a/src/mountainash_data/core/settings/snowflake.py b/src/mountainash_data/core/settings/snowflake.py index 929d4c8..32bebb4 100644 --- a/src/mountainash_data/core/settings/snowflake.py +++ b/src/mountainash_data/core/settings/snowflake.py @@ -12,7 +12,7 @@ from ..constants import CONST_DB_PROVIDER_TYPE from .adapters import snowflake as _adapter from mountainash_settings.auth import CertificateAuth, OAuth2Auth, PasswordAuth, TokenAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register @@ -25,7 +25,7 @@ class SnowflakeAuthenticator(StrEnum): PASSWORD_MFA = "username_password_mfa" -SNOWFLAKE_DESCRIPTOR = BackendDescriptor( +SNOWFLAKE_SPEC = BackendSpec( name="snowflake", provider_type=CONST_DB_PROVIDER_TYPE.SNOWFLAKE, connection_string_scheme="snowflake://", @@ -67,7 +67,7 @@ class SnowflakeAuthenticator(StrEnum): ) -@register(SNOWFLAKE_DESCRIPTOR) +@register class SnowflakeAuthSettings(ConnectionProfile): - __descriptor__ = SNOWFLAKE_DESCRIPTOR + __spec__ = SNOWFLAKE_SPEC __adapter__ = staticmethod(_adapter.build_driver_kwargs) diff --git a/src/mountainash_data/core/settings/sqlite.py b/src/mountainash_data/core/settings/sqlite.py index 5bf872c..dec4f0a 100644 --- a/src/mountainash_data/core/settings/sqlite.py +++ b/src/mountainash_data/core/settings/sqlite.py @@ -11,14 +11,14 @@ from ..constants import CONST_DB_PROVIDER_TYPE from mountainash_settings.auth import NoAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -__all__ = ["SQLiteAuthSettings", "SQLITE_DESCRIPTOR"] +__all__ = ["SQLiteAuthSettings", "SQLITE_SPEC"] -SQLITE_DESCRIPTOR = BackendDescriptor( +SQLITE_SPEC = BackendSpec( name="sqlite", provider_type=CONST_DB_PROVIDER_TYPE.SQLITE, connection_string_scheme="sqlite://", @@ -45,6 +45,6 @@ ) -@register(SQLITE_DESCRIPTOR) +@register class SQLiteAuthSettings(ConnectionProfile): - __descriptor__ = SQLITE_DESCRIPTOR + __spec__ = SQLITE_SPEC diff --git a/src/mountainash_data/core/settings/trino.py b/src/mountainash_data/core/settings/trino.py index 72e656a..f663d59 100644 --- a/src/mountainash_data/core/settings/trino.py +++ b/src/mountainash_data/core/settings/trino.py @@ -13,12 +13,12 @@ from ..constants import CONST_DB_PROVIDER_TYPE from .adapters import trino as _adapter from mountainash_settings.auth import JWTAuth, KerberosAuth, NoAuth, PasswordAuth -from .descriptor import BackendDescriptor, ParameterSpec +from .descriptor import BackendSpec, ParameterSpec from .profile import ConnectionProfile from .registry import register -TRINO_DESCRIPTOR = BackendDescriptor( +TRINO_SPEC = BackendSpec( name="trino", provider_type=CONST_DB_PROVIDER_TYPE.TRINO, default_port=8080, @@ -75,7 +75,7 @@ ) -@register(TRINO_DESCRIPTOR) +@register class TrinoAuthSettings(ConnectionProfile): - __descriptor__ = TRINO_DESCRIPTOR + __spec__ = TRINO_SPEC __adapter__ = staticmethod(_adapter.build_driver_kwargs) From 88e012b92b9c7a4f0bce5183a50a360603b79d9d Mon Sep 17 00:00:00 2001 From: Nathaniel Ramm Date: Wed, 13 May 2026 19:45:45 +1000 Subject: [PATCH 4/6] refactor(tests): update test files for new ProfileSpec/Profile vocabulary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test_descriptor.py: BackendDescriptor → BackendSpec, test class renamed - test_profile.py: BackendDescriptor → BackendSpec, DUMMY_DESCRIPTOR → DUMMY_SPEC, __descriptor__ → __spec__ throughout - test_descriptors_invariants.py: descriptor_invariants_for → spec_invariants_for - profile.py: fix to_connection_string() to use lookup_class_var(type(self), "__spec__") instead of self.__descriptor__ (which is never set on unregistered test fixtures) Co-Authored-By: Claude Sonnet 4.6 (1M context) --- src/mountainash_data/core/settings/profile.py | 4 ++-- tests/test_unit/core/settings/test_descriptor.py | 14 +++++++------- .../core/settings/test_descriptors_invariants.py | 10 +++++----- tests/test_unit/core/settings/test_profile.py | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/mountainash_data/core/settings/profile.py b/src/mountainash_data/core/settings/profile.py index 8e2f0fe..8e25e6c 100644 --- a/src/mountainash_data/core/settings/profile.py +++ b/src/mountainash_data/core/settings/profile.py @@ -24,7 +24,7 @@ class ConnectionProfile(DescriptorProfile): - :meth:`to_connection_string` — URL form, or ``NotImplementedError`` if the descriptor has no ``connection_string_scheme`` metadata. - Subclasses set ``__descriptor__`` (a :class:`ProfileDescriptor`) and + Subclasses set ``__spec__`` (a :class:`BackendSpec`) and optionally ``__adapter__``. Field installation, auth union, and template wiring are inherited from :class:`DescriptorProfile`. """ @@ -51,7 +51,7 @@ def to_connection_string(self) -> str: (or a typed ``connection_string_scheme`` attribute if the descriptor subclass provides one). Raises :class:`NotImplementedError` if absent. """ - desc = self.__descriptor__ + desc = lookup_class_var(type(self), "__spec__") scheme = getattr(desc, "connection_string_scheme", None) if scheme is None: scheme = desc.metadata.get("connection_string_scheme") diff --git a/tests/test_unit/core/settings/test_descriptor.py b/tests/test_unit/core/settings/test_descriptor.py index ded8375..3469d7a 100644 --- a/tests/test_unit/core/settings/test_descriptor.py +++ b/tests/test_unit/core/settings/test_descriptor.py @@ -1,18 +1,18 @@ -"""Tests for the database-flavored BackendDescriptor subclass.""" +"""Tests for the database-flavored BackendSpec subclass.""" import pytest from mountainash_data.core.settings.auth import NoAuth from mountainash_data.core.settings.descriptor import ( - BackendDescriptor, + BackendSpec, ParameterSpec, ) @pytest.mark.unit -class TestBackendDescriptor: +class TestBackendSpec: def test_default_port_field(self): - d = BackendDescriptor( + d = BackendSpec( name="x", provider_type="x", parameters=[], auth_modes=[NoAuth], default_port=5432, @@ -20,7 +20,7 @@ def test_default_port_field(self): assert d.default_port == 5432 def test_connection_string_scheme_field(self): - d = BackendDescriptor( + d = BackendSpec( name="x", provider_type="x", parameters=[], auth_modes=[NoAuth], connection_string_scheme="postgresql://", @@ -28,7 +28,7 @@ def test_connection_string_scheme_field(self): assert d.connection_string_scheme == "postgresql://" def test_rides_on_field(self): - d = BackendDescriptor( + d = BackendSpec( name="motherduck", provider_type="motherduck", parameters=[], auth_modes=[NoAuth], rides_on="duckdb", @@ -36,7 +36,7 @@ def test_rides_on_field(self): assert d.rides_on == "duckdb" def test_frozen(self): - d = BackendDescriptor( + d = BackendSpec( name="x", provider_type="x", parameters=[], auth_modes=[NoAuth], ) diff --git a/tests/test_unit/core/settings/test_descriptors_invariants.py b/tests/test_unit/core/settings/test_descriptors_invariants.py index 8868b48..d8d07ad 100644 --- a/tests/test_unit/core/settings/test_descriptors_invariants.py +++ b/tests/test_unit/core/settings/test_descriptors_invariants.py @@ -1,7 +1,7 @@ -"""Parametric descriptor invariants for all registered database backends. +"""Parametric spec invariants for all registered database backends. -Generated from the shared ``descriptor_invariants_for`` helper in -``mountainash-settings``. Every descriptor in ``DATABASES_REGISTRY`` gets +Generated from the shared ``spec_invariants_for`` helper in +``mountainash-settings``. Every spec in ``DATABASES_REGISTRY`` gets checked against the invariants for free — no per-backend test additions required. """ @@ -13,6 +13,6 @@ import mountainash_data.core.settings # noqa: F401 from mountainash_data.core.settings.registry import DATABASES_REGISTRY -from mountainash_settings.profiles import descriptor_invariants_for +from mountainash_settings.profiles import spec_invariants_for -TestDatabaseInvariants = descriptor_invariants_for(DATABASES_REGISTRY) +TestDatabaseInvariants = spec_invariants_for(DATABASES_REGISTRY) diff --git a/tests/test_unit/core/settings/test_profile.py b/tests/test_unit/core/settings/test_profile.py index f5d9f0b..9e6bf92 100644 --- a/tests/test_unit/core/settings/test_profile.py +++ b/tests/test_unit/core/settings/test_profile.py @@ -12,13 +12,13 @@ from mountainash_data.core.settings.auth import NoAuth, PasswordAuth from mountainash_data.core.settings.descriptor import ( - BackendDescriptor, + BackendSpec, ParameterSpec, ) from mountainash_data.core.settings.profile import ConnectionProfile -DUMMY_DESCRIPTOR = BackendDescriptor( +DUMMY_SPEC = BackendSpec( name="dummy", provider_type="dummy", default_port=9999, @@ -34,7 +34,7 @@ class DummyProfile(ConnectionProfile): - __descriptor__ = DUMMY_DESCRIPTOR + __spec__ = DUMMY_SPEC @pytest.mark.unit @@ -60,7 +60,7 @@ def _adapter(profile): return {"only": "thing"} class Adapted(ConnectionProfile): - __descriptor__ = DUMMY_DESCRIPTOR + __spec__ = DUMMY_SPEC __adapter__ = staticmethod(_adapter) p = Adapted(HOST="h", auth=NoAuth()) @@ -84,13 +84,13 @@ def test_to_connection_string_url_encodes_secrets(self): assert "p%40ss%3Aw%2Ford" in url def test_to_connection_string_no_scheme_raises(self): - desc = BackendDescriptor( + spec = BackendSpec( name="x", provider_type="x", parameters=[], auth_modes=[NoAuth], connection_string_scheme=None, ) class P(ConnectionProfile): - __descriptor__ = desc + __spec__ = spec p = P(auth=NoAuth()) with pytest.raises(NotImplementedError): From 1d8474eba31395245d2ad5082c2c8a459fdeb56e Mon Sep 17 00:00:00 2001 From: Nathaniel Ramm Date: Wed, 13 May 2026 19:51:42 +1000 Subject: [PATCH 5/6] refactor(settings): finish profile/spec vocabulary sweep - profile.py: DescriptorProfile -> Profile (import, class base, docstrings) - backend.py: __descriptor__ -> __spec__ in _init_from_settings(); update error message strings from "descriptor" to "spec" - test_profile.py: update module docstring to use Profile not DescrecatorProfile Co-Authored-By: Claude Opus 4.6 (1M context) --- src/mountainash_data/backends/ibis/backend.py | 6 +++--- src/mountainash_data/core/settings/profile.py | 10 +++++----- tests/test_unit/core/settings/test_profile.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/mountainash_data/backends/ibis/backend.py b/src/mountainash_data/backends/ibis/backend.py index 79267b1..e39a7bd 100644 --- a/src/mountainash_data/backends/ibis/backend.py +++ b/src/mountainash_data/backends/ibis/backend.py @@ -227,16 +227,16 @@ def _init_from_settings( obj_settings = settings_params.settings_class.get_settings( settings_parameters=settings_params ) - descriptor = getattr(obj_settings, "__descriptor__", None) + descriptor = getattr(obj_settings, "__spec__", None) if descriptor is None or getattr(descriptor, "ibis_dialect", None) is None: raise ValueError( f"Settings class {type(obj_settings).__name__} has no " - f"ibis_dialect on its descriptor" + f"ibis_dialect on its spec" ) resolved_dialect = descriptor.ibis_dialect if resolved_dialect not in DIALECTS: raise KeyError( - f"Unknown ibis dialect {resolved_dialect!r} from descriptor. " + f"Unknown ibis dialect {resolved_dialect!r} from spec. " f"Available: {sorted(DIALECTS)}" ) driver_kwargs = obj_settings.to_driver_kwargs() diff --git a/src/mountainash_data/core/settings/profile.py b/src/mountainash_data/core/settings/profile.py index 8e25e6c..b0d2855 100644 --- a/src/mountainash_data/core/settings/profile.py +++ b/src/mountainash_data/core/settings/profile.py @@ -1,8 +1,8 @@ -"""ConnectionProfile — database-flavored subclass of DescriptorProfile. +"""ConnectionProfile — database-flavored subclass of Profile. Adds ``to_driver_kwargs()`` and ``to_connection_string()`` on top of the generic mechanism provided by -:class:`mountainash_settings.profiles.DescriptorProfile`. +:class:`mountainash_settings.profiles.Profile`. """ from __future__ import annotations @@ -11,12 +11,12 @@ from urllib.parse import quote from mountainash_settings import lookup_class_var -from mountainash_settings.profiles import DescriptorProfile +from mountainash_settings.profiles import Profile __all__ = ["ConnectionProfile"] -class ConnectionProfile(DescriptorProfile): +class ConnectionProfile(Profile): """Database connection settings. Public API: @@ -26,7 +26,7 @@ class ConnectionProfile(DescriptorProfile): Subclasses set ``__spec__`` (a :class:`BackendSpec`) and optionally ``__adapter__``. Field installation, auth union, and template - wiring are inherited from :class:`DescriptorProfile`. + wiring are inherited from :class:`Profile`. """ def to_driver_kwargs(self) -> dict[str, t.Any]: diff --git a/tests/test_unit/core/settings/test_profile.py b/tests/test_unit/core/settings/test_profile.py index 9e6bf92..3a7aa24 100644 --- a/tests/test_unit/core/settings/test_profile.py +++ b/tests/test_unit/core/settings/test_profile.py @@ -1,6 +1,6 @@ -"""Tests for ConnectionProfile — database-flavored DescriptorProfile. +"""Tests for ConnectionProfile — database-flavored Profile. -DescriptorProfile mechanism tests live in mountainash-settings. Here we only +Profile mechanism tests live in mountainash-settings. Here we only exercise the database-specific methods: to_driver_kwargs() and to_connection_string(). """ From 1254928c55c9b0c5932707a6255c0325dd96f3c9 Mon Sep 17 00:00:00 2001 From: Nathaniel Ramm Date: Wed, 13 May 2026 19:55:57 +1000 Subject: [PATCH 6/6] release: bump version to 2026.05.0; fix ruff errors - Move `import warnings` to top of descriptor.py (E402 fix) - Remove unused ProfileSpec TYPE_CHECKING import from registry.py (F401 fix) - Bump version from 2026.04.2 to 2026.05.0 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/mountainash_data/__version__.py | 2 +- src/mountainash_data/core/settings/descriptor.py | 4 +--- src/mountainash_data/core/settings/registry.py | 3 --- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/mountainash_data/__version__.py b/src/mountainash_data/__version__.py index aa02796..a4ad378 100644 --- a/src/mountainash_data/__version__.py +++ b/src/mountainash_data/__version__.py @@ -1 +1 @@ -__version__="2026.04.2" +__version__="2026.05.0" diff --git a/src/mountainash_data/core/settings/descriptor.py b/src/mountainash_data/core/settings/descriptor.py index e8d147f..0459d70 100644 --- a/src/mountainash_data/core/settings/descriptor.py +++ b/src/mountainash_data/core/settings/descriptor.py @@ -7,6 +7,7 @@ from __future__ import annotations +import warnings from dataclasses import dataclass from mountainash_settings.profiles import ( @@ -38,9 +39,6 @@ class BackendSpec(ProfileSpec): rides_on: str | None = None -import warnings - - _DEPRECATED = { "BackendDescriptor": ("BackendSpec", BackendSpec), "_Missing": ("Missing", Missing), diff --git a/src/mountainash_data/core/settings/registry.py b/src/mountainash_data/core/settings/registry.py index db5c3b9..98eb415 100644 --- a/src/mountainash_data/core/settings/registry.py +++ b/src/mountainash_data/core/settings/registry.py @@ -15,9 +15,6 @@ from .descriptor import BackendSpec from .profile import ConnectionProfile -if t.TYPE_CHECKING: - from mountainash_settings.profiles import ProfileSpec - __all__ = [ "DATABASES_REGISTRY", "REGISTRY",