rats-apps 0.1.3.dev20240812192218__py3-none-any.whl → 0.2.0.dev20240813092513__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ # type: ignore[reportUntypedFunctionDecorator]
2
+ """..."""
3
+
4
+ from typing import NamedTuple
5
+
6
+ from rats import annotations
7
+
8
+
9
+ class FruitId(NamedTuple):
10
+ """Any named tuple can be attached to functions."""
11
+
12
+ name: str
13
+ color: str
14
+
15
+
16
+ def fruit(fid: FruitId) -> annotations.DecoratorType:
17
+ """A decorator that attached a fruit object to a function."""
18
+ return annotations.annotation("fruits", fid)
19
+
20
+
21
+ @fruit(FruitId("apple", "red"))
22
+ def some_function() -> None:
23
+ """..."""
24
+ pass
25
+
26
+
27
+ @fruit(FruitId("banana", "yellow"))
28
+ class SomeClass:
29
+ """Class definitions can also be annotated."""
30
+
31
+ @fruit(FruitId("cherry", "red"))
32
+ def some_method(self) -> None:
33
+ """Or class methods."""
34
+ pass
35
+
36
+
37
+ def _example() -> None:
38
+ """Let's define a couple objects and annotate them."""
39
+ print(annotations.get_class_annotations(SomeClass))
40
+ print(annotations.get_annotations(SomeClass))
41
+ print(annotations.get_annotations(some_function))
42
+ print(annotations.get_annotations(SomeClass.some_method))
43
+
44
+
45
+ if __name__ == "__main__":
46
+ _example()
@@ -3,15 +3,14 @@ from __future__ import annotations
3
3
  from collections import defaultdict
4
4
  from collections.abc import Callable
5
5
  from functools import cache
6
- from typing import Any, Generic, ParamSpec, TypeVar
7
- from typing import NamedTuple as tNamedTuple
6
+ from typing import Any, Generic, NamedTuple, ParamSpec, TypeVar
8
7
 
9
- from typing_extensions import NamedTuple
8
+ from typing_extensions import NamedTuple as ExtNamedTuple
10
9
 
11
10
  T_GroupType = TypeVar("T_GroupType", bound=NamedTuple)
12
11
 
13
12
 
14
- class GroupAnnotations(NamedTuple, Generic[T_GroupType]):
13
+ class GroupAnnotations(ExtNamedTuple, Generic[T_GroupType]):
15
14
  """The list of T_GroupType objects identified by a given name in a namespace."""
16
15
 
17
16
  name: str
@@ -21,7 +20,7 @@ class GroupAnnotations(NamedTuple, Generic[T_GroupType]):
21
20
  groups: tuple[T_GroupType, ...]
22
21
 
23
22
 
24
- class AnnotationsContainer(tNamedTuple):
23
+ class AnnotationsContainer(NamedTuple):
25
24
  """
26
25
  Holds metadata about the annotated functions or class methods.
27
26
 
@@ -65,7 +64,7 @@ class AnnotationsBuilder:
65
64
  def __init__(self) -> None:
66
65
  self._group_ids = defaultdict(set)
67
66
 
68
- def add(self, namespace: str, group_id: NamedTuple | tNamedTuple) -> None:
67
+ def add(self, namespace: str, group_id: ExtNamedTuple | NamedTuple) -> None:
69
68
  self._group_ids[namespace].add(group_id)
70
69
 
71
70
  def make(self, name: str) -> AnnotationsContainer:
@@ -84,7 +83,7 @@ DecoratorType = TypeVar("DecoratorType", bound=Callable[..., Any])
84
83
 
85
84
  def annotation(
86
85
  namespace: str,
87
- group_id: NamedTuple | tNamedTuple,
86
+ group_id: ExtNamedTuple | NamedTuple,
88
87
  ) -> Callable[[DecoratorType], DecoratorType]:
89
88
  """
90
89
  Decorator to add an annotation to a function.
rats/apps/__init__.py CHANGED
@@ -15,15 +15,15 @@ from ._annotations import (
15
15
  )
16
16
  from ._composite_container import CompositeContainer
17
17
  from ._container import (
18
- AnnotatedContainer, # type: ignore[reportDeprecated]
19
18
  Container,
20
19
  DuplicateServiceError,
20
+ Provider,
21
21
  ServiceNotFoundError,
22
22
  ServiceProvider,
23
23
  container,
24
24
  )
25
25
  from ._executables import App, Executable
26
- from ._ids import ServiceId, T_ExecutableType
26
+ from ._ids import ServiceId, T_ExecutableType, T_ServiceType
27
27
  from ._namespaces import ProviderNamespaces
28
28
  from ._plugin_container import PluginContainers
29
29
  from ._plugins import PluginRunner
@@ -32,7 +32,6 @@ from ._scoping import autoscope
32
32
  from ._simple_apps import AppServices, SimpleApplication, StandardRuntime
33
33
 
34
34
  __all__ = [
35
- "AnnotatedContainer",
36
35
  "App",
37
36
  "CompositeContainer",
38
37
  "Container",
@@ -41,6 +40,7 @@ __all__ = [
41
40
  "PluginContainers",
42
41
  "ProviderNamespaces",
43
42
  "ServiceProvider",
43
+ "Provider",
44
44
  "ServiceId",
45
45
  "ServiceNotFoundError",
46
46
  "autoid_service",
@@ -53,6 +53,7 @@ __all__ = [
53
53
  "autoid",
54
54
  "service",
55
55
  "T_ExecutableType",
56
+ "T_ServiceType",
56
57
  "Runtime",
57
58
  "NullRuntime",
58
59
  "AppServices",
rats/apps/__main__.py ADDED
@@ -0,0 +1,79 @@
1
+ """..."""
2
+
3
+ import json
4
+ import logging
5
+ import os
6
+ import uuid
7
+ from collections.abc import Iterator
8
+
9
+ from rats import apps
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class ExampleData:
15
+ """A simple data source that might come from another package."""
16
+
17
+ _num_samples: int
18
+
19
+ def __init__(self, num_samples: int) -> None:
20
+ """..."""
21
+ self._num_samples = num_samples
22
+
23
+ def fetch(self) -> Iterator[str]:
24
+ """Yields random samples."""
25
+ for i in range(self._num_samples):
26
+ yield json.dumps({"index": i, "sample": str(uuid.uuid4())})
27
+
28
+
29
+ class ExampleExe(apps.Executable):
30
+ """..."""
31
+
32
+ _example_data: ExampleData
33
+
34
+ def __init__(self, example_data: ExampleData) -> None:
35
+ """..."""
36
+ self._example_data = example_data
37
+
38
+ def execute(self) -> None:
39
+ """..."""
40
+ for row in self._example_data.fetch():
41
+ print(row)
42
+
43
+
44
+ @apps.autoscope
45
+ class PluginServices:
46
+ """..."""
47
+
48
+ MAIN_EXE = apps.ServiceId[apps.Executable]("main")
49
+ EXAMPLE_DATA = apps.ServiceId[ExampleData]("example-data")
50
+
51
+
52
+ class PluginContainer(apps.Container):
53
+ """..."""
54
+
55
+ _app: apps.Container
56
+
57
+ def __init__(self, app: apps.Container) -> None:
58
+ """..."""
59
+ self._app = app
60
+
61
+ @apps.service(PluginServices.MAIN_EXE)
62
+ def _main_exe(self) -> apps.Executable:
63
+ """..."""
64
+ return ExampleExe(
65
+ example_data=self._app.get(PluginServices.EXAMPLE_DATA),
66
+ )
67
+
68
+ @apps.service(PluginServices.EXAMPLE_DATA)
69
+ def _example_data(self) -> ExampleData:
70
+ """..."""
71
+ return ExampleData(
72
+ num_samples=int(os.environ.get("EXAMPLE_DATA_NUM_SAMPLES", "5")),
73
+ )
74
+
75
+
76
+ if __name__ == "__main__":
77
+ apps.SimpleApplication(runtime_plugin=PluginContainer).execute(
78
+ PluginServices.MAIN_EXE,
79
+ )
rats/apps/_annotations.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from collections.abc import Callable
2
- from typing import Any, ParamSpec
2
+ from typing import Any, NamedTuple, ParamSpec, cast
3
3
 
4
4
  from rats import annotations
5
5
 
@@ -14,19 +14,19 @@ def service(
14
14
  service_id: ServiceId[T_ServiceType],
15
15
  ) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
16
16
  """A service is anything you would create instances of?"""
17
- return annotations.annotation(ProviderNamespaces.SERVICES, service_id)
17
+ return annotations.annotation(ProviderNamespaces.SERVICES, cast(NamedTuple, service_id))
18
18
 
19
19
 
20
20
  def autoid_service(fn: Callable[P, T_ServiceType]) -> Callable[P, T_ServiceType]:
21
21
  _service_id = autoid(fn)
22
- return annotations.annotation(ProviderNamespaces.SERVICES, _service_id)(fn)
22
+ return annotations.annotation(ProviderNamespaces.SERVICES, cast(NamedTuple, _service_id))(fn)
23
23
 
24
24
 
25
25
  def group(
26
26
  group_id: ServiceId[T_ServiceType],
27
27
  ) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
28
28
  """A group is a collection of services."""
29
- return annotations.annotation(ProviderNamespaces.GROUPS, group_id)
29
+ return annotations.annotation(ProviderNamespaces.GROUPS, cast(NamedTuple, group_id))
30
30
 
31
31
 
32
32
  def fallback_service(
@@ -35,7 +35,7 @@ def fallback_service(
35
35
  """A fallback service gets used if no service is defined."""
36
36
  return annotations.annotation(
37
37
  ProviderNamespaces.FALLBACK_SERVICES,
38
- service_id,
38
+ cast(NamedTuple, service_id),
39
39
  )
40
40
 
41
41
 
@@ -45,7 +45,7 @@ def fallback_group(
45
45
  """A fallback group gets used if no group is defined."""
46
46
  return annotations.annotation(
47
47
  ProviderNamespaces.FALLBACK_GROUPS,
48
- group_id,
48
+ cast(NamedTuple, group_id),
49
49
  )
50
50
 
51
51
 
rats/apps/_container.py CHANGED
@@ -1,10 +1,7 @@
1
- import abc
2
1
  import logging
3
2
  from abc import abstractmethod
4
3
  from collections.abc import Callable, Iterator
5
- from typing import Generic, ParamSpec, Protocol
6
-
7
- from typing_extensions import deprecated
4
+ from typing import Generic, NamedTuple, ParamSpec, Protocol, cast
8
5
 
9
6
  from rats import annotations
10
7
 
@@ -14,12 +11,16 @@ from ._namespaces import ProviderNamespaces
14
11
  logger = logging.getLogger(__name__)
15
12
 
16
13
 
17
- class ServiceProvider(Protocol[Tco_ServiceType]):
14
+ class Provider(Protocol[Tco_ServiceType]):
18
15
  @abstractmethod
19
16
  def __call__(self) -> Tco_ServiceType:
20
17
  """Return the service instance."""
21
18
 
22
19
 
20
+ # temporary alias for backwards compatibility
21
+ ServiceProvider = Provider
22
+
23
+
23
24
  class GroupProvider(Protocol[Tco_ServiceType]):
24
25
  @abstractmethod
25
26
  def __call__(self) -> Iterator[Tco_ServiceType]:
@@ -119,8 +120,9 @@ class Container(Protocol):
119
120
  ) -> Iterator[T_ServiceType]:
120
121
  """Retrieve a service group by its id, within a given service namespace."""
121
122
  tates = annotations.get_class_annotations(type(self))
123
+ # containers are a special service namespace that we look through recursively
122
124
  containers = tates.with_namespace(ProviderNamespaces.CONTAINERS)
123
- groups = tates.with_group(namespace, group_id)
125
+ groups = tates.with_group(namespace, cast(NamedTuple, group_id))
124
126
 
125
127
  for annotation in groups.annotations:
126
128
  if not hasattr(self, f"__rats_cache_{annotation.name}"):
@@ -140,33 +142,14 @@ class Container(Protocol):
140
142
  yield from c.get_namespaced_group(namespace, group_id)
141
143
 
142
144
 
143
- @deprecated(
144
- " ".join(
145
- [
146
- "AnnotatedContainer is deprecated and will be removed in the next major release.",
147
- "The functionality has been moved into the apps.Container protocol.",
148
- "Please extend apps.Container directly.",
149
- ]
150
- ),
151
- stacklevel=2,
152
- )
153
- class AnnotatedContainer(Container, abc.ABC):
154
- """
155
- A Container implementation that extracts providers from its annotated methods.
156
-
157
- .. deprecated:: 0.1.3
158
- The behavior of this class has been made the default within ``Container``.
159
- """
160
-
161
-
162
- DEFAULT_CONTAINER_GROUP = ServiceId[Container]("__default__")
145
+ DEFAULT_CONTAINER_GROUP = ServiceId[Container](f"{__name__}:__default__")
163
146
  P = ParamSpec("P")
164
147
 
165
148
 
166
149
  def container(
167
150
  group_id: ServiceId[T_ServiceType] = DEFAULT_CONTAINER_GROUP,
168
151
  ) -> Callable[[Callable[P, T_ServiceType]], Callable[P, T_ServiceType]]:
169
- return annotations.annotation(ProviderNamespaces.CONTAINERS, group_id)
152
+ return annotations.annotation(ProviderNamespaces.CONTAINERS, cast(NamedTuple, group_id))
170
153
 
171
154
 
172
155
  class ServiceNotFoundError(RuntimeError, Generic[T_ServiceType]):
rats/apps/_runtimes.py CHANGED
@@ -1,9 +1,12 @@
1
+ import logging
1
2
  from abc import abstractmethod
2
3
  from collections.abc import Callable
3
4
  from typing import Protocol, final
4
5
 
5
6
  from ._ids import ServiceId, T_ExecutableType
6
7
 
8
+ logger = logging.getLogger(__name__)
9
+
7
10
 
8
11
  class Runtime(Protocol):
9
12
  @abstractmethod
@@ -31,11 +34,17 @@ class Runtime(Protocol):
31
34
 
32
35
  @final
33
36
  class NullRuntime(Runtime):
37
+ _msg: str
38
+
39
+ def __init__(self, msg: str) -> None:
40
+ self._msg = msg
41
+
34
42
  def execute(self, *exe_ids: ServiceId[T_ExecutableType]) -> None:
35
- raise NotImplementedError("NullRuntime does not support execution.")
43
+ logger.error(self._msg)
44
+ raise NotImplementedError(f"NullRuntime cannot execute ids: {exe_ids}")
36
45
 
37
46
  def execute_group(self, *exe_group_ids: ServiceId[T_ExecutableType]) -> None:
38
- raise NotImplementedError("NullRuntime does not support execution.")
47
+ raise NotImplementedError(f"NullRuntime cannot execute groups: {exe_group_ids}")
39
48
 
40
49
  def execute_callable(self, *callables: Callable[[], None]) -> None:
41
- raise NotImplementedError("NullRuntime does not support execution.")
50
+ raise NotImplementedError(f"NullRuntime cannot execute callables: {callables}")
rats/cli/__init__.py CHANGED
@@ -1,22 +1,17 @@
1
1
  """Uses `rats.cli` to streamline the creation of CLI commands written with Click."""
2
2
 
3
- from ._annotations import CommandId, command, group
4
- from ._click import ClickCommandGroup, ClickCommandMapper
5
- from ._executable import ClickExecutable
3
+ from ._annotations import command, get_class_commands, get_class_groups, group
4
+ from ._app import ClickApp
5
+ from ._container import CommandContainer
6
6
  from ._plugin import PluginContainer, PluginServices
7
- from ._plugins import AttachClickCommands, AttachClickGroup, ClickGroupPlugin, CommandContainer
8
7
 
9
8
  __all__ = [
10
- "CommandId",
11
9
  "PluginContainer",
12
10
  "command",
11
+ "get_class_commands",
12
+ "get_class_groups",
13
13
  "group",
14
+ "ClickApp",
14
15
  "PluginServices",
15
- "ClickCommandMapper",
16
- "ClickExecutable",
17
- "ClickGroupPlugin",
18
- "ClickCommandGroup",
19
- "AttachClickCommands",
20
- "AttachClickGroup",
21
16
  "CommandContainer",
22
17
  ]
rats/cli/__main__.py ADDED
@@ -0,0 +1,64 @@
1
+ """..."""
2
+
3
+ import logging
4
+
5
+ import click
6
+
7
+ from rats import apps, cli
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class ExampleCommands(cli.CommandContainer):
13
+ """An example collection of cli commands."""
14
+
15
+ @cli.command()
16
+ @click.option("--exe-id", multiple=True)
17
+ def _run_this(self, exe_id: tuple[str, ...]) -> None:
18
+ """Example cli command called run-this."""
19
+ print(f"running these exes: {exe_id}")
20
+
21
+ @cli.command()
22
+ @click.option("--exe-id", multiple=True)
23
+ def _run_that(self, exe_id: tuple[str, ...]) -> None:
24
+ """Example cli command called run-that."""
25
+ print(f"running those exes: {exe_id}")
26
+
27
+ @cli.group()
28
+ def _run_these(self) -> None:
29
+ """Example cli command called run-these."""
30
+ print("running these sub-things")
31
+
32
+
33
+ @apps.autoscope
34
+ class ExampleServices:
35
+ """
36
+ Services used by the example container.
37
+
38
+ These classes are global constants to identify the provided services.
39
+ """
40
+
41
+ MAIN = apps.ServiceId[apps.Executable]("main")
42
+
43
+
44
+ class ExampleContainer(apps.Container):
45
+ """An example container of services."""
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(
56
+ group=click.Group("example", help="An example application."),
57
+ commands=ExampleCommands(),
58
+ )
59
+
60
+
61
+ if __name__ == "__main__":
62
+ apps.SimpleApplication(runtime_plugin=ExampleContainer).execute(
63
+ ExampleServices.MAIN,
64
+ )
rats/cli/_annotations.py CHANGED
@@ -4,37 +4,38 @@ from collections.abc import Callable
4
4
  from typing import Any, NamedTuple, TypeVar
5
5
 
6
6
  from rats import annotations as anns
7
+ from rats import apps
7
8
 
8
9
 
9
10
  class CommandId(NamedTuple):
10
11
  name: str
11
12
 
12
- # does this api make it impossible to reference a given command that was auto generated?
13
- @staticmethod
14
- def auto() -> CommandId:
15
- return CommandId(name=f"{__name__}:auto")
16
-
17
13
 
14
+ AUTO_COMMAND = CommandId("__auto__")
18
15
  T = TypeVar("T", bound=Callable[[Any], Any])
19
16
 
20
17
 
21
- def command(command_id: CommandId) -> Callable[[T], T]:
18
+ def command(command_id: CommandId = AUTO_COMMAND) -> Callable[..., apps.Executable]:
22
19
  def decorator(fn: T) -> T:
23
- if command_id == CommandId.auto():
20
+ if command_id == AUTO_COMMAND:
24
21
  return anns.annotation("commands", CommandId(fn.__name__.replace("_", "-")))(fn)
25
22
  return anns.annotation("commands", command_id)(fn)
26
23
 
27
- return decorator
24
+ return decorator # type: ignore[reportReturnType]
28
25
 
29
26
 
30
- def group(command_id: CommandId) -> Callable[[T], T]:
27
+ def group(command_id: CommandId = AUTO_COMMAND) -> Callable[..., apps.Executable]:
31
28
  def decorator(fn: T) -> T:
32
- if command_id == CommandId.auto():
29
+ if command_id == AUTO_COMMAND:
33
30
  return anns.annotation("command-groups", CommandId(fn.__name__.replace("_", "-")))(fn)
34
31
  return anns.annotation("commands", command_id)(fn)
35
32
 
36
- return decorator
33
+ return decorator # type: ignore[reportReturnType]
37
34
 
38
35
 
39
36
  def get_class_commands(cls: type) -> anns.AnnotationsContainer:
40
37
  return anns.get_class_annotations(cls).with_namespace("commands")
38
+
39
+
40
+ def get_class_groups(cls: type) -> anns.AnnotationsContainer:
41
+ return anns.get_class_annotations(cls).with_namespace("command-groups")
rats/cli/_app.py ADDED
@@ -0,0 +1,29 @@
1
+ from typing import final
2
+
3
+ import click
4
+
5
+ from rats import apps
6
+
7
+ from ._container import CommandContainer
8
+
9
+
10
+ @final
11
+ class ClickApp(apps.Executable):
12
+ """..."""
13
+
14
+ _group: click.Group
15
+ _commands: CommandContainer
16
+
17
+ def __init__(
18
+ self,
19
+ group: click.Group,
20
+ commands: CommandContainer,
21
+ ) -> None:
22
+ """Not sure this is the right interface."""
23
+ self._group = group
24
+ self._commands = commands
25
+
26
+ def execute(self) -> None:
27
+ """This app executes a click application after letting rats plugins attach commands."""
28
+ self._commands.attach(self._group)
29
+ self._group.main()
rats/cli/_container.py ADDED
@@ -0,0 +1,50 @@
1
+ import logging
2
+ from collections.abc import Callable
3
+ from functools import partial
4
+ from typing import Any, Protocol
5
+
6
+ import click
7
+
8
+ from rats import apps
9
+
10
+ from ._annotations import get_class_commands
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class CommandContainer(apps.Container, Protocol):
16
+ """A container that can attach click commands to a click group."""
17
+
18
+ def attach(self, group: click.Group) -> None:
19
+ """..."""
20
+
21
+ def cb(_method: Callable[..., None], *args: Any, **kwargs: Any) -> None:
22
+ """
23
+ Callback handed to `click.Command`. Calls the method with matching name on this class.
24
+
25
+ When the command is decorated with `@click.params` and `@click.option`, `click` will
26
+ call this callback with the parameters in the order they were defined. This callback
27
+ then calls the method with the same name on this class, passing the parameters in
28
+ reverse order. This is because the method is defined with the parameters in the
29
+ reverse order to the decorator, so we need to reverse them again to get the correct
30
+ order.
31
+ """
32
+ _method(*args, **kwargs)
33
+
34
+ commands = get_class_commands(type(self))
35
+ tates = commands.annotations
36
+
37
+ for tate in tates:
38
+ method = getattr(self, tate.name)
39
+ params = list(reversed(getattr(method, "__click_params__", [])))
40
+ logger.debug(tate.namespace)
41
+ for command in tate.groups:
42
+ if tate.namespace == "commands":
43
+ group.add_command(
44
+ click.Command(
45
+ name=command.name,
46
+ callback=partial(cb, method),
47
+ short_help=method.__doc__,
48
+ params=params,
49
+ )
50
+ )
rats/cli/_plugin.py CHANGED
@@ -7,24 +7,11 @@ from rats import apps
7
7
 
8
8
  @apps.autoscope
9
9
  class _PluginEvents:
10
- @staticmethod
11
- def command_open(cmd_id: apps.ServiceId[apps.Executable]) -> apps.ServiceId[apps.Executable]:
12
- return apps.ServiceId(f"command-open[{cmd_id.name}]")
13
-
14
- @staticmethod
15
- def command_execute(
16
- cmd_id: apps.ServiceId[apps.Executable],
17
- ) -> apps.ServiceId[apps.Executable]:
18
- return apps.ServiceId(f"command-execute[{cmd_id.name}]")
19
-
20
- @staticmethod
21
- def command_close(cmd_id: apps.ServiceId[apps.Executable]) -> apps.ServiceId[apps.Executable]:
22
- return apps.ServiceId(f"command-close[{cmd_id.name}]")
10
+ pass
23
11
 
24
12
 
25
13
  @apps.autoscope
26
14
  class PluginServices:
27
- ROOT_COMMAND = apps.ServiceId[apps.Executable]("root-command")
28
15
  EVENTS = _PluginEvents
29
16
 
30
17
  @staticmethod
@@ -45,24 +32,3 @@ class PluginContainer(apps.Container):
45
32
 
46
33
  def __init__(self, app: apps.Container) -> None:
47
34
  self._app = app
48
-
49
- @apps.service(PluginServices.ROOT_COMMAND)
50
- def _root_command(self) -> apps.Executable:
51
- def run() -> None:
52
- runtime = self._app.get(apps.AppServices.RUNTIME)
53
- runtime.execute_group(
54
- PluginServices.EVENTS.command_open(PluginServices.ROOT_COMMAND),
55
- PluginServices.EVENTS.command_execute(PluginServices.ROOT_COMMAND),
56
- PluginServices.EVENTS.command_close(PluginServices.ROOT_COMMAND),
57
- )
58
-
59
- return apps.App(run)
60
-
61
- @apps.fallback_group(PluginServices.EVENTS.command_execute(PluginServices.ROOT_COMMAND))
62
- def _default_command(self) -> apps.Executable:
63
- group = self._app.get(PluginServices.click_command(PluginServices.ROOT_COMMAND))
64
- return apps.App(lambda: group())
65
-
66
- @apps.service(PluginServices.click_command(PluginServices.ROOT_COMMAND))
67
- def _root_click_command(self) -> click.Group:
68
- return click.Group("groot")
rats/logs/py.typed ADDED
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rats-apps
3
- Version: 0.1.3.dev20240812192218
3
+ Version: 0.2.0.dev20240813092513
4
4
  Summary: research analysis tools for building applications
5
5
  Home-page: https://github.com/microsoft/rats/
6
6
  License: MIT
@@ -0,0 +1,32 @@
1
+ rats/annotations/__init__.py,sha256=wsGhRQzZrV2oJTnBAX0aGgpyT1kYT235jkP3Wb8BTRY,498
2
+ rats/annotations/__main__.py,sha256=vlzQOM9y82P0OL5tYcmSM_4jTg0s8jayAcvEoi9cBvI,1065
3
+ rats/annotations/_functions.py,sha256=2rIWruEVZ-mBviS_7un88ZODbfGmiJgzIp4CXZoAgEE,4346
4
+ rats/annotations/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ rats/apps/__init__.py,sha256=LiN5DSEwzB8IeAvTTEH7tQF5btD9QT4WNQwu7_48ZyU,1493
6
+ rats/apps/__main__.py,sha256=KjdadN4rdP0xhWiLzdmtCsXejWx_gxOK-ah-L1r1dTI,1818
7
+ rats/apps/_annotations.py,sha256=yOf4MKFGQz3x4hpaWgMf2y2GlbXtbIh4uED15voXzyY,2566
8
+ rats/apps/_composite_container.py,sha256=s_of6NyyrjFVYWGVehyEHe9WJIPRCnbB-tyWyNF8zyc,585
9
+ rats/apps/_container.py,sha256=sghUmXUFtzm2YwvhW6H1vlxX6-knxAsB6CNBApbJhK0,5851
10
+ rats/apps/_executables.py,sha256=QJ5_UPdZPmDQ1a3cLRJDUoeUMzNMwa3ZHYhYeS3AVq4,971
11
+ rats/apps/_ids.py,sha256=T8Onrj79t8NPfBMQBk0xI6fIWDKF0m2JfFNrdtXAbWg,353
12
+ rats/apps/_namespaces.py,sha256=THUV_Xj5PtweC23Ob-zsSpk8exC4fT-qRwjpQ6IDm0U,188
13
+ rats/apps/_plugin_container.py,sha256=wmaBgxmvKo82ue9CrKHRXafgik5wIXh8XkEYMfhcTjs,1530
14
+ rats/apps/_plugins.py,sha256=mvSYQPi11wTGZpM4umoyD37Rc8CQX8nt5eAJbmLrBFM,688
15
+ rats/apps/_runtimes.py,sha256=qKhsuaH3ZesSP4pGwRbS8zj2mwapysSxyS_F9pkUtM4,1738
16
+ rats/apps/_scoping.py,sha256=EIUopw3b38CEV57kCmSKQTAnQQMcXHZ_vwtk9t0K__g,1453
17
+ rats/apps/_simple_apps.py,sha256=n-3zeHY3iintZ9LN597c7zDHv3DiIdl7c8NTk0gUk1Y,5477
18
+ rats/apps/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ rats/cli/__init__.py,sha256=_g9xWv6Uy73AudzCn-A9JKP-IpfZMRgvoO1b64dAxPw,464
20
+ rats/cli/__main__.py,sha256=3JZ7mrTTrrODQHutefm2zJp1-cQQB7I5-1xhYw7SMBU,1656
21
+ rats/cli/_annotations.py,sha256=xSOfGFRYI2s9MTUdXgJ-ZcgcaD6EntZpiAuMqAVh0sI,1316
22
+ rats/cli/_app.py,sha256=NjJfXKZYBdd1CZuLbrXyUFB_wRJQah1Rvtxe_zj4y_M,641
23
+ rats/cli/_container.py,sha256=VddjrsH1lqiarJ6rXf4KUbuNtNXEduCr38UH_TwGgFE,1872
24
+ rats/cli/_plugin.py,sha256=o5emP-E0LLOGvD14ZBYNY6h407pngrJf8ODMB5Wdd8U,711
25
+ rats/cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ rats/logs/__init__.py,sha256=fCn4pfpYiAcTtt5CsnUZX68CjOB3KJHxMSiYxsma4qE,183
27
+ rats/logs/_plugin.py,sha256=eAAG4ci-XS9A9ituXj9PrcI6zH-xlCqhJlUbSGC-MYM,2175
28
+ rats/logs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ rats_apps-0.2.0.dev20240813092513.dist-info/METADATA,sha256=HItBWvbdKnqpKz3vSiVb-swPARAqeXrjm2Gqup6Llfk,774
30
+ rats_apps-0.2.0.dev20240813092513.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
31
+ rats_apps-0.2.0.dev20240813092513.dist-info/entry_points.txt,sha256=9oOvf2loQr5ACWQgvuu9Q3KZIVIxKE5Aa-rLuUII5WQ,91
32
+ rats_apps-0.2.0.dev20240813092513.dist-info/RECORD,,
rats/cli/_click.py DELETED
@@ -1,38 +0,0 @@
1
- from collections.abc import Callable, Mapping
2
- from typing import final
3
-
4
- import click
5
-
6
-
7
- class ClickCommandMapper:
8
- _commands: Mapping[str, Callable[[], click.Command]]
9
-
10
- def __init__(
11
- self,
12
- commands: Mapping[str, Callable[[], click.Command]],
13
- ) -> None:
14
- self._commands = commands
15
-
16
- def names(self) -> frozenset[str]:
17
- return frozenset(self._commands.keys())
18
-
19
- def get(self, name: str) -> click.Command:
20
- if name not in self._commands:
21
- raise ValueError(f"Command {name} not found")
22
-
23
- return self._commands[name]()
24
-
25
-
26
- @final
27
- class ClickCommandGroup(click.Group):
28
- _mapper: ClickCommandMapper
29
-
30
- def __init__(self, name: str, mapper: ClickCommandMapper) -> None:
31
- super().__init__(name=name)
32
- self._mapper = mapper
33
-
34
- def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
35
- return self._mapper.get(cmd_name)
36
-
37
- def list_commands(self, ctx: click.Context) -> list[str]:
38
- return list(self._mapper.names())
rats/cli/_executable.py DELETED
@@ -1,23 +0,0 @@
1
- import click
2
-
3
- from rats import apps
4
-
5
- from ._plugins import ClickGroupPlugin
6
-
7
-
8
- class ClickExecutable(apps.Executable):
9
- _command: apps.ServiceProvider[click.Group]
10
- _plugins: apps.PluginRunner[ClickGroupPlugin]
11
-
12
- def __init__(
13
- self,
14
- command: apps.ServiceProvider[click.Group],
15
- plugins: apps.PluginRunner[ClickGroupPlugin],
16
- ) -> None:
17
- self._command = command
18
- self._plugins = plugins
19
-
20
- def execute(self) -> None:
21
- cmd = self._command()
22
- self._plugins.apply(lambda plugin: plugin.on_group_open(cmd))
23
- cmd()
rats/cli/_plugins.py DELETED
@@ -1,81 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from abc import abstractmethod
4
- from collections.abc import Callable, Iterator
5
- from functools import partial
6
- from typing import Any, Protocol
7
-
8
- import click
9
-
10
- from rats import apps
11
-
12
- from ._annotations import get_class_commands
13
-
14
-
15
- class ClickGroupPlugin(Protocol):
16
- @abstractmethod
17
- def on_group_open(self, group: click.Group) -> None:
18
- pass
19
-
20
-
21
- class AttachClickCommands(ClickGroupPlugin):
22
- """When a group is opened, attach a set of commands to it."""
23
-
24
- _commands: Iterator[click.Command]
25
-
26
- def __init__(self, commands: Iterator[click.Command]) -> None:
27
- self._commands = commands
28
-
29
- def on_group_open(self, group: click.Group) -> None:
30
- for command in self._commands:
31
- group.add_command(command)
32
-
33
-
34
- class AttachClickGroup(ClickGroupPlugin):
35
- _group: apps.ServiceProvider[click.Group]
36
- _plugins: apps.PluginRunner[ClickGroupPlugin]
37
-
38
- def __init__(
39
- self,
40
- group: apps.ServiceProvider[click.Group],
41
- plugins: apps.PluginRunner[ClickGroupPlugin],
42
- ) -> None:
43
- self._group = group
44
- self._plugins = plugins
45
-
46
- def on_group_open(self, group: click.Group) -> None:
47
- cmd = self._group()
48
- self._plugins.apply(lambda plugin: plugin.on_group_open(cmd))
49
- group.add_command(cmd)
50
-
51
-
52
- class CommandContainer(ClickGroupPlugin):
53
- def on_group_open(self, group: click.Group) -> None:
54
- def cb(_method: Callable[[Any], Any], *args: Any, **kwargs: Any) -> None:
55
- """
56
- Callback handed to `click.Command`. Calls the method with matching name on this class.
57
-
58
- When the command is decorated with `@click.params` and `@click.option`, `click` will
59
- call this callback with the parameters in the order they were defined. This callback
60
- then calls the method with the same name on this class, passing the parameters in
61
- reverse order. This is because the method is defined with the parameters in the
62
- reverse order to the decorator, so we need to reverse them again to get the correct
63
- order.
64
- """
65
- _method(*args, **kwargs)
66
-
67
- commands = get_class_commands(type(self))
68
- tates = commands.annotations
69
-
70
- for tate in tates:
71
- method = getattr(self, tate.name)
72
- params = list(reversed(getattr(method, "__click_params__", [])))
73
- for command in tate.groups:
74
- group.add_command(
75
- click.Command(
76
- name=command.name,
77
- callback=partial(cb, method),
78
- short_help=method.__doc__,
79
- params=params,
80
- )
81
- )
@@ -1,29 +0,0 @@
1
- rats/annotations/__init__.py,sha256=wsGhRQzZrV2oJTnBAX0aGgpyT1kYT235jkP3Wb8BTRY,498
2
- rats/annotations/_functions.py,sha256=ziAZOqS1lojfjDjZW7qPYNJUAFuLBWtVAoPsmJqxqpE,4356
3
- rats/annotations/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- rats/apps/__init__.py,sha256=z_6eujGnU6pW12xTRR1caTJzv5v8VEbt8yLmwnMBiik,1511
5
- rats/apps/_annotations.py,sha256=6qBqjxfBBZyVMLT3COGgQ-flQL6Wkuzp_TtndD4TwmE,2458
6
- rats/apps/_composite_container.py,sha256=s_of6NyyrjFVYWGVehyEHe9WJIPRCnbB-tyWyNF8zyc,585
7
- rats/apps/_container.py,sha256=GTPu6qmVCy69w87kMsQcL5BJavgW9O9RxH0z0zQu3fY,6242
8
- rats/apps/_executables.py,sha256=QJ5_UPdZPmDQ1a3cLRJDUoeUMzNMwa3ZHYhYeS3AVq4,971
9
- rats/apps/_ids.py,sha256=T8Onrj79t8NPfBMQBk0xI6fIWDKF0m2JfFNrdtXAbWg,353
10
- rats/apps/_namespaces.py,sha256=THUV_Xj5PtweC23Ob-zsSpk8exC4fT-qRwjpQ6IDm0U,188
11
- rats/apps/_plugin_container.py,sha256=wmaBgxmvKo82ue9CrKHRXafgik5wIXh8XkEYMfhcTjs,1530
12
- rats/apps/_plugins.py,sha256=mvSYQPi11wTGZpM4umoyD37Rc8CQX8nt5eAJbmLrBFM,688
13
- rats/apps/_runtimes.py,sha256=nQWwC-O-j0bKCEkGpBSXhdtE0-X2ACeP7zXZfqaPGqg,1545
14
- rats/apps/_scoping.py,sha256=EIUopw3b38CEV57kCmSKQTAnQQMcXHZ_vwtk9t0K__g,1453
15
- rats/apps/_simple_apps.py,sha256=n-3zeHY3iintZ9LN597c7zDHv3DiIdl7c8NTk0gUk1Y,5477
16
- rats/apps/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- rats/cli/__init__.py,sha256=dMrubxMJocbl8QoI_3fKI0Dt0QHYAM5Mxh5Vj8OaX-w,664
18
- rats/cli/_annotations.py,sha256=0dI8hu_y754Y53Pka1-mGEgHjjVcnIOGd8l1SFx8OBY,1190
19
- rats/cli/_click.py,sha256=7-ClnYSW4poFr_B-Q6NT45DnMF1XL7ntUgwQqQ7q_eo,1036
20
- rats/cli/_executable.py,sha256=kAQ9hImv3hBaScu6e19o_BMvl7tdYJql38E76S3FjSk,580
21
- rats/cli/_plugin.py,sha256=j9exzjL4sTCrGvine0rxumdfl0RPGpOFk7Ysk73aKWk,2278
22
- rats/cli/_plugins.py,sha256=H3-QdaICPJhCC5FkLHdXpwqe7Z0mpvsenakhNiPllC8,2739
23
- rats/cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- rats/logs/__init__.py,sha256=fCn4pfpYiAcTtt5CsnUZX68CjOB3KJHxMSiYxsma4qE,183
25
- rats/logs/_plugin.py,sha256=eAAG4ci-XS9A9ituXj9PrcI6zH-xlCqhJlUbSGC-MYM,2175
26
- rats_apps-0.1.3.dev20240812192218.dist-info/METADATA,sha256=kHFSAtQ6vCGDztELYoYp6GCYz76XPL6RPtdU-n6sojA,774
27
- rats_apps-0.1.3.dev20240812192218.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
28
- rats_apps-0.1.3.dev20240812192218.dist-info/entry_points.txt,sha256=9oOvf2loQr5ACWQgvuu9Q3KZIVIxKE5Aa-rLuUII5WQ,91
29
- rats_apps-0.1.3.dev20240812192218.dist-info/RECORD,,