diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 127a43f..4be529a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,5 +3,5 @@ Fixes #__. To do: - [ ] ... -Cuurent changes in PR: +Current changes in PR: - ... diff --git a/README.md b/README.md index c2204d8..e4e4357 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

collegamento v0.1.1

+

collegamento v0.2.0

A tool that makes it much easier to make offload work when asyncio isn't an option. diff --git a/collegamento/__init__.py b/collegamento/__init__.py index 2731795..fdc6461 100644 --- a/collegamento/__init__.py +++ b/collegamento/__init__.py @@ -6,7 +6,6 @@ from .simple_client_server import ( # noqa: F401, E402 USER_FUNCTION, CollegamentoError, - Notification, Request, Response, SimpleClient, diff --git a/collegamento/files_variant.py b/collegamento/files_variant.py index 44cf72a..5e81417 100644 --- a/collegamento/files_variant.py +++ b/collegamento/files_variant.py @@ -5,7 +5,6 @@ from .simple_client_server import ( USER_FUNCTION, CollegamentoError, - Notification, Request, SimpleClient, SimpleServer, @@ -17,10 +16,17 @@ class FileRequest(Request): file: NotRequired[str] -class FileNotification(Notification): - file: str - remove: bool - contents: NotRequired[str] +def update_files(server: "FileServer", request: Request) -> None: + file: str = request["file"] # type: ignore + + if request["remove"]: # type: ignore + server.logger.info(f"File {file} was requested for removal") + server.files.pop(file) + server.logger.info(f"File {file} has been removed") + else: + contents: str = request["contents"] # type: ignore + server.files[file] = contents + server.logger.info(f"File {file} has been updated with new contents") class FileClient(SimpleClient): @@ -34,8 +40,12 @@ def __init__( ) -> None: self.files: dict[str, str] = {} + commands["FileNotification"] = update_files + super().__init__(commands, id_max, FileServer) + self.priority_commands = ["FileNotification"] + def create_server(self) -> None: """Creates the main_server through a subprocess - internal API""" @@ -71,14 +81,15 @@ def update_file(self, file: str, current_state: str) -> None: self.files[file] = current_state self.logger.debug("Creating notification dict") - notification: dict = { + file_notification: dict = { + "command": "FileNotification", "file": file, "remove": False, "contents": self.files[file], } self.logger.debug("Notifying server of file update") - super().notify_server(notification) + super().request(file_notification) def remove_file(self, file: str) -> None: """Removes a file from the main_server - external API""" @@ -91,12 +102,13 @@ def remove_file(self, file: str) -> None: ) self.logger.info("Notifying server of file deletion") - notification: dict = { + file_notification: dict = { + "command": "FileNotification", "file": file, "remove": True, } self.logger.debug("Notifying server of file removal") - super().notify_server(notification) + super().request(file_notification) class FileServer(SimpleServer): @@ -111,35 +123,16 @@ def __init__( ) -> None: self.files: dict[str, str] = {} - super().__init__(commands, response_queue, requests_queue, logger) - - def parse_line(self, message: Request | Notification) -> None: - self.logger.debug("Parsing Message from user - pre-super") - id: int = message["id"] - - if message["type"] == "notification": - self.logger.debug("Mesage is of type notification") - - file: str = message["file"] # type: ignore - - if message["remove"]: # type: ignore - self.logger.info(f"File {file} was requested for removal") - self.files.pop(file) - self.logger.info(f"File {file} has been removed") - else: - contents: str = message["contents"] # type: ignore - self.files[file] = contents - self.logger.info( - f"File {file} has been updated with new contents" - ) - - self.simple_id_response(id, False) - return - - super().parse_line(message) + super().__init__( + commands, + response_queue, + requests_queue, + logger, + ["FileNotification"], + ) def handle_request(self, request: Request) -> None: - if "file" in request: + if "file" in request and request["command"] != "FileNotification": file = request["file"] request["file"] = self.files[file] diff --git a/collegamento/simple_client_server/__init__.py b/collegamento/simple_client_server/__init__.py index 1ae4f93..e49df62 100644 --- a/collegamento/simple_client_server/__init__.py +++ b/collegamento/simple_client_server/__init__.py @@ -1,11 +1,9 @@ -from .client import SimpleClient # noqa: F401, E402 +from .client import SimpleClient, SimpleServer # noqa: F401, E402 from .misc import ( # noqa: F401, E402 USER_FUNCTION, CollegamentoError, - Notification, Request, RequestQueueType, Response, ResponseQueueType, ) -from .server import SimpleServer # noqa: F401, E402 diff --git a/collegamento/simple_client_server/client.py b/collegamento/simple_client_server/client.py index 3cf7208..e336f42 100644 --- a/collegamento/simple_client_server/client.py +++ b/collegamento/simple_client_server/client.py @@ -5,7 +5,6 @@ from .misc import ( USER_FUNCTION, CollegamentoError, - Notification, Request, RequestQueueType, Response, @@ -16,7 +15,6 @@ class SimpleClient: """The IPC class is used to talk to the server and run commands. The public API includes the following methods: - - SimpleClient.notify_server() - SimpleClient.request() - SimpleClient.add_command() - SimpleClient.kill_IPC() @@ -83,22 +81,6 @@ def create_message_id(self) -> int: return id - def notify_server( - self, - notification_dict: dict, - ) -> None: - self.logger.info("Creating notification for server") - - id: int = self.create_message_id() - final_notification: Notification = { - "id": id, - "type": "notification", - } - final_notification.update(notification_dict) - self.logger.debug(f"Notification created: {final_notification}") - self.requests_queue.put(final_notification) - self.logger.info("Message sent") - def request( self, request_details: dict, diff --git a/collegamento/simple_client_server/misc.py b/collegamento/simple_client_server/misc.py index 8b7a86b..6f5e7d4 100644 --- a/collegamento/simple_client_server/misc.py +++ b/collegamento/simple_client_server/misc.py @@ -17,12 +17,6 @@ class Request(Message): command: str -class Notification(Message): - """Notifies the server to store or update its storage of something""" - - contents: NotRequired[Any] - - class Response(Message): """Server responses to requests and notifications""" @@ -31,11 +25,11 @@ class Response(Message): result: NotRequired[Any] -USER_FUNCTION = Callable[[Request], Any] +USER_FUNCTION = Callable[["SimpleServer", Request], Any] # type: ignore if TYPE_CHECKING: ResponseQueueType = GenericQueueClass[Response] - RequestQueueType = GenericQueueClass[Request | Notification] + RequestQueueType = GenericQueueClass[Request] # Else, this is CPython < 3.12. We are now in the No Man's Land # of Typing. In this case, avoid subscripting "GenericQueue". Ugh. else: diff --git a/collegamento/simple_client_server/server.py b/collegamento/simple_client_server/server.py index e4e1acd..718df1a 100644 --- a/collegamento/simple_client_server/server.py +++ b/collegamento/simple_client_server/server.py @@ -5,7 +5,6 @@ from .misc import ( USER_FUNCTION, - Notification, Request, RequestQueueType, Response, @@ -22,6 +21,7 @@ def __init__( response_queue: GenericQueueClass, requests_queue: GenericQueueClass, logger: Logger, + priority_commands: list[str] = [], ) -> None: self.logger: Logger = logger self.logger.info("Starting server setup") @@ -31,6 +31,7 @@ def __init__( self.all_ids: list[int] = [] self.newest_ids: dict[str, int] = {} self.newest_requests: dict[str, Request | None] = {} + self.priority_commands: list[str] = priority_commands self.commands: dict[str, USER_FUNCTION] = commands for command in self.commands: @@ -54,11 +55,11 @@ def simple_id_response(self, id: int, cancelled: bool = True) -> None: self.response_queue.put(response) self.logger.info(f"Simple response for id {id} sent") - def parse_line(self, message: Request | Notification) -> None: + def parse_line(self, message: Request) -> None: self.logger.debug("Parsing Message from user") id: int = message["id"] - if message["type"] not in {"notification", "request"}: + if message["type"] != "request": self.logger.warning( f"Unknown type {type}. Sending simple response" ) @@ -66,14 +67,6 @@ def parse_line(self, message: Request | Notification) -> None: self.logger.debug(f"Simple response for id {id} sent") return - if message["type"] == "notification": - self.logger.debug("Mesage is of type notification") - self.simple_id_response(id, False) - self.logger.debug( - f"Notification response for id {id} has been sent" - ) - return - self.logger.info(f"Mesage with id {id} is of type request") self.all_ids.append(id) command: str = message["command"] # type: ignore @@ -84,7 +77,7 @@ def parse_line(self, message: Request | Notification) -> None: def cancel_all_ids_except_newest(self) -> None: self.logger.info("Cancelling all old id's") - # NOTE: Used to be list comprehension but thats ugly + # NOTE: It used to be list comprehension but that was ugly ids = [] for request in list(self.newest_requests.values()): if request is not None: @@ -132,7 +125,7 @@ def handle_request(self, request: Request) -> None: response["cancelled"] = True else: self.logger.debug(f"Running user function for command {command}") - response["result"] = self.commands[command](request) + response["result"] = self.commands[command](self, request) self.logger.debug("Response created") self.response_queue.put(response) @@ -155,7 +148,19 @@ def run_tasks(self) -> None: self.cancel_all_ids_except_newest() # Actual work - for request in list(self.newest_requests.values()): + requests_list: list[Request] = [ + request + for request in self.newest_requests.values() + if request is not None + ] + requests_list = sorted( + requests_list, + key=lambda request: ( + request["command"] not in self.priority_commands, + ), + ) + + for request in requests_list: if request is None: continue command: str = request["command"] diff --git a/docs/source/classes.rst b/docs/source/classes.rst index c353fd4..40bde22 100644 --- a/docs/source/classes.rst +++ b/docs/source/classes.rst @@ -30,11 +30,17 @@ The ``Response`` class is what is returned by the "ref:`SimpleClient Overview` o The ``SimpleClient`` class can do: -- ``SimpleClient.notify_server(notification_dict: dict)`` (as a base class, this has no use case, but it will likely be used by any given subclass) - ``SimpleClient.request(request_details: dict)`` (all details in request_details are specific to the command in the request_details) - ``SimpleClient.add_command(name: str, command: USER_FUNCTION)`` (adds the function with the name provided that takes input of :ref:`Request Overview` and returns anything`` - ``SimpleClient.kill_IPC()`` (kills the IPC server) +.. _SimpleServer Overview: + +``SimpleServer`` +**************** + +The SimpleServer is a backend piece of code made visible for commands that can be given to a ``SimpleClient``. If you want to know more about it, check out the source code ;). + .. _FileClient Overview: ``FileClient`` @@ -46,3 +52,10 @@ The ``SimpleClient`` class can do: - ``FileClient.remove_file(file: str)`` (removes the file specified from the system and notifies the server to fo the same) This class also has some changed functionality. When you make a ``.request()`` and add a file to the request, it chnages the request's name to its contents for the function to use. + +.. _FileServer Overview: + +``FileServer`` +************** + +The ``FileServer`` is a backend piece of code made visible for commands that can be given to a ``FileClient``. If you want to know more about it, check out the source code ;). diff --git a/docs/source/conf.py b/docs/source/conf.py index e253d96..42b4788 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,7 +14,7 @@ project = "collegamento" copyright = "2024, Moosems" author = "Moosems" -release = "v0.1.1" +release = "v0.2.0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/source/example-usage.rst b/docs/source/example-usage.rst index 7864c5c..a3096ff 100644 --- a/docs/source/example-usage.rst +++ b/docs/source/example-usage.rst @@ -8,10 +8,10 @@ Now that you have ``Collegamento`` installed, let's try running a simple example from time import sleep - from collegamento import USER_FUNCTION, Request, Response, SimpleClient + from collegamento import USER_FUNCTION, Request, Response, SimpleClient, SimpleServer - def foo(bar: Request) -> bool: + def foo(server: "SimpleServer", bar: Request) -> bool: if bar["command"] == "test": return True return False @@ -26,12 +26,13 @@ Now that you have ``Collegamento`` installed, let's try running a simple example sleep(1) output: Response | None = context.get_response("test") - if output is not None and output["result"] == True: # type: ignore + if output is not None and output["result"]: # type: ignore print("Yippee! It worked!") else: print("Aww, maybe your compute is just a little slow?") context.kill_IPC() + if __name__ == "__main__": main() diff --git a/docs/source/examples/class_example.rst b/docs/source/examples/class_example.rst index c35833f..b4798bc 100644 --- a/docs/source/examples/class_example.rst +++ b/docs/source/examples/class_example.rst @@ -6,7 +6,7 @@ Class Example from time import sleep - from collegamento import FileClient, Request + from collegamento import FileClient, FileServer, Request class MyClient: @@ -27,7 +27,7 @@ Class Example return output["result"] # type: ignore return output - def split_str(self, arg: Request) -> list[str]: + def split_str(self, server: FileServer, arg: Request) -> list[str]: file = arg["file"] # type: ignore return file.split(" ") diff --git a/docs/source/examples/file_example.rst b/docs/source/examples/file_example.rst index 33c3fee..17f2720 100644 --- a/docs/source/examples/file_example.rst +++ b/docs/source/examples/file_example.rst @@ -6,10 +6,16 @@ File Example from time import sleep - from collegamento import USER_FUNCTION, FileClient, Request, Response + from collegamento import ( + USER_FUNCTION, + FileClient, + FileServer, + Request, + Response, + ) - def split_str(arg: Request) -> list[str]: + def split_str(server: "FileServer", arg: Request) -> list[str]: file = arg["file"] # type: ignore return file.split(" ") @@ -19,6 +25,7 @@ File Example context = FileClient(commands) context.update_file("test", "test contents") + sleep(1) context.request({"command": "test", "file": "test"}) sleep(1) diff --git a/docs/source/examples/simple_example.rst b/docs/source/examples/simple_example.rst index e58ff13..6451b6e 100644 --- a/docs/source/examples/simple_example.rst +++ b/docs/source/examples/simple_example.rst @@ -4,12 +4,16 @@ Simple Example .. code-block:: python - from time import sleep + from collegamento import ( + USER_FUNCTION, + Request, + Response, + SimpleClient, + SimpleServer, + ) - from collegamento import USER_FUNCTION, Request, Response, SimpleClient - - def foo(bar: Request) -> bool: + def foo(server: "SimpleServer", bar: Request) -> bool: if bar["command"] == "test": return True return False @@ -21,8 +25,6 @@ Simple Example context.request({"command": "test"}) - sleep(1) - output: Response | None = context.get_response("test") if output is not None and output["result"]: # type: ignore print("Yippee! It worked!") diff --git a/docs/source/variables.rst b/docs/source/variables.rst index 7161d8f..8718369 100644 --- a/docs/source/variables.rst +++ b/docs/source/variables.rst @@ -7,4 +7,4 @@ Variables ``USER_FUNCTION`` ***************** -``USER_FUNCTION`` is a type variable that simply states that any function that matches this type takes in a :ref:`Request Overview` class and returns anything or even nothing. +``USER_FUNCTION`` is a type variable that simply states that any function that matches this type takes in a :ref:`SimpleServer Overview` and :ref:`Request Overview` class (positionally) and returns anything or even nothing. diff --git a/examples/class_example.py b/examples/class_example.py index 02d55b9..f0143ca 100644 --- a/examples/class_example.py +++ b/examples/class_example.py @@ -1,6 +1,6 @@ from time import sleep -from collegamento import FileClient, Request +from collegamento import FileClient, FileServer, Request class MyClient: @@ -21,7 +21,7 @@ def check_split(self) -> list[str] | None: return output["result"] # type: ignore return output - def split_str(self, arg: Request) -> list[str]: + def split_str(self, server: FileServer, arg: Request) -> list[str]: file = arg["file"] # type: ignore return file.split(" ") diff --git a/examples/file_example.py b/examples/file_example.py index a60e993..edf71b4 100644 --- a/examples/file_example.py +++ b/examples/file_example.py @@ -1,9 +1,15 @@ from time import sleep -from collegamento import USER_FUNCTION, FileClient, Request, Response +from collegamento import ( + USER_FUNCTION, + FileClient, + FileServer, + Request, + Response, +) -def split_str(arg: Request) -> list[str]: +def split_str(server: "FileServer", arg: Request) -> list[str]: file = arg["file"] # type: ignore return file.split(" ") @@ -13,6 +19,7 @@ def main(): context = FileClient(commands) context.update_file("test", "test contents") + sleep(1) context.request({"command": "test", "file": "test"}) sleep(1) diff --git a/examples/simple_example.py b/examples/simple_example.py index 1b5441e..9cb46e2 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -1,9 +1,13 @@ -from time import sleep +from collegamento import ( + USER_FUNCTION, + Request, + Response, + SimpleClient, + SimpleServer, +) -from collegamento import USER_FUNCTION, Request, Response, SimpleClient - -def foo(bar: Request) -> bool: +def foo(server: "SimpleServer", bar: Request) -> bool: if bar["command"] == "test": return True return False @@ -15,8 +19,6 @@ def main(): context.request({"command": "test"}) - sleep(1) - output: Response | None = context.get_response("test") if output is not None and output["result"]: # type: ignore print("Yippee! It worked!") diff --git a/setup.py b/setup.py index fb62ca3..f8a140c 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="collegamento", - version="0.1.1", + version="0.2.0", description="Collegamento provides an easy to use Client/Server IPC backend", author="Moosems", author_email="moosems.j@gmail.com", diff --git a/tests/test_file_variant.py b/tests/test_file_variant.py index 025890e..df0a126 100644 --- a/tests/test_file_variant.py +++ b/tests/test_file_variant.py @@ -1,13 +1,19 @@ from time import sleep -from collegamento import USER_FUNCTION, FileClient, Request, Response +from collegamento import ( + USER_FUNCTION, + FileClient, + FileServer, + Request, + Response, +) -def func(test_arg: Request) -> bool: +def func(server: FileServer, request: Request) -> bool: return True -def split_str(arg: Request) -> list[str]: +def split_str(server: FileServer, arg: Request) -> list[str]: file = arg["file"] # type: ignore return file.split(" ") diff --git a/tests/test_simple.py b/tests/test_simple.py index b4c623b..fcafcb4 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -1,9 +1,15 @@ from time import sleep -from collegamento import USER_FUNCTION, Request, Response, SimpleClient +from collegamento import ( + USER_FUNCTION, + Request, + Response, + SimpleClient, + SimpleServer, +) -def foo(bar: Request) -> bool: +def foo(server: SimpleServer, bar: Request) -> bool: if bar["command"] == "test": return True return False @@ -14,9 +20,6 @@ def test_Client_Server(): context = SimpleClient(commands) context.request({"command": "test"}) - - sleep(1) - context.add_command("test1", foo) sleep(1)