hdmi 0.2.0__tar.gz → 0.2.2__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 (65) hide show
  1. hdmi-0.2.2/.github/workflows/cicd.yml +256 -0
  2. hdmi-0.2.2/.gitignore +43 -0
  3. hdmi-0.2.2/.readthedocs.yaml +23 -0
  4. hdmi-0.2.2/CHANGELOG.md +43 -0
  5. hdmi-0.2.2/CLAUDE.md +198 -0
  6. hdmi-0.2.2/Makefile +70 -0
  7. hdmi-0.2.2/PKG-INFO +119 -0
  8. hdmi-0.2.2/README.md +72 -0
  9. hdmi-0.2.2/RELEASE.md +168 -0
  10. hdmi-0.2.2/demo.py +136 -0
  11. hdmi-0.2.2/docs/README.md +111 -0
  12. hdmi-0.2.2/docs/conf.py +66 -0
  13. hdmi-0.2.2/docs/explanation/architecture.rst +493 -0
  14. hdmi-0.2.2/docs/explanation/index.rst +57 -0
  15. hdmi-0.2.2/docs/explanation/why-late-binding.rst +307 -0
  16. hdmi-0.2.2/docs/how-to/index.rst +12 -0
  17. hdmi-0.2.2/docs/how-to/use-service-definitions.rst +370 -0
  18. hdmi-0.2.2/docs/index.rst +79 -0
  19. hdmi-0.2.2/docs/reference/api.rst +274 -0
  20. hdmi-0.2.2/docs/reference/index.rst +12 -0
  21. hdmi-0.2.2/docs/tutorials/index.rst +13 -0
  22. {hdmi-0.2.0 → hdmi-0.2.2}/pyproject.toml +17 -3
  23. hdmi-0.2.2/sandbox/basics.py +52 -0
  24. hdmi-0.2.2/sandbox/complex.py +46 -0
  25. hdmi-0.2.2/sandbox/diamond.py +98 -0
  26. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/__init__.py +3 -1
  27. hdmi-0.2.2/tests/__init__.py +1 -0
  28. hdmi-0.2.2/tests/builders/__init__.py +1 -0
  29. hdmi-0.2.2/tests/builders/test_default.py +381 -0
  30. hdmi-0.2.2/tests/conftest.py +9 -0
  31. hdmi-0.2.2/tests/containers/__init__.py +1 -0
  32. hdmi-0.2.2/tests/containers/test_default.py +275 -0
  33. hdmi-0.2.2/tests/containers/test_scoped.py +166 -0
  34. hdmi-0.2.2/tests/integration/__init__.py +0 -0
  35. hdmi-0.2.2/tests/integration/test_async_container.py +76 -0
  36. hdmi-0.2.2/tests/integration/test_boolean_scope_api.py +193 -0
  37. hdmi-0.2.2/tests/integration/test_concurrent_resolution.py +109 -0
  38. hdmi-0.2.2/tests/integration/test_integration.py +156 -0
  39. hdmi-0.2.2/tests/integration/test_lifecycle.py +181 -0
  40. hdmi-0.2.2/tests/integration/test_scope_transient_dependencies.py +138 -0
  41. hdmi-0.2.2/tests/integration/test_scope_validation.py +314 -0
  42. hdmi-0.2.2/tests/integration/test_scoped_task_sharing.py +165 -0
  43. hdmi-0.2.2/tests/integration/test_scoped_transient.py +219 -0
  44. hdmi-0.2.2/tests/integration/test_task_deduplication.py +137 -0
  45. hdmi-0.2.2/tests/integration/test_task_sharing.py +88 -0
  46. hdmi-0.2.2/tests/types/__init__.py +0 -0
  47. hdmi-0.2.2/tests/types/test_definitions.py +193 -0
  48. hdmi-0.2.2/tests/utils/__init__.py +0 -0
  49. hdmi-0.2.2/tests/utils/test_typing.py +60 -0
  50. hdmi-0.2.2/uv.lock +1381 -0
  51. hdmi-0.2.0/PKG-INFO +0 -183
  52. hdmi-0.2.0/README.md +0 -142
  53. {hdmi-0.2.0 → hdmi-0.2.2}/LICENSE +0 -0
  54. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/builders/__init__.py +0 -0
  55. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/builders/default.py +0 -0
  56. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/containers/__init__.py +0 -0
  57. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/containers/default.py +0 -0
  58. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/containers/scoped.py +0 -0
  59. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/exceptions.py +0 -0
  60. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/py.typed +0 -0
  61. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/types/__init__.py +0 -0
  62. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/types/containers.py +0 -0
  63. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/types/definitions.py +0 -0
  64. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/utils/__init__.py +0 -0
  65. {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/utils/typing.py +0 -0
@@ -0,0 +1,256 @@
1
+ name: CI/CD
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - '**'
7
+ tags:
8
+ - '*.*.*' # Semantic version tags (e.g., 0.1.0)
9
+ - '*.*.*-*' # Pre-release tags (e.g., 0.1.0-rc1, 0.1.0-beta.1)
10
+ pull_request:
11
+
12
+ permissions:
13
+ contents: write # Required for creating GitHub releases
14
+ id-token: write # Required for trusted publishing to PyPI
15
+
16
+ jobs:
17
+ test:
18
+ name: Test (Py${{ matrix.python-version }})
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ python-version: ['3.13']
24
+ permissions:
25
+ contents: read
26
+
27
+ steps:
28
+ - name: Checkout code
29
+ uses: actions/checkout@v4
30
+
31
+ - name: Set up Python
32
+ uses: actions/setup-python@v5
33
+ with:
34
+ python-version: ${{ matrix.python-version }}
35
+ allow-prereleases: true
36
+
37
+ - name: Install uv
38
+ uses: astral-sh/setup-uv@v5
39
+ with:
40
+ enable-cache: true
41
+
42
+ - name: Install dependencies
43
+ run: uv sync --frozen --all-extras
44
+
45
+ - name: Run linting
46
+ run: |
47
+ uv run ruff check .
48
+ uv run ruff format --check .
49
+
50
+ - name: Run type checking
51
+ run: uv run basedpyright
52
+
53
+ - name: Run tests
54
+ run: uv run pytest --cov=hdmi --cov-report=xml --cov-report=term
55
+
56
+ - name: Upload coverage to Codecov
57
+ uses: codecov/codecov-action@v5
58
+ with:
59
+ file: ./coverage.xml
60
+ fail_ci_if_error: false
61
+
62
+ build-python-package:
63
+ name: Build Python Package
64
+ runs-on: ubuntu-latest
65
+ permissions:
66
+ contents: read
67
+
68
+ steps:
69
+ - name: Checkout code
70
+ uses: actions/checkout@v4
71
+ with:
72
+ fetch-depth: 0
73
+
74
+ - name: Extract version
75
+ id: version
76
+ run: |
77
+ if [ "${{ github.ref_type }}" = "tag" ]; then
78
+ VERSION=${GITHUB_REF#refs/tags/}
79
+ echo "Building release version: $VERSION"
80
+ else
81
+ VERSION=$(git describe --tags --always --dirty)
82
+ echo "Building development version: $VERSION"
83
+ fi
84
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
85
+
86
+ - name: Validate version matches tag
87
+ if: github.ref_type == 'tag'
88
+ run: |
89
+ TAG_VERSION="${{ steps.version.outputs.version }}"
90
+ # Extract version from __init__.py (dynamic versioning via hatch)
91
+ PACKAGE_VERSION=$(grep -E '^__version__ = ' src/hdmi/__init__.py | sed -E 's/__version__ = "(.+)"/\1/')
92
+ echo "Tag version: $TAG_VERSION"
93
+ echo "Package version: $PACKAGE_VERSION"
94
+ if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
95
+ echo "❌ Error: Tag version ($TAG_VERSION) does not match package version ($PACKAGE_VERSION)"
96
+ exit 1
97
+ fi
98
+ echo "✅ Version validation passed"
99
+
100
+ - name: Set up Python
101
+ uses: actions/setup-python@v5
102
+ with:
103
+ python-version: '3.13'
104
+
105
+ - name: Install uv
106
+ uses: astral-sh/setup-uv@v5
107
+ with:
108
+ enable-cache: true
109
+
110
+ - name: Build package
111
+ run: uv build
112
+
113
+ - name: Install dependencies for validation
114
+ run: uv sync --all-extras
115
+
116
+ - name: Check package
117
+ run: uv run twine check dist/*
118
+
119
+ - name: Upload build artifacts
120
+ uses: actions/upload-artifact@v4
121
+ with:
122
+ name: python-package-distributions
123
+ path: dist/
124
+ retention-days: 7
125
+
126
+ all-checks-passed:
127
+ name: All Checks Passed
128
+ needs:
129
+ - build-python-package
130
+ - test
131
+ runs-on: ubuntu-latest
132
+ permissions:
133
+ contents: read
134
+
135
+ steps:
136
+ - name: All checks passed
137
+ run: echo "✅ All tests and package builds completed successfully"
138
+
139
+ - name: Checkout code
140
+ uses: actions/checkout@v4
141
+
142
+ - name: Set up Python
143
+ uses: actions/setup-python@v5
144
+ with:
145
+ python-version: '3.13'
146
+ allow-prereleases: true
147
+
148
+ - name: Download wheel artifact
149
+ uses: actions/download-artifact@v4
150
+ with:
151
+ name: python-package-distributions
152
+ path: dist/
153
+
154
+ - name: Install uv
155
+ uses: astral-sh/setup-uv@v5
156
+
157
+ - name: Test wheel installation
158
+ run: |
159
+ WHEEL=$(ls dist/*.whl)
160
+ echo "Testing wheel: $WHEEL"
161
+ uv pip install --system "$WHEEL"
162
+ python -c "import hdmi; print(f'hdmi installation successful')"
163
+ python -c "import sys; print(f'Python: {sys.version}')"
164
+
165
+ publish-to-testpypi:
166
+ name: Publish to TestPyPI
167
+ needs: all-checks-passed
168
+ runs-on: ubuntu-latest
169
+ if: github.ref_type == 'tag'
170
+ environment:
171
+ name: testpypi
172
+ url: https://test.pypi.org/p/hdmi
173
+ permissions:
174
+ id-token: write
175
+
176
+ steps:
177
+ - name: Download wheel artifact
178
+ uses: actions/download-artifact@v4
179
+ with:
180
+ name: python-package-distributions
181
+ path: dist/
182
+
183
+ - name: Publish to TestPyPI
184
+ uses: pypa/gh-action-pypi-publish@release/v1
185
+ with:
186
+ repository-url: https://test.pypi.org/legacy/
187
+
188
+ publish-to-pypi:
189
+ name: Publish to PyPI
190
+ needs: publish-to-testpypi
191
+ runs-on: ubuntu-latest
192
+ environment:
193
+ name: pypi
194
+ url: https://pypi.org/p/hdmi
195
+ permissions:
196
+ id-token: write
197
+
198
+ steps:
199
+ - name: Download wheel artifact
200
+ uses: actions/download-artifact@v4
201
+ with:
202
+ name: python-package-distributions
203
+ path: dist/
204
+
205
+ - name: Publish to PyPI
206
+ uses: pypa/gh-action-pypi-publish@release/v1
207
+
208
+ create-github-release:
209
+ name: Create GitHub Release
210
+ needs: publish-to-pypi
211
+ runs-on: ubuntu-latest
212
+ permissions:
213
+ contents: write
214
+
215
+ steps:
216
+ - name: Checkout code
217
+ uses: actions/checkout@v4
218
+ with:
219
+ fetch-depth: 0
220
+
221
+ - name: Download wheel artifact
222
+ uses: actions/download-artifact@v4
223
+ with:
224
+ name: python-package-distributions
225
+ path: dist/
226
+
227
+ - name: Extract version from tag
228
+ id: version
229
+ run: |
230
+ VERSION=${GITHUB_REF#refs/tags/}
231
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
232
+ echo "Version: $VERSION"
233
+
234
+ - name: Create release notes
235
+ run: |
236
+ cat > release-notes.md <<EOF
237
+ # Release ${{ steps.version.outputs.version }}
238
+
239
+ This release was automatically created from tag ${{ github.ref_name }}.
240
+
241
+ ## Installation
242
+
243
+ \`\`\`bash
244
+ pip install hdmi==${{ steps.version.outputs.version }}
245
+ \`\`\`
246
+
247
+ See the [documentation](https://github.com/${{ github.repository }}) for more information.
248
+ EOF
249
+
250
+ - name: Create GitHub Release
251
+ uses: softprops/action-gh-release@v2
252
+ with:
253
+ body_path: release-notes.md
254
+ files: dist/*
255
+ draft: false
256
+ prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') }}
hdmi-0.2.2/.gitignore ADDED
@@ -0,0 +1,43 @@
1
+ # OS
2
+ .DS_Store
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ *.egg-info/
9
+ *.egg
10
+ *.so
11
+ .venv/
12
+ .python-version
13
+
14
+ # Testing & Coverage
15
+ .coverage
16
+ .coverage.*
17
+ htmlcov/
18
+ .pytest_cache/
19
+ .hypothesis/
20
+
21
+ # IDEs
22
+ .idea/
23
+ .vscode/
24
+ *.iml
25
+
26
+ # Build outputs
27
+ build/
28
+ dist/
29
+ wheels/
30
+ *.whl
31
+
32
+ # Documentation
33
+ docs/_build/
34
+
35
+ # Type checkers
36
+ .mypy_cache/
37
+ .pytype/
38
+ .pyre/
39
+ .ruff_cache/
40
+
41
+ # Claude Code
42
+ .claude/*
43
+ !.claude/commands/
@@ -0,0 +1,23 @@
1
+ # Read the Docs configuration file
2
+ # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3
+
4
+ # Required
5
+ version: 2
6
+
7
+ # Set the OS, Python version, and other tools you might need
8
+ build:
9
+ os: ubuntu-24.04
10
+ tools:
11
+ python: "3.13"
12
+
13
+ # Build documentation in the "docs/" directory with Sphinx
14
+ sphinx:
15
+ configuration: docs/conf.py
16
+
17
+ # Install the package with documentation dependencies
18
+ python:
19
+ install:
20
+ - method: pip
21
+ path: .
22
+ extra_requirements:
23
+ - dev
@@ -0,0 +1,43 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - Initial implementation of dependency injection framework with type-driven configuration
13
+ - `ContainerBuilder` for service registration with lifecycle scopes
14
+ - `Container` for lazy service resolution at runtime
15
+ - Automatic dependency discovery from Python type annotations
16
+ - Comprehensive Sphinx documentation organized using Diátaxis framework
17
+ - Support for multi-level dependency chains with recursive resolution
18
+ - `IContainer` protocol for consistent container interface
19
+ - Static type checking with basedpyright
20
+ - `ServiceDefinition` class for declarative service configuration with optional factory and name support
21
+ - Support for custom factory functions to control service instantiation
22
+ - Named service registration for future multi-registration support
23
+ - **Scoped Transient services** - new service type requiring scope context but creating fresh instances per request
24
+ - Async container support with concurrent dependency resolution
25
+ - Service lifecycle hooks (initializers and finalizers)
26
+ - Task deduplication for diamond dependency patterns
27
+ - Boolean-based scope API with `scoped` and `transient` flags
28
+ - Circular dependency detection at build time with descriptive error messages showing the cycle path
29
+
30
+ ### Changed
31
+
32
+ - Reorganized container implementation into `hdmi.containers` package for better modularity
33
+ - `ServiceDefinition` is now exported from main `hdmi` package for direct usage
34
+ - `ContainerBuilder.register()` now raises `ValueError` when both `ServiceDefinition` and `scope` parameter are provided
35
+ - `UnresolvableDependencyError` now extends `KeyError` for backward compatibility while providing clearer error messages
36
+ - Container resolution failures now raise `UnresolvableDependencyError` instead of raw `KeyError` with helpful guidance on how to fix the issue
37
+ - **BREAKING**: Replaced string-based `scope` parameter with boolean flags `scoped` and `transient` in `ContainerBuilder.register()`
38
+ - Before: `builder.register(Service, scope="singleton|scoped|transient")`
39
+ - After: `builder.register(Service, scoped=True/False, transient=True/False)`
40
+ - **BREAKING**: Relaxed scope validation - singleton and scoped services can now depend on transient services
41
+ - Transient dependencies are instantiated once during dependent's construction
42
+ - Only restriction: non-scoped services cannot depend on scoped services
43
+ - Reorganized package structure: `ServiceDefinition` moved to `hdmi.types.definitions`, type utilities to `hdmi.utils.typing`
hdmi-0.2.2/CLAUDE.md ADDED
@@ -0,0 +1,198 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ **hdmi** is a dependency injection framework for Python that manages dynamic dependencies with late (just-in-time)
8
+ resolution. The framework provides:
9
+
10
+ - Late-binding dependency resolution (instantiated only when needed)
11
+ - Type-annotation-based configuration using Python's standard typing system
12
+ - Scope-aware dependency validation (singleton, scoped, transient)
13
+ - Early error detection at build time (before runtime)
14
+
15
+ ## Development Setup
16
+
17
+ This project uses **uv** for dependency management and **pytest** for testing.
18
+
19
+ ### Common Commands
20
+
21
+ **Quick Testing (pytest only):**
22
+ ```bash
23
+ # Run only pytest tests (no linting or type checking)
24
+ uv run pytest
25
+
26
+ # Run a single test file
27
+ uv run pytest tests/test_filename.py
28
+
29
+ # Run a specific test
30
+ uv run pytest tests/test_filename.py::test_function_name
31
+
32
+ # Run tests with verbose output
33
+ uv run pytest -v
34
+
35
+ # Run tests with coverage
36
+ uv run pytest --cov=hdmi --cov-report=html
37
+ ```
38
+
39
+ **Full Quality Checks (recommended for commits):**
40
+ ```bash
41
+ # Run all checks: linting, formatting, type checking, and tests
42
+ make test
43
+
44
+ # Run with verbose test output
45
+ make test TEST_VERBOSE=1
46
+
47
+ # Run with coverage report
48
+ make test TEST_COVERAGE=1
49
+
50
+ # Show all available make targets
51
+ make help
52
+ ```
53
+
54
+ ## Development Methodology
55
+
56
+ **This project strictly follows Test-Driven Development (TDD).**
57
+
58
+ All code must be developed using the Red-Green-Refactor cycle:
59
+
60
+ 1. **Red**: Write a failing test that describes the desired behavior
61
+ 2. **Green**: Write the minimum code necessary to make the test pass
62
+ 3. **Refactor**: Improve the code structure while keeping tests green
63
+
64
+ **Key TDD Principles:**
65
+ - Never write production code without a failing test first
66
+ - Write only enough code to make the current test pass
67
+ - Tests define the specification and behavior of the system
68
+ - Each commit should include both tests and implementation
69
+
70
+ **Testing Guidelines:**
71
+ - Tests should be behavioral and describe what the code does, not how it does it
72
+ - Use descriptive test names that explain the expected behavior
73
+ - Keep tests focused on a single behavior
74
+ - **Tests directory structure MUST mirror the Python package structure**:
75
+ - For `src/hdmi/module.py`, tests go in `tests/test_module.py`
76
+ - For `src/hdmi/subpackage/module.py`, tests go in `tests/subpackage/test_module.py`
77
+ - Maintain the same package hierarchy in tests as in src
78
+ - This ensures tests are organized, discoverable, and maintainable
79
+
80
+ ### Commit Guidelines
81
+
82
+ **ALWAYS run `make test` before committing** to ensure all quality checks pass (linting, type checking, and tests).
83
+
84
+ **Commit Message Best Practices:**
85
+ - Focus on **what changed and why** for the user, not implementation details
86
+ - Use conventional commits format: `feat:`, `fix:`, `refactor:`, `test:`, `docs:`
87
+ - **NEVER mention tests passing or coverage** in commit messages
88
+ - Tests passing is a prerequisite for all commits (enforced by `make test`)
89
+ - This information adds no value to the commit history
90
+ - Keep messages concise and user-focused
91
+ - Example: `feat: add scope validation for dependency graph` (good)
92
+ - Example: `feat: add scope validation with tests passing at 95% coverage` (bad - unnecessary noise)
93
+
94
+ ## Documentation
95
+
96
+ All features and architecture must be documented in the `docs/` directory using **Sphinx** and organized according to the **Diátaxis framework**.
97
+
98
+ ### Diátaxis Framework
99
+
100
+ Documentation is organized into four categories:
101
+
102
+ 1. **Tutorials** (`docs/tutorials/`): Learning-oriented guides that help newcomers learn by doing
103
+ - Step-by-step instructions
104
+ - Complete working examples
105
+ - Assumes little prior knowledge
106
+
107
+ 2. **How-To Guides** (`docs/how-to/`): Goal-oriented guides for solving specific problems
108
+ - Focused on accomplishing specific tasks
109
+ - Assumes basic knowledge
110
+ - Practical and actionable
111
+
112
+ 3. **Reference** (`docs/reference/`): Information-oriented technical descriptions
113
+ - API documentation
114
+ - Complete and accurate technical details
115
+ - Generated from docstrings where appropriate
116
+
117
+ 4. **Explanation** (`docs/explanation/`): Understanding-oriented discussions
118
+ - Architecture and design decisions
119
+ - Conceptual background
120
+ - Why things are the way they are
121
+
122
+ ### Documentation Commands
123
+
124
+ ```bash
125
+ # Build documentation
126
+ make docs
127
+
128
+ # Build and watch for changes (auto-rebuild on file changes)
129
+ make docs-watch
130
+
131
+ # Clean all build artifacts (including docs)
132
+ make clean
133
+ ```
134
+
135
+ ## Architecture
136
+
137
+ **hdmi** follows a two-phase architecture: **ContainerBuilder → Container**
138
+
139
+ ### Two-Phase Flow
140
+
141
+ 1. **ContainerBuilder** (Configuration Phase)
142
+ - Register service types and their dependencies
143
+ - Define lifecycles (singleton, scoped, transient)
144
+ - Configure using Python type annotations
145
+ - Mutable: can add/modify service definitions
146
+
147
+ 2. **Container** (Validation & Runtime Phase)
148
+ - Built from ContainerBuilder via `.build()`
149
+ - Validates the dependency graph (no cycles, all resolvable, scope-safe)
150
+ - Checks type compatibility and scope hierarchies
151
+ - Immutable: dependency graph is frozen
152
+ - Used at runtime to resolve service instances via `.get(ServiceType)`
153
+ - Services created lazily (just-in-time) when first requested
154
+
155
+ ### Example Flow
156
+
157
+ ```python
158
+ # Phase 1: Configuration
159
+ builder = ContainerBuilder()
160
+ builder.register(DatabaseConnection) # singleton (default)
161
+ builder.register(UserRepository, scoped=True) # scoped service
162
+ builder.register(UserService, transient=True) # transient service
163
+
164
+ # Phase 2: Build & Runtime
165
+ container = builder.build() # validates graph, no instantiation yet
166
+
167
+ # Service resolution (lazy instantiation)
168
+ user_service = container.get(UserService) # creates all dependencies on-demand
169
+ ```
170
+
171
+ ### Core Concepts
172
+
173
+ 1. **Type-Driven Dependencies**: Python type annotations define the dependency graph automatically
174
+
175
+ 2. **Scope Hierarchy & Validation**: Services can only depend on services in the same or higher scope
176
+ - **Singleton** (highest): One instance per container, lives for container lifetime
177
+ - **Scoped** (middle): One instance per scope (e.g., per web request)
178
+ - **Transient** (lowest): New instance every time it's requested
179
+
180
+ Validation rules:
181
+ - Singleton services can only depend on other singleton services
182
+ - Scoped services can depend on singleton or scoped services
183
+ - Transient services can depend on any scope
184
+
185
+ 3. **Late Resolution**: Services instantiated just-in-time, not eagerly (see `docs/explanation/why-late-binding.rst`)
186
+
187
+ 4. **Early Validation**: Configuration errors (cycles, missing deps, scope violations) caught at `.build()` time
188
+
189
+ 5. **Immutable Containers**: Once built, the dependency graph cannot be modified
190
+
191
+ ### Design Principles
192
+
193
+ - **Fail Fast, Run Lazy**: Validate early (scope violations, cycles), instantiate late
194
+ - **Type Annotations as Truth**: No separate configuration required
195
+ - **Scope Safety**: Compile-time prevention of scope-related lifetime bugs
196
+ - **Simplicity by Design**: Two core concepts (ContainerBuilder, Container), one clear workflow
197
+ - **No External DSL**: Pure Python, no YAML/XML required (unlike harp/rodi)
198
+ - **Minimal Overhead**: Lightweight and fast
hdmi-0.2.2/Makefile ADDED
@@ -0,0 +1,70 @@
1
+ .PHONY: test install install-dev check docs docs-watch clean wheel help
2
+
3
+ UV ?= $(shell command -v uv 2>/dev/null || echo "uv")
4
+ RUN ?= $(UV) run
5
+ TEST_VERBOSE ?=
6
+ TEST_COVERAGE ?=
7
+
8
+ # helpers
9
+ define execute
10
+ @echo "⚙️ \033[36m$@\033[0m: \033[2m$(1)\033[0m"
11
+ @$(1)
12
+ endef
13
+
14
+ help: ## Show available commands
15
+ @echo "Available commands:"
16
+ @echo
17
+ @echo "\033[1mDevelopment\033[0m"
18
+ @grep -E '^(install|install-dev):.*?##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?##"}; {printf " make \033[36m%-20s\033[0m %s\n", $$1, $$2}'
19
+ @echo
20
+ @echo "\033[1mTesting & Quality\033[0m"
21
+ @grep -E '^(test|check):.*?##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?##"}; {printf " make \033[36m%-20s\033[0m %s\n", $$1, $$2}'
22
+ @echo
23
+ @echo "\033[1mDocumentation\033[0m"
24
+ @grep -E '^(docs|docs-watch):.*?##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?##"}; {printf " make \033[36m%-20s\033[0m %s\n", $$1, $$2}'
25
+ @echo
26
+ @echo "\033[1mBuild\033[0m"
27
+ @grep -E '^(wheel):.*?##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?##"}; {printf " make \033[36m%-20s\033[0m %s\n", $$1, $$2}'
28
+ @echo
29
+ @echo "\033[1mCleanup\033[0m"
30
+ @grep -E '^(clean):.*?##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?##"}; {printf " make \033[36m%-20s\033[0m %s\n", $$1, $$2}'
31
+ @echo
32
+
33
+ install: ## Install dependencies (without dev tools)
34
+ $(call execute,$(UV) sync)
35
+
36
+ install-dev: ## Install dependencies with dev tools
37
+ $(call execute,$(UV) sync --extra dev)
38
+
39
+ check: install-dev ## Check and fix code with ruff (lint + format)
40
+ $(call execute,$(RUN) ruff check --fix .)
41
+ $(call execute,$(RUN) ruff format .)
42
+ $(call execute,$(RUN) basedpyright)
43
+
44
+ test: install-dev check ## Run all tests
45
+ $(call execute,$(RUN) pytest $(if $(TEST_VERBOSE),--verbose,) $(if $(TEST_COVERAGE),--cov=hdmi --cov-report=html --cov-report=term,))
46
+
47
+ docs: install-dev ## Build documentation with Sphinx
48
+ $(call execute,$(RUN) sphinx-build -b html docs docs/_build/html)
49
+
50
+ docs-watch: install-dev ## Build documentation and watch for changes
51
+ $(call execute,$(RUN) sphinx-autobuild docs docs/_build/html --watch src)
52
+
53
+ clean: ## Clean up temporary files
54
+ rm -rf .pytest_cache
55
+ rm -rf htmlcov
56
+ rm -rf .coverage
57
+ rm -rf docs/_build
58
+ rm -rf dist
59
+ rm -rf build
60
+ rm -rf *.egg-info
61
+ find . -type d -name __pycache__ -exec rm -rf {} +
62
+ find . -type f -name "*.pyc" -delete
63
+
64
+ wheel: test clean ## Build and check wheel for PyPI distribution
65
+ $(call execute,$(UV) build)
66
+ $(call execute,$(RUN) twine check dist/*)
67
+ @echo ""
68
+ @echo "Wheel built and validated successfully!"
69
+ @echo "Files ready for upload:"
70
+ @ls -lh dist/