cli-ih 0.6.2__tar.gz → 0.6.3__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli_ih
3
- Version: 0.6.2
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)
@@ -49,6 +49,27 @@ handler.start()
49
49
  # > exit
50
50
  ```
51
51
 
52
+ ## New Async client
53
+ ```python
54
+ import asyncio
55
+ from cli_ih import AsyncInputHandler
56
+
57
+ print(cli_ih.__version__)
58
+
59
+ handler = AsyncInputHandler(cursor="> ")
60
+
61
+ @handler.command(name="greet", description="Greets the user. Usage: greet [name]")
62
+ async def greet(name, *args):
63
+ await asyncio.sleep(1)
64
+ print(f"Hello, {name}{" " if args else ""}{' '.join(args)}!")
65
+ # NEW
66
+ @handler.command(name="add", description="Performs the `+` operator on the first 2 arguments.")
67
+ async def add(a, b):
68
+ print(a+b)
69
+
70
+ asyncio.run(handler.start())
71
+ ```
72
+
52
73
  ## Additional Info
53
74
 
54
75
  - You can provide a valid logger `logger=logger` to the `InputHandler` to enable logging (this will be removed soon)
@@ -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, "is_async": inspect.iscoroutinefunction(func)}
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
- while self.is_running:
79
- try:
80
- user_input = await asyncio.to_thread(input, self.cursor)
81
- if not user_input:
82
- continue
83
-
84
- cmdargs = user_input.split(' ')
85
- command_name = cmdargs[0].lower()
86
- args = cmdargs[1:]
87
- if command_name in self.commands:
88
- await _run_command(self.commands, command_name, args)
89
- else:
90
- self.__warning(f"Unknown command: '{command_name}'")
91
- except EOFError:
92
- self.__error("Input ended unexpectedly.")
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
- await asyncio.to_thread(func, args)
129
- elif is_async:
130
- await func(*args)
120
+ if inspect.iscoroutinefunction(func):
121
+ await func(args)
122
+ else:
123
+ await asyncio.to_thread(func, args)
131
124
  else:
132
- await asyncio.to_thread(func, *args)
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.")
@@ -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.2
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)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cli_ih"
7
- version = "0.6.2"
7
+ version = "0.6.3"
8
8
  description = "A background command handler for python's command-line interface."
9
9
  readme = "README.md"
10
10
  requires-python = ">= 3.8"
File without changes
File without changes
File without changes