matrx-orm 3.1.63__tar.gz → 3.1.64__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 (328) hide show
  1. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/PKG-INFO +1 -1
  2. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/admin/router.py +9 -31
  3. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/catalog.py +6 -6
  4. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/FEATURE.md +1 -0
  5. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/introspection.py +2 -2
  6. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/staging_load.py +2 -2
  7. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/README.md +3 -1
  8. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/lifecycle.py +3 -3
  9. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/managed.py +45 -61
  10. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/op.py +10 -7
  11. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/pyproject.toml +1 -1
  12. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_admin_db_columns.py +20 -0
  13. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_catalog.py +8 -6
  14. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_insert_ignore_and_admin_primitives.py +1 -1
  15. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_introspect_rls_policies.py +17 -1
  16. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_managed_write_guard.py +29 -15
  17. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_coalesce.py +1 -1
  18. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_dag.py +1 -1
  19. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_disk_spill_fallback.py +4 -3
  20. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_governed_write.py +5 -5
  21. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/.gitignore +0 -0
  22. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/AGENTS.md +0 -0
  23. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/BASE_CLASS_METHODS.md +0 -0
  24. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/CLAUDE.md +0 -0
  25. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/MODEL_API.md +0 -0
  26. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/README.md +0 -0
  27. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/database/orm/extended/managers/ai_model_base.py +0 -0
  28. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/docs/migrations.md +0 -0
  29. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/MODULE_README.md +0 -0
  30. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/__init__.py +0 -0
  31. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/MODULE_README.md +0 -0
  32. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/__init__.py +0 -0
  33. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/async_postgresql.py +0 -0
  34. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/base_adapter.py +0 -0
  35. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/postgresql.py +0 -0
  36. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  37. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/adapters/supabase_adapter.py +0 -0
  38. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/admin/__init__.py +0 -0
  39. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/__init__.py +0 -0
  40. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/auth.py +0 -0
  41. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/config.py +0 -0
  42. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/handlers.py +0 -0
  43. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/protocol.py +0 -0
  44. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/api/server.py +0 -0
  45. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/associations.py +0 -0
  46. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/cache_debug.py +0 -0
  47. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/MODULE_README.md +0 -0
  48. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/__init__.py +0 -0
  49. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/postgres_connection.py +0 -0
  50. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/postgrest.py +0 -0
  51. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/supabase_auth.py +0 -0
  52. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/supabase_config.py +0 -0
  53. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/client/supabase_manager.py +0 -0
  54. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  55. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/MODULE_README.md +0 -0
  56. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  57. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/TASKS.md +0 -0
  58. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/__init__.py +0 -0
  59. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/async_db_manager.py +0 -0
  60. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/base.py +0 -0
  61. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/config.py +0 -0
  62. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/connections.py +0 -0
  63. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/db_function.py +0 -0
  64. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/diagnostics.py +0 -0
  65. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/expressions.py +0 -0
  66. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/extended.py +0 -0
  67. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/fields.py +0 -0
  68. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/listen.py +0 -0
  69. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/loop_filters.py +0 -0
  70. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/model_dto.py +0 -0
  71. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/model_view.py +0 -0
  72. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/one_database.py +0 -0
  73. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/paginator.py +0 -0
  74. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/pool_maintenance.py +0 -0
  75. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/pydantic_bridge.py +0 -0
  76. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/registry.py +0 -0
  77. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/relations.py +0 -0
  78. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/resilience.py +0 -0
  79. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/rls_session.py +0 -0
  80. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/signals.py +0 -0
  81. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/tls.py +0 -0
  82. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/transaction.py +0 -0
  83. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/types.py +0 -0
  84. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/core/write_queue.py +0 -0
  85. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/entity.py +0 -0
  86. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/error_handling.py +0 -0
  87. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/exceptions.py +0 -0
  88. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/extended/__init__.py +0 -0
  89. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/extended/app_error_handler.py +0 -0
  90. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/identity_rule.py +0 -0
  91. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/middleware/__init__.py +0 -0
  92. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/middleware/base.py +0 -0
  93. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/MODULE_README.md +0 -0
  94. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/__init__.py +0 -0
  95. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/cli.py +0 -0
  96. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/ddl.py +0 -0
  97. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/diff.py +0 -0
  98. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/executor.py +0 -0
  99. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/integration.py +0 -0
  100. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/loader.py +0 -0
  101. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/operations.py +0 -0
  102. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/state.py +0 -0
  103. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/migrations/table_filter.py +0 -0
  104. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/MODULE_README.md +0 -0
  105. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/__init__.py +0 -0
  106. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/bulk_update_values.py +0 -0
  107. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/conflict.py +0 -0
  108. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/conflict_writes.py +0 -0
  109. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/create.py +0 -0
  110. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/db_functions.py +0 -0
  111. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/delete.py +0 -0
  112. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/dynamic_admin.py +0 -0
  113. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/dynamic_crud.py +0 -0
  114. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/graph_walk.py +0 -0
  115. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/latest_rows.py +0 -0
  116. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/queue_claim.py +0 -0
  117. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/read.py +0 -0
  118. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/relational_backfill.py +0 -0
  119. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/operations/update.py +0 -0
  120. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/platform_db.py +0 -0
  121. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/py.typed +0 -0
  122. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/python_sql/MODULE_README.md +0 -0
  123. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/python_sql/__init__.py +0 -0
  124. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/python_sql/db_objects.py +0 -0
  125. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  126. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  127. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/query/FEATURE.md +0 -0
  128. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/query/MODULE_README.md +0 -0
  129. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/query/__init__.py +0 -0
  130. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/query/builder.py +0 -0
  131. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/query/executor.py +0 -0
  132. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  133. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/__init__.py +0 -0
  134. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/code_handler.py +0 -0
  135. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  136. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/columns.py +0 -0
  137. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/common.py +0 -0
  138. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/diff_preview.py +0 -0
  139. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  140. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/generator.py +0 -0
  141. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  142. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  143. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  144. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  145. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/package_wiring.py +0 -0
  146. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/relationships.py +0 -0
  147. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/runner.py +0 -0
  148. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/schema.py +0 -0
  149. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/schema_manager.py +0 -0
  150. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/tables.py +0 -0
  151. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/schema_builder/views.py +0 -0
  152. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  153. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/__init__.py +0 -0
  154. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/attachments.py +0 -0
  155. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/crypto.py +0 -0
  156. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/items.py +0 -0
  157. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/model.py +0 -0
  158. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/org_service.py +0 -0
  159. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/secrets_battery/service.py +0 -0
  160. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/__init__.py +0 -0
  161. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/coalesce.py +0 -0
  162. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/dag.py +0 -0
  163. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/errors.py +0 -0
  164. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/fallback.py +0 -0
  165. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/flush.py +0 -0
  166. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/reads.py +0 -0
  167. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/session.py +0 -0
  168. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/session/telemetry.py +0 -0
  169. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  170. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/__init__.py +0 -0
  171. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/executor.py +0 -0
  172. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/queries.py +0 -0
  173. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/registry.py +0 -0
  174. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/types.py +0 -0
  175. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/sql_executor/utils.py +0 -0
  176. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/state.py +0 -0
  177. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/utils/__init__.py +0 -0
  178. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/utils/sql_utils.py +0 -0
  179. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/matrx_orm/utils/type_converters.py +0 -0
  180. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/scripts/git-branches.sh +0 -0
  181. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/scripts/publish.sh +0 -0
  182. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/scripts/release.sh +0 -0
  183. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/MODULE_README.md +0 -0
  184. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/__init__.py +0 -0
  185. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/conftest.py +0 -0
  186. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/MODULE_README.md +0 -0
  187. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/__init__.py +0 -0
  188. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_admin_search_negation.py +0 -0
  189. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_admin_search_sentinels.py +0 -0
  190. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_advisory_lock.py +0 -0
  191. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_agent_message.py +0 -0
  192. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_api_auth.py +0 -0
  193. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_api_config.py +0 -0
  194. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_api_handlers.py +0 -0
  195. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_api_protocol.py +0 -0
  196. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_array_agg_order_by.py +0 -0
  197. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_associations.py +0 -0
  198. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_bulk_update_by_pk.py +0 -0
  199. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  200. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_cache_debug.py +0 -0
  201. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_call_function.py +0 -0
  202. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_case_when.py +0 -0
  203. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_composite_pk_filter.py +0 -0
  204. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_composite_pk_write_paths.py +0 -0
  205. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_config.py +0 -0
  206. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_conflict_target.py +0 -0
  207. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_conflict_writes.py +0 -0
  208. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_connection_codecs.py +0 -0
  209. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_connection_poison_guard.py +0 -0
  210. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_connection_primitives.py +0 -0
  211. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_count_composite_distinct.py +0 -0
  212. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_db_function_field.py +0 -0
  213. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_ddl_generator.py +0 -0
  214. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_derived_table.py +0 -0
  215. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_dirty_tracking.py +0 -0
  216. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_dynamic_crud.py +0 -0
  217. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_exceptions.py +0 -0
  218. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  219. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_expression_primitives.py +0 -0
  220. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_fields.py +0 -0
  221. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  222. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_from_alias.py +0 -0
  223. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_fts.py +0 -0
  224. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_func_expression_args.py +0 -0
  225. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_graph_walk.py +0 -0
  226. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_having_annotate_alias.py +0 -0
  227. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_join_and_aggregate.py +0 -0
  228. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_latest_rows.py +0 -0
  229. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_listen_notify.py +0 -0
  230. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_matrx_entity.py +0 -0
  231. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_migration_diff_types.py +0 -0
  232. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_migration_loader.py +0 -0
  233. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_model_instance.py +0 -0
  234. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_model_meta.py +0 -0
  235. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_optimistic_concurrency.py +0 -0
  236. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_order_by_expression.py +0 -0
  237. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_parameter_name_hydration.py +0 -0
  238. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_patch_jsonb_path.py +0 -0
  239. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_pool_loop_recreation.py +0 -0
  240. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_pool_maintenance.py +0 -0
  241. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_postgrest_filters.py +0 -0
  242. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_pydantic_bridge.py +0 -0
  243. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_query_builder.py +0 -0
  244. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_query_error_detail.py +0 -0
  245. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_query_executor_sql.py +0 -0
  246. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_query_timeout.py +0 -0
  247. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  248. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_queue_claim.py +0 -0
  249. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_registry.py +0 -0
  250. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_registry_multi_database.py +0 -0
  251. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_registry_multi_schema.py +0 -0
  252. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_relational_backfill.py +0 -0
  253. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_relations.py +0 -0
  254. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_resilience.py +0 -0
  255. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_schema_exists.py +0 -0
  256. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_session_advisory_lock.py +0 -0
  257. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_state_cache.py +0 -0
  258. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_subquery_filter_raw.py +0 -0
  259. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_subquery_in_filter.py +0 -0
  260. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_supabase_auth.py +0 -0
  261. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_supabase_config.py +0 -0
  262. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_tls.py +0 -0
  263. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_typed_join_projection.py +0 -0
  264. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_typed_predicates.py +0 -0
  265. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_update_case_expression.py +0 -0
  266. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_update_subquery.py +0 -0
  267. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_update_with_rebase.py +0 -0
  268. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_upsert_default_leak.py +0 -0
  269. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_upsert_with_conflict.py +0 -0
  270. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_values_jsonb_decode.py +0 -0
  271. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_watched_lifecycle.py +0 -0
  272. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level1/test_write_retry_semantics.py +0 -0
  273. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/MODULE_README.md +0 -0
  274. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/__init__.py +0 -0
  275. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/conftest.py +0 -0
  276. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_bulk_ops.py +0 -0
  277. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_cache_integration.py +0 -0
  278. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_crud.py +0 -0
  279. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_foreign_keys.py +0 -0
  280. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_m2m.py +0 -0
  281. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_manager.py +0 -0
  282. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_migrations_live.py +0 -0
  283. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_query_execution.py +0 -0
  284. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/level2/test_schema_diff.py +0 -0
  285. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/.env.example +0 -0
  286. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/README.md +0 -0
  287. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/__init__.py +0 -0
  288. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/generate.py +0 -0
  289. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/generated/.gitkeep +0 -0
  290. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/matrx_orm.yaml +0 -0
  291. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project/test_schema_generation.py +0 -0
  292. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/.env.example +0 -0
  293. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/README.md +0 -0
  294. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/__init__.py +0 -0
  295. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/client_example.py +0 -0
  296. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/client_example.ts +0 -0
  297. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  298. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/sample_project_desktop/server.py +0 -0
  299. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/entity_tests.py +0 -0
  300. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_base_generation.py +0 -0
  301. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  302. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_composite_unique_generation.py +0 -0
  303. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_dto_identity_generation.py +0 -0
  304. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_entity_capability_generation.py +0 -0
  305. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_generate_schema.py +0 -0
  306. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_junction_analysis.py +0 -0
  307. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_multi_schema_output.py +0 -0
  308. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_sql_expression_defaults.py +0 -0
  309. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema/test_view_generation.py +0 -0
  310. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  311. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  312. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/__init__.py +0 -0
  313. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/conftest.py +0 -0
  314. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_cache_no_downgrade.py +0 -0
  315. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_capture_poison_proof.py +0 -0
  316. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_flush_individual_fallback.py +0 -0
  317. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_managed.py +0 -0
  318. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_op_enqueue_site.py +0 -0
  319. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_reads.py +0 -0
  320. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/session/test_session.py +0 -0
  321. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_database_name_alias.py +0 -0
  322. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_db_functions.py +0 -0
  323. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_model_cls_refactor.py +0 -0
  324. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  325. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_one_database.py +0 -0
  326. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_secrets_battery.py +0 -0
  327. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_sql_param_casts.py +0 -0
  328. {matrx_orm-3.1.63 → matrx_orm-3.1.64}/tests/test_sync_twins_guard.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matrx-orm
3
- Version: 3.1.63
3
+ Version: 3.1.64
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
@@ -57,43 +57,21 @@ _COMPOSITE_ROW_ID_PREFIX = "cmp1_"
57
57
 
58
58
 
59
59
  def _model_by_table(table_name: str, schema: str | None = None) -> type | None:
60
- matches = [
61
- model
62
- for model in model_registry.all_models().values()
63
- if model._meta.table_name == table_name
64
- ]
65
- if not matches:
66
- return None
67
- if schema:
68
- for model in matches:
69
- if _model_schema(model) == schema:
70
- return model
71
- return None
72
- if len(matches) == 1:
73
- return matches[0]
74
- for model in matches:
75
- if _model_schema(model) == "public":
76
- return model
77
- return matches[0]
60
+ return model_registry.get_model_by_table(schema or "public", table_name)
78
61
 
79
62
 
80
63
  def _require_model(table_name: str, schema: str | None = None) -> type:
81
64
  model = _model_by_table(table_name, schema)
82
65
  if model is None:
83
- if schema:
84
- log.warning(
85
- "Table '%s.%s' not found in model registry",
86
- schema,
87
- table_name,
88
- )
89
- raise HTTPException(
90
- status_code=404,
91
- detail=f"Table '{schema}.{table_name}' not found in model registry",
92
- )
93
- log.warning("Table '%s' not found in model registry", table_name)
66
+ effective_schema = schema or "public"
67
+ log.warning(
68
+ "Table '%s.%s' not found in model registry",
69
+ effective_schema,
70
+ table_name,
71
+ )
94
72
  raise HTTPException(
95
73
  status_code=404,
96
- detail=f"Table '{table_name}' not found in model registry",
74
+ detail=f"Table '{effective_schema}.{table_name}' not found in model registry",
97
75
  )
98
76
  return model
99
77
 
@@ -176,7 +154,7 @@ def _safe_scalar(value: Any) -> str | int | float | bool | None:
176
154
  """Return value only if it is a JSON-safe scalar; drop everything else."""
177
155
  if value is None or callable(value):
178
156
  return None
179
- if isinstance(value, (str, int, float, bool)):
157
+ if isinstance(value, str | int | float | bool):
180
158
  return value
181
159
  return None
182
160
 
@@ -23,7 +23,7 @@ __all__ = [
23
23
  "relation_privileges",
24
24
  "role_service_state",
25
25
  "softdelete_unique_conflicts",
26
- "table_schema_map",
26
+ "qualified_relation_names",
27
27
  "trigger_function_usage",
28
28
  "column_privileges",
29
29
  "entity_capability_state",
@@ -258,17 +258,17 @@ async def entity_capability_state(database: str) -> list[dict[str, Any]]:
258
258
  )
259
259
 
260
260
 
261
- async def table_schema_map(database: str) -> dict[str, list[str]]:
262
- """Return every table/view name mapped to its distinct schemas."""
261
+ async def qualified_relation_names(database: str) -> set[str]:
262
+ """Return every table/view as an exact ``schema.table`` identity."""
263
263
  rows = await _query(
264
264
  database,
265
265
  """
266
- SELECT table_name, array_agg(DISTINCT table_schema ORDER BY table_schema) AS schemas
266
+ SELECT DISTINCT table_schema, table_name
267
267
  FROM information_schema.tables
268
- GROUP BY table_name
268
+ ORDER BY table_schema, table_name
269
269
  """,
270
270
  )
271
- return {str(row["table_name"]): list(row["schemas"] or []) for row in rows}
271
+ return {f'{row["table_schema"]}.{row["table_name"]}' for row in rows}
272
272
 
273
273
 
274
274
  async def first_party_function_bodies(
@@ -45,5 +45,6 @@ Verified against code: 2026-08-13.
45
45
 
46
46
  ## Change log
47
47
 
48
+ - 2026-08-13: Registry, admin, recovery, catalog, and diagnostic identities are now exact `schema.table`; bare names are accepted only as uncached discovery terms and ambiguity fails loudly.
48
49
  - 2026-08-13: Added atomic recovery from a pool left at zero connections after an upstream database restart.
49
50
  - 2026-08-09: Pinned positional-mapping hydration for parameter-shaped column names.
@@ -80,7 +80,7 @@ WHERE table_schema = $1
80
80
 
81
81
 
82
82
  async def introspect_columns(database: str, schema: str, tables: list[str]) -> dict[str, list[str]]:
83
- """Live column names per table for ``schema`` — ``{table: [column, ...]}``.
83
+ """Live columns keyed by exact identity — ``{schema.table: [column, ...]}``.
84
84
 
85
85
  The catalog question "what columns does this table actually have right
86
86
  now?" belongs to the ORM: it is a fact about Postgres, not a row in an
@@ -96,7 +96,7 @@ async def introspect_columns(database: str, schema: str, tables: list[str]) -> d
96
96
  rows = await AsyncDatabaseManager.execute_query(database, _COLUMNS_SQL, schema, tables)
97
97
  out: dict[str, list[str]] = {}
98
98
  for r in rows:
99
- out.setdefault(r["table_name"], []).append(r["column_name"])
99
+ out.setdefault(f'{schema}.{r["table_name"]}', []).append(r["column_name"])
100
100
  return out
101
101
 
102
102
 
@@ -355,7 +355,7 @@ async def approx_row_counts(
355
355
  """``pg_class.reltuples`` approximate row counts for a set of tables in
356
356
  one schema — the cheap "how much data is loaded" read (exact ``COUNT(*)``
357
357
  is too slow on large bulk-load tables; autovacuum keeps ``reltuples``
358
- warm). Returns ``{table_name: approx_rows}``; a table with no matching
358
+ warm). Returns ``{schema.table: approx_rows}``; a table with no matching
359
359
  ``pg_class`` row (not created yet) is simply absent from the result."""
360
360
  _ident(schema, what="schema")
361
361
  for t in table_names:
@@ -370,7 +370,7 @@ async def approx_row_counts(
370
370
  rows = await _fetch(
371
371
  config_name, sql, schema, list(set(table_names)), connection=connection, pool=pool
372
372
  )
373
- return {r["table_name"]: max(0, int(r["approx_rows"])) for r in rows}
373
+ return {f'{schema}.{r["table_name"]}': max(0, int(r["approx_rows"])) for r in rows}
374
374
 
375
375
 
376
376
  async def copy_records(
@@ -192,7 +192,9 @@ When `Session.flush()` raises, every dropped op is captured to
192
192
  `public.system_write_failure` on a fresh asyncpg connection (bypasses
193
193
  the failed transaction's pool slot). The recovery tool
194
194
  (`scripts/replay_write_failures.py` in aidream) can replay them
195
- later. Same schema as Phase 1 — no migration change.
195
+ later. `table_target` is always the exact `schema.table`; a model without both
196
+ identity parts is rejected before capture. Replay never guesses a schema from a
197
+ bare name.
196
198
 
197
199
  If the fallback ALSO fails, a red banner prints every op to stdout and
198
200
  the structured logger captures them with full payload.
@@ -526,7 +526,7 @@ async def sweep_once(
526
526
  database: str | None = None,
527
527
  limit: int = SWEEP_LIMIT_DEFAULT,
528
528
  ) -> dict[str, int]:
529
- """One pass over every registered config. Returns ``{table: stuck_count}``."""
529
+ """One pass over every registered config. Returns ``{schema.table: stuck_count}``."""
530
530
  out: dict[str, int] = {}
531
531
  now = datetime.now(UTC)
532
532
 
@@ -541,11 +541,11 @@ async def sweep_once(
541
541
  # logger.error (NOT .exception): the in-package asyncpg traceback is
542
542
  # noise — the banner already carries the clean reason + origin.
543
543
  logger.error("watchdog_select_failed", extra=structured)
544
- out[cfg.table] = 0
544
+ out[f"{cfg.schema}.{cfg.table}"] = 0
545
545
  continue
546
546
 
547
547
  stuck_count = len(rows)
548
- out[cfg.table] = stuck_count
548
+ out[f"{cfg.schema}.{cfg.table}"] = stuck_count
549
549
  if stuck_count == 0:
550
550
  continue
551
551
 
@@ -164,30 +164,35 @@ _WRITE_TARGET_RE = re.compile(
164
164
  )
165
165
 
166
166
 
167
- def _register_strict_table(model_cls: type) -> None:
168
- """Record a strict-managed Model's table name(s) for the execute_write guard."""
167
+ def _canonical_table_key(value: str) -> str:
168
+ """Return an exact ``schema.table`` key for a model or SQL target."""
169
+ parts = [part.strip().strip('"') for part in value.strip().split(".")]
170
+ if len(parts) == 2 and all(parts):
171
+ return f"{parts[0]}.{parts[1]}"
172
+ raise ValueError(f"invalid table target {value!r}; expected schema.table")
173
+
174
+
175
+ def _model_table_key(model_cls: type) -> str | None:
169
176
  meta = getattr(model_cls, "_meta", None)
170
177
  if meta is None:
171
- return
172
- qualified = getattr(meta, "qualified_table_name", None)
173
- bare = getattr(meta, "table_name", None)
174
- for name in (qualified, bare):
175
- if name:
176
- _STRICT_TABLES.add(str(name).strip('"'))
178
+ return None
179
+ table = getattr(meta, "table_name", None)
180
+ if not table:
181
+ return None
182
+ schema = getattr(meta, "db_schema", None) or "public"
183
+ return _canonical_table_key(f"{schema}.{table}")
184
+
185
+
186
+ def _register_strict_table(model_cls: type) -> None:
187
+ """Record a strict-managed Model under its exact ``schema.table`` key."""
188
+ if key := _model_table_key(model_cls):
189
+ _STRICT_TABLES.add(key)
177
190
 
178
191
 
179
192
  def _register_coordinator_table(model_cls: type, required_scope: str) -> None:
180
- meta = getattr(model_cls, "_meta", None)
181
- if meta is None:
182
- return
183
- for name in (
184
- getattr(meta, "qualified_table_name", None),
185
- getattr(meta, "table_name", None),
186
- ):
187
- if name:
188
- normalized = str(name).strip('"')
189
- _COORDINATOR_TABLES[normalized] = required_scope
190
- _STRICT_TABLES.discard(normalized)
193
+ if key := _model_table_key(model_cls):
194
+ _COORDINATOR_TABLES[key] = required_scope
195
+ _STRICT_TABLES.discard(key)
191
196
 
192
197
 
193
198
  def register_coordinator_owned_model(entry: Any) -> None:
@@ -202,16 +207,8 @@ def coordinator_policy_tables() -> dict[str, str]:
202
207
 
203
208
 
204
209
  def _coordinator_scope_for_model(model_cls: type) -> str | None:
205
- meta = getattr(model_cls, "_meta", None)
206
- if meta is None:
207
- return None
208
- for name in (
209
- getattr(meta, "qualified_table_name", None),
210
- getattr(meta, "table_name", None),
211
- ):
212
- if name and (scope := _COORDINATOR_TABLES.get(str(name).strip('"'))):
213
- return scope
214
- return None
210
+ key = _model_table_key(model_cls)
211
+ return _COORDINATOR_TABLES.get(key) if key else None
215
212
 
216
213
 
217
214
  def assert_model_write_allowed(
@@ -274,12 +271,21 @@ def assert_managed_write_allowed(query: str) -> None:
274
271
  m = _WRITE_TARGET_RE.match(query)
275
272
  if not m:
276
273
  return
277
- token = m.group(1).strip().strip('"')
278
- bare_token = token.split(".")[-1].strip('"')
274
+ raw_target = m.group(1)
275
+ try:
276
+ token = _canonical_table_key(raw_target)
277
+ except ValueError as exc:
278
+ message = (
279
+ f"Bare write target {raw_target!r} refused at the managed-write chokepoint; "
280
+ "write SQL must name schema.table so guards cannot bind the wrong relation"
281
+ )
282
+ vcprint(message, color="red")
283
+ logger.error(message)
284
+ raise ManagedWriteViolation(raw_target, "write SQL without schema.table") from exc
279
285
 
280
- required_scope = _COORDINATOR_TABLES.get(token) or _COORDINATOR_TABLES.get(bare_token)
286
+ required_scope = _COORDINATOR_TABLES.get(token)
281
287
  if required_scope is not None:
282
- model_cls = _model_for_coordinator_table(token, bare_token)
288
+ model_cls = _model_for_coordinator_table(token)
283
289
  if model_cls is not None:
284
290
  assert_model_write_allowed(model_cls, "write")
285
291
  else:
@@ -300,7 +306,7 @@ def assert_managed_write_allowed(query: str) -> None:
300
306
  except ImportError:
301
307
  pass
302
308
 
303
- if token in _STRICT_TABLES or token.split(".")[-1].strip('"') in _STRICT_TABLES:
309
+ if token in _STRICT_TABLES:
304
310
  banner = _format_chokepoint_banner(token)
305
311
  vcprint(banner, color="red")
306
312
  logger.error(
@@ -318,24 +324,10 @@ def _active_write_scope() -> str | None:
318
324
  return None
319
325
 
320
326
 
321
- def _model_for_coordinator_table(token: str, bare_token: str) -> type | None:
327
+ def _model_for_coordinator_table(token: str) -> type | None:
322
328
  from matrx_orm.core.registry import ModelRegistry
323
-
324
- for model_cls in ModelRegistry.all_models().values():
325
- meta = getattr(model_cls, "_meta", None)
326
- if meta is None:
327
- continue
328
- names = {
329
- str(name).strip('"')
330
- for name in (
331
- getattr(meta, "qualified_table_name", None),
332
- getattr(meta, "table_name", None),
333
- )
334
- if name
335
- }
336
- if token in names or bare_token in names:
337
- return model_cls
338
- return None
329
+ schema, table = token.split(".", 1)
330
+ return ModelRegistry.get_model_by_table(schema, table)
339
331
 
340
332
 
341
333
  def is_strict_table(model_cls: type) -> bool:
@@ -346,16 +338,8 @@ def is_strict_table(model_cls: type) -> bool:
346
338
  """
347
339
  if not _STRICT_TABLES:
348
340
  return False
349
- meta = getattr(model_cls, "_meta", None)
350
- if meta is None:
351
- return False
352
- for name in (
353
- getattr(meta, "qualified_table_name", None),
354
- getattr(meta, "table_name", None),
355
- ):
356
- if name and str(name).strip('"') in _STRICT_TABLES:
357
- return True
358
- return False
341
+ key = _model_table_key(model_cls)
342
+ return key in _STRICT_TABLES if key else False
359
343
 
360
344
 
361
345
  @asynccontextmanager
@@ -103,20 +103,23 @@ class SessionOp:
103
103
 
104
104
  @property
105
105
  def table(self) -> str:
106
- """Convenience accessor for telemetry / logging. Reads the Model's
107
- configured table name, falling back to the class name."""
106
+ """Exact ``schema.table`` identity for telemetry and durable replay."""
108
107
  meta = getattr(self.model_cls, "_meta", None)
109
108
  if meta is not None:
110
- return getattr(meta, "table_name", None) or self.model_cls.__name__
111
- return self.model_cls.__name__
109
+ table = getattr(meta, "table_name", None)
110
+ if table:
111
+ schema = getattr(meta, "db_schema", None) or "public"
112
+ return f"{schema}.{table}"
113
+ raise ValueError(
114
+ f"SessionOp model {self.model_cls.__name__} has no canonical schema.table identity"
115
+ )
112
116
 
113
117
  def to_record(self) -> dict[str, Any]:
114
118
  """Serialize for the fallback writer.
115
119
 
116
120
  Returns a dict that the fallback can write into system_write_failure
117
- without any further introspection. The ``table_target`` field uses
118
- the table name (string) because the fallback is schema-agnostic and
119
- recovery tools may not have the Model class available.
121
+ without any further introspection. ``table_target`` is always the exact
122
+ ``schema.table`` key so replay cannot bind a same-named table elsewhere.
120
123
  """
121
124
  return {
122
125
  "op_id": self.op_id,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "matrx-orm"
3
- version = "3.1.63"
3
+ version = "3.1.64"
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" }
@@ -9,6 +9,7 @@ from matrx_orm.admin.router import (
9
9
  _admin_column_descriptors,
10
10
  _db_row_to_descriptor,
11
11
  _decode_row_id,
12
+ _model_by_table,
12
13
  _serialize,
13
14
  )
14
15
 
@@ -27,6 +28,25 @@ def test_serialize_includes_extra_data() -> None:
27
28
  assert out["role"] == "authenticated"
28
29
 
29
30
 
31
+ def test_admin_model_lookup_defaults_to_exact_public_key() -> None:
32
+ sentinel = object()
33
+ with patch(
34
+ "matrx_orm.admin.router.model_registry.get_model_by_table",
35
+ return_value=sentinel,
36
+ ) as lookup:
37
+ assert _model_by_table("definition") is sentinel
38
+ lookup.assert_called_once_with("public", "definition")
39
+
40
+
41
+ def test_admin_model_lookup_uses_requested_schema() -> None:
42
+ with patch(
43
+ "matrx_orm.admin.router.model_registry.get_model_by_table",
44
+ return_value=None,
45
+ ) as lookup:
46
+ assert _model_by_table("definition", "agent") is None
47
+ lookup.assert_called_once_with("agent", "definition")
48
+
49
+
30
50
  def test_composite_row_identity_round_trips() -> None:
31
51
  meta = SimpleNamespace(
32
52
  fields={"site_id": object(), "page_id": object()},
@@ -106,17 +106,19 @@ async def test_function_access_zips_requested_functions(query_spy):
106
106
 
107
107
 
108
108
  @pytest.mark.asyncio
109
- async def test_table_schema_map_normalizes_rows(monkeypatch):
109
+ async def test_qualified_relation_names_normalizes_rows(monkeypatch):
110
110
  async def execute_query(database, sql, *params):
111
111
  return [
112
- {"table_name": "notes", "schemas": ["public", "workbench"]},
113
- {"table_name": "files", "schemas": ["files"]},
112
+ {"table_schema": "public", "table_name": "notes"},
113
+ {"table_schema": "workbench", "table_name": "notes"},
114
+ {"table_schema": "files", "table_name": "files"},
114
115
  ]
115
116
 
116
117
  monkeypatch.setattr(catalog.AsyncDatabaseManager, "execute_query", execute_query)
117
- assert await catalog.table_schema_map("main") == {
118
- "notes": ["public", "workbench"],
119
- "files": ["files"],
118
+ assert await catalog.qualified_relation_names("main") == {
119
+ "public.notes",
120
+ "workbench.notes",
121
+ "files.files",
120
122
  }
121
123
 
122
124
 
@@ -339,7 +339,7 @@ async def test_approx_row_counts_shape() -> None:
339
339
  out = await staging_load.approx_row_counts(
340
340
  "db", schema="legal", table_names=["courts", "dockets"], connection=conn
341
341
  )
342
- assert out == {"courts": 1234}
342
+ assert out == {"legal.courts": 1234}
343
343
  _kind, sql, params = conn.calls[0]
344
344
  assert "pg_class" in sql and "pg_namespace" in sql
345
345
  assert params[0] == "legal"
@@ -16,7 +16,7 @@ from __future__ import annotations
16
16
  from unittest.mock import AsyncMock
17
17
 
18
18
  import pytest
19
- from matrx_orm import introspect_rls_policies
19
+ from matrx_orm import introspect_columns, introspect_rls_policies
20
20
 
21
21
  pytestmark = pytest.mark.asyncio
22
22
 
@@ -77,3 +77,19 @@ async def test_empty_input_short_circuits_without_a_query(monkeypatch) -> None:
77
77
  async def test_mismatched_array_lengths_raise() -> None:
78
78
  with pytest.raises(ValueError, match="same length"):
79
79
  await introspect_rls_policies("db", ["public", "workspace"], ["notes"])
80
+
81
+
82
+ async def test_introspect_columns_returns_qualified_keys(monkeypatch) -> None:
83
+ mock_execute = AsyncMock(
84
+ return_value=[
85
+ {"table_name": "definition", "column_name": "id"},
86
+ {"table_name": "definition", "column_name": "name"},
87
+ ]
88
+ )
89
+ monkeypatch.setattr(
90
+ "matrx_orm.core.introspection.AsyncDatabaseManager.execute_query", mock_execute
91
+ )
92
+
93
+ assert await introspect_columns("db", "workflow", ["definition"]) == {
94
+ "workflow.definition": ["id", "name"]
95
+ }
@@ -58,27 +58,41 @@ class TestStrictGuard:
58
58
  M.assert_managed_write_allowed("UPDATE cx_message SET x=1") # no raise
59
59
 
60
60
  def test_ungoverned_write_to_strict_table_raises(self):
61
- M._STRICT_TABLES.add("cx_message")
61
+ M._STRICT_TABLES.add("public.cx_message")
62
62
  with pytest.raises(ManagedWriteViolation):
63
- M.assert_managed_write_allowed("UPDATE cx_message SET x=1 WHERE id=$1")
63
+ M.assert_managed_write_allowed("UPDATE public.cx_message SET x=1 WHERE id=$1")
64
64
 
65
- def test_non_strict_table_passes(self):
66
- M._STRICT_TABLES.add("cx_message")
67
- M.assert_managed_write_allowed("UPDATE some_other_table SET x=1") # no raise
65
+ def test_non_strict_qualified_table_passes(self):
66
+ M._STRICT_TABLES.add("public.cx_message")
67
+ M.assert_managed_write_allowed("UPDATE public.some_other_table SET x=1") # no raise
68
68
 
69
- def test_qualified_sql_matches_bare_registration(self):
70
- M._STRICT_TABLES.add("cx_message")
69
+ def test_bare_write_target_is_refused_even_when_not_managed(self):
70
+ M._STRICT_TABLES.add("public.cx_message")
71
+ with pytest.raises(ManagedWriteViolation, match="schema.table"):
72
+ M.assert_managed_write_allowed("UPDATE some_other_table SET x=1")
73
+
74
+ def test_qualified_sql_matches_qualified_registration(self):
75
+ M._STRICT_TABLES.add("public.cx_message")
71
76
  with pytest.raises(ManagedWriteViolation):
72
77
  M.assert_managed_write_allowed("INSERT INTO public.cx_message (id) VALUES ($1)")
73
78
 
79
+ def test_same_bare_name_in_another_schema_does_not_match(self):
80
+ M._STRICT_TABLES.add("chat.message")
81
+ M.assert_managed_write_allowed("UPDATE public.message SET x=1")
82
+
83
+ def test_quoted_qualified_target_matches_exact_key(self):
84
+ M._STRICT_TABLES.add("chat.message")
85
+ with pytest.raises(ManagedWriteViolation):
86
+ M.assert_managed_write_allowed('UPDATE "chat"."message" SET x=1')
87
+
74
88
  def test_flush_context_is_allowed(self, monkeypatch):
75
- M._STRICT_TABLES.add("cx_message")
89
+ M._STRICT_TABLES.add("public.cx_message")
76
90
  monkeypatch.setattr(flush_mod, "is_flushing", lambda: True)
77
- M.assert_managed_write_allowed("UPDATE cx_message SET x=1") # no raise
91
+ M.assert_managed_write_allowed("UPDATE public.cx_message SET x=1") # no raise
78
92
 
79
93
  def test_unparseable_query_is_allowed(self):
80
94
  # A query we can't parse falls through (wrappers remain first defense).
81
- M._STRICT_TABLES.add("cx_message")
95
+ M._STRICT_TABLES.add("public.cx_message")
82
96
  M.assert_managed_write_allowed("WITH t AS (...) UPDATE cx_message ...") # no raise
83
97
 
84
98
 
@@ -91,9 +105,9 @@ class TestDeclareManagedRegistersStrictTable:
91
105
  from matrx_orm.session.managed import declare_managed
92
106
 
93
107
  declare_managed(model, mode="strict")
94
- assert "strict_thing" in M._STRICT_TABLES
108
+ assert "public.strict_thing" in M._STRICT_TABLES
95
109
  with pytest.raises(ManagedWriteViolation):
96
- M.assert_managed_write_allowed("DELETE FROM strict_thing WHERE id=$1")
110
+ M.assert_managed_write_allowed("DELETE FROM public.strict_thing WHERE id=$1")
97
111
 
98
112
  def test_elevate_declaration_does_not_register_strict(self):
99
113
  model = _make_model(
@@ -103,8 +117,8 @@ class TestDeclareManagedRegistersStrictTable:
103
117
  from matrx_orm.session.managed import declare_managed
104
118
 
105
119
  declare_managed(model, mode="elevate")
106
- assert "elevate_thing" not in M._STRICT_TABLES
107
- M.assert_managed_write_allowed("UPDATE elevate_thing SET x=1") # no raise
120
+ assert "public.elevate_thing" not in M._STRICT_TABLES
121
+ M.assert_managed_write_allowed("UPDATE public.elevate_thing SET x=1") # no raise
108
122
 
109
123
 
110
124
  class TestCoordinatorOwnedGuard:
@@ -266,7 +280,7 @@ class TestCoordinatorOwnedGuard:
266
280
  {"id": UUIDField(primary_key=True), "_table_name": "owned_thing"},
267
281
  )
268
282
  M.declare_managed(model, mode="coordinator")
269
- M.assert_managed_write_allowed("UPDATE owned_thing SET x=1")
283
+ M.assert_managed_write_allowed("UPDATE public.owned_thing SET x=1")
270
284
  assert "COORDINATOR OWNERSHIP BYPASS" in caplog.text
271
285
 
272
286
  def test_internal_flush_is_silent_without_session_stack(self, caplog, monkeypatch):
@@ -174,7 +174,7 @@ def test_summarize_counts_per_table(FakeMessage, FakeRequest):
174
174
  make_insert(FakeMessage, {"id": "m2"}),
175
175
  make_insert(FakeRequest, {"id": "r1", "conversation_id": "c1"}),
176
176
  ]
177
- assert summarize(ops) == {"cx_message": 2, "cx_request": 1}
177
+ assert summarize(ops) == {"public.cx_message": 2, "public.cx_request": 1}
178
178
 
179
179
 
180
180
  def test_real_world_persist_completed_request_shape(
@@ -77,7 +77,7 @@ def test_tier_summary_counts(FakeConversation, FakeMessage):
77
77
  ]
78
78
  tiers = tier_ops(ops)
79
79
  summary = tier_summary(tiers)
80
- assert summary == [{"cx_conversation": 1}, {"cx_message": 2}]
80
+ assert summary == [{"public.cx_conversation": 1}, {"public.cx_message": 2}]
81
81
 
82
82
 
83
83
  class _UnresolvableFK:
@@ -27,6 +27,7 @@ from matrx_orm.session.op import SessionOp
27
27
 
28
28
  class _FakeMeta:
29
29
  table_name = "cx_message"
30
+ db_schema = "public"
30
31
 
31
32
 
32
33
  class _FakeModel:
@@ -50,7 +51,7 @@ def _row(op_id: str = "op-1", pk: str = "m1") -> dict:
50
51
  "request_id": "req-1",
51
52
  "user_id": None,
52
53
  "conversation_id": None,
53
- "table_target": "cx_message",
54
+ "table_target": "public.cx_message",
54
55
  "op_type": "insert",
55
56
  "primary_key": json.dumps({"id": pk}),
56
57
  "payload": json.dumps({"id": pk, "content": "hi"}),
@@ -122,7 +123,7 @@ def test_spill_writes_one_file_per_op(spill_dir):
122
123
  # Content round-trips to the exact row shape.
123
124
  loaded = json.loads((spill_dir / "op-a.json").read_text())
124
125
  assert loaded["op_id"] == "op-a"
125
- assert loaded["table_target"] == "cx_message"
126
+ assert loaded["table_target"] == "public.cx_message"
126
127
  # No temp files left behind.
127
128
  assert not list(spill_dir.glob(".*.tmp"))
128
129
 
@@ -164,7 +165,7 @@ async def test_record_failures_spills_when_db_capture_fails(spill_dir, monkeypat
164
165
  assert len(files) == 1
165
166
  row = json.loads(files[0].read_text())
166
167
  assert row["op_id"] == "op-outage"
167
- assert row["table_target"] == "cx_message"
168
+ assert row["table_target"] == "public.cx_message"
168
169
  assert row["request_id"] == "req-9"
169
170
 
170
171