Skip to content

Commit

Permalink
Prepare Release Candidate 0
Browse files Browse the repository at this point in the history
  • Loading branch information
Moosems committed Sep 7, 2024
1 parent a252db8 commit e804381
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 57 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h1 align="center">collegamento v0.2.0</h1>
<h1 align="center">collegamento v0.3.0-rc0</h1>

A tool that makes it much easier to make offload work when asyncio isn't an option.

Expand Down
37 changes: 25 additions & 12 deletions collegamento/client_server/client.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# TODO: make sure everything is type hinted while removing redundancy
"""Defines the Client and Server class which provides a convenient and easy to use IPC interface.
>>> def test(*args):
... return
>>> c = Client({"test": (test, True)})
>>> c.request({"command": "test"})
>>> c.request({"command": "test"})
>>> from time import sleep
>>> sleep(1)
>>> assert c.get_response("test") is not None
>>> res = c.get_response("test")
>>> res
[{...}, {...}]
>>> assert len(res) == 2
>>> c.kill_IPC()
"""

Expand All @@ -20,7 +23,9 @@
USER_FUNCTION,
CollegamentoError,
Request,
RequestQueueType,
Response,
ResponseQueueType,
)


Expand All @@ -47,13 +52,13 @@ def __init__(
The most common input is commands and id_max. server_type is only really useful for wrappers."""

self.all_ids: list[int] = []
self.id_max = id_max
self.id_max: int = id_max

# int corresponds to str and str to int = int -> str & str -> int
self.current_ids: dict[str | int, int | str] = {}

self.newest_responses: dict[str, list[Response]] = {}
self.server_type = server_type
self.server_type: type = server_type

self.commands: dict[str, tuple[USER_FUNCTION, bool]] = {}

Expand All @@ -67,8 +72,8 @@ def __init__(
self.commands[command] = func
self.newest_responses[command] = []

self.request_queue: Queue
self.response_queue: Queue
self.request_queue: RequestQueueType
self.response_queue: ResponseQueueType
self.main_process: Process
self.create_server()

Expand All @@ -81,6 +86,9 @@ def create_server(self):
# so it doesn't try to access queues that no longer exist
self.main_process.terminate()

self.check_responses()
self.all_ids = [] # The remaining ones will never have been finished

self.request_queue = Queue()
self.response_queue = Queue()
self.main_process = Process(
Expand All @@ -99,7 +107,7 @@ def create_message_id(self) -> int:

# In cases where there are many many requests being sent it may be faster to choose a
# random id than to iterate through the list of id's and find an unclaimed one
id = randint(1, self.id_max) # 0 is reserved for the empty case
id: int = 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)
Expand Down Expand Up @@ -136,13 +144,13 @@ def request(self, command: str, **kwargs) -> None:

def parse_response(self, res: Response) -> None:
"""Parses main process output and discards useless responses - internal API"""
id = res["id"]
id: int = res["id"]
self.all_ids.remove(id)

if "command" not in res:
return

command = res["command"]
command: str = res["command"]

if command == "add-command":
return
Expand Down Expand Up @@ -177,7 +185,9 @@ def get_response(self, command: str) -> Response | list[Response] | None:

# If we know that the command doesn't allow multiple requests don't give a list
if not self.commands[command][1]:
return response[0]
return response[
0
] # Will only ever be one but we know its at index 0

return response

Expand All @@ -198,8 +208,11 @@ def add_command(
"type": "request",
"command": "add-command",
}
command_tuple = (command, multiple_requests)
final_request.update({"name": name, "function": command_tuple}) # type: ignore
command_tuple: tuple[USER_FUNCTION, bool] = (
command,
multiple_requests,
)
final_request.update(**{"name": name, "function": command_tuple})

self.request_queue.put(final_request)
self.commands[name] = command_tuple
Expand Down
4 changes: 2 additions & 2 deletions collegamento/client_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def parse_line(self, message: Request) -> None:
self.simple_id_response(id)
return

command: str = message["command"] # type: ignore
command: str = message["command"]

if command == "add-command":
request_name: str = message["name"] # type: ignore
Expand All @@ -91,7 +91,7 @@ def parse_line(self, message: Request) -> None:
self.newest_requests[command].append(message)

def cancel_old_ids(self) -> None:
accepted_ids = [
accepted_ids: list[int] = [
request["id"]
for request_list in list(self.newest_requests.values())
for request in request_list
Expand Down
2 changes: 0 additions & 2 deletions collegamento/client_server/test_func.py

This file was deleted.

19 changes: 11 additions & 8 deletions collegamento/client_server/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from beartype.typing import Callable


# TODO: Move away from TypedDict
class Message(TypedDict):
"""Base class for messages in and out of the server"""

Expand All @@ -25,19 +26,21 @@ class Response(Message):
result: NotRequired[Any]


class CollegamentoError(Exception): ... # I don't like the boilerplate either


USER_FUNCTION = Callable[["Server", Request], Any] # type: ignore
COMMANDS_MAPPING = dict[
str, USER_FUNCTION | tuple[USER_FUNCTION, bool]
] # if bool is true the command allows multiple requests


ResponseQueueType = GenericQueueClass
RequestQueueType = GenericQueueClass

# "If this is CPython < 3.12. We are now in the No Man's Land
# of Typing. In this case, avoid subscripting "GenericQueue". Ugh."
# - @leycec, maintainer of the amazing @beartype
if TYPE_CHECKING:
ResponseQueueType = GenericQueueClass[Response]
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:
ResponseQueueType = GenericQueueClass
RequestQueueType = GenericQueueClass


class CollegamentoError(Exception): ... # I don't like the boilerplate either
47 changes: 18 additions & 29 deletions collegamento/files_variant.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ def update_files(server: "FileServer", request: Request) -> None:

if request["remove"]: # type: ignore
server.files.pop(file)
else:
contents: str = request["contents"] # type: ignore
server.files[file] = contents
return

contents: str = request["contents"] # type: ignore
server.files[file] = contents


class FileClient(Client):
Expand All @@ -47,18 +48,15 @@ def create_server(self) -> None:

super().create_server()

files_copy = self.files.copy()
self.files = {}
for file, data in files_copy.items():
for file, data in self.files.items():
self.update_file(file, data)

def request(self, command: str, **kwargs) -> None:
if "file" in kwargs:
file = kwargs["file"]
if file not in self.files:
raise CollegamentoError(
f"File {file} not in files! Files are {self.files.keys()}"
)
file: str | None = kwargs.get("file")
if file and file not in self.files:
raise CollegamentoError(
f"File {file} not in files! Files are {self.files.keys()}"
)

super().request(command, **kwargs)

Expand All @@ -67,14 +65,12 @@ def update_file(self, file: str, current_state: str) -> None:

self.files[file] = current_state

file_notification: dict = {
"command": "FileNotification",
"file": file,
"remove": False,
"contents": self.files[file],
}

super().request(**file_notification)
super().request(
"FileNotification",
file=file,
remove=False,
contents=self.files[file],
)

def remove_file(self, file: str) -> None:
"""Removes a file from the main_server - external API"""
Expand All @@ -83,13 +79,7 @@ def remove_file(self, file: str) -> None:
f"Cannot remove file {file} as file is not in file database!"
)

file_notification: dict = {
"command": "FileNotification",
"file": file,
"remove": True,
}

super().request(**file_notification)
super().request("FileNotification", file=file, remove=True)


class FileServer(Server):
Expand All @@ -112,7 +102,6 @@ def __init__(

def handle_request(self, request: Request) -> None:
if "file" in request and request["command"] != "FileNotification":
file = request["file"]
request["file"] = self.files[file]
request["file"] = self.files[request["file"]]

super().handle_request(request)
16 changes: 15 additions & 1 deletion docs/source/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The ``CollegamentoError`` class is a simple error class for ``Collegamento``.
``Request``
***********

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.
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 they aren't outlined in the ``TypedDict`` (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:

Expand Down Expand Up @@ -71,3 +71,17 @@ This class also has some changed functionality. When you make a ``.request()`` a
**************

The ``FileServer`` is a backend piece of code made visible for commands that can be given to a ``FileClient``. See my explanation on :ref:`Server Overview`

.. _RequestQueueType Overview:

``RequestQueueType``
*********************

A subtype hint for `multiprocessing.Queue[Request]` that is meant to be used for the ``Request`` class that defaults to `multiprocessing.Queue` if the python version is less than 3.12.

.. _ResponseQueueType Overview:

``ResponseQueueType``
**********************

A subtype hint for `multiprocessing.Queue[Response]` that is meant to be used for the ``Response`` class that defaults to `multiprocessing.Queue` if the python version is less than 3.12.
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
project = "collegamento"
copyright = "2024, Moosems"
author = "Moosems"
release = "v0.2.0"
release = "v0.3.0-rc0"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
7 changes: 7 additions & 0 deletions docs/source/variables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ Variables
*****************

``USER_FUNCTION`` is a type variable that simply states that any function that matches this type takes in a :ref:`Server Overview` and :ref:`Request Overview` class (positionally) and can return anything it pleases (though this will never be used).

.. _COMMANDS_MAPPING Overview:

``COMMANDS_MAPPING``
********************

``COMMANDS_MAPPING`` is a type variable that states that any dictionary that matches this type has keys that are strings and values that are either functions that match the :ref:`USER_FUNCTION Overview` type or a tuple with that function and a boolean that indicates whether the function can have multiple :ref:``Request Overview``'s.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name="collegamento",
version="0.2.0",
version="0.3.0-rc0",
description="Collegamento provides an easy to use Client/Server IPC backend",
author="Moosems",
author_email="[email protected]",
Expand Down
2 changes: 2 additions & 0 deletions tests/test_file_variant.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ def test_file_variants():

context.update_file("test", "test contents")
context.update_file("test2", "test contents2")
sleep(1)
context.create_server()
context.request("test")

sleep(1)
Expand Down

0 comments on commit e804381

Please sign in to comment.