rats-apps 0.4.0.dev20241217004929__py3-none-any.whl → 0.5.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.
@@ -15,11 +15,11 @@ from ._functions import (
15
15
  )
16
16
 
17
17
  __all__ = [
18
- "annotation",
19
- "DecoratorType",
20
18
  "AnnotationsContainer",
21
- "get_annotations",
22
- "get_class_annotations",
19
+ "DecoratorType",
23
20
  "GroupAnnotations",
24
21
  "T_GroupType",
22
+ "annotation",
23
+ "get_annotations",
24
+ "get_class_annotations",
25
25
  ]
rats/apps/__init__.py CHANGED
@@ -15,6 +15,14 @@ from ._annotations import (
15
15
  group,
16
16
  service,
17
17
  )
18
+ from ._app_containers import (
19
+ AppBundle,
20
+ AppContainer,
21
+ AppPlugin,
22
+ CompositePlugin,
23
+ ContainerPlugin,
24
+ PluginMixin,
25
+ )
18
26
  from ._composite_container import CompositeContainer
19
27
  from ._container import (
20
28
  Container,
@@ -22,51 +30,54 @@ from ._container import (
22
30
  GroupProvider,
23
31
  Provider,
24
32
  ServiceNotFoundError,
25
- ServiceProvider,
26
33
  container,
27
34
  )
28
35
  from ._executables import App, Executable
29
36
  from ._ids import ServiceId, T_ExecutableType, T_ServiceType
37
+ from ._mains import run, run_plugin
30
38
  from ._namespaces import ProviderNamespaces
31
- from ._plugin_container import PluginContainers
32
- from ._plugins import PluginRunner
33
- from ._runtimes import NullRuntime, Runtime
39
+ from ._plugin_container import PythonEntryPointContainer
40
+ from ._runtimes import NullRuntime, Runtime, StandardRuntime
34
41
  from ._scoping import autoscope
35
- from ._simple_apps import AppServices, SimpleApplication, StandardRuntime
36
- from ._static_container import StaticContainer, StaticProvider
42
+ from ._static_container import StaticContainer, StaticProvider, static_group, static_service
37
43
 
38
44
  __all__ = [
39
45
  "App",
46
+ "AppBundle",
47
+ "AppContainer",
48
+ "AppPlugin",
40
49
  "CompositeContainer",
50
+ "CompositePlugin",
41
51
  "Container",
42
- "StaticContainer",
43
- "StaticProvider",
52
+ "ContainerPlugin",
44
53
  "DuplicateServiceError",
45
54
  "Executable",
46
- "PluginContainers",
47
- "ProviderNamespaces",
48
- "ServiceProvider",
49
- "Provider",
50
55
  "GroupProvider",
56
+ "NullRuntime",
57
+ "PluginMixin",
58
+ "Provider",
59
+ "ProviderNamespaces",
60
+ "PythonEntryPointContainer",
61
+ "Runtime",
51
62
  "ServiceId",
52
63
  "ServiceNotFoundError",
64
+ "StandardRuntime",
65
+ "StaticContainer",
66
+ "StaticProvider",
67
+ "T_ExecutableType",
68
+ "T_ServiceType",
69
+ "autoid",
70
+ "autoid_factory_service",
53
71
  "autoid_service",
54
72
  "autoscope",
55
73
  "container",
56
- "PluginRunner",
74
+ "factory_service",
57
75
  "fallback_group",
58
76
  "fallback_service",
59
77
  "group",
60
- "autoid",
78
+ "run",
79
+ "run_plugin",
61
80
  "service",
62
- "T_ExecutableType",
63
- "T_ServiceType",
64
- "Runtime",
65
- "NullRuntime",
66
- "AppServices",
67
- "StandardRuntime",
68
- "StandardRuntime",
69
- "SimpleApplication",
70
- "factory_service",
71
- "autoid_factory_service",
81
+ "static_group",
82
+ "static_service",
72
83
  ]
@@ -0,0 +1,199 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from abc import abstractmethod
5
+ from collections.abc import Callable
6
+ from functools import cache
7
+ from typing import Protocol, final
8
+
9
+ from ._composite_container import CompositeContainer
10
+ from ._container import Container, container
11
+ from ._executables import Executable
12
+
13
+ logger = logging.getLogger(__name__)
14
+ EMPTY_CONTEXT = CompositeContainer()
15
+
16
+
17
+ def _empty_plugin(app: Container) -> Container:
18
+ return CompositeContainer()
19
+
20
+
21
+ EMPTY_PLUGIN = _empty_plugin
22
+
23
+
24
+ class AppContainer(Container, Executable, Protocol):
25
+ """The combination of a [rats.apps.Container][] an [rats.apps.Executable][]."""
26
+
27
+ def execute(self) -> None:
28
+ """The main application entry point."""
29
+ logger.warning(f"empty execute method in application: {self.__class__}")
30
+
31
+
32
+ class _AppPluginType(Protocol):
33
+ @abstractmethod
34
+ def __call__(self, app: Container) -> AppContainer:
35
+ pass
36
+
37
+
38
+ class _ContainerPluginType(Protocol):
39
+ @abstractmethod
40
+ def __call__(self, app: Container) -> Container:
41
+ pass
42
+
43
+
44
+ AppPlugin = _AppPluginType | Callable[[Container], AppContainer]
45
+ """
46
+ Main interface for a function that returns an [rats.apps.AppContainer][] instance.
47
+
48
+ Functions that act as runners or application factories often take this as their input type in
49
+ order to manage the top most [rats.apps.Container][] instance, allowing most containers to use the
50
+ [rats.apps.PluginMixin][] mixin and rely on the top most container being managed automatically.
51
+ This is the companion type to [rats.apps.ContainerPlugin][].
52
+ """
53
+
54
+ ContainerPlugin = _ContainerPluginType | Callable[[Container], Container]
55
+ """
56
+ Main interface for a function that returns an [rats.apps.Container][] instance.
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.
61
+ """
62
+
63
+
64
+ class PluginMixin:
65
+ """
66
+ Mix into your [Container][] classes to add our default constructor.
67
+
68
+ This mixin adds a common constructor to a `Container` in order to quickly create types that
69
+ are compatible with functions asking for `AppPlugin` and `ContainerPlugin` arguments.
70
+
71
+ !!! warning
72
+ Avoid using mixins as an input type to your functions, because we don't want to restrict
73
+ others to containers with a private `_app` property. Instead, use this as a shortcut to
74
+ some commonly used implementation details.
75
+
76
+ Example:
77
+ ```python
78
+ from rats import apps
79
+
80
+ class ExampleApplication(apps.AppContainer, apps.PluginMixin):
81
+
82
+ def execute() -> None:
83
+ print("hello, world!")
84
+
85
+
86
+ if __name__ == "__main__":
87
+ apps.run_plugin(ExampleApplication)
88
+ ```
89
+ """
90
+
91
+ _app: Container
92
+
93
+ def __init__(self, app: Container) -> None:
94
+ self._app = app
95
+
96
+
97
+ class CompositePlugin:
98
+ """
99
+ Similar to [rats.apps.CompositeContainer][] but takes a list of plugin container types.
100
+
101
+ Example:
102
+ ```python
103
+ from rats import apps
104
+ from rats_e2e.apps import inputs
105
+
106
+
107
+ class ExamplePlugin1(apps.Container, apps.PluginMixin):
108
+ pass
109
+
110
+
111
+ class ExamplePlugin2(apps.Container, apps.PluginMixin):
112
+ pass
113
+
114
+
115
+ apps.run(
116
+ apps.AppBundle(
117
+ app_plugin=inputs.Application,
118
+ container_plugin=apps.CompositePlugin(
119
+ ExamplePlugin1,
120
+ ExamplePlugin2,
121
+ ),
122
+ )
123
+ )
124
+ ```
125
+ """
126
+
127
+ _plugins: tuple[ContainerPlugin, ...]
128
+
129
+ def __init__(self, *plugins: ContainerPlugin) -> None:
130
+ self._plugins = plugins
131
+
132
+ def __call__(self, app: Container) -> Container:
133
+ return CompositeContainer(*[plugin(app) for plugin in self._plugins])
134
+
135
+
136
+ @final
137
+ class AppBundle(AppContainer):
138
+ """
139
+ Brings together different types of containers to construct an executable application.
140
+
141
+ Use this class to defer the creation of an [rats.apps.AppContainer][] instance in order to
142
+ combine services with additional [rats.apps.ContainerPlugin][] classes.
143
+ """
144
+
145
+ _app_plugin: AppPlugin
146
+ _container_plugin: ContainerPlugin
147
+ _context: Container
148
+
149
+ def __init__(
150
+ self,
151
+ *,
152
+ app_plugin: AppPlugin,
153
+ container_plugin: ContainerPlugin = EMPTY_PLUGIN,
154
+ context: Container = EMPTY_CONTEXT,
155
+ ):
156
+ """
157
+ Create an instance by providing the [rats.apps.AppPlugin] type and any additional context.
158
+
159
+ Example:
160
+ ```python
161
+ from rats import apps
162
+
163
+
164
+ class ExamplePlugin(apps.Container, apps.PluginMixin):
165
+ @apps.service(apps.ServiceId[str]("some-value"))
166
+ def _some_value() -> str:
167
+ return "hello, world!"
168
+
169
+
170
+ class ExampleApplication(apps.AppContainer, apps.PluginMixin):
171
+ def execute() -> None:
172
+ print(self._app.get(apps.ServiceId[str]("some-value")))
173
+
174
+
175
+ if __name__ == "__main__":
176
+ apps.run(apps.AppBundle(ExampleApplication, ExamplePlugin))
177
+ ```
178
+
179
+ Args:
180
+ app_plugin: the class reference to the application container.
181
+ container_plugin: the class reference to an additional plugin container.
182
+ context: an optional plugin container to make part of the container tree.
183
+ """
184
+ self._app_plugin = app_plugin
185
+ self._container_plugin = container_plugin
186
+ self._context = context
187
+
188
+ def execute(self) -> None:
189
+ """Initializes a new [rats.apps.AppContainer] with the provided nodes before executing it."""
190
+ app, _ = self._get_or_create_containers()
191
+ app.execute()
192
+
193
+ @container()
194
+ def _plugins(self) -> Container:
195
+ return CompositeContainer(*self._get_or_create_containers(), self._context)
196
+
197
+ @cache # noqa: B019
198
+ def _get_or_create_containers(self) -> tuple[AppContainer, Container]:
199
+ return self._app_plugin(self), self._container_plugin(self)
rats/apps/_container.py CHANGED
@@ -19,10 +19,6 @@ class Provider(Protocol[Tco_ServiceType]):
19
19
  """Return the service instance."""
20
20
 
21
21
 
22
- # temporary alias for backwards compatibility
23
- ServiceProvider = Provider
24
-
25
-
26
22
  class GroupProvider(Protocol[Tco_ServiceType]):
27
23
  @abstractmethod
28
24
  def __call__(self) -> Iterator[Tco_ServiceType]:
rats/apps/_executables.py CHANGED
@@ -19,16 +19,30 @@ class Executable(Protocol):
19
19
 
20
20
  class App(Executable):
21
21
  """
22
- Wraps a plain callable objects as an executable.
22
+ Wraps a plain callable objects as a [rats.apps.Executable][].
23
23
 
24
24
  This simple object allows for turning any callable object into an executable that is recognized
25
25
  by the rest of the rats application.
26
+
27
+ Example:
28
+ ```python
29
+ from rats import apps
30
+
31
+ apps.App(lambda: print("hello, world")).execute()
32
+ ```
26
33
  """
27
34
 
28
35
  _callback: Callable[[], None]
29
36
 
30
37
  def __init__(self, callback: Callable[[], None]) -> None:
38
+ """
39
+ Created by providing a reference to a `Callable[[], None]` function.
40
+
41
+ Args:
42
+ callback: called when the application instance is executed.
43
+ """
31
44
  self._callback = callback
32
45
 
33
46
  def execute(self) -> None:
47
+ """Runs the provided callback."""
34
48
  self._callback()
rats/apps/_mains.py ADDED
@@ -0,0 +1,42 @@
1
+ import logging
2
+
3
+ from ._app_containers import AppBundle, AppPlugin
4
+ from ._executables import Executable
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ def run(*apps: Executable) -> None:
10
+ """Shortcut for running a list of apps."""
11
+ for app in apps:
12
+ app.execute()
13
+
14
+
15
+ def run_plugin(*app_plugins: AppPlugin) -> None:
16
+ """
17
+ Shortcut to create and execute instances of `apps.AppPlugin`.
18
+
19
+ This function is most commonly used in a `console_script` function `main()` entry point.
20
+
21
+ Example:
22
+ ```python
23
+ from rats import apps
24
+
25
+
26
+ class Application(apps.AppContainer, apps.AppPlugin):
27
+ def execute(self) -> None:
28
+ print("hello, world")
29
+
30
+
31
+ def main() -> None:
32
+ apps.run_plugin(Application)
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()
37
+ ```
38
+
39
+ Args:
40
+ *app_plugins: one or more class types to be instantiated and executed.
41
+ """
42
+ run(*[AppBundle(app_plugin=plugin) for plugin in app_plugins])
@@ -6,9 +6,9 @@ from ._container import Container
6
6
  from ._ids import ServiceId, T_ServiceType
7
7
 
8
8
 
9
- class PluginContainers(Container):
9
+ class PythonEntryPointContainer(Container):
10
10
  """
11
- A container that loads plugins using importlib.metadata.entry_points.
11
+ A container that loads plugins using [importlib.metadata.entry_points][].
12
12
 
13
13
  When looking for groups, the container loads the specified entry_points and defers the lookups
14
14
  to the plugins. Plugin containers are expected to be Callable[[Container], Container] objects,
rats/apps/_runtimes.py CHANGED
@@ -3,6 +3,7 @@ from abc import abstractmethod
3
3
  from collections.abc import Callable
4
4
  from typing import Protocol, final
5
5
 
6
+ from ._container import Container
6
7
  from ._ids import ServiceId, T_ExecutableType
7
8
 
8
9
  logger = logging.getLogger(__name__)
@@ -23,14 +24,6 @@ class Runtime(Protocol):
23
24
  parallel or in any order that is convenient.
24
25
  """
25
26
 
26
- @abstractmethod
27
- def execute_callable(self, *callables: Callable[[], None]) -> None:
28
- """
29
- Execute provided callables by automatically turning them into apps.Executable objects.
30
-
31
- The used ServiceId is determined by the Runtime implementation.
32
- """
33
-
34
27
 
35
28
  @final
36
29
  class NullRuntime(Runtime):
@@ -48,3 +41,22 @@ class NullRuntime(Runtime):
48
41
 
49
42
  def execute_callable(self, *callables: Callable[[], None]) -> None:
50
43
  raise NotImplementedError(f"NullRuntime cannot execute callables: {callables}")
44
+
45
+
46
+ @final
47
+ class StandardRuntime(Runtime):
48
+ """A simple runtime that executes sequentially and in a single thread."""
49
+
50
+ _app: Container
51
+
52
+ def __init__(self, app: Container) -> None:
53
+ self._app = app
54
+
55
+ def execute(self, *exe_ids: ServiceId[T_ExecutableType]) -> None:
56
+ for exe_id in exe_ids:
57
+ self._app.get(exe_id).execute()
58
+
59
+ def execute_group(self, *exe_group_ids: ServiceId[T_ExecutableType]) -> None:
60
+ for exe_group_id in exe_group_ids:
61
+ for exe in self._app.get_group(exe_group_id):
62
+ exe.execute()
@@ -1,16 +1,17 @@
1
- from collections.abc import Callable, Iterator
1
+ from collections.abc import Iterator
2
+ from dataclasses import dataclass
2
3
  from typing import Any, Generic
3
4
 
4
- from typing_extensions import NamedTuple as ExtNamedTuple
5
-
6
- from ._container import Container
5
+ from ._container import Container, Provider
7
6
  from ._ids import ServiceId, T_ServiceType
7
+ from ._namespaces import ProviderNamespaces
8
8
 
9
9
 
10
- class StaticProvider(ExtNamedTuple, Generic[T_ServiceType]):
10
+ @dataclass(frozen=True)
11
+ class StaticProvider(Generic[T_ServiceType]):
11
12
  namespace: str
12
13
  service_id: ServiceId[T_ServiceType]
13
- provider: Callable[[], T_ServiceType]
14
+ call: Provider[T_ServiceType]
14
15
 
15
16
 
16
17
  class StaticContainer(Container):
@@ -26,4 +27,40 @@ class StaticContainer(Container):
26
27
  ) -> Iterator[T_ServiceType]:
27
28
  for provider in self._providers:
28
29
  if provider.namespace == namespace and provider.service_id == group_id:
29
- yield provider.provider()
30
+ yield provider.call()
31
+
32
+
33
+ def static_service(
34
+ service_id: ServiceId[T_ServiceType],
35
+ provider: Provider[T_ServiceType],
36
+ ) -> StaticProvider[T_ServiceType]:
37
+ """
38
+ Factory function for a `StaticProvider` instance for `ProviderNamespaces.SERVICES`.
39
+
40
+ Args:
41
+ service_id: the identifier for the provided service.
42
+ provider: a callable that returns an instance of T_ServiceType.
43
+
44
+ Returns: StaticProvider instance for the provided service_id.
45
+ """
46
+ return StaticProvider(ProviderNamespaces.SERVICES, service_id, provider)
47
+
48
+
49
+ def static_group(
50
+ group_id: ServiceId[T_ServiceType],
51
+ provider: Provider[T_ServiceType],
52
+ ) -> StaticProvider[T_ServiceType]:
53
+ """
54
+ Factory function for a `StaticProvider` instance for `ProviderNamespaces.GROUPS`.
55
+
56
+ !!! warning
57
+ Unlike group providers in a container, the provider function argument here should return
58
+ a single instance of the service group.
59
+
60
+ Args:
61
+ group_id: the identifier for the provided service group.
62
+ provider: a callable that returns an instance of T_ServiceType.
63
+
64
+ Returns: StaticProvider instance for the provided group_id.
65
+ """
66
+ return StaticProvider(ProviderNamespaces.GROUPS, group_id, provider)
rats/cli/__init__.py CHANGED
@@ -3,19 +3,18 @@
3
3
  from ._annotations import CommandId, command, get_class_commands, get_class_groups, group
4
4
  from ._app import ClickApp
5
5
  from ._container import CompositeContainer, Container
6
- from ._plugin import PluginContainer, PluginServices, attach, create_group
6
+ from ._plugin import PluginServices, attach, create_group
7
7
 
8
8
  __all__ = [
9
- "PluginContainer",
9
+ "ClickApp",
10
+ "CommandId",
11
+ "CompositeContainer",
12
+ "Container",
13
+ "PluginServices",
14
+ "attach",
10
15
  "command",
16
+ "create_group",
11
17
  "get_class_commands",
12
18
  "get_class_groups",
13
- "create_group",
14
- "attach",
15
19
  "group",
16
- "ClickApp",
17
- "PluginServices",
18
- "CommandId",
19
- "Container",
20
- "CompositeContainer",
21
20
  ]
rats/cli/__main__.py CHANGED
@@ -41,24 +41,16 @@ class ExampleServices:
41
41
  MAIN = apps.ServiceId[apps.Executable]("main")
42
42
 
43
43
 
44
- class ExampleContainer(apps.Container):
44
+ class ExampleContainer(apps.Container, apps.PluginMixin):
45
45
  """An example container of services."""
46
46
 
47
- _app: apps.Container
48
-
49
- def __init__(self, app: apps.Container) -> None:
50
- """The root container allows us to access services in other plugins."""
51
- self._app = app
52
-
53
- @apps.service(ExampleServices.MAIN)
54
- def _main(self) -> apps.Executable:
55
- return cli.ClickApp(
47
+ def execute(self) -> None:
48
+ """Run our ExampleCommands click group."""
49
+ cli.create_group(
56
50
  group=click.Group("example", help="An example application."),
57
- commands=ExampleCommands(),
58
- )
51
+ container=ExampleCommands(),
52
+ )()
59
53
 
60
54
 
61
55
  if __name__ == "__main__":
62
- apps.SimpleApplication(runtime_plugin=ExampleContainer).execute(
63
- ExampleServices.MAIN,
64
- )
56
+ apps.run_plugin(ExampleContainer)
rats/cli/_plugin.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import cast, final
1
+ from typing import cast
2
2
 
3
3
  import click
4
4
 
@@ -41,11 +41,3 @@ class PluginServices:
41
41
  @staticmethod
42
42
  def click_command(cmd_id: apps.ServiceId[apps.Executable]) -> apps.ServiceId[click.Group]:
43
43
  return cast(apps.ServiceId[click.Group], cmd_id)
44
-
45
-
46
- @final
47
- class PluginContainer(apps.Container):
48
- _app: apps.Container
49
-
50
- def __init__(self, app: apps.Container) -> None:
51
- self._app = app
rats/logs/__init__.py CHANGED
@@ -1,8 +1,7 @@
1
1
  """Small package to help configure logging for rats applications."""
2
2
 
3
- from ._plugin import PluginContainer, PluginServices
3
+ from ._app import ConfigureApplication
4
4
 
5
5
  __all__ = [
6
- "PluginServices",
7
- "PluginContainer",
6
+ "ConfigureApplication",
8
7
  ]
rats/logs/_app.py ADDED
@@ -0,0 +1,45 @@
1
+ import logging.config
2
+
3
+ from rats import apps
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class ConfigureApplication(apps.AppContainer, apps.PluginMixin):
9
+ def execute(self) -> None:
10
+ logging.config.dictConfig(
11
+ {
12
+ "version": 1,
13
+ "disable_existing_loggers": False,
14
+ "formatters": {
15
+ "colored": {
16
+ "()": "colorlog.ColoredFormatter",
17
+ "format": (
18
+ "%(log_color)s%(asctime)s %(levelname)-8s [%(name)s][%(lineno)d]: "
19
+ "%(message)s%(reset)s"
20
+ ),
21
+ "datefmt": "%Y-%m-%d %H:%M:%S",
22
+ "log_colors": {
23
+ "DEBUG": "white",
24
+ "INFO": "green",
25
+ "WARNING": "yellow",
26
+ "ERROR": "red,",
27
+ "CRITICAL": "bold_red",
28
+ },
29
+ }
30
+ },
31
+ "handlers": {
32
+ "console": {
33
+ "class": "logging.StreamHandler",
34
+ "level": "DEBUG",
35
+ "formatter": "colored",
36
+ "stream": "ext://sys.stderr",
37
+ }
38
+ },
39
+ "loggers": {
40
+ "": {"level": "INFO", "handlers": ["console"]},
41
+ "azure": {"level": "WARNING", "handlers": ["console"]},
42
+ },
43
+ }
44
+ )
45
+ logger.debug("done configuring logging")
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: rats-apps
3
- Version: 0.4.0.dev20241217004929
3
+ Version: 0.5.0
4
4
  Summary: research analysis tools for building applications
5
- Home-page: https://github.com/microsoft/rats/
6
5
  License: MIT
7
6
  Keywords: pipelines,machine learning,research
8
7
  Requires-Python: >=3.10,<4.0