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
python_tty/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ from python_tty.config import Config
2
+ from python_tty.console_factory import ConsoleFactory
3
+ from python_tty.ui.events import UIEvent, UIEventLevel, UIEventListener, UIEventSpeaker
4
+ from python_tty.ui.output import proxy_print
5
+
6
+ __all__ = [
7
+ "UIEvent",
8
+ "UIEventLevel",
9
+ "UIEventListener",
10
+ "UIEventSpeaker",
11
+ "ConsoleFactory",
12
+ "Config",
13
+ "proxy_print",
14
+ ]
15
+
@@ -0,0 +1,24 @@
1
+ from python_tty.commands.core import BaseCommands, CommandValidator
2
+ from python_tty.commands.registry import (
3
+ ArgSpec,
4
+ CommandDef,
5
+ CommandInfo,
6
+ CommandRegistry,
7
+ CommandStyle,
8
+ COMMAND_REGISTRY,
9
+ define_command_style,
10
+ )
11
+
12
+ __all__ = [
13
+ "ArgSpec",
14
+ "BaseCommands",
15
+ "CommandDef",
16
+ "CommandInfo",
17
+ "CommandRegistry",
18
+ "CommandStyle",
19
+ "COMMAND_REGISTRY",
20
+ "CommandValidator",
21
+ "define_command_style",
22
+ ]
23
+
24
+
@@ -0,0 +1,119 @@
1
+ from prompt_toolkit.completion import NestedCompleter
2
+ from prompt_toolkit.document import Document
3
+ from prompt_toolkit.validation import DummyValidator, Validator, ValidationError
4
+
5
+ from python_tty.commands.registry import COMMAND_REGISTRY, ArgSpec
6
+ from python_tty.exceptions.console_exception import ConsoleInitException
7
+ from python_tty.utils import split_cmd
8
+
9
+
10
+ class CommandValidator(Validator):
11
+ def __init__(self, command_validators: dict, enable_undefined_command=False):
12
+ self.command_validators = command_validators
13
+ self.enable_undefined_command = enable_undefined_command
14
+ super().__init__()
15
+
16
+ def validate(self, document: Document) -> None:
17
+ try:
18
+ token, arg_text, _ = split_cmd(document.text)
19
+ if token in self.command_validators.keys():
20
+ cmd_validator = self.command_validators[token]
21
+ cmd_validator.validate(Document(text=arg_text))
22
+ else:
23
+ if not self.enable_undefined_command:
24
+ raise ValidationError(message="Bad command")
25
+ except ValueError:
26
+ return
27
+
28
+
29
+ class BaseCommands:
30
+ def __init__(self, console, registry=None):
31
+ self.console = console
32
+ self.registry = registry if registry is not None else COMMAND_REGISTRY
33
+ self.command_defs = []
34
+ self.command_defs_by_name = {}
35
+ self.command_defs_by_id = {}
36
+ self.command_completers = {}
37
+ self.command_validators = {}
38
+ self.command_funcs = {}
39
+ self._init_funcs()
40
+ self.completer = NestedCompleter.from_nested_dict(self.command_completers)
41
+ self.validator = CommandValidator(self.command_validators, self.enable_undefined_command)
42
+
43
+ @property
44
+ def enable_undefined_command(self):
45
+ return False
46
+
47
+ def _init_funcs(self):
48
+ if self.console is None:
49
+ raise ConsoleInitException("Console is None")
50
+ defs = self.registry.get_command_defs_for_console(self.console.__class__)
51
+ if len(defs) == 0:
52
+ defs = self.registry.collect_from_commands_cls(self.__class__)
53
+ self.command_defs = defs
54
+ self._collect_completer_and_validator(defs)
55
+
56
+ def _collect_completer_and_validator(self, defs):
57
+ for command_def in defs:
58
+ self._map_components(command_def)
59
+
60
+ def _map_components(self, command_def):
61
+ command_id = self._build_command_id(command_def)
62
+ if command_id is not None:
63
+ self.command_defs_by_id[command_id] = command_def
64
+ for command_name in command_def.all_names():
65
+ self.command_funcs[command_name] = command_def.func
66
+ self.command_defs_by_name[command_name] = command_def
67
+ if command_def.completer is None:
68
+ self.command_completers[command_name] = None
69
+ else:
70
+ self.command_completers[command_name] = self._build_completer(command_def)
71
+ self.command_validators[command_name] = self._build_validator(command_def)
72
+
73
+ def _build_completer(self, command_def):
74
+ try:
75
+ return command_def.completer(self.console, command_def.arg_spec)
76
+ except TypeError:
77
+ try:
78
+ return command_def.completer(self.console)
79
+ except TypeError as exc:
80
+ raise ConsoleInitException(
81
+ "Completer init failed. Use completer_from(...) to adapt "
82
+ "prompt_toolkit completers."
83
+ ) from exc
84
+
85
+ def _build_validator(self, command_def):
86
+ if command_def.validator is None:
87
+ return DummyValidator()
88
+ try:
89
+ return command_def.validator(self.console, command_def.func, command_def.arg_spec)
90
+ except TypeError:
91
+ return command_def.validator(self.console, command_def.func)
92
+
93
+ def get_command_def(self, command_name):
94
+ command_def = self.command_defs_by_id.get(command_name)
95
+ if command_def is not None:
96
+ return command_def
97
+ return self.command_defs_by_name.get(command_name)
98
+
99
+ def get_command_def_by_id(self, command_id):
100
+ return self.command_defs_by_id.get(command_id)
101
+
102
+ def get_command_id(self, command_name):
103
+ command_def = self.command_defs_by_name.get(command_name)
104
+ if command_def is None:
105
+ return None
106
+ return self._build_command_id(command_def)
107
+
108
+ def _build_command_id(self, command_def):
109
+ console_name = getattr(self.console, "console_name", None)
110
+ if not console_name:
111
+ console_name = self.console.__class__.__name__.lower()
112
+ return f"cmd:{console_name}:{command_def.func_name}"
113
+
114
+ def deserialize_args(self, command_def, raw_text):
115
+ if command_def.arg_spec is None:
116
+ arg_spec = ArgSpec.from_signature(command_def.func)
117
+ return arg_spec.parse(raw_text)
118
+ return command_def.arg_spec.parse(raw_text)
119
+
@@ -0,0 +1,58 @@
1
+ from functools import wraps
2
+
3
+ from prompt_toolkit.completion import Completer
4
+ from prompt_toolkit.validation import Validator
5
+
6
+ from python_tty.commands import BaseCommands
7
+ from python_tty.commands.registry import COMMAND_REGISTRY, CommandInfo, CommandStyle, define_command_style
8
+ from python_tty.exceptions.console_exception import ConsoleInitException
9
+
10
+
11
+ def commands(commands_cls):
12
+ """Bind a BaseCommands subclass to a Console class for auto command wiring."""
13
+ if not issubclass(commands_cls, BaseCommands):
14
+ raise ConsoleInitException("Commands must inherit BaseCommands")
15
+
16
+ def decorator(console_cls):
17
+ from python_tty.consoles import MainConsole, SubConsole
18
+ if not issubclass(console_cls, (MainConsole, SubConsole)):
19
+ raise ConsoleInitException("commands decorator must target a Console class")
20
+ existing = getattr(console_cls, "__commands_cls__", None)
21
+ if existing is not None and existing is not commands_cls:
22
+ raise ConsoleInitException(
23
+ f"{console_cls.__name__} already binds to {existing.__name__}; "
24
+ f"cannot bind to {commands_cls.__name__} again"
25
+ )
26
+ setattr(console_cls, "__commands_cls__", commands_cls)
27
+ COMMAND_REGISTRY.register_console_commands(console_cls, commands_cls)
28
+ return console_cls
29
+
30
+ return decorator
31
+
32
+
33
+ def register_command(command_name: str, command_description: str, command_alias=None,
34
+ command_style=CommandStyle.LOWERCASE,
35
+ completer=None, validator=None, arg_spec=None):
36
+ """Declare command metadata for a command method on a BaseCommands subclass."""
37
+ if completer is not None and not isinstance(completer, type):
38
+ raise ConsoleInitException("Command completer must be a class")
39
+ if validator is not None and not isinstance(validator, type):
40
+ raise ConsoleInitException("Command validator must be a class")
41
+ if completer is not None and not issubclass(completer, Completer):
42
+ raise ConsoleInitException("Command completer must inherit Completer")
43
+ if validator is not None and not issubclass(validator, Validator):
44
+ raise ConsoleInitException("Command validator must inherit Validator")
45
+ def inner_wrapper(func):
46
+ func.info = CommandInfo(define_command_style(command_name, command_style), command_description,
47
+ completer, validator, command_alias, arg_spec)
48
+ func.type = None
49
+
50
+ @wraps(func)
51
+ def wrapper(*args, **kwargs):
52
+ result = func(*args, **kwargs)
53
+ return result
54
+
55
+ return wrapper
56
+
57
+ return inner_wrapper
58
+
@@ -0,0 +1,8 @@
1
+ from python_tty.commands.examples.root_commands import RootCommands
2
+ from python_tty.commands.examples.sub_commands import SubCommands
3
+
4
+ __all__ = [
5
+ "RootCommands",
6
+ "SubCommands",
7
+ ]
8
+
@@ -0,0 +1,33 @@
1
+ from python_tty.commands import BaseCommands
2
+ from python_tty.commands.decorators import register_command
3
+ from python_tty.commands.general import GeneralValidator
4
+ from python_tty.commands.mixins import HelpMixin, QuitMixin
5
+ from python_tty.ui.events import UIEventLevel
6
+ from python_tty.ui.output import proxy_print
7
+
8
+
9
+ class RootCommands(BaseCommands, HelpMixin, QuitMixin):
10
+ @property
11
+ def enable_undefined_command(self):
12
+ return True
13
+
14
+ @register_command("use", "Enter sub console", validator=GeneralValidator)
15
+ def run_use(self, console_name):
16
+ manager = getattr(self.console, "manager", None)
17
+ if manager is None:
18
+ proxy_print("Console manager not configured", UIEventLevel.WARNING)
19
+ return
20
+ if not manager.is_registered(console_name):
21
+ proxy_print(f"Console [{console_name}] not registered", UIEventLevel.ERROR)
22
+ return
23
+ manager.push(console_name)
24
+
25
+ @register_command("debug", "Debug root console, display some information", validator=GeneralValidator)
26
+ def run_debug(self, *args):
27
+ framework = self.console.service
28
+ proxy_print(str(framework))
29
+
30
+
31
+ if __name__ == '__main__':
32
+ pass
33
+
@@ -0,0 +1,11 @@
1
+ from python_tty.commands import BaseCommands
2
+ from python_tty.commands.decorators import register_command
3
+ from python_tty.commands.general import GeneralValidator
4
+ from python_tty.commands.mixins import BackMixin, HelpMixin, QuitMixin
5
+
6
+
7
+ class SubCommands(BaseCommands, HelpMixin, QuitMixin, BackMixin):
8
+ @register_command("debug", "Debug command, display some information", [], validator=GeneralValidator)
9
+ def run_debug(self):
10
+ pass
11
+
@@ -0,0 +1,107 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from prompt_toolkit.completion import Completer, WordCompleter
4
+ from prompt_toolkit.document import Document
5
+ from prompt_toolkit.validation import ValidationError, Validator
6
+
7
+ from python_tty.commands.registry import ArgSpec
8
+
9
+
10
+ class GeneralValidator(Validator):
11
+ """Default validator that checks argument count and allows custom validation."""
12
+ def __init__(self, console, func, arg_spec=None):
13
+ self.console = console
14
+ self.func = func
15
+ self.arg_spec = arg_spec or ArgSpec.from_signature(func)
16
+ super().__init__()
17
+
18
+ def validate(self, document: Document) -> None:
19
+ try:
20
+ args = self.arg_spec.parse(document.text)
21
+ self.arg_spec.validate_count(len(args))
22
+ except ValidationError:
23
+ raise
24
+ except ValueError as exc:
25
+ raise ValidationError(message=str(exc)) from exc
26
+ try:
27
+ self.custom_validate(args, document.text)
28
+ except TypeError:
29
+ self.custom_validate(document.text)
30
+
31
+ def custom_validate(self, args, text: str):
32
+ pass
33
+
34
+
35
+ def _allow_complete_for_spec(arg_spec, text, args):
36
+ if arg_spec.max_args is None:
37
+ return True
38
+ if text != "" and text[-1].isspace():
39
+ return len(args) < arg_spec.max_args
40
+ return len(args) <= arg_spec.max_args
41
+
42
+
43
+ class GeneralCompleter(Completer, ABC):
44
+ """Base completer with ArgSpec-aware completion and console injection."""
45
+ def __init__(self, console, arg_spec=None, ignore_case=True):
46
+ self.console = console
47
+ self.arg_spec = arg_spec or ArgSpec()
48
+ self.ignore_case = ignore_case
49
+ super().__init__()
50
+
51
+ @abstractmethod
52
+ def get_candidates(self, args, text: str):
53
+ pass
54
+
55
+ def get_completions(self, document, complete_event):
56
+ text = document.text_before_cursor
57
+ try:
58
+ args = self.arg_spec.parse(text)
59
+ except ValueError:
60
+ return
61
+ if not _allow_complete_for_spec(self.arg_spec, text, args):
62
+ return
63
+ words = self.get_candidates(args, text)
64
+ if not words:
65
+ return
66
+ completer = WordCompleter(words, ignore_case=self.ignore_case)
67
+ yield from completer.get_completions(document, complete_event)
68
+
69
+ def _allow_complete(self, text, args):
70
+ return _allow_complete_for_spec(self.arg_spec, text, args)
71
+
72
+
73
+ class PromptToolkitCompleterAdapter(Completer):
74
+ completer_cls = None
75
+ completer_kwargs = {}
76
+
77
+ def __init__(self, console, arg_spec=None):
78
+ self.console = console
79
+ self.arg_spec = arg_spec or ArgSpec()
80
+ if self.completer_cls is None:
81
+ raise ValueError("completer_cls must be set for adapter")
82
+ self._inner = self.completer_cls(**self.get_completer_kwargs())
83
+ super().__init__()
84
+
85
+ def get_completer_kwargs(self):
86
+ return dict(self.completer_kwargs)
87
+
88
+ def get_completions(self, document, complete_event):
89
+ text = document.text_before_cursor
90
+ try:
91
+ args = self.arg_spec.parse(text)
92
+ except ValueError:
93
+ return
94
+ if not _allow_complete_for_spec(self.arg_spec, text, args):
95
+ return
96
+ yield from self._inner.get_completions(document, complete_event)
97
+
98
+
99
+ def completer_from(completer_cls, **kwargs):
100
+ """Build a completer adapter class for a prompt_toolkit completer."""
101
+ class _Adapter(PromptToolkitCompleterAdapter):
102
+ pass
103
+
104
+ _Adapter.completer_cls = completer_cls
105
+ _Adapter.completer_kwargs = kwargs
106
+ return _Adapter
107
+
@@ -0,0 +1,52 @@
1
+ import inspect
2
+
3
+ from python_tty.ui.output import proxy_print
4
+ from python_tty.commands import BaseCommands
5
+ from python_tty.commands.decorators import register_command
6
+ from python_tty.commands.general import GeneralValidator
7
+ from python_tty.exceptions.console_exception import ConsoleExit, SubConsoleExit
8
+ from python_tty.utils.table import Table
9
+
10
+
11
+ class CommandMixin:
12
+ pass
13
+
14
+
15
+ class BackMixin(CommandMixin):
16
+ @register_command("back", "Back to forward tty", validator=GeneralValidator)
17
+ def run_back(self):
18
+ raise SubConsoleExit
19
+
20
+
21
+ class QuitMixin(CommandMixin):
22
+ @register_command("quit", "Quit Console", ["exit", "q"], validator=GeneralValidator)
23
+ def run_quit(self):
24
+ raise ConsoleExit
25
+
26
+
27
+ class HelpMixin(CommandMixin):
28
+ @register_command("help", "Display help information", ["?"], validator=GeneralValidator)
29
+ def run_help(self):
30
+ header = ["Command", "Description"]
31
+ base_funcs = []
32
+ custom_funcs = []
33
+ base_commands_funcs = []
34
+ for cls in self.__class__.mro():
35
+ if cls is CommandMixin:
36
+ continue
37
+ if issubclass(cls, CommandMixin):
38
+ base_commands_funcs.extend([member[1] for member in inspect.getmembers(cls, inspect.isfunction)])
39
+ for name, func in self.command_funcs.items():
40
+ row = [name, func.info.func_description]
41
+ if func in base_commands_funcs:
42
+ base_funcs.append(row)
43
+ else:
44
+ custom_funcs.append(row)
45
+ if base_funcs:
46
+ proxy_print(Table(header, base_funcs, "Core Commands"))
47
+ if custom_funcs:
48
+ proxy_print(Table(header, custom_funcs, "Custom Commands"))
49
+
50
+ class DefaultCommands(BaseCommands, HelpMixin, QuitMixin):
51
+ pass
52
+
@@ -0,0 +1,185 @@
1
+ import re
2
+ import enum
3
+ import inspect
4
+
5
+ from prompt_toolkit.completion import Completer
6
+ from prompt_toolkit.validation import ValidationError, Validator
7
+ from python_tty.exceptions.console_exception import ConsoleInitException
8
+ from python_tty.utils.tokenize import tokenize_cmd
9
+
10
+
11
+
12
+ def define_command_style(command_name, style):
13
+ if style == CommandStyle.NONE:
14
+ return command_name
15
+ elif style == CommandStyle.LOWERCASE:
16
+ return command_name.lower()
17
+ elif style == CommandStyle.UPPERCASE:
18
+ return command_name.upper()
19
+ command_name = re.sub(r'(.)([A-Z][a-z]+)', r'\1-\2', command_name)
20
+ command_name = re.sub(r'([a-z0-9])([A-Z])', r'\1-\2', command_name)
21
+ if style == CommandStyle.POWERSHELL:
22
+ return command_name
23
+ elif style == CommandStyle.SLUGIFIED:
24
+ return command_name.lower()
25
+
26
+
27
+ class CommandStyle(enum.Enum):
28
+ NONE = 0 # ClassName => ClassName
29
+ LOWERCASE = 1 # ClassName => classname
30
+ UPPERCASE = 2 # ClassName => CLASSNAME
31
+ POWERSHELL = 3 # ClassName => Class-Name
32
+ SLUGIFIED = 4 # ClassName => class-name
33
+
34
+
35
+ class ArgSpec:
36
+ def __init__(self, min_args=0, max_args=0, variadic=False):
37
+ self.min_args = min_args
38
+ self.max_args = max_args
39
+ self.variadic = variadic
40
+
41
+ @classmethod
42
+ def from_signature(cls, func, skip_first=True):
43
+ sig = inspect.signature(func)
44
+ params = list(sig.parameters.values())
45
+ if skip_first and params:
46
+ params = params[1:]
47
+ min_args = 0
48
+ max_args = 0
49
+ variadic = False
50
+ for param in params:
51
+ if param.kind == param.VAR_POSITIONAL:
52
+ variadic = True
53
+ continue
54
+ if param.default is param.empty:
55
+ min_args += 1
56
+ max_args += 1
57
+ if variadic:
58
+ max_args = None
59
+ return cls(min_args, max_args, variadic)
60
+
61
+ def parse(self, text):
62
+ tokens = tokenize_cmd(text)
63
+ return tokens
64
+
65
+ def count_args(self, text):
66
+ tokens = tokenize_cmd(text)
67
+ return len(tokens)
68
+
69
+ def validate_count(self, count):
70
+ if count < self.min_args:
71
+ raise ValidationError(message="Not enough parameters set!")
72
+ if self.max_args is not None and count > self.max_args:
73
+ raise ValidationError(message="Too many parameters set!")
74
+
75
+
76
+ class CommandInfo:
77
+ def __init__(self, func_name, func_description,
78
+ completer=None, validator=None,
79
+ command_alias=None, arg_spec=None):
80
+ self.func_name = func_name
81
+ self.func_description = func_description
82
+ self.completer = completer
83
+ self.validator = validator
84
+ self.arg_spec = arg_spec
85
+ if command_alias is None:
86
+ self.alias = []
87
+ else:
88
+ if type(command_alias) == str:
89
+ self.alias = [command_alias]
90
+ elif type(command_alias) == list:
91
+ self.alias = command_alias
92
+ else:
93
+ self.alias = []
94
+
95
+
96
+ class CommandDef:
97
+ def __init__(self, func_name, func, func_description,
98
+ command_alias=None, completer=None, validator=None,
99
+ arg_spec=None):
100
+ self.func_name = func_name
101
+ self.func = func
102
+ self.func_description = func_description
103
+ self.completer = completer
104
+ self.validator = validator
105
+ self.arg_spec = arg_spec
106
+ if command_alias is None:
107
+ self.alias = []
108
+ else:
109
+ self.alias = command_alias
110
+
111
+ def all_names(self):
112
+ return [self.func_name] + list(self.alias)
113
+
114
+
115
+ class CommandRegistry:
116
+ def __init__(self):
117
+ self._console_command_classes = {}
118
+ self._commands_defs = {}
119
+ self._console_defs = {}
120
+
121
+ def register_console_commands(self, console_cls, commands_cls):
122
+ self._console_command_classes[console_cls] = commands_cls
123
+
124
+ def get_commands_cls(self, console_cls):
125
+ return self._console_command_classes.get(console_cls)
126
+
127
+ def register(self, func, console_cls=None, commands_cls=None,
128
+ command_name=None, command_description="", command_alias=None,
129
+ command_style=CommandStyle.LOWERCASE,
130
+ completer=None, validator=None, arg_spec=None):
131
+ if completer is not None and not isinstance(completer, type):
132
+ raise ConsoleInitException("Command completer must be a class")
133
+ if validator is not None and not isinstance(validator, type):
134
+ raise ConsoleInitException("Command validator must be a class")
135
+ if completer is not None and not issubclass(completer, Completer):
136
+ raise ConsoleInitException("Command completer must inherit Completer")
137
+ if validator is not None and not issubclass(validator, Validator):
138
+ raise ConsoleInitException("Command validator must inherit Validator")
139
+ if command_name is None:
140
+ command_name = func.__name__
141
+ info = CommandInfo(define_command_style(command_name, command_style),
142
+ command_description, completer, validator,
143
+ command_alias, arg_spec)
144
+ func.info = info
145
+ func.type = None
146
+ command_def = CommandDef(info.func_name, func, info.func_description,
147
+ info.alias, info.completer, info.validator,
148
+ info.arg_spec)
149
+ if commands_cls is not None:
150
+ self._commands_defs.setdefault(commands_cls, []).append(command_def)
151
+ if console_cls is not None:
152
+ self._console_defs.setdefault(console_cls, []).append(command_def)
153
+ return command_def
154
+
155
+ def collect_from_commands_cls(self, commands_cls):
156
+ if commands_cls in self._commands_defs:
157
+ return self._commands_defs[commands_cls]
158
+ defs = []
159
+ for member_name in dir(commands_cls):
160
+ if member_name.startswith("_"):
161
+ continue
162
+ member = getattr(commands_cls, member_name)
163
+ if (inspect.ismethod(member) or inspect.isfunction(member)) and hasattr(member, "info"):
164
+ command_info = member.info
165
+ arg_spec = command_info.arg_spec or ArgSpec.from_signature(member)
166
+ defs.append(CommandDef(command_info.func_name, member,
167
+ command_info.func_description,
168
+ command_info.alias,
169
+ command_info.completer,
170
+ command_info.validator,
171
+ arg_spec))
172
+ self._commands_defs[commands_cls] = defs
173
+ return defs
174
+
175
+ def get_command_defs_for_console(self, console_cls):
176
+ defs = []
177
+ commands_cls = self.get_commands_cls(console_cls)
178
+ if commands_cls is not None:
179
+ defs.extend(self.collect_from_commands_cls(commands_cls))
180
+ defs.extend(self._console_defs.get(console_cls, []))
181
+ return defs
182
+
183
+
184
+ COMMAND_REGISTRY = CommandRegistry()
185
+
@@ -0,0 +1,9 @@
1
+ from python_tty.config.config import Config, ConsoleFactoryConfig, ConsoleManagerConfig, ExecutorConfig
2
+
3
+ __all__ = [
4
+ "Config",
5
+ "ConsoleFactoryConfig",
6
+ "ConsoleManagerConfig",
7
+ "ExecutorConfig",
8
+ ]
9
+
@@ -0,0 +1,35 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Optional, TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from python_tty.ui.output import OutputRouter
6
+
7
+
8
+ @dataclass
9
+ class ExecutorConfig:
10
+ workers: int = 1
11
+ retain_last_n: Optional[int] = None
12
+ ttl_seconds: Optional[float] = None
13
+ pop_on_wait: bool = False
14
+
15
+
16
+ @dataclass
17
+ class ConsoleManagerConfig:
18
+ use_patch_stdout: bool = True
19
+ output_router: Optional["OutputRouter"] = None
20
+
21
+
22
+ @dataclass
23
+ class ConsoleFactoryConfig:
24
+ start_executor: bool = True
25
+ executor_in_thread: bool = True
26
+ executor_thread_name: str = "ExecutorLoop"
27
+ shutdown_executor: bool = True
28
+
29
+
30
+ @dataclass
31
+ class Config:
32
+ console_manager: ConsoleManagerConfig = field(default_factory=ConsoleManagerConfig)
33
+ executor: ExecutorConfig = field(default_factory=ExecutorConfig)
34
+ console_factory: ConsoleFactoryConfig = field(default_factory=ConsoleFactoryConfig)
35
+