typewirepy 0.1.0__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.
- typewirepy-0.1.0/.claude/skills/release/SKILL.md +95 -0
- typewirepy-0.1.0/.envrc +14 -0
- typewirepy-0.1.0/.github/workflows/ci.yml +116 -0
- typewirepy-0.1.0/.github/workflows/publish.yml +108 -0
- typewirepy-0.1.0/.gitignore +50 -0
- typewirepy-0.1.0/.python-version +1 -0
- typewirepy-0.1.0/CONTRIBUTING.md +40 -0
- typewirepy-0.1.0/LICENSE +21 -0
- typewirepy-0.1.0/PKG-INFO +166 -0
- typewirepy-0.1.0/README.md +141 -0
- typewirepy-0.1.0/docs/SPEC.md +1094 -0
- typewirepy-0.1.0/flake.lock +312 -0
- typewirepy-0.1.0/flake.nix +53 -0
- typewirepy-0.1.0/justfile +15 -0
- typewirepy-0.1.0/pyproject.toml +109 -0
- typewirepy-0.1.0/src/typewirepy/__init__.py +30 -0
- typewirepy-0.1.0/src/typewirepy/_introspect.py +65 -0
- typewirepy-0.1.0/src/typewirepy/_token.py +17 -0
- typewirepy-0.1.0/src/typewirepy/container.py +102 -0
- typewirepy-0.1.0/src/typewirepy/core.py +79 -0
- typewirepy-0.1.0/src/typewirepy/errors.py +42 -0
- typewirepy-0.1.0/src/typewirepy/group.py +40 -0
- typewirepy-0.1.0/src/typewirepy/integrations/__init__.py +0 -0
- typewirepy-0.1.0/src/typewirepy/integrations/fastapi.py +51 -0
- typewirepy-0.1.0/src/typewirepy/protocols.py +28 -0
- typewirepy-0.1.0/src/typewirepy/py.typed +0 -0
- typewirepy-0.1.0/src/typewirepy/scope.py +8 -0
- typewirepy-0.1.0/src/typewirepy/wire.py +197 -0
- typewirepy-0.1.0/tests/conftest.py +27 -0
- typewirepy-0.1.0/tests/test_circular.py +109 -0
- typewirepy-0.1.0/tests/test_container.py +87 -0
- typewirepy-0.1.0/tests/test_distributed.py +41 -0
- typewirepy-0.1.0/tests/test_fastapi.py +136 -0
- typewirepy-0.1.0/tests/test_generators.py +85 -0
- typewirepy-0.1.0/tests/test_group.py +57 -0
- typewirepy-0.1.0/tests/test_import.py +19 -0
- typewirepy-0.1.0/tests/test_overrides.py +46 -0
- typewirepy-0.1.0/tests/test_sync.py +44 -0
- typewirepy-0.1.0/tests/test_use_cases.py +176 -0
- typewirepy-0.1.0/tests/test_validation.py +50 -0
- typewirepy-0.1.0/tests/test_wire.py +113 -0
- typewirepy-0.1.0/uv.lock +582 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release
|
|
3
|
+
description: Analyze commits and determine next version for PyPI release
|
|
4
|
+
user_invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /release — Release Analysis Skill
|
|
8
|
+
|
|
9
|
+
Analyzes conventional commits since the last GitHub release and determines the next version to publish. Does NOT modify any files or create any commits.
|
|
10
|
+
|
|
11
|
+
Supports two modes:
|
|
12
|
+
- `/release` — standard release
|
|
13
|
+
- `/release dev` — dev pre-release
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
|
|
17
|
+
### 1. Find the last GitHub release
|
|
18
|
+
|
|
19
|
+
Run:
|
|
20
|
+
```bash
|
|
21
|
+
gh release list --limit 1 --json tagName -q '.[0].tagName'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- If the command fails (non-zero exit), stop and tell the user to run `gh auth login`.
|
|
25
|
+
- If the output is empty (no releases exist), this is the **first release**:
|
|
26
|
+
- Read `pyproject.toml` and extract the `version` value (e.g., `0.1.0`)
|
|
27
|
+
- Skip commit analysis — use that version as-is
|
|
28
|
+
- Jump to **Display summary** with bump type "initial"
|
|
29
|
+
|
|
30
|
+
### 2. List commits since last release
|
|
31
|
+
|
|
32
|
+
Run (replacing `<tag>` with the tag from step 1):
|
|
33
|
+
```bash
|
|
34
|
+
git log <tag>..HEAD --format='%s%n%b---'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- If no output (no commits since last release), stop and tell the user: "No commits since the last release. Nothing to release."
|
|
38
|
+
|
|
39
|
+
### 3. Parse conventional commits
|
|
40
|
+
|
|
41
|
+
For each commit, determine its bump level:
|
|
42
|
+
|
|
43
|
+
1. If the commit body contains `BREAKING CHANGE:` (literal text) OR the subject matches `<type>[(scope)]!:` (note the `!`) → **major**
|
|
44
|
+
2. If the subject starts with `feat:` or `feat(` → **minor**
|
|
45
|
+
3. All other commits → **patch** (including `fix`, `refactor`, `docs`, `chore`, `ci`, `test`, `perf`, `style`, `build`, and non-conventional formats)
|
|
46
|
+
|
|
47
|
+
The **highest bump wins** across all commits: major > minor > patch.
|
|
48
|
+
|
|
49
|
+
### 4. Calculate new version
|
|
50
|
+
|
|
51
|
+
Extract the base version from the last release tag (strip leading `v`). Apply the bump:
|
|
52
|
+
- **major**: increment major, reset minor and patch to 0
|
|
53
|
+
- **minor**: increment minor, reset patch to 0
|
|
54
|
+
- **patch**: increment patch
|
|
55
|
+
|
|
56
|
+
### 5. Display summary
|
|
57
|
+
|
|
58
|
+
Show the user:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
## Release Summary
|
|
62
|
+
|
|
63
|
+
### Commits included
|
|
64
|
+
- <commit subject> → <bump level>
|
|
65
|
+
- ...
|
|
66
|
+
|
|
67
|
+
### Version
|
|
68
|
+
Bump type: <major|minor|patch|initial>
|
|
69
|
+
Version: <old> → <new>
|
|
70
|
+
|
|
71
|
+
### Publish command
|
|
72
|
+
gh workflow run publish.yml -f version=<new-version> -f dry-run=true
|
|
73
|
+
|
|
74
|
+
Change dry-run=false when ready to actually publish.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Dev release flow (`/release dev`)
|
|
78
|
+
|
|
79
|
+
Same as above through step 4, then:
|
|
80
|
+
|
|
81
|
+
1. Determine the next version from conventional commits (same logic as standard flow)
|
|
82
|
+
2. Query PyPI for existing dev versions. Run:
|
|
83
|
+
```bash
|
|
84
|
+
curl -s -o /tmp/pypi.json -w '%{http_code}' https://pypi.org/pypi/typewirepy/json
|
|
85
|
+
```
|
|
86
|
+
- If HTTP status is `404` (project doesn't exist on PyPI yet), start at `.dev1`
|
|
87
|
+
- If `200`, parse `/tmp/pypi.json`: extract all release keys matching `<next-version>.dev*`, parse the dev number as an **integer**, find the max, and increment by 1
|
|
88
|
+
- Use **numeric comparison** (not lexicographic) for dev numbers
|
|
89
|
+
3. Version becomes `<next-version>.dev<N>` (e.g., `0.2.0.dev1`)
|
|
90
|
+
4. Display summary with the dev version and provide the dispatch command
|
|
91
|
+
|
|
92
|
+
## First release (no prior GitHub releases)
|
|
93
|
+
|
|
94
|
+
- Use the version from `pyproject.toml` directly (no bump)
|
|
95
|
+
- Provide the dispatch command for that version
|
typewirepy-0.1.0/.envrc
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
watch_file flake.nix
|
|
2
|
+
watch_file flake.lock
|
|
3
|
+
|
|
4
|
+
if ! has nix_direnv_version || ! nix_direnv_version 3.1.0; then
|
|
5
|
+
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.1.0/direnvrc" "sha256-buKPWv3gHXcQVIjkNxwBcaVQt+zIBqGB6WIZt3GJR/0="
|
|
6
|
+
fi
|
|
7
|
+
|
|
8
|
+
DEVENV_IN_DIRENV_SHELL=1
|
|
9
|
+
export DEVENV_IN_DIRENV_SHELL
|
|
10
|
+
|
|
11
|
+
if ! use flake . --no-pure-eval
|
|
12
|
+
then
|
|
13
|
+
echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to flake.nix and hit enter to try again." >&2
|
|
14
|
+
fi
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_call:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
name: Test / Python ${{ matrix.python-version }}
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v5
|
|
23
|
+
with:
|
|
24
|
+
enable-cache: true
|
|
25
|
+
|
|
26
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
27
|
+
run: uv python install ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: uv sync --python ${{ matrix.python-version }}
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: uv run pytest --tb=short -q
|
|
34
|
+
|
|
35
|
+
lint:
|
|
36
|
+
name: Lint
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
|
|
41
|
+
- name: Install uv
|
|
42
|
+
uses: astral-sh/setup-uv@v5
|
|
43
|
+
with:
|
|
44
|
+
enable-cache: true
|
|
45
|
+
|
|
46
|
+
- name: Install dependencies
|
|
47
|
+
run: uv sync
|
|
48
|
+
|
|
49
|
+
- name: Ruff check
|
|
50
|
+
run: uv run ruff check src/ tests/
|
|
51
|
+
|
|
52
|
+
- name: Ruff format check
|
|
53
|
+
run: uv run ruff format --check src/ tests/
|
|
54
|
+
|
|
55
|
+
type-check:
|
|
56
|
+
name: Types / ${{ matrix.checker }}
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
strategy:
|
|
59
|
+
fail-fast: false
|
|
60
|
+
matrix:
|
|
61
|
+
checker: [mypy, pyright, ty, pyrefly]
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v4
|
|
64
|
+
|
|
65
|
+
- name: Install uv
|
|
66
|
+
uses: astral-sh/setup-uv@v5
|
|
67
|
+
with:
|
|
68
|
+
enable-cache: true
|
|
69
|
+
|
|
70
|
+
- name: Set up Python
|
|
71
|
+
run: uv python install 3.12
|
|
72
|
+
|
|
73
|
+
- name: Install dependencies
|
|
74
|
+
run: uv sync
|
|
75
|
+
|
|
76
|
+
- name: Run mypy
|
|
77
|
+
if: matrix.checker == 'mypy'
|
|
78
|
+
run: uv run mypy src/typewirepy/
|
|
79
|
+
|
|
80
|
+
- name: Run pyright
|
|
81
|
+
if: matrix.checker == 'pyright'
|
|
82
|
+
run: uv run pyright src/typewirepy/
|
|
83
|
+
|
|
84
|
+
- name: Run ty
|
|
85
|
+
if: matrix.checker == 'ty'
|
|
86
|
+
run: uv run ty check src/typewirepy/
|
|
87
|
+
|
|
88
|
+
- name: Run pyrefly
|
|
89
|
+
if: matrix.checker == 'pyrefly'
|
|
90
|
+
run: uv run pyrefly check src/typewirepy/
|
|
91
|
+
|
|
92
|
+
build:
|
|
93
|
+
name: Build
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
steps:
|
|
96
|
+
- uses: actions/checkout@v4
|
|
97
|
+
|
|
98
|
+
- name: Install uv
|
|
99
|
+
uses: astral-sh/setup-uv@v5
|
|
100
|
+
with:
|
|
101
|
+
enable-cache: true
|
|
102
|
+
|
|
103
|
+
- name: Build package
|
|
104
|
+
run: uv build
|
|
105
|
+
|
|
106
|
+
ci-ok:
|
|
107
|
+
name: CI OK
|
|
108
|
+
needs: [test, lint, type-check, build]
|
|
109
|
+
runs-on: ubuntu-latest
|
|
110
|
+
if: always()
|
|
111
|
+
steps:
|
|
112
|
+
- run: |
|
|
113
|
+
test "${{ needs.test.result }}" = "success" \
|
|
114
|
+
&& test "${{ needs.lint.result }}" = "success" \
|
|
115
|
+
&& test "${{ needs.type-check.result }}" = "success" \
|
|
116
|
+
&& test "${{ needs.build.result }}" = "success"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
version:
|
|
7
|
+
type: string
|
|
8
|
+
required: true
|
|
9
|
+
description: "Version to publish (e.g. 0.2.0, 0.2.0.dev1)"
|
|
10
|
+
dry-run:
|
|
11
|
+
type: boolean
|
|
12
|
+
default: true
|
|
13
|
+
description: "Dry run (build only, skip publish)"
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: publish-${{ github.event.inputs.version }}
|
|
17
|
+
cancel-in-progress: false
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
ci:
|
|
21
|
+
name: CI
|
|
22
|
+
uses: ./.github/workflows/ci.yml
|
|
23
|
+
|
|
24
|
+
publish:
|
|
25
|
+
name: Publish
|
|
26
|
+
needs: [ci]
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
environment: ${{ inputs['dry-run'] == false && 'pypi' || 'dry-run' }}
|
|
29
|
+
permissions:
|
|
30
|
+
id-token: write
|
|
31
|
+
contents: write
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
with:
|
|
35
|
+
fetch-depth: 0
|
|
36
|
+
fetch-tags: true
|
|
37
|
+
|
|
38
|
+
- name: Validate version format
|
|
39
|
+
run: |
|
|
40
|
+
VERSION="${{ inputs.version }}"
|
|
41
|
+
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(\.dev[0-9]+)?$'; then
|
|
42
|
+
echo "::error::Invalid version format: $VERSION (expected X.Y.Z or X.Y.Z.devN)"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
- name: Check tag doesn't already exist
|
|
47
|
+
run: |
|
|
48
|
+
if git rev-parse "v${{ inputs.version }}" >/dev/null 2>&1; then
|
|
49
|
+
echo "::error::Tag v${{ inputs.version }} already exists"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
- name: Install uv
|
|
54
|
+
uses: astral-sh/setup-uv@v5
|
|
55
|
+
|
|
56
|
+
- name: Set up Python
|
|
57
|
+
run: uv python install 3.12
|
|
58
|
+
|
|
59
|
+
- name: Patch version
|
|
60
|
+
run: |
|
|
61
|
+
python3 -c "
|
|
62
|
+
import re, pathlib
|
|
63
|
+
p = pathlib.Path('pyproject.toml')
|
|
64
|
+
text = p.read_text()
|
|
65
|
+
text = re.sub(r'^version\s*=\s*\"[^\"]*\"', 'version = \"${{ inputs.version }}\"', text, count=1, flags=re.MULTILINE)
|
|
66
|
+
p.write_text(text)
|
|
67
|
+
"
|
|
68
|
+
|
|
69
|
+
- name: Build package
|
|
70
|
+
run: uv build
|
|
71
|
+
|
|
72
|
+
- name: Verify package version
|
|
73
|
+
run: |
|
|
74
|
+
PKG_VERSION=$(python3 -c "
|
|
75
|
+
import zipfile, glob, re
|
|
76
|
+
whl = glob.glob('dist/*.whl')[0]
|
|
77
|
+
with zipfile.ZipFile(whl) as z:
|
|
78
|
+
meta = [n for n in z.namelist() if n.endswith('METADATA')][0]
|
|
79
|
+
text = z.read(meta).decode()
|
|
80
|
+
print(re.search(r'^Version: (.+)$', text, re.MULTILINE).group(1))
|
|
81
|
+
")
|
|
82
|
+
if [ "$PKG_VERSION" != "${{ inputs.version }}" ]; then
|
|
83
|
+
echo "::error::Built package version ($PKG_VERSION) doesn't match input (${{ inputs.version }})"
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
- name: Publish to PyPI
|
|
88
|
+
if: inputs['dry-run'] == false
|
|
89
|
+
run: uv publish
|
|
90
|
+
|
|
91
|
+
- name: Create tag
|
|
92
|
+
if: inputs['dry-run'] == false
|
|
93
|
+
run: |
|
|
94
|
+
git tag "v${{ inputs.version }}"
|
|
95
|
+
git push origin "v${{ inputs.version }}"
|
|
96
|
+
|
|
97
|
+
- name: Create GitHub Release
|
|
98
|
+
if: inputs['dry-run'] == false
|
|
99
|
+
uses: softprops/action-gh-release@v2
|
|
100
|
+
with:
|
|
101
|
+
tag_name: "v${{ inputs.version }}"
|
|
102
|
+
generate_release_notes: true
|
|
103
|
+
|
|
104
|
+
- name: Dry run summary
|
|
105
|
+
if: inputs['dry-run'] == true
|
|
106
|
+
run: |
|
|
107
|
+
echo "::notice::Dry run complete — built v${{ inputs.version }} successfully"
|
|
108
|
+
echo "::notice::To publish for real, re-run with dry-run=false"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# ── Python ──
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
*.egg
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.whl
|
|
10
|
+
|
|
11
|
+
# ── Virtual environments ──
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
ENV/
|
|
15
|
+
|
|
16
|
+
# ── UV ──
|
|
17
|
+
# uv.lock is committed (reproducibility)
|
|
18
|
+
# .python-version is committed
|
|
19
|
+
|
|
20
|
+
# ── IDE ──
|
|
21
|
+
.idea/
|
|
22
|
+
.vscode/
|
|
23
|
+
*.swp
|
|
24
|
+
*.swo
|
|
25
|
+
*~
|
|
26
|
+
.DS_Store
|
|
27
|
+
|
|
28
|
+
# ── Testing ──
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
htmlcov/
|
|
31
|
+
.coverage
|
|
32
|
+
.coverage.*
|
|
33
|
+
coverage.xml
|
|
34
|
+
|
|
35
|
+
# ── Type checkers ──
|
|
36
|
+
.mypy_cache/
|
|
37
|
+
.pyright/
|
|
38
|
+
.ty/
|
|
39
|
+
|
|
40
|
+
# ── Nix / devenv / direnv ──
|
|
41
|
+
result
|
|
42
|
+
result-*
|
|
43
|
+
.devenv/
|
|
44
|
+
.devenv.flake.nix
|
|
45
|
+
.direnv/
|
|
46
|
+
# flake.lock is committed (reproducibility)
|
|
47
|
+
|
|
48
|
+
# ── Distribution ──
|
|
49
|
+
dist/
|
|
50
|
+
*.tar.gz
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Contributing to TypeWirePy
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- Python 3.10+
|
|
6
|
+
- [uv](https://docs.astral.sh/uv/) (package manager)
|
|
7
|
+
|
|
8
|
+
## Development setup
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
git clone https://github.com/typewirepy/typewirepy.git
|
|
12
|
+
cd typewirepy
|
|
13
|
+
uv sync
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Running tests
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv run pytest
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Linting & formatting
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv run ruff check src/ tests/
|
|
26
|
+
uv run ruff format src/ tests/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Type checking
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv run mypy src/typewirepy/
|
|
33
|
+
uv run pyright src/typewirepy/
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Pull request guidelines
|
|
37
|
+
|
|
38
|
+
1. Branch from `main`
|
|
39
|
+
2. Ensure all CI checks pass (tests, lint, format, type-check)
|
|
40
|
+
3. Keep changes focused — one feature or fix per PR
|
typewirepy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 TypeWirePy Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: typewirepy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight, container-agnostic dependency injection for Python — typed, explicit, composable.
|
|
5
|
+
Project-URL: Homepage, https://github.com/typewirepy/typewirepy
|
|
6
|
+
Project-URL: Repository, https://github.com/typewirepy/typewirepy
|
|
7
|
+
Project-URL: Issues, https://github.com/typewirepy/typewirepy/issues
|
|
8
|
+
Author: TypeWireTS Contributors
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: container,dependency-injection,di,ioc,typed,typesafe
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Provides-Extra: fastapi
|
|
23
|
+
Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# typewirepy
|
|
27
|
+
|
|
28
|
+
[](https://github.com/typewirepy/typewirepy/actions/workflows/ci.yml)
|
|
29
|
+
[](https://www.python.org)
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
|
|
32
|
+
Lightweight, container-agnostic dependency injection for Python — typed, explicit, composable.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **Zero runtime dependencies** — only stdlib
|
|
37
|
+
- **Fully typed** — strict mypy + pyright out of the box
|
|
38
|
+
- **Async-first** — native `async`/`await`; sync wrappers included
|
|
39
|
+
- **Immutable wires** — safe to share across threads and modules
|
|
40
|
+
- **Scopes** — singleton (default) and transient
|
|
41
|
+
- **Generator lifecycle** — `yield`-based creators with automatic cleanup
|
|
42
|
+
- **FastAPI integration** — `WireDepends()` bridges wires to FastAPI's `Depends()`
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install typewirepy
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
With FastAPI support:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install typewirepy[fastapi]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
### Async (primary)
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from typewirepy import Scope, TypeWireContainer, type_wire_group_of, type_wire_of
|
|
62
|
+
|
|
63
|
+
config_wire = type_wire_of(token="Config", creator=lambda: {"db_url": "sqlite://"})
|
|
64
|
+
|
|
65
|
+
db_wire = type_wire_of(
|
|
66
|
+
token="Database",
|
|
67
|
+
imports={"config": config_wire},
|
|
68
|
+
create_with=lambda deps: f"db({deps['config']['db_url']})",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
app_wires = type_wire_group_of([config_wire, db_wire])
|
|
72
|
+
|
|
73
|
+
async def main():
|
|
74
|
+
async with TypeWireContainer() as container:
|
|
75
|
+
await app_wires.apply(container)
|
|
76
|
+
db = await db_wire.get_instance(container)
|
|
77
|
+
print(db) # "db(sqlite://)"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Sync
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from typewirepy import TypeWireContainer, type_wire_of
|
|
84
|
+
|
|
85
|
+
wire = type_wire_of(token="Greeting", creator=lambda: "hello")
|
|
86
|
+
|
|
87
|
+
with TypeWireContainer.sync() as container:
|
|
88
|
+
wire.apply_sync(container)
|
|
89
|
+
print(wire.get_instance_sync(container)) # "hello"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Key Concepts
|
|
93
|
+
|
|
94
|
+
### Wires
|
|
95
|
+
|
|
96
|
+
A **wire** is an immutable description of a dependency — its token (name), how to create it, and what it depends on. Create wires with `type_wire_of()`:
|
|
97
|
+
|
|
98
|
+
- **Leaf wire** — uses `creator` (zero-arg callable)
|
|
99
|
+
- **Composed wire** — uses `create_with` + `imports` to receive resolved dependencies
|
|
100
|
+
|
|
101
|
+
### Imports
|
|
102
|
+
|
|
103
|
+
Imports declare which other wires a composed wire depends on. They're passed as a `dict[str, TypeWire]` and delivered to `create_with` as a dict (Convention A) or as keyword arguments (Convention B):
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# Convention A: dict parameter
|
|
107
|
+
type_wire_of(token="Svc", imports={"db": db_wire}, create_with=lambda deps: deps["db"])
|
|
108
|
+
|
|
109
|
+
# Convention B: keyword-only parameters
|
|
110
|
+
def create_svc(*, db: Database) -> Service: ...
|
|
111
|
+
type_wire_of(token="Svc", imports={"db": db_wire}, create_with=create_svc)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Scopes
|
|
115
|
+
|
|
116
|
+
- `Scope.SINGLETON` (default) — resolved once, cached for the container's lifetime
|
|
117
|
+
- `Scope.TRANSIENT` — resolved fresh on every call
|
|
118
|
+
|
|
119
|
+
### Groups
|
|
120
|
+
|
|
121
|
+
A `TypeWireGroup` bundles wires together for batch application:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
group = type_wire_group_of([config_wire, db_wire, service_wire])
|
|
125
|
+
await group.apply(container)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Override wires for testing with `with_extra_wires()`:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
test_group = group.with_extra_wires([service_wire.with_creator(lambda _: mock_svc)])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## FastAPI Integration
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from contextlib import asynccontextmanager
|
|
138
|
+
|
|
139
|
+
from fastapi import FastAPI
|
|
140
|
+
from typewirepy import TypeWireContainer, type_wire_group_of, type_wire_of
|
|
141
|
+
from typewirepy.integrations.fastapi import WireDepends
|
|
142
|
+
|
|
143
|
+
db_wire = type_wire_of(token="DB", creator=lambda: "db_connection")
|
|
144
|
+
app_wires = type_wire_group_of([db_wire])
|
|
145
|
+
|
|
146
|
+
@asynccontextmanager
|
|
147
|
+
async def lifespan(app: FastAPI):
|
|
148
|
+
async with TypeWireContainer() as container:
|
|
149
|
+
await app_wires.apply(container)
|
|
150
|
+
app.state.typewire_container = container
|
|
151
|
+
yield
|
|
152
|
+
|
|
153
|
+
app = FastAPI(lifespan=lifespan)
|
|
154
|
+
|
|
155
|
+
@app.get("/")
|
|
156
|
+
async def root(db: str = WireDepends(db_wire)):
|
|
157
|
+
return {"db": db}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Contributing
|
|
161
|
+
|
|
162
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|