From 099dbc97be5af35ecc08a98f057e94496b407692 Mon Sep 17 00:00:00 2001 From: Zhao vistart Date: Wed, 11 Nov 2026 21:41:59 +0800 Subject: [PATCH 1/3] feat!: declare models by overriding accessors, not by dunder Breaking, following rhosocial-activerecord: model fixtures and examples now override table_name(), primary_key(), pk_auto_generated(), read_only() and the locator methods instead of assigning __table_name__ and friends. The framework stopped reading those dunders, so a model still declaring them would fail with NotImplementedError at the first read. Produced by the core repository's declaration sweep (scripts/ d5_migrate_declarations.py), so every repository in the family carries the same shape. No new lint findings: the totals drop where the removed assignments took their imports with them. CI for this branch installs the family from the matching feature/declaration-surface ref, so a run exercises one revision across core, testsuite and the backend under test rather than a mixture of two. --- .github/workflows/test.yml | 12 ++--- tests/providers/basic_connection.py | 11 ++-- tests/providers/query_connection.py | 11 ++-- .../backend/ddl/test_ddl_source_mixin.py | 54 ++++++++++++++----- 4 files changed, 64 insertions(+), 24 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6b16237..f0a0192 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,8 +33,8 @@ jobs: run: | pip install -r requirements.txt pip install -r requirements-dev.txt - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/error-taxonomy" # NOSONAR - pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/error-taxonomy#egg=rhosocial-activerecord-testsuite # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface#egg=rhosocial-activerecord-testsuite # NOSONAR - name: Install package run: | @@ -84,8 +84,8 @@ jobs: run: | pip install -r requirements.txt pip install -r requirements-dev.txt - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/error-taxonomy" # NOSONAR - pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/error-taxonomy#egg=rhosocial-activerecord-testsuite # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface#egg=rhosocial-activerecord-testsuite # NOSONAR - name: Install package run: | @@ -117,8 +117,8 @@ jobs: run: | pip install -r requirements.txt pip install -r requirements-dev.txt - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/error-taxonomy" # NOSONAR - pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/error-taxonomy#egg=rhosocial-activerecord-testsuite # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface#egg=rhosocial-activerecord-testsuite # NOSONAR - name: Install package run: | diff --git a/tests/providers/basic_connection.py b/tests/providers/basic_connection.py index 012c530..3a9269b 100644 --- a/tests/providers/basic_connection.py +++ b/tests/providers/basic_connection.py @@ -7,7 +7,6 @@ from rhosocial.activerecord.model import ActiveRecord, AsyncActiveRecord from rhosocial.activerecord.backend.impl.snowflake.backend import SnowflakeBackend -from rhosocial.activerecord.backend.impl.snowflake.config import SnowflakeConnectionConfig from rhosocial.activerecord.connection.pool import BackendPool, AsyncBackendPool, PoolConfig from rhosocial.activerecord.backend.options import ExecutionOptions from rhosocial.activerecord.backend.schema import StatementType @@ -18,7 +17,10 @@ class SyncTestUser(ActiveRecord): """Sync test user model for connection pool tests.""" - __table_name__ = "test_users" + @classmethod + def table_name(cls): + return "test_users" + id: Optional[int] = None name: str email: str @@ -26,7 +28,10 @@ class SyncTestUser(ActiveRecord): class AsyncTestUser(AsyncActiveRecord): """Async test user model for connection pool tests.""" - __table_name__ = "test_users" + @classmethod + def table_name(cls): + return "test_users" + id: Optional[int] = None name: str email: str diff --git a/tests/providers/query_connection.py b/tests/providers/query_connection.py index 5b94db7..e3b19e4 100644 --- a/tests/providers/query_connection.py +++ b/tests/providers/query_connection.py @@ -6,7 +6,6 @@ from rhosocial.activerecord.model import ActiveRecord, AsyncActiveRecord from rhosocial.activerecord.backend.impl.snowflake.backend import SnowflakeBackend -from rhosocial.activerecord.backend.impl.snowflake.config import SnowflakeConnectionConfig from rhosocial.activerecord.connection.pool import BackendPool, AsyncBackendPool, PoolConfig from rhosocial.activerecord.backend.options import ExecutionOptions from rhosocial.activerecord.backend.schema import StatementType @@ -16,14 +15,20 @@ class SyncQueryTestUser(ActiveRecord): - __table_name__ = "test_users" + @classmethod + def table_name(cls): + return "test_users" + id: Optional[int] = None name: str email: str class AsyncQueryTestUser(AsyncActiveRecord): - __table_name__ = "test_users" + @classmethod + def table_name(cls): + return "test_users" + id: Optional[int] = None name: str email: str diff --git a/tests/rhosocial/activerecord_snowflake_test/feature/backend/ddl/test_ddl_source_mixin.py b/tests/rhosocial/activerecord_snowflake_test/feature/backend/ddl/test_ddl_source_mixin.py index 0de0262..3d459eb 100644 --- a/tests/rhosocial/activerecord_snowflake_test/feature/backend/ddl/test_ddl_source_mixin.py +++ b/tests/rhosocial/activerecord_snowflake_test/feature/backend/ddl/test_ddl_source_mixin.py @@ -174,9 +174,17 @@ def create_table_statement_classes(cls): class DeclarationModel(TableDeclarationOverrides, ActiveRecord): - __table_name__ = "ddl_source_declarations" - __schema_name__ = "analytics" - __primary_key__ = "record_id" + @classmethod + def table_name(cls): + return "ddl_source_declarations" + + @classmethod + def schema_name(cls): + return "analytics" + + @classmethod + def primary_key(cls): + return "record_id" id: Annotated[int, UseColumn("record_id")] value: Annotated[ @@ -199,9 +207,17 @@ class DeclarationModel(TableDeclarationOverrides, ActiveRecord): class AsyncDeclarationModel(TableDeclarationOverrides, AsyncActiveRecord): - __table_name__ = "ddl_source_declarations_async" - __schema_name__ = "analytics" - __primary_key__ = "record_id" + @classmethod + def table_name(cls): + return "ddl_source_declarations_async" + + @classmethod + def schema_name(cls): + return "analytics" + + @classmethod + def primary_key(cls): + return "record_id" id: Annotated[int, UseColumn("record_id")] value: Annotated[ @@ -224,22 +240,31 @@ class AsyncDeclarationModel(TableDeclarationOverrides, AsyncActiveRecord): class PlainModel(ActiveRecord): - __table_name__ = "ddl_source_plain" + @classmethod + def table_name(cls): + return "ddl_source_plain" id: int value: Optional[str] = None class AsyncPlainModel(AsyncActiveRecord): - __table_name__ = "ddl_source_plain_async" + @classmethod + def table_name(cls): + return "ddl_source_plain_async" id: int value: Optional[str] = None class CompositeModel(TableDeclarationOverrides, ActiveRecord): - __table_name__ = "ddl_source_composite" - __primary_key__ = ("tenant_id", "order_id") + @classmethod + def table_name(cls): + return "ddl_source_composite" + + @classmethod + def primary_key(cls): + return ("tenant_id", "order_id") tenant_ref: Annotated[int, UseColumn("tenant_id")] order_ref: Annotated[int, UseColumn("order_id")] @@ -249,8 +274,13 @@ class CompositeModel(TableDeclarationOverrides, ActiveRecord): class AsyncCompositeModel(TableDeclarationOverrides, AsyncActiveRecord): - __table_name__ = "ddl_source_composite_async" - __primary_key__ = ("tenant_id", "order_id") + @classmethod + def table_name(cls): + return "ddl_source_composite_async" + + @classmethod + def primary_key(cls): + return ("tenant_id", "order_id") tenant_ref: Annotated[int, UseColumn("tenant_id")] order_ref: Annotated[int, UseColumn("order_id")] From 33a28ab16997046512dd9ca23efcbcabc41c2626 Mon Sep 17 00:00:00 2001 From: Zhao vistart Date: Thu, 12 Nov 2026 14:30:28 +0800 Subject: [PATCH 2/3] ci: track the released core instead of the feature branch Workflow 'test.yml' installs this backend's dependencies from the other repositories in the family. Core's PR #140 is merged into release/v1.0.0.dev30, so the core ref now names that branch: a run of this repository tests the released core rather than a feature branch that no longer exists. The testsuite ref is unchanged and still names feature/declaration-surface: its migration has no merge yet, and pointing it at release/v1.0.0.dev17 would install fixtures that still declare the removed dunders. --- .check_expression_baseline.json | 245 ++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 6 +- 2 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 .check_expression_baseline.json diff --git a/.check_expression_baseline.json b/.check_expression_baseline.json new file mode 100644 index 0000000..01fad94 --- /dev/null +++ b/.check_expression_baseline.json @@ -0,0 +1,245 @@ +{ + "entries": { + "000fdb28d1e4b1f3": 1, + "0128cfc3d86e741c": 1, + "046928bfa9e93efc": 1, + "0dcc99864f33c99d": 1, + "12cb38c51f9062a1": 1, + "12ea12fb6987314d": 1, + "1e4cb0bc77e96ad9": 1, + "25a59eb9ca8081ee": 1, + "2c214789e0a3ff5e": 1, + "2e80c5cfff30cbf7": 1, + "395df3ab580b0c5c": 1, + "3c04f7889ceb01ff": 1, + "4110f75ce143a25c": 1, + "44ba077081b1b0ed": 1, + "4778dd97933f0117": 1, + "49c7576782073d89": 1, + "503a255157dab4e3": 1, + "51eb74491f34738b": 1, + "5926360061729558": 1, + "610a81662f02c94a": 1, + "699052faed699bb4": 1, + "70c4fe53aa42b628": 1, + "733a80ff99bde342": 1, + "97a47f891ca83e93": 1, + "98c81df0c74722d5": 1, + "a4dcb922b6c2d5bf": 1, + "bd84a4c82a61e389": 2, + "c7e7533c8557bd86": 1, + "c84cca3f916b1f33": 1, + "d0c211e378b61c9e": 1, + "d323840361b73af3": 1, + "d4a934f00ab61966": 1, + "d5d445a3d30cc2be": 1, + "d8c4291b982c504b": 1, + "e6062eaaf5788a68": 1, + "f1527465bc068d26": 1, + "f8a461376423c485": 1, + "fed8889710b52075": 2 + }, + "findings": [ + { + "found": "core_kwargs: Any", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/materialized_view.py", + "rule": "ED-KWARGS" + }, + { + "found": "auto_ingest: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/pipe.py", + "rule": "ED-10.2" + }, + { + "found": "pipe_execution_paused: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/pipe.py", + "rule": "ED-10.2" + }, + { + "found": "directory: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/stage.py", + "rule": "ED-10.2" + }, + { + "found": "force: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/stage.py", + "rule": "ED-10.2" + }, + { + "found": "purge: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/stage.py", + "rule": "ED-10.2" + }, + { + "found": "header: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/stage.py", + "rule": "ED-10.2" + }, + { + "found": "overwrite: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/stage.py", + "rule": "ED-10.2" + }, + { + "found": "single: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/stage.py", + "rule": "ED-10.2" + }, + { + "found": "append_only: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/stream.py", + "rule": "ED-10.2" + }, + { + "found": "insert_only: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/stream.py", + "rule": "ED-10.2" + }, + { + "found": "show_initial_rows: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/stream.py", + "rule": "ED-10.2" + }, + { + "found": "copy_grants: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/stream.py", + "rule": "ED-10.2" + }, + { + "found": "allow_overlapping_execution: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/task.py", + "rule": "ED-10.2" + }, + { + "found": "auto_resume: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/warehouse.py", + "rule": "ED-10.2" + }, + { + "found": "initially_suspended: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/warehouse.py", + "rule": "ED-10.2" + }, + { + "found": "auto_resume: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/warehouse.py", + "rule": "ED-10.2" + }, + { + "found": "auto_resume: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/warehouse_options.py", + "rule": "ED-10.2" + }, + { + "found": "initially_suspended: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/expression/ddl/warehouse_options.py", + "rule": "ED-10.2" + }, + { + "found": "getattr(action, \"if_not_exists\", None)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"if_exists\", None)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"if_exists\", None)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"cascade\", None)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"transient\", False)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl_database.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"clone\", None)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl_database.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"comment\", None)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl_type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"cascade\", False)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl_type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"restrict\", False)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl_type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'definition_kind', type(expr).__name__)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl_type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'action_kind', type(expr).__name__)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/ddl_type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"overwrite\", False)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/dml.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr.type_, \"value\", expr.type_)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/file_format.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"with_data\", False)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/materialized_view.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"no_data\", False)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/materialized_view.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"or_replace\", False)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/materialized_view.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"if_not_exists\", False)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/materialized_view.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"column_aliases\", None)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/materialized_view.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"cluster_by\", None)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/materialized_view.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"comment\", None)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/materialized_view.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"options\", None)", + "path": "src/rhosocial/activerecord/backend/impl/snowflake/mixins/time_travel.py", + "rule": "ED-11.2" + } + ], + "format": 1 +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f0a0192..eeb5b0f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: run: | pip install -r requirements.txt pip install -r requirements-dev.txt - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface#egg=rhosocial-activerecord-testsuite # NOSONAR - name: Install package @@ -84,7 +84,7 @@ jobs: run: | pip install -r requirements.txt pip install -r requirements-dev.txt - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface#egg=rhosocial-activerecord-testsuite # NOSONAR - name: Install package @@ -117,7 +117,7 @@ jobs: run: | pip install -r requirements.txt pip install -r requirements-dev.txt - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface#egg=rhosocial-activerecord-testsuite # NOSONAR - name: Install package From 93d3e937fe9507706b8c2fe47fd604e337859716 Mon Sep 17 00:00:00 2001 From: Zhao vistart Date: Thu, 12 Nov 2026 15:20:37 +0800 Subject: [PATCH 3/3] ci: track the merged testsuite instead of the feature branch The testsuite's PR #32 is merged into release/v1.0.0.dev17, so the testsuite ref now names that branch. Every cross-repository install in a run therefore resolves to a released artifact: core from release/v1.0.0.dev30, the testsuite from release/v1.0.0.dev17, and this repository from its own feature branch. --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eeb5b0f..ed41b4e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: pip install -r requirements.txt pip install -r requirements-dev.txt pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR - pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface#egg=rhosocial-activerecord-testsuite # NOSONAR + pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17#egg=rhosocial-activerecord-testsuite # NOSONAR - name: Install package run: | @@ -85,7 +85,7 @@ jobs: pip install -r requirements.txt pip install -r requirements-dev.txt pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR - pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface#egg=rhosocial-activerecord-testsuite # NOSONAR + pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17#egg=rhosocial-activerecord-testsuite # NOSONAR - name: Install package run: | @@ -118,7 +118,7 @@ jobs: pip install -r requirements.txt pip install -r requirements-dev.txt pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR - pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface#egg=rhosocial-activerecord-testsuite # NOSONAR + pip install git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17#egg=rhosocial-activerecord-testsuite # NOSONAR - name: Install package run: |