Skip to content

Instantly share code, notes, and snippets.

@hgrecco
Last active August 29, 2015 14:15
Show Gist options
  • Save hgrecco/0765d7c51ccc08d7d9fe to your computer and use it in GitHub Desktop.
Save hgrecco/0765d7c51ccc08d7d9fe to your computer and use it in GitHub Desktop.
Example of Wrapping a driver with a dynamically generated Qt Class
# The example is more complex that it could be as it already contains the hooks
# used to communicate the intervening parts: lantz-core, lantz-qt and user code.
# It allows to register multiple gui wrappers (e.g. qt, enaml, others??)
# We used an approach like the one used in collections.namedtuple
# Metaclass is also a viable way but this looks easier yet powerful
# (and eventhough is weird, it is used in the standard library)
# What is pending is that the `post_` action should call the emit signal
# if the value has changed. But this could be done easily by registering calls
# in qt_wrap.
# This shows that the principle works. Names and implementation are open for discussion.
######
### Within the Lantz core there is a register of GUI wrappers:
######
#: Maps GUI names to GUI wrappers
#: (str) -> (object -> object)
_GUI_WRAPPERS = {}
def register_gui_wrapper(name, wrapper):
"""Registers a wrapper in the registry.
"""
if name in _GUI_WRAPPERS:
raise ValueError('There is already a wrapper registered for %s' % name)
_GUI_WRAPPERS[name] = wrapper
#: Wrapper use by default if none is provided:
_DEFAULT_GUI = None
def set_default_gui_wrapper(name):
"""Set a given wrapper as the default.
"""
global _DEFAULT_GUI
if name not in _GUI_WRAPPERS:
raise ValueError('Unknown wrapper: %s' % name)
_DEFAULT_GUI = name
def wrap(obj, name):
"""Wraps a driver.
"""
return _GUI_WRAPPERS[name](obj)
## The base class will be aware of this
class Base(object):
# This provides the way to hook to GUI signals.
# This is a dictionary that maps feature name to a callable (signal emit).
# Only one callable per feature should be enough
_on_changed_dict = {}
def __new__(cls, *args, **kwargs):
gui = kwargs.pop('gui', _DEFAULT_GUI)
obj = super(Base, cls).__new__(cls, *args, **kwargs)
if not gui:
return obj
return wrap(obj, gui)
## This would be a Feat or equivalent.
class MyProperty(property):
pass
#######
### In the lantz-qt there will be something like this
#######
import inspect
from PyQt4 import QtCore
# This how a named tuple works. It is uglier than Metaclasses but straight forward
_class_template = '''\
class {typename}(QtCore.QObject):
{signals}
def __init__(self, wrapped_object):
QtCore.QObject.__init__(self)
self.__wrapped_object = wrapped_object
def __getattr__(self, name):
return getattr(self.__wrapped_object, name)
'''
_signal_template = "{name}_changed = QtCore.pyqtSignal(object, object)\n"
# Classes are created dynamically so we keep a registry to avoid creating
# the same many tiems.
_DYNAMIC_CLASSES = {}
def qt_wrap(obj, verbose=False):
if obj.__class__ in _DYNAMIC_CLASSES:
return _DYNAMIC_CLASSES[obj.__class__](obj)
typename = 'QtWrapped' + obj.__class__.__name__
signals = []
for name, value in inspect.getmembers(obj.__class__):
if isinstance(value, MyProperty):
signal_name = _signal_template.format(name=name)
signals.append(signal_name)
# This is the only thing we would need to agree: the emitter signature.
# I propose: new value, old_value, info (a dict with any extra thing we need to pass)
emitter = lambda gui_obj, new_value, old_value, info: getattr(gui_obj, signal_name).emit(new_value, old_value, info)
obj._on_changed_dict[name] = emitter
class_definition = _class_template.format(typename=typename,
signals='\n'.join(signals))
if verbose:
print(class_definition)
namespace = dict(QtCore=QtCore)
try:
exec class_definition in namespace
except SyntaxError as e:
raise SyntaxError(e.message + ':\n' + class_definition)
result = namespace[typename]
_DYNAMIC_CLASSES[obj.__class__] = result
return result(obj)
register_gui_wrapper('qt', qt_wrap)
######
### Within a user application.
######
class Example(Base):
"""Some driver
"""
@MyProperty
def amplitude(self):
return 1
# It could be used directly in a command line application
inst1 = Example()
print(inst1, inst1.amplitude)
# or wrapped explicitely
gui_inst1 = wrap(inst1, 'qt')
print(gui_inst1, gui_inst1.amplitude)
# or wrap upon creation
gui_inst2 = Example(gui='qt')
print(gui_inst2, gui_inst2.amplitude)
# or wrap every driver creation
set_default_gui_wrapper('qt')
gui_inst3 = Example()
print(gui_inst3, gui_inst3.amplitude)
print(type(gui_inst1) is type(gui_inst2))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment