cli-ih 0.6.2.1__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.1
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
@@ -1,12 +1,12 @@
1
- from typing import Coroutine
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: Coroutine, 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: Coroutine, 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: Coroutine):
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,16 +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
+ 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()
77
93
 
78
94
  async def _run_command(commands: dict, name: str, args: list):
79
95
  """Executes a command from the command dictionary if it exists."""
80
96
  command = commands.get(name)
81
97
  if not command:
82
98
  self.__warning(f"Command '{name}' not found.")
99
+ return
83
100
 
84
101
  func = command.get("cmd")
85
102
  is_legacy = command.get("legacy", False)
86
- is_async = command.get("is_async", False)
87
103
 
88
104
  if not callable(func):
89
105
  raise ValueError(f"The command '{name}' is not callable.")
@@ -101,12 +117,12 @@ class AsyncInputHandler:
101
117
  try:
102
118
  if is_legacy:
103
119
  warnings.warn("This way of running commands id Deprecated. And should be changed to the new decorator way.", DeprecationWarning, 2)
104
- if is_async:
120
+ if inspect.iscoroutinefunction(func):
105
121
  await func(args)
106
122
  else:
107
123
  await asyncio.to_thread(func, args)
108
124
  else:
109
- if is_async:
125
+ if inspect.iscoroutinefunction(func):
110
126
  await func(*args)
111
127
  else:
112
128
  await asyncio.to_thread(func, *args)
@@ -118,7 +134,15 @@ class AsyncInputHandler:
118
134
 
119
135
  while self.is_running:
120
136
  try:
121
- user_input = await asyncio.to_thread(input, self.cursor)
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
+
122
146
  if not user_input:
123
147
  continue
124
148
 
@@ -142,14 +166,14 @@ class AsyncInputHandler:
142
166
 
143
167
  def register_default_commands(self):
144
168
  @self.command(name="help", description="Displays all the available commands")
145
- def help():
169
+ async def help(*args):
146
170
  str_out = "Available commands:\n"
147
171
  for command, data in self.commands.items():
148
172
  str_out += f" {command}: {data['description']}\n"
149
173
  print(str_out)
150
174
 
151
175
  @self.command(name="debug", description="If a logger is present changes the logging level to DEBUG.")
152
- def debug_mode():
176
+ async def debug_mode(*args):
153
177
  logger = self.global_logger
154
178
  if not logger:
155
179
  return self.__warning("No logger defined for this InputHandler instance.")
@@ -169,5 +193,5 @@ class AsyncInputHandler:
169
193
  self.__info(message)
170
194
 
171
195
  @self.command(name="exit", description="Exits the Input Handler irreversibly.")
172
- def exit_thread():
196
+ async def exit_thread(*args):
173
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.1
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
@@ -1,3 +1,2 @@
1
- build
2
1
  cli_ih
3
2
  dist
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cli_ih"
7
- version = "0.6.2.1"
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
File without changes