rats-apps 0.10.3.dev20250520164722__py3-none-any.whl → 0.11.0__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 +54 -2
- rats/app_context/_collection.py +55 -10
- rats/app_context/_context.py +25 -1
- rats/apps/_app_containers.py +1 -5
- rats/cli/_click_app.py +1 -1
- rats/runtime/__init__.py +64 -0
- rats/runtime/__main__.py +5 -0
- rats/runtime/_app.py +160 -0
- rats/runtime/_request.py +23 -0
- rats/runtime/py.typed +0 -0
- {rats_apps-0.10.3.dev20250520164722.dist-info → rats_apps-0.11.0.dist-info}/METADATA +2 -1
- {rats_apps-0.10.3.dev20250520164722.dist-info → rats_apps-0.11.0.dist-info}/RECORD +21 -8
- rats_apps-0.11.0.dist-info/entry_points.txt +6 -0
- rats_e2e/apps/py.typed +0 -0
- rats_e2e/runtime/__init__.py +7 -0
- rats_e2e/runtime/_app.py +23 -0
- rats_e2e/runtime/_data.py +8 -0
- rats_e2e/runtime/py.typed +0 -0
- rats_resources/runtime/__init__.py +0 -0
- rats_resources/runtime/example-context.yaml +9 -0
- {rats_apps-0.10.3.dev20250520164722.dist-info → rats_apps-0.11.0.dist-info}/WHEEL +0 -0
rats/app_context/__init__.py
CHANGED
@@ -1,4 +1,55 @@
|
|
1
|
-
"""
|
1
|
+
"""
|
2
|
+
Allows marshaling of simple value objects across processes and machines.
|
3
|
+
|
4
|
+
The [rats.app_context.Collection][] and [rats.app_context.Context][] classes are the building
|
5
|
+
blocks for creating a collection of services that can be serialized to json and shared across
|
6
|
+
applications. In order to be easily serializable, all service values managed by this module must
|
7
|
+
be instances of [dataclasses.dataclass][], must be immutable, and must be made up of simple value
|
8
|
+
types.
|
9
|
+
|
10
|
+
!!! example
|
11
|
+
If we have an example data structure, `MySimpleData`, we can store instances of it into a
|
12
|
+
[rats.app_context.Context][] object by assigning them a service id. Every service id in a
|
13
|
+
context maps to zero or more instances of our data structure. Zero or more context objects can
|
14
|
+
be used to create [rats.app_context.Collection][] instances, which can be serialized with the
|
15
|
+
[rats.app_context.dumps][] and [rats.app_context.loads][] functions.
|
16
|
+
|
17
|
+
```python
|
18
|
+
from dataclasses import dataclass
|
19
|
+
from rats import apps, app_context
|
20
|
+
|
21
|
+
|
22
|
+
@dataclass(frozen=True)
|
23
|
+
class MySimpleData:
|
24
|
+
blob_path: tuple[str, str, str]
|
25
|
+
offset: int
|
26
|
+
|
27
|
+
|
28
|
+
data1 = MySimpleData(
|
29
|
+
blob_path=("accountname", "containername", "/some/blob/path"),
|
30
|
+
offset=3,
|
31
|
+
)
|
32
|
+
|
33
|
+
data2 = MySimpleData(
|
34
|
+
blob_path=("accountname", "containername", "/another/blob/path"),
|
35
|
+
offset=3,
|
36
|
+
)
|
37
|
+
|
38
|
+
service_id = apps.ServiceId[MySimpleData]("example-service")
|
39
|
+
|
40
|
+
ctx = app_context.Context[MySimpleData].make(service_id, data1, data2)
|
41
|
+
collection = app_context.Collection.make(ctx)
|
42
|
+
|
43
|
+
# the encoded json string can be moved between machines through files, env variables, etc.
|
44
|
+
json_encoded = app_context.dumps(collection)
|
45
|
+
# on the remote process, we can rebuild the original collection object
|
46
|
+
original_collection = app_context.loads(json_encoded)
|
47
|
+
# we can retrieve the json-encoded contexts
|
48
|
+
values = original_collection.values(service_id)
|
49
|
+
# and we can decode the values back into the `MySimpleData` instances
|
50
|
+
simple_data_instances = original_collection.decoded_values(MySimpleData, service_id)
|
51
|
+
```
|
52
|
+
"""
|
2
53
|
|
3
54
|
from ._collection import (
|
4
55
|
EMPTY_COLLECTION,
|
@@ -7,12 +58,13 @@ from ._collection import (
|
|
7
58
|
loads,
|
8
59
|
)
|
9
60
|
from ._container import GroupContainer, ServiceContainer
|
10
|
-
from ._context import Context, T_ContextType
|
61
|
+
from ._context import Context, ContextValue, T_ContextType
|
11
62
|
|
12
63
|
__all__ = [
|
13
64
|
"EMPTY_COLLECTION",
|
14
65
|
"Collection",
|
15
66
|
"Context",
|
67
|
+
"ContextValue",
|
16
68
|
"GroupContainer",
|
17
69
|
"ServiceContainer",
|
18
70
|
"T_ContextType",
|
rats/app_context/_collection.py
CHANGED
@@ -25,24 +25,37 @@ class Collection(Generic[T_ContextType]):
|
|
25
25
|
"""
|
26
26
|
|
27
27
|
items: tuple[Context[T_ContextType], ...]
|
28
|
+
"""Access to the raw [rats.app_context.Context][] instances in the collection."""
|
28
29
|
|
29
30
|
@staticmethod
|
30
31
|
def merge(*collections: Collection[T_ContextType]) -> Collection[T_ContextType]:
|
32
|
+
"""
|
33
|
+
Merge multiple collections into a new, combined instance.
|
34
|
+
|
35
|
+
Args:
|
36
|
+
*collections: zero or more collections to merge into one.
|
37
|
+
"""
|
31
38
|
return Collection[T_ContextType].make(
|
32
39
|
*[ctx for collection in collections for ctx in collection.items]
|
33
40
|
)
|
34
41
|
|
35
42
|
@staticmethod
|
36
43
|
def empty() -> Collection[T_ContextType]:
|
37
|
-
"""
|
44
|
+
"""A shortcut to retrieving an empty collection."""
|
38
45
|
return Collection(items=())
|
39
46
|
|
40
47
|
@staticmethod
|
41
48
|
def make(*items: Context[T_ContextType]) -> Collection[T_ContextType]:
|
42
|
-
|
49
|
+
"""
|
50
|
+
Convenience function to create a collection from a list of contexts.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
*items: zero or more context objects to turn into a collection.
|
54
|
+
"""
|
43
55
|
return Collection(items=items)
|
44
56
|
|
45
57
|
def service_ids(self) -> set[apps.ServiceId[T_ContextType]]:
|
58
|
+
"""Get the list of service items found in the collection, across all context instances."""
|
46
59
|
return {item.service_id for item in self.items}
|
47
60
|
|
48
61
|
def decoded_values(
|
@@ -50,9 +63,31 @@ class Collection(Generic[T_ContextType]):
|
|
50
63
|
cls: type[T_ContextType],
|
51
64
|
service_id: apps.ServiceId[T_ContextType],
|
52
65
|
) -> tuple[T_ContextType, ...]:
|
66
|
+
"""
|
67
|
+
Given a reference to a dataclass type, retrieves and builds instances matching a service id.
|
68
|
+
|
69
|
+
!!! info
|
70
|
+
The dataclass used in the two communicating processes does not need to be the same, but
|
71
|
+
we expect the constructor arguments to be compatible.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
cls: the type all selected context objects will be cast into before returning.
|
75
|
+
service_id: the selector for the expected context objects.
|
76
|
+
"""
|
53
77
|
return tuple(dataclass_wizard.fromlist(cls, list(self.values(service_id))))
|
54
78
|
|
55
79
|
def values(self, service_id: apps.ServiceId[T_ContextType]) -> tuple[ContextValue, ...]:
|
80
|
+
"""
|
81
|
+
Retrieves the raw data structures matching a service id.
|
82
|
+
|
83
|
+
The values returned here have been encoded into simple dictionaries and are ready to be
|
84
|
+
serialized and transferred across machines. See the
|
85
|
+
[rats.app_context.Collection.decoded_values][] method to retrieve context values that have
|
86
|
+
been turned back into the desired dataclass objects.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
service_id: the selector for the expected context objects.
|
90
|
+
"""
|
56
91
|
results: list[ContextValue] = []
|
57
92
|
for item in self.with_id(service_id).items:
|
58
93
|
for value in item.values:
|
@@ -61,19 +96,32 @@ class Collection(Generic[T_ContextType]):
|
|
61
96
|
return tuple(results)
|
62
97
|
|
63
98
|
def add(self, *items: Context[T_ContextType]) -> Collection[T_ContextType]:
|
64
|
-
"""
|
99
|
+
"""
|
100
|
+
Creates a new Collection with the provided items added to the current ones.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
*items: the context items to be added in the new collection.
|
104
|
+
"""
|
65
105
|
return Collection[T_ContextType].make(
|
66
106
|
*self.items,
|
67
107
|
*items,
|
68
108
|
)
|
69
109
|
|
70
110
|
def with_id(self, *service_ids: apps.ServiceId[T_ContextType]) -> Collection[T_ContextType]:
|
71
|
-
"""
|
111
|
+
"""
|
112
|
+
Filter out context items not matching the provided service ids and return a new Collection.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
*service_ids: the selector for the returned context collection.
|
116
|
+
"""
|
72
117
|
return Collection[T_ContextType](
|
73
118
|
items=tuple(item for item in self.items if item.service_id in service_ids),
|
74
119
|
)
|
75
120
|
|
76
121
|
|
122
|
+
# disable key transformation for our collection class
|
123
|
+
dataclass_wizard.DumpMeta(key_transform="NONE").bind_to(Collection) # type: ignore[reportUnknownMemberType]
|
124
|
+
dataclass_wizard.LoadMeta(key_transform="NONE").bind_to(Collection) # type: ignore[reportUnknownMemberType]
|
77
125
|
EMPTY_COLLECTION = Collection[Any].empty()
|
78
126
|
"""Empty collection constant usable as default method values."""
|
79
127
|
|
@@ -92,10 +140,10 @@ def loads(context: str) -> Collection[Any]:
|
|
92
140
|
items=tuple(
|
93
141
|
[
|
94
142
|
Context(
|
95
|
-
service_id=apps.ServiceId(*x["
|
143
|
+
service_id=apps.ServiceId(*x["service_id"]),
|
96
144
|
values=tuple(x["values"]),
|
97
145
|
)
|
98
|
-
for x in data
|
146
|
+
for x in data.get("items", [])
|
99
147
|
]
|
100
148
|
),
|
101
149
|
)
|
@@ -107,8 +155,5 @@ def dumps(collection: Collection[Any]) -> str:
|
|
107
155
|
|
108
156
|
Args:
|
109
157
|
collection: collection of serializable services wanting to be shared between machines.
|
110
|
-
|
111
|
-
Returns: a string that can easily be shared between machines.
|
112
|
-
|
113
158
|
"""
|
114
|
-
return json.dumps(dataclass_wizard.asdict(collection)) # type: ignore
|
159
|
+
return json.dumps(dataclass_wizard.asdict(collection), indent=4) # type: ignore
|
rats/app_context/_context.py
CHANGED
@@ -10,7 +10,17 @@ from rats import apps
|
|
10
10
|
|
11
11
|
logger = logging.getLogger(__name__)
|
12
12
|
T_ContextType = TypeVar("T_ContextType") # TODO: figure out how to bind this to dataclasses :(
|
13
|
+
"""
|
14
|
+
Generic type for context value objects.
|
15
|
+
|
16
|
+
!!! warning
|
17
|
+
Python lacks the ability to bind generic values to require them to be a
|
18
|
+
[dataclasses.dataclass][] instance, but all [rats.app_context.T_ContextType][] instances must
|
19
|
+
be dataclasses in order for them to serialize and deserialize. This is mostly a leaky private
|
20
|
+
detail, and we hope to remove this requirement in the future.
|
21
|
+
"""
|
13
22
|
ContextValue = dict[str, Any]
|
23
|
+
"""Values found in [rats.app_context.Context][] in their simple value type representations."""
|
14
24
|
|
15
25
|
|
16
26
|
@final
|
@@ -25,7 +35,11 @@ class Context(dataclass_wizard.JSONSerializable, Generic[T_ContextType]):
|
|
25
35
|
"""
|
26
36
|
|
27
37
|
service_id: apps.ServiceId[T_ContextType]
|
38
|
+
"""
|
39
|
+
The [rats.apps.ServiceId][] the remote app will be able to use to retrieve the stored values.
|
40
|
+
"""
|
28
41
|
values: tuple[ContextValue, ...]
|
42
|
+
"""The encoded value object, ready to be turned into json for marshaling."""
|
29
43
|
|
30
44
|
@staticmethod
|
31
45
|
def make(
|
@@ -39,18 +53,28 @@ class Context(dataclass_wizard.JSONSerializable, Generic[T_ContextType]):
|
|
39
53
|
from dataclass import dataclass
|
40
54
|
|
41
55
|
|
56
|
+
@dataclass(frozen=True)
|
42
57
|
class MySimpleData:
|
43
58
|
blob_path: Tuple[str, str, str]
|
44
59
|
offset: int
|
45
60
|
|
46
61
|
|
62
|
+
service_id = apps.ServiceId[MySimpleData]("example-service")
|
47
63
|
ctx = app_context.Context[MySimpleData].make(
|
64
|
+
service_id,
|
48
65
|
MySimpleData(
|
49
66
|
blob_path=("accountname", "containername", "/some/blob/path"),
|
50
|
-
)
|
67
|
+
),
|
51
68
|
)
|
52
69
|
```
|
70
|
+
|
71
|
+
Args:
|
72
|
+
service_id: The service id the context values will be retrievable as.
|
73
|
+
*contexts: Zero or more [rats.app_context.T_ContextType][] instances.
|
53
74
|
"""
|
75
|
+
if len(contexts) > 0:
|
76
|
+
cls = contexts[0].__class__
|
77
|
+
dataclass_wizard.DumpMeta(key_transform="NONE").bind_to(cls) # type: ignore[reportUnknownMemberType]
|
54
78
|
return Context(
|
55
79
|
service_id,
|
56
80
|
tuple([dataclass_wizard.asdict(ctx) for ctx in contexts]), # type: ignore
|
rats/apps/_app_containers.py
CHANGED
@@ -53,11 +53,7 @@ This is the companion type to [rats.apps.ContainerPlugin][].
|
|
53
53
|
|
54
54
|
ContainerPlugin = _ContainerPluginType | Callable[[Container], Container]
|
55
55
|
"""
|
56
|
-
|
57
|
-
|
58
|
-
Containers that implement this type—for example, by using the [rats.apps.PluginMixin][] mixin—can
|
59
|
-
be used easily in functions that need to defer the construction of an application container. See
|
60
|
-
[rats.apps.AppBundle][] for additional examples.
|
56
|
+
Factory function protocol that returns an [rats.apps.Container][] instance.
|
61
57
|
"""
|
62
58
|
|
63
59
|
|
rats/cli/_click_app.py
CHANGED
@@ -40,7 +40,7 @@ class ClickApp(apps.Executable):
|
|
40
40
|
Attach any commands provided by the [rats.cli.Container][] and then execute the group.
|
41
41
|
|
42
42
|
!!! note
|
43
|
-
Currently, this method runs [click.
|
43
|
+
Currently, this method runs [click.Command.main][] without arguments, causing Click
|
44
44
|
to pull the command arguments from [sys.argv][]. If you are needing to change this,
|
45
45
|
skip using this class and implement the custom logic yourself.
|
46
46
|
"""
|
rats/runtime/__init__.py
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
"""
|
2
|
+
Run rats applications, providing external context.
|
3
|
+
|
4
|
+
The provided `rats-runtime` cli allows the registration and execution of [rats.apps.AppContainer][]
|
5
|
+
applications. Register to the python plugin group called `rats.runtime.apps`, and list available
|
6
|
+
plugins with `rats-runtime list`.
|
7
|
+
|
8
|
+
```toml title="pyproject.toml"
|
9
|
+
[project.entry-points."rats.runtime.apps"]
|
10
|
+
"rats_e2e.runtime" = "rats_e2e.runtime:Application"
|
11
|
+
```
|
12
|
+
|
13
|
+
We register an example application you can run with `rats-runtime run rats_e2e.runtime`. The name
|
14
|
+
of the registered entry point in your `pyproject.toml` will map to the name provided to
|
15
|
+
`rats-runtime run`.
|
16
|
+
|
17
|
+
```bash
|
18
|
+
$ rats-runtime run rats_e2e.runtime
|
19
|
+
hello, world!
|
20
|
+
looking for any registered context: rats_e2e.runtime._app:AppServices[example-data]
|
21
|
+
```
|
22
|
+
|
23
|
+
Our example application looks for any context services, which can be provided to the `run` command.
|
24
|
+
|
25
|
+
=== "/code"
|
26
|
+
```bash
|
27
|
+
$ rats-runtime run \
|
28
|
+
rats_e2e.runtime \
|
29
|
+
--context-file src/rats_resources/runtime/example-context.yaml
|
30
|
+
hello, world!
|
31
|
+
looking for any registered context: rats_e2e.runtime._app:AppServices[example-data]
|
32
|
+
found example data element: ExampleData(id='111', thing_a='111', thing_b='111')
|
33
|
+
found example data element: ExampleData(id='222', thing_a='222', thing_b='222')
|
34
|
+
```
|
35
|
+
=== "src/rats_resources/runtime/example-context.yaml"
|
36
|
+
```yaml
|
37
|
+
--8<-- "rats_resources/runtime/example-context.yaml"
|
38
|
+
```
|
39
|
+
=== "src/rats_e2e/runtime/_app.py"
|
40
|
+
```python
|
41
|
+
--8<-- "rats_e2e/runtime/_app.py"
|
42
|
+
```
|
43
|
+
=== "src/rats_e2e/runtime/_data.py"
|
44
|
+
```python
|
45
|
+
--8<-- "rats_e2e/runtime/_data.py"
|
46
|
+
```
|
47
|
+
|
48
|
+
!!! info
|
49
|
+
Most users won't typically use `rats-runtime` directly, but this interface is used by other
|
50
|
+
abstractions to execute applications remotely, like the [rats.aml][] module, adding the ability
|
51
|
+
to execute applications within an azure ml job.
|
52
|
+
"""
|
53
|
+
|
54
|
+
from ._app import Application, AppServices, main
|
55
|
+
from ._request import DuplicateRequestError, Request, RequestNotFoundError
|
56
|
+
|
57
|
+
__all__ = [
|
58
|
+
"AppServices",
|
59
|
+
"Application",
|
60
|
+
"DuplicateRequestError",
|
61
|
+
"Request",
|
62
|
+
"RequestNotFoundError",
|
63
|
+
"main",
|
64
|
+
]
|
rats/runtime/__main__.py
ADDED
rats/runtime/_app.py
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import json
|
4
|
+
import logging
|
5
|
+
import sys
|
6
|
+
from collections.abc import Iterator
|
7
|
+
from functools import cache
|
8
|
+
from importlib import metadata
|
9
|
+
from importlib.metadata import EntryPoint
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import Any, final
|
12
|
+
|
13
|
+
import click
|
14
|
+
import yaml
|
15
|
+
|
16
|
+
from rats import app_context, apps, cli, logs
|
17
|
+
|
18
|
+
from ._request import DuplicateRequestError, Request, RequestNotFoundError
|
19
|
+
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
|
23
|
+
@apps.autoscope
|
24
|
+
class AppServices:
|
25
|
+
"""Services used by the `rats.runtime.Application` cli command."""
|
26
|
+
|
27
|
+
CONTEXT = apps.ServiceId[app_context.Collection[Any]]("app-ctx-collection.config")
|
28
|
+
"""
|
29
|
+
[rats.app_context.Collection][] available in the application run using `rats-runtime run`.
|
30
|
+
|
31
|
+
```python
|
32
|
+
from rats import apps, runtime
|
33
|
+
|
34
|
+
|
35
|
+
class Application(apps.AppContainer, apps.PluginMixin):
|
36
|
+
def execute(self) -> None:
|
37
|
+
context = self._app.get(runtime.AppConfigs.CONTEXT)
|
38
|
+
print("loaded context:")
|
39
|
+
for item in context_collection.items:
|
40
|
+
print(f"{item.service_id} -> {item.values}")
|
41
|
+
```
|
42
|
+
"""
|
43
|
+
REQUEST = apps.ServiceId[Request]("runtime-details-client")
|
44
|
+
"""
|
45
|
+
The request information, available after the call to [rats.runtime.Application.execute][].
|
46
|
+
"""
|
47
|
+
|
48
|
+
|
49
|
+
@final
|
50
|
+
class Application(apps.AppContainer, cli.Container, apps.PluginMixin):
|
51
|
+
"""
|
52
|
+
The `rats-runtime` cli application.
|
53
|
+
|
54
|
+
```bash
|
55
|
+
$ rats-runtime --help
|
56
|
+
Usage: rats-runtime [OPTIONS] COMMAND [ARGS]...
|
57
|
+
|
58
|
+
Options:
|
59
|
+
--help Show this message and exit.
|
60
|
+
|
61
|
+
Commands:
|
62
|
+
list List all the exes and groups that are configured to run with `rats-
|
63
|
+
runtime run`.
|
64
|
+
run Run one or more apps, adding an optional additional context.
|
65
|
+
```
|
66
|
+
"""
|
67
|
+
|
68
|
+
_request: Request | None = None
|
69
|
+
|
70
|
+
def execute(self) -> None:
|
71
|
+
"""Runs the `rats-runtime` cli."""
|
72
|
+
argv = self._app.get(cli.PluginConfigs.ARGV)
|
73
|
+
cli.create_group(click.Group("rats-runtime"), self).main(
|
74
|
+
args=argv[1:],
|
75
|
+
prog_name=Path(argv[0]).name,
|
76
|
+
auto_envvar_prefix="RATS_RUNTIME",
|
77
|
+
# don't end the process
|
78
|
+
standalone_mode=False,
|
79
|
+
)
|
80
|
+
|
81
|
+
@cli.command()
|
82
|
+
def _list(self) -> None:
|
83
|
+
"""List all the exes and groups that are configured to run with `rats-runtime run`."""
|
84
|
+
for entry in self._get_app_entrypoints():
|
85
|
+
click.echo(entry.name)
|
86
|
+
|
87
|
+
@cli.command()
|
88
|
+
@click.argument("app-ids", nargs=-1)
|
89
|
+
@click.option("--context", default="{}")
|
90
|
+
@click.option("--context-file")
|
91
|
+
def _run(self, app_ids: tuple[str, ...], context: str, context_file: str | None) -> None:
|
92
|
+
"""Run one or more apps, adding an optional additional context."""
|
93
|
+
if self._request is not None:
|
94
|
+
print(self._request)
|
95
|
+
raise DuplicateRequestError()
|
96
|
+
|
97
|
+
ctx_collection = app_context.loads(context)
|
98
|
+
if context_file:
|
99
|
+
p = Path(context_file)
|
100
|
+
if not p.is_file():
|
101
|
+
raise RuntimeError(f"context file not found: {context_file}")
|
102
|
+
|
103
|
+
data = yaml.safe_load(p.read_text())
|
104
|
+
ctx_collection = ctx_collection.merge(app_context.loads(json.dumps(data)))
|
105
|
+
|
106
|
+
self._request = Request(
|
107
|
+
app_ids=app_ids,
|
108
|
+
context=ctx_collection,
|
109
|
+
)
|
110
|
+
|
111
|
+
def _load_app(name: str, ctx: app_context.Collection[Any]) -> apps.AppContainer:
|
112
|
+
return apps.AppBundle(
|
113
|
+
app_plugin=self._find_app(name),
|
114
|
+
context=apps.StaticContainer(
|
115
|
+
apps.StaticProvider(
|
116
|
+
namespace=apps.ProviderNamespaces.SERVICES,
|
117
|
+
service_id=AppServices.CONTEXT,
|
118
|
+
call=lambda: ctx,
|
119
|
+
),
|
120
|
+
),
|
121
|
+
)
|
122
|
+
|
123
|
+
if len(app_ids) == 0:
|
124
|
+
logger.warning("No applications were passed to the command")
|
125
|
+
|
126
|
+
for app_id in app_ids:
|
127
|
+
app = _load_app(app_id, ctx_collection)
|
128
|
+
app.execute()
|
129
|
+
|
130
|
+
def _find_app(self, name: str) -> type[apps.AppContainer]:
|
131
|
+
for e in self._get_app_entrypoints():
|
132
|
+
if e.name == name:
|
133
|
+
return e.load()
|
134
|
+
|
135
|
+
raise RuntimeError(f"rats app-id not found: {name}")
|
136
|
+
|
137
|
+
@cache # noqa: B019
|
138
|
+
def _get_app_entrypoints(self) -> Iterator[EntryPoint]:
|
139
|
+
yield from metadata.entry_points(group="rats.runtime.apps")
|
140
|
+
|
141
|
+
@apps.service(AppServices.REQUEST)
|
142
|
+
def _request_provider(self) -> Request:
|
143
|
+
if self._request is None:
|
144
|
+
raise RequestNotFoundError()
|
145
|
+
|
146
|
+
return self._request
|
147
|
+
|
148
|
+
@apps.container()
|
149
|
+
def _plugins(self) -> apps.Container:
|
150
|
+
return cli.PluginContainer(self._app)
|
151
|
+
|
152
|
+
|
153
|
+
def main() -> None:
|
154
|
+
"""Main entry-point to the `rats-runtime` cli command."""
|
155
|
+
try:
|
156
|
+
apps.run_plugin(logs.ConfigureApplication)
|
157
|
+
apps.run_plugin(Application)
|
158
|
+
except click.exceptions.ClickException as e:
|
159
|
+
e.show()
|
160
|
+
sys.exit(e.exit_code)
|
rats/runtime/_request.py
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
from typing import Any, NamedTuple
|
2
|
+
|
3
|
+
from rats import app_context
|
4
|
+
|
5
|
+
|
6
|
+
class Request(NamedTuple):
|
7
|
+
app_ids: tuple[str, ...]
|
8
|
+
"""The app ids being run by [rats.runtime.Application][]."""
|
9
|
+
|
10
|
+
context: app_context.Collection[Any]
|
11
|
+
"""The [rats.app_context.Collection][] that was submitted and made available to executed apps."""
|
12
|
+
|
13
|
+
|
14
|
+
class RequestNotFoundError(RuntimeError):
|
15
|
+
"""Thrown if [rats.runtime.AppServices.REQUEST][] is accessed without the application being run."""
|
16
|
+
|
17
|
+
def __init__(self) -> None:
|
18
|
+
super().__init__("no running runtime request found.")
|
19
|
+
|
20
|
+
|
21
|
+
class DuplicateRequestError(RuntimeError):
|
22
|
+
def __init__(self) -> None:
|
23
|
+
super().__init__("request already executed.")
|
rats/runtime/py.typed
ADDED
File without changes
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: rats-apps
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.11.0
|
4
4
|
Summary: research analysis tools for building applications
|
5
5
|
License: MIT
|
6
6
|
Keywords: pipelines,machine learning,research
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Requires-Dist: click
|
17
17
|
Requires-Dist: colorlog
|
18
18
|
Requires-Dist: dataclass-wizard
|
19
|
+
Requires-Dist: pyyaml
|
19
20
|
Requires-Dist: typing-extensions
|
20
21
|
Project-URL: Documentation, https://microsoft.github.io/rats
|
21
22
|
Project-URL: Repository, https://github.com/microsoft/rats
|
@@ -2,14 +2,14 @@ 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/app_context/__init__.py,sha256=
|
6
|
-
rats/app_context/_collection.py,sha256=
|
5
|
+
rats/app_context/__init__.py,sha256=8d8LCe2fOi4_W1bPnJlk5t_HETHW8sV4c35z5iwAuxo,2529
|
6
|
+
rats/app_context/_collection.py,sha256=f9sqdAokVsyt7zFz0Va7_48wK18wT44H7berPrx2RpM,5571
|
7
7
|
rats/app_context/_container.py,sha256=YkNG25jngfN_064jv8fZmKW1FSuL6RRJAp9F_hm2NZg,2954
|
8
|
-
rats/app_context/_context.py,sha256=
|
8
|
+
rats/app_context/_context.py,sha256=S7D8NLefZJI4MDEpYKdenfNsemus4bUlQCG-gd3U3g8,2777
|
9
9
|
rats/app_context/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
rats/apps/__init__.py,sha256=6B5qQvzx1xlRrNRY4QJi6GEywuQ1MoW454kC4nO2Yqg,2084
|
11
11
|
rats/apps/_annotations.py,sha256=6M_M7K8haNVda0Tx02EpFf3s9EjnWYacNMjTIkNEdRU,4617
|
12
|
-
rats/apps/_app_containers.py,sha256=
|
12
|
+
rats/apps/_app_containers.py,sha256=vVhL27dogcKhGO0Blh6ddD0JYJOGtq1dqx4fS5KyTGk,6773
|
13
13
|
rats/apps/_composite_container.py,sha256=FdpmH_xIly6LxNZrA_nZCznukptjVLXttXTMtf_tnv8,695
|
14
14
|
rats/apps/_container.py,sha256=sYISCG-qVzl-bsBwX_CAWEP9gtXCnG-Jls3l8IKb4DQ,7208
|
15
15
|
rats/apps/_executables.py,sha256=hXExNmAnuPU1KJXihNw1jEDAQpMlQ9E9_aPV8tpGbOY,1347
|
@@ -24,7 +24,7 @@ rats/apps/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
24
|
rats/cli/__init__.py,sha256=zMywV-4vqbtlobIoMItwbxxdvYGC20wiGU2TKm3II2A,714
|
25
25
|
rats/cli/__main__.py,sha256=WeldAKjA3Kmz9ZRnZVd3G8Ud66Y_gSRDLIPNE1JyhV0,1418
|
26
26
|
rats/cli/_annotations.py,sha256=-B2Y1bYjPbeTNTBMZzMdAU92qu9AyutKHN_NdFy-xhA,2964
|
27
|
-
rats/cli/_click_app.py,sha256=
|
27
|
+
rats/cli/_click_app.py,sha256=Jvs6OqNC4Yoe6kbc2tCzjzUieNFa1n_zwXPsdo971Wc,1450
|
28
28
|
rats/cli/_command.py,sha256=kyU3UqqF9aiTTaFvlQFBKDLXvArQS1QgjoQqlMbKzok,597
|
29
29
|
rats/cli/_container.py,sha256=FIQBqi9PTNpE2P52qkfGET51BczdD-JvcWXLTjCNPDI,3512
|
30
30
|
rats/cli/_functions.py,sha256=BNmgWVquQUEqJAYsed_l8vLnlLP7u3XC1TDyEFI1AiU,1552
|
@@ -33,6 +33,11 @@ rats/cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
rats/logs/__init__.py,sha256=dSO-V2eu2W7w4w7oRau-UOfXXIkyZ2bHB6-kaasbeFM,970
|
34
34
|
rats/logs/_app.py,sha256=SoTxk6g84tygOP_346NwXPcG1o6KZLd7lIgayq5vMdc,3239
|
35
35
|
rats/logs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
|
+
rats/runtime/__init__.py,sha256=f9aEnynHuePP9OP5XCtB-6nCBvNWWlQQLTzuxFEueoQ,2150
|
37
|
+
rats/runtime/__main__.py,sha256=y01yOymsL075poX95pc02sJR1HD0pDNFRZdpOdi0R6Y,79
|
38
|
+
rats/runtime/_app.py,sha256=zR4o5kd3Xg6oJB-S92iG5ffoUafG1xDYQfcTBnshUuA,5001
|
39
|
+
rats/runtime/_request.py,sha256=E2Oos_mkruPh4h-chys64AMFR_YMdrAx95avXsPeaog,706
|
40
|
+
rats/runtime/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
41
|
rats_e2e/apps/__init__.py,sha256=c2QG818ACyS9MBXs_Evg5yYT6k-KPMQfIqF7Z77Cau0,142
|
37
42
|
rats_e2e/apps/__main__.py,sha256=ETECGDD8glcbmx0P-WWmjQQX6gtF-5L7_DxZjNmx3X4,350
|
38
43
|
rats_e2e/apps/_example_cli.py,sha256=XzWOdnQrSp-F_FZEdm2CCm8dUJZWOSm2KFPIK0jzoi8,2840
|
@@ -42,6 +47,14 @@ rats_e2e/apps/inputs/_app.py,sha256=FiaLgOZc-d1ryKSwKnL5XBNGcOP1bHbxxeMJqoU_RJg,
|
|
42
47
|
rats_e2e/apps/minimal/__init__.py,sha256=bUR6Oexx6Jsouxor0cL9emXoVha4cm3WqyhU1pgchsI,521
|
43
48
|
rats_e2e/apps/minimal/__main__.py,sha256=Mf-a2iQKTTgh9hMd6AeuzmU9araMIyf1AtdWkh_L07E,117
|
44
49
|
rats_e2e/apps/minimal/_app.py,sha256=CQ09LVTNRarz7Pb1wiSuNHrZ_2KGcgH8nUqy4BjxMUY,849
|
45
|
-
|
46
|
-
|
47
|
-
|
50
|
+
rats_e2e/apps/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
51
|
+
rats_e2e/runtime/__init__.py,sha256=0oWKhywQaXzjs2bHI3WHYBF8vIbK-NAWBJsxTal7sC0,163
|
52
|
+
rats_e2e/runtime/_app.py,sha256=hz0BB15CIkjLsz8YPfZiIm9jKiOq8JFaHR7_cJGBkMA,790
|
53
|
+
rats_e2e/runtime/_data.py,sha256=3d1F_JO2gEOPUjBp_KYMP3TefyneiG_ktlJjdIIYUy8,125
|
54
|
+
rats_e2e/runtime/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
|
+
rats_resources/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
|
+
rats_resources/runtime/example-context.yaml,sha256=eiLsNFquFfkIpUhxUCQLzLigH21QF2F00fzA_e_aOKk,215
|
57
|
+
rats_apps-0.11.0.dist-info/METADATA,sha256=KkivE4YFIzrkqHisGVwMXOcUPbTraSjtAG9xPmJevi8,871
|
58
|
+
rats_apps-0.11.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
59
|
+
rats_apps-0.11.0.dist-info/entry_points.txt,sha256=Gf6bPwxIVjWd3Xx71upZo7eDJA5cujniLew6fxJMgA4,117
|
60
|
+
rats_apps-0.11.0.dist-info/RECORD,,
|
rats_e2e/apps/py.typed
ADDED
File without changes
|
rats_e2e/runtime/_app.py
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
from collections.abc import Iterator
|
2
|
+
|
3
|
+
from rats import apps, runtime
|
4
|
+
|
5
|
+
from ._data import ExampleData
|
6
|
+
|
7
|
+
|
8
|
+
@apps.autoscope
|
9
|
+
class AppServices:
|
10
|
+
EXAMPLE_DATA = apps.ServiceId[ExampleData]("example-data")
|
11
|
+
|
12
|
+
|
13
|
+
class Application(apps.AppContainer, apps.PluginMixin):
|
14
|
+
def execute(self) -> None:
|
15
|
+
print("hello, world!")
|
16
|
+
print(f"looking for any registered context: {AppServices.EXAMPLE_DATA.name}")
|
17
|
+
for data in self._app.get_group(AppServices.EXAMPLE_DATA):
|
18
|
+
print(f"found example data element: {data}")
|
19
|
+
|
20
|
+
@apps.fallback_group(AppServices.EXAMPLE_DATA)
|
21
|
+
def _data_from_context(self) -> Iterator[ExampleData]:
|
22
|
+
collection = self._app.get(runtime.AppServices.CONTEXT)
|
23
|
+
yield from collection.decoded_values(ExampleData, AppServices.EXAMPLE_DATA)
|
File without changes
|
File without changes
|
File without changes
|