pico-ioc 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl

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/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '1.0.0'
1
+ __version__ = '1.1.0'
pico_ioc/api.py CHANGED
@@ -3,7 +3,7 @@
3
3
  import inspect
4
4
  import logging
5
5
  from contextlib import contextmanager
6
- from typing import Callable, Optional, Tuple
6
+ from typing import Callable, Optional, Tuple, Any, Dict # ⬅️ Any, Dict
7
7
 
8
8
  from .container import PicoContainer, Binder
9
9
  from .plugins import PicoPlugin
@@ -24,14 +24,14 @@ def init(
24
24
  auto_exclude_caller: bool = True,
25
25
  plugins: Tuple[PicoPlugin, ...] = (),
26
26
  reuse: bool = True,
27
+ overrides: Optional[Dict[Any, Any]] = None, # ⬅️ NUEVO
27
28
  ) -> PicoContainer:
28
- """
29
- Initialize and configure a PicoContainer by scanning a root package.
30
- """
29
+
31
30
  root_name = root_package if isinstance(root_package, str) else getattr(root_package, "__name__", None)
32
31
 
33
- # Reuse only if the existing container was built for the same root
34
32
  if reuse and _state._container and _state._root_name == root_name:
33
+ if overrides:
34
+ _apply_overrides(_state._container, overrides)
35
35
  return _state._container
36
36
 
37
37
  combined_exclude = _build_exclude(exclude, auto_exclude_caller, root_name=root_name)
@@ -48,6 +48,9 @@ def init(
48
48
  plugins=plugins,
49
49
  )
50
50
 
51
+ if overrides:
52
+ _apply_overrides(container, overrides)
53
+
51
54
  _run_hooks(plugins, "after_bind", container, binder)
52
55
  _run_hooks(plugins, "before_eager", container, binder)
53
56
 
@@ -57,22 +60,32 @@ def init(
57
60
 
58
61
  logging.info("Container configured and ready.")
59
62
  _state._container = container
60
- _state._root_name = root_name # remember which root this container represents
63
+ _state._root_name = root_name
61
64
  return container
62
65
 
63
66
 
64
67
  # -------------------- helpers --------------------
65
68
 
69
+ def _apply_overrides(container: PicoContainer, overrides: Dict[Any, Any]) -> None:
70
+ for key, val in overrides.items():
71
+ lazy = False
72
+ if isinstance(val, tuple) and len(val) == 2 and callable(val[0]) and isinstance(val[1], bool):
73
+ provider = val[0]
74
+ lazy = val[1]
75
+ elif callable(val):
76
+ provider = val
77
+ else:
78
+ def provider(v=val):
79
+ return v
80
+ container.bind(key, provider, lazy=lazy)
81
+
82
+
66
83
  def _build_exclude(
67
84
  exclude: Optional[Callable[[str], bool]],
68
85
  auto_exclude_caller: bool,
69
86
  *,
70
87
  root_name: Optional[str] = None,
71
88
  ) -> Optional[Callable[[str], bool]]:
72
- """
73
- Compose the exclude predicate. When auto_exclude_caller=True, exclude only
74
- the exact calling module, but never exclude modules under the root being scanned.
75
- """
76
89
  if not auto_exclude_caller:
77
90
  return exclude
78
91
 
@@ -91,7 +104,6 @@ def _build_exclude(
91
104
 
92
105
 
93
106
  def _get_caller_module_name() -> Optional[str]:
94
- """Return the module name that called `init`."""
95
107
  try:
96
108
  f = inspect.currentframe()
97
109
  # frame -> _get_caller_module_name -> _build_exclude -> init
pico_ioc/container.py CHANGED
@@ -28,6 +28,9 @@ class PicoContainer:
28
28
  self._singletons: Dict[Any, Any] = {}
29
29
 
30
30
  def bind(self, key: Any, provider, *, lazy: bool):
31
+ # 🔧 rebind must evict any cached singleton for this key
32
+ self._singletons.pop(key, None)
33
+
31
34
  meta = {"factory": provider, "lazy": bool(lazy)}
32
35
  try:
33
36
  q = getattr(key, QUALIFIERS_KEY, ())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pico-ioc
3
- Version: 1.0.0
3
+ Version: 1.1.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
@@ -72,6 +72,7 @@ It helps you build loosely-coupled, testable apps without manual wiring. Inspire
72
72
  - **Plugins** — lifecycle hooks (`before_scan`, `after_ready`).
73
73
  - **Public API helper** — auto-export decorated symbols in `__init__.py`.
74
74
  - **Thread/async safe** — isolation via `ContextVar`.
75
+ - **Overrides for testing** — inject mocks/fakes directly via `init(overrides={...})`.
75
76
 
76
77
  ---
77
78
 
@@ -117,7 +118,21 @@ print(svc.run())
117
118
  ```
118
119
  fetching from sqlite:///demo.db
119
120
  ```
121
+ ---
122
+
123
+ ### Quick overrides for testing
124
+
125
+ ```python
126
+ from pico_ioc import init
127
+ import myapp
120
128
 
129
+ fake = {"repo": "fake-data"}
130
+ c = init(myapp, overrides={
131
+ "fast_model": fake, # constant instance
132
+ "user_service": lambda: {"id": 1}, # provider
133
+ })
134
+ assert c.get("fast_model") == {"repo": "fake-data"}
135
+ ```
121
136
  ---
122
137
 
123
138
  ## 📖 Documentation
@@ -137,6 +152,12 @@ tox
137
152
 
138
153
  ---
139
154
 
155
+ ## 📜 Changelog
156
+
157
+ See [CHANGELOG.md](./CHANGELOG.md) for version history.
158
+
159
+ ---
160
+
140
161
  ## 📜 License
141
162
 
142
163
  MIT — see [LICENSE](https://opensource.org/licenses/MIT)
@@ -1,8 +1,8 @@
1
1
  pico_ioc/__init__.py,sha256=7EyifZ02LFfhkAR6zzAaQJvByyMI2_-qNrw49gPoJkA,633
2
2
  pico_ioc/_state.py,sha256=KHNtdPrv1s-uynfot2IsNkWotBPyORPCcM2xe9qMMPo,286
3
- pico_ioc/_version.py,sha256=RsZjRjMprNcDm97wqRRSk6rTLgTX8N0GyicZyZ8OsBQ,22
4
- pico_ioc/api.py,sha256=pvzAx725UG5Z6TPqQqiWdfytb0ddk26-cxnkTJRaK8o,3648
5
- pico_ioc/container.py,sha256=81Jys4Ipu57B4pM5xQuq48S5oEhuKin7Y8vEERg-4_4,5085
3
+ pico_ioc/_version.py,sha256=b6-WiVk0Li5MaV2rBnHYl104TsouINojSWKqHDas0js,22
4
+ pico_ioc/api.py,sha256=pInjKJ1oi8bM4N0u4T3XLFa2qmYiHpQ7omcdXFFkGmE,3928
5
+ pico_ioc/container.py,sha256=oyCCeiPFVx7qw51IR9_ReuccInnpYMNuYhKxOKsioYE,5193
6
6
  pico_ioc/decorators.py,sha256=gNPxLFNEdFUFYlxILKjU76XyoPohediK0DNiv-RpK3I,1902
7
7
  pico_ioc/plugins.py,sha256=JbI-28VLGJaik7ysXi3L-YGTGxhqwJH4W5QYuWSruDE,589
8
8
  pico_ioc/proxy.py,sha256=-e3Z9z7Bc_2wxswwUJI_s8AfvCTps8f8RWUJ9RuEp7E,4606
@@ -10,8 +10,8 @@ pico_ioc/public_api.py,sha256=E3sArCoI1xxkIw7xQBvLYAWcIoVJjcq1s0kH-0qIVDE,2383
10
10
  pico_ioc/resolver.py,sha256=PN5uq2dFEStAzzTEl0n4AEyC-k7TowqCu7-uwIDDgEk,3897
11
11
  pico_ioc/scanner.py,sha256=-iOj_qX15IBFmOAFQlP_1rIkBfamGzdqYdvIPOvq7sY,7709
12
12
  pico_ioc/typing_utils.py,sha256=JQ4bkR60pKxFs3f8JlEz41ruKDsWj-SmkKv3DLJriec,950
13
- pico_ioc-1.0.0.dist-info/licenses/LICENSE,sha256=N1_nOvHTM6BobYnOTNXiQkroDqCEi6EzfGBv8lWtyZ0,1077
14
- pico_ioc-1.0.0.dist-info/METADATA,sha256=CZqjp3KYScMMGV25CGrAnDVj37ZbGw3BD9eWC9tjOxw,5434
15
- pico_ioc-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
- pico_ioc-1.0.0.dist-info/top_level.txt,sha256=_7_RLu616z_dtRw16impXn4Mw8IXe2J4BeX5912m5dQ,9
17
- pico_ioc-1.0.0.dist-info/RECORD,,
13
+ pico_ioc-1.1.0.dist-info/licenses/LICENSE,sha256=N1_nOvHTM6BobYnOTNXiQkroDqCEi6EzfGBv8lWtyZ0,1077
14
+ pico_ioc-1.1.0.dist-info/METADATA,sha256=9tZ6dAnCgwLxD2GinaNb3U0FsCTPHTmicJxk9YsFY18,5920
15
+ pico_ioc-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ pico_ioc-1.1.0.dist-info/top_level.txt,sha256=_7_RLu616z_dtRw16impXn4Mw8IXe2J4BeX5912m5dQ,9
17
+ pico_ioc-1.1.0.dist-info/RECORD,,