From 12e89c7e37c9be0cbf4700f9314cd7c81e2025cb Mon Sep 17 00:00:00 2001 From: Moosems <95927277+Moosems@users.noreply.github.com> Date: Sun, 28 Jul 2024 18:48:20 -0600 Subject: [PATCH 1/8] First commit Still need to: - finsh docs - add examples - refactor and prettify - Update readme to remove bottom text --- .github/workflows/pypi.yml | 2 +- .gitignore | 2 +- PROJECT_NAME/__init__.py | 5 - README.md | 8 +- collegamento/__init__.py | 16 +++ collegamento/files_variant.py | 121 +++++++++++++++++++ collegamento/ipc/__init__.py | 10 ++ collegamento/ipc/client.py | 220 ++++++++++++++++++++++++++++++++++ collegamento/ipc/misc.py | 43 +++++++ collegamento/ipc/server.py | 165 +++++++++++++++++++++++++ docs/source/conf.py | 2 +- docs/source/example-usage.rst | 2 +- docs/source/examples.rst | 4 +- docs/source/index.rst | 4 +- docs/source/installation.rst | 6 +- setup.py | 10 +- tests/test_file_variant.py | 41 +++++++ tests/test_simple.py | 37 ++++++ tests/test_xyz.py | 2 - 19 files changed, 673 insertions(+), 27 deletions(-) delete mode 100644 PROJECT_NAME/__init__.py create mode 100644 collegamento/__init__.py create mode 100644 collegamento/files_variant.py create mode 100644 collegamento/ipc/__init__.py create mode 100644 collegamento/ipc/client.py create mode 100644 collegamento/ipc/misc.py create mode 100644 collegamento/ipc/server.py create mode 100644 tests/test_file_variant.py create mode 100644 tests/test_simple.py delete mode 100644 tests/test_xyz.py diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 57915b5..38bdb5c 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -1,4 +1,4 @@ -name: Upload PROJECT_NAME to Pypi +name: Upload collegamento to Pypi on: release: diff --git a/.gitignore b/.gitignore index 3d690d7..f1de24a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ dist/ build/ .ruff_cache/ .pytest_cache/ -PROJECT_NAME.egg-info/ +collegamento.egg-info/ # Pycharm .idea diff --git a/PROJECT_NAME/__init__.py b/PROJECT_NAME/__init__.py deleted file mode 100644 index 9df0ecf..0000000 --- a/PROJECT_NAME/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from beartype.claw import beartype_this_package - -beartype_this_package() - -# xyz module level imports go here diff --git a/README.md b/README.md index 4d0d12e..762c257 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -

PROJECT_NAME v0.1.0

+

collegamento v0.1.0

-A tool that makes it much easier to do xyz. +A tool that makes it much easier to make offload work when asyncio isn't an option. # Installation -In the Command Line, paste the following: `pip install PROJECT_NAME` +In the Command Line, paste the following: `pip install collegamento` ## Description -PROJECT_NAME is a library that can be used for xyz. Docs are listed on this [ReadTheDocs page](https://PROJECT_NAME.readthedocs.io/en/master/) +Collegamento is a library that can be used for Client/Server IPC's with the goal of offloading major workloads to a second process. Docs are listed on this [ReadTheDocs page](https://collegamento.readthedocs.io/en/master/) ## Contributing diff --git a/collegamento/__init__.py b/collegamento/__init__.py new file mode 100644 index 0000000..a05f1d3 --- /dev/null +++ b/collegamento/__init__.py @@ -0,0 +1,16 @@ +from beartype.claw import beartype_this_package + +beartype_this_package() + +from .files_variant import ( # noqa: F401, E402 + FileClient, + FileNotification, + FileRequest, + FileServer, +) + +# xyz module level imports go here +from .ipc import USER_FUNCTION # noqa: F401, E402 +from .ipc import Client as SimpleClient # noqa: F401, E402 +from .ipc import Notification, Request, Response # noqa: F401, E402 +from .ipc import Server as SimpleServer # noqa: F401, E402 diff --git a/collegamento/files_variant.py b/collegamento/files_variant.py new file mode 100644 index 0000000..336aaf3 --- /dev/null +++ b/collegamento/files_variant.py @@ -0,0 +1,121 @@ +from logging import Logger +from multiprocessing.queues import Queue as GenericQueueClass +from typing import NotRequired + +from .ipc import USER_FUNCTION, Client, Notification, Request, Server + + +class FileRequest(Request): + # There may be commands that don't require a file but some might + file: NotRequired[str] + + +class FileNotification(Notification): + file: str + remove: bool + contents: NotRequired[str] + + +class FileClient(Client): + def __init__( + self, commands: dict[str, USER_FUNCTION], id_max: int = 15_000 + ) -> None: + self.files: dict[str, str] = {} + + super().__init__(commands, id_max, FileServer) + + def create_server(self) -> None: + """Creates the main_server through a subprocess - internal API""" + + super().create_server() + + self.logger.info("Copying files to server") + files_copy = self.files.copy() + self.files = {} + for file, data in files_copy.items(): + self.update_file(file, data) + self.logger.debug("Finished copying files to server") + + def update_file(self, file: str, current_state: str) -> None: + """Updates files in the system - external API""" + + self.logger.info(f"Updating file: {file}") + self.files[file] = current_state + + self.logger.debug("Creating notification dict") + notification: FileNotification = { + "id": super().create_message_id(), + "type": "notification", + "file": file, + "remove": False, + "contents": self.files[file], + } + + self.logger.debug("Notifying server of file update") + super().notify_server(notification) + + def remove_file(self, file: str) -> None: + """Removes a file from the main_server - external API""" + if file not in list(self.files.keys()): + self.logger.exception( + f"Cannot remove file {file} as file is not in file database!" + ) + raise Exception( + f"Cannot remove file {file} as file is not in file database!" + ) + + self.logger.info("Notifying server of file deletion") + # self.create_message("notification", remove=True, file=file) + notification: FileNotification = { + "id": super().create_message_id(), + "type": "notification", + "file": file, + "remove": True, + } + self.logger.debug("Notifying server of file removal") + super().notify_server(notification) + + +class FileServer(Server): + def __init__( + self, + commands: dict[str, USER_FUNCTION], + response_queue: GenericQueueClass, + requests_queue: GenericQueueClass, + logger: Logger, + ) -> 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) + + def handle_request(self, request: Request) -> None: + if "file" in request: + file = request["file"] + request["file"] = self.files[file] + + super().handle_request(request) diff --git a/collegamento/ipc/__init__.py b/collegamento/ipc/__init__.py new file mode 100644 index 0000000..416b173 --- /dev/null +++ b/collegamento/ipc/__init__.py @@ -0,0 +1,10 @@ +from .client import Client # noqa: F401, E402 +from .misc import ( # noqa: F401, E402 + USER_FUNCTION, + Notification, + Request, + RequestQueueType, + Response, + ResponseQueueType, +) +from .server import Server # noqa: F401, E402 diff --git a/collegamento/ipc/client.py b/collegamento/ipc/client.py new file mode 100644 index 0000000..7951b9e --- /dev/null +++ b/collegamento/ipc/client.py @@ -0,0 +1,220 @@ +from logging import Logger, getLogger +from multiprocessing import Process, Queue, freeze_support +from random import randint + +from .misc import ( + USER_FUNCTION, + Notification, + Request, + RequestQueueType, + Response, + ResponseQueueType, +) +from .server import Server + + +class Client: + """The IPC class is used to talk to the server and run commands. The public API includes the following methods: + - Client.notify_server() + - Client.request() + - Client.add_command() + - Client.cancel_request() + - Client.kill_IPC() + """ + + def __init__( + self, + commands: dict[str, USER_FUNCTION], + id_max: int = 15_000, + server_type: type = Server, + ) -> None: + self.all_ids: list[int] = [] + self.id_max = id_max + self.current_ids: dict[str, int] = {} + self.newest_responses: dict[str, Response | None] = {} + self.server_type: type[Server] = server_type + + self.commands = commands + for command in self.commands: + self.current_ids[command] = 0 + self.newest_responses[command] = None + + self.logger: Logger = getLogger("IPC") + self.logger.info("Creating server") + self.response_queue: ResponseQueueType = Queue() + self.requests_queue: RequestQueueType = Queue() + self.main_server: Process + self.create_server() + self.logger.info("Initialization is complete") + + def create_server(self) -> None: + """Creates the main_server through a subprocess - internal API""" + freeze_support() + server_logger = getLogger("Server") + self.main_server = Process( + target=self.server_type, + args=( + self.commands, + self.response_queue, + self.requests_queue, + server_logger, + ), + daemon=True, + ) + self.main_server.start() + self.logger.info("Server created") + + def create_message_id(self) -> int: + """Creates a Message based on the args and kwawrgs provided. Highly flexible. - internal API""" + self.logger.info("Creating message for server") + id = randint(1, self.id_max) # 0 is reserved for the empty case + while id in self.all_ids: + id = randint(1, self.id_max) + self.all_ids.append(id) + + self.logger.debug("ID for message created") + + if not self.main_server.is_alive(): + # No point in an id if the server's dead + self.logger.critical( + "Server was killed at some point, creating server" + ) + self.create_server() + + return id + + def notify_server( + self, + notification_dict: Notification, + ) -> 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, + ) -> None: + """Sends the main_server a request of type command with given kwargs - external API""" + self.logger.debug("Beginning request") + + # TODO: Should this just be a walrus operator? "(command := request_dict["command"])" + command: str = request_details["command"] + if command not in self.commands: + self.logger.exception( + f"Command {command} not in builtin commands. Those are {self.commands}!" + ) + raise Exception( + f"Command {command} not in builtin commands. Those are {self.commands}!" + ) + + self.logger.info("Creating request for server") + + id: int = self.create_message_id() + + self.current_ids[command] = id + final_request: Request = { + "id": id, + "type": "request", + "command": command, + } + final_request.update(request_details) # type: ignore + self.logger.debug(f"Request created: {final_request}") + + self.requests_queue.put(final_request) + self.logger.info("Message sent") + + def cancel_request(self, command: str) -> None: + """Cancels a request of type command - external API""" + if command not in self.commands: + self.logger.exception( + f"Cannot cancel command {command}, valid commands are {self.commands}" + ) + raise Exception( + f"Cannot cancel command {command}, valid commands are {self.commands}" + ) + + self.logger.info(f"Cancelled command: {command}") + self.current_ids[command] = 0 + + def parse_response(self, res: Response) -> None: + """Parses main_server output line and discards useless responses - internal API""" + self.logger.debug("Parsing server response") + id = res["id"] + self.all_ids.remove(id) + + if "command" not in res: + self.logger.info("Response was notification response") + return + + command = res["command"] + + if command == "add-command": + return + + if id != self.current_ids[command]: + self.logger.info("Response is from old request") + return + + self.logger.info(f"Response is useful for command type: {command}") + self.current_ids[command] = 0 + self.newest_responses[command] = res + + def check_responses(self) -> None: + """Checks all main_server output by calling IPC.parse_line() on each response - internal API""" + self.logger.debug("Checking responses") + while not self.response_queue.empty(): + self.parse_response(self.response_queue.get()) + + def get_response(self, command: str) -> Response | None: + """Checks responses and returns the current response of type command if it has been returned - external API""" + self.logger.info(f"Getting response for type: {command}") + if command not in self.commands: + self.logger.exception( + f"Cannot get response of command {command}, valid commands are {self.commands}" + ) + raise Exception( + f"Cannot get response of command {command}, valid commands are {self.commands}" + ) + + self.check_responses() + response: Response | None = self.newest_responses[command] + self.newest_responses[command] = None + self.logger.info("Response retrieved") + return response + + def add_commmand(self, name: str, command: USER_FUNCTION) -> None: + if name == "add-command": + self.logger.exception( + "Cannot add command add-command as it is a special builtin" + ) + raise Exception( + "Cannot add command add-command as it is a special builtin" + ) + + id: int = self.create_message_id() + final_request: Request = { + "id": id, + "type": "request", + "command": "add-command", + } + final_request.update({"name": name, "function": command}) # type: ignore + self.logger.debug(f"Add Command Request created: {final_request}") + + self.requests_queue.put(final_request) + self.logger.info("Message sent") + self.commands[name] = command + + def kill_IPC(self) -> None: + """Kills the main_server when salve_ipc's services are no longer required - external API""" + self.logger.info("Killing server") + self.main_server.kill() diff --git a/collegamento/ipc/misc.py b/collegamento/ipc/misc.py new file mode 100644 index 0000000..b734b13 --- /dev/null +++ b/collegamento/ipc/misc.py @@ -0,0 +1,43 @@ +from multiprocessing.queues import Queue as GenericQueueClass +from typing import TYPE_CHECKING, Any, NotRequired, TypedDict + +from beartype.typing import Callable + + +class Message(TypedDict): + """Base class for messages in and out of the server""" + + id: int + type: str # Can be "request", "response", "notification" + + +class Request(Message): + """Request from the IPC class to the server with command specific input""" + + 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""" + + cancelled: bool + command: NotRequired[str] + result: NotRequired[Any] + + +USER_FUNCTION = Callable[[Request], Any] + +if TYPE_CHECKING: + ResponseQueueType = GenericQueueClass[Response] + RequestQueueType = GenericQueueClass[Request | Notification] +# 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: + ResponseQueueType = GenericQueueClass + RequestQueueType = GenericQueueClass diff --git a/collegamento/ipc/server.py b/collegamento/ipc/server.py new file mode 100644 index 0000000..386c2f7 --- /dev/null +++ b/collegamento/ipc/server.py @@ -0,0 +1,165 @@ +from logging import Logger +from multiprocessing.queues import Queue as GenericQueueClass +from time import sleep +from typing import Any + +from .misc import ( + USER_FUNCTION, + Notification, + Request, + RequestQueueType, + Response, + ResponseQueueType, +) + + +class Server: + """Handles input from the user and returns output from special functions. Not an external API.""" + + def __init__( + self, + commands: dict[str, USER_FUNCTION], + response_queue: GenericQueueClass, + requests_queue: GenericQueueClass, + logger: Logger, + ) -> None: + self.logger: Logger = logger + self.logger.info("Starting server setup") + + self.response_queue: ResponseQueueType = response_queue + self.requests_queue: RequestQueueType = requests_queue + self.all_ids: list[int] = [] + self.newest_ids: dict[str, int] = {} + self.newest_requests: dict[str, Request | None] = {} + + self.commands: dict[str, USER_FUNCTION] = commands + for command in self.commands: + self.newest_ids[command] = 0 + self.newest_requests[command] = None + + self.logger.info("Server setup complete") + + while True: + self.run_tasks() + sleep(0.0025) + + def simple_id_response(self, id: int, cancelled: bool = True) -> None: + self.logger.debug(f"Creating simple response for id {id}") + response: Response = { + "id": id, + "type": "response", + "cancelled": cancelled, + } + self.logger.debug(f"Sending simple response for id {id}") + self.response_queue.put(response) + self.logger.info(f"Simple response for id {id} sent") + + def parse_line(self, message: Request | Notification) -> None: + self.logger.debug("Parsing Message from user") + id: int = message["id"] + + if message["type"] not in {"notification", "request"}: + self.logger.warning( + f"Unknown type {type}. Sending simple response" + ) + self.simple_id_response(id) + 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 + self.newest_ids[command] = id + self.newest_requests[command] = message # type: ignore + self.logger.debug("Request stored for parsing") + + 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 + ids = [] + for request in list(self.newest_requests.values()): + if request is not None: + ids.append(request["id"]) + + for request in self.all_ids: + if request in ids: + self.logger.debug(f"Id {request} is newest of its command") + continue + + self.logger.debug( + f"Id {request} is an old request, sending simple respone" + ) + self.simple_id_response(request) + + self.all_ids = [] + self.logger.debug("All ids list reset") + + def handle_request(self, request: Request) -> None: + command: str = request["command"] + id: int = self.newest_ids[command] + result: Any # noqa: F842 + + command = request["command"] + response: Response = { + "id": id, + "type": "response", + "cancelled": False, + "command": command, + } + + if command == "add-command": + self.commands[request["name"]] = request["function"] # type: ignore + response["result"] = None + response["cancelled"] = True + self.logger.debug("Response created") + self.response_queue.put(response) + self.newest_ids[command] = 0 + self.logger.info(f"Response sent for request of command {command}") + return + + if command not in self.commands: + self.logger.warning(f"Command {command} not recognized") + response["result"] = None + response["cancelled"] = True + else: + self.logger.debug(f"Running user function for command {command}") + response["result"] = self.commands[command](request) + + self.logger.debug("Response created") + self.response_queue.put(response) + self.newest_ids[command] = 0 + self.logger.info(f"Response sent for request of command {command}") + + def run_tasks(self) -> None: + if self.requests_queue.empty(): + return + + self.logger.debug("New request in queue") + while not self.requests_queue.empty(): + self.logger.debug("Parsing request") + self.parse_line(self.requests_queue.get()) + + if not self.all_ids: + self.logger.debug("All requests were notifications") + + self.logger.debug("Cancelling all old id's") + self.cancel_all_ids_except_newest() + + # Actual work + for request in list(self.newest_requests.values()): + if request is None: + continue + command: str = request["command"] + self.logger.info(f"Handling request of command {command}") + self.handle_request(request) + self.newest_requests[command] = None + self.logger.debug("Request completed") diff --git a/docs/source/conf.py b/docs/source/conf.py index f5e34ae..b4cc849 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -11,7 +11,7 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = "PROJECT_NAME" +project = "collegamento" copyright = "2024, Moosems" author = "Moosems" release = "v0.1.0" diff --git a/docs/source/example-usage.rst b/docs/source/example-usage.rst index 531b9a0..5e07c6a 100644 --- a/docs/source/example-usage.rst +++ b/docs/source/example-usage.rst @@ -2,7 +2,7 @@ Example Usage ============= -Now that you have ``PROJECT_NAME`` installed, let's try running a simple example that does xyz: +Now that you have ``collegamento`` installed, let's try running a simple example that does xyz: .. code-block:: python diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 77558e8..3b5ce4a 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -1,8 +1,8 @@ ========================= -``PROJECT_NAME`` Examples +``Collegamento`` Examples ========================= -Below are links to all the examples on using ``PROJECT_NAME``. +Below are links to all the examples on using ``Collegamento``. .. toctree:: :maxdepth: 1 diff --git a/docs/source/index.rst b/docs/source/index.rst index 9080b8f..a385bdc 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,8 +1,8 @@ ============================== -``PROJECT_NAME`` Documentation +``Collegamento`` Documentation ============================== -Welcome to ``PROJECT_NAME``'s Documentation! ``PROJECT_NAME`` is a library that can be to do xyz. To get started with ``PROJECT_NAME``, visit the :doc:`installation` page! +Welcome to ``Collegamento``'s Documentation! ``Collegamento`` is a library that can be to do xyz. To get started with ``Collegamento``, visit the :doc:`installation` page! .. note:: diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 5a2265d..d53ee05 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -2,12 +2,12 @@ Installation ============ -To start using ``PROJECT_NAME``, first install it using pip: +To start using ``Collegamento``, first install it using pip: .. code-block:: console - $ pip install PROJECT_NAME + $ pip install collegamento And it's installed! Congratulations on xyz! -Let's move on to the :doc:`example-usage` page to give ``PROJECT_NAME`` a try! +Let's move on to the :doc:`example-usage` page to give ``Collegamento`` a try! diff --git a/setup.py b/setup.py index 6c8a91a..013565e 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# pip install -U -r requirements-dev.txt --break-system-packages; pip uninstall albero -y --break-system-packages; pip install . --break-system-packages --no-build-isolation; python3 -m pytest . +# pip install -U -r requirements-dev.txt --break-system-packages; pip uninstall collegamento -y --break-system-packages; pip install . --break-system-packages --no-build-isolation; python3 -m pytest . from setuptools import setup with open("README.md", "r") as file: @@ -6,12 +6,12 @@ setup( - name="PROJECT_NAME", + name="collegamento", version="0.1.0", - description="PROJECT_NAME does xyz", + description="Collegamento provides an easy to use Client/Server IPC backend", author="Moosems", author_email="moosems.j@gmail.com", - url="https://github.com/salve-org/PROJECT_NAME", + url="https://github.com/salve-org/collegamento", long_description=long_description, long_description_content_type="text/markdown", install_requires=open("requirements.txt", "r+") @@ -27,5 +27,5 @@ "License :: OSI Approved :: MIT License", "Typing :: Typed", ], - packages=["PROJECT_NAME"], # , "PROJECT_NAME.subpackages"], + packages=["collegamento", "collegamento.ipc"], ) diff --git a/tests/test_file_variant.py b/tests/test_file_variant.py new file mode 100644 index 0000000..0f14bca --- /dev/null +++ b/tests/test_file_variant.py @@ -0,0 +1,41 @@ +from time import sleep + +from collegamento import USER_FUNCTION, FileClient, Request, Response + + +def func(test_arg: Request) -> bool: + return True + + +def split_str(arg: Request) -> list[str]: + file = arg["file"] # type: ignore + return file.split(" ") + + +def test_file_variants(): + commands: dict[str, USER_FUNCTION] = {"test": func} + context = FileClient(commands) + + context.update_file("test", "test contents") + context.request({"command": "test"}) + + sleep(1) + + output: Response | None = context.get_response("test") + assert output is not None # noqa: E711 + assert output["result"] is True # noqa: E712 # type: ignore + + context.add_commmand("test1", split_str) + context.request({"command": "test1", "file": "test"}) + + sleep(1) + + output: Response | None = context.get_response("test1") + assert output is not None # noqa: E711 + assert output["result"] == ["test", "contents"] # noqa: E712 # type: ignore + + context.kill_IPC() + + +if __name__ == "__main__": + test_file_variants() diff --git a/tests/test_simple.py b/tests/test_simple.py new file mode 100644 index 0000000..70c4629 --- /dev/null +++ b/tests/test_simple.py @@ -0,0 +1,37 @@ +from time import sleep + +from collegamento import USER_FUNCTION, Request, Response, SimpleClient + + +def foo(bar: Request) -> bool: + if bar["command"] == "test": + return True + return False + + +def test_Client_Server(): + commands: dict[str, USER_FUNCTION] = {"test": foo} + context = SimpleClient(commands) + + context.request({"command": "test"}) + + sleep(1) + + context.add_commmand("test1", foo) + + sleep(1) + + output: Response | None = context.get_response("test") + + assert output is not None # noqa: E711 + assert output["result"] == True # noqa: E712 # type: ignore + + context.request({"command": "test1"}) + + sleep(1) + + output: Response | None = context.get_response("test1") + assert output is not None # noqa: E711 + assert output["result"] == False # noqa: E712 # type: ignore + + context.kill_IPC() diff --git a/tests/test_xyz.py b/tests/test_xyz.py deleted file mode 100644 index 93f5228..0000000 --- a/tests/test_xyz.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_xyz(): - pass From 893f45dfd870d34ca04e2ac0e59da3dd8bcac53e Mon Sep 17 00:00:00 2001 From: Moosems <95927277+Moosems@users.noreply.github.com> Date: Sun, 28 Jul 2024 19:41:24 -0600 Subject: [PATCH 2/8] Add examples and continue docs --- collegamento/__init__.py | 13 +++-- collegamento/files_variant.py | 17 +++++-- .../{ipc => simple_client_server}/__init__.py | 4 +- .../{ipc => simple_client_server}/client.py | 8 +-- .../{ipc => simple_client_server}/misc.py | 0 .../{ipc => simple_client_server}/server.py | 2 +- docs/source/classes.rst | 41 ++++++++++++++-- docs/source/example-usage.rst | 32 +++++++++++- docs/source/examples/class_example.rst | 49 +++++++++++++++++++ docs/source/examples/file_example.rst | 35 +++++++++++++ docs/source/examples/simple_example.rst | 37 ++++++++++++++ docs/source/functions.rst | 10 ---- docs/source/index.rst | 2 - docs/source/installation.rst | 2 +- docs/source/variables.rst | 8 +-- examples/class_example.py | 41 ++++++++++++++++ examples/file_example.py | 27 ++++++++++ examples/simple_example.py | 29 +++++++++++ setup.py | 2 +- 19 files changed, 319 insertions(+), 40 deletions(-) rename collegamento/{ipc => simple_client_server}/__init__.py (58%) rename collegamento/{ipc => simple_client_server}/client.py (98%) rename collegamento/{ipc => simple_client_server}/misc.py (100%) rename collegamento/{ipc => simple_client_server}/server.py (99%) create mode 100644 docs/source/examples/class_example.rst create mode 100644 docs/source/examples/file_example.rst create mode 100644 docs/source/examples/simple_example.rst delete mode 100644 docs/source/functions.rst create mode 100644 examples/class_example.py create mode 100644 examples/file_example.py create mode 100644 examples/simple_example.py diff --git a/collegamento/__init__.py b/collegamento/__init__.py index a05f1d3..cb44f5a 100644 --- a/collegamento/__init__.py +++ b/collegamento/__init__.py @@ -9,8 +9,11 @@ FileServer, ) -# xyz module level imports go here -from .ipc import USER_FUNCTION # noqa: F401, E402 -from .ipc import Client as SimpleClient # noqa: F401, E402 -from .ipc import Notification, Request, Response # noqa: F401, E402 -from .ipc import Server as SimpleServer # noqa: F401, E402 +from .simple_client_server import ( # noqa: F401, E402 + USER_FUNCTION, + Notification, + Request, + Response, + SimpleClient, + SimpleServer, +) diff --git a/collegamento/files_variant.py b/collegamento/files_variant.py index 336aaf3..1921eed 100644 --- a/collegamento/files_variant.py +++ b/collegamento/files_variant.py @@ -2,7 +2,13 @@ from multiprocessing.queues import Queue as GenericQueueClass from typing import NotRequired -from .ipc import USER_FUNCTION, Client, Notification, Request, Server +from .simple_client_server import ( + USER_FUNCTION, + SimpleClient, + Notification, + Request, + SimpleServer, +) class FileRequest(Request): @@ -16,7 +22,11 @@ class FileNotification(Notification): contents: NotRequired[str] -class FileClient(Client): +class FileClient(SimpleClient): + """File handling variant of SImpleClient. Extra methods: + - FileClient.update_file() + - FileClient.remove_file() + """ def __init__( self, commands: dict[str, USER_FUNCTION], id_max: int = 15_000 ) -> None: @@ -76,7 +86,8 @@ def remove_file(self, file: str) -> None: super().notify_server(notification) -class FileServer(Server): +class FileServer(SimpleServer): + """File handling variant of SimpleServer""" def __init__( self, commands: dict[str, USER_FUNCTION], diff --git a/collegamento/ipc/__init__.py b/collegamento/simple_client_server/__init__.py similarity index 58% rename from collegamento/ipc/__init__.py rename to collegamento/simple_client_server/__init__.py index 416b173..ca06316 100644 --- a/collegamento/ipc/__init__.py +++ b/collegamento/simple_client_server/__init__.py @@ -1,4 +1,4 @@ -from .client import Client # noqa: F401, E402 +from .client import SimpleClient # noqa: F401, E402 from .misc import ( # noqa: F401, E402 USER_FUNCTION, Notification, @@ -7,4 +7,4 @@ Response, ResponseQueueType, ) -from .server import Server # noqa: F401, E402 +from .server import SimpleServer # noqa: F401, E402 diff --git a/collegamento/ipc/client.py b/collegamento/simple_client_server/client.py similarity index 98% rename from collegamento/ipc/client.py rename to collegamento/simple_client_server/client.py index 7951b9e..5b97211 100644 --- a/collegamento/ipc/client.py +++ b/collegamento/simple_client_server/client.py @@ -10,10 +10,10 @@ Response, ResponseQueueType, ) -from .server import Server +from .server import SimpleServer -class Client: +class SimpleClient: """The IPC class is used to talk to the server and run commands. The public API includes the following methods: - Client.notify_server() - Client.request() @@ -26,13 +26,13 @@ def __init__( self, commands: dict[str, USER_FUNCTION], id_max: int = 15_000, - server_type: type = Server, + server_type: type = SimpleServer, ) -> None: self.all_ids: list[int] = [] self.id_max = id_max self.current_ids: dict[str, int] = {} self.newest_responses: dict[str, Response | None] = {} - self.server_type: type[Server] = server_type + self.server_type: type[SimpleServer] = server_type self.commands = commands for command in self.commands: diff --git a/collegamento/ipc/misc.py b/collegamento/simple_client_server/misc.py similarity index 100% rename from collegamento/ipc/misc.py rename to collegamento/simple_client_server/misc.py diff --git a/collegamento/ipc/server.py b/collegamento/simple_client_server/server.py similarity index 99% rename from collegamento/ipc/server.py rename to collegamento/simple_client_server/server.py index 386c2f7..e4e1acd 100644 --- a/collegamento/ipc/server.py +++ b/collegamento/simple_client_server/server.py @@ -13,7 +13,7 @@ ) -class Server: +class SimpleServer: """Handles input from the user and returns output from special functions. Not an external API.""" def __init__( diff --git a/docs/source/classes.rst b/docs/source/classes.rst index 9fa76fc..4cf0b43 100644 --- a/docs/source/classes.rst +++ b/docs/source/classes.rst @@ -2,11 +2,42 @@ Classes ======= -.. _Example Class Overview: +.. _Notification Overview: -``Example_Class`` -***************** +``Notification`` +**************** -``Exampmle_class`` can do: +The ``Notification`` class is for xyz. - - xyz +.. _Request Overview: + +``Request`` +*********** + +The ``Request`` class is for xyz. + +.. _Response Overview: + +``Response`` +************ + +The ``Response`` class is for xyz. + +.. _SimpleClient Overview: + +``SimpleClient`` +**************** + +The ``SimpleClient`` class can do: + +- ``SimpleClient.xyz`` +- ``SimpleClient.yippee`` + +.. _FileClient Overview: + +``FileClient`` +************** + +``FileClient`` can do: + +- ``xyz`` diff --git a/docs/source/example-usage.rst b/docs/source/example-usage.rst index 5e07c6a..7864c5c 100644 --- a/docs/source/example-usage.rst +++ b/docs/source/example-usage.rst @@ -2,8 +2,36 @@ Example Usage ============= -Now that you have ``collegamento`` installed, let's try running a simple example that does xyz: +Now that you have ``Collegamento`` installed, let's try running a simple example that should print out ``"Yippee! It worked!"``: .. code-block:: python - xyz + from time import sleep + + from collegamento import USER_FUNCTION, Request, Response, SimpleClient + + + def foo(bar: Request) -> bool: + if bar["command"] == "test": + return True + return False + + + def main(): + commands: dict[str, USER_FUNCTION] = {"test": foo} + context = SimpleClient(commands) + + context.request({"command": "test"}) + + sleep(1) + + output: Response | None = context.get_response("test") + if output is not None and output["result"] == True: # 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 new file mode 100644 index 0000000..c35833f --- /dev/null +++ b/docs/source/examples/class_example.rst @@ -0,0 +1,49 @@ +============= +Class Example +============= + +.. code-block:: python + + from time import sleep + + from collegamento import FileClient, Request + + + class MyClient: + def __init__(self): + self.context = FileClient({"MyClientFunc": self.split_str}) + + self.context.update_file("user_file", "") + + def change_file(self, new_contents: str) -> None: + self.context.update_file("user_file", new_contents) + + def request_split(self) -> None: + self.context.request({"command": "MyClientFunc", "file": "user_file"}) + + def check_split(self) -> list[str] | None: + output = self.context.get_response("MyClientFunc") + if output is not None: + return output["result"] # type: ignore + return output + + def split_str(self, arg: Request) -> list[str]: + file = arg["file"] # type: ignore + return file.split(" ") + + + def main(): + mc = MyClient() + mc.change_file("Test File") + mc.request_split() + + sleep(1) + + output = mc.check_split() + print(output) + + + if __name__ == "__main__": + main() + +See the file example file `here `_. \ No newline at end of file diff --git a/docs/source/examples/file_example.rst b/docs/source/examples/file_example.rst new file mode 100644 index 0000000..33c3fee --- /dev/null +++ b/docs/source/examples/file_example.rst @@ -0,0 +1,35 @@ +============ +File Example +============ + +.. code-block:: python + + from time import sleep + + from collegamento import USER_FUNCTION, FileClient, Request, Response + + + def split_str(arg: Request) -> list[str]: + file = arg["file"] # type: ignore + return file.split(" ") + + + def main(): + commands: dict[str, USER_FUNCTION] = {"test": split_str} + context = FileClient(commands) + + context.update_file("test", "test contents") + context.request({"command": "test", "file": "test"}) + + sleep(1) + + output: Response | None = context.get_response("test") + print(output) + + context.kill_IPC() + + + if __name__ == "__main__": + main() + +See the file example file `here `_. \ No newline at end of file diff --git a/docs/source/examples/simple_example.rst b/docs/source/examples/simple_example.rst new file mode 100644 index 0000000..08f6ad9 --- /dev/null +++ b/docs/source/examples/simple_example.rst @@ -0,0 +1,37 @@ +============== +Simple Example +============== + +.. code-block:: python + + from time import sleep + + from collegamento import USER_FUNCTION, Request, Response, SimpleClient + + + def foo(bar: Request) -> bool: + if bar["command"] == "test": + return True + return False + + + def main(): + commands: dict[str, USER_FUNCTION] = {"test": foo} + context = SimpleClient(commands) + + context.request({"command": "test"}) + + sleep(1) + + output: Response | None = context.get_response("test") + if output is not None and output["result"] == True: # type: ignore + print("Yippee! It worked!") + else: + print("Aww, maybe your compute is just a little slow?") + + context.kill_IPC() + + if __name__ == "__main__": + main() + +See the file example file `here `_. \ No newline at end of file diff --git a/docs/source/functions.rst b/docs/source/functions.rst deleted file mode 100644 index c112dee..0000000 --- a/docs/source/functions.rst +++ /dev/null @@ -1,10 +0,0 @@ -========= -Functions -========= - -.. _Example Function Overview: - -``Example_Function()`` -********************** - -Example_Function() does xyz diff --git a/docs/source/index.rst b/docs/source/index.rst index a385bdc..71d6f2c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,6 +15,4 @@ Welcome to ``Collegamento``'s Documentation! ``Collegamento`` is a library that installation example-usage classes - variables - functions examples diff --git a/docs/source/installation.rst b/docs/source/installation.rst index d53ee05..c81d838 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -8,6 +8,6 @@ To start using ``Collegamento``, first install it using pip: $ pip install collegamento -And it's installed! Congratulations on xyz! +And it's installed! Congratulations on freeing up your main thread! Let's move on to the :doc:`example-usage` page to give ``Collegamento`` a try! diff --git a/docs/source/variables.rst b/docs/source/variables.rst index 299e57c..eada3c8 100644 --- a/docs/source/variables.rst +++ b/docs/source/variables.rst @@ -2,9 +2,9 @@ Variables ========= -.. _Example Variable Overview: +.. _USER_FUNCTION Overview: -``Example variable`` -******************** +``USER_FUNCTION`` +***************** -Example variable does xyz +USER_FUNCTION does xyz diff --git a/examples/class_example.py b/examples/class_example.py new file mode 100644 index 0000000..02d55b9 --- /dev/null +++ b/examples/class_example.py @@ -0,0 +1,41 @@ +from time import sleep + +from collegamento import FileClient, Request + + +class MyClient: + def __init__(self): + self.context = FileClient({"MyClientFunc": self.split_str}) + + self.context.update_file("user_file", "") + + def change_file(self, new_contents: str) -> None: + self.context.update_file("user_file", new_contents) + + def request_split(self) -> None: + self.context.request({"command": "MyClientFunc", "file": "user_file"}) + + def check_split(self) -> list[str] | None: + output = self.context.get_response("MyClientFunc") + if output is not None: + return output["result"] # type: ignore + return output + + def split_str(self, arg: Request) -> list[str]: + file = arg["file"] # type: ignore + return file.split(" ") + + +def main(): + mc = MyClient() + mc.change_file("Test File") + mc.request_split() + + sleep(1) + + output = mc.check_split() + print(output) + + +if __name__ == "__main__": + main() diff --git a/examples/file_example.py b/examples/file_example.py new file mode 100644 index 0000000..a60e993 --- /dev/null +++ b/examples/file_example.py @@ -0,0 +1,27 @@ +from time import sleep + +from collegamento import USER_FUNCTION, FileClient, Request, Response + + +def split_str(arg: Request) -> list[str]: + file = arg["file"] # type: ignore + return file.split(" ") + + +def main(): + commands: dict[str, USER_FUNCTION] = {"test": split_str} + context = FileClient(commands) + + context.update_file("test", "test contents") + context.request({"command": "test", "file": "test"}) + + sleep(1) + + output: Response | None = context.get_response("test") + print(output) + + context.kill_IPC() + + +if __name__ == "__main__": + main() diff --git a/examples/simple_example.py b/examples/simple_example.py new file mode 100644 index 0000000..98fd1e8 --- /dev/null +++ b/examples/simple_example.py @@ -0,0 +1,29 @@ +from time import sleep + +from collegamento import USER_FUNCTION, Request, Response, SimpleClient + + +def foo(bar: Request) -> bool: + if bar["command"] == "test": + return True + return False + + +def main(): + commands: dict[str, USER_FUNCTION] = {"test": foo} + context = SimpleClient(commands) + + context.request({"command": "test"}) + + sleep(1) + + output: Response | None = context.get_response("test") + if output is not None and output["result"] == True: # 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/setup.py b/setup.py index 013565e..5d852d2 100644 --- a/setup.py +++ b/setup.py @@ -27,5 +27,5 @@ "License :: OSI Approved :: MIT License", "Typing :: Typed", ], - packages=["collegamento", "collegamento.ipc"], + packages=["collegamento", "collegamento.simple_client_server"], ) From 880d56fe6ef3d211120a56ab95908f824606c80e Mon Sep 17 00:00:00 2001 From: Moosems <95927277+Moosems@users.noreply.github.com> Date: Sun, 28 Jul 2024 19:42:35 -0600 Subject: [PATCH 3/8] Formatting --- collegamento/__init__.py | 1 - collegamento/files_variant.py | 8 +++++--- docs/source/examples/simple_example.rst | 1 + examples/simple_example.py | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/collegamento/__init__.py b/collegamento/__init__.py index cb44f5a..feb88cd 100644 --- a/collegamento/__init__.py +++ b/collegamento/__init__.py @@ -8,7 +8,6 @@ FileRequest, FileServer, ) - from .simple_client_server import ( # noqa: F401, E402 USER_FUNCTION, Notification, diff --git a/collegamento/files_variant.py b/collegamento/files_variant.py index 1921eed..809f3a9 100644 --- a/collegamento/files_variant.py +++ b/collegamento/files_variant.py @@ -4,9 +4,9 @@ from .simple_client_server import ( USER_FUNCTION, - SimpleClient, Notification, Request, + SimpleClient, SimpleServer, ) @@ -24,9 +24,10 @@ class FileNotification(Notification): class FileClient(SimpleClient): """File handling variant of SImpleClient. Extra methods: - - FileClient.update_file() - - FileClient.remove_file() + - FileClient.update_file() + - FileClient.remove_file() """ + def __init__( self, commands: dict[str, USER_FUNCTION], id_max: int = 15_000 ) -> None: @@ -88,6 +89,7 @@ def remove_file(self, file: str) -> None: class FileServer(SimpleServer): """File handling variant of SimpleServer""" + def __init__( self, commands: dict[str, USER_FUNCTION], diff --git a/docs/source/examples/simple_example.rst b/docs/source/examples/simple_example.rst index 08f6ad9..7226e75 100644 --- a/docs/source/examples/simple_example.rst +++ b/docs/source/examples/simple_example.rst @@ -31,6 +31,7 @@ Simple Example context.kill_IPC() + if __name__ == "__main__": main() diff --git a/examples/simple_example.py b/examples/simple_example.py index 98fd1e8..ae9c585 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -25,5 +25,6 @@ def main(): context.kill_IPC() + if __name__ == "__main__": main() From 66461cf53f6a92b97e0740044ee4f708ad1b8726 Mon Sep 17 00:00:00 2001 From: Moosems <95927277+Moosems@users.noreply.github.com> Date: Sun, 28 Jul 2024 19:44:12 -0600 Subject: [PATCH 4/8] Ruff check fix --- examples/simple_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_example.py b/examples/simple_example.py index ae9c585..1b5441e 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -18,7 +18,7 @@ def main(): 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?") From 1ae4972a0b5659ecd5be54674967ca6f8abe2d2c Mon Sep 17 00:00:00 2001 From: Moosems <95927277+Moosems@users.noreply.github.com> Date: Mon, 29 Jul 2024 01:05:33 -0600 Subject: [PATCH 5/8] Fix bugs and improve tests marginally --- collegamento/files_variant.py | 4 +--- collegamento/simple_client_server/client.py | 1 - tests/test_file_variant.py | 2 ++ tests/test_simple.py | 2 ++ 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/collegamento/files_variant.py b/collegamento/files_variant.py index 809f3a9..08c37de 100644 --- a/collegamento/files_variant.py +++ b/collegamento/files_variant.py @@ -54,9 +54,7 @@ def update_file(self, file: str, current_state: str) -> None: self.files[file] = current_state self.logger.debug("Creating notification dict") - notification: FileNotification = { - "id": super().create_message_id(), - "type": "notification", + notification: FileNotification = { # type: ignore "file": file, "remove": False, "contents": self.files[file], diff --git a/collegamento/simple_client_server/client.py b/collegamento/simple_client_server/client.py index 5b97211..aa7dcbb 100644 --- a/collegamento/simple_client_server/client.py +++ b/collegamento/simple_client_server/client.py @@ -96,7 +96,6 @@ def notify_server( } final_notification.update(notification_dict) self.logger.debug(f"Notification created: {final_notification}") - self.requests_queue.put(final_notification) self.logger.info("Message sent") diff --git a/tests/test_file_variant.py b/tests/test_file_variant.py index 0f14bca..ca2174c 100644 --- a/tests/test_file_variant.py +++ b/tests/test_file_variant.py @@ -34,6 +34,8 @@ def test_file_variants(): assert output is not None # noqa: E711 assert output["result"] == ["test", "contents"] # noqa: E712 # type: ignore + assert context.all_ids == [] + context.kill_IPC() diff --git a/tests/test_simple.py b/tests/test_simple.py index 70c4629..d943df3 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -34,4 +34,6 @@ def test_Client_Server(): assert output is not None # noqa: E711 assert output["result"] == False # noqa: E712 # type: ignore + assert context.all_ids == [] + context.kill_IPC() From 03b427270b4d551988b0f65ac80c3b5b9a4c07ad Mon Sep 17 00:00:00 2001 From: Moosems <95927277+Moosems@users.noreply.github.com> Date: Mon, 29 Jul 2024 01:29:38 -0600 Subject: [PATCH 6/8] Custom error class and continue docs --- collegamento/__init__.py | 1 + collegamento/files_variant.py | 3 ++- collegamento/simple_client_server/__init__.py | 1 + collegamento/simple_client_server/client.py | 23 ++++++++++--------- collegamento/simple_client_server/misc.py | 3 +++ docs/source/classes.rst | 20 +++++++++------- tests/test_file_variant.py | 2 +- tests/test_simple.py | 2 +- 8 files changed, 33 insertions(+), 22 deletions(-) diff --git a/collegamento/__init__.py b/collegamento/__init__.py index feb88cd..609381e 100644 --- a/collegamento/__init__.py +++ b/collegamento/__init__.py @@ -10,6 +10,7 @@ ) from .simple_client_server import ( # noqa: F401, E402 USER_FUNCTION, + CollegamentoError, Notification, Request, Response, diff --git a/collegamento/files_variant.py b/collegamento/files_variant.py index 08c37de..0a869d2 100644 --- a/collegamento/files_variant.py +++ b/collegamento/files_variant.py @@ -4,6 +4,7 @@ from .simple_client_server import ( USER_FUNCTION, + CollegamentoError, Notification, Request, SimpleClient, @@ -69,7 +70,7 @@ def remove_file(self, file: str) -> None: self.logger.exception( f"Cannot remove file {file} as file is not in file database!" ) - raise Exception( + raise CollegamentoError( f"Cannot remove file {file} as file is not in file database!" ) diff --git a/collegamento/simple_client_server/__init__.py b/collegamento/simple_client_server/__init__.py index ca06316..1ae4f93 100644 --- a/collegamento/simple_client_server/__init__.py +++ b/collegamento/simple_client_server/__init__.py @@ -1,6 +1,7 @@ from .client import SimpleClient # noqa: F401, E402 from .misc import ( # noqa: F401, E402 USER_FUNCTION, + CollegamentoError, Notification, Request, RequestQueueType, diff --git a/collegamento/simple_client_server/client.py b/collegamento/simple_client_server/client.py index aa7dcbb..a457705 100644 --- a/collegamento/simple_client_server/client.py +++ b/collegamento/simple_client_server/client.py @@ -4,6 +4,7 @@ from .misc import ( USER_FUNCTION, + CollegamentoError, Notification, Request, RequestQueueType, @@ -15,11 +16,11 @@ class SimpleClient: """The IPC class is used to talk to the server and run commands. The public API includes the following methods: - - Client.notify_server() - - Client.request() - - Client.add_command() - - Client.cancel_request() - - Client.kill_IPC() + - SimpleClient.notify_server() + - SimpleClient.request() + - SimpleClient.add_command() + - SimpleClient.cancel_request() + - SimpleClient.kill_IPC() """ def __init__( @@ -85,7 +86,7 @@ def create_message_id(self) -> int: def notify_server( self, - notification_dict: Notification, + notification_dict: dict, ) -> None: self.logger.info("Creating notification for server") @@ -112,7 +113,7 @@ def request( self.logger.exception( f"Command {command} not in builtin commands. Those are {self.commands}!" ) - raise Exception( + raise CollegamentoError( f"Command {command} not in builtin commands. Those are {self.commands}!" ) @@ -138,7 +139,7 @@ def cancel_request(self, command: str) -> None: self.logger.exception( f"Cannot cancel command {command}, valid commands are {self.commands}" ) - raise Exception( + raise CollegamentoError( f"Cannot cancel command {command}, valid commands are {self.commands}" ) @@ -181,7 +182,7 @@ def get_response(self, command: str) -> Response | None: self.logger.exception( f"Cannot get response of command {command}, valid commands are {self.commands}" ) - raise Exception( + raise CollegamentoError( f"Cannot get response of command {command}, valid commands are {self.commands}" ) @@ -191,12 +192,12 @@ def get_response(self, command: str) -> Response | None: self.logger.info("Response retrieved") return response - def add_commmand(self, name: str, command: USER_FUNCTION) -> None: + def add_command(self, name: str, command: USER_FUNCTION) -> None: if name == "add-command": self.logger.exception( "Cannot add command add-command as it is a special builtin" ) - raise Exception( + raise CollegamentoError( "Cannot add command add-command as it is a special builtin" ) diff --git a/collegamento/simple_client_server/misc.py b/collegamento/simple_client_server/misc.py index b734b13..8b7a86b 100644 --- a/collegamento/simple_client_server/misc.py +++ b/collegamento/simple_client_server/misc.py @@ -41,3 +41,6 @@ class Response(Message): else: ResponseQueueType = GenericQueueClass RequestQueueType = GenericQueueClass + + +class CollegamentoError(Exception): ... # I don't like the boilerplate either diff --git a/docs/source/classes.rst b/docs/source/classes.rst index 4cf0b43..a387389 100644 --- a/docs/source/classes.rst +++ b/docs/source/classes.rst @@ -2,26 +2,26 @@ Classes ======= -.. _Notification Overview: +.. _CollegamentoError Overview: -``Notification`` -**************** +``CollegamentoError`` +********************* -The ``Notification`` class is for xyz. +The ``CollegamentoError`` class is a simple error class for ``Collegamento``. .. _Request Overview: ``Request`` *********** -The ``Request`` class is for xyz. +The ``Request`` class is a TypedDict meant to provide a framework for items given to functions used by the IPC. It *will* almsot always contain extra items regardless of the fact that that's not supposed to happen (just add ``# type: ignore`` to the end of the line to shut up the langserver). The data provided will not be typed checked to make sure its proper. The responsibility of data rests on the user. .. _Response Overview: ``Response`` ************ -The ``Response`` class is for xyz. +The ``Response`` class is what is returned by the ``SimpleClient`` or one of it's variants to the user. The useful data is found at ``some_response["result"]``. .. _SimpleClient Overview: @@ -30,8 +30,12 @@ The ``Response`` class is for xyz. The ``SimpleClient`` class can do: -- ``SimpleClient.xyz`` -- ``SimpleClient.yippee`` +- ``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 ``Request`` and returns anything`` +- ``SimpleClient.cancel_request(command: str)`` (will not give output for any requests of type command. When is this ever used? No idea! Why? Because it doesn't actually stop the server from completing it) +TODO: remove this stupid feature. No point in it. +- ``SimpleClient.kill_IPC()`` (kills the IPC server) .. _FileClient Overview: diff --git a/tests/test_file_variant.py b/tests/test_file_variant.py index ca2174c..025890e 100644 --- a/tests/test_file_variant.py +++ b/tests/test_file_variant.py @@ -25,7 +25,7 @@ def test_file_variants(): assert output is not None # noqa: E711 assert output["result"] is True # noqa: E712 # type: ignore - context.add_commmand("test1", split_str) + context.add_command("test1", split_str) context.request({"command": "test1", "file": "test"}) sleep(1) diff --git a/tests/test_simple.py b/tests/test_simple.py index d943df3..b4c623b 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -17,7 +17,7 @@ def test_Client_Server(): sleep(1) - context.add_commmand("test1", foo) + context.add_command("test1", foo) sleep(1) From ab28d033df0b61a3ff8292e27809e9d457ae17b6 Mon Sep 17 00:00:00 2001 From: Moosems <95927277+Moosems@users.noreply.github.com> Date: Mon, 29 Jul 2024 01:35:19 -0600 Subject: [PATCH 7/8] Fix TODO's --- collegamento/simple_client_server/client.py | 21 ++++----------------- docs/source/classes.rst | 2 -- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/collegamento/simple_client_server/client.py b/collegamento/simple_client_server/client.py index a457705..3cf7208 100644 --- a/collegamento/simple_client_server/client.py +++ b/collegamento/simple_client_server/client.py @@ -19,7 +19,6 @@ class SimpleClient: - SimpleClient.notify_server() - SimpleClient.request() - SimpleClient.add_command() - - SimpleClient.cancel_request() - SimpleClient.kill_IPC() """ @@ -107,9 +106,10 @@ def request( """Sends the main_server a request of type command with given kwargs - external API""" self.logger.debug("Beginning request") - # TODO: Should this just be a walrus operator? "(command := request_dict["command"])" - command: str = request_details["command"] - if command not in self.commands: + # NOTE: this variable could've been a standalone line but I thought it would just be better + # to use the walrus operator. No point in a language feature if its never used. Plus, + # it also looks quite nice :D + if (command := request_details["command"]) not in self.commands: self.logger.exception( f"Command {command} not in builtin commands. Those are {self.commands}!" ) @@ -133,19 +133,6 @@ def request( self.requests_queue.put(final_request) self.logger.info("Message sent") - def cancel_request(self, command: str) -> None: - """Cancels a request of type command - external API""" - if command not in self.commands: - self.logger.exception( - f"Cannot cancel command {command}, valid commands are {self.commands}" - ) - raise CollegamentoError( - f"Cannot cancel command {command}, valid commands are {self.commands}" - ) - - self.logger.info(f"Cancelled command: {command}") - self.current_ids[command] = 0 - def parse_response(self, res: Response) -> None: """Parses main_server output line and discards useless responses - internal API""" self.logger.debug("Parsing server response") diff --git a/docs/source/classes.rst b/docs/source/classes.rst index a387389..67cb34d 100644 --- a/docs/source/classes.rst +++ b/docs/source/classes.rst @@ -33,8 +33,6 @@ 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 ``Request`` and returns anything`` -- ``SimpleClient.cancel_request(command: str)`` (will not give output for any requests of type command. When is this ever used? No idea! Why? Because it doesn't actually stop the server from completing it) -TODO: remove this stupid feature. No point in it. - ``SimpleClient.kill_IPC()`` (kills the IPC server) .. _FileClient Overview: From dc60140199f71af4d4aa13ea06b9fab77f637f44 Mon Sep 17 00:00:00 2001 From: Moosems <95927277+Moosems@users.noreply.github.com> Date: Mon, 29 Jul 2024 01:46:34 -0600 Subject: [PATCH 8/8] Finish up docs --- README.md | 2 -- collegamento/__init__.py | 7 +------ collegamento/files_variant.py | 7 ++----- docs/source/classes.rst | 11 +++++++---- docs/source/examples/simple_example.rst | 2 +- docs/source/index.rst | 3 ++- docs/source/variables.rst | 2 +- 7 files changed, 14 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 762c257..2ea1ac9 100644 --- a/README.md +++ b/README.md @@ -22,5 +22,3 @@ Currently 3.11 is the minimum (instead of 3.10) as this was developed under 3.12 ## License This project is licensed under the MIT License - see the [LICENSE](./LICENSE). - -Keywords to `git grep` for when using template are `xyz` and `PROJECT_NAME` diff --git a/collegamento/__init__.py b/collegamento/__init__.py index 609381e..2731795 100644 --- a/collegamento/__init__.py +++ b/collegamento/__init__.py @@ -2,12 +2,7 @@ beartype_this_package() -from .files_variant import ( # noqa: F401, E402 - FileClient, - FileNotification, - FileRequest, - FileServer, -) +from .files_variant import FileClient, FileServer # noqa: F401, E402 from .simple_client_server import ( # noqa: F401, E402 USER_FUNCTION, CollegamentoError, diff --git a/collegamento/files_variant.py b/collegamento/files_variant.py index 0a869d2..3ac5d8b 100644 --- a/collegamento/files_variant.py +++ b/collegamento/files_variant.py @@ -55,7 +55,7 @@ def update_file(self, file: str, current_state: str) -> None: self.files[file] = current_state self.logger.debug("Creating notification dict") - notification: FileNotification = { # type: ignore + notification: dict = { "file": file, "remove": False, "contents": self.files[file], @@ -75,10 +75,7 @@ def remove_file(self, file: str) -> None: ) self.logger.info("Notifying server of file deletion") - # self.create_message("notification", remove=True, file=file) - notification: FileNotification = { - "id": super().create_message_id(), - "type": "notification", + notification: dict = { "file": file, "remove": True, } diff --git a/docs/source/classes.rst b/docs/source/classes.rst index 67cb34d..c353fd4 100644 --- a/docs/source/classes.rst +++ b/docs/source/classes.rst @@ -21,7 +21,7 @@ The ``Request`` class is a TypedDict meant to provide a framework for items give ``Response`` ************ -The ``Response`` class is what is returned by the ``SimpleClient`` or one of it's variants to the user. The useful data is found at ``some_response["result"]``. +The ``Response`` class is what is returned by the "ref:`SimpleClient Overview` or one of it's variants to the user. The useful data is found at ``some_response["result"]``. .. _SimpleClient Overview: @@ -32,7 +32,7 @@ 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 ``Request`` and returns anything`` +- ``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) .. _FileClient Overview: @@ -40,6 +40,9 @@ The ``SimpleClient`` class can do: ``FileClient`` ************** -``FileClient`` can do: +``FileClient`` has the additional methods: -- ``xyz`` +- ``FileClient.update_file(file: str, current_state: str)`` (adds or updates the file with the new contents and notifies server of changes) +- ``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. diff --git a/docs/source/examples/simple_example.rst b/docs/source/examples/simple_example.rst index 7226e75..e58ff13 100644 --- a/docs/source/examples/simple_example.rst +++ b/docs/source/examples/simple_example.rst @@ -24,7 +24,7 @@ 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?") diff --git a/docs/source/index.rst b/docs/source/index.rst index 71d6f2c..222f5ef 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -2,7 +2,7 @@ ``Collegamento`` Documentation ============================== -Welcome to ``Collegamento``'s Documentation! ``Collegamento`` is a library that can be to do xyz. To get started with ``Collegamento``, visit the :doc:`installation` page! +Welcome to ``Collegamento``'s Documentation! ``Collegamento`` is a library that can be used for Client/Server IPC's with the goal of offloading major workloads to a second process. To get started with ``Collegamento``, visit the :doc:`installation` page! .. note:: @@ -14,5 +14,6 @@ Welcome to ``Collegamento``'s Documentation! ``Collegamento`` is a library that installation example-usage + variables classes examples diff --git a/docs/source/variables.rst b/docs/source/variables.rst index eada3c8..7161d8f 100644 --- a/docs/source/variables.rst +++ b/docs/source/variables.rst @@ -7,4 +7,4 @@ Variables ``USER_FUNCTION`` ***************** -USER_FUNCTION does xyz +``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.