11from __future__ import annotations
22
33import asyncio
4- from collections .abc import AsyncIterator , Awaitable , Callable , Coroutine , Iterator
5- from contextlib import asynccontextmanager , contextmanager
4+ from abc import ABC , abstractmethod
5+ from collections .abc import Awaitable , Callable , Coroutine , Iterator , Sequence
6+ from contextlib import contextmanager
67from dataclasses import dataclass , field
78from functools import wraps
89from types import ModuleType as PythonModule
9- from typing import TYPE_CHECKING , Any , Concatenate , Protocol , Self , final , overload
10+ from typing import (
11+ TYPE_CHECKING ,
12+ Any ,
13+ Concatenate ,
14+ Protocol ,
15+ Self ,
16+ overload ,
17+ runtime_checkable ,
18+ )
1019
1120from injection import Module
1221from injection .loaders import ProfileLoader , PythonModuleLoader
1322
1423__all__ = ("AsyncEntrypoint" , "Entrypoint" , "entrypointmaker" )
1524
25+ type Entrypoint [** P , T ] = EntrypointBuilder [P , Any , T ]
26+ type AsyncEntrypoint [** P , T ] = Entrypoint [P , Awaitable [T ]]
1627
17- type AsyncEntrypoint [** P , T ] = Entrypoint [P , Coroutine [Any , Any , T ]]
1828type EntrypointSetupMethod [** P , ** EPP , T1 , T2 ] = Callable [
1929 Concatenate [Entrypoint [EPP , T1 ], P ],
2030 Entrypoint [EPP , T2 ],
2131]
2232
2333
34+ class Rule [** P , T1 , T2 ](ABC ):
35+ __slots__ = ()
36+
37+ @abstractmethod
38+ def apply (self , wrapped : Callable [P , T1 ]) -> Callable [P , T2 ]:
39+ raise NotImplementedError
40+
41+
42+ @dataclass (repr = False , eq = False , frozen = True , slots = True )
43+ class _AsyncToSyncRule [** P , T ](Rule [P , Awaitable [T ], T ]):
44+ run : Callable [[Awaitable [T ]], T ]
45+
46+ def apply (self , wrapped : Callable [P , Awaitable [T ]]) -> Callable [P , T ]:
47+ @wraps (wrapped )
48+ def wrapper (* args : P .args , ** kwargs : P .kwargs ) -> T :
49+ return self .run (wrapped (* args , ** kwargs ))
50+
51+ return wrapper
52+
53+
54+ @dataclass (repr = False , eq = False , frozen = True , slots = True )
55+ class _DecorateRule [** P , T1 , T2 ](Rule [P , T1 , T2 ]):
56+ decorator : Callable [[Callable [P , T1 ]], Callable [P , T2 ]]
57+
58+ def apply (self , wrapped : Callable [P , T1 ]) -> Callable [P , T2 ]:
59+ return self .decorator (wrapped )
60+
61+
62+ @dataclass (repr = False , eq = False , frozen = True , slots = True )
63+ class _InjectRule [** P , T ](Rule [P , T , T ]):
64+ module : Module
65+
66+ def apply (self , wrapped : Callable [P , T ]) -> Callable [P , T ]:
67+ return self .module .make_injected_function (wrapped )
68+
69+
70+ @dataclass (repr = False , eq = False , frozen = True , slots = True )
71+ class _LoadModulesRule [** P , T ](Rule [P , T , T ]):
72+ loader : PythonModuleLoader
73+ packages : Sequence [PythonModule | str ]
74+
75+ def apply (self , wrapped : Callable [P , T ]) -> Callable [P , T ]:
76+ return self .__decorator ()(wrapped )
77+
78+ @contextmanager
79+ def __decorator (self ) -> Iterator [None ]:
80+ self .loader .load (* self .packages )
81+ yield
82+
83+
84+ @dataclass (repr = False , eq = False , frozen = True , slots = True )
85+ class _LoadProfileRule [** P , T ](Rule [P , T , T ]):
86+ loader : ProfileLoader
87+ profile_name : str
88+
89+ def apply (self , wrapped : Callable [P , T ]) -> Callable [P , T ]:
90+ return self .__decorator ()(wrapped )
91+
92+ @contextmanager
93+ def __decorator (self ) -> Iterator [None ]:
94+ with self .loader .load (self .profile_name ):
95+ yield
96+
97+
98+ @runtime_checkable
2499class _EntrypointDecorator [** P , T1 , T2 ](Protocol ):
100+ __slots__ = ()
101+
25102 if TYPE_CHECKING : # pragma: no cover
26103
27104 @overload
@@ -42,18 +119,21 @@ def __call__(
42119 autocall : bool = ...,
43120 ) -> Callable [[Callable [P , T1 ]], Callable [P , T2 ]]: ...
44121
122+ @abstractmethod
45123 def __call__ (
46124 self ,
47125 wrapped : Callable [P , T1 ] | None = ...,
48126 / ,
49127 * ,
50128 autocall : bool = ...,
51- ) -> Any : ...
129+ ) -> Any :
130+ raise NotImplementedError
52131
53132
54133# SMP = Setup Method Parameters
55134# EPP = EntryPoint Parameters
56135
136+
57137if TYPE_CHECKING : # pragma: no cover
58138
59139 @overload
@@ -85,114 +165,74 @@ def entrypointmaker[**SMP, **EPP, T1, T2](
85165 def decorator (
86166 wp : EntrypointSetupMethod [SMP , EPP , T1 , T2 ],
87167 ) -> _EntrypointDecorator [EPP , T1 , T2 ]:
88- return Entrypoint ._make_decorator (wp , profile_loader )
168+ pl = (profile_loader or ProfileLoader ()).init ()
169+ setup_method = pl .module .make_injected_function (wp )
170+ return setup_method (EntrypointBuilder (pl )) # type: ignore[call-arg]
89171
90172 return decorator (wrapped ) if wrapped else decorator
91173
92174
93- @final
94175@dataclass (repr = False , eq = False , frozen = True , slots = True )
95- class Entrypoint [** P , T ]:
96- function : Callable [P , T ]
176+ class EntrypointBuilder [** P , T1 , T2 ](_EntrypointDecorator [P , T1 , T2 ]):
97177 profile_loader : ProfileLoader = field (default_factory = ProfileLoader )
178+ __rules : list [Rule [P , Any , Any ]] = field (default_factory = list , init = False )
179+
180+ def __call__ (
181+ self ,
182+ wrapped : Callable [P , T1 ] | None = None ,
183+ / ,
184+ * ,
185+ autocall : bool = False ,
186+ ) -> Any :
187+ def decorator (wp : Callable [P , T1 ]) -> Callable [P , T2 ]:
188+ wrapper = self ._apply (wp )
189+
190+ if autocall :
191+ wrapper () # type: ignore[call-arg]
98192
99- def __call__ (self , / , * args : P .args , ** kwargs : P .kwargs ) -> T :
100- return self .function (* args , ** kwargs )
193+ return wrapper
101194
102- @property
103- def __module (self ) -> Module :
104- return self .profile_loader .module
195+ return decorator (wrapped ) if wrapped else decorator
105196
106197 def async_to_sync [_T ](
107- self : AsyncEntrypoint [P , _T ],
198+ self : EntrypointBuilder [P , T1 , Awaitable [ _T ] ],
108199 run : Callable [[Coroutine [Any , Any , _T ]], _T ] = asyncio .run ,
109200 / ,
110- ) -> Entrypoint [ P , _T ]:
111- function = self .function
201+ ) -> EntrypointBuilder [ P , T1 , _T ]:
202+ return self ._add_rule ( _AsyncToSyncRule ( run )) # type: ignore[arg-type]
112203
113- @wraps (function )
114- def wrapper (* args : P .args , ** kwargs : P .kwargs ) -> _T :
115- return run (function (* args , ** kwargs ))
116-
117- return self .__recreate (wrapper )
118-
119- def decorate (
204+ def decorate [_T ](
120205 self ,
121- decorator : Callable [[Callable [P , T ]], Callable [P , T ]],
206+ decorator : Callable [[Callable [P , T2 ]], Callable [P , _T ]],
122207 / ,
123- ) -> Self :
124- return self .__recreate ( decorator ( self . function ))
208+ ) -> EntrypointBuilder [ P , T1 , _T ] :
209+ return self ._add_rule ( _DecorateRule ( decorator ))
125210
126211 def inject (self ) -> Self :
127- return self .decorate (self .__module .make_injected_function )
212+ self ._add_rule (_InjectRule (self .profile_loader .module ))
213+ return self
128214
129215 def load_modules (
130216 self ,
131- / ,
132217 loader : PythonModuleLoader ,
133218 * packages : PythonModule | str ,
134219 ) -> Self :
135- return self .setup (lambda : loader .load (* packages ))
220+ self ._add_rule (_LoadModulesRule (loader , packages ))
221+ return self
136222
137223 def load_profile (self , name : str , / ) -> Self :
138- @contextmanager
139- def decorator (loader : ProfileLoader ) -> Iterator [None ]:
140- with loader .load (name ):
141- yield
142-
143- return self .decorate (decorator (self .profile_loader ))
144-
145- def setup (self , function : Callable [..., Any ], / ) -> Self :
146- @contextmanager
147- def decorator () -> Iterator [Any ]:
148- yield function ()
224+ self ._add_rule (_LoadProfileRule (self .profile_loader , name ))
225+ return self
149226
150- return self .decorate (decorator ())
151-
152- def async_setup [_T ](
153- self : AsyncEntrypoint [P , _T ],
154- function : Callable [..., Awaitable [Any ]],
155- / ,
156- ) -> AsyncEntrypoint [P , _T ]:
157- @asynccontextmanager
158- async def decorator () -> AsyncIterator [Any ]:
159- yield await function ()
160-
161- return self .decorate (decorator ())
162-
163- def __recreate [** _P , _T ](
164- self : Entrypoint [Any , Any ],
165- function : Callable [_P , _T ],
166- / ,
167- ) -> Entrypoint [_P , _T ]:
168- return type (self )(function , self .profile_loader )
169-
170- @classmethod
171- def _make_decorator [** _P , _T ](
172- cls ,
173- setup_method : EntrypointSetupMethod [_P , P , T , _T ],
174- / ,
175- profile_loader : ProfileLoader | None = None ,
176- ) -> _EntrypointDecorator [P , T , _T ]:
177- profile_loader = profile_loader or ProfileLoader ()
178- setup_method = profile_loader .module .make_injected_function (setup_method )
179-
180- def entrypoint_decorator (
181- wrapped : Callable [P , T ] | None = None ,
182- / ,
183- * ,
184- autocall : bool = False ,
185- ) -> Any :
186- def decorator (wp : Callable [P , T ]) -> Callable [P , _T ]:
187- profile_loader .init ()
188- self = cls (wp , profile_loader )
189- wrapper = setup_method (self ).function # type: ignore[call-arg]
190-
191- if autocall :
192- wrapper () # type: ignore[call-arg]
193-
194- return wrapper
227+ def _add_rule [_T ](
228+ self ,
229+ rule : Rule [P , T2 , _T ],
230+ ) -> EntrypointBuilder [P , T1 , _T ]:
231+ self .__rules .append (rule )
232+ return self # type: ignore[return-value]
195233
196- return decorator (wrapped ) if wrapped else decorator
234+ def _apply (self , function : Callable [P , T1 ], / ) -> Callable [P , T2 ]:
235+ for rule in self .__rules :
236+ function = rule .apply (function )
197237
198- return entrypoint_decorator
238+ return function # type: ignore[return-value]
0 commit comments