diff --git a/fsl/data/featdesign.py b/fsl/data/featdesign.py index 263ba244a78e58b49d0c04d82d40d92fdb037c92..640a6cd2ba5bbdb9834a345121c7b39234a50e65 100644 --- a/fsl/data/featdesign.py +++ b/fsl/data/featdesign.py @@ -130,16 +130,20 @@ class FEATFSFDesign(object): with FSL 5.0.9 and older. """ - def __init__(self, featDir, settings): + def __init__(self, featDir, settings=None): """Create a ``FEATFSFDesign``. :arg featDir: Path to the FEAT directory. :arg settings: A dictionary containing the FEAT analysis - settings from its ``design.fsf`` file (see - :func:`.featanalysis.loadSettings`). + settings from its ``design.fsf``. If not provided, + is loaded via :func:`.featanalysis.loadSettings`. """ + if settings is None: + from .featanalysis import loadSettings + settings = loadSettings(featDir) + # Get the design matrix, and some # information about the analysis designMatrix = loadDesignMat(featDir) diff --git a/fsl/utils/notifier.py b/fsl/utils/notifier.py index d9b6a143f9f35352511d62ca1c1b5b0c4943e2db..f26d4141c5718f561e03670fef28afcf1877ec6a 100644 --- a/fsl/utils/notifier.py +++ b/fsl/utils/notifier.py @@ -16,9 +16,9 @@ import collections import six -import props -import fsl.utils.async as async +import fsl.utils.async as async +import fsl.utils.weakfuncref as weakfuncref log = logging.getLogger(__name__) @@ -41,7 +41,7 @@ class _Listener(object): # We use a WeakFunctionRef so we can refer to # both functions and class/instance methods - self.__callback = props.WeakFunctionRef(callback) + self.__callback = weakfuncref.WeakFunctionRef(callback) self.topic = topic self.runOnIdle = runOnIdle self.enabled = True @@ -105,10 +105,6 @@ class Notifier(object): # { topic : enabled } mappings. new.__enabled = {} - if isinstance(new, props.HasProperties): - log.warning('Warning: {} is a sub-class of both ' - 'Notifier and props.HasProperties!') - return new diff --git a/fsl/utils/typedict.py b/fsl/utils/typedict.py index 0ac4cdcde3c3f2853fcf645e866e6641ae0b7c1a..f3b9510bc672f08865ba7484a67579c3e534a285 100644 --- a/fsl/utils/typedict.py +++ b/fsl/utils/typedict.py @@ -28,12 +28,11 @@ class TypeDict(object): Let's say we have a class with some properties:: - import props import fsl.utils.typedict as td - class Animal(props.HasProperties): - isMammal = props.Boolean() - numLegs = props.Int() + class Animal(object): + isMammal = True + numLegs = 4 And we want to associate some tooltips with those properties:: @@ -63,7 +62,7 @@ class TypeDict(object): This functionality also works across class hierarchies:: class Cat(Animal): - numYoutubeHits = props.Int() + numYoutubeHits = 10 tooltips = td.TypeDict({ diff --git a/fsl/utils/weakfuncref.py b/fsl/utils/weakfuncref.py new file mode 100644 index 0000000000000000000000000000000000000000..8cc4b91813a136a90ad402944b24e1b0f91340c1 --- /dev/null +++ b/fsl/utils/weakfuncref.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +# +# weakfuncref.py - The WeakFunctionRef class +# +# Author: Paul McCarthy <pauldmccarthy@gmail.com> +# +"""This module provides the :class:`WeakFunctionRef` class. """ + + +import six +import types +import weakref +import inspect + + +class WeakFunctionRef(object): + """Class which encapsulates a :mod:`weakref` to a function or method. + + This class is used by :class:`.Notifier` instances to reference + listeners which have been registered to be notified of property value + or attribute changes. + """ + + + def __init__(self, func): + """Create a new ``WeakFunctionRef`` to encapsulate the given + function or bound/unbound method. + """ + + # Bound method + if self.__isMethod(func): + + boundMeth = six.get_method_function(func) + boundSelf = six.get_method_self( func) + + # We can't take a weakref of the method + # object, so we have to weakref the object + # and the unbound class function. The + # function method will search for and + # return the bound method, though. + self.obj = weakref.ref(boundSelf) + self.func = weakref.ref(boundMeth) + + self.objType = type(boundSelf).__name__ + self.funcName = boundMeth .__name__ + + # Unbound/class method or function + else: + + self.obj = None + self.objType = None + self.func = weakref.ref(func) + self.funcName = func.__name__ + + + def __str__(self): + """Return a string representation of the function.""" + + selftype = type(self).__name__ + func = self.function() + + if self.obj is None: + s = '{}: {}' .format(selftype, self.funcName) + else: + s = '{}: {}.{}'.format(selftype, self.objType, self.funcName) + + if func is None: return '{} <dead>'.format(s) + else: return s + + + def __repr__(self): + """Return a string representation of the function.""" + return self.__str__() + + + def __isMethod(self, func): + """Returns ``True`` if the given function is a bound method, + ``False`` otherwise. + + This seems to be one of the few areas where python 2 and 3 are + irreconcilably incompatible (or just where :mod:`six` does not have a + function to help us). + + In Python 2 there is no difference between an unbound method and a + function. But in Python 3, an unbound method is still a method (and + inspect.ismethod returns True). + """ + + ismethod = False + + # Therefore, in python2 we need to test + # whether the function is a method, and + # also test whether it is bound. + if six.PY2: + ismethod = (inspect.ismethod(func) and + six.get_method_self(func) is not None) + + # But in python3, if the function is a + # method it is, by definition, bound. + elif six.PY3: + ismethod = inspect.ismethod(func) + + return ismethod + + + def __findPrivateMethod(self): + """Finds and returns the bound method associated with the encapsulated + function. + """ + + obj = self.obj() + func = self.func() + methName = self.funcName + + # Find all attributes on the object which end with + # the method name - there will be more than one of + # these if the object has base classes which have + # private methods of the same name. + attNames = dir(obj) + attNames = [a for a in attNames if a.endswith(methName)] + + # Find the attribute with the correct name, which + # is a method, and has the correct function. + for name in attNames: + + att = getattr(obj, name) + + if isinstance(att, types.MethodType) and \ + six.get_method_function(att) is func: + return att + + return None + + + def function(self): + """Return a reference to the encapsulated function or method, + or ``None`` if the function has been garbage collected. + """ + + # Unbound/class method or function + if self.obj is None: + return self.func() + + # The instance owning the method has been destroyed + if self.obj() is None or self.func() is None: + return None + + obj = self.obj() + + # Return the bound method object + try: return getattr(obj, self.funcName) + + # If the function is a bound private method, + # its name on the instance will have been + # mangled, so we need to search for it + except: return self.__findPrivateMethod()