cli-ih 0.6.1.2__tar.gz → 0.6.2__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.1.2
3
+ Version: 0.6.2
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,4 +1,5 @@
1
- from .Input_Handler import InputHandler
1
+ from .client import InputHandler
2
+ from .asyncClient import AsyncInputHandler
2
3
  import importlib.metadata
3
4
 
4
5
  try:
@@ -0,0 +1,169 @@
1
+ from typing import Callable
2
+ from .exceptions import HandlerClosed
3
+ import logging, warnings, asyncio, inspect
4
+
5
+ class AsyncInputHandler:
6
+ def __init__(self, cursor = "", *, logger: logging.Logger | None = None, register_defaults: bool = True):
7
+ self.commands = {}
8
+ self.is_running = False
9
+ self.cursor = f"{cursor.strip()} "
10
+ self.global_logger = logger if logger else None
11
+ self.logger = logger.getChild("InputHandler") if logger else None
12
+ self.register_defaults = register_defaults
13
+ if self.register_defaults:
14
+ self.register_default_commands()
15
+ else:
16
+ self.__warning("The default commands are disabled in the current instance.")
17
+
18
+ def get_logger(self):
19
+ return self.logger
20
+
21
+ def __debug(self, msg: str):
22
+ if self.logger:
23
+ self.logger.debug(msg)
24
+ else:
25
+ print(f"[DEBUG]: {msg}")
26
+
27
+ def __info(self, msg: str):
28
+ if self.logger:
29
+ self.logger.info(msg)
30
+ else:
31
+ print(f"[INFO]: {msg}")
32
+
33
+ def __warning(self, msg: str):
34
+ if self.logger:
35
+ self.logger.warning(msg)
36
+ else:
37
+ print(f"[WARNING]: {msg}")
38
+
39
+ def __error(self, msg: str):
40
+ if self.logger:
41
+ self.logger.error(msg)
42
+ else:
43
+ print(f"[ERROR]: {msg}")
44
+
45
+ def __exeption(self, msg: str, e: Exception):
46
+ if self.logger:
47
+ self.logger.exception(f"{msg}: {e}")
48
+ else:
49
+ print(f"[EXEPTION]: {msg}: {e}")
50
+
51
+ def __register_cmd(self, name: str, func: Callable, description: str = "", legacy=False):
52
+ name = name.lower()
53
+ if not description:
54
+ description = "A command"
55
+ if ' ' in name:
56
+ raise SyntaxError("Command name must not have spaces")
57
+ if name in self.commands:
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)}
60
+
61
+ def register_command(self, name: str, func: Callable, description: str = ""):
62
+ """(DEPRECATED) Registers a command with its associated function."""
63
+ warnings.warn("Registering commands with `register_command` is deprecated, and should not be used.", DeprecationWarning, 2)
64
+ self.__register_cmd(name, func, description, legacy=True)
65
+
66
+ def command(self, *, name: str = "", description: str = ""):
67
+ """Registers a command with its associated function as a decorator."""
68
+ def decorator(func: Callable):
69
+ lname = name or func.__name__
70
+ self.__register_cmd(lname, func, description)
71
+ return func
72
+ return decorator
73
+
74
+ async def start(self):
75
+ """Starts the input handler loop in a separate thread if thread mode is enabled."""
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
101
+
102
+ async def _run_command(commands: dict, name: str, args: list):
103
+ """Executes a command from the command dictionary if it exists."""
104
+ command = commands.get(name)
105
+ if not command:
106
+ self.__warning(f"Command '{name}' not found.")
107
+
108
+ func = command.get("cmd")
109
+ is_legacy = command.get("legacy", False)
110
+ is_async = command.get("is_async", False)
111
+
112
+ if not callable(func):
113
+ raise ValueError(f"The command '{name}' is not callable.")
114
+
115
+ try:
116
+ sig = inspect.signature(func)
117
+ if is_legacy:
118
+ sig.bind(args)
119
+ else:
120
+ sig.bind(*args)
121
+ except TypeError as e:
122
+ self.__warning(f"Argument error for legacy command '{name}': {e}")
123
+ return
124
+
125
+ try:
126
+ if is_legacy:
127
+ 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)
131
+ else:
132
+ await asyncio.to_thread(func, *args)
133
+
134
+ except HandlerClosed as e:
135
+ raise e
136
+ except Exception as e:
137
+ self.__exeption(f"An error occurred in command '{name}'", e)
138
+
139
+ def register_default_commands(self):
140
+ @self.command(name="help", description="Displays all the available commands")
141
+ def help():
142
+ str_out = "Available commands:\n"
143
+ for command, data in self.commands.items():
144
+ str_out += f" {command}: {data['description']}\n"
145
+ print(str_out)
146
+
147
+ @self.command(name="debug", description="If a logger is present changes the logging level to DEBUG.")
148
+ def debug_mode():
149
+ logger = self.global_logger
150
+ if not logger:
151
+ return self.__warning("No logger defined for this InputHandler instance.")
152
+
153
+ if logger.getEffectiveLevel() == logging.DEBUG:
154
+ new_level = logging.INFO
155
+ message = "Debug mode is now off"
156
+ else:
157
+ new_level = logging.DEBUG
158
+ message = "Debug mode is now on"
159
+
160
+ logger.setLevel(new_level)
161
+
162
+ for handler in logger.handlers:
163
+ if isinstance(handler, logging.StreamHandler):
164
+ handler.setLevel(new_level)
165
+ self.__info(message)
166
+
167
+ @self.command(name="exit", description="Exits the Input Handler irreversibly.")
168
+ def exit_thread():
169
+ raise HandlerClosed("Handler was closed with exit command.")
@@ -1,9 +1,7 @@
1
1
  from typing import Callable
2
+ from .exceptions import HandlerClosed
2
3
  import logging, warnings
3
4
 
4
- class HandlerClosed(Exception): ...
5
- class MissingParameter(Exception): ...
6
-
7
5
  class InputHandler:
8
6
  def __init__(self, thread_mode = True, cursor = "", *, logger: logging.Logger | None = None, register_defaults: bool = True):
9
7
  self.commands = {}
@@ -63,16 +61,14 @@ class InputHandler:
63
61
  self.commands[name] = {"cmd": func, "description": description, "legacy": legacy}
64
62
 
65
63
  def register_command(self, name: str, func: Callable, description: str = ""):
66
- """Registers a command with its associated function."""
67
- warnings.warn("Registering commands with `register_command` is deprecated, and will be removed in the next big update.", DeprecationWarning, 2)
64
+ """(DEPRECATED) Registers a command with its associated function."""
65
+ warnings.warn("Registering commands with `register_command` is deprecated, and should not be used.", DeprecationWarning, 2)
68
66
  self.__register_cmd(name, func, description, legacy=True)
69
67
 
70
68
  def command(self, *, name: str = "", description: str = ""):
71
69
  """Registers a command with its associated function as a decorator."""
72
70
  def decorator(func: Callable):
73
- lname = name
74
- if not lname:
75
- lname = func.__name__
71
+ lname = name or func.__name__
76
72
  self.__register_cmd(lname, func, description)
77
73
  return func
78
74
  return decorator
@@ -132,7 +128,7 @@ class InputHandler:
132
128
  continue
133
129
 
134
130
  cmdargs = user_input.split(' ')
135
- command_name = cmdargs[0]
131
+ command_name = cmdargs[0].lower()
136
132
  args = cmdargs[1:]
137
133
  if command_name in self.commands:
138
134
  _run_command(self.commands, command_name, args)
@@ -0,0 +1,4 @@
1
+ class HandlerException(Exception): ... # Base handler exception
2
+
3
+ class HandlerClosed(HandlerException): ...
4
+ class MissingParameter(HandlerException): ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli_ih
3
- Version: 0.6.1.2
3
+ Version: 0.6.2
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,7 +1,9 @@
1
1
  README.md
2
2
  pyproject.toml
3
- cli_ih/Input_Handler.py
4
3
  cli_ih/__init__.py
4
+ cli_ih/asyncClient.py
5
+ cli_ih/client.py
6
+ cli_ih/exceptions.py
5
7
  cli_ih.egg-info/PKG-INFO
6
8
  cli_ih.egg-info/SOURCES.txt
7
9
  cli_ih.egg-info/dependency_links.txt
@@ -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.1.2"
7
+ version = "0.6.2"
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