matrx-orm 3.1.36__tar.gz → 3.1.37__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.36 → matrx_orm-3.1.37}/CLAUDE.md +25 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/PKG-INFO +1 -1
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/update.py +8 -2
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/query/builder.py +46 -4
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/state.py +53 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/pyproject.toml +1 -1
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_cache_no_downgrade.py +39 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/.gitignore +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/AGENTS.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/BASE_CLASS_METHODS.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/MODEL_API.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/database/orm/extended/managers/ai_model_base.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/docs/migrations.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/async_postgresql.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/base_adapter.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/postgresql.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/supabase_adapter.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/admin/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/admin/router.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/auth.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/config.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/handlers.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/protocol.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/server.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/associations.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/cache_debug.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/catalog.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/postgres_connection.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/postgrest.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/supabase_auth.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/supabase_config.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/supabase_manager.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/EXTENDED-TASK.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/TASKS.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/async_db_manager.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/base.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/config.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/connections.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/db_function.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/diagnostics.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/expressions.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/extended.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/fields.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/introspection.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/listen.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/loop_filters.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/model_dto.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/model_view.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/paginator.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/pool_maintenance.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/pydantic_bridge.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/registry.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/relations.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/resilience.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/rls_session.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/signals.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/tls.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/transaction.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/types.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/write_queue.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/entity.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/error_handling.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/exceptions.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/extended/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/extended/app_error_handler.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/identity_rule.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/middleware/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/middleware/base.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/cli.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/ddl.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/diff.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/executor.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/integration.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/loader.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/operations.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/state.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/table_filter.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/bulk_update_values.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/conflict.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/conflict_writes.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/create.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/db_functions.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/delete.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/dynamic_admin.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/dynamic_crud.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/graph_walk.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/latest_rows.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/queue_claim.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/read.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/relational_backfill.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/staging_load.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/py.typed +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/python_sql/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/python_sql/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/python_sql/db_objects.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/query/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/query/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/query/executor.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/code_handler.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/codegen_writer.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/columns.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/common.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/diff_preview.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/generator.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/package_wiring.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/relationships.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/runner.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/schema.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/schema_manager.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/tables.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/views.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/FEATURE.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/crypto.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/items.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/model.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/org_service.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/service.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/coalesce.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/dag.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/errors.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/fallback.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/flush.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/lifecycle.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/managed.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/op.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/reads.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/session.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/telemetry.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/executor.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/queries.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/registry.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/types.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/utils.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/utils/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/utils/sql_utils.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/utils/type_converters.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/scripts/git-branches.sh +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/scripts/publish.sh +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/scripts/release.sh +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/conftest.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_admin_db_columns.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_admin_search_negation.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_admin_search_sentinels.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_advisory_lock.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_agent_message.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_api_auth.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_api_config.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_api_handlers.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_api_protocol.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_array_agg_order_by.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_associations.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_bulk_update_by_pk.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_cache_debug.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_call_function.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_case_when.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_catalog.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_composite_pk_filter.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_composite_pk_write_paths.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_config.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_conflict_target.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_conflict_writes.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_connection_codecs.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_connection_poison_guard.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_connection_primitives.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_count_composite_distinct.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_db_function_field.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_ddl_generator.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_derived_table.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_dirty_tracking.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_dynamic_crud.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_exceptions.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_expression_primitives.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_fields.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_from_alias.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_fts.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_func_expression_args.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_graph_walk.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_having_annotate_alias.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_introspect_rls_policies.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_join_and_aggregate.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_latest_rows.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_listen_notify.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_managed_write_guard.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_matrx_entity.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_migration_diff_types.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_migration_loader.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_model_instance.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_model_meta.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_order_by_expression.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_patch_jsonb_path.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_pool_loop_recreation.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_pool_maintenance.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_postgrest_filters.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_pydantic_bridge.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_query_builder.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_query_error_detail.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_query_executor_sql.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_query_timeout.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_queue_claim.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_registry.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_registry_multi_database.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_registry_multi_schema.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_relational_backfill.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_relations.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_resilience.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_schema_exists.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_session_advisory_lock.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_state_cache.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_subquery_filter_raw.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_subquery_in_filter.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_supabase_auth.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_supabase_config.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_tls.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_typed_join_projection.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_typed_predicates.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_update_case_expression.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_update_subquery.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_upsert_default_leak.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_upsert_with_conflict.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_values_jsonb_decode.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_watched_lifecycle.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_write_retry_semantics.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/MODULE_README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/conftest.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_bulk_ops.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_cache_integration.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_crud.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_foreign_keys.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_m2m.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_manager.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_migrations_live.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_query_execution.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_schema_diff.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/.env.example +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/generate.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/generated/.gitkeep +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/matrx_orm.yaml +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/test_schema_generation.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/.env.example +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/README.md +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/client_example.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/client_example.ts +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/client_supabase_example.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/server.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/entity_tests.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_base_generation.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_composite_pk_fk_generation.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_composite_unique_generation.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_dto_identity_generation.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_entity_capability_generation.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_generate_schema.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_junction_analysis.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_multi_schema_output.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_sql_expression_defaults.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_view_generation.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema_builder/test_focused_generation_plan.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/__init__.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/conftest.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_capture_poison_proof.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_coalesce.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_dag.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_disk_spill_fallback.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_flush_individual_fallback.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_governed_write.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_managed.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_op_enqueue_site.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_reads.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_session.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_database_name_alias.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_db_functions.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_model_cls_refactor.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_no_blocking_asyncpg_tls.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_secrets_battery.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_sql_param_casts.py +0 -0
- {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_sync_twins_guard.py +0 -0
|
@@ -116,6 +116,31 @@ the target and those explicitly named cross-schema FK dependencies.
|
|
|
116
116
|
|
|
117
117
|
---
|
|
118
118
|
|
|
119
|
+
## 🚨 THE IDENTITY CACHE MUST NEVER LIE (read before touching any write path)
|
|
120
|
+
|
|
121
|
+
`StateManager` keeps a process-local per-model identity cache (default
|
|
122
|
+
SHORT_TERM = **10 minutes**). Cached reads are the default (`use_cache=True`), so
|
|
123
|
+
anything wrong in that cache is served as truth for ten minutes. Three rules, all
|
|
124
|
+
enforced in code:
|
|
125
|
+
|
|
126
|
+
1. **No downgrades.** A cache entry may only be replaced by a record at least as
|
|
127
|
+
informed. Instances built from a column SUBSET must call
|
|
128
|
+
`matrx_orm.state.mark_partial(...)` — `ModelState.cache` evicts them instead
|
|
129
|
+
of storing them. An undeclared downgrade evicts AND screams.
|
|
130
|
+
2. **Writes refresh or evict — never leave a pre-write copy.** `QueryBuilder.update`
|
|
131
|
+
refreshes from its own `RETURNING *` rows; `QueryBuilder.delete` evicts the
|
|
132
|
+
affected PKs (or clears the model when the filter isn't PK-shaped).
|
|
133
|
+
3. **Identity reads are authoritative.** Any read that decides ownership, org
|
|
134
|
+
attribution, or whether a request may run at all passes `use_cache=False`
|
|
135
|
+
(`load_by_id(id, use_cache=False)`). Fast is not a reason to guess.
|
|
136
|
+
|
|
137
|
+
This is not theoretical hygiene. On 2026-07-25 rule 1 was missing: `session/flush.py`
|
|
138
|
+
builds a `{pk + payload}` stub per queued UPDATE and `bulk_update` cached it, so a
|
|
139
|
+
chat conversation read back with `organization_id = None` right after its own
|
|
140
|
+
turn-1 title/`message_count` UPDATE flushed — and aidream stopped the user's next
|
|
141
|
+
turn on a value Postgres had held correctly the entire time. Guards:
|
|
142
|
+
`tests/session/test_cache_no_downgrade.py`.
|
|
143
|
+
|
|
119
144
|
## Known issues
|
|
120
145
|
|
|
121
146
|
- **No CLAUDE.md existed before this commit** — that was the main gap.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matrx-orm
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.37
|
|
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
|
|
@@ -64,8 +64,14 @@ async def bulk_update(model_cls: type[Model], objects: Sequence[Model], fields:
|
|
|
64
64
|
result = await QueryBuilder(model_cls).filter(**pk_filter).update(**update_data)
|
|
65
65
|
if result.rows_affected > 0:
|
|
66
66
|
rows_affected += 1
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
# Do NOT cache `obj` here. It is the CALLER's instance and is
|
|
68
|
+
# routinely a partial {pk + payload} stub (see
|
|
69
|
+
# session/flush.py), so writing it into the identity cache
|
|
70
|
+
# blanked every column the update didn't touch — the
|
|
71
|
+
# 2026-07-25 "conversation has no durable organization_id"
|
|
72
|
+
# bug. QueryBuilder.update already refreshed the cache from
|
|
73
|
+
# the statement's own RETURNING * rows, which is the
|
|
74
|
+
# authoritative post-write state.
|
|
69
75
|
|
|
70
76
|
if debug and (i + 1) % 10 == 0:
|
|
71
77
|
vcprint(f"Processed {i + 1}/{len(valid_objects)} updates...", color="cyan")
|
|
@@ -765,6 +765,30 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
765
765
|
combined[f"exclude__{k}"] = v
|
|
766
766
|
return combined
|
|
767
767
|
|
|
768
|
+
def _invalidate_cache_for_filters(self) -> None:
|
|
769
|
+
"""Evict the rows this statement affected from the identity cache.
|
|
770
|
+
|
|
771
|
+
Precise when the filter is a plain primary-key match (``pk=`` or
|
|
772
|
+
``pk__in=``) — the common case by far. Anything else (multi-column
|
|
773
|
+
predicate, operator suffix, excludes) means the affected ids are unknown,
|
|
774
|
+
so the model's cache is cleared. Correctness over warmth.
|
|
775
|
+
"""
|
|
776
|
+
pks = self.model._meta.primary_keys or ["id"]
|
|
777
|
+
combined = self._merge_filters_excludes()
|
|
778
|
+
if len(pks) == 1 and not self.excludes and set(combined) <= {pks[0], f"{pks[0]}__in"}:
|
|
779
|
+
values = combined.get(pks[0])
|
|
780
|
+
in_values = combined.get(f"{pks[0]}__in")
|
|
781
|
+
targets = list(in_values) if isinstance(in_values, (list, tuple, set)) else []
|
|
782
|
+
if values is not None:
|
|
783
|
+
targets.append(values)
|
|
784
|
+
if targets and StateManager.is_registered(self.model):
|
|
785
|
+
database = self.model.get_database_name()
|
|
786
|
+
state = StateManager._states[(database, self.model.__name__)]
|
|
787
|
+
for value in targets:
|
|
788
|
+
state.evict_by_id(value)
|
|
789
|
+
return
|
|
790
|
+
StateManager.clear_model(self.model)
|
|
791
|
+
|
|
768
792
|
def _merge_having(self) -> dict[str, Any]:
|
|
769
793
|
combined: dict[str, Any] = {}
|
|
770
794
|
for h in self.having_filters:
|
|
@@ -862,11 +886,19 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
862
886
|
return None
|
|
863
887
|
|
|
864
888
|
async def update(self, **kwargs: Any) -> UpdateResult:
|
|
865
|
-
"""Update matching records.
|
|
889
|
+
"""Update matching records.
|
|
890
|
+
|
|
891
|
+
The identity cache is refreshed from the statement's own
|
|
892
|
+
``RETURNING *`` rows — an UPDATE here used to leave the PRE-write copy
|
|
893
|
+
cached and readable for the rest of the TTL (SHORT_TERM = 10 min), so a
|
|
894
|
+
row could be read back with values Postgres no longer held.
|
|
895
|
+
"""
|
|
866
896
|
try:
|
|
867
897
|
if not kwargs:
|
|
868
898
|
raise ValidationError(model=self.model, reason="No update data provided")
|
|
869
|
-
|
|
899
|
+
result = await self._get_executor().update(**kwargs)
|
|
900
|
+
await StateManager.refresh_from_rows(self.model, result.updated_rows)
|
|
901
|
+
return result
|
|
870
902
|
except ValidationError:
|
|
871
903
|
raise
|
|
872
904
|
except ORMException as e:
|
|
@@ -874,9 +906,19 @@ class QueryBuilder(Generic[ModelT]):
|
|
|
874
906
|
raise
|
|
875
907
|
|
|
876
908
|
async def delete(self) -> int:
|
|
877
|
-
"""Delete matching records.
|
|
909
|
+
"""Delete matching records.
|
|
910
|
+
|
|
911
|
+
Evicts the deleted rows from the identity cache — a filtered DELETE used
|
|
912
|
+
to leave them cached and readable for the rest of the TTL. When the
|
|
913
|
+
filter isn't a plain primary-key match the affected ids are unknown, so
|
|
914
|
+
the model's cache is cleared outright: losing warmth for one model is
|
|
915
|
+
free, serving a deleted row for 10 minutes is not.
|
|
916
|
+
"""
|
|
878
917
|
try:
|
|
879
|
-
|
|
918
|
+
deleted = await self._get_executor().delete()
|
|
919
|
+
if deleted:
|
|
920
|
+
self._invalidate_cache_for_filters()
|
|
921
|
+
return deleted
|
|
880
922
|
except ORMException as e:
|
|
881
923
|
e.enrich(model=self.model, operation="delete", filters=self._merge_filters_excludes())
|
|
882
924
|
raise
|
|
@@ -555,6 +555,59 @@ class StateManager:
|
|
|
555
555
|
original_error=e,
|
|
556
556
|
) from e
|
|
557
557
|
|
|
558
|
+
@classmethod
|
|
559
|
+
async def refresh_from_rows(cls, model_class, rows, *, max_rows: int = 200) -> None:
|
|
560
|
+
"""Write authoritative post-write rows over whatever is cached.
|
|
561
|
+
|
|
562
|
+
For write paths that already have the true row in hand (``UPDATE …
|
|
563
|
+
RETURNING *``). Unlike :meth:`cache_bulk` this OVERWRITES an existing
|
|
564
|
+
entry — that is the point: a targeted UPDATE otherwise left the pre-write
|
|
565
|
+
copy cached and readable for the rest of the TTL.
|
|
566
|
+
|
|
567
|
+
Above ``max_rows`` the rows are evicted instead of cached, so a mass
|
|
568
|
+
UPDATE can't flood the cache with rows nobody asked for. Never raises —
|
|
569
|
+
a cache refresh must not fail the write that already committed.
|
|
570
|
+
"""
|
|
571
|
+
if not rows or not cls.is_registered(model_class):
|
|
572
|
+
return
|
|
573
|
+
database = model_class.get_database_name()
|
|
574
|
+
state = cls._states[(database, model_class.__name__)]
|
|
575
|
+
pks = model_class._meta.primary_keys or ["id"]
|
|
576
|
+
evict_only = len(rows) > max_rows
|
|
577
|
+
for row in rows:
|
|
578
|
+
try:
|
|
579
|
+
if evict_only:
|
|
580
|
+
values = [row.get(pk) if isinstance(row, dict) else getattr(row, pk, None) for pk in pks]
|
|
581
|
+
if all(v is not None for v in values):
|
|
582
|
+
state.evict_by_id(*values)
|
|
583
|
+
continue
|
|
584
|
+
record = row if not isinstance(row, dict) else model_class.from_db_result(row)
|
|
585
|
+
await state.cache(record)
|
|
586
|
+
except Exception as exc: # noqa: BLE001 — never fail a committed write
|
|
587
|
+
vcprint(
|
|
588
|
+
f"[matrx_orm.state] refresh_from_rows skipped one "
|
|
589
|
+
f"{model_class.__name__} row ({type(exc).__name__}: {exc})",
|
|
590
|
+
color="yellow",
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
@classmethod
|
|
594
|
+
def clear_model(cls, model_class) -> int:
|
|
595
|
+
"""Drop every cached record for a model. Returns the count dropped.
|
|
596
|
+
|
|
597
|
+
The correctness fallback for a write whose affected PKs are unknown
|
|
598
|
+
(a filtered DELETE). Losing cache warmth for one model is free; serving
|
|
599
|
+
a deleted row for 10 minutes is not.
|
|
600
|
+
"""
|
|
601
|
+
if not cls.is_registered(model_class):
|
|
602
|
+
return 0
|
|
603
|
+
database = model_class.get_database_name()
|
|
604
|
+
state = cls._states[(database, model_class.__name__)]
|
|
605
|
+
dropped = len(state._cache)
|
|
606
|
+
state._cache.clear()
|
|
607
|
+
state._cache_times.clear()
|
|
608
|
+
state._subscriptions.clear()
|
|
609
|
+
return dropped
|
|
610
|
+
|
|
558
611
|
@classmethod
|
|
559
612
|
async def remove(cls, model_class, record):
|
|
560
613
|
"""Removes a specific record from the cache."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "matrx-orm"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.37"
|
|
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" }
|
|
@@ -95,3 +95,42 @@ async def test_equally_informed_record_still_replaces():
|
|
|
95
95
|
assert cached is fresh
|
|
96
96
|
assert cached.title == "Renamed"
|
|
97
97
|
assert cached.organization_id == "org-1"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pytest.mark.asyncio
|
|
101
|
+
async def test_refresh_from_rows_overwrites_a_stale_entry():
|
|
102
|
+
"""A targeted UPDATE has the true post-write row in hand (RETURNING *), so the
|
|
103
|
+
cache is refreshed with it — cache_bulk's "only if absent" rule would have
|
|
104
|
+
left the PRE-write copy readable for the rest of the TTL."""
|
|
105
|
+
state = _state()
|
|
106
|
+
await state.cache(_full_row())
|
|
107
|
+
|
|
108
|
+
from matrx_orm.state import StateManager
|
|
109
|
+
|
|
110
|
+
StateManager._states[("test_db", "_Conversation")] = state
|
|
111
|
+
_Conversation.from_db_result = classmethod(lambda cls, row: cls(**row))
|
|
112
|
+
try:
|
|
113
|
+
await StateManager.refresh_from_rows(
|
|
114
|
+
_Conversation,
|
|
115
|
+
[{"id": "c1", "organization_id": "org-1", "title": "Renamed", "message_count": 4}],
|
|
116
|
+
)
|
|
117
|
+
cached = await state.get(id="c1")
|
|
118
|
+
assert cached.title == "Renamed"
|
|
119
|
+
assert cached.organization_id == "org-1"
|
|
120
|
+
finally:
|
|
121
|
+
StateManager._states.pop(("test_db", "_Conversation"), None)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@pytest.mark.asyncio
|
|
125
|
+
async def test_clear_model_drops_every_entry():
|
|
126
|
+
state = _state()
|
|
127
|
+
await state.cache(_full_row())
|
|
128
|
+
|
|
129
|
+
from matrx_orm.state import StateManager
|
|
130
|
+
|
|
131
|
+
StateManager._states[("test_db", "_Conversation")] = state
|
|
132
|
+
try:
|
|
133
|
+
assert StateManager.clear_model(_Conversation) == 1
|
|
134
|
+
assert await state.get(id="c1") is None
|
|
135
|
+
finally:
|
|
136
|
+
StateManager._states.pop(("test_db", "_Conversation"), None)
|
|
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
|
|
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
|
|
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
|