cli-ih 0.6.2__py3-none-any.whl → 0.6.3__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.
- cli_ih/asyncClient.py +67 -39
- cli_ih/client.py +1 -1
- {cli_ih-0.6.2.dist-info → cli_ih-0.6.3.dist-info}/METADATA +22 -1
- cli_ih-0.6.3.dist-info/RECORD +8 -0
- cli_ih-0.6.2.dist-info/RECORD +0 -8
- {cli_ih-0.6.2.dist-info → cli_ih-0.6.3.dist-info}/WHEEL +0 -0
- {cli_ih-0.6.2.dist-info → cli_ih-0.6.3.dist-info}/top_level.txt +0 -0
cli_ih/asyncClient.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from typing import Callable
|
|
1
|
+
from typing import Coroutine, Callable, Any
|
|
2
2
|
from .exceptions import HandlerClosed
|
|
3
|
-
import logging, warnings, asyncio, inspect
|
|
3
|
+
import logging, warnings, asyncio, inspect, threading
|
|
4
4
|
|
|
5
5
|
class AsyncInputHandler:
|
|
6
6
|
def __init__(self, cursor = "", *, logger: logging.Logger | None = None, register_defaults: bool = True):
|
|
7
7
|
self.commands = {}
|
|
8
8
|
self.is_running = False
|
|
9
|
-
self.cursor = f"{cursor.strip()} "
|
|
9
|
+
self.cursor = f"{cursor.strip()} " if cursor else ""
|
|
10
10
|
self.global_logger = logger if logger else None
|
|
11
11
|
self.logger = logger.getChild("InputHandler") if logger else None
|
|
12
12
|
self.register_defaults = register_defaults
|
|
@@ -48,7 +48,7 @@ class AsyncInputHandler:
|
|
|
48
48
|
else:
|
|
49
49
|
print(f"[EXEPTION]: {msg}: {e}")
|
|
50
50
|
|
|
51
|
-
def __register_cmd(self, name: str, func: Callable, description: str = "", legacy=False):
|
|
51
|
+
def __register_cmd(self, name: str, func: Callable[..., Any], description: str = "", legacy=False):
|
|
52
52
|
name = name.lower()
|
|
53
53
|
if not description:
|
|
54
54
|
description = "A command"
|
|
@@ -56,16 +56,16 @@ class AsyncInputHandler:
|
|
|
56
56
|
raise SyntaxError("Command name must not have spaces")
|
|
57
57
|
if name in self.commands:
|
|
58
58
|
raise SyntaxError(f"Command '{name}' is already registered. If theese commands have a different case and they need to stay the same, downgrade the package version to 0.5.x")
|
|
59
|
-
self.commands[name] = {"cmd": func, "description": description, "legacy": legacy
|
|
59
|
+
self.commands[name] = {"cmd": func, "description": description, "legacy": legacy}
|
|
60
60
|
|
|
61
|
-
def register_command(self, name: str, func: Callable, description: str = ""):
|
|
61
|
+
def register_command(self, name: str, func: Callable[..., Any], description: str = ""):
|
|
62
62
|
"""(DEPRECATED) Registers a command with its associated function."""
|
|
63
63
|
warnings.warn("Registering commands with `register_command` is deprecated, and should not be used.", DeprecationWarning, 2)
|
|
64
64
|
self.__register_cmd(name, func, description, legacy=True)
|
|
65
65
|
|
|
66
66
|
def command(self, *, name: str = "", description: str = ""):
|
|
67
67
|
"""Registers a command with its associated function as a decorator."""
|
|
68
|
-
def decorator(func: Callable):
|
|
68
|
+
def decorator(func: Callable[..., Any]):
|
|
69
69
|
lname = name or func.__name__
|
|
70
70
|
self.__register_cmd(lname, func, description)
|
|
71
71
|
return func
|
|
@@ -74,40 +74,32 @@ class AsyncInputHandler:
|
|
|
74
74
|
async def start(self):
|
|
75
75
|
"""Starts the input handler loop in a separate thread if thread mode is enabled."""
|
|
76
76
|
self.is_running = True
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
break
|
|
94
|
-
except KeyboardInterrupt:
|
|
95
|
-
self.__error("Input interrupted.")
|
|
96
|
-
break
|
|
97
|
-
except HandlerClosed:
|
|
98
|
-
self.__info("Input Handler exited.")
|
|
99
|
-
break
|
|
100
|
-
self.is_running = False
|
|
77
|
+
loop = asyncio.get_running_loop()
|
|
78
|
+
input_queue = asyncio.Queue()
|
|
79
|
+
|
|
80
|
+
def _input_worker():
|
|
81
|
+
while self.is_running:
|
|
82
|
+
try:
|
|
83
|
+
text = input(self.cursor)
|
|
84
|
+
loop.call_soon_threadsafe(input_queue.put_nowait, text)
|
|
85
|
+
except EOFError:
|
|
86
|
+
loop.call_soon_threadsafe(input_queue.put_nowait, EOFError)
|
|
87
|
+
break
|
|
88
|
+
except Exception:
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
thread = threading.Thread(target=_input_worker, daemon=True)
|
|
92
|
+
thread.start()
|
|
101
93
|
|
|
102
94
|
async def _run_command(commands: dict, name: str, args: list):
|
|
103
95
|
"""Executes a command from the command dictionary if it exists."""
|
|
104
96
|
command = commands.get(name)
|
|
105
97
|
if not command:
|
|
106
98
|
self.__warning(f"Command '{name}' not found.")
|
|
99
|
+
return
|
|
107
100
|
|
|
108
101
|
func = command.get("cmd")
|
|
109
102
|
is_legacy = command.get("legacy", False)
|
|
110
|
-
is_async = command.get("is_async", False)
|
|
111
103
|
|
|
112
104
|
if not callable(func):
|
|
113
105
|
raise ValueError(f"The command '{name}' is not callable.")
|
|
@@ -125,27 +117,63 @@ class AsyncInputHandler:
|
|
|
125
117
|
try:
|
|
126
118
|
if is_legacy:
|
|
127
119
|
warnings.warn("This way of running commands id Deprecated. And should be changed to the new decorator way.", DeprecationWarning, 2)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
120
|
+
if inspect.iscoroutinefunction(func):
|
|
121
|
+
await func(args)
|
|
122
|
+
else:
|
|
123
|
+
await asyncio.to_thread(func, args)
|
|
131
124
|
else:
|
|
132
|
-
|
|
125
|
+
if inspect.iscoroutinefunction(func):
|
|
126
|
+
await func(*args)
|
|
127
|
+
else:
|
|
128
|
+
await asyncio.to_thread(func, *args)
|
|
133
129
|
|
|
134
130
|
except HandlerClosed as e:
|
|
135
131
|
raise e
|
|
136
132
|
except Exception as e:
|
|
137
133
|
self.__exeption(f"An error occurred in command '{name}'", e)
|
|
138
134
|
|
|
135
|
+
while self.is_running:
|
|
136
|
+
try:
|
|
137
|
+
try:
|
|
138
|
+
user_input = await asyncio.wait_for(input_queue.get(), timeout=0.1)
|
|
139
|
+
except asyncio.TimeoutError:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
if user_input is EOFError:
|
|
143
|
+
self.__error("Input ended unexpectedly.")
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
if not user_input:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
cmdargs = user_input.split(' ')
|
|
150
|
+
command_name = cmdargs[0].lower()
|
|
151
|
+
args = cmdargs[1:]
|
|
152
|
+
if command_name in self.commands:
|
|
153
|
+
await _run_command(self.commands, command_name, args)
|
|
154
|
+
else:
|
|
155
|
+
self.__warning(f"Unknown command: '{command_name}'")
|
|
156
|
+
except EOFError:
|
|
157
|
+
self.__error("Input ended unexpectedly.")
|
|
158
|
+
break
|
|
159
|
+
except KeyboardInterrupt:
|
|
160
|
+
self.__error("Input interrupted.")
|
|
161
|
+
break
|
|
162
|
+
except HandlerClosed:
|
|
163
|
+
self.__info("Input Handler exited.")
|
|
164
|
+
break
|
|
165
|
+
self.is_running = False
|
|
166
|
+
|
|
139
167
|
def register_default_commands(self):
|
|
140
168
|
@self.command(name="help", description="Displays all the available commands")
|
|
141
|
-
def help():
|
|
169
|
+
async def help(*args):
|
|
142
170
|
str_out = "Available commands:\n"
|
|
143
171
|
for command, data in self.commands.items():
|
|
144
172
|
str_out += f" {command}: {data['description']}\n"
|
|
145
173
|
print(str_out)
|
|
146
174
|
|
|
147
175
|
@self.command(name="debug", description="If a logger is present changes the logging level to DEBUG.")
|
|
148
|
-
def debug_mode():
|
|
176
|
+
async def debug_mode(*args):
|
|
149
177
|
logger = self.global_logger
|
|
150
178
|
if not logger:
|
|
151
179
|
return self.__warning("No logger defined for this InputHandler instance.")
|
|
@@ -165,5 +193,5 @@ class AsyncInputHandler:
|
|
|
165
193
|
self.__info(message)
|
|
166
194
|
|
|
167
195
|
@self.command(name="exit", description="Exits the Input Handler irreversibly.")
|
|
168
|
-
def exit_thread():
|
|
196
|
+
async def exit_thread(*args):
|
|
169
197
|
raise HandlerClosed("Handler was closed with exit command.")
|
cli_ih/client.py
CHANGED
|
@@ -7,7 +7,7 @@ class InputHandler:
|
|
|
7
7
|
self.commands = {}
|
|
8
8
|
self.is_running = False
|
|
9
9
|
self.thread_mode = thread_mode
|
|
10
|
-
self.cursor = f"{cursor.strip()} "
|
|
10
|
+
self.cursor = f"{cursor.strip()} " if cursor else ""
|
|
11
11
|
self.thread = None
|
|
12
12
|
self.global_logger = logger if logger else None
|
|
13
13
|
self.logger = logger.getChild("InputHandler") if logger else None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cli_ih
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: A background command handler for python's command-line interface.
|
|
5
5
|
Author-email: Hotment <michatchuplay@gmail.com>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -57,6 +57,27 @@ handler.start()
|
|
|
57
57
|
# > exit
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
## New Async client
|
|
61
|
+
```python
|
|
62
|
+
import asyncio
|
|
63
|
+
from cli_ih import AsyncInputHandler
|
|
64
|
+
|
|
65
|
+
print(cli_ih.__version__)
|
|
66
|
+
|
|
67
|
+
handler = AsyncInputHandler(cursor="> ")
|
|
68
|
+
|
|
69
|
+
@handler.command(name="greet", description="Greets the user. Usage: greet [name]")
|
|
70
|
+
async def greet(name, *args):
|
|
71
|
+
await asyncio.sleep(1)
|
|
72
|
+
print(f"Hello, {name}{" " if args else ""}{' '.join(args)}!")
|
|
73
|
+
# NEW
|
|
74
|
+
@handler.command(name="add", description="Performs the `+` operator on the first 2 arguments.")
|
|
75
|
+
async def add(a, b):
|
|
76
|
+
print(a+b)
|
|
77
|
+
|
|
78
|
+
asyncio.run(handler.start())
|
|
79
|
+
```
|
|
80
|
+
|
|
60
81
|
## Additional Info
|
|
61
82
|
|
|
62
83
|
- You can provide a valid logger `logger=logger` to the `InputHandler` to enable logging (this will be removed soon)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
cli_ih/__init__.py,sha256=0dtZNjrIlZMny9M0s6ygYTuo-9sM7BCSJHkBMDYkn8g,247
|
|
2
|
+
cli_ih/asyncClient.py,sha256=VNjBwO5Y0IVl5Gv_oq-GR0GpmnM2jk6gBH08z9VjaKM,7939
|
|
3
|
+
cli_ih/client.py,sha256=WGlA8sxvF173eKEhVbrk_Z3TjHRHIBZzum1rsvE9rsY,7786
|
|
4
|
+
cli_ih/exceptions.py,sha256=0xqaMCVu-yEURIrB6wEhbf6kfdsRmODJBoDsQTOp29s,156
|
|
5
|
+
cli_ih-0.6.3.dist-info/METADATA,sha256=9Qyw0eFfA3RQp9WxRnoY2cJucjqQBcu29kLJrO2odYc,2789
|
|
6
|
+
cli_ih-0.6.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
+
cli_ih-0.6.3.dist-info/top_level.txt,sha256=Ve1CRLNXhPyPSkpN0xLu26roh30LQCpNzkF61BZYfk0,7
|
|
8
|
+
cli_ih-0.6.3.dist-info/RECORD,,
|
cli_ih-0.6.2.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
cli_ih/__init__.py,sha256=0dtZNjrIlZMny9M0s6ygYTuo-9sM7BCSJHkBMDYkn8g,247
|
|
2
|
-
cli_ih/asyncClient.py,sha256=GrfybfvoX8TttSuoXnnhy-hqcmofL5yaFOh0Q0dmnqk,6852
|
|
3
|
-
cli_ih/client.py,sha256=Mka6UmhS9wSFVGNqqsNIZ6cYON8HWnYTudkZWiPdmgI,7768
|
|
4
|
-
cli_ih/exceptions.py,sha256=0xqaMCVu-yEURIrB6wEhbf6kfdsRmODJBoDsQTOp29s,156
|
|
5
|
-
cli_ih-0.6.2.dist-info/METADATA,sha256=VHT0-j60ellnPtpCq07wZEGL-VvuozSLQ9Q8lPvjzBc,2237
|
|
6
|
-
cli_ih-0.6.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
-
cli_ih-0.6.2.dist-info/top_level.txt,sha256=Ve1CRLNXhPyPSkpN0xLu26roh30LQCpNzkF61BZYfk0,7
|
|
8
|
-
cli_ih-0.6.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|