Source code for schrodinger.application.msv.utils
import functools
import inspect
import schrodinger
from schrodinger.Qt import QtCore
DEBUG_MODE = schrodinger.in_dev_env()
[docs]def get_rolling_average(data, window_padding=4):
"""
Returns the running average of data in a window
:param data: Data to average. Must not contain any None values.
:type data: list
:param window_padding: The number of elements on either side of a given
element to include in the average. Must be greater than 0.
:type window_padding: int
:return: A list of rolling averages. This list does not contain any values
for the first or last `window_padding` elements of `data`, as those
positions do not have a fully populated window. (Note that this list
does not contain `None`s in those positions. It is simply
2 * window_padding elements shorter than `data`.)
:rtype: list
"""
window_size = window_padding * 2 + 1
# Formatting this as a list comprehension gives us a slight speed boost over
# the equivalent for loop
return [
sum(data[i - window_padding:i + window_padding + 1]) / window_size
for i in range(window_padding,
len(data) - window_padding)
]
[docs]def const(method):
"""
A decorator that adds a 'const' attribute to a method. This decorator is
used in conjunction with `WrapperMetaClass` below and must only be applied
to methods in a wrapped class that do not modify the state of the class
(similar to the `const` method declaration in C++).
"""
method.const = True
return method
[docs]class DocstringWrapperMetaClass(type):
"""
A metaclass for a class that wraps another class. This metaclass copies
docstrings from wrapped methods to reimplemented wrapper methods of the same
name.
"""
def __new__(cls, clsname, bases, dct, *, wraps=None):
"""
See Python documentation for positional argument documentation.
:param wraps: The class to wrap. If None, no wrapping will be done.
(This allows for subclassing of the wrapper class.)
:type wraps: type or None
"""
if wraps is not None:
cls._copyDocstrings(dct, wraps)
return super().__new__(cls, clsname, bases, dct)
@classmethod
def _copyDocstrings(cls, dct, wrapped_class):
"""
Copy all docstrings from the wrapped class to the wrapper class.
:param dct: The class dictionary of the wrapper class. The contents of
this dictionary will be updated by this method.
:type dct: dict
:param wrapped_class: The class being wrapped.
:type wrapped_class: type
"""
for name, wrapper_func in dct.items():
wrapped_func = getattr(wrapped_class, name, None)
if (inspect.isfunction(wrapped_func) and
inspect.isfunction(wrapper_func) and
wrapped_func.__doc__ is not None and
wrapper_func.__doc__ is None):
wrapper_func.__doc__ = wrapped_func.__doc__
[docs]class QtDocstringWrapperMetaClass(DocstringWrapperMetaClass,
type(QtCore.QObject)):
"""
A metaclass for QObject subclasses that wraps another class. This metaclass
only copies docstrings from wrapped methods to reimplemented wrapper methods
of the same name.
"""
[docs]class WrapperMetaClass(QtDocstringWrapperMetaClass):
"""
This metaclass makes it easy to create a class that wraps another one. In
particular, this is intended to help in creating an undoable version of the
wrapped class. Methods that don't modify the state of the wrapped class
(which must be indicated using the `@const` decorator) may be called
directly on the wrapper. Custom undoable wrappings will need to be written
for any methods that do modify the state of the wrapped class.
See the tests for this class for a simple example.
In general, if class A wraps class B, then:
* Every static method, class method, public class attribute, and property
on B is accessible from A, unless A already has attributes with the same
name defined
* Every method on B decorated by @const will be available from A, unless A
already has a method with the same name defined. Note that @const methods
from B will take precedence over methods that A inherits. (This ensures
that common magic methods like `__str__` and `__repr__` will get wrapped
properly.)
* Every instance attribute on B named in instance_attrs will be available
from A.
This may sound like a clumsy reinvention of classical inheritance. But
it can be useful to use a wrapper when we want the wrapped class to be
able to call any of its own methods without any danger of the wrapper
methods being called, while at the same time ensuring that we can
conveniently provide the same interface from the wrapped class. For
example, we have a ProteinAlignment class that encapsulates operations
on structured collections of protein sequences. We use this metaclass
to create an undoable ProteinAlignment that presents an identical
interface but that can safely perform redo and undo operations by
manipulating the protein alignment it wraps.
"""
def __new__(cls,
clsname,
bases,
dct,
*,
wraps=None,
wrapped_name=None,
instance_attrs=None):
"""
See Python documentation for positional argument documentation.
:param wraps: The class to wrap. If None, no wrapping will be done.
(This allows for subclassing of the wrapper class.)
:type wraps: type or None
:param wrapped_name: The name of the wrapped component on the
wrapping instance
:type wrapped_name: str
:param instance_attrs: The names of instance attributes to be wrapped
:type instance_attrs: tuple of str
:return: A MetaClass suitable for creating wrapper objects
:rtype: type
"""
if wraps is not None:
cls._wrapClassAttrs(bases, dct, wraps, wrapped_name)
cls._wrapInstanceAttrs(dct, wrapped_name, instance_attrs)
return super().__new__(cls, clsname, bases, dct, wraps=wraps)
@classmethod
def _wrapClassAttrs(cls, bases, dct, wrapped_class, wrapped_name):
"""
Wrap all appropriate class attributes.
:param bases: The base classes of the wrapper class.
:type bases: tuple(type)
:param dct: The class dictionary of the wrapper class. The contents of
this dictionary will be updated by this method.
:type dct: dict
:param wrapped_class: The class to wrap.
:type wrapped_class: type
:param wrapped_name: The name of the wrapped component on the
wrapping instance.
:type wrapped_name: str
"""
for attr in inspect.classify_class_attrs(wrapped_class):
name = attr.name
if name in dct:
# never override anything defined in the wrapper class
pass
elif (not hasattr(attr.object, 'const') and
any(hasattr(base, name) for base in bases)):
# never override anything that the wrapper class has inherited
# from a superclass unless the wrapped class has a const method
# with the same name
pass
elif attr.kind == 'data' and not name.startswith("_"):
dct[name] = attr.object
elif attr.kind == 'class method':
dct[name] = cls.makeClassMethodDelegate(name, wrapped_class)
elif attr.kind == 'method' and hasattr(attr.object, 'const'):
dct[name] = cls.makeMethodDelegate(name, wrapped_class,
wrapped_name)
elif attr.kind == 'static method':
dct[name] = attr.object
elif attr.kind == 'property':
getter = attr.object.fget is not None
setter = attr.object.fset is not None
dct[name] = cls.makeProperty(name, getter, setter, wrapped_name)
@classmethod
def _wrapInstanceAttrs(cls, dct, wrapped_name, instance_attrs):
"""
Wrap all specified instance attributes.
:param dct: The class dictionary of the wrapper class. The contents of
this dictionary will be updated by this method.
:type dct: dict
:param wrapped_name: The name of the wrapped component on the
wrapping instance.
:type wrapped_name: str
:param instance_attrs: The names of instance attributes to be wrapped.
:type instance_attrs: tuple of str
"""
for attr in instance_attrs:
dct[attr] = cls.makeProperty(attr, True, True, wrapped_name)
[docs] @classmethod
def makeMethodDelegate(cls, meth_name, wrapped_class, wrapped_name):
"""
Returns a method that delegates method calls to the wrapped instance
:param meth_name: The name of the method to wrap
:type meth_name: str
"""
@functools.wraps(getattr(wrapped_class, meth_name))
def const_meth(self, *args, **kwargs):
wrapped_obj = getattr(self, wrapped_name)
aln_method = getattr(wrapped_obj, meth_name)
return aln_method(*args, **kwargs)
return const_meth
[docs] @classmethod
def makeClassMethodDelegate(cls, meth_name, wrapped_class):
"""
Returns a class method that delegates class method calls to the wrapped
class
:param meth_name: The name of the method to wrap
:type meth_name: str
"""
@functools.wraps(getattr(wrapped_class, meth_name))
def const_meth(cls, *args, **kwargs):
aln_method = getattr(wrapped_class, meth_name)
return aln_method.__func__(cls, *args, **kwargs)
return classmethod(const_meth)
[docs] @classmethod
def makeProperty(cls, prop_name, getter, setter, wrapped_name):
"""
Returns a property that delegates property access to the wrapped
instance
:param prop_name: The name of the property
:type prop_name: str
:param getter: Whether to add a getter
:type getter: bool
:param setter: Whether to add a setter
:type setter: bool
:return: A property which delegates to the wrapped object
:rtype: property
"""
def fget(self):
wrapped_obj = getattr(self, wrapped_name)
return getattr(wrapped_obj, prop_name)
def fset(self, value):
wrapped_obj = getattr(self, wrapped_name)
setattr(wrapped_obj, prop_name, value)
fset = fset if setter else None
fget = fget if getter else None
return property(fget, fset)