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.
- loom_kernel-0.2.1/CHANGELOG.md +105 -0
- loom_kernel-0.2.1/CHANGELOG_RELEASE.md +39 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/PKG-INFO +4 -6
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/README.md +3 -5
- loom_kernel-0.2.1/docs/_static/custom.css +13 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/conf.py +4 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/examples-repo/index.md +171 -6
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/guides/fake-repo-examples.md +18 -1
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/guides/quickstart.md +24 -6
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/guides/use-case-dsl.md +4 -1
- loom_kernel-0.2.1/docs/index.rst +68 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/pyproject.toml +2 -2
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/bootstrap.py +3 -2
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/di/container.py +26 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/_utils.py +3 -5
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/logger/config.py +23 -1
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/__init__.py +2 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/base.py +7 -15
- loom_kernel-0.2.1/src/loom/core/model/struct.py +14 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/timestamped.py +4 -13
- loom_kernel-0.2.1/src/loom/core/repository/__init__.py +39 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/abc/__init__.py +15 -1
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/abc/query.py +2 -2
- loom_kernel-0.2.1/src/loom/core/repository/abc/repo_for.py +151 -0
- loom_kernel-0.2.1/src/loom/core/repository/registration.py +213 -0
- loom_kernel-0.2.1/src/loom/core/repository/registry.py +140 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/__init__.py +4 -10
- loom_kernel-0.2.1/src/loom/core/repository/sqlalchemy/registry.py +121 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/repository.py +17 -2
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/response/base.py +2 -2
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/factory.py +1 -25
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/use_case.py +47 -17
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/prometheus/__init__.py +1 -1
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/prometheus/middleware.py +3 -8
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/autocrud.py +43 -3
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/auto.py +21 -5
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/model.py +1 -1
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/repository_harness.py +11 -5
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/rest/test_auto_interface_integration.py +77 -6
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/rest/test_fastapi_app_integration.py +11 -8
- loom_kernel-0.2.1/tests/integration/core/use_case/test_custom_repository_integration.py +238 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/repository.py +4 -3
- loom_kernel-0.2.1/tests/integration/support/__init__.py +1 -0
- loom_kernel-0.2.1/tests/integration/support/logical_repo_fixtures.py +34 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/test_bootstrap_metrics.py +2 -2
- loom_kernel-0.2.1/tests/unit/core/model/test_struct.py +12 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_factory.py +49 -16
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_autocrud.py +9 -9
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_fastapi_auto_logger.py +29 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_rest_adapter.py +34 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_rest_model.py +2 -2
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_router_runtime.py +3 -3
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/testing/test_golden.py +12 -4
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/testing/test_http_harness.py +3 -4
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/testing/test_runner.py +11 -3
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/uv.lock +1 -1
- loom_kernel-0.2.0/CHANGELOG.md +0 -120
- loom_kernel-0.2.0/CHANGELOG_RELEASE.md +0 -125
- loom_kernel-0.2.0/docs/_static/custom.css +0 -11
- loom_kernel-0.2.0/docs/index.rst +0 -41
- loom_kernel-0.2.0/src/loom/core/repository/__init__.py +0 -21
- loom_kernel-0.2.0/src/loom/core/repository/abc/repo_for.py +0 -74
- loom_kernel-0.2.0/src/loom/core/repository/sqlalchemy/registry.py +0 -125
- loom_kernel-0.2.0/tests/integration/core/use_case/test_custom_repository_integration.py +0 -103
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.github/workflows/ci-main.yml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.github/workflows/ci-pr.yml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.github/workflows/docs.yml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.github/workflows/release.yml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.gitignore +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.pre-commit-config.yaml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/.readthedocs.yaml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/LICENSE +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/Makefile +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/codecov.yml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/_static/.gitkeep +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/_static/logo-transparent.png +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/_static/logo.svg +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/architecture/adr/README.md +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/architecture/clean-architecture.md +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/architecture/overview.md +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/guides/autocrud.md +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/guides/celery.md +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/reference/api/core.rst +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/reference/api/repository.rst +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/reference/api/rest.rst +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/reference/api/testing.rst +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/reference/index.rst +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/docs/requirements.txt +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/sonar-project.properties +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/auto.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/config.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/constants.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/event_loop.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/runner.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/celery/service.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/backend/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/backend/core_model.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/backend/protocol.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/backend/sqlalchemy.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/bootstrap/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/bootstrap/bootstrap.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/bootstrap/kernel.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/backend.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/config.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/abc/dependency.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/codec.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/decorators.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/dependency.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/gateway.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/keys.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/repository.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/cache/serializer.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/command/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/command/adapter.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/command/base.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/command/field.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/command/introspection.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/config/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/config/errors.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/config/keys.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/config/loader.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/config/model.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/contracts/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/contracts/manifest.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/di/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/di/scope.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/base.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/interfaces.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/manifest.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/discovery/modules.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/compilable.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/compiler.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/events.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/executor.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/metrics.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/engine/plan.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/errors/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/errors/codes.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/errors/errors.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/callback.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/context.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/handle.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/job.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/job/service.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/logger/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/logger/abc.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/logger/registry.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/logger/structlogger.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/enums.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/field.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/introspection.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/projection.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/relation.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/types.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/model/types_postgres.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/projection/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/projection/loaders.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/projection/runtime.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/abc/repository.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/mutation.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/integrity.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/loaders.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/mixins.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/model.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/profile_loader.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/projection.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/compiler.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/cursor.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/errors.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/filters.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/ordering.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/paths.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/query_compiler/subquery.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/session_manager.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/transactional.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/repository/sqlalchemy/uow.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/response/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/tracing/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/tracing/context.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/transport/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/transport/adapter.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/uow/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/uow/abc.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/uow/context.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/_predicates.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/compute.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/constants.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/field_ref.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/invoker.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/keys.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/markers.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/registry.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/core/use_case/rule.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/prometheus/adapter.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/adapter.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/compiler.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/constants.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/errors.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/app.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/openapi.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/response.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/fastapi/router_runtime.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/middleware.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/rest/rest_adapter.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/golden.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/http_harness.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/in_memory.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/src/loom/testing/runner.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/conftest.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/golden/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/golden/baselines/.gitkeep +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/golden/outputs/.gitkeep +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/golden/plans/.gitkeep +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/helpers/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/celery_bootstrap/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/celery_bootstrap/config/conf.celery.integration.yaml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/celery_bootstrap/test_auto_create_app_integration.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/celery_bootstrap/test_bootstrap_worker.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/conftest.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/repository/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/conftest.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/test_cache_integration.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/repository/sqlalchemy/test_repository_integration.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/rest/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/core/use_case/test_use_case_crud_integration.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/conf.interfaces.yaml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/conf.manifest.yaml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/conf.modules.yaml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/config/conf.yaml +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/main.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/manifest.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/category/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/category/model.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/category/schemas.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/interface.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/jobs.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/model.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/relations.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/repository_contract.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/review/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/review/model.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/review/schemas.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/schemas.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/integration/fake_repo/product/use_cases.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_bootstrap/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_bootstrap/test_bootstrap.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_bootstrap/test_event_loop.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_auto.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_celery_service.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_config.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/celery_jobs/test_runner.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/backend/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/backend/test_backend_compiler.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/test_bootstrap.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/bootstrap/test_kernel.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/cache/test_cached_repository.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/command/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_command_base.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_command_field.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_command_patch.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/command/test_introspection.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/config/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/config/test_config.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/di/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/di/test_container.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/discovery/test_manifest.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_compiler.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_executor.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_executor_trace.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_executor_uow.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_metrics.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/engine/test_plan.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/errors/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/errors/test_errors.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/conftest.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_callback.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_context.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_handle.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_inline_service.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/job/test_job.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/logger/test_registry.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/model/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/model/test_model.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/model/test_timestamped.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/projection/test_runtime.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/abc/conftest.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/abc/test_query.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/abc/test_repository_contract.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/conftest.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/test_loaders.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/test_repository.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/repository/sqlalchemy/test_transactional.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/tracing/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/tracing/test_context.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/uow/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/uow/test_executor_uow.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/uow/test_sqlalchemy_uow.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/uow/test_uow_protocols.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_compute.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_field_ref.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_invoker.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_markers.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_rule.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/core/use_case/test_use_case.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/prometheus/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/prometheus/test_adapter.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/prometheus/test_middleware.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/__init__.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_middleware.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_pydantic_adapter.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_response.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/rest/test_rest_compiler.py +0 -0
- {loom_kernel-0.2.0 → loom_kernel-0.2.1}/tests/unit/testing/__init__.py +0 -0
- {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.
|
|
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
|
-
<
|
|
37
|
-
<
|
|
38
|
-
|
|
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
|
-
<
|
|
2
|
-
<
|
|
3
|
-
|
|
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
|
|
|
@@ -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
|
|
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
|
-
-
|
|
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(
|
|
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=
|
|
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=
|
|
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=
|
|
401
|
+
via=_compute_tax,
|
|
384
402
|
),
|
|
385
403
|
)
|
|
386
404
|
rules = (
|
|
387
|
-
Rule.check(F(PricingCommand).unit_price, via=
|
|
388
|
-
Rule.check(F(PricingCommand).country, via=
|
|
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=
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|