From 1acd6097fb08e964d6f626bc02d4fc8ce66cd74c Mon Sep 17 00:00:00 2001 From: Zhao vistart Date: Wed, 11 Nov 2026 21:41:44 +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 | 4 +- tests/providers/basic_connection.py | 10 ++++- tests/providers/query_connection.py | 10 ++++- .../feature/backend/test_ddl_source_mixin.py | 44 ++++++++++++++----- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 03e8f41..a34e8ec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,14 +68,14 @@ jobs: - name: Install activerecord and testsuite run: | - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/error-taxonomy" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR # NOSONAR(githubactions:S8541, S8544): both sibling projects are installed # from a git reference on purpose. The release branches are the contract # between these repositories, the test suite is still on a development # branch until it merges, and neither repository publishes a lock file to # pin against -- a hash-pinned install of a moving branch would be a pin # that goes stale on the next commit instead of a safety property. - pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/error-taxonomy" # NOSONAR + pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR - name: Install dependencies run: | diff --git a/tests/providers/basic_connection.py b/tests/providers/basic_connection.py index 3316303..72ac28b 100644 --- a/tests/providers/basic_connection.py +++ b/tests/providers/basic_connection.py @@ -12,14 +12,20 @@ class SyncTestUser(ActiveRecord): - __table_name__ = "test_users" + @classmethod + def table_name(cls): + return "test_users" + id: Optional[int] = None name: str email: str class AsyncTestUser(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/providers/query_connection.py b/tests/providers/query_connection.py index 9b4c023..2f485e5 100644 --- a/tests/providers/query_connection.py +++ b/tests/providers/query_connection.py @@ -12,14 +12,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_firebird_test/feature/backend/test_ddl_source_mixin.py b/tests/rhosocial/activerecord_firebird_test/feature/backend/test_ddl_source_mixin.py index 4e155b5..53898dd 100644 --- a/tests/rhosocial/activerecord_firebird_test/feature/backend/test_ddl_source_mixin.py +++ b/tests/rhosocial/activerecord_firebird_test/feature/backend/test_ddl_source_mixin.py @@ -206,8 +206,13 @@ def table_tablespace(cls): class SyncModel(DeclarationMixin, ActiveRecord): - __table_name__ = "ddl_source_sync" - __primary_key__ = "id" + @classmethod + def table_name(cls): + return "ddl_source_sync" + + @classmethod + def primary_key(cls): + return "id" pk_field: PK_TYPE value_field: VALUE_TYPE = DEFAULT_VALUE @@ -216,8 +221,13 @@ class SyncModel(DeclarationMixin, ActiveRecord): class AsyncModel(DeclarationMixin, AsyncActiveRecord): - __table_name__ = "ddl_source_async" - __primary_key__ = "id" + @classmethod + def table_name(cls): + return "ddl_source_async" + + @classmethod + def primary_key(cls): + return "id" pk_field: PK_TYPE value_field: VALUE_TYPE = DEFAULT_VALUE @@ -226,8 +236,13 @@ class AsyncModel(DeclarationMixin, AsyncActiveRecord): class CompositeModel(DeclarationMixin, ActiveRecord): - __table_name__ = "ddl_source_composite" - __primary_key__ = ("left_col", "right_col") + @classmethod + def table_name(cls): + return "ddl_source_composite" + + @classmethod + def primary_key(cls): + return ("left_col", "right_col") left_field: Annotated[int, UseColumn("left_col")] right_field: Annotated[int, UseColumn("right_col")] @@ -235,8 +250,13 @@ class CompositeModel(DeclarationMixin, ActiveRecord): class AsyncCompositeModel(DeclarationMixin, AsyncActiveRecord): - __table_name__ = "ddl_source_async_composite" - __primary_key__ = ("left_col", "right_col") + @classmethod + def table_name(cls): + return "ddl_source_async_composite" + + @classmethod + def primary_key(cls): + return ("left_col", "right_col") left_field: Annotated[int, UseColumn("left_col")] right_field: Annotated[int, UseColumn("right_col")] @@ -244,14 +264,18 @@ class AsyncCompositeModel(DeclarationMixin, AsyncActiveRecord): class PlainModel(ActiveRecord): - __table_name__ = "ddl_source_plain" + @classmethod + def table_name(cls): + return "ddl_source_plain" id: int optional: Optional[str] = None class AsyncPlainModel(AsyncActiveRecord): - __table_name__ = "ddl_source_async_plain" + @classmethod + def table_name(cls): + return "ddl_source_async_plain" id: int optional: Optional[str] = None From 1e63b1403e0413f51949cf2dfa94d01fbb6adbad Mon Sep 17 00:00:00 2001 From: Zhao vistart Date: Thu, 12 Nov 2026 14:30:08 +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 | 279 ++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 2 +- 2 files changed, 280 insertions(+), 1 deletion(-) 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..744e13e --- /dev/null +++ b/.check_expression_baseline.json @@ -0,0 +1,279 @@ +{ + "entries": { + "006cf938941e0f38": 1, + "0783ceb9d9d7d08a": 2, + "158a8e7eb186caa8": 1, + "1b39110af8c6ca97": 1, + "27a9ada09e4810bb": 1, + "346125e0326d312e": 1, + "38cc94c45b4c2c3f": 2, + "3943ef6206d2884f": 1, + "399e795ca27ab2d1": 1, + "3eb99b50a2a3d9b2": 1, + "3ff673f304a31109": 1, + "443ded1ac9a77ed6": 1, + "46a6affe2f0f3b22": 1, + "4a73735e029b3025": 1, + "4b22352574c367e8": 1, + "51458e0c4e6ca3b8": 1, + "5596bd3a716e8a0f": 1, + "5947f1167760513f": 1, + "6173fa08bf6afd7a": 1, + "6a899df0efd62f3b": 1, + "6b3114c2bd725f52": 1, + "6b7ec2ae6d677118": 1, + "6be45be76e3421e8": 1, + "6cd3798cd6614326": 1, + "73e43b67c2f31044": 2, + "7e6bc8ab0f756377": 1, + "84db66013ec4aae6": 1, + "8608fd912b2a0f39": 1, + "95131e4c04c07965": 1, + "967355752cc57859": 1, + "a593b7cf3e7bebfa": 1, + "abc06dfaaf7f11b0": 1, + "ad7cc0879fe84986": 1, + "bc4bcf4a7bf452a0": 1, + "c1420d89e4ac2b06": 1, + "c3e73f0a65f568bf": 1, + "ceedbad867205604": 1, + "e186add3df80ec8e": 1, + "edc30b88a433dd76": 1, + "f260cd0e2846a0ea": 1, + "f364265d1fde1455": 1, + "f554d5e90270365c": 2 + }, + "findings": [ + { + "found": "on_commit_delete: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/firebird/expression/create_table.py", + "rule": "ED-10.2" + }, + { + "found": "getattr(expr.object_type, \"value\", expr.object_type)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/comment.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"force_write\", False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/database.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr.sql_security, \"value\", expr.sql_security)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/database.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"if_not_exists\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_alter_table_modifier.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"if_exists\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_alter_table_modifier.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"if_exists\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_alter_table_modifier.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"restart_with\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_alter_table_modifier.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"tablespace\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"inherits\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'partition', None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(getattr(expr, 'table_options', None), 'comment', None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'table_options', None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'temporary', False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'if_not_exists', False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'temporary', False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'on_commit_delete', True)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'external_file', None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(col_def, \"comment\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(constraint, 'is_auto_increment', False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(col_def, 'computed_by', None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(col_def, 'generated_expression', None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(col_def, 'collation', None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"match_type\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"_transaction\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/dml.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"_caller_privileges\", False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/dml.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"_params\", [])", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/dml.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"from_\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/dql.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(clause, \"with_ties\", False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/dql.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"value\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/expression.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"cases\", [])", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/expression.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"else_result\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/expression.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"alias\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/expression.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'alias', None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/expression.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"func_name\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/expression.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"by_value\", False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/external_function.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"free_it\", False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/external_function.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"body\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/package.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"body\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/package.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"body\", False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/package.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"returns\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/routine.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"returns\", None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/routine.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'use_generator', False)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/sequence.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'active', True)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/trigger.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'position', 0)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/trigger.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'when_condition', None)", + "path": "src/rhosocial/activerecord/backend/impl/firebird/mixins/trigger.py", + "rule": "ED-11.2" + } + ], + "format": 1 +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a34e8ec..f821dad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,7 +68,7 @@ jobs: - name: Install activerecord and testsuite run: | - 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 # NOSONAR(githubactions:S8541, S8544): both sibling projects are installed # from a git reference on purpose. The release branches are the contract # between these repositories, the test suite is still on a development From b778e8bf978b29493e0300d98ba7f1521d74188f Mon Sep 17 00:00:00 2001 From: Zhao vistart Date: Thu, 12 Nov 2026 15:20:20 +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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f821dad..944fc24 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,7 @@ jobs: # branch until it merges, and neither repository publishes a lock file to # pin against -- a hash-pinned install of a moving branch would be a pin # that goes stale on the next commit instead of a safety property. - pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17" # NOSONAR - name: Install dependencies run: |