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.
Files changed (317) hide show
  1. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/CLAUDE.md +25 -0
  2. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/PKG-INFO +1 -1
  3. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/update.py +8 -2
  4. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/query/builder.py +46 -4
  5. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/state.py +53 -0
  6. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/pyproject.toml +1 -1
  7. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_cache_no_downgrade.py +39 -0
  8. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/.gitignore +0 -0
  9. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/AGENTS.md +0 -0
  10. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/BASE_CLASS_METHODS.md +0 -0
  11. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/MODEL_API.md +0 -0
  12. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/README.md +0 -0
  13. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/database/orm/extended/managers/ai_model_base.py +0 -0
  14. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/docs/migrations.md +0 -0
  15. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/MODULE_README.md +0 -0
  16. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/__init__.py +0 -0
  17. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/MODULE_README.md +0 -0
  18. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/__init__.py +0 -0
  19. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/async_postgresql.py +0 -0
  20. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/base_adapter.py +0 -0
  21. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/postgresql.py +0 -0
  22. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  23. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/adapters/supabase_adapter.py +0 -0
  24. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/admin/__init__.py +0 -0
  25. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/admin/router.py +0 -0
  26. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/__init__.py +0 -0
  27. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/auth.py +0 -0
  28. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/config.py +0 -0
  29. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/handlers.py +0 -0
  30. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/protocol.py +0 -0
  31. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/api/server.py +0 -0
  32. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/associations.py +0 -0
  33. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/cache_debug.py +0 -0
  34. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/catalog.py +0 -0
  35. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/MODULE_README.md +0 -0
  36. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/__init__.py +0 -0
  37. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/postgres_connection.py +0 -0
  38. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/postgrest.py +0 -0
  39. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/supabase_auth.py +0 -0
  40. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/supabase_config.py +0 -0
  41. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/client/supabase_manager.py +0 -0
  42. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  43. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/MODULE_README.md +0 -0
  44. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  45. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/TASKS.md +0 -0
  46. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/__init__.py +0 -0
  47. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/async_db_manager.py +0 -0
  48. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/base.py +0 -0
  49. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/config.py +0 -0
  50. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/connections.py +0 -0
  51. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/db_function.py +0 -0
  52. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/diagnostics.py +0 -0
  53. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/expressions.py +0 -0
  54. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/extended.py +0 -0
  55. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/fields.py +0 -0
  56. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/introspection.py +0 -0
  57. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/listen.py +0 -0
  58. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/loop_filters.py +0 -0
  59. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/model_dto.py +0 -0
  60. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/model_view.py +0 -0
  61. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/paginator.py +0 -0
  62. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/pool_maintenance.py +0 -0
  63. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/pydantic_bridge.py +0 -0
  64. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/registry.py +0 -0
  65. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/relations.py +0 -0
  66. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/resilience.py +0 -0
  67. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/rls_session.py +0 -0
  68. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/signals.py +0 -0
  69. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/tls.py +0 -0
  70. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/transaction.py +0 -0
  71. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/types.py +0 -0
  72. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/core/write_queue.py +0 -0
  73. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/entity.py +0 -0
  74. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/error_handling.py +0 -0
  75. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/exceptions.py +0 -0
  76. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/extended/__init__.py +0 -0
  77. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/extended/app_error_handler.py +0 -0
  78. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/identity_rule.py +0 -0
  79. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/middleware/__init__.py +0 -0
  80. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/middleware/base.py +0 -0
  81. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/MODULE_README.md +0 -0
  82. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/__init__.py +0 -0
  83. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/cli.py +0 -0
  84. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/ddl.py +0 -0
  85. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/diff.py +0 -0
  86. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/executor.py +0 -0
  87. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/integration.py +0 -0
  88. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/loader.py +0 -0
  89. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/operations.py +0 -0
  90. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/state.py +0 -0
  91. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/migrations/table_filter.py +0 -0
  92. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/MODULE_README.md +0 -0
  93. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/__init__.py +0 -0
  94. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/bulk_update_values.py +0 -0
  95. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/conflict.py +0 -0
  96. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/conflict_writes.py +0 -0
  97. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/create.py +0 -0
  98. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/db_functions.py +0 -0
  99. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/delete.py +0 -0
  100. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/dynamic_admin.py +0 -0
  101. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/dynamic_crud.py +0 -0
  102. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/graph_walk.py +0 -0
  103. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/latest_rows.py +0 -0
  104. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/queue_claim.py +0 -0
  105. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/read.py +0 -0
  106. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/relational_backfill.py +0 -0
  107. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/operations/staging_load.py +0 -0
  108. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/py.typed +0 -0
  109. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/python_sql/MODULE_README.md +0 -0
  110. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/python_sql/__init__.py +0 -0
  111. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/python_sql/db_objects.py +0 -0
  112. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  113. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  114. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/query/MODULE_README.md +0 -0
  115. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/query/__init__.py +0 -0
  116. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/query/executor.py +0 -0
  117. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  118. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/__init__.py +0 -0
  119. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/code_handler.py +0 -0
  120. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  121. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/columns.py +0 -0
  122. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/common.py +0 -0
  123. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/diff_preview.py +0 -0
  124. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  125. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/generator.py +0 -0
  126. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  127. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  128. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  129. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  130. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/package_wiring.py +0 -0
  131. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/relationships.py +0 -0
  132. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/runner.py +0 -0
  133. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/schema.py +0 -0
  134. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/schema_manager.py +0 -0
  135. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/tables.py +0 -0
  136. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/schema_builder/views.py +0 -0
  137. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  138. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/__init__.py +0 -0
  139. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/crypto.py +0 -0
  140. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/items.py +0 -0
  141. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/model.py +0 -0
  142. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/org_service.py +0 -0
  143. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/secrets_battery/service.py +0 -0
  144. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/README.md +0 -0
  145. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/__init__.py +0 -0
  146. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/coalesce.py +0 -0
  147. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/dag.py +0 -0
  148. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/errors.py +0 -0
  149. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/fallback.py +0 -0
  150. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/flush.py +0 -0
  151. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/lifecycle.py +0 -0
  152. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/managed.py +0 -0
  153. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/op.py +0 -0
  154. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/reads.py +0 -0
  155. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/session.py +0 -0
  156. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/session/telemetry.py +0 -0
  157. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  158. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/__init__.py +0 -0
  159. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/executor.py +0 -0
  160. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/queries.py +0 -0
  161. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/registry.py +0 -0
  162. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/types.py +0 -0
  163. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/sql_executor/utils.py +0 -0
  164. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/utils/__init__.py +0 -0
  165. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/utils/sql_utils.py +0 -0
  166. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/matrx_orm/utils/type_converters.py +0 -0
  167. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/scripts/git-branches.sh +0 -0
  168. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/scripts/publish.sh +0 -0
  169. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/scripts/release.sh +0 -0
  170. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/MODULE_README.md +0 -0
  171. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/__init__.py +0 -0
  172. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/conftest.py +0 -0
  173. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/MODULE_README.md +0 -0
  174. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/__init__.py +0 -0
  175. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_admin_db_columns.py +0 -0
  176. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_admin_search_negation.py +0 -0
  177. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_admin_search_sentinels.py +0 -0
  178. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_advisory_lock.py +0 -0
  179. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_agent_message.py +0 -0
  180. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_api_auth.py +0 -0
  181. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_api_config.py +0 -0
  182. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_api_handlers.py +0 -0
  183. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_api_protocol.py +0 -0
  184. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_array_agg_order_by.py +0 -0
  185. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_associations.py +0 -0
  186. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_bulk_update_by_pk.py +0 -0
  187. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  188. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_cache_debug.py +0 -0
  189. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_call_function.py +0 -0
  190. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_case_when.py +0 -0
  191. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_catalog.py +0 -0
  192. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_composite_pk_filter.py +0 -0
  193. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_composite_pk_write_paths.py +0 -0
  194. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_config.py +0 -0
  195. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_conflict_target.py +0 -0
  196. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_conflict_writes.py +0 -0
  197. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_connection_codecs.py +0 -0
  198. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_connection_poison_guard.py +0 -0
  199. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_connection_primitives.py +0 -0
  200. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_count_composite_distinct.py +0 -0
  201. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_db_function_field.py +0 -0
  202. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_ddl_generator.py +0 -0
  203. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_derived_table.py +0 -0
  204. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_dirty_tracking.py +0 -0
  205. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_dynamic_crud.py +0 -0
  206. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_exceptions.py +0 -0
  207. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_expression_primitives.py +0 -0
  208. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_fields.py +0 -0
  209. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  210. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_from_alias.py +0 -0
  211. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_fts.py +0 -0
  212. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_func_expression_args.py +0 -0
  213. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_graph_walk.py +0 -0
  214. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_having_annotate_alias.py +0 -0
  215. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  216. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_introspect_rls_policies.py +0 -0
  217. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_join_and_aggregate.py +0 -0
  218. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_latest_rows.py +0 -0
  219. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_listen_notify.py +0 -0
  220. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_managed_write_guard.py +0 -0
  221. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_matrx_entity.py +0 -0
  222. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_migration_diff_types.py +0 -0
  223. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_migration_loader.py +0 -0
  224. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_model_instance.py +0 -0
  225. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_model_meta.py +0 -0
  226. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_order_by_expression.py +0 -0
  227. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_patch_jsonb_path.py +0 -0
  228. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_pool_loop_recreation.py +0 -0
  229. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_pool_maintenance.py +0 -0
  230. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_postgrest_filters.py +0 -0
  231. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_pydantic_bridge.py +0 -0
  232. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_query_builder.py +0 -0
  233. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_query_error_detail.py +0 -0
  234. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_query_executor_sql.py +0 -0
  235. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_query_timeout.py +0 -0
  236. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  237. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_queue_claim.py +0 -0
  238. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_registry.py +0 -0
  239. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_registry_multi_database.py +0 -0
  240. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_registry_multi_schema.py +0 -0
  241. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_relational_backfill.py +0 -0
  242. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_relations.py +0 -0
  243. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_resilience.py +0 -0
  244. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_schema_exists.py +0 -0
  245. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_session_advisory_lock.py +0 -0
  246. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_state_cache.py +0 -0
  247. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_subquery_filter_raw.py +0 -0
  248. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_subquery_in_filter.py +0 -0
  249. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_supabase_auth.py +0 -0
  250. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_supabase_config.py +0 -0
  251. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_tls.py +0 -0
  252. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_typed_join_projection.py +0 -0
  253. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_typed_predicates.py +0 -0
  254. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_update_case_expression.py +0 -0
  255. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_update_subquery.py +0 -0
  256. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_upsert_default_leak.py +0 -0
  257. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_upsert_with_conflict.py +0 -0
  258. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_values_jsonb_decode.py +0 -0
  259. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_watched_lifecycle.py +0 -0
  260. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level1/test_write_retry_semantics.py +0 -0
  261. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/MODULE_README.md +0 -0
  262. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/__init__.py +0 -0
  263. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/conftest.py +0 -0
  264. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_bulk_ops.py +0 -0
  265. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_cache_integration.py +0 -0
  266. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_crud.py +0 -0
  267. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_foreign_keys.py +0 -0
  268. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_m2m.py +0 -0
  269. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_manager.py +0 -0
  270. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_migrations_live.py +0 -0
  271. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_query_execution.py +0 -0
  272. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/level2/test_schema_diff.py +0 -0
  273. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/.env.example +0 -0
  274. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/README.md +0 -0
  275. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/__init__.py +0 -0
  276. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/generate.py +0 -0
  277. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/generated/.gitkeep +0 -0
  278. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/matrx_orm.yaml +0 -0
  279. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project/test_schema_generation.py +0 -0
  280. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/.env.example +0 -0
  281. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/README.md +0 -0
  282. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/__init__.py +0 -0
  283. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/client_example.py +0 -0
  284. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/client_example.ts +0 -0
  285. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  286. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/sample_project_desktop/server.py +0 -0
  287. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/entity_tests.py +0 -0
  288. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_base_generation.py +0 -0
  289. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  290. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_composite_unique_generation.py +0 -0
  291. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_dto_identity_generation.py +0 -0
  292. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_entity_capability_generation.py +0 -0
  293. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_generate_schema.py +0 -0
  294. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_junction_analysis.py +0 -0
  295. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_multi_schema_output.py +0 -0
  296. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_sql_expression_defaults.py +0 -0
  297. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema/test_view_generation.py +0 -0
  298. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  299. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/__init__.py +0 -0
  300. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/conftest.py +0 -0
  301. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_capture_poison_proof.py +0 -0
  302. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_coalesce.py +0 -0
  303. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_dag.py +0 -0
  304. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_disk_spill_fallback.py +0 -0
  305. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_flush_individual_fallback.py +0 -0
  306. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_governed_write.py +0 -0
  307. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_managed.py +0 -0
  308. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_op_enqueue_site.py +0 -0
  309. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_reads.py +0 -0
  310. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/session/test_session.py +0 -0
  311. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_database_name_alias.py +0 -0
  312. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_db_functions.py +0 -0
  313. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_model_cls_refactor.py +0 -0
  314. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  315. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_secrets_battery.py +0 -0
  316. {matrx_orm-3.1.36 → matrx_orm-3.1.37}/tests/test_sql_param_casts.py +0 -0
  317. {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.36
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
- await StateManager.cache(model_cls, obj)
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
- return await self._get_executor().update(**kwargs)
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
- return await self._get_executor().delete()
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.36"
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