Beispiel #1
0
 def add_value(notification, kw=None):
     pair = (notification, kw)
     if pair in self.__value_notifications[prop_name]:
         return
     logger.debug("Will call %s.%s after assignment to %s.%s",
         observer.__class__.__name__, notification.__name__,
         self.__class__.__name__, prop_name)
     self.__value_notifications[prop_name].append(pair)
Beispiel #2
0
        def add_signal(notification, kw=None):
            if not isinstance(value, Signal):
                return

            pair = (notification, kw)
            if pair in self.__signal_notif[prop_name]:
                return
            logger.debug("Will call %s.%s after emit on %s.%s",
                observer.__class__.__name__, notification.__name__,
                self.__class__.__name__, prop_name)

            self.__signal_notif[prop_name].append(pair)
Beispiel #3
0
    def __init__(cls, name, bases, _dict):
        """class constructor"""
        type.__init__(cls, name, bases, _dict)

        # the set of all obs (it is calculated and stored below)
        obs = set()

        # processes now all names in __observables__
        conc_props, log_props = type(cls).__get_observables_sets__(cls)

        # processes all concrete properties
        for prop in conc_props:
            val = _dict[prop]  # must exist if concrete
            type(cls).__create_conc_prop_accessors__(cls, prop, val)
            obs.add(prop)

        # processes all logical properties, and adds the actual log
        # properties to the list of all observables
        _getdict = getattr(cls, LOGICAL_GETTERS_MAP_NAME, dict())
        _setdict = getattr(cls, LOGICAL_SETTERS_MAP_NAME, dict())

        real_log_props = type(cls).__create_log_props(cls, log_props,
                                                      _getdict, _setdict)
        obs |= real_log_props

        # after using the maps, it is time to clear them to make
        # them available to the next class in the mro.
        _getdict.clear()
        _setdict.clear()

        # processes all names in __properties__ (deprecated,
        # overloaded by __observables__)
        props = getattr(cls, PROPS_MAP_NAME, {})
        if len(props) > 0:
            import warnings
            warnings.warn("In class %s.%s the use of attribute '%s' in "
                          "models is deprecated."
                          " Use the tuple '%s' instead (see the manual)" \
                              % (cls.__module__, cls.__name__,
                                 PROPS_MAP_NAME, OBS_TUPLE_NAME),
                          DeprecationWarning)

        for prop in (x for x in props if x not in obs):
            type(cls).__create_conc_prop_accessors__(cls, prop, props[prop])
            obs.add(prop)

        # generates the list of _all_ properties available for this
        # class (also from bases)
        for base in bases: obs |= getattr(base, ALL_OBS_SET, set())
        setattr(cls, ALL_OBS_SET, frozenset(obs))
        logger.debug("class %s.%s has observables: %s" \
                         % (cls.__module__, cls.__name__, obs))
        return
Beispiel #4
0
        def add_after(notification, kw=None):
            if (not isinstance(value, ObsWrapperBase) or
                isinstance(value, Signal)):
                return

            pair = (notification, kw)
            if pair in self.__instance_notif_after[prop_name]:
                return
            logger.debug("Will call %s.%s after mutation of %s.%s",
                observer.__class__.__name__, notification.__name__,
                self.__class__.__name__, prop_name)

            self.__instance_notif_after[prop_name].append(pair)
Beispiel #5
0
    def __create_conc_prop_accessors__(cls, prop_name, default_val):  # @NoSelf
        """Private method that creates getter and setter, and the
        corresponding property. This is used for concrete
        properties."""
        getter_name = "get_prop_%s" % prop_name
        setter_name = "set_prop_%s" % prop_name

        members_names = frozenset(cls.__dict__.keys())

        # checks if accessors are already defined:
        if getter_name not in members_names:
            _getter = type(cls).get_getter(cls, prop_name)
            setattr(cls, getter_name, _getter)
        else:
            logger.debug("Custom member '%s' overloads generated getter "
                         "of property '%s'", getter_name, prop_name)

        if setter_name not in members_names:
            _setter = type(cls).get_setter(cls, prop_name)
            setattr(cls, setter_name, _setter)
        else:
            logger.warning("Custom member '%s' overloads generated setter "
                           "of property '%s'", setter_name, prop_name)

        # creates the concrete property
        prop = PropertyMeta.ConcreteOP(getattr(cls, getter_name),
                                       getattr(cls, setter_name))
        setattr(cls, prop_name, prop)

        # creates the underlaying variable if needed
        varname = PROP_NAME % {'prop_name' : prop_name}
        if varname not in members_names:
            setattr(cls, varname, cls.create_value(varname, default_val))

        else:
            logger.warning("In class %s.%s automatic property builder found"
                           " a possible clashing with attribute '%s'",
                           cls.__module__, cls.__name__, varname)
Beispiel #6
0
    def __remove_observer_notification(self, observer, prop_name):
        """
        Remove all stored notifications.

        *observer* an instance.

        *prop_name* a string.
        """

        def side_effect(seq):
            for meth, kw in reversed(seq):
                if meth.__self__ is observer:
                    seq.remove((meth, kw))
                    yield meth

        for meth in side_effect(self.__value_notifications.get(prop_name, ())):
            logger.debug("Stop calling %s.%s after assignment to %s.%s",
                observer.__class__.__name__, meth.__name__,
                self.__class__.__name__, prop_name)

        for meth in side_effect(self.__signal_notif.get(prop_name, ())):
            logger.debug("Stop calling %s.%s after emit on %s.%s",
                observer.__class__.__name__, meth.__name__,
                self.__class__.__name__, prop_name)

        for meth in side_effect(
                        self.__instance_notif_before.get(prop_name, ())):
            logger.debug("Stop calling %s.%s before mutation of %s.%s",
                observer.__class__.__name__, meth.__name__,
                self.__class__.__name__, prop_name)

        for meth in side_effect(
                        self.__instance_notif_after.get(prop_name, ())):
            logger.debug("Stop calling %s.%s after mutation of %s.%s",
                observer.__class__.__name__, meth.__name__,
                self.__class__.__name__, prop_name)
Beispiel #7
0
    def adapt(self, *args, **kwargs):
        """
        There are five ways to call this:

        .. method:: adapt()
           :noindex:

           Take properties from the model for which ``adapt`` has not yet been
           called, match them to the view by name, and create adapters fitting
           for the respective widget type.

           That information comes from :mod:`gtkmvc3.adapters.default`.
           See :meth:`_find_widget_match` for name patterns.

           .. versionchanged:: 1.99.1
              Allow incomplete auto-adaption, meaning properties for which no
              widget is found.

        .. method:: adapt(ad)
           :noindex:

           Keep track of manually created adapters for future ``adapt()``
           calls.

           *ad* is an adapter instance already connected to a widget.

        .. method:: adapt(prop_name)
           :noindex:

           Like ``adapt()`` for a single property.

           *prop_name* is a string.

        .. method:: adapt(prop_name, wid_name)
           :noindex:

           Like ``adapt(prop_name)`` but without widget name matching.

           *wid_name* has to exist in the view.

        .. method:: adapt(prop_name, wid_name, gprop_name)
           :noindex:

           Like ``adapt(prop_name, wid_name)`` but without using default
           adapters. This is useful to adapt secondary properties like
           button sensitivity.

           *gprop_name* is a string naming a property of the widget. No cast
           is attempted, so *prop_name* must match its type exactly.

           .. versionadded:: 1.99.2

        In all cases, optional keyword argument ``flavour=value``
        can be used to specify a particular flavour from those
        available in :mod:`gtkmvc3.adapters.default` adapters.
        """

        # checks arguments
        n = len(args)

        flavour = kwargs.get("flavour", None)

        if n==0:
            adapters = []
            props = self.model.get_properties()
            # matches all properties not previoulsy adapter by the user:
            for prop_name in (p for p in props
                              if p not in self.__user_props):
                try: wid_name = self._find_widget_match(prop_name)
                except TooManyCandidatesError as e:
                    # multiple candidates, gives up
                    raise e
                except ValueError as e:
                    # no widgets found for given property, continue after emitting a warning
                    if e.args:
                        logger.warn(e[0])
                    else:
                        logger.warn("No widget candidates match property '%s'"
                            % prop_name)
                else:
                    logger.debug("Auto-adapting property %s and widget %s" % \
                                     (prop_name, wid_name))
                    adapters += self.__create_adapters__(prop_name, wid_name, flavour)

        elif n == 1: #one argument
            if isinstance(args[0], Adapter): adapters = (args[0],)

            elif isinstance(args[0], str):
                prop_name = args[0]
                wid_name = self._find_widget_match(prop_name)
                adapters = self.__create_adapters__(prop_name, wid_name, flavour)

            else:
                raise TypeError("Argument of adapt() must be either an "
                                "Adapter or a string")

        elif n == 2: # two arguments
            if not (isinstance(args[0], str) and
                    isinstance(args[1], str)):
                raise TypeError("Arguments of adapt() must be two strings")

            # retrieves both property and widget, and creates an adapter
            prop_name, wid_name = args
            adapters = self.__create_adapters__(prop_name, wid_name, flavour)

        elif n == 3:
            for arg in args:
                if not isinstance(arg, str):
                    raise TypeError("names must be strings")

            prop_name, wid_name, gprop_name = args
            ad = Adapter(self.model, prop_name)
            ad.connect_widget(self.view[wid_name],
                              getter=lambda w: w.get_property(gprop_name),
                              setter=lambda w, v: w.set_property(gprop_name, v),
                              signal='notify::%s' % gprop_name,
                              flavour=flavour)
            adapters = [ad]

        else:
            raise TypeError(
                "adapt() takes at most three arguments (%i given)" % n)

        for ad in adapters:
            self.__adapters.append(ad)
            # remember properties added by the user
            if n > 0: self.__user_props.add(ad.get_property_name())
Beispiel #8
0
    def _calculate_logical_deps(self):
        """Internal service which calculates dependencies information
        based on those given with getters.

        The graph has to be reversed, as the getter tells that a
        property depends on a set of others, but the model needs to
        know how has to be notified (i.e. needs to know which OP is
        affected by an OP).  Only proximity of edges is considered,
        the rest is demanded at runtime)

        Result is stored inside internal dict __log_prop_deps which
        represents the dependencies graph.
        """
        self.__log_prop_deps = {}  # the result goes here

        # this is used in messages
        _mod_cls = "%s.%s" % (self.__class__.__module__,
                              self.__class__.__name__)

        logic_ops = ((name, opr.deps)
                     for name, opr in getmembers(type(self),
  lambda x: isinstance(x, metaclasses.ObservablePropertyMeta.LogicalOP)
                                                ))
        # reverses the graph
        for name, deps in logic_ops:
            for dep in deps:
                if not self.has_property(dep):
                    raise ValueError("In class %s dependencies of logical "
                                     "property '%s' refer non-existant "
                                     "OP '%s'" % (_mod_cls, name, dep))
                rdeps = self.__log_prop_deps.get(dep, [])
                # name must appear only once in DAG
                assert name not in rdeps
                rdeps.append(name)
                self.__log_prop_deps[dep] = rdeps

        # emits debugging info about dependencies
        for name, rdeps in self.__log_prop_deps.items():
            logger.debug("In class %s changes to OP %s affects "
                         "logical OPs: %s",
                         _mod_cls, name, ", ".join(rdeps))

        # --------------------------------------------------
        # Here the graph is checked to be a DAG
        # --------------------------------------------------
        graph = dict((prop, frozenset(deps))
                     for prop, deps in self.__log_prop_deps.items())

        # makes the graph total
        graph.update((prop, frozenset())
                     for prop in functools.reduce(set.union,
                                                  map(set, graph.values()),
                                                  set()) - set(graph.keys()))
        # DFS searching for leaves
        while True:
            leaves = frozenset(prop for prop, deps in graph.items()
                               if not deps)
            if not leaves:
                break
            # remove leaves from graph
            graph = dict((prop, (deps - leaves))
                         for prop, deps in graph.items()
                         if prop not in leaves)

        # here remaining vertex are in a loop (over-approximated)
        if graph:
            raise ValueError("In class %s found a loop among logical OPs: %s"\
                                 % (_mod_cls, ", ".join(graph.keys())))

        # here the graph is a DAG
        return