rats-apps 0.1.3.dev75__py3-none-any.whl → 0.1.3.dev20240624080525__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/annotations/_functions.py +27 -7
- rats/apps/__init__.py +12 -5
- rats/apps/_annotations.py +34 -150
- rats/apps/_container.py +100 -4
- rats/apps/_executables.py +32 -21
- rats/apps/_plugin_container.py +7 -2
- rats/apps/_plugins.py +6 -0
- rats/apps/_runtimes.py +32 -0
- rats/apps/_scoping.py +4 -4
- rats/apps/_simple_apps.py +144 -0
- rats/cli/__init__.py +4 -1
- rats/cli/_plugin.py +69 -0
- rats/logs/__init__.py +8 -0
- rats/logs/_plugin.py +60 -0
- {rats_apps-0.1.3.dev75.dist-info → rats_apps-0.1.3.dev20240624080525.dist-info}/METADATA +2 -1
- rats_apps-0.1.3.dev20240624080525.dist-info/RECORD +29 -0
- rats_apps-0.1.3.dev20240624080525.dist-info/entry_points.txt +4 -0
- rats_apps-0.1.3.dev75.dist-info/RECORD +0 -23
- {rats_apps-0.1.3.dev75.dist-info → rats_apps-0.1.3.dev20240624080525.dist-info}/WHEEL +0 -0
rats/annotations/_functions.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# type: ignore
|
2
1
|
from __future__ import annotations
|
3
2
|
|
4
3
|
from collections import defaultdict
|
@@ -22,14 +21,14 @@ class GroupAnnotations(NamedTuple, Generic[T_GroupType]):
|
|
22
21
|
groups: tuple[T_GroupType, ...]
|
23
22
|
|
24
23
|
|
25
|
-
class AnnotationsContainer(
|
24
|
+
class AnnotationsContainer(tNamedTuple):
|
26
25
|
"""
|
27
|
-
Holds metadata about the annotated
|
26
|
+
Holds metadata about the annotated functions or class methods.
|
28
27
|
|
29
28
|
Loosely inspired by: https://peps.python.org/pep-3107/.
|
30
29
|
"""
|
31
30
|
|
32
|
-
annotations: tuple[GroupAnnotations[
|
31
|
+
annotations: tuple[GroupAnnotations[Any], ...]
|
33
32
|
|
34
33
|
@staticmethod
|
35
34
|
def empty() -> AnnotationsContainer:
|
@@ -38,7 +37,7 @@ class AnnotationsContainer(NamedTuple):
|
|
38
37
|
def with_group(
|
39
38
|
self,
|
40
39
|
namespace: str,
|
41
|
-
group_id:
|
40
|
+
group_id: NamedTuple,
|
42
41
|
) -> AnnotationsContainer:
|
43
42
|
return AnnotationsContainer(
|
44
43
|
annotations=tuple(
|
@@ -87,6 +86,13 @@ def annotation(
|
|
87
86
|
namespace: str,
|
88
87
|
group_id: NamedTuple | tNamedTuple,
|
89
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
|
+
|
90
96
|
def decorator(fn: DecoratorType) -> DecoratorType:
|
91
97
|
if not hasattr(fn, "__rats_annotations__"):
|
92
98
|
fn.__rats_annotations__ = AnnotationsBuilder() # type: ignore[reportFunctionMemberAccess]
|
@@ -100,14 +106,22 @@ def annotation(
|
|
100
106
|
|
101
107
|
@cache
|
102
108
|
def get_class_annotations(cls: type) -> AnnotationsContainer:
|
103
|
-
|
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]] = []
|
104
117
|
|
105
118
|
for method_name in dir(cls):
|
106
119
|
method = getattr(cls, method_name)
|
107
120
|
if not hasattr(method, "__rats_annotations__"):
|
108
121
|
continue
|
109
122
|
|
110
|
-
|
123
|
+
builder: AnnotationsBuilder = method.__rats_annotations__
|
124
|
+
tates.extend(builder.make(method_name).annotations)
|
111
125
|
|
112
126
|
return AnnotationsContainer(annotations=tuple(tates))
|
113
127
|
|
@@ -116,6 +130,12 @@ P = ParamSpec("P")
|
|
116
130
|
|
117
131
|
|
118
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
|
+
"""
|
119
139
|
builder: AnnotationsBuilder = getattr(
|
120
140
|
fn,
|
121
141
|
"__rats_annotations__",
|
rats/apps/__init__.py
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
"""
|
2
|
-
|
2
|
+
Libraries to help create applications with a strong focus on composability and testability.
|
3
3
|
|
4
4
|
Applications give you the ability to define a development experience to match your project's
|
5
5
|
domain.
|
6
6
|
"""
|
7
7
|
|
8
8
|
from ._annotations import (
|
9
|
-
AnnotatedContainer,
|
10
9
|
autoid,
|
11
10
|
autoid_service,
|
12
11
|
config,
|
13
|
-
container,
|
14
12
|
fallback_config,
|
15
13
|
fallback_group,
|
16
14
|
fallback_service,
|
@@ -19,23 +17,26 @@ from ._annotations import (
|
|
19
17
|
)
|
20
18
|
from ._composite_container import CompositeContainer
|
21
19
|
from ._container import (
|
20
|
+
AnnotatedContainer, # type: ignore[reportDeprecated]
|
22
21
|
ConfigProvider,
|
23
22
|
Container,
|
24
23
|
DuplicateServiceError,
|
25
24
|
ServiceNotFoundError,
|
26
25
|
ServiceProvider,
|
26
|
+
container,
|
27
27
|
)
|
28
|
-
from ._executables import App,
|
28
|
+
from ._executables import App, Executable
|
29
29
|
from ._ids import ConfigId, ServiceId
|
30
30
|
from ._namespaces import ProviderNamespaces
|
31
31
|
from ._plugin_container import PluginContainers
|
32
32
|
from ._plugins import PluginRunner
|
33
|
+
from ._runtimes import Runtime, T_ExecutableType
|
33
34
|
from ._scoping import autoscope
|
35
|
+
from ._simple_apps import AppServices, SimpleApplication, SimpleRuntime
|
34
36
|
|
35
37
|
__all__ = [
|
36
38
|
"AnnotatedContainer",
|
37
39
|
"App",
|
38
|
-
"AppContainer",
|
39
40
|
"CompositeContainer",
|
40
41
|
"ConfigId",
|
41
42
|
"Container",
|
@@ -58,4 +59,10 @@ __all__ = [
|
|
58
59
|
"group",
|
59
60
|
"autoid",
|
60
61
|
"service",
|
62
|
+
"T_ExecutableType",
|
63
|
+
"Runtime",
|
64
|
+
"AppServices",
|
65
|
+
"SimpleRuntime",
|
66
|
+
"SimpleRuntime",
|
67
|
+
"SimpleApplication",
|
61
68
|
]
|
rats/apps/_annotations.py
CHANGED
@@ -1,157 +1,72 @@
|
|
1
|
-
import
|
2
|
-
from
|
3
|
-
from collections.abc import Callable, Iterator
|
4
|
-
from functools import cache
|
5
|
-
from typing import Any, ParamSpec, cast
|
1
|
+
from collections.abc import Callable
|
2
|
+
from typing import Any, ParamSpec
|
6
3
|
|
7
|
-
from
|
4
|
+
from rats import annotations
|
8
5
|
|
9
|
-
from ._container import Container
|
10
6
|
from ._ids import ConfigId, ServiceId, T_ConfigType, T_ServiceType
|
11
7
|
from ._namespaces import ProviderNamespaces
|
12
8
|
from ._scoping import scope_service_name
|
13
9
|
|
14
|
-
DEFAULT_CONTAINER_GROUP = ServiceId[Container]("__default__")
|
15
|
-
|
16
|
-
|
17
|
-
class GroupAnnotations(NamedTuple):
|
18
|
-
"""
|
19
|
-
The list of service ids attached to a given function.
|
20
|
-
|
21
|
-
The `name` attribute is the name of the function, and the `namespace` attribute represents a
|
22
|
-
specific meaning for the group of services.
|
23
|
-
"""
|
24
|
-
|
25
|
-
name: str
|
26
|
-
namespace: str
|
27
|
-
groups: tuple[ServiceId[Any], ...]
|
28
|
-
|
29
|
-
|
30
|
-
class FunctionAnnotations(NamedTuple):
|
31
|
-
"""
|
32
|
-
Holds metadata about the annotated service provider.
|
33
|
-
|
34
|
-
Loosely inspired by: https://peps.python.org/pep-3107/.
|
35
|
-
"""
|
36
|
-
|
37
|
-
providers: tuple[GroupAnnotations, ...]
|
38
|
-
|
39
|
-
def group_in_namespace(
|
40
|
-
self,
|
41
|
-
namespace: str,
|
42
|
-
group_id: ServiceId[T_ServiceType],
|
43
|
-
) -> tuple[GroupAnnotations, ...]:
|
44
|
-
return tuple([x for x in self.with_namespace(namespace) if group_id in x.groups])
|
45
|
-
|
46
|
-
def with_namespace(
|
47
|
-
self,
|
48
|
-
namespace: str,
|
49
|
-
) -> tuple[GroupAnnotations, ...]:
|
50
|
-
return tuple([x for x in self.providers if x.namespace == namespace])
|
51
|
-
|
52
|
-
|
53
|
-
class FunctionAnnotationsBuilder:
|
54
|
-
_service_ids: dict[str, list[ServiceId[Any]]]
|
55
|
-
|
56
|
-
def __init__(self) -> None:
|
57
|
-
self._service_ids = defaultdict(list)
|
58
|
-
|
59
|
-
def add(self, namespace: str, service_id: ServiceId[T_ServiceType]) -> None:
|
60
|
-
self._service_ids[namespace].append(service_id)
|
61
|
-
|
62
|
-
def get_service_names(self, namespace: str) -> tuple[str, ...]:
|
63
|
-
return tuple(s.name for s in self._service_ids.get(namespace, []))
|
64
|
-
|
65
|
-
def make(self, name: str) -> tuple[GroupAnnotations, ...]:
|
66
|
-
return tuple(
|
67
|
-
[
|
68
|
-
GroupAnnotations(name=name, namespace=namespace, groups=tuple(services))
|
69
|
-
for namespace, services in self._service_ids.items()
|
70
|
-
]
|
71
|
-
)
|
72
|
-
|
73
|
-
|
74
|
-
class AnnotatedContainer(Container, abc.ABC):
|
75
|
-
def get_namespaced_group(
|
76
|
-
self,
|
77
|
-
namespace: str,
|
78
|
-
group_id: ServiceId[T_ServiceType],
|
79
|
-
) -> Iterator[T_ServiceType]:
|
80
|
-
annotations = _extract_class_annotations(type(self))
|
81
|
-
containers = annotations.with_namespace(ProviderNamespaces.CONTAINERS)
|
82
|
-
groups = annotations.group_in_namespace(namespace, group_id)
|
83
|
-
|
84
|
-
for annotation in groups:
|
85
|
-
yield getattr(self, annotation.name)()
|
86
|
-
|
87
|
-
for container in containers:
|
88
|
-
c = getattr(self, container.name)()
|
89
|
-
yield from c.get_namespaced_group(namespace, group_id)
|
90
|
-
|
91
|
-
|
92
10
|
P = ParamSpec("P")
|
93
11
|
|
94
12
|
|
95
13
|
def service(
|
96
14
|
service_id: ServiceId[T_ServiceType],
|
97
15
|
) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
|
98
|
-
|
16
|
+
"""A service is anything you would create instances of?"""
|
17
|
+
return annotations.annotation(ProviderNamespaces.SERVICES, service_id)
|
99
18
|
|
100
19
|
|
101
20
|
def autoid_service(fn: Callable[P, T_ServiceType]) -> Callable[P, T_ServiceType]:
|
102
21
|
_service_id = autoid(fn)
|
103
|
-
|
104
|
-
cached_fn = cache(fn)
|
105
|
-
return cast(Callable[P, T_ServiceType], cached_fn)
|
22
|
+
return annotations.annotation(ProviderNamespaces.SERVICES, _service_id)(fn)
|
106
23
|
|
107
24
|
|
108
25
|
def group(
|
109
26
|
group_id: ServiceId[T_ServiceType],
|
110
27
|
) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
|
111
|
-
|
28
|
+
"""A group is a collection of services."""
|
29
|
+
return annotations.annotation(ProviderNamespaces.GROUPS, group_id)
|
112
30
|
|
113
31
|
|
114
32
|
def config(
|
115
33
|
config_id: ConfigId[T_ConfigType],
|
116
34
|
) -> Callable[[Callable[P, T_ConfigType]], Callable[P, T_ConfigType]]:
|
117
|
-
|
35
|
+
"""A service that provides simple data-structures."""
|
36
|
+
return annotations.annotation(
|
37
|
+
ProviderNamespaces.SERVICES,
|
38
|
+
config_id,
|
39
|
+
)
|
118
40
|
|
119
41
|
|
120
42
|
def fallback_service(
|
121
43
|
service_id: ServiceId[T_ServiceType],
|
122
44
|
) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
|
123
|
-
|
45
|
+
"""A fallback service gets used if no service is defined."""
|
46
|
+
return annotations.annotation(
|
47
|
+
ProviderNamespaces.FALLBACK_SERVICES,
|
48
|
+
service_id,
|
49
|
+
)
|
124
50
|
|
125
51
|
|
126
52
|
def fallback_group(
|
127
53
|
group_id: ServiceId[T_ServiceType],
|
128
54
|
) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
|
129
|
-
|
55
|
+
"""A fallback group gets used if no group is defined."""
|
56
|
+
return annotations.annotation(
|
57
|
+
ProviderNamespaces.FALLBACK_GROUPS,
|
58
|
+
group_id,
|
59
|
+
)
|
130
60
|
|
131
61
|
|
132
62
|
def fallback_config(
|
133
63
|
config_id: ConfigId[T_ConfigType],
|
134
64
|
) -> Callable[[Callable[P, T_ConfigType]], Callable[P, T_ConfigType]]:
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
group_id: ServiceId[T_ServiceType] = DEFAULT_CONTAINER_GROUP,
|
140
|
-
) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
|
141
|
-
return fn_annotation_decorator(ProviderNamespaces.CONTAINERS, group_id)
|
142
|
-
|
143
|
-
|
144
|
-
def _get_method_service_id_name(method: Callable[..., Any]) -> str:
|
145
|
-
existing_names = _get_annotations_builder(method).get_service_names(
|
146
|
-
ProviderNamespaces.SERVICES
|
65
|
+
"""A fallback config gets used if no config is defined."""
|
66
|
+
return annotations.annotation(
|
67
|
+
ProviderNamespaces.FALLBACK_SERVICES,
|
68
|
+
config_id,
|
147
69
|
)
|
148
|
-
if existing_names:
|
149
|
-
return existing_names[0]
|
150
|
-
else:
|
151
|
-
module_name = method.__module__
|
152
|
-
class_name, method_name = method.__qualname__.rsplit(".", 1)
|
153
|
-
service_name = scope_service_name(module_name, class_name, method_name)
|
154
|
-
return service_name
|
155
70
|
|
156
71
|
|
157
72
|
def autoid(method: Callable[..., T_ServiceType]) -> ServiceId[T_ServiceType]:
|
@@ -168,43 +83,12 @@ def autoid(method: Callable[..., T_ServiceType]) -> ServiceId[T_ServiceType]:
|
|
168
83
|
return ServiceId[T_ServiceType](service_name)
|
169
84
|
|
170
85
|
|
171
|
-
def
|
172
|
-
|
173
|
-
service_id: ServiceId[T_ServiceType],
|
174
|
-
) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
|
175
|
-
def wrapper(
|
176
|
-
fn: Callable[P, T_ServiceType],
|
177
|
-
) -> Callable[P, T_ServiceType]:
|
178
|
-
_service_id = service_id
|
179
|
-
_add_annotation(namespace, fn, _service_id)
|
180
|
-
cached_fn = cache(fn)
|
181
|
-
# The static type of cached_fn should be correct, but it does not maintain the param-spec,
|
182
|
-
# so we need to cast.
|
183
|
-
return cast(Callable[P, T_ServiceType], cached_fn)
|
184
|
-
|
185
|
-
return wrapper
|
186
|
-
|
187
|
-
|
188
|
-
@cache
|
189
|
-
def _extract_class_annotations(cls: Any) -> FunctionAnnotations:
|
190
|
-
function_annotations: list[GroupAnnotations] = []
|
191
|
-
for method_name in dir(cls):
|
192
|
-
if method_name.startswith("_"):
|
193
|
-
continue
|
194
|
-
|
195
|
-
builder = _get_annotations_builder(getattr(cls, method_name))
|
196
|
-
function_annotations.extend(list(builder.make(method_name)))
|
197
|
-
|
198
|
-
return FunctionAnnotations(tuple(function_annotations))
|
199
|
-
|
200
|
-
|
201
|
-
def _add_annotation(namespace: str, fn: Any, service_id: ServiceId[T_ServiceType]) -> None:
|
202
|
-
builder = _get_annotations_builder(fn)
|
203
|
-
builder.add(namespace, service_id)
|
204
|
-
|
86
|
+
def _get_method_service_id_name(method: Callable[..., Any]) -> str:
|
87
|
+
tates = annotations.get_annotations(method).with_namespace(ProviderNamespaces.SERVICES)
|
205
88
|
|
206
|
-
|
207
|
-
|
208
|
-
fn.__rats_service_annotations__ = FunctionAnnotationsBuilder()
|
89
|
+
for a in tates.annotations:
|
90
|
+
return a.groups[0].name
|
209
91
|
|
210
|
-
|
92
|
+
module_name = method.__module__
|
93
|
+
class_name, method_name = method.__qualname__.rsplit(".", 1)
|
94
|
+
return scope_service_name(module_name, class_name, method_name)
|
rats/apps/_container.py
CHANGED
@@ -1,10 +1,18 @@
|
|
1
|
+
import abc
|
2
|
+
import logging
|
1
3
|
from abc import abstractmethod
|
2
|
-
from collections.abc import Iterator
|
3
|
-
from typing import Generic, Protocol
|
4
|
+
from collections.abc import Callable, Iterator
|
5
|
+
from typing import Generic, ParamSpec, Protocol
|
6
|
+
|
7
|
+
from typing_extensions import deprecated
|
8
|
+
|
9
|
+
from rats import annotations
|
4
10
|
|
5
11
|
from ._ids import ServiceId, T_ServiceType, Tco_ConfigType, Tco_ServiceType
|
6
12
|
from ._namespaces import ProviderNamespaces
|
7
13
|
|
14
|
+
logger = logging.getLogger(__name__)
|
15
|
+
|
8
16
|
|
9
17
|
class ServiceProvider(Protocol[Tco_ServiceType]):
|
10
18
|
@abstractmethod
|
@@ -19,15 +27,55 @@ class ConfigProvider(ServiceProvider[Tco_ConfigType], Protocol[Tco_ConfigType]):
|
|
19
27
|
|
20
28
|
|
21
29
|
class Container(Protocol):
|
22
|
-
"""
|
30
|
+
"""
|
31
|
+
Main interface for service containers.
|
32
|
+
|
33
|
+
The default methods in this protocol attempt to find service providers that have been
|
34
|
+
annotated.
|
35
|
+
|
36
|
+
Example:
|
37
|
+
.. code-block:: python
|
38
|
+
|
39
|
+
from rats import apps
|
40
|
+
|
41
|
+
|
42
|
+
class MyStorageClient:
|
43
|
+
def save(self, data: str) -> None:
|
44
|
+
print(f"Saving data: {data}")
|
45
|
+
|
46
|
+
|
47
|
+
class MyPluginServices:
|
48
|
+
STORAGE_CLIENT = ServiceId[MyStorageClient]("storage-client")
|
49
|
+
|
50
|
+
|
51
|
+
class MyPluginContainer(apps.Container):
|
52
|
+
@apps.service(MyPluginServices.STORAGE_CLIENT)
|
53
|
+
def _storage_client() -> MyStorageClient:
|
54
|
+
return MyStorageClient()
|
55
|
+
|
56
|
+
|
57
|
+
container = MyPluginContainer()
|
58
|
+
storage_client = container.get(MyPluginServices.STORAGE_CLIENT)
|
59
|
+
storage_client.save("Hello, world!")
|
60
|
+
"""
|
23
61
|
|
24
62
|
def has(self, service_id: ServiceId[T_ServiceType]) -> bool:
|
63
|
+
"""
|
64
|
+
Check if a service is provided by this container.
|
65
|
+
|
66
|
+
Example:
|
67
|
+
.. code-block:: python
|
68
|
+
|
69
|
+
if not container.has(MyPluginServices.STORAGE_CLIENT):
|
70
|
+
print("Did you forget to configure a storage client?")
|
71
|
+
"""
|
25
72
|
try:
|
26
73
|
return self.get(service_id) is not None
|
27
74
|
except ServiceNotFoundError:
|
28
75
|
return False
|
29
76
|
|
30
77
|
def has_group(self, group_id: ServiceId[T_ServiceType]) -> bool:
|
78
|
+
"""Check if a service group has at least one provider in the container."""
|
31
79
|
try:
|
32
80
|
return next(self.get_group(group_id)) is not None
|
33
81
|
except StopIteration:
|
@@ -64,13 +112,61 @@ class Container(Protocol):
|
|
64
112
|
|
65
113
|
yield from self.get_namespaced_group(ProviderNamespaces.GROUPS, group_id)
|
66
114
|
|
67
|
-
@abstractmethod
|
68
115
|
def get_namespaced_group(
|
69
116
|
self,
|
70
117
|
namespace: str,
|
71
118
|
group_id: ServiceId[T_ServiceType],
|
72
119
|
) -> Iterator[T_ServiceType]:
|
73
120
|
"""Retrieve a service group by its id, within a given service namespace."""
|
121
|
+
tates = annotations.get_class_annotations(type(self))
|
122
|
+
containers = tates.with_namespace(ProviderNamespaces.CONTAINERS)
|
123
|
+
groups = tates.with_group(namespace, group_id)
|
124
|
+
|
125
|
+
for annotation in groups.annotations:
|
126
|
+
if not hasattr(self, f"__rats_cache_{annotation.name}"):
|
127
|
+
setattr(self, f"__rats_cache_{annotation.name}", getattr(self, annotation.name)())
|
128
|
+
|
129
|
+
yield getattr(self, f"__rats_cache_{annotation.name}")
|
130
|
+
|
131
|
+
for annotation in containers.annotations:
|
132
|
+
if not hasattr(self, f"__rats_container_cache_{annotation.name}"):
|
133
|
+
setattr(
|
134
|
+
self,
|
135
|
+
f"__rats_container_cache_{annotation.name}",
|
136
|
+
getattr(self, annotation.name)(),
|
137
|
+
)
|
138
|
+
|
139
|
+
c = getattr(self, f"__rats_container_cache_{annotation.name}")
|
140
|
+
yield from c.get_namespaced_group(namespace, group_id)
|
141
|
+
|
142
|
+
|
143
|
+
@deprecated(
|
144
|
+
" ".join(
|
145
|
+
[
|
146
|
+
"AnnotatedContainer is deprecated and will be removed in the next major release.",
|
147
|
+
"The functionality has been moved into the apps.Container protocol.",
|
148
|
+
"Please extend apps.Container directly.",
|
149
|
+
]
|
150
|
+
),
|
151
|
+
stacklevel=2,
|
152
|
+
)
|
153
|
+
class AnnotatedContainer(Container, abc.ABC):
|
154
|
+
"""
|
155
|
+
A Container implementation that extracts providers from its annotated methods.
|
156
|
+
|
157
|
+
.. deprecated:: 0.1.3
|
158
|
+
The behavior of this class has been made the default within ``Container``.
|
159
|
+
"""
|
160
|
+
|
161
|
+
|
162
|
+
DEFAULT_CONTAINER_GROUP = ServiceId[Container]("__default__")
|
163
|
+
P = ParamSpec("P")
|
164
|
+
|
165
|
+
|
166
|
+
def container(
|
167
|
+
group_id: ServiceId[T_ServiceType] = DEFAULT_CONTAINER_GROUP,
|
168
|
+
) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
|
169
|
+
return annotations.annotation(ProviderNamespaces.CONTAINERS, group_id)
|
74
170
|
|
75
171
|
|
76
172
|
class ServiceNotFoundError(RuntimeError, Generic[T_ServiceType]):
|
rats/apps/_executables.py
CHANGED
@@ -1,19 +1,30 @@
|
|
1
1
|
from abc import abstractmethod
|
2
|
-
from collections.abc import Callable
|
3
|
-
from functools import cache
|
2
|
+
from collections.abc import Callable
|
4
3
|
from typing import Protocol
|
5
4
|
|
6
|
-
from ._container import Container, ServiceId
|
7
|
-
from ._ids import T_ServiceType
|
8
|
-
|
9
5
|
|
10
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
|
+
|
11
15
|
@abstractmethod
|
12
16
|
def execute(self) -> None:
|
13
17
|
"""Execute the application."""
|
14
18
|
|
15
19
|
|
16
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
|
+
|
17
28
|
_callback: Callable[[], None]
|
18
29
|
|
19
30
|
def __init__(self, callback: Callable[[], None]) -> None:
|
@@ -23,19 +34,19 @@ class App(Executable):
|
|
23
34
|
self._callback()
|
24
35
|
|
25
36
|
|
26
|
-
class AppContainer(Container):
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
# class AppContainer(Container):
|
38
|
+
# _container: Callable[[Container], Container]
|
39
|
+
#
|
40
|
+
# def __init__(self, container: Callable[[Container], Container]) -> None:
|
41
|
+
# self._container = container
|
42
|
+
#
|
43
|
+
# def get_namespaced_group(
|
44
|
+
# self,
|
45
|
+
# namespace: str,
|
46
|
+
# group_id: ServiceId[T_ServiceType],
|
47
|
+
# ) -> Iterator[T_ServiceType]:
|
48
|
+
# yield from self._load_container().get_namespaced_group(namespace, group_id)
|
49
|
+
#
|
50
|
+
# @cache
|
51
|
+
# def _load_container(self) -> Container:
|
52
|
+
# return self._container(self)
|
rats/apps/_plugin_container.py
CHANGED
@@ -9,10 +9,12 @@ from ._ids import ServiceId, T_ServiceType
|
|
9
9
|
class PluginContainers(Container):
|
10
10
|
_app: Container
|
11
11
|
_group: str
|
12
|
+
_names: tuple[str, ...]
|
12
13
|
|
13
|
-
def __init__(self, app: Container, group: str) -> None:
|
14
|
+
def __init__(self, app: Container, group: str, *names: str) -> None:
|
14
15
|
self._app = app
|
15
16
|
self._group = group
|
17
|
+
self._names = names
|
16
18
|
|
17
19
|
def get_namespaced_group(
|
18
20
|
self,
|
@@ -25,4 +27,7 @@ class PluginContainers(Container):
|
|
25
27
|
@cache # noqa: B019
|
26
28
|
def _load_containers(self) -> Iterable[Container]:
|
27
29
|
entries = entry_points(group=self._group)
|
28
|
-
return tuple(entry.load()(self._app) for entry in entries)
|
30
|
+
return tuple(entry.load()(self._app) for entry in entries if self._is_enabled(entry.name))
|
31
|
+
|
32
|
+
def _is_enabled(self, plugin_name: str) -> bool:
|
33
|
+
return len(self._names) == 0 or plugin_name in self._names
|
rats/apps/_plugins.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import warnings
|
1
2
|
from collections.abc import Callable, Iterator
|
2
3
|
from typing import Generic, TypeVar
|
3
4
|
|
@@ -13,5 +14,10 @@ class PluginRunner(Generic[T_PluginType]):
|
|
13
14
|
self._plugins = plugins
|
14
15
|
|
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
|
+
)
|
16
22
|
for plugin in self._plugins:
|
17
23
|
handler(plugin)
|
rats/apps/_runtimes.py
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
from abc import abstractmethod
|
2
|
+
from collections.abc import Callable
|
3
|
+
from typing import Protocol, TypeVar
|
4
|
+
|
5
|
+
from ._executables import Executable
|
6
|
+
from ._ids import ServiceId
|
7
|
+
|
8
|
+
T_ExecutableType = TypeVar("T_ExecutableType", bound=Executable)
|
9
|
+
|
10
|
+
|
11
|
+
class Runtime(Protocol):
|
12
|
+
@abstractmethod
|
13
|
+
def execute(self, *exe_ids: ServiceId[T_ExecutableType]) -> None:
|
14
|
+
"""Execute a list of executables sequentially."""
|
15
|
+
|
16
|
+
@abstractmethod
|
17
|
+
def execute_group(self, *exe_group_ids: ServiceId[T_ExecutableType]) -> None:
|
18
|
+
"""
|
19
|
+
Execute one or more groups of executables sequentially.
|
20
|
+
|
21
|
+
Although each group is expected to be executed sequentially, the groups themselves are not
|
22
|
+
executed in a deterministic order. Runtime implementations are free to execute groups in
|
23
|
+
parallel or in any order that is convenient.
|
24
|
+
"""
|
25
|
+
|
26
|
+
@abstractmethod
|
27
|
+
def execute_callable(self, *callables: Callable[[], None]) -> None:
|
28
|
+
"""
|
29
|
+
Execute provided callables by automatically turning them into apps.Executable objects.
|
30
|
+
|
31
|
+
The used ServiceId is determined by the Runtime implementation.
|
32
|
+
"""
|
rats/apps/_scoping.py
CHANGED
@@ -8,10 +8,6 @@ T = TypeVar("T")
|
|
8
8
|
P = ParamSpec("P")
|
9
9
|
|
10
10
|
|
11
|
-
def scope_service_name(module_name: str, cls_name: str, name: str) -> str:
|
12
|
-
return f"{module_name}:{cls_name}[{name}]"
|
13
|
-
|
14
|
-
|
15
11
|
def autoscope(cls: type[T]) -> type[T]:
|
16
12
|
"""
|
17
13
|
Decorator that replaces all ServiceId instances in the class with scoped ServiceId instances.
|
@@ -45,3 +41,7 @@ def autoscope(cls: type[T]) -> type[T]:
|
|
45
41
|
setattr(cls, prop_name, prop)
|
46
42
|
|
47
43
|
return cls
|
44
|
+
|
45
|
+
|
46
|
+
def scope_service_name(module_name: str, cls_name: str, name: str) -> str:
|
47
|
+
return f"{module_name}:{cls_name}[{name}]"
|
@@ -0,0 +1,144 @@
|
|
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
|
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
|
+
CONTAINER = ServiceId[Container]("app-container")
|
60
|
+
CALLABLE_EXE_CTX = ServiceId[ExecutableCallableContext]("callable-exe-ctx")
|
61
|
+
CALLABLE_EXE = ServiceId[Executable]("callable-exe")
|
62
|
+
|
63
|
+
|
64
|
+
@final
|
65
|
+
class SimpleRuntime(Runtime):
|
66
|
+
"""A simple runtime that executes sequentially and in a single thread."""
|
67
|
+
|
68
|
+
_app: Container
|
69
|
+
|
70
|
+
def __init__(self, app: Container) -> None:
|
71
|
+
self._app = app
|
72
|
+
|
73
|
+
def execute(self, *exe_ids: ServiceId[T_ExecutableType]) -> None:
|
74
|
+
for exe_id in exe_ids:
|
75
|
+
self._app.get(exe_id).execute()
|
76
|
+
|
77
|
+
def execute_group(self, *exe_group_ids: ServiceId[T_ExecutableType]) -> None:
|
78
|
+
for exe_group_id in exe_group_ids:
|
79
|
+
for exe in self._app.get_group(exe_group_id):
|
80
|
+
exe.execute()
|
81
|
+
|
82
|
+
def execute_callable(self, *callables: Callable[[], None]) -> None:
|
83
|
+
ctx: ExecutableCallableContext = self._app.get(AppServices.CALLABLE_EXE_CTX)
|
84
|
+
for cb in callables:
|
85
|
+
with ctx.open_callable(cb) as exe:
|
86
|
+
exe.execute()
|
87
|
+
|
88
|
+
|
89
|
+
@final
|
90
|
+
class SimpleApplication(Runtime, Container):
|
91
|
+
"""An application without anything fancy."""
|
92
|
+
|
93
|
+
_plugin_groups: Iterable[str]
|
94
|
+
|
95
|
+
def __init__(self, *plugin_groups: str) -> None:
|
96
|
+
self._plugin_groups = plugin_groups
|
97
|
+
|
98
|
+
def execute(self, *exe_ids: ServiceId[T_ExecutableType]) -> None:
|
99
|
+
self._runtime().execute(*exe_ids)
|
100
|
+
|
101
|
+
def execute_group(self, *exe_group_ids: ServiceId[T_ExecutableType]) -> None:
|
102
|
+
self._runtime().execute_group(*exe_group_ids)
|
103
|
+
|
104
|
+
def execute_callable(self, *callables: Callable[[], None]) -> None:
|
105
|
+
for cb in callables:
|
106
|
+
self._runtime().execute_callable(cb)
|
107
|
+
|
108
|
+
@fallback_service(AppServices.RUNTIME)
|
109
|
+
def _runtime(self) -> Runtime:
|
110
|
+
"""
|
111
|
+
The default runtime is an instance of SimpleRuntime.
|
112
|
+
|
113
|
+
Define a non-fallback service to override this default implementation.
|
114
|
+
"""
|
115
|
+
return SimpleRuntime(self)
|
116
|
+
|
117
|
+
@fallback_service(AppServices.CALLABLE_EXE)
|
118
|
+
def _callable_exe(self) -> Executable:
|
119
|
+
"""We use the callable exe ctx here, so we can treat it like any other app downstream."""
|
120
|
+
return self.get(AppServices.CALLABLE_EXE_CTX)
|
121
|
+
|
122
|
+
@fallback_service(AppServices.CALLABLE_EXE_CTX)
|
123
|
+
def _callable_exe_ctx(self) -> ExecutableCallableContext:
|
124
|
+
"""
|
125
|
+
The default executable context client for executing raw callables.
|
126
|
+
|
127
|
+
Define a non-fallback service to override this default implementation.
|
128
|
+
"""
|
129
|
+
return ExecutableCallableContext(AppServices.CALLABLE_EXE)
|
130
|
+
|
131
|
+
@fallback_service(AppServices.CONTAINER)
|
132
|
+
def _container(self) -> Container:
|
133
|
+
"""
|
134
|
+
The default container is the root application instance.
|
135
|
+
|
136
|
+
Define a non-fallback service to override this default implementation.
|
137
|
+
"""
|
138
|
+
return self
|
139
|
+
|
140
|
+
@container()
|
141
|
+
def _plugins(self) -> Container:
|
142
|
+
return CompositeContainer(
|
143
|
+
*[PluginContainers(self, group) for group in self._plugin_groups],
|
144
|
+
)
|
rats/cli/__init__.py
CHANGED
@@ -1,14 +1,17 @@
|
|
1
|
-
"""Uses `rats.
|
1
|
+
"""Uses `rats.cli` to streamline the creation of CLI commands written with Click."""
|
2
2
|
|
3
3
|
from ._annotations import CommandId, command, group
|
4
4
|
from ._click import ClickCommandGroup, ClickCommandMapper
|
5
5
|
from ._executable import ClickExecutable
|
6
|
+
from ._plugin import PluginContainer, PluginServices
|
6
7
|
from ._plugins import AttachClickCommands, AttachClickGroup, ClickGroupPlugin, CommandContainer
|
7
8
|
|
8
9
|
__all__ = [
|
9
10
|
"CommandId",
|
11
|
+
"PluginContainer",
|
10
12
|
"command",
|
11
13
|
"group",
|
14
|
+
"PluginServices",
|
12
15
|
"ClickCommandMapper",
|
13
16
|
"ClickExecutable",
|
14
17
|
"ClickGroupPlugin",
|
rats/cli/_plugin.py
ADDED
@@ -0,0 +1,69 @@
|
|
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
|
+
# autowrapped!
|
40
|
+
return cast(apps.ServiceId[click.Group], cmd_id)
|
41
|
+
|
42
|
+
|
43
|
+
@final
|
44
|
+
class PluginContainer(apps.Container):
|
45
|
+
_app: apps.Container
|
46
|
+
|
47
|
+
def __init__(self, app: apps.Container) -> None:
|
48
|
+
self._app = app
|
49
|
+
|
50
|
+
@apps.service(PluginServices.ROOT_COMMAND)
|
51
|
+
def _root_command(self) -> apps.Executable:
|
52
|
+
def run() -> None:
|
53
|
+
runtime = self._app.get(apps.AppServices.RUNTIME)
|
54
|
+
runtime.execute_group(
|
55
|
+
PluginServices.EVENTS.command_open(PluginServices.ROOT_COMMAND),
|
56
|
+
PluginServices.EVENTS.command_execute(PluginServices.ROOT_COMMAND),
|
57
|
+
PluginServices.EVENTS.command_close(PluginServices.ROOT_COMMAND),
|
58
|
+
)
|
59
|
+
|
60
|
+
return apps.App(run)
|
61
|
+
|
62
|
+
@apps.fallback_group(PluginServices.EVENTS.command_execute(PluginServices.ROOT_COMMAND))
|
63
|
+
def _default_command(self) -> apps.Executable:
|
64
|
+
group = self._app.get(PluginServices.click_command(PluginServices.ROOT_COMMAND))
|
65
|
+
return apps.App(lambda: group())
|
66
|
+
|
67
|
+
@apps.service(PluginServices.click_command(PluginServices.ROOT_COMMAND))
|
68
|
+
def _root_click_command(self) -> click.Group:
|
69
|
+
return click.Group("groot")
|
rats/logs/__init__.py
ADDED
rats/logs/_plugin.py
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
import logging.config
|
2
|
+
|
3
|
+
from rats import apps
|
4
|
+
|
5
|
+
logger = logging.getLogger(__name__)
|
6
|
+
|
7
|
+
|
8
|
+
@apps.autoscope
|
9
|
+
class _PluginEvents:
|
10
|
+
CONFIGURE_LOGGING = apps.ServiceId[apps.Executable]("configure-logging")
|
11
|
+
|
12
|
+
|
13
|
+
@apps.autoscope
|
14
|
+
class PluginServices:
|
15
|
+
EVENTS = _PluginEvents
|
16
|
+
|
17
|
+
|
18
|
+
class PluginContainer(apps.Container):
|
19
|
+
_app: apps.Container
|
20
|
+
|
21
|
+
def __init__(self, app: apps.Container) -> None:
|
22
|
+
self._app = app
|
23
|
+
|
24
|
+
@apps.group(PluginServices.EVENTS.CONFIGURE_LOGGING)
|
25
|
+
def _configure_logging(self) -> apps.Executable:
|
26
|
+
# in the future, we can use this plugin to make logging easily configurable
|
27
|
+
return apps.App(
|
28
|
+
lambda: logging.config.dictConfig(
|
29
|
+
{
|
30
|
+
"version": 1,
|
31
|
+
"disable_existing_loggers": False,
|
32
|
+
"formatters": {
|
33
|
+
"colored": {
|
34
|
+
"()": "colorlog.ColoredFormatter",
|
35
|
+
"format": (
|
36
|
+
"%(log_color)s%(asctime)s %(levelname)-8s [%(name)s][%(lineno)d]: "
|
37
|
+
"%(message)s%(reset)s"
|
38
|
+
),
|
39
|
+
"datefmt": "%Y-%m-%d %H:%M:%S",
|
40
|
+
"log_colors": {
|
41
|
+
"DEBUG": "white",
|
42
|
+
"INFO": "green",
|
43
|
+
"WARNING": "yellow",
|
44
|
+
"ERROR": "red,",
|
45
|
+
"CRITICAL": "bold_red",
|
46
|
+
},
|
47
|
+
}
|
48
|
+
},
|
49
|
+
"handlers": {
|
50
|
+
"console": {
|
51
|
+
"class": "logging.StreamHandler",
|
52
|
+
"level": "DEBUG",
|
53
|
+
"formatter": "colored",
|
54
|
+
"stream": "ext://sys.stderr",
|
55
|
+
}
|
56
|
+
},
|
57
|
+
"root": {"level": "INFO", "handlers": ["console"]},
|
58
|
+
}
|
59
|
+
)
|
60
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: rats-apps
|
3
|
-
Version: 0.1.3.
|
3
|
+
Version: 0.1.3.dev20240624080525
|
4
4
|
Summary: research analysis tools for building applications
|
5
5
|
Home-page: https://github.com/microsoft/rats/
|
6
6
|
License: MIT
|
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
14
14
|
Requires-Dist: click
|
15
|
+
Requires-Dist: colorlog
|
15
16
|
Requires-Dist: typing_extensions
|
16
17
|
Project-URL: Documentation, https://microsoft.github.io/rats/
|
17
18
|
Project-URL: Repository, https://github.com/microsoft/rats/
|
@@ -0,0 +1,29 @@
|
|
1
|
+
rats/annotations/__init__.py,sha256=wsGhRQzZrV2oJTnBAX0aGgpyT1kYT235jkP3Wb8BTRY,498
|
2
|
+
rats/annotations/_functions.py,sha256=ziAZOqS1lojfjDjZW7qPYNJUAFuLBWtVAoPsmJqxqpE,4356
|
3
|
+
rats/annotations/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
rats/apps/__init__.py,sha256=yvwCwhSZrPre0_SNESf0asSrabZrru9P_JteKcljemU,1611
|
5
|
+
rats/apps/_annotations.py,sha256=2dvq0ZXDUMC9LK4athRzHrVlx23rGYtw4Gn0aNA9aSk,3063
|
6
|
+
rats/apps/_composite_container.py,sha256=wSWVQWPin2xxIlEoSgk_D1rlc3N2gpTxQ2y9UFdqXy0,553
|
7
|
+
rats/apps/_container.py,sha256=tIpU4o1nK5tQaOKZHh35qGBw6TV4uuU0k5vDY6NwS3Y,6280
|
8
|
+
rats/apps/_executables.py,sha256=acwOXlWpGmLuSFr30dPzg2LU6-ae1yfOtbDRTIXLEjc,1520
|
9
|
+
rats/apps/_ids.py,sha256=dxWCPMpMA_vpaTDJEKNByIBJaX97Db203XqWLhaOezo,457
|
10
|
+
rats/apps/_namespaces.py,sha256=THUV_Xj5PtweC23Ob-zsSpk8exC4fT-qRwjpQ6IDm0U,188
|
11
|
+
rats/apps/_plugin_container.py,sha256=Vhh1IdCzpUVwRdh301sALX7DQKny7Hru-yF_GwdxVqc,1075
|
12
|
+
rats/apps/_plugins.py,sha256=mvSYQPi11wTGZpM4umoyD37Rc8CQX8nt5eAJbmLrBFM,688
|
13
|
+
rats/apps/_runtimes.py,sha256=r6aRoI4rAkUh0uv3IKn-rNuwEu2MmB724rHsJpHDO74,1138
|
14
|
+
rats/apps/_scoping.py,sha256=EIUopw3b38CEV57kCmSKQTAnQQMcXHZ_vwtk9t0K__g,1453
|
15
|
+
rats/apps/_simple_apps.py,sha256=wjrPUt2_GLnp065gKSfIgG_3aLAU9ixi9rRt2d9mIJw,4742
|
16
|
+
rats/apps/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
rats/cli/__init__.py,sha256=dMrubxMJocbl8QoI_3fKI0Dt0QHYAM5Mxh5Vj8OaX-w,664
|
18
|
+
rats/cli/_annotations.py,sha256=0dI8hu_y754Y53Pka1-mGEgHjjVcnIOGd8l1SFx8OBY,1190
|
19
|
+
rats/cli/_click.py,sha256=7-ClnYSW4poFr_B-Q6NT45DnMF1XL7ntUgwQqQ7q_eo,1036
|
20
|
+
rats/cli/_executable.py,sha256=kAQ9hImv3hBaScu6e19o_BMvl7tdYJql38E76S3FjSk,580
|
21
|
+
rats/cli/_plugin.py,sha256=iUf2-ScR8o5p3g-0M1qNTAb3l9Q-FIBVjT_HEskISJY,2301
|
22
|
+
rats/cli/_plugins.py,sha256=H3-QdaICPJhCC5FkLHdXpwqe7Z0mpvsenakhNiPllC8,2739
|
23
|
+
rats/cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
+
rats/logs/__init__.py,sha256=fCn4pfpYiAcTtt5CsnUZX68CjOB3KJHxMSiYxsma4qE,183
|
25
|
+
rats/logs/_plugin.py,sha256=j3hLJDcdF4DvxeZ84zdMGP4EHgfMEzZc0wBGQjvrzGc,2039
|
26
|
+
rats_apps-0.1.3.dev20240624080525.dist-info/METADATA,sha256=Vy9kUx6YxdnFpwc9rhhw6toJ3F6LiVfDR5_oH3_QcT4,774
|
27
|
+
rats_apps-0.1.3.dev20240624080525.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
28
|
+
rats_apps-0.1.3.dev20240624080525.dist-info/entry_points.txt,sha256=9oOvf2loQr5ACWQgvuu9Q3KZIVIxKE5Aa-rLuUII5WQ,91
|
29
|
+
rats_apps-0.1.3.dev20240624080525.dist-info/RECORD,,
|
@@ -1,23 +0,0 @@
|
|
1
|
-
rats/annotations/__init__.py,sha256=wsGhRQzZrV2oJTnBAX0aGgpyT1kYT235jkP3Wb8BTRY,498
|
2
|
-
rats/annotations/_functions.py,sha256=r4Y-9fOZ9M0lDpcIv8nMuggEffc2bgZLeubWRj8uj-o,3552
|
3
|
-
rats/annotations/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
rats/apps/__init__.py,sha256=e9RQPGGgm3IG1XZgaK4e2u18-jQ4D9Seo7vpGgwuD0k,1340
|
5
|
-
rats/apps/_annotations.py,sha256=LOuVckzeUmbDWko8tB2HpACc_xdLoldeNJpAgvclf-s,6991
|
6
|
-
rats/apps/_composite_container.py,sha256=wSWVQWPin2xxIlEoSgk_D1rlc3N2gpTxQ2y9UFdqXy0,553
|
7
|
-
rats/apps/_container.py,sha256=5uiCyxN6HS2z97XcTOFP-t72cNoB1U1sJMkMcfSfDps,3129
|
8
|
-
rats/apps/_executables.py,sha256=8ITn__pjTLHo7FEb-3C6ZQrs1mow0gZn6d-24XGBSu8,1079
|
9
|
-
rats/apps/_ids.py,sha256=dxWCPMpMA_vpaTDJEKNByIBJaX97Db203XqWLhaOezo,457
|
10
|
-
rats/apps/_namespaces.py,sha256=THUV_Xj5PtweC23Ob-zsSpk8exC4fT-qRwjpQ6IDm0U,188
|
11
|
-
rats/apps/_plugin_container.py,sha256=W_xQD2btc0N2dEb3c5tXM-ZZ4A4diMpkCjbOZdlXYuI,853
|
12
|
-
rats/apps/_plugins.py,sha256=i1K5dCRC9cRA5QLiIdVUDJNM2rG935fdvqSTAK49h38,499
|
13
|
-
rats/apps/_scoping.py,sha256=plSVEq3rJ8JFAu2epVg2NQpuTbpSTA3a0Tha_DwJL_Y,1453
|
14
|
-
rats/apps/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
rats/cli/__init__.py,sha256=LJe3zGI4IH2Siti4kQjqHOrxygv03g3hDE7jwpfYS9E,574
|
16
|
-
rats/cli/_annotations.py,sha256=0dI8hu_y754Y53Pka1-mGEgHjjVcnIOGd8l1SFx8OBY,1190
|
17
|
-
rats/cli/_click.py,sha256=7-ClnYSW4poFr_B-Q6NT45DnMF1XL7ntUgwQqQ7q_eo,1036
|
18
|
-
rats/cli/_executable.py,sha256=kAQ9hImv3hBaScu6e19o_BMvl7tdYJql38E76S3FjSk,580
|
19
|
-
rats/cli/_plugins.py,sha256=H3-QdaICPJhCC5FkLHdXpwqe7Z0mpvsenakhNiPllC8,2739
|
20
|
-
rats/cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
rats_apps-0.1.3.dev75.dist-info/METADATA,sha256=LLwWeGo0LQUWWTvwzATROfpAPux4cSLOydQcLHcYK8o,738
|
22
|
-
rats_apps-0.1.3.dev75.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
23
|
-
rats_apps-0.1.3.dev75.dist-info/RECORD,,
|
File without changes
|