drifTech-lib 0.2.7__tar.gz → 0.4.0__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.

Potentially problematic release.


This version of drifTech-lib might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drifTech-lib
3
- Version: 0.2.7
3
+ Version: 0.4.0
4
4
  Summary: Common files/functions across most drifTech software
5
5
  Author: driftbluestone
6
6
  Author-email: driftbluestone <driftbluestone@gmail.com>
@@ -8,7 +8,7 @@ build-backend = "uv_build"
8
8
 
9
9
  [project]
10
10
  name = "drifTech-lib"
11
- version = "0.2.7"
11
+ version = "0.4.0"
12
12
  description = "Common files/functions across most drifTech software"
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.14"
@@ -8,7 +8,7 @@ build-backend = "uv_build"
8
8
 
9
9
  [project]
10
10
  name = "drifTech-lib"
11
- version = "0.2.7"
11
+ version = "0.4.0"
12
12
  authors = [{name = "driftbluestone", email = "driftbluestone@gmail.com"}]
13
13
  description = "Common files/functions across most drifTech software"
14
14
  readme = "README.md"
@@ -4,9 +4,9 @@ __all__ = ["Connector"]
4
4
 
5
5
  class Connector:
6
6
  """
7
- Inherit this class to overwrite the on_message() function
7
+ Inherit this class to overwrite the on_message() function.
8
8
 
9
- run await Connector().start() to connect to DCM-core
9
+ Run `await Connector().start()` to initialize a connection.
10
10
  """
11
11
  def __init__(self, HOST, PORT):
12
12
  self.HOST = HOST
@@ -35,7 +35,7 @@ class Connector:
35
35
  """Background task to continuously read any data pushed by the server."""
36
36
  try:
37
37
  while True:
38
- data = await reader.read(1024)
38
+ data = await reader.readline()
39
39
  if not data:
40
40
  logger.error("[DISCONNECTED] Server closed the connection.")
41
41
  break
@@ -46,9 +46,7 @@ class Connector:
46
46
  logger.error(f"[ERROR] Reading error: {e}")
47
47
 
48
48
  async def on_message(self, message: bytes):
49
- """
50
- Override this function
51
- """
49
+ """Override this function."""
52
50
  pass
53
51
 
54
52
  async def send_message(self, message: str | bytes):
@@ -57,7 +55,6 @@ class Connector:
57
55
  await self.command_queue.put(message)
58
56
 
59
57
  async def _send(self, writer: asyncio.StreamWriter):
60
- loop = asyncio.get_running_loop()
61
58
  try:
62
59
  while True:
63
60
  user_input: bytes = await self.command_queue.get()
@@ -0,0 +1,100 @@
1
+ import asyncio, logging, sys
2
+ logger = logging.getLogger("ipc:server")
3
+ logging.basicConfig(stream=sys.stdout, level=logging.INFO)
4
+ __all__ = ["Server", "connections"]
5
+ Port = int
6
+
7
+ conn_count = 1
8
+ connections: dict[Port, Server] = {}
9
+
10
+ class Server:
11
+ """
12
+ Inherit this class to overwrite the on_message() function.
13
+
14
+ Run `await Server().start()` to initialize a server.
15
+ Do not apply class attributes when initializing,
16
+ they are only used for the connections,
17
+ which can be accessed within the `connections` dict.
18
+ """
19
+ def __init__(self, addr: str = None, port: int = None,
20
+ reader: asyncio.StreamReader = None,
21
+ writer: asyncio.StreamWriter = None):
22
+
23
+ self.addr = addr
24
+ self.port = port
25
+ self.reader = reader
26
+ self.writer = writer
27
+ self.command_queue: asyncio.Queue[bytes] = asyncio.Queue(10)
28
+
29
+ async def start(self, HOST = "127.0.0.1", PORT = 8000):
30
+ server = await asyncio.start_server(self._interface, HOST, PORT)
31
+ logger.info(f"[LISTENING] Server is running on {HOST}:{PORT}")
32
+ async with server:
33
+ await server.serve_forever()
34
+
35
+ async def _interface(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
36
+ global conn_count
37
+ addr, port = writer.get_extra_info("peername")
38
+ logger.info(f"[CONNECTION] Connection opened for {addr} #{conn_count}")
39
+ conn_count += 1
40
+ try:
41
+ conn = self.__class__(addr, port, reader, writer)
42
+ connections[port] = conn
43
+ await conn._run()
44
+ except (asyncio.CancelledError, ConnectionResetError, BrokenPipeError, OSError):
45
+ conn_count -= 1
46
+ logger.info(f"[DISCONNECTED] Connection closed for {addr} #{conn_count-1}")
47
+ finally:
48
+ conn_count -= 1
49
+ logger.info(f"[DISCONNECTED] Connection closed for {addr} #{conn_count-1}")
50
+
51
+ async def _run(self):
52
+ listen_task = asyncio.create_task(self._listen())
53
+
54
+ await self._send()
55
+
56
+ listen_task.cancel()
57
+ self.writer.close()
58
+ await self.writer.wait_closed()
59
+
60
+ async def _listen(self):
61
+ try:
62
+ while True:
63
+ data = await self.reader.readline()
64
+ if not data:
65
+ break
66
+ await self.on_message(data)
67
+ except asyncio.CancelledError:
68
+ pass
69
+ except Exception as e:
70
+ logger.error(f"[ERROR] Reading error: {e}")
71
+
72
+ async def on_message(self):
73
+ """Override this function."""
74
+ pass
75
+
76
+ async def send_message(self, message: bytes):
77
+ if isinstance(message, str):
78
+ message = message.encode()
79
+ await self.command_queue.put(message)
80
+
81
+ async def _send(self):
82
+ try:
83
+ while True:
84
+ user_input: bytes = await self.command_queue.get()
85
+ message = user_input.strip()
86
+
87
+ if not message:
88
+ continue
89
+
90
+ if message.lower() == b"exit":
91
+ logger.info("Closing connection...")
92
+ break
93
+
94
+ self.writer.write(message)
95
+ await self.writer.drain()
96
+
97
+ except Exception as e:
98
+ logger.error(f"Writing error: {e}")
99
+
100
+ asyncio.run(Server().start())
@@ -0,0 +1,123 @@
1
+ import sys, time, inspect
2
+ from pathlib import Path
3
+ from collections.abc import Callable
4
+
5
+ default_LoSs = {
6
+ "UNSET": 0,
7
+ "DEBUG": 10,
8
+ "INFO": 20,
9
+ "WARN": 30,
10
+ "ERROR": 40,
11
+ "FATAL": 50
12
+ }
13
+
14
+ class Logger:
15
+ def __init__(self,
16
+ name: str,
17
+ file: str | Path = None,
18
+ reset_file_on_restart: bool = True,
19
+ console = sys.stdout,
20
+ min_console_severity: int = 0,
21
+ min_file_severity: int = 0,
22
+ time_format: str = "%Y-%m-%d %H:%M:%S",
23
+ LoSs: dict[str, int] = default_LoSs,
24
+ ):
25
+ self.reset_file_on_restart = reset_file_on_restart
26
+ self.name = name
27
+ self.console = console
28
+ self.file = file
29
+ self.time_format = time_format
30
+ self.LoSs = LoSs
31
+ self.RLoSs = {v: k for k, v in LoSs.items()}
32
+ self.min_console_severity = min_console_severity
33
+ self.min_file_severity = min_file_severity
34
+
35
+ if reset_file_on_restart and file:
36
+ with open(file, "w") as f:
37
+ f.write("")
38
+
39
+ def add_LoS(self, name: str, severity: int):
40
+ self.LoSs[name] = severity
41
+ self.RLoSs[severity] = name
42
+
43
+ def _premessage(self, severity):
44
+ tm = time.strftime(self.time_format)
45
+ return f"{tm} [{self.name}/{self.RLoSs[severity]}]: "
46
+
47
+ async def alog(self, severity: int, content: str, secondary: Callable = None, kwarg: str = None):
48
+ """Async compatible implementation of `log`"""
49
+ if isinstance(severity, str):
50
+ severity = self.LoSs[severity]
51
+
52
+ content = f"{self._premessage(severity)}{content}\n"
53
+
54
+ if secondary and inspect.iscoroutinefunction(secondary):
55
+ if kwarg:
56
+ await secondary(**{kwarg: content})
57
+ else:
58
+ await secondary(content)
59
+ elif secondary:
60
+ if kwarg:
61
+ secondary(**{kwarg: content})
62
+ else:
63
+ secondary(content)
64
+
65
+ if (severity >= self.min_console_severity) and (self.console):
66
+ self.console.write(content)
67
+ if inspect.iscoroutinefunction(self.console.flush):
68
+ await self.console.flush()
69
+ else:
70
+ self.console.flush()
71
+
72
+ if (severity >= self.min_file_severity) and (self.file):
73
+ with open(self.file, "a") as file:
74
+ file.write(content)
75
+
76
+ def log(self, severity: int, content: str, secondary: Callable = None, kwarg: str = None):
77
+ if isinstance(severity, str):
78
+ severity = self.LoSs[severity]
79
+
80
+ content = f"{self._premessage(severity)}{content}\n"
81
+
82
+ if secondary:
83
+ if kwarg:
84
+ secondary(**{kwarg: content})
85
+ else:
86
+ secondary(content)
87
+ if (severity >= self.min_console_severity) and (self.console):
88
+ self.console.write(content)
89
+ self.console.flush()
90
+
91
+ if (severity >= self.min_file_severity) and (self.file):
92
+ with open(self.file, "a") as file:
93
+ file.write(content)
94
+
95
+ def debug(self, content: str, secondary: Callable = None, kwarg: str = None):
96
+ self.log(self.LoSs["DEBUG"], content, secondary, kwarg)
97
+
98
+ def info(self, content: str, secondary: Callable = None, kwarg: str = None):
99
+ self.log(self.LoSs["INFO"], content, secondary, kwarg)
100
+
101
+ def warn(self, content: str, secondary: Callable = None, kwarg: str = None):
102
+ self.log(self.LoSs["WARN"], content, secondary, kwarg)
103
+
104
+ def error(self, content: str, secondary: Callable = None, kwarg: str = None):
105
+ self.log(self.LoSs["ERROR"], content, secondary, kwarg)
106
+
107
+ def fatal(self, content: str, secondary: Callable = None, kwarg: str = None):
108
+ self.log(self.LoSs["FATAL"], content, secondary, kwarg)
109
+
110
+ async def adebug(self, content: str, secondary: Callable = None, kwarg: str = None):
111
+ await self.alog(self.LoSs["DEBUG"], content, secondary, kwarg)
112
+
113
+ async def ainfo(self, content: str, secondary: Callable = None, kwarg: str = None):
114
+ await self.alog(self.LoSs["INFO"], content, secondary, kwarg)
115
+
116
+ async def awarn(self, content: str, secondary: Callable = None, kwarg: str = None):
117
+ await self.alog(self.LoSs["WARN"], content, secondary, kwarg)
118
+
119
+ async def aerror(self, content: str, secondary: Callable = None, kwarg: str = None):
120
+ await self.alog(self.LoSs["ERROR"], content, secondary, kwarg)
121
+
122
+ async def afatal(self, content: str, secondary: Callable = None, kwarg: str = None):
123
+ await self.alog(self.LoSs["FATAL"], content, secondary, kwarg)
@@ -1,35 +0,0 @@
1
- import asyncio, logging
2
- logger = logging.getLogger("ipc:server")
3
- __all__ = ["start"]
4
-
5
- open_connections = 1
6
-
7
- async def start(HOST = "127.0.0.1", PORT = 8000):
8
- server = await asyncio.start_server(interface, HOST, PORT)
9
- logger.info(f"[LISTENING] Server is running on {HOST}:{PORT}")
10
- async with server:
11
- await server.serve_forever()
12
-
13
- async def interface(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
14
- global open_connections
15
- addr = writer.get_extra_info("peername")
16
- logger.info(f"[CONNECTION] Connection opened for {addr} #{open_connections}")
17
- open_connections += 1
18
- try:
19
- while True:
20
- data = await reader.read(1024)
21
- if not data:
22
- break
23
- logger.info(f"[{addr}] Received: {data.decode()}")
24
- writer.write(b"Message processed")
25
- await writer.drain() # Ensure data is flushed to the network buffer
26
- except (asyncio.CancelledError, ConnectionResetError, BrokenPipeError, OSError):
27
- open_connections -= 1
28
- logger.info(f"[DISCONNECTED] Connection closed for {addr} #{open_connections-1}")
29
- writer.close()
30
- await writer.wait_closed()
31
- finally:
32
- open_connections -= 1
33
- logger.info(f"[DISCONNECTED] Connection closed for {addr} #{open_connections-1}")
34
- writer.close()
35
- await writer.wait_closed()
File without changes
File without changes