Skip to content

Commit db8317a

Browse files
authored
refactoring: ♻️ Entrypoint autocall
1 parent f5210f2 commit db8317a

5 files changed

Lines changed: 145 additions & 116 deletions

File tree

documentation/entrypoint.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ Developing a CLI is a good example of using multiple entrypoints:
8282
```python
8383
# src/cli.py
8484

85-
from injection.entrypoint import autocall
8685
from typer import Typer
8786

8887
from src.entrypoint import entrypoint # the previously defined `entrypoint` decorator
@@ -92,15 +91,13 @@ app = Typer()
9291

9392
@app.command()
9493
def hello(name: str) -> None:
95-
@autocall # allows automatically calling the function
96-
@entrypoint
94+
@entrypoint(autocall=True)
9795
async def _(logger: AsyncLogger) -> None:
9896
await logger.info(f"Hello {name}!")
9997

10098
@app.command()
10199
def goodbye(name: str) -> None:
102-
@autocall
103-
@entrypoint
100+
@entrypoint(autocall=True)
104101
async def _(logger: AsyncLogger) -> None:
105102
await logger.info(f"Goodbye {name}!")
106103

injection/entrypoint.py

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,49 @@
77
from functools import wraps
88
from types import MethodType
99
from types import ModuleType as PythonModule
10-
from typing import TYPE_CHECKING, Any, Concatenate, Self, final, overload
10+
from typing import TYPE_CHECKING, Any, Concatenate, Protocol, Self, final, overload
1111

1212
from injection import Module
1313
from injection.loaders import ProfileLoader, PythonModuleLoader
1414

15-
__all__ = ("AsyncEntrypoint", "Entrypoint", "autocall", "entrypointmaker")
15+
__all__ = ("AsyncEntrypoint", "Entrypoint", "entrypointmaker")
1616

17-
type AsyncEntrypoint[**P, T] = Entrypoint[P, Coroutine[Any, Any, T]]
18-
type EntrypointDecorator[**P, T1, T2] = Callable[[Callable[P, T1]], Callable[P, T2]]
19-
type EntrypointSetupMethod[**P, **EPP, T1, T2] = Callable[
20-
Concatenate[Entrypoint[EPP, T1], P],
21-
Entrypoint[EPP, T2],
22-
]
2317

24-
if TYPE_CHECKING: # pragma: no cover
18+
class _EntrypointDecorator[**P, T1, T2](Protocol):
19+
if TYPE_CHECKING: # pragma: no cover
2520

26-
@overload
27-
def autocall[T: Callable[..., Any]](wrapped: T, /) -> T: ...
28-
29-
@overload
30-
def autocall[T: Callable[..., Any]](wrapped: None = ..., /) -> Callable[[T], T]: ...
21+
@overload
22+
def __call__(
23+
self,
24+
wrapped: Callable[P, T1],
25+
/,
26+
*,
27+
autocall: bool = ...,
28+
) -> Callable[P, T2]: ...
3129

30+
@overload
31+
def __call__(
32+
self,
33+
wrapped: None = ...,
34+
/,
35+
*,
36+
autocall: bool = ...,
37+
) -> Callable[[Callable[P, T1]], Callable[P, T2]]: ...
3238

33-
def autocall[T: Callable[..., Any]](
34-
wrapped: T | None = None,
35-
/,
36-
) -> T | Callable[[T], T]:
37-
def decorator(wp: T) -> T:
38-
wp()
39-
return wp
39+
def __call__(
40+
self,
41+
wrapped: Callable[P, T1] | None = ...,
42+
/,
43+
*,
44+
autocall: bool = ...,
45+
) -> Any: ...
4046

41-
return decorator(wrapped) if wrapped else decorator
4247

48+
type AsyncEntrypoint[**P, T] = Entrypoint[P, Coroutine[Any, Any, T]]
49+
type EntrypointSetupMethod[**P, **EPP, T1, T2] = Callable[
50+
Concatenate[Entrypoint[EPP, T1], P],
51+
Entrypoint[EPP, T2],
52+
]
4353

4454
# SMP = Setup Method Parameters
4555
# EPP = EntryPoint Parameters
@@ -52,7 +62,7 @@ def entrypointmaker[**SMP, **EPP, T1, T2](
5262
/,
5363
*,
5464
profile_loader: ProfileLoader = ...,
55-
) -> EntrypointDecorator[EPP, T1, T2]: ...
65+
) -> _EntrypointDecorator[EPP, T1, T2]: ...
5666

5767
@overload
5868
def entrypointmaker[**SMP, **EPP, T1, T2](
@@ -62,7 +72,7 @@ def entrypointmaker[**SMP, **EPP, T1, T2](
6272
profile_loader: ProfileLoader = ...,
6373
) -> Callable[
6474
[EntrypointSetupMethod[SMP, EPP, T1, T2]],
65-
EntrypointDecorator[EPP, T1, T2],
75+
_EntrypointDecorator[EPP, T1, T2],
6676
]: ...
6777

6878

@@ -74,7 +84,7 @@ def entrypointmaker[**SMP, **EPP, T1, T2](
7484
) -> Any:
7585
def decorator(
7686
wp: EntrypointSetupMethod[SMP, EPP, T1, T2],
77-
) -> EntrypointDecorator[EPP, T1, T2]:
87+
) -> _EntrypointDecorator[EPP, T1, T2]:
7888
return Entrypoint._make_decorator(wp, profile_loader)
7989

8090
return decorator(wrapped) if wrapped else decorator
@@ -163,13 +173,26 @@ def _make_decorator[**_P, _T](
163173
setup_method: EntrypointSetupMethod[_P, P, T, _T],
164174
/,
165175
profile_loader: ProfileLoader | None = None,
166-
) -> EntrypointDecorator[P, T, _T]:
176+
) -> _EntrypointDecorator[P, T, _T]:
167177
profile_loader = profile_loader or ProfileLoader()
168178
setup_method = profile_loader.module.make_injected_function(setup_method)
169179

170-
def decorator(function: Callable[P, T]) -> Callable[P, _T]:
171-
profile_loader.init()
172-
self = cls(function, profile_loader)
173-
return MethodType(setup_method, self)().function
180+
def entrypoint_decorator(
181+
function: Callable[P, T] | None = None,
182+
/,
183+
*,
184+
autocall: bool = False,
185+
) -> Any:
186+
def decorator(fn: Callable[P, T]) -> Callable[P, _T]:
187+
profile_loader.init()
188+
self = cls(fn, profile_loader)
189+
wrapper = MethodType(setup_method, self)().function
190+
191+
if autocall:
192+
wrapper()
193+
194+
return wrapper
195+
196+
return decorator(function) if function else decorator
174197

175-
return decorator
198+
return entrypoint_decorator

tests/entrypoint/test_autocall.py

Lines changed: 0 additions & 12 deletions
This file was deleted.

tests/entrypoint/test_entrypointmaker.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,20 @@ def function(): ...
1717

1818
function()
1919
assert count == 1
20+
21+
22+
def test_entrypointmaker_with_autocall_return_entrypoint_decorator():
23+
count = 0
24+
25+
def increment() -> None:
26+
nonlocal count
27+
count += 1
28+
29+
@entrypointmaker
30+
def entrypoint[**P, T](self: Entrypoint[P, T]) -> Entrypoint[P, T]:
31+
return self.setup(increment)
32+
33+
@entrypoint(autocall=True)
34+
def _(): ...
35+
36+
assert count == 1

0 commit comments

Comments
 (0)