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)
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)
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
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)
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)
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)
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())
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