matrx-orm 3.1.159__tar.gz → 3.1.161__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.159 → matrx_orm-3.1.161}/BASE_CLASS_METHODS.md +14 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/PKG-INFO +2 -2
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/__init__.py +12 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/adapters/MODULE_README.md +0 -29
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/transaction.py +22 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/query/executor.py +11 -0
- matrx_orm-3.1.161/matrx_orm/write_actor.py +234 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/pyproject.toml +2 -2
- matrx_orm-3.1.159/matrx_orm/adapters/postgresql.py +0 -167
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/.gitignore +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/AGENTS.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/CLAUDE.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/MODEL_API.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/docs/migrations.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/FEATURE.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/FEATURE.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/pooler_role.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/session_context.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/core/write_retry.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/no_org_assignment.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/catalog_select.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/platform_access.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/pytest_lock_guard.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/pytest_plugin.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/resources/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/secrets_battery/host_authorization.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/scripts/release.sh +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/conftest.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_adapter_registry.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_associations_wiring.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_committed_row_guard.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_count_limit_param_leak.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_deadlock_retry.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_lock_contention_retry.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_org_assignment_inventory.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_persisted_count.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_read_connection_retry.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_standalone_write_session_context.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_subquery_nested_expressions.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_transaction_database_identity.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_write_queue_deadline.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_enum_ordering.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_schema_analysis.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_record_error_bounded_insert.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_record_error_storm.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/session/test_spill_guard_plugin.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_array_arg_element_type.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_dynamic_select_unregistered.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_immutable_insert_or_exact_verify.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_keyed_digest.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_order_by_desc_nulls_last.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_secrets_battery.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_secrets_minting_keys.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_shared_lock_guard.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_sync_twins_guard.py +0 -0
- {matrx_orm-3.1.159 → matrx_orm-3.1.161}/tests/test_user_visible_lookup.py +0 -0
|
@@ -559,6 +559,20 @@ Tests: `packages/matrx-orm/tests/level1/test_update_case_expression.py`.
|
|
|
559
559
|
|
|
560
560
|
### Session-scoped advisory lock — `advisory_lock` / `try_advisory_lock` / `advisory_unlock`
|
|
561
561
|
|
|
562
|
+
> 🚨 **A session lock is only a lock where the connection has backend affinity.**
|
|
563
|
+
> Through a connection pooler in TRANSACTION mode (Supavisor, PgBouncer, RDS Proxy…)
|
|
564
|
+
> consecutive statements on one leased connection can land on DIFFERENT backends: the
|
|
565
|
+
> lock is released the instant its own statement ends, `pg_advisory_unlock` reports it
|
|
566
|
+
> was never held, and two independent callers BOTH acquire the same key while the code
|
|
567
|
+
> reads as correct. Measured, not theorised — one deployment reached this way billed two
|
|
568
|
+
> model calls for one answer and let two concurrent OAuth refreshes replay a one-use
|
|
569
|
+
> refresh token (2026-09-17). Prove `SELECT pg_backend_pid()` is stable across two
|
|
570
|
+
> statements on your connection before using these three functions. If it is not, guard
|
|
571
|
+
> the work with a durable claim ROW (`INSERT … ON CONFLICT DO NOTHING` plus a lease), or
|
|
572
|
+
> with `advisory_xact_lock` inside ONE `transaction()` when the guarded section fits in a
|
|
573
|
+
> single transaction — a pooler pins one backend for an explicit transaction.
|
|
574
|
+
> The tests below assert the SQL text only; no unit test can catch this.
|
|
575
|
+
|
|
562
576
|
Three top-level primitives (`matrx_orm.core.transaction`, exported from `matrx_orm`) —
|
|
563
577
|
the SESSION-scoped sibling of `advisory_xact_lock`/`try_advisory_xact_lock`. A
|
|
564
578
|
transaction-scoped lock auto-releases at COMMIT/ROLLBACK, which doesn't fit a lock held
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.161
|
|
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
|
|
@@ -25,7 +25,7 @@ Requires-Python: >=3.13
|
|
|
25
25
|
Requires-Dist: asyncpg>=0.31.0
|
|
26
26
|
Requires-Dist: cryptography>=43.0
|
|
27
27
|
Requires-Dist: gitpython>=3.1.40
|
|
28
|
-
Requires-Dist: matrx-utils>=2.0.
|
|
28
|
+
Requires-Dist: matrx-utils>=2.0.48
|
|
29
29
|
Requires-Dist: psycopg-pool>=3.2.5
|
|
30
30
|
Requires-Dist: psycopg[binary]>=3.2.5
|
|
31
31
|
Requires-Dist: python-dotenv>=1.0
|
|
@@ -264,6 +264,13 @@ from .core.session_context import (
|
|
|
264
264
|
declaring_actor,
|
|
265
265
|
session_context_gucs,
|
|
266
266
|
)
|
|
267
|
+
from .write_actor import (
|
|
268
|
+
ACTOR_REQUIRED_TABLES,
|
|
269
|
+
UndeterminedWriteActor,
|
|
270
|
+
set_write_feature_resolver,
|
|
271
|
+
write_needs_named_actor,
|
|
272
|
+
writing_system,
|
|
273
|
+
)
|
|
267
274
|
from .core.signals import (
|
|
268
275
|
Signal,
|
|
269
276
|
post_create,
|
|
@@ -685,6 +692,11 @@ __all__ = [
|
|
|
685
692
|
"declared_actor_gucs",
|
|
686
693
|
"declaring_actor",
|
|
687
694
|
"session_context_gucs",
|
|
695
|
+
"ACTOR_REQUIRED_TABLES",
|
|
696
|
+
"UndeterminedWriteActor",
|
|
697
|
+
"set_write_feature_resolver",
|
|
698
|
+
"write_needs_named_actor",
|
|
699
|
+
"writing_system",
|
|
688
700
|
"advisory_xact_lock",
|
|
689
701
|
"try_advisory_xact_lock",
|
|
690
702
|
"advisory_lock",
|
|
@@ -71,35 +71,6 @@ Filepath: src/matrx_orm/adapters/__init__.py [python]
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
---
|
|
75
|
-
Filepath: src/matrx_orm/adapters/postgresql.py [python]
|
|
76
|
-
|
|
77
|
-
class PostgreSQLAdapter(BaseAdapter):
|
|
78
|
-
def __init__(self)
|
|
79
|
-
async def _get_connection(self)
|
|
80
|
-
async def execute_query(self, query: Dict[str, Any]) -> List[Dict[str, Any]]
|
|
81
|
-
async def count(self, query: Dict[str, Any]) -> int
|
|
82
|
-
async def exists(self, query: Dict[str, Any]) -> bool
|
|
83
|
-
async def insert(self, query: Dict[str, Any]) -> Dict[str, Any]
|
|
84
|
-
async def bulk_insert(self, query: Dict[str, Any]) -> List[Dict[str, Any]]
|
|
85
|
-
async def update(self, query: Dict[str, Any], data: Dict[str, Any]) -> int
|
|
86
|
-
async def bulk_update(self, query: Dict[str, Any]) -> int
|
|
87
|
-
async def delete(self, query: Dict[str, Any]) -> int
|
|
88
|
-
async def close(self)
|
|
89
|
-
async def transaction(self)
|
|
90
|
-
async def savepoint(self, name: str)
|
|
91
|
-
async def rollback_to_savepoint(self, name: str)
|
|
92
|
-
async def release_savepoint(self, name: str)
|
|
93
|
-
def _build_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]
|
|
94
|
-
def _build_count_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]
|
|
95
|
-
def _build_exists_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]
|
|
96
|
-
def _build_insert_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]
|
|
97
|
-
def _build_bulk_insert_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]
|
|
98
|
-
def _build_update_sql(self, query: Dict[str, Any], data: Dict[str, Any]) -> Tuple[str, List[Any]]
|
|
99
|
-
def _build_bulk_update_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]
|
|
100
|
-
def _build_delete_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]
|
|
101
|
-
|
|
102
|
-
|
|
103
74
|
---
|
|
104
75
|
Filepath: src/matrx_orm/adapters/base_adapter.py [python]
|
|
105
76
|
|
|
@@ -143,6 +143,24 @@ async def advisory_lock(key: str | int, *, connection: Any) -> None:
|
|
|
143
143
|
same connection (e.g. a long-running background pass that itself opens
|
|
144
144
|
its own nested transactions).
|
|
145
145
|
|
|
146
|
+
🚨 **REQUIRES CONNECTION AFFINITY — READ THIS BEFORE USING IT.** A
|
|
147
|
+
session-scoped lock lives in the backend session, so it is only a lock if
|
|
148
|
+
every statement of the guarded section reaches the SAME backend. Through a
|
|
149
|
+
connection pooler in TRANSACTION mode (Supavisor, PgBouncer, RDS Proxy…)
|
|
150
|
+
consecutive statements on one leased connection can land on DIFFERENT
|
|
151
|
+
backends: the lock is then released the instant its own statement ends,
|
|
152
|
+
``pg_advisory_unlock`` reports it was never held, and two independent
|
|
153
|
+
callers BOTH acquire the same key while the code reads as correct. Measured,
|
|
154
|
+
not theorised — a deployment reached this way billed two model calls for one
|
|
155
|
+
answer and let two concurrent OAuth refreshes replay a one-use refresh
|
|
156
|
+
token. Before reaching for this: prove the backend pid is stable across two
|
|
157
|
+
statements on your connection (``SELECT pg_backend_pid()`` twice). If it is
|
|
158
|
+
not, this function cannot help you — guard the work with a durable claim ROW
|
|
159
|
+
(``INSERT … ON CONFLICT DO NOTHING`` plus a lease) instead, or use
|
|
160
|
+
:func:`advisory_xact_lock` INSIDE one ``transaction()`` when the whole
|
|
161
|
+
guarded section fits in a single transaction (a pooler pins one backend for
|
|
162
|
+
an explicit transaction).
|
|
163
|
+
|
|
146
164
|
Requires an explicit ``connection=`` (an asyncpg connection obtained via
|
|
147
165
|
:func:`acquire_connection`, held for the duration of the locked section) —
|
|
148
166
|
a session-scoped lock has no meaning tied to the ``transaction()``
|
|
@@ -175,6 +193,10 @@ async def try_advisory_lock(key: str | int, *, connection: Any) -> bool:
|
|
|
175
193
|
blocks/queues). Same connection/release contract as :func:`advisory_lock`
|
|
176
194
|
— pair with :func:`advisory_unlock` in a ``try/finally``.
|
|
177
195
|
|
|
196
|
+
🚨 The same connection-affinity warning as :func:`advisory_lock` applies in
|
|
197
|
+
full: through a transaction-mode pooler this returns ``True`` for TWO
|
|
198
|
+
concurrent callers on the same key.
|
|
199
|
+
|
|
178
200
|
The single-worker serialization shape: several callers race to start the
|
|
179
201
|
same background pass (e.g. one per org); the first to arrive wins and
|
|
180
202
|
runs it, the rest skip (no waiting) and let the winner's release + their
|
|
@@ -11,6 +11,7 @@ from matrx_utils import vcprint
|
|
|
11
11
|
from matrx_orm.adapters import AdapterRegistry
|
|
12
12
|
from matrx_orm.adapters.base_adapter import BaseAdapter
|
|
13
13
|
from matrx_orm.core.types import AggregateResult, UpdateResult
|
|
14
|
+
from matrx_orm.write_actor import names_its_actor
|
|
14
15
|
from matrx_orm.exceptions import (
|
|
15
16
|
DatabaseError,
|
|
16
17
|
IntegrityError,
|
|
@@ -1192,6 +1193,7 @@ class QueryExecutor:
|
|
|
1192
1193
|
)
|
|
1193
1194
|
return await self.db.execute_write_dict(query)
|
|
1194
1195
|
|
|
1196
|
+
@names_its_actor
|
|
1195
1197
|
async def insert(self, query: dict[str, Any]) -> dict[str, Any]:
|
|
1196
1198
|
"""INSERT a single row, RETURNING *."""
|
|
1197
1199
|
self._guard_writable("insert")
|
|
@@ -1236,6 +1238,7 @@ class QueryExecutor:
|
|
|
1236
1238
|
# inside another IntegrityError, producing banner-in-banner output.
|
|
1237
1239
|
raise
|
|
1238
1240
|
|
|
1241
|
+
@names_its_actor
|
|
1239
1242
|
async def bulk_insert(self, query: dict[str, Any]) -> list[Model]:
|
|
1240
1243
|
"""Bulk INSERT, RETURNING *."""
|
|
1241
1244
|
self._guard_writable("bulk_insert")
|
|
@@ -1310,6 +1313,7 @@ class QueryExecutor:
|
|
|
1310
1313
|
},
|
|
1311
1314
|
) from e
|
|
1312
1315
|
|
|
1316
|
+
@names_its_actor
|
|
1313
1317
|
async def upsert(self, query: dict[str, Any]) -> dict[str, Any]:
|
|
1314
1318
|
"""INSERT ... ON CONFLICT (...) DO UPDATE SET ... RETURNING *
|
|
1315
1319
|
|
|
@@ -1379,6 +1383,7 @@ class QueryExecutor:
|
|
|
1379
1383
|
# Already classified upstream — see note on single-insert.
|
|
1380
1384
|
raise
|
|
1381
1385
|
|
|
1386
|
+
@names_its_actor
|
|
1382
1387
|
async def bulk_upsert(self, query: dict[str, Any]) -> list[Model]:
|
|
1383
1388
|
"""Bulk INSERT ... ON CONFLICT DO UPDATE.
|
|
1384
1389
|
|
|
@@ -1524,6 +1529,7 @@ class QueryExecutor:
|
|
|
1524
1529
|
inner_where = f" WHERE {where_clause}" if where_clause else ""
|
|
1525
1530
|
return f" WHERE {pk} IN (SELECT {pk} FROM {table}{inner_where} ORDER BY {pk} FOR UPDATE)"
|
|
1526
1531
|
|
|
1532
|
+
@names_its_actor
|
|
1527
1533
|
async def update(self, **kwargs: Any) -> UpdateResult:
|
|
1528
1534
|
"""UPDATE rows matching the WHERE clause built from filters."""
|
|
1529
1535
|
self._guard_writable("update")
|
|
@@ -1655,6 +1661,11 @@ class QueryExecutor:
|
|
|
1655
1661
|
details={"operation": "update", "error": describe_db_exception(e).as_text()},
|
|
1656
1662
|
) from e
|
|
1657
1663
|
|
|
1664
|
+
# DELETE too, and not out of tidiness: a hard delete on
|
|
1665
|
+
# ``platform.associations`` fires ``_gc_assoc_harddelete``, which writes
|
|
1666
|
+
# OTHER tables inside this statement. That cascade is exactly where the
|
|
1667
|
+
# unnamed-actor refusal surfaced "nowhere near the writer that caused it".
|
|
1668
|
+
@names_its_actor
|
|
1658
1669
|
async def delete(self) -> int:
|
|
1659
1670
|
"""DELETE rows matching the WHERE clause."""
|
|
1660
1671
|
self._guard_writable("delete")
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""ONE DOOR: a write to a table that refuses an unnamed actor names one here.
|
|
2
|
+
|
|
3
|
+
🚨 THE CLASS THIS CLOSES (2026-09-17). ``platform.associations`` refuses a write
|
|
4
|
+
that declares ``actor_tier=code`` and names no system — "an AI did it" with no
|
|
5
|
+
name is not provenance — and it reads that name from ``SET LOCAL`` GUCs, which
|
|
6
|
+
exist only inside a transaction that declared them.
|
|
7
|
+
|
|
8
|
+
``68db29e50b`` put the fix on the ASSOCIATION ENGINE (``assoc.associate(...)``),
|
|
9
|
+
which covered the eleven service writers that go through it and covered nothing
|
|
10
|
+
else. Four production writers call the model straight —
|
|
11
|
+
``aidream/services/agent_data/operations.py``, ``kind_records/store.py``,
|
|
12
|
+
``kind_records/children.py``, ``google_sync/records.py`` — and the engine's own
|
|
13
|
+
``associate`` reaches the table through ``get_or_create``, a fifth method the
|
|
14
|
+
census grep for ``Associations.create`` never saw. A safe path beside an unsafe
|
|
15
|
+
one is not a fix: the door has to be removed, not signposted.
|
|
16
|
+
|
|
17
|
+
So the seam sits UNDER every one of them, at the ORM's DML floor
|
|
18
|
+
(``QueryExecutor.insert`` / ``bulk_insert`` / ``upsert`` / ``bulk_upsert`` /
|
|
19
|
+
``update``). Nothing writes a row without passing through those five methods —
|
|
20
|
+
``create``, ``bulk_create``, ``get_or_create``, ``update_or_create``, ``save``,
|
|
21
|
+
``upsert``, the managers in ``matrx_orm.extended`` and any future entry point
|
|
22
|
+
all funnel here — so there is exactly one place a write to an actor-required
|
|
23
|
+
table can happen, and it declares the actor.
|
|
24
|
+
|
|
25
|
+
WHAT IT DECLARES, AND WHY NEVER A CONSTANT
|
|
26
|
+
------------------------------------------
|
|
27
|
+
A caller that already declared an actor passes straight through, byte for byte
|
|
28
|
+
unchanged: the seam only speaks when nobody did. What it then says is the name
|
|
29
|
+
of the code ACTUALLY making the write — the running feature when the host's
|
|
30
|
+
context names one, else the calling module. There is no fallback string. The
|
|
31
|
+
first version of this walk stopped at the generic ``aidream_association_write``
|
|
32
|
+
whenever it found no ``aidream.`` frame, which satisfies the database's
|
|
33
|
+
constraint while defeating the entire point of it: the operator reading
|
|
34
|
+
``updated_by_system`` learns nothing, and a script, a package or a worker —
|
|
35
|
+
exactly the automated paths this exists for — all resolve to the same
|
|
36
|
+
meaningless word. When the writer cannot be named this raises
|
|
37
|
+
:class:`UndeterminedWriteActor` AT THE CALL SITE, naming the frame it could not
|
|
38
|
+
resolve, which is the loud failure the doctrine requires of a stand-in.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import functools
|
|
44
|
+
from collections.abc import AsyncIterator, Callable
|
|
45
|
+
from contextlib import asynccontextmanager
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
from types import FrameType
|
|
48
|
+
from typing import Any
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"ACTOR_REQUIRED_TABLES",
|
|
52
|
+
"UndeterminedWriteActor",
|
|
53
|
+
"actor_named_for_write",
|
|
54
|
+
"names_its_actor",
|
|
55
|
+
"set_write_feature_resolver",
|
|
56
|
+
"write_needs_named_actor",
|
|
57
|
+
"writing_system",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
#: ``(schema, table)`` pairs whose database-side rule refuses a write that
|
|
61
|
+
#: declares ``actor_tier=code`` and names no system. Membership is what makes
|
|
62
|
+
#: the seam speak; every other table's writes are untouched (no extra
|
|
63
|
+
#: transaction, no extra round trip).
|
|
64
|
+
ACTOR_REQUIRED_TABLES: frozenset[tuple[str, str]] = frozenset(
|
|
65
|
+
{
|
|
66
|
+
("platform", "associations"),
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
#: How long a system name may be. ``app.actor_system`` is text, but a name
|
|
71
|
+
#: longer than this is a stack artefact, not a name a person can act on.
|
|
72
|
+
_MAX_SYSTEM = 64
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class UndeterminedWriteActor(RuntimeError):
|
|
76
|
+
"""The code making this write could not be named, so nothing was written.
|
|
77
|
+
|
|
78
|
+
Deliberately fatal. The alternative — a constant like
|
|
79
|
+
``aidream_association_write`` — passes the database's check and tells the
|
|
80
|
+
operator reading the row nothing at all.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
_feature_resolver: Callable[[], str | None] | None = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def set_write_feature_resolver(resolver: Callable[[], str | None] | None) -> None:
|
|
88
|
+
"""Teach the seam how to ask the HOST which feature is running.
|
|
89
|
+
|
|
90
|
+
matrx-orm cannot import the host, and the running feature is the truthful,
|
|
91
|
+
operator-actionable answer when there is one. ``aidream`` wires
|
|
92
|
+
``try_get_app_context().source_feature`` here at startup. Unwired (a script,
|
|
93
|
+
a test, another package) the seam falls back to the calling module, which is
|
|
94
|
+
still a real name — never a constant.
|
|
95
|
+
"""
|
|
96
|
+
global _feature_resolver
|
|
97
|
+
_feature_resolver = resolver
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def write_needs_named_actor(model: Any) -> bool:
|
|
101
|
+
"""Does a write to ``model``'s table have to name its actor?"""
|
|
102
|
+
if model is None:
|
|
103
|
+
return False
|
|
104
|
+
schema = getattr(model, "_db_schema", None)
|
|
105
|
+
table = getattr(model, "_table_name", None)
|
|
106
|
+
if not schema or not table:
|
|
107
|
+
return False
|
|
108
|
+
return (str(schema), str(table)) in ACTOR_REQUIRED_TABLES
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Frames that are the plumbing, never the author. The ORM's own modules and the
|
|
112
|
+
# generic async/context machinery it is wrapped in say nothing about WHO wrote
|
|
113
|
+
# the row, so the walk steps over them to reach the first frame that does.
|
|
114
|
+
_PLUMBING_PREFIXES: tuple[str, ...] = (
|
|
115
|
+
"matrx_orm.",
|
|
116
|
+
"contextlib",
|
|
117
|
+
"asyncio.",
|
|
118
|
+
"functools",
|
|
119
|
+
)
|
|
120
|
+
_PLUMBING_MODULES: frozenset[str] = frozenset({"matrx_orm", "asyncio", "contextlib", "functools"})
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _is_plumbing(module: str) -> bool:
|
|
124
|
+
return module in _PLUMBING_MODULES or module.startswith(_PLUMBING_PREFIXES)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _name_of(frame: FrameType) -> str | None:
|
|
128
|
+
"""A name a person can act on for the code running in ``frame``.
|
|
129
|
+
|
|
130
|
+
The last TWO dotted segments of the module, because the leaf alone is a
|
|
131
|
+
coin flip: ``records`` is ``google_sync/records.py`` and four other files,
|
|
132
|
+
and an operator reading ``updated_by_system='records'`` is no better off
|
|
133
|
+
than with a constant. ``google_sync.records`` names the writer.
|
|
134
|
+
"""
|
|
135
|
+
module = str(frame.f_globals.get("__name__") or "")
|
|
136
|
+
if module and module != "__main__":
|
|
137
|
+
return ".".join(module.split(".")[-2:])[:_MAX_SYSTEM]
|
|
138
|
+
filename = str(frame.f_globals.get("__file__") or "")
|
|
139
|
+
stem = Path(filename).stem if filename else ""
|
|
140
|
+
return stem[:_MAX_SYSTEM] or None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def writing_system() -> str:
|
|
144
|
+
"""WHICH system is writing this row — read off the running program, never invented.
|
|
145
|
+
|
|
146
|
+
Raises :class:`UndeterminedWriteActor` rather than returning a placeholder.
|
|
147
|
+
"""
|
|
148
|
+
if _feature_resolver is not None:
|
|
149
|
+
try:
|
|
150
|
+
feature = _feature_resolver()
|
|
151
|
+
except Exception: # noqa: BLE001 — a broken host hook must not name the row
|
|
152
|
+
feature = None
|
|
153
|
+
if feature and str(feature).strip():
|
|
154
|
+
return str(feature).strip()[:_MAX_SYSTEM]
|
|
155
|
+
|
|
156
|
+
import sys
|
|
157
|
+
|
|
158
|
+
frame: FrameType | None = sys._getframe(1)
|
|
159
|
+
# The frame a person would look at if this refuses: the innermost one that
|
|
160
|
+
# is not the ORM itself. Reporting the OUTERMOST frame instead names
|
|
161
|
+
# `.venv/bin/pytest` or the process entry point, which is useless.
|
|
162
|
+
nearest: FrameType | None = frame
|
|
163
|
+
while frame is not None:
|
|
164
|
+
module = str(frame.f_globals.get("__name__") or "")
|
|
165
|
+
if nearest is None or str(nearest.f_globals.get("__name__") or "").startswith("matrx_orm"):
|
|
166
|
+
if not module.startswith("matrx_orm"):
|
|
167
|
+
nearest = frame
|
|
168
|
+
if not _is_plumbing(module):
|
|
169
|
+
name = _name_of(frame)
|
|
170
|
+
if name:
|
|
171
|
+
return name
|
|
172
|
+
frame = frame.f_back
|
|
173
|
+
|
|
174
|
+
where = (
|
|
175
|
+
f"{nearest.f_code.co_filename}:{nearest.f_lineno}"
|
|
176
|
+
if nearest is not None
|
|
177
|
+
else "an unreadable stack"
|
|
178
|
+
)
|
|
179
|
+
raise UndeterminedWriteActor(
|
|
180
|
+
"A write to an actor-required table could not name the code making it. "
|
|
181
|
+
f"The outermost frame the seam could read was {where}, and every frame above "
|
|
182
|
+
"it belongs to the ORM or to async plumbing. Declare the actor at the call "
|
|
183
|
+
"site — `async with actor_session('code', '<your system>'):` — rather than "
|
|
184
|
+
"letting an unnamed write reach the database, which would be refused there "
|
|
185
|
+
"anyway, at CASCADE time, nowhere near here."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@asynccontextmanager
|
|
190
|
+
async def actor_named_for_write(database: str) -> AsyncIterator[None]:
|
|
191
|
+
"""Run the block with an actor declared, if nobody declared one already.
|
|
192
|
+
|
|
193
|
+
Pass-through when ``current_actor()`` is set: a caller that named itself is
|
|
194
|
+
never second-guessed, and no transaction is opened on its behalf.
|
|
195
|
+
"""
|
|
196
|
+
from matrx_orm.core.session_context import current_actor, declaring_actor
|
|
197
|
+
from matrx_orm.core.transaction import set_local_guc, transaction
|
|
198
|
+
|
|
199
|
+
if current_actor() is not None:
|
|
200
|
+
yield
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
system = writing_system()
|
|
204
|
+
# The GUCs are transaction-local, so the declaration is only worth anything
|
|
205
|
+
# inside one. Nested, this opens a SAVEPOINT and the SET LOCAL applies to
|
|
206
|
+
# the enclosing transaction — which is correct: the enclosing work had no
|
|
207
|
+
# actor either.
|
|
208
|
+
async with transaction(database=database):
|
|
209
|
+
with declaring_actor("code", system) as actor:
|
|
210
|
+
for guc_name, guc_value in actor.as_gucs().items():
|
|
211
|
+
await set_local_guc(guc_name, guc_value)
|
|
212
|
+
yield
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def names_its_actor(method: Callable[..., Any]) -> Callable[..., Any]:
|
|
216
|
+
"""Mark a ``QueryExecutor`` DML method as passing through the seam.
|
|
217
|
+
|
|
218
|
+
Every DML method on the executor carries this, and
|
|
219
|
+
``matrx_orm/tests/test_every_write_door_names_its_actor.py`` walks the
|
|
220
|
+
executor's AST to prove a newly added one does too — a new write door is
|
|
221
|
+
the only way this class could re-open.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
@functools.wraps(method)
|
|
225
|
+
async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
|
|
226
|
+
model = getattr(self, "model", None)
|
|
227
|
+
if not write_needs_named_actor(model):
|
|
228
|
+
return await method(self, *args, **kwargs)
|
|
229
|
+
database = getattr(self, "database", None) or getattr(model, "_database", None)
|
|
230
|
+
async with actor_named_for_write(str(database)):
|
|
231
|
+
return await method(self, *args, **kwargs)
|
|
232
|
+
|
|
233
|
+
wrapper.__matrx_names_its_actor__ = True # type: ignore[attr-defined]
|
|
234
|
+
return wrapper
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.161"
|
|
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" }
|
|
@@ -41,7 +41,7 @@ dependencies = [
|
|
|
41
41
|
"gitpython>=3.1.40",
|
|
42
42
|
"psycopg[binary]>=3.2.5",
|
|
43
43
|
"psycopg-pool>=3.2.5",
|
|
44
|
-
"matrx-utils>=2.0.
|
|
44
|
+
"matrx-utils>=2.0.48",
|
|
45
45
|
"pyyaml>=6.0.3",
|
|
46
46
|
"python-dotenv>=1.0",
|
|
47
47
|
"cryptography>=43.0",
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Tuple
|
|
2
|
-
import asyncpg
|
|
3
|
-
from matrx_orm.adapters.base_adapter import BaseAdapter
|
|
4
|
-
from matrx_orm import get_database_config
|
|
5
|
-
from matrx_orm.core.tls import resolve_asyncpg_ssl
|
|
6
|
-
from matrx_utils.conf import settings
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class PostgreSQLAdapter(BaseAdapter):
|
|
10
|
-
def __init__(self):
|
|
11
|
-
self.current_database = settings.DEFAULT_DB_PROJECT
|
|
12
|
-
self.config = get_database_config(self.current_database)
|
|
13
|
-
self.connection = None
|
|
14
|
-
|
|
15
|
-
async def _get_connection(self):
|
|
16
|
-
if self.connection is None or self.connection.is_closed():
|
|
17
|
-
ssl_config = await resolve_asyncpg_ssl(self.config.get("ssl", "require"))
|
|
18
|
-
self.connection = await asyncpg.connect(
|
|
19
|
-
host=self.config["host"],
|
|
20
|
-
port=self.config["port"],
|
|
21
|
-
database=self.config["database_name"],
|
|
22
|
-
user=self.config["user"],
|
|
23
|
-
password=self.config["password"],
|
|
24
|
-
ssl=ssl_config,
|
|
25
|
-
)
|
|
26
|
-
return self.connection
|
|
27
|
-
|
|
28
|
-
async def execute_query(self, query: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
29
|
-
sql, params = self._build_sql(query)
|
|
30
|
-
connection = await self._get_connection()
|
|
31
|
-
rows = await connection.fetch(sql, *params)
|
|
32
|
-
return [dict(row) for row in rows]
|
|
33
|
-
|
|
34
|
-
async def count(self, query: Dict[str, Any]) -> int:
|
|
35
|
-
sql, params = self._build_count_sql(query)
|
|
36
|
-
connection = await self._get_connection()
|
|
37
|
-
row = await connection.fetchrow(sql, *params)
|
|
38
|
-
return row["count"]
|
|
39
|
-
|
|
40
|
-
async def exists(self, query: Dict[str, Any]) -> bool:
|
|
41
|
-
sql, params = self._build_exists_sql(query)
|
|
42
|
-
connection = await self._get_connection()
|
|
43
|
-
row = await connection.fetchrow(sql, *params)
|
|
44
|
-
return row["exists"]
|
|
45
|
-
|
|
46
|
-
async def insert(self, query: Dict[str, Any]) -> Dict[str, Any]:
|
|
47
|
-
sql, params = self._build_insert_sql(query)
|
|
48
|
-
connection = await self._get_connection()
|
|
49
|
-
row = await connection.fetchrow(sql, *params)
|
|
50
|
-
return dict(row)
|
|
51
|
-
|
|
52
|
-
async def bulk_insert(self, query: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
53
|
-
sql, params = self._build_bulk_insert_sql(query)
|
|
54
|
-
connection = await self._get_connection()
|
|
55
|
-
await connection.executemany(sql, params)
|
|
56
|
-
return await connection.fetch(sql, *params)
|
|
57
|
-
|
|
58
|
-
async def update(self, query: Dict[str, Any], data: Dict[str, Any]) -> int:
|
|
59
|
-
sql, params = self._build_update_sql(query, data)
|
|
60
|
-
connection = await self._get_connection()
|
|
61
|
-
result = await connection.execute(sql, *params)
|
|
62
|
-
return result
|
|
63
|
-
|
|
64
|
-
async def bulk_update(self, query: Dict[str, Any]) -> int:
|
|
65
|
-
sql, params = self._build_bulk_update_sql(query)
|
|
66
|
-
connection = await self._get_connection()
|
|
67
|
-
result = await connection.executemany(sql, params)
|
|
68
|
-
return result
|
|
69
|
-
|
|
70
|
-
async def delete(self, query: Dict[str, Any]) -> int:
|
|
71
|
-
sql, params = self._build_delete_sql(query)
|
|
72
|
-
connection = await self._get_connection()
|
|
73
|
-
result = await connection.execute(sql, *params)
|
|
74
|
-
return result
|
|
75
|
-
|
|
76
|
-
async def close(self):
|
|
77
|
-
if self.connection is not None and not self.connection.is_closed():
|
|
78
|
-
await self.connection.close()
|
|
79
|
-
|
|
80
|
-
async def transaction(self):
|
|
81
|
-
connection = await self._get_connection()
|
|
82
|
-
async with connection.transaction():
|
|
83
|
-
yield
|
|
84
|
-
|
|
85
|
-
async def savepoint(self, name: str):
|
|
86
|
-
connection = await self._get_connection()
|
|
87
|
-
await connection.execute(f"SAVEPOINT {name}")
|
|
88
|
-
|
|
89
|
-
async def rollback_to_savepoint(self, name: str):
|
|
90
|
-
connection = await self._get_connection()
|
|
91
|
-
await connection.execute(f"ROLLBACK TO SAVEPOINT {name}")
|
|
92
|
-
|
|
93
|
-
async def release_savepoint(self, name: str):
|
|
94
|
-
connection = await self._get_connection()
|
|
95
|
-
await connection.execute(f"RELEASE SAVEPOINT {name}")
|
|
96
|
-
|
|
97
|
-
def _build_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]:
|
|
98
|
-
# Generic SQL building logic for select statements
|
|
99
|
-
sql = f"SELECT * FROM {query['model'].__tablename__} WHERE "
|
|
100
|
-
filters = []
|
|
101
|
-
params = []
|
|
102
|
-
for field, value in query["filters"].items():
|
|
103
|
-
filters.append(f"{field} = ${len(params) + 1}")
|
|
104
|
-
params.append(value)
|
|
105
|
-
sql += " AND ".join(filters)
|
|
106
|
-
return sql, params
|
|
107
|
-
|
|
108
|
-
def _build_count_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]:
|
|
109
|
-
sql = f"SELECT COUNT(*) FROM {query['model'].__tablename__} WHERE "
|
|
110
|
-
filters = []
|
|
111
|
-
params = []
|
|
112
|
-
for field, value in query["filters"].items():
|
|
113
|
-
filters.append(f"{field} = ${len(params) + 1}")
|
|
114
|
-
params.append(value)
|
|
115
|
-
sql += " AND ".join(filters)
|
|
116
|
-
return sql, params
|
|
117
|
-
|
|
118
|
-
def _build_exists_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]:
|
|
119
|
-
sql = f"SELECT EXISTS(SELECT 1 FROM {query['model'].__tablename__} WHERE "
|
|
120
|
-
filters = []
|
|
121
|
-
params = []
|
|
122
|
-
for field, value in query["filters"].items():
|
|
123
|
-
filters.append(f"{field} = ${len(params) + 1}")
|
|
124
|
-
params.append(value)
|
|
125
|
-
sql += " AND ".join(filters) + ")"
|
|
126
|
-
return sql, params
|
|
127
|
-
|
|
128
|
-
def _build_insert_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]:
|
|
129
|
-
fields = ", ".join(query["data"].keys())
|
|
130
|
-
placeholders = ", ".join([f"${i + 1}" for i in range(len(query["data"]))])
|
|
131
|
-
sql = f"INSERT INTO {query['model'].__tablename__} ({fields}) VALUES ({placeholders}) RETURNING *"
|
|
132
|
-
params = list(query["data"].values())
|
|
133
|
-
return sql, params
|
|
134
|
-
|
|
135
|
-
def _build_bulk_insert_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]:
|
|
136
|
-
fields = ", ".join(query["data"][0].keys())
|
|
137
|
-
placeholders = ", ".join([f"${i + 1}" for i in range(len(query["data"][0]))])
|
|
138
|
-
sql = f"INSERT INTO {query['model'].__tablename__} ({fields}) VALUES ({placeholders})"
|
|
139
|
-
params = [tuple(row.values()) for row in query["data"]]
|
|
140
|
-
return sql, params
|
|
141
|
-
|
|
142
|
-
def _build_update_sql(
|
|
143
|
-
self, query: Dict[str, Any], data: Dict[str, Any]
|
|
144
|
-
) -> Tuple[str, List[Any]]:
|
|
145
|
-
set_clause = ", ".join([f"{key} = ${i + 1}" for i, key in enumerate(data.keys())])
|
|
146
|
-
sql = f"UPDATE {query['model'].__tablename__} SET {set_clause} WHERE "
|
|
147
|
-
filters = []
|
|
148
|
-
params = list(data.values())
|
|
149
|
-
for field, value in query["filters"].items():
|
|
150
|
-
filters.append(f"{field} = ${len(params) + 1}")
|
|
151
|
-
params.append(value)
|
|
152
|
-
sql += " AND ".join(filters)
|
|
153
|
-
return sql, params
|
|
154
|
-
|
|
155
|
-
def _build_bulk_update_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]:
|
|
156
|
-
# Similar to update but for bulk updates
|
|
157
|
-
raise NotImplementedError("Bulk update logic to be implemented")
|
|
158
|
-
|
|
159
|
-
def _build_delete_sql(self, query: Dict[str, Any]) -> Tuple[str, List[Any]]:
|
|
160
|
-
sql = f"DELETE FROM {query['model'].__tablename__} WHERE "
|
|
161
|
-
filters = []
|
|
162
|
-
params = []
|
|
163
|
-
for field, value in query["filters"].items():
|
|
164
|
-
filters.append(f"{field} = ${len(params) + 1}")
|
|
165
|
-
params.append(value)
|
|
166
|
-
sql += " AND ".join(filters)
|
|
167
|
-
return sql, params
|
|
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
|