repl-toolkit 1.0.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.

Potentially problematic release.


This version of repl-toolkit might be problematic. Click here for more details.

@@ -0,0 +1,58 @@
1
+ """
2
+ REPL Toolkit - A Python toolkit for building interactive REPL and headless interfaces.
3
+
4
+ This package provides tools for creating interactive command-line interfaces
5
+ with support for both commands and keyboard shortcuts, featuring late backend
6
+ binding for resource context scenarios.
7
+
8
+ Key Features:
9
+ - Action system with commands and keyboard shortcuts
10
+ - Late backend binding for resource contexts
11
+ - Protocol-based architecture for type safety
12
+ - Comprehensive test coverage
13
+ - Async-native design
14
+
15
+ Example:
16
+ >>> import asyncio
17
+ >>> from repl_toolkit import run_async_repl, ActionRegistry
18
+ >>>
19
+ >>> class MyBackend:
20
+ ... async def handle_input(self, user_input: str) -> bool:
21
+ ... print(f"You said: {user_input}")
22
+ ... return True
23
+ >>>
24
+ >>> async def main():
25
+ ... backend = MyBackend()
26
+ ... await run_async_repl(backend=backend)
27
+ >>>
28
+ >>> # asyncio.run(main())
29
+ """
30
+
31
+ __version__ = "1.0.0"
32
+ __author__ = "REPL Toolkit Contributors"
33
+ __license__ = "MIT"
34
+
35
+ # Core exports
36
+ from .async_repl import AsyncREPL, run_async_repl
37
+ from .headless_repl import run_headless_mode, HeadlessREPL
38
+ from .actions import ActionRegistry, Action, ActionContext, ActionError
39
+ from .ptypes import AsyncBackend, ActionHandler, Completer
40
+
41
+ __all__ = [
42
+ # Core classes
43
+ "AsyncREPL",
44
+ "HeadlessREPL",
45
+ "ActionRegistry",
46
+ "Action",
47
+ "ActionContext",
48
+ "ActionError",
49
+
50
+ # Convenience functions
51
+ "run_async_repl",
52
+ "run_headless_mode",
53
+
54
+ # Protocols/Types
55
+ "AsyncBackend",
56
+ "ActionHandler",
57
+ "Completer",
58
+ ]
@@ -0,0 +1,25 @@
1
+ """
2
+ Action system for repl_toolkit.
3
+
4
+ This module provides the action architecture that combines command
5
+ and keyboard shortcut handling into a single, extensible framework.
6
+
7
+ The action system allows developers to define actions that can be triggered
8
+ by either typed commands (e.g., /help) or keyboard shortcuts (e.g., F1),
9
+ providing a consistent and discoverable interface for users.
10
+ """
11
+
12
+ from .action import Action, ActionContext, ActionError, ActionValidationError, ActionExecutionError
13
+ from .registry import ActionRegistry
14
+
15
+ __all__ = [
16
+ # Core action types
17
+ "Action",
18
+ "ActionContext",
19
+ "ActionError",
20
+ "ActionValidationError",
21
+ "ActionExecutionError",
22
+
23
+ # Registry
24
+ "ActionRegistry",
25
+ ]
@@ -0,0 +1,217 @@
1
+ """
2
+ Core action definition and context for the action system.
3
+
4
+ This module defines the Action dataclass and ActionContext that form the
5
+ foundation of the command and keyboard shortcut system.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Callable, List, Optional, Union, TYPE_CHECKING
10
+ from loguru import logger
11
+
12
+ if TYPE_CHECKING:
13
+ from .registry import ActionRegistry
14
+
15
+
16
+ @dataclass
17
+ class Action:
18
+ """
19
+ Action definition that can be triggered by commands or keyboard shortcuts.
20
+
21
+ Actions provide a way to define functionality that can be accessed
22
+ through multiple interaction methods (commands, shortcuts) while maintaining
23
+ consistent behavior and documentation.
24
+
25
+ Example:
26
+ # Action with both command and shortcut
27
+ help_action = Action(
28
+ name="show_help",
29
+ description="Show help information",
30
+ category="General",
31
+ handler=show_help_function,
32
+ command="/help",
33
+ command_usage="/help [command] - Show help for all or specific command",
34
+ keys="F1",
35
+ keys_description="Show help"
36
+ )
37
+
38
+ # Command-only action
39
+ history_action = Action(
40
+ name="show_history",
41
+ description="Display conversation history",
42
+ category="Information",
43
+ handler=show_history_function,
44
+ command="/history",
45
+ command_usage="/history - Display message history"
46
+ )
47
+
48
+ # Shortcut-only action
49
+ save_action = Action(
50
+ name="quick_save",
51
+ description="Quick save current state",
52
+ category="File",
53
+ handler=quick_save_function,
54
+ keys="ctrl-s",
55
+ keys_description="Quick save"
56
+ )
57
+
58
+ # Main-loop action (handled externally)
59
+ exit_action = Action(
60
+ name="exit_repl",
61
+ description="Exit the application",
62
+ category="Control",
63
+ handler=None, # Handled by main loop
64
+ command="/exit",
65
+ command_usage="/exit - Exit the application"
66
+ )
67
+ """
68
+
69
+ # Core action definition
70
+ name: str # Unique action identifier
71
+ description: str # Human-readable description
72
+ category: str # Category for grouping/organization
73
+ handler: Optional[Union[Callable, str]] # Handler function, import path, or None for main-loop
74
+
75
+ # Command binding (optional)
76
+ command: Optional[str] = None # Command string (e.g., "/help")
77
+ command_args_description: Optional[str] = None # Description of command arguments
78
+ command_usage: Optional[str] = None # Full usage description
79
+
80
+ # Keyboard shortcut binding (optional)
81
+ keys: Optional[Union[str, List[str]]] = None # Key combination(s)
82
+ keys_description: Optional[str] = None # Shortcut description for help
83
+
84
+ # Metadata and control
85
+ enabled: bool = True # Whether action is currently enabled
86
+ context: Optional[str] = None # Context where action is available
87
+ requires_backend: bool = False # Whether action needs backend access
88
+ hidden: bool = False # Hide from help listings
89
+
90
+ def __post_init__(self):
91
+ """Validate action definition after initialization."""
92
+ logger.trace("Action.__post_init__() entry")
93
+
94
+ if not self.name:
95
+ raise ValueError("Action name cannot be empty")
96
+
97
+ if not self.description:
98
+ raise ValueError("Action description cannot be empty")
99
+
100
+ if not self.category:
101
+ raise ValueError("Action category cannot be empty")
102
+
103
+ # Handler can be None for main-loop actions like exit/quit
104
+ # but if provided, it cannot be empty string
105
+ if self.handler == "":
106
+ raise ValueError("Action handler cannot be empty string (use None for main-loop actions)")
107
+
108
+ if not self.command and not self.keys:
109
+ raise ValueError("Action must have either command or keys binding")
110
+
111
+ # Validate command format
112
+ if self.command and not self.command.startswith('/'):
113
+ raise ValueError(f"Command '{self.command}' must start with '/'")
114
+
115
+ # Ensure command usage is provided for commands
116
+ if self.command and not self.command_usage:
117
+ logger.warning(f"Action '{self.name}' has command but no usage description")
118
+
119
+ # Ensure keys description is provided for shortcuts
120
+ if self.keys and not self.keys_description:
121
+ logger.warning(f"Action '{self.name}' has keys but no keys description")
122
+
123
+ logger.trace("Action.__post_init__() exit")
124
+
125
+ @property
126
+ def has_command(self) -> bool:
127
+ """Check if action has a command binding."""
128
+ logger.trace("Action.has_command() entry/exit")
129
+ return self.command is not None
130
+
131
+ @property
132
+ def has_shortcut(self) -> bool:
133
+ """Check if action has a keyboard shortcut binding."""
134
+ logger.trace("Action.has_shortcut() entry/exit")
135
+ return self.keys is not None
136
+
137
+ @property
138
+ def is_main_loop_action(self) -> bool:
139
+ """Check if action is handled by the main loop (handler is None)."""
140
+ logger.trace("Action.is_main_loop_action() entry/exit")
141
+ return self.handler is None
142
+
143
+ def get_keys_list(self) -> List[str]:
144
+ """Get keys as a list, handling both string and list formats."""
145
+ logger.trace("Action.get_keys_list() entry")
146
+
147
+ if not self.keys:
148
+ logger.trace("Action.get_keys_list() exit - no keys")
149
+ return []
150
+
151
+ result = [self.keys] if isinstance(self.keys, str) else self.keys
152
+ logger.trace("Action.get_keys_list() exit")
153
+ return result
154
+
155
+
156
+ @dataclass
157
+ class ActionContext:
158
+ """
159
+ Context information passed to action handlers.
160
+
161
+ ActionContext provides handlers with access to the registry, backend,
162
+ and context-specific information needed to execute actions properly.
163
+
164
+ The context varies depending on how the action was triggered:
165
+ - Command: args contains parsed command arguments
166
+ - Keyboard: event contains the key press event
167
+ - Programmatic: context can be customized
168
+ """
169
+
170
+ registry: "ActionRegistry" # Reference to action registry
171
+ backend: Optional[Any] = None # Backend instance (if available)
172
+ event: Optional[Any] = None # KeyPress event for shortcuts
173
+ args: List[str] = field(default_factory=list) # Command arguments
174
+ triggered_by: str = "unknown" # How action was triggered
175
+ user_input: Optional[str] = None # Original user input
176
+ headless_mode: bool = False # Whether in headless mode
177
+ buffer: Optional[Any] = None # Reference to input buffer (if applicable)
178
+
179
+ def __post_init__(self):
180
+ """Set triggered_by based on available context."""
181
+ logger.trace("ActionContext.__post_init__() entry")
182
+
183
+ if self.triggered_by == "unknown":
184
+ if self.event is not None:
185
+ self.triggered_by = "shortcut"
186
+ elif self.args or self.user_input:
187
+ self.triggered_by = "command"
188
+ else:
189
+ self.triggered_by = "programmatic"
190
+
191
+ logger.trace("ActionContext.__post_init__() exit")
192
+
193
+
194
+ class ActionError(Exception):
195
+ """Base exception for action-related errors."""
196
+
197
+ def __init__(self, message: str, action_name: Optional[str] = None):
198
+ """
199
+ Initialize action error.
200
+
201
+ Args:
202
+ message: Error description
203
+ action_name: Name of action that caused error (optional)
204
+ """
205
+ logger.trace("ActionError.__init__() entry/exit")
206
+ super().__init__(message)
207
+ self.action_name = action_name
208
+
209
+
210
+ class ActionValidationError(ActionError):
211
+ """Exception raised when action validation fails."""
212
+ pass
213
+
214
+
215
+ class ActionExecutionError(ActionError):
216
+ """Exception raised when action execution fails."""
217
+ pass