matrx-orm 3.1.76__tar.gz → 3.1.78__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 (344) hide show
  1. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/CLAUDE.md +4 -1
  2. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/PKG-INFO +5 -2
  3. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/README.md +2 -0
  4. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/__init__.py +7 -0
  5. matrx_orm-3.1.78/matrx_orm/admin/FEATURE.md +34 -0
  6. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/admin/router.py +105 -36
  7. matrx_orm-3.1.78/matrx_orm/local_sqlite.py +40 -0
  8. matrx_orm-3.1.78/matrx_orm/operations/FEATURE.md +23 -0
  9. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/conflict_writes.py +9 -2
  10. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/pyproject.toml +3 -2
  11. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_admin_db_columns.py +64 -2
  12. matrx_orm-3.1.78/tests/level1/test_admin_response_rendering.py +61 -0
  13. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_conflict_writes.py +74 -0
  14. matrx_orm-3.1.78/tests/level1/test_local_sqlite.py +58 -0
  15. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/.gitignore +0 -0
  16. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/AGENTS.md +0 -0
  17. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/BASE_CLASS_METHODS.md +0 -0
  18. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/MODEL_API.md +0 -0
  19. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/database/orm/extended/managers/ai_model_base.py +0 -0
  20. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/docs/migrations.md +0 -0
  21. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/MODULE_README.md +0 -0
  22. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/adapters/MODULE_README.md +0 -0
  23. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/adapters/__init__.py +0 -0
  24. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/adapters/async_postgresql.py +0 -0
  25. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/adapters/base_adapter.py +0 -0
  26. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/adapters/postgresql.py +0 -0
  27. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  28. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/adapters/supabase_adapter.py +0 -0
  29. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/admin/__init__.py +0 -0
  30. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/api/__init__.py +0 -0
  31. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/api/auth.py +0 -0
  32. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/api/config.py +0 -0
  33. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/api/handlers.py +0 -0
  34. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/api/protocol.py +0 -0
  35. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/api/server.py +0 -0
  36. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/associations.py +0 -0
  37. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/cache_debug.py +0 -0
  38. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/catalog.py +0 -0
  39. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/catalog_sql.py +0 -0
  40. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/client/MODULE_README.md +0 -0
  41. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/client/__init__.py +0 -0
  42. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/client/postgres_connection.py +0 -0
  43. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/client/postgrest.py +0 -0
  44. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/client/supabase_auth.py +0 -0
  45. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/client/supabase_config.py +0 -0
  46. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/client/supabase_manager.py +0 -0
  47. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  48. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/FEATURE.md +0 -0
  49. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/MODULE_README.md +0 -0
  50. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  51. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/TASKS.md +0 -0
  52. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/__init__.py +0 -0
  53. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/async_db_manager.py +0 -0
  54. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/base.py +0 -0
  55. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/config.py +0 -0
  56. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/connections.py +0 -0
  57. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/db_function.py +0 -0
  58. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/diagnostics.py +0 -0
  59. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/expressions.py +0 -0
  60. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/extended.py +0 -0
  61. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/fields.py +0 -0
  62. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/introspection.py +0 -0
  63. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/listen.py +0 -0
  64. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/loop_filters.py +0 -0
  65. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/model_dto.py +0 -0
  66. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/model_view.py +0 -0
  67. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/one_database.py +0 -0
  68. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/paginator.py +0 -0
  69. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/pool_maintenance.py +0 -0
  70. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/pool_watch.py +0 -0
  71. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/pooler_role.py +0 -0
  72. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/pydantic_bridge.py +0 -0
  73. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/registry.py +0 -0
  74. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/relations.py +0 -0
  75. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/resilience.py +0 -0
  76. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/rls_session.py +0 -0
  77. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/signals.py +0 -0
  78. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/tls.py +0 -0
  79. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/transaction.py +0 -0
  80. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/types.py +0 -0
  81. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/core/write_queue.py +0 -0
  82. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/entity.py +0 -0
  83. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/error_handling.py +0 -0
  84. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/exceptions.py +0 -0
  85. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/extended/__init__.py +0 -0
  86. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/extended/app_error_handler.py +0 -0
  87. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/identity_rule.py +0 -0
  88. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/middleware/__init__.py +0 -0
  89. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/middleware/base.py +0 -0
  90. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/MODULE_README.md +0 -0
  91. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/__init__.py +0 -0
  92. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/cli.py +0 -0
  93. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/ddl.py +0 -0
  94. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/diff.py +0 -0
  95. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/executor.py +0 -0
  96. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/integration.py +0 -0
  97. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/loader.py +0 -0
  98. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/operations.py +0 -0
  99. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/state.py +0 -0
  100. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/migrations/table_filter.py +0 -0
  101. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/MODULE_README.md +0 -0
  102. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/__init__.py +0 -0
  103. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/bulk_update_values.py +0 -0
  104. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/conflict.py +0 -0
  105. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/create.py +0 -0
  106. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/db_functions.py +0 -0
  107. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/delete.py +0 -0
  108. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/dynamic_admin.py +0 -0
  109. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/dynamic_crud.py +0 -0
  110. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/graph_walk.py +0 -0
  111. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/latest_rows.py +0 -0
  112. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/queue_claim.py +0 -0
  113. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/read.py +0 -0
  114. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/relational_backfill.py +0 -0
  115. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/staging_load.py +0 -0
  116. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/operations/update.py +0 -0
  117. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/platform_db.py +0 -0
  118. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/py.typed +0 -0
  119. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/python_sql/MODULE_README.md +0 -0
  120. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/python_sql/__init__.py +0 -0
  121. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/python_sql/db_objects.py +0 -0
  122. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  123. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  124. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/query/FEATURE.md +0 -0
  125. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/query/MODULE_README.md +0 -0
  126. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/query/__init__.py +0 -0
  127. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/query/builder.py +0 -0
  128. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/query/executor.py +0 -0
  129. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/FEATURE.md +0 -0
  130. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  131. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/__init__.py +0 -0
  132. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/code_handler.py +0 -0
  133. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  134. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/columns.py +0 -0
  135. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/common.py +0 -0
  136. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/diff_preview.py +0 -0
  137. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  138. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/generator.py +0 -0
  139. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  140. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  141. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  142. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  143. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/package_wiring.py +0 -0
  144. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/relationships.py +0 -0
  145. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/runner.py +0 -0
  146. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/schema.py +0 -0
  147. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/schema_manager.py +0 -0
  148. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/tables.py +0 -0
  149. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/schema_builder/views.py +0 -0
  150. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/secrets_battery/FEATURE.md +0 -0
  151. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/secrets_battery/__init__.py +0 -0
  152. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/secrets_battery/attachments.py +0 -0
  153. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/secrets_battery/crypto.py +0 -0
  154. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/secrets_battery/items.py +0 -0
  155. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/secrets_battery/model.py +0 -0
  156. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/secrets_battery/org_service.py +0 -0
  157. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/secrets_battery/service.py +0 -0
  158. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/README.md +0 -0
  159. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/__init__.py +0 -0
  160. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/coalesce.py +0 -0
  161. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/dag.py +0 -0
  162. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/errors.py +0 -0
  163. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/fallback.py +0 -0
  164. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/flush.py +0 -0
  165. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/lifecycle.py +0 -0
  166. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/managed.py +0 -0
  167. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/op.py +0 -0
  168. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/reads.py +0 -0
  169. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/session.py +0 -0
  170. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/session/telemetry.py +0 -0
  171. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  172. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/sql_executor/__init__.py +0 -0
  173. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/sql_executor/executor.py +0 -0
  174. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/sql_executor/queries.py +0 -0
  175. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/sql_executor/registry.py +0 -0
  176. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/sql_executor/types.py +0 -0
  177. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/sql_executor/utils.py +0 -0
  178. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/state.py +0 -0
  179. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/utils/__init__.py +0 -0
  180. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/utils/sql_utils.py +0 -0
  181. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/matrx_orm/utils/type_converters.py +0 -0
  182. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/scripts/git-branches.sh +0 -0
  183. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/scripts/publish.sh +0 -0
  184. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/scripts/release.sh +0 -0
  185. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/MODULE_README.md +0 -0
  186. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/__init__.py +0 -0
  187. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/conftest.py +0 -0
  188. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/MODULE_README.md +0 -0
  189. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/__init__.py +0 -0
  190. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_admin_search_negation.py +0 -0
  191. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_admin_search_sentinels.py +0 -0
  192. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_advisory_lock.py +0 -0
  193. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_agent_message.py +0 -0
  194. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_api_auth.py +0 -0
  195. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_api_config.py +0 -0
  196. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_api_handlers.py +0 -0
  197. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_api_protocol.py +0 -0
  198. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_array_agg_order_by.py +0 -0
  199. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_associations.py +0 -0
  200. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_bulk_hydration_offload.py +0 -0
  201. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_bulk_update_by_pk.py +0 -0
  202. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  203. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_cache_debug.py +0 -0
  204. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_call_function.py +0 -0
  205. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_case_when.py +0 -0
  206. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_catalog.py +0 -0
  207. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_composite_pk_filter.py +0 -0
  208. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_composite_pk_write_paths.py +0 -0
  209. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_config.py +0 -0
  210. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_conflict_target.py +0 -0
  211. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_connection_codecs.py +0 -0
  212. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_connection_poison_guard.py +0 -0
  213. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_connection_primitives.py +0 -0
  214. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_count_composite_distinct.py +0 -0
  215. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_db_function_field.py +0 -0
  216. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_ddl_generator.py +0 -0
  217. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_derived_table.py +0 -0
  218. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_dirty_tracking.py +0 -0
  219. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_dynamic_crud.py +0 -0
  220. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_exceptions.py +0 -0
  221. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_execute_query_integrity_mapping.py +0 -0
  222. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_expression_primitives.py +0 -0
  223. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_fields.py +0 -0
  224. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  225. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_from_alias.py +0 -0
  226. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_fts.py +0 -0
  227. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_func_expression_args.py +0 -0
  228. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_graph_walk.py +0 -0
  229. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_having_annotate_alias.py +0 -0
  230. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  231. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_introspect_rls_policies.py +0 -0
  232. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_join_and_aggregate.py +0 -0
  233. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_latest_rows.py +0 -0
  234. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_listen_notify.py +0 -0
  235. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_managed_write_guard.py +0 -0
  236. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_matrx_entity.py +0 -0
  237. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_migration_diff_types.py +0 -0
  238. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_migration_loader.py +0 -0
  239. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_model_instance.py +0 -0
  240. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_model_meta.py +0 -0
  241. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_optimistic_concurrency.py +0 -0
  242. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_order_by_expression.py +0 -0
  243. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_parameter_name_hydration.py +0 -0
  244. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_patch_jsonb_path.py +0 -0
  245. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_pool_holder_census.py +0 -0
  246. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_pool_loop_recreation.py +0 -0
  247. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_pool_maintenance.py +0 -0
  248. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_postgrest_filters.py +0 -0
  249. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_pydantic_bridge.py +0 -0
  250. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_query_builder.py +0 -0
  251. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_query_error_detail.py +0 -0
  252. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_query_executor_sql.py +0 -0
  253. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_query_timeout.py +0 -0
  254. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  255. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_queue_claim.py +0 -0
  256. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_registry.py +0 -0
  257. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_registry_multi_database.py +0 -0
  258. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_registry_multi_schema.py +0 -0
  259. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_relational_backfill.py +0 -0
  260. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_relations.py +0 -0
  261. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_resilience.py +0 -0
  262. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_rls_session_scope.py +0 -0
  263. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_schema_exists.py +0 -0
  264. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_session_advisory_lock.py +0 -0
  265. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_state_cache.py +0 -0
  266. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_subquery_filter_raw.py +0 -0
  267. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_subquery_in_filter.py +0 -0
  268. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_supabase_auth.py +0 -0
  269. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_supabase_config.py +0 -0
  270. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_tls.py +0 -0
  271. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_typed_join_projection.py +0 -0
  272. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_typed_predicates.py +0 -0
  273. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_update_case_expression.py +0 -0
  274. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_update_subquery.py +0 -0
  275. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_update_with_rebase.py +0 -0
  276. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_upsert_default_leak.py +0 -0
  277. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_upsert_with_conflict.py +0 -0
  278. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_values_jsonb_decode.py +0 -0
  279. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_watched_lifecycle.py +0 -0
  280. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level1/test_write_retry_semantics.py +0 -0
  281. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/MODULE_README.md +0 -0
  282. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/__init__.py +0 -0
  283. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/conftest.py +0 -0
  284. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/test_bulk_ops.py +0 -0
  285. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/test_cache_integration.py +0 -0
  286. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/test_crud.py +0 -0
  287. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/test_foreign_keys.py +0 -0
  288. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/test_m2m.py +0 -0
  289. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/test_manager.py +0 -0
  290. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/test_migrations_live.py +0 -0
  291. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/test_query_execution.py +0 -0
  292. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/level2/test_schema_diff.py +0 -0
  293. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/live_pooler_role.py +0 -0
  294. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project/.env.example +0 -0
  295. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project/README.md +0 -0
  296. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project/__init__.py +0 -0
  297. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project/generate.py +0 -0
  298. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project/generated/.gitkeep +0 -0
  299. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project/matrx_orm.yaml +0 -0
  300. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project/test_schema_generation.py +0 -0
  301. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project_desktop/.env.example +0 -0
  302. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project_desktop/README.md +0 -0
  303. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project_desktop/__init__.py +0 -0
  304. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project_desktop/client_example.py +0 -0
  305. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project_desktop/client_example.ts +0 -0
  306. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  307. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/sample_project_desktop/server.py +0 -0
  308. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/entity_tests.py +0 -0
  309. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_base_generation.py +0 -0
  310. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  311. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_composite_unique_generation.py +0 -0
  312. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_dto_identity_generation.py +0 -0
  313. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_entity_capability_generation.py +0 -0
  314. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_generate_schema.py +0 -0
  315. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_junction_analysis.py +0 -0
  316. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_multi_schema_output.py +0 -0
  317. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_sql_expression_defaults.py +0 -0
  318. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_table_relationships_query.py +0 -0
  319. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema/test_view_generation.py +0 -0
  320. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema_builder/test_array_default_parsing.py +0 -0
  321. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema_builder/test_focused_generation_plan.py +0 -0
  322. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/schema_builder/test_host_models_aggregator.py +0 -0
  323. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/__init__.py +0 -0
  324. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/conftest.py +0 -0
  325. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_cache_no_downgrade.py +0 -0
  326. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_capture_poison_proof.py +0 -0
  327. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_coalesce.py +0 -0
  328. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_dag.py +0 -0
  329. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_disk_spill_fallback.py +0 -0
  330. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_flush_individual_fallback.py +0 -0
  331. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_governed_write.py +0 -0
  332. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_managed.py +0 -0
  333. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_op_enqueue_site.py +0 -0
  334. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_reads.py +0 -0
  335. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/session/test_session.py +0 -0
  336. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/test_database_name_alias.py +0 -0
  337. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/test_db_functions.py +0 -0
  338. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/test_model_cls_refactor.py +0 -0
  339. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  340. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/test_one_database.py +0 -0
  341. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/test_pooler_role_guard.py +0 -0
  342. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/test_secrets_battery.py +0 -0
  343. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/test_sql_param_casts.py +0 -0
  344. {matrx_orm-3.1.76 → matrx_orm-3.1.78}/tests/test_sync_twins_guard.py +0 -0
@@ -38,8 +38,11 @@ Every table that becomes a Model needs row identity: a single-column primary key
38
38
  Large reconciliation/export scans call `.all(use_cache=False)` so returned
39
39
  models do not quadratically populate the process identity cache. Contract:
40
40
  [`matrx_orm/query/FEATURE.md`](matrx_orm/query/FEATURE.md).
41
+ - **Conflict writes**: bulk insert-ignore and counter-upsert row coercion runs
42
+ off the event loop before statement chunking. Contract:
43
+ [`matrx_orm/operations/FEATURE.md`](matrx_orm/operations/FEATURE.md).
41
44
  - **Migrations**: `MigrationDB`, `MigrationLoader`, `MigrationExecutor`, `makemigrations`, `migrate` — migrations declare explicit `dependencies` and apply in topological order (no multi-head/merge support yet).
42
- - **Admin router** (FastAPI): `admin_router` exposes read/write endpoints for any registered model.
45
+ - **Admin router** (FastAPI): `admin_router` exposes read/write endpoints for any registered model; bulk response rendering stays off the event loop. Contract: [`matrx_orm/admin/FEATURE.md`](matrx_orm/admin/FEATURE.md).
43
46
  - **API layer** (optional `[api]` extra): `APIServer`, `APIConfig`, `TokenAuth`.
44
47
  - **Adapters**: `AsyncPostgreSQLAdapter`, `SupabaseAdapter`, `PostgRESTClientAdapter`.
45
48
  - **Signals**: `pre_create`, `post_create`, `pre_save`, `post_save`, `pre_delete`, `post_delete`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matrx-orm
3
- Version: 3.1.76
3
+ Version: 3.1.78
4
4
  Summary: Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching
5
5
  Project-URL: Homepage, https://github.com/AI-Matrix-Engine/aidream-current
6
6
  Project-URL: Repository, https://github.com/AI-Matrix-Engine/aidream-current
@@ -25,13 +25,14 @@ Requires-Python: >=3.13
25
25
  Requires-Dist: asyncpg>=0.31.0
26
26
  Requires-Dist: cryptography>=43.0
27
27
  Requires-Dist: gitpython>=3.1.40
28
- Requires-Dist: matrx-utils>=2.0.17
28
+ Requires-Dist: matrx-utils>=2.0.19
29
29
  Requires-Dist: psycopg-pool>=3.2.5
30
30
  Requires-Dist: psycopg[binary]>=3.2.5
31
31
  Requires-Dist: python-dotenv>=1.0
32
32
  Requires-Dist: pyyaml>=6.0.3
33
33
  Provides-Extra: admin
34
34
  Requires-Dist: fastapi>=0.115; extra == 'admin'
35
+ Requires-Dist: orjson>=3.10; extra == 'admin'
35
36
  Provides-Extra: api
36
37
  Requires-Dist: aiohttp>=3.9.0; extra == 'api'
37
38
  Description-Content-Type: text/markdown
@@ -56,6 +57,8 @@ Python 3.13+ required. Needs a PostgreSQL server (or Supabase / any Postgres-com
56
57
  - **Admin router** (FastAPI): `admin_router` exposes a ready-to-mount set of read/write endpoints for every registered model.
57
58
  - **API layer** (optional `[api]` extra): `APIServer`, `APIConfig`, `TokenAuth`.
58
59
  - **Adapters**: `AsyncPostgreSQLAdapter`, `SupabaseAdapter`, `PostgRESTClientAdapter`.
60
+ - **Local artifact reads**: `read_local_sqlite_rows` provides a validated, read-only
61
+ projection boundary for SQLite files owned by external tools such as Chromium.
59
62
  - **Signals**: `pre_create`, `post_create`, `pre_save`, `post_save`, `pre_delete`, `post_delete`.
60
63
  - **Schema builder** (`matrx_orm.schema_builder`): code generation for Python + TypeScript type definitions from the live DB schema — useful for keeping a frontend's row types in sync.
61
64
 
@@ -18,6 +18,8 @@ Python 3.13+ required. Needs a PostgreSQL server (or Supabase / any Postgres-com
18
18
  - **Admin router** (FastAPI): `admin_router` exposes a ready-to-mount set of read/write endpoints for every registered model.
19
19
  - **API layer** (optional `[api]` extra): `APIServer`, `APIConfig`, `TokenAuth`.
20
20
  - **Adapters**: `AsyncPostgreSQLAdapter`, `SupabaseAdapter`, `PostgRESTClientAdapter`.
21
+ - **Local artifact reads**: `read_local_sqlite_rows` provides a validated, read-only
22
+ projection boundary for SQLite files owned by external tools such as Chromium.
21
23
  - **Signals**: `pre_create`, `post_create`, `pre_save`, `post_save`, `pre_delete`, `post_delete`.
22
24
  - **Schema builder** (`matrx_orm.schema_builder`): code generation for Python + TypeScript type definitions from the live DB schema — useful for keeping a frontend's row types in sync.
23
25
 
@@ -43,6 +43,7 @@ from .platform_db import (
43
43
  platform_connection_url,
44
44
  register_platform_db,
45
45
  )
46
+ from .local_sqlite import SQLiteValue, read_local_sqlite_rows
46
47
  from .core.diagnostics import collect_pool_diagnostics, log_pool_diagnostics
47
48
  from .core.introspection import introspect_columns, introspect_rls_policies, schema_exists
48
49
  from .core.one_database import OneDatabaseReport, PoolIdentity, audit_one_database
@@ -633,6 +634,12 @@ __all__ = [
633
634
  "AsyncPostgreSQLAdapter",
634
635
  "SupabaseAdapter",
635
636
  "PostgRESTClientAdapter",
637
+ "create_external_pool",
638
+ "get_registered_pool",
639
+ "open_listener_connection",
640
+ # ── Read-only local SQLite artifacts ──────────────────────────────────────
641
+ "SQLiteValue",
642
+ "read_local_sqlite_rows",
636
643
  # ── Cache ──────────────────────────────────────────────────────────────────
637
644
  "CachePolicy",
638
645
  "StateManager",
@@ -0,0 +1,34 @@
1
+ # Admin router
2
+
3
+ `router.py` exposes model-registry CRUD and cache operations through the optional
4
+ FastAPI admin surface.
5
+
6
+ ## Response rendering
7
+
8
+ - **Bulk row rendering never runs on the event loop.** `list_rows` passes row
9
+ serialization and JSON encoding through `_render_list_rows_response`, which
10
+ offloads the CPU-bound work with `asyncio.to_thread`; `orjson` bounds the
11
+ GIL-held encoding section.
12
+ - **Keep both list aliases.** `data` and `rows` contain the same serialized rows;
13
+ admin clients use both shapes during compatibility transitions.
14
+ - **The page cap is 2,000 rows.** Keep `ADMIN_LIST_MAX_PAGE_SIZE` aligned with the
15
+ dashboard constant in `orm-proxy.ts`.
16
+
17
+ ## Database identity
18
+
19
+ - **A table is `database.schema.table`.** `GET /tables` and cache stats expose
20
+ `database_name`; every columns, row, mutation, bulk, and cache endpoint accepts
21
+ `database=` and passes it to the multi-database model registry.
22
+ - **Ambiguity is a controlled conflict.** An omitted database that matches more
23
+ than one registered model returns HTTP 409 with the candidates. It never guesses
24
+ and never escapes as a request crash.
25
+
26
+ Verified against `router.py` and
27
+ `tests/level1/test_admin_db_columns.py` on 2026-08-17.
28
+
29
+ ## Change Log
30
+
31
+ - 2026-08-17 — Moved bulk row serialization and JSON encoding off the event
32
+ loop after a 1.1-second production stall.
33
+ - 2026-08-17 — Carried database identity through every admin relation endpoint
34
+ and converted unqualified cross-database ambiguity into HTTP 409.
@@ -24,6 +24,7 @@ Effective routes (relative to the mount prefix):
24
24
 
25
25
  from __future__ import annotations
26
26
 
27
+ import asyncio
27
28
  import base64
28
29
  import binascii
29
30
  import json
@@ -32,7 +33,8 @@ import math
32
33
  import re
33
34
  from typing import Any
34
35
 
35
- from fastapi import APIRouter, HTTPException, Query, Request
36
+ import orjson
37
+ from fastapi import APIRouter, HTTPException, Query, Request, Response
36
38
  from fastapi.responses import JSONResponse
37
39
 
38
40
  from matrx_orm.core.expressions import Q
@@ -56,22 +58,42 @@ _COMPOSITE_ROW_ID_PREFIX = "cmp1_"
56
58
  # ---------------------------------------------------------------------------
57
59
 
58
60
 
59
- def _model_by_table(table_name: str, schema: str | None = None) -> type | None:
60
- return model_registry.get_model_by_table(schema or "public", table_name)
61
+ def _model_by_table(
62
+ table_name: str,
63
+ schema: str | None = None,
64
+ database: str | None = None,
65
+ ) -> type | None:
66
+ return model_registry.get_model_by_table(
67
+ schema or "public",
68
+ table_name,
69
+ database=database,
70
+ )
61
71
 
62
72
 
63
- def _require_model(table_name: str, schema: str | None = None) -> type:
64
- model = _model_by_table(table_name, schema)
73
+ def _require_model(
74
+ table_name: str,
75
+ schema: str | None = None,
76
+ database: str | None = None,
77
+ ) -> type:
78
+ try:
79
+ model = _model_by_table(table_name, schema, database)
80
+ except ValueError as exc:
81
+ raise HTTPException(status_code=409, detail=str(exc)) from exc
65
82
  if model is None:
66
83
  effective_schema = schema or "public"
84
+ database_suffix = f" in database '{database}'" if database else ""
67
85
  log.warning(
68
- "Table '%s.%s' not found in model registry",
86
+ "Table '%s.%s' not found in model registry%s",
69
87
  effective_schema,
70
88
  table_name,
89
+ database_suffix,
71
90
  )
72
91
  raise HTTPException(
73
92
  status_code=404,
74
- detail=f"Table '{effective_schema}.{table_name}' not found in model registry",
93
+ detail=(
94
+ f"Table '{effective_schema}.{table_name}' not found in model registry"
95
+ f"{database_suffix}"
96
+ ),
75
97
  )
76
98
  return model
77
99
 
@@ -113,6 +135,46 @@ def _serialize(instance: Any) -> dict[str, Any]:
113
135
  return complete
114
136
 
115
137
 
138
+ def _build_list_rows_response(
139
+ items: list[Any],
140
+ *,
141
+ total_count: int,
142
+ page: int,
143
+ page_size: int,
144
+ ) -> Response:
145
+ data = [_serialize(row) for row in items]
146
+ total_pages = math.ceil(total_count / page_size) if page_size > 0 else 0
147
+ return Response(
148
+ content=orjson.dumps(
149
+ {
150
+ "data": data,
151
+ "rows": data,
152
+ "total": total_count,
153
+ "page": page,
154
+ "page_size": page_size,
155
+ "total_pages": total_pages,
156
+ }
157
+ ),
158
+ media_type="application/json",
159
+ )
160
+
161
+
162
+ async def _render_list_rows_response(
163
+ items: list[Any],
164
+ *,
165
+ total_count: int,
166
+ page: int,
167
+ page_size: int,
168
+ ) -> Response:
169
+ return await asyncio.to_thread(
170
+ _build_list_rows_response,
171
+ items,
172
+ total_count=total_count,
173
+ page=page,
174
+ page_size=page_size,
175
+ )
176
+
177
+
116
178
  def _decode_row_id(model: type, row_id: str) -> str | dict[str, Any]:
117
179
  primary_keys = list(model._meta.primary_keys or ())
118
180
  if len(primary_keys) <= 1:
@@ -386,16 +448,19 @@ async def _fetch_check_choices(model: type) -> dict[str, list[str]]:
386
448
  async def list_tables() -> list[dict[str, object]]:
387
449
  models = model_registry.all_models()
388
450
  log.info("list_tables: %d models in registry", len(models))
389
- sample_model = next(iter(models.values()), None)
390
- db_counts = await _fetch_db_column_counts(sample_model) if sample_model else {}
451
+ counts_by_database: dict[str, dict[tuple[str, str], int]] = {}
391
452
  tables = []
392
453
  for model in models.values():
393
454
  meta = model._meta
394
455
  schema = _model_schema(model)
456
+ database = model.get_database_name()
457
+ if database not in counts_by_database:
458
+ counts_by_database[database] = await _fetch_db_column_counts(model)
395
459
  orm_count = len(meta.fields)
396
- db_count = db_counts.get((schema, meta.table_name))
460
+ db_count = counts_by_database[database].get((schema, meta.table_name))
397
461
  tables.append(
398
462
  {
463
+ "database_name": database,
399
464
  "table_name": meta.table_name,
400
465
  "table_schema": schema,
401
466
  "name": meta.table_name,
@@ -403,7 +468,7 @@ async def list_tables() -> list[dict[str, object]]:
403
468
  "column_count": db_count if db_count is not None else orm_count,
404
469
  }
405
470
  )
406
- tables.sort(key=lambda t: (t["table_schema"], t["table_name"]))
471
+ tables.sort(key=lambda t: (t["database_name"], t["table_schema"], t["table_name"]))
407
472
  return tables
408
473
 
409
474
 
@@ -411,8 +476,9 @@ async def list_tables() -> list[dict[str, object]]:
411
476
  async def get_columns(
412
477
  table_name: str,
413
478
  schema: str | None = Query(None, description="PostgreSQL schema name"),
479
+ database: str | None = Query(None, description="Registered database project name"),
414
480
  ) -> list[dict[str, object]]:
415
- model = _require_model(table_name, schema)
481
+ model = _require_model(table_name, schema, database)
416
482
  columns = await _admin_column_descriptors(model)
417
483
 
418
484
  # Enrich with CHECK constraint choices from the database
@@ -605,8 +671,9 @@ async def list_rows(
605
671
  sort_dir: str = Query("asc", pattern="^(asc|desc)$"),
606
672
  search: str | None = Query(None, description="Column filters as 'col1:val1,col2:val2'"),
607
673
  schema: str | None = Query(None, description="PostgreSQL schema name"),
608
- ) -> JSONResponse:
609
- model = _require_model(table_name, schema)
674
+ database: str | None = Query(None, description="Registered database project name"),
675
+ ) -> Response:
676
+ model = _require_model(table_name, schema, database)
610
677
  log.info(
611
678
  "list_rows: table=%s page=%d size=%d sort_by=%s sort_dir=%s search=%s",
612
679
  table_name,
@@ -629,25 +696,18 @@ async def list_rows(
629
696
  paginator = Paginator(qb, per_page=page_size)
630
697
  # page() is 1-based; our API uses 0-based pages
631
698
  page_obj = await paginator.page(page + 1)
632
- data = [_serialize(row) for row in page_obj.items]
633
- total_pages = math.ceil(page_obj.total_count / page_size) if page_size > 0 else 0
634
-
635
699
  log.info(
636
700
  "list_rows: %s returned %d rows (total %d)",
637
701
  table_name,
638
- len(data),
702
+ len(page_obj.items),
639
703
  page_obj.total_count,
640
704
  )
641
705
 
642
- return JSONResponse(
643
- content={
644
- "data": data,
645
- "rows": data,
646
- "total": page_obj.total_count,
647
- "page": page,
648
- "page_size": page_size,
649
- "total_pages": total_pages,
650
- }
706
+ return await _render_list_rows_response(
707
+ page_obj.items,
708
+ total_count=page_obj.total_count,
709
+ page=page,
710
+ page_size=page_size,
651
711
  )
652
712
 
653
713
  except ValueError:
@@ -672,8 +732,9 @@ async def get_row(
672
732
  table_name: str,
673
733
  row_id: str,
674
734
  schema: str | None = Query(None, description="PostgreSQL schema name"),
735
+ database: str | None = Query(None, description="Registered database project name"),
675
736
  ) -> JSONResponse:
676
- model = _require_model(table_name, schema)
737
+ model = _require_model(table_name, schema, database)
677
738
  log.info("get_row: table=%s id=%s", table_name, row_id)
678
739
 
679
740
  try:
@@ -689,8 +750,9 @@ async def create_row(
689
750
  table_name: str,
690
751
  request: Request,
691
752
  schema: str | None = Query(None, description="PostgreSQL schema name"),
753
+ database: str | None = Query(None, description="Registered database project name"),
692
754
  ) -> JSONResponse:
693
- model = _require_model(table_name, schema)
755
+ model = _require_model(table_name, schema, database)
694
756
  body: dict[str, Any] = await request.json()
695
757
  log.info("create_row: table=%s fields=%s", table_name, list(body.keys()))
696
758
 
@@ -709,8 +771,9 @@ async def update_row(
709
771
  row_id: str,
710
772
  request: Request,
711
773
  schema: str | None = Query(None, description="PostgreSQL schema name"),
774
+ database: str | None = Query(None, description="Registered database project name"),
712
775
  ) -> JSONResponse:
713
- model = _require_model(table_name, schema)
776
+ model = _require_model(table_name, schema, database)
714
777
  body: dict[str, Any] = await request.json()
715
778
  log.info("update_row: table=%s id=%s fields=%s", table_name, row_id, list(body.keys()))
716
779
 
@@ -729,8 +792,9 @@ async def delete_row(
729
792
  table_name: str,
730
793
  row_id: str,
731
794
  schema: str | None = Query(None, description="PostgreSQL schema name"),
795
+ database: str | None = Query(None, description="Registered database project name"),
732
796
  ) -> JSONResponse:
733
- model = _require_model(table_name, schema)
797
+ model = _require_model(table_name, schema, database)
734
798
  log.info("delete_row: table=%s id=%s", table_name, row_id)
735
799
 
736
800
  try:
@@ -748,10 +812,11 @@ async def bulk_delete_rows(
748
812
  table_name: str,
749
813
  request: Request,
750
814
  schema: str | None = Query(None, description="PostgreSQL schema name"),
815
+ database: str | None = Query(None, description="Registered database project name"),
751
816
  ) -> JSONResponse:
752
817
  """Delete many rows in one call. Never fails the whole batch on one bad id —
753
818
  returns a partial-result envelope so the caller can report what landed."""
754
- model = _require_model(table_name, schema)
819
+ model = _require_model(table_name, schema, database)
755
820
  body: dict[str, Any] = await request.json()
756
821
  ids = body.get("ids")
757
822
  if not isinstance(ids, list) or not ids:
@@ -795,11 +860,12 @@ async def bulk_update_rows(
795
860
  table_name: str,
796
861
  request: Request,
797
862
  schema: str | None = Query(None, description="PostgreSQL schema name"),
863
+ database: str | None = Query(None, description="Registered database project name"),
798
864
  ) -> JSONResponse:
799
865
  """Apply the same field changes to many rows in one call. Never fails the
800
866
  whole batch on one bad id — returns a partial-result envelope (same shape
801
867
  as bulk-delete) so the caller can report what landed."""
802
- model = _require_model(table_name, schema)
868
+ model = _require_model(table_name, schema, database)
803
869
  body: dict[str, Any] = await request.json()
804
870
  ids = body.get("ids")
805
871
  data = body.get("data")
@@ -866,12 +932,13 @@ async def cache_stats() -> list[dict[str, object]]:
866
932
  results.append(
867
933
  {
868
934
  "model_name": model.__name__,
935
+ "database_name": model.get_database_name(),
869
936
  "table_name": model._meta.table_name,
870
937
  "table_schema": _model_schema(model),
871
938
  "cached_count": count,
872
939
  }
873
940
  )
874
- results.sort(key=lambda r: (r["table_schema"], r["table_name"]))
941
+ results.sort(key=lambda r: (r["database_name"], r["table_schema"], r["table_name"]))
875
942
  return results
876
943
 
877
944
 
@@ -879,9 +946,10 @@ async def cache_stats() -> list[dict[str, object]]:
879
946
  async def clear_table_cache(
880
947
  table_name: str,
881
948
  schema: str | None = Query(None, description="PostgreSQL schema name"),
949
+ database: str | None = Query(None, description="Registered database project name"),
882
950
  ) -> JSONResponse:
883
951
  """Flush the entire in-memory cache for a table."""
884
- model = _require_model(table_name, schema)
952
+ model = _require_model(table_name, schema, database)
885
953
  log.info("clear_table_cache: %s", table_name)
886
954
 
887
955
  try:
@@ -897,9 +965,10 @@ async def evict_row_cache(
897
965
  table_name: str,
898
966
  row_id: str,
899
967
  schema: str | None = Query(None, description="PostgreSQL schema name"),
968
+ database: str | None = Query(None, description="Registered database project name"),
900
969
  ) -> JSONResponse:
901
970
  """Evict a single record from the in-memory cache."""
902
- model = _require_model(table_name, schema)
971
+ model = _require_model(table_name, schema, database)
903
972
  log.info("evict_row_cache: %s/%s", table_name, row_id)
904
973
 
905
974
  try:
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import sqlite3
5
+ from collections.abc import Sequence
6
+ from pathlib import Path
7
+
8
+ type SQLiteValue = str | int | float | bytes | None
9
+
10
+ _IDENTIFIER = re.compile(r"[A-Za-z_][A-Za-z0-9_]*\Z")
11
+
12
+
13
+ def _identifier(value: str) -> str:
14
+ if not _IDENTIFIER.fullmatch(value):
15
+ raise ValueError(f"invalid SQLite identifier: {value!r}")
16
+ return f'"{value}"'
17
+
18
+
19
+ def read_local_sqlite_rows(
20
+ database: str | Path,
21
+ *,
22
+ table: str,
23
+ columns: Sequence[str],
24
+ order_by: Sequence[str] = (),
25
+ ) -> list[tuple[SQLiteValue, ...]]:
26
+ """Read a projected table from a local SQLite artifact in read-only mode."""
27
+ if not columns:
28
+ raise ValueError("columns must contain at least one identifier")
29
+
30
+ projection = ", ".join(_identifier(column) for column in columns)
31
+ statement = f"SELECT {projection} FROM {_identifier(table)}"
32
+ if order_by:
33
+ statement += " ORDER BY " + ", ".join(_identifier(column) for column in order_by)
34
+
35
+ path = Path(database).expanduser().resolve()
36
+ connection = sqlite3.connect(f"{path.as_uri()}?mode=ro", uri=True)
37
+ try:
38
+ return [tuple(row) for row in connection.execute(statement)]
39
+ finally:
40
+ connection.close()
@@ -0,0 +1,23 @@
1
+ # Conflict-write operations
2
+
3
+ `conflict_writes.py` provides typed insert-ignore and conflict-update primitives
4
+ for append-only rows, partial unique indexes, and atomic counters.
5
+
6
+ ## Event-loop safety
7
+
8
+ - **Bulk row coercion never runs on the event loop.** `bulk_insert_ignore` and
9
+ `bulk_upsert_increment` pass model construction, defaults, and field DB-value
10
+ conversion through `_prep_bulk_rows`, which uses `asyncio.to_thread`.
11
+ - **Single-row writes stay inline.** `insert_ignore` and
12
+ `upsert_with_conflict` avoid thread-dispatch overhead for one row.
13
+ - **Statement chunking still enforces the bind-parameter ceiling.** Offloading
14
+ row preparation does not change `_MAX_BIND_PARAMS` or caller-connection
15
+ transaction semantics.
16
+
17
+ Verified against `conflict_writes.py` and
18
+ `tests/level1/test_conflict_writes.py` on 2026-08-17.
19
+
20
+ ## Change Log
21
+
22
+ - 2026-08-17 — Moved bulk model materialization off the event loop after a
23
+ scheduled search-performance import froze unrelated requests for 2.8 seconds.
@@ -33,6 +33,7 @@ primitive instead of hand-rolled f-strings.
33
33
 
34
34
  from __future__ import annotations
35
35
 
36
+ import asyncio
36
37
  import re
37
38
  from typing import TYPE_CHECKING, Any
38
39
 
@@ -115,6 +116,12 @@ def _prep_rows(model_cls: type[Model], objects_data: list[dict[str, Any]]) -> li
115
116
  return data_list
116
117
 
117
118
 
119
+ async def _prep_bulk_rows(
120
+ model_cls: type[Model], objects_data: list[dict[str, Any]]
121
+ ) -> list[dict[str, Any]]:
122
+ return await asyncio.to_thread(_prep_rows, model_cls, objects_data)
123
+
124
+
118
125
  async def _run(
119
126
  model_cls: type[Model],
120
127
  sql: str,
@@ -171,7 +178,7 @@ async def bulk_insert_ignore(
171
178
  if isinstance(returning, str) and not _IDENT_RE.match(returning):
172
179
  raise ValueError(f"returning column must be a plain identifier; got {returning!r}")
173
180
  returning_clause = "*" if returning is True else returning if returning else "1"
174
- data_list = _prep_rows(model_cls, objects_data)
181
+ data_list = await _prep_bulk_rows(model_cls, objects_data)
175
182
  target_clause = _render_conflict_target(on_conflict)
176
183
  table = model_cls._meta.qualified_table_name
177
184
  col_count = len({k for row in data_list for k in row})
@@ -362,7 +369,7 @@ async def bulk_upsert_increment(
362
369
  raise ValueError(f"set_fields column must be a plain identifier; got {col!r}")
363
370
  if col in increments:
364
371
  raise ValueError(f"{col!r} is in both increments and set_fields — pick one")
365
- data_list = _prep_rows(model_cls, objects_data)
372
+ data_list = await _prep_bulk_rows(model_cls, objects_data)
366
373
  target_clause = _render_conflict_target(on_conflict)
367
374
  table = model_cls._meta.qualified_table_name
368
375
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "matrx-orm"
3
- version = "3.1.76"
3
+ version = "3.1.78"
4
4
  description = "Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -41,7 +41,7 @@ dependencies = [
41
41
  "gitpython>=3.1.40",
42
42
  "psycopg[binary]>=3.2.5",
43
43
  "psycopg-pool>=3.2.5",
44
- "matrx-utils>=2.0.17",
44
+ "matrx-utils>=2.0.19",
45
45
  "pyyaml>=6.0.3",
46
46
  "python-dotenv>=1.0",
47
47
  "cryptography>=43.0",
@@ -59,6 +59,7 @@ api = [
59
59
  ]
60
60
  admin = [
61
61
  "fastapi>=0.115",
62
+ "orjson>=3.10",
62
63
  ]
63
64
 
64
65
  [project.scripts]
@@ -4,13 +4,16 @@ from types import SimpleNamespace
4
4
  from unittest.mock import AsyncMock, MagicMock, patch
5
5
 
6
6
  import pytest
7
+ from fastapi import HTTPException
7
8
  from matrx_orm.admin.router import (
8
9
  COMPOSITE_ROW_ID_FIELD,
9
10
  _admin_column_descriptors,
10
11
  _db_row_to_descriptor,
11
12
  _decode_row_id,
12
13
  _model_by_table,
14
+ _require_model,
13
15
  _serialize,
16
+ list_tables,
14
17
  )
15
18
 
16
19
 
@@ -35,7 +38,7 @@ def test_admin_model_lookup_defaults_to_exact_public_key() -> None:
35
38
  return_value=sentinel,
36
39
  ) as lookup:
37
40
  assert _model_by_table("definition") is sentinel
38
- lookup.assert_called_once_with("public", "definition")
41
+ lookup.assert_called_once_with("public", "definition", database=None)
39
42
 
40
43
 
41
44
  def test_admin_model_lookup_uses_requested_schema() -> None:
@@ -44,7 +47,66 @@ def test_admin_model_lookup_uses_requested_schema() -> None:
44
47
  return_value=None,
45
48
  ) as lookup:
46
49
  assert _model_by_table("definition", "agent") is None
47
- lookup.assert_called_once_with("agent", "definition")
50
+ lookup.assert_called_once_with("agent", "definition", database=None)
51
+
52
+
53
+ def test_admin_model_lookup_uses_requested_database() -> None:
54
+ sentinel = object()
55
+ with patch(
56
+ "matrx_orm.admin.router.model_registry.get_model_by_table",
57
+ return_value=sentinel,
58
+ ) as lookup:
59
+ assert _model_by_table("users", "auth", "main") is sentinel
60
+ lookup.assert_called_once_with("auth", "users", database="main")
61
+
62
+
63
+ def test_admin_model_lookup_returns_conflict_for_ambiguous_relation() -> None:
64
+ with (
65
+ patch(
66
+ "matrx_orm.admin.router.model_registry.get_model_by_table",
67
+ side_effect=ValueError("Table 'auth.users' is ambiguous across databases"),
68
+ ),
69
+ pytest.raises(HTTPException) as exc_info,
70
+ ):
71
+ _require_model("users", "auth")
72
+
73
+ assert exc_info.value.status_code == 409
74
+ assert "ambiguous" in str(exc_info.value.detail)
75
+
76
+
77
+ @pytest.mark.asyncio
78
+ async def test_admin_table_inventory_exposes_database_identity() -> None:
79
+ model = SimpleNamespace(
80
+ __name__="Users",
81
+ _meta=SimpleNamespace(
82
+ table_name="users",
83
+ db_schema="auth",
84
+ fields={"id": object()},
85
+ ),
86
+ get_database_name=lambda: "supabase_automation_matrix",
87
+ )
88
+ with (
89
+ patch(
90
+ "matrx_orm.admin.router.model_registry.all_models",
91
+ return_value={("supabase_automation_matrix", "auth", "Users"): model},
92
+ ),
93
+ patch(
94
+ "matrx_orm.admin.router._fetch_db_column_counts",
95
+ new=AsyncMock(return_value={("auth", "users"): 24}),
96
+ ),
97
+ ):
98
+ tables = await list_tables()
99
+
100
+ assert tables == [
101
+ {
102
+ "database_name": "supabase_automation_matrix",
103
+ "table_name": "users",
104
+ "table_schema": "auth",
105
+ "name": "users",
106
+ "model_name": "Users",
107
+ "column_count": 24,
108
+ }
109
+ ]
48
110
 
49
111
 
50
112
  def test_composite_row_identity_round_trips() -> None: