-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from Distributive-Network/severn/serialize-pyt…
…hon-types Feature: Serialize Python Values, Add Job API, and create BF2 Work Function Wrapper
- Loading branch information
Showing
15 changed files
with
725 additions
and
194 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
""" | ||
compute_do API | ||
Author: Severn Lortie <[email protected]> | ||
Date: Aug 2024 | ||
""" | ||
|
||
import pythonmonkey as pm | ||
import dill | ||
from types import FunctionType | ||
|
||
def compute_do_maker(Job): | ||
def compute_do(*args, **kwargs): | ||
args = list(args) | ||
for i in range(len(args)): | ||
arg = args[i] | ||
if isinstance(arg, FunctionType): | ||
args[i] = dill.source.getsource(arg) | ||
compute_do_js = pm.eval("globalThis.dcp.compute.do") | ||
job_js = dry.aio.blockify(computedo_js)(*args, **kwargs) | ||
return Job(job_js) | ||
return compute_do |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,23 @@ | ||
""" | ||
compute_for API | ||
Author: Will Pringle <[email protected]>, Severn Lortie <[email protected]> | ||
Date: July 2024 | ||
""" | ||
|
||
import pythonmonkey as pm | ||
import dill | ||
from .. import dry | ||
from .. import js | ||
from types import FunctionType | ||
|
||
def compute_for_maker(): | ||
def compute_for_maker(Job): | ||
def compute_for(*args, **kwargs): | ||
args = list(args) | ||
for i in range(len(args)): | ||
arg = args[i] | ||
if isinstance(arg, FunctionType): | ||
args[i] = dill.source.getsource(arg) | ||
compute_for_js = pm.eval("globalThis.dcp.compute.for") | ||
ret_val = dry.aio.blockify(compute_for_js)(*args, **kwargs) | ||
return dry.class_manager.wrap_obj(ret_val) | ||
job_js = dry.aio.blockify(compute_for_js)(*args, **kwargs) | ||
return Job(job_js) | ||
return compute_for | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
""" | ||
Converts Job Env arguments for Pyodide worktime. | ||
Author: Severn Lortie <[email protected]> | ||
Date: Aug 2024 | ||
""" | ||
|
||
def convert_env_to_arguments(env): | ||
if not len(env): | ||
return [] | ||
args = ["env"] | ||
for env_key in env: | ||
args.append(f"{env_key}={env[env_key]}") | ||
return args |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
""" | ||
Converts Job modules (Pyodide core packages) for Pyodide worktime. | ||
Author: Severn Lortie <[email protected]> | ||
Date: Aug 2024 | ||
""" | ||
|
||
def convert_modules_to_requires(modules): | ||
requires = [] | ||
for module in modules: | ||
requires.append(f"pyodide-{module}/pyodide-{module}.js") | ||
return requires | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
""" | ||
Responsible for serializing Job arguments and input data. | ||
Author: Severn Lortie <[email protected]> | ||
Date: July 2024 | ||
""" | ||
|
||
import cloudpickle | ||
import dill | ||
from collections.abc import Iterator | ||
|
||
def numpy_save_interrogate(value): | ||
import numpy as np | ||
return isinstance(value, np.ndarray) | ||
def numpy_save_serialize(value): | ||
import numpy as np | ||
from io import BytesIO | ||
byte_buffer = BytesIO() | ||
np.save(byte_buffer, value) | ||
buffer.seek(0) | ||
return buffer.read() | ||
def numpy_save_deserialize(value): | ||
import numpy as np | ||
from io import BytesIO | ||
byte_buffer = BytesIO() | ||
buffer.seek(0) | ||
return np.load(value) | ||
|
||
def pickle_interrogate(value): | ||
return True | ||
def pickle_serialize(value): | ||
import cloudpickle | ||
return cloudpickle.dumps(value) | ||
def pickle_deserialize(value): | ||
import cloudpickle | ||
return cloudpickle.loads(value) | ||
|
||
default_serializers = [ | ||
{ | ||
"name": "numpy-save", | ||
"interrogator": numpy_save_interrogate, | ||
"serializer": numpy_save_serialize, | ||
"deserializer": numpy_save_deserialize, | ||
}, | ||
{ | ||
"name": "pickle", | ||
"interrogator": pickle_interrogate, | ||
"serializer": pickle_serialize, | ||
"deserializer": pickle_deserialize, | ||
}, | ||
] | ||
|
||
def validate_serializers(serializers): | ||
required_keys = ['name', 'interrogator', 'serializer', 'deserializer'] | ||
for i in range(len(serializers)): | ||
serializer = serializers[i] | ||
missing_keys = [key for key in required_keys if key not in serializer] | ||
if len(missing_keys) > 0: | ||
raise TypeError(f"Serializer at index {i} is missing keys: {missing_keys}") | ||
if len(serializer["name"]) > 256: | ||
raise TypeError(f"Serializer at index {i} has name '{serializer.name}' which exceeds 256 characters") | ||
|
||
def serialize(value, serializers): | ||
class IteratorWrapper: | ||
def __init__(self, iterator): | ||
self.iterator = iterator | ||
|
||
def __iter__(self): | ||
return self | ||
|
||
def __next__(self): | ||
value = next(self.iterator) | ||
return serialize(value) | ||
|
||
primitive_types = (int, float, bool, str, bytes) | ||
if isinstance(value, primitive_types): | ||
return value | ||
if isinstance(value, Iterator): | ||
return IteratorWrapper(value) | ||
|
||
for serializer in serializers: | ||
if serializer["interrogator"](value): | ||
serialized_value_bytes = serializer["serializer"](value) | ||
serialized_serializer_name_bytes = serializer["name"].encode('utf-8') | ||
serializer_name_length = len(serializer["name"]) | ||
serializer_name_length_byte = bytearray(serializer_name_length.to_bytes(1, byteorder='big')) | ||
serialized_serializer_name_byte_array = bytearray(serialized_serializer_name_bytes) | ||
serialized_value_byte_array = bytearray(serialized_value_bytes) | ||
return serializer_name_length_byte + serialized_serializer_name_byte_array + serialized_value_byte_array | ||
|
||
def deserialize(serialized_value, serializers): | ||
if isinstance(serialized_value, memoryview): | ||
value = bytearray(serialized_value.tobytes()) | ||
elif not isinstance(serialized_value, bytearray): | ||
return serialized_value | ||
else: | ||
value = serialized_value.copy() | ||
serializer_name_length = value[0] | ||
if serializer_name_length > len(value): | ||
return serialized_value | ||
name_start_idx = 1 | ||
name_end_idx = serializer_name_length + 1 | ||
serializer_name_bytes = value[name_start_idx:name_end_idx] | ||
del value[0:name_end_idx] | ||
serializer_name = serializer_name_bytes.decode('utf-8') | ||
allowed_serializer_names = [serializer["name"] for serializer in serializers] | ||
if serializer_name not in allowed_serializer_names: | ||
return serialized_value | ||
|
||
serializer = next((serializer for serializer in serializers if serializer["name"] == serializer_name), None) | ||
return serializer["deserializer"](value) | ||
|
||
def convert_serializers_to_arguments(serializers): | ||
stringified_serializers = [] | ||
for serializer in serializers: | ||
stringified_serializers.append({ | ||
"name": serializer["name"], | ||
"interrogator": dill.source.getsource(serializer["interrogator"], lstrip=True), | ||
"serializer": dill.source.getsource(serializer["serializer"], lstrip=True), | ||
"deserializer": dill.source.getsource(serializer["deserializer"], lstrip=True) | ||
}) | ||
serialized_serializers = bytearray(cloudpickle.dumps(stringified_serializers)) | ||
return serialized_serializers | ||
|
Oops, something went wrong.