rats-apps 0.6.0.dev20250218170831__py3-none-any.whl → 0.6.0.dev20250310152143__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,19 @@
1
+ """Application context package to help share [apps.Container][] state across machines."""
2
+
3
+ from ._collection import (
4
+ Collection,
5
+ dumps,
6
+ loads,
7
+ )
8
+ from ._container import GroupContainer, ServiceContainer
9
+ from ._context import Context, T_ContextType
10
+
11
+ __all__ = [
12
+ "Collection",
13
+ "Context",
14
+ "GroupContainer",
15
+ "ServiceContainer",
16
+ "T_ContextType",
17
+ "dumps",
18
+ "loads",
19
+ ]
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import logging
5
+ from dataclasses import dataclass
6
+ from typing import Any, Generic, final
7
+
8
+ import dataclass_wizard
9
+
10
+ from rats import apps
11
+
12
+ from ._context import Context, ContextValue, T_ContextType
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @final
18
+ @dataclass(frozen=True)
19
+ class Collection(Generic[T_ContextType]):
20
+ """
21
+ Collection of context objects linking service ids to serializable data structures.
22
+
23
+ It is up to the user of this class to provide the correct mapping between services and the
24
+ constructor for those services.
25
+ """
26
+
27
+ items: tuple[Context[T_ContextType], ...]
28
+
29
+ @staticmethod
30
+ def merge(*collections: Collection[T_ContextType]) -> Collection[T_ContextType]:
31
+ return Collection[T_ContextType].make(
32
+ *[ctx for collection in collections for ctx in collection.items]
33
+ )
34
+
35
+ @staticmethod
36
+ def empty() -> Collection[T_ContextType]:
37
+ """Useful when wanting to define a simple default value for a context collection."""
38
+ return Collection(items=())
39
+
40
+ @staticmethod
41
+ def make(*items: Context[T_ContextType]) -> Collection[T_ContextType]:
42
+ # just a handy shortcut to remove a nested tuple from the end-user code.
43
+ return Collection(items=items)
44
+
45
+ def service_ids(self) -> set[apps.ServiceId[T_ContextType]]:
46
+ return {item.service_id for item in self.items}
47
+
48
+ def decoded_values(
49
+ self,
50
+ cls: type[T_ContextType],
51
+ service_id: apps.ServiceId[T_ContextType],
52
+ ) -> tuple[T_ContextType, ...]:
53
+ return tuple(dataclass_wizard.fromlist(cls, list(self.values(service_id))))
54
+
55
+ def values(self, service_id: apps.ServiceId[T_ContextType]) -> tuple[ContextValue, ...]:
56
+ results: list[ContextValue] = []
57
+ for item in self.with_id(service_id).items:
58
+ for value in item.values:
59
+ results.append(value)
60
+
61
+ return tuple(results)
62
+
63
+ def add(self, *items: Context[T_ContextType]) -> Collection[T_ContextType]:
64
+ """Creates a new Collection with the provided items added to the current ones."""
65
+ return Collection[T_ContextType].make(
66
+ *self.items,
67
+ *items,
68
+ )
69
+
70
+ def with_id(self, *service_ids: apps.ServiceId[T_ContextType]) -> Collection[T_ContextType]:
71
+ """Filter out context items not matching the provided service ids and return a new Collection."""
72
+ return Collection[T_ContextType](
73
+ items=tuple(item for item in self.items if item.service_id in service_ids),
74
+ )
75
+
76
+
77
+ def loads(context: str) -> Collection[Any]:
78
+ """
79
+ Transforms a json string into a [rats.app_context.Collection][] instance.
80
+
81
+ This function does not attempt to validate the mapping between services and their values.
82
+
83
+ Args:
84
+ context: json string containing an `items` key of `service_id`, `values` mappings.
85
+ """
86
+ data = json.loads(context)
87
+ return Collection[Any](
88
+ items=tuple(
89
+ [
90
+ Context(
91
+ service_id=apps.ServiceId(*x["serviceId"]),
92
+ values=tuple(x["values"]),
93
+ )
94
+ for x in data["items"]
95
+ ]
96
+ ),
97
+ )
98
+
99
+
100
+ def dumps(collection: Collection[Any]) -> str:
101
+ """
102
+ Serializes a [rats.app_context.Collection][] instance to a json string.
103
+
104
+ Args:
105
+ collection: collection of serializable services wanting to be shared between machines.
106
+
107
+ Returns: a string that can easily be shared between machines.
108
+
109
+ """
110
+ return json.dumps(dataclass_wizard.asdict(collection)) # type: ignore
@@ -0,0 +1,92 @@
1
+ import logging
2
+ from collections.abc import Iterator
3
+ from functools import partial
4
+ from typing import Generic, final
5
+
6
+ from rats import apps
7
+
8
+ from ._collection import Collection
9
+ from ._context import T_ContextType
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ @final
15
+ class ServiceContainer(apps.Container, Generic[T_ContextType]):
16
+ """A [rats.apps.Container][] that provides services from a [rats.app_context.Collection][]."""
17
+
18
+ _cls: type[T_ContextType]
19
+ _namespace: str
20
+ _collection: Collection[T_ContextType]
21
+
22
+ def __init__(
23
+ self,
24
+ cls: type[T_ContextType],
25
+ namespace: str,
26
+ collection: Collection[T_ContextType],
27
+ ) -> None:
28
+ self._cls = cls
29
+ self._namespace = namespace
30
+ self._collection = collection
31
+
32
+ @apps.container()
33
+ def _contexts(self) -> apps.Container:
34
+ containers: list[apps.Container] = []
35
+
36
+ def _provider(_service_id: apps.ServiceId[T_ContextType]) -> T_ContextType:
37
+ contexts = self._collection.decoded_values(self._cls, _service_id)
38
+ if len(contexts) > 1:
39
+ raise apps.DuplicateServiceError(_service_id)
40
+ if len(contexts) == 0:
41
+ raise apps.ServiceNotFoundError(_service_id)
42
+
43
+ return contexts[0]
44
+
45
+ for service_id in self._collection.service_ids():
46
+ containers.append(
47
+ apps.StaticContainer(
48
+ apps.StaticProvider[T_ContextType](
49
+ namespace=apps.ProviderNamespaces.SERVICES,
50
+ service_id=service_id,
51
+ call=partial(_provider, service_id),
52
+ ),
53
+ )
54
+ )
55
+
56
+ return apps.CompositeContainer(*containers)
57
+
58
+
59
+ @final
60
+ class GroupContainer(apps.Container, Generic[T_ContextType]):
61
+ """A [rats.apps.Container][] that provides services from a [rats.app_context.Collection][]."""
62
+
63
+ _cls: type[T_ContextType]
64
+ _collection: Collection[T_ContextType]
65
+
66
+ def __init__(
67
+ self,
68
+ cls: type[T_ContextType],
69
+ collection: Collection[T_ContextType],
70
+ ) -> None:
71
+ self._cls = cls
72
+ self._collection = collection
73
+
74
+ @apps.container()
75
+ def _contexts(self) -> apps.Container:
76
+ containers: list[apps.Container] = []
77
+
78
+ def _group_provider(_service_id: apps.ServiceId[T_ContextType]) -> Iterator[T_ContextType]:
79
+ yield from self._collection.decoded_values(self._cls, _service_id)
80
+
81
+ for service_id in self._collection.service_ids():
82
+ containers.append(
83
+ apps.StaticContainer(
84
+ apps.StaticProvider[T_ContextType](
85
+ namespace=apps.ProviderNamespaces.GROUPS,
86
+ service_id=service_id,
87
+ provider=partial(_group_provider, service_id), # type: ignore
88
+ ),
89
+ )
90
+ )
91
+
92
+ return apps.CompositeContainer(*containers)
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from dataclasses import dataclass
5
+ from typing import Any, Generic, TypeVar, final
6
+
7
+ import dataclass_wizard
8
+
9
+ from rats import apps
10
+
11
+ logger = logging.getLogger(__name__)
12
+ T_ContextType = TypeVar("T_ContextType") # TODO: figure out how to bind this to dataclasses :(
13
+ ContextValue = dict[str, Any]
14
+
15
+
16
+ @final
17
+ @dataclass(frozen=True)
18
+ class Context(dataclass_wizard.JSONSerializable, Generic[T_ContextType]):
19
+ """
20
+ An easily serializable class to hold one or more values tied to a service id.
21
+
22
+ We can use these instances to share simple value types across a cluster of machines, and
23
+ integrate them with [rats.apps.Container][] to make the business logic unaware of any
24
+ complexity in here.
25
+ """
26
+
27
+ service_id: apps.ServiceId[T_ContextType]
28
+ values: tuple[ContextValue, ...]
29
+
30
+ @staticmethod
31
+ def make(
32
+ service_id: apps.ServiceId[T_ContextType],
33
+ *contexts: T_ContextType,
34
+ ) -> Context[T_ContextType]:
35
+ """
36
+ Convert a dataclass into a context object tied to the provided service id.
37
+
38
+ ```python
39
+ from dataclass import dataclass
40
+
41
+
42
+ class MySimpleData:
43
+ blob_path: Tuple[str, str, str]
44
+ offset: int
45
+
46
+
47
+ ctx = app_context.Context[MySimpleData].make(
48
+ MySimpleData(
49
+ blob_path=("accountname", "containername", "/some/blob/path"),
50
+ )
51
+ )
52
+ ```
53
+ """
54
+ return Context(
55
+ service_id,
56
+ tuple([dataclass_wizard.asdict(ctx) for ctx in contexts]), # type: ignore
57
+ )
File without changes
rats/apps/__init__.py CHANGED
@@ -23,7 +23,7 @@ from ._app_containers import (
23
23
  ContainerPlugin,
24
24
  PluginMixin,
25
25
  )
26
- from ._composite_container import CompositeContainer
26
+ from ._composite_container import EMPTY_CONTAINER, CompositeContainer
27
27
  from ._container import (
28
28
  Container,
29
29
  DuplicateServiceError,
@@ -42,6 +42,7 @@ from ._scoping import autoscope
42
42
  from ._static_container import StaticContainer, StaticProvider, static_group, static_service
43
43
 
44
44
  __all__ = [
45
+ "EMPTY_CONTAINER",
45
46
  "App",
46
47
  "AppBundle",
47
48
  "AppContainer",
@@ -19,3 +19,7 @@ class CompositeContainer(Container):
19
19
  ) -> Iterator[T_ServiceType]:
20
20
  for container in self._containers:
21
21
  yield from container.get_namespaced_group(namespace, group_id)
22
+
23
+
24
+ EMPTY_CONTAINER = CompositeContainer()
25
+ """Convenience [rats.apps.Container][] instance with no services."""
rats/apps/_runtimes.py CHANGED
@@ -10,6 +10,16 @@ logger = logging.getLogger(__name__)
10
10
 
11
11
 
12
12
  class Runtime(Protocol):
13
+ """
14
+ Classes that run services that implement the [rats.apps.Executable][] interface.
15
+
16
+ Many of the lower level interfaces use [rats.apps.Executable][] to provide the user with a
17
+ class that has an [rats.apps.Executable.execute][] method; most notably, [rats.apps.App][] and
18
+ [rats.apps.AppContainer][]. These classes typically represent the main entry points to an
19
+ application, and runtime implementations provide a way to execute these applications in
20
+ threads, processes, and remote environments.
21
+ """
22
+
13
23
  @abstractmethod
14
24
  def execute(self, *exe_ids: ServiceId[T_ExecutableType]) -> None:
15
25
  """Execute a list of executables sequentially."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rats-apps
3
- Version: 0.6.0.dev20250218170831
3
+ Version: 0.6.0.dev20250310152143
4
4
  Summary: research analysis tools for building applications
5
5
  License: MIT
6
6
  Keywords: pipelines,machine learning,research
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Dist: click
15
15
  Requires-Dist: colorlog
16
+ Requires-Dist: dataclass-wizard
16
17
  Requires-Dist: typing_extensions
17
18
  Project-URL: Documentation, https://microsoft.github.io/rats/
18
19
  Project-URL: Repository, https://github.com/microsoft/rats/
@@ -2,17 +2,22 @@ rats/annotations/__init__.py,sha256=YmydaopUSlpoX6MvQ-3p18sgEC_DB_y683wVICD-jnU,
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=uTPk2ipOy7MgLoV0s9l22U1Qyo8aL2yKVwDukOY9WGw,1934
5
+ rats/app_context/__init__.py,sha256=UoTUhj1VvefMsD_k0rruTuQxauXUEk9N8qcqgyyPXS0,401
6
+ rats/app_context/_collection.py,sha256=hOfKF5OExsuuDhdj8urNWoYRLchAMWq2gwyc72J_9i0,3607
7
+ rats/app_context/_container.py,sha256=YkNG25jngfN_064jv8fZmKW1FSuL6RRJAp9F_hm2NZg,2954
8
+ rats/app_context/_context.py,sha256=VUm-cg2WKtFU2VvIJI8OmTdRRoDLXeWPBYu_ynpUjlY,1599
9
+ rats/app_context/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ rats/apps/__init__.py,sha256=4dzcE7MZTtZD5NA-pnzQbElABUxUsepf3GknBbXjfwc,1974
6
11
  rats/apps/_annotations.py,sha256=6M_M7K8haNVda0Tx02EpFf3s9EjnWYacNMjTIkNEdRU,4617
7
12
  rats/apps/_app_containers.py,sha256=ygZL0NM-fSRSemFddBsVMwcmSQw07sbb7bIqWTr-_yU,6147
8
- rats/apps/_composite_container.py,sha256=s_of6NyyrjFVYWGVehyEHe9WJIPRCnbB-tyWyNF8zyc,585
13
+ rats/apps/_composite_container.py,sha256=FdpmH_xIly6LxNZrA_nZCznukptjVLXttXTMtf_tnv8,695
9
14
  rats/apps/_container.py,sha256=sYISCG-qVzl-bsBwX_CAWEP9gtXCnG-Jls3l8IKb4DQ,7208
10
15
  rats/apps/_executables.py,sha256=hXExNmAnuPU1KJXihNw1jEDAQpMlQ9E9_aPV8tpGbOY,1347
11
16
  rats/apps/_ids.py,sha256=T8Onrj79t8NPfBMQBk0xI6fIWDKF0m2JfFNrdtXAbWg,353
12
17
  rats/apps/_mains.py,sha256=2Q97mNk1cBzYROc_pJcm57EEeHmwRbXOWpfYXH37qcA,995
13
18
  rats/apps/_namespaces.py,sha256=THUV_Xj5PtweC23Ob-zsSpk8exC4fT-qRwjpQ6IDm0U,188
14
19
  rats/apps/_plugin_container.py,sha256=cad9Xm1YsJrPkKfMb3xvegIxaL3HVL5JYreHKqooy1A,1543
15
- rats/apps/_runtimes.py,sha256=l5Z26jZVHk4CvT8VyKUK5tpcCAKt2N1DWipd4gX_Bls,2060
20
+ rats/apps/_runtimes.py,sha256=yzWPVbY0I-orRRN23on-iEX8Bo0BqoRiYeR3dwcBlGg,2592
16
21
  rats/apps/_scoping.py,sha256=6C2-ID22cCPR9Cbexf3CvCF3o9F_7ieURbwqkf6DI68,1360
17
22
  rats/apps/_static_container.py,sha256=KH4AwRMX5QPIwYrD9W_HayIpQIrbVn7clEMx44LFAGc,2113
18
23
  rats/apps/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -35,6 +40,6 @@ rats_e2e/apps/inputs/_app.py,sha256=FiaLgOZc-d1ryKSwKnL5XBNGcOP1bHbxxeMJqoU_RJg,
35
40
  rats_e2e/apps/minimal/__init__.py,sha256=bUR6Oexx6Jsouxor0cL9emXoVha4cm3WqyhU1pgchsI,521
36
41
  rats_e2e/apps/minimal/__main__.py,sha256=Mf-a2iQKTTgh9hMd6AeuzmU9araMIyf1AtdWkh_L07E,117
37
42
  rats_e2e/apps/minimal/_app.py,sha256=CQ09LVTNRarz7Pb1wiSuNHrZ_2KGcgH8nUqy4BjxMUY,849
38
- rats_apps-0.6.0.dev20250218170831.dist-info/METADATA,sha256=RoQiYMoEdygZ7Q3hVavblFvkcjEvz3YEG_5wfanxYpU,779
39
- rats_apps-0.6.0.dev20250218170831.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
40
- rats_apps-0.6.0.dev20250218170831.dist-info/RECORD,,
43
+ rats_apps-0.6.0.dev20250310152143.dist-info/METADATA,sha256=qyliU36pf-1mlVwt4xDDdtLaDIKpEchKRynXbqBZVvo,811
44
+ rats_apps-0.6.0.dev20250310152143.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
45
+ rats_apps-0.6.0.dev20250310152143.dist-info/RECORD,,