rats-apps 0.2.0.dev20240904170114__py3-none-any.whl → 0.3.0.dev20240928043035__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 CHANGED
@@ -7,7 +7,9 @@ domain.
7
7
 
8
8
  from ._annotations import (
9
9
  autoid,
10
+ autoid_factory_service,
10
11
  autoid_service,
12
+ factory_service,
11
13
  fallback_group,
12
14
  fallback_service,
13
15
  group,
@@ -63,4 +65,6 @@ __all__ = [
63
65
  "StandardRuntime",
64
66
  "StandardRuntime",
65
67
  "SimpleApplication",
68
+ "factory_service",
69
+ "autoid_factory_service",
66
70
  ]
rats/apps/_annotations.py CHANGED
@@ -1,5 +1,5 @@
1
- from collections.abc import Callable
2
- from typing import Any, NamedTuple, ParamSpec, cast
1
+ from collections.abc import Callable, Iterator
2
+ from typing import Any, Concatenate, Generic, NamedTuple, ParamSpec, TypeVar, cast
3
3
 
4
4
  from rats import annotations
5
5
 
@@ -8,6 +8,8 @@ from ._namespaces import ProviderNamespaces
8
8
  from ._scoping import scope_service_name
9
9
 
10
10
  P = ParamSpec("P")
11
+ R = TypeVar("R")
12
+ T_Container = TypeVar("T_Container")
11
13
 
12
14
 
13
15
  def service(
@@ -24,7 +26,7 @@ def autoid_service(fn: Callable[P, T_ServiceType]) -> Callable[P, T_ServiceType]
24
26
 
25
27
  def group(
26
28
  group_id: ServiceId[T_ServiceType],
27
- ) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
29
+ ) -> Callable[[Callable[P, Iterator[T_ServiceType]]], Callable[P, Iterator[T_ServiceType]]]:
28
30
  """A group is a collection of services."""
29
31
  return annotations.annotation(ProviderNamespaces.GROUPS, cast(NamedTuple, group_id))
30
32
 
@@ -41,7 +43,7 @@ def fallback_service(
41
43
 
42
44
  def fallback_group(
43
45
  group_id: ServiceId[T_ServiceType],
44
- ) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
46
+ ) -> Callable[[Callable[P, Iterator[T_ServiceType]]], Callable[P, Iterator[T_ServiceType]]]:
45
47
  """A fallback group gets used if no group is defined."""
46
48
  return annotations.annotation(
47
49
  ProviderNamespaces.FALLBACK_GROUPS,
@@ -49,6 +51,61 @@ def fallback_group(
49
51
  )
50
52
 
51
53
 
54
+ def _factory_to_factory_provider(
55
+ method: Callable[Concatenate[T_Container, P], R],
56
+ ) -> Callable[[T_Container], Callable[P, R]]:
57
+ """Convert a factory method a factory provider method returning the original method."""
58
+
59
+ def new_method(self: T_Container) -> Callable[P, R]:
60
+ def factory(*args: P.args, **kwargs: P.kwargs) -> R:
61
+ return method(self, *args, **kwargs)
62
+
63
+ return factory
64
+
65
+ new_method.__name__ = method.__name__
66
+ new_method.__module__ = method.__module__
67
+ new_method.__qualname__ = method.__qualname__
68
+ new_method.__doc__ = method.__doc__
69
+ return new_method
70
+
71
+
72
+ class _FactoryService(Generic[P, R]):
73
+ """
74
+ A decorator to create a factory service.
75
+
76
+ Decorate a method that takes any number of arguments and returns an object. The resulting
77
+ service will be that factory - taking the same arguments and returning a new object each time.
78
+ """
79
+
80
+ _service_id: ServiceId[Callable[P, R]]
81
+
82
+ def __init__(self, service_id: ServiceId[Callable[P, R]]) -> None:
83
+ self._service_id = service_id
84
+
85
+ def __call__(
86
+ self, method: Callable[Concatenate[T_Container, P], R]
87
+ ) -> Callable[[T_Container], Callable[P, R]]:
88
+ new_method = _factory_to_factory_provider(method)
89
+ return service(self._service_id)(new_method)
90
+
91
+
92
+ # alias so we can think of it as a function
93
+ factory_service = _FactoryService
94
+
95
+
96
+ def autoid_factory_service(
97
+ method: Callable[Concatenate[T_Container, P], R],
98
+ ) -> Callable[[T_Container], Callable[P, R]]:
99
+ """
100
+ A decorator to create a factory service, with an automatically generated service id.
101
+
102
+ Decorate a method that takes any number of arguments and returns an object. The resulting
103
+ service will be that factory - taking the same arguments and returning a new object each time.
104
+ """
105
+ new_method = _factory_to_factory_provider(method)
106
+ return autoid_service(new_method)
107
+
108
+
52
109
  def autoid(method: Callable[..., T_ServiceType]) -> ServiceId[T_ServiceType]:
53
110
  """
54
111
  Get a service id for a method.
rats/apps/_container.py CHANGED
@@ -1,7 +1,9 @@
1
1
  import logging
2
2
  from abc import abstractmethod
3
3
  from collections.abc import Callable, Iterator
4
- from typing import Generic, NamedTuple, ParamSpec, Protocol, cast
4
+ from typing import Any, Generic, NamedTuple, ParamSpec, Protocol, cast
5
+
6
+ from typing_extensions import NamedTuple as ExtNamedTuple
5
7
 
6
8
  from rats import annotations
7
9
 
@@ -109,9 +111,13 @@ class Container(Protocol):
109
111
  ) -> Iterator[T_ServiceType]:
110
112
  """Retrieve a service group by its id."""
111
113
  if not self.has_namespace(ProviderNamespaces.GROUPS, group_id):
112
- yield from self.get_namespaced_group(ProviderNamespaces.FALLBACK_GROUPS, group_id)
114
+ # groups are expected to return iterable services
115
+ # TODO: we need to clean up the meaning of groups and services somehow
116
+ for i in self.get_namespaced_group(ProviderNamespaces.FALLBACK_GROUPS, group_id):
117
+ yield from cast(Iterator[T_ServiceType], i)
113
118
 
114
- yield from self.get_namespaced_group(ProviderNamespaces.GROUPS, group_id)
119
+ for i in self.get_namespaced_group(ProviderNamespaces.GROUPS, group_id):
120
+ yield from cast(Iterator[T_ServiceType], i)
115
121
 
116
122
  def get_namespaced_group(
117
123
  self,
@@ -119,31 +125,67 @@ class Container(Protocol):
119
125
  group_id: ServiceId[T_ServiceType],
120
126
  ) -> Iterator[T_ServiceType]:
121
127
  """Retrieve a service group by its id, within a given service namespace."""
122
- tates = annotations.get_class_annotations(type(self))
123
- # containers are a special service namespace that we look through recursively
124
- containers = tates.with_namespace(ProviderNamespaces.CONTAINERS)
125
- groups = tates.with_group(namespace, cast(NamedTuple, group_id))
126
-
127
- for annotation in groups.annotations:
128
- if not hasattr(self, f"__rats_cache_{annotation.name}_{group_id.name}"):
129
- setattr(
130
- self,
131
- f"__rats_cache_{annotation.name}_{group_id.name}",
132
- getattr(self, annotation.name)(),
133
- )
134
-
135
- yield getattr(self, f"__rats_cache_{annotation.name}_{group_id.name}")
136
-
137
- for annotation in containers.annotations:
138
- if not hasattr(self, f"__rats_container_cache_{annotation.name}"):
139
- setattr(
140
- self,
141
- f"__rats_container_cache_{annotation.name}",
142
- getattr(self, annotation.name)(),
143
- )
144
-
145
- c: Container = getattr(self, f"__rats_container_cache_{annotation.name}")
146
- yield from c.get_namespaced_group(namespace, group_id)
128
+ yield from _get_cached_services_for_group(self, namespace, group_id)
129
+
130
+ for subcontainer in _get_subcontainers(self):
131
+ yield from subcontainer.get_namespaced_group(namespace, group_id)
132
+
133
+
134
+ def _get_subcontainers(c: Container) -> Iterator[Container]:
135
+ yield from _get_cached_services_for_group(
136
+ c, ProviderNamespaces.CONTAINERS, DEFAULT_CONTAINER_GROUP
137
+ )
138
+
139
+
140
+ class _ProviderInfo(ExtNamedTuple, Generic[T_ServiceType]):
141
+ attr: str
142
+ group_id: ServiceId[T_ServiceType]
143
+
144
+
145
+ def _get_cached_services_for_group(
146
+ c: Container,
147
+ namespace: str,
148
+ group_id: ServiceId[T_ServiceType],
149
+ ) -> Iterator[T_ServiceType]:
150
+ provider_cache = _get_provider_cache(c)
151
+ info_cache = _get_provider_info_cache(c)
152
+
153
+ if (namespace, group_id) not in info_cache:
154
+ info_cache[(namespace, group_id)] = list(_get_providers_for_group(c, namespace, group_id))
155
+
156
+ for provider in info_cache[(namespace, group_id)]:
157
+ if provider not in provider_cache:
158
+ provider_cache[provider] = getattr(c, provider.attr)()
159
+
160
+ yield provider_cache[provider]
161
+
162
+
163
+ def _get_provider_cache(obj: object) -> dict[_ProviderInfo[Any], Any]:
164
+ if not hasattr(obj, "__rats_apps_provider_cache__"):
165
+ obj.__rats_apps_provider_cache__ = {} # type: ignore[reportAttributeAccessIssue]
166
+
167
+ return obj.__rats_apps_provider_cache__ # type: ignore[reportAttributeAccessIssue]
168
+
169
+
170
+ def _get_provider_info_cache(
171
+ obj: object,
172
+ ) -> dict[tuple[str, ServiceId[Any]], list[_ProviderInfo[Any]]]:
173
+ if not hasattr(obj, "__rats_apps_provider_info_cache__"):
174
+ obj.__rats_apps_provider_info_cache__ = {} # type: ignore[reportAttributeAccessIssue]
175
+
176
+ return obj.__rats_apps_provider_info_cache__ # type: ignore[reportAttributeAccessIssue]
177
+
178
+
179
+ def _get_providers_for_group(
180
+ c: Container,
181
+ namespace: str,
182
+ group_id: ServiceId[T_ServiceType],
183
+ ) -> Iterator[_ProviderInfo[T_ServiceType]]:
184
+ tates = annotations.get_class_annotations(type(c))
185
+ groups = tates.with_group(namespace, cast(NamedTuple, group_id))
186
+
187
+ for annotation in groups.annotations:
188
+ yield _ProviderInfo(annotation.name, group_id)
147
189
 
148
190
 
149
191
  DEFAULT_CONTAINER_GROUP = ServiceId[Container](f"{__name__}:__default__")
rats/logs/_plugin.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import logging.config
2
+ from collections.abc import Iterator
2
3
 
3
4
  from rats import apps
4
5
 
@@ -22,9 +23,9 @@ class PluginContainer(apps.Container):
22
23
  self._app = app
23
24
 
24
25
  @apps.group(PluginServices.EVENTS.CONFIGURE_LOGGING)
25
- def _configure_logging(self) -> apps.Executable:
26
+ def _configure_logging(self) -> Iterator[apps.Executable]:
26
27
  # in the future, we can use this plugin to make logging easily configurable
27
- return apps.App(
28
+ yield apps.App(
28
29
  lambda: logging.config.dictConfig(
29
30
  {
30
31
  "version": 1,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rats-apps
3
- Version: 0.2.0.dev20240904170114
3
+ Version: 0.3.0.dev20240928043035
4
4
  Summary: research analysis tools for building applications
5
5
  Home-page: https://github.com/microsoft/rats/
6
6
  License: MIT
@@ -2,11 +2,11 @@ rats/annotations/__init__.py,sha256=wsGhRQzZrV2oJTnBAX0aGgpyT1kYT235jkP3Wb8BTRY,
2
2
  rats/annotations/__main__.py,sha256=vlzQOM9y82P0OL5tYcmSM_4jTg0s8jayAcvEoi9cBvI,1065
3
3
  rats/annotations/_functions.py,sha256=UkHh3zdBivluE7dBeGQ17zoIfGdyIokMAkFmpWaIlDc,4284
4
4
  rats/annotations/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- rats/apps/__init__.py,sha256=KtAsBY62BS5lYom6j5x69BDItfEYeOxu5EY0kbRWC0g,1601
5
+ rats/apps/__init__.py,sha256=SlqFKChSf661J6nlcuSGNQO-fLceqZrwOGn1YoyYDzs,1703
6
6
  rats/apps/__main__.py,sha256=KjdadN4rdP0xhWiLzdmtCsXejWx_gxOK-ah-L1r1dTI,1818
7
- rats/apps/_annotations.py,sha256=yOf4MKFGQz3x4hpaWgMf2y2GlbXtbIh4uED15voXzyY,2566
7
+ rats/apps/_annotations.py,sha256=6M_M7K8haNVda0Tx02EpFf3s9EjnWYacNMjTIkNEdRU,4617
8
8
  rats/apps/_composite_container.py,sha256=s_of6NyyrjFVYWGVehyEHe9WJIPRCnbB-tyWyNF8zyc,585
9
- rats/apps/_container.py,sha256=rLUPnsDfLR26YjFUGGu3YYpqIta1l0q8p077Hzfflwo,5989
9
+ rats/apps/_container.py,sha256=Gb7jJmGyKGDZFjoe0s0C_gC7ymSpXaqordfQaJZGJf4,7283
10
10
  rats/apps/_executables.py,sha256=QJ5_UPdZPmDQ1a3cLRJDUoeUMzNMwa3ZHYhYeS3AVq4,971
11
11
  rats/apps/_ids.py,sha256=T8Onrj79t8NPfBMQBk0xI6fIWDKF0m2JfFNrdtXAbWg,353
12
12
  rats/apps/_namespaces.py,sha256=THUV_Xj5PtweC23Ob-zsSpk8exC4fT-qRwjpQ6IDm0U,188
@@ -25,9 +25,9 @@ rats/cli/_container.py,sha256=VddjrsH1lqiarJ6rXf4KUbuNtNXEduCr38UH_TwGgFE,1872
25
25
  rats/cli/_plugin.py,sha256=o5emP-E0LLOGvD14ZBYNY6h407pngrJf8ODMB5Wdd8U,711
26
26
  rats/cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  rats/logs/__init__.py,sha256=fCn4pfpYiAcTtt5CsnUZX68CjOB3KJHxMSiYxsma4qE,183
28
- rats/logs/_plugin.py,sha256=eAAG4ci-XS9A9ituXj9PrcI6zH-xlCqhJlUbSGC-MYM,2175
28
+ rats/logs/_plugin.py,sha256=OQ5fXToBm60YJRrMUVJ9_HVytUs3c69vaHY57jpivb0,2221
29
29
  rats/logs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- rats_apps-0.2.0.dev20240904170114.dist-info/METADATA,sha256=MbekP29Jvud3udbD7O3YU986gUyhy7d61wtIsx9KlIM,774
31
- rats_apps-0.2.0.dev20240904170114.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
32
- rats_apps-0.2.0.dev20240904170114.dist-info/entry_points.txt,sha256=9oOvf2loQr5ACWQgvuu9Q3KZIVIxKE5Aa-rLuUII5WQ,91
33
- rats_apps-0.2.0.dev20240904170114.dist-info/RECORD,,
30
+ rats_apps-0.3.0.dev20240928043035.dist-info/METADATA,sha256=k0UESmQA1tr66nQVFUMKtsfhQicSC75kzJS6hFTcyBQ,774
31
+ rats_apps-0.3.0.dev20240928043035.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
32
+ rats_apps-0.3.0.dev20240928043035.dist-info/entry_points.txt,sha256=9oOvf2loQr5ACWQgvuu9Q3KZIVIxKE5Aa-rLuUII5WQ,91
33
+ rats_apps-0.3.0.dev20240928043035.dist-info/RECORD,,