pico-ioc 0.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/__init__.py ADDED
@@ -0,0 +1,213 @@
1
+ # src/pico_ioc/__init__.py
2
+
3
+ import functools, inspect, pkgutil, importlib, logging, sys
4
+ from typing import Callable, Any, Iterator, AsyncIterator
5
+
6
+ try:
7
+ # written at build time by setuptools-scm
8
+ from ._version import __version__
9
+ except Exception: # pragma: no cover
10
+ __version__ = "0.0.0"
11
+
12
+ __all__ = ["__version__"]
13
+
14
+ # ==============================================================================
15
+ # --- 1. Container and Chameleon Proxy (Framework-Agnostic) ---
16
+ # ==============================================================================
17
+ class PicoContainer:
18
+ def __init__(self):
19
+ self._providers = {}
20
+ self._singletons = {}
21
+
22
+ def bind(self, key: Any, provider: Callable[[], Any]):
23
+ self._providers[key] = provider
24
+
25
+ def get(self, key: Any) -> Any:
26
+ if key in self._singletons:
27
+ return self._singletons[key]
28
+ if key in self._providers:
29
+ instance = self._providers[key]()
30
+ self._singletons[key] = instance
31
+ return instance
32
+ raise NameError(f"No provider found for key: {key}")
33
+
34
+ class LazyProxy:
35
+ """
36
+ A full-fledged lazy proxy that delegates almost all operations
37
+ to the real object, which is created only on first access.
38
+ It is completely framework-agnostic.
39
+ """
40
+ def __init__(self, object_creator: Callable[[], Any]):
41
+ object.__setattr__(self, "_object_creator", object_creator)
42
+ object.__setattr__(self, "__real_object", None)
43
+
44
+ def _get_real_object(self) -> Any:
45
+ real_obj = object.__getattribute__(self, "__real_object")
46
+ if real_obj is None:
47
+ real_obj = object.__getattribute__(self, "_object_creator")()
48
+ object.__setattr__(self, "__real_object", real_obj)
49
+ return real_obj
50
+
51
+ # --- Core Proxying and Representation ---
52
+ @property
53
+ def __class__(self):
54
+ return self._get_real_object().__class__
55
+
56
+ def __getattr__(self, name):
57
+ return getattr(self._get_real_object(), name)
58
+
59
+ def __setattr__(self, name, value):
60
+ setattr(self._get_real_object(), name, value)
61
+
62
+ def __delattr__(self, name):
63
+ delattr(self._get_real_object(), name)
64
+
65
+ def __str__(self):
66
+ return str(self._get_real_object())
67
+
68
+ def __repr__(self):
69
+ return repr(self._get_real_object())
70
+
71
+ def __dir__(self):
72
+ return dir(self._get_real_object())
73
+
74
+ # --- Emulation of container types ---
75
+ def __len__(self): return len(self._get_real_object())
76
+ def __getitem__(self, key): return self._get_real_object()[key]
77
+ def __setitem__(self, key, value): self._get_real_object()[key] = value
78
+ def __delitem__(self, key): del self._get_real_object()[key]
79
+ def __iter__(self): return iter(self._get_real_object())
80
+ def __reversed__(self): return reversed(self._get_real_object())
81
+ def __contains__(self, item): return item in self._get_real_object()
82
+
83
+ # --- Emulation of numeric types and operators ---
84
+ def __add__(self, other): return self._get_real_object() + other
85
+ def __sub__(self, other): return self._get_real_object() - other
86
+ def __mul__(self, other): return self._get_real_object() * other
87
+ def __matmul__(self, other): return self._get_real_object() @ other
88
+ def __truediv__(self, other): return self._get_real_object() / other
89
+ def __floordiv__(self, other): return self._get_real_object() // other
90
+ def __mod__(self, other): return self._get_real_object() % other
91
+ def __divmod__(self, other): return divmod(self._get_real_object(), other)
92
+ def __pow__(self, other, modulo=None): return pow(self._get_real_object(), other, modulo)
93
+ def __lshift__(self, other): return self._get_real_object() << other
94
+ def __rshift__(self, other): return self._get_real_object() >> other
95
+ def __and__(self, other): return self._get_real_object() & other
96
+ def __xor__(self, other): return self._get_real_object() ^ other
97
+ def __or__(self, other): return self._get_real_object() | other
98
+
99
+ # --- Right-hand side numeric operators ---
100
+ def __radd__(self, other): return other + self._get_real_object()
101
+ def __rsub__(self, other): return other - self._get_real_object()
102
+ def __rmul__(self, other): return other * self._get_real_object()
103
+ def __rmatmul__(self, other): return other @ self._get_real_object()
104
+ def __rtruediv__(self, other): return other / self._get_real_object()
105
+ def __rfloordiv__(self, other): return other // self._get_real_object()
106
+ def __rmod__(self, other): return other % self._get_real_object()
107
+ def __rdivmod__(self, other): return divmod(other, self._get_real_object())
108
+ def __rpow__(self, other): return pow(other, self._get_real_object())
109
+ def __rlshift__(self, other): return other << self._get_real_object()
110
+ def __rrshift__(self, other): return other >> self._get_real_object()
111
+ def __rand__(self, other): return other & self._get_real_object()
112
+ def __rxor__(self, other): return other ^ self._get_real_object()
113
+ def __ror__(self, other): return other | self._get_real_object()
114
+
115
+ # --- Unary operators ---
116
+ def __neg__(self): return -self._get_real_object()
117
+ def __pos__(self): return +self._get_real_object()
118
+ def __abs__(self): return abs(self._get_real_object())
119
+ def __invert__(self): return ~self._get_real_object()
120
+
121
+ # --- Comparison operators ---
122
+ def __eq__(self, other): return self._get_real_object() == other
123
+ def __ne__(self, other): return self._get_real_object() != other
124
+ def __lt__(self, other): return self._get_real_object() < other
125
+ def __le__(self, other): return self._get_real_object() <= other
126
+ def __gt__(self, other): return self._get_real_object() > other
127
+ def __ge__(self, other): return self._get_real_object() >= other
128
+ def __hash__(self): return hash(self._get_real_object())
129
+
130
+ # --- Truthiness, Callability and Context Management ---
131
+ def __bool__(self): return bool(self._get_real_object())
132
+ def __call__(self, *args, **kwargs): return self._get_real_object()(*args, **kwargs)
133
+ def __enter__(self): return self._get_real_object().__enter__()
134
+ def __exit__(self, exc_type, exc_val, exc_tb): return self._get_real_object().__exit__(exc_type, exc_val, exc_tb)
135
+
136
+ # ==============================================================================
137
+ # --- 2. The Scanner and `init` Facade ---
138
+ # ==============================================================================
139
+ def _scan_and_configure(package_or_name, container: PicoContainer):
140
+ package = importlib.import_module(package_or_name) if isinstance(package_or_name, str) else package_or_name
141
+ logging.info(f"🚀 Scanning in '{package.__name__}'...")
142
+ component_classes, factory_classes = [], []
143
+ for _, name, _ in pkgutil.walk_packages(package.__path__, package.__name__ + '.'):
144
+ try:
145
+ module = importlib.import_module(name)
146
+ for _, obj in inspect.getmembers(module, inspect.isclass):
147
+ if hasattr(obj, '_is_component'):
148
+ component_classes.append(obj)
149
+ elif hasattr(obj, '_is_factory_component'):
150
+ factory_classes.append(obj)
151
+ except Exception as e:
152
+ logging.warning(f" ⚠️ Module {name} not processed: {e}")
153
+
154
+ for factory_cls in factory_classes:
155
+ try:
156
+ sig = inspect.signature(factory_cls.__init__)
157
+ instance = factory_cls(container) if 'container' in sig.parameters else factory_cls()
158
+ for _, method in inspect.getmembers(instance, inspect.ismethod):
159
+ if hasattr(method, '_provides_name'):
160
+ container.bind(getattr(method, '_provides_name'), method)
161
+ except Exception as e:
162
+ logging.error(f" ❌ Error in factory {factory_cls.__name__}: {e}", exc_info=True)
163
+
164
+ for component_cls in component_classes:
165
+ key = getattr(component_cls, '_component_key', component_cls)
166
+ def create_component(cls=component_cls):
167
+ sig = inspect.signature(cls.__init__)
168
+ deps = {}
169
+ for p in sig.parameters.values():
170
+ if p.name == 'self' or p.kind in (
171
+ inspect.Parameter.VAR_POSITIONAL, # *args
172
+ inspect.Parameter.VAR_KEYWORD, # **kwargs
173
+ ):
174
+ continue
175
+ dep_key = p.annotation if p.annotation is not inspect._empty else p.name
176
+ deps[p.name] = container.get(dep_key)
177
+ return cls(**deps)
178
+ container.bind(key, create_component)
179
+
180
+ _container = None
181
+ def init(root_package):
182
+ global _container
183
+ if _container:
184
+ return _container
185
+ _container = PicoContainer()
186
+ logging.info("🔌 Initializing 'pico-ioc'...")
187
+ _scan_and_configure(root_package, _container)
188
+ logging.info("✅ Container configured and ready.")
189
+ return _container
190
+
191
+ # ==============================================================================
192
+ # --- 3. The Decorators ---
193
+ # ==============================================================================
194
+ def factory_component(cls):
195
+ setattr(cls, '_is_factory_component', True)
196
+ return cls
197
+
198
+ def provides(name: str, lazy: bool = True):
199
+ def decorator(func):
200
+ @functools.wraps(func)
201
+ def wrapper(*args, **kwargs):
202
+ return LazyProxy(lambda: func(*args, **kwargs)) if lazy else func(*args, **kwargs)
203
+ setattr(wrapper, '_provides_name', name)
204
+ return wrapper
205
+ return decorator
206
+
207
+ def component(cls=None, *, name: str = None):
208
+ def decorator(cls):
209
+ setattr(cls, '_is_component', True)
210
+ setattr(cls, '_component_key', name if name is not None else cls)
211
+ return cls
212
+ return decorator(cls) if cls else decorator
213
+
pico_ioc/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = '0.1.0'
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: pico-ioc
3
+ Version: 0.1.0
4
+ Summary: A minimalist, zero-dependency Inversion of Control (IoC) container for Python.
5
+ Author-email: David Perez Cabrera <dperezcabrera@gmail.com>
6
+ Project-URL: Homepage, https://github.com/dperezcabrera/pico-ioc
7
+ Project-URL: Repository, https://github.com/dperezcabrera/pico-ioc
8
+ Project-URL: Issue Tracker, https://github.com/dperezcabrera/pico-ioc/issues
9
+ Keywords: ioc,di,dependency injection,inversion of control,decorator
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: OS Independent
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
@@ -0,0 +1,6 @@
1
+ pico_ioc/__init__.py,sha256=SI-ee8rpiNU0f3uTndpTmBmZJ5RZG8MUSGOJXroJfKk,9710
2
+ pico_ioc/_version.py,sha256=IMjkMO3twhQzluVTo8Z6rE7Eg-9U79_LGKMcsWLKBkY,22
3
+ pico_ioc-0.1.0.dist-info/METADATA,sha256=VZHRU852qmS5KyavUYx2DJFjVRurY96bJy_I4qKNOsI,1090
4
+ pico_ioc-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
+ pico_ioc-0.1.0.dist-info/top_level.txt,sha256=_7_RLu616z_dtRw16impXn4Mw8IXe2J4BeX5912m5dQ,9
6
+ pico_ioc-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ pico_ioc