Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V0.2.0 #3

Merged
merged 6 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ Fixes #__.
To do:
- [ ] ...

Cuurent changes in PR:
Current changes in PR:
- ...
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.1.1</h1>
<h1 align="center">collegamento v0.2.0</h1>

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

Expand Down
1 change: 0 additions & 1 deletion collegamento/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from .simple_client_server import ( # noqa: F401, E402
USER_FUNCTION,
CollegamentoError,
Notification,
Request,
Response,
SimpleClient,
Expand Down
65 changes: 29 additions & 36 deletions collegamento/files_variant.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from .simple_client_server import (
USER_FUNCTION,
CollegamentoError,
Notification,
Request,
SimpleClient,
SimpleServer,
Expand All @@ -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):
Expand All @@ -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"""

Expand Down Expand Up @@ -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"""
Expand All @@ -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):
Expand All @@ -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]

Expand Down
4 changes: 1 addition & 3 deletions collegamento/simple_client_server/__init__.py
Original file line number Diff line number Diff line change
@@ -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
18 changes: 0 additions & 18 deletions collegamento/simple_client_server/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from .misc import (
USER_FUNCTION,
CollegamentoError,
Notification,
Request,
RequestQueueType,
Response,
Expand All @@ -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()
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 2 additions & 8 deletions collegamento/simple_client_server/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""

Expand All @@ -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:
Expand Down
33 changes: 19 additions & 14 deletions collegamento/simple_client_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from .misc import (
USER_FUNCTION,
Notification,
Request,
RequestQueueType,
Response,
Expand All @@ -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")
Expand All @@ -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:
Expand All @@ -54,26 +55,18 @@ 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"
)
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
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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"]
Expand Down
15 changes: 14 additions & 1 deletion docs/source/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``
Expand All @@ -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 ;).
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.1.1"
release = "v0.2.0"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
7 changes: 4 additions & 3 deletions docs/source/example-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
4 changes: 2 additions & 2 deletions docs/source/examples/class_example.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Class Example

from time import sleep

from collegamento import FileClient, Request
from collegamento import FileClient, FileServer, Request


class MyClient:
Expand All @@ -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(" ")

Expand Down
Loading
Loading