pico-ioc 0.5.2__tar.gz → 0.6.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 (40) hide show
  1. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/PKG-INFO +17 -5
  2. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/README.md +16 -4
  3. pico_ioc-0.6.0/doc4llm/architecture-pico-ioc.md +288 -0
  4. pico_ioc-0.6.0/doc4llm/usage-patterns.md +198 -0
  5. pico_ioc-0.6.0/src/pico_ioc/__init__.py +27 -0
  6. pico_ioc-0.6.0/src/pico_ioc/_state.py +8 -0
  7. pico_ioc-0.6.0/src/pico_ioc/_version.py +1 -0
  8. pico_ioc-0.6.0/src/pico_ioc/api.py +74 -0
  9. pico_ioc-0.6.0/src/pico_ioc/container.py +43 -0
  10. pico_ioc-0.6.0/src/pico_ioc/decorators.py +33 -0
  11. pico_ioc-0.6.0/src/pico_ioc/plugins.py +12 -0
  12. pico_ioc-0.6.0/src/pico_ioc/proxy.py +77 -0
  13. pico_ioc-0.6.0/src/pico_ioc/resolver.py +58 -0
  14. pico_ioc-0.6.0/src/pico_ioc/scanner.py +105 -0
  15. pico_ioc-0.6.0/src/pico_ioc/typing_utils.py +24 -0
  16. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/src/pico_ioc.egg-info/PKG-INFO +17 -5
  17. pico_ioc-0.6.0/src/pico_ioc.egg-info/SOURCES.txt +35 -0
  18. pico_ioc-0.6.0/tests/test_api_unit.py +123 -0
  19. pico_ioc-0.6.0/tests/test_container_unit.py +125 -0
  20. pico_ioc-0.6.0/tests/test_decorators_unit.py +138 -0
  21. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/tests/test_pico_ioc.py +4 -1
  22. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/tests/test_pico_ioc_additional.py +8 -2
  23. pico_ioc-0.6.0/tests/test_pico_ioc_discovery.py +95 -0
  24. pico_ioc-0.6.0/tests/test_proxy_unit.py +322 -0
  25. pico_ioc-0.6.0/tests/test_resolver_unit.py +162 -0
  26. pico_ioc-0.6.0/tests/test_scanner_unit.py +190 -0
  27. pico_ioc-0.6.0/tests/test_typing_utils_unit.py +102 -0
  28. pico_ioc-0.5.2/src/pico_ioc/__init__.py +0 -408
  29. pico_ioc-0.5.2/src/pico_ioc/_version.py +0 -1
  30. pico_ioc-0.5.2/src/pico_ioc.egg-info/SOURCES.txt +0 -16
  31. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/.coveragerc +0 -0
  32. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/.github/workflows/ci.yml +0 -0
  33. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/.github/workflows/publish-to-pypi.yml +0 -0
  34. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/LICENSE +0 -0
  35. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/MANIFEST.in +0 -0
  36. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/pyproject.toml +0 -0
  37. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/setup.cfg +0 -0
  38. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/src/pico_ioc.egg-info/dependency_links.txt +0 -0
  39. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/src/pico_ioc.egg-info/top_level.txt +0 -0
  40. {pico_ioc-0.5.2 → pico_ioc-0.6.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pico-ioc
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: A minimalist, zero-dependency Inversion of Control (IoC) container for Python.
5
5
  Author-email: David Perez Cabrera <dperezcabrera@gmail.com>
6
6
  License: MIT License
@@ -156,10 +156,10 @@ print(COUNTER["value"]) # 1
156
156
 
157
157
  Starting with **v0.5.0**, Pico-IoC enforces **name-first resolution**:
158
158
 
159
- 1. **Parameter name** (highest priority)
160
- 2. **Exact type annotation**
161
- 3. **MRO fallback** (walk base classes)
162
- 4. **String(name)**
159
+ 1. **Parameter name** (highest priority)
160
+ 2. **Exact type annotation**
161
+ 3. **MRO fallback** (walk base classes)
162
+ 4. **String(name)**
163
163
 
164
164
  This means that if a dependency could match both by name and type, **the name match wins**.
165
165
 
@@ -185,6 +185,18 @@ class NameVsTypeFactory:
185
185
  container = init(__name__)
186
186
  assert container.get("choose") == "by-name"
187
187
  ```
188
+ ---
189
+
190
+ ## 📝 Notes on Annotations (PEP 563)
191
+
192
+ Pico-IoC fully supports **postponed evaluation of annotations**
193
+ (`from __future__ import annotations`, a.k.a. **PEP 563**) in Python 3.8–3.10.
194
+
195
+ * Type hints are evaluated with `typing.get_type_hints` and safely resolved.
196
+ * Missing dependencies always raise a **`NameError`**, never a `TypeError`.
197
+ * Behavior is consistent across Python 3.8+ and Python 3.11+ (where PEP 563 is no longer default).
198
+
199
+ This means you can freely use either direct type hints or string-based annotations in your components and factories, without breaking dependency injection.
188
200
 
189
201
  ---
190
202
 
@@ -109,10 +109,10 @@ print(COUNTER["value"]) # 1
109
109
 
110
110
  Starting with **v0.5.0**, Pico-IoC enforces **name-first resolution**:
111
111
 
112
- 1. **Parameter name** (highest priority)
113
- 2. **Exact type annotation**
114
- 3. **MRO fallback** (walk base classes)
115
- 4. **String(name)**
112
+ 1. **Parameter name** (highest priority)
113
+ 2. **Exact type annotation**
114
+ 3. **MRO fallback** (walk base classes)
115
+ 4. **String(name)**
116
116
 
117
117
  This means that if a dependency could match both by name and type, **the name match wins**.
118
118
 
@@ -138,6 +138,18 @@ class NameVsTypeFactory:
138
138
  container = init(__name__)
139
139
  assert container.get("choose") == "by-name"
140
140
  ```
141
+ ---
142
+
143
+ ## 📝 Notes on Annotations (PEP 563)
144
+
145
+ Pico-IoC fully supports **postponed evaluation of annotations**
146
+ (`from __future__ import annotations`, a.k.a. **PEP 563**) in Python 3.8–3.10.
147
+
148
+ * Type hints are evaluated with `typing.get_type_hints` and safely resolved.
149
+ * Missing dependencies always raise a **`NameError`**, never a `TypeError`.
150
+ * Behavior is consistent across Python 3.8+ and Python 3.11+ (where PEP 563 is no longer default).
151
+
152
+ This means you can freely use either direct type hints or string-based annotations in your components and factories, without breaking dependency injection.
141
153
 
142
154
  ---
143
155
 
@@ -0,0 +1,288 @@
1
+ # Pico-IoC Architecture & LLM Guide
2
+
3
+ ## What is Pico-IoC?
4
+
5
+ Pico-IoC is a tiny, zero-dependency Inversion of Control (IoC) container for Python.
6
+ It discovers components via decorators, wires dependencies automatically, and instantiates everything eagerly by default (fail-fast).
7
+ You can opt into lazy creation via a lightweight proxy.
8
+
9
+ Target Python: 3.8+
10
+ Core design goals: minimal API, predictable resolution, easy testing, framework-agnostic.
11
+
12
+ ---
13
+
14
+ ## Core Concepts (Glossary)
15
+
16
+ * **Container (`PicoContainer`)**
17
+ Holds bindings (providers) and singletons. Keys may be classes or strings.
18
+
19
+ * **Binder (`Binder`)**
20
+ A thin façade for plugins to `bind/has/get` during scan & lifecycle hooks.
21
+
22
+ * **Decorators**
23
+ * `@component(cls=None, *, name=None, lazy=False)` → register a class.
24
+ - Key: class type by default, or `name` (string) if provided.
25
+ - `lazy=True` returns a `ComponentProxy` until first real use.
26
+ * `@factory_component` → register a factory class whose methods can provide components.
27
+ * `@provides(key, *, lazy=False)` → mark a factory method as a provider for `key` (string or type).
28
+
29
+ * **Factory DI**
30
+ * Constructor of a `@factory_component` receives DI like regular components.
31
+ * Each `@provides` method can also have DI in its parameters (by name/type).
32
+ * Defaulted params are **optional**: if not bound, the method uses its default.
33
+
34
+ * **Resolution Order**
35
+ 1. **parameter name**, 2) type annotation, 3) MRO fallback, 4) `str(name)`.
36
+
37
+ * **ComponentProxy**
38
+ A transparent proxy for lazy components that defers creation until first actual interaction.
39
+ Forwards common dunder methods (e.g., `__len__`, `__getitem__`, `__iter__`, `__bool__`, operators, context managers, etc.).
40
+
41
+ * **Re-entrancy Guard**
42
+ Accessing `container.get(...)` during package scanning raises a clear error (prevents re-entrant use).
43
+
44
+ ---
45
+
46
+ ## Lifecycle
47
+
48
+ 1. **init(root, …)**
49
+ * Scans `root` package (string/module).
50
+ * Auto-excludes the calling module by default (`auto_exclude_caller=True`).
51
+ * Calls plugin hooks (`before_scan`, `visit_class`, `after_scan`, `after_bind`, `before_eager`, `after_ready`).
52
+ * **Binding order**: components first, then factories.
53
+ * **Blueprint**: eagerly instantiate all non-lazy bindings. Errors fail startup.
54
+
55
+ 2. **get(key)**
56
+ Returns the singleton instance for `key` (creates if needed).
57
+ For lazy bindings, returns a proxy first.
58
+
59
+ ---
60
+
61
+ ## How Pico-IoC Resolves Dependencies
62
+
63
+ Given a constructor or provider method parameter `p`:
64
+
65
+ * If a binding exists for the **parameter name**, use it.
66
+ * Else if the **type annotation** is bound, use it.
67
+ * Else try **MRO** (walk base classes) and use the first bound base type.
68
+ * Else if `str(name)` is bound, use that.
69
+ * Else: raise `NameError("No provider found for key: …")`.
70
+
71
+ **Optional defaulted params**: If resolution fails and the param has a default (e.g., `hint: T = None`), the resolver **omits the argument** so Python uses the default.
72
+
73
+ ---
74
+
75
+ ## Quick Recipes
76
+
77
+ ### 1) Basic components
78
+ ```python
79
+ from pico_ioc import component, init
80
+
81
+ @component
82
+ class Config:
83
+ url = "postgresql://…"
84
+
85
+ @component
86
+ class Repo:
87
+ def __init__(self, config: Config): # type-based DI
88
+ self.url = config.url
89
+
90
+ c = init(__name__)
91
+ repo = c.get(Repo)
92
+ ````
93
+
94
+ ### 2) Inject by name
95
+
96
+ ```python
97
+ from pico_ioc import component
98
+
99
+ @component(name="fast_model")
100
+ class Model:
101
+ pass
102
+
103
+ @component
104
+ class NeedsByName:
105
+ def __init__(self, fast_model): # name-based DI (highest priority)
106
+ self.m = fast_model
107
+ ```
108
+
109
+ ### 3) Factory with lazy provider and provider-param DI
110
+
111
+ ```python
112
+ from pico_ioc import factory_component, provides, component
113
+
114
+ @component
115
+ class Counter:
116
+ def __init__(self): self.value = 0
117
+
118
+ @factory_component
119
+ class Factory:
120
+ @provides("dataset", lazy=True)
121
+ def make_dataset(self, counter: Counter): # DI into provider method
122
+ return list(range(counter.value, counter.value + 3))
123
+ ```
124
+
125
+ ### 4) Static/class methods as providers
126
+
127
+ ```python
128
+ @factory_component
129
+ class F:
130
+ @staticmethod
131
+ @provides("static_result", lazy=True)
132
+ def make_static():
133
+ return "ok"
134
+
135
+ @classmethod
136
+ @provides("class_result")
137
+ def make_class(cls):
138
+ return "ok"
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Error Messages & What They Mean
144
+
145
+ * **`NameError: No provider found for key: ...`**
146
+ A dependency couldn’t be resolved. Check the key being looked up:
147
+
148
+ * For name-based DI: ensure a binding with that exact name exists (`@component(name=...)` or `@provides("name")`).
149
+ * For type-based DI: ensure that class (or a base in its MRO) is provided.
150
+
151
+ * **`RuntimeError: pico-ioc: re-entrant container access during scan.`**
152
+ Something called `get()` while scanning modules (e.g., at import time).
153
+ Move that call out of module top-level or mark the dependent thing `lazy=True` and defer access.
154
+
155
+ ---
156
+
157
+ ## Plugins & Extensions
158
+
159
+ * Implement any subset of:
160
+
161
+ * `before_scan(package, binder)`
162
+ * `visit_class(module, cls, binder)`
163
+ * `after_scan(package, binder)`
164
+ * `after_bind(container, binder)`
165
+ * `before_eager(container, binder)`
166
+ * `after_ready(container, binder)`
167
+
168
+ * The `Binder` lets you register extra bindings, e.g.:
169
+
170
+ ```python
171
+ class MarkerPlugin:
172
+ def visit_class(self, module, cls, binder):
173
+ if cls.__name__ == "SpecialService" and not binder.has("marker"):
174
+ binder.bind("marker", lambda: {"ok": True}, lazy=False)
175
+ ```
176
+
177
+ * Public helpers (also useful for plugins):
178
+
179
+ * `create_instance(cls, container)` – construct with DI & precedence rules.
180
+ * `resolve_param(container, inspect.Parameter)` – resolve a single param.
181
+
182
+ ---
183
+
184
+ ## Internals (Stable Behaviors)
185
+
186
+ * **Singletons**: All components/providers default to singletons (first creation cached).
187
+ * **Binding Order**: Components are bound **before** factories so factory constructors can be injected.
188
+ * **Eager by default**: `lazy=False` (default) is instantiated at the end of `init()`.
189
+ * **Lazy**: `lazy=True` yields a `ComponentProxy`; realization occurs on first real interaction.
190
+ * **Provider DI**: Provider methods can declare DI params; defaults make them optional.
191
+ * **Thread safety**: Scanning and resolution use `ContextVar`, isolating state per thread/async task.
192
+
193
+ ---
194
+
195
+ ## Troubleshooting Playbook (for an LLM)
196
+
197
+ When a user asks for help:
198
+
199
+ 1. **Identify binding key**
200
+
201
+ * If they mention a quoted name, assume **string key**.
202
+ * If they mention a class, assume **type key**.
203
+
204
+ 2. **Check resolution path**
205
+
206
+ * Name → Type → MRO → `str(name)`.
207
+
208
+ 3. **Suggest fixes**
209
+
210
+ * For missing name: `@component(name="…")` or a `@provides("…")`.
211
+ * For type: ensure class is scanned or provided by a factory.
212
+ * For optional hint (`param: T = None`): remind that defaults are optional.
213
+
214
+ 4. **Re-entrancy**
215
+
216
+ * If errors mention “re-entrant during scan”, move `get()` out of import time.
217
+
218
+ 5. **Factories**
219
+
220
+ * Factory constructor and provider params both support DI.
221
+ * Lazy provider builds only on first use; eager builds at `init()`.
222
+
223
+ 6. **Testing advice**
224
+
225
+ * For missing dep scenarios, mark component/provider `lazy=True` and force realization in the test.
226
+
227
+ ---
228
+
229
+ ## Anti-Patterns to Avoid
230
+
231
+ * Calling `container.get(...)` at module import time in scanned packages.
232
+ * Expecting type-based injection when a **name** binding exists (name wins).
233
+ * Providers that create new instances each call (Pico-IoC expects singletons; use callables if you need factories).
234
+
235
+ ---
236
+
237
+ ## Example: Minimal App Layout
238
+
239
+ ```
240
+ myapp/
241
+ __init__.py
242
+ services/
243
+ __init__.py
244
+ components.py # @component classes
245
+ factories.py # @factory_component classes + @provides methods
246
+ ```
247
+
248
+ ```python
249
+ # bootstrap.py
250
+ import myapp
251
+ from pico_ioc import init
252
+
253
+ container = init(myapp)
254
+ svc = container.get("heavy_service")
255
+ ```
256
+
257
+ ---
258
+
259
+ ## FAQ (short)
260
+
261
+ * **Can I bind primitives or dicts?** Yes, via `@provides("name")` returning e.g. a dict.
262
+ * **Thread safety?** Yes. Singletons are created once per container. ContextVar ensures safe state isolation.
263
+ * **How do I exclude modules from scanning?** Use `init(root, exclude=callable)`.
264
+ * **Can I scan by module string?** Yes: `init("myapp.services")`.
265
+
266
+ ---
267
+
268
+ ## Version Notes Relevant to LLM Answers
269
+
270
+ * **v0.6.0**: `ComponentProxy` made fully transparent — supports operators, context managers, and more dunder methods.
271
+ * **v0.5.2**: Name-first resolution; defaulted params in constructors and provider methods are optional.
272
+ * Earlier versions: basic DI without name-first precedence.
273
+
274
+ ---
275
+
276
+ ## How to “Think” When Explaining Usage (LLM prompting tips)
277
+
278
+ * Be explicit about the **binding key** (type vs string).
279
+ * Map constructor/provider params to resolution rules.
280
+ * Show tiny, runnable snippets (no external deps).
281
+ * Prefer **lazy** for failure demos; prefer **eager** for production.
282
+ * If user hits `NameError`, propose concrete fixes (name, type, factory).
283
+ * Don’t promise async/background work — stick to code-level fixes.
284
+
285
+ ---
286
+
287
+ End of document.
288
+
@@ -0,0 +1,198 @@
1
+ # Usage Patterns for pico-ioc (LLM Guidance)
2
+
3
+ This document provides examples of typical questions and answers an LLM should handle when assisting developers with **pico-ioc**.
4
+ The goal is to give consistent, concise, and technically accurate responses.
5
+
6
+ ---
7
+
8
+ ## 1. General Questions
9
+
10
+ **Q:** What is pico-ioc?
11
+ **A:** pico-ioc is a lightweight Inversion of Control (IoC) and Dependency Injection (DI) container for Python.
12
+ It uses decorators (`@component`, `@factory_component`, `@provides`) to register dependencies and automatically resolve them at runtime.
13
+ It is inspired by frameworks like Spring (Java) but designed to be minimal, fast, and Pythonic.
14
+
15
+ ---
16
+
17
+ ## 2. How to Start
18
+
19
+ **Q:** How do I initialize pico-ioc in my project?
20
+ **A:**
21
+
22
+ ```python
23
+ import pico_ioc
24
+ import myapp
25
+
26
+ container = pico_ioc.init(myapp)
27
+ service = container.get(MyService)
28
+ ```
29
+
30
+ This scans the `myapp` package for components and wires them automatically.
31
+
32
+ ---
33
+
34
+ ## 3. Registering Components
35
+
36
+ **Q:** How do I register a class as a component?
37
+ **A:**
38
+
39
+ ```python
40
+ from pico_ioc import component
41
+
42
+ @component
43
+ class MyService:
44
+ def greet(self):
45
+ return "Hello from MyService!"
46
+ ```
47
+
48
+ ---
49
+
50
+ ## 4. Using Factories
51
+
52
+ **Q:** How do I create instances using a factory?
53
+ **A:**
54
+
55
+ ```python
56
+ from pico_ioc import factory_component, provides
57
+
58
+ class MyService:
59
+ def greet(self):
60
+ return "Hello!"
61
+
62
+ @factory_component
63
+ class MyFactory:
64
+ @provides("my_service")
65
+ def build_service(self) -> MyService:
66
+ return MyService()
67
+ ```
68
+
69
+ Factories let you encapsulate creation logic. Each `@provides` method binds a key to the produced object.
70
+
71
+ ---
72
+
73
+ ## 5. Providing Interfaces
74
+
75
+ **Q:** How do I bind an interface to an implementation?
76
+ **A:**
77
+
78
+ ```python
79
+ from pico_ioc import provides
80
+
81
+ class Storage:
82
+ ...
83
+
84
+ class FileStorage(Storage):
85
+ ...
86
+
87
+ @provides(Storage)
88
+ def provide_storage() -> Storage:
89
+ return FileStorage()
90
+ ```
91
+
92
+ Now any class that requires `Storage` will receive a `FileStorage`.
93
+
94
+ ---
95
+
96
+ ## 6. Resolving Dependencies
97
+
98
+ **Q:** How are constructor parameters injected?
99
+ **A:** pico-ioc inspects type hints in constructors and provides the required dependencies.
100
+
101
+ ```python
102
+ @component
103
+ class UserService:
104
+ def __init__(self, storage: Storage):
105
+ self.storage = storage
106
+ ```
107
+
108
+ When `UserService` is requested, pico-ioc automatically injects a `Storage`.
109
+
110
+ ---
111
+
112
+ ## 7. Thread Safety
113
+
114
+ **Q:** Is pico-ioc thread-safe?
115
+ **A:** Yes. Dependency resolution and scanning states are tracked using `ContextVar`, which isolates these flags per thread and per async task. This ensures safe operation in multithreaded and asynchronous environments without shared-state conflicts.
116
+
117
+ ---
118
+
119
+ ## 8. Lazy vs. Eager Components
120
+
121
+ By default, bindings are **eager** (instantiated immediately).
122
+ If `lazy=True` is passed, pico-ioc returns a `ComponentProxy` instead. The proxy defers creation until the first real use.
123
+
124
+ ```python
125
+ from pico_ioc import component
126
+
127
+ @component(lazy=True)
128
+ class HeavyService:
129
+ def __init__(self):
130
+ print("Expensive init...")
131
+ self.value = 42
132
+
133
+ container = pico_ioc.init(__name__)
134
+ svc = container.get(HeavyService) # not yet created
135
+ print(svc.value) # triggers creation
136
+ ```
137
+
138
+ **Note (since v0.6.0):** `ComponentProxy` fully forwards operators, attribute access, context manager protocols, etc., making lazy components behave transparently like the real object.
139
+
140
+ ---
141
+
142
+ ## 9. Best Practices
143
+
144
+ * Always use type hints so dependencies can be resolved correctly.
145
+ * Prefer `@component` for classes and `@provides` for interfaces.
146
+ * Use factories (`@factory_component` + `@provides`) for advanced creation logic.
147
+ * Avoid global state; let the container manage lifecycle.
148
+ * Use `init(package)` at the root of your application to wire everything automatically.
149
+ * Consider `lazy=True` for heavy or rarely used services.
150
+
151
+ ---
152
+
153
+ ## 10. Example Flow
154
+
155
+ ```python
156
+ import pico_ioc
157
+ from pico_ioc import component
158
+
159
+ @component
160
+ class Repo:
161
+ def fetch(self):
162
+ return "data"
163
+
164
+ @component
165
+ class Service:
166
+ def __init__(self, repo: Repo):
167
+ self.repo = repo
168
+
169
+ def run(self):
170
+ return f"Service using {self.repo.fetch()}"
171
+
172
+ # Bootstrap
173
+ import myapp
174
+ container = pico_ioc.init(myapp)
175
+ svc = container.get(Service)
176
+ print(svc.run())
177
+ ```
178
+
179
+ **Expected output:**
180
+
181
+ ```
182
+ Service using data
183
+ ```
184
+
185
+ ---
186
+
187
+ ## 11. Troubleshooting
188
+
189
+ * **Error:** "Cannot resolve parameter" → Add proper type hints.
190
+ * **Error:** "No provider found" → Ensure the class/function is decorated with `@component` or `@provides`.
191
+ * **Unexpected singleton reuse** → Check if you intended a new instance vs. a shared one.
192
+ * **Lazy service not instantiated** → Remember: `lazy=True` returns a proxy, actual creation happens only on first real usage.
193
+
194
+ ---
195
+
196
+ ✅ With these usage patterns, an LLM should be able to explain **how pico-ioc works**, provide **practical examples**, and help users debug typical problems.
197
+ **New in v0.6.0:** transparent `ComponentProxy` support for all Python protocols, making lazy components nearly indistinguishable from eager ones.
198
+
@@ -0,0 +1,27 @@
1
+ from ._version import __version__
2
+ from .container import PicoContainer, Binder
3
+ from .decorators import component, factory_component, provides
4
+ from .plugins import PicoPlugin
5
+ from .resolver import Resolver
6
+ from .api import init, reset
7
+ from .proxy import ComponentProxy
8
+
9
+ try:
10
+ from ._version import __version__
11
+ except Exception:
12
+ __version__ = "0.0.0"
13
+
14
+ __all__ = [
15
+ "__version__",
16
+ "PicoContainer",
17
+ "Binder",
18
+ "PicoPlugin",
19
+ "init",
20
+ "reset",
21
+ "component",
22
+ "factory_component",
23
+ "provides",
24
+ "resolve_param",
25
+ "create_instance",
26
+ ]
27
+
@@ -0,0 +1,8 @@
1
+ # pico_ioc/_state.py
2
+ from contextvars import ContextVar
3
+
4
+ _scanning: ContextVar[bool] = ContextVar("pico_scanning", default=False)
5
+ _resolving: ContextVar[bool] = ContextVar("pico_resolving", default=False)
6
+
7
+ _container = None
8
+
@@ -0,0 +1 @@
1
+ __version__ = '0.6.0'
@@ -0,0 +1,74 @@
1
+ import inspect
2
+ import logging
3
+ from typing import Callable, Optional, Tuple
4
+ from .container import PicoContainer, Binder
5
+ from .plugins import PicoPlugin
6
+ from .scanner import scan_and_configure
7
+ from . import _state
8
+
9
+
10
+ def reset() -> None:
11
+ _state._container = None
12
+
13
+
14
+ def init(
15
+ root_package,
16
+ *,
17
+ exclude: Optional[Callable[[str], bool]] = None,
18
+ auto_exclude_caller: bool = True,
19
+ plugins: Tuple[PicoPlugin, ...] = (),
20
+ reuse: bool = True,
21
+ ) -> PicoContainer:
22
+ if reuse and _state._container:
23
+ return _state._container
24
+
25
+ combined_exclude = exclude
26
+ if auto_exclude_caller:
27
+ try:
28
+ caller_frame = inspect.stack()[1].frame
29
+ caller_module = inspect.getmodule(caller_frame)
30
+ caller_name = getattr(caller_module, "__name__", None)
31
+ except Exception:
32
+ caller_name = None
33
+ if caller_name:
34
+ if combined_exclude is None:
35
+ def combined_exclude(mod: str, _caller=caller_name):
36
+ return mod == _caller
37
+ else:
38
+ prev = combined_exclude
39
+ def combined_exclude(mod: str, _caller=caller_name, _prev=prev):
40
+ return mod == _caller or _prev(mod)
41
+
42
+ container = PicoContainer()
43
+ binder = Binder(container)
44
+ logging.info("Initializing pico-ioc...")
45
+
46
+ tok = _state._scanning.set(True)
47
+ try:
48
+ scan_and_configure(root_package, container, exclude=combined_exclude, plugins=plugins)
49
+ finally:
50
+ _state._scanning.reset(tok)
51
+
52
+ for pl in plugins:
53
+ try:
54
+ getattr(pl, "after_bind", lambda *a, **k: None)(container, binder)
55
+ except Exception:
56
+ logging.exception("Plugin after_bind failed")
57
+ for pl in plugins:
58
+ try:
59
+ getattr(pl, "before_eager", lambda *a, **k: None)(container, binder)
60
+ except Exception:
61
+ logging.exception("Plugin before_eager failed")
62
+
63
+ container.eager_instantiate_all()
64
+
65
+ for pl in plugins:
66
+ try:
67
+ getattr(pl, "after_ready", lambda *a, **k: None)(container, binder)
68
+ except Exception:
69
+ logging.exception("Plugin after_ready failed")
70
+
71
+ logging.info("Container configured and ready.")
72
+ _state._container = container
73
+ return container
74
+