pico-ioc 0.4.0__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pico_ioc-0.5.1/.coveragerc +17 -0
- pico_ioc-0.5.1/.github/workflows/ci.yml +102 -0
- {pico_ioc-0.4.0 → pico_ioc-0.5.1}/.github/workflows/publish-to-pypi.yml +4 -0
- pico_ioc-0.5.1/LICENSE +21 -0
- pico_ioc-0.5.1/MANIFEST.in +24 -0
- pico_ioc-0.5.1/PKG-INFO +284 -0
- pico_ioc-0.5.1/README.md +237 -0
- {pico_ioc-0.4.0 → pico_ioc-0.5.1}/src/pico_ioc/__init__.py +91 -46
- pico_ioc-0.5.1/src/pico_ioc/_version.py +1 -0
- pico_ioc-0.5.1/src/pico_ioc.egg-info/PKG-INFO +284 -0
- {pico_ioc-0.4.0 → pico_ioc-0.5.1}/src/pico_ioc.egg-info/SOURCES.txt +5 -3
- pico_ioc-0.5.1/tests/test_pico_ioc_additional.py +186 -0
- pico_ioc-0.5.1/tox.ini +19 -0
- pico_ioc-0.4.0/.github/workflows/ci.yml +0 -35
- pico_ioc-0.4.0/Dockerfile.test +0 -9
- pico_ioc-0.4.0/Makefile +0 -14
- pico_ioc-0.4.0/PKG-INFO +0 -321
- pico_ioc-0.4.0/README.md +0 -298
- pico_ioc-0.4.0/src/pico_ioc/_version.py +0 -1
- pico_ioc-0.4.0/src/pico_ioc.egg-info/PKG-INFO +0 -321
- pico_ioc-0.4.0/tox.ini +0 -10
- {pico_ioc-0.4.0 → pico_ioc-0.5.1}/pyproject.toml +0 -0
- {pico_ioc-0.4.0 → pico_ioc-0.5.1}/setup.cfg +0 -0
- {pico_ioc-0.4.0 → pico_ioc-0.5.1}/src/pico_ioc.egg-info/dependency_links.txt +0 -0
- {pico_ioc-0.4.0 → pico_ioc-0.5.1}/src/pico_ioc.egg-info/top_level.txt +0 -0
- {pico_ioc-0.4.0 → pico_ioc-0.5.1}/tests/test_pico_ioc.py +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[run]
|
|
2
|
+
branch = True
|
|
3
|
+
source =
|
|
4
|
+
pico_ioc
|
|
5
|
+
omit =
|
|
6
|
+
*/_version.py
|
|
7
|
+
|
|
8
|
+
[paths]
|
|
9
|
+
pico_ioc =
|
|
10
|
+
src/pico_ioc
|
|
11
|
+
.tox/*/lib/*/site-packages/pico_ioc
|
|
12
|
+
.tox/*/site-packages/pico_ioc
|
|
13
|
+
*/site-packages/pico_ioc
|
|
14
|
+
|
|
15
|
+
[report]
|
|
16
|
+
show_missing = True
|
|
17
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
name: CI & Coverage
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
tests:
|
|
11
|
+
name: Tests (Python ${{ matrix.python-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13" ]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
cache: pip
|
|
29
|
+
|
|
30
|
+
- name: Install tox
|
|
31
|
+
run: |
|
|
32
|
+
python -m pip install --upgrade pip
|
|
33
|
+
pip install tox
|
|
34
|
+
|
|
35
|
+
- name: Run tests with coverage (writes to repo root)
|
|
36
|
+
shell: bash
|
|
37
|
+
run: |
|
|
38
|
+
set -euxo pipefail
|
|
39
|
+
tox -e cov
|
|
40
|
+
# Rename to per-version file for artifact + merge job
|
|
41
|
+
mv .coverage .coverage.${{ matrix.python-version }}
|
|
42
|
+
ls -la .coverage.${{ matrix.python-version }}
|
|
43
|
+
|
|
44
|
+
- name: Upload .coverage artifact
|
|
45
|
+
uses: actions/upload-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: coverage-${{ matrix.python-version }}
|
|
48
|
+
path: .coverage.${{ matrix.python-version }}
|
|
49
|
+
include-hidden-files: true
|
|
50
|
+
if-no-files-found: error
|
|
51
|
+
|
|
52
|
+
coverage-merge:
|
|
53
|
+
name: Merge coverage
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
needs: tests
|
|
56
|
+
steps:
|
|
57
|
+
- name: Checkout (for repo context)
|
|
58
|
+
uses: actions/checkout@v4
|
|
59
|
+
|
|
60
|
+
- name: Download all coverage artifacts
|
|
61
|
+
uses: actions/download-artifact@v4
|
|
62
|
+
with:
|
|
63
|
+
path: coverage-reports
|
|
64
|
+
|
|
65
|
+
- name: Combine coverage and generate reports
|
|
66
|
+
shell: bash
|
|
67
|
+
run: |
|
|
68
|
+
set -euxo pipefail
|
|
69
|
+
python -m pip install coverage
|
|
70
|
+
files=$(find coverage-reports -type f -name ".coverage.*" -print)
|
|
71
|
+
if [ -z "$files" ]; then
|
|
72
|
+
echo "No coverage data files found"; exit 1
|
|
73
|
+
fi
|
|
74
|
+
coverage combine $files
|
|
75
|
+
coverage report -m --rcfile=.coveragerc
|
|
76
|
+
coverage xml -o coverage.xml --rcfile=.coveragerc
|
|
77
|
+
coverage html -d htmlcov --rcfile=.coveragerc
|
|
78
|
+
|
|
79
|
+
- name: Upload combined coverage XML
|
|
80
|
+
uses: actions/upload-artifact@v4
|
|
81
|
+
with:
|
|
82
|
+
name: coverage-xml
|
|
83
|
+
path: coverage.xml
|
|
84
|
+
if-no-files-found: error
|
|
85
|
+
|
|
86
|
+
- name: Upload HTML coverage report
|
|
87
|
+
uses: actions/upload-artifact@v4
|
|
88
|
+
with:
|
|
89
|
+
name: coverage-html
|
|
90
|
+
path: htmlcov
|
|
91
|
+
if-no-files-found: error
|
|
92
|
+
|
|
93
|
+
- name: Upload to Codecov
|
|
94
|
+
if: always() && !cancelled()
|
|
95
|
+
uses: codecov/codecov-action@v5
|
|
96
|
+
continue-on-error: true
|
|
97
|
+
with:
|
|
98
|
+
files: coverage.xml
|
|
99
|
+
fail_ci_if_error: false
|
|
100
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
101
|
+
slug: dperezcabrera/pico-ioc
|
|
102
|
+
|
pico_ioc-0.5.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 David Pérez Cabrera
|
|
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,24 @@
|
|
|
1
|
+
# Incluir ficheros importantes
|
|
2
|
+
include README.md
|
|
3
|
+
include LICENSE
|
|
4
|
+
|
|
5
|
+
# Incluir todo el código fuente
|
|
6
|
+
recursive-include src *.py
|
|
7
|
+
|
|
8
|
+
# Incluir datos de tests si quieres que otros puedan correrlos desde el sdist
|
|
9
|
+
# (opcional — puedes quitarlo si no quieres incluir tests en PyPI)
|
|
10
|
+
recursive-include tests *.py
|
|
11
|
+
|
|
12
|
+
# Excluir cosas innecesarias
|
|
13
|
+
exclude .gitignore
|
|
14
|
+
exclude Dockerfile*
|
|
15
|
+
exclude docker-compose*.yml
|
|
16
|
+
exclude Makefile
|
|
17
|
+
|
|
18
|
+
# Excluir carpetas de build, virtualenvs, caches, etc.
|
|
19
|
+
prune build
|
|
20
|
+
prune dist
|
|
21
|
+
prune .tox
|
|
22
|
+
prune .pytest_cache
|
|
23
|
+
prune __pycache__
|
|
24
|
+
|
pico_ioc-0.5.1/PKG-INFO
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pico-ioc
|
|
3
|
+
Version: 0.5.1
|
|
4
|
+
Summary: A minimalist, zero-dependency Inversion of Control (IoC) container for Python.
|
|
5
|
+
Author-email: David Perez Cabrera <dperezcabrera@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 David Pérez Cabrera
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/dperezcabrera/pico-ioc
|
|
29
|
+
Project-URL: Repository, https://github.com/dperezcabrera/pico-ioc
|
|
30
|
+
Project-URL: Issue Tracker, https://github.com/dperezcabrera/pico-ioc/issues
|
|
31
|
+
Keywords: ioc,di,dependency injection,inversion of control,decorator
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
42
|
+
Classifier: Operating System :: OS Independent
|
|
43
|
+
Requires-Python: >=3.8
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
License-File: LICENSE
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
Got it ✅
|
|
49
|
+
Here’s your **updated README.md in full English**, keeping all original sections but now including the **name-first resolution** feature and new tests section.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
````markdown
|
|
54
|
+
# 📦 Pico-IoC: A Minimalist IoC Container for Python
|
|
55
|
+
|
|
56
|
+
[](https://pypi.org/project/pico-ioc/)
|
|
57
|
+
[](https://opensource.org/licenses/MIT)
|
|
58
|
+

|
|
59
|
+
[](https://codecov.io/gh/dperezcabrera/pico-ioc)
|
|
60
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
61
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
62
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
63
|
+
|
|
64
|
+
**Pico-IoC** is a tiny, zero-dependency, decorator-based Inversion of Control container for Python.
|
|
65
|
+
Build loosely-coupled, testable apps without manual wiring. Inspired by the Spring ecosystem.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## ✨ Key Features
|
|
70
|
+
|
|
71
|
+
* **Zero dependencies** — pure Python.
|
|
72
|
+
* **Decorator API** — `@component`, `@factory_component`, `@provides`.
|
|
73
|
+
* **Auto discovery** — scans a package and registers components.
|
|
74
|
+
* **Eager by default, fail-fast** — non-lazy bindings are instantiated immediately after `init()`. Missing deps fail startup.
|
|
75
|
+
* **Opt-in lazy** — set `lazy=True` to defer creation (wrapped in `ComponentProxy`).
|
|
76
|
+
* **Factories** — encapsulate complex creation logic.
|
|
77
|
+
* **Smart resolution order** — **parameter name** takes precedence over **type annotation**, then **MRO fallback**, then **string(name)**.
|
|
78
|
+
* **Re-entrancy guard** — prevents `get()` during scanning.
|
|
79
|
+
* **Auto-exclude caller** — `init()` skips the calling module to avoid double scanning.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 📦 Installation
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pip install pico-ioc
|
|
87
|
+
````
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 🚀 Quick Start
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from pico_ioc import component, init
|
|
95
|
+
|
|
96
|
+
@component
|
|
97
|
+
class AppConfig:
|
|
98
|
+
def get_db_url(self):
|
|
99
|
+
return "postgresql://user:pass@host/db"
|
|
100
|
+
|
|
101
|
+
@component
|
|
102
|
+
class DatabaseService:
|
|
103
|
+
def __init__(self, config: AppConfig):
|
|
104
|
+
self._cs = config.get_db_url()
|
|
105
|
+
def get_data(self):
|
|
106
|
+
return f"Data from {self._cs}"
|
|
107
|
+
|
|
108
|
+
container = init(__name__) # blueprint runs here (eager + fail-fast)
|
|
109
|
+
db = container.get(DatabaseService)
|
|
110
|
+
print(db.get_data())
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 🧩 Custom Component Keys
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from pico_ioc import component, init
|
|
119
|
+
|
|
120
|
+
@component(name="config") # custom key
|
|
121
|
+
class AppConfig:
|
|
122
|
+
db_url = "postgresql://user:pass@localhost/db"
|
|
123
|
+
|
|
124
|
+
@component
|
|
125
|
+
class Repository:
|
|
126
|
+
def __init__(self, config: "config"): # resolve by NAME
|
|
127
|
+
self.url = config.db_url
|
|
128
|
+
|
|
129
|
+
container = init(__name__)
|
|
130
|
+
print(container.get("config").db_url)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 🏭 Factories and `@provides`
|
|
136
|
+
|
|
137
|
+
* Default is **eager** (`lazy=False`). Eager bindings are constructed at the end of `init()`.
|
|
138
|
+
* Use `lazy=True` for on-first-use creation via `ComponentProxy`.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from pico_ioc import factory_component, provides, init
|
|
142
|
+
|
|
143
|
+
COUNTER = {"value": 0}
|
|
144
|
+
|
|
145
|
+
@factory_component
|
|
146
|
+
class ServicesFactory:
|
|
147
|
+
@provides(key="heavy_service", lazy=True)
|
|
148
|
+
def heavy(self):
|
|
149
|
+
COUNTER["value"] += 1
|
|
150
|
+
return {"payload": "hello"}
|
|
151
|
+
|
|
152
|
+
container = init(__name__)
|
|
153
|
+
svc = container.get("heavy_service") # not created yet
|
|
154
|
+
print(COUNTER["value"]) # 0
|
|
155
|
+
print(svc["payload"]) # triggers creation
|
|
156
|
+
print(COUNTER["value"]) # 1
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 🧠 Dependency Resolution Order (Updated in v0.5.0)
|
|
162
|
+
|
|
163
|
+
Starting with **v0.5.0**, Pico-IoC enforces **name-first resolution**:
|
|
164
|
+
|
|
165
|
+
1. **Parameter name** (highest priority)
|
|
166
|
+
2. **Exact type annotation**
|
|
167
|
+
3. **MRO fallback** (walk base classes)
|
|
168
|
+
4. **String(name)**
|
|
169
|
+
|
|
170
|
+
This means that if a dependency could match both by name and type, **the name match wins**.
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from pico_ioc import component, factory_component, provides, init
|
|
176
|
+
|
|
177
|
+
class BaseType: ...
|
|
178
|
+
class Impl(BaseType): ...
|
|
179
|
+
|
|
180
|
+
@component(name="inject_by_name")
|
|
181
|
+
class InjectByName:
|
|
182
|
+
def __init__(self):
|
|
183
|
+
self.value = "by-name"
|
|
184
|
+
|
|
185
|
+
@factory_component
|
|
186
|
+
class NameVsTypeFactory:
|
|
187
|
+
@provides("choose", lazy=True)
|
|
188
|
+
def make(self, inject_by_name, hint: BaseType = None):
|
|
189
|
+
return inject_by_name.value
|
|
190
|
+
|
|
191
|
+
container = init(__name__)
|
|
192
|
+
assert container.get("choose") == "by-name"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## ⚡ Eager vs. Lazy (Blueprint Behavior)
|
|
198
|
+
|
|
199
|
+
At the end of `init()`, Pico-IoC performs a **blueprint**:
|
|
200
|
+
|
|
201
|
+
* **Eager** (`lazy=False`, default): instantiated immediately; failures stop startup.
|
|
202
|
+
* **Lazy** (`lazy=True`): returns a `ComponentProxy`; instantiated on first real use.
|
|
203
|
+
|
|
204
|
+
**Lifecycle:**
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
┌───────────────────────┐
|
|
208
|
+
│ init() │
|
|
209
|
+
└───────────────────────┘
|
|
210
|
+
│
|
|
211
|
+
▼
|
|
212
|
+
┌───────────────────────┐
|
|
213
|
+
│ Scan & bind deps │
|
|
214
|
+
└───────────────────────┘
|
|
215
|
+
│
|
|
216
|
+
▼
|
|
217
|
+
┌─────────────────────────────┐
|
|
218
|
+
│ Blueprint instantiates all │
|
|
219
|
+
│ non-lazy (eager) beans │
|
|
220
|
+
└─────────────────────────────┘
|
|
221
|
+
│
|
|
222
|
+
┌───────────────────────┐
|
|
223
|
+
│ Container ready │
|
|
224
|
+
└───────────────────────┘
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 🛠 API Reference
|
|
230
|
+
|
|
231
|
+
### `init(root, *, exclude=None, auto_exclude_caller=True) -> PicoContainer`
|
|
232
|
+
|
|
233
|
+
Scan and bind components in `root` (str module name or module).
|
|
234
|
+
Skips the calling module if `auto_exclude_caller=True`.
|
|
235
|
+
Runs blueprint (instantiate all `lazy=False` bindings).
|
|
236
|
+
|
|
237
|
+
### `@component(cls=None, *, name=None, lazy=False)`
|
|
238
|
+
|
|
239
|
+
Register a class as a component.
|
|
240
|
+
Use `name` for a custom key.
|
|
241
|
+
Set `lazy=True` to defer creation.
|
|
242
|
+
|
|
243
|
+
### `@factory_component`
|
|
244
|
+
|
|
245
|
+
Mark a class as a component factory (its methods can `@provides` bindings).
|
|
246
|
+
|
|
247
|
+
### `@provides(key, *, lazy=False)`
|
|
248
|
+
|
|
249
|
+
Declare that a factory method provides a component under `key`.
|
|
250
|
+
Set `lazy=True` for deferred creation (`ComponentProxy`).
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 🧪 Testing
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
pip install tox
|
|
258
|
+
tox
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**New in v0.5.0:**
|
|
262
|
+
Additional tests verify:
|
|
263
|
+
|
|
264
|
+
* Name vs. type precedence.
|
|
265
|
+
* Mixed binding key resolution in factories.
|
|
266
|
+
* Eager vs. lazy instantiation edge cases.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 🔌 Extensibility: Plugins, Binder, and Lifecycle Hooks
|
|
271
|
+
|
|
272
|
+
From `v0.4.0` onward, Pico-IoC can be cleanly extended without patching the core.
|
|
273
|
+
|
|
274
|
+
*(plugin API docs unchanged from before)*
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 📜 License
|
|
279
|
+
|
|
280
|
+
MIT — see [LICENSE](https://opensource.org/licenses/MIT)
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
|
pico_ioc-0.5.1/README.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
Got it ✅
|
|
2
|
+
Here’s your **updated README.md in full English**, keeping all original sections but now including the **name-first resolution** feature and new tests section.
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
````markdown
|
|
7
|
+
# 📦 Pico-IoC: A Minimalist IoC Container for Python
|
|
8
|
+
|
|
9
|
+
[](https://pypi.org/project/pico-ioc/)
|
|
10
|
+
[](https://opensource.org/licenses/MIT)
|
|
11
|
+

|
|
12
|
+
[](https://codecov.io/gh/dperezcabrera/pico-ioc)
|
|
13
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
14
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
15
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-ioc)
|
|
16
|
+
|
|
17
|
+
**Pico-IoC** is a tiny, zero-dependency, decorator-based Inversion of Control container for Python.
|
|
18
|
+
Build loosely-coupled, testable apps without manual wiring. Inspired by the Spring ecosystem.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ✨ Key Features
|
|
23
|
+
|
|
24
|
+
* **Zero dependencies** — pure Python.
|
|
25
|
+
* **Decorator API** — `@component`, `@factory_component`, `@provides`.
|
|
26
|
+
* **Auto discovery** — scans a package and registers components.
|
|
27
|
+
* **Eager by default, fail-fast** — non-lazy bindings are instantiated immediately after `init()`. Missing deps fail startup.
|
|
28
|
+
* **Opt-in lazy** — set `lazy=True` to defer creation (wrapped in `ComponentProxy`).
|
|
29
|
+
* **Factories** — encapsulate complex creation logic.
|
|
30
|
+
* **Smart resolution order** — **parameter name** takes precedence over **type annotation**, then **MRO fallback**, then **string(name)**.
|
|
31
|
+
* **Re-entrancy guard** — prevents `get()` during scanning.
|
|
32
|
+
* **Auto-exclude caller** — `init()` skips the calling module to avoid double scanning.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 📦 Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install pico-ioc
|
|
40
|
+
````
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 🚀 Quick Start
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from pico_ioc import component, init
|
|
48
|
+
|
|
49
|
+
@component
|
|
50
|
+
class AppConfig:
|
|
51
|
+
def get_db_url(self):
|
|
52
|
+
return "postgresql://user:pass@host/db"
|
|
53
|
+
|
|
54
|
+
@component
|
|
55
|
+
class DatabaseService:
|
|
56
|
+
def __init__(self, config: AppConfig):
|
|
57
|
+
self._cs = config.get_db_url()
|
|
58
|
+
def get_data(self):
|
|
59
|
+
return f"Data from {self._cs}"
|
|
60
|
+
|
|
61
|
+
container = init(__name__) # blueprint runs here (eager + fail-fast)
|
|
62
|
+
db = container.get(DatabaseService)
|
|
63
|
+
print(db.get_data())
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🧩 Custom Component Keys
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from pico_ioc import component, init
|
|
72
|
+
|
|
73
|
+
@component(name="config") # custom key
|
|
74
|
+
class AppConfig:
|
|
75
|
+
db_url = "postgresql://user:pass@localhost/db"
|
|
76
|
+
|
|
77
|
+
@component
|
|
78
|
+
class Repository:
|
|
79
|
+
def __init__(self, config: "config"): # resolve by NAME
|
|
80
|
+
self.url = config.db_url
|
|
81
|
+
|
|
82
|
+
container = init(__name__)
|
|
83
|
+
print(container.get("config").db_url)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 🏭 Factories and `@provides`
|
|
89
|
+
|
|
90
|
+
* Default is **eager** (`lazy=False`). Eager bindings are constructed at the end of `init()`.
|
|
91
|
+
* Use `lazy=True` for on-first-use creation via `ComponentProxy`.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from pico_ioc import factory_component, provides, init
|
|
95
|
+
|
|
96
|
+
COUNTER = {"value": 0}
|
|
97
|
+
|
|
98
|
+
@factory_component
|
|
99
|
+
class ServicesFactory:
|
|
100
|
+
@provides(key="heavy_service", lazy=True)
|
|
101
|
+
def heavy(self):
|
|
102
|
+
COUNTER["value"] += 1
|
|
103
|
+
return {"payload": "hello"}
|
|
104
|
+
|
|
105
|
+
container = init(__name__)
|
|
106
|
+
svc = container.get("heavy_service") # not created yet
|
|
107
|
+
print(COUNTER["value"]) # 0
|
|
108
|
+
print(svc["payload"]) # triggers creation
|
|
109
|
+
print(COUNTER["value"]) # 1
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 🧠 Dependency Resolution Order (Updated in v0.5.0)
|
|
115
|
+
|
|
116
|
+
Starting with **v0.5.0**, Pico-IoC enforces **name-first resolution**:
|
|
117
|
+
|
|
118
|
+
1. **Parameter name** (highest priority)
|
|
119
|
+
2. **Exact type annotation**
|
|
120
|
+
3. **MRO fallback** (walk base classes)
|
|
121
|
+
4. **String(name)**
|
|
122
|
+
|
|
123
|
+
This means that if a dependency could match both by name and type, **the name match wins**.
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from pico_ioc import component, factory_component, provides, init
|
|
129
|
+
|
|
130
|
+
class BaseType: ...
|
|
131
|
+
class Impl(BaseType): ...
|
|
132
|
+
|
|
133
|
+
@component(name="inject_by_name")
|
|
134
|
+
class InjectByName:
|
|
135
|
+
def __init__(self):
|
|
136
|
+
self.value = "by-name"
|
|
137
|
+
|
|
138
|
+
@factory_component
|
|
139
|
+
class NameVsTypeFactory:
|
|
140
|
+
@provides("choose", lazy=True)
|
|
141
|
+
def make(self, inject_by_name, hint: BaseType = None):
|
|
142
|
+
return inject_by_name.value
|
|
143
|
+
|
|
144
|
+
container = init(__name__)
|
|
145
|
+
assert container.get("choose") == "by-name"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## ⚡ Eager vs. Lazy (Blueprint Behavior)
|
|
151
|
+
|
|
152
|
+
At the end of `init()`, Pico-IoC performs a **blueprint**:
|
|
153
|
+
|
|
154
|
+
* **Eager** (`lazy=False`, default): instantiated immediately; failures stop startup.
|
|
155
|
+
* **Lazy** (`lazy=True`): returns a `ComponentProxy`; instantiated on first real use.
|
|
156
|
+
|
|
157
|
+
**Lifecycle:**
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
┌───────────────────────┐
|
|
161
|
+
│ init() │
|
|
162
|
+
└───────────────────────┘
|
|
163
|
+
│
|
|
164
|
+
▼
|
|
165
|
+
┌───────────────────────┐
|
|
166
|
+
│ Scan & bind deps │
|
|
167
|
+
└───────────────────────┘
|
|
168
|
+
│
|
|
169
|
+
▼
|
|
170
|
+
┌─────────────────────────────┐
|
|
171
|
+
│ Blueprint instantiates all │
|
|
172
|
+
│ non-lazy (eager) beans │
|
|
173
|
+
└─────────────────────────────┘
|
|
174
|
+
│
|
|
175
|
+
┌───────────────────────┐
|
|
176
|
+
│ Container ready │
|
|
177
|
+
└───────────────────────┘
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 🛠 API Reference
|
|
183
|
+
|
|
184
|
+
### `init(root, *, exclude=None, auto_exclude_caller=True) -> PicoContainer`
|
|
185
|
+
|
|
186
|
+
Scan and bind components in `root` (str module name or module).
|
|
187
|
+
Skips the calling module if `auto_exclude_caller=True`.
|
|
188
|
+
Runs blueprint (instantiate all `lazy=False` bindings).
|
|
189
|
+
|
|
190
|
+
### `@component(cls=None, *, name=None, lazy=False)`
|
|
191
|
+
|
|
192
|
+
Register a class as a component.
|
|
193
|
+
Use `name` for a custom key.
|
|
194
|
+
Set `lazy=True` to defer creation.
|
|
195
|
+
|
|
196
|
+
### `@factory_component`
|
|
197
|
+
|
|
198
|
+
Mark a class as a component factory (its methods can `@provides` bindings).
|
|
199
|
+
|
|
200
|
+
### `@provides(key, *, lazy=False)`
|
|
201
|
+
|
|
202
|
+
Declare that a factory method provides a component under `key`.
|
|
203
|
+
Set `lazy=True` for deferred creation (`ComponentProxy`).
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 🧪 Testing
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
pip install tox
|
|
211
|
+
tox
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**New in v0.5.0:**
|
|
215
|
+
Additional tests verify:
|
|
216
|
+
|
|
217
|
+
* Name vs. type precedence.
|
|
218
|
+
* Mixed binding key resolution in factories.
|
|
219
|
+
* Eager vs. lazy instantiation edge cases.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 🔌 Extensibility: Plugins, Binder, and Lifecycle Hooks
|
|
224
|
+
|
|
225
|
+
From `v0.4.0` onward, Pico-IoC can be cleanly extended without patching the core.
|
|
226
|
+
|
|
227
|
+
*(plugin API docs unchanged from before)*
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 📜 License
|
|
232
|
+
|
|
233
|
+
MIT — see [LICENSE](https://opensource.org/licenses/MIT)
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
|