matrx-orm 3.1.255__tar.gz → 3.1.256__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/CLAUDE.md +13 -24
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/PKG-INFO +1 -1
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/__init__.py +2 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/platform_access.py +10 -1
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/query/FEATURE.md +20 -36
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/query/builder.py +12 -3
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/query/executor.py +10 -6
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/pyproject.toml +1 -1
- matrx_orm-3.1.256/tests/level1/test_row_lock_modes.py +138 -0
- matrx_orm-3.1.256/tests/test_platform_access_strict.py +38 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/.gitignore +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/AGENTS.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/CHANGELOG.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/MODEL_API.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/docs/migrations.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/FEATURE.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/FEATURE.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/admin_lane.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/foreign_session_state.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/pooler_role.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/read_write.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/session_context.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/statement_ceiling.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/core/write_retry.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/no_org_assignment.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/catalog_select.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/pytest_lock_guard.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/pytest_plugin.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/resources/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/excluded_targets.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/backup_restore.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/host_authorization.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/native_enable.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/native_lifecycle.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/native_passkeys.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/native_source.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/org_vault_gate.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/use_stamp.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/secrets_battery/vault_history.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/matrx_orm/write_actor.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/scripts/release.sh +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/scripts/verify_wheel.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/conftest.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_adapter_registry.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_association_trigger_actor_naming.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_associations_wiring.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_borrowed_role_never_reaches_server_calls.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_column_scoped_writes.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_committed_row_guard.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_count_limit_param_leak.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_deadlock_retry.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_fetch_on_init_holds_whole_table.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_fk_cycle_resolution.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_guarded_read_never_reruns_empty.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_lock_contention_retry.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_org_assignment_inventory.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_percentile_cont.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_persisted_count.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_person_scoped_session_bypasses_shared_cache.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_pool_create_never_wedges.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_read_connection_retry.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_read_write_declared.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_registry_reads_outside_the_person_session.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_standalone_write_session_context.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_subquery_nested_expressions.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_sync_pool_declares_read_write.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_transaction_database_identity.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_transaction_gucs_one_round_trip.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_transaction_isolation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_transaction_pool_release_sends_nothing.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_transaction_top_level_cleanup.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_wheel_contents.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_write_queue_deadline.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/native_source_cases.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/native_source_oracle.rs +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_enum_ordering.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_excluded_fk_targets.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_schema_analysis.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema_builder/test_enum_array_column.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/schema_builder/test_never_filter_columns.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_a_failed_capture_still_names_the_failure.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_a_grouped_update_writes_only_each_rows_own_fields.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_pending_insert_honors_operator_filters.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_record_error_bounded_insert.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_record_error_group_open.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_record_error_storm.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_spill_guard_plugin.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/session/test_tests_never_write_live_system_error.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_a_backend_handed_over_mid_transaction_never_takes_our_writes.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_a_bulk_cache_never_stalls_the_loop.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_a_failed_use_stamp_never_fails_the_read.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_a_keyword_column_refuses_generation.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_a_list_of_uuids_binds_as_uuid_array.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_a_read_only_backend_never_refuses_our_writes.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_a_sealed_field_shows_no_character_of_its_value.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_array_arg_element_type.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_attachment_mutation_serialization.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_backup_alias_conflict.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_backup_history_restore.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_backup_restore_live.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_call_function_as.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_call_function_none_mode.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_composite_fk_is_not_a_column_fk.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_dsn_password_masking.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_dynamic_select_unregistered.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_first_asks_the_database_for_one_row.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_foreign_session_state.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_immutable_insert_or_exact_verify.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_instance_update_runs_expressions.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_jsonb_postgres_illegal_text.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_keyed_digest.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_native_enable.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_native_lifecycle.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_native_lifecycle_fences.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_native_passkey_receipts.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_native_source.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_order_by_desc_nulls_last.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_org_vault_gate.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_package_wiring_views.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_secret_audit_names_its_organization.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_secrets_battery.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_secrets_minting_keys.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_shared_lock_guard.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_state_cache_sweeps_stale.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_statement_ceiling_warning.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_sync_twins_guard.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_update_result_truthiness.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_user_visible_lookup.py +0 -0
- {matrx_orm-3.1.255 → matrx_orm-3.1.256}/tests/test_vault_aggregate_recovery_stateful.py +0 -0
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
## Read this first
|
|
13
13
|
|
|
14
|
-
matrx-orm is a standalone async Postgres ORM (`Model`, `QueryBuilder`, migrations, admin router, multi-project registry).
|
|
14
|
+
matrx-orm is a standalone async Postgres ORM (`Model`, `QueryBuilder`, migrations, admin router, multi-project registry). Before adding *any* import, verify it resolves in a fresh venv that only has matrx-orm + matrx-utils.
|
|
15
15
|
|
|
16
16
|
### Row identity
|
|
17
17
|
|
|
18
|
-
Every table that becomes a Model needs row identity: a single-column primary key (usually `id`) or a composite primary key. Intentionally PK-less tables (backup, staging, scratch) belong in the host's `matrx_orm.yaml` `exclude_tables` — do not invent a PK. Tables named with an archival prefix (`_bak_*` / `_backup_*`) are auto-skipped at schema discovery (`is_archival_table_name`) and never need an exclude entry
|
|
18
|
+
Every table that becomes a Model needs row identity: a single-column primary key (usually `id`) or a composite primary key. Intentionally PK-less tables (backup, staging, scratch) belong in the host's `matrx_orm.yaml` `exclude_tables` — do not invent a PK. Tables named with an archival prefix (`_bak_*` / `_backup_*`) are auto-skipped at schema discovery (`is_archival_table_name`) and never need an exclude entry. Never add a surrogate `id` only to satisfy codegen when a natural or composite key already exists. Canonical copy + error text: [`matrx_orm/identity_rule.py`](matrx_orm/identity_rule.py).
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -24,12 +24,12 @@ Every table that becomes a Model needs row identity: a single-column primary key
|
|
|
24
24
|
- **Config / project registry** (`matrx_orm.core.config`): `DatabaseProjectConfig`, `register_database`, `register_database_from_env`, `get_database_config`, `get_manager_config`, `DatabaseRegistry`. A single process can run multiple named database projects concurrently.
|
|
25
25
|
- 🚨 **ONE CONNECTION, REQUIRED** (`matrx_orm.platform_db`): `register_platform_db(name, ...)` + `platform_connection_url()` — the ONLY sanctioned way any Matrx service or package turns env vars into a pool. One set of variables (`SUPABASE_MATRIX_*`), owned here; TLS is a deployment input (`SUPABASE_MATRIX_SSL`), never hardcoded; **RAISES** if it isn't given a database — no degraded mode. Every package's `bootstrap_db()` is one call to it. A package may absolutely run on its OWN Postgres — that is a change of VALUES, never a new variable name; what it may never have is a *second candidate* or a fallback chain. `register_database_from_env` is the low-level primitive underneath — **application and package code must not call it directly** (guard: `scripts/check_one_database.py`, blocking in release).
|
|
26
26
|
- **Core model layer**: `Model`, `BaseManager`, `BaseDTO`, `ModelView`, `model_registry`, plus 50+ field types (`CharField`, `IntegerField`, `ForeignKey`, `ManyToManyField`, `JSONField`, …).
|
|
27
|
-
- **`MatrxEntity`** (`matrx_orm/entity.py`) — **the platform entity contract as an ORM primitive.** A table registered in `platform.entity_types` generates as a `MatrxEntity` (everything else stays a plain `Model`) and gets the behavior instead of every consumer hand-wiring it: `await page.versions()` (not a query on `history.row_versions`), `await page.restore(3)` (not a `version_restore` RPC + a cache bust — `restore()` busts the cache itself), `Note.alive()` / `.dead()` / `.soft_delete()` / `.undelete()`, and `Page.capabilities()`. Capability flags (`_entity_token`, `_is_versioned`, `_has_soft_delete`, `_is_org_scoped`, `_rls_variant`) are **stamped by the generator from the LIVE DATABASE** (does the table really carry the `_version_capture` trigger? a `deleted_at` column?) — never from `entity_types`' own flags
|
|
27
|
+
- **`MatrxEntity`** (`matrx_orm/entity.py`) — **the platform entity contract as an ORM primitive.** A table registered in `platform.entity_types` generates as a `MatrxEntity` (everything else stays a plain `Model`) and gets the behavior instead of every consumer hand-wiring it: `await page.versions()` (not a query on `history.row_versions`), `await page.restore(3)` (not a `version_restore` RPC + a cache bust — `restore()` busts the cache itself), `Note.alive()` / `.dead()` / `.soft_delete()` / `.undelete()`, and `Page.capabilities()`. Capability flags (`_entity_token`, `_is_versioned`, `_has_soft_delete`, `_is_org_scoped`, `_rls_variant`) are **stamped by the generator from the LIVE DATABASE** (does the table really carry the `_version_capture` trigger? a `deleted_at` column?) — never from `entity_types`' own flags. **The database is truth; a flag is a claim.** Using a capability an entity does not declare RAISES (`EntityCapabilityError`) and names the fix — it never returns silently-wrong data, and `is_deleted` refuses to answer from a column a partial fetch never loaded. Drift guard: `scripts/check_entity_drift.py` (loud, non-blocking, in `release.sh`).
|
|
28
28
|
- **Optimistic concurrency (`expected_version=`)** — opt-in compare-and-swap on the canonical `version` int column: `update_where` / `update_item` / instance `update()` take `expected_version=N` (`WHERE version = N`, `SET version = N + 1`; 0 rows → `DoesNotExist` if gone, `OptimisticLockError` if changed — carrying the **decision package**: `current_version`, `current_row`, `contested_fields` (current values of ONLY the fields this write attempted), and `changed_by`/`changed_at` from `history.row_versions` where versioned); `bulk_update(..., check_version=True)` guards per row; `MatrxEntity.update_with_rebase` auto-rebases past DISJOINT concurrent writes using `history.row_versions` snapshots. **Never automatic** — an unguarded write stays last-write-wins (the `_touch_row` trigger bumps `version` on every update, so auto-guarding would raise spurious conflicts). Contract: `common-docs/systems/architecture/optimistic-concurrency/FEATURE.md`.
|
|
29
|
-
- **Change tracking** — every instance records which columns were **loaded** (a SELECTed row, or the caller's kwargs) and which were **changed**. `save()` writes only those, so `.only("id","email")` + `save()` can never NULL the columns it never fetched
|
|
30
|
-
- **Upsert writes only what the caller supplied.** `upsert` / `bulk_upsert` / `upsert_with_conflict` materialize every field's Python default so the **INSERT** half is complete — but `ON CONFLICT DO UPDATE SET` covers **only the columns present in the caller's `data`** (an explicit `update_fields` still wins).
|
|
29
|
+
- **Change tracking** — every instance records which columns were **loaded** (a SELECTed row, or the caller's kwargs) and which were **changed**. `save()` writes only those, so `.only("id","email")` + `save()` can never NULL the columns it never fetched. Mutable containers (jsonb/arrays) are always rewritten when loaded, because `obj.metadata["k"]=1` cannot be seen by `__setattr__`; **reassigning** (`obj.metadata = {...}`) is tracked precisely and is the sanctioned way. `Model._hydrate()` is the ONE row→model entry point.
|
|
30
|
+
- **Upsert writes only what the caller supplied.** `upsert` / `bulk_upsert` / `upsert_with_conflict` materialize every field's Python default so the **INSERT** half is complete — but `ON CONFLICT DO UPDATE SET` covers **only the columns present in the caller's `data`** (an explicit `update_fields` still wins). A caller-supplied `None` is excluded too — it is dropped from the INSERT, and `EXCLUDED.<col>` on an omitted column resolves to that column's **DEFAULT**. An upsert of only conflict keys means "ensure this row exists" and reconciles to a no-op self-assignment, never a raise (a bare `DO NOTHING` would break `RETURNING *`). Pinned by `tests/level1/test_upsert_default_leak.py`.
|
|
31
31
|
- **Read-only models** (`_read_only = True`): a model that can be queried but NEVER written through the ORM — every write path raises `ReadOnlyModelError` (enforced at `QueryExecutor.insert/bulk_insert/upsert/update/delete`, so it covers all of `create`/`save`/`update`/`delete`/`update_where`/`delete_where`/`upsert`/`bulk_*`). Used for DB-managed tables the app must not mutate. The canonical **`auth.users`** model is baked into the schema generator (`get_string_user_model`) as the full, curated Supabase auth schema (safe columns only — no password/token fields), `_read_only`, bound per-database. Read a user's profile/metadata there; mutate users only through Supabase Auth (GoTrue).
|
|
32
|
-
- **Connection-level type codecs** (`async_db_manager._init_connection_default`): every pooled connection decodes **uuid → `str`**, **vector → `list[float]`**, and **json/jsonb → `dict`/`list`** at the driver boundary — the TYPE decides the decode, never the call path. So `call_function` scalar/rows, raw fetches, and field hydration all agree; a caller must **never `json.loads` a fetched json/jsonb value** (it's already decoded
|
|
32
|
+
- **Connection-level type codecs** (`async_db_manager._init_connection_default`): every pooled connection decodes **uuid → `str`**, **vector → `list[float]`**, and **json/jsonb → `dict`/`list`** at the driver boundary — the TYPE decides the decode, never the call path. So `call_function` scalar/rows, raw fetches, and field hydration all agree; a caller must **never `json.loads` a fetched json/jsonb value** (it's already decoded). Encoding: a pre-serialised JSON `str` passes through untouched; a native dict/list is dumped (`_encode_json`). Pinned by `tests/level1/test_connection_codecs.py`.
|
|
33
33
|
- **Connection pools are event-loop-owned.** `AsyncDatabaseManager.get_pool()` recreates a pool when a caller arrives on a different loop. If the former owner loop is already closed, stale-pool termination and cleanup discard that pool without trying to schedule callbacks onto the dead loop; unrelated runtime errors still raise. Pinned by `tests/level1/test_pool_loop_recreation.py`.
|
|
34
34
|
- **Exception diagnostics never read source files.** `ORMException` captures compact caller metadata by walking live frames; `traceback.extract_stack()` is forbidden because its `linecache` filesystem checks can stall an async server. Contract: [`matrx_orm/FEATURE.md`](matrx_orm/FEATURE.md).
|
|
35
35
|
- **A zero-connection pool is dead, not busy.** After bounded SQL-safe acquire retries, `AsyncDatabaseManager.get_connection()` atomically replaces a pool whose physical size is still zero, then tries once against the replacement. It never replaces a busy `size > 0` pool. Contract: [`matrx_orm/core/FEATURE.md`](matrx_orm/core/FEATURE.md).
|
|
@@ -51,9 +51,9 @@ Every table that becomes a Model needs row identity: a single-column primary key
|
|
|
51
51
|
- **Signals**: `pre_create`, `post_create`, `pre_save`, `post_save`, `pre_delete`, `post_delete`.
|
|
52
52
|
- **Schema builder** (`matrx_orm.schema_builder`): code generation for Python + TypeScript type definitions from the DB schema. Postgres **VIEWs** are emitted as **read-only `Model` classes** into the same schema models file (on by default; per-entry `output.include_views: false` opts a schema out, `output.exclude_views: [...]` skips individual views). View models get no manager/DTO/base-class/auto_config (views are not writable), synthesize `_primary_keys` from an `id` column (else the first column, for cache-key/`load_by_id` only), and emit plain typed fields with **no `ForeignKey`** (views carry no FK metadata — nothing cross-schema to resolve). Never hand-write a view model again.
|
|
53
53
|
- **Secrets battery** (`matrx_orm.secrets_battery`): the ONE canonical user-credential vault — `users.user_secrets` CRUD + the platform's single Fernet primitive, keyed by `CREDENTIALS_ENCRYPTION_KEY`. Any package: `configure_secrets(db_config_name=...)` once, then `await require_user_secret(user_id, key)`. No package ever calls another service to resolve a credential. Contract: [`matrx_orm/secrets_battery/FEATURE.md`](matrx_orm/secrets_battery/FEATURE.md).
|
|
54
|
-
- **Polymorphic associations** (`matrx_orm.associations`): `EntityRegistry` (token→Model) + `Associations` — a registry-driven, **access-gated** edge engine over a host's polymorphic edge table. Every `source→target` hop is org-scoped *and* each resolved endpoint is re-authorized against the ACTOR (fail-closed, screams on a genuine leak), so a hop can't widen access past what the actor may reach. 🚨 **The gate is `access_check` (wire `matrx_orm.platform_access_check` → `iam.has_access_for`), NEVER an org-column comparison** — a person belongs to several orgs on purpose, so org-equality denies her own data across her own workspaces
|
|
54
|
+
- **Polymorphic associations** (`matrx_orm.associations`): `EntityRegistry` (token→Model) + `Associations` — a registry-driven, **access-gated** edge engine over a host's polymorphic edge table. Every `source→target` hop is org-scoped *and* each resolved endpoint is re-authorized against the ACTOR (fail-closed, screams on a genuine leak), so a hop can't widen access past what the actor may reach. 🚨 **The gate is `access_check` (wire `matrx_orm.platform_access_check` → `iam.has_access_for`), NEVER an org-column comparison** — a person belongs to several orgs on purpose, so org-equality denies her own data across her own workspaces while granting any co-member of a private row. The edge's `org_id` is a TENANCY stamp, not a permission. Org-equality survives only for UNATTENDED writes (no actor) as an integrity guard, and **system vocabulary is exempt entirely** (existence still required): a model generated with `_rls_variant="system"` is platform-shared dictionary data (seo.keyword/topic) every tenant legitimately links to. Hardcodes no table names — the host wires its own edge + token-registry models. A token whose rows have no Model (a record store's Records) resolves to an **entity door** (`is_entity_door`: `load_by_ids(ids, *, actor_id)`), read as the actor — never by a service identity.
|
|
55
55
|
- **The host's declared session context** (`matrx_orm.core.session_context`): `configure_session_context(provider)` once at startup, and every **top-level** `transaction()` — including the one `rls_session` opens, before it drops to the restricted role — emits whatever the provider returns as transaction-local GUCs. This is how an application-level declaration reaches Postgres at all: a GUC set outside a transaction has no meaning, so the BEGIN is the only place it can land. aidream wires it to its provenance declaration (`app.actor_tier` / `app.actor_system`, read by `platform.actor_tier()`, the history capture and the tier rule). A nested entry is a savepoint inside a transaction that already carries them and emits nothing. A standalone write (`execute_write` outside any transaction) made while the provider has something to say opens its own transaction for that one statement — and still goes through the write queue, inside a copy of the caller's context, so the queue's concurrency gate is kept and the drain task's foreign context cannot hide the declaration (`tests/level1/test_standalone_write_session_context.py`). No provider configured ⇒ no GUCs (standalone behavior unchanged); a provider that raises fails the transaction loudly rather than committing rows with a false provenance.
|
|
56
|
-
- **The platform access policy** (`matrx_orm.platform_access`): `platform_access_check(actor, token, id, level)` → `iam.has_access_for`. The ONE answer to "may this actor touch this row?", shared with RLS and the file-access gate. Fails closed. Never re-derive it from `organization_id` or `created_by` — both are strict subsets that deny legitimate access.
|
|
56
|
+
- **The platform access policy** (`matrx_orm.platform_access`): `platform_access_check(actor, token, id, level)` → `iam.has_access_for`. The ONE answer to "may this actor touch this row?", shared with RLS and the file-access gate. Fails closed by default; `raise_on_error=True` keeps genuine denial false but raises `PlatformAccessUnavailableError` when the policy cannot be called. Never re-derive it from `organization_id` or `created_by` — both are strict subsets that deny legitimate access.
|
|
57
57
|
|
|
58
58
|
Public API is defined explicitly in `matrx_orm/__init__.py` (100+ exports). Treat changes there as semver-breaking.
|
|
59
59
|
|
|
@@ -126,9 +126,7 @@ command is `uv run python db/generate.py --schema <schema> --models-only`.
|
|
|
126
126
|
is the entire `from db.models import X` surface; it is always written from the FULL,
|
|
127
127
|
unfiltered `generate[]` list, never from the `only_schemas` subset — and model names for
|
|
128
128
|
schemas the run did not regenerate are read back from their modules on disk (AST, no
|
|
129
|
-
import) so cross-schema collision de-flattening stays complete.
|
|
130
|
-
once truncated it to one schema and broke imports repo-wide, silently at generation time
|
|
131
|
-
(hand-repaired in 79505fee8). `_write_host_models_aggregator` now RAISES
|
|
129
|
+
import) so cross-schema collision de-flattening stays complete. `_write_host_models_aggregator` now RAISES
|
|
132
130
|
`AggregatorShrinkError` naming every schema that would be lost, and writes nothing.
|
|
133
131
|
Guard: `tests/schema_builder/test_host_models_aggregator.py`.
|
|
134
132
|
|
|
@@ -162,22 +160,13 @@ enforced in code:
|
|
|
162
160
|
as stale (`ModelState.sweep_stale`, every `STALE_SWEEP_EVERY_WRITES`); an
|
|
163
161
|
append-only writer of big JSON passes `returning=[...]` to `upsert` /
|
|
164
162
|
`bulk_create` (or uses `insert_returning`) so its rows are neither read back
|
|
165
|
-
nor cached.
|
|
166
|
-
workflow worker's checkpoints/outcomes/events grew it until ECS killed the
|
|
167
|
-
task for memory (MKT-111, 2026-10-15). The sweep pops oldest-first and stops at
|
|
163
|
+
nor cached. The sweep pops oldest-first and stops at
|
|
168
164
|
the first fresh entry, and `cache_bulk` yields the loop once per sweep window, so
|
|
169
|
-
caching a big read never stalls the server
|
|
170
|
-
21k-row read, 2026-10-17). Guards: `tests/test_state_cache_sweeps_stale.py`,
|
|
165
|
+
caching a big read never stalls the server. Guards: `tests/test_state_cache_sweeps_stale.py`,
|
|
171
166
|
`tests/test_a_bulk_cache_never_stalls_the_loop.py`.
|
|
172
167
|
|
|
173
|
-
|
|
174
|
-
builds a `{pk + payload}` stub per queued UPDATE and `bulk_update` cached it, so a
|
|
175
|
-
chat conversation read back with `organization_id = None` right after its own
|
|
176
|
-
turn-1 title/`message_count` UPDATE flushed — and aidream stopped the user's next
|
|
177
|
-
turn on a value Postgres had held correctly the entire time. Guards:
|
|
178
|
-
`tests/session/test_cache_no_downgrade.py`.
|
|
168
|
+
The no-downgrade guard is `tests/session/test_cache_no_downgrade.py`.
|
|
179
169
|
|
|
180
170
|
## Known issues
|
|
181
171
|
|
|
182
|
-
|
|
183
|
-
- Otherwise this package is the reference-quality standalone package in the monorepo. See root `PACKAGES_MIGRATION_PLAN.md` if any new gaps emerge.
|
|
172
|
+
Package migration gaps are tracked in the root `PACKAGES_MIGRATION_PLAN.md`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.256
|
|
4
4
|
Summary: Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching
|
|
5
5
|
Project-URL: Homepage, https://github.com/AI-Matrix-Engine/aidream-current
|
|
6
6
|
Project-URL: Repository, https://github.com/AI-Matrix-Engine/aidream-current
|
|
@@ -49,6 +49,7 @@ from .platform_access import (
|
|
|
49
49
|
PermissionLevel,
|
|
50
50
|
configure_platform_access,
|
|
51
51
|
permission_level_rank,
|
|
52
|
+
PlatformAccessUnavailableError,
|
|
52
53
|
platform_access_check,
|
|
53
54
|
platform_accessible_ids,
|
|
54
55
|
platform_edge_requirement,
|
|
@@ -439,6 +440,7 @@ __all__ = [
|
|
|
439
440
|
"register_database",
|
|
440
441
|
"register_database_from_env",
|
|
441
442
|
"register_platform_db",
|
|
443
|
+
"PlatformAccessUnavailableError",
|
|
442
444
|
"platform_access_check",
|
|
443
445
|
"platform_accessible_ids",
|
|
444
446
|
"configure_platform_access",
|
|
@@ -169,11 +169,17 @@ def _resolve_database() -> str:
|
|
|
169
169
|
raise
|
|
170
170
|
|
|
171
171
|
|
|
172
|
+
class PlatformAccessUnavailableError(RuntimeError):
|
|
173
|
+
"""The canonical policy could not be evaluated."""
|
|
174
|
+
|
|
175
|
+
|
|
172
176
|
async def platform_access_check(
|
|
173
177
|
actor_id: str,
|
|
174
178
|
entity_token: str,
|
|
175
179
|
entity_id: str,
|
|
176
180
|
required_level: str = "viewer",
|
|
181
|
+
*,
|
|
182
|
+
raise_on_error: bool = False,
|
|
177
183
|
) -> bool:
|
|
178
184
|
"""Return whether ``actor_id`` may reach ``entity_token:entity_id``.
|
|
179
185
|
|
|
@@ -190,7 +196,8 @@ async def platform_access_check(
|
|
|
190
196
|
raise escapes them as a 500 on an unrelated code path. The FIRST failure
|
|
191
197
|
screams (a wiring bug must never be quiet), and
|
|
192
198
|
:func:`verify_platform_access_wiring` exists so startup catches it before a
|
|
193
|
-
user does.
|
|
199
|
+
user does. ``raise_on_error=True`` preserves a policy False but raises a fixed
|
|
200
|
+
unavailable error when the policy cannot be evaluated.
|
|
194
201
|
"""
|
|
195
202
|
if not actor_id or not entity_token or not entity_id:
|
|
196
203
|
return False
|
|
@@ -227,6 +234,8 @@ async def platform_access_check(
|
|
|
227
234
|
"This message prints once per process.",
|
|
228
235
|
color="red",
|
|
229
236
|
)
|
|
237
|
+
if raise_on_error:
|
|
238
|
+
raise PlatformAccessUnavailableError("Access policy is temporarily unavailable.") from None
|
|
230
239
|
return False
|
|
231
240
|
|
|
232
241
|
|
|
@@ -15,6 +15,16 @@ request in the process.
|
|
|
15
15
|
|
|
16
16
|
`compile()` remains deliberately synchronous for synchronous callers.
|
|
17
17
|
|
|
18
|
+
## Row-lock strength
|
|
19
|
+
|
|
20
|
+
`Model.filter(...).select_for_update()` retains `FOR UPDATE` by default;
|
|
21
|
+
`select_for_update(mode="SHARE")` emits `FOR SHARE` through the same query and
|
|
22
|
+
terminal paths. The keyword-only mode accepts only `UPDATE` or `SHARE`;
|
|
23
|
+
`of`, `nowait`, and `skip_locked` apply to either mode. Use row locks inside
|
|
24
|
+
the transaction that consumes the protected rows. SQL compilation checks are
|
|
25
|
+
in `tests/level1/test_row_lock_modes.py`; they do not establish database lock
|
|
26
|
+
compatibility or transaction lifetime.
|
|
27
|
+
|
|
18
28
|
## Large scans and the identity cache
|
|
19
29
|
|
|
20
30
|
`.all()` populates `StateManager` by default so ordinary repeated identity reads
|
|
@@ -85,20 +95,15 @@ resource authorization; ordinary `get()` remains read-your-own-writes.
|
|
|
85
95
|
`count()`, `update()` and `delete()` rebuild SQL from the WHERE tail that
|
|
86
96
|
`_extract_where_clause` peels off the built SELECT. That peel drops the
|
|
87
97
|
SELECT's trailing clauses, so `_prune_param_refs` drops the binds that belonged
|
|
88
|
-
to them and renumbers the survivors `$1..$k`.
|
|
89
|
-
`Model.filter().limit(1).count()` shipped `SELECT COUNT(*) …` (no placeholders)
|
|
90
|
-
with LIMIT's bind still attached and asyncpg raised *the server expects 0
|
|
91
|
-
arguments for this query, 1 was passed* — surfaced as `CacheError`, and it
|
|
92
|
-
hard-500'd `GET /rag/admin/overview` until 2026-09-12. Invariant:
|
|
98
|
+
to them and renumbers the survivors `$1..$k`. Invariant:
|
|
93
99
|
`_max_param_ref(sql) == len(params)` for every emitted statement
|
|
94
100
|
(`tests/level1/test_count_limit_param_leak.py`).
|
|
95
101
|
|
|
96
102
|
## One predicate family per expression
|
|
97
103
|
|
|
98
104
|
`Q` composes with `Q`; a typed `Predicate` composes with a `Predicate`. Mixing
|
|
99
|
-
them (`Q(a=1) | Is(F("x"), None)`) raises `TypeError` naming the remedy
|
|
100
|
-
|
|
101
|
-
no attribute 'kwargs'`. `Filter` accepts direct aggregate calls only:
|
|
105
|
+
them (`Q(a=1) | Is(F("x"), None)`) raises `TypeError` naming the remedy.
|
|
106
|
+
`Filter` accepts direct aggregate calls only:
|
|
102
107
|
`Aggregate` subclasses and the explicit two-expression `JsonbObjectAgg`.
|
|
103
108
|
`JsonbObjectAgg` is accepted because PostgreSQL permits
|
|
104
109
|
`jsonb_object_agg(...) FILTER (WHERE ...)`, even though it cannot inherit the
|
|
@@ -116,31 +121,10 @@ rewrites the next `update()` to `WHERE pk IN (SELECT pk FROM t WHERE <filters>
|
|
|
116
121
|
ORDER BY pk FOR UPDATE)`. Use it on any multi-row UPDATE that concurrent callers
|
|
117
122
|
can run over an OVERLAPPING row set.
|
|
118
123
|
|
|
119
|
-
Sorting the id array
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
sub-select is the only form that pins acquisition order (`LockRows` sits above
|
|
127
|
-
the `Sort`), and it stays ONE statement — a 185-row telemetry write does not
|
|
128
|
-
become 185 round trips. Requires a single-column primary key and an unaliased
|
|
129
|
-
target; both refuse loudly. A non-SQL adapter cannot express it and says so
|
|
130
|
-
rather than dropping the guard silently. Guard:
|
|
131
|
-
`tests/level1/test_deadlock_retry.py`.
|
|
132
|
-
|
|
133
|
-
## Change log
|
|
134
|
-
|
|
135
|
-
- 2026-09-12 — Added `lock_rows_in_pk_order()`: a multi-row UPDATE can take its
|
|
136
|
-
row locks ascending by primary key instead of in plan-scan order.
|
|
137
|
-
- 2026-09-12 — Rebuilt statements prune unreferenced binds; Q/Predicate mixing
|
|
138
|
-
and invalid wrapped `Filter` targets are refused at construction, while
|
|
139
|
-
direct `JsonbObjectAgg` FILTER expressions remain valid.
|
|
140
|
-
- 2026-09-10 — Projection follows caller order: `select`/`values` no longer
|
|
141
|
-
reversed, `only()` no longer hash-ordered.
|
|
142
|
-
- 2026-08-31 — Kept tuple-valued collection lookups as one array bind parameter.
|
|
143
|
-
- 2026-08-28 — Added persisted-state counts that bypass pending-row hydration.
|
|
144
|
-
- 2026-08-18 — Moved async terminal SQL compilation off the event loop.
|
|
145
|
-
- 2026-08-15 — Moved bulk hydration off the event loop, made complete-primary-key
|
|
146
|
-
misses constant-time, and documented cache bypass for request-local scans.
|
|
124
|
+
Sorting the caller's id array does not control lock order: the query plan does.
|
|
125
|
+
A sequential scan locks in heap order, which updates can change. Concurrent
|
|
126
|
+
statements can acquire overlapping locks in opposite orders and deadlock
|
|
127
|
+
(40P01). Ordering inside a `FOR UPDATE` sub-select pins acquisition order
|
|
128
|
+
(`LockRows` sits above `Sort`) in one statement. Requires a single-column
|
|
129
|
+
primary key and an unaliased target; unsupported targets and adapters fail
|
|
130
|
+
explicitly. Guard: `tests/level1/test_deadlock_retry.py`.
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import re
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar
|
|
6
6
|
|
|
7
7
|
from matrx_utils import vcprint
|
|
8
8
|
|
|
@@ -550,13 +550,22 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
550
550
|
nowait: bool = False,
|
|
551
551
|
skip_locked: bool = False,
|
|
552
552
|
of: list[str] | None = None,
|
|
553
|
+
*,
|
|
554
|
+
mode: Literal["UPDATE", "SHARE"] = "UPDATE",
|
|
553
555
|
) -> QueryBuilder[ModelT]:
|
|
554
|
-
"""Append ``FOR UPDATE [NOWAIT | SKIP LOCKED]
|
|
556
|
+
"""Append ``FOR UPDATE|SHARE [OF ...] [NOWAIT | SKIP LOCKED]`` to the query.
|
|
555
557
|
|
|
556
558
|
Must be used inside a transaction.
|
|
557
559
|
"""
|
|
560
|
+
if mode not in ("UPDATE", "SHARE"):
|
|
561
|
+
raise ValueError("Row-lock mode must be UPDATE or SHARE")
|
|
558
562
|
new = self._clone()
|
|
559
|
-
new.for_update_opts = {
|
|
563
|
+
new.for_update_opts = {
|
|
564
|
+
"nowait": nowait,
|
|
565
|
+
"skip_locked": skip_locked,
|
|
566
|
+
"of": of or [],
|
|
567
|
+
"mode": mode,
|
|
568
|
+
}
|
|
560
569
|
return new
|
|
561
570
|
|
|
562
571
|
def lock_rows_in_pk_order(self) -> QueryBuilder[ModelT]:
|
|
@@ -51,6 +51,7 @@ _TOP_LEVEL_WHERE_STOPPERS = (
|
|
|
51
51
|
"LIMIT",
|
|
52
52
|
"OFFSET",
|
|
53
53
|
"FOR UPDATE",
|
|
54
|
+
"FOR SHARE",
|
|
54
55
|
)
|
|
55
56
|
|
|
56
57
|
|
|
@@ -937,19 +938,22 @@ class QueryExecutor:
|
|
|
937
938
|
sql += f" OFFSET ${len(params) + 1}"
|
|
938
939
|
params.append(self._full_query_dict["offset"])
|
|
939
940
|
|
|
940
|
-
# --
|
|
941
|
+
# -- Row locks --------------------------------------------------
|
|
941
942
|
for_update = self._full_query_dict.get("for_update")
|
|
942
943
|
if for_update:
|
|
943
|
-
|
|
944
|
-
if
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
clause += " SKIP LOCKED"
|
|
944
|
+
mode = for_update.get("mode", "UPDATE")
|
|
945
|
+
if mode not in ("UPDATE", "SHARE"):
|
|
946
|
+
raise ValueError("Row-lock mode must be UPDATE or SHARE")
|
|
947
|
+
clause = " FOR " + mode
|
|
948
948
|
of_tables = for_update.get("of", [])
|
|
949
949
|
if of_tables:
|
|
950
950
|
from matrx_orm.core.expressions import F
|
|
951
951
|
|
|
952
952
|
clause += " OF " + ", ".join(F(table).field_name for table in of_tables)
|
|
953
|
+
if for_update.get("nowait"):
|
|
954
|
+
clause += " NOWAIT"
|
|
955
|
+
elif for_update.get("skip_locked"):
|
|
956
|
+
clause += " SKIP LOCKED"
|
|
953
957
|
sql += clause
|
|
954
958
|
|
|
955
959
|
if debug:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.256"
|
|
4
4
|
description = "Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""The public model query must carry its chosen lock mode to the SQL adapter.
|
|
2
|
+
|
|
3
|
+
Only the external database is replaced. SQL compilation, cloning, async terminals
|
|
4
|
+
and model hydration remain real; literal SQL is the independent oracle.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
from matrx_orm.adapters import AdapterRegistry
|
|
13
|
+
from matrx_orm.core.base import Model, ModelMeta
|
|
14
|
+
from matrx_orm.core.fields import IntegerField, TextField
|
|
15
|
+
from matrx_orm.query.executor import QueryExecutor
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _CapturingDatabase:
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self.calls: list[tuple[str, tuple[Any, ...]]] = []
|
|
21
|
+
|
|
22
|
+
async def execute_query_dict(self, query: dict[str, Any]) -> Any:
|
|
23
|
+
return NotImplemented
|
|
24
|
+
|
|
25
|
+
async def execute_query(self, sql: str, *params: Any) -> list[dict[str, Any]]:
|
|
26
|
+
self.calls.append((sql, params))
|
|
27
|
+
if sql.startswith("SELECT COUNT(*)"):
|
|
28
|
+
return [{"count": 2}]
|
|
29
|
+
return [{"id": 17, "name": "Harbor Dental"}]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def query_surface(monkeypatch: pytest.MonkeyPatch) -> tuple[type[Model], _CapturingDatabase]:
|
|
34
|
+
model = ModelMeta("RowLockPractice", (Model,), {
|
|
35
|
+
"id": IntegerField(primary_key=True),
|
|
36
|
+
"name": TextField(),
|
|
37
|
+
"_database": "row_lock_offline",
|
|
38
|
+
"_table_name": "practice",
|
|
39
|
+
})
|
|
40
|
+
database = _CapturingDatabase()
|
|
41
|
+
monkeypatch.setattr(AdapterRegistry, "get", classmethod(lambda cls, name: database))
|
|
42
|
+
return model, database
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.parametrize(("options", "suffix"), [
|
|
46
|
+
({}, "FOR UPDATE"),
|
|
47
|
+
({"mode": "UPDATE"}, "FOR UPDATE"),
|
|
48
|
+
({"mode": "SHARE"}, "FOR SHARE"),
|
|
49
|
+
({"mode": "SHARE", "nowait": True}, "FOR SHARE NOWAIT"),
|
|
50
|
+
({"mode": "UPDATE", "skip_locked": True}, "FOR UPDATE SKIP LOCKED"),
|
|
51
|
+
({"mode": "SHARE", "skip_locked": True}, "FOR SHARE SKIP LOCKED"),
|
|
52
|
+
({"mode": "UPDATE", "of": ["practice"], "nowait": True}, "FOR UPDATE OF practice NOWAIT"),
|
|
53
|
+
({"mode": "SHARE", "of": ["practice"], "nowait": True}, "FOR SHARE OF practice NOWAIT"),
|
|
54
|
+
({"mode": "SHARE", "of": ["practice"], "skip_locked": True}, "FOR SHARE OF practice SKIP LOCKED"),
|
|
55
|
+
# Existing precedence is retained when both wait options are supplied.
|
|
56
|
+
({"mode": "SHARE", "nowait": True, "skip_locked": True}, "FOR SHARE NOWAIT"),
|
|
57
|
+
])
|
|
58
|
+
def test_compile_preserves_selected_mode_and_wait_options(query_surface, options, suffix):
|
|
59
|
+
model, _ = query_surface
|
|
60
|
+
query = model.filter(id=17).select_for_update(**options).order_by("id").limit(2)
|
|
61
|
+
assert query.compile("id") == (
|
|
62
|
+
"SELECT id FROM public.practice WHERE id = $1 ORDER BY id ASC LIMIT $2 " + suffix,
|
|
63
|
+
[17, 2],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_existing_positional_lock_options_still_compile(query_surface):
|
|
68
|
+
model, _ = query_surface
|
|
69
|
+
assert model.filter().select_for_update(True, False, ["practice"]).compile() == (
|
|
70
|
+
"SELECT * FROM public.practice FOR UPDATE OF practice NOWAIT", [],
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_lock_branches_do_not_change_the_base_or_each_other(query_surface):
|
|
75
|
+
model, _ = query_surface
|
|
76
|
+
base = model.filter(id=17)
|
|
77
|
+
shared = base.select_for_update(mode="SHARE")
|
|
78
|
+
updated = shared.select_for_update().order_by("id")
|
|
79
|
+
assert base.compile() == ("SELECT * FROM public.practice WHERE id = $1", [17])
|
|
80
|
+
assert shared.compile() == ("SELECT * FROM public.practice WHERE id = $1 FOR SHARE", [17])
|
|
81
|
+
assert updated.compile() == (
|
|
82
|
+
"SELECT * FROM public.practice WHERE id = $1 ORDER BY id ASC FOR UPDATE", [17],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.parametrize("mode", ["SHARE", "UPDATE"])
|
|
87
|
+
@pytest.mark.parametrize("terminal", ["values", "all"])
|
|
88
|
+
async def test_async_terminal_sends_lock_to_database(query_surface, mode, terminal):
|
|
89
|
+
model, database = query_surface
|
|
90
|
+
query = model.filter(id=17).select_for_update(mode=mode).order_by("id")
|
|
91
|
+
if terminal == "values":
|
|
92
|
+
assert await query.values("id", "name") == [{"id": 17, "name": "Harbor Dental"}]
|
|
93
|
+
projection = "id, name"
|
|
94
|
+
else:
|
|
95
|
+
rows = await query.all(use_cache=False)
|
|
96
|
+
assert [(row.id, row.name) for row in rows] == [(17, "Harbor Dental")]
|
|
97
|
+
projection = "*"
|
|
98
|
+
assert database.calls == [(
|
|
99
|
+
f"SELECT {projection} FROM public.practice WHERE id = $1 ORDER BY id ASC FOR {mode}",
|
|
100
|
+
(17,),
|
|
101
|
+
)]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.parametrize("mode", ["SHARE", "UPDATE"])
|
|
105
|
+
async def test_count_rebuild_drops_lock_suffix_without_leaking_params(query_surface, mode):
|
|
106
|
+
model, database = query_surface
|
|
107
|
+
result = await model.filter(id=17).select_for_update(mode=mode).count()
|
|
108
|
+
assert result == 2
|
|
109
|
+
assert database.calls == [("SELECT COUNT(*) as count FROM public.practice WHERE id = $1", (17,))]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@pytest.mark.parametrize("mode", ["share", "NO KEY UPDATE", "SHARE; SELECT 1", "", None, 1])
|
|
113
|
+
def test_builder_refuses_unrecognized_lock_mode(query_surface, mode):
|
|
114
|
+
model, _ = query_surface
|
|
115
|
+
with pytest.raises(ValueError, match="Row-lock mode must be UPDATE or SHARE"):
|
|
116
|
+
model.filter().select_for_update(mode=mode)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_executor_refuses_mode_injected_through_query_dictionary(query_surface):
|
|
120
|
+
model, database = query_surface
|
|
121
|
+
query = model.filter().select_for_update()._build_query()
|
|
122
|
+
query["for_update"]["mode"] = "SHARE; SELECT 1"
|
|
123
|
+
with pytest.raises(ValueError, match="Row-lock mode must be UPDATE or SHARE"):
|
|
124
|
+
QueryExecutor(query)
|
|
125
|
+
assert database.calls == []
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_legacy_query_dictionary_defaults_to_update(query_surface):
|
|
129
|
+
model, _ = query_surface
|
|
130
|
+
query = model.filter()._build_query()
|
|
131
|
+
query["for_update"] = {"nowait": True}
|
|
132
|
+
assert QueryExecutor(query).query == "SELECT * FROM public.practice FOR UPDATE NOWAIT"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_lock_table_identifier_is_validated(query_surface):
|
|
136
|
+
model, _ = query_surface
|
|
137
|
+
with pytest.raises(ValueError, match="Unsafe field reference"):
|
|
138
|
+
model.filter().select_for_update(mode="SHARE", of=["practice; SELECT 1"]).compile()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""The strict opt-in distinguishes policy denial from unavailable authority."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from unittest.mock import AsyncMock
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from matrx_orm import PlatformAccessUnavailableError
|
|
8
|
+
from matrx_orm import platform_access as access
|
|
9
|
+
from matrx_orm.core import db_function
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.parametrize("strict", [False, True])
|
|
13
|
+
async def test_real_policy_false_is_denial(monkeypatch, strict):
|
|
14
|
+
monkeypatch.setattr(access, "_resolve_database", lambda: "test")
|
|
15
|
+
call = AsyncMock(return_value=False)
|
|
16
|
+
monkeypatch.setattr(db_function, "call_function", call)
|
|
17
|
+
assert not await access.platform_access_check("actor", "record", "id", raise_on_error=strict)
|
|
18
|
+
assert call.await_count == 1
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def test_actual_invocation_failure_raises_only_for_strict(monkeypatch):
|
|
22
|
+
monkeypatch.setattr(access, "_resolve_database", lambda: "test")
|
|
23
|
+
monkeypatch.setattr(access, "_policy_failure_announced", True)
|
|
24
|
+
call = AsyncMock(side_effect=ConnectionError("private transport diagnostic"))
|
|
25
|
+
monkeypatch.setattr(db_function, "call_function", call)
|
|
26
|
+
assert not await access.platform_access_check("actor", "record", "id")
|
|
27
|
+
with pytest.raises(
|
|
28
|
+
PlatformAccessUnavailableError, match="^Access policy is temporarily unavailable.$"
|
|
29
|
+
):
|
|
30
|
+
await access.platform_access_check("actor", "record", "id", raise_on_error=True)
|
|
31
|
+
assert call.await_count == 2
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def test_cancel_propagates(monkeypatch):
|
|
35
|
+
monkeypatch.setattr(access, "_resolve_database", lambda: "test")
|
|
36
|
+
monkeypatch.setattr(db_function, "call_function", AsyncMock(side_effect=asyncio.CancelledError))
|
|
37
|
+
with pytest.raises(asyncio.CancelledError):
|
|
38
|
+
await access.platform_access_check("actor", "record", "id", raise_on_error=True)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|