matrx-orm 3.1.232__tar.gz → 3.1.233__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.232 → matrx_orm-3.1.233}/.gitignore +1 -1
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/CLAUDE.md +5 -1
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/PKG-INFO +2 -2
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/catalog_select.py +99 -3
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/state.py +60 -26
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/pyproject.toml +2 -2
- matrx_orm-3.1.233/tests/test_a_bulk_cache_never_stalls_the_loop.py +113 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_dynamic_select_unregistered.py +67 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/AGENTS.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/CHANGELOG.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/MODEL_API.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/docs/migrations.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/FEATURE.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/admin/FEATURE.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/catalog_sql.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/FEATURE.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/admin_lane.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/foreign_session_state.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/one_database.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/pool_watch.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/pooler_role.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/read_write.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/session_context.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/statement_ceiling.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/core/write_retry.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/local_sqlite.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/no_org_assignment.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/FEATURE.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/operations/update.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/platform_access.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/platform_db.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/pytest_lock_guard.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/pytest_plugin.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/query/FEATURE.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/query/builder.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/resources/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/resources/additional-assigners-2026-09-12.json +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/resources/org_assignment_trigger_baseline.json +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/FEATURE.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/excluded_targets.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/attachments.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/backup_restore.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/host_authorization.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/mutation_receipts.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/native_enable.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/native_lifecycle.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/native_passkeys.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/native_source.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/org_vault_gate.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/secrets_battery/vault_history.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/matrx_orm/write_actor.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/scripts/release.sh +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/conftest.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_adapter_registry.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_admin_response_rendering.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_association_trigger_actor_naming.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_associations_wiring.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_bulk_hydration_offload.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_column_scoped_writes.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_committed_row_guard.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_count_limit_param_leak.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_deadlock_retry.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_fk_cycle_resolution.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_local_sqlite.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_lock_contention_retry.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_optimistic_concurrency.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_org_assignment_inventory.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_orm_banners_are_client_safe.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_parameter_name_hydration.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_percentile_cont.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_persisted_count.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_person_scoped_session_bypasses_shared_cache.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_pool_create_never_wedges.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_pool_holder_census.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_read_connection_retry.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_read_write_declared.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_rls_session_scope.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_standalone_write_session_context.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_subquery_nested_expressions.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_transaction_database_identity.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_transaction_gucs_one_round_trip.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_transaction_pool_release_sends_nothing.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_update_with_rebase.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_write_queue_deadline.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/level2/verify_committed_row_guard_pg17.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/live_pooler_role.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/native_source_cases.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/native_source_oracle.rs +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_enum_ordering.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_excluded_fk_targets.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_schema_analysis.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_table_relationships_query.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema_builder/test_array_default_parsing.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema_builder/test_enum_array_column.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema_builder/test_host_models_aggregator.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/schema_builder/test_never_filter_columns.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_a_failed_capture_still_names_the_failure.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_cache_no_downgrade.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_record_error_bounded_insert.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_record_error_storm.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/session/test_spill_guard_plugin.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_a_backend_handed_over_mid_transaction_never_takes_our_writes.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_a_keyword_column_refuses_generation.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_a_list_of_uuids_binds_as_uuid_array.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_a_read_only_backend_never_refuses_our_writes.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_a_sealed_field_shows_no_character_of_its_value.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_array_arg_element_type.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_attachment_mutation_serialization.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_backup_alias_conflict.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_backup_history_restore.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_backup_restore_live.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_call_function_as.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_call_function_none_mode.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_composite_fk_is_not_a_column_fk.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_dsn_password_masking.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_foreign_session_state.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_immutable_insert_or_exact_verify.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_keyed_digest.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_native_enable.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_native_lifecycle.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_native_lifecycle_fences.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_native_passkey_receipts.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_native_source.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_one_database.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_order_by_desc_nulls_last.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_org_vault_gate.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_package_wiring_views.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_pooler_role_guard.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_secret_audit_names_its_organization.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_secrets_battery.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_secrets_minting_keys.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_shared_lock_guard.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_state_cache_sweeps_stale.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_statement_ceiling_warning.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_sync_twins_guard.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_update_result_truthiness.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_user_visible_lookup.py +0 -0
- {matrx_orm-3.1.232 → matrx_orm-3.1.233}/tests/test_vault_aggregate_recovery_stateful.py +0 -0
|
@@ -164,7 +164,11 @@ enforced in code:
|
|
|
164
164
|
`bulk_create` (or uses `insert_returning`) so its rows are neither read back
|
|
165
165
|
nor cached. Before this an unread entry lived for the whole process, and a
|
|
166
166
|
workflow worker's checkpoints/outcomes/events grew it until ECS killed the
|
|
167
|
-
task for memory (MKT-111, 2026-10-15).
|
|
167
|
+
task for memory (MKT-111, 2026-10-15). The sweep pops oldest-first and stops at
|
|
168
|
+
the first fresh entry, and `cache_bulk` yields the loop once per sweep window, so
|
|
169
|
+
caching a big read never stalls the server (a full-scan sweep froze it 2-6 s on a
|
|
170
|
+
21k-row read, 2026-10-17). Guards: `tests/test_state_cache_sweeps_stale.py`,
|
|
171
|
+
`tests/test_a_bulk_cache_never_stalls_the_loop.py`.
|
|
168
172
|
|
|
169
173
|
This is not theoretical hygiene. On 2026-07-25 rule 1 was missing: `session/flush.py`
|
|
170
174
|
builds a `{pk + payload}` stub per queued UPDATE and `bulk_update` cached it, so a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.233
|
|
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
|
|
@@ -26,7 +26,7 @@ Requires-Dist: asyncpg>=0.31.0
|
|
|
26
26
|
Requires-Dist: cbor2<7,>=6.1.3
|
|
27
27
|
Requires-Dist: cryptography>=43.0
|
|
28
28
|
Requires-Dist: gitpython>=3.1.40
|
|
29
|
-
Requires-Dist: matrx-utils>=2.0.
|
|
29
|
+
Requires-Dist: matrx-utils>=2.0.75
|
|
30
30
|
Requires-Dist: psycopg-pool>=3.2.5
|
|
31
31
|
Requires-Dist: psycopg[binary]>=3.2.5
|
|
32
32
|
Requires-Dist: python-dotenv>=1.0
|
|
@@ -58,6 +58,65 @@ class UnregisteredRelationError(ValueError):
|
|
|
58
58
|
"""
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
# JSON path selections on a json/jsonb column, e.g. ``provider_entry->>version``.
|
|
62
|
+
#
|
|
63
|
+
# Accepted forms (the ONLY ones; anything else is refused naming this list):
|
|
64
|
+
# col->key jsonb value col->>key text value
|
|
65
|
+
# col->k1->k2->>k3 chained steps col#>{k1,k2} jsonb at path
|
|
66
|
+
# col#>>{k1,k2} text at path (keys may be quoted: col->>'a-b')
|
|
67
|
+
# A key is letters, digits, ``_``, ``-`` or ``.``; an all-digit key is an array
|
|
68
|
+
# index. Keys are NEVER interpolated: they travel as one bound ``text[]`` parameter
|
|
69
|
+
# to ``#>`` / ``#>>``, and the column itself must be a live json/jsonb column.
|
|
70
|
+
JSON_PATH_FORMS = (
|
|
71
|
+
"col->key, col->>key, col->k1->k2->>k3, col#>{k1,k2}, col#>>{k1,k2} "
|
|
72
|
+
"(col must be a json/jsonb column; keys are letters, digits, _ - .)"
|
|
73
|
+
)
|
|
74
|
+
_BARE_KEY = r"[A-Za-z0-9_.]+(?:-(?!>)[A-Za-z0-9_.]+)*" # a "-" inside a key, never the "->" arrow
|
|
75
|
+
_JSON_KEY = rf"(?:'{_BARE_KEY}'|{_BARE_KEY})"
|
|
76
|
+
_JSON_ARROWS = re.compile(
|
|
77
|
+
rf"^(?P<col>[A-Za-z_][A-Za-z0-9_$]*)(?P<steps>(?:->>?{_JSON_KEY})+)$"
|
|
78
|
+
)
|
|
79
|
+
_JSON_HASH = re.compile(
|
|
80
|
+
rf"^(?P<col>[A-Za-z_][A-Za-z0-9_$]*)(?P<op>#>>?)\{{(?P<keys>{_JSON_KEY}(?:,{_JSON_KEY})*)\}}$"
|
|
81
|
+
)
|
|
82
|
+
_JSON_STEP = re.compile(rf"(->>?)({_JSON_KEY})")
|
|
83
|
+
_JSON_TYPES = {"json", "jsonb"}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def looks_like_json_path(spec: str) -> bool:
|
|
87
|
+
return "->" in spec or "#>" in spec
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def parse_json_path(spec: str) -> tuple[str, bool, list[str]]:
|
|
91
|
+
"""``spec`` -> ``(column, as_text, keys)`` or raise naming the accepted forms."""
|
|
92
|
+
text = spec.strip()
|
|
93
|
+
m = _JSON_HASH.match(text)
|
|
94
|
+
if m:
|
|
95
|
+
keys = [k.strip("'") for k in m.group("keys").split(",")]
|
|
96
|
+
return m.group("col"), m.group("op") == "#>>", keys
|
|
97
|
+
m = _JSON_ARROWS.match(text)
|
|
98
|
+
if m:
|
|
99
|
+
steps = _JSON_STEP.findall(m.group("steps"))
|
|
100
|
+
# ``->>`` returns text, so it can only be the LAST step.
|
|
101
|
+
if any(op == "->>" for op, _ in steps[:-1]):
|
|
102
|
+
raise UnregisteredRelationError(
|
|
103
|
+
f"Field {spec!r}: ->> returns text, so it can only be the last step. "
|
|
104
|
+
f"Accepted JSON path forms: {JSON_PATH_FORMS}."
|
|
105
|
+
)
|
|
106
|
+
return m.group("col"), steps[-1][0] == "->>", [k.strip("'") for _, k in steps]
|
|
107
|
+
raise UnregisteredRelationError(
|
|
108
|
+
f"Field {spec!r} is not a column or a supported JSON path. "
|
|
109
|
+
f"Accepted JSON path forms: {JSON_PATH_FORMS}."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _quote_alias(spec: str) -> str:
|
|
114
|
+
"""Quote a validated JSON-path spec as the result column's name."""
|
|
115
|
+
if '"' in spec or "\x00" in spec:
|
|
116
|
+
raise UnregisteredRelationError(f"Unsafe field name: {spec!r}")
|
|
117
|
+
return f'"{spec}"'
|
|
118
|
+
|
|
119
|
+
|
|
61
120
|
def _quote(ident: str) -> str:
|
|
62
121
|
if not _SAFE_IDENT.match(ident):
|
|
63
122
|
raise UnregisteredRelationError(f"Unsafe SQL identifier: {ident!r}")
|
|
@@ -98,6 +157,13 @@ async def resolve_relation(table: str, *, database: str | None = None) -> tuple[
|
|
|
98
157
|
)
|
|
99
158
|
|
|
100
159
|
|
|
160
|
+
async def _describe(schema: str, name: str, database: str) -> list[dict[str, Any]]:
|
|
161
|
+
from matrx_orm.catalog import describe_relation_columns
|
|
162
|
+
|
|
163
|
+
rows = await describe_relation_columns(database, schema=schema, table=name)
|
|
164
|
+
return [r for r in rows if isinstance(r, dict) and r.get("column_name")]
|
|
165
|
+
|
|
166
|
+
|
|
101
167
|
async def live_columns(schema: str, name: str, *, database: str | None = None) -> list[str]:
|
|
102
168
|
"""Ordered live column names for a relation, straight from pg_catalog."""
|
|
103
169
|
from matrx_orm.catalog import describe_relation_columns
|
|
@@ -202,7 +268,9 @@ async def catalog_select(
|
|
|
202
268
|
raise UnregisteredRelationError(f'relation "{schema}.{name}" does not exist')
|
|
203
269
|
live_set = set(live)
|
|
204
270
|
|
|
205
|
-
|
|
271
|
+
requested_all = [c for c in columns if c != "*"]
|
|
272
|
+
json_specs = {c: parse_json_path(c) for c in requested_all if looks_like_json_path(c)}
|
|
273
|
+
requested = [c for c in requested_all if c not in json_specs]
|
|
206
274
|
order_fields = [f.removeprefix("-") for f in order_by]
|
|
207
275
|
filter_fields = [getattr(f, "field", "") for f in filters]
|
|
208
276
|
unknown = [
|
|
@@ -211,7 +279,27 @@ async def catalog_select(
|
|
|
211
279
|
if c and c not in live_set
|
|
212
280
|
]
|
|
213
281
|
if unknown:
|
|
214
|
-
|
|
282
|
+
err = _unknown_columns_error(schema, name, unknown, live, registered_fields)
|
|
283
|
+
if any(looks_like_json_path(u) for u in unknown):
|
|
284
|
+
raise UnregisteredRelationError(
|
|
285
|
+
f"{err}\nJSON paths are accepted in `fields` only (not match/order_by). "
|
|
286
|
+
f"Accepted forms: {JSON_PATH_FORMS}."
|
|
287
|
+
)
|
|
288
|
+
raise err
|
|
289
|
+
if json_specs:
|
|
290
|
+
type_by_col = {
|
|
291
|
+
str(r["column_name"]): str(r.get("data_type") or "").lower()
|
|
292
|
+
for r in await _describe(schema, name, db)
|
|
293
|
+
}
|
|
294
|
+
for spec, (col, _as_text, _keys) in json_specs.items():
|
|
295
|
+
if col not in live_set:
|
|
296
|
+
raise _unknown_columns_error(schema, name, [col], live, registered_fields)
|
|
297
|
+
dtype = type_by_col.get(col)
|
|
298
|
+
if dtype and dtype not in _JSON_TYPES:
|
|
299
|
+
raise UnregisteredRelationError(
|
|
300
|
+
f"Field {spec!r}: column {col!r} is {dtype}, not json/jsonb, so a JSON "
|
|
301
|
+
f"path cannot be taken from it. Accepted JSON path forms: {JSON_PATH_FORMS}."
|
|
302
|
+
)
|
|
215
303
|
|
|
216
304
|
params: list[Any] = []
|
|
217
305
|
|
|
@@ -219,7 +307,15 @@ async def catalog_select(
|
|
|
219
307
|
params.append(value)
|
|
220
308
|
return f"${len(params)}"
|
|
221
309
|
|
|
222
|
-
|
|
310
|
+
parts_select: list[str] = []
|
|
311
|
+
for c in requested_all:
|
|
312
|
+
if c in json_specs:
|
|
313
|
+
col, as_text, keys = json_specs[c]
|
|
314
|
+
op = "#>>" if as_text else "#>"
|
|
315
|
+
parts_select.append(f"{_quote(col)} {op} {next_param(keys)}::text[] AS {_quote_alias(c)}")
|
|
316
|
+
else:
|
|
317
|
+
parts_select.append(_quote(c))
|
|
318
|
+
select_list = ", ".join(parts_select) if parts_select else "*"
|
|
223
319
|
sql = f"SELECT {select_list} FROM {_quote(schema)}.{_quote(name)}"
|
|
224
320
|
|
|
225
321
|
predicates = [
|
|
@@ -269,25 +269,33 @@ class ModelState:
|
|
|
269
269
|
)
|
|
270
270
|
return matches[0] if matches else None
|
|
271
271
|
|
|
272
|
+
def _stale_before(self, now: datetime | None = None) -> datetime | None:
|
|
273
|
+
"""The cache time an entry must be newer than to be fresh; None = never stale.
|
|
274
|
+
|
|
275
|
+
One model has ONE age rule, so staleness is a pure function of the
|
|
276
|
+
entry's cache time — which is what lets :meth:`sweep_stale` stop at the
|
|
277
|
+
first fresh entry instead of re-checking every row.
|
|
278
|
+
"""
|
|
279
|
+
now = now or datetime.now()
|
|
280
|
+
if self.timeout is not None:
|
|
281
|
+
return now - timedelta(seconds=self.timeout)
|
|
282
|
+
if self.policy == CachePolicy.PERMANENT:
|
|
283
|
+
return None
|
|
284
|
+
if self.policy == CachePolicy.LONG_TERM:
|
|
285
|
+
return now - timedelta(hours=4)
|
|
286
|
+
if self.policy == CachePolicy.SHORT_TERM:
|
|
287
|
+
return now - timedelta(minutes=10)
|
|
288
|
+
if self.policy == CachePolicy.INSTANT:
|
|
289
|
+
return now - timedelta(minutes=1)
|
|
290
|
+
return datetime.max
|
|
291
|
+
|
|
272
292
|
def _is_stale(self, record):
|
|
273
293
|
cache_key = self._get_record_cache_key(record)
|
|
274
294
|
cache_time = self._cache_times.get(cache_key)
|
|
275
295
|
if not cache_time:
|
|
276
296
|
return True
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return (datetime.now() - cache_time).total_seconds() > self.timeout
|
|
280
|
-
|
|
281
|
-
now = datetime.now()
|
|
282
|
-
if self.policy == CachePolicy.PERMANENT:
|
|
283
|
-
return False
|
|
284
|
-
elif self.policy == CachePolicy.LONG_TERM:
|
|
285
|
-
return now - cache_time > timedelta(hours=4)
|
|
286
|
-
elif self.policy == CachePolicy.SHORT_TERM:
|
|
287
|
-
return now - cache_time > timedelta(minutes=10)
|
|
288
|
-
elif self.policy == CachePolicy.INSTANT:
|
|
289
|
-
return now - cache_time > timedelta(minutes=1)
|
|
290
|
-
return True
|
|
297
|
+
cutoff = self._stale_before()
|
|
298
|
+
return cutoff is not None and cache_time < cutoff
|
|
291
299
|
|
|
292
300
|
async def cache(self, record):
|
|
293
301
|
"""Cache a record with proper error handling.
|
|
@@ -336,7 +344,7 @@ class ModelState:
|
|
|
336
344
|
return
|
|
337
345
|
|
|
338
346
|
self._cache[cache_key] = record
|
|
339
|
-
self.
|
|
347
|
+
self._stamp(cache_key)
|
|
340
348
|
self._writes_since_sweep = getattr(self, "_writes_since_sweep", 0) + 1
|
|
341
349
|
if self._writes_since_sweep >= STALE_SWEEP_EVERY_WRITES:
|
|
342
350
|
self.sweep_stale()
|
|
@@ -356,6 +364,15 @@ class ModelState:
|
|
|
356
364
|
original_error=e,
|
|
357
365
|
) from e
|
|
358
366
|
|
|
367
|
+
def _stamp(self, cache_key) -> None:
|
|
368
|
+
"""Record ``cache_key`` as cached NOW, at the END of ``_cache_times``.
|
|
369
|
+
|
|
370
|
+
``_cache_times`` is kept in cache-time order (a re-cache moves the key to
|
|
371
|
+
the end), so the oldest entries are always at the front.
|
|
372
|
+
"""
|
|
373
|
+
self._cache_times.pop(cache_key, None)
|
|
374
|
+
self._cache_times[cache_key] = datetime.now()
|
|
375
|
+
|
|
359
376
|
def sweep_stale(self) -> int:
|
|
360
377
|
"""Drop every entry its policy already treats as stale. Returns how many.
|
|
361
378
|
|
|
@@ -366,18 +383,30 @@ class ModelState:
|
|
|
366
383
|
cache) accumulated for the life of the process, multi-megabyte JSON
|
|
367
384
|
included, until ECS killed the task for memory. A stale entry is
|
|
368
385
|
already a miss, so dropping it changes no read.
|
|
386
|
+
|
|
387
|
+
THE STALL THIS CLOSES (2026-10-17): the first version re-checked EVERY
|
|
388
|
+
entry on every sweep. A sweep runs every ``STALE_SWEEP_EVERY_WRITES``
|
|
389
|
+
writes, so one bulk read of N fresh rows did N/200 full scans — the AI
|
|
390
|
+
catalog's 21,677-row rule-cell reload held the event loop 2-6 s and
|
|
391
|
+
froze every request on the server. ``_cache_times`` is in cache-time
|
|
392
|
+
order and one model has one age rule, so the sweep pops from the front
|
|
393
|
+
and stops at the first fresh entry: its cost is what it drops, never
|
|
394
|
+
what is cached.
|
|
369
395
|
"""
|
|
370
396
|
self._writes_since_sweep = 0
|
|
397
|
+
cutoff = self._stale_before()
|
|
398
|
+
times = self._cache_times
|
|
371
399
|
dropped = 0
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
400
|
+
while times:
|
|
401
|
+
key = next(iter(times))
|
|
402
|
+
if key in self._cache and (cutoff is None or times[key] >= cutoff):
|
|
403
|
+
break
|
|
404
|
+
times.pop(key, None)
|
|
405
|
+
self._cache.pop(key, None)
|
|
406
|
+
lock = self._locks.get(key)
|
|
407
|
+
if lock is not None and not lock.locked():
|
|
408
|
+
self._locks.pop(key, None)
|
|
409
|
+
dropped += 1
|
|
381
410
|
return dropped
|
|
382
411
|
|
|
383
412
|
def _downgraded_fields(self, existing, incoming) -> list[str]:
|
|
@@ -419,7 +448,7 @@ class ModelState:
|
|
|
419
448
|
record = self.model_class.from_db_result(data)
|
|
420
449
|
cache_key = self._get_record_cache_key(record)
|
|
421
450
|
self._cache[cache_key] = record
|
|
422
|
-
self.
|
|
451
|
+
self._stamp(cache_key)
|
|
423
452
|
|
|
424
453
|
def get_all_cached(self):
|
|
425
454
|
"""Returns all cached records as a list."""
|
|
@@ -620,7 +649,7 @@ class StateManager:
|
|
|
620
649
|
reason="Model not registered with StateManager",
|
|
621
650
|
)
|
|
622
651
|
|
|
623
|
-
for record in records:
|
|
652
|
+
for index, record in enumerate(records, start=1):
|
|
624
653
|
try:
|
|
625
654
|
pk_kwargs = {pk: getattr(record, pk) for pk in record._meta.primary_keys}
|
|
626
655
|
if not await state.get(**pk_kwargs):
|
|
@@ -628,6 +657,11 @@ class StateManager:
|
|
|
628
657
|
except Exception as e:
|
|
629
658
|
vcprint(f"Failed to cache record: {str(e)}", color="yellow")
|
|
630
659
|
continue
|
|
660
|
+
# An uncontended lock never yields, so a 20k-row read cached in
|
|
661
|
+
# one go held the loop for its whole length. Hand the loop back
|
|
662
|
+
# once per sweep window: other requests run between chunks.
|
|
663
|
+
if index % STALE_SWEEP_EVERY_WRITES == 0:
|
|
664
|
+
await asyncio.sleep(0)
|
|
631
665
|
except StateError:
|
|
632
666
|
raise
|
|
633
667
|
except Exception as e:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.233"
|
|
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.75",
|
|
45
45
|
"pyyaml>=6.0.3",
|
|
46
46
|
"python-dotenv>=1.0",
|
|
47
47
|
"cryptography>=43.0",
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""A bulk read never freezes the server while its rows are cached (2026-10-17).
|
|
2
|
+
|
|
3
|
+
The stale sweep (MKT-111) re-checked EVERY cached entry each time it ran, and it ran
|
|
4
|
+
every ``STALE_SWEEP_EVERY_WRITES`` writes — so caching N fresh rows did N/200 full
|
|
5
|
+
scans. The AI catalog's 21,677-row rule-cell reload held the event loop 2-6 s in
|
|
6
|
+
production (ops.system_error EventLoopStall, frame ``state.py:342 sweep_stale``) and
|
|
7
|
+
froze every request on the server. ``cache_bulk`` also never yielded, so even a
|
|
8
|
+
linear pass over 20k rows held the loop for its whole length.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import types
|
|
15
|
+
from datetime import datetime, timedelta
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from matrx_orm import state as state_module
|
|
20
|
+
from matrx_orm.state import CachePolicy, ModelState, StateManager
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _model(policy: CachePolicy, name: str):
|
|
24
|
+
meta = types.SimpleNamespace(primary_keys=["id"])
|
|
25
|
+
return type(
|
|
26
|
+
name,
|
|
27
|
+
(),
|
|
28
|
+
{
|
|
29
|
+
"_cache_policy": policy,
|
|
30
|
+
"_fields": {},
|
|
31
|
+
"_meta": meta,
|
|
32
|
+
"get_database_name": staticmethod(lambda: "bulk-cache-test"),
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _record(model, i: int) -> object:
|
|
38
|
+
rec = types.SimpleNamespace(id=f"row-{i}")
|
|
39
|
+
rec._meta = model._meta
|
|
40
|
+
return rec
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.asyncio
|
|
44
|
+
async def test_caching_fresh_rows_costs_one_key_per_row(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
45
|
+
model = _model(CachePolicy.SHORT_TERM, "_LinearModel")
|
|
46
|
+
state = ModelState(model)
|
|
47
|
+
calls = 0
|
|
48
|
+
real = ModelState._get_record_cache_key
|
|
49
|
+
|
|
50
|
+
def counting(self, record):
|
|
51
|
+
nonlocal calls
|
|
52
|
+
calls += 1
|
|
53
|
+
return real(self, record)
|
|
54
|
+
|
|
55
|
+
monkeypatch.setattr(ModelState, "_get_record_cache_key", counting)
|
|
56
|
+
n = 4000
|
|
57
|
+
for i in range(n):
|
|
58
|
+
await state.cache(_record(model, i))
|
|
59
|
+
assert state.count() == n
|
|
60
|
+
# The quadratic sweep made this ~n + n*n/(2*200) = 44,000; the ordered sweep is n.
|
|
61
|
+
assert calls <= 2 * n, f"{calls} per-row checks to cache {n} fresh rows"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.asyncio
|
|
65
|
+
async def test_bulk_cache_hands_the_loop_back() -> None:
|
|
66
|
+
model = _model(CachePolicy.SHORT_TERM, "_YieldModel")
|
|
67
|
+
StateManager.register_model(model)
|
|
68
|
+
turns = 0
|
|
69
|
+
done = False
|
|
70
|
+
|
|
71
|
+
async def other_request() -> None:
|
|
72
|
+
nonlocal turns
|
|
73
|
+
while not done:
|
|
74
|
+
turns += 1
|
|
75
|
+
await asyncio.sleep(0)
|
|
76
|
+
|
|
77
|
+
other = asyncio.create_task(other_request())
|
|
78
|
+
await asyncio.sleep(0)
|
|
79
|
+
start = turns
|
|
80
|
+
await StateManager.cache_bulk(model, [_record(model, i) for i in range(2000)])
|
|
81
|
+
ran_during = turns - start
|
|
82
|
+
done = True
|
|
83
|
+
await other
|
|
84
|
+
assert await StateManager.count(model) == 2000
|
|
85
|
+
assert ran_during >= 5, f"another request ran {ran_during} times during a 2000-row cache"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
async def test_the_sweep_drops_only_what_aged_out(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
90
|
+
monkeypatch.setattr(state_module, "STALE_SWEEP_EVERY_WRITES", 10_000)
|
|
91
|
+
model = _model(CachePolicy.SHORT_TERM, "_AgeModel")
|
|
92
|
+
state = ModelState(model)
|
|
93
|
+
for i in range(6):
|
|
94
|
+
await state.cache(_record(model, i))
|
|
95
|
+
old = datetime.now() - timedelta(minutes=11) # SHORT_TERM is 10 minutes
|
|
96
|
+
for key in list(state._cache_times):
|
|
97
|
+
state._cache_times[key] = old
|
|
98
|
+
# row-2 is cached again now: it moves behind the old ones and must survive.
|
|
99
|
+
await state.cache(_record(model, 2))
|
|
100
|
+
await state.cache(_record(model, 9))
|
|
101
|
+
assert state.sweep_stale() == 5
|
|
102
|
+
assert sorted(state._cache) == ["row-2", "row-9"]
|
|
103
|
+
assert state.sweep_stale() == 0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.asyncio
|
|
107
|
+
async def test_a_permanent_model_is_never_swept() -> None:
|
|
108
|
+
model = _model(CachePolicy.PERMANENT, "_PermanentModel")
|
|
109
|
+
state = ModelState(model)
|
|
110
|
+
await state.cache(_record(model, 1))
|
|
111
|
+
state._cache_times["row-1"] = datetime.now() - timedelta(days=30)
|
|
112
|
+
assert state.sweep_stale() == 0
|
|
113
|
+
assert "row-1" in state._cache
|
|
@@ -166,3 +166,70 @@ async def test_unsafe_identifier_never_reaches_postgres(monkeypatch) -> None:
|
|
|
166
166
|
|
|
167
167
|
with pytest.raises(UnregisteredRelationError):
|
|
168
168
|
await dynamic_select("ai.provider", columns=('evil"; drop table x; --',), database=DB)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _wire_typed(monkeypatch, columns: dict[str, str], rows: list[dict[str, Any]]) -> dict[str, Any]:
|
|
172
|
+
seen = _wire_catalog(monkeypatch, columns=list(columns), rows=rows)
|
|
173
|
+
|
|
174
|
+
async def describe_relation_columns(database: str, *, schema: str, table: str):
|
|
175
|
+
return [{"column_name": c, "data_type": t} for c, t in columns.items()]
|
|
176
|
+
|
|
177
|
+
monkeypatch.setattr("matrx_orm.catalog.describe_relation_columns", describe_relation_columns)
|
|
178
|
+
return seen
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@pytest.mark.asyncio
|
|
182
|
+
async def test_json_path_selection_is_built_with_bound_keys(monkeypatch) -> None:
|
|
183
|
+
"""2026-10-17: an agent asked for ``provider_entry->>version`` and was told
|
|
184
|
+
the column did not exist. Paths are validated against the live json column and
|
|
185
|
+
the keys travel as a bound text[] — never interpolated."""
|
|
186
|
+
monkeypatch.setattr(
|
|
187
|
+
dynamic_crud,
|
|
188
|
+
"resolve_dynamic_model",
|
|
189
|
+
lambda table, *, database=None: (_ for _ in ()).throw(ValueError("none")),
|
|
190
|
+
)
|
|
191
|
+
seen = _wire_typed(
|
|
192
|
+
monkeypatch,
|
|
193
|
+
{"model_id": "text", "provider_entry": "jsonb"},
|
|
194
|
+
rows=[{"model_id": "m", "provider_entry->>version": "001"}],
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
rows = await dynamic_select(
|
|
198
|
+
"ai.provider_sync_candidates",
|
|
199
|
+
columns=("model_id", "provider_entry->>version", "provider_entry->a->>'b-c'", "provider_entry#>{x,0}"),
|
|
200
|
+
database=DB,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
assert rows == [{"model_id": "m", "provider_entry->>version": "001"}]
|
|
204
|
+
assert seen["sql"] == (
|
|
205
|
+
'SELECT "model_id", "provider_entry" #>> $1::text[] AS "provider_entry->>version", '
|
|
206
|
+
'"provider_entry" #>> $2::text[] AS "provider_entry->a->>\'b-c\'", '
|
|
207
|
+
'"provider_entry" #> $3::text[] AS "provider_entry#>{x,0}" '
|
|
208
|
+
'FROM "ai"."provider_sync_candidates"'
|
|
209
|
+
)
|
|
210
|
+
assert seen["params"] == [["version"], ["a", "b-c"], ["x", "0"]]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@pytest.mark.asyncio
|
|
214
|
+
@pytest.mark.parametrize(
|
|
215
|
+
"spec, needle",
|
|
216
|
+
[
|
|
217
|
+
("provider_entry->>ver sion", "Accepted JSON path forms"),
|
|
218
|
+
("provider_entry->>a->b", "only be the last step"),
|
|
219
|
+
("provider_entry->>x; drop table t", "Accepted JSON path forms"),
|
|
220
|
+
("model_id->>version", "not json/jsonb"),
|
|
221
|
+
("nope->>version", "does not exist"),
|
|
222
|
+
],
|
|
223
|
+
)
|
|
224
|
+
async def test_bad_json_path_names_the_accepted_forms(monkeypatch, spec, needle) -> None:
|
|
225
|
+
monkeypatch.setattr(
|
|
226
|
+
dynamic_crud,
|
|
227
|
+
"resolve_dynamic_model",
|
|
228
|
+
lambda table, *, database=None: (_ for _ in ()).throw(ValueError("none")),
|
|
229
|
+
)
|
|
230
|
+
_wire_typed(monkeypatch, {"model_id": "text", "provider_entry": "jsonb"}, rows=[])
|
|
231
|
+
|
|
232
|
+
with pytest.raises(UnregisteredRelationError) as excinfo:
|
|
233
|
+
await dynamic_select("ai.provider_sync_candidates", columns=(spec,), database=DB)
|
|
234
|
+
|
|
235
|
+
assert needle in str(excinfo.value)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|