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.
Files changed (42) hide show
  1. typewirepy-0.1.0/.claude/skills/release/SKILL.md +95 -0
  2. typewirepy-0.1.0/.envrc +14 -0
  3. typewirepy-0.1.0/.github/workflows/ci.yml +116 -0
  4. typewirepy-0.1.0/.github/workflows/publish.yml +108 -0
  5. typewirepy-0.1.0/.gitignore +50 -0
  6. typewirepy-0.1.0/.python-version +1 -0
  7. typewirepy-0.1.0/CONTRIBUTING.md +40 -0
  8. typewirepy-0.1.0/LICENSE +21 -0
  9. typewirepy-0.1.0/PKG-INFO +166 -0
  10. typewirepy-0.1.0/README.md +141 -0
  11. typewirepy-0.1.0/docs/SPEC.md +1094 -0
  12. typewirepy-0.1.0/flake.lock +312 -0
  13. typewirepy-0.1.0/flake.nix +53 -0
  14. typewirepy-0.1.0/justfile +15 -0
  15. typewirepy-0.1.0/pyproject.toml +109 -0
  16. typewirepy-0.1.0/src/typewirepy/__init__.py +30 -0
  17. typewirepy-0.1.0/src/typewirepy/_introspect.py +65 -0
  18. typewirepy-0.1.0/src/typewirepy/_token.py +17 -0
  19. typewirepy-0.1.0/src/typewirepy/container.py +102 -0
  20. typewirepy-0.1.0/src/typewirepy/core.py +79 -0
  21. typewirepy-0.1.0/src/typewirepy/errors.py +42 -0
  22. typewirepy-0.1.0/src/typewirepy/group.py +40 -0
  23. typewirepy-0.1.0/src/typewirepy/integrations/__init__.py +0 -0
  24. typewirepy-0.1.0/src/typewirepy/integrations/fastapi.py +51 -0
  25. typewirepy-0.1.0/src/typewirepy/protocols.py +28 -0
  26. typewirepy-0.1.0/src/typewirepy/py.typed +0 -0
  27. typewirepy-0.1.0/src/typewirepy/scope.py +8 -0
  28. typewirepy-0.1.0/src/typewirepy/wire.py +197 -0
  29. typewirepy-0.1.0/tests/conftest.py +27 -0
  30. typewirepy-0.1.0/tests/test_circular.py +109 -0
  31. typewirepy-0.1.0/tests/test_container.py +87 -0
  32. typewirepy-0.1.0/tests/test_distributed.py +41 -0
  33. typewirepy-0.1.0/tests/test_fastapi.py +136 -0
  34. typewirepy-0.1.0/tests/test_generators.py +85 -0
  35. typewirepy-0.1.0/tests/test_group.py +57 -0
  36. typewirepy-0.1.0/tests/test_import.py +19 -0
  37. typewirepy-0.1.0/tests/test_overrides.py +46 -0
  38. typewirepy-0.1.0/tests/test_sync.py +44 -0
  39. typewirepy-0.1.0/tests/test_use_cases.py +176 -0
  40. typewirepy-0.1.0/tests/test_validation.py +50 -0
  41. typewirepy-0.1.0/tests/test_wire.py +113 -0
  42. 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
@@ -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
@@ -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
+ [![CI](https://github.com/typewirepy/typewirepy/actions/workflows/ci.yml/badge.svg)](https://github.com/typewirepy/typewirepy/actions/workflows/ci.yml)
29
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org)
30
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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