matrx-orm 3.1.63__tar.gz → 3.1.64__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.63 → matrx_orm-3.1.64}/PKG-INFO +1 -1
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/admin/router.py +9 -31
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/catalog.py +6 -6
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/FEATURE.md +1 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/introspection.py +2 -2
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/staging_load.py +2 -2
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/README.md +3 -1
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/lifecycle.py +3 -3
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/managed.py +45 -61
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/op.py +10 -7
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/pyproject.toml +1 -1
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_admin_db_columns.py +20 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_catalog.py +8 -6
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_insert_ignore_and_admin_primitives.py +1 -1
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_introspect_rls_policies.py +17 -1
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_managed_write_guard.py +29 -15
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_coalesce.py +1 -1
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_dag.py +1 -1
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_disk_spill_fallback.py +4 -3
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_governed_write.py +5 -5
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/.gitignore +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/AGENTS.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/CLAUDE.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/MODEL_API.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/docs/migrations.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/postgresql.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/scripts/release.sh +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/conftest.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_secrets_battery.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_sync_twins_guard.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.64
|
|
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
|
|
@@ -57,43 +57,21 @@ _COMPOSITE_ROW_ID_PREFIX = "cmp1_"
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def _model_by_table(table_name: str, schema: str | None = None) -> type | None:
|
|
60
|
-
|
|
61
|
-
model
|
|
62
|
-
for model in model_registry.all_models().values()
|
|
63
|
-
if model._meta.table_name == table_name
|
|
64
|
-
]
|
|
65
|
-
if not matches:
|
|
66
|
-
return None
|
|
67
|
-
if schema:
|
|
68
|
-
for model in matches:
|
|
69
|
-
if _model_schema(model) == schema:
|
|
70
|
-
return model
|
|
71
|
-
return None
|
|
72
|
-
if len(matches) == 1:
|
|
73
|
-
return matches[0]
|
|
74
|
-
for model in matches:
|
|
75
|
-
if _model_schema(model) == "public":
|
|
76
|
-
return model
|
|
77
|
-
return matches[0]
|
|
60
|
+
return model_registry.get_model_by_table(schema or "public", table_name)
|
|
78
61
|
|
|
79
62
|
|
|
80
63
|
def _require_model(table_name: str, schema: str | None = None) -> type:
|
|
81
64
|
model = _model_by_table(table_name, schema)
|
|
82
65
|
if model is None:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
raise HTTPException(
|
|
90
|
-
status_code=404,
|
|
91
|
-
detail=f"Table '{schema}.{table_name}' not found in model registry",
|
|
92
|
-
)
|
|
93
|
-
log.warning("Table '%s' not found in model registry", table_name)
|
|
66
|
+
effective_schema = schema or "public"
|
|
67
|
+
log.warning(
|
|
68
|
+
"Table '%s.%s' not found in model registry",
|
|
69
|
+
effective_schema,
|
|
70
|
+
table_name,
|
|
71
|
+
)
|
|
94
72
|
raise HTTPException(
|
|
95
73
|
status_code=404,
|
|
96
|
-
detail=f"Table '{table_name}' not found in model registry",
|
|
74
|
+
detail=f"Table '{effective_schema}.{table_name}' not found in model registry",
|
|
97
75
|
)
|
|
98
76
|
return model
|
|
99
77
|
|
|
@@ -176,7 +154,7 @@ def _safe_scalar(value: Any) -> str | int | float | bool | None:
|
|
|
176
154
|
"""Return value only if it is a JSON-safe scalar; drop everything else."""
|
|
177
155
|
if value is None or callable(value):
|
|
178
156
|
return None
|
|
179
|
-
if isinstance(value,
|
|
157
|
+
if isinstance(value, str | int | float | bool):
|
|
180
158
|
return value
|
|
181
159
|
return None
|
|
182
160
|
|
|
@@ -23,7 +23,7 @@ __all__ = [
|
|
|
23
23
|
"relation_privileges",
|
|
24
24
|
"role_service_state",
|
|
25
25
|
"softdelete_unique_conflicts",
|
|
26
|
-
"
|
|
26
|
+
"qualified_relation_names",
|
|
27
27
|
"trigger_function_usage",
|
|
28
28
|
"column_privileges",
|
|
29
29
|
"entity_capability_state",
|
|
@@ -258,17 +258,17 @@ async def entity_capability_state(database: str) -> list[dict[str, Any]]:
|
|
|
258
258
|
)
|
|
259
259
|
|
|
260
260
|
|
|
261
|
-
async def
|
|
262
|
-
"""Return every table/view
|
|
261
|
+
async def qualified_relation_names(database: str) -> set[str]:
|
|
262
|
+
"""Return every table/view as an exact ``schema.table`` identity."""
|
|
263
263
|
rows = await _query(
|
|
264
264
|
database,
|
|
265
265
|
"""
|
|
266
|
-
SELECT
|
|
266
|
+
SELECT DISTINCT table_schema, table_name
|
|
267
267
|
FROM information_schema.tables
|
|
268
|
-
|
|
268
|
+
ORDER BY table_schema, table_name
|
|
269
269
|
""",
|
|
270
270
|
)
|
|
271
|
-
return {
|
|
271
|
+
return {f'{row["table_schema"]}.{row["table_name"]}' for row in rows}
|
|
272
272
|
|
|
273
273
|
|
|
274
274
|
async def first_party_function_bodies(
|
|
@@ -45,5 +45,6 @@ Verified against code: 2026-08-13.
|
|
|
45
45
|
|
|
46
46
|
## Change log
|
|
47
47
|
|
|
48
|
+
- 2026-08-13: Registry, admin, recovery, catalog, and diagnostic identities are now exact `schema.table`; bare names are accepted only as uncached discovery terms and ambiguity fails loudly.
|
|
48
49
|
- 2026-08-13: Added atomic recovery from a pool left at zero connections after an upstream database restart.
|
|
49
50
|
- 2026-08-09: Pinned positional-mapping hydration for parameter-shaped column names.
|
|
@@ -80,7 +80,7 @@ WHERE table_schema = $1
|
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
async def introspect_columns(database: str, schema: str, tables: list[str]) -> dict[str, list[str]]:
|
|
83
|
-
"""Live
|
|
83
|
+
"""Live columns keyed by exact identity — ``{schema.table: [column, ...]}``.
|
|
84
84
|
|
|
85
85
|
The catalog question "what columns does this table actually have right
|
|
86
86
|
now?" belongs to the ORM: it is a fact about Postgres, not a row in an
|
|
@@ -96,7 +96,7 @@ async def introspect_columns(database: str, schema: str, tables: list[str]) -> d
|
|
|
96
96
|
rows = await AsyncDatabaseManager.execute_query(database, _COLUMNS_SQL, schema, tables)
|
|
97
97
|
out: dict[str, list[str]] = {}
|
|
98
98
|
for r in rows:
|
|
99
|
-
out.setdefault(r["table_name"], []).append(r["column_name"])
|
|
99
|
+
out.setdefault(f'{schema}.{r["table_name"]}', []).append(r["column_name"])
|
|
100
100
|
return out
|
|
101
101
|
|
|
102
102
|
|
|
@@ -355,7 +355,7 @@ async def approx_row_counts(
|
|
|
355
355
|
"""``pg_class.reltuples`` approximate row counts for a set of tables in
|
|
356
356
|
one schema — the cheap "how much data is loaded" read (exact ``COUNT(*)``
|
|
357
357
|
is too slow on large bulk-load tables; autovacuum keeps ``reltuples``
|
|
358
|
-
warm). Returns ``{
|
|
358
|
+
warm). Returns ``{schema.table: approx_rows}``; a table with no matching
|
|
359
359
|
``pg_class`` row (not created yet) is simply absent from the result."""
|
|
360
360
|
_ident(schema, what="schema")
|
|
361
361
|
for t in table_names:
|
|
@@ -370,7 +370,7 @@ async def approx_row_counts(
|
|
|
370
370
|
rows = await _fetch(
|
|
371
371
|
config_name, sql, schema, list(set(table_names)), connection=connection, pool=pool
|
|
372
372
|
)
|
|
373
|
-
return {r["table_name"]: max(0, int(r["approx_rows"])) for r in rows}
|
|
373
|
+
return {f'{schema}.{r["table_name"]}': max(0, int(r["approx_rows"])) for r in rows}
|
|
374
374
|
|
|
375
375
|
|
|
376
376
|
async def copy_records(
|
|
@@ -192,7 +192,9 @@ When `Session.flush()` raises, every dropped op is captured to
|
|
|
192
192
|
`public.system_write_failure` on a fresh asyncpg connection (bypasses
|
|
193
193
|
the failed transaction's pool slot). The recovery tool
|
|
194
194
|
(`scripts/replay_write_failures.py` in aidream) can replay them
|
|
195
|
-
later.
|
|
195
|
+
later. `table_target` is always the exact `schema.table`; a model without both
|
|
196
|
+
identity parts is rejected before capture. Replay never guesses a schema from a
|
|
197
|
+
bare name.
|
|
196
198
|
|
|
197
199
|
If the fallback ALSO fails, a red banner prints every op to stdout and
|
|
198
200
|
the structured logger captures them with full payload.
|
|
@@ -526,7 +526,7 @@ async def sweep_once(
|
|
|
526
526
|
database: str | None = None,
|
|
527
527
|
limit: int = SWEEP_LIMIT_DEFAULT,
|
|
528
528
|
) -> dict[str, int]:
|
|
529
|
-
"""One pass over every registered config. Returns ``{table: stuck_count}``."""
|
|
529
|
+
"""One pass over every registered config. Returns ``{schema.table: stuck_count}``."""
|
|
530
530
|
out: dict[str, int] = {}
|
|
531
531
|
now = datetime.now(UTC)
|
|
532
532
|
|
|
@@ -541,11 +541,11 @@ async def sweep_once(
|
|
|
541
541
|
# logger.error (NOT .exception): the in-package asyncpg traceback is
|
|
542
542
|
# noise — the banner already carries the clean reason + origin.
|
|
543
543
|
logger.error("watchdog_select_failed", extra=structured)
|
|
544
|
-
out[cfg.table] = 0
|
|
544
|
+
out[f"{cfg.schema}.{cfg.table}"] = 0
|
|
545
545
|
continue
|
|
546
546
|
|
|
547
547
|
stuck_count = len(rows)
|
|
548
|
-
out[cfg.table] = stuck_count
|
|
548
|
+
out[f"{cfg.schema}.{cfg.table}"] = stuck_count
|
|
549
549
|
if stuck_count == 0:
|
|
550
550
|
continue
|
|
551
551
|
|
|
@@ -164,30 +164,35 @@ _WRITE_TARGET_RE = re.compile(
|
|
|
164
164
|
)
|
|
165
165
|
|
|
166
166
|
|
|
167
|
-
def
|
|
168
|
-
"""
|
|
167
|
+
def _canonical_table_key(value: str) -> str:
|
|
168
|
+
"""Return an exact ``schema.table`` key for a model or SQL target."""
|
|
169
|
+
parts = [part.strip().strip('"') for part in value.strip().split(".")]
|
|
170
|
+
if len(parts) == 2 and all(parts):
|
|
171
|
+
return f"{parts[0]}.{parts[1]}"
|
|
172
|
+
raise ValueError(f"invalid table target {value!r}; expected schema.table")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _model_table_key(model_cls: type) -> str | None:
|
|
169
176
|
meta = getattr(model_cls, "_meta", None)
|
|
170
177
|
if meta is None:
|
|
171
|
-
return
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
return None
|
|
179
|
+
table = getattr(meta, "table_name", None)
|
|
180
|
+
if not table:
|
|
181
|
+
return None
|
|
182
|
+
schema = getattr(meta, "db_schema", None) or "public"
|
|
183
|
+
return _canonical_table_key(f"{schema}.{table}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _register_strict_table(model_cls: type) -> None:
|
|
187
|
+
"""Record a strict-managed Model under its exact ``schema.table`` key."""
|
|
188
|
+
if key := _model_table_key(model_cls):
|
|
189
|
+
_STRICT_TABLES.add(key)
|
|
177
190
|
|
|
178
191
|
|
|
179
192
|
def _register_coordinator_table(model_cls: type, required_scope: str) -> None:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
for name in (
|
|
184
|
-
getattr(meta, "qualified_table_name", None),
|
|
185
|
-
getattr(meta, "table_name", None),
|
|
186
|
-
):
|
|
187
|
-
if name:
|
|
188
|
-
normalized = str(name).strip('"')
|
|
189
|
-
_COORDINATOR_TABLES[normalized] = required_scope
|
|
190
|
-
_STRICT_TABLES.discard(normalized)
|
|
193
|
+
if key := _model_table_key(model_cls):
|
|
194
|
+
_COORDINATOR_TABLES[key] = required_scope
|
|
195
|
+
_STRICT_TABLES.discard(key)
|
|
191
196
|
|
|
192
197
|
|
|
193
198
|
def register_coordinator_owned_model(entry: Any) -> None:
|
|
@@ -202,16 +207,8 @@ def coordinator_policy_tables() -> dict[str, str]:
|
|
|
202
207
|
|
|
203
208
|
|
|
204
209
|
def _coordinator_scope_for_model(model_cls: type) -> str | None:
|
|
205
|
-
|
|
206
|
-
if
|
|
207
|
-
return None
|
|
208
|
-
for name in (
|
|
209
|
-
getattr(meta, "qualified_table_name", None),
|
|
210
|
-
getattr(meta, "table_name", None),
|
|
211
|
-
):
|
|
212
|
-
if name and (scope := _COORDINATOR_TABLES.get(str(name).strip('"'))):
|
|
213
|
-
return scope
|
|
214
|
-
return None
|
|
210
|
+
key = _model_table_key(model_cls)
|
|
211
|
+
return _COORDINATOR_TABLES.get(key) if key else None
|
|
215
212
|
|
|
216
213
|
|
|
217
214
|
def assert_model_write_allowed(
|
|
@@ -274,12 +271,21 @@ def assert_managed_write_allowed(query: str) -> None:
|
|
|
274
271
|
m = _WRITE_TARGET_RE.match(query)
|
|
275
272
|
if not m:
|
|
276
273
|
return
|
|
277
|
-
|
|
278
|
-
|
|
274
|
+
raw_target = m.group(1)
|
|
275
|
+
try:
|
|
276
|
+
token = _canonical_table_key(raw_target)
|
|
277
|
+
except ValueError as exc:
|
|
278
|
+
message = (
|
|
279
|
+
f"Bare write target {raw_target!r} refused at the managed-write chokepoint; "
|
|
280
|
+
"write SQL must name schema.table so guards cannot bind the wrong relation"
|
|
281
|
+
)
|
|
282
|
+
vcprint(message, color="red")
|
|
283
|
+
logger.error(message)
|
|
284
|
+
raise ManagedWriteViolation(raw_target, "write SQL without schema.table") from exc
|
|
279
285
|
|
|
280
|
-
required_scope = _COORDINATOR_TABLES.get(token)
|
|
286
|
+
required_scope = _COORDINATOR_TABLES.get(token)
|
|
281
287
|
if required_scope is not None:
|
|
282
|
-
model_cls = _model_for_coordinator_table(token
|
|
288
|
+
model_cls = _model_for_coordinator_table(token)
|
|
283
289
|
if model_cls is not None:
|
|
284
290
|
assert_model_write_allowed(model_cls, "write")
|
|
285
291
|
else:
|
|
@@ -300,7 +306,7 @@ def assert_managed_write_allowed(query: str) -> None:
|
|
|
300
306
|
except ImportError:
|
|
301
307
|
pass
|
|
302
308
|
|
|
303
|
-
if token in _STRICT_TABLES
|
|
309
|
+
if token in _STRICT_TABLES:
|
|
304
310
|
banner = _format_chokepoint_banner(token)
|
|
305
311
|
vcprint(banner, color="red")
|
|
306
312
|
logger.error(
|
|
@@ -318,24 +324,10 @@ def _active_write_scope() -> str | None:
|
|
|
318
324
|
return None
|
|
319
325
|
|
|
320
326
|
|
|
321
|
-
def _model_for_coordinator_table(token: str
|
|
327
|
+
def _model_for_coordinator_table(token: str) -> type | None:
|
|
322
328
|
from matrx_orm.core.registry import ModelRegistry
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
meta = getattr(model_cls, "_meta", None)
|
|
326
|
-
if meta is None:
|
|
327
|
-
continue
|
|
328
|
-
names = {
|
|
329
|
-
str(name).strip('"')
|
|
330
|
-
for name in (
|
|
331
|
-
getattr(meta, "qualified_table_name", None),
|
|
332
|
-
getattr(meta, "table_name", None),
|
|
333
|
-
)
|
|
334
|
-
if name
|
|
335
|
-
}
|
|
336
|
-
if token in names or bare_token in names:
|
|
337
|
-
return model_cls
|
|
338
|
-
return None
|
|
329
|
+
schema, table = token.split(".", 1)
|
|
330
|
+
return ModelRegistry.get_model_by_table(schema, table)
|
|
339
331
|
|
|
340
332
|
|
|
341
333
|
def is_strict_table(model_cls: type) -> bool:
|
|
@@ -346,16 +338,8 @@ def is_strict_table(model_cls: type) -> bool:
|
|
|
346
338
|
"""
|
|
347
339
|
if not _STRICT_TABLES:
|
|
348
340
|
return False
|
|
349
|
-
|
|
350
|
-
if
|
|
351
|
-
return False
|
|
352
|
-
for name in (
|
|
353
|
-
getattr(meta, "qualified_table_name", None),
|
|
354
|
-
getattr(meta, "table_name", None),
|
|
355
|
-
):
|
|
356
|
-
if name and str(name).strip('"') in _STRICT_TABLES:
|
|
357
|
-
return True
|
|
358
|
-
return False
|
|
341
|
+
key = _model_table_key(model_cls)
|
|
342
|
+
return key in _STRICT_TABLES if key else False
|
|
359
343
|
|
|
360
344
|
|
|
361
345
|
@asynccontextmanager
|
|
@@ -103,20 +103,23 @@ class SessionOp:
|
|
|
103
103
|
|
|
104
104
|
@property
|
|
105
105
|
def table(self) -> str:
|
|
106
|
-
"""
|
|
107
|
-
configured table name, falling back to the class name."""
|
|
106
|
+
"""Exact ``schema.table`` identity for telemetry and durable replay."""
|
|
108
107
|
meta = getattr(self.model_cls, "_meta", None)
|
|
109
108
|
if meta is not None:
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
table = getattr(meta, "table_name", None)
|
|
110
|
+
if table:
|
|
111
|
+
schema = getattr(meta, "db_schema", None) or "public"
|
|
112
|
+
return f"{schema}.{table}"
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"SessionOp model {self.model_cls.__name__} has no canonical schema.table identity"
|
|
115
|
+
)
|
|
112
116
|
|
|
113
117
|
def to_record(self) -> dict[str, Any]:
|
|
114
118
|
"""Serialize for the fallback writer.
|
|
115
119
|
|
|
116
120
|
Returns a dict that the fallback can write into system_write_failure
|
|
117
|
-
without any further introspection.
|
|
118
|
-
|
|
119
|
-
recovery tools may not have the Model class available.
|
|
121
|
+
without any further introspection. ``table_target`` is always the exact
|
|
122
|
+
``schema.table`` key so replay cannot bind a same-named table elsewhere.
|
|
120
123
|
"""
|
|
121
124
|
return {
|
|
122
125
|
"op_id": self.op_id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.64"
|
|
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" }
|
|
@@ -9,6 +9,7 @@ from matrx_orm.admin.router import (
|
|
|
9
9
|
_admin_column_descriptors,
|
|
10
10
|
_db_row_to_descriptor,
|
|
11
11
|
_decode_row_id,
|
|
12
|
+
_model_by_table,
|
|
12
13
|
_serialize,
|
|
13
14
|
)
|
|
14
15
|
|
|
@@ -27,6 +28,25 @@ def test_serialize_includes_extra_data() -> None:
|
|
|
27
28
|
assert out["role"] == "authenticated"
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
def test_admin_model_lookup_defaults_to_exact_public_key() -> None:
|
|
32
|
+
sentinel = object()
|
|
33
|
+
with patch(
|
|
34
|
+
"matrx_orm.admin.router.model_registry.get_model_by_table",
|
|
35
|
+
return_value=sentinel,
|
|
36
|
+
) as lookup:
|
|
37
|
+
assert _model_by_table("definition") is sentinel
|
|
38
|
+
lookup.assert_called_once_with("public", "definition")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_admin_model_lookup_uses_requested_schema() -> None:
|
|
42
|
+
with patch(
|
|
43
|
+
"matrx_orm.admin.router.model_registry.get_model_by_table",
|
|
44
|
+
return_value=None,
|
|
45
|
+
) as lookup:
|
|
46
|
+
assert _model_by_table("definition", "agent") is None
|
|
47
|
+
lookup.assert_called_once_with("agent", "definition")
|
|
48
|
+
|
|
49
|
+
|
|
30
50
|
def test_composite_row_identity_round_trips() -> None:
|
|
31
51
|
meta = SimpleNamespace(
|
|
32
52
|
fields={"site_id": object(), "page_id": object()},
|
|
@@ -106,17 +106,19 @@ async def test_function_access_zips_requested_functions(query_spy):
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
@pytest.mark.asyncio
|
|
109
|
-
async def
|
|
109
|
+
async def test_qualified_relation_names_normalizes_rows(monkeypatch):
|
|
110
110
|
async def execute_query(database, sql, *params):
|
|
111
111
|
return [
|
|
112
|
-
{"
|
|
113
|
-
{"
|
|
112
|
+
{"table_schema": "public", "table_name": "notes"},
|
|
113
|
+
{"table_schema": "workbench", "table_name": "notes"},
|
|
114
|
+
{"table_schema": "files", "table_name": "files"},
|
|
114
115
|
]
|
|
115
116
|
|
|
116
117
|
monkeypatch.setattr(catalog.AsyncDatabaseManager, "execute_query", execute_query)
|
|
117
|
-
assert await catalog.
|
|
118
|
-
"notes"
|
|
119
|
-
"
|
|
118
|
+
assert await catalog.qualified_relation_names("main") == {
|
|
119
|
+
"public.notes",
|
|
120
|
+
"workbench.notes",
|
|
121
|
+
"files.files",
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
|
{matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_insert_ignore_and_admin_primitives.py
RENAMED
|
@@ -339,7 +339,7 @@ async def test_approx_row_counts_shape() -> None:
|
|
|
339
339
|
out = await staging_load.approx_row_counts(
|
|
340
340
|
"db", schema="legal", table_names=["courts", "dockets"], connection=conn
|
|
341
341
|
)
|
|
342
|
-
assert out == {"courts": 1234}
|
|
342
|
+
assert out == {"legal.courts": 1234}
|
|
343
343
|
_kind, sql, params = conn.calls[0]
|
|
344
344
|
assert "pg_class" in sql and "pg_namespace" in sql
|
|
345
345
|
assert params[0] == "legal"
|
|
@@ -16,7 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
from unittest.mock import AsyncMock
|
|
17
17
|
|
|
18
18
|
import pytest
|
|
19
|
-
from matrx_orm import introspect_rls_policies
|
|
19
|
+
from matrx_orm import introspect_columns, introspect_rls_policies
|
|
20
20
|
|
|
21
21
|
pytestmark = pytest.mark.asyncio
|
|
22
22
|
|
|
@@ -77,3 +77,19 @@ async def test_empty_input_short_circuits_without_a_query(monkeypatch) -> None:
|
|
|
77
77
|
async def test_mismatched_array_lengths_raise() -> None:
|
|
78
78
|
with pytest.raises(ValueError, match="same length"):
|
|
79
79
|
await introspect_rls_policies("db", ["public", "workspace"], ["notes"])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def test_introspect_columns_returns_qualified_keys(monkeypatch) -> None:
|
|
83
|
+
mock_execute = AsyncMock(
|
|
84
|
+
return_value=[
|
|
85
|
+
{"table_name": "definition", "column_name": "id"},
|
|
86
|
+
{"table_name": "definition", "column_name": "name"},
|
|
87
|
+
]
|
|
88
|
+
)
|
|
89
|
+
monkeypatch.setattr(
|
|
90
|
+
"matrx_orm.core.introspection.AsyncDatabaseManager.execute_query", mock_execute
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
assert await introspect_columns("db", "workflow", ["definition"]) == {
|
|
94
|
+
"workflow.definition": ["id", "name"]
|
|
95
|
+
}
|
|
@@ -58,27 +58,41 @@ class TestStrictGuard:
|
|
|
58
58
|
M.assert_managed_write_allowed("UPDATE cx_message SET x=1") # no raise
|
|
59
59
|
|
|
60
60
|
def test_ungoverned_write_to_strict_table_raises(self):
|
|
61
|
-
M._STRICT_TABLES.add("cx_message")
|
|
61
|
+
M._STRICT_TABLES.add("public.cx_message")
|
|
62
62
|
with pytest.raises(ManagedWriteViolation):
|
|
63
|
-
M.assert_managed_write_allowed("UPDATE cx_message SET x=1 WHERE id=$1")
|
|
63
|
+
M.assert_managed_write_allowed("UPDATE public.cx_message SET x=1 WHERE id=$1")
|
|
64
64
|
|
|
65
|
-
def
|
|
66
|
-
M._STRICT_TABLES.add("cx_message")
|
|
67
|
-
M.assert_managed_write_allowed("UPDATE some_other_table SET x=1") # no raise
|
|
65
|
+
def test_non_strict_qualified_table_passes(self):
|
|
66
|
+
M._STRICT_TABLES.add("public.cx_message")
|
|
67
|
+
M.assert_managed_write_allowed("UPDATE public.some_other_table SET x=1") # no raise
|
|
68
68
|
|
|
69
|
-
def
|
|
70
|
-
M._STRICT_TABLES.add("cx_message")
|
|
69
|
+
def test_bare_write_target_is_refused_even_when_not_managed(self):
|
|
70
|
+
M._STRICT_TABLES.add("public.cx_message")
|
|
71
|
+
with pytest.raises(ManagedWriteViolation, match="schema.table"):
|
|
72
|
+
M.assert_managed_write_allowed("UPDATE some_other_table SET x=1")
|
|
73
|
+
|
|
74
|
+
def test_qualified_sql_matches_qualified_registration(self):
|
|
75
|
+
M._STRICT_TABLES.add("public.cx_message")
|
|
71
76
|
with pytest.raises(ManagedWriteViolation):
|
|
72
77
|
M.assert_managed_write_allowed("INSERT INTO public.cx_message (id) VALUES ($1)")
|
|
73
78
|
|
|
79
|
+
def test_same_bare_name_in_another_schema_does_not_match(self):
|
|
80
|
+
M._STRICT_TABLES.add("chat.message")
|
|
81
|
+
M.assert_managed_write_allowed("UPDATE public.message SET x=1")
|
|
82
|
+
|
|
83
|
+
def test_quoted_qualified_target_matches_exact_key(self):
|
|
84
|
+
M._STRICT_TABLES.add("chat.message")
|
|
85
|
+
with pytest.raises(ManagedWriteViolation):
|
|
86
|
+
M.assert_managed_write_allowed('UPDATE "chat"."message" SET x=1')
|
|
87
|
+
|
|
74
88
|
def test_flush_context_is_allowed(self, monkeypatch):
|
|
75
|
-
M._STRICT_TABLES.add("cx_message")
|
|
89
|
+
M._STRICT_TABLES.add("public.cx_message")
|
|
76
90
|
monkeypatch.setattr(flush_mod, "is_flushing", lambda: True)
|
|
77
|
-
M.assert_managed_write_allowed("UPDATE cx_message SET x=1") # no raise
|
|
91
|
+
M.assert_managed_write_allowed("UPDATE public.cx_message SET x=1") # no raise
|
|
78
92
|
|
|
79
93
|
def test_unparseable_query_is_allowed(self):
|
|
80
94
|
# A query we can't parse falls through (wrappers remain first defense).
|
|
81
|
-
M._STRICT_TABLES.add("cx_message")
|
|
95
|
+
M._STRICT_TABLES.add("public.cx_message")
|
|
82
96
|
M.assert_managed_write_allowed("WITH t AS (...) UPDATE cx_message ...") # no raise
|
|
83
97
|
|
|
84
98
|
|
|
@@ -91,9 +105,9 @@ class TestDeclareManagedRegistersStrictTable:
|
|
|
91
105
|
from matrx_orm.session.managed import declare_managed
|
|
92
106
|
|
|
93
107
|
declare_managed(model, mode="strict")
|
|
94
|
-
assert "strict_thing" in M._STRICT_TABLES
|
|
108
|
+
assert "public.strict_thing" in M._STRICT_TABLES
|
|
95
109
|
with pytest.raises(ManagedWriteViolation):
|
|
96
|
-
M.assert_managed_write_allowed("DELETE FROM strict_thing WHERE id=$1")
|
|
110
|
+
M.assert_managed_write_allowed("DELETE FROM public.strict_thing WHERE id=$1")
|
|
97
111
|
|
|
98
112
|
def test_elevate_declaration_does_not_register_strict(self):
|
|
99
113
|
model = _make_model(
|
|
@@ -103,8 +117,8 @@ class TestDeclareManagedRegistersStrictTable:
|
|
|
103
117
|
from matrx_orm.session.managed import declare_managed
|
|
104
118
|
|
|
105
119
|
declare_managed(model, mode="elevate")
|
|
106
|
-
assert "elevate_thing" not in M._STRICT_TABLES
|
|
107
|
-
M.assert_managed_write_allowed("UPDATE elevate_thing SET x=1") # no raise
|
|
120
|
+
assert "public.elevate_thing" not in M._STRICT_TABLES
|
|
121
|
+
M.assert_managed_write_allowed("UPDATE public.elevate_thing SET x=1") # no raise
|
|
108
122
|
|
|
109
123
|
|
|
110
124
|
class TestCoordinatorOwnedGuard:
|
|
@@ -266,7 +280,7 @@ class TestCoordinatorOwnedGuard:
|
|
|
266
280
|
{"id": UUIDField(primary_key=True), "_table_name": "owned_thing"},
|
|
267
281
|
)
|
|
268
282
|
M.declare_managed(model, mode="coordinator")
|
|
269
|
-
M.assert_managed_write_allowed("UPDATE owned_thing SET x=1")
|
|
283
|
+
M.assert_managed_write_allowed("UPDATE public.owned_thing SET x=1")
|
|
270
284
|
assert "COORDINATOR OWNERSHIP BYPASS" in caplog.text
|
|
271
285
|
|
|
272
286
|
def test_internal_flush_is_silent_without_session_stack(self, caplog, monkeypatch):
|
|
@@ -174,7 +174,7 @@ def test_summarize_counts_per_table(FakeMessage, FakeRequest):
|
|
|
174
174
|
make_insert(FakeMessage, {"id": "m2"}),
|
|
175
175
|
make_insert(FakeRequest, {"id": "r1", "conversation_id": "c1"}),
|
|
176
176
|
]
|
|
177
|
-
assert summarize(ops) == {"cx_message": 2, "cx_request": 1}
|
|
177
|
+
assert summarize(ops) == {"public.cx_message": 2, "public.cx_request": 1}
|
|
178
178
|
|
|
179
179
|
|
|
180
180
|
def test_real_world_persist_completed_request_shape(
|
|
@@ -77,7 +77,7 @@ def test_tier_summary_counts(FakeConversation, FakeMessage):
|
|
|
77
77
|
]
|
|
78
78
|
tiers = tier_ops(ops)
|
|
79
79
|
summary = tier_summary(tiers)
|
|
80
|
-
assert summary == [{"cx_conversation": 1}, {"cx_message": 2}]
|
|
80
|
+
assert summary == [{"public.cx_conversation": 1}, {"public.cx_message": 2}]
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
class _UnresolvableFK:
|
|
@@ -27,6 +27,7 @@ from matrx_orm.session.op import SessionOp
|
|
|
27
27
|
|
|
28
28
|
class _FakeMeta:
|
|
29
29
|
table_name = "cx_message"
|
|
30
|
+
db_schema = "public"
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
class _FakeModel:
|
|
@@ -50,7 +51,7 @@ def _row(op_id: str = "op-1", pk: str = "m1") -> dict:
|
|
|
50
51
|
"request_id": "req-1",
|
|
51
52
|
"user_id": None,
|
|
52
53
|
"conversation_id": None,
|
|
53
|
-
"table_target": "cx_message",
|
|
54
|
+
"table_target": "public.cx_message",
|
|
54
55
|
"op_type": "insert",
|
|
55
56
|
"primary_key": json.dumps({"id": pk}),
|
|
56
57
|
"payload": json.dumps({"id": pk, "content": "hi"}),
|
|
@@ -122,7 +123,7 @@ def test_spill_writes_one_file_per_op(spill_dir):
|
|
|
122
123
|
# Content round-trips to the exact row shape.
|
|
123
124
|
loaded = json.loads((spill_dir / "op-a.json").read_text())
|
|
124
125
|
assert loaded["op_id"] == "op-a"
|
|
125
|
-
assert loaded["table_target"] == "cx_message"
|
|
126
|
+
assert loaded["table_target"] == "public.cx_message"
|
|
126
127
|
# No temp files left behind.
|
|
127
128
|
assert not list(spill_dir.glob(".*.tmp"))
|
|
128
129
|
|
|
@@ -164,7 +165,7 @@ async def test_record_failures_spills_when_db_capture_fails(spill_dir, monkeypat
|
|
|
164
165
|
assert len(files) == 1
|
|
165
166
|
row = json.loads(files[0].read_text())
|
|
166
167
|
assert row["op_id"] == "op-outage"
|
|
167
|
-
assert row["table_target"] == "cx_message"
|
|
168
|
+
assert row["table_target"] == "public.cx_message"
|
|
168
169
|
assert row["request_id"] == "req-9"
|
|
169
170
|
|
|
170
171
|
|