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 , 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+ Coroutine ,
15+ Protocol ,
16+ Self ,
17+ overload ,
18+ runtime_checkable ,
19+ )
1020
1121from injection import Module
1222from injection .loaders import ProfileLoader , PythonModuleLoader
1323
1424__all__ = ("AsyncEntrypoint" , "Entrypoint" , "entrypointmaker" )
1525
26+ type Entrypoint [** P , T ] = EntrypointBuilder [P , Any , T ]
27+ type AsyncEntrypoint [** P , T ] = Entrypoint [P , Awaitable [T ]]
1628
17- type AsyncEntrypoint [** P , T ] = Entrypoint [P , Coroutine [Any , Any , T ]]
1829type EntrypointSetupMethod [** P , ** EPP , T1 , T2 ] = Callable [
1930 Concatenate [Entrypoint [EPP , T1 ], P ],
2031 Entrypoint [EPP , T2 ],
2132]
2233
2334
35+ class Rule [** P , T1 , T2 ](ABC ):
36+ __slots__ = ()
37+
38+ @abstractmethod
39+ def apply (self , wrapped : Callable [P , T1 ]) -> Callable [P , T2 ]:
40+ raise NotImplementedError
41+
42+
43+ @dataclass (repr = False , eq = False , frozen = True , slots = True )
44+ class _AsyncToSyncRule [** P , T ](Rule [P , Awaitable [T ], T ]):
45+ run : Callable [[Awaitable [T ]], T ]
46+
47+ def apply (self , wrapped : Callable [P , Awaitable [T ]]) -> Callable [P , T ]:
48+ @wraps (wrapped )
49+ def wrapper (* args : P .args , ** kwargs : P .kwargs ) -> T :
50+ return self .run (wrapped (* args , ** kwargs ))
51+
52+ return wrapper
53+
54+
55+ @dataclass (repr = False , eq = False , frozen = True , slots = True )
56+ class _DecorateRule [** P , T1 , T2 ](Rule [P , T1 , T2 ]):
57+ decorator : Callable [[Callable [P , T1 ]], Callable [P , T2 ]]
58+
59+ def apply (self , wrapped : Callable [P , T1 ]) -> Callable [P , T2 ]:
60+ return self .decorator (wrapped )
61+
62+
63+ @dataclass (repr = False , eq = False , frozen = True , slots = True )
64+ class _InjectRule [** P , T ](Rule [P , T , T ]):
65+ module : Module
66+
67+ def apply (self , wrapped : Callable [P , T ]) -> Callable [P , T ]:
68+ return self .module .make_injected_function (wrapped )
69+
70+
71+ @dataclass (repr = False , eq = False , frozen = True , slots = True )
72+ class _LoadModulesRule [** P , T ](Rule [P , T , T ]):
73+ loader : PythonModuleLoader
74+ packages : Sequence [PythonModule | str ]
75+
76+ def apply (self , wrapped : Callable [P , T ]) -> Callable [P , T ]:
77+ return self .__decorator ()(wrapped )
78+
79+ @contextmanager
80+ def __decorator (self ) -> Iterator [None ]:
81+ self .loader .load (* self .packages )
82+ yield
83+
84+
85+ @dataclass (repr = False , eq = False , frozen = True , slots = True )
86+ class _LoadProfileRule [** P , T ](Rule [P , T , T ]):
87+ loader : ProfileLoader
88+ profile_name : str
89+
90+ def apply (self , wrapped : Callable [P , T ]) -> Callable [P , T ]:
91+ return self .__decorator ()(wrapped )
92+
93+ @contextmanager
94+ def __decorator (self ) -> Iterator [None ]:
95+ with self .loader .load (self .profile_name ):
96+ yield
97+
98+
99+ @runtime_checkable
24100class _EntrypointDecorator [** P , T1 , T2 ](Protocol ):
101+ __slots__ = ()
102+
25103 if TYPE_CHECKING : # pragma: no cover
26104
27105 @overload
@@ -42,18 +120,21 @@ def __call__(
42120 autocall : bool = ...,
43121 ) -> Callable [[Callable [P , T1 ]], Callable [P , T2 ]]: ...
44122
123+ @abstractmethod
45124 def __call__ (
46125 self ,
47126 wrapped : Callable [P , T1 ] | None = ...,
48127 / ,
49128 * ,
50129 autocall : bool = ...,
51- ) -> Any : ...
130+ ) -> Any :
131+ raise NotImplementedError
52132
53133
54134# SMP = Setup Method Parameters
55135# EPP = EntryPoint Parameters
56136
137+
57138if TYPE_CHECKING : # pragma: no cover
58139
59140 @overload
@@ -85,114 +166,74 @@ def entrypointmaker[**SMP, **EPP, T1, T2](
85166 def decorator (
86167 wp : EntrypointSetupMethod [SMP , EPP , T1 , T2 ],
87168 ) -> _EntrypointDecorator [EPP , T1 , T2 ]:
88- return Entrypoint ._make_decorator (wp , profile_loader )
169+ pl = (profile_loader or ProfileLoader ()).init ()
170+ setup_method = pl .module .make_injected_function (wp )
171+ return setup_method (EntrypointBuilder (pl )) # type: ignore[call-arg]
89172
90173 return decorator (wrapped ) if wrapped else decorator
91174
92175
93- @final
94176@dataclass (repr = False , eq = False , frozen = True , slots = True )
95- class Entrypoint [** P , T ]:
96- function : Callable [P , T ]
177+ class EntrypointBuilder [** P , T1 , T2 ](_EntrypointDecorator [P , T1 , T2 ]):
97178 profile_loader : ProfileLoader = field (default_factory = ProfileLoader )
179+ __rules : list [Rule [P , Any , Any ]] = field (default_factory = list , init = False )
180+
181+ def __call__ (
182+ self ,
183+ wrapped : Callable [P , T1 ] | None = None ,
184+ / ,
185+ * ,
186+ autocall : bool = False ,
187+ ) -> Any :
188+ def decorator (wp : Callable [P , T1 ]) -> Callable [P , T2 ]:
189+ wrapper = self ._apply (wp )
190+
191+ if autocall :
192+ wrapper () # type: ignore[call-arg]
98193
99- def __call__ (self , / , * args : P .args , ** kwargs : P .kwargs ) -> T :
100- return self .function (* args , ** kwargs )
194+ return wrapper
101195
102- @property
103- def __module (self ) -> Module :
104- return self .profile_loader .module
196+ return decorator (wrapped ) if wrapped else decorator
105197
106198 def async_to_sync [_T ](
107- self : AsyncEntrypoint [P , _T ],
199+ self : EntrypointBuilder [P , T1 , Awaitable [ _T ] ],
108200 run : Callable [[Coroutine [Any , Any , _T ]], _T ] = asyncio .run ,
109201 / ,
110- ) -> Entrypoint [ P , _T ]:
111- function = self .function
202+ ) -> EntrypointBuilder [ P , T1 , _T ]:
203+ return self ._add_rule ( _AsyncToSyncRule ( run )) # type: ignore[arg-type]
112204
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 (
205+ def decorate [_T ](
120206 self ,
121- decorator : Callable [[Callable [P , T ]], Callable [P , T ]],
207+ decorator : Callable [[Callable [P , T2 ]], Callable [P , _T ]],
122208 / ,
123- ) -> Self :
124- return self .__recreate ( decorator ( self . function ))
209+ ) -> EntrypointBuilder [ P , T1 , _T ] :
210+ return self ._add_rule ( _DecorateRule ( decorator ))
125211
126212 def inject (self ) -> Self :
127- return self .decorate (self .__module .make_injected_function )
213+ self ._add_rule (_InjectRule (self .profile_loader .module ))
214+ return self
128215
129216 def load_modules (
130217 self ,
131- / ,
132218 loader : PythonModuleLoader ,
133219 * packages : PythonModule | str ,
134220 ) -> Self :
135- return self .setup (lambda : loader .load (* packages ))
221+ self ._add_rule (_LoadModulesRule (loader , packages ))
222+ return self
136223
137224 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 ()
225+ self ._add_rule (_LoadProfileRule (self .profile_loader , name ))
226+ return self
149227
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
228+ def _add_rule [_T ](
229+ self ,
230+ rule : Rule [P , T2 , _T ],
231+ ) -> EntrypointBuilder [P , T1 , _T ]:
232+ self .__rules .append (rule )
233+ return self # type: ignore[return-value]
195234
196- return decorator (wrapped ) if wrapped else decorator
235+ def _apply (self , function : Callable [P , T1 ], / ) -> Callable [P , T2 ]:
236+ for rule in self .__rules :
237+ function = rule .apply (function )
197238
198- return entrypoint_decorator
239+ return function # type: ignore[return-value]
0 commit comments