rats-apps 0.11.0.dev20250521180658__py3-none-any.whl → 0.11.1__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/_app_containers.py +10 -9
- rats/apps/_container.py +8 -2
- rats/cli/_container.py +1 -1
- rats/logs/__init__.py +50 -4
- rats/logs/_app.py +63 -4
- rats/logs/_showwarning.py +51 -0
- rats_apps-0.11.1.dist-info/METADATA +238 -0
- {rats_apps-0.11.0.dev20250521180658.dist-info → rats_apps-0.11.1.dist-info}/RECORD +10 -9
- rats_apps-0.11.0.dev20250521180658.dist-info/METADATA +0 -27
- {rats_apps-0.11.0.dev20250521180658.dist-info → rats_apps-0.11.1.dist-info}/WHEEL +0 -0
- {rats_apps-0.11.0.dev20250521180658.dist-info → rats_apps-0.11.1.dist-info}/entry_points.txt +0 -0
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
|
|
@@ -155,7 +151,7 @@ class AppBundle(AppContainer):
|
|
155
151
|
context: Container = EMPTY_CONTEXT,
|
156
152
|
):
|
157
153
|
"""
|
158
|
-
Create an instance by providing the [rats.apps.AppPlugin] type and any additional context.
|
154
|
+
Create an instance by providing the [rats.apps.AppPlugin][] type and any additional context.
|
159
155
|
|
160
156
|
Example:
|
161
157
|
```python
|
@@ -164,17 +160,22 @@ class AppBundle(AppContainer):
|
|
164
160
|
|
165
161
|
class ExamplePlugin(apps.Container, apps.PluginMixin):
|
166
162
|
@apps.service(apps.ServiceId[str]("some-value"))
|
167
|
-
def _some_value() -> str:
|
163
|
+
def _some_value(self) -> str:
|
168
164
|
return "hello, world!"
|
169
165
|
|
170
166
|
|
171
167
|
class ExampleApplication(apps.AppContainer, apps.PluginMixin):
|
172
|
-
def execute() -> None:
|
168
|
+
def execute(self) -> None:
|
173
169
|
print(self._app.get(apps.ServiceId[str]("some-value")))
|
174
170
|
|
175
171
|
|
176
172
|
if __name__ == "__main__":
|
177
|
-
apps.run(
|
173
|
+
apps.run(
|
174
|
+
apps.AppBundle(
|
175
|
+
app_plugin=ExampleApplication,
|
176
|
+
container_plugin=ExamplePlugin,
|
177
|
+
)
|
178
|
+
)
|
178
179
|
```
|
179
180
|
|
180
181
|
Args:
|
rats/apps/_container.py
CHANGED
@@ -108,7 +108,6 @@ class Container(Protocol):
|
|
108
108
|
"""Retrieve a service group by its id."""
|
109
109
|
if not self.has_namespace(ProviderNamespaces.GROUPS, group_id):
|
110
110
|
# groups are expected to return iterable services
|
111
|
-
# TODO: we need to clean up the meaning of groups and services somehow
|
112
111
|
for i in self.get_namespaced_group(ProviderNamespaces.FALLBACK_GROUPS, group_id):
|
113
112
|
yield from cast(Iterator[T_ServiceType], i)
|
114
113
|
|
@@ -151,7 +150,14 @@ def _get_cached_services_for_group(
|
|
151
150
|
|
152
151
|
for provider in info_cache[(namespace, group_id)]:
|
153
152
|
if provider not in provider_cache:
|
154
|
-
|
153
|
+
if namespace in [
|
154
|
+
ProviderNamespaces.CONTAINERS,
|
155
|
+
ProviderNamespaces.SERVICES,
|
156
|
+
ProviderNamespaces.FALLBACK_SERVICES,
|
157
|
+
]:
|
158
|
+
provider_cache[provider] = getattr(c, provider.attr)()
|
159
|
+
else:
|
160
|
+
provider_cache[provider] = list(getattr(c, provider.attr)())
|
155
161
|
|
156
162
|
yield provider_cache[provider]
|
157
163
|
|
rats/cli/_container.py
CHANGED
@@ -55,11 +55,11 @@ class Container(Protocol):
|
|
55
55
|
|
56
56
|
commands = get_class_commands(type(self))
|
57
57
|
tates = commands.annotations
|
58
|
+
logger.debug(f"[{type(self)}] commands: {tates}")
|
58
59
|
|
59
60
|
for tate in tates:
|
60
61
|
method = getattr(self, tate.name)
|
61
62
|
params = list(reversed(getattr(method, "__click_params__", [])))
|
62
|
-
logger.debug(tate.namespace)
|
63
63
|
for command in tate.groups:
|
64
64
|
if tate.namespace == "commands":
|
65
65
|
group.add_command(
|
rats/logs/__init__.py
CHANGED
@@ -7,16 +7,62 @@ handle configuring the python logging libraries, with a few configuration option
|
|
7
7
|
!!! warning
|
8
8
|
We expose the logging functionality through a [rats.apps.AppContainer][] in order to leverage
|
9
9
|
the built in plugin system to give users the ability to adjust the default settings, but this
|
10
|
-
|
11
|
-
|
10
|
+
application should be lightweight and should not contain very complex logic, avoiding logic
|
11
|
+
that is very time consuming or has a chance of failing with confusing errors.
|
12
|
+
|
13
|
+
## Verbosity Env Variables
|
14
|
+
|
15
|
+
To help develop modules, logging output can be configured with the `DEBUG_LOGS_*`, `QUIET_LOGS_*`,
|
16
|
+
and `LEVEL_LOGS_*` environment variables. The suffix of the environment variables match the module
|
17
|
+
name wanting to be configured.
|
18
|
+
|
19
|
+
### Module Loggers
|
20
|
+
|
21
|
+
Environment variables named `DEBUG_LOGS_*` cause logs from the given module to be shown; the
|
22
|
+
`QUIET_LOGS_*` environment variables silence logs emitted by the module; and `LEVEL_LOGS_*` allows
|
23
|
+
a specific [logging-level](https://docs.python.org/3/library/logging.html#logging-levels) to be
|
24
|
+
used.
|
25
|
+
|
26
|
+
```
|
27
|
+
$ rats-ci fix
|
28
|
+
All checks passed!
|
29
|
+
63 files left unchanged
|
30
|
+
ran 2 fix commands
|
31
|
+
```
|
32
|
+
|
33
|
+
```bash
|
34
|
+
# Enable DEBUG level logs for the `rats.*` modules
|
35
|
+
export DEBUG_LOGS_RATS="1"
|
36
|
+
# Enable WARNING level logs for the `rats.projects.*` modules
|
37
|
+
export LEVEL_LOGS_RATS_PROJECTS="WARNING"
|
38
|
+
# Disable logs for `rats.cli.*` modules
|
39
|
+
export QUIET_LOGS_RATS_CLI="1"
|
40
|
+
```
|
41
|
+
```
|
42
|
+
$ rats-ci fix
|
43
|
+
2025-05-27 00:49:45 DEBUG [rats.logs._app:96]: done configuring logging
|
44
|
+
All checks passed!
|
45
|
+
63 files left unchanged
|
46
|
+
ran 2 fix commands
|
47
|
+
```
|
48
|
+
|
49
|
+
### Root Logger
|
50
|
+
|
51
|
+
The root logger can be configured with the `DEBUG_LOGS`, `QUIET_LOGS`, and `LEVEL_LOGS` environment
|
52
|
+
variables.
|
53
|
+
|
54
|
+
```bash
|
55
|
+
# Enable WARNING level logs for all logs
|
56
|
+
export LEVEL_LOGS="WARNING"
|
57
|
+
```
|
12
58
|
|
13
|
-
If the logging options made available through this module are far from what is desired, instead
|
14
|
-
of adding flags and options to this module, we recommend configuring logging in your own code.
|
15
59
|
"""
|
16
60
|
|
17
61
|
from ._app import AppConfigs, ConfigureApplication
|
62
|
+
from ._showwarning import showwarning
|
18
63
|
|
19
64
|
__all__ = [
|
20
65
|
"AppConfigs",
|
21
66
|
"ConfigureApplication",
|
67
|
+
"showwarning",
|
22
68
|
]
|
rats/logs/_app.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
import logging.config
|
2
|
+
import os
|
2
3
|
import warnings
|
3
4
|
from collections.abc import Iterator
|
4
5
|
from typing import Any
|
5
6
|
|
6
7
|
from rats import apps
|
7
8
|
|
9
|
+
from ._showwarning import showwarning
|
10
|
+
|
8
11
|
logger = logging.getLogger(__name__)
|
9
12
|
LoggerConfigEntry = tuple[str, dict[str, Any]]
|
10
13
|
|
@@ -61,12 +64,12 @@ class ConfigureApplication(apps.AppContainer, apps.PluginMixin):
|
|
61
64
|
"colored": {
|
62
65
|
"()": "colorlog.ColoredFormatter",
|
63
66
|
"format": (
|
64
|
-
"%(log_color)s%(asctime)s %(levelname)-8s [%(name)s
|
67
|
+
"%(log_color)s%(asctime)s %(levelname)-8s [%(name)s:%(lineno)d]: "
|
65
68
|
"%(message)s%(reset)s"
|
66
69
|
),
|
67
70
|
"datefmt": "%Y-%m-%d %H:%M:%S",
|
68
71
|
"log_colors": {
|
69
|
-
"DEBUG": "
|
72
|
+
"DEBUG": "cyan",
|
70
73
|
"INFO": "green",
|
71
74
|
"WARNING": "yellow",
|
72
75
|
"ERROR": "red,",
|
@@ -88,9 +91,65 @@ class ConfigureApplication(apps.AppContainer, apps.PluginMixin):
|
|
88
91
|
# enable deprecation warnings by default
|
89
92
|
logging.captureWarnings(True)
|
90
93
|
warnings.simplefilter("default", DeprecationWarning)
|
94
|
+
# our modified `showwarning` method logs warnings with the module logger that emitted it
|
95
|
+
warnings.showwarning = showwarning
|
91
96
|
logger.debug("done configuring logging")
|
92
97
|
|
93
98
|
@apps.fallback_group(AppConfigs.LOGGERS)
|
94
99
|
def _default_loggers(self) -> Iterator[LoggerConfigEntry]:
|
95
|
-
|
96
|
-
|
100
|
+
logger_mapping = {
|
101
|
+
"": "INFO",
|
102
|
+
"azure": "WARNING",
|
103
|
+
"py.warnings": "CRITICAL",
|
104
|
+
}
|
105
|
+
searched_prefixes = [
|
106
|
+
"DEBUG_LOGS",
|
107
|
+
"QUIET_LOGS",
|
108
|
+
"LEVEL_LOGS",
|
109
|
+
]
|
110
|
+
for name in os.environ.keys():
|
111
|
+
env_prefix = name[0:10]
|
112
|
+
if env_prefix not in searched_prefixes:
|
113
|
+
# this isn't a logging config env
|
114
|
+
continue
|
115
|
+
|
116
|
+
# our above prefixes are conveniently all 10 characters
|
117
|
+
# everything after the prefix is the logger name, skip the underscore after the prefix
|
118
|
+
logger_name = name.lower()[11:].replace("_", ".")
|
119
|
+
if logger_name not in logger_mapping:
|
120
|
+
# the default here is ignored
|
121
|
+
logger_mapping[logger_name] = "INFO"
|
122
|
+
|
123
|
+
for name, default in logger_mapping.items():
|
124
|
+
yield self._build_logger_entry(name, default)
|
125
|
+
|
126
|
+
def _build_logger_entry(self, name: str, default: str = "INFO") -> LoggerConfigEntry:
|
127
|
+
# https://docs.python.org/3/library/logging.html#logging-levels
|
128
|
+
valid_levels = [
|
129
|
+
"NOTSET",
|
130
|
+
"DEBUG",
|
131
|
+
"INFO",
|
132
|
+
"WARNING",
|
133
|
+
"ERROR",
|
134
|
+
"CRITICAL",
|
135
|
+
]
|
136
|
+
suffix = f"_{'_'.join(name.split('.')).upper()}" if name != "" else ""
|
137
|
+
|
138
|
+
debug_env_name = f"DEBUG_LOGS{suffix}"
|
139
|
+
quiet_env_name = f"QUIET_LOGS{suffix}"
|
140
|
+
level_env_name = f"LEVEL_LOGS{suffix}"
|
141
|
+
if os.environ.get(level_env_name):
|
142
|
+
# user specified exactly what log level is wanted for this module
|
143
|
+
lvl = os.environ[level_env_name].upper()
|
144
|
+
if lvl not in valid_levels:
|
145
|
+
raise ValueError(f"invalid log level specified: {level_env_name}={lvl}")
|
146
|
+
return name, {"level": lvl, "handlers": ["console"], "propagate": False}
|
147
|
+
elif os.environ.get(debug_env_name):
|
148
|
+
# show as many logs as possible
|
149
|
+
return name, {"level": "DEBUG", "handlers": ["console"], "propagate": False}
|
150
|
+
elif os.environ.get(quiet_env_name):
|
151
|
+
# only show critical logs when QUIET_LOGS is enabled
|
152
|
+
return name, {"level": "CRITICAL", "handlers": ["console"], "propagate": False}
|
153
|
+
else:
|
154
|
+
# set default logging to INFO
|
155
|
+
return name, {"level": default, "handlers": ["console"], "propagate": False}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import logging
|
2
|
+
import sys
|
3
|
+
import warnings
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
logger = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
|
9
|
+
def showwarning(
|
10
|
+
message: Warning | str,
|
11
|
+
category: type[Warning],
|
12
|
+
filename: str,
|
13
|
+
lineno: int,
|
14
|
+
file: str | None = None,
|
15
|
+
line: str | None = None,
|
16
|
+
) -> None:
|
17
|
+
"""
|
18
|
+
Handler that logs warnings using the module logger from the module that emitted the warning.
|
19
|
+
|
20
|
+
```python
|
21
|
+
import warnings
|
22
|
+
from rats.logs import showwarning
|
23
|
+
|
24
|
+
warnings.showwarning = showwarning
|
25
|
+
warnings.warn("This is a custom warning", UserWarning)
|
26
|
+
```
|
27
|
+
|
28
|
+
Args:
|
29
|
+
message: The warning message instance or string.
|
30
|
+
category: The category of the warning (e.g., [DeprecationWarning][], [UserWarning][]).
|
31
|
+
filename: The path to the file where the warning originated.
|
32
|
+
lineno: The line number in the file where the warning originated.
|
33
|
+
file: Ignored. Included for compatibility with the standard warnings.showwarning signature.
|
34
|
+
line: The line of source code to be included in the warning message, if available.
|
35
|
+
"""
|
36
|
+
if file is not None:
|
37
|
+
raise ValueError(file)
|
38
|
+
|
39
|
+
formatted_message = warnings.formatwarning(message, category, filename, lineno, line)
|
40
|
+
|
41
|
+
for _module_name, module in sys.modules.items():
|
42
|
+
module_path = getattr(module, "__file__", None)
|
43
|
+
if module_path and Path(filename).is_file() and Path(module_path).samefile(filename):
|
44
|
+
module_name = _module_name
|
45
|
+
break
|
46
|
+
else:
|
47
|
+
# unsure what module to use, but we can default to "py.warnings" like the original handler
|
48
|
+
module_name = "py.warnings"
|
49
|
+
|
50
|
+
source_logger = logging.getLogger(module_name)
|
51
|
+
source_logger.warning(formatted_message)
|
@@ -0,0 +1,238 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: rats-apps
|
3
|
+
Version: 0.11.1
|
4
|
+
Summary: research analysis tools for building applications
|
5
|
+
License: MIT
|
6
|
+
Keywords: pipelines,machine learning,research
|
7
|
+
Author: Elon Portugaly
|
8
|
+
Author-email: elonp@microsoft.com
|
9
|
+
Requires-Python: >=3.10,<4.0
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
16
|
+
Requires-Dist: click
|
17
|
+
Requires-Dist: colorlog
|
18
|
+
Requires-Dist: dataclass-wizard
|
19
|
+
Requires-Dist: pyyaml
|
20
|
+
Requires-Dist: typing-extensions
|
21
|
+
Project-URL: Documentation, https://microsoft.github.io/rats
|
22
|
+
Project-URL: Repository, https://github.com/microsoft/rats
|
23
|
+
Description-Content-Type: text/markdown
|
24
|
+
|
25
|
+
---
|
26
|
+
title: introduction
|
27
|
+
---
|
28
|
+
|
29
|
+
The `rats-apps` package helps create applications; a small set of modules that eliminate the most
|
30
|
+
common boilerplate code when creating applications of any kind. We do this mainly by providing a
|
31
|
+
set of libraries to define service containers, and using the service containers to hide the
|
32
|
+
complexity of creating services–like authentication, storage, or database clients–from other parts
|
33
|
+
of the system, allowing developers to focus on the business logic of the application. Often referred
|
34
|
+
to as [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection), we use our
|
35
|
+
service containers to separate the concerns of constructing objects and using them.
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
Use `pip` or your favorite packaging tool to install the package from PyPI.
|
40
|
+
|
41
|
+
```bash
|
42
|
+
pip install rats-apps
|
43
|
+
```
|
44
|
+
|
45
|
+
## What is an application?
|
46
|
+
|
47
|
+
Let's start with a standard python script, and slowly migrate it to be a rats application in order
|
48
|
+
to explain a few main details. Creating a `__main__.py` file in a package makes it executable.
|
49
|
+
After the common python boilerplate code is added, this is typically what we find in the wild:
|
50
|
+
|
51
|
+
=== ":material-language-python: ~/code/src/foo/\_\_main\_\_.py"
|
52
|
+
```python
|
53
|
+
def main() -> None:
|
54
|
+
print("hello, world")
|
55
|
+
|
56
|
+
|
57
|
+
if __name__ == "__main__":
|
58
|
+
main()
|
59
|
+
```
|
60
|
+
|
61
|
+
=== ":material-console: ~/code"
|
62
|
+
```bash
|
63
|
+
python -m foo
|
64
|
+
```
|
65
|
+
|
66
|
+
Using this pattern, we can define our application with a small modification, turning our `main()`
|
67
|
+
function into a runnable rats application, and then running it:
|
68
|
+
|
69
|
+
=== ":material-language-python: ~/code/src/foo/\_\_main\_\_.py"
|
70
|
+
```python
|
71
|
+
from rats import apps
|
72
|
+
|
73
|
+
|
74
|
+
def main() -> None:
|
75
|
+
print("hello, world")
|
76
|
+
|
77
|
+
|
78
|
+
if __name__ == "__main__":
|
79
|
+
apps.run(apps.App(main))
|
80
|
+
```
|
81
|
+
|
82
|
+
!!! info
|
83
|
+
The [rats.apps.App][] class is used to quickly turn a script entry point–`Callable[[], None]`
|
84
|
+
–into an object with an `execute()` method, defined by our [rats.apps.Executable][]
|
85
|
+
interface.
|
86
|
+
|
87
|
+
## Application Containers & Plugins
|
88
|
+
|
89
|
+
We can start to leverage [rats.apps][] to make our application easy to extend. Let's replace our use
|
90
|
+
of the [rats.apps.App][] wrapper and move our `main()` function into a class. Using the
|
91
|
+
[rats.apps.AppContainer][] and [rats.apps.PluginMixin][] classes, we finish adapting our example
|
92
|
+
to be a rats application.
|
93
|
+
|
94
|
+
=== ":material-language-python: ~/code/src/foo/\_\_main\_\_.py"
|
95
|
+
|
96
|
+
```python
|
97
|
+
from rats import apps
|
98
|
+
|
99
|
+
|
100
|
+
class Application(apps.AppContainer, apps.PluginMixin):
|
101
|
+
def execute() -> None:
|
102
|
+
print("hello, world")
|
103
|
+
|
104
|
+
|
105
|
+
if __name__ == "__main__":
|
106
|
+
apps.run_plugin(Application)
|
107
|
+
```
|
108
|
+
|
109
|
+
!!! info
|
110
|
+
If you're making these changes to an existing code base, you should be able to run things to
|
111
|
+
validate that everything still works as it did before. Your `main()` function is now in
|
112
|
+
the `execute()` method of your new `Application` class, but none of the behavior of your
|
113
|
+
application should have been affected.
|
114
|
+
|
115
|
+
Now that we have a fully defined rats application; we can use [rats.apps.Container][] instances to
|
116
|
+
make services available to our application while remaining decoupled from the details of how these
|
117
|
+
services are initialized. A common use case for this is to give our team access to the azure
|
118
|
+
storage clients without needing to specify the authentication details; allowing us to ensure our
|
119
|
+
application is functional in many compute environments.
|
120
|
+
|
121
|
+
=== ":material-language-python: ~/code/src/foo/\_\_init\_\_.py"
|
122
|
+
|
123
|
+
```python
|
124
|
+
from ._plugin import PluginContainer
|
125
|
+
|
126
|
+
__all__ = ["PluginContainer"]
|
127
|
+
```
|
128
|
+
|
129
|
+
=== ":material-language-python: ~/code/src/foo/\_\_main\_\_.py"
|
130
|
+
|
131
|
+
```python
|
132
|
+
from rats import apps
|
133
|
+
import foo
|
134
|
+
|
135
|
+
|
136
|
+
class Application(apps.AppContainer, apps.PluginMixin):
|
137
|
+
|
138
|
+
def execute() -> None:
|
139
|
+
blob_client = self._app.get(foo.BLOB_SERVICE_ID)
|
140
|
+
print(f"hello, world. loaded blob client: {blob_client}")
|
141
|
+
|
142
|
+
@apps.container()
|
143
|
+
def _plugins(self) -> apps.Container:
|
144
|
+
return foo.PluginContainer(self._app)
|
145
|
+
|
146
|
+
|
147
|
+
if __name__ == "__main__":
|
148
|
+
apps.run_plugin(Application)
|
149
|
+
```
|
150
|
+
|
151
|
+
=== ":material-language-python: ~/code/src/foo/_plugin.py"
|
152
|
+
|
153
|
+
```python
|
154
|
+
from rats import apps
|
155
|
+
from azure.storage.blob import BlobServiceClient
|
156
|
+
|
157
|
+
BLOB_SERVICE_ID = apps.ServiceId[BlobServiceClient]("blob-client")
|
158
|
+
|
159
|
+
|
160
|
+
class PluginContainer(apps.Container, apps.PluginMixin):
|
161
|
+
|
162
|
+
@apps.service(BLOB_SERVICE_ID)
|
163
|
+
def _blob_client(self) -> BlobServiceClient:
|
164
|
+
credential = DefaultAzureCredential()
|
165
|
+
return BlobServiceClient(
|
166
|
+
account_url=f"https://example.blob.core.windows.net/",
|
167
|
+
credential=credential,
|
168
|
+
)
|
169
|
+
```
|
170
|
+
|
171
|
+
!!! success
|
172
|
+
Following these patterns, we can make services available to others with plugin containers; and
|
173
|
+
we can combine these containers to create applications. The [rats.apps][] module has additional
|
174
|
+
libraries to help define different types of applications, designed to help your solutions
|
175
|
+
evolve as ideas mature. Our first runnable example was a single instance of the
|
176
|
+
[rats.apps.AppContainer][] interface with an `execute()` method; but a larger project might
|
177
|
+
have a few modules providing different aspects of the application's needs by sharing a set of
|
178
|
+
service ids and a plugin container.
|
179
|
+
|
180
|
+
## Installable Plugins
|
181
|
+
|
182
|
+
We use the standard [Entry Points](https://packaging.python.org/en/latest/specifications/entry-points/)
|
183
|
+
mechanism to allow authors to make their application extensible. These plugins are instances of
|
184
|
+
[rats.apps.Container][] and loaded into applications like our previous examples.
|
185
|
+
|
186
|
+
=== ":material-file-settings: ~/code/pyproject.toml"
|
187
|
+
```toml
|
188
|
+
[tool.poetry.plugins."foo.plugins"]
|
189
|
+
"foo" = "foo:PluginContainer"
|
190
|
+
```
|
191
|
+
|
192
|
+
=== ":material-language-python: ~/code/src/foo/\_\_main\_\_.py"
|
193
|
+
```python
|
194
|
+
from rats import apps
|
195
|
+
import foo
|
196
|
+
|
197
|
+
|
198
|
+
class Application(apps.AppContainer, apps.PluginMixin):
|
199
|
+
|
200
|
+
def execute() -> None:
|
201
|
+
blob_client = self._app.get(foo.BLOB_SERVICE_ID)
|
202
|
+
print(f"hello, world. loaded blob client: {blob_client}")
|
203
|
+
|
204
|
+
@apps.container()
|
205
|
+
def _plugins(self) -> apps.Container:
|
206
|
+
return apps.PythonEntryPointContainer("foo.plugins")
|
207
|
+
|
208
|
+
|
209
|
+
if __name__ == "__main__":
|
210
|
+
apps.run_plugin(Application)
|
211
|
+
```
|
212
|
+
|
213
|
+
!!! success
|
214
|
+
We used [rats.app] to define an application, and used a service provided by a plugin container
|
215
|
+
that is loaded through a python entry point. You can create a script entry in your
|
216
|
+
`pyproject.toml` file to expose your application through a terminal command:
|
217
|
+
|
218
|
+
=== ":material-language-python: ~/code/src/foo/\_\_init\_\_.py"
|
219
|
+
```python
|
220
|
+
from ._app import Application, main
|
221
|
+
from ._plugin import PluginContainer
|
222
|
+
|
223
|
+
|
224
|
+
__all__ = [
|
225
|
+
"Application",
|
226
|
+
"main",
|
227
|
+
"PluginContainer",
|
228
|
+
]
|
229
|
+
```
|
230
|
+
=== ":material-file-settings: ~/code/pyproject.toml"
|
231
|
+
```toml
|
232
|
+
[tool.poetry.plugins."foo.plugins"]
|
233
|
+
"foo" = "foo:PluginContainer"
|
234
|
+
|
235
|
+
[tool.poetry.scripts]
|
236
|
+
foo = "foo:main"
|
237
|
+
```
|
238
|
+
|
@@ -9,9 +9,9 @@ rats/app_context/_context.py,sha256=S7D8NLefZJI4MDEpYKdenfNsemus4bUlQCG-gd3U3g8,
|
|
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=1rtq92rOGvCbroQmDgOM24jxxcNNFQHy65fBLO5LIXo,6920
|
13
13
|
rats/apps/_composite_container.py,sha256=FdpmH_xIly6LxNZrA_nZCznukptjVLXttXTMtf_tnv8,695
|
14
|
-
rats/apps/_container.py,sha256=
|
14
|
+
rats/apps/_container.py,sha256=N8qR0oBLi03xZvPfc8SxaSci0oVYfX_rftzfaBkTSfQ,7415
|
15
15
|
rats/apps/_executables.py,sha256=hXExNmAnuPU1KJXihNw1jEDAQpMlQ9E9_aPV8tpGbOY,1347
|
16
16
|
rats/apps/_ids.py,sha256=T8Onrj79t8NPfBMQBk0xI6fIWDKF0m2JfFNrdtXAbWg,353
|
17
17
|
rats/apps/_mains.py,sha256=2Q97mNk1cBzYROc_pJcm57EEeHmwRbXOWpfYXH37qcA,995
|
@@ -26,12 +26,13 @@ rats/cli/__main__.py,sha256=WeldAKjA3Kmz9ZRnZVd3G8Ud66Y_gSRDLIPNE1JyhV0,1418
|
|
26
26
|
rats/cli/_annotations.py,sha256=-B2Y1bYjPbeTNTBMZzMdAU92qu9AyutKHN_NdFy-xhA,2964
|
27
27
|
rats/cli/_click_app.py,sha256=Jvs6OqNC4Yoe6kbc2tCzjzUieNFa1n_zwXPsdo971Wc,1450
|
28
28
|
rats/cli/_command.py,sha256=kyU3UqqF9aiTTaFvlQFBKDLXvArQS1QgjoQqlMbKzok,597
|
29
|
-
rats/cli/_container.py,sha256=
|
29
|
+
rats/cli/_container.py,sha256=BoZLOu-eT4SzweNwMepWHd4PyxQAg--_p-rM7XkQNAA,3529
|
30
30
|
rats/cli/_functions.py,sha256=BNmgWVquQUEqJAYsed_l8vLnlLP7u3XC1TDyEFI1AiU,1552
|
31
31
|
rats/cli/_plugin.py,sha256=o-pmEqU6mVH3QoRfRBrbG-XRTWCzt6pLKtSV3-5VSx0,1144
|
32
32
|
rats/cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
33
|
-
rats/logs/__init__.py,sha256=
|
34
|
-
rats/logs/_app.py,sha256=
|
33
|
+
rats/logs/__init__.py,sha256=_ZdkBmvW1yr5UOIB_m4OldNsUxK6NTDunNW4mvgQmpI,2115
|
34
|
+
rats/logs/_app.py,sha256=EVjSjSaxJIL5LMJZf4yo9EpUBkkavaVXyg42cDsUt3I,5702
|
35
|
+
rats/logs/_showwarning.py,sha256=g5Gq0I7GoHgABI4eCobq6xd4NaKuLGJFyK-SCvegDHA,1726
|
35
36
|
rats/logs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
37
|
rats/runtime/__init__.py,sha256=f9aEnynHuePP9OP5XCtB-6nCBvNWWlQQLTzuxFEueoQ,2150
|
37
38
|
rats/runtime/__main__.py,sha256=y01yOymsL075poX95pc02sJR1HD0pDNFRZdpOdi0R6Y,79
|
@@ -54,7 +55,7 @@ rats_e2e/runtime/_data.py,sha256=3d1F_JO2gEOPUjBp_KYMP3TefyneiG_ktlJjdIIYUy8,125
|
|
54
55
|
rats_e2e/runtime/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
56
|
rats_resources/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
57
|
rats_resources/runtime/example-context.yaml,sha256=eiLsNFquFfkIpUhxUCQLzLigH21QF2F00fzA_e_aOKk,215
|
57
|
-
rats_apps-0.11.
|
58
|
-
rats_apps-0.11.
|
59
|
-
rats_apps-0.11.
|
60
|
-
rats_apps-0.11.
|
58
|
+
rats_apps-0.11.1.dist-info/METADATA,sha256=zUanJsEqbI1Z4kibOwJKNmGs5aYYoTe0Bame-D7CIag,8274
|
59
|
+
rats_apps-0.11.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
60
|
+
rats_apps-0.11.1.dist-info/entry_points.txt,sha256=Gf6bPwxIVjWd3Xx71upZo7eDJA5cujniLew6fxJMgA4,117
|
61
|
+
rats_apps-0.11.1.dist-info/RECORD,,
|
@@ -1,27 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.3
|
2
|
-
Name: rats-apps
|
3
|
-
Version: 0.11.0.dev20250521180658
|
4
|
-
Summary: research analysis tools for building applications
|
5
|
-
License: MIT
|
6
|
-
Keywords: pipelines,machine learning,research
|
7
|
-
Author: Elon Portugaly
|
8
|
-
Author-email: elonp@microsoft.com
|
9
|
-
Requires-Python: >=3.10,<4.0
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
15
|
-
Classifier: Programming Language :: Python :: 3.13
|
16
|
-
Requires-Dist: click
|
17
|
-
Requires-Dist: colorlog
|
18
|
-
Requires-Dist: dataclass-wizard
|
19
|
-
Requires-Dist: pyyaml
|
20
|
-
Requires-Dist: typing-extensions
|
21
|
-
Project-URL: Documentation, https://microsoft.github.io/rats
|
22
|
-
Project-URL: Repository, https://github.com/microsoft/rats
|
23
|
-
Description-Content-Type: text/markdown
|
24
|
-
|
25
|
-
# rats-apps
|
26
|
-
...
|
27
|
-
|
File without changes
|
{rats_apps-0.11.0.dev20250521180658.dist-info → rats_apps-0.11.1.dist-info}/entry_points.txt
RENAMED
File without changes
|