rats-apps 0.1.2.dev14__py3-none-any.whl → 0.1.3__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.
@@ -0,0 +1,25 @@
1
+ """
2
+ General purpose library to attach annotations to functions.
3
+
4
+ Annotations are typically, but not exclusively, attached using decorators.
5
+ """
6
+
7
+ from ._functions import (
8
+ AnnotationsContainer,
9
+ DecoratorType,
10
+ GroupAnnotations,
11
+ T_GroupType,
12
+ annotation,
13
+ get_annotations,
14
+ get_class_annotations,
15
+ )
16
+
17
+ __all__ = [
18
+ "annotation",
19
+ "DecoratorType",
20
+ "AnnotationsContainer",
21
+ "get_annotations",
22
+ "get_class_annotations",
23
+ "GroupAnnotations",
24
+ "T_GroupType",
25
+ ]
@@ -0,0 +1,145 @@
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__)
File without changes
rats/apps/__init__.py CHANGED
@@ -1,39 +1,62 @@
1
1
  """
2
- Provides a small set of libraries to help create new applications.
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
- config,
11
- container,
12
- fallback_config,
9
+ autoid,
10
+ autoid_service,
13
11
  fallback_group,
14
12
  fallback_service,
15
13
  group,
16
14
  service,
17
15
  )
18
- from ._container import Container, DuplicateServiceError, ServiceNotFoundError
19
- from ._ids import ConfigId, ServiceId
16
+ from ._composite_container import CompositeContainer
17
+ from ._container import (
18
+ AnnotatedContainer, # type: ignore[reportDeprecated]
19
+ Container,
20
+ DuplicateServiceError,
21
+ ServiceNotFoundError,
22
+ ServiceProvider,
23
+ container,
24
+ )
25
+ from ._executables import App, Executable
26
+ from ._ids import ServiceId, T_ExecutableType
20
27
  from ._namespaces import ProviderNamespaces
28
+ from ._plugin_container import PluginContainers
29
+ from ._plugins import PluginRunner
30
+ from ._runtimes import NullRuntime, Runtime
21
31
  from ._scoping import autoscope
32
+ from ._simple_apps import AppServices, SimpleApplication, StandardRuntime
22
33
 
23
34
  __all__ = [
24
35
  "AnnotatedContainer",
25
- "ConfigId",
36
+ "App",
37
+ "CompositeContainer",
26
38
  "Container",
27
39
  "DuplicateServiceError",
40
+ "Executable",
41
+ "PluginContainers",
28
42
  "ProviderNamespaces",
43
+ "ServiceProvider",
29
44
  "ServiceId",
30
45
  "ServiceNotFoundError",
46
+ "autoid_service",
31
47
  "autoscope",
32
- "config",
33
48
  "container",
34
- "fallback_config",
49
+ "PluginRunner",
35
50
  "fallback_group",
36
51
  "fallback_service",
37
52
  "group",
53
+ "autoid",
38
54
  "service",
55
+ "T_ExecutableType",
56
+ "Runtime",
57
+ "NullRuntime",
58
+ "AppServices",
59
+ "StandardRuntime",
60
+ "StandardRuntime",
61
+ "SimpleApplication",
39
62
  ]
rats/apps/_annotations.py CHANGED
@@ -1,164 +1,74 @@
1
- from collections import defaultdict
2
- from collections.abc import Callable, Iterator
3
- from functools import cache
4
- from typing import Any, cast
1
+ from collections.abc import Callable
2
+ from typing import Any, ParamSpec
5
3
 
6
- from typing_extensions import NamedTuple
4
+ from rats import annotations
7
5
 
8
- from ._container import Container
9
- from ._ids import ConfigId, ServiceId, T_ConfigType, T_ServiceType
6
+ from ._ids import ServiceId, T_ServiceType
10
7
  from ._namespaces import ProviderNamespaces
8
+ from ._scoping import scope_service_name
11
9
 
12
- DEFAULT_CONTAINER_GROUP = ServiceId[Container]("__default__")
13
-
14
-
15
- class GroupAnnotations(NamedTuple):
16
- """
17
- The list of service ids attached to a given function.
18
-
19
- The `name` attribute is the name of the function, and the `namespace` attribute represents a
20
- specific meaning for the group of services.
21
- """
22
-
23
- name: str
24
- namespace: str
25
- groups: tuple[ServiceId[Any], ...]
26
-
27
-
28
- class FunctionAnnotations(NamedTuple):
29
- """
30
- Holds metadata about the annotated service provider.
31
-
32
- Loosely inspired by: https://peps.python.org/pep-3107/.
33
- """
34
-
35
- providers: tuple[GroupAnnotations, ...]
36
-
37
- def group_in_namespace(
38
- self,
39
- namespace: str,
40
- group_id: ServiceId[T_ServiceType],
41
- ) -> tuple[GroupAnnotations, ...]:
42
- return tuple([x for x in self.with_namespace(namespace) if group_id in x.groups])
43
-
44
- def with_namespace(
45
- self,
46
- namespace: str,
47
- ) -> tuple[GroupAnnotations, ...]:
48
- return tuple([x for x in self.providers if x.namespace == namespace])
49
-
50
-
51
- class FunctionAnnotationsBuilder:
52
- _service_ids: dict[str, list[ServiceId[Any]]]
53
-
54
- def __init__(self) -> None:
55
- self._service_ids = defaultdict(list)
56
-
57
- def add(self, namespace: str, service_id: ServiceId[T_ServiceType]) -> None:
58
- self._service_ids[namespace].append(service_id)
59
-
60
- def make(self, name: str) -> tuple[GroupAnnotations, ...]:
61
- return tuple(
62
- [
63
- GroupAnnotations(name=name, namespace=namespace, groups=tuple(services))
64
- for namespace, services in self._service_ids.items()
65
- ]
66
- )
67
-
68
-
69
- class AnnotatedContainer(Container):
70
- def get_namespaced_group(
71
- self,
72
- namespace: str,
73
- group_id: ServiceId[T_ServiceType],
74
- ) -> Iterator[T_ServiceType]:
75
- annotations = _extract_class_annotations(type(self))
76
- containers = annotations.with_namespace(ProviderNamespaces.CONTAINERS)
77
- groups = annotations.group_in_namespace(namespace, group_id)
78
-
79
- for annotation in groups:
80
- yield getattr(self, annotation.name)()
81
-
82
- for container in containers:
83
- c = getattr(self, container.name)()
84
- yield from c.get_namespaced_group(namespace, group_id)
10
+ P = ParamSpec("P")
85
11
 
86
12
 
87
13
  def service(
88
14
  service_id: ServiceId[T_ServiceType],
89
- ) -> Callable[..., Callable[..., T_ServiceType]]:
90
- return fn_annotation_decorator(ProviderNamespaces.SERVICES, service_id)
15
+ ) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
16
+ """A service is anything you would create instances of?"""
17
+ return annotations.annotation(ProviderNamespaces.SERVICES, service_id)
91
18
 
92
19
 
93
- def group(
94
- group_id: ServiceId[T_ServiceType],
95
- ) -> Callable[..., Callable[..., T_ServiceType]]:
96
- return fn_annotation_decorator(ProviderNamespaces.GROUPS, group_id)
20
+ def autoid_service(fn: Callable[P, T_ServiceType]) -> Callable[P, T_ServiceType]:
21
+ _service_id = autoid(fn)
22
+ return annotations.annotation(ProviderNamespaces.SERVICES, _service_id)(fn)
97
23
 
98
24
 
99
- def config(
100
- config_id: ConfigId[T_ConfigType],
101
- ) -> Callable[..., Callable[..., T_ConfigType]]:
102
- return fn_annotation_decorator(ProviderNamespaces.SERVICES, config_id)
25
+ def group(
26
+ group_id: ServiceId[T_ServiceType],
27
+ ) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
28
+ """A group is a collection of services."""
29
+ return annotations.annotation(ProviderNamespaces.GROUPS, group_id)
103
30
 
104
31
 
105
32
  def fallback_service(
106
33
  service_id: ServiceId[T_ServiceType],
107
- ) -> Callable[..., Callable[..., T_ServiceType]]:
108
- return fn_annotation_decorator(ProviderNamespaces.FALLBACK_SERVICES, service_id)
34
+ ) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
35
+ """A fallback service gets used if no service is defined."""
36
+ return annotations.annotation(
37
+ ProviderNamespaces.FALLBACK_SERVICES,
38
+ service_id,
39
+ )
109
40
 
110
41
 
111
42
  def fallback_group(
112
43
  group_id: ServiceId[T_ServiceType],
113
- ) -> Callable[..., Callable[..., T_ServiceType]]:
114
- return fn_annotation_decorator(ProviderNamespaces.FALLBACK_GROUPS, group_id)
115
-
116
-
117
- def fallback_config(
118
- config_id: ConfigId[T_ConfigType],
119
- ) -> Callable[..., Callable[..., T_ConfigType]]:
120
- return fn_annotation_decorator(ProviderNamespaces.FALLBACK_SERVICES, config_id)
44
+ ) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
45
+ """A fallback group gets used if no group is defined."""
46
+ return annotations.annotation(
47
+ ProviderNamespaces.FALLBACK_GROUPS,
48
+ group_id,
49
+ )
121
50
 
122
51
 
123
- def container(
124
- group_id: ServiceId[T_ServiceType] = DEFAULT_CONTAINER_GROUP,
125
- ) -> Callable[..., Callable[..., T_ServiceType]]:
126
- return fn_annotation_decorator(ProviderNamespaces.CONTAINERS, group_id)
127
-
128
-
129
- def fn_annotation_decorator(
130
- namespace: str,
131
- service_id: ServiceId[T_ServiceType],
132
- ) -> Callable[..., Callable[..., T_ServiceType]]:
133
- def wrapper(
134
- fn: Callable[..., T_ServiceType],
135
- ) -> Callable[..., T_ServiceType]:
136
- _add_annotation(namespace, fn, service_id)
137
- return cache(fn)
138
-
139
- return wrapper
140
-
141
-
142
- @cache
143
- def _extract_class_annotations(cls: Any) -> FunctionAnnotations:
144
- function_annotations: list[GroupAnnotations] = []
145
- for method_name in dir(cls):
146
- if method_name.startswith("_"):
147
- continue
148
-
149
- builder = _get_annotations_builder(getattr(cls, method_name))
150
- function_annotations.extend(list(builder.make(method_name)))
52
+ def autoid(method: Callable[..., T_ServiceType]) -> ServiceId[T_ServiceType]:
53
+ """
54
+ Get a service id for a method.
151
55
 
152
- return FunctionAnnotations(tuple(function_annotations))
56
+ The service id is constructed from the module, class and method name. It should be identical
57
+ regardless of whether the method is bound or not, and regardless of the instance it is bound
58
+ to.
153
59
 
60
+ The service type is the return type of the method.
61
+ """
62
+ service_name = _get_method_service_id_name(method)
63
+ return ServiceId[T_ServiceType](service_name)
154
64
 
155
- def _add_annotation(namespace: str, fn: Any, service_id: ServiceId[T_ServiceType]) -> None:
156
- builder = _get_annotations_builder(fn)
157
- builder.add(namespace, service_id)
158
65
 
66
+ def _get_method_service_id_name(method: Callable[..., Any]) -> str:
67
+ tates = annotations.get_annotations(method).with_namespace(ProviderNamespaces.SERVICES)
159
68
 
160
- def _get_annotations_builder(fn: Any) -> FunctionAnnotationsBuilder:
161
- if not hasattr(fn, "__rats_service_annotations__"):
162
- fn.__rats_service_annotations__ = FunctionAnnotationsBuilder()
69
+ for a in tates.annotations:
70
+ return a.groups[0].name
163
71
 
164
- return cast(FunctionAnnotationsBuilder, fn.__rats_service_annotations__)
72
+ module_name = method.__module__
73
+ class_name, method_name = method.__qualname__.rsplit(".", 1)
74
+ return scope_service_name(module_name, class_name, method_name)
@@ -1,11 +1,13 @@
1
1
  from collections.abc import Iterator
2
+ from typing import final
2
3
 
3
4
  from ._container import Container
4
5
  from ._ids import ServiceId, T_ServiceType
5
6
 
6
7
 
8
+ @final
7
9
  class CompositeContainer(Container):
8
- _contailers: tuple[Container, ...]
10
+ _containers: tuple[Container, ...]
9
11
 
10
12
  def __init__(self, *containers: Container) -> None:
11
13
  self._containers = containers
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
4
6
 
5
- from ._ids import ServiceId, T_ServiceType, Tco_ConfigType, Tco_ServiceType
7
+ from typing_extensions import deprecated
8
+
9
+ from rats import annotations
10
+
11
+ from ._ids import ServiceId, T_ServiceType, 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
@@ -12,22 +20,62 @@ class ServiceProvider(Protocol[Tco_ServiceType]):
12
20
  """Return the service instance."""
13
21
 
14
22
 
15
- class ConfigProvider(ServiceProvider[Tco_ConfigType], Protocol[Tco_ConfigType]):
23
+ class GroupProvider(Protocol[Tco_ServiceType]):
16
24
  @abstractmethod
17
- def __call__(self) -> Tco_ConfigType:
18
- """Return the config instance."""
25
+ def __call__(self) -> Iterator[Tco_ServiceType]:
26
+ """Return the group instances."""
19
27
 
20
28
 
21
29
  class Container(Protocol):
22
- """Main interface for service containers."""
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]):
@@ -0,0 +1,34 @@
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/_ids.py CHANGED
@@ -1,17 +1,13 @@
1
- import typing
2
1
  from typing import Generic, TypeVar
3
2
 
4
3
  from typing_extensions import NamedTuple
5
4
 
5
+ from ._executables import Executable
6
+
6
7
  T_ServiceType = TypeVar("T_ServiceType")
7
- T_ConfigType = TypeVar("T_ConfigType", bound=typing.NamedTuple)
8
+ T_ExecutableType = TypeVar("T_ExecutableType", bound=Executable)
8
9
  Tco_ServiceType = TypeVar("Tco_ServiceType", covariant=True)
9
- Tco_ConfigType = TypeVar("Tco_ConfigType", bound=NamedTuple, covariant=True)
10
10
 
11
11
 
12
12
  class ServiceId(NamedTuple, Generic[T_ServiceType]):
13
13
  name: str
14
-
15
-
16
- class ConfigId(ServiceId[T_ConfigType]):
17
- pass