def get_mpl_hist_data(self, update=None, skip=None):
        """
        Returns a dictionary containing information on *bins*, *range*, *label*, and *log*, that can
        be passed to `matplotlib histograms
        <https://matplotlib.org/api/_as_gen/matplotlib.pyplot.hist.html>`_. When *update* is set,
        the returned dict is updated with *update*. When *skip* is set, it can be a single key or a
        sequence of keys that will not be added to the returned dictionary.
        """
        data = {
            "bins": self.n_bins,
            "range": (self.x_min, self.x_max),
            "label": self.name,
        }
        if self.log_x:
            data["log"] = True

        # update?
        if update:
            data.update(update)

        # skip some values?
        if skip:
            for key in make_list(skip):
                if key in data:
                    del data[key]

        return data
    def keys(self, keys):
        # keys parser
        _keys = []
        for key in make_list(keys):
            if not isinstance(key, six.string_types):
                raise TypeError("invalid key type: {}".format(key))
            _keys.append(str(key))

        return _keys
Exemple #3
0
 def has_tag(self, tag, mode=any, **kwargs):
     """ has_tag(tag, mode=any, **kwargs)
     Returns *True* when this object is tagged with *tag*, *False* otherwise. When *tag* is a
     sequence of tags, the behavior is defined by *mode*. For *any*, the object is considered
     *tagged* when at least one of the provided tags matches. When *all*, all provided tags have
     to match. Each *tag* can be a *fnmatch* or *re* pattern. All *kwargs* are passed to
     :py:func:`util.multi_match`.
     """
     match = lambda tag: any(multi_match(t, [tag], **kwargs) for t in self.tags)
     return mode(match(tag) for tag in make_list(tag))
Exemple #4
0
    def extend(self, objs):
        """
        Adds multiple new objects to the index. All elements of the sequence *objs* are forwarded to
        :py:meth:`add` and the list of return values is returned. When an object is a dictionary or
        a tuple, it is expanded for the invocation of :py:meth:`add`.
        """
        results = []

        objs = objs.values() if isinstance(
            objs, UniqueObjectIndex) else make_list(objs)
        for obj in objs:
            if isinstance(obj, dict):
                obj = self.add(**obj)
            elif isinstance(obj, tuple):
                obj = self.add(*obj)
            else:
                obj = self.add(obj)
            results.append(obj)

        return results
Exemple #5
0
    def decorator(unique_cls):
        if not issubclass(unique_cls, UniqueObject):
            raise TypeError(
                "decorated class must inherit from UniqueObject: {}".format(
                    unique_cls))

        # determine configuration defaults
        cls = kwargs.get("cls", unique_cls)
        singular = kwargs.get("singular", cls.__name__).lower()
        plural = kwargs.get("plural", singular + "s").lower()
        parents = kwargs.get("parents", True)
        deep_children = kwargs.get("deep_children", False)
        deep_parents = kwargs.get("deep_parents", False)
        skip = make_list(kwargs.get("skip", None) or [])

        # decorator for registering new instance methods with proper name and doc string
        # functionality is almost similar to functools.wraps, except for the customized function
        # naming and automatic transfer to the unique_class to extend
        def patch(name=None, prop=False, **kwargs):
            def decorator(f):
                _name = name
                if _name is not None and hasattr(f, "__name__"):
                    f.__name__ = _name
                elif _name is None and hasattr(f, "__name__"):
                    _name = f.__name__
                if f.__doc__:
                    f.__doc__ = f.__doc__.format(name=_name,
                                                 singular=singular,
                                                 plural=plural,
                                                 **kwargs)
                if prop:
                    f = property(f)
                # only patch when there is not attribute with that name
                if not hasattr(unique_cls, _name) and _name not in skip:
                    setattr(unique_cls, _name, f)
                return f

            return decorator

        # patch the init method
        orig_init = unique_cls.__init__

        def __init__(self, *args, **kwargs):
            # register the child and parent indexes
            setattr(self, "_" + plural, UniqueObjectIndex(cls=cls))
            if parents:
                setattr(self, "_parent_" + plural, UniqueObjectIndex(cls=cls))
            orig_init(self, *args, **kwargs)

        unique_cls.__init__ = __init__

        # add attribute docs
        if unique_cls.__doc__:
            unique_cls.__doc__ += """
    .. py:attribute:: {plural}
       type: UniqueObjectIndex
       read-only

       The :py:class:`~order.unique.UniqueObjectIndex` of child {plural}.
    """.format(plural=plural)

            if parents:
                unique_cls.__doc__ += """
    .. py:attribute:: parent_{plural}
       type: UniqueObjectIndex
       read-only

       The :py:class:`~order.unique.UniqueObjectIndex` of parent {plural}.
    """.format(plural=plural)

        #
        # child methods, independent of parents
        #

        # direct child index access
        @patch()
        @typed(setter=False, name=plural)
        def get_index(self):
            pass

        if not deep_children:

            # has child method
            @patch("has_" + singular)
            def has(self, obj, context=None):
                """
                Checks if the :py:attr:`{plural}` index for *context* contains an *obj* which might
                be a *name*, *id*, or an instance. When *context* is *None*, the *default_context*
                of the :py:attr:`{plural}` index is used.
                """
                return getattr(self, plural).has(obj, context=context)

            # get child method
            @patch("get_" + singular)
            def get(self, obj, default=_no_default, context=None):
                """
                Returns a child {singular} given by *obj*, which might be a *name*, *id*, or an
                instance from the :py:attr:`{plural}` index for *context*. When no {singular} is
                found, *default* is returned when set. Otherwise, an error is raised. When *context*
                is *None*, the *default_context* of the :py:attr:`{plural}` index is used.
                """
                return getattr(self, plural).get(obj,
                                                 default=default,
                                                 context=context)

        else:

            # has child method
            @patch("has_" + singular)
            def has(self, obj, deep=True, context=None):
                """
                Checks if the :py:attr:`{plural}` index for *context* contains an *obj* which might
                be a *name*, *id*, or an instance. If *deep* is *True*, the lookup is recursive.
                When *context* is *None*, the *default_context* of the :py:attr:`{plural}` index is
                used.
                """
                return getattr(self, "get_" + singular)(
                    obj, default=_not_found, deep=deep,
                    context=context) != _not_found

            # get child method
            @patch("get_" + singular)
            def get(self, obj, default=_no_default, deep=True, context=None):
                """
                Returns a child {singular} given by *obj*, which might be a *name*, *id*, or an
                instance from the :py:attr:`{plural}` index for *context*. If *deep* is *True*, the
                lookup is recursive. When no {singular} is found, *default* is returned when set.
                Otherwise, an error is raised. When *context* is *None*, the *default_context* of
                the :py:attr:`{plural}` index is used.
                """
                indexes = [getattr(self, plural)]
                while len(indexes) > 0:
                    index = indexes.pop(0)
                    _obj = index.get(obj, default=_not_found, context=context)
                    if _obj != _not_found:
                        return _obj
                    elif deep:
                        indexes.extend(getattr(_obj, plural) for _obj in index)
                else:
                    if default != _no_default:
                        return default
                    else:
                        raise ValueError("unknown {}: {}".format(
                            singular, obj))

            # walk children method
            @patch("walk_" + plural)
            def walk(self, context=None):
                """
                Walks through the :py:attr:`{plural}` index for *context* and per iteration, yields
                a child {singular}, its depth relative to *this* {singular}, and its child {plural}
                in a list that can be modified to alter the walking. When *context* is *None*, the
                *default_context* of the :py:attr:`{plural}` index is used. When *context* is *all*,
                all indices are traversed.
                """
                lookup = [
                    (obj, 1)
                    for obj in getattr(self, plural).values(context=context)
                ]
                while lookup:
                    obj, depth = lookup.pop(0)
                    objs = list(getattr(obj, plural).values(context=context))

                    yield (obj, depth, objs)

                    lookup.extend((obj, depth + 1) for obj in objs)

            if unique_cls == cls:

                # is leaf method
                @patch("is_leaf_" + singular, prop=True)
                def is_leaf(self):
                    """
                    Returns *True* when this {singular} has no child {plural}, *False* otherwise.
                    """
                    return len(getattr(self, plural)) == 0

        #
        # child methods, disabled parents
        #

        if not parents:

            # add child method
            @patch("add_" + singular)
            def add(self, *args, **kwargs):
                """
                Adds a child {singular}. See :py:meth:`UniqueObjectIndex.add` for more info.
                """
                return getattr(self, plural).add(*args, **kwargs)

            # remove child method
            @patch("remove_" + singular)
            def remove(self, obj, context=None, silent=False):
                """
                Removes a child {singular} given by *obj*, which might be a *name*, *id*, or an
                instance from the :py:attr:`{plural}` index for *context* and returns the removed
                object. When *context* is *None*, the *default_context* of the :py:attr:`{plural}`
                index is used. Unless *silent* is *True*, an error is raised if the object was not
                found. See :py:meth:`UniqueObjectIndex.remove` for more info.
                """
                return getattr(self, plural).remove(obj,
                                                    context=context,
                                                    silent=silent)

        #
        # child methods, enabled parents
        #

        else:

            # remove child method with limited number of parents
            @patch("remove_" + singular)
            def remove(self, obj, context=None, silent=False):
                """
                Removes a child {singular} given by *obj*, which might be a *name*, *id*, or an
                instance from the :py:attr:`{plural}` index for *context* and returns the removed
                object. Also removes *this* {singular} from the :py:attr:`parent_{plural}` index of
                the removed {singular}. When *context* is *None*, the *default_context* of the
                :py:attr:`{plural}` index is used. Unless *silent* is *True*, an error is raised if
                the object was not found. See :py:meth:`UniqueObjectIndex.remove` for more info.
                """
                obj = getattr(self, plural).remove(obj,
                                                   context=context,
                                                   silent=silent)
                if obj is not None:
                    getattr(obj,
                            "parent_" + plural).remove(self,
                                                       context=obj.context,
                                                       silent=silent)
                return obj

        #
        # child methods, enabled and unlimited parents
        #

            if isinstance(parents, six.integer_types):

                # add child method with infinite number of parents
                @patch("add_" + singular)
                def add(self, *args, **kwargs):
                    """
                    Adds a child {singular}. Also adds *this* {singular} to the parent index of the
                    added {singular}. See :py:meth:`UniqueObjectIndex.add` for more info.
                    """
                    obj = getattr(self, plural).add(*args, **kwargs)
                    getattr(obj, "parent_" + plural).add(self)
                    return obj

        #
        # child methods, enabled but limited parents
        #

            else:

                # add child method with limited number of parents
                @patch("add_" + singular)
                def add(self, *args, **kwargs):
                    """
                    Adds a child {singular}. Also adds *this* {singular} to the parent index of the
                    added {singular}. An exception is raised when the number of allowed parents is
                    exceeded. See :py:meth:`UniqueObjectIndex.add` for more info.
                    """
                    index = getattr(self, plural)
                    obj = index.add(*args, **kwargs)
                    parent_index = getattr(obj, "parent_" + plural)
                    if len(parent_index) >= parents:
                        index.remove(obj)
                        raise Exception(
                            "number of parents exceeded: {}".format(parents))
                    parent_index.add(self)
                    return obj

        #
        # parent methods, independent of number
        #

        # direct parent index access

            @patch()  # noqa: F811
            @typed(setter=False, name="parent_" + plural)
            def get_index(self):
                pass

            # remove parent method
            @patch("remove_parent_" + singular)  # noqa: F811
            def remove(self, obj, context=None, silent=False):
                """
                Removes a parent {singular} *obj* which might be a *name*, *id*, or an instance from
                the :py:attr:`parent_{plural}` index for *context*. Also removes *this* instance
                from the child index of the removed {singular}. Returns the removed object. When
                *context* is *None*, the *default_context* of the :py:attr:`parent_{plural}` index
                is used. Unless *silent* is *True*, an error is raised if the object was not found.
                See :py:meth:`UniqueObjectIndex.remove` for more info.
                """
                obj = getattr(self, "parent_" + plural).remove(obj,
                                                               context=context,
                                                               silent=silent)
                if obj is not None:
                    getattr(obj, plural).remove(self,
                                                context=obj.context,
                                                silent=silent)
                return obj

            if not deep_parents:

                @patch("has_parent_" + singular)  # noqa: F811
                def has(self, obj, context=None):
                    """
                    Checks if the :py:attr:`parent_{plural}` index for *context* contains an *obj*
                    which might be a *name*, *id*, or an instance. When *context* is *None*, the
                    *default_context* of the :py:attr:`parent_{plural}` index is used.
                    """
                    return getattr(self,
                                   "parent_" + plural).has(obj,
                                                           context=context)

                # get child method
                @patch("get_parent_" + singular)  # noqa: F811
                def get(self, obj, default=_no_default, context=None):
                    """
                    Returns a parent {singular} given by *obj*, which might be a *name*, *id*, or an
                    instance from the :py:attr:`parent_{plural}` index for *context*. When no
                    {singular} is found, *default* is returned when set. Otherwise, an error is
                    raised. When *context* is *None*, the *default_context* of the
                    :py:attr:`parent_{plural}` index is used.
                    """
                    return getattr(self,
                                   "parent_" + plural).get(obj,
                                                           default=default,
                                                           context=context)

            else:

                # has child method
                @patch("has_parent_" + singular)
                def has(self, obj, deep=True, context=None):
                    """
                    Checks if the :py:attr:`parent_{plural}` index for *context* contains an *obj*,
                    which might be a *name*, *id*, or an instance. If *deep* is *True*, the lookup
                    is recursive. When *context* is *None*, the *default_context* of the
                    :py:attr:`parent_{plural}` index is used.
                    """
                    return getattr(self, "get_parent_" + singular)(
                        obj, default=_not_found, deep=deep,
                        context=context) != _not_found

                # get parent method
                @patch("get_parent_" + singular)
                def get(self,
                        obj,
                        default=_no_default,
                        deep=True,
                        context=None):
                    """
                    Returns a parent {singular} given by *obj*, which might be a *name*, *id*, or an
                    instance from the :py:attr:`parent_{plural}` index for *context*. If *deep* is
                    *True*, the lookup is recursive. When no {singular} is found, *default* is
                    returned when set. Otherwise, an error is raised. When *context* is *None*, the
                    *default_context* of the :py:attr:`parent_{plural}` index is used.
                    """
                    indexes = [getattr(self, "parent_" + plural)]
                    while len(indexes) > 0:
                        index = indexes.pop(0)
                        _obj = index.get(obj,
                                         default=_not_found,
                                         context=context)
                        if _obj != _not_found:
                            return _obj
                        elif deep:
                            indexes.extend(
                                getattr(_obj, "parent_" + plural)
                                for _obj in index)
                    else:
                        if default != _no_default:
                            return default
                        else:
                            raise ValueError("unknown {}: {}".format(
                                singular, obj))

                # walk parents method
                @patch("walk_parent_" + plural)  # noqa: F811
                def walk(self, context=None):
                    """
                    Walks through the :py:attr:`parent_{plural}` index for *context* and per
                    iteration, yields a parent {singular}, its depth relative to *this* {singular},
                    and its parent {plural} in a list that can be modified to alter the walking.
                    When *context* is *None*, the *default_context* of the
                    :py:attr:`parent_{plural}` index is used. When *context* is *all*, all indices
                    are traversed.
                    """
                    lookup = [
                        (obj, 1)
                        for obj in getattr(self, "parent_" + plural).values()
                    ]
                    while lookup:
                        obj, depth = lookup.pop(0)
                        objs = list(getattr(obj, "parent_" + plural).values())

                        yield (obj, depth, objs)

                        lookup.extend((obj, depth + 1) for obj in objs)

                if unique_cls == cls:

                    # is_root method
                    @patch("is_root_" + singular, prop=True)
                    def is_root(self):
                        """
                        Returns *True* when this {singular} has no parent {plural}, *False*
                        otherwise.
                        """
                        return len(getattr(self, "parent_" + plural)) == 0

        #
        # parent methods, unlimited number
        #

            if not isinstance(parents, six.integer_types):

                # add parent method with inf number of parents
                @patch("add_parent_" + singular)  # noqa: F811
                def add(self, *args, **kwargs):
                    """
                    Adds a parent {singular}. Also adds *this* {singular} to the child index of the
                    added {singular}. See :py:meth:`UniqueObjectIndex.add` for more info.
                    """
                    obj = getattr(self,
                                  "parent_" + plural).add(*args, **kwargs)
                    getattr(obj, plural).add(self)
                    return obj

        #
        # parent methods, limited number
        #

            else:

                # add parent method with inf number of parents
                @patch("add_parent_" + singular)
                def add(self, *args, **kwargs):
                    """
                    Adds a parent {singular}. Also adds *this* {singular} to the child index of the
                    added {singular}. See :py:meth:`UniqueObjectIndex.add` for more info.
                    """
                    parent_index = getattr(self, "parent_" + plural)
                    if len(parent_index) >= parents:
                        raise Exception(
                            "number of parents exceeded: {}".format(parents))
                    obj = parent_index.add(*args, **kwargs)
                    getattr(obj, plural).add(self)
                    return obj

        #
        # convenient parent methods, exactly 1 parent
        #

                if not isinstance(parents, bool) and parents == 1:

                    # direct parent access
                    @patch(name="parent_" + singular)
                    @property
                    def parent(self):
                        index = getattr(self, "parent_" + plural)
                        return None if len(index) != 1 else list(
                            index.values(context=index.ALL))[0]

        return unique_cls