Skip to content

Integration of client & ml model id in end-to-end ml streaming flow #196

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

Open
wants to merge 29 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
74c6696
`/register/` endpoint to register a new client
AHReccese Feb 19, 2025
27d8700
`/request_model_id/` endpoint to request a new model id
AHReccese Feb 19, 2025
2b405ee
`/clients/` endpoint to get all clients
AHReccese Feb 19, 2025
0eabbb5
`/client/models/` endpoint to request a specific model within a speci…
AHReccese Feb 19, 2025
f374552
integration with previous 4 endpoints in `RESTClientCommunicator`
AHReccese Feb 19, 2025
aae314a
update previous endpoints to apply client/model id check/filter
AHReccese Feb 19, 2025
ec3c8b6
pass download information to `PyMiloServer` in `WebSocketServerCommun…
AHReccese Feb 19, 2025
885243c
`uuid` used to generate universal unique ids
AHReccese Feb 19, 2025
3c96d37
use `HTTPException` to handle erros
AHReccese Feb 19, 2025
4ac4155
integrate id creation/allocation for model and client in PyMiloClient
AHReccese Feb 19, 2025
47eb6d1
integrate id creation/allocation for model and client in PyMiloServer
AHReccese Feb 19, 2025
043de44
develop id creation/allocation functions for ml model and client in P…
AHReccese Feb 19, 2025
28a6b85
add client_id and model_id to scenario3
AHReccese Feb 19, 2025
a41b118
add init client/model in PyMiloServer setup
AHReccese Feb 19, 2025
244a5a9
add client/model initiation
AHReccese Feb 19, 2025
440ecdc
fulfill docstring
AHReccese Feb 19, 2025
e732e5f
run `autopep8.sh`
AHReccese Feb 19, 2025
f9701df
remove trailing whitespaces
AHReccese Feb 19, 2025
faf109e
update naming
AHReccese Feb 20, 2025
2f97df6
Merge branch 'dev' into add/allocate_id
AHReccese Jun 20, 2025
58e25f2
restructure and massive update on `REST[Server|Client]Communicator`
AHReccese Jun 26, 2025
e70b617
update `ClientCommunicator` interface to support new features
AHReccese Jun 26, 2025
0e948b8
update `PymiloClient` to make it compatible with latest updates
AHReccese Jun 26, 2025
7352961
update `PyMiloServer` to make it compatible with latest updates + add…
AHReccese Jun 26, 2025
8a72108
add api version param
AHReccese Jun 26, 2025
4f66f73
comment `websocket` test, to be fixed in the next PR.
AHReccese Jun 26, 2025
dc3b545
update interface
AHReccese Jun 26, 2025
963e03c
remove trailing whitespaces
AHReccese Jun 30, 2025
d2dceea
remove trailing whitespaces
AHReccese Jun 30, 2025
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
338 changes: 258 additions & 80 deletions pymilo/streaming/communicator.py

Large diffs are not rendered by default.

154 changes: 140 additions & 14 deletions pymilo/streaming/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,35 +63,161 @@ class ClientCommunicator(ABC):
"""
ClientCommunicator Interface.

Each ClientCommunicator has methods to upload the local ML model, download the remote ML model and delegate attribute call to the remote server.
Defines the contract for client-server communication. Each implementation is responsible for:
- Registering and removing clients and models
- Uploading and downloading ML models
- Handling delegated attribute access
- Managing model allowances between clients
"""

@abstractmethod
def upload(self, payload):
def register_client(self):
"""
Upload the given payload to the remote server.
Register the client in the remote server.

:param payload: request payload
:type payload: dict
:return: remote server response
:return: newly allocated client ID
:rtype: str
"""

@abstractmethod
def download(self, payload):
def remove_client(self, client_id):
"""
Download the remote ML model to local.
Remove the client from the remote server.

:param payload: request payload
:type payload: dict
:return: remote server response
:param client_id: client ID to remove
:type client_id: str
:return: success status
:rtype: bool
"""

@abstractmethod
def register_model(self, client_id):
"""
Register an ML model for the given client.

:param client_id: client ID
:type client_id: str
:return: newly allocated model ID
:rtype: str
"""

@abstractmethod
def attribute_call(self, payload):
def remove_model(self, client_id, model_id):
"""
Remove the specified ML model for the client.

:param client_id: client ID
:type client_id: str
:param model_id: model ID
:type model_id: str
:return: success status
:rtype: bool
"""

@abstractmethod
def get_ml_models(self, client_id):
"""
Get the list of ML models for the given client.

:param client_id: client ID
:type client_id: str
:return: list of model IDs
:rtype: list[str]
"""

@abstractmethod
def grant_access(self, allower_id, allowee_id, model_id):
"""
Grant access to a model from one client to another.

:param allower_id: client who owns the model
:type allower_id: str
:param allowee_id: client to be granted access
:type allowee_id: str
:param model_id: model ID
:type model_id: str
:return: success status
:rtype: bool
"""

@abstractmethod
def revoke_access(self, revoker_id, revokee_id, model_id):
"""
Revoke model access from one client to another.

:param revoker_id: client who owns the model
:type revoker_id: str
:param revokee_id: client to be revoked
:type revokee_id: str
:param model_id: model ID
:type model_id: str
:return: success status
:rtype: bool
"""

@abstractmethod
def get_allowance(self, allower_id):
"""
Get all clients and models this client has allowed.

:param allower_id: client who granted access
:type allower_id: str
:return: dictionary mapping allowee_id to list of model_ids
:rtype: dict
"""

@abstractmethod
def get_allowed_models(self, allower_id, allowee_id):
"""
Get the list of model IDs that `allowee_id` is allowed to access from `allower_id`.

:param allower_id: model owner
:type allower_id: str
:param allowee_id: recipient
:type allowee_id: str
:return: list of allowed model IDs
:rtype: list[str]
"""

@abstractmethod
def upload(self, client_id, model_id, model):
"""
Upload the local ML model to the remote server.

:param client_id: ID of the client
:param model_id: ID of the model
:param model: serialized model content
:return: True if upload was successful, False otherwise
"""

@abstractmethod
def download(self, client_id, model_id):
"""
Download the remote ML model.

:param client_id: ID of the requesting client
:param model_id: ID of the model to download
:return: string serialized model
"""

@abstractmethod
def attribute_call(self, client_id, model_id, call_payload):
"""
Execute an attribute call on the remote server.

:param payload: request payload
:type payload: dict
:param client_id: ID of the client
:param model_id: ID of the model
:param call_payload: payload containing attribute name, args, and kwargs
:return: remote server response
"""

@abstractmethod
def attribute_type(self, client_id, model_id, type_payload):
"""
Identify the attribute type (method or field) on the remote model.

:param client_id: client ID
:param model_id: model ID
:param type_payload: payload containing targeted attribute
:return: remote server response
"""
2 changes: 2 additions & 0 deletions pymilo/streaming/param.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
PYMILO_SERVER_NON_EXISTENT_ATTRIBUTE = "The requested attribute doesn't exist in this model."
PYMILO_INVALID_URL = "The given URL is not valid."
PYMILO_CLIENT_WEBSOCKET_NOT_CONNECTED = "WebSocket is not connected."

REST_API_PREFIX = "/api/v1"
127 changes: 101 additions & 26 deletions pymilo/streaming/pymilo_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ def encrypt_compress(self, body):
:return: the compressed and encrypted version of the body payload
"""
return self._encryptor.encrypt(
self._compressor.compress(
body
)
self._compressor.compress(body)
)

def toggle_mode(self, mode=Mode.LOCAL):
Expand All @@ -83,12 +81,8 @@ def download(self):
:return: None
"""
serialized_model = self._communicator.download(
self.encrypt_compress(
{
"client_id": self.client_id,
"ml_model_id": self.ml_model_id,
}
)
self.client_id,
self.ml_model_id
)
if serialized_model is None:
print(PYMILO_CLIENT_FAILED_TO_DOWNLOAD_REMOTE_MODEL)
Expand All @@ -103,19 +97,100 @@ def upload(self):
:return: None
"""
succeed = self._communicator.upload(
self.encrypt_compress(
{
"client_id": self.client_id,
"ml_model_id": self.ml_model_id,
"model": Export(self.model).to_json(),
}
)
self.client_id,
self.ml_model_id,
self.encrypt_compress({"model": Export(self.model).to_json()})
)
if succeed:
print(PYMILO_CLIENT_LOCAL_MODEL_UPLOADED)
else:
print(PYMILO_CLIENT_LOCAL_MODEL_UPLOAD_FAILED)

def register(self):
"""
Register client in the remote server.

:return: None
"""
self.client_id = self._communicator.register_client()

def deregister(self):
"""
Deregister client in the remote server.

:return: None
"""
self._communicator.remove_client(self.client_id)
self.client_id = "0x_client_id"

def register_ml_model(self):
"""
Register ML model in the remote server.

:return: None
"""
self.ml_model_id = self._communicator.register_model(self.client_id)

def deregister_ml_model(self):
"""
Deregister ML model in the remote server.

:return: None
"""
self._communicator.remove_model(self.client_id, self.ml_model_id)
self.ml_model_id = "0x_ml_model_id"

def get_ml_models(self):
"""
Get all registered ml models in the remote server for this client.

:return: list of ml model ids
"""
return self._communicator.get_ml_models(self.client_id)

def grant_access(self, allowee_id):
"""
Grant access to one of this client's models to another client.

:param allowee_id: The client ID to grant access to
:return: True if successful, False otherwise
"""
return self._communicator.grant_access(
self.client_id,
allowee_id,
self.ml_model_id
)

def revoke_access(self, revokee_id):
"""
Revoke access previously granted to another client.

:param revokee_id: The client ID to revoke access from
:return: True if successful, False otherwise
"""
return self._communicator.revoke_access(
self.client_id,
revokee_id,
self.ml_model_id
)

def get_allowance(self):
"""
Get a dictionary of all clients who have access to this client's models.

:return: Dict of allowee_id -> list of model_ids
"""
return self._communicator.get_allowance(self.client_id)

def get_allowed_models(self, allower_id):
"""
Get a list of models you are allowed to access from another client.

:param allower_id: The client ID who owns the models
:return: list of allowed model IDs
"""
return self._communicator.get_allowed_models(allower_id, self.client_id)

def __getattr__(self, attribute):
"""
Overwrite the __getattr__ default function to extract requested.
Expand All @@ -133,13 +208,13 @@ def __getattr__(self, attribute):
elif self._mode == PymiloClient.Mode.DELEGATE:
gdst = GeneralDataStructureTransporter()
response = self._communicator.attribute_type(
self.encrypt_compress(
{
"client_id": self.client_id,
"ml_model_id": self.ml_model_id,
"attribute": attribute,
}
)
self.client_id,
self.ml_model_id,
self.encrypt_compress({
"attribute": attribute,
"client_id": self.client_id,
"ml_model_id": self.ml_model_id,
})
)
if response["attribute type"] == "field":
return gdst.deserialize(response, "attribute value", None)
Expand All @@ -155,9 +230,9 @@ def relayer(*args, **kwargs):
payload["args"] = gdst.serialize(payload, "args", None)
payload["kwargs"] = gdst.serialize(payload, "kwargs", None)
result = self._communicator.attribute_call(
self.encrypt_compress(
payload
)
self.client_id,
self.ml_model_id,
self.encrypt_compress(payload)
)
return gdst.deserialize(result, "payload", None)
return relayer
Loading