dioxide 0.0.2a1__cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.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.
dioxide/__init__.py ADDED
@@ -0,0 +1,51 @@
1
+ """dioxide: Fast, Rust-backed declarative dependency injection for Python.
2
+
3
+ dioxide is a modern dependency injection framework that combines:
4
+ - Declarative Python API with @component decorators
5
+ - High-performance Rust-backed container implementation
6
+ - Type-safe dependency resolution with IDE autocomplete support
7
+ - Support for SINGLETON and FACTORY component lifecycles
8
+
9
+ Quick Start (using global singleton container):
10
+ >>> from dioxide import container, component
11
+ >>>
12
+ >>> @component
13
+ ... class Database:
14
+ ... pass
15
+ >>>
16
+ >>> @component
17
+ ... class UserService:
18
+ ... def __init__(self, db: Database):
19
+ ... self.db = db
20
+ >>>
21
+ >>> container.scan()
22
+ >>> service = container.resolve(UserService)
23
+ >>> # Or use bracket syntax:
24
+ >>> service = container[UserService]
25
+ >>> assert isinstance(service.db, Database)
26
+
27
+ Advanced: Creating separate containers for testing isolation:
28
+ >>> from dioxide import Container, component
29
+ >>>
30
+ >>> test_container = Container()
31
+ >>> test_container.scan()
32
+ >>> service = test_container.resolve(UserService)
33
+
34
+ For more information, see the README and documentation.
35
+ """
36
+
37
+ from .container import Container, container
38
+ from .decorators import _clear_registry, _get_registered_components, component
39
+ from .profile import profile
40
+ from .scope import Scope
41
+
42
+ __version__ = '0.1.0'
43
+ __all__ = [
44
+ 'Container',
45
+ 'Scope',
46
+ '_clear_registry',
47
+ '_get_registered_components',
48
+ 'component',
49
+ 'container',
50
+ 'profile',
51
+ ]
Binary file
@@ -0,0 +1,18 @@
1
+ """Type stubs for Rust core module."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import TypeVar
5
+
6
+ T = TypeVar('T')
7
+
8
+ class Container:
9
+ """Rust-backed container implementation."""
10
+
11
+ def __init__(self) -> None: ...
12
+ def register_instance(self, py_type: type[T], instance: T) -> None: ...
13
+ def register_class(self, py_type: type[T], implementation: type[T]) -> None: ...
14
+ def register_singleton_factory(self, py_type: type[T], factory: Callable[[], T]) -> None: ...
15
+ def register_transient_factory(self, py_type: type[T], factory: Callable[[], T]) -> None: ...
16
+ def resolve(self, py_type: type[T]) -> T: ...
17
+ def is_empty(self) -> bool: ...
18
+ def __len__(self) -> int: ...
dioxide/container.py ADDED
@@ -0,0 +1,606 @@
1
+ """Dependency injection container.
2
+
3
+ The Container class is the heart of dioxide's dependency injection system.
4
+ It manages component registration, dependency resolution, and lifecycle scopes.
5
+ The container supports both automatic discovery via @component decorators and
6
+ manual registration for fine-grained control.
7
+ """
8
+
9
+ import inspect
10
+ from collections.abc import Callable
11
+ from typing import Any, TypeVar, get_type_hints
12
+
13
+ from dioxide._dioxide_core import Container as RustContainer
14
+
15
+ T = TypeVar('T')
16
+
17
+
18
+ class Container:
19
+ """Dependency injection container.
20
+
21
+ The Container manages component registration and dependency resolution
22
+ for your application. It supports both automatic discovery via the
23
+ @component decorator and manual registration for fine-grained control.
24
+
25
+ The container is backed by a high-performance Rust implementation that
26
+ handles provider caching, singleton management, and type resolution.
27
+
28
+ Features:
29
+ - Type-safe dependency resolution with full IDE support
30
+ - Automatic dependency injection based on type hints
31
+ - SINGLETON and FACTORY lifecycle scopes
32
+ - Thread-safe singleton caching (Rust-backed)
33
+ - Automatic discovery via @component decorator
34
+ - Manual registration for non-decorated classes
35
+
36
+ Examples:
37
+ Automatic discovery with @component:
38
+ >>> from dioxide import Container, component
39
+ >>>
40
+ >>> @component
41
+ ... class Database:
42
+ ... def query(self, sql):
43
+ ... return f'Executing: {sql}'
44
+ >>>
45
+ >>> @component
46
+ ... class UserService:
47
+ ... def __init__(self, db: Database):
48
+ ... self.db = db
49
+ >>>
50
+ >>> container = Container()
51
+ >>> container.scan() # Auto-discover @component classes
52
+ >>> service = container.resolve(UserService)
53
+ >>> result = service.db.query('SELECT * FROM users')
54
+
55
+ Manual registration:
56
+ >>> from dioxide import Container
57
+ >>>
58
+ >>> class Config:
59
+ ... def __init__(self, env: str):
60
+ ... self.env = env
61
+ >>>
62
+ >>> container = Container()
63
+ >>> container.register_singleton(Config, lambda: Config('production'))
64
+ >>> config = container.resolve(Config)
65
+ >>> assert config.env == 'production'
66
+
67
+ Factory scope for per-request objects:
68
+ >>> from dioxide import Container, component, Scope
69
+ >>>
70
+ >>> @component(scope=Scope.FACTORY)
71
+ ... class RequestContext:
72
+ ... def __init__(self):
73
+ ... self.id = id(self)
74
+ >>>
75
+ >>> container = Container()
76
+ >>> container.scan()
77
+ >>> ctx1 = container.resolve(RequestContext)
78
+ >>> ctx2 = container.resolve(RequestContext)
79
+ >>> assert ctx1 is not ctx2 # Different instances
80
+
81
+ Note:
82
+ The container should be created once at application startup and
83
+ reused throughout the application lifecycle. Each container maintains
84
+ its own singleton cache and registration state.
85
+ """
86
+
87
+ def __init__(self) -> None:
88
+ """Initialize a new dependency injection container.
89
+
90
+ Creates a new container with an empty registry. The container is
91
+ ready to accept registrations via scan() for @component classes
92
+ or via manual registration methods.
93
+
94
+ Example:
95
+ >>> from dioxide import Container
96
+ >>> container = Container()
97
+ >>> assert container.is_empty()
98
+ """
99
+ self._rust_core = RustContainer()
100
+
101
+ def register_instance(self, component_type: type[T], instance: T) -> None:
102
+ """Register a pre-created instance for a given type.
103
+
104
+ This method registers an already-instantiated object that will be
105
+ returned whenever the type is resolved. Useful for registering
106
+ configuration objects or external dependencies.
107
+
108
+ Args:
109
+ component_type: The type to register. This is used as the lookup
110
+ key when resolving dependencies.
111
+ instance: The pre-created instance to return for this type. Must
112
+ be an instance of component_type or a compatible type.
113
+
114
+ Raises:
115
+ KeyError: If the type is already registered in this container.
116
+ Each type can only be registered once.
117
+
118
+ Example:
119
+ >>> from dioxide import Container
120
+ >>>
121
+ >>> class Config:
122
+ ... def __init__(self, debug: bool):
123
+ ... self.debug = debug
124
+ >>>
125
+ >>> container = Container()
126
+ >>> config_instance = Config(debug=True)
127
+ >>> container.register_instance(Config, config_instance)
128
+ >>> resolved = container.resolve(Config)
129
+ >>> assert resolved is config_instance
130
+ >>> assert resolved.debug is True
131
+ """
132
+ self._rust_core.register_instance(component_type, instance)
133
+
134
+ def register_class(self, component_type: type[T], implementation: type[T]) -> None:
135
+ """Register a class to instantiate for a given type.
136
+
137
+ Registers a class that will be instantiated with no arguments when
138
+ the type is resolved. The class's __init__ method will be called
139
+ without parameters.
140
+
141
+ Args:
142
+ component_type: The type to register. This is used as the lookup
143
+ key when resolving dependencies.
144
+ implementation: The class to instantiate. Must have a no-argument
145
+ __init__ method (or no __init__ at all).
146
+
147
+ Raises:
148
+ KeyError: If the type is already registered in this container.
149
+
150
+ Example:
151
+ >>> from dioxide import Container
152
+ >>>
153
+ >>> class DatabaseConnection:
154
+ ... def __init__(self):
155
+ ... self.connected = True
156
+ >>>
157
+ >>> container = Container()
158
+ >>> container.register_class(DatabaseConnection, DatabaseConnection)
159
+ >>> db = container.resolve(DatabaseConnection)
160
+ >>> assert db.connected is True
161
+
162
+ Note:
163
+ For classes requiring constructor arguments, use
164
+ register_singleton_factory() or register_transient_factory()
165
+ with a lambda that provides the arguments.
166
+ """
167
+ self._rust_core.register_class(component_type, implementation)
168
+
169
+ def register_singleton_factory(self, component_type: type[T], factory: Callable[[], T]) -> None:
170
+ """Register a singleton factory function for a given type.
171
+
172
+ The factory will be called once when the type is first resolved,
173
+ and the result will be cached. All subsequent resolve() calls for
174
+ this type will return the same cached instance.
175
+
176
+ Args:
177
+ component_type: The type to register. This is used as the lookup
178
+ key when resolving dependencies.
179
+ factory: A callable that takes no arguments and returns an instance
180
+ of component_type. Called exactly once, on first resolve().
181
+
182
+ Raises:
183
+ KeyError: If the type is already registered in this container.
184
+
185
+ Example:
186
+ >>> from dioxide import Container
187
+ >>>
188
+ >>> class ExpensiveService:
189
+ ... def __init__(self, config_path: str):
190
+ ... self.config_path = config_path
191
+ ... self.initialized = True
192
+ >>>
193
+ >>> container = Container()
194
+ >>> container.register_singleton_factory(ExpensiveService, lambda: ExpensiveService('/etc/config.yaml'))
195
+ >>> service1 = container.resolve(ExpensiveService)
196
+ >>> service2 = container.resolve(ExpensiveService)
197
+ >>> assert service1 is service2 # Same instance
198
+
199
+ Note:
200
+ This is the recommended registration method for most services,
201
+ as it provides lazy initialization and instance sharing.
202
+ """
203
+ self._rust_core.register_singleton_factory(component_type, factory)
204
+
205
+ def register_transient_factory(self, component_type: type[T], factory: Callable[[], T]) -> None:
206
+ """Register a transient factory function for a given type.
207
+
208
+ The factory will be called every time the type is resolved, creating
209
+ a new instance for each resolve() call. Use this for stateful objects
210
+ that should not be shared.
211
+
212
+ Args:
213
+ component_type: The type to register. This is used as the lookup
214
+ key when resolving dependencies.
215
+ factory: A callable that takes no arguments and returns an instance
216
+ of component_type. Called on every resolve() to create a fresh
217
+ instance.
218
+
219
+ Raises:
220
+ KeyError: If the type is already registered in this container.
221
+
222
+ Example:
223
+ >>> from dioxide import Container
224
+ >>>
225
+ >>> class RequestHandler:
226
+ ... _counter = 0
227
+ ...
228
+ ... def __init__(self):
229
+ ... RequestHandler._counter += 1
230
+ ... self.request_id = RequestHandler._counter
231
+ >>>
232
+ >>> container = Container()
233
+ >>> container.register_transient_factory(RequestHandler, lambda: RequestHandler())
234
+ >>> handler1 = container.resolve(RequestHandler)
235
+ >>> handler2 = container.resolve(RequestHandler)
236
+ >>> assert handler1 is not handler2 # Different instances
237
+ >>> assert handler1.request_id != handler2.request_id
238
+
239
+ Note:
240
+ Use this for objects with per-request or per-operation lifecycle.
241
+ For shared services, use register_singleton_factory() instead.
242
+ """
243
+ self._rust_core.register_transient_factory(component_type, factory)
244
+
245
+ def register_singleton(self, component_type: type[T], factory: Callable[[], T]) -> None:
246
+ """Register a singleton provider manually.
247
+
248
+ Convenience method that calls register_singleton_factory(). The factory
249
+ will be called once when the type is first resolved, and the result
250
+ will be cached for the lifetime of the container.
251
+
252
+ Args:
253
+ component_type: The type to register. This is used as the lookup
254
+ key when resolving dependencies.
255
+ factory: A callable that takes no arguments and returns an instance
256
+ of component_type. Called exactly once, on first resolve().
257
+
258
+ Raises:
259
+ KeyError: If the type is already registered in this container.
260
+
261
+ Example:
262
+ >>> from dioxide import Container
263
+ >>>
264
+ >>> class Config:
265
+ ... def __init__(self, db_url: str):
266
+ ... self.db_url = db_url
267
+ >>>
268
+ >>> container = Container()
269
+ >>> container.register_singleton(Config, lambda: Config('postgresql://localhost'))
270
+ >>> config = container.resolve(Config)
271
+ >>> assert config.db_url == 'postgresql://localhost'
272
+
273
+ Note:
274
+ This is an alias for register_singleton_factory() provided for
275
+ convenience and clarity.
276
+ """
277
+ self.register_singleton_factory(component_type, factory)
278
+
279
+ def register_factory(self, component_type: type[T], factory: Callable[[], T]) -> None:
280
+ """Register a transient (factory) provider manually.
281
+
282
+ Convenience method that calls register_transient_factory(). The factory
283
+ will be called every time the type is resolved, creating a new instance
284
+ for each resolve() call.
285
+
286
+ Args:
287
+ component_type: The type to register. This is used as the lookup
288
+ key when resolving dependencies.
289
+ factory: A callable that takes no arguments and returns an instance
290
+ of component_type. Called on every resolve() to create a fresh
291
+ instance.
292
+
293
+ Raises:
294
+ KeyError: If the type is already registered in this container.
295
+
296
+ Example:
297
+ >>> from dioxide import Container
298
+ >>>
299
+ >>> class Transaction:
300
+ ... _id_counter = 0
301
+ ...
302
+ ... def __init__(self):
303
+ ... Transaction._id_counter += 1
304
+ ... self.tx_id = Transaction._id_counter
305
+ >>>
306
+ >>> container = Container()
307
+ >>> container.register_factory(Transaction, lambda: Transaction())
308
+ >>> tx1 = container.resolve(Transaction)
309
+ >>> tx2 = container.resolve(Transaction)
310
+ >>> assert tx1.tx_id != tx2.tx_id # Different instances
311
+
312
+ Note:
313
+ This is an alias for register_transient_factory() provided for
314
+ convenience and clarity.
315
+ """
316
+ self.register_transient_factory(component_type, factory)
317
+
318
+ def resolve(self, component_type: type[T]) -> T:
319
+ """Resolve a component instance.
320
+
321
+ Retrieves or creates an instance of the requested type based on its
322
+ registration. For singletons, returns the cached instance (creating
323
+ it on first call). For factories, creates a new instance every time.
324
+
325
+ Args:
326
+ component_type: The type to resolve. Must have been previously
327
+ registered via scan() or manual registration methods.
328
+
329
+ Returns:
330
+ An instance of the requested type. For SINGLETON scope, the same
331
+ instance is returned on every call. For FACTORY scope, a new
332
+ instance is created on each call.
333
+
334
+ Raises:
335
+ KeyError: If the type is not registered in this container.
336
+
337
+ Example:
338
+ >>> from dioxide import Container, component
339
+ >>>
340
+ >>> @component
341
+ ... class Logger:
342
+ ... def log(self, msg: str):
343
+ ... print(f'LOG: {msg}')
344
+ >>>
345
+ >>> @component
346
+ ... class Application:
347
+ ... def __init__(self, logger: Logger):
348
+ ... self.logger = logger
349
+ >>>
350
+ >>> container = Container()
351
+ >>> container.scan()
352
+ >>> app = container.resolve(Application)
353
+ >>> app.logger.log('Application started')
354
+
355
+ Note:
356
+ Type annotations in constructors enable automatic dependency
357
+ injection. The container recursively resolves all dependencies.
358
+ """
359
+ return self._rust_core.resolve(component_type)
360
+
361
+ def __getitem__(self, component_type: type[T]) -> T:
362
+ """Resolve a component using bracket syntax.
363
+
364
+ Provides an alternative, more Pythonic syntax for resolving components.
365
+ This method is equivalent to calling resolve() and simply delegates to it.
366
+
367
+ Args:
368
+ component_type: The type to resolve. Must have been previously
369
+ registered via scan() or manual registration methods.
370
+
371
+ Returns:
372
+ An instance of the requested type. For SINGLETON scope, the same
373
+ instance is returned on every call. For FACTORY scope, a new
374
+ instance is created on each call.
375
+
376
+ Raises:
377
+ KeyError: If the type is not registered in this container.
378
+
379
+ Example:
380
+ >>> from dioxide import container, component
381
+ >>>
382
+ >>> @component
383
+ ... class Logger:
384
+ ... def log(self, msg: str):
385
+ ... print(f'LOG: {msg}')
386
+ >>>
387
+ >>> container.scan()
388
+ >>> logger = container[Logger] # Bracket syntax
389
+ >>> logger.log('Using bracket notation')
390
+
391
+ Note:
392
+ This is purely a convenience method. Both container[Type] and
393
+ container.resolve(Type) work identically and return the same
394
+ instance for singleton-scoped components.
395
+ """
396
+ return self.resolve(component_type)
397
+
398
+ def is_empty(self) -> bool:
399
+ """Check if container has no registered providers.
400
+
401
+ Returns:
402
+ True if no types have been registered, False if at least one
403
+ type has been registered.
404
+
405
+ Example:
406
+ >>> from dioxide import Container
407
+ >>>
408
+ >>> container = Container()
409
+ >>> assert container.is_empty()
410
+ >>>
411
+ >>> container.scan() # Register @component classes
412
+ >>> # If any @component classes exist, container is no longer empty
413
+ """
414
+ return self._rust_core.is_empty()
415
+
416
+ def __len__(self) -> int:
417
+ """Get count of registered providers.
418
+
419
+ Returns:
420
+ The number of types that have been registered in this container.
421
+
422
+ Example:
423
+ >>> from dioxide import Container, component
424
+ >>>
425
+ >>> @component
426
+ ... class ServiceA:
427
+ ... pass
428
+ >>>
429
+ >>> @component
430
+ ... class ServiceB:
431
+ ... pass
432
+ >>>
433
+ >>> container = Container()
434
+ >>> assert len(container) == 0
435
+ >>> container.scan()
436
+ >>> assert len(container) == 2
437
+ """
438
+ return len(self._rust_core)
439
+
440
+ def scan(self, package: str | None = None, profile: str | None = None) -> None:
441
+ """Discover and register all @component decorated classes.
442
+
443
+ Scans the global component registry for all classes decorated with
444
+ @component and registers them with the container. Dependencies are
445
+ automatically resolved based on constructor type hints.
446
+
447
+ This is the primary method for setting up the container in a
448
+ declarative style. Call it once after all components are imported.
449
+
450
+ Args:
451
+ package: Optional package name to scan. If None, scans all registered
452
+ components. If provided, only scans components from the specified
453
+ package. (Not yet implemented - reserved for future use)
454
+ profile: Optional profile name to filter components. If None, registers
455
+ all components regardless of profile. If provided, only registers
456
+ components that have the matching profile in their __dioxide_profiles__
457
+ attribute. Profile names are normalized to lowercase for matching.
458
+
459
+ Registration behavior:
460
+ - SINGLETON scope (default): Creates singleton factory with caching
461
+ - FACTORY scope: Creates transient factory for new instances
462
+ - Manual registrations take precedence over @component decorators
463
+ - Already-registered types are silently skipped
464
+ - Profile filtering applies to components with @profile decorator
465
+
466
+ Example:
467
+ >>> from dioxide import Container, component, Scope, profile
468
+ >>>
469
+ >>> @component
470
+ ... class Database:
471
+ ... def __init__(self):
472
+ ... self.connected = True
473
+ >>>
474
+ >>> @component
475
+ ... class UserRepository:
476
+ ... def __init__(self, db: Database):
477
+ ... self.db = db
478
+ >>>
479
+ >>> @component(scope=Scope.FACTORY)
480
+ >>> @profile.production
481
+ ... class RequestHandler:
482
+ ... def __init__(self, repo: UserRepository):
483
+ ... self.repo = repo
484
+ >>>
485
+ >>> container = Container()
486
+ >>> container.scan() # Scans all components
487
+ >>>
488
+ >>> # Or with profile filtering
489
+ >>> prod_container = Container()
490
+ >>> prod_container.scan(profile='production') # Only production components
491
+
492
+ Note:
493
+ - Ensure all component classes are imported before calling scan()
494
+ - Constructor dependencies must have type hints
495
+ - Circular dependencies will cause infinite recursion
496
+ - Manual registrations (register_*) take precedence over scan()
497
+ - Profile names are case-insensitive (normalized to lowercase)
498
+ """
499
+ from dioxide.decorators import _get_registered_components
500
+ from dioxide.profile import PROFILE_ATTRIBUTE
501
+ from dioxide.scope import Scope
502
+
503
+ # Normalize profile to lowercase if provided
504
+ normalized_profile = profile.lower() if profile else None
505
+
506
+ for component_class in _get_registered_components():
507
+ # Apply profile filtering if profile parameter provided
508
+ if normalized_profile is not None:
509
+ # Get component's profiles (if any)
510
+ component_profiles: frozenset[str] = getattr(component_class, PROFILE_ATTRIBUTE, frozenset())
511
+
512
+ # Skip if component doesn't have the requested profile
513
+ if normalized_profile not in component_profiles:
514
+ continue
515
+
516
+ # Create a factory that auto-injects dependencies
517
+ factory = self._create_auto_injecting_factory(component_class)
518
+
519
+ # Check the scope
520
+ scope = getattr(component_class, '__dioxide_scope__', Scope.SINGLETON)
521
+
522
+ # Check if this class implements a protocol
523
+ protocol_class = getattr(component_class, '__dioxide_implements__', None)
524
+
525
+ # Register the implementation under its concrete type
526
+ try:
527
+ if scope == Scope.SINGLETON:
528
+ # Register as singleton factory (Rust will cache the result)
529
+ self.register_singleton_factory(component_class, factory)
530
+ else:
531
+ # Register as transient factory (Rust creates new instance each time)
532
+ self.register_transient_factory(component_class, factory)
533
+ except KeyError:
534
+ # Already registered manually - skip it (manual takes precedence)
535
+ pass
536
+
537
+ # If this class implements a protocol, also register it under the protocol type
538
+ # IMPORTANT: For singleton scope, both protocol and concrete class must resolve
539
+ # to the same instance. We achieve this by creating a factory that resolves
540
+ # the concrete class (which is already cached by Rust if singleton).
541
+ if protocol_class is not None:
542
+ # Create a factory that resolves via the concrete class
543
+ # This ensures singleton instances are shared between protocol and concrete type
544
+ def create_protocol_factory(impl_class: type[Any]) -> Callable[[], Any]:
545
+ """Create factory that resolves the concrete implementation."""
546
+ return lambda: self.resolve(impl_class)
547
+
548
+ protocol_factory = create_protocol_factory(component_class)
549
+ try:
550
+ if scope == Scope.SINGLETON:
551
+ self.register_singleton_factory(protocol_class, protocol_factory)
552
+ else:
553
+ self.register_transient_factory(protocol_class, protocol_factory)
554
+ except KeyError:
555
+ # Protocol already has an implementation registered - skip it
556
+ # (This will happen with multiple implementations - we'll handle
557
+ # profile-based selection in a future iteration)
558
+ pass
559
+
560
+ def _create_auto_injecting_factory(self, cls: type[T]) -> Callable[[], T]:
561
+ """Create a factory function that auto-injects dependencies from type hints.
562
+
563
+ Internal method used by scan() to create factory functions that
564
+ automatically resolve constructor dependencies and instantiate classes.
565
+
566
+ Args:
567
+ cls: The class to create a factory for. Must be a class type.
568
+
569
+ Returns:
570
+ A factory function that:
571
+ - Inspects the class's __init__ type hints
572
+ - Resolves each dependency from the container
573
+ - Instantiates the class with resolved dependencies
574
+ - Returns the fully-constructed instance
575
+
576
+ Note:
577
+ - If the class has no __init__ or no type hints, returns the class itself
578
+ - Only parameters with type hints are resolved from the container
579
+ - Parameters without type hints are skipped (not passed to __init__)
580
+ """
581
+ try:
582
+ init_signature = inspect.signature(cls.__init__)
583
+ # Pass the class's module globals to resolve forward references
584
+ type_hints = get_type_hints(cls.__init__, globalns=cls.__init__.__globals__)
585
+ except (ValueError, AttributeError, NameError):
586
+ # No __init__ or no type hints, or can't resolve type hints - just instantiate directly
587
+ return cls
588
+
589
+ # Build factory that resolves dependencies
590
+ def factory() -> T:
591
+ kwargs: dict[str, Any] = {}
592
+ for param_name in init_signature.parameters:
593
+ if param_name == 'self':
594
+ continue
595
+ if param_name in type_hints:
596
+ dependency_type = type_hints[param_name]
597
+ kwargs[param_name] = self.resolve(dependency_type)
598
+ return cls(**kwargs)
599
+
600
+ return factory
601
+
602
+
603
+ # Global singleton container instance for simplified API
604
+ # This provides the MLP-style ergonomic API while keeping Container class
605
+ # available for advanced use cases (testing isolation, multi-tenant apps)
606
+ container: Container = Container()