-
-
Save hgrecco/0765d7c51ccc08d7d9fe to your computer and use it in GitHub Desktop.
Example of Wrapping a driver with a dynamically generated Qt Class
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
# 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