matrx-orm 3.1.22__tar.gz → 3.1.24__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 (291) hide show
  1. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/CLAUDE.md +10 -0
  2. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/PKG-INFO +2 -1
  3. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/conflict.py +54 -0
  4. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/query/executor.py +15 -12
  5. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/runner.py +89 -9
  6. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/pyproject.toml +2 -1
  7. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_conflict_target.py +20 -0
  8. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_expression_primitives.py +19 -1
  9. matrx_orm-3.1.24/tests/schema_builder/test_focused_generation_plan.py +58 -0
  10. matrx_orm-3.1.24/tests/test_secrets_battery.py +135 -0
  11. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/.gitignore +0 -0
  12. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/AGENTS.md +0 -0
  13. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/BASE_CLASS_METHODS.md +0 -0
  14. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/MODEL_API.md +0 -0
  15. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/README.md +0 -0
  16. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/database/orm/extended/managers/ai_model_base.py +0 -0
  17. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/docs/migrations.md +0 -0
  18. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/MODULE_README.md +0 -0
  19. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/__init__.py +0 -0
  20. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/adapters/MODULE_README.md +0 -0
  21. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/adapters/__init__.py +0 -0
  22. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/adapters/async_postgresql.py +0 -0
  23. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/adapters/base_adapter.py +0 -0
  24. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/adapters/postgresql.py +0 -0
  25. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/adapters/postgrest_client_adapter.py +0 -0
  26. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/adapters/supabase_adapter.py +0 -0
  27. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/admin/__init__.py +0 -0
  28. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/admin/router.py +0 -0
  29. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/api/__init__.py +0 -0
  30. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/api/auth.py +0 -0
  31. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/api/config.py +0 -0
  32. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/api/handlers.py +0 -0
  33. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/api/protocol.py +0 -0
  34. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/api/server.py +0 -0
  35. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/associations.py +0 -0
  36. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/cache_debug.py +0 -0
  37. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/client/MODULE_README.md +0 -0
  38. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/client/__init__.py +0 -0
  39. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/client/postgres_connection.py +0 -0
  40. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/client/postgrest.py +0 -0
  41. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/client/supabase_auth.py +0 -0
  42. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/client/supabase_config.py +0 -0
  43. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/client/supabase_manager.py +0 -0
  44. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/EXTENDED-TASK.md +0 -0
  45. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/MODULE_README.md +0 -0
  46. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  47. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/TASKS.md +0 -0
  48. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/__init__.py +0 -0
  49. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/async_db_manager.py +0 -0
  50. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/base.py +0 -0
  51. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/config.py +0 -0
  52. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/db_function.py +0 -0
  53. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/diagnostics.py +0 -0
  54. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/expressions.py +0 -0
  55. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/extended.py +0 -0
  56. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/fields.py +0 -0
  57. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/introspection.py +0 -0
  58. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/listen.py +0 -0
  59. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/loop_filters.py +0 -0
  60. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/model_dto.py +0 -0
  61. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/model_view.py +0 -0
  62. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/paginator.py +0 -0
  63. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/pool_maintenance.py +0 -0
  64. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/pydantic_bridge.py +0 -0
  65. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/registry.py +0 -0
  66. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/relations.py +0 -0
  67. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/resilience.py +0 -0
  68. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/rls_session.py +0 -0
  69. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/signals.py +0 -0
  70. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/tls.py +0 -0
  71. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/transaction.py +0 -0
  72. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/types.py +0 -0
  73. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/core/write_queue.py +0 -0
  74. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/entity.py +0 -0
  75. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/error_handling.py +0 -0
  76. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/exceptions.py +0 -0
  77. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/extended/__init__.py +0 -0
  78. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/extended/app_error_handler.py +0 -0
  79. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/identity_rule.py +0 -0
  80. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/middleware/__init__.py +0 -0
  81. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/middleware/base.py +0 -0
  82. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/MODULE_README.md +0 -0
  83. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/__init__.py +0 -0
  84. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/cli.py +0 -0
  85. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/ddl.py +0 -0
  86. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/diff.py +0 -0
  87. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/executor.py +0 -0
  88. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/integration.py +0 -0
  89. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/loader.py +0 -0
  90. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/operations.py +0 -0
  91. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/state.py +0 -0
  92. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/migrations/table_filter.py +0 -0
  93. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/MODULE_README.md +0 -0
  94. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/__init__.py +0 -0
  95. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/bulk_update_values.py +0 -0
  96. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/conflict_writes.py +0 -0
  97. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/create.py +0 -0
  98. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/delete.py +0 -0
  99. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/dynamic_admin.py +0 -0
  100. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/queue_claim.py +0 -0
  101. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/read.py +0 -0
  102. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/staging_load.py +0 -0
  103. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/operations/update.py +0 -0
  104. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/py.typed +0 -0
  105. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/python_sql/MODULE_README.md +0 -0
  106. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/python_sql/__init__.py +0 -0
  107. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/python_sql/db_objects.py +0 -0
  108. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  109. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  110. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/query/MODULE_README.md +0 -0
  111. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/query/__init__.py +0 -0
  112. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/query/builder.py +0 -0
  113. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/MODULE_README.md +0 -0
  114. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/__init__.py +0 -0
  115. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/code_handler.py +0 -0
  116. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/codegen_writer.py +0 -0
  117. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/columns.py +0 -0
  118. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/common.py +0 -0
  119. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/diff_preview.py +0 -0
  120. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/entity_capabilities.py +0 -0
  121. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/generator.py +0 -0
  122. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  123. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  124. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  125. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  126. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/package_wiring.py +0 -0
  127. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/relationships.py +0 -0
  128. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/schema.py +0 -0
  129. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/schema_manager.py +0 -0
  130. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/tables.py +0 -0
  131. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/schema_builder/views.py +0 -0
  132. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/README.md +0 -0
  133. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/__init__.py +0 -0
  134. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/coalesce.py +0 -0
  135. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/dag.py +0 -0
  136. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/errors.py +0 -0
  137. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/fallback.py +0 -0
  138. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/flush.py +0 -0
  139. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/lifecycle.py +0 -0
  140. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/managed.py +0 -0
  141. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/op.py +0 -0
  142. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/reads.py +0 -0
  143. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/session.py +0 -0
  144. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/session/telemetry.py +0 -0
  145. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/sql_executor/MODULE_README.md +0 -0
  146. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/sql_executor/__init__.py +0 -0
  147. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/sql_executor/executor.py +0 -0
  148. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/sql_executor/queries.py +0 -0
  149. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/sql_executor/registry.py +0 -0
  150. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/sql_executor/types.py +0 -0
  151. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/sql_executor/utils.py +0 -0
  152. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/state.py +0 -0
  153. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/utils/__init__.py +0 -0
  154. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/utils/sql_utils.py +0 -0
  155. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/matrx_orm/utils/type_converters.py +0 -0
  156. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/scripts/git-branches.sh +0 -0
  157. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/scripts/publish.sh +0 -0
  158. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/scripts/release.sh +0 -0
  159. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/MODULE_README.md +0 -0
  160. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/__init__.py +0 -0
  161. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/conftest.py +0 -0
  162. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/MODULE_README.md +0 -0
  163. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/__init__.py +0 -0
  164. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_admin_db_columns.py +0 -0
  165. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_admin_search_negation.py +0 -0
  166. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_admin_search_sentinels.py +0 -0
  167. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_advisory_lock.py +0 -0
  168. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_agent_message.py +0 -0
  169. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_api_auth.py +0 -0
  170. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_api_config.py +0 -0
  171. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_api_handlers.py +0 -0
  172. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_api_protocol.py +0 -0
  173. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_array_agg_order_by.py +0 -0
  174. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_associations.py +0 -0
  175. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_bulk_update_by_pk.py +0 -0
  176. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_bulk_upsert_increment_set_fields.py +0 -0
  177. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_cache_debug.py +0 -0
  178. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_call_function.py +0 -0
  179. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_case_when.py +0 -0
  180. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_composite_pk_filter.py +0 -0
  181. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_composite_pk_write_paths.py +0 -0
  182. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_config.py +0 -0
  183. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_conflict_writes.py +0 -0
  184. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_connection_codecs.py +0 -0
  185. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_connection_poison_guard.py +0 -0
  186. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_count_composite_distinct.py +0 -0
  187. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_db_function_field.py +0 -0
  188. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_ddl_generator.py +0 -0
  189. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_derived_table.py +0 -0
  190. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_dirty_tracking.py +0 -0
  191. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_exceptions.py +0 -0
  192. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_execute_admin_sql.py +0 -0
  193. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_fields.py +0 -0
  194. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_filter_jsonb_agg_array_index.py +0 -0
  195. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_filter_raw.py +0 -0
  196. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_from_alias.py +0 -0
  197. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_fts.py +0 -0
  198. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_func_expression_args.py +0 -0
  199. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_having_annotate_alias.py +0 -0
  200. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_insert_ignore_and_admin_primitives.py +0 -0
  201. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_introspect_rls_policies.py +0 -0
  202. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_join_and_aggregate.py +0 -0
  203. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_listen_notify.py +0 -0
  204. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_managed_write_guard.py +0 -0
  205. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_matrx_entity.py +0 -0
  206. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_migration_diff_types.py +0 -0
  207. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_migration_loader.py +0 -0
  208. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_model_instance.py +0 -0
  209. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_model_meta.py +0 -0
  210. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_order_by_expression.py +0 -0
  211. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_patch_jsonb_path.py +0 -0
  212. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_pool_maintenance.py +0 -0
  213. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_postgrest_filters.py +0 -0
  214. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_query_builder.py +0 -0
  215. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_query_error_detail.py +0 -0
  216. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_query_executor_sql.py +0 -0
  217. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_query_timeout.py +0 -0
  218. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_querybuilder_clone_semantics.py +0 -0
  219. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_queue_claim.py +0 -0
  220. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_registry.py +0 -0
  221. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_registry_multi_database.py +0 -0
  222. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_registry_multi_schema.py +0 -0
  223. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_relations.py +0 -0
  224. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_resilience.py +0 -0
  225. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_schema_exists.py +0 -0
  226. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_session_advisory_lock.py +0 -0
  227. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_state_cache.py +0 -0
  228. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_subquery_filter_raw.py +0 -0
  229. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_subquery_in_filter.py +0 -0
  230. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_supabase_auth.py +0 -0
  231. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_supabase_config.py +0 -0
  232. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_tls.py +0 -0
  233. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_update_case_expression.py +0 -0
  234. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_update_subquery.py +0 -0
  235. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_upsert_with_conflict.py +0 -0
  236. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_values_jsonb_decode.py +0 -0
  237. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_watched_lifecycle.py +0 -0
  238. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level1/test_write_retry_semantics.py +0 -0
  239. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/MODULE_README.md +0 -0
  240. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/__init__.py +0 -0
  241. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/conftest.py +0 -0
  242. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/test_bulk_ops.py +0 -0
  243. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/test_cache_integration.py +0 -0
  244. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/test_crud.py +0 -0
  245. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/test_foreign_keys.py +0 -0
  246. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/test_m2m.py +0 -0
  247. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/test_manager.py +0 -0
  248. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/test_migrations_live.py +0 -0
  249. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/test_query_execution.py +0 -0
  250. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/level2/test_schema_diff.py +0 -0
  251. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project/.env.example +0 -0
  252. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project/README.md +0 -0
  253. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project/__init__.py +0 -0
  254. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project/generate.py +0 -0
  255. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project/generated/.gitkeep +0 -0
  256. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project/matrx_orm.yaml +0 -0
  257. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project/test_schema_generation.py +0 -0
  258. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project_desktop/.env.example +0 -0
  259. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project_desktop/README.md +0 -0
  260. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project_desktop/__init__.py +0 -0
  261. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project_desktop/client_example.py +0 -0
  262. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project_desktop/client_example.ts +0 -0
  263. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  264. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/sample_project_desktop/server.py +0 -0
  265. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/schema/entity_tests.py +0 -0
  266. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/schema/test_base_generation.py +0 -0
  267. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/schema/test_composite_pk_fk_generation.py +0 -0
  268. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/schema/test_composite_unique_generation.py +0 -0
  269. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/schema/test_dto_identity_generation.py +0 -0
  270. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/schema/test_entity_capability_generation.py +0 -0
  271. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/schema/test_generate_schema.py +0 -0
  272. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/schema/test_multi_schema_output.py +0 -0
  273. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/schema/test_sql_expression_defaults.py +0 -0
  274. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/schema/test_view_generation.py +0 -0
  275. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/__init__.py +0 -0
  276. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/conftest.py +0 -0
  277. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/test_capture_poison_proof.py +0 -0
  278. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/test_coalesce.py +0 -0
  279. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/test_dag.py +0 -0
  280. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/test_disk_spill_fallback.py +0 -0
  281. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/test_flush_individual_fallback.py +0 -0
  282. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/test_governed_write.py +0 -0
  283. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/test_managed.py +0 -0
  284. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/test_op_enqueue_site.py +0 -0
  285. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/test_reads.py +0 -0
  286. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/session/test_session.py +0 -0
  287. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/test_database_name_alias.py +0 -0
  288. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/test_model_cls_refactor.py +0 -0
  289. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/test_no_blocking_asyncpg_tls.py +0 -0
  290. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/test_sql_param_casts.py +0 -0
  291. {matrx_orm-3.1.22 → matrx_orm-3.1.24}/tests/test_sync_twins_guard.py +0 -0
@@ -32,6 +32,7 @@ Every table that becomes a Model needs row identity: a single-column primary key
32
32
  - **Adapters**: `AsyncPostgreSQLAdapter`, `SupabaseAdapter`, `PostgRESTClientAdapter`.
33
33
  - **Signals**: `pre_create`, `post_create`, `pre_save`, `post_save`, `pre_delete`, `post_delete`.
34
34
  - **Schema builder** (`matrx_orm.schema_builder`): code generation for Python + TypeScript type definitions from the DB schema. Postgres **VIEWs** are emitted as **read-only `Model` classes** into the same schema models file (on by default; per-entry `output.include_views: false` opts a schema out, `output.exclude_views: [...]` skips individual views). View models get no manager/DTO/base-class/auto_config (views are not writable), synthesize `_primary_keys` from an `id` column (else the first column, for cache-key/`load_by_id` only), and emit plain typed fields with **no `ForeignKey`** (views carry no FK metadata — nothing cross-schema to resolve). Never hand-write a view model again.
35
+ - **Secrets battery** (`matrx_orm.secrets`): the ONE canonical user-credential vault — `users.user_secrets` CRUD + the platform's single Fernet primitive, keyed by `CREDENTIALS_ENCRYPTION_KEY`. Any package: `configure_secrets(db_config_name=...)` once, then `await require_user_secret(user_id, key)`. No package ever calls another service to resolve a credential. Contract: [`matrx_orm/secrets/FEATURE.md`](matrx_orm/secrets/FEATURE.md).
35
36
  - **Polymorphic associations** (`matrx_orm.associations`): `EntityRegistry` (token→Model) + `Associations` — a registry-driven, **ownership-gated** edge engine over a host's polymorphic edge table. Every `source→target` hop is org-scoped *and* the resolved target's `org_id` is re-verified (fail-closed, screams on mismatch), so a hop can't widen access past the tenant boundary. Hardcodes no table names — the host wires its own edge + token-registry models.
36
37
 
37
38
  Public API is defined explicitly in `matrx_orm/__init__.py` (100+ exports). Treat changes there as semver-breaking.
@@ -98,6 +99,15 @@ uv run pytest packages/matrx-orm/tests
98
99
 
99
100
  Tests must run against a Postgres instance the developer provides (or a testcontainers-based fixture). No sibling matrx package is needed. If a test needs `matrx-connect`, that's a sign the test is in the wrong package.
100
101
 
102
+ Host generators may expose a focused schema pass by forwarding
103
+ `only_schemas={...}` to `run_schema_generation`; aidream's focused package-model
104
+ command is `uv run python db/generate.py --schema <schema> --models-only`.
105
+ `models_only=True` skips unrelated schema/type/helper outputs but still performs
106
+ live introspection and writes the canonical generated Model file. The unfiltered
107
+ command remains the release-wide regeneration path. A package target may declare
108
+ `dependency_schemas` in `db/matrx_orm.yaml`; a focused pass then introspects only
109
+ the target and those explicitly named cross-schema FK dependencies.
110
+
101
111
  ---
102
112
 
103
113
  ## Known issues
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrx-orm
3
- Version: 3.1.22
3
+ Version: 3.1.24
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
@@ -23,6 +23,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Classifier: Typing :: Typed
24
24
  Requires-Python: >=3.13
25
25
  Requires-Dist: asyncpg>=0.31.0
26
+ Requires-Dist: cryptography>=43.0
26
27
  Requires-Dist: gitpython>=3.1.40
27
28
  Requires-Dist: matrx-utils>=1.0.20
28
29
  Requires-Dist: psycopg-pool>=3.2.5
@@ -40,6 +40,29 @@ Example::
40
40
  from __future__ import annotations
41
41
 
42
42
  from dataclasses import dataclass
43
+ import re
44
+ from typing import Any
45
+
46
+
47
+ _IDENTIFIER_RE = re.compile(r"^[a-z_][a-z0-9_]*$")
48
+
49
+
50
+ def _identifier(value: str) -> str:
51
+ if not _IDENTIFIER_RE.fullmatch(value):
52
+ raise ValueError(f"ConflictTarget column must be a simple identifier: {value!r}")
53
+ return value
54
+
55
+
56
+ def _literal(value: Any) -> str:
57
+ if value is None:
58
+ return "NULL"
59
+ if isinstance(value, bool):
60
+ return "TRUE" if value else "FALSE"
61
+ if isinstance(value, int | float):
62
+ return str(value)
63
+ if isinstance(value, str):
64
+ return "'" + value.replace("'", "''") + "'"
65
+ raise TypeError(f"Unsupported ConflictTarget literal: {type(value).__name__}")
43
66
 
44
67
 
45
68
  @dataclass(frozen=True)
@@ -83,5 +106,36 @@ class ConflictTarget:
83
106
  "ConflictTarget(where=...) must be a non-empty string when provided"
84
107
  )
85
108
 
109
+ @classmethod
110
+ def partial(
111
+ cls,
112
+ columns: list[str] | tuple[str, ...],
113
+ *,
114
+ coalesce: dict[str, Any] | None = None,
115
+ where_is_null: tuple[str, ...] = (),
116
+ where_is_not_null: tuple[str, ...] = (),
117
+ where_equals: dict[str, Any] | None = None,
118
+ ) -> ConflictTarget:
119
+ """Build an expression/partial-index target without SQL at call sites."""
120
+ coalesce = coalesce or {}
121
+ unknown = set(coalesce) - set(columns)
122
+ if unknown:
123
+ raise ValueError(f"coalesce columns are not in conflict target: {sorted(unknown)}")
124
+ expressions = [
125
+ f"COALESCE({_identifier(column)}, {_literal(coalesce[column])})"
126
+ if column in coalesce
127
+ else _identifier(column)
128
+ for column in columns
129
+ ]
130
+ predicates = [f"{_identifier(column)} IS NULL" for column in where_is_null]
131
+ predicates.extend(
132
+ f"{_identifier(column)} IS NOT NULL" for column in where_is_not_null
133
+ )
134
+ predicates.extend(
135
+ f"{_identifier(column)} = {_literal(value)}"
136
+ for column, value in (where_equals or {}).items()
137
+ )
138
+ return cls(expressions=expressions, where=" AND ".join(predicates) or None)
139
+
86
140
 
87
141
  __all__ = ["ConflictTarget"]
@@ -168,6 +168,15 @@ def _shift_param_refs(sql: str, delta: int) -> str:
168
168
  return "".join(out)
169
169
 
170
170
 
171
+ def _render_update_expression(value: Any, params: list[Any]) -> str:
172
+ """Render an expression after existing SET params without reusing $1..$N."""
173
+ sub_params: list[Any] = []
174
+ sql = value.as_sql(sub_params)
175
+ sql = _shift_param_refs(sql, len(params))
176
+ params.extend(sub_params)
177
+ return sql
178
+
179
+
171
180
  # Postgres' wire protocol caps a single statement at 65535 bind parameters and
172
181
  # asyncpg enforces a 32767 (int16) limit. We chunk bulk writes well under that
173
182
  # so the per-row column count never pushes a batch over the edge.
@@ -417,7 +426,7 @@ class QueryExecutor:
417
426
  return f"{field} <@ {placeholder}::jsonb", v
418
427
  if operator == "json_path":
419
428
  # field #>> '{key1,key2}' = $1 (value is (path_tuple, expected))
420
- if isinstance(value, (list, tuple)):
429
+ if isinstance(value, list | tuple):
421
430
  path = "{" + ",".join(str(p) for p in value) + "}"
422
431
  return f"{field} #>> {placeholder}", path
423
432
  return f"{field} #>> {placeholder}", str(value)
@@ -581,7 +590,7 @@ class QueryExecutor:
581
590
  for agg in aggregations:
582
591
  name = agg["name"]
583
592
  func = agg["function"]
584
- if isinstance(func, (_Subquery, _Exists)):
593
+ if isinstance(func, _Subquery | _Exists):
585
594
  # Correlated scalar subquery in the SELECT list — its as_sql needs
586
595
  # the OUTER table so OuterRef("x") resolves to <table>.x. Params land
587
596
  # here (SELECT position, before WHERE) — correct textual order.
@@ -656,7 +665,7 @@ class QueryExecutor:
656
665
  from matrx_orm.core.expressions import Exists as ExistsExpr
657
666
  from matrx_orm.core.expressions import TsMatch as TsMatchExpr
658
667
 
659
- if isinstance(item, (ExistsExpr, TsMatchExpr)):
668
+ if isinstance(item, ExistsExpr | TsMatchExpr):
660
669
  where_conditions.append(item.as_sql(table, params))
661
670
  elif isinstance(item, ColumnComparison):
662
671
  where_conditions.append(item.as_sql(params))
@@ -695,7 +704,7 @@ class QueryExecutor:
695
704
  field_name, operator = self._parse_lookup(key)
696
705
  agg_func = agg_by_alias.get(field_name)
697
706
  if agg_func is not None:
698
- if isinstance(agg_func, (_Subquery, _Exists)):
707
+ if isinstance(agg_func, _Subquery | _Exists):
699
708
  field_expr = agg_func.as_sql(table, params)
700
709
  elif hasattr(agg_func, "as_sql"):
701
710
  field_expr = agg_func.as_sql(params)
@@ -1331,10 +1340,7 @@ class QueryExecutor:
1331
1340
  set_clause.append(f"{field_name} = {expr_sql}")
1332
1341
  param_index = len(params) + 1
1333
1342
  elif isinstance(value, Expression):
1334
- sub_params: list[Any] = []
1335
- expr_sql = value.as_sql(sub_params)
1336
- for sp in sub_params:
1337
- params.append(sp)
1343
+ expr_sql = _render_update_expression(value, params)
1338
1344
  set_clause.append(f"{field_name} = {expr_sql}")
1339
1345
  param_index = len(params) + 1
1340
1346
  elif hasattr(value, "as_sql"):
@@ -1343,10 +1349,7 @@ class QueryExecutor:
1343
1349
  # duck-typed convention as _render_func_arg's fallback, so
1344
1350
  # SET col = CASE WHEN ... END works in .update()/
1345
1351
  # .update_where() without hardcoding every expression class.
1346
- sub_params = []
1347
- expr_sql = value.as_sql(sub_params)
1348
- for sp in sub_params:
1349
- params.append(sp)
1352
+ expr_sql = _render_update_expression(value, params)
1350
1353
  set_clause.append(f"{field_name} = {expr_sql}")
1351
1354
  param_index = len(params) + 1
1352
1355
  else:
@@ -51,8 +51,68 @@ def _load_yaml(path: Path) -> dict[str, Any]:
51
51
  return yaml.safe_load(f) or {}
52
52
 
53
53
 
54
+ def _select_generation_targets(
55
+ generate_entries: list[dict[str, Any]],
56
+ only_schemas: set[str] | None,
57
+ ) -> tuple[list[dict[str, Any]], dict[str, set[str] | None]]:
58
+ selected = list(generate_entries)
59
+ scoped_by_database: dict[str, set[str] | None] = {}
60
+ if not only_schemas:
61
+ return selected, scoped_by_database
62
+ selected = [
63
+ entry for entry in selected if entry.get("schema", "public") in only_schemas
64
+ ]
65
+ found = {entry.get("schema", "public") for entry in selected}
66
+ missing = only_schemas - found
67
+ if missing:
68
+ raise ValueError(
69
+ "matrx-orm generation requested unknown schema target(s): "
70
+ + ", ".join(sorted(missing))
71
+ )
72
+ for entry in selected:
73
+ database_name = entry.get("database")
74
+ if not database_name:
75
+ continue
76
+ if "dependency_schemas" not in entry:
77
+ scoped_by_database[database_name] = None
78
+ continue
79
+ existing = scoped_by_database.get(database_name, set())
80
+ if existing is None:
81
+ continue
82
+ existing.add(entry.get("schema", "public"))
83
+ existing.update(entry.get("dependency_schemas") or [])
84
+ scoped_by_database[database_name] = existing
85
+ return selected, scoped_by_database
86
+
87
+
88
+ def _database_registration_plan(
89
+ databases: list[dict[str, Any]],
90
+ scoped_by_database: dict[str, set[str] | None],
91
+ *,
92
+ focused: bool,
93
+ models_only: bool,
94
+ ) -> list[dict[str, Any]]:
95
+ plan: list[dict[str, Any]] = []
96
+ for database in databases:
97
+ name = database.get("name")
98
+ if focused and name not in scoped_by_database:
99
+ continue
100
+ item = dict(database)
101
+ scoped = scoped_by_database.get(name)
102
+ if focused and scoped is not None:
103
+ item["additional_schemas"] = sorted(scoped)
104
+ item["pool_min"] = 1 if models_only else None
105
+ item["pool_max"] = 4 if models_only else None
106
+ plan.append(item)
107
+ return plan
108
+
109
+
54
110
  def run_schema_generation(
55
- config_path: str | Path = "matrx_orm.yaml", skip_git_check: bool = False
111
+ config_path: str | Path = "matrx_orm.yaml",
112
+ skip_git_check: bool = False,
113
+ *,
114
+ only_schemas: set[str] | None = None,
115
+ models_only: bool = False,
56
116
  ) -> None:
57
117
  """
58
118
  Read a matrx_orm.yaml config file, register all databases, apply debug
@@ -192,6 +252,13 @@ def run_schema_generation(
192
252
  ignore_path_prefixes=_ignore_prefixes,
193
253
  )
194
254
 
255
+ # Resolve targets before database registration. A focused package-model
256
+ # pass should not connect to unrelated databases, and targets may declare
257
+ # the schemas needed to resolve their cross-schema foreign keys.
258
+ generate_list, scoped_schemas_by_database = _select_generation_targets(
259
+ cfg.get("generate", []), only_schemas
260
+ )
261
+
195
262
  # -------------------------------------------------------------------------
196
263
  # Register databases
197
264
  # -------------------------------------------------------------------------
@@ -202,7 +269,13 @@ def run_schema_generation(
202
269
  )
203
270
  return
204
271
 
205
- for db in databases:
272
+ registration_plan = _database_registration_plan(
273
+ databases,
274
+ scoped_schemas_by_database,
275
+ focused=bool(only_schemas),
276
+ models_only=models_only,
277
+ )
278
+ for db in registration_plan:
206
279
  name = db.get("name")
207
280
  prefix = db.get("env_prefix")
208
281
  if not name or not prefix:
@@ -221,19 +294,19 @@ def run_schema_generation(
221
294
  field_overrides=db.get("field_overrides") or {},
222
295
  manager_config_overrides=db.get("manager_config_overrides") or {},
223
296
  env_var_overrides=db.get("env_var_overrides") or {},
297
+ pool_min=db["pool_min"],
298
+ pool_max=db["pool_max"],
224
299
  )
225
300
 
226
301
  # -------------------------------------------------------------------------
227
302
  # Run generation for each configured target
228
303
  # -------------------------------------------------------------------------
229
- generate_list = cfg.get("generate", [])
230
304
  if not generate_list:
231
305
  vcprint(
232
306
  "[MATRX ORM] No entries in 'generate' — databases registered but nothing generated.",
233
307
  color="yellow",
234
308
  )
235
309
  return
236
-
237
310
  # model_name_prefix is RETIRED. The schema-aware model_registry (keyed on
238
311
  # (schema, class_name)) makes same-named tables across schemas coexist with
239
312
  # clean, prefix-free class names. A lingering prefix would silently re-create
@@ -309,7 +382,8 @@ def run_schema_generation(
309
382
  output_overrides=output_overrides,
310
383
  )
311
384
  manager.initialize()
312
- manager.schema.generate_schema_files()
385
+ if not models_only:
386
+ manager.schema.generate_schema_files()
313
387
  manager.schema.generate_models()
314
388
  vcprint(
315
389
  f"[MATRX ORM] Done — database='{db_name}' schema='{schema}'\n",
@@ -337,14 +411,20 @@ def run_schema_generation(
337
411
  _close_pools()
338
412
  raise
339
413
 
340
- _write_host_models_aggregator(
341
- generate_list, python_root, output_config, host_schema_model_names
342
- )
414
+ if not models_only:
415
+ _write_host_models_aggregator(
416
+ generate_list, python_root, output_config, host_schema_model_names
417
+ )
343
418
 
344
419
  # Declarative host→package DB wiring: resolve each incorporated package's
345
420
  # DB_REQUIREMENTS manifest against the inventory just written above and emit
346
421
  # the aidream-side wiring module. Only when writing Python directly to disk.
347
- if cfg.get("package_wiring") and output_config.python and output_config.save_direct:
422
+ if (
423
+ not models_only
424
+ and cfg.get("package_wiring")
425
+ and output_config.python
426
+ and output_config.save_direct
427
+ ):
348
428
  try:
349
429
  from matrx_orm.schema_builder.package_wiring import generate_package_wiring
350
430
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "matrx-orm"
3
- version = "3.1.22"
3
+ version = "3.1.24"
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" }
@@ -44,6 +44,7 @@ dependencies = [
44
44
  "matrx-utils>=1.0.20",
45
45
  "pyyaml>=6.0.3",
46
46
  "python-dotenv>=1.0",
47
+ "cryptography>=43.0",
47
48
  ]
48
49
 
49
50
  [project.urls]
@@ -74,6 +74,26 @@ def test_conflict_target_normalises_list_to_tuple() -> None:
74
74
  assert t.expressions == ("lower(name)", "kind")
75
75
 
76
76
 
77
+ def test_typed_partial_target_renders_coalesce_and_predicates() -> None:
78
+ target = ConflictTarget.partial(
79
+ ["project_id", "region", "status"],
80
+ coalesce={"region": ""},
81
+ where_is_null=("deleted_at",),
82
+ where_is_not_null=("host_resource_id",),
83
+ where_equals={"status": "active"},
84
+ )
85
+
86
+ assert target.expressions == ("project_id", "COALESCE(region, '')", "status")
87
+ assert target.where == (
88
+ "deleted_at IS NULL AND host_resource_id IS NOT NULL AND status = 'active'"
89
+ )
90
+
91
+
92
+ def test_typed_partial_target_rejects_non_identifier_columns() -> None:
93
+ with pytest.raises(ValueError, match="simple identifier"):
94
+ ConflictTarget.partial(["project_id; DROP TABLE x"])
95
+
96
+
77
97
  # ── Executor SQL shape ───────────────────────────────────────────────────────
78
98
 
79
99
 
@@ -24,7 +24,7 @@ from matrx_orm.core.expressions import (
24
24
  _iter_subquery_conditions,
25
25
  )
26
26
  from matrx_orm.core.expressions import Q
27
- from matrx_orm.query.executor import QueryExecutor
27
+ from matrx_orm.query.executor import QueryExecutor, _render_update_expression
28
28
 
29
29
 
30
30
  # ── Array functions ─────────────────────────────────────────────────────────
@@ -262,3 +262,21 @@ def test_binary_expr_duck_types_as_sql_for_the_executor_fallback():
262
262
  assert hasattr(expr, "as_sql")
263
263
  params: list = []
264
264
  assert expr.as_sql(params) == "(COALESCE(weight, $1) + $2)"
265
+
266
+
267
+ def test_multiple_update_expressions_shift_parameters_across_set_fields():
268
+ from matrx_orm import Coalesce, F
269
+
270
+ params: list = ["fetched-at", "task-id"]
271
+ request_sql = _render_update_expression(F("request_count") + 1, params)
272
+ provider_sql = _render_update_expression(
273
+ Coalesce(F("provider_cost"), 0) + 0.12, params
274
+ )
275
+ estimated_sql = _render_update_expression(
276
+ Coalesce(F("estimated_cost"), 0) + 0.10, params
277
+ )
278
+
279
+ assert request_sql == "request_count + $3"
280
+ assert provider_sql == "(COALESCE(provider_cost, $4) + $5)"
281
+ assert estimated_sql == "(COALESCE(estimated_cost, $6) + $7)"
282
+ assert params == ["fetched-at", "task-id", 1, 0, 0.12, 0, 0.10]
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from matrx_orm.schema_builder.runner import (
6
+ _database_registration_plan,
7
+ _select_generation_targets,
8
+ )
9
+
10
+
11
+ def test_focused_target_scopes_dependencies_and_skips_unrelated_database() -> None:
12
+ entries = [
13
+ {
14
+ "database": "main",
15
+ "schema": "seo",
16
+ "dependency_schemas": ["auth", "iam", "platform"],
17
+ },
18
+ {"database": "cms", "schema": "public"},
19
+ ]
20
+ selected, scoped = _select_generation_targets(entries, {"seo"})
21
+ plan = _database_registration_plan(
22
+ [
23
+ {"name": "main", "additional_schemas": ["seo", "web", "auth"]},
24
+ {"name": "cms", "additional_schemas": ["history"]},
25
+ ],
26
+ scoped,
27
+ focused=True,
28
+ models_only=True,
29
+ )
30
+
31
+ assert selected == [entries[0]]
32
+ assert scoped == {"main": {"seo", "auth", "iam", "platform"}}
33
+ assert [item["name"] for item in plan] == ["main"]
34
+ assert plan[0]["additional_schemas"] == ["auth", "iam", "platform", "seo"]
35
+ assert (plan[0]["pool_min"], plan[0]["pool_max"]) == (1, 4)
36
+
37
+
38
+ def test_focused_target_without_dependencies_preserves_full_database_scope() -> None:
39
+ entries = [{"database": "main", "schema": "web"}]
40
+ _, scoped = _select_generation_targets(entries, {"web"})
41
+ plan = _database_registration_plan(
42
+ [{"name": "main", "additional_schemas": ["web", "auth", "iam"]}],
43
+ scoped,
44
+ focused=True,
45
+ models_only=False,
46
+ )
47
+
48
+ assert scoped == {"main": None}
49
+ assert plan[0]["additional_schemas"] == ["web", "auth", "iam"]
50
+ assert (plan[0]["pool_min"], plan[0]["pool_max"]) == (None, None)
51
+
52
+
53
+ def test_unknown_focused_schema_fails_before_registration() -> None:
54
+ with pytest.raises(ValueError, match="unknown schema target.*missing"):
55
+ _select_generation_targets(
56
+ [{"database": "main", "schema": "seo"}],
57
+ {"missing"},
58
+ )
@@ -0,0 +1,135 @@
1
+ """Secrets battery — crypto + contract tests (no DB required).
2
+
3
+ The DB-facing CRUD is exercised through aidream's existing user_secrets
4
+ tests; here we pin the parts that must never drift: key resolution,
5
+ round-trips, mask shape, env parsing, and the historical-compatibility
6
+ guarantee that a ciphertext written by the old aidream implementation
7
+ decrypts with the battery.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import base64
13
+ import hashlib
14
+
15
+ import pytest
16
+
17
+ from matrx_orm.secrets import crypto
18
+
19
+
20
+ @pytest.fixture(autouse=True)
21
+ def _clean_crypto_state(monkeypatch):
22
+ crypto.reset_encryption_for_tests()
23
+ monkeypatch.delenv("CREDENTIALS_ENCRYPTION_KEY", raising=False)
24
+ monkeypatch.delenv("SUPABASE_SECRET_KEY", raising=False)
25
+ yield
26
+ crypto.reset_encryption_for_tests()
27
+
28
+
29
+ def _fernet_key() -> str:
30
+ from cryptography.fernet import Fernet
31
+
32
+ return Fernet.generate_key().decode()
33
+
34
+
35
+ def test_no_key_is_loud():
36
+ with pytest.raises(RuntimeError, match="no encryption key configured"):
37
+ crypto.get_fernet()
38
+
39
+
40
+ def test_string_round_trip_env_key(monkeypatch):
41
+ monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", _fernet_key())
42
+ ct = crypto.encrypt_value("s3cret-value")
43
+ assert isinstance(ct, bytes)
44
+ assert crypto.decrypt_value(ct) == "s3cret-value"
45
+
46
+
47
+ def test_empty_string_round_trips(monkeypatch):
48
+ monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", _fernet_key())
49
+ assert crypto.decrypt_value(crypto.encrypt_value("")) == ""
50
+
51
+
52
+ def test_payload_round_trip(monkeypatch):
53
+ monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", _fernet_key())
54
+ payload = {"api_key": "abc", "nested": {"n": 1}}
55
+ assert crypto.decrypt_payload(crypto.encrypt_payload(payload)) == payload
56
+
57
+
58
+ def test_configure_encryption_overrides_env(monkeypatch):
59
+ monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", _fernet_key())
60
+ injected = _fernet_key()
61
+ crypto.configure_encryption(injected)
62
+ ct = crypto.encrypt_value("via-injected-key")
63
+ # Same key injected fresh must decrypt; env key must NOT have been used.
64
+ crypto.reset_encryption_for_tests()
65
+ crypto.configure_encryption(injected)
66
+ assert crypto.decrypt_value(ct) == "via-injected-key"
67
+
68
+
69
+ def test_non_fernet_key_is_sha256_derived_like_legacy(monkeypatch):
70
+ """A non-44-char CREDENTIALS_ENCRYPTION_KEY derives via SHA-256 —
71
+ byte-identical to the historical aidream implementation, so every
72
+ existing ciphertext keeps decrypting."""
73
+ from cryptography.fernet import Fernet
74
+
75
+ monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", "some-passphrase-style-key")
76
+ legacy = Fernet(base64.urlsafe_b64encode(hashlib.sha256(b"some-passphrase-style-key").digest()))
77
+ legacy_ct = legacy.encrypt(b"written-by-old-code")
78
+ assert crypto.get_fernet().decrypt(legacy_ct) == b"written-by-old-code"
79
+
80
+
81
+ def test_supabase_seed_fallback_derivation(monkeypatch):
82
+ from cryptography.fernet import Fernet
83
+
84
+ monkeypatch.setenv("SUPABASE_SECRET_KEY", "seed-material")
85
+ derived = Fernet(base64.urlsafe_b64encode(hashlib.sha256(b"seed-material").digest()))
86
+ assert crypto.get_fernet().decrypt(derived.encrypt(b"x")) == b"x"
87
+
88
+
89
+ def test_mask_value_shapes():
90
+ assert crypto.mask_value("") == ""
91
+ assert crypto.mask_value("ghp_1234567890abcd") == "ghp_…abcd"
92
+ short = crypto.mask_value("abcd")
93
+ assert "…" in short and len(short) <= 5
94
+
95
+
96
+ def test_type_guards(monkeypatch):
97
+ monkeypatch.setenv("CREDENTIALS_ENCRYPTION_KEY", _fernet_key())
98
+ with pytest.raises(TypeError):
99
+ crypto.encrypt_value(123) # type: ignore[arg-type]
100
+ with pytest.raises(TypeError):
101
+ crypto.decrypt_value("not-bytes") # type: ignore[arg-type]
102
+
103
+
104
+ def test_parse_env_text():
105
+ from matrx_orm.secrets import parse_env_text
106
+
107
+ text = """
108
+ # comment
109
+ export FOO=bar
110
+ QUOTED="with spaces"
111
+ SINGLE='sq'
112
+ EMPTY=
113
+ novalue
114
+ """
115
+ pairs = dict(parse_env_text(text))
116
+ assert pairs == {"FOO": "bar", "QUOTED": "with spaces", "SINGLE": "sq", "EMPTY": ""}
117
+
118
+
119
+ def test_public_api_surface():
120
+ import matrx_orm.secrets as s
121
+
122
+ for name in (
123
+ "configure_secrets",
124
+ "require_user_secret",
125
+ "get_user_secret",
126
+ "create_user_secret",
127
+ "update_user_secret",
128
+ "delete_user_secret",
129
+ "list_user_secrets",
130
+ "bulk_upsert_from_env_text",
131
+ "encrypt_payload",
132
+ "decrypt_payload",
133
+ "UserSecret",
134
+ ):
135
+ assert hasattr(s, name), name
File without changes
File without changes
File without changes
File without changes