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.
- hdmi-0.2.2/.github/workflows/cicd.yml +256 -0
- hdmi-0.2.2/.gitignore +43 -0
- hdmi-0.2.2/.readthedocs.yaml +23 -0
- hdmi-0.2.2/CHANGELOG.md +43 -0
- hdmi-0.2.2/CLAUDE.md +198 -0
- hdmi-0.2.2/Makefile +70 -0
- hdmi-0.2.2/PKG-INFO +119 -0
- hdmi-0.2.2/README.md +72 -0
- hdmi-0.2.2/RELEASE.md +168 -0
- hdmi-0.2.2/demo.py +136 -0
- hdmi-0.2.2/docs/README.md +111 -0
- hdmi-0.2.2/docs/conf.py +66 -0
- hdmi-0.2.2/docs/explanation/architecture.rst +493 -0
- hdmi-0.2.2/docs/explanation/index.rst +57 -0
- hdmi-0.2.2/docs/explanation/why-late-binding.rst +307 -0
- hdmi-0.2.2/docs/how-to/index.rst +12 -0
- hdmi-0.2.2/docs/how-to/use-service-definitions.rst +370 -0
- hdmi-0.2.2/docs/index.rst +79 -0
- hdmi-0.2.2/docs/reference/api.rst +274 -0
- hdmi-0.2.2/docs/reference/index.rst +12 -0
- hdmi-0.2.2/docs/tutorials/index.rst +13 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/pyproject.toml +17 -3
- hdmi-0.2.2/sandbox/basics.py +52 -0
- hdmi-0.2.2/sandbox/complex.py +46 -0
- hdmi-0.2.2/sandbox/diamond.py +98 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/__init__.py +3 -1
- hdmi-0.2.2/tests/__init__.py +1 -0
- hdmi-0.2.2/tests/builders/__init__.py +1 -0
- hdmi-0.2.2/tests/builders/test_default.py +381 -0
- hdmi-0.2.2/tests/conftest.py +9 -0
- hdmi-0.2.2/tests/containers/__init__.py +1 -0
- hdmi-0.2.2/tests/containers/test_default.py +275 -0
- hdmi-0.2.2/tests/containers/test_scoped.py +166 -0
- hdmi-0.2.2/tests/integration/__init__.py +0 -0
- hdmi-0.2.2/tests/integration/test_async_container.py +76 -0
- hdmi-0.2.2/tests/integration/test_boolean_scope_api.py +193 -0
- hdmi-0.2.2/tests/integration/test_concurrent_resolution.py +109 -0
- hdmi-0.2.2/tests/integration/test_integration.py +156 -0
- hdmi-0.2.2/tests/integration/test_lifecycle.py +181 -0
- hdmi-0.2.2/tests/integration/test_scope_transient_dependencies.py +138 -0
- hdmi-0.2.2/tests/integration/test_scope_validation.py +314 -0
- hdmi-0.2.2/tests/integration/test_scoped_task_sharing.py +165 -0
- hdmi-0.2.2/tests/integration/test_scoped_transient.py +219 -0
- hdmi-0.2.2/tests/integration/test_task_deduplication.py +137 -0
- hdmi-0.2.2/tests/integration/test_task_sharing.py +88 -0
- hdmi-0.2.2/tests/types/__init__.py +0 -0
- hdmi-0.2.2/tests/types/test_definitions.py +193 -0
- hdmi-0.2.2/tests/utils/__init__.py +0 -0
- hdmi-0.2.2/tests/utils/test_typing.py +60 -0
- hdmi-0.2.2/uv.lock +1381 -0
- hdmi-0.2.0/PKG-INFO +0 -183
- hdmi-0.2.0/README.md +0 -142
- {hdmi-0.2.0 → hdmi-0.2.2}/LICENSE +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/builders/__init__.py +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/builders/default.py +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/containers/__init__.py +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/containers/default.py +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/containers/scoped.py +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/exceptions.py +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/py.typed +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/types/__init__.py +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/types/containers.py +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/types/definitions.py +0 -0
- {hdmi-0.2.0 → hdmi-0.2.2}/src/hdmi/utils/__init__.py +0 -0
- {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
|
hdmi-0.2.2/CHANGELOG.md
ADDED
|
@@ -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/
|