loom-kernel 0.1.0__tar.gz → 0.2.1__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 (351) hide show
  1. loom_kernel-0.2.1/CHANGELOG.md +105 -0
  2. loom_kernel-0.2.1/CHANGELOG_RELEASE.md +39 -0
  3. loom_kernel-0.2.1/PKG-INFO +457 -0
  4. loom_kernel-0.2.1/README.md +422 -0
  5. loom_kernel-0.2.1/docs/_static/custom.css +13 -0
  6. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/conf.py +4 -0
  7. loom_kernel-0.2.1/docs/examples-repo/index.md +642 -0
  8. loom_kernel-0.2.1/docs/guides/autocrud.md +166 -0
  9. loom_kernel-0.2.1/docs/guides/celery.md +538 -0
  10. loom_kernel-0.2.1/docs/guides/fake-repo-examples.md +45 -0
  11. loom_kernel-0.2.1/docs/guides/quickstart.md +423 -0
  12. loom_kernel-0.2.1/docs/guides/use-case-dsl.md +477 -0
  13. loom_kernel-0.2.1/docs/index.rst +68 -0
  14. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/pyproject.toml +4 -2
  15. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/celery/__init__.py +2 -0
  16. loom_kernel-0.2.1/src/loom/celery/auto.py +45 -0
  17. loom_kernel-0.2.1/src/loom/celery/bootstrap.py +777 -0
  18. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/celery/config.py +20 -0
  19. loom_kernel-0.2.1/src/loom/celery/constants.py +22 -0
  20. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/celery/runner.py +9 -7
  21. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/celery/service.py +42 -21
  22. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/backend/__init__.py +6 -0
  23. loom_kernel-0.2.1/src/loom/core/backend/core_model.py +426 -0
  24. loom_kernel-0.2.1/src/loom/core/backend/sqlalchemy.py +909 -0
  25. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/bootstrap/__init__.py +8 -1
  26. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/bootstrap/bootstrap.py +15 -5
  27. loom_kernel-0.2.1/src/loom/core/bootstrap/kernel.py +100 -0
  28. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/repository.py +107 -4
  29. loom_kernel-0.2.1/src/loom/core/config/keys.py +24 -0
  30. loom_kernel-0.2.1/src/loom/core/contracts/__init__.py +1 -0
  31. loom_kernel-0.2.1/src/loom/core/contracts/manifest.py +38 -0
  32. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/di/container.py +26 -0
  33. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/discovery/_utils.py +70 -27
  34. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/discovery/interfaces.py +1 -1
  35. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/discovery/manifest.py +30 -6
  36. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/discovery/modules.py +1 -1
  37. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/engine/compiler.py +1 -0
  38. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/engine/executor.py +26 -11
  39. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/engine/plan.py +5 -0
  40. loom_kernel-0.2.1/src/loom/core/errors/codes.py +32 -0
  41. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/errors/errors.py +8 -6
  42. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/job/service.py +0 -1
  43. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/logger/config.py +23 -1
  44. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/model/__init__.py +2 -4
  45. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/model/base.py +7 -15
  46. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/model/projection.py +19 -19
  47. loom_kernel-0.2.1/src/loom/core/model/struct.py +14 -0
  48. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/model/timestamped.py +4 -13
  49. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/projection/__init__.py +8 -6
  50. loom_kernel-0.2.1/src/loom/core/projection/loaders.py +265 -0
  51. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/projection/runtime.py +89 -159
  52. loom_kernel-0.2.1/src/loom/core/repository/__init__.py +39 -0
  53. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/abc/__init__.py +15 -1
  54. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/abc/query.py +2 -2
  55. loom_kernel-0.2.1/src/loom/core/repository/abc/repo_for.py +151 -0
  56. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/abc/repository.py +8 -0
  57. loom_kernel-0.2.1/src/loom/core/repository/registration.py +213 -0
  58. loom_kernel-0.2.1/src/loom/core/repository/registry.py +140 -0
  59. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/__init__.py +6 -8
  60. loom_kernel-0.2.1/src/loom/core/repository/sqlalchemy/loaders.py +134 -0
  61. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/mixins.py +179 -310
  62. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/compiler.py +44 -40
  63. loom_kernel-0.2.1/src/loom/core/repository/sqlalchemy/registry.py +121 -0
  64. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/repository.py +17 -2
  65. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/response/base.py +2 -2
  66. loom_kernel-0.2.1/src/loom/core/use_case/constants.py +27 -0
  67. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/use_case/factory.py +4 -27
  68. loom_kernel-0.2.1/src/loom/core/use_case/invoker.py +120 -0
  69. loom_kernel-0.2.1/src/loom/core/use_case/keys.py +55 -0
  70. loom_kernel-0.2.1/src/loom/core/use_case/registry.py +88 -0
  71. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/use_case/use_case.py +56 -20
  72. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/prometheus/__init__.py +1 -1
  73. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/prometheus/adapter.py +43 -4
  74. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/prometheus/middleware.py +3 -8
  75. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/autocrud.py +68 -27
  76. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/compiler.py +4 -0
  77. loom_kernel-0.2.1/src/loom/rest/constants.py +29 -0
  78. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/errors.py +7 -6
  79. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/app.py +12 -6
  80. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/auto.py +180 -85
  81. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/openapi.py +17 -15
  82. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/router_runtime.py +13 -10
  83. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/model.py +1 -1
  84. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/testing/in_memory.py +29 -16
  85. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/testing/repository_harness.py +12 -1
  86. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/testing/runner.py +1 -1
  87. loom_kernel-0.2.1/tests/integration/celery_bootstrap/config/conf.celery.integration.yaml +51 -0
  88. loom_kernel-0.2.1/tests/integration/celery_bootstrap/test_auto_create_app_integration.py +402 -0
  89. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/celery_bootstrap/test_bootstrap_worker.py +6 -5
  90. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/test_repository_integration.py +88 -1
  91. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/core/rest/test_auto_interface_integration.py +77 -6
  92. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/core/rest/test_fastapi_app_integration.py +21 -10
  93. loom_kernel-0.2.1/tests/integration/core/use_case/test_custom_repository_integration.py +238 -0
  94. loom_kernel-0.2.1/tests/integration/fake_repo/config/conf.interfaces.yaml +14 -0
  95. loom_kernel-0.2.1/tests/integration/fake_repo/config/conf.manifest.yaml +8 -0
  96. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/conf.modules.yaml +4 -17
  97. loom_kernel-0.1.0/tests/integration/fake_repo/config/conf.manifest.yaml → loom_kernel-0.2.1/tests/integration/fake_repo/config/conf.yaml +0 -4
  98. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/manifest.py +4 -0
  99. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/interface.py +6 -0
  100. loom_kernel-0.2.1/tests/integration/fake_repo/product/jobs.py +17 -0
  101. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/model.py +7 -21
  102. loom_kernel-0.2.1/tests/integration/fake_repo/product/repository.py +38 -0
  103. loom_kernel-0.2.1/tests/integration/fake_repo/product/repository_contract.py +14 -0
  104. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/use_cases.py +10 -0
  105. loom_kernel-0.2.1/tests/integration/support/__init__.py +1 -0
  106. loom_kernel-0.2.1/tests/integration/support/logical_repo_fixtures.py +34 -0
  107. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/celery_bootstrap/test_bootstrap.py +162 -3
  108. loom_kernel-0.2.1/tests/unit/celery_jobs/test_auto.py +73 -0
  109. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_celery_service.py +8 -4
  110. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_runner.py +9 -4
  111. loom_kernel-0.2.1/tests/unit/core/backend/test_backend_compiler.py +246 -0
  112. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/test_bootstrap.py +8 -0
  113. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/test_bootstrap_metrics.py +22 -16
  114. loom_kernel-0.2.1/tests/unit/core/bootstrap/test_kernel.py +58 -0
  115. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/cache/test_cached_repository.py +120 -0
  116. loom_kernel-0.2.1/tests/unit/core/discovery/test_manifest.py +50 -0
  117. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_executor.py +118 -0
  118. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_executor_uow.py +53 -0
  119. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/errors/test_errors.py +8 -7
  120. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_inline_service.py +2 -4
  121. loom_kernel-0.2.1/tests/unit/core/model/test_struct.py +12 -0
  122. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/projection/test_runtime.py +13 -29
  123. loom_kernel-0.2.1/tests/unit/core/repository/sqlalchemy/test_loaders.py +113 -0
  124. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/test_repository.py +89 -1
  125. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_factory.py +49 -16
  126. loom_kernel-0.2.1/tests/unit/core/use_case/test_invoker.py +152 -0
  127. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/rest/test_autocrud.py +30 -27
  128. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/rest/test_fastapi_auto_logger.py +29 -0
  129. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/rest/test_rest_adapter.py +36 -1
  130. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/rest/test_rest_compiler.py +50 -0
  131. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/rest/test_rest_model.py +2 -2
  132. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/rest/test_router_runtime.py +3 -3
  133. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/testing/test_golden.py +12 -4
  134. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/testing/test_http_harness.py +3 -4
  135. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/testing/test_in_memory.py +9 -3
  136. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/testing/test_runner.py +11 -3
  137. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/uv.lock +18 -4
  138. loom_kernel-0.1.0/CHANGELOG.md +0 -69
  139. loom_kernel-0.1.0/CHANGELOG_RELEASE.md +0 -74
  140. loom_kernel-0.1.0/PKG-INFO +0 -248
  141. loom_kernel-0.1.0/README.md +0 -215
  142. loom_kernel-0.1.0/docs/_static/custom.css +0 -11
  143. loom_kernel-0.1.0/docs/examples-repo/index.md +0 -16
  144. loom_kernel-0.1.0/docs/guides/autocrud.md +0 -25
  145. loom_kernel-0.1.0/docs/guides/fake-repo-examples.md +0 -17
  146. loom_kernel-0.1.0/docs/guides/quickstart.md +0 -59
  147. loom_kernel-0.1.0/docs/guides/use-case-dsl.md +0 -76
  148. loom_kernel-0.1.0/docs/index.rst +0 -38
  149. loom_kernel-0.1.0/src/loom/celery/bootstrap.py +0 -317
  150. loom_kernel-0.1.0/src/loom/core/backend/sqlalchemy.py +0 -291
  151. loom_kernel-0.1.0/src/loom/core/projection/loaders.py +0 -94
  152. loom_kernel-0.1.0/src/loom/core/repository/__init__.py +0 -21
  153. loom_kernel-0.1.0/src/loom/core/repository/abc/repo_for.py +0 -56
  154. loom_kernel-0.1.0/src/loom/core/repository/sqlalchemy/loaders.py +0 -235
  155. loom_kernel-0.1.0/tests/integration/fake_repo/config/conf.interfaces.yaml +0 -24
  156. loom_kernel-0.1.0/tests/integration/fake_repo/config/conf.yaml +0 -24
  157. loom_kernel-0.1.0/tests/unit/core/backend/test_backend_compiler.py +0 -114
  158. loom_kernel-0.1.0/tests/unit/core/repository/sqlalchemy/test_loaders.py +0 -493
  159. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/.github/workflows/ci-main.yml +0 -0
  160. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/.github/workflows/ci-pr.yml +0 -0
  161. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/.github/workflows/docs.yml +0 -0
  162. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/.github/workflows/release.yml +0 -0
  163. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/.gitignore +0 -0
  164. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/.pre-commit-config.yaml +0 -0
  165. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/.readthedocs.yaml +0 -0
  166. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/LICENSE +0 -0
  167. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/Makefile +0 -0
  168. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/codecov.yml +0 -0
  169. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/_static/.gitkeep +0 -0
  170. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/_static/logo-transparent.png +0 -0
  171. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/_static/logo.svg +0 -0
  172. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/architecture/adr/README.md +0 -0
  173. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/architecture/clean-architecture.md +0 -0
  174. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/architecture/overview.md +0 -0
  175. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/reference/api/core.rst +0 -0
  176. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/reference/api/repository.rst +0 -0
  177. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/reference/api/rest.rst +0 -0
  178. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/reference/api/testing.rst +0 -0
  179. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/reference/index.rst +0 -0
  180. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/docs/requirements.txt +0 -0
  181. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/sonar-project.properties +0 -0
  182. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/__init__.py +0 -0
  183. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/celery/event_loop.py +0 -0
  184. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/backend/protocol.py +0 -0
  185. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/__init__.py +0 -0
  186. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/__init__.py +0 -0
  187. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/backend.py +0 -0
  188. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/config.py +0 -0
  189. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/dependency.py +0 -0
  190. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/codec.py +0 -0
  191. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/decorators.py +0 -0
  192. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/dependency.py +0 -0
  193. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/gateway.py +0 -0
  194. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/keys.py +0 -0
  195. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/cache/serializer.py +0 -0
  196. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/command/__init__.py +0 -0
  197. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/command/adapter.py +0 -0
  198. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/command/base.py +0 -0
  199. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/command/field.py +0 -0
  200. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/command/introspection.py +0 -0
  201. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/config/__init__.py +0 -0
  202. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/config/errors.py +0 -0
  203. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/config/loader.py +0 -0
  204. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/config/model.py +0 -0
  205. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/di/__init__.py +0 -0
  206. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/di/scope.py +0 -0
  207. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/discovery/__init__.py +0 -0
  208. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/discovery/base.py +0 -0
  209. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/engine/__init__.py +0 -0
  210. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/engine/compilable.py +0 -0
  211. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/engine/events.py +0 -0
  212. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/engine/metrics.py +0 -0
  213. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/errors/__init__.py +0 -0
  214. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/job/__init__.py +0 -0
  215. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/job/callback.py +0 -0
  216. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/job/context.py +0 -0
  217. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/job/handle.py +0 -0
  218. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/job/job.py +0 -0
  219. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/logger/__init__.py +0 -0
  220. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/logger/abc.py +0 -0
  221. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/logger/registry.py +0 -0
  222. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/logger/structlogger.py +0 -0
  223. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/model/enums.py +0 -0
  224. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/model/field.py +0 -0
  225. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/model/introspection.py +0 -0
  226. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/model/relation.py +0 -0
  227. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/model/types.py +0 -0
  228. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/model/types_postgres.py +0 -0
  229. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/mutation.py +0 -0
  230. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/integrity.py +0 -0
  231. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/model.py +0 -0
  232. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/profile_loader.py +0 -0
  233. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/projection.py +0 -0
  234. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/__init__.py +0 -0
  235. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/cursor.py +0 -0
  236. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/errors.py +0 -0
  237. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/filters.py +0 -0
  238. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/ordering.py +0 -0
  239. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/paths.py +0 -0
  240. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/subquery.py +0 -0
  241. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/session_manager.py +0 -0
  242. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/transactional.py +0 -0
  243. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/uow.py +0 -0
  244. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/response/__init__.py +0 -0
  245. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/tracing/__init__.py +0 -0
  246. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/tracing/context.py +0 -0
  247. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/transport/__init__.py +0 -0
  248. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/transport/adapter.py +0 -0
  249. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/uow/__init__.py +0 -0
  250. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/uow/abc.py +0 -0
  251. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/uow/context.py +0 -0
  252. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/use_case/__init__.py +0 -0
  253. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/use_case/_predicates.py +0 -0
  254. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/use_case/compute.py +0 -0
  255. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/use_case/field_ref.py +0 -0
  256. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/use_case/markers.py +0 -0
  257. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/core/use_case/rule.py +0 -0
  258. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/__init__.py +0 -0
  259. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/adapter.py +0 -0
  260. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/__init__.py +0 -0
  261. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/response.py +0 -0
  262. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/middleware.py +0 -0
  263. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/rest/rest_adapter.py +0 -0
  264. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/testing/__init__.py +0 -0
  265. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/testing/golden.py +0 -0
  266. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/src/loom/testing/http_harness.py +0 -0
  267. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/__init__.py +0 -0
  268. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/conftest.py +0 -0
  269. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/golden/__init__.py +0 -0
  270. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/golden/baselines/.gitkeep +0 -0
  271. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/golden/outputs/.gitkeep +0 -0
  272. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/golden/plans/.gitkeep +0 -0
  273. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/helpers/__init__.py +0 -0
  274. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/__init__.py +0 -0
  275. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/celery_bootstrap/__init__.py +0 -0
  276. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/conftest.py +0 -0
  277. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/core/__init__.py +0 -0
  278. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/core/repository/__init__.py +0 -0
  279. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/__init__.py +0 -0
  280. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/conftest.py +0 -0
  281. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/test_cache_integration.py +0 -0
  282. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/core/rest/__init__.py +0 -0
  283. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/core/use_case/test_use_case_crud_integration.py +0 -0
  284. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/__init__.py +0 -0
  285. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/__init__.py +0 -0
  286. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/main.py +0 -0
  287. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/__init__.py +0 -0
  288. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/category/__init__.py +0 -0
  289. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/category/model.py +0 -0
  290. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/category/schemas.py +0 -0
  291. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/relations.py +0 -0
  292. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/review/__init__.py +0 -0
  293. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/review/model.py +0 -0
  294. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/review/schemas.py +0 -0
  295. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/schemas.py +0 -0
  296. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/celery_bootstrap/__init__.py +0 -0
  297. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/celery_bootstrap/test_event_loop.py +0 -0
  298. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/__init__.py +0 -0
  299. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_config.py +0 -0
  300. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/backend/__init__.py +0 -0
  301. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/__init__.py +0 -0
  302. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/command/__init__.py +0 -0
  303. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_command_base.py +0 -0
  304. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_command_field.py +0 -0
  305. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_command_patch.py +0 -0
  306. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_introspection.py +0 -0
  307. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/config/__init__.py +0 -0
  308. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/config/test_config.py +0 -0
  309. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/di/__init__.py +0 -0
  310. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/di/test_container.py +0 -0
  311. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/engine/__init__.py +0 -0
  312. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_compiler.py +0 -0
  313. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_executor_trace.py +0 -0
  314. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_metrics.py +0 -0
  315. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_plan.py +0 -0
  316. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/errors/__init__.py +0 -0
  317. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/job/__init__.py +0 -0
  318. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/job/conftest.py +0 -0
  319. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_callback.py +0 -0
  320. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_context.py +0 -0
  321. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_handle.py +0 -0
  322. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_job.py +0 -0
  323. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/logger/test_registry.py +0 -0
  324. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/model/__init__.py +0 -0
  325. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/model/test_model.py +0 -0
  326. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/model/test_timestamped.py +0 -0
  327. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/repository/abc/conftest.py +0 -0
  328. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/repository/abc/test_query.py +0 -0
  329. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/repository/abc/test_repository_contract.py +0 -0
  330. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/conftest.py +0 -0
  331. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/test_transactional.py +0 -0
  332. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/tracing/__init__.py +0 -0
  333. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/tracing/test_context.py +0 -0
  334. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/uow/__init__.py +0 -0
  335. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/uow/test_executor_uow.py +0 -0
  336. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/uow/test_sqlalchemy_uow.py +0 -0
  337. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/uow/test_uow_protocols.py +0 -0
  338. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/__init__.py +0 -0
  339. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_compute.py +0 -0
  340. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_field_ref.py +0 -0
  341. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_markers.py +0 -0
  342. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_rule.py +0 -0
  343. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_use_case.py +0 -0
  344. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/prometheus/__init__.py +0 -0
  345. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/prometheus/test_adapter.py +0 -0
  346. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/prometheus/test_middleware.py +0 -0
  347. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/rest/__init__.py +0 -0
  348. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/rest/test_middleware.py +0 -0
  349. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/rest/test_pydantic_adapter.py +0 -0
  350. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/rest/test_response.py +0 -0
  351. {loom_kernel-0.1.0 → loom_kernel-0.2.1}/tests/unit/testing/__init__.py +0 -0
@@ -0,0 +1,105 @@
1
+ # 🚀 Release 0.2.1 ([#12](https://github.com/the-reacher-data/loom-py/pull/12)) ([`87f7d1f`](https://github.com/the-reacher-data/loom-py/commit/87f7d1f1eb1ccde71d0aca1c5584b83317e30707))
2
+
3
+
4
+ ## ✨ Features
5
+ ### logger
6
+ - **logger:** support per-logger levels from config
7
+
8
+ ### repository
9
+ - **repository:** generalize main repo registration for loom structs
10
+
11
+
12
+ ## 🐛 Fixes
13
+ ### rest
14
+ - **rest:** serialize pagination envelopes in camel case
15
+ - **rest:** support loom structs in autocrud tests
16
+
17
+ ### prometheus
18
+ - **prometheus:** expose metrics at exact path
19
+
20
+ ### review
21
+ - **review:** apply quick-fix pass from code review
22
+
23
+
24
+ ## 📖 Documentation
25
+ ### repository
26
+ - **repository:** align custom repo examples
27
+
28
+
29
+
30
+ ## ♻️ Refactor
31
+ ### repository
32
+ - **repository:** clarify sqlalchemy registration builder
33
+ - **repository:** inject default repository builder
34
+ - **repository:** simplify registration module
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+ # 🚀 Release 0.2.0 ([#9](https://github.com/the-reacher-data/loom-py/pull/9)) ([`2f669ab`](https://github.com/the-reacher-data/loom-py/commit/2f669ab205c7255eb6494e4cdb8ab8092817af62))
43
+
44
+ ## ✨ Features
45
+
46
+ ### cache
47
+ - **cache:** aiocache gateway with auto-inferred invalidation specs<br>
48
+ > CachedRepository wraps any repository with read-through/write-through caching. ONE_TO_MANY depends_on specs are auto-generated from field annotations — no explicit declaration needed. Explicit depends_on always wins.
49
+
50
+ ### celery
51
+ - **celery:** production-ready Celery integration layer<br>
52
+ > CeleryJobService, persistent worker event loop, trace propagation, eager fallback, and task_default_queue routing so callbacks land on the correct consumed queue. bootstrap_worker compiles use cases, repositories, and registers Celery tasks in a single call.
53
+
54
+ - **celery:** worker job discovery from modules or manifest<br>
55
+ > bootstrap_worker discovers and registers Job classes automatically from module include paths (mode: modules) or from a typed WorkerManifest (mode: manifest). WorkerManifest replaces scattered JOBS/USE_CASES/INTERFACES module attributes with a single typed contract.
56
+
57
+ - **celery:** interfaces= and use_cases= on bootstrap_worker<br>
58
+ > Callbacks that call ApplicationInvoker need matching use-case keys compiled in the worker. interfaces= extracts use-case types from RestInterface route declarations (including AutoCRUD-generated ones). use_cases= handles non-AutoCRUD scenarios. Both can be combined with discovery mode.
59
+
60
+ ### core
61
+ - **core:** typed repository abstractions and SQLAlchemy backend<br>
62
+ > Async repository protocol (RepositoryRead, RepositoryWrite, RepoFor) backed by SQLAlchemy 2.0 async session. Struct-based model system using msgspec.Struct as the single source of truth — models compile to SA mapped classes at startup via compile_all(). count() and UPDATE RETURNING included as first-class operations.
63
+
64
+ - **core:** use-case DSL with field refs, compute, rules and typed markers<br>
65
+ > Declarative use-case definition via Input, Load, LoadById, Exists, Compute and Rule markers. Signature inspection runs once at compile time; RuntimeExecutor drives execution from an immutable ExecutionPlan. No per-request reflection.
66
+
67
+ - **core:** ApplicationInvoker and named use-case registry<br>
68
+ > Use cases and job callbacks invoke other use cases by type through ApplicationInvoker without direct coupling. A named registry maps use-case keys to compiled instances at bootstrap, providing a stable cross-invocation contract.
69
+
70
+ - **core:** compiled model artifact and cache entity keys<br>
71
+ > compile_all() produces a typed CompiledCore artifact exposing stable entity keys used by the cache layer for deterministic repository-level invalidation across reads and writes.
72
+
73
+ - **core:** executor skips UoW for read-only use cases and GET routes<br>
74
+ > UseCase.read_only=True and all GET routes bypass UoW.begin/commit, removing at minimum one BEGIN+COMMIT round-trip from every read request on PostgreSQL.
75
+
76
+ ### job
77
+ - **job:** async job domain model and orchestration primitives<br>
78
+ > Job[ResultT] base class with Celery routing ClassVars. JobHandle / JobGroup with dual-mode waiting (Celery + inline). JobCallback lifecycle with on_success/on_failure. Dispatch is transactionally safe — jobs flush on UoW commit and are cleared on rollback.
79
+
80
+ ### observability
81
+ - **observability:** trace_id propagation and Prometheus adapter<br>
82
+ > trace_id injected into every request context and propagated to job callbacks. MetricsAdapter protocol emits execution events; PrometheusAdapter records latency histograms and error counters with low cardinality labels.
83
+
84
+ ### projection
85
+ - **projection:** compiler-driven memory/SQL routing<br>
86
+ > Projections are source-agnostic at declaration time. The backend compiler decides at compile_all() whether each projection runs in-memory (relation already loaded in the active profile) or via SQL. Users declare only CountLoader, ExistsLoader, or JoinFieldsLoader — no source= parameter. Internal _Memory* and _Sql* loaders are synthesized at compile time.
87
+
88
+ ### rest
89
+ - **rest:** AutoCRUD and FastAPI adapter<br>
90
+ > RestInterface.auto=True generates full CRUD routes at class definition time via build_auto_routes(). OpenAPI contracts expose query params, pagination defaults, and decoupled CreateInput/UpdateInput write DTOs. Discovery engine mounts all declared interfaces at bootstrap.
91
+
92
+ ## 📖 Documentation
93
+
94
+ - Sphinx documentation platform with full public guides<br>
95
+ > Quickstart, use-case DSL reference, AutoCRUD guide, Celery integration guide (job definition, dispatch, callbacks, YAML reference, bootstrap options, ApplicationInvoker, Docker-compose stack), and dummy-loom examples-repo walkthrough. Deployed to Read the Docs.
96
+
97
+ ## ⚡ Performance
98
+
99
+ ### engine
100
+ - **engine:** UPDATE RETURNING replaces SELECT + flush + refresh<br>
101
+ > SQLAlchemyUpdateMixin.update() issues a single UPDATE ... RETURNING round-trip. Server-side onupdate expressions are pre-computed at init time and injected into the SET clause automatically.
102
+
103
+ ### repository
104
+ - **repository:** single-query total count for offset pagination<br>
105
+ > list_with_query with PaginationMode.OFFSET issues a single SELECT COUNT(*) instead of a separate full-table scan, eliminating one round-trip per paginated list operation.
@@ -0,0 +1,39 @@
1
+ # 🚀 Release 0.2.1 ([#12](https://github.com/the-reacher-data/loom-py/pull/12)) ([`87f7d1f`](https://github.com/the-reacher-data/loom-py/commit/87f7d1f1eb1ccde71d0aca1c5584b83317e30707))
2
+
3
+
4
+ ## ✨ Features
5
+ ### logger
6
+ - **logger:** support per-logger levels from config
7
+
8
+ ### repository
9
+ - **repository:** generalize main repo registration for loom structs
10
+
11
+
12
+ ## 🐛 Fixes
13
+ ### rest
14
+ - **rest:** serialize pagination envelopes in camel case
15
+ - **rest:** support loom structs in autocrud tests
16
+
17
+ ### prometheus
18
+ - **prometheus:** expose metrics at exact path
19
+
20
+ ### review
21
+ - **review:** apply quick-fix pass from code review
22
+
23
+
24
+ ## 📖 Documentation
25
+ ### repository
26
+ - **repository:** align custom repo examples
27
+
28
+
29
+
30
+ ## ♻️ Refactor
31
+ ### repository
32
+ - **repository:** clarify sqlalchemy registration builder
33
+ - **repository:** inject default repository builder
34
+ - **repository:** simplify registration module
35
+
36
+
37
+
38
+
39
+
@@ -0,0 +1,457 @@
1
+ Metadata-Version: 2.4
2
+ Name: loom-kernel
3
+ Version: 0.2.1
4
+ Summary: Loom Python project
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: aiocache<1.0,>=0.12
8
+ Requires-Dist: msgspec<1.0,>=0.18
9
+ Requires-Dist: omegaconf<3.0,>=2.3
10
+ Requires-Dist: prometheus-client>=0.20
11
+ Requires-Dist: structlog<26.0,>=24.4
12
+ Provides-Extra: cache
13
+ Requires-Dist: aiocache<1.0,>=0.12; extra == 'cache'
14
+ Requires-Dist: omegaconf<3.0,>=2.3; extra == 'cache'
15
+ Provides-Extra: celery
16
+ Requires-Dist: celery<6.0,>=5.3; extra == 'celery'
17
+ Requires-Dist: kombu<6.0,>=5.3; extra == 'celery'
18
+ Requires-Dist: redis<6.0,>=5.0; extra == 'celery'
19
+ Provides-Extra: config
20
+ Requires-Dist: omegaconf<3.0,>=2.3; extra == 'config'
21
+ Provides-Extra: prometheus
22
+ Requires-Dist: prometheus-client>=0.20; extra == 'prometheus'
23
+ Provides-Extra: pyspark
24
+ Requires-Dist: pyspark<4.0,>=3.5; extra == 'pyspark'
25
+ Provides-Extra: rest
26
+ Requires-Dist: fastapi<1.0,>=0.115; extra == 'rest'
27
+ Requires-Dist: httptools<1.0,>=0.6; extra == 'rest'
28
+ Requires-Dist: prometheus-client>=0.20; extra == 'rest'
29
+ Requires-Dist: pydantic<3.0,>=2.0; extra == 'rest'
30
+ Requires-Dist: uvicorn<1.0,>=0.30; extra == 'rest'
31
+ Requires-Dist: uvloop<1.0,>=0.21; (sys_platform != 'win32') and extra == 'rest'
32
+ Provides-Extra: sqlalchemy
33
+ Requires-Dist: sqlalchemy<3.0,>=2.0; extra == 'sqlalchemy'
34
+ Description-Content-Type: text/markdown
35
+
36
+ <p align="center">
37
+ <img src="docs/_static/logo-transparent.png" alt="loom-kernel" width="160" style="background:#ffffff;border-radius:6px;padding:8px 20px;" />
38
+ </p>
39
+
40
+ # loom-kernel
41
+
42
+ [![CI](https://img.shields.io/github/actions/workflow/status/the-reacher-data/loom-py/ci-main.yml?branch=master&label=ci)](https://github.com/the-reacher-data/loom-py/actions/workflows/ci-main.yml)
43
+ [![Docs](https://img.shields.io/github/actions/workflow/status/the-reacher-data/loom-py/docs.yml?branch=master&label=docs)](https://github.com/the-reacher-data/loom-py/actions/workflows/docs.yml)
44
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=the-reacher-data_loom-py&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=the-reacher-data_loom-py)
45
+ [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=the-reacher-data_loom-py&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=the-reacher-data_loom-py)
46
+ [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=the-reacher-data_loom-py&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=the-reacher-data_loom-py)
47
+ [![Coverage](https://codecov.io/gh/the-reacher-data/loom-py/branch/master/graph/badge.svg)](https://app.codecov.io/gh/the-reacher-data/loom-py/tree/master)
48
+ [![PyPI](https://img.shields.io/pypi/v/loom-kernel)](https://pypi.org/project/loom-kernel/)
49
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://github.com/the-reacher-data/loom-py/blob/master/pyproject.toml)
50
+
51
+ Framework-agnostic Python toolkit to build backend applications with:
52
+
53
+ - **Auto-CRUD** — full REST surface from a model declaration, two lines of code
54
+ - typed use cases (`msgspec.Struct`) with rules, computes, and dependency injection
55
+ - repositories decoupled from infrastructure
56
+ - REST/FastAPI adapters with OpenAPI generation
57
+ - background jobs and Celery workers, first-class
58
+ - testing utilities for business workflows
59
+
60
+ ## Purpose
61
+
62
+ `loom-kernel` helps you ship production APIs faster without sacrificing clean
63
+ architecture. Declare your domain model, describe your business rules, and let the
64
+ framework handle the infrastructure plumbing — DB wiring, DI, routing, serialization.
65
+
66
+ The library separates core contracts from concrete adapters so you can swap
67
+ infrastructure (DB, cache, transport) without breaking business logic.
68
+
69
+ ## Documentation
70
+
71
+ - Usage guides and architecture docs are available in the `docs/` site.
72
+ - API reference is autogenerated from public docstrings.
73
+ - End-to-end demo application: [`dummy-loom`](https://github.com/the-reacher-data/dummy-loom).
74
+
75
+ ## Main subpaths
76
+
77
+ | Subpath | What it is for |
78
+ | --- | --- |
79
+ | `src/loom/core/use_case` | `UseCase` definition, rules (`Rule`), and compute steps (`Compute`). |
80
+ | `src/loom/core/engine` | Compilation and runtime execution of a use-case plan. |
81
+ | `src/loom/core/repository/abc` | Repository contracts, pagination, and typed query spec. |
82
+ | `src/loom/core/repository/sqlalchemy` | Concrete async SQLAlchemy repository implementation. |
83
+ | `src/loom/core/model` | Base model, fields, relations, and entity introspection. |
84
+ | `src/loom/core/cache` | Decorators and cached repository with dependency invalidation. |
85
+ | `src/loom/rest` | Framework-agnostic REST model and route compiler. |
86
+ | `src/loom/rest/fastapi` | Direct FastAPI integration (auto wiring and runtime router). |
87
+ | `src/loom/prometheus` | Middleware and adapter for runtime metrics. |
88
+ | `src/loom/testing` | Harnesses for unit/integration tests and golden tests. |
89
+
90
+ ## Quick start
91
+
92
+ ### 1. Define your model
93
+
94
+ ```python
95
+ from loom.core.model import ColumnField, OnDelete, TimestampedModel
96
+
97
+ class User(TimestampedModel):
98
+ __tablename__ = "users"
99
+ id: int = ColumnField(primary_key=True, autoincrement=True)
100
+ full_name: str = ColumnField(length=120)
101
+ email: str = ColumnField(length=255, unique=True, index=True)
102
+
103
+ class Address(TimestampedModel):
104
+ __tablename__ = "addresses"
105
+ id: int = ColumnField(primary_key=True, autoincrement=True)
106
+ user_id: int = ColumnField(foreign_key="users.id", on_delete=OnDelete.CASCADE, index=True)
107
+ city: str = ColumnField(length=120)
108
+ country: str = ColumnField(length=120)
109
+ ```
110
+
111
+ ### 2. Write use cases
112
+
113
+ Use cases declare their inputs and invariants declaratively. The engine resolves them before `execute()` runs.
114
+
115
+ ```python
116
+ import re
117
+ from loom.core.command import Command, Patch
118
+ from loom.core.errors import NotFound
119
+ from loom.core.use_case import Exists, F, Input, LoadById, OnMissing, Rule
120
+ from loom.core.use_case.use_case import UseCase
121
+
122
+ _EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
123
+
124
+ class CreateUser(Command, frozen=True):
125
+ full_name: str
126
+ email: str
127
+
128
+ class UpdateUser(Command, frozen=True):
129
+ full_name: Patch[str] = None
130
+ email: Patch[str] = None
131
+
132
+ def _name_must_not_be_blank(full_name: str) -> str | None:
133
+ return None if full_name.strip() else "full_name must not be blank"
134
+
135
+ def _email_must_be_valid(email: str) -> str | None:
136
+ return None if _EMAIL_RE.fullmatch(email) else "email must be valid"
137
+
138
+ class CreateUserUseCase(UseCase[User, User]):
139
+ rules = [
140
+ Rule.check(F(CreateUser).full_name, via=_name_must_not_be_blank),
141
+ Rule.check(F(CreateUser).email, via=_email_must_be_valid),
142
+ Rule.forbid(lambda _, __, exists: exists, message="email already exists").from_params("email_exists"),
143
+ ]
144
+
145
+ async def execute(
146
+ self,
147
+ cmd: CreateUser = Input(),
148
+ email_exists: bool = Exists(User, from_command="email", against="email"),
149
+ ) -> User:
150
+ return await self.main_repo.create(cmd)
151
+
152
+ class UpdateUserUseCase(UseCase[User, User | None]):
153
+ rules = [Rule.check(F(UpdateUser).full_name, via=_name_must_not_be_blank).when_present(F(UpdateUser).full_name)]
154
+
155
+ async def execute(
156
+ self,
157
+ user_id: int,
158
+ cmd: UpdateUser = Input(),
159
+ current_user: User = LoadById(User, by="user_id"), # loaded automatically
160
+ ) -> User | None:
161
+ return await self.main_repo.update(user_id, cmd)
162
+ ```
163
+
164
+ **`Exists`** checks a DB condition before execute runs — no boilerplate in the body.
165
+ **`LoadById`** fetches an entity by a path/command parameter, available in rules and the body.
166
+ **`Patch[T]`** marks a field as optional in partial updates; `.when_present(...)` gates rules on whether the field was sent.
167
+
168
+ ### 3. Scope resources under a parent
169
+
170
+ Use `from_param` to guard nested routes (e.g. `/users/{user_id}/addresses/{address_id}`):
171
+
172
+ ```python
173
+ from loom.core.use_case import Exists, Input, OnMissing
174
+
175
+ class CreateAddressUseCase(UseCase[Address, Address]):
176
+ async def execute(
177
+ self,
178
+ user_id: int,
179
+ cmd: CreateUserAddress = Input(),
180
+ _user_exists: bool = Exists(User, from_param="user_id", against="id", on_missing=OnMissing.RAISE),
181
+ ) -> Address:
182
+ return await self.main_repo.create(CreateAddressRecord(user_id=user_id, **cmd.__dict__))
183
+ ```
184
+
185
+ `OnMissing.RAISE` returns a structured 404 automatically — no `if` in the body.
186
+
187
+ ### 4. Structured queries
188
+
189
+ Build explicit queries without raw SQL:
190
+
191
+ ```python
192
+ from loom.core.repository.abc.query import (
193
+ FilterGroup, FilterOp, FilterSpec, PageResult, PaginationMode, QuerySpec, SortSpec,
194
+ )
195
+
196
+ class ListLowStockProductsUseCase(UseCase[Product, PageResult[Product]]):
197
+ async def execute(self, profile: str = "default") -> PageResult[Product]:
198
+ query = QuerySpec(
199
+ filters=FilterGroup(filters=(FilterSpec(field="stock", op=FilterOp.LTE, value=5),)),
200
+ sort=(SortSpec(field="stock", direction="ASC"),),
201
+ pagination=PaginationMode.OFFSET,
202
+ limit=20,
203
+ page=1,
204
+ )
205
+ result = await self.main_repo.list_with_query(query, profile=profile)
206
+ if not isinstance(result, PageResult):
207
+ raise RuntimeError("expected offset result")
208
+ return result
209
+ ```
210
+
211
+ ### 5. Background jobs
212
+
213
+ Jobs are use-case-like executors that run in a queue. `LoadById` works the same way:
214
+
215
+ ```python
216
+ from loom.core.job.job import Job
217
+ from loom.core.use_case import Input, LoadById
218
+
219
+ class SendRestockEmailJob(Job[bool]):
220
+ __queue__ = "notifications"
221
+
222
+ async def execute(
223
+ self,
224
+ product_id: int,
225
+ cmd: SendRestockEmailCommand = Input(),
226
+ product: Product = LoadById(Product, by="product_id"),
227
+ ) -> bool:
228
+ if product.stock > 0:
229
+ return False
230
+ # send email to cmd.recipient_email ...
231
+ return True
232
+ ```
233
+
234
+ ### 6. Dispatch jobs from use cases + callbacks
235
+
236
+ ```python
237
+ from loom.core.job.service import JobService
238
+ from loom.core.use_case.use_case import UseCase
239
+
240
+ class DispatchRestockEmailUseCase(UseCase[Product, DispatchRestockEmailResponse]):
241
+ def __init__(self, job_service: JobService) -> None:
242
+ self._jobs = job_service
243
+
244
+ async def execute(self, product_id: str, cmd: DispatchRestockEmailCommand = Input()) -> DispatchRestockEmailResponse:
245
+ handle = self._jobs.dispatch(
246
+ SendRestockEmailJob,
247
+ params={"product_id": int(product_id)},
248
+ payload={"product_id": int(product_id), "recipient_email": cmd.recipient_email},
249
+ on_success=RestockEmailSuccessCallback,
250
+ on_failure=RestockEmailFailureCallback,
251
+ )
252
+ return DispatchRestockEmailResponse(job_id=handle.job_id, queue=handle.queue)
253
+ ```
254
+
255
+ Callbacks are resolved by the DI container and receive the job result + context:
256
+
257
+ ```python
258
+ class RestockEmailSuccessCallback:
259
+ def __init__(self, app: ApplicationInvoker) -> None:
260
+ self._app = app
261
+
262
+ async def on_success(self, job_id: str, result: Any, **context: Any) -> None:
263
+ if not result:
264
+ return
265
+ entity = self._app.entity(Product)
266
+ product = await entity.get(params={"id": context["product_id"]})
267
+ if product:
268
+ await entity.update(params={"id": product.id}, payload={"category": f"{product.category}-notified"})
269
+ ```
270
+
271
+ ### 7. Chain use cases (workflow pattern)
272
+
273
+ `ApplicationInvoker` lets a use case call another use case by type — no tight coupling:
274
+
275
+ ```python
276
+ from loom.core.use_case.invoker import ApplicationInvoker
277
+
278
+ class RestockWorkflowUseCase(UseCase[Product, RestockWorkflowResponse]):
279
+ def __init__(self, app: ApplicationInvoker, job_service: JobService) -> None:
280
+ self._app = app
281
+ self._jobs = job_service
282
+
283
+ async def execute(self, product_id: str, cmd: DispatchRestockEmailCommand = Input()) -> RestockWorkflowResponse:
284
+ summary = await self._app.invoke(BuildProductSummaryUseCase, params={"product_id": int(product_id)})
285
+ handle = self._jobs.dispatch(SendRestockEmailJob, params={"product_id": int(product_id)}, payload={...})
286
+ return RestockWorkflowResponse(summary=summary.summary, restock_job_id=handle.job_id, queue=handle.queue)
287
+ ```
288
+
289
+ ### 8. Declare REST interfaces
290
+
291
+ ```python
292
+ from loom.rest.autocrud import build_auto_routes
293
+ from loom.rest.model import PaginationMode, RestInterface, RestRoute
294
+
295
+ class ProductRestInterface(RestInterface[Product]):
296
+ prefix = "/products"
297
+ tags = ("Products",)
298
+ pagination_mode = PaginationMode.CURSOR
299
+ routes = (
300
+ RestRoute(use_case=ListLowStockProductsUseCase, method="GET", path="/low-stock",
301
+ summary="List low stock products"),
302
+ RestRoute(use_case=DispatchRestockEmailUseCase, method="POST",
303
+ path="/{product_id}/jobs/restock-email", status_code=202,
304
+ summary="Dispatch restock email"),
305
+ RestRoute(use_case=RestockWorkflowUseCase, method="POST",
306
+ path="/{product_id}/workflows/restock", status_code=202,
307
+ summary="Run restock workflow"),
308
+ *build_auto_routes(Product, ()), # adds GET, POST, PATCH, DELETE automatically
309
+ )
310
+ ```
311
+
312
+ Nested resource interfaces work the same way — routes mirror the URL hierarchy:
313
+
314
+ ```python
315
+ class AddressRestInterface(RestInterface[Address]):
316
+ prefix = "/users"
317
+ tags = ("UserAddresses",)
318
+ routes = (
319
+ RestRoute(use_case=CreateAddressUseCase, method="POST", path="/{user_id}/addresses/", status_code=201),
320
+ RestRoute(use_case=ListAddressesUseCase, method="GET", path="/{user_id}/addresses/"),
321
+ RestRoute(use_case=GetAddressUseCase, method="GET", path="/{user_id}/addresses/{address_id}"),
322
+ RestRoute(use_case=UpdateAddressUseCase, method="PATCH", path="/{user_id}/addresses/{address_id}"),
323
+ RestRoute(use_case=DeleteAddressUseCase, method="DELETE", path="/{user_id}/addresses/{address_id}"),
324
+ )
325
+ ```
326
+
327
+ ### 9. Bootstrap with YAML
328
+
329
+ The `create_app()` factory wires everything — DB, cache, DI, routes — from a YAML config:
330
+
331
+ ```yaml
332
+ # config/api.yaml
333
+ app:
334
+ name: my_store
335
+ code_path: src
336
+ discovery:
337
+ mode: modules
338
+ modules:
339
+ include:
340
+ - app.user.model
341
+ - app.user.interface
342
+ - app.product.model
343
+ - app.product.interface
344
+ rest:
345
+ backend: fastapi
346
+ title: My Store API
347
+ version: 0.1.0
348
+
349
+ database:
350
+ url: ${oc.env:DATABASE_URL,sqlite+aiosqlite:///store.db}
351
+
352
+ metrics:
353
+ enabled: false
354
+ ```
355
+
356
+ ```python
357
+ # main.py — 3 lines
358
+ from loom.rest.fastapi.auto import create_app
359
+
360
+ app = create_app("config/api.yaml")
361
+ ```
362
+
363
+ For larger projects, use `mode: manifest` and a manifest module:
364
+
365
+ ```python
366
+ # app/manifest.py
367
+ from app.user.model import User
368
+ from app.user.interface import UserRestInterface
369
+
370
+ MODELS = [User, ...]
371
+ INTERFACES = [UserRestInterface, ...]
372
+ ```
373
+
374
+ ```yaml
375
+ discovery:
376
+ mode: manifest
377
+ manifest:
378
+ module: app.manifest
379
+ ```
380
+
381
+ ### 10. Rules + Computes (advanced)
382
+
383
+ For compute-heavy write flows, declare field derivations and run them before rules:
384
+
385
+ ```python
386
+ from loom.core.use_case import Compute, F
387
+
388
+ class PricingPreviewUseCase(UseCase[Record, PricingPreviewResponse]):
389
+ computes = (
390
+ Compute.set(F(PricingCommand).normalized_email).from_command(
391
+ F(PricingCommand).email, via=lambda v: v.strip().lower(),
392
+ ),
393
+ Compute.set(F(PricingCommand).subtotal).from_command(
394
+ F(PricingCommand).unit_price, F(PricingCommand).quantity,
395
+ via=lambda price, qty: price * qty,
396
+ ),
397
+ Compute.set(F(PricingCommand).tax_amount).from_command(
398
+ F(PricingCommand).subtotal, F(PricingCommand).tax_rate,
399
+ via=lambda sub, rate: sub * rate,
400
+ ),
401
+ )
402
+ rules = (
403
+ Rule.check(F(PricingCommand).unit_price, via=lambda v: v <= 0, message="unit_price must be > 0"),
404
+ Rule.check(F(PricingCommand).country, via=lambda v: v not in TAX_RATES, message="Unsupported country"),
405
+ )
406
+
407
+ async def execute(self, record_id: int, cmd: PricingCommand = Input()) -> PricingPreviewResponse:
408
+ ...
409
+ ```
410
+
411
+ Computes run in declaration order — later computes can reference fields set by earlier ones.
412
+
413
+ For deeper references, review the integration examples under
414
+ `tests/integration/fake_repo`.
415
+ For a runnable full-stack sample with all patterns combined, check the companion repository
416
+ [`dummy-loom`](https://github.com/the-reacher-data/dummy-loom).
417
+
418
+ ## Performance
419
+
420
+ `loom-kernel` adds zero measurable overhead at the concurrency levels typical of
421
+ production REST APIs. The benchmark below compares `loompy` (loom-kernel + SQLAlchemy)
422
+ against a hand-written FastAPI application hitting the same PostgreSQL database.
423
+
424
+ **Methodology:** 3 independent runs × 3 concurrency levels (20 / 100 / 300 workers),
425
+ median RPS reported. Dataset: 1 200 records, 3 notes each.
426
+ Infrastructure: each target runs in its own isolated Docker container with a dedicated
427
+ PostgreSQL instance.
428
+
429
+ **loompy vs hand-written FastAPI (median RPS, 3 repeats):**
430
+
431
+ | Scenario | c=20 | c=100 | c=300 |
432
+ | --- | --- | --- | --- |
433
+ | `GET /:id` with joins | ≈ tied | **+8.9 %** | −11.7 % |
434
+ | `LIST` cursor | −2.8 % | **+3.8 %** | −19.4 % |
435
+ | `LIST` offset + `COUNT(*)` | ≈ tied | **+6.2 %** | −25.4 % |
436
+ | `PATCH` (UPDATE RETURNING) | **+2.1 %** | ≈ tied | **+12.7 %** |
437
+ | `GET /ping` (no DB) | ≈ tied | −5.1 % | **+16.5 %** |
438
+
439
+ At the production sweet spot (moderate concurrency, c=100), loom-kernel matches or
440
+ outperforms the baseline in 4 out of 5 scenarios — without a single line of hand-tuned
441
+ SQL. The `GET` and `LIST` advantages come from the compiled single-pass SQL read path.
442
+ The `PATCH` advantage at high concurrency comes from the `UPDATE … RETURNING` pattern
443
+ (one round-trip vs three in a naive implementation).
444
+
445
+ The full benchmark suite is available in [`dummy-loom`](https://github.com/the-reacher-data/dummy-loom).
446
+
447
+ ## Status
448
+
449
+ Project under active development.
450
+
451
+ ---
452
+
453
+ ## Author
454
+
455
+ Designed and built by [Massive Data Scope](mailto:massivedatascope@gmail.com).
456
+
457
+ For questions, feedback, or collaboration: **massivedatascope@gmail.com**