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+ typeEntrypoint [** P ,T ]= EntrypointBuilder [P ,Any ,T ]
26+ typeAsyncEntrypoint [** P ,T ]= Entrypoint [P ,Awaitable [T ]]
1627
17- typeAsyncEntrypoint [** P ,T ]= Entrypoint [P ,Coroutine [Any ,Any ,T ]]
1828typeEntrypointSetupMethod [** P ,** EPP ,T1 ,T2 ]= Callable [
1929Concatenate [Entrypoint [EPP ,T1 ],P ],
2030Entrypoint [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+
25102if TYPE_CHECKING :# pragma: no cover
26103
27104@overload
@@ -42,18 +119,21 @@ def __call__(
42119autocall :bool = ...,
43120 )-> Callable [[Callable [P ,T1 ]],Callable [P ,T2 ]]: ...
44121
122+ @abstractmethod
45123def __call__ (
46124self ,
47125wrapped :Callable [P ,T1 ]| None = ...,
48126/ ,
49127* ,
50128autocall :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](
85165def decorator (
86166wp :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
90172return 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 ]):
97177profile_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
106197def async_to_sync [_T ](
107- self :AsyncEntrypoint [P ,_T ],
198+ self :EntrypointBuilder [P ,T1 , Awaitable [ _T ] ],
108199run :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 ](
120205self ,
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
126211def 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
129215def load_modules (
130216self ,
131- / ,
132217loader :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
137223def 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]