aspyx 1.2.0__py3-none-any.whl → 1.3.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.

Potentially problematic release.


This version of aspyx might be problematic. Click here for more details.

aspyx/di/di.py CHANGED
@@ -9,7 +9,8 @@ import logging
9
9
  from abc import abstractmethod, ABC
10
10
  from enum import Enum
11
11
  import threading
12
- from typing import Type, Dict, TypeVar, Generic, Optional, cast, Callable
12
+ from operator import truediv
13
+ from typing import Type, Dict, TypeVar, Generic, Optional, cast, Callable, TypedDict
13
14
 
14
15
  from aspyx.di.util import StringBuilder
15
16
  from aspyx.reflection import Decorators, TypeDescriptor, DecoratorDescriptor
@@ -31,16 +32,31 @@ class DIException(Exception):
31
32
  """
32
33
  Exception raised for errors in the injector.
33
34
  """
35
+ def __init__(self, message: str):
36
+ super().__init__(message)
34
37
 
35
38
  class DIRegistrationException(DIException):
36
39
  """
37
40
  Exception raised during the registration of dependencies.
38
41
  """
42
+ def __init__(self, message: str):
43
+ super().__init__(message)
44
+
45
+ class ProviderCollisionException(DIRegistrationException):
46
+ def __init__(self, message: str, *providers: AbstractInstanceProvider):
47
+ super().__init__(message)
48
+
49
+ self.providers = providers
50
+
51
+ def __str__(self):
52
+ return f"[{self.args[0]} {self.providers[1].location()} collides with {self.providers[0].location()}"
39
53
 
40
54
  class DIRuntimeException(DIException):
41
55
  """
42
56
  Exception raised during the runtime.
43
57
  """
58
+ def __init__(self, message: str):
59
+ super().__init__(message)
44
60
 
45
61
  class AbstractInstanceProvider(ABC, Generic[T]):
46
62
  """
@@ -65,17 +81,23 @@ class AbstractInstanceProvider(ABC, Generic[T]):
65
81
  def get_scope(self) -> str:
66
82
  pass
67
83
 
68
- @abstractmethod
69
- def get_dependencies(self) -> list[AbstractInstanceProvider]:
70
- pass
84
+ def get_dependencies(self) -> (list[Type],int):
85
+ return [],1
71
86
 
72
87
  @abstractmethod
73
88
  def create(self, environment: Environment, *args):
74
89
  pass
75
90
 
76
- @abstractmethod
77
- def resolve(self, context: Providers.Context):
78
- pass
91
+ def report(self) -> str:
92
+ return str(self)
93
+
94
+ def location(self) -> str:
95
+ host = self.get_host()
96
+
97
+ file = inspect.getfile(host)
98
+ line = inspect.getsourcelines(host)[1]
99
+
100
+ return f"{file}:{line}"
79
101
 
80
102
  def check_factories(self):
81
103
  pass
@@ -89,8 +111,7 @@ class InstanceProvider(AbstractInstanceProvider):
89
111
  "host",
90
112
  "type",
91
113
  "eager",
92
- "scope",
93
- "dependencies"
114
+ "scope"
94
115
  ]
95
116
 
96
117
  # constructor
@@ -100,12 +121,6 @@ class InstanceProvider(AbstractInstanceProvider):
100
121
  self.type = t
101
122
  self.eager = eager
102
123
  self.scope = scope
103
- self.dependencies : Optional[list[AbstractInstanceProvider]] = None
104
-
105
- # internal
106
-
107
- def _is_resolved(self) -> bool:
108
- return self.dependencies is not None
109
124
 
110
125
  # implement AbstractInstanceProvider
111
126
 
@@ -113,10 +128,6 @@ class InstanceProvider(AbstractInstanceProvider):
113
128
  return self.host
114
129
 
115
130
  def check_factories(self):
116
- #register_factories(self.host)
117
- pass
118
-
119
- def resolve(self, context: Providers.Context):
120
131
  pass
121
132
 
122
133
  def get_module(self) -> str:
@@ -131,22 +142,11 @@ class InstanceProvider(AbstractInstanceProvider):
131
142
  def get_scope(self) -> str:
132
143
  return self.scope
133
144
 
134
- def get_dependencies(self) -> list[AbstractInstanceProvider]:
135
- return self.dependencies
136
-
137
145
  # public
138
146
 
139
147
  def module(self) -> str:
140
148
  return self.host.__module__
141
149
 
142
- def add_dependency(self, provider: AbstractInstanceProvider):
143
- if any(issubclass(provider.get_type(), dependency.get_type()) for dependency in self.dependencies):
144
- return False
145
-
146
- self.dependencies.append(provider)
147
-
148
- return True
149
-
150
150
  @abstractmethod
151
151
  def create(self, environment: Environment, *args):
152
152
  pass
@@ -204,15 +204,12 @@ class AmbiguousProvider(AbstractInstanceProvider):
204
204
  def get_scope(self) -> str:
205
205
  return "singleton"
206
206
 
207
- def get_dependencies(self) -> list[AbstractInstanceProvider]:
208
- return []
209
-
210
- def resolve(self, context: Providers.Context):
211
- pass
212
-
213
207
  def create(self, environment: Environment, *args):
214
208
  raise DIException(f"multiple candidates for type {self.type}")
215
209
 
210
+ def report(self) -> str:
211
+ return "ambiguous: " + ",".join([provider.report() for provider in self.providers])
212
+
216
213
  def __str__(self):
217
214
  return f"AmbiguousProvider({self.type})"
218
215
 
@@ -257,8 +254,8 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
257
254
  __slots__ = [
258
255
  "environment",
259
256
  "scope_instance",
260
- "provider",
261
257
  "dependencies",
258
+ "provider"
262
259
  ]
263
260
 
264
261
  # constructor
@@ -268,14 +265,30 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
268
265
 
269
266
  self.environment = environment
270
267
  self.provider = provider
271
- self.dependencies : list[AbstractInstanceProvider] = []
272
-
268
+ self.dependencies = []
273
269
  self.scope_instance = Scopes.get(provider.get_scope(), environment)
274
270
 
275
271
  # implement
276
272
 
277
- def resolve(self, context: Providers.Context):
278
- pass # noop
273
+ def resolve(self, context: Providers.ResolveContext):
274
+ context.add(self)
275
+
276
+ if not context.is_resolved(self):
277
+ context.provider_dependencies[self] = [] #?
278
+
279
+ type_and_params = self.provider.get_dependencies()
280
+ params = type_and_params[1]
281
+ for type in type_and_params[0]:
282
+ if params > 0:
283
+ params -= 1
284
+ self.dependencies.append(context.get_provider(type))
285
+
286
+ provider = context.add_provider_dependency(self, type)
287
+ if provider is not None:
288
+ provider.resolve(context)
289
+
290
+ else:
291
+ context.add(*context.get_provider_dependencies(self))
279
292
 
280
293
  def get_module(self) -> str:
281
294
  return self.provider.get_module()
@@ -289,18 +302,10 @@ class EnvironmentInstanceProvider(AbstractInstanceProvider):
289
302
  def get_scope(self) -> str:
290
303
  return self.provider.get_scope()
291
304
 
292
- # custom logic
293
-
294
- def tweak_dependencies(self, providers: dict[Type, AbstractInstanceProvider]):
295
- for dependency in self.provider.get_dependencies():
296
- instance_provider = providers.get(dependency.get_type(), None)
297
- if instance_provider is None:
298
- raise DIRegistrationException(f"missing import for {dependency.get_type()} ")
299
-
300
- self.dependencies.append(instance_provider)
305
+ def report(self) -> str:
306
+ return self.provider.report()
301
307
 
302
- def get_dependencies(self) -> list[AbstractInstanceProvider]:
303
- return self.provider.get_dependencies()
308
+ # own logic
304
309
 
305
310
  def create(self, environment: Environment, *args):
306
311
  return self.scope_instance.get(self.provider, self.environment, lambda: [provider.create(environment) for provider in self.dependencies]) # already scope property!
@@ -329,41 +334,36 @@ class ClassInstanceProvider(InstanceProvider):
329
334
  def check_factories(self):
330
335
  register_factories(self.host)
331
336
 
332
- def resolve(self, context: Providers.Context):
333
- context.add(self)
334
-
335
- if not self._is_resolved():
336
- self.dependencies = []
337
+ def get_dependencies(self) -> (list[Type],int):
338
+ types : list[Type] = []
337
339
 
338
- # check constructor
340
+ # check constructor
339
341
 
340
- init = TypeDescriptor.for_type(self.type).get_method("__init__")
341
- if init is None:
342
- raise DIRegistrationException(f"{self.type.__name__} does not implement __init__")
342
+ init = TypeDescriptor.for_type(self.type).get_method("__init__")
343
+ if init is None:
344
+ raise DIRegistrationException(f"{self.type.__name__} does not implement __init__")
343
345
 
344
- for param in init.param_types:
345
- provider = Providers.get_provider(param)
346
- self.params += 1
347
- if self.add_dependency(provider): # a dependency can occur multiple times, e.g in __init__ and in an injected method
348
- provider.resolve(context)
346
+ self.params = len(init.param_types)
347
+ for param in init.param_types:
348
+ types.append(param)
349
349
 
350
- # check @inject
350
+ # check @inject
351
351
 
352
- for method in TypeDescriptor.for_type(self.type).get_methods():
353
- if method.has_decorator(inject):
354
- for param in method.param_types:
355
- provider = Providers.get_provider(param)
352
+ for method in TypeDescriptor.for_type(self.type).get_methods():
353
+ if method.has_decorator(inject):
354
+ for param in method.param_types:
355
+ types.append(param)
356
356
 
357
- if self.add_dependency(provider):
358
- provider.resolve(context)
359
- else: # check if the dependencies create a cycle
360
- context.add(*self.dependencies)
357
+ return (types, self.params)
361
358
 
362
359
  def create(self, environment: Environment, *args):
363
360
  Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
364
361
 
365
362
  return environment.created(self.type(*args[:self.params]))
366
363
 
364
+ def report(self) -> str:
365
+ return f"{self.host.__name__}.__init__"
366
+
367
367
  # object
368
368
 
369
369
  def __str__(self):
@@ -387,25 +387,19 @@ class FunctionInstanceProvider(InstanceProvider):
387
387
 
388
388
  # implement
389
389
 
390
- def resolve(self, context: Providers.Context):
391
- context.add(self)
392
-
393
- if not self._is_resolved():
394
- self.dependencies = []
395
-
396
- provider = Providers.get_provider(self.host)
397
- if self.add_dependency(provider):
398
- provider.resolve(context)
399
- else: # check if the dependencies crate a cycle
400
- context.add(*self.dependencies)
390
+ def get_dependencies(self) -> (list[Type],int):
391
+ return [self.host], 1
401
392
 
402
393
  def create(self, environment: Environment, *args):
403
394
  Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
404
395
 
405
- instance = self.method(*args)
396
+ instance = self.method(*args) # args[0]=self
406
397
 
407
398
  return environment.created(instance)
408
399
 
400
+ def report(self) -> str:
401
+ return f"{self.host.__name__}.{self.method.__name__}"
402
+
409
403
  def __str__(self):
410
404
  return f"FunctionInstanceProvider({self.host.__name__}.{self.method.__name__} -> {self.type.__name__})"
411
405
 
@@ -429,32 +423,24 @@ class FactoryInstanceProvider(InstanceProvider):
429
423
 
430
424
  # implement
431
425
 
432
- def resolve(self, context: Providers.Context):
433
- context.add(self)
434
-
435
- if not self._is_resolved():
436
- self.dependencies = []
437
-
438
- provider = Providers.get_provider(self.host)
439
- if self.add_dependency(provider):
440
- provider.resolve(context)
441
- else: # check if the dependencies crate a cycle
442
- context.add(*self.dependencies)
443
-
444
- return self
426
+ def get_dependencies(self) -> (list[Type],int):
427
+ return [self.host],1
445
428
 
446
429
  def create(self, environment: Environment, *args):
447
430
  Environment.logger.debug("%s create class %s", self, self.type.__qualname__)
448
431
 
449
432
  return environment.created(args[0].create())
450
433
 
434
+ def report(self) -> str:
435
+ return f"{self.host.__name__}.create"
436
+
451
437
  def __str__(self):
452
438
  return f"FactoryInstanceProvider({self.host.__name__} -> {self.type.__name__})"
453
439
 
454
440
 
455
441
  class Lifecycle(Enum):
456
442
  """
457
- This enum defines the lifecycle events that can be processed by lifecycle processors.
443
+ This enum defines the lifecycle phases that can be processed by lifecycle processors.
458
444
  """
459
445
 
460
446
  __slots__ = []
@@ -466,7 +452,7 @@ class Lifecycle(Enum):
466
452
 
467
453
  class LifecycleProcessor(ABC):
468
454
  """
469
- A LifecycleProcessor is used to perform any side effects on managed objects during their lifecycle.
455
+ A LifecycleProcessor is used to perform any side effects on managed objects during different lifecycle phases.
470
456
  """
471
457
  __slots__ = [
472
458
  "order"
@@ -506,13 +492,54 @@ class Providers:
506
492
  """
507
493
  # local class
508
494
 
509
- class Context:
510
- __slots__ = ["dependencies"]
495
+ class ResolveContext:
496
+ __slots__ = [
497
+ "dependencies",
498
+ "providers",
499
+ "provider_dependencies"
500
+ ]
501
+
502
+ # constructor
503
+
504
+ def __init__(self, providers: Dict[Type, EnvironmentInstanceProvider]):
505
+ self.dependencies : list[EnvironmentInstanceProvider] = []
506
+ self.providers = providers
507
+ self.provider_dependencies : dict[EnvironmentInstanceProvider, list[EnvironmentInstanceProvider]] = {}
508
+
509
+ # public
510
+
511
+ def is_resolved(self, provider: EnvironmentInstanceProvider) -> bool:
512
+ return self.provider_dependencies.get(provider, None) is not None
513
+
514
+ def get_provider_dependencies(self, provider: EnvironmentInstanceProvider) -> list[EnvironmentInstanceProvider]:
515
+ return self.provider_dependencies[provider]
516
+
517
+ def add_provider_dependency(self, provider: EnvironmentInstanceProvider, type: Type) -> Optional[EnvironmentInstanceProvider]:
518
+ provider_dependencies = self.provider_dependencies.get(provider, None)
519
+ if provider_dependencies is None:
520
+ provider_dependencies = []
521
+ self.provider_dependencies[provider] = provider_dependencies
522
+
523
+ provider = self.get_provider(type)
524
+
525
+ if any(issubclass(provider.get_type(), dependency.get_type()) for dependency in provider_dependencies):
526
+ return None
527
+
528
+ provider_dependencies.append(provider)
529
+
530
+ return provider
511
531
 
512
- def __init__(self):
513
- self.dependencies : list[AbstractInstanceProvider] = []
532
+ def next(self):
533
+ self.dependencies.clear()
514
534
 
515
- def add(self, *providers: AbstractInstanceProvider):
535
+ def get_provider(self, type: Type) -> EnvironmentInstanceProvider:
536
+ provider = self.providers.get(type, None)
537
+ if provider is None:
538
+ raise DIRegistrationException(f"Provider for {type} is not defined")
539
+
540
+ return provider
541
+
542
+ def add(self, *providers: EnvironmentInstanceProvider):
516
543
  for provider in providers:
517
544
  if next((p for p in self.dependencies if p.get_type() is provider.get_type()), None) is not None:
518
545
  raise DIRegistrationException(self.cycle_report(provider))
@@ -529,19 +556,17 @@ class Providers:
529
556
 
530
557
  first = False
531
558
 
532
- cycle += f"{p.get_type().__name__}"
559
+ cycle += f"{p.report()}"
533
560
 
534
- cycle += f" <> {provider.get_type().__name__}"
561
+ cycle += f" <> {provider.report()}"
535
562
 
536
563
  return cycle
537
564
 
538
565
 
539
566
  # class properties
540
567
 
541
- check: list[AbstractInstanceProvider] = []
542
-
543
- providers : Dict[Type,AbstractInstanceProvider] = {}
544
- cache: Dict[Type, AbstractInstanceProvider] = {}
568
+ check : list[AbstractInstanceProvider] = []
569
+ providers : Dict[Type,list[AbstractInstanceProvider]] = {}
545
570
 
546
571
  resolved = False
547
572
 
@@ -549,7 +574,58 @@ class Providers:
549
574
  def register(cls, provider: AbstractInstanceProvider):
550
575
  Environment.logger.debug("register provider %s(%s)", provider.get_type().__qualname__, provider.get_type().__name__)
551
576
 
552
- # local functions
577
+ Providers.check.append(provider)
578
+ candidates = Providers.providers.get(provider.get_type(), None)
579
+ if candidates is None:
580
+ Providers.providers[provider.get_type()] = [provider]
581
+ else:
582
+ candidates.append(provider)
583
+
584
+ # add factories lazily
585
+
586
+ @classmethod
587
+ def check_factories(cls):
588
+ for check in Providers.check:
589
+ check.check_factories()
590
+
591
+ Providers.check.clear()
592
+
593
+ @classmethod
594
+ def filter(cls, environment: Environment) -> Dict[Type,AbstractInstanceProvider]:
595
+ cache: Dict[Type,AbstractInstanceProvider] = {}
596
+
597
+ context: ConditionContext = {
598
+ "requires_feature": lambda feature : environment.has_feature(feature),
599
+ "requires_class": lambda clazz : cache.get(clazz, None) is not None # ? only works if the class is in the cache already?
600
+ }
601
+
602
+ Providers.check_factories() # check for additional factories
603
+
604
+ # local methods
605
+
606
+ def filter_type(clazz: Type) -> Optional[AbstractInstanceProvider]:
607
+ result = None
608
+ for provider in Providers.providers[clazz]:
609
+ if provider_applies(provider):
610
+ if result is not None:
611
+ raise ProviderCollisionException(f"type {clazz.__name__} already registered", result, provider)
612
+
613
+ else:
614
+ result = provider
615
+
616
+ return result
617
+
618
+ def provider_applies(provider: AbstractInstanceProvider) -> bool:
619
+ descriptor = TypeDescriptor.for_type(provider.get_host())
620
+ if descriptor.has_decorator(conditional):
621
+ conditions: list[Condition] = [*descriptor.get_decorator(conditional).args]
622
+ for condition in conditions:
623
+ if not condition.apply(context):
624
+ return False
625
+
626
+ return True
627
+
628
+ return True
553
629
 
554
630
  def is_injectable(type: Type) -> bool:
555
631
  if type is object:
@@ -558,34 +634,21 @@ class Providers:
558
634
  if inspect.isabstract(type):
559
635
  return False
560
636
 
561
- #for decorator in Decorators.get(type):
562
- # if decorator.decorator is injectable:
563
- # return True
564
-
565
- # darn
566
-
567
637
  return True
568
638
 
569
639
  def cache_provider_for_type(provider: AbstractInstanceProvider, type: Type):
570
- def location(provider: AbstractInstanceProvider):
571
- host = provider.get_host()
572
- file = inspect.getfile(host)
573
- line = inspect.getsourcelines(host)[1]
574
-
575
- return f"{file}:{line}"
576
-
577
- existing_provider = Providers.cache.get(type)
640
+ existing_provider = cache.get(type)
578
641
  if existing_provider is None:
579
- Providers.cache[type] = provider
642
+ cache[type] = provider
580
643
 
581
644
  else:
582
645
  if type is provider.get_type():
583
- raise DIRegistrationException(f"{type} already registered in {location(existing_provider)}, override in {location(provider)}")
646
+ raise ProviderCollisionException(f"type {type.__name__} already registered", existing_provider, provider)
584
647
 
585
648
  if isinstance(existing_provider, AmbiguousProvider):
586
649
  cast(AmbiguousProvider, existing_provider).add_provider(provider)
587
650
  else:
588
- Providers.cache[type] = AmbiguousProvider(type, existing_provider, provider)
651
+ cache[type] = AmbiguousProvider(type, existing_provider, provider)
589
652
 
590
653
  # recursion
591
654
 
@@ -593,39 +656,35 @@ class Providers:
593
656
  if is_injectable(super_class):
594
657
  cache_provider_for_type(provider, super_class)
595
658
 
596
- # go
597
-
598
- Providers.check.append(provider)
659
+ # filter conditional providers and fill base classes as well
599
660
 
600
- Providers.providers[provider.get_type()] = provider
661
+ for provider_type, providers in Providers.providers.items():
662
+ matching_provider = filter_type(provider_type)
663
+ if matching_provider is not None:
664
+ cache_provider_for_type(matching_provider, provider_type)
601
665
 
602
- # cache providers
666
+ # replace by EnvironmentInstanceProvider
603
667
 
604
- cache_provider_for_type(provider, provider.get_type())
605
-
606
- @classmethod
607
- def resolve(cls):
668
+ mapped = {}
669
+ result = {}
670
+ for provider_type, provider in cache.items():
671
+ environment_provider = mapped.get(provider, None)
672
+ if environment_provider is None:
673
+ environment_provider = EnvironmentInstanceProvider(environment, provider)
674
+ mapped[provider] = environment_provider
608
675
 
609
- for check in Providers.check:
610
- check.check_factories()
676
+ result[provider_type] = environment_provider
611
677
 
612
- # in the loop additional providers may be added
613
- while len(Providers.check) > 0:
614
- check = Providers.check.pop(0)
615
- check.resolve(Providers.Context())
678
+ # and resolve
616
679
 
617
- @classmethod
618
- def report(cls):
619
- for provider in Providers.cache.values():
620
- print(f"provider {provider.get_type().__qualname__}")
680
+ provider_context = Providers.ResolveContext(result)
681
+ for provider in mapped.values():
682
+ provider.resolve(provider_context)
683
+ provider_context.next() # clear dependencies
621
684
 
622
- @classmethod
623
- def get_provider(cls, type: Type) -> AbstractInstanceProvider:
624
- provider = Providers.cache.get(type, None)
625
- if provider is None:
626
- raise DIException(f"{type.__name__} not registered as injectable")
685
+ # done
627
686
 
628
- return provider
687
+ return result
629
688
 
630
689
  def register_factories(cls: Type):
631
690
  descriptor = TypeDescriptor.for_type(cls)
@@ -750,6 +809,49 @@ def inject_environment():
750
809
 
751
810
  return decorator
752
811
 
812
+ # conditional stuff
813
+
814
+ class ConditionContext(TypedDict):
815
+ requires_feature: Callable[[str], bool]
816
+ requires_class: Callable[[Type], bool]
817
+
818
+ class Condition(ABC):
819
+ @abstractmethod
820
+ def apply(self, context: ConditionContext) -> bool:
821
+ pass
822
+
823
+ class FeatureCondition(Condition):
824
+ def __init__(self, feature: str):
825
+ super().__init__()
826
+
827
+ self.feature = feature
828
+
829
+ def apply(self, context: ConditionContext) -> bool:
830
+ return context["requires_feature"](self.feature)
831
+
832
+ class ClassCondition(Condition):
833
+ def __init__(self, clazz: Type):
834
+ super().__init__()
835
+
836
+ self.clazz = clazz
837
+
838
+ def apply(self, context: ConditionContext) -> bool:
839
+ return context["requires_class"](self.clazz)
840
+
841
+ def requires_feature(feature: str):
842
+ return FeatureCondition(feature)
843
+
844
+ def requires_class(clazz: Type):
845
+ return ClassCondition(clazz)
846
+
847
+ def conditional(*conditions: Condition):
848
+ def decorator(cls):
849
+ Decorators.add(cls, conditional, *conditions)
850
+
851
+ return cls
852
+
853
+ return decorator
854
+
753
855
  class Environment:
754
856
  """
755
857
  Central class that manages the lifecycle of instances and their dependencies.
@@ -766,12 +868,13 @@ class Environment:
766
868
  "providers",
767
869
  "lifecycle_processors",
768
870
  "parent",
871
+ "features",
769
872
  "instances"
770
873
  ]
771
874
 
772
875
  # constructor
773
876
 
774
- def __init__(self, env: Type, parent : Optional[Environment] = None):
877
+ def __init__(self, env: Type, features: list[str] = [], parent : Optional[Environment] = None):
775
878
  """
776
879
  Creates a new Environment instance.
777
880
 
@@ -779,6 +882,9 @@ class Environment:
779
882
  env (Type): The environment class that controls the scanning of managed objects.
780
883
  parent (Optional[Environment]): Optional parent environment, whose objects are inherited.
781
884
  """
885
+
886
+ Environment.logger.debug("create environment for class %s", env.__qualname__)
887
+
782
888
  # initialize
783
889
 
784
890
  self.type = env
@@ -786,20 +892,24 @@ class Environment:
786
892
  if self.parent is None and env is not Boot:
787
893
  self.parent = Boot.get_environment() # inherit environment including its manged instances!
788
894
 
895
+ self.features = features
789
896
  self.providers: Dict[Type, AbstractInstanceProvider] = {}
790
897
  self.lifecycle_processors: list[LifecycleProcessor] = []
791
898
 
792
899
  if self.parent is not None:
793
900
  self.providers |= self.parent.providers
794
901
  self.lifecycle_processors += self.parent.lifecycle_processors
902
+ else: #if self.type is Boot:
903
+ self.providers[SingletonScope] = SingletonScopeInstanceProvider()
904
+ self.providers[RequestScope] = RequestScopeInstanceProvider()
795
905
 
796
906
  self.instances = []
797
907
 
798
908
  Environment.instance = self
799
909
 
800
- # resolve providers on a static basis. This is executed for all new providers ( in case of new modules multiple times) !
910
+ # filter conditional providers
801
911
 
802
- Providers.resolve()
912
+ overall_providers = Providers.filter(self)
803
913
 
804
914
  loaded = set()
805
915
 
@@ -808,12 +918,6 @@ class Environment:
808
918
 
809
919
  self.providers[type] = provider
810
920
 
811
- # bootstrapping hack, they will be overwritten by the "real" providers
812
-
813
- if env is Boot:
814
- add_provider(SingletonScope, SingletonScopeInstanceProvider())
815
- add_provider(RequestScope, RequestScopeInstanceProvider())
816
-
817
921
  def load_environment(env: Type):
818
922
  if env not in loaded:
819
923
  Environment.logger.debug("load environment %s", env.__qualname__)
@@ -835,39 +939,18 @@ class Environment:
835
939
  for import_environment in decorator.args[0] or []:
836
940
  load_environment(import_environment)
837
941
 
838
- # load providers
839
-
840
- local_providers = {type: provider for type, provider in Providers.cache.items() if provider.get_module().startswith(scan)}
841
-
842
- # register providers
843
-
844
- # make sure, that for every type only a single EnvironmentInstanceProvider is created!
845
- # otherwise inheritance will fuck it up
846
-
847
- environment_providers : dict[AbstractInstanceProvider, EnvironmentInstanceProvider] = {}
848
-
849
- for type, provider in local_providers.items():
850
- environment_provider = environment_providers.get(provider, None)
851
- if environment_provider is None:
852
- environment_provider = EnvironmentInstanceProvider(self, provider)
853
- environment_providers[provider] = environment_provider
942
+ # filter and load providers according to their module
854
943
 
855
- add_provider(type, environment_provider)
856
-
857
- # tweak dependencies
858
-
859
- for type, provider in local_providers.items():
860
- cast(EnvironmentInstanceProvider, self.providers[type]).tweak_dependencies(self.providers)
861
-
862
- # return local providers
944
+ for type, provider in overall_providers.items():
945
+ if provider.get_module().startswith(scan):
946
+ add_provider(type, provider)
947
+ # go
863
948
 
864
- return environment_providers.values()
865
- else:
866
- return []
949
+ load_environment(env)
867
950
 
868
951
  # construct eager objects for local providers
869
952
 
870
- for provider in load_environment(env):
953
+ for provider in set(self.providers.values()):
871
954
  if provider.is_eager():
872
955
  provider.create(self)
873
956
 
@@ -878,6 +961,9 @@ class Environment:
878
961
 
879
962
  # internal
880
963
 
964
+ def has_feature(self, feature: str) -> bool:
965
+ return feature in self.features
966
+
881
967
  def execute_processors(self, lifecycle: Lifecycle, instance: T) -> T:
882
968
  for processor in self.lifecycle_processors:
883
969
  processor.process_lifecycle(lifecycle, instance, self)
@@ -927,8 +1013,9 @@ class Environment:
927
1013
 
928
1014
  builder.append("Providers \n")
929
1015
  for result_type, provider in self.providers.items():
930
- if cast(EnvironmentInstanceProvider, provider).environment is self:
931
- builder.append(f"- {result_type.__name__}: {cast(EnvironmentInstanceProvider, provider).provider}\n")
1016
+ if isinstance(provider, EnvironmentInstanceProvider):
1017
+ if cast(EnvironmentInstanceProvider, provider).environment is self:
1018
+ builder.append(f"- {result_type.__name__}: {provider.report()}\n")
932
1019
 
933
1020
  # instances
934
1021
 
@@ -973,6 +1060,9 @@ class Environment:
973
1060
 
974
1061
  return provider.create(self)
975
1062
 
1063
+ def __str__(self):
1064
+ return f"Environment({self.type.__name__})"
1065
+
976
1066
  class LifecycleCallable:
977
1067
  __slots__ = [
978
1068
  "decorator",
@@ -1155,20 +1245,20 @@ class InjectLifecycleCallable(LifecycleCallable):
1155
1245
  def args(self, decorator: DecoratorDescriptor, method: TypeDescriptor.MethodDescriptor, environment: Environment):
1156
1246
  return [environment.get(type) for type in method.param_types]
1157
1247
 
1158
- def scope(name: str):
1248
+ def scope(name: str, register=True):
1159
1249
  def decorator(cls):
1160
1250
  Scopes.register(cls, name)
1161
1251
 
1162
1252
  Decorators.add(cls, scope)
1163
- # Decorators.add(cls, injectable)
1164
1253
 
1165
- Providers.register(ClassInstanceProvider(cls, eager=True, scope="request"))
1254
+ if register:
1255
+ Providers.register(ClassInstanceProvider(cls, eager=True, scope="request"))
1166
1256
 
1167
1257
  return cls
1168
1258
 
1169
1259
  return decorator
1170
1260
 
1171
- @scope("request")
1261
+ @scope("request", register=False)
1172
1262
  class RequestScope(Scope):
1173
1263
  # properties
1174
1264
 
@@ -1180,7 +1270,7 @@ class RequestScope(Scope):
1180
1270
  def get(self, provider: AbstractInstanceProvider, environment: Environment, arg_provider: Callable[[],list]):
1181
1271
  return provider.create(environment, *arg_provider())
1182
1272
 
1183
- @scope("singleton")
1273
+ @scope("singleton", register=False)
1184
1274
  class SingletonScope(Scope):
1185
1275
  # properties
1186
1276