loom-kernel 0.2.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 (338) 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.0 → loom_kernel-0.2.1}/PKG-INFO +4 -6
  4. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/README.md +3 -5
  5. loom_kernel-0.2.1/docs/_static/custom.css +13 -0
  6. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/conf.py +4 -0
  7. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/examples-repo/index.md +171 -6
  8. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/guides/fake-repo-examples.md +18 -1
  9. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/guides/quickstart.md +24 -6
  10. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/guides/use-case-dsl.md +4 -1
  11. loom_kernel-0.2.1/docs/index.rst +68 -0
  12. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/pyproject.toml +2 -2
  13. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/bootstrap.py +3 -2
  14. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/di/container.py +26 -0
  15. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/_utils.py +3 -5
  16. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/logger/config.py +23 -1
  17. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/__init__.py +2 -0
  18. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/base.py +7 -15
  19. loom_kernel-0.2.1/src/loom/core/model/struct.py +14 -0
  20. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/timestamped.py +4 -13
  21. loom_kernel-0.2.1/src/loom/core/repository/__init__.py +39 -0
  22. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/abc/__init__.py +15 -1
  23. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/abc/query.py +2 -2
  24. loom_kernel-0.2.1/src/loom/core/repository/abc/repo_for.py +151 -0
  25. loom_kernel-0.2.1/src/loom/core/repository/registration.py +213 -0
  26. loom_kernel-0.2.1/src/loom/core/repository/registry.py +140 -0
  27. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/__init__.py +4 -10
  28. loom_kernel-0.2.1/src/loom/core/repository/sqlalchemy/registry.py +121 -0
  29. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/repository.py +17 -2
  30. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/response/base.py +2 -2
  31. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/factory.py +1 -25
  32. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/use_case.py +47 -17
  33. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/prometheus/__init__.py +1 -1
  34. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/prometheus/middleware.py +3 -8
  35. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/autocrud.py +43 -3
  36. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/auto.py +21 -5
  37. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/model.py +1 -1
  38. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/repository_harness.py +11 -5
  39. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/rest/test_auto_interface_integration.py +77 -6
  40. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/rest/test_fastapi_app_integration.py +11 -8
  41. loom_kernel-0.2.1/tests/integration/core/use_case/test_custom_repository_integration.py +238 -0
  42. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/repository.py +4 -3
  43. loom_kernel-0.2.1/tests/integration/support/__init__.py +1 -0
  44. loom_kernel-0.2.1/tests/integration/support/logical_repo_fixtures.py +34 -0
  45. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/test_bootstrap_metrics.py +2 -2
  46. loom_kernel-0.2.1/tests/unit/core/model/test_struct.py +12 -0
  47. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_factory.py +49 -16
  48. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_autocrud.py +9 -9
  49. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_fastapi_auto_logger.py +29 -0
  50. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_rest_adapter.py +34 -0
  51. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_rest_model.py +2 -2
  52. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_router_runtime.py +3 -3
  53. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/testing/test_golden.py +12 -4
  54. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/testing/test_http_harness.py +3 -4
  55. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/testing/test_runner.py +11 -3
  56. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/uv.lock +1 -1
  57. loom_kernel-0.2.0/CHANGELOG.md +0 -120
  58. loom_kernel-0.2.0/CHANGELOG_RELEASE.md +0 -125
  59. loom_kernel-0.2.0/docs/_static/custom.css +0 -11
  60. loom_kernel-0.2.0/docs/index.rst +0 -41
  61. loom_kernel-0.2.0/src/loom/core/repository/__init__.py +0 -21
  62. loom_kernel-0.2.0/src/loom/core/repository/abc/repo_for.py +0 -74
  63. loom_kernel-0.2.0/src/loom/core/repository/sqlalchemy/registry.py +0 -125
  64. loom_kernel-0.2.0/tests/integration/core/use_case/test_custom_repository_integration.py +0 -103
  65. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.github/workflows/ci-main.yml +0 -0
  66. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.github/workflows/ci-pr.yml +0 -0
  67. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.github/workflows/docs.yml +0 -0
  68. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.github/workflows/release.yml +0 -0
  69. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.gitignore +0 -0
  70. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.pre-commit-config.yaml +0 -0
  71. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.readthedocs.yaml +0 -0
  72. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/LICENSE +0 -0
  73. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/Makefile +0 -0
  74. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/codecov.yml +0 -0
  75. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/_static/.gitkeep +0 -0
  76. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/_static/logo-transparent.png +0 -0
  77. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/_static/logo.svg +0 -0
  78. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/architecture/adr/README.md +0 -0
  79. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/architecture/clean-architecture.md +0 -0
  80. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/architecture/overview.md +0 -0
  81. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/guides/autocrud.md +0 -0
  82. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/guides/celery.md +0 -0
  83. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/reference/api/core.rst +0 -0
  84. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/reference/api/repository.rst +0 -0
  85. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/reference/api/rest.rst +0 -0
  86. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/reference/api/testing.rst +0 -0
  87. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/reference/index.rst +0 -0
  88. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/requirements.txt +0 -0
  89. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/sonar-project.properties +0 -0
  90. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/__init__.py +0 -0
  91. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/__init__.py +0 -0
  92. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/auto.py +0 -0
  93. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/config.py +0 -0
  94. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/constants.py +0 -0
  95. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/event_loop.py +0 -0
  96. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/runner.py +0 -0
  97. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/service.py +0 -0
  98. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/backend/__init__.py +0 -0
  99. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/backend/core_model.py +0 -0
  100. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/backend/protocol.py +0 -0
  101. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/backend/sqlalchemy.py +0 -0
  102. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/bootstrap/__init__.py +0 -0
  103. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/bootstrap/bootstrap.py +0 -0
  104. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/bootstrap/kernel.py +0 -0
  105. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/__init__.py +0 -0
  106. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/__init__.py +0 -0
  107. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/backend.py +0 -0
  108. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/config.py +0 -0
  109. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/dependency.py +0 -0
  110. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/codec.py +0 -0
  111. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/decorators.py +0 -0
  112. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/dependency.py +0 -0
  113. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/gateway.py +0 -0
  114. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/keys.py +0 -0
  115. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/repository.py +0 -0
  116. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/serializer.py +0 -0
  117. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/command/__init__.py +0 -0
  118. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/command/adapter.py +0 -0
  119. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/command/base.py +0 -0
  120. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/command/field.py +0 -0
  121. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/command/introspection.py +0 -0
  122. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/config/__init__.py +0 -0
  123. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/config/errors.py +0 -0
  124. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/config/keys.py +0 -0
  125. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/config/loader.py +0 -0
  126. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/config/model.py +0 -0
  127. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/contracts/__init__.py +0 -0
  128. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/contracts/manifest.py +0 -0
  129. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/di/__init__.py +0 -0
  130. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/di/scope.py +0 -0
  131. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/__init__.py +0 -0
  132. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/base.py +0 -0
  133. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/interfaces.py +0 -0
  134. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/manifest.py +0 -0
  135. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/modules.py +0 -0
  136. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/__init__.py +0 -0
  137. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/compilable.py +0 -0
  138. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/compiler.py +0 -0
  139. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/events.py +0 -0
  140. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/executor.py +0 -0
  141. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/metrics.py +0 -0
  142. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/plan.py +0 -0
  143. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/errors/__init__.py +0 -0
  144. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/errors/codes.py +0 -0
  145. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/errors/errors.py +0 -0
  146. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/__init__.py +0 -0
  147. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/callback.py +0 -0
  148. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/context.py +0 -0
  149. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/handle.py +0 -0
  150. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/job.py +0 -0
  151. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/service.py +0 -0
  152. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/logger/__init__.py +0 -0
  153. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/logger/abc.py +0 -0
  154. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/logger/registry.py +0 -0
  155. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/logger/structlogger.py +0 -0
  156. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/enums.py +0 -0
  157. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/field.py +0 -0
  158. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/introspection.py +0 -0
  159. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/projection.py +0 -0
  160. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/relation.py +0 -0
  161. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/types.py +0 -0
  162. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/types_postgres.py +0 -0
  163. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/projection/__init__.py +0 -0
  164. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/projection/loaders.py +0 -0
  165. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/projection/runtime.py +0 -0
  166. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/abc/repository.py +0 -0
  167. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/mutation.py +0 -0
  168. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/integrity.py +0 -0
  169. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/loaders.py +0 -0
  170. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/mixins.py +0 -0
  171. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/model.py +0 -0
  172. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/profile_loader.py +0 -0
  173. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/projection.py +0 -0
  174. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/__init__.py +0 -0
  175. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/compiler.py +0 -0
  176. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/cursor.py +0 -0
  177. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/errors.py +0 -0
  178. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/filters.py +0 -0
  179. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/ordering.py +0 -0
  180. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/paths.py +0 -0
  181. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/subquery.py +0 -0
  182. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/session_manager.py +0 -0
  183. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/transactional.py +0 -0
  184. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/uow.py +0 -0
  185. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/response/__init__.py +0 -0
  186. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/tracing/__init__.py +0 -0
  187. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/tracing/context.py +0 -0
  188. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/transport/__init__.py +0 -0
  189. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/transport/adapter.py +0 -0
  190. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/uow/__init__.py +0 -0
  191. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/uow/abc.py +0 -0
  192. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/uow/context.py +0 -0
  193. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/__init__.py +0 -0
  194. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/_predicates.py +0 -0
  195. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/compute.py +0 -0
  196. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/constants.py +0 -0
  197. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/field_ref.py +0 -0
  198. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/invoker.py +0 -0
  199. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/keys.py +0 -0
  200. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/markers.py +0 -0
  201. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/registry.py +0 -0
  202. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/rule.py +0 -0
  203. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/prometheus/adapter.py +0 -0
  204. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/__init__.py +0 -0
  205. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/adapter.py +0 -0
  206. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/compiler.py +0 -0
  207. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/constants.py +0 -0
  208. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/errors.py +0 -0
  209. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/__init__.py +0 -0
  210. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/app.py +0 -0
  211. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/openapi.py +0 -0
  212. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/response.py +0 -0
  213. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/router_runtime.py +0 -0
  214. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/middleware.py +0 -0
  215. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/rest_adapter.py +0 -0
  216. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/__init__.py +0 -0
  217. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/golden.py +0 -0
  218. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/http_harness.py +0 -0
  219. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/in_memory.py +0 -0
  220. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/runner.py +0 -0
  221. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/__init__.py +0 -0
  222. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/conftest.py +0 -0
  223. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/golden/__init__.py +0 -0
  224. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/golden/baselines/.gitkeep +0 -0
  225. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/golden/outputs/.gitkeep +0 -0
  226. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/golden/plans/.gitkeep +0 -0
  227. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/helpers/__init__.py +0 -0
  228. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/__init__.py +0 -0
  229. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/celery_bootstrap/__init__.py +0 -0
  230. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/celery_bootstrap/config/conf.celery.integration.yaml +0 -0
  231. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/celery_bootstrap/test_auto_create_app_integration.py +0 -0
  232. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/celery_bootstrap/test_bootstrap_worker.py +0 -0
  233. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/conftest.py +0 -0
  234. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/__init__.py +0 -0
  235. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/repository/__init__.py +0 -0
  236. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/__init__.py +0 -0
  237. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/conftest.py +0 -0
  238. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/test_cache_integration.py +0 -0
  239. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/test_repository_integration.py +0 -0
  240. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/rest/__init__.py +0 -0
  241. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/use_case/test_use_case_crud_integration.py +0 -0
  242. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/__init__.py +0 -0
  243. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/__init__.py +0 -0
  244. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/conf.interfaces.yaml +0 -0
  245. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/conf.manifest.yaml +0 -0
  246. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/conf.modules.yaml +0 -0
  247. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/conf.yaml +0 -0
  248. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/main.py +0 -0
  249. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/manifest.py +0 -0
  250. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/__init__.py +0 -0
  251. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/category/__init__.py +0 -0
  252. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/category/model.py +0 -0
  253. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/category/schemas.py +0 -0
  254. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/interface.py +0 -0
  255. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/jobs.py +0 -0
  256. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/model.py +0 -0
  257. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/relations.py +0 -0
  258. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/repository_contract.py +0 -0
  259. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/review/__init__.py +0 -0
  260. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/review/model.py +0 -0
  261. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/review/schemas.py +0 -0
  262. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/schemas.py +0 -0
  263. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/use_cases.py +0 -0
  264. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_bootstrap/__init__.py +0 -0
  265. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_bootstrap/test_bootstrap.py +0 -0
  266. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_bootstrap/test_event_loop.py +0 -0
  267. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/__init__.py +0 -0
  268. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_auto.py +0 -0
  269. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_celery_service.py +0 -0
  270. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_config.py +0 -0
  271. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_runner.py +0 -0
  272. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/backend/__init__.py +0 -0
  273. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/backend/test_backend_compiler.py +0 -0
  274. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/__init__.py +0 -0
  275. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/test_bootstrap.py +0 -0
  276. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/test_kernel.py +0 -0
  277. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/cache/test_cached_repository.py +0 -0
  278. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/command/__init__.py +0 -0
  279. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_command_base.py +0 -0
  280. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_command_field.py +0 -0
  281. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_command_patch.py +0 -0
  282. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_introspection.py +0 -0
  283. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/config/__init__.py +0 -0
  284. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/config/test_config.py +0 -0
  285. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/di/__init__.py +0 -0
  286. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/di/test_container.py +0 -0
  287. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/discovery/test_manifest.py +0 -0
  288. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/__init__.py +0 -0
  289. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_compiler.py +0 -0
  290. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_executor.py +0 -0
  291. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_executor_trace.py +0 -0
  292. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_executor_uow.py +0 -0
  293. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_metrics.py +0 -0
  294. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_plan.py +0 -0
  295. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/errors/__init__.py +0 -0
  296. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/errors/test_errors.py +0 -0
  297. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/__init__.py +0 -0
  298. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/conftest.py +0 -0
  299. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_callback.py +0 -0
  300. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_context.py +0 -0
  301. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_handle.py +0 -0
  302. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_inline_service.py +0 -0
  303. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_job.py +0 -0
  304. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/logger/test_registry.py +0 -0
  305. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/model/__init__.py +0 -0
  306. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/model/test_model.py +0 -0
  307. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/model/test_timestamped.py +0 -0
  308. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/projection/test_runtime.py +0 -0
  309. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/abc/conftest.py +0 -0
  310. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/abc/test_query.py +0 -0
  311. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/abc/test_repository_contract.py +0 -0
  312. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/conftest.py +0 -0
  313. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/test_loaders.py +0 -0
  314. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/test_repository.py +0 -0
  315. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/test_transactional.py +0 -0
  316. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/tracing/__init__.py +0 -0
  317. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/tracing/test_context.py +0 -0
  318. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/uow/__init__.py +0 -0
  319. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/uow/test_executor_uow.py +0 -0
  320. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/uow/test_sqlalchemy_uow.py +0 -0
  321. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/uow/test_uow_protocols.py +0 -0
  322. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/__init__.py +0 -0
  323. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_compute.py +0 -0
  324. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_field_ref.py +0 -0
  325. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_invoker.py +0 -0
  326. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_markers.py +0 -0
  327. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_rule.py +0 -0
  328. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_use_case.py +0 -0
  329. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/prometheus/__init__.py +0 -0
  330. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/prometheus/test_adapter.py +0 -0
  331. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/prometheus/test_middleware.py +0 -0
  332. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/__init__.py +0 -0
  333. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_middleware.py +0 -0
  334. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_pydantic_adapter.py +0 -0
  335. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_response.py +0 -0
  336. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_rest_compiler.py +0 -0
  337. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/testing/__init__.py +0 -0
  338. {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/testing/test_in_memory.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
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loom-kernel
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Loom Python project
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -33,11 +33,9 @@ Provides-Extra: sqlalchemy
33
33
  Requires-Dist: sqlalchemy<3.0,>=2.0; extra == 'sqlalchemy'
34
34
  Description-Content-Type: text/markdown
35
35
 
36
- <div style="width:100%;background:#fff;padding:4px 0;box-sizing:border-box;">
37
- <p align="center" style="margin:0;">
38
- <img src="docs/_static/logo-transparent.png" alt="loom-kernel" style="display:block;width:100%;max-width:none;max-height:220px;height:auto;object-fit:contain;margin:0 auto;" />
39
- </p>
40
- </div>
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>
41
39
 
42
40
  # loom-kernel
43
41
 
@@ -1,8 +1,6 @@
1
- <div style="width:100%;background:#fff;padding:4px 0;box-sizing:border-box;">
2
- <p align="center" style="margin:0;">
3
- <img src="docs/_static/logo-transparent.png" alt="loom-kernel" style="display:block;width:100%;max-width:none;max-height:220px;height:auto;object-fit:contain;margin:0 auto;" />
4
- </p>
5
- </div>
1
+ <p align="center">
2
+ <img src="docs/_static/logo-transparent.png" alt="loom-kernel" width="160" style="background:#ffffff;border-radius:6px;padding:8px 20px;" />
3
+ </p>
6
4
 
7
5
  # loom-kernel
8
6
 
@@ -0,0 +1,13 @@
1
+ .loom-hero {
2
+ padding: 1rem 0 1.5rem;
3
+ text-align: center;
4
+ }
5
+
6
+ .loom-hero img {
7
+ display: inline-block;
8
+ width: 160px;
9
+ height: auto;
10
+ background: #ffffff;
11
+ border-radius: 6px;
12
+ padding: 8px 20px;
13
+ }
@@ -80,12 +80,16 @@ intersphinx_mapping = {
80
80
  # Optional dependencies are mocked to keep docs builds lightweight and stable.
81
81
  autodoc_mock_imports = [
82
82
  "aiocache",
83
+ "celery",
83
84
  "fastapi",
85
+ "kombu",
84
86
  "omegaconf",
85
87
  "prometheus_client",
86
88
  "pydantic",
87
89
  "pyspark",
90
+ "redis",
88
91
  "sqlalchemy",
92
+ "starlette",
89
93
  "uvicorn",
90
94
  ]
91
95
 
@@ -171,11 +171,9 @@ from typing import Protocol
171
171
 
172
172
  import msgspec
173
173
 
174
+ from loom.core.repository import repository_for
174
175
  from loom.core.repository.abc import RepoFor
175
- from loom.core.repository.sqlalchemy import (
176
- RepositorySQLAlchemy,
177
- repository_for,
178
- )
176
+ from loom.core.repository.sqlalchemy import RepositorySQLAlchemy
179
177
 
180
178
 
181
179
  class ProductRepo(RepoFor[Product], Protocol):
@@ -183,8 +181,8 @@ class ProductRepo(RepoFor[Product], Protocol):
183
181
  ...
184
182
 
185
183
 
186
- @repository_for(Product, contract=ProductRepo)
187
- class ProductRepository(RepositorySQLAlchemy[Product, int]):
184
+ @repository_for(Product)
185
+ class ProductRepository(RepositorySQLAlchemy[Product, int], ProductRepo):
188
186
  async def create(self, data: msgspec.Struct) -> Product:
189
187
  payload = msgspec.to_builtins(data)
190
188
  payload["name"] = str(payload["name"]).strip()
@@ -197,6 +195,173 @@ class CreateProductUseCase(UseCase[Product, Product]):
197
195
  return await self.main_repo.create(cmd)
198
196
  ```
199
197
 
198
+ ### Logical type with automatic `main_repo`
199
+
200
+ `main_repo` can also be bound to a non-persistible logical type. Use
201
+ `Response` when the returned object is part of your public API contract and you
202
+ want the usual REST `camelCase` output.
203
+
204
+ ```python
205
+ from typing import Protocol
206
+
207
+ from loom.core.repository import repository_for
208
+ from loom.core.response import Response
209
+ from loom.core.use_case.use_case import UseCase
210
+
211
+
212
+ class TaskView(Response):
213
+ task_id: str
214
+ state: str
215
+
216
+
217
+ class TaskViewRepo(Protocol):
218
+ async def get_by_id(self, obj_id: str, profile: str = "default") -> TaskView | None:
219
+ ...
220
+
221
+
222
+ @repository_for(TaskView)
223
+ class TaskViewRepository(TaskViewRepo):
224
+ def __init__(self) -> None:
225
+ self._items = {"t-1": TaskView(task_id="t-1", state="done")}
226
+
227
+ async def get_by_id(self, obj_id: str, profile: str = "default") -> TaskView | None:
228
+ return self._items.get(obj_id)
229
+
230
+
231
+ class GetTaskViewUseCase(UseCase[TaskView, TaskView | None, TaskViewRepo]):
232
+ async def execute(self, task_id: str) -> TaskView | None:
233
+ return await self.main_repo.get_by_id(task_id)
234
+ ```
235
+
236
+ Use `LoomStruct` instead of `Response` when you want a logical type without
237
+ REST-specific `camelCase` serialization.
238
+
239
+ ### Default repository builder and explicit builder override
240
+
241
+ The framework now resolves the default repository through the
242
+ `DefaultRepositoryBuilder` dependency. In the current SQLAlchemy stack, the
243
+ bootstrap registers `SQLAlchemyDefaultRepositoryBuilder` as that default, and
244
+ that builder keeps `SessionManager` inside the SQLAlchemy adapter layer.
245
+
246
+ This means:
247
+
248
+ - the core registry no longer depends on SQLAlchemy constructor details
249
+ - SQLAlchemy keeps `SessionManager` as an infrastructure concern
250
+ - applications can replace the default builder without changing `UseCase`
251
+
252
+ Conceptually, the current SQLAlchemy fallback is:
253
+
254
+ ```python
255
+ from dataclasses import dataclass
256
+
257
+ from loom.core.repository import DefaultRepositoryBuilder, RepositoryBuildContext
258
+ from loom.core.repository.sqlalchemy import RepositorySQLAlchemy
259
+ from loom.core.repository.sqlalchemy.session_manager import SessionManager
260
+
261
+
262
+ @dataclass(frozen=True)
263
+ class SQLAlchemyDefaultRepositoryBuilder:
264
+ session_manager: SessionManager
265
+
266
+ def __call__(self, context: RepositoryBuildContext) -> object:
267
+ return RepositorySQLAlchemy(
268
+ session_manager=self.session_manager,
269
+ model=context.model,
270
+ )
271
+ ```
272
+
273
+ An application that wants a different project-wide base repository can
274
+ register another `DefaultRepositoryBuilder` implementation in the container.
275
+
276
+ That is the extension point for replacing the default backend globally. For
277
+ example, an application can swap the SQLAlchemy fallback for its own base
278
+ repository while keeping `UseCase[Model, Result]` unchanged:
279
+
280
+ ```python
281
+ from dataclasses import dataclass
282
+
283
+ from loom.core.repository import DefaultRepositoryBuilder, RepositoryBuildContext
284
+
285
+
286
+ @dataclass(frozen=True)
287
+ class MyBaseRepositoryBuilder:
288
+ settings: AppSettings
289
+
290
+ def __call__(self, context: RepositoryBuildContext) -> object:
291
+ return MyBaseRepository(model=context.model, settings=self.settings)
292
+
293
+
294
+ container.register_instance(
295
+ DefaultRepositoryBuilder,
296
+ MyBaseRepositoryBuilder(settings=settings),
297
+ )
298
+ ```
299
+
300
+ After that registration:
301
+
302
+ - every model without an explicit `repository_for(...)` uses `MyBaseRepository`
303
+ - `UseCase[Model, Result]` keeps working through `self.main_repo`
304
+ - model-specific overrides still win when needed
305
+
306
+ For per-model overrides that need extra dependencies, use `builder=` on
307
+ `repository_for(...)`:
308
+
309
+ ```python
310
+ from dataclasses import dataclass
311
+
312
+ from loom.core.di.scope import Scope
313
+ from loom.core.repository import RepositoryBuildContext, repository_for
314
+ from loom.core.response import Response
315
+
316
+
317
+ class TaskSnapshot(Response):
318
+ task_id: str
319
+ state: str
320
+
321
+
322
+ class TaskSnapshotRepo(Protocol):
323
+ async def get_by_id(self, obj_id: str, profile: str = "default") -> TaskSnapshot | None:
324
+ ...
325
+
326
+
327
+ @dataclass(frozen=True)
328
+ class TaskRepoSettings:
329
+ state: str
330
+
331
+
332
+ def build_task_snapshot_repository(context: RepositoryBuildContext) -> object:
333
+ settings = context.container.resolve(TaskRepoSettings)
334
+ return TaskSnapshotRepository(settings=settings)
335
+
336
+
337
+ @repository_for(TaskSnapshot, builder=build_task_snapshot_repository)
338
+ class TaskSnapshotRepository(TaskSnapshotRepo):
339
+ def __init__(self, settings: TaskRepoSettings) -> None:
340
+ self._settings = settings
341
+
342
+ async def get_by_id(self, obj_id: str, profile: str = "default") -> TaskSnapshot | None:
343
+ return TaskSnapshot(task_id=obj_id, state=self._settings.state)
344
+ ```
345
+
346
+ The application bootstrap only has to register the extra dependency:
347
+
348
+ ```python
349
+ container.register(
350
+ TaskRepoSettings,
351
+ lambda: TaskRepoSettings(state="from-builder"),
352
+ scope=Scope.APPLICATION,
353
+ )
354
+ ```
355
+
356
+ Resolution order is:
357
+
358
+ 1. explicit `repository_for(..., builder=...)`
359
+ 2. explicit `repository_for(...)` by class
360
+ 3. `DefaultRepositoryBuilder`
361
+
362
+ Use the first two for per-model customization; use `DefaultRepositoryBuilder`
363
+ to replace the project-wide backend.
364
+
200
365
  ---
201
366
 
202
367
  ## 4. Background jobs
@@ -23,6 +23,23 @@ Useful files:
23
23
  Custom repository pattern:
24
24
 
25
25
  - Declare the custom contract as `RepoFor[Model]` plus only the extra methods.
26
- - Register the SQLAlchemy implementation with `@repository_for(Model, contract=...)`.
26
+ - Import `repository_for` from `loom.core.repository`, not from the SQLAlchemy package.
27
+ - Register the SQLAlchemy implementation with `@repository_for(Model)` and make the
28
+ repository class inherit the capability `Protocol` directly.
27
29
  - Keep CRUD-only use cases on `self.main_repo`; they automatically receive the custom implementation.
28
30
  - Reserve constructor-injected repository contracts for advanced cases where a use case or job needs a secondary repository dependency; the primary public example should stay on `main_repo`.
31
+ - `repository_for(...)` also works for non-persistible logical types:
32
+ - use `LoomStruct` for neutral internal structs
33
+ - use `Response` for API-facing structs that should serialize in `camelCase`
34
+ - The SQLAlchemy bootstrap module now registers `DefaultRepositoryBuilder` with
35
+ `SQLAlchemyDefaultRepositoryBuilder` as the current fallback for `BaseModel`.
36
+ - That fallback keeps `SessionManager` inside the SQLAlchemy adapter, so SQLAlchemy
37
+ construction details do not leak into the core repository contract.
38
+ - To replace the project-wide backend, register another `DefaultRepositoryBuilder`
39
+ implementation in the container; `UseCase[Model, Result]` will continue to
40
+ resolve `self.main_repo` through that new base repository.
41
+ - Explicit `repository_for(...)` bindings still take precedence for both
42
+ persistible and non-persistible types.
43
+ - When a custom repository needs more constructor dependencies than the default
44
+ class path supports, use `repository_for(..., builder=...)` and resolve those
45
+ dependencies from `RepositoryBuildContext.container`.
@@ -100,11 +100,14 @@ def _name_must_not_be_blank(full_name: str) -> str | None:
100
100
  def _email_must_be_valid(email: str) -> str | None:
101
101
  return None if _EMAIL_RE.fullmatch(email) else "email must be valid"
102
102
 
103
+ def _email_is_taken(cmd: CreateUser, fields_set: frozenset[str], email_exists: bool) -> bool:
104
+ return email_exists
105
+
103
106
  class CreateUserUseCase(UseCase[User, User]):
104
107
  rules = [
105
108
  Rule.check(F(CreateUser).full_name, via=_name_must_not_be_blank),
106
109
  Rule.check(F(CreateUser).email, via=_email_must_be_valid),
107
- Rule.forbid(lambda _, __, exists: exists, message="email already exists").from_params("email_exists"),
110
+ Rule.forbid(_email_is_taken, message="email already exists").from_params("email_exists"),
108
111
  ]
109
112
 
110
113
  async def execute(
@@ -369,23 +372,38 @@ For compute-heavy write flows, declare field derivations and run them before rul
369
372
  ```python
370
373
  from loom.core.use_case import Compute, F
371
374
 
375
+ def _normalize_email(email: str) -> str:
376
+ return email.strip().lower()
377
+
378
+ def _compute_subtotal(unit_price: float, quantity: int) -> float:
379
+ return unit_price * quantity
380
+
381
+ def _compute_tax(subtotal: float, tax_rate: float) -> float:
382
+ return subtotal * tax_rate
383
+
384
+ def _unit_price_invalid(unit_price: float) -> bool:
385
+ return unit_price <= 0
386
+
387
+ def _country_unsupported(country: str) -> bool:
388
+ return country not in TAX_RATES
389
+
372
390
  class PricingPreviewUseCase(UseCase[Record, PricingPreviewResponse]):
373
391
  computes = (
374
392
  Compute.set(F(PricingCommand).normalized_email).from_command(
375
- F(PricingCommand).email, via=lambda v: v.strip().lower(),
393
+ F(PricingCommand).email, via=_normalize_email,
376
394
  ),
377
395
  Compute.set(F(PricingCommand).subtotal).from_command(
378
396
  F(PricingCommand).unit_price, F(PricingCommand).quantity,
379
- via=lambda price, qty: price * qty,
397
+ via=_compute_subtotal,
380
398
  ),
381
399
  Compute.set(F(PricingCommand).tax_amount).from_command(
382
400
  F(PricingCommand).subtotal, F(PricingCommand).tax_rate,
383
- via=lambda sub, rate: sub * rate,
401
+ via=_compute_tax,
384
402
  ),
385
403
  )
386
404
  rules = (
387
- Rule.check(F(PricingCommand).unit_price, via=lambda v: v <= 0, message="unit_price must be > 0"),
388
- Rule.check(F(PricingCommand).country, via=lambda v: v not in TAX_RATES, message="Unsupported country"),
405
+ Rule.check(F(PricingCommand).unit_price, via=_unit_price_invalid, message="unit_price must be > 0"),
406
+ Rule.check(F(PricingCommand).country, via=_country_unsupported, message="Unsupported country"),
389
407
  )
390
408
 
391
409
  async def execute(self, record_id: int, cmd: PricingCommand = Input()) -> PricingPreviewResponse:
@@ -186,11 +186,14 @@ CREATE_NORMALIZE_PRICE = Compute.set(F(CreateProduct).price).from_command(
186
186
  Computes can read multiple fields:
187
187
 
188
188
  ```python
189
+ def _compute_subtotal(unit_price: float, quantity: int) -> float:
190
+ return unit_price * quantity
191
+
189
192
  # Derives subtotal from unit_price × quantity
190
193
  CREATE_SUBTOTAL = Compute.set(F(PricingCommand).subtotal).from_command(
191
194
  F(PricingCommand).unit_price,
192
195
  F(PricingCommand).quantity,
193
- via=lambda price, qty: price * qty,
196
+ via=_compute_subtotal,
194
197
  )
195
198
  ```
196
199
 
@@ -0,0 +1,68 @@
1
+ loom-kernel documentation
2
+ =========================
3
+
4
+ .. raw:: html
5
+
6
+ <div class="loom-hero">
7
+ <img src="_static/logo-transparent.png" alt="loom-kernel logo" />
8
+ </div>
9
+ <p align="center" style="margin:0.5rem 0 1.5rem;">
10
+ <a href="https://github.com/the-reacher-data/loom-py/actions/workflows/ci-main.yml">
11
+ <img src="https://img.shields.io/github/actions/workflow/status/the-reacher-data/loom-py/ci-main.yml?branch=master&label=ci" alt="CI" />
12
+ </a>
13
+ &nbsp;
14
+ <a href="https://github.com/the-reacher-data/loom-py/actions/workflows/docs.yml">
15
+ <img src="https://img.shields.io/github/actions/workflow/status/the-reacher-data/loom-py/docs.yml?branch=master&label=docs" alt="Docs" />
16
+ </a>
17
+ &nbsp;
18
+ <a href="https://sonarcloud.io/summary/new_code?id=the-reacher-data_loom-py">
19
+ <img src="https://sonarcloud.io/api/project_badges/measure?project=the-reacher-data_loom-py&metric=alert_status" alt="Quality Gate" />
20
+ </a>
21
+ &nbsp;
22
+ <a href="https://sonarcloud.io/summary/new_code?id=the-reacher-data_loom-py">
23
+ <img src="https://sonarcloud.io/api/project_badges/measure?project=the-reacher-data_loom-py&metric=security_rating" alt="Security" />
24
+ </a>
25
+ &nbsp;
26
+ <a href="https://app.codecov.io/gh/the-reacher-data/loom-py/tree/master">
27
+ <img src="https://codecov.io/gh/the-reacher-data/loom-py/branch/master/graph/badge.svg" alt="Coverage" />
28
+ </a>
29
+ &nbsp;
30
+ <a href="https://pypi.org/project/loom-kernel/">
31
+ <img src="https://img.shields.io/pypi/v/loom-kernel" alt="PyPI" />
32
+ </a>
33
+ &nbsp;
34
+ <img src="https://img.shields.io/badge/python-3.11%2B-blue" alt="Python" />
35
+ </p>
36
+
37
+ Framework-agnostic toolkit to build backend applications with typed use cases,
38
+ repository contracts, and transport adapters.
39
+
40
+ Official hosted documentation:
41
+ `loom-py.readthedocs.io <https://loom-py.readthedocs.io/en/latest/>`_.
42
+ Companion demo application:
43
+ `dummy-loom <https://github.com/the-reacher-data/dummy-loom>`_.
44
+
45
+ .. toctree::
46
+ :maxdepth: 2
47
+ :caption: Guides
48
+
49
+ guides/autocrud
50
+ guides/quickstart
51
+ examples-repo/index
52
+ guides/use-case-dsl
53
+ guides/celery
54
+ guides/fake-repo-examples
55
+
56
+ .. toctree::
57
+ :maxdepth: 2
58
+ :caption: Architecture
59
+
60
+ architecture/overview
61
+ architecture/clean-architecture
62
+ architecture/adr/README
63
+
64
+ .. toctree::
65
+ :maxdepth: 2
66
+ :caption: Reference
67
+
68
+ reference/index
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "loom-kernel"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Loom Python project"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -144,7 +144,7 @@ log_cli_format = "%(asctime)s %(levelname)s %(name)s %(message)s"
144
144
 
145
145
  [tool.commitizen]
146
146
  name = "cz_conventional_commits"
147
- version = "0.2.0"
147
+ version = "0.2.1"
148
148
  tag_format = "v$version"
149
149
  version_files = [
150
150
  "pyproject.toml:project.version",
@@ -77,7 +77,7 @@ from loom.core.engine.compilable import Compilable
77
77
  from loom.core.job.job import Job
78
78
  from loom.core.logger import LoggerConfig, configure_logging_from_values
79
79
  from loom.core.model import BaseModel
80
- from loom.core.repository.sqlalchemy import build_repository_registration_module
80
+ from loom.core.repository.sqlalchemy import build_sqlalchemy_repository_registration_module
81
81
  from loom.core.repository.sqlalchemy.session_manager import SessionManager
82
82
  from loom.core.repository.sqlalchemy.uow import SQLAlchemyUnitOfWorkFactory
83
83
  from loom.core.uow.abc import UnitOfWorkFactory
@@ -547,7 +547,7 @@ def _register_repositories(
547
547
  models: Sequence[type[BaseModel]],
548
548
  ) -> Callable[[LoomContainer], None]:
549
549
  """Build a container module that registers SQLAlchemy repositories."""
550
- return build_repository_registration_module(session_manager, models)
550
+ return build_sqlalchemy_repository_registration_module(session_manager, models)
551
551
 
552
552
 
553
553
  def _compile_models(models: Sequence[type[BaseModel]]) -> tuple[type[BaseModel], ...]:
@@ -577,6 +577,7 @@ def _configure_logging(raw: DictConfig) -> None:
577
577
  renderer=logger_cfg.renderer,
578
578
  colors=logger_cfg.colors,
579
579
  level=logger_cfg.level,
580
+ named_levels=logger_cfg.named_levels,
580
581
  handlers=logger_cfg.handlers,
581
582
  )
582
583
 
@@ -102,6 +102,32 @@ class LoomContainer:
102
102
  factory = provider
103
103
  self._bindings[interface] = _Binding(provider=factory, scope=scope)
104
104
 
105
+ def register_instance(self, interface: BindingKey, instance: Any) -> None:
106
+ """Register an already-constructed singleton instance.
107
+
108
+ Equivalent to ``register(interface, lambda: instance,
109
+ scope=Scope.APPLICATION)`` but skips the factory indirection — the
110
+ instance is stored directly and returned on every :meth:`resolve` call.
111
+
112
+ Use this when the caller owns the lifecycle of the object (e.g. a
113
+ ``SessionManager`` created by the bootstrap) and the container should
114
+ treat it as an application-scope singleton without constructing it.
115
+
116
+ Args:
117
+ interface: A hashable DI key used as the resolution key.
118
+ instance: The singleton instance to bind.
119
+
120
+ Example::
121
+
122
+ container.register_instance(SessionManager, session_manager)
123
+ assert container.resolve(SessionManager) is session_manager
124
+ """
125
+ self._bindings[interface] = _Binding(
126
+ provider=lambda: instance,
127
+ scope=Scope.APPLICATION,
128
+ _instance=instance,
129
+ )
130
+
105
131
  def resolve(self, interface: BindingKey) -> Any:
106
132
  """Return an instance bound to ``interface``.
107
133