@@ -463,6 +463,28 @@ def factory(context, parent_type):
463463 return FactoryResolver (concrete_type , factory , life_style )(resolver_context )
464464
465465
466+ def get_mixed_type_provider (
467+ concrete_type : Type ,
468+ args_callbacks : list ,
469+ annotation_resolvers : Mapping [str , Callable ],
470+ life_style : ServiceLifeStyle ,
471+ resolver_context : ResolutionContext ,
472+ ):
473+ """
474+ Provider that combines __init__ argument injection with class-level annotation
475+ property injection. Used when a class defines both a custom __init__ (with or
476+ without parameters) and class-level annotated attributes.
477+ """
478+
479+ def factory (context , parent_type ):
480+ instance = concrete_type (* [fn (context , parent_type ) for fn in args_callbacks ])
481+ for name , resolver in annotation_resolvers .items ():
482+ setattr (instance , name , resolver (context , parent_type ))
483+ return instance
484+
485+ return FactoryResolver (concrete_type , factory , life_style )(resolver_context )
486+
487+
466488def _get_plain_class_factory (concrete_type : Type ):
467489 def factory (* args ):
468490 return concrete_type ()
@@ -645,6 +667,48 @@ def _resolve_by_annotations(
645667 self .concrete_type , resolvers , self .life_style , context
646668 )
647669
670+ def _resolve_by_init_and_annotations (
671+ self , context : ResolutionContext , extra_annotations : dict [str , Type ]
672+ ):
673+ """
674+ Resolves by both __init__ parameters and class-level annotated properties.
675+ Used when a class defines a custom __init__ AND class-level type annotations.
676+ The __init__ parameters are injected as constructor arguments; the class
677+ annotations are injected via setattr after instantiation.
678+ """
679+ sig = Signature .from_callable (self .concrete_type .__init__ )
680+ params = {
681+ key : Dependency (key , value .annotation )
682+ for key , value in sig .parameters .items ()
683+ }
684+
685+ if sys .version_info >= (3 , 10 ): # pragma: no cover
686+ globalns = dict (vars (sys .modules [self .concrete_type .__module__ ]))
687+ globalns .update (_get_obj_globals (self .concrete_type ))
688+ annotations = get_type_hints (
689+ self .concrete_type .__init__ ,
690+ globalns ,
691+ _get_obj_locals (self .concrete_type ),
692+ )
693+ for key , value in params .items ():
694+ if key in annotations :
695+ value .annotation = annotations [key ]
696+
697+ concrete_type = self .concrete_type
698+ init_fns = self ._get_resolvers_for_parameters (concrete_type , context , params )
699+
700+ ann_params = {
701+ key : Dependency (key , value ) for key , value in extra_annotations .items ()
702+ }
703+ ann_fns = self ._get_resolvers_for_parameters (concrete_type , context , ann_params )
704+ annotation_resolvers = {
705+ name : ann_fns [i ] for i , name in enumerate (ann_params .keys ())
706+ }
707+
708+ return get_mixed_type_provider (
709+ concrete_type , init_fns , annotation_resolvers , self .life_style , context
710+ )
711+
648712 def __call__ (self , context : ResolutionContext ):
649713 concrete_type = self .concrete_type
650714
@@ -670,6 +734,35 @@ def __call__(self, context: ResolutionContext):
670734 concrete_type , _get_plain_class_factory (concrete_type ), self .life_style
671735 )(context )
672736
737+ # Custom __init__: also check for class-level annotations to inject as
738+ # properties. The cheap __annotations__ check avoids the expensive
739+ # get_type_hints call for the common case of no class-level annotations.
740+ if concrete_type .__annotations__ :
741+ class_annotations = get_type_hints (
742+ concrete_type ,
743+ {
744+ ** dict (vars (sys .modules [concrete_type .__module__ ])),
745+ ** _get_obj_globals (concrete_type ),
746+ },
747+ _get_obj_locals (concrete_type ),
748+ )
749+ if class_annotations :
750+ sig = Signature .from_callable (concrete_type .__init__ )
751+ init_param_names = set (sig .parameters .keys ()) - {"self" }
752+ extra_annotations = {
753+ k : v
754+ for k , v in class_annotations .items ()
755+ if k not in init_param_names
756+ and not self ._ignore_class_attribute (k , v )
757+ }
758+ if extra_annotations :
759+ try :
760+ return self ._resolve_by_init_and_annotations (
761+ context , extra_annotations
762+ )
763+ except RecursionError :
764+ raise CircularDependencyException (chain [0 ], concrete_type )
765+
673766 try :
674767 return self ._resolve_by_init_method (context )
675768 except RecursionError :
0 commit comments