rats-apps 0.6.2__py3-none-any.whl → 0.7.0.dev20250314224701__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/app_context/__init__.py +19 -0
- rats/app_context/_collection.py +110 -0
- rats/app_context/_container.py +92 -0
- rats/app_context/_context.py +57 -0
- rats/app_context/py.typed +0 -0
- rats/apps/__init__.py +2 -1
- rats/apps/_composite_container.py +4 -0
- rats/apps/_runtimes.py +10 -0
- {rats_apps-0.6.2.dist-info → rats_apps-0.7.0.dev20250314224701.dist-info}/METADATA +7 -4
- {rats_apps-0.6.2.dist-info → rats_apps-0.7.0.dev20250314224701.dist-info}/RECORD +13 -8
- rats_e2e/apps/__main__.py +1 -2
- rats_e2e/apps/_example_cli.py +1 -1
- {rats_apps-0.6.2.dist-info → rats_apps-0.7.0.dev20250314224701.dist-info}/WHEEL +0 -0
@@ -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,9 +1,11 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: rats-apps
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.0.dev20250314224701
|
4
4
|
Summary: research analysis tools for building applications
|
5
5
|
License: MIT
|
6
6
|
Keywords: pipelines,machine learning,research
|
7
|
+
Author: Elon Portugaly
|
8
|
+
Author-email: elonp@microsoft.com
|
7
9
|
Requires-Python: >=3.10,<4.0
|
8
10
|
Classifier: License :: OSI Approved :: MIT License
|
9
11
|
Classifier: Programming Language :: Python :: 3
|
@@ -13,9 +15,10 @@ Classifier: Programming Language :: Python :: 3.12
|
|
13
15
|
Classifier: Programming Language :: Python :: 3.13
|
14
16
|
Requires-Dist: click
|
15
17
|
Requires-Dist: colorlog
|
16
|
-
Requires-Dist:
|
17
|
-
|
18
|
-
Project-URL:
|
18
|
+
Requires-Dist: dataclass-wizard
|
19
|
+
Requires-Dist: typing-extensions
|
20
|
+
Project-URL: Documentation, https://microsoft.github.io/rats
|
21
|
+
Project-URL: Repository, https://github.com/microsoft/rats
|
19
22
|
Description-Content-Type: text/markdown
|
20
23
|
|
21
24
|
# rats-apps
|
@@ -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/
|
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=
|
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=
|
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
|
@@ -27,14 +32,14 @@ rats/logs/__init__.py,sha256=sizLa6JbqekhJGKDyRZUUQvTuqiJx6yxwA5jT-tDQck,152
|
|
27
32
|
rats/logs/_app.py,sha256=TF6rfQXQfaHd3AFW2anAoAGaxzU31kqJMoKkjuBtbF0,1617
|
28
33
|
rats/logs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
29
34
|
rats_e2e/apps/__init__.py,sha256=c2QG818ACyS9MBXs_Evg5yYT6k-KPMQfIqF7Z77Cau0,142
|
30
|
-
rats_e2e/apps/__main__.py,sha256=
|
31
|
-
rats_e2e/apps/_example_cli.py,sha256=
|
35
|
+
rats_e2e/apps/__main__.py,sha256=ETECGDD8glcbmx0P-WWmjQQX6gtF-5L7_DxZjNmx3X4,350
|
36
|
+
rats_e2e/apps/_example_cli.py,sha256=XzWOdnQrSp-F_FZEdm2CCm8dUJZWOSm2KFPIK0jzoi8,2840
|
32
37
|
rats_e2e/apps/inputs/__init__.py,sha256=A9OActelrdQR-YYxUAGzogUFiOt3sDayGtQ3_6orVzc,785
|
33
38
|
rats_e2e/apps/inputs/__main__.py,sha256=Mf-a2iQKTTgh9hMd6AeuzmU9araMIyf1AtdWkh_L07E,117
|
34
39
|
rats_e2e/apps/inputs/_app.py,sha256=FiaLgOZc-d1ryKSwKnL5XBNGcOP1bHbxxeMJqoU_RJg,1446
|
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.
|
39
|
-
rats_apps-0.
|
40
|
-
rats_apps-0.
|
43
|
+
rats_apps-0.7.0.dev20250314224701.dist-info/METADATA,sha256=I024wegREBazzEeZxr_GLtXZsTrdq6sXsQP3qyJvYxQ,866
|
44
|
+
rats_apps-0.7.0.dev20250314224701.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
45
|
+
rats_apps-0.7.0.dev20250314224701.dist-info/RECORD,,
|
rats_e2e/apps/__main__.py
CHANGED
rats_e2e/apps/_example_cli.py
CHANGED
File without changes
|