Skip to content

Commit

Permalink
refactor(dry/class_manager): restructure class mgmt
Browse files Browse the repository at this point in the history
And clean up the code.
  • Loading branch information
wiwichips committed Jul 8, 2024
1 parent a32fa75 commit 016823e
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 130 deletions.
8 changes: 4 additions & 4 deletions dcp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NOTE TO SELF - have to load class reg before classes that use it

from .dry import make_dcp_class, class_manager, wrap_js_obj, asyncify, blockify
from .dry import class_manager, aio
from . import js
from .api import compute_for as api_compute_for #TODO - we should handle compute for and inheritance in same place /:
import sys
Expand Down Expand Up @@ -48,17 +48,17 @@ def init_dcp_module(py_parent, js_module, js_name):
if js.utils.isclass(prop_ref):
new_bfclass = class_manager.reg.find(prop_ref)
if new_bfclass is None:
new_bfclass = make_dcp_class(prop_ref, name=prop_name)
new_bfclass = class_manager.wrap_class(prop_ref, name=prop_name)
new_bfclass = class_manager.reg.add(new_bfclass)

setattr(module, prop_name, new_bfclass)

else:
setattr(module, prop_name, blockify(prop_ref))
setattr(module, prop_name, aio.blockify(prop_ref))

# js object
elif prop_ref is js.utils.PMDict:
setattr(module, prop_name, wrap_js_obj(prop_ref))
setattr(module, prop_name, class_manager.wrap_obj(prop_ref))

# py dict
else:
Expand Down
4 changes: 2 additions & 2 deletions dcp/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from .compute_for import compute_for
from .job import Job

inheritance_hooks = {
sub_classes = {
'Job': Job,
}

__all__ = ['compute_for', 'inheritance_hooks']
__all__ = ['compute_for', 'sub_classes']

5 changes: 2 additions & 3 deletions dcp/dry/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from .bfclass import make_dcp_class, wrap_js_obj
from . import class_manager
from .aio import asyncify, blockify
from . import aio

__all__ = ['make_dcp_class', 'wrap_js_obj', class_manager]
__all__ = ['class_manager', 'aio']

80 changes: 0 additions & 80 deletions dcp/dry/bfclass.py

This file was deleted.

106 changes: 76 additions & 30 deletions dcp/dry/class_manager.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,92 @@
"""
Manage Bifrost 2 Class wrappers.
Decorate the raw PythonMonkey JS Proxies with Pythonic Bifrost 2 API classes.
Functions:
- wrap_class(js_class, name=None): Creates a proxy class.
- wrap_obj(js_val): Returns a proxy instance.
Properties:
- reg: a class registry of saved pythonic wrapper classes.
Author: Will Pringle <[email protected]>
Date: June 2024
"""

import pythonmonkey as pm
from .aio import asyncify, blockify
from .class_registry import ClassRegistry
from .. import js
from .. import api

reg = ClassRegistry()


class ClassRegistry:
def __init__(self):
self._list = []
def wrap_class(js_class, name=None):
"""Wraps a PythonMonkey JS Proxy Class Function as a Pythonic Class"""
name = name or js.utils.class_name(js_class)

def _find(self, cmp):
return next((c for c in self._list if cmp(c)), None)
def __init__(self, *args, **kwargs):
# if the sole argument to the ctor is a js instance, use it as the ref
if len(args) == 1 and js.utils.instanceof(args[0], js_class):
self.js_ref = args[0]
# otherwise, instantiate a new underlying js ref using the ctor args
else:
async_wrapped_ctor = blockify(pm.new(js_class))
self.js_ref = async_wrapped_ctor(*args, **kwargs)

# TODO: this feels wrong, it's doing too many things. it should just add
# classes to the registry... instead it also does the inheritance stuff. ):
def add(self, bfclass):
if base_class := api.inheritance_hooks.get(bfclass.__name__):
bfclass = type(bfclass.__name__, (bfclass,), dict(base_class.__dict__))
self._list.append(bfclass)
return bfclass
class AsyncAttrs:
"""For instance.aio.*"""
def __init__(self, parent):
self.parent = parent

def find_from_js_instance(self, js_inst):
return self._find(lambda c: js.utils.instanceof(js_inst, c.get_js_class()))
def __getattr__(self, name):
return asyncify(self.parent.js_ref[name])

def find_from_name(self, name):
return self._find(lambda c: c.__name__ == name)
self.aio = AsyncAttrs(self)

def find_from_js_ctor(self, js_ctor):
return self._find(lambda c: js.utils.equals(c.get_js_class(), js_ctor))
def __getattr__(self, name):
js_attr = self.js_ref[name]
if not callable(js_attr):
if isinstance(js_attr, pm.JSObjectProxy):
return wrap_obj(js_attr)
return js_attr

def find(self, value):
if isinstance(value, pm.JSFunctionProxy):
return self.find_from_js_ctor(value)
elif isinstance(value, pm.JSObjectProxy):
return self.find_from_js_instance(value)
elif isinstance(value, str):
return self.find_from_name(value)
def method(*args, **kwargs):
return blockify(js_attr)(*args, **kwargs)
return method

def __setattr__(self, name, value):
if name == 'js_ref':
object.__setattr__(self, name, value)
else:
self.js_ref[name] = value

def __str__(self):
return str(self._list)
return str(self.js_ref)

def __repr__(self):
return self.__str__()
props = {
'__init__': __init__,
'__getattr__': __getattr__,
'__setattr__': __setattr__,
'__str__': __str__,
'get_js_class': staticmethod(lambda: js_class),
}

new_class = type(name, (object,), props)

reg = ClassRegistry()
return new_class


def wrap_obj(js_val):
"""Wraps a PythonMonkey JS Proxy instance as a Pythonic Class instance"""
if isinstance(js_val, pm.JSObjectProxy):
bfclass = reg.find(js_val)

if bfclass is None:
bfclass = wrap_class(js.utils.obj_ctor(js_val))
bfclass = reg.add(bfclass)

return bfclass(js_val)
return js_val

56 changes: 56 additions & 0 deletions dcp/dry/class_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""
Store dynamically created classes in a registry for later use.
Classes:
- ClassRegistry().
Author: Will Pringle <[email protected]>
Date: June 2024
"""

import pythonmonkey as pm
from .. import js
from .. import api


class ClassRegistry:
def __init__(self):
self._list = []

def _find(self, cmp):
return next((c for c in self._list if cmp(c)), None)

# TODO: this feels wrong, it's doing too many things. it should just add
# classes to the registry... instead it also does the inheritance stuff. ):
def add(self, bfclass):
"""Registers a new BF2 Wrapper class, replaces with api subclasses."""
if sub_class := api.sub_classes.get(bfclass.__name__):
bfclass = type(bfclass.__name__, (bfclass,), dict(sub_class.__dict__))
self._list.append(bfclass)
return bfclass

def find_from_js_instance(self, js_inst):
return self._find(lambda c: js.utils.instanceof(js_inst, c.get_js_class()))

def find_from_name(self, name):
return self._find(lambda c: c.__name__ == name)

def find_from_js_ctor(self, js_ctor):
return self._find(lambda c: js.utils.equals(c.get_js_class(), js_ctor))

def find(self, value):
"""Finds the corresponding BF2 Wrapper class from the registry."""
if isinstance(value, pm.JSFunctionProxy):
return self.find_from_js_ctor(value)
if isinstance(value, pm.JSObjectProxy):
return self.find_from_js_instance(value)
if isinstance(value, str):
return self.find_from_name(value)
return None

def __str__(self):
return str(self._list)

def __repr__(self):
return self.__str__()

7 changes: 3 additions & 4 deletions tests/test_js_wrappers/test_class_registry.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import unittest
import pythonmonkey as pm
from dcp import dry
from dcp.dry import make_dcp_class as make_class

JSRectangle = pm.eval("""
class JSRectangle
Expand Down Expand Up @@ -53,9 +52,9 @@ class JSHuman
JSHuman;
""")

PyRect = make_class(JSRectangle)
PyCoff = make_class(JSCoffee)
PyHuma = make_class(JSHuman)
PyRect = dry.class_manager.wrap_class(JSRectangle)
PyCoff = dry.class_manager.wrap_class(JSCoffee)
PyHuma = dry.class_manager.wrap_class(JSHuman)


class TestClassRegistry(unittest.TestCase):
Expand Down
13 changes: 6 additions & 7 deletions tests/test_js_wrappers/test_js_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import inspect
import pythonmonkey as pm
import dcp
from dcp.dry import make_dcp_class as make_class
from dcp.dry import wrap_js_obj
from dcp.dry import class_manager


class TestJSObjectFunction(unittest.TestCase):
Expand All @@ -29,7 +28,7 @@ class Rectangle
Rectangle;
""")

PyRect = make_class(JSRect)
PyRect = class_manager.wrap_class(JSRect)
my_rect = PyRect(2, 7)

self.assertTrue(my_rect.area == 2 * 7)
Expand All @@ -51,7 +50,7 @@ class Coffee
Coffee;
""")

Coffee = make_class(MyClass)
Coffee = class_manager.wrap_class(MyClass)
cup_of_joe = Coffee()

# should be able to sleep synchronously
Expand Down Expand Up @@ -80,7 +79,7 @@ class Human
Human;
""")

HumanPy = make_class(Human)
HumanPy = class_manager.wrap_class(Human)

# verify constructor promise has been resolved
baby = HumanPy('Joe')
Expand All @@ -92,10 +91,10 @@ def test_wrapping_js_instance(self):
dcp.init()
Address = pm.globalThis.dcp.wallet.Address

make_class(Address)
class_manager.wrap_class(Address)

address = pm.new(Address)(hex_code)
py_obj = wrap_js_obj(address)
py_obj = class_manager.wrap_obj(address)

self.assertTrue(py_obj.address == hex_code)

Expand Down

0 comments on commit 016823e

Please sign in to comment.