rats-apps 0.1.3__py3-none-any.whl → 0.1.3.dev8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rats/apps/__init__.py +10 -33
- rats/apps/_annotations.py +136 -46
- rats/apps/_composite_container.py +1 -3
- rats/apps/_container.py +8 -104
- rats/apps/_ids.py +7 -3
- rats/apps/_plugin_container.py +2 -17
- rats/apps/_scoping.py +2 -6
- {rats_apps-0.1.3.dist-info → rats_apps-0.1.3.dev8.dist-info}/METADATA +1 -3
- rats_apps-0.1.3.dev8.dist-info/RECORD +13 -0
- rats_apps-0.1.3.dev8.dist-info/entry_points.txt +3 -0
- rats/annotations/__init__.py +0 -25
- rats/annotations/_functions.py +0 -145
- rats/annotations/py.typed +0 -0
- rats/apps/_executables.py +0 -34
- rats/apps/_plugins.py +0 -23
- rats/apps/_runtimes.py +0 -41
- rats/apps/_simple_apps.py +0 -163
- rats/cli/__init__.py +0 -22
- rats/cli/_annotations.py +0 -40
- rats/cli/_click.py +0 -38
- rats/cli/_executable.py +0 -23
- rats/cli/_plugin.py +0 -68
- rats/cli/_plugins.py +0 -81
- rats/cli/py.typed +0 -0
- rats/logs/__init__.py +0 -8
- rats/logs/_plugin.py +0 -63
- rats_apps-0.1.3.dist-info/RECORD +0 -29
- rats_apps-0.1.3.dist-info/entry_points.txt +0 -4
- {rats_apps-0.1.3.dist-info → rats_apps-0.1.3.dev8.dist-info}/WHEEL +0 -0
rats/annotations/_functions.py
DELETED
@@ -1,145 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from collections import defaultdict
|
4
|
-
from collections.abc import Callable
|
5
|
-
from functools import cache
|
6
|
-
from typing import Any, Generic, ParamSpec, TypeVar
|
7
|
-
from typing import NamedTuple as tNamedTuple
|
8
|
-
|
9
|
-
from typing_extensions import NamedTuple
|
10
|
-
|
11
|
-
T_GroupType = TypeVar("T_GroupType", bound=NamedTuple)
|
12
|
-
|
13
|
-
|
14
|
-
class GroupAnnotations(NamedTuple, Generic[T_GroupType]):
|
15
|
-
"""The list of T_GroupType objects identified by a given name in a namespace."""
|
16
|
-
|
17
|
-
name: str
|
18
|
-
"""The name of the annotation, typically the name of the function in a class"""
|
19
|
-
namespace: str
|
20
|
-
"""All the groups in a namespace are expected to be of the same T_GroupType."""
|
21
|
-
groups: tuple[T_GroupType, ...]
|
22
|
-
|
23
|
-
|
24
|
-
class AnnotationsContainer(tNamedTuple):
|
25
|
-
"""
|
26
|
-
Holds metadata about the annotated functions or class methods.
|
27
|
-
|
28
|
-
Loosely inspired by: https://peps.python.org/pep-3107/.
|
29
|
-
"""
|
30
|
-
|
31
|
-
annotations: tuple[GroupAnnotations[Any], ...]
|
32
|
-
|
33
|
-
@staticmethod
|
34
|
-
def empty() -> AnnotationsContainer:
|
35
|
-
return AnnotationsContainer(annotations=())
|
36
|
-
|
37
|
-
def with_group(
|
38
|
-
self,
|
39
|
-
namespace: str,
|
40
|
-
group_id: NamedTuple,
|
41
|
-
) -> AnnotationsContainer:
|
42
|
-
return AnnotationsContainer(
|
43
|
-
annotations=tuple(
|
44
|
-
[
|
45
|
-
annotation_group
|
46
|
-
for x in self.with_namespace(namespace)
|
47
|
-
for annotation_group in x
|
48
|
-
if group_id in annotation_group.groups
|
49
|
-
]
|
50
|
-
),
|
51
|
-
)
|
52
|
-
|
53
|
-
def with_namespace(
|
54
|
-
self,
|
55
|
-
namespace: str,
|
56
|
-
) -> AnnotationsContainer:
|
57
|
-
return AnnotationsContainer(
|
58
|
-
annotations=tuple([x for x in self.annotations if x.namespace == namespace]),
|
59
|
-
)
|
60
|
-
|
61
|
-
|
62
|
-
class AnnotationsBuilder:
|
63
|
-
_group_ids: dict[str, set[Any]]
|
64
|
-
|
65
|
-
def __init__(self) -> None:
|
66
|
-
self._group_ids = defaultdict(set)
|
67
|
-
|
68
|
-
def add(self, namespace: str, group_id: NamedTuple | tNamedTuple) -> None:
|
69
|
-
self._group_ids[namespace].add(group_id)
|
70
|
-
|
71
|
-
def make(self, name: str) -> AnnotationsContainer:
|
72
|
-
return AnnotationsContainer(
|
73
|
-
annotations=tuple(
|
74
|
-
[
|
75
|
-
GroupAnnotations[Any](name=name, namespace=namespace, groups=tuple(groups))
|
76
|
-
for namespace, groups in self._group_ids.items()
|
77
|
-
]
|
78
|
-
),
|
79
|
-
)
|
80
|
-
|
81
|
-
|
82
|
-
DecoratorType = TypeVar("DecoratorType", bound=Callable[..., Any])
|
83
|
-
|
84
|
-
|
85
|
-
def annotation(
|
86
|
-
namespace: str,
|
87
|
-
group_id: NamedTuple | tNamedTuple,
|
88
|
-
) -> Callable[[DecoratorType], DecoratorType]:
|
89
|
-
"""
|
90
|
-
Decorator to add an annotation to a function.
|
91
|
-
|
92
|
-
Typically used to create domain-specific annotation functions for things like DI Containers.
|
93
|
-
For examples, see the rats.apps annotations, like service() and group().
|
94
|
-
"""
|
95
|
-
|
96
|
-
def decorator(fn: DecoratorType) -> DecoratorType:
|
97
|
-
if not hasattr(fn, "__rats_annotations__"):
|
98
|
-
fn.__rats_annotations__ = AnnotationsBuilder() # type: ignore[reportFunctionMemberAccess]
|
99
|
-
|
100
|
-
fn.__rats_annotations__.add(namespace, group_id) # type: ignore[reportFunctionMemberAccess]
|
101
|
-
|
102
|
-
return fn
|
103
|
-
|
104
|
-
return decorator
|
105
|
-
|
106
|
-
|
107
|
-
@cache
|
108
|
-
def get_class_annotations(cls: type) -> AnnotationsContainer:
|
109
|
-
"""
|
110
|
-
Get all annotations for a class.
|
111
|
-
|
112
|
-
Traverses the class methods looking for any annotated with "__rats_annotations__" and returns
|
113
|
-
an instance of AnnotationsContainer. This function tries to cache the results to avoid any
|
114
|
-
expensive parsing of the class methods.
|
115
|
-
"""
|
116
|
-
tates: list[GroupAnnotations[Any]] = []
|
117
|
-
|
118
|
-
for method_name in dir(cls):
|
119
|
-
method = getattr(cls, method_name)
|
120
|
-
if not hasattr(method, "__rats_annotations__"):
|
121
|
-
continue
|
122
|
-
|
123
|
-
builder: AnnotationsBuilder = method.__rats_annotations__
|
124
|
-
tates.extend(builder.make(method_name).annotations)
|
125
|
-
|
126
|
-
return AnnotationsContainer(annotations=tuple(tates))
|
127
|
-
|
128
|
-
|
129
|
-
P = ParamSpec("P")
|
130
|
-
|
131
|
-
|
132
|
-
def get_annotations(fn: Callable[..., Any]) -> AnnotationsContainer:
|
133
|
-
"""
|
134
|
-
Get all annotations for a function or class method.
|
135
|
-
|
136
|
-
Builds an instance of AnnotationsContainer from the annotations found in the object's
|
137
|
-
"__rats_annotations__" attribute.
|
138
|
-
"""
|
139
|
-
builder: AnnotationsBuilder = getattr(
|
140
|
-
fn,
|
141
|
-
"__rats_annotations__",
|
142
|
-
AnnotationsBuilder(),
|
143
|
-
)
|
144
|
-
|
145
|
-
return builder.make(fn.__name__)
|
rats/annotations/py.typed
DELETED
File without changes
|
rats/apps/_executables.py
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
from abc import abstractmethod
|
2
|
-
from collections.abc import Callable
|
3
|
-
from typing import Protocol
|
4
|
-
|
5
|
-
|
6
|
-
class Executable(Protocol):
|
7
|
-
"""
|
8
|
-
An interface for an executable object.
|
9
|
-
|
10
|
-
One of the lowest level abstractions in the rats-apps library, executables are meant to be
|
11
|
-
easy to run from anywhere, with limited knowledge of the implementation details of the object,
|
12
|
-
by ensuring that the object has an `execute` method with no arguments.
|
13
|
-
"""
|
14
|
-
|
15
|
-
@abstractmethod
|
16
|
-
def execute(self) -> None:
|
17
|
-
"""Execute the application."""
|
18
|
-
|
19
|
-
|
20
|
-
class App(Executable):
|
21
|
-
"""
|
22
|
-
Wraps a plain callable objects as an executable.
|
23
|
-
|
24
|
-
This simple object allows for turning any callable object into an executable that is recognized
|
25
|
-
by the rest of the rats application.
|
26
|
-
"""
|
27
|
-
|
28
|
-
_callback: Callable[[], None]
|
29
|
-
|
30
|
-
def __init__(self, callback: Callable[[], None]) -> None:
|
31
|
-
self._callback = callback
|
32
|
-
|
33
|
-
def execute(self) -> None:
|
34
|
-
self._callback()
|
rats/apps/_plugins.py
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
import warnings
|
2
|
-
from collections.abc import Callable, Iterator
|
3
|
-
from typing import Generic, TypeVar
|
4
|
-
|
5
|
-
T_PluginType = TypeVar("T_PluginType")
|
6
|
-
|
7
|
-
|
8
|
-
class PluginRunner(Generic[T_PluginType]):
|
9
|
-
"""Client to apply a function to a list of plugins."""
|
10
|
-
|
11
|
-
_plugins: Iterator[T_PluginType]
|
12
|
-
|
13
|
-
def __init__(self, plugins: Iterator[T_PluginType]) -> None:
|
14
|
-
self._plugins = plugins
|
15
|
-
|
16
|
-
def apply(self, handler: Callable[[T_PluginType], None]) -> None:
|
17
|
-
warnings.warn(
|
18
|
-
"PluginRunner is deprecated. Use PluginContainer instances instead.",
|
19
|
-
DeprecationWarning,
|
20
|
-
stacklevel=2,
|
21
|
-
)
|
22
|
-
for plugin in self._plugins:
|
23
|
-
handler(plugin)
|
rats/apps/_runtimes.py
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
from abc import abstractmethod
|
2
|
-
from collections.abc import Callable
|
3
|
-
from typing import Protocol, final
|
4
|
-
|
5
|
-
from ._ids import ServiceId, T_ExecutableType
|
6
|
-
|
7
|
-
|
8
|
-
class Runtime(Protocol):
|
9
|
-
@abstractmethod
|
10
|
-
def execute(self, *exe_ids: ServiceId[T_ExecutableType]) -> None:
|
11
|
-
"""Execute a list of executables sequentially."""
|
12
|
-
|
13
|
-
@abstractmethod
|
14
|
-
def execute_group(self, *exe_group_ids: ServiceId[T_ExecutableType]) -> None:
|
15
|
-
"""
|
16
|
-
Execute one or more groups of executables sequentially.
|
17
|
-
|
18
|
-
Although each group is expected to be executed sequentially, the groups themselves are not
|
19
|
-
executed in a deterministic order. Runtime implementations are free to execute groups in
|
20
|
-
parallel or in any order that is convenient.
|
21
|
-
"""
|
22
|
-
|
23
|
-
@abstractmethod
|
24
|
-
def execute_callable(self, *callables: Callable[[], None]) -> None:
|
25
|
-
"""
|
26
|
-
Execute provided callables by automatically turning them into apps.Executable objects.
|
27
|
-
|
28
|
-
The used ServiceId is determined by the Runtime implementation.
|
29
|
-
"""
|
30
|
-
|
31
|
-
|
32
|
-
@final
|
33
|
-
class NullRuntime(Runtime):
|
34
|
-
def execute(self, *exe_ids: ServiceId[T_ExecutableType]) -> None:
|
35
|
-
raise NotImplementedError("NullRuntime does not support execution.")
|
36
|
-
|
37
|
-
def execute_group(self, *exe_group_ids: ServiceId[T_ExecutableType]) -> None:
|
38
|
-
raise NotImplementedError("NullRuntime does not support execution.")
|
39
|
-
|
40
|
-
def execute_callable(self, *callables: Callable[[], None]) -> None:
|
41
|
-
raise NotImplementedError("NullRuntime does not support execution.")
|
rats/apps/_simple_apps.py
DELETED
@@ -1,163 +0,0 @@
|
|
1
|
-
from collections.abc import Callable, Iterable, Iterator
|
2
|
-
from contextlib import contextmanager
|
3
|
-
from typing import final
|
4
|
-
|
5
|
-
from ._annotations import fallback_service, service
|
6
|
-
from ._composite_container import CompositeContainer
|
7
|
-
from ._container import Container, container
|
8
|
-
from ._executables import App, Executable
|
9
|
-
from ._ids import ServiceId
|
10
|
-
from ._plugin_container import PluginContainers
|
11
|
-
from ._runtimes import Runtime, T_ExecutableType
|
12
|
-
from ._scoping import autoscope
|
13
|
-
|
14
|
-
|
15
|
-
class ExecutableCallableContext(Executable):
|
16
|
-
"""
|
17
|
-
An executable that can be set dynamically with a callable.
|
18
|
-
|
19
|
-
We use this class to support the use of `rats.apps` with a plain callable, like is expected of
|
20
|
-
standard python scripts. We give this class a service id, and set the callable before using
|
21
|
-
`apps.Runtime` to execute the chosen service id.
|
22
|
-
"""
|
23
|
-
|
24
|
-
_exe_id: ServiceId[Executable]
|
25
|
-
_callables: list[Callable[[], None]]
|
26
|
-
|
27
|
-
def __init__(self, exe_id: ServiceId[Executable]) -> None:
|
28
|
-
self._exe_id = exe_id
|
29
|
-
self._callables = []
|
30
|
-
|
31
|
-
def execute(self) -> None:
|
32
|
-
if len(self._callables) == 0:
|
33
|
-
raise RuntimeError("No active executable found.")
|
34
|
-
|
35
|
-
self._callables[-1]()
|
36
|
-
|
37
|
-
@contextmanager
|
38
|
-
def open_callable(
|
39
|
-
self,
|
40
|
-
callable: Callable[[], None],
|
41
|
-
) -> Iterator[Executable]:
|
42
|
-
self._callables.append(callable)
|
43
|
-
try:
|
44
|
-
yield App(callable)
|
45
|
-
finally:
|
46
|
-
self._callables.pop()
|
47
|
-
|
48
|
-
|
49
|
-
@autoscope
|
50
|
-
class AppServices:
|
51
|
-
"""
|
52
|
-
Services used by simple apps that can generally be used anywhere.
|
53
|
-
|
54
|
-
Owners of applications can decide not to use or not to make these services available to plugin
|
55
|
-
authors.
|
56
|
-
"""
|
57
|
-
|
58
|
-
RUNTIME = ServiceId[Runtime]("app-runtime")
|
59
|
-
STANDARD_RUNTIME = ServiceId[Runtime]("standard-runtime")
|
60
|
-
CONTAINER = ServiceId[Container]("app-container")
|
61
|
-
CALLABLE_EXE_CTX = ServiceId[ExecutableCallableContext]("callable-exe-ctx")
|
62
|
-
CALLABLE_EXE = ServiceId[Executable]("callable-exe")
|
63
|
-
|
64
|
-
|
65
|
-
@final
|
66
|
-
class StandardRuntime(Runtime):
|
67
|
-
"""A simple runtime that executes sequentially and in a single thread."""
|
68
|
-
|
69
|
-
_app: Container
|
70
|
-
|
71
|
-
def __init__(self, app: Container) -> None:
|
72
|
-
self._app = app
|
73
|
-
|
74
|
-
def execute(self, *exe_ids: ServiceId[T_ExecutableType]) -> None:
|
75
|
-
for exe_id in exe_ids:
|
76
|
-
self._app.get(exe_id).execute()
|
77
|
-
|
78
|
-
def execute_group(self, *exe_group_ids: ServiceId[T_ExecutableType]) -> None:
|
79
|
-
for exe_group_id in exe_group_ids:
|
80
|
-
for exe in self._app.get_group(exe_group_id):
|
81
|
-
exe.execute()
|
82
|
-
|
83
|
-
def execute_callable(self, *callables: Callable[[], None]) -> None:
|
84
|
-
ctx: ExecutableCallableContext = self._app.get(AppServices.CALLABLE_EXE_CTX)
|
85
|
-
for cb in callables:
|
86
|
-
with ctx.open_callable(cb) as exe:
|
87
|
-
exe.execute()
|
88
|
-
|
89
|
-
|
90
|
-
@final
|
91
|
-
class SimpleApplication(Runtime, Container):
|
92
|
-
"""An application without anything fancy."""
|
93
|
-
|
94
|
-
_plugin_groups: Iterable[str]
|
95
|
-
_runtime_plugin: Callable[[Container], Container] | None
|
96
|
-
|
97
|
-
def __init__(
|
98
|
-
self,
|
99
|
-
*plugin_groups: str,
|
100
|
-
runtime_plugin: Callable[[Container], Container] | None = None,
|
101
|
-
) -> None:
|
102
|
-
self._plugin_groups = plugin_groups
|
103
|
-
self._runtime_plugin = runtime_plugin
|
104
|
-
|
105
|
-
def execute(self, *exe_ids: ServiceId[T_ExecutableType]) -> None:
|
106
|
-
self._runtime().execute(*exe_ids)
|
107
|
-
|
108
|
-
def execute_group(self, *exe_group_ids: ServiceId[T_ExecutableType]) -> None:
|
109
|
-
self._runtime().execute_group(*exe_group_ids)
|
110
|
-
|
111
|
-
def execute_callable(self, *callables: Callable[[], None]) -> None:
|
112
|
-
for cb in callables:
|
113
|
-
self._runtime().execute_callable(cb)
|
114
|
-
|
115
|
-
@fallback_service(AppServices.RUNTIME)
|
116
|
-
def _runtime(self) -> Runtime:
|
117
|
-
"""
|
118
|
-
The default runtime defers to AppServices.STANDARD_RUNTIME.
|
119
|
-
|
120
|
-
Define a non-fallback service to override this default implementation.
|
121
|
-
"""
|
122
|
-
return self.get(AppServices.STANDARD_RUNTIME)
|
123
|
-
|
124
|
-
@service(AppServices.STANDARD_RUNTIME)
|
125
|
-
def _std_runtime(self) -> Runtime:
|
126
|
-
"""
|
127
|
-
The standard locally executed runtime.
|
128
|
-
|
129
|
-
Regardless of the configured AppServices.RUNTIME provider, everyone has access to this
|
130
|
-
class for plugin development.
|
131
|
-
"""
|
132
|
-
return StandardRuntime(self)
|
133
|
-
|
134
|
-
@fallback_service(AppServices.CALLABLE_EXE)
|
135
|
-
def _callable_exe(self) -> Executable:
|
136
|
-
"""We use the callable exe ctx here, so we can treat it like any other app downstream."""
|
137
|
-
return self.get(AppServices.CALLABLE_EXE_CTX)
|
138
|
-
|
139
|
-
@fallback_service(AppServices.CALLABLE_EXE_CTX)
|
140
|
-
def _callable_exe_ctx(self) -> ExecutableCallableContext:
|
141
|
-
"""
|
142
|
-
The default executable context client for executing raw callables.
|
143
|
-
|
144
|
-
Define a non-fallback service to override this default implementation.
|
145
|
-
"""
|
146
|
-
return ExecutableCallableContext(AppServices.CALLABLE_EXE)
|
147
|
-
|
148
|
-
@fallback_service(AppServices.CONTAINER)
|
149
|
-
def _container(self) -> Container:
|
150
|
-
"""
|
151
|
-
The default container is the root application instance.
|
152
|
-
|
153
|
-
Define a non-fallback service to override this default implementation.
|
154
|
-
"""
|
155
|
-
return self
|
156
|
-
|
157
|
-
@container()
|
158
|
-
def _plugins(self) -> Container:
|
159
|
-
plugins: list[Container] = [PluginContainers(self, group) for group in self._plugin_groups]
|
160
|
-
if self._runtime_plugin:
|
161
|
-
plugins.append(self._runtime_plugin(self))
|
162
|
-
|
163
|
-
return CompositeContainer(*plugins)
|
rats/cli/__init__.py
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
"""Uses `rats.cli` to streamline the creation of CLI commands written with Click."""
|
2
|
-
|
3
|
-
from ._annotations import CommandId, command, group
|
4
|
-
from ._click import ClickCommandGroup, ClickCommandMapper
|
5
|
-
from ._executable import ClickExecutable
|
6
|
-
from ._plugin import PluginContainer, PluginServices
|
7
|
-
from ._plugins import AttachClickCommands, AttachClickGroup, ClickGroupPlugin, CommandContainer
|
8
|
-
|
9
|
-
__all__ = [
|
10
|
-
"CommandId",
|
11
|
-
"PluginContainer",
|
12
|
-
"command",
|
13
|
-
"group",
|
14
|
-
"PluginServices",
|
15
|
-
"ClickCommandMapper",
|
16
|
-
"ClickExecutable",
|
17
|
-
"ClickGroupPlugin",
|
18
|
-
"ClickCommandGroup",
|
19
|
-
"AttachClickCommands",
|
20
|
-
"AttachClickGroup",
|
21
|
-
"CommandContainer",
|
22
|
-
]
|
rats/cli/_annotations.py
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from collections.abc import Callable
|
4
|
-
from typing import Any, NamedTuple, TypeVar
|
5
|
-
|
6
|
-
from rats import annotations as anns
|
7
|
-
|
8
|
-
|
9
|
-
class CommandId(NamedTuple):
|
10
|
-
name: str
|
11
|
-
|
12
|
-
# does this api make it impossible to reference a given command that was auto generated?
|
13
|
-
@staticmethod
|
14
|
-
def auto() -> CommandId:
|
15
|
-
return CommandId(name=f"{__name__}:auto")
|
16
|
-
|
17
|
-
|
18
|
-
T = TypeVar("T", bound=Callable[[Any], Any])
|
19
|
-
|
20
|
-
|
21
|
-
def command(command_id: CommandId) -> Callable[[T], T]:
|
22
|
-
def decorator(fn: T) -> T:
|
23
|
-
if command_id == CommandId.auto():
|
24
|
-
return anns.annotation("commands", CommandId(fn.__name__.replace("_", "-")))(fn)
|
25
|
-
return anns.annotation("commands", command_id)(fn)
|
26
|
-
|
27
|
-
return decorator
|
28
|
-
|
29
|
-
|
30
|
-
def group(command_id: CommandId) -> Callable[[T], T]:
|
31
|
-
def decorator(fn: T) -> T:
|
32
|
-
if command_id == CommandId.auto():
|
33
|
-
return anns.annotation("command-groups", CommandId(fn.__name__.replace("_", "-")))(fn)
|
34
|
-
return anns.annotation("commands", command_id)(fn)
|
35
|
-
|
36
|
-
return decorator
|
37
|
-
|
38
|
-
|
39
|
-
def get_class_commands(cls: type) -> anns.AnnotationsContainer:
|
40
|
-
return anns.get_class_annotations(cls).with_namespace("commands")
|
rats/cli/_click.py
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
from collections.abc import Callable, Mapping
|
2
|
-
from typing import final
|
3
|
-
|
4
|
-
import click
|
5
|
-
|
6
|
-
|
7
|
-
class ClickCommandMapper:
|
8
|
-
_commands: Mapping[str, Callable[[], click.Command]]
|
9
|
-
|
10
|
-
def __init__(
|
11
|
-
self,
|
12
|
-
commands: Mapping[str, Callable[[], click.Command]],
|
13
|
-
) -> None:
|
14
|
-
self._commands = commands
|
15
|
-
|
16
|
-
def names(self) -> frozenset[str]:
|
17
|
-
return frozenset(self._commands.keys())
|
18
|
-
|
19
|
-
def get(self, name: str) -> click.Command:
|
20
|
-
if name not in self._commands:
|
21
|
-
raise ValueError(f"Command {name} not found")
|
22
|
-
|
23
|
-
return self._commands[name]()
|
24
|
-
|
25
|
-
|
26
|
-
@final
|
27
|
-
class ClickCommandGroup(click.Group):
|
28
|
-
_mapper: ClickCommandMapper
|
29
|
-
|
30
|
-
def __init__(self, name: str, mapper: ClickCommandMapper) -> None:
|
31
|
-
super().__init__(name=name)
|
32
|
-
self._mapper = mapper
|
33
|
-
|
34
|
-
def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
|
35
|
-
return self._mapper.get(cmd_name)
|
36
|
-
|
37
|
-
def list_commands(self, ctx: click.Context) -> list[str]:
|
38
|
-
return list(self._mapper.names())
|
rats/cli/_executable.py
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
import click
|
2
|
-
|
3
|
-
from rats import apps
|
4
|
-
|
5
|
-
from ._plugins import ClickGroupPlugin
|
6
|
-
|
7
|
-
|
8
|
-
class ClickExecutable(apps.Executable):
|
9
|
-
_command: apps.ServiceProvider[click.Group]
|
10
|
-
_plugins: apps.PluginRunner[ClickGroupPlugin]
|
11
|
-
|
12
|
-
def __init__(
|
13
|
-
self,
|
14
|
-
command: apps.ServiceProvider[click.Group],
|
15
|
-
plugins: apps.PluginRunner[ClickGroupPlugin],
|
16
|
-
) -> None:
|
17
|
-
self._command = command
|
18
|
-
self._plugins = plugins
|
19
|
-
|
20
|
-
def execute(self) -> None:
|
21
|
-
cmd = self._command()
|
22
|
-
self._plugins.apply(lambda plugin: plugin.on_group_open(cmd))
|
23
|
-
cmd()
|
rats/cli/_plugin.py
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
from typing import cast, final
|
2
|
-
|
3
|
-
import click
|
4
|
-
|
5
|
-
from rats import apps
|
6
|
-
|
7
|
-
|
8
|
-
@apps.autoscope
|
9
|
-
class _PluginEvents:
|
10
|
-
@staticmethod
|
11
|
-
def command_open(cmd_id: apps.ServiceId[apps.Executable]) -> apps.ServiceId[apps.Executable]:
|
12
|
-
return apps.ServiceId(f"command-open[{cmd_id.name}]")
|
13
|
-
|
14
|
-
@staticmethod
|
15
|
-
def command_execute(
|
16
|
-
cmd_id: apps.ServiceId[apps.Executable],
|
17
|
-
) -> apps.ServiceId[apps.Executable]:
|
18
|
-
return apps.ServiceId(f"command-execute[{cmd_id.name}]")
|
19
|
-
|
20
|
-
@staticmethod
|
21
|
-
def command_close(cmd_id: apps.ServiceId[apps.Executable]) -> apps.ServiceId[apps.Executable]:
|
22
|
-
return apps.ServiceId(f"command-close[{cmd_id.name}]")
|
23
|
-
|
24
|
-
|
25
|
-
@apps.autoscope
|
26
|
-
class PluginServices:
|
27
|
-
ROOT_COMMAND = apps.ServiceId[apps.Executable]("root-command")
|
28
|
-
EVENTS = _PluginEvents
|
29
|
-
|
30
|
-
@staticmethod
|
31
|
-
def sub_command(
|
32
|
-
parent: apps.ServiceId[apps.Executable],
|
33
|
-
name: str,
|
34
|
-
) -> apps.ServiceId[apps.Executable]:
|
35
|
-
return apps.ServiceId(f"{parent.name}[{name}]")
|
36
|
-
|
37
|
-
@staticmethod
|
38
|
-
def click_command(cmd_id: apps.ServiceId[apps.Executable]) -> apps.ServiceId[click.Group]:
|
39
|
-
return cast(apps.ServiceId[click.Group], cmd_id)
|
40
|
-
|
41
|
-
|
42
|
-
@final
|
43
|
-
class PluginContainer(apps.Container):
|
44
|
-
_app: apps.Container
|
45
|
-
|
46
|
-
def __init__(self, app: apps.Container) -> None:
|
47
|
-
self._app = app
|
48
|
-
|
49
|
-
@apps.service(PluginServices.ROOT_COMMAND)
|
50
|
-
def _root_command(self) -> apps.Executable:
|
51
|
-
def run() -> None:
|
52
|
-
runtime = self._app.get(apps.AppServices.RUNTIME)
|
53
|
-
runtime.execute_group(
|
54
|
-
PluginServices.EVENTS.command_open(PluginServices.ROOT_COMMAND),
|
55
|
-
PluginServices.EVENTS.command_execute(PluginServices.ROOT_COMMAND),
|
56
|
-
PluginServices.EVENTS.command_close(PluginServices.ROOT_COMMAND),
|
57
|
-
)
|
58
|
-
|
59
|
-
return apps.App(run)
|
60
|
-
|
61
|
-
@apps.fallback_group(PluginServices.EVENTS.command_execute(PluginServices.ROOT_COMMAND))
|
62
|
-
def _default_command(self) -> apps.Executable:
|
63
|
-
group = self._app.get(PluginServices.click_command(PluginServices.ROOT_COMMAND))
|
64
|
-
return apps.App(lambda: group())
|
65
|
-
|
66
|
-
@apps.service(PluginServices.click_command(PluginServices.ROOT_COMMAND))
|
67
|
-
def _root_click_command(self) -> click.Group:
|
68
|
-
return click.Group("groot")
|
rats/cli/_plugins.py
DELETED
@@ -1,81 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from abc import abstractmethod
|
4
|
-
from collections.abc import Callable, Iterator
|
5
|
-
from functools import partial
|
6
|
-
from typing import Any, Protocol
|
7
|
-
|
8
|
-
import click
|
9
|
-
|
10
|
-
from rats import apps
|
11
|
-
|
12
|
-
from ._annotations import get_class_commands
|
13
|
-
|
14
|
-
|
15
|
-
class ClickGroupPlugin(Protocol):
|
16
|
-
@abstractmethod
|
17
|
-
def on_group_open(self, group: click.Group) -> None:
|
18
|
-
pass
|
19
|
-
|
20
|
-
|
21
|
-
class AttachClickCommands(ClickGroupPlugin):
|
22
|
-
"""When a group is opened, attach a set of commands to it."""
|
23
|
-
|
24
|
-
_commands: Iterator[click.Command]
|
25
|
-
|
26
|
-
def __init__(self, commands: Iterator[click.Command]) -> None:
|
27
|
-
self._commands = commands
|
28
|
-
|
29
|
-
def on_group_open(self, group: click.Group) -> None:
|
30
|
-
for command in self._commands:
|
31
|
-
group.add_command(command)
|
32
|
-
|
33
|
-
|
34
|
-
class AttachClickGroup(ClickGroupPlugin):
|
35
|
-
_group: apps.ServiceProvider[click.Group]
|
36
|
-
_plugins: apps.PluginRunner[ClickGroupPlugin]
|
37
|
-
|
38
|
-
def __init__(
|
39
|
-
self,
|
40
|
-
group: apps.ServiceProvider[click.Group],
|
41
|
-
plugins: apps.PluginRunner[ClickGroupPlugin],
|
42
|
-
) -> None:
|
43
|
-
self._group = group
|
44
|
-
self._plugins = plugins
|
45
|
-
|
46
|
-
def on_group_open(self, group: click.Group) -> None:
|
47
|
-
cmd = self._group()
|
48
|
-
self._plugins.apply(lambda plugin: plugin.on_group_open(cmd))
|
49
|
-
group.add_command(cmd)
|
50
|
-
|
51
|
-
|
52
|
-
class CommandContainer(ClickGroupPlugin):
|
53
|
-
def on_group_open(self, group: click.Group) -> None:
|
54
|
-
def cb(_method: Callable[[Any], Any], *args: Any, **kwargs: Any) -> None:
|
55
|
-
"""
|
56
|
-
Callback handed to `click.Command`. Calls the method with matching name on this class.
|
57
|
-
|
58
|
-
When the command is decorated with `@click.params` and `@click.option`, `click` will
|
59
|
-
call this callback with the parameters in the order they were defined. This callback
|
60
|
-
then calls the method with the same name on this class, passing the parameters in
|
61
|
-
reverse order. This is because the method is defined with the parameters in the
|
62
|
-
reverse order to the decorator, so we need to reverse them again to get the correct
|
63
|
-
order.
|
64
|
-
"""
|
65
|
-
_method(*args, **kwargs)
|
66
|
-
|
67
|
-
commands = get_class_commands(type(self))
|
68
|
-
tates = commands.annotations
|
69
|
-
|
70
|
-
for tate in tates:
|
71
|
-
method = getattr(self, tate.name)
|
72
|
-
params = list(reversed(getattr(method, "__click_params__", [])))
|
73
|
-
for command in tate.groups:
|
74
|
-
group.add_command(
|
75
|
-
click.Command(
|
76
|
-
name=command.name,
|
77
|
-
callback=partial(cb, method),
|
78
|
-
short_help=method.__doc__,
|
79
|
-
params=params,
|
80
|
-
)
|
81
|
-
)
|
rats/cli/py.typed
DELETED
File without changes
|