matrx-orm 3.1.136__tar.gz → 3.1.138__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.136 → matrx_orm-3.1.138}/.gitignore +3 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/PKG-INFO +2 -2
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/FEATURE.md +7 -3
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/__init__.py +20 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/catalog.py +55 -54
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/FEATURE.md +6 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/expressions.py +57 -12
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/resilience.py +10 -2
- matrx_orm-3.1.138/matrx_orm/core/session_context.py +222 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/catalog_select.py +3 -1
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/query/FEATURE.md +32 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/query/executor.py +40 -3
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/README.md +21 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/coalesce.py +18 -1
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/flush.py +62 -6
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/op.py +24 -1
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/session.py +49 -34
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/pyproject.toml +2 -2
- matrx_orm-3.1.138/tests/level1/test_count_limit_param_leak.py +103 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_filter_jsonb_agg_array_index.py +52 -18
- matrx_orm-3.1.138/tests/level1/test_org_assignment_inventory.py +105 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_query_builder.py +2 -2
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_resilience.py +32 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_flush_individual_fallback.py +33 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_dynamic_select_unregistered.py +1 -1
- matrx_orm-3.1.138/tests/test_order_by_desc_nulls_last.py +61 -0
- matrx_orm-3.1.136/matrx_orm/core/session_context.py +0 -94
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/AGENTS.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/CLAUDE.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/MODEL_API.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/docs/migrations.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/postgresql.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/pooler_role.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/platform_access.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/pytest_plugin.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/host_authorization.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/state.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/scripts/release.sh +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/conftest.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_adapter_registry.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_associations_wiring.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_persisted_count.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_read_connection_retry.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_subquery_nested_expressions.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_write_queue_deadline.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_enum_ordering.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_schema_analysis.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_record_error_bounded_insert.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_record_error_storm.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/session/test_spill_guard_plugin.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_array_arg_element_type.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_keyed_digest.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_secrets_battery.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_secrets_minting_keys.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.136 → matrx_orm-3.1.138}/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.138
|
|
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.37
|
|
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
|
|
@@ -118,9 +118,13 @@
|
|
|
118
118
|
the authoritative outcome channel.
|
|
119
119
|
- 2026-08-21 — NO NULL ORG on the capture lane (db-rules §2). Both capture
|
|
120
120
|
writers now stamp `organization_id` (`_capture_org()`); `ops.system_error` and
|
|
121
|
-
`ops.system_write_failure` are `NOT NULL
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
`ops.system_write_failure` are `NOT NULL`. Their historical assignment
|
|
122
|
+
attachment is migration debt, never a valid fallback for a missing org.
|
|
123
|
+
- 2026-09-12 — Replaced the old `org_backstop_coverage` health-shaped catalog
|
|
124
|
+
helper with `organization_assignment_inventory`. It reports the eight
|
|
125
|
+
schema-qualified known assigner functions and every `organization_id` column
|
|
126
|
+
default so the release ratchet can block growth without treating an automatic
|
|
127
|
+
assignment as protection.
|
|
124
128
|
- 2026-08-21 — Fixed the `ON CONFLICT` retry counter in `_bulk_insert_failures`:
|
|
125
129
|
it qualified the target as `public.system_write_failure`, which no longer
|
|
126
130
|
exists, so every retry-conflict capture raised `UndefinedTableError` and
|
|
@@ -241,8 +241,18 @@ from .core.registry import (
|
|
|
241
241
|
from .core.relations import ManyToManyField
|
|
242
242
|
from .core.rls_session import rls_session
|
|
243
243
|
from .core.session_context import (
|
|
244
|
+
ACTOR_SYSTEM_GUC,
|
|
245
|
+
ACTOR_TIER_GUC,
|
|
246
|
+
VALID_TIERS,
|
|
247
|
+
ActorRef,
|
|
248
|
+
ActorTier,
|
|
244
249
|
SessionContextProvider,
|
|
245
250
|
configure_session_context,
|
|
251
|
+
current_actor,
|
|
252
|
+
current_tier,
|
|
253
|
+
declared_actor,
|
|
254
|
+
declared_actor_gucs,
|
|
255
|
+
declaring_actor,
|
|
246
256
|
session_context_gucs,
|
|
247
257
|
)
|
|
248
258
|
from .core.signals import (
|
|
@@ -638,7 +648,17 @@ __all__ = [
|
|
|
638
648
|
"acquire_connection",
|
|
639
649
|
"set_local_guc",
|
|
640
650
|
"SessionContextProvider",
|
|
651
|
+
"ACTOR_SYSTEM_GUC",
|
|
652
|
+
"ACTOR_TIER_GUC",
|
|
653
|
+
"VALID_TIERS",
|
|
654
|
+
"ActorRef",
|
|
655
|
+
"ActorTier",
|
|
641
656
|
"configure_session_context",
|
|
657
|
+
"current_actor",
|
|
658
|
+
"current_tier",
|
|
659
|
+
"declared_actor",
|
|
660
|
+
"declared_actor_gucs",
|
|
661
|
+
"declaring_actor",
|
|
642
662
|
"session_context_gucs",
|
|
643
663
|
"advisory_xact_lock",
|
|
644
664
|
"try_advisory_xact_lock",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from .catalog_sql import PG_CATALOG_COLUMNS_CTE, PG_CATALOG_TABLES_CTE
|
|
@@ -19,7 +20,7 @@ __all__ = [
|
|
|
19
20
|
"relation_column_counts",
|
|
20
21
|
"relation_check_constraints",
|
|
21
22
|
"relation_schemas",
|
|
22
|
-
"
|
|
23
|
+
"organization_assignment_inventory",
|
|
23
24
|
"publication_column_status",
|
|
24
25
|
"relation_privileges",
|
|
25
26
|
"role_service_state",
|
|
@@ -480,60 +481,60 @@ async def publication_column_status(
|
|
|
480
481
|
return None if not rows else bool(rows[0]["published"])
|
|
481
482
|
|
|
482
483
|
|
|
483
|
-
async def
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
""",
|
|
484
|
+
async def organization_assignment_inventory(
|
|
485
|
+
database: str,
|
|
486
|
+
*,
|
|
487
|
+
function_identities: list[tuple[str, str]],
|
|
488
|
+
) -> dict[str, list[dict[str, Any]]]:
|
|
489
|
+
"""Inventory known database organization assigners and column defaults.
|
|
490
|
+
|
|
491
|
+
This is an inventory of migration debt, never a health check. A writer
|
|
492
|
+
must provide ``organization_id`` itself; discovering one of these rows is
|
|
493
|
+
evidence to remove, not a reason to permit a missing value.
|
|
494
|
+
"""
|
|
495
|
+
schemas = [schema for schema, _ in function_identities]
|
|
496
|
+
names = [name for _, name in function_identities]
|
|
497
|
+
trigger_rows, default_rows = await asyncio.gather(
|
|
498
|
+
_query(
|
|
499
|
+
database,
|
|
500
|
+
"""
|
|
501
|
+
WITH requested AS (
|
|
502
|
+
SELECT function_schema, function_name
|
|
503
|
+
FROM unnest($1::text[], $2::text[])
|
|
504
|
+
AS requested(function_schema, function_name)
|
|
505
|
+
)
|
|
506
|
+
SELECT pns.nspname AS function_schema, p.proname AS function_name,
|
|
507
|
+
tns.nspname AS schema, c.relname AS table, t.tgname AS trigger
|
|
508
|
+
FROM pg_trigger t
|
|
509
|
+
JOIN pg_class c ON c.oid = t.tgrelid
|
|
510
|
+
JOIN pg_namespace tns ON tns.oid = c.relnamespace
|
|
511
|
+
JOIN pg_proc p ON p.oid = t.tgfoid
|
|
512
|
+
JOIN pg_namespace pns ON pns.oid = p.pronamespace
|
|
513
|
+
JOIN requested r ON r.function_schema = pns.nspname
|
|
514
|
+
AND r.function_name = p.proname
|
|
515
|
+
WHERE NOT t.tgisinternal
|
|
516
|
+
ORDER BY pns.nspname, p.proname, tns.nspname, c.relname, t.tgname
|
|
517
|
+
""",
|
|
518
|
+
schemas,
|
|
519
|
+
names,
|
|
520
|
+
),
|
|
521
|
+
_query(
|
|
522
|
+
database,
|
|
523
|
+
"""
|
|
524
|
+
SELECT n.nspname AS schema, c.relname AS table,
|
|
525
|
+
pg_get_expr(ad.adbin, ad.adrelid) AS default
|
|
526
|
+
FROM pg_attribute a
|
|
527
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
528
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
529
|
+
JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum
|
|
530
|
+
WHERE a.attname = 'organization_id'
|
|
531
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
532
|
+
AND c.relkind = ANY ('{r,p}')
|
|
533
|
+
ORDER BY n.nspname, c.relname
|
|
534
|
+
""",
|
|
535
|
+
),
|
|
536
536
|
)
|
|
537
|
+
return {"triggers": trigger_rows, "defaults": default_rows}
|
|
537
538
|
|
|
538
539
|
|
|
539
540
|
async def trigger_function_usage(
|
|
@@ -65,6 +65,12 @@ for scalar, row-set, discarded-result, and active-transaction calls. Callers can
|
|
|
65
65
|
therefore distinguish an ambiguous in-flight RPC from a pool-acquire failure and
|
|
66
66
|
must choose replay policy explicitly.
|
|
67
67
|
|
|
68
|
+
**Opt-in transient retries can be bounded and desynchronized.**
|
|
69
|
+
`retry_on_transient(..., jitter_ratio=...)` retains deterministic exponential
|
|
70
|
+
backoff by default; a caller recovering a replay-safe lock conflict can add at
|
|
71
|
+
most `jitter_ratio * base_delay` to each retry without changing its retry count
|
|
72
|
+
or exception classification.
|
|
73
|
+
|
|
68
74
|
**Read-side connection loss retries once on a fresh checkout.** A read-only SQL
|
|
69
75
|
statement is safe to repeat after `ConnectionDoesNotExistError`. The same retry
|
|
70
76
|
applies when empty-read reproof loses its connection and transaction teardown
|
|
@@ -237,13 +237,35 @@ class Q:
|
|
|
237
237
|
|
|
238
238
|
# -- Boolean operators --------------------------------------------------
|
|
239
239
|
|
|
240
|
+
@staticmethod
|
|
241
|
+
def _require_q(other: Any, op: str) -> None:
|
|
242
|
+
"""Q composes with Q; Predicate composes with Predicate. Never mixed.
|
|
243
|
+
|
|
244
|
+
``Q(a=1) | Is(F("x"), None)`` used to build a Q holding a Predicate
|
|
245
|
+
child and then die inside key validation with *\'Is\' object has no
|
|
246
|
+
attribute \'kwargs\'* — which is how GET /rag/admin/overview 500'd.
|
|
247
|
+
"""
|
|
248
|
+
if isinstance(other, Q):
|
|
249
|
+
return
|
|
250
|
+
if isinstance(other, Predicate):
|
|
251
|
+
raise TypeError(
|
|
252
|
+
f"Q {op} {type(other).__name__}: a typed Predicate cannot be "
|
|
253
|
+
"combined with Q. Express the whole condition in ONE family — "
|
|
254
|
+
'Compare(F("field"), "=", value) '
|
|
255
|
+
f"{op} {type(other).__name__}(...) — and pass that predicate "
|
|
256
|
+
"to filter()."
|
|
257
|
+
)
|
|
258
|
+
raise TypeError(f"Q {op} requires another Q, got {type(other).__name__}")
|
|
259
|
+
|
|
240
260
|
def __and__(self, other: Q) -> Q:
|
|
261
|
+
self._require_q(other, "&")
|
|
241
262
|
node = Q()
|
|
242
263
|
node.connector = self.AND
|
|
243
264
|
node.children = [self, other]
|
|
244
265
|
return node
|
|
245
266
|
|
|
246
267
|
def __or__(self, other: Q) -> Q:
|
|
268
|
+
self._require_q(other, "|")
|
|
247
269
|
node = Q()
|
|
248
270
|
node.connector = self.OR
|
|
249
271
|
node.children = [self, other]
|
|
@@ -866,9 +888,7 @@ def _render_subquery_condition(
|
|
|
866
888
|
expr_sql = value.as_sql(combined)
|
|
867
889
|
sql_operator = _SUBQUERY_COMPARISON_OPERATORS.get(operator)
|
|
868
890
|
if sql_operator is None:
|
|
869
|
-
raise ValueError(
|
|
870
|
-
f"Lookup {operator!r} is not supported with a database expression"
|
|
871
|
-
)
|
|
891
|
+
raise ValueError(f"Lookup {operator!r} is not supported with a database expression")
|
|
872
892
|
condition = f"{field_name} {sql_operator} {expr_sql}"
|
|
873
893
|
elif isinstance(value, Exists):
|
|
874
894
|
condition = value.as_sql(
|
|
@@ -879,9 +899,7 @@ def _render_subquery_condition(
|
|
|
879
899
|
else:
|
|
880
900
|
sub_sql = value.as_sql(current_table, combined)
|
|
881
901
|
condition = (
|
|
882
|
-
f"{field_name} IN {sub_sql}"
|
|
883
|
-
if operator == "in"
|
|
884
|
-
else f"{field_name} = {sub_sql}"
|
|
902
|
+
f"{field_name} IN {sub_sql}" if operator == "in" else f"{field_name} = {sub_sql}"
|
|
885
903
|
)
|
|
886
904
|
sub_params.extend(combined[already_bound:])
|
|
887
905
|
return condition
|
|
@@ -1613,9 +1631,14 @@ def _describe_literal(value: Any) -> str:
|
|
|
1613
1631
|
suffix = "," if len(value) == 1 else ""
|
|
1614
1632
|
return "(" + ", ".join(_describe_literal(item) for item in value) + suffix + ")"
|
|
1615
1633
|
if isinstance(value, dict):
|
|
1616
|
-
return
|
|
1617
|
-
|
|
1618
|
-
|
|
1634
|
+
return (
|
|
1635
|
+
"{"
|
|
1636
|
+
+ ", ".join(
|
|
1637
|
+
f"{_describe_literal(key)}: {_describe_literal(item)}"
|
|
1638
|
+
for key, item in value.items()
|
|
1639
|
+
)
|
|
1640
|
+
+ "}"
|
|
1641
|
+
)
|
|
1619
1642
|
if isinstance(value, datetime | date):
|
|
1620
1643
|
return value.isoformat()
|
|
1621
1644
|
if isinstance(value, Decimal | UUID | str | int | float | bool) or value is None:
|
|
@@ -1653,9 +1676,13 @@ def _describe_expression(value: Any) -> str:
|
|
|
1653
1676
|
def _describe_predicate(predicate: Predicate) -> str:
|
|
1654
1677
|
"""Render every built-in predicate form without SQL placeholders or object reprs."""
|
|
1655
1678
|
if isinstance(predicate, CompoundPredicate):
|
|
1656
|
-
return
|
|
1657
|
-
|
|
1658
|
-
|
|
1679
|
+
return (
|
|
1680
|
+
"("
|
|
1681
|
+
+ f" {predicate.connector} ".join(
|
|
1682
|
+
_describe_predicate(child) for child in predicate.children
|
|
1683
|
+
)
|
|
1684
|
+
+ ")"
|
|
1685
|
+
)
|
|
1659
1686
|
if isinstance(predicate, NegatedPredicate):
|
|
1660
1687
|
return f"NOT ({_describe_predicate(predicate.predicate)})"
|
|
1661
1688
|
if isinstance(predicate, Compare):
|
|
@@ -1962,6 +1989,24 @@ class Filter:
|
|
|
1962
1989
|
raise TypeError("Filter aggregate requires a typed SQL expression")
|
|
1963
1990
|
if not isinstance(condition, Predicate):
|
|
1964
1991
|
raise TypeError("Filter condition requires a typed Predicate")
|
|
1992
|
+
# Postgres attaches FILTER to an aggregate CALL and nothing else, so
|
|
1993
|
+
# ``Filter(ArrayIndex(ArrayAgg(x), 1), cond)`` emits
|
|
1994
|
+
# ``(array_agg(x))[1] FILTER (WHERE …)`` — *syntax error at or near
|
|
1995
|
+
# "FILTER"*. It shipped that way and 500'd GET /rag/admin/sources.
|
|
1996
|
+
# Refuse it here, where the remedy is obvious, instead of at the wire.
|
|
1997
|
+
# JsonbObjectAgg is also a PostgreSQL aggregate call, but it has two
|
|
1998
|
+
# positional expressions so it cannot inherit Aggregate's single-field
|
|
1999
|
+
# renderer. Keep the accepted set explicit rather than accepting any
|
|
2000
|
+
# Func wrapper (which would reintroduce invalid SQL at the wire).
|
|
2001
|
+
if not isinstance(agg, (Aggregate, JsonbObjectAgg)):
|
|
2002
|
+
raise TypeError(
|
|
2003
|
+
"Filter() attaches FILTER (WHERE ...) to an AGGREGATE CALL; "
|
|
2004
|
+
f"got {type(agg).__name__}, which Postgres rejects with "
|
|
2005
|
+
"'syntax error at or near \"FILTER\"'. Wrap the aggregate "
|
|
2006
|
+
"itself and apply the outer expression around the Filter — "
|
|
2007
|
+
"ArrayIndex(Filter(ArrayAgg(x), cond), 1), not "
|
|
2008
|
+
"Filter(ArrayIndex(ArrayAgg(x), 1), cond)."
|
|
2009
|
+
)
|
|
1965
2010
|
self.agg = agg
|
|
1966
2011
|
self.condition = condition
|
|
1967
2012
|
|
|
@@ -29,6 +29,7 @@ from __future__ import annotations
|
|
|
29
29
|
|
|
30
30
|
import asyncio
|
|
31
31
|
import logging
|
|
32
|
+
import random
|
|
32
33
|
import re
|
|
33
34
|
from typing import Awaitable, Callable, TypeVar
|
|
34
35
|
|
|
@@ -52,6 +53,7 @@ async def retry_on_transient(
|
|
|
52
53
|
*,
|
|
53
54
|
attempts: int = 3,
|
|
54
55
|
base_backoff: float = 0.25,
|
|
56
|
+
jitter_ratio: float = 0.0,
|
|
55
57
|
retry_on: tuple[type[BaseException], ...] = DEFAULT_TRANSIENT,
|
|
56
58
|
label: str = "db-unit",
|
|
57
59
|
) -> T:
|
|
@@ -59,13 +61,17 @@ async def retry_on_transient(
|
|
|
59
61
|
|
|
60
62
|
``factory`` is a zero-arg async callable invoked fresh on every attempt, so
|
|
61
63
|
it must (re)open its own transaction/connection — a rolled-back attempt
|
|
62
|
-
leaves nothing behind. Backoff is exponential from ``base_backoff``.
|
|
63
|
-
|
|
64
|
+
leaves nothing behind. Backoff is exponential from ``base_backoff``. An
|
|
65
|
+
optional positive ``jitter_ratio`` adds up to that fraction of each base
|
|
66
|
+
delay, so callers recovering lock contention need not retry in lockstep.
|
|
67
|
+
On the final attempt the original exception propagates unchanged.
|
|
64
68
|
|
|
65
69
|
Loud on every retry AND on heal, so a masked slow-query/contention problem
|
|
66
70
|
is never silent — it shows up as a WARNING you can grep, not a swallowed
|
|
67
71
|
hiccup.
|
|
68
72
|
"""
|
|
73
|
+
if jitter_ratio < 0:
|
|
74
|
+
raise ValueError("jitter_ratio must be non-negative")
|
|
69
75
|
last_exc: BaseException | None = None
|
|
70
76
|
for attempt in range(1, attempts + 1):
|
|
71
77
|
try:
|
|
@@ -91,6 +97,8 @@ async def retry_on_transient(
|
|
|
91
97
|
)
|
|
92
98
|
raise
|
|
93
99
|
backoff = base_backoff * (2 ** (attempt - 1))
|
|
100
|
+
if jitter_ratio:
|
|
101
|
+
backoff += random.uniform(0.0, backoff * jitter_ratio) # noqa: S311 -- retry desynchronization
|
|
94
102
|
logger.warning(
|
|
95
103
|
"[resilience] %s attempt %d/%d hit transient DB failure (%s) — "
|
|
96
104
|
"retrying in %.2fs (rolled back, safe to re-run).",
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""The host's DECLARED session context — GUCs every transaction opens with.
|
|
2
|
+
|
|
3
|
+
Some facts about a block of work are known by the application and readable only
|
|
4
|
+
by the database: *which tier of actor* is authoring these writes, for instance.
|
|
5
|
+
Postgres learns them through transaction-local GUCs (``set_config(..., true)``),
|
|
6
|
+
and a GUC set outside a transaction has no meaning — so the ONE place that can
|
|
7
|
+
reliably apply them is the moment this package opens a transaction.
|
|
8
|
+
|
|
9
|
+
The host declares a provider once at startup::
|
|
10
|
+
|
|
11
|
+
from matrx_orm import configure_session_context
|
|
12
|
+
configure_session_context(lambda: {"app.actor_tier": "ai", "app.actor_system": "x"})
|
|
13
|
+
|
|
14
|
+
and from then on every TOP-LEVEL ``transaction()`` (which includes the one
|
|
15
|
+
``rls_session`` opens, before it drops to the restricted role) emits whatever
|
|
16
|
+
the provider returns at that instant. A savepoint (nested transaction) emits
|
|
17
|
+
nothing: it is already inside the transaction the GUCs were set on.
|
|
18
|
+
|
|
19
|
+
Why a provider and not a value: what is true changes per task — the provider is
|
|
20
|
+
normally a ContextVar read, so a block that declares "an AI is writing this"
|
|
21
|
+
affects exactly the work inside it and nothing running beside it.
|
|
22
|
+
|
|
23
|
+
The package NEVER imports the host: no provider configured ⇒ no GUCs, which is
|
|
24
|
+
exactly how matrx-orm behaves standalone. A provider that raises is a host bug
|
|
25
|
+
and it surfaces — the transaction fails loudly rather than silently committing
|
|
26
|
+
writes whose provenance is a lie.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import logging
|
|
32
|
+
import re
|
|
33
|
+
from collections.abc import AsyncIterator, Callable, Iterator, Mapping
|
|
34
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
35
|
+
from contextvars import ContextVar
|
|
36
|
+
from dataclasses import dataclass
|
|
37
|
+
from typing import Any, Literal
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger("matrx_orm.core.session_context")
|
|
40
|
+
|
|
41
|
+
# Same shape matrx_orm.core.transaction.set_local_guc accepts.
|
|
42
|
+
_GUC_NAME_RE = re.compile(r"^[a-z_][a-z0-9_.]*$")
|
|
43
|
+
|
|
44
|
+
SessionContextProvider = Callable[[], Mapping[str, str] | None]
|
|
45
|
+
|
|
46
|
+
_provider: SessionContextProvider | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def configure_session_context(provider: SessionContextProvider | None) -> None:
|
|
50
|
+
"""Bind (or with ``None``, unbind) the host's session-context provider.
|
|
51
|
+
|
|
52
|
+
Call once at startup, alongside the other package ``configure_*`` calls.
|
|
53
|
+
``provider`` is invoked on every top-level transaction open and must return
|
|
54
|
+
a mapping of GUC name → value (or ``None`` / ``{}`` for "nothing to say").
|
|
55
|
+
"""
|
|
56
|
+
global _provider
|
|
57
|
+
if provider is not None and not callable(provider):
|
|
58
|
+
raise TypeError("configure_session_context(provider) requires a callable or None")
|
|
59
|
+
_provider = provider
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def session_context_gucs() -> dict[str, str]:
|
|
63
|
+
"""The GUCs to set on a transaction being opened right now.
|
|
64
|
+
|
|
65
|
+
Empty when no provider is configured. Raises when a configured provider
|
|
66
|
+
returns something unusable — a malformed declaration is a host defect, and
|
|
67
|
+
writing rows with a wrong (or absent) provenance stamp is worse than a loud
|
|
68
|
+
failure at BEGIN.
|
|
69
|
+
"""
|
|
70
|
+
provider = _provider
|
|
71
|
+
if provider is None:
|
|
72
|
+
return {}
|
|
73
|
+
declared = provider()
|
|
74
|
+
if not declared:
|
|
75
|
+
return {}
|
|
76
|
+
if not isinstance(declared, Mapping):
|
|
77
|
+
raise TypeError(
|
|
78
|
+
"the session-context provider must return a mapping of GUC name → value; "
|
|
79
|
+
f"got {type(declared).__name__}"
|
|
80
|
+
)
|
|
81
|
+
out: dict[str, str] = {}
|
|
82
|
+
for name, value in declared.items():
|
|
83
|
+
if not isinstance(name, str) or not _GUC_NAME_RE.match(name):
|
|
84
|
+
raise ValueError(f"invalid session-context GUC name: {name!r}")
|
|
85
|
+
out[name] = "" if value is None else str(value)
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def apply_session_context(connection: Any) -> None:
|
|
90
|
+
"""Emit the declared GUCs on ``connection`` (already inside a BEGIN)."""
|
|
91
|
+
for name, value in session_context_gucs().items():
|
|
92
|
+
await connection.execute("SELECT set_config($1, $2, true)", name, value)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# THE ACTOR DECLARATION — who authored the writes in a block.
|
|
97
|
+
#
|
|
98
|
+
# This lives HERE, beside the provider that emits it, because the declaration
|
|
99
|
+
# is only useful to a package that can never import the host: the chat door in
|
|
100
|
+
# matrx-ai declares "a person typed this row" / "the model produced this row",
|
|
101
|
+
# and matrx-orm is the common floor both the host and that package stand on.
|
|
102
|
+
# The host (aidream) re-exports these names and wires ``declared_actor_gucs``
|
|
103
|
+
# into ``configure_session_context`` at startup.
|
|
104
|
+
#
|
|
105
|
+
# CONVERGE: actor tier literal code|ai|human; target vocabulary
|
|
106
|
+
# system|agent|user - declared 2026-09-10, Data Doctrine 1.5/7.
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
ActorTier = Literal["code", "ai", "human"]
|
|
110
|
+
|
|
111
|
+
ACTOR_TIER_GUC = "app.actor_tier"
|
|
112
|
+
ACTOR_SYSTEM_GUC = "app.actor_system"
|
|
113
|
+
|
|
114
|
+
VALID_TIERS: frozenset[str] = frozenset({"code", "ai", "human"})
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True, slots=True)
|
|
118
|
+
class ActorRef:
|
|
119
|
+
"""The actor a block of DB work is attributed to."""
|
|
120
|
+
|
|
121
|
+
tier: ActorTier
|
|
122
|
+
system: str | None = None
|
|
123
|
+
|
|
124
|
+
def as_gucs(self) -> dict[str, str]:
|
|
125
|
+
"""The two session GUCs this actor implies."""
|
|
126
|
+
return {ACTOR_TIER_GUC: self.tier, ACTOR_SYSTEM_GUC: self.system or ""}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
_current_actor: ContextVar[ActorRef | None] = ContextVar("_provenance_actor", default=None)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def current_actor() -> ActorRef | None:
|
|
133
|
+
"""The actor declared by the innermost open declaration, if any."""
|
|
134
|
+
return _current_actor.get()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def current_tier() -> ActorTier | None:
|
|
138
|
+
"""The declared tier, or None when nothing declared one."""
|
|
139
|
+
actor = _current_actor.get()
|
|
140
|
+
return actor.tier if actor else None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def declared_actor_gucs() -> dict[str, str]:
|
|
144
|
+
"""The session GUCs the currently declared actor implies.
|
|
145
|
+
|
|
146
|
+
This is the provider a host passes to :func:`configure_session_context`.
|
|
147
|
+
Empty when nothing declared an actor: the DB resolver then applies its
|
|
148
|
+
documented default (``human`` with an identity present, ``code`` without)
|
|
149
|
+
and logs that it did.
|
|
150
|
+
"""
|
|
151
|
+
actor = _current_actor.get()
|
|
152
|
+
if actor is None:
|
|
153
|
+
return {}
|
|
154
|
+
return actor.as_gucs()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _validated_actor(tier: str, system: str, *, caller: str) -> ActorRef:
|
|
158
|
+
if tier not in VALID_TIERS:
|
|
159
|
+
raise ValueError(f"{caller} tier must be one of {sorted(VALID_TIERS)}; got {tier!r}")
|
|
160
|
+
if not system or not system.strip():
|
|
161
|
+
raise ValueError(f"{caller} requires a non-empty system (who specifically)")
|
|
162
|
+
return ActorRef(tier=tier, system=system.strip()) # type: ignore[arg-type]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@contextmanager
|
|
166
|
+
def declaring_actor(tier: ActorTier, system: str) -> Iterator[ActorRef]:
|
|
167
|
+
"""Synchronous form of :func:`declared_actor` - same ContextVar, no await.
|
|
168
|
+
|
|
169
|
+
A write door is often a plain function (``queue_message_create``), and the
|
|
170
|
+
thing that must carry the declaration is the SYNCHRONOUS act of queueing
|
|
171
|
+
the op: a deferred write commits later, inside a flush that ran long after
|
|
172
|
+
this block closed, so the op itself captures the actor declared here
|
|
173
|
+
(``matrx_orm.session.op``) and the flush re-declares it per row. Nothing
|
|
174
|
+
here touches a connection.
|
|
175
|
+
"""
|
|
176
|
+
actor = _validated_actor(tier, system, caller="declaring_actor")
|
|
177
|
+
token = _current_actor.set(actor)
|
|
178
|
+
try:
|
|
179
|
+
yield actor
|
|
180
|
+
finally:
|
|
181
|
+
_current_actor.reset(token)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@asynccontextmanager
|
|
185
|
+
async def declared_actor(tier: ActorTier, system: str) -> AsyncIterator[ActorRef]:
|
|
186
|
+
"""Declare the actor for the block WITHOUT opening a transaction.
|
|
187
|
+
|
|
188
|
+
The declaration is a ContextVar, so the GUCs land when the work inside
|
|
189
|
+
opens its own transaction (this module's provider is asked at every
|
|
190
|
+
top-level BEGIN, ``rls_session`` included) - or, for a write deferred onto
|
|
191
|
+
a Session, when the op that captured this declaration is executed at flush.
|
|
192
|
+
|
|
193
|
+
That is what makes it usable at a DISPATCHER, above code whose write paths
|
|
194
|
+
are its own business: ``rls_session`` refuses to nest inside an existing
|
|
195
|
+
transaction, so a chokepoint that opened one would break every handler that
|
|
196
|
+
acts as the user.
|
|
197
|
+
"""
|
|
198
|
+
actor = _validated_actor(tier, system, caller="declared_actor")
|
|
199
|
+
token = _current_actor.set(actor)
|
|
200
|
+
logger.debug("provenance: declared tier=%s system=%s", actor.tier, actor.system)
|
|
201
|
+
try:
|
|
202
|
+
yield actor
|
|
203
|
+
finally:
|
|
204
|
+
_current_actor.reset(token)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
__all__ = [
|
|
208
|
+
"ACTOR_SYSTEM_GUC",
|
|
209
|
+
"ACTOR_TIER_GUC",
|
|
210
|
+
"VALID_TIERS",
|
|
211
|
+
"ActorRef",
|
|
212
|
+
"ActorTier",
|
|
213
|
+
"SessionContextProvider",
|
|
214
|
+
"apply_session_context",
|
|
215
|
+
"configure_session_context",
|
|
216
|
+
"current_actor",
|
|
217
|
+
"current_tier",
|
|
218
|
+
"declared_actor",
|
|
219
|
+
"declared_actor_gucs",
|
|
220
|
+
"declaring_actor",
|
|
221
|
+
"session_context_gucs",
|
|
222
|
+
]
|
|
@@ -238,7 +238,9 @@ async def catalog_select(
|
|
|
238
238
|
parts = []
|
|
239
239
|
for field in order_by:
|
|
240
240
|
descending = field.startswith("-")
|
|
241
|
-
|
|
241
|
+
# "-col" = largest/newest first; a NULL is never the newest, so DESC
|
|
242
|
+
# gets NULLS LAST (Postgres' default for DESC is NULLS FIRST).
|
|
243
|
+
parts.append(f"{_quote(field.removeprefix('-'))} {'DESC NULLS LAST' if descending else 'ASC'}")
|
|
242
244
|
sql += " ORDER BY " + ", ".join(parts)
|
|
243
245
|
|
|
244
246
|
if limit is not None:
|