python-tty 0.1.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.
Files changed (40) hide show
  1. python_tty/__init__.py +15 -0
  2. python_tty/commands/__init__.py +24 -0
  3. python_tty/commands/core.py +119 -0
  4. python_tty/commands/decorators.py +58 -0
  5. python_tty/commands/examples/__init__.py +8 -0
  6. python_tty/commands/examples/root_commands.py +33 -0
  7. python_tty/commands/examples/sub_commands.py +11 -0
  8. python_tty/commands/general.py +107 -0
  9. python_tty/commands/mixins.py +52 -0
  10. python_tty/commands/registry.py +185 -0
  11. python_tty/config/__init__.py +9 -0
  12. python_tty/config/config.py +35 -0
  13. python_tty/console_factory.py +100 -0
  14. python_tty/consoles/__init__.py +16 -0
  15. python_tty/consoles/core.py +140 -0
  16. python_tty/consoles/decorators.py +42 -0
  17. python_tty/consoles/examples/__init__.py +8 -0
  18. python_tty/consoles/examples/root_console.py +34 -0
  19. python_tty/consoles/examples/sub_console.py +34 -0
  20. python_tty/consoles/loader.py +14 -0
  21. python_tty/consoles/manager.py +146 -0
  22. python_tty/consoles/registry.py +102 -0
  23. python_tty/exceptions/__init__.py +7 -0
  24. python_tty/exceptions/console_exception.py +12 -0
  25. python_tty/executor/__init__.py +10 -0
  26. python_tty/executor/executor.py +335 -0
  27. python_tty/executor/models.py +38 -0
  28. python_tty/frontends/__init__.py +0 -0
  29. python_tty/meta/__init__.py +0 -0
  30. python_tty/ui/__init__.py +13 -0
  31. python_tty/ui/events.py +55 -0
  32. python_tty/ui/output.py +102 -0
  33. python_tty/utils/__init__.py +13 -0
  34. python_tty/utils/table.py +126 -0
  35. python_tty/utils/tokenize.py +45 -0
  36. python_tty/utils/ui_logger.py +17 -0
  37. python_tty-0.1.0.dist-info/METADATA +66 -0
  38. python_tty-0.1.0.dist-info/RECORD +40 -0
  39. python_tty-0.1.0.dist-info/WHEEL +5 -0
  40. python_tty-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,100 @@
1
+ import asyncio
2
+ import threading
3
+
4
+ from python_tty.config import Config
5
+ from python_tty.consoles.loader import load_consoles
6
+ from python_tty.consoles.manager import ConsoleManager
7
+ from python_tty.consoles.registry import REGISTRY
8
+ from python_tty.executor import CommandExecutor
9
+
10
+
11
+ class ConsoleFactory:
12
+ """Bootstrap console system by loading modules and registering consoles.
13
+
14
+ Example:
15
+ # Pass your business core instance here to make it available
16
+ # to all console/commands classes via the manager service.
17
+ factory = ConsoleFactory(object())
18
+ factory.start()
19
+
20
+ Notes:
21
+ - To auto-load consoles, update DEFAULT_CONSOLE_MODULES in
22
+ python_tty.consoles.loader with the modules that define your console classes.
23
+ - Or call load_consoles(...) yourself before starting to register
24
+ consoles via their decorators.
25
+ """
26
+ def __init__(self, service=None, config: Config = None):
27
+ if config is None:
28
+ config = Config()
29
+ self.config = config
30
+ self.executor = CommandExecutor(config=config.executor)
31
+ self._executor_loop = None
32
+ self._executor_thread = None
33
+ self.manager = ConsoleManager(
34
+ service=service,
35
+ executor=self.executor,
36
+ on_shutdown=self.shutdown,
37
+ config=config.console_manager,
38
+ )
39
+ load_consoles()
40
+ REGISTRY.register_all(self.manager)
41
+
42
+ def start(self):
43
+ """Start the console loop with the registered root console."""
44
+ self._start_executor_if_needed()
45
+ self.manager.run()
46
+
47
+ def start_executor(self, loop=None):
48
+ """Start executor workers on the provided asyncio loop."""
49
+ self.executor.start(loop=loop)
50
+
51
+ def shutdown_executor(self, wait=True, timeout=None):
52
+ """Shutdown executor workers after RPC/TTY stop."""
53
+ loop = self._executor_loop
54
+ if loop is not None and loop.is_running():
55
+ self.executor.shutdown_threadsafe(wait=wait, timeout=timeout)
56
+ loop.call_soon_threadsafe(loop.stop)
57
+ if self._executor_thread is not None and wait:
58
+ self._executor_thread.join(timeout)
59
+ return None
60
+ return self.executor.shutdown_threadsafe(wait=wait, timeout=timeout)
61
+
62
+ def shutdown(self):
63
+ """Shutdown all resources owned by the factory."""
64
+ if self.config.console_factory.shutdown_executor:
65
+ self.shutdown_executor()
66
+
67
+ def _start_executor_if_needed(self):
68
+ if not self.config.console_factory.start_executor:
69
+ return
70
+ if self._executor_thread is not None and self._executor_thread.is_alive():
71
+ return
72
+ if self.config.console_factory.executor_in_thread:
73
+ self._start_executor_thread()
74
+ else:
75
+ self.start_executor()
76
+
77
+ def _start_executor_thread(self):
78
+ if self._executor_thread is not None and self._executor_thread.is_alive():
79
+ return
80
+ loop = asyncio.new_event_loop()
81
+ self._executor_loop = loop
82
+
83
+ def _run_loop():
84
+ asyncio.set_event_loop(loop)
85
+ self.start_executor(loop=loop)
86
+ loop.run_forever()
87
+ pending = asyncio.all_tasks(loop)
88
+ if pending:
89
+ for task in pending:
90
+ task.cancel()
91
+ loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
92
+ loop.close()
93
+
94
+ self._executor_thread = threading.Thread(
95
+ target=_run_loop,
96
+ name=self.config.console_factory.executor_thread_name,
97
+ daemon=True,
98
+ )
99
+ self._executor_thread.start()
100
+
@@ -0,0 +1,16 @@
1
+ from python_tty.consoles.core import BaseConsole, MainConsole, SubConsole
2
+ from python_tty.consoles.decorators import root, sub, multi
3
+ from python_tty.consoles.registry import REGISTRY
4
+ from python_tty.consoles.loader import DEFAULT_CONSOLE_MODULES
5
+
6
+ __all__ = [
7
+ "BaseConsole",
8
+ "MainConsole",
9
+ "SubConsole",
10
+ "DEFAULT_CONSOLE_MODULES",
11
+ "REGISTRY",
12
+ "root",
13
+ "sub",
14
+ "multi",
15
+ ]
16
+
@@ -0,0 +1,140 @@
1
+ import uuid
2
+ from abc import ABC
3
+
4
+ from prompt_toolkit import PromptSession
5
+
6
+ from python_tty.ui.events import UIEventListener, UIEventSpeaker
7
+ from python_tty.executor import Invocation
8
+ from python_tty.exceptions.console_exception import ConsoleExit, ConsoleInitException, SubConsoleExit
9
+ from python_tty.ui.output import proxy_print
10
+ from python_tty.utils import split_cmd
11
+
12
+
13
+ class BaseConsole(ABC, UIEventListener):
14
+ forward_console = None
15
+
16
+ def __init__(self, console_message, console_style, parent=None, manager=None):
17
+ self.uid = str(uuid.uuid4())
18
+ self.parent = parent
19
+ self.manager = manager if manager is not None else getattr(parent, "manager", None)
20
+ if self.manager is not None:
21
+ self.service = self.manager.service
22
+ else:
23
+ self.service = getattr(parent, "service", None)
24
+ BaseConsole.forward_console = self
25
+ self.commands = self._build_commands()
26
+ self.session = PromptSession(console_message, style=console_style,
27
+ completer=self.commands.completer,
28
+ validator=self.commands.validator)
29
+ if isinstance(self.service, UIEventSpeaker):
30
+ self.service.add_event_listener(self)
31
+
32
+ def init_commands(self):
33
+ return None
34
+
35
+ def _build_commands(self):
36
+ from python_tty.commands import COMMAND_REGISTRY
37
+ commands_cls = getattr(self.__class__, "__commands_cls__", None)
38
+ if commands_cls is None:
39
+ commands = self.init_commands()
40
+ if commands is not None:
41
+ return commands
42
+ commands_cls = COMMAND_REGISTRY.get_commands_cls(self.__class__)
43
+ if commands_cls is None:
44
+ from python_tty.commands.mixins import DefaultCommands
45
+ commands_cls = DefaultCommands
46
+ return commands_cls(self)
47
+
48
+ def handler_event(self, event):
49
+ if BaseConsole.forward_console is not None and BaseConsole.forward_console == self:
50
+ proxy_print(event.msg, event.level)
51
+
52
+ def run(self, invocation: Invocation):
53
+ command_def = self.commands.get_command_def_by_id(invocation.command_id)
54
+ if command_def is None and invocation.command_name is not None:
55
+ command_def = self.commands.get_command_def(invocation.command_name)
56
+ if command_def is None:
57
+ raise ValueError(f"Command not found: {invocation.command_id}")
58
+ if len(invocation.argv) == 0:
59
+ return command_def.func(self.commands)
60
+ return command_def.func(self.commands, *invocation.argv)
61
+
62
+ def execute(self, cmd):
63
+ try:
64
+ invocation, token = self._build_invocation(cmd)
65
+ if invocation is None:
66
+ if token != "":
67
+ self.cmd_invoke_miss(cmd)
68
+ return
69
+ executor = getattr(self.manager, "executor", None) if self.manager is not None else None
70
+ if executor is None:
71
+ self.run(invocation)
72
+ return
73
+ executor.submit_threadsafe(invocation, handler=self.run)
74
+ except ValueError:
75
+ return
76
+
77
+ def _build_invocation(self, cmd):
78
+ token, arg_text, _ = split_cmd(cmd)
79
+ if token == "":
80
+ return None, token
81
+ command_def = self.commands.get_command_def(token)
82
+ if command_def is None:
83
+ return None, token
84
+ param_list = self.commands.deserialize_args(command_def, arg_text)
85
+ command_id = self.commands.get_command_id(token)
86
+ invocation = Invocation(
87
+ run_id=str(uuid.uuid4()),
88
+ source="tty",
89
+ console_id=self.uid,
90
+ command_id=command_id,
91
+ command_name=token,
92
+ argv=param_list,
93
+ raw_cmd=cmd,
94
+ )
95
+ return invocation, token
96
+
97
+ def cmd_invoke_miss(self, cmd: str):
98
+ pass
99
+
100
+ def clean_console(self):
101
+ if isinstance(self.service, UIEventSpeaker):
102
+ self.service.remove_event_listener(self)
103
+ if BaseConsole.forward_console == self:
104
+ BaseConsole.forward_console = self.parent
105
+
106
+ def start(self):
107
+ if self.manager is not None:
108
+ self.manager.run_with(self)
109
+ return
110
+ while True:
111
+ try:
112
+ cmd = self.session.prompt()
113
+ self.execute(cmd)
114
+ except ConsoleExit:
115
+ if self.parent is None:
116
+ break
117
+ else:
118
+ raise ConsoleExit
119
+ except SubConsoleExit:
120
+ break
121
+ except (KeyboardInterrupt, ValueError):
122
+ # FIXME: Careful deal this!
123
+ # continue
124
+ break
125
+ self.clean_console()
126
+
127
+
128
+ class MainConsole(BaseConsole):
129
+ def __init__(self, console_message, console_style, parent=None, manager=None):
130
+ if parent is not None:
131
+ raise ConsoleInitException("MainConsole parent must be None")
132
+ super().__init__(console_message, console_style, parent=None, manager=manager)
133
+
134
+
135
+ class SubConsole(BaseConsole):
136
+ def __init__(self, console_message, console_style, parent=None, manager=None):
137
+ if parent is None:
138
+ raise ConsoleInitException("SubConsole parent is None")
139
+ super().__init__(console_message, console_style, parent=parent, manager=manager)
140
+
@@ -0,0 +1,42 @@
1
+ from python_tty.consoles.registry import REGISTRY
2
+ from python_tty.exceptions.console_exception import ConsoleInitException
3
+
4
+
5
+ def root(console_cls):
6
+ """Mark a MainConsole subclass as the single root console."""
7
+ from python_tty.consoles import MainConsole
8
+ if not issubclass(console_cls, MainConsole):
9
+ raise ConsoleInitException("Root console must inherit MainConsole")
10
+ REGISTRY.set_root(console_cls)
11
+ return console_cls
12
+
13
+
14
+ def sub(parent_name):
15
+ """Register a SubConsole subclass to a single parent console by name."""
16
+ if not isinstance(parent_name, str) or parent_name == "":
17
+ raise ConsoleInitException("Sub console parent name is empty")
18
+
19
+ def decorator(console_cls):
20
+ from python_tty.consoles import SubConsole
21
+ if not issubclass(console_cls, SubConsole):
22
+ raise ConsoleInitException("Sub console must inherit SubConsole")
23
+ REGISTRY.add_sub(console_cls, parent_name)
24
+ return console_cls
25
+
26
+ return decorator
27
+
28
+
29
+ def multi(parent_map):
30
+ """Register a reusable SubConsole for multiple parents with instance names."""
31
+ if not isinstance(parent_map, dict) or len(parent_map) <= 0:
32
+ raise ConsoleInitException("Multi console mapping is empty")
33
+
34
+ def decorator(console_cls):
35
+ from python_tty.consoles import SubConsole
36
+ if not issubclass(console_cls, SubConsole):
37
+ raise ConsoleInitException("Multi console must inherit SubConsole")
38
+ REGISTRY.add_multi(console_cls, parent_map)
39
+ return console_cls
40
+
41
+ return decorator
42
+
@@ -0,0 +1,8 @@
1
+ from python_tty.consoles.examples.root_console import RootConsole
2
+ from python_tty.consoles.examples.sub_console import ModuleConsole
3
+
4
+ __all__ = [
5
+ "RootConsole",
6
+ "ModuleConsole",
7
+ ]
8
+
@@ -0,0 +1,34 @@
1
+ from prompt_toolkit.styles import Style
2
+
3
+ from python_tty.commands.decorators import commands
4
+ from python_tty.commands.examples.root_commands import RootCommands
5
+ from python_tty.consoles import MainConsole, root
6
+
7
+ message = [
8
+ ('class:host', 'vef1'),
9
+ ('class:prompt', ' '),
10
+ ('class:symbol', '>'),
11
+ ('class:prompt', ' ')
12
+ ]
13
+ style = Style.from_dict({
14
+ # User input(default text)
15
+ '': '',
16
+ 'host': '#00aa00 underline',
17
+ 'symbol': '#00ffff'
18
+ })
19
+
20
+
21
+ @root
22
+ @commands(RootCommands)
23
+ class RootConsole(MainConsole):
24
+ console_name = "root"
25
+
26
+ def __init__(self, parent=None, manager=None):
27
+ super().__init__(message, style, parent=parent, manager=manager)
28
+
29
+ def cmd_invoke_miss(self, cmd: str):
30
+ print(f"Invoke os shell command [{cmd}]")
31
+
32
+ def clean_console(self):
33
+ super().clean_console()
34
+
@@ -0,0 +1,34 @@
1
+ from prompt_toolkit.styles import Style
2
+
3
+ from python_tty.commands.decorators import commands
4
+ from python_tty.commands.examples.sub_commands import SubCommands
5
+ from python_tty.consoles import SubConsole, sub
6
+
7
+
8
+ style = Style.from_dict({
9
+ # User input(default text)
10
+ '': '',
11
+ 'host': '#00aaaa',
12
+ 'symbol': '#00ffaa'
13
+ })
14
+
15
+
16
+ @sub("root")
17
+ @commands(SubCommands)
18
+ class ModuleConsole(SubConsole):
19
+ console_name = "module"
20
+
21
+ def __init__(self, module_name=None, parent=None, manager=None):
22
+ if module_name is None:
23
+ module_name = self.console_name
24
+ message = [
25
+ ('class:host', module_name),
26
+ ('class:prompt', ' '),
27
+ ('class:symbol', '>'),
28
+ ('class:prompt', ' ')
29
+ ]
30
+ super().__init__(message, style, parent=parent, manager=manager)
31
+
32
+ def clean_console(self):
33
+ super().clean_console()
34
+
@@ -0,0 +1,14 @@
1
+ import importlib
2
+
3
+
4
+ DEFAULT_CONSOLE_MODULES = ()
5
+
6
+
7
+ def load_consoles(modules=None):
8
+ """Import console modules so class decorators can register themselves."""
9
+ if modules is None:
10
+ modules = DEFAULT_CONSOLE_MODULES
11
+ elif not isinstance(modules, (list, tuple)):
12
+ modules = [modules]
13
+ for module in modules:
14
+ importlib.import_module(module)
@@ -0,0 +1,146 @@
1
+ from prompt_toolkit.patch_stdout import patch_stdout
2
+
3
+ from python_tty.config import ConsoleManagerConfig
4
+ from python_tty.exceptions.console_exception import ConsoleExit, ConsoleInitException, SubConsoleExit
5
+ from python_tty.ui.events import UIEventLevel, UIEventSpeaker
6
+ from python_tty.ui.output import get_output_router, proxy_print
7
+
8
+
9
+ class ConsoleEntry:
10
+ def __init__(self, console_cls, kwargs):
11
+ self.console_cls = console_cls
12
+ self.kwargs = kwargs
13
+
14
+
15
+ class ConsoleManager:
16
+ def __init__(self, service=None, executor=None, on_shutdown=None, config: ConsoleManagerConfig = None):
17
+ self._registry = {}
18
+ self._stack = []
19
+ self._console_tree = None
20
+ self._root_name = None
21
+ self._service = service
22
+ self._executor = executor
23
+ self._on_shutdown = on_shutdown
24
+ self._shutdown_called = False
25
+ self._config = config if config is not None else ConsoleManagerConfig()
26
+ self._output_router = self._config.output_router or get_output_router()
27
+ self._use_patch_stdout = self._config.use_patch_stdout
28
+ self._warn_service_if_needed(service)
29
+
30
+ def register(self, name, console_cls, **kwargs):
31
+ self._registry[name] = ConsoleEntry(console_cls, kwargs)
32
+
33
+ def is_registered(self, name):
34
+ return name in self._registry
35
+
36
+ def set_root_name(self, root_name):
37
+ self._root_name = root_name
38
+
39
+ def set_console_tree(self, console_tree):
40
+ self._console_tree = console_tree
41
+
42
+ @property
43
+ def console_tree(self):
44
+ return self._console_tree
45
+
46
+ @property
47
+ def service(self):
48
+ return self._service
49
+
50
+ @property
51
+ def executor(self):
52
+ return self._executor
53
+
54
+ def clean(self):
55
+ if self._shutdown_called:
56
+ return
57
+ self._shutdown_called = True
58
+ if self._output_router is not None:
59
+ self._output_router.clear_session()
60
+ if callable(self._on_shutdown):
61
+ self._on_shutdown()
62
+
63
+ def _warn_service_if_needed(self, service):
64
+ if service is not None and not isinstance(service, UIEventSpeaker):
65
+ msg = f"The Service core[{service.__class__}] doesn't extend the [UIEventSpeaker],"\
66
+ " which may affect the output of the Service core on the UI!"
67
+ proxy_print(msg, UIEventLevel.WARNING)
68
+
69
+ @property
70
+ def current(self):
71
+ return self._stack[-1] if self._stack else None
72
+
73
+ def push(self, name, **kwargs):
74
+ if name not in self._registry:
75
+ raise KeyError(f"Console [{name}] not registered")
76
+ entry = self._registry[name]
77
+ init_kwargs = dict(entry.kwargs)
78
+ init_kwargs.update(kwargs)
79
+ parent = self.current
80
+ console = entry.console_cls(parent=parent, manager=self, **init_kwargs)
81
+ console.console_name = name
82
+ self._stack.append(console)
83
+ self._bind_output_router()
84
+ return console
85
+
86
+ def pop(self):
87
+ if not self._stack:
88
+ return None
89
+ console = self._stack.pop()
90
+ console.clean_console()
91
+ self._bind_output_router()
92
+ return self.current
93
+
94
+ def run(self, root_name=None, **kwargs):
95
+ if root_name is None:
96
+ root_name = self._root_name
97
+ if root_name is None:
98
+ raise ConsoleInitException("Root console not registered")
99
+ self.push(root_name, **kwargs)
100
+ self._loop()
101
+
102
+ def run_with(self, root_console):
103
+ if root_console.parent is not None:
104
+ raise ConsoleInitException("Root console parent must be None")
105
+ root_console.manager = self
106
+ if getattr(root_console, "console_name", None) is None:
107
+ root_console.console_name = root_console.__class__.__name__.lower()
108
+ self._stack.append(root_console)
109
+ self._bind_output_router()
110
+ self._loop()
111
+
112
+ def _loop(self):
113
+ try:
114
+ while self._stack:
115
+ try:
116
+ self._bind_output_router()
117
+ cmd = self._prompt()
118
+ self.current.execute(cmd)
119
+ except SubConsoleExit:
120
+ self.pop()
121
+ except ConsoleExit:
122
+ while self._stack:
123
+ self.pop()
124
+ break
125
+ except (KeyboardInterrupt, ValueError):
126
+ while self._stack:
127
+ self.pop()
128
+ break
129
+ finally:
130
+ self.clean()
131
+
132
+ def _prompt(self):
133
+ if self._use_patch_stdout:
134
+ with patch_stdout():
135
+ return self.current.session.prompt()
136
+ return self.current.session.prompt()
137
+
138
+ def _bind_output_router(self):
139
+ if self._output_router is None:
140
+ return
141
+ current = self.current
142
+ if current is None:
143
+ self._output_router.clear_session()
144
+ return
145
+ self._output_router.bind_session(current.session)
146
+
@@ -0,0 +1,102 @@
1
+ from python_tty.exceptions.console_exception import ConsoleInitException
2
+
3
+
4
+ def _get_console_name(console_cls):
5
+ name = getattr(console_cls, "console_name", None)
6
+ if name:
7
+ return name
8
+ name = getattr(console_cls, "CONSOLE_NAME", None)
9
+ if name:
10
+ return name
11
+ return console_cls.__name__.lower()
12
+
13
+
14
+ def _get_console_kwargs(console_cls):
15
+ kwargs = getattr(console_cls, "console_kwargs", None)
16
+ if kwargs is None:
17
+ return {}
18
+ if not isinstance(kwargs, dict):
19
+ raise ConsoleInitException("console_kwargs must be dict")
20
+ return kwargs
21
+
22
+
23
+ class SubConsoleEntry:
24
+ def __init__(self, console_cls, parent_name, name):
25
+ self.console_cls = console_cls
26
+ self.parent_name = parent_name
27
+ self.name = name
28
+
29
+
30
+ class ConsoleRegistry:
31
+ def __init__(self):
32
+ self._root_cls = None
33
+ self._root_name = None
34
+ self._subs = {}
35
+
36
+ def set_root(self, console_cls):
37
+ if self._root_cls is not None:
38
+ raise ConsoleInitException("Root console already set")
39
+ self._root_cls = console_cls
40
+ self._root_name = _get_console_name(console_cls)
41
+
42
+ def add_sub(self, console_cls, parent_name):
43
+ name = _get_console_name(console_cls)
44
+ if name == self._root_name or name in self._subs:
45
+ raise ConsoleInitException(f"Console name duplicate [{name}]")
46
+ self._subs[name] = SubConsoleEntry(console_cls, parent_name, name)
47
+
48
+ def add_multi(self, console_cls, parent_map):
49
+ if not isinstance(parent_map, dict) or len(parent_map) <= 0:
50
+ raise ConsoleInitException("Multi console mapping is empty")
51
+ for parent_name, instance_name in parent_map.items():
52
+ if not isinstance(parent_name, str) or parent_name == "":
53
+ raise ConsoleInitException("Multi console parent name is empty")
54
+ if not isinstance(instance_name, str) or instance_name == "":
55
+ raise ConsoleInitException("Multi console instance name is empty")
56
+ resolved_name = _resolve_instance_name(parent_name, instance_name)
57
+ if resolved_name == self._root_name or resolved_name in self._subs:
58
+ raise ConsoleInitException(f"Console name duplicate [{resolved_name}]")
59
+ self._subs[resolved_name] = SubConsoleEntry(console_cls, parent_name, resolved_name)
60
+
61
+ def register_all(self, manager):
62
+ if self._root_cls is None:
63
+ raise ConsoleInitException("Root console not set")
64
+ manager.set_root_name(self._root_name)
65
+ manager.register(self._root_name, self._root_cls, **_get_console_kwargs(self._root_cls))
66
+ for name, entry in self._subs.items():
67
+ manager.register(name, entry.console_cls, **_get_console_kwargs(entry.console_cls))
68
+ for name, entry in self._subs.items():
69
+ if not manager.is_registered(entry.parent_name):
70
+ raise ConsoleInitException(
71
+ f"Parent console [{entry.parent_name}] for [{name}] not registered"
72
+ )
73
+ console_tree = _build_console_tree(self._root_name, self._subs)
74
+ manager.set_console_tree(console_tree)
75
+
76
+
77
+ def _resolve_instance_name(parent_name, instance_name):
78
+ prefix = f"{parent_name}_"
79
+ if instance_name.startswith(prefix):
80
+ return instance_name
81
+ return f"{prefix}{instance_name}"
82
+
83
+
84
+ def _build_console_tree(root_name, sub_entries):
85
+ children_map = {}
86
+ for name, entry in sub_entries.items():
87
+ children_map.setdefault(entry.parent_name, []).append(name)
88
+ for _, children in children_map.items():
89
+ children.sort()
90
+ all_names = [root_name]
91
+ all_names.extend(list(sub_entries.keys()))
92
+ for name in all_names:
93
+ if name not in children_map:
94
+ children_map[name] = []
95
+ return {
96
+ "root": root_name,
97
+ "children": children_map,
98
+ }
99
+
100
+
101
+ REGISTRY = ConsoleRegistry()
102
+
@@ -0,0 +1,7 @@
1
+ class UIBaseException(Exception):
2
+ def __init__(self, msg):
3
+ super().__init__()
4
+ self.msg = msg
5
+
6
+ def __str__(self):
7
+ return self.msg
@@ -0,0 +1,12 @@
1
+ class ConsoleInitException(Exception):
2
+ """ Console init exception """
3
+ def __init__(self, message: str):
4
+ super().__init__(message)
5
+
6
+
7
+ class ConsoleExit(Exception):
8
+ pass
9
+
10
+
11
+ class SubConsoleExit(Exception):
12
+ pass
@@ -0,0 +1,10 @@
1
+ from python_tty.executor.executor import CommandExecutor
2
+ from python_tty.executor.models import Invocation, RunState, RunStatus
3
+
4
+ __all__ = [
5
+ "CommandExecutor",
6
+ "Invocation",
7
+ "RunState",
8
+ "RunStatus",
9
+ ]
10
+