matrx-orm 3.0.26__tar.gz → 3.0.28__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 (186) hide show
  1. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/PKG-INFO +3 -1
  2. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/README.md +2 -0
  3. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/pyproject.toml +1 -1
  4. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/__init__.py +2 -0
  5. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/adapters/__init__.py +1 -0
  6. matrx_orm-3.0.28/src/matrx_orm/adapters/postgrest_client_adapter.py +392 -0
  7. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/base.py +18 -9
  8. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/extended.py +35 -18
  9. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/relations.py +12 -10
  10. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/query/executor.py +72 -0
  11. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/schema.py +27 -12
  12. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/.arman/pending/versioning/INITIAL.md +0 -0
  13. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/.env.example +0 -0
  14. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/.github/workflows/publish.yml +0 -0
  15. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/.gitignore +0 -0
  16. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/.python-version +0 -0
  17. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/.vscode/settings.json +0 -0
  18. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/CLAUDE.md +0 -0
  19. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/DESKTOP-API-INTEGRATION.md +0 -0
  20. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/MIGRATIONS.md +0 -0
  21. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/MODULE_README.md +0 -0
  22. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/RESERVED_NAMES.md +0 -0
  23. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/TASKS-TEMPLATE.md +0 -0
  24. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/TASKS.md +0 -0
  25. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/TRANSACTION_POOLER_PLAN.MD +0 -0
  26. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/WRITE-QUEUE-UPGRADE.md +0 -0
  27. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/database/orm/extended/managers/ai_model_base.py +0 -0
  28. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/docs/migrations.md +0 -0
  29. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/extended_jan5_backup.py +0 -0
  30. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/main.py +0 -0
  31. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/release.sh +0 -0
  32. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/scripts/git-branches.sh +0 -0
  33. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/scripts/publish.sh +0 -0
  34. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/scripts/release.sh +0 -0
  35. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/MODULE_README.md +0 -0
  36. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/MODULE_README.md +0 -0
  37. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/adapters/MODULE_README.md +0 -0
  38. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/adapters/async_postgresql.py +0 -0
  39. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/adapters/base_adapter.py +0 -0
  40. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/adapters/postgresql.py +0 -0
  41. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/adapters/supabase_adapter.py +0 -0
  42. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/api/__init__.py +0 -0
  43. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/api/auth.py +0 -0
  44. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/api/config.py +0 -0
  45. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/api/handlers.py +0 -0
  46. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/api/protocol.py +0 -0
  47. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/api/server.py +0 -0
  48. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/client/MODULE_README.md +0 -0
  49. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/client/__init__.py +0 -0
  50. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/client/postgres_connection.py +0 -0
  51. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/client/postgrest.py +0 -0
  52. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/client/supabase_auth.py +0 -0
  53. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/client/supabase_config.py +0 -0
  54. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/client/supabase_manager.py +0 -0
  55. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/EXTENDED-TASK.md +0 -0
  56. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/MODULE_README.md +0 -0
  57. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/RELATIONS-TASKS.md +0 -0
  58. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/TASKS.md +0 -0
  59. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/__init__.py +0 -0
  60. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/async_db_manager.py +0 -0
  61. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/config.py +0 -0
  62. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/expressions.py +0 -0
  63. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/fields.py +0 -0
  64. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/model_dto.py +0 -0
  65. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/model_view.py +0 -0
  66. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/paginator.py +0 -0
  67. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/pydantic_bridge.py +0 -0
  68. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/registry.py +0 -0
  69. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/signals.py +0 -0
  70. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/transaction.py +0 -0
  71. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/types.py +0 -0
  72. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/core/write_queue.py +0 -0
  73. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/error_handling.py +0 -0
  74. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/exceptions.py +0 -0
  75. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/extended/__init__.py +0 -0
  76. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/extended/app_error_handler.py +0 -0
  77. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/middleware/__init__.py +0 -0
  78. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/middleware/base.py +0 -0
  79. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/MODULE_README.md +0 -0
  80. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/__init__.py +0 -0
  81. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/cli.py +0 -0
  82. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/ddl.py +0 -0
  83. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/diff.py +0 -0
  84. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/executor.py +0 -0
  85. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/integration.py +0 -0
  86. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/loader.py +0 -0
  87. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/operations.py +0 -0
  88. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/state.py +0 -0
  89. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/migrations/table_filter.py +0 -0
  90. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/operations/MODULE_README.md +0 -0
  91. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/operations/__init__.py +0 -0
  92. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/operations/create.py +0 -0
  93. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/operations/delete.py +0 -0
  94. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/operations/read.py +0 -0
  95. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/operations/update.py +0 -0
  96. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/py.typed +0 -0
  97. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/python_sql/MODULE_README.md +0 -0
  98. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/python_sql/__init__.py +0 -0
  99. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/python_sql/db_objects.py +0 -0
  100. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/python_sql/table_detailed_relationships.py +0 -0
  101. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/python_sql/table_typescript_relationship.py +0 -0
  102. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/query/MODULE_README.md +0 -0
  103. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/query/__init__.py +0 -0
  104. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/query/builder.py +0 -0
  105. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/MODULE_README.md +0 -0
  106. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/__init__.py +0 -0
  107. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/code_handler.py +0 -0
  108. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/columns.py +0 -0
  109. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/common.py +0 -0
  110. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/generator.py +0 -0
  111. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/helpers/__init__.py +0 -0
  112. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/helpers/base_generators.py +0 -0
  113. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/helpers/entity_generators.py +0 -0
  114. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/helpers/git_checker.py +0 -0
  115. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/relationships.py +0 -0
  116. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/runner.py +0 -0
  117. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/schema_manager.py +0 -0
  118. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/tables.py +0 -0
  119. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/schema_builder/views.py +0 -0
  120. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/sql_executor/MODULE_README.md +0 -0
  121. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/sql_executor/__init__.py +0 -0
  122. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/sql_executor/executor.py +0 -0
  123. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/sql_executor/queries.py +0 -0
  124. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/sql_executor/registry.py +0 -0
  125. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/sql_executor/types.py +0 -0
  126. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/sql_executor/utils.py +0 -0
  127. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/state.py +0 -0
  128. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/utils/__init__.py +0 -0
  129. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/utils/sql_utils.py +0 -0
  130. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/src/matrx_orm/utils/type_converters.py +0 -0
  131. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/MODULE_README.md +0 -0
  132. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/__init__.py +0 -0
  133. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/conftest.py +0 -0
  134. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/MODULE_README.md +0 -0
  135. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/__init__.py +0 -0
  136. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_api_auth.py +0 -0
  137. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_api_config.py +0 -0
  138. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_api_handlers.py +0 -0
  139. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_api_protocol.py +0 -0
  140. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_config.py +0 -0
  141. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_ddl_generator.py +0 -0
  142. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_exceptions.py +0 -0
  143. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_fields.py +0 -0
  144. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_migration_diff_types.py +0 -0
  145. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_migration_loader.py +0 -0
  146. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_model_instance.py +0 -0
  147. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_model_meta.py +0 -0
  148. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_postgrest_filters.py +0 -0
  149. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_query_builder.py +0 -0
  150. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_query_executor_sql.py +0 -0
  151. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_registry.py +0 -0
  152. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_relations.py +0 -0
  153. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_state_cache.py +0 -0
  154. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_supabase_auth.py +0 -0
  155. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level1/test_supabase_config.py +0 -0
  156. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/MODULE_README.md +0 -0
  157. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/__init__.py +0 -0
  158. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/conftest.py +0 -0
  159. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/test_bulk_ops.py +0 -0
  160. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/test_cache_integration.py +0 -0
  161. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/test_crud.py +0 -0
  162. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/test_foreign_keys.py +0 -0
  163. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/test_m2m.py +0 -0
  164. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/test_manager.py +0 -0
  165. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/test_migrations_live.py +0 -0
  166. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/test_query_execution.py +0 -0
  167. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/level2/test_schema_diff.py +0 -0
  168. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project/.env.example +0 -0
  169. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project/README.md +0 -0
  170. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project/__init__.py +0 -0
  171. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project/generate.py +0 -0
  172. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project/generated/.gitkeep +0 -0
  173. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project/matrx_orm.yaml +0 -0
  174. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project/test_schema_generation.py +0 -0
  175. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project_desktop/.env.example +0 -0
  176. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project_desktop/README.md +0 -0
  177. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project_desktop/__init__.py +0 -0
  178. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project_desktop/client_example.py +0 -0
  179. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project_desktop/client_example.ts +0 -0
  180. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project_desktop/client_supabase_example.py +0 -0
  181. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/sample_project_desktop/server.py +0 -0
  182. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/schema/entity_tests.py +0 -0
  183. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/schema/test_base_generation.py +0 -0
  184. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/schema/test_generate_schema.py +0 -0
  185. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/tests/test_model_cls_refactor.py +0 -0
  186. {matrx_orm-3.0.26 → matrx_orm-3.0.28}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrx-orm
3
- Version: 3.0.26
3
+ Version: 3.0.28
4
4
  Summary: Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching. Works with any PostgreSQL database.
5
5
  Project-URL: Homepage, https://github.com/armanisadeghi/matrx-orm
6
6
  Project-URL: Repository, https://github.com/armanisadeghi/matrx-orm
@@ -1838,6 +1838,8 @@ When you run the schema builder against a database that has `vector(n)` columns,
1838
1838
 
1839
1839
  | Version | Highlights |
1840
1840
  |---|---|
1841
+ | **v3.0.28** | Patch release |
1842
+ | **v3.0.27** | Patch release |
1841
1843
  | **v3.0.26** | Patch release |
1842
1844
  | **v3.0.25** | Patch release |
1843
1845
  | **v3.0.24** | Patch release |
@@ -1795,6 +1795,8 @@ When you run the schema builder against a database that has `vector(n)` columns,
1795
1795
 
1796
1796
  | Version | Highlights |
1797
1797
  |---|---|
1798
+ | **v3.0.28** | Patch release |
1799
+ | **v3.0.27** | Patch release |
1798
1800
  | **v3.0.26** | Patch release |
1799
1801
  | **v3.0.25** | Patch release |
1800
1802
  | **v3.0.24** | Patch release |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "matrx-orm"
3
- version = "3.0.26"
3
+ version = "3.0.28"
4
4
  description = "Async-first PostgreSQL ORM with bidirectional migrations, schema introspection, many-to-many relationships, and built-in state caching. Works with any PostgreSQL database."
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -151,6 +151,7 @@ from .core.signals import (
151
151
  from .core.transaction import transaction
152
152
  from .adapters import AdapterRegistry, BaseAdapter, AsyncPostgreSQLAdapter
153
153
  from .adapters.supabase_adapter import SupabaseAdapter
154
+ from .adapters.postgrest_client_adapter import PostgRESTClientAdapter
154
155
  from .exceptions import (
155
156
  ORMException,
156
157
  ValidationError,
@@ -358,6 +359,7 @@ __all__ = [
358
359
  "BaseAdapter",
359
360
  "AsyncPostgreSQLAdapter",
360
361
  "SupabaseAdapter",
362
+ "PostgRESTClientAdapter",
361
363
  # ── Cache ──────────────────────────────────────────────────────────────────
362
364
  "CachePolicy",
363
365
  "StateManager",
@@ -40,6 +40,7 @@ __all__ = [
40
40
  "BaseAdapter",
41
41
  "AsyncPostgreSQLAdapter",
42
42
  "SupabaseAdapter",
43
+ "PostgRESTClientAdapter",
43
44
  "AdapterRegistry",
44
45
  ]
45
46
 
@@ -0,0 +1,392 @@
1
+ """PostgREST client adapter — routes all ORM queries through Supabase PostgREST.
2
+
3
+ This adapter allows the full BaseManager / QueryBuilder / QueryExecutor stack
4
+ to work transparently against a Supabase project using only the publishable
5
+ anon key + the user's JWT. No asyncpg connection is opened; every operation
6
+ goes through the Supabase REST API, which enforces Row-Level Security
7
+ automatically.
8
+
9
+ Typical setup (called by matrx_ai.initialize(client_mode=True, ...))::
10
+
11
+ from matrx_orm.adapters import AdapterRegistry
12
+ from matrx_orm.adapters.postgrest_client_adapter import PostgRESTClientAdapter
13
+ from matrx_orm.client import SupabaseClientConfig, SupabaseAuth
14
+
15
+ config = SupabaseClientConfig(url="https://abc.supabase.co", anon_key="eyJ...")
16
+ auth = SupabaseAuth(config)
17
+ AdapterRegistry.register("supabase_automation_matrix",
18
+ PostgRESTClientAdapter(config, auth))
19
+
20
+ After that, every ``Model.filter().all()``, ``BaseManager.load_items()``,
21
+ ``create_item()``, ``update_item()``, etc. routes through PostgREST.
22
+
23
+ Limitations
24
+ -----------
25
+ - Raw SQL (``Model.raw()``, ``Model.raw_sql()``) is **not supported** — PostgREST
26
+ does not accept arbitrary SQL. Those methods will raise ``NotImplementedError``
27
+ when this adapter is active. Use ``SupabaseManager.rpc()`` for stored
28
+ procedures instead.
29
+ - M2M junction-table writes (``Model.ensure_m2m_tables()``) are also not
30
+ supported in client mode — schema DDL cannot be issued via PostgREST.
31
+ - Transactions (``get_connection()`` context manager) are not supported. Each
32
+ PostgREST request is its own implicit transaction.
33
+ - ``select_related`` (SQL JOINs) fall back to PostgREST embedded resource
34
+ notation when possible; complex JOINs will degrade gracefully to a
35
+ non-joined result.
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ import logging
41
+ from contextlib import asynccontextmanager
42
+ from typing import Any, AsyncIterator
43
+
44
+ from matrx_orm.adapters.base_adapter import BaseAdapter
45
+
46
+ logger = logging.getLogger("matrx_orm.adapters.postgrest_client")
47
+
48
+
49
+ class PostgRESTClientAdapter(BaseAdapter):
50
+ """BaseAdapter implementation that speaks to Supabase via PostgREST HTTP.
51
+
52
+ Two construction patterns are supported:
53
+
54
+ **Pattern 1 — with SupabaseAuth (standalone client-side use)**::
55
+
56
+ from matrx_orm.client import SupabaseClientConfig, SupabaseAuth
57
+ from matrx_orm.adapters.postgrest_client_adapter import PostgRESTClientAdapter
58
+
59
+ config = SupabaseClientConfig(url="https://abc.supabase.co", anon_key="eyJ...")
60
+ auth = SupabaseAuth(config)
61
+ adapter = PostgRESTClientAdapter(config=config, auth=auth)
62
+
63
+ **Pattern 2 — with a get_jwt callable (matrx_ai client mode)**::
64
+
65
+ adapter = PostgRESTClientAdapter(
66
+ url="https://abc.supabase.co",
67
+ anon_key="eyJ...",
68
+ get_jwt=lambda: current_token, # sync OR async callable
69
+ )
70
+
71
+ Parameters
72
+ ----------
73
+ config:
74
+ ``SupabaseClientConfig`` **or** ``None``. When ``None``, ``url`` and
75
+ ``anon_key`` must be provided and a minimal config is built internally.
76
+ auth:
77
+ ``SupabaseAuth`` instance, **or** ``None`` when ``get_jwt`` is provided.
78
+ url:
79
+ Supabase project URL. Only used when ``config`` is ``None``.
80
+ anon_key:
81
+ Supabase publishable anon key. Only used when ``config`` is ``None``.
82
+ get_jwt:
83
+ Sync or async callable (no arguments) that returns the current user's
84
+ JWT string, or ``None`` / empty string when unauthenticated. When
85
+ provided, ``auth`` is ignored for token resolution. Falls back to
86
+ ``anon_key`` when the callable returns ``None`` or raises.
87
+ """
88
+
89
+ def __init__(
90
+ self,
91
+ config: Any = None,
92
+ auth: Any = None,
93
+ *,
94
+ url: str = "",
95
+ anon_key: str = "",
96
+ get_jwt: Any = None,
97
+ ) -> None:
98
+ from matrx_orm.client.postgrest import PostgRESTClient
99
+ from matrx_orm.client.supabase_config import SupabaseClientConfig
100
+
101
+ if config is None:
102
+ if not url or not anon_key:
103
+ raise ValueError(
104
+ "Either a SupabaseClientConfig must be passed as 'config', "
105
+ "or 'url' and 'anon_key' must both be provided."
106
+ )
107
+ config = SupabaseClientConfig(url=url, anon_key=anon_key)
108
+
109
+ self._config = config
110
+ self._auth = auth
111
+ self._get_jwt = get_jwt # sync or async callable, or None
112
+ self._client = PostgRESTClient(config)
113
+
114
+ # ------------------------------------------------------------------
115
+ # Token helper
116
+ # ------------------------------------------------------------------
117
+
118
+ async def _token(self) -> str:
119
+ """Return a valid JWT.
120
+
121
+ Resolution order:
122
+ 1. ``get_jwt`` callable if provided (sync or async).
123
+ 2. ``SupabaseAuth.ensure_valid_session()`` if auth provided.
124
+ 3. Anon key fallback (works for public / RLS-unrestricted tables).
125
+ """
126
+ import asyncio
127
+
128
+ if self._get_jwt is not None:
129
+ try:
130
+ result = self._get_jwt()
131
+ if asyncio.iscoroutine(result):
132
+ result = await result
133
+ if result:
134
+ return result
135
+ except Exception:
136
+ pass
137
+
138
+ if self._auth is not None:
139
+ try:
140
+ session = await self._auth.ensure_valid_session()
141
+ if session and session.access_token:
142
+ return session.access_token
143
+ except Exception:
144
+ pass
145
+
146
+ return self._config.anon_key
147
+
148
+ # ------------------------------------------------------------------
149
+ # execute_query_dict — primary SELECT hook for QueryExecutor
150
+ # ------------------------------------------------------------------
151
+
152
+ async def execute_query_dict(
153
+ self,
154
+ query_dict: dict[str, Any],
155
+ ) -> list[dict[str, Any]] | type[NotImplemented]:
156
+ """Translate a QueryBuilder query dict to a PostgREST SELECT call.
157
+
158
+ This is called by ``QueryExecutor._execute()`` **before** the SQL path.
159
+ Returning a list (even empty) signals that PostgREST handled the query
160
+ and the executor should skip SQL entirely.
161
+ """
162
+ table: str = query_dict.get("table", "")
163
+ if not table:
164
+ return NotImplemented
165
+
166
+ token = await self._token()
167
+ filters = self._translate_filters(query_dict.get("filters", {}))
168
+ order = self._translate_order(query_dict.get("order_by", []))
169
+ limit: int | None = query_dict.get("limit")
170
+ offset: int | None = query_dict.get("offset")
171
+ select_cols: list[str] | None = query_dict.get("select")
172
+ columns = ", ".join(select_cols) if select_cols else "*"
173
+
174
+ logger.debug(
175
+ "[PostgRESTClientAdapter] SELECT %s filters=%s limit=%s",
176
+ table, filters, limit,
177
+ )
178
+
179
+ try:
180
+ result = await self._client.select(
181
+ table,
182
+ token,
183
+ columns=columns,
184
+ filters=filters if filters else None,
185
+ order=order if order else None,
186
+ limit=limit,
187
+ offset=offset,
188
+ )
189
+ if isinstance(result, list):
190
+ return result
191
+ # single-object response (shouldn't happen on list queries)
192
+ return [result] if result else []
193
+ except Exception as exc:
194
+ logger.error(
195
+ "[PostgRESTClientAdapter] SELECT failed for %s: %s", table, exc
196
+ )
197
+ raise
198
+
199
+ # ------------------------------------------------------------------
200
+ # execute_write_dict — write hook for QueryExecutor DML
201
+ # ------------------------------------------------------------------
202
+
203
+ async def execute_write_dict(
204
+ self,
205
+ query_dict: dict[str, Any],
206
+ ) -> list[dict[str, Any]] | type[NotImplemented]:
207
+ """Translate a write query dict to the appropriate PostgREST call.
208
+
209
+ Called by ``QueryExecutor`` for INSERT / UPDATE / DELETE / UPSERT
210
+ before it falls through to the SQL path.
211
+
212
+ query_dict keys used:
213
+ - ``operation``: "insert" | "update" | "delete" | "upsert" | "bulk_insert"
214
+ | "bulk_upsert"
215
+ - ``table``: table name
216
+ - ``data``: dict (single row) or list of dicts (bulk)
217
+ - ``filters``: ORM filter dict (for UPDATE / DELETE)
218
+ - ``conflict_fields``: list[str] (for UPSERT)
219
+ - ``update_fields``: list[str] (for UPSERT, optional)
220
+ """
221
+ operation: str = query_dict.get("operation", "")
222
+ table: str = query_dict.get("table", "")
223
+ if not table or not operation:
224
+ return NotImplemented
225
+
226
+ token = await self._token()
227
+ filters = self._translate_filters(query_dict.get("filters", {}))
228
+ data: dict | list = query_dict.get("data", {})
229
+
230
+ logger.debug(
231
+ "[PostgRESTClientAdapter] %s %s", operation.upper(), table
232
+ )
233
+
234
+ try:
235
+ if operation == "insert":
236
+ result = await self._client.insert(table, token, data)
237
+ return [result] if isinstance(result, dict) else (result or [])
238
+
239
+ if operation == "bulk_insert":
240
+ result = await self._client.insert(table, token, data)
241
+ return result if isinstance(result, list) else ([result] if result else [])
242
+
243
+ if operation == "upsert":
244
+ conflict_fields: list[str] = query_dict.get("conflict_fields", [])
245
+ on_conflict = ",".join(conflict_fields) if conflict_fields else "id"
246
+ result = await self._client.insert(
247
+ table, token, data,
248
+ on_conflict=on_conflict,
249
+ upsert=True,
250
+ )
251
+ return [result] if isinstance(result, dict) else (result or [])
252
+
253
+ if operation == "bulk_upsert":
254
+ conflict_fields = query_dict.get("conflict_fields", [])
255
+ on_conflict = ",".join(conflict_fields) if conflict_fields else "id"
256
+ result = await self._client.insert(
257
+ table, token, data,
258
+ on_conflict=on_conflict,
259
+ upsert=True,
260
+ )
261
+ return result if isinstance(result, list) else ([result] if result else [])
262
+
263
+ if operation == "update":
264
+ result = await self._client.update(
265
+ table, token, data,
266
+ filters=filters if filters else None,
267
+ )
268
+ return result if isinstance(result, list) else ([result] if result else [])
269
+
270
+ if operation == "delete":
271
+ if not filters:
272
+ raise ValueError(
273
+ "DELETE via PostgREST requires at least one filter. "
274
+ "Use explicit filter kwargs to identify which rows to delete."
275
+ )
276
+ result = await self._client.delete(
277
+ table, token,
278
+ filters=filters,
279
+ )
280
+ return result if isinstance(result, list) else ([result] if result else [])
281
+
282
+ except Exception as exc:
283
+ logger.error(
284
+ "[PostgRESTClientAdapter] %s failed for %s: %s",
285
+ operation.upper(), table, exc,
286
+ )
287
+ raise
288
+
289
+ return NotImplemented
290
+
291
+ # ------------------------------------------------------------------
292
+ # execute_query / execute_write — SQL fallbacks (not supported)
293
+ # ------------------------------------------------------------------
294
+
295
+ async def execute_query(
296
+ self,
297
+ sql: str,
298
+ *args: Any,
299
+ timeout: float = 10.0,
300
+ ) -> list[dict[str, Any]]:
301
+ """Raw SQL is not supported via PostgREST.
302
+
303
+ This is called only when ``execute_query_dict`` returns ``NotImplemented``
304
+ (which shouldn't happen for normal ORM queries). If you reach this
305
+ method, the query uses a feature that PostgREST cannot represent —
306
+ complex CTEs, raw aggregations, pgvector similarity, etc.
307
+
308
+ Consider using ``SupabaseManager.rpc()`` with a PostgreSQL function
309
+ for these cases, or switch to a direct asyncpg adapter for server-side
310
+ deployments.
311
+ """
312
+ raise NotImplementedError(
313
+ "Raw SQL queries are not supported in PostgREST (client) mode.\n"
314
+ "This query requires a direct database connection (asyncpg).\n"
315
+ f"SQL attempted: {sql[:200]!r}"
316
+ )
317
+
318
+ async def execute_write(
319
+ self,
320
+ sql: str,
321
+ *args: Any,
322
+ timeout: float = 10.0,
323
+ max_retries: int = 2,
324
+ ) -> list[dict[str, Any]]:
325
+ """Raw SQL writes are not supported via PostgREST."""
326
+ raise NotImplementedError(
327
+ "Raw SQL writes are not supported in PostgREST (client) mode.\n"
328
+ "This write requires a direct database connection (asyncpg).\n"
329
+ f"SQL attempted: {sql[:200]!r}"
330
+ )
331
+
332
+ # ------------------------------------------------------------------
333
+ # Connection management — not supported in client mode
334
+ # ------------------------------------------------------------------
335
+
336
+ @asynccontextmanager
337
+ async def get_connection(self, timeout: float = 10.0) -> AsyncIterator[Any]:
338
+ """Transactions are not supported via PostgREST."""
339
+ raise NotImplementedError(
340
+ "Direct connections and transactions are not available in PostgREST "
341
+ "(client) mode. Each PostgREST request is its own implicit transaction."
342
+ )
343
+ yield # pragma: no cover
344
+
345
+ def get_active_connection(self) -> Any | None:
346
+ """No persistent connection in PostgREST mode."""
347
+ return None
348
+
349
+ # ------------------------------------------------------------------
350
+ # Lifecycle
351
+ # ------------------------------------------------------------------
352
+
353
+ async def close(self) -> None:
354
+ """Nothing to close — PostgREST uses stateless HTTP."""
355
+ pass
356
+
357
+ # ------------------------------------------------------------------
358
+ # Filter / order translation helpers
359
+ # ------------------------------------------------------------------
360
+
361
+ def _translate_filters(
362
+ self, filters: dict[str, Any] | None
363
+ ) -> dict[str, Any]:
364
+ """Pass ORM filter dicts straight through.
365
+
366
+ ``PostgRESTClient._build_query_params()`` already understands the ORM
367
+ ``field__operator`` convention, so no translation is needed here.
368
+ The executor hands us the same filter dict the query builder built.
369
+ """
370
+ if not filters:
371
+ return {}
372
+ cleaned: dict[str, Any] = {}
373
+ for key, value in filters.items():
374
+ # Skip None values — they mean "no filter"
375
+ if value is not None:
376
+ cleaned[key] = value
377
+ return cleaned
378
+
379
+ def _translate_order(self, order_by: list[str] | None) -> list[str] | None:
380
+ """Pass order_by list straight through — same format as PostgRESTClient."""
381
+ return order_by if order_by else None
382
+
383
+ # ------------------------------------------------------------------
384
+ # Repr
385
+ # ------------------------------------------------------------------
386
+
387
+ def __repr__(self) -> str:
388
+ return (
389
+ f"PostgRESTClientAdapter("
390
+ f"url={getattr(self._config, 'url', '?')!r}, "
391
+ f"auth={'get_jwt' if self._get_jwt else ('SupabaseAuth' if self._auth else 'anon_key')})"
392
+ )
@@ -703,10 +703,12 @@ class Model(RuntimeMixin, metaclass=ModelMeta):
703
703
  Usage::
704
704
 
705
705
  users = await User.raw("SELECT * FROM users WHERE age > $1", 18)
706
- """
707
- from matrx_orm.core.async_db_manager import AsyncDatabaseManager
708
706
 
709
- results = await AsyncDatabaseManager.execute_query(config_name=cls.get_database_name(), query=sql, *params)
707
+ Not supported in PostgREST (client) mode — raises ``NotImplementedError``.
708
+ """
709
+ from matrx_orm.adapters import AdapterRegistry
710
+ adapter = AdapterRegistry.get(cls.get_database_name())
711
+ results = await adapter.execute_query(sql, *params)
710
712
  instances = []
711
713
  for row in results:
712
714
  try:
@@ -724,10 +726,12 @@ class Model(RuntimeMixin, metaclass=ModelMeta):
724
726
  Usage::
725
727
 
726
728
  rows = await User.raw_sql("SELECT count(*) as cnt, role FROM users GROUP BY role")
727
- """
728
- from matrx_orm.core.async_db_manager import AsyncDatabaseManager
729
729
 
730
- results = await AsyncDatabaseManager.execute_query(cls.get_database_name(), sql, *params)
730
+ Not supported in PostgREST (client) mode — raises ``NotImplementedError``.
731
+ """
732
+ from matrx_orm.adapters import AdapterRegistry
733
+ adapter = AdapterRegistry.get(cls.get_database_name())
734
+ results = await adapter.execute_query(sql, *params)
731
735
  return [dict(row) for row in results]
732
736
 
733
737
  @classmethod
@@ -1376,13 +1380,18 @@ class Model(RuntimeMixin, metaclass=ModelMeta):
1376
1380
 
1377
1381
  @classmethod
1378
1382
  async def ensure_m2m_tables(cls) -> None:
1379
- """Create any pending junction tables that don't yet exist in the database."""
1383
+ """Create any pending junction tables that don't yet exist in the database.
1384
+
1385
+ Not supported in PostgREST (client) mode — DDL cannot be issued via the
1386
+ REST API. Schema migrations must be applied server-side.
1387
+ """
1380
1388
  if not cls._pending_junction_tables:
1381
1389
  return
1382
1390
 
1383
- from matrx_orm.core.async_db_manager import AsyncDatabaseManager
1391
+ from matrx_orm.adapters import AdapterRegistry
1384
1392
 
1385
1393
  db_name = cls.get_database_name()
1394
+ adapter = AdapterRegistry.get(db_name)
1386
1395
  for jt in cls._pending_junction_tables:
1387
1396
  sql = (
1388
1397
  f"CREATE TABLE IF NOT EXISTS {jt['junction_table']} ("
@@ -1391,7 +1400,7 @@ class Model(RuntimeMixin, metaclass=ModelMeta):
1391
1400
  f"PRIMARY KEY ({jt['source_column']}, {jt['target_column']})"
1392
1401
  f")"
1393
1402
  )
1394
- await AsyncDatabaseManager.execute_query(db_name, sql)
1403
+ await adapter.execute_query(sql)
1395
1404
 
1396
1405
  @classmethod
1397
1406
  async def get_many(cls, **kwargs: Any) -> list[Self]:
@@ -57,25 +57,42 @@ class BaseManager(Generic[ModelT]):
57
57
  def _initialize_manager(self) -> None:
58
58
  """Initialize the manager and trigger auto-fetch if configured.
59
59
 
60
- Auto-fetch now works correctly in all contexts thanks to automatic
61
- event loop detection and pool recreation in AsyncDatabaseManager.
60
+ Auto-fetch is skipped when no database configuration or adapter has been
61
+ registered yet (e.g. in client/PostgREST mode where asyncpg is not used
62
+ and the adapter is registered asynchronously after module import).
62
63
  """
63
- if int(self.fetch_on_init_limit) > 0:
64
- try:
65
- asyncio.get_running_loop()
66
- asyncio.create_task(coro=self._auto_fetch_on_init_async())
67
- vcprint(
68
- f"[{self.model.__name__}] Auto-fetching on init (async)",
69
- verbose=info,
70
- color="yellow",
71
- )
72
- except RuntimeError:
73
- vcprint(
74
- f"[{self.model.__name__}] Auto-fetching on init (sync)",
75
- verbose=info,
76
- color="yellow",
77
- )
78
- self._auto_fetch_on_init_sync()
64
+ if int(self.fetch_on_init_limit) <= 0:
65
+ return
66
+
67
+ # Skip auto-fetch if no adapter/database config is registered — avoids
68
+ # DatabaseConfigError in client mode where asyncpg is not configured.
69
+ try:
70
+ from matrx_orm.adapters import AdapterRegistry
71
+ from matrx_orm.core.config import registry as _db_registry
72
+ db_name = self.model.get_database_name() if hasattr(self.model, "get_database_name") else None
73
+ if db_name:
74
+ adapter_exists = AdapterRegistry.get_or_none(db_name) is not None
75
+ config_exists = db_name in _db_registry._configs
76
+ if not adapter_exists and not config_exists:
77
+ return
78
+ except Exception:
79
+ return
80
+
81
+ try:
82
+ asyncio.get_running_loop()
83
+ asyncio.create_task(coro=self._auto_fetch_on_init_async())
84
+ vcprint(
85
+ f"[{self.model.__name__}] Auto-fetching on init (async)",
86
+ verbose=info,
87
+ color="yellow",
88
+ )
89
+ except RuntimeError:
90
+ vcprint(
91
+ f"[{self.model.__name__}] Auto-fetching on init (sync)",
92
+ verbose=info,
93
+ color="yellow",
94
+ )
95
+ self._auto_fetch_on_init_sync()
79
96
 
80
97
  def _validate_input_data(
81
98
  self,
@@ -335,7 +335,7 @@ class ManyToManyReference:
335
335
 
336
336
  async def fetch_related(self, instance: Model) -> list[Model]:
337
337
  """Single-query JOIN: junction table + target model in one round-trip."""
338
- from matrx_orm.core.async_db_manager import AsyncDatabaseManager
338
+ from matrx_orm.adapters import AdapterRegistry
339
339
  from matrx_orm.exceptions import ORMException
340
340
 
341
341
  pk_field = instance._meta.primary_keys[0]
@@ -345,6 +345,7 @@ class ManyToManyReference:
345
345
 
346
346
  try:
347
347
  db_name = await self._get_db_name(instance)
348
+ adapter = AdapterRegistry.get(db_name)
348
349
  junction = self.qualified_junction_table
349
350
  target_model = self.resolved_model
350
351
  target_table = target_model._meta.qualified_table_name
@@ -357,7 +358,7 @@ class ManyToManyReference:
357
358
  f"JOIN {junction} j ON j.{self.target_column} = t.{target_pk} "
358
359
  f"WHERE j.{self.source_column} = $1"
359
360
  )
360
- rows = await AsyncDatabaseManager.execute_query(db_name, sql, source_value)
361
+ rows = await adapter.execute_query(sql, source_value)
361
362
 
362
363
  if not rows:
363
364
  return []
@@ -372,7 +373,7 @@ class ManyToManyReference:
372
373
  if not target_ids:
373
374
  return 0
374
375
 
375
- from matrx_orm.core.async_db_manager import AsyncDatabaseManager
376
+ from matrx_orm.adapters import AdapterRegistry
376
377
  from matrx_orm.exceptions import ORMException
377
378
 
378
379
  pk_field = instance._meta.primary_keys[0]
@@ -380,6 +381,7 @@ class ManyToManyReference:
380
381
 
381
382
  try:
382
383
  db_name = await self._get_db_name(instance)
384
+ adapter = AdapterRegistry.get(db_name)
383
385
  junction = self.qualified_junction_table
384
386
 
385
387
  placeholders = ", ".join(
@@ -395,7 +397,7 @@ class ManyToManyReference:
395
397
  for tid in target_ids:
396
398
  params.extend([source_value, tid])
397
399
 
398
- rows = await AsyncDatabaseManager.execute_query(db_name, sql, *params)
400
+ rows = await adapter.execute_query(sql, *params)
399
401
  return len(rows) if rows else len(target_ids)
400
402
  except ORMException as e:
401
403
  e.enrich(model=instance.__class__, operation="add_m2m", **self._enrich_context())
@@ -406,7 +408,7 @@ class ManyToManyReference:
406
408
  if not target_ids:
407
409
  return 0
408
410
 
409
- from matrx_orm.core.async_db_manager import AsyncDatabaseManager
411
+ from matrx_orm.adapters import AdapterRegistry
410
412
  from matrx_orm.exceptions import ORMException
411
413
 
412
414
  pk_field = instance._meta.primary_keys[0]
@@ -414,6 +416,7 @@ class ManyToManyReference:
414
416
 
415
417
  try:
416
418
  db_name = await self._get_db_name(instance)
419
+ adapter = AdapterRegistry.get(db_name)
417
420
  junction = self.qualified_junction_table
418
421
 
419
422
  sql = (
@@ -421,9 +424,7 @@ class ManyToManyReference:
421
424
  f"WHERE {self.source_column} = $1 AND {self.target_column} = ANY($2) "
422
425
  f"RETURNING *"
423
426
  )
424
- rows = await AsyncDatabaseManager.execute_query(
425
- db_name, sql, source_value, list(target_ids)
426
- )
427
+ rows = await adapter.execute_query(sql, source_value, list(target_ids))
427
428
  return len(rows) if rows else 0
428
429
  except ORMException as e:
429
430
  e.enrich(model=instance.__class__, operation="remove_m2m", **self._enrich_context())
@@ -437,7 +438,7 @@ class ManyToManyReference:
437
438
 
438
439
  async def clear(self, instance: Model) -> int:
439
440
  """Remove all junction rows for this instance."""
440
- from matrx_orm.core.async_db_manager import AsyncDatabaseManager
441
+ from matrx_orm.adapters import AdapterRegistry
441
442
  from matrx_orm.exceptions import ORMException
442
443
 
443
444
  pk_field = instance._meta.primary_keys[0]
@@ -445,10 +446,11 @@ class ManyToManyReference:
445
446
 
446
447
  try:
447
448
  db_name = await self._get_db_name(instance)
449
+ adapter = AdapterRegistry.get(db_name)
448
450
  junction = self.qualified_junction_table
449
451
 
450
452
  sql = f"DELETE FROM {junction} WHERE {self.source_column} = $1 RETURNING *"
451
- rows = await AsyncDatabaseManager.execute_query(db_name, sql, source_value)
453
+ rows = await adapter.execute_query(sql, source_value)
452
454
  return len(rows) if rows else 0
453
455
  except ORMException as e:
454
456
  e.enrich(model=instance.__class__, operation="clear_m2m", **self._enrich_context())