Raw File
# Copyright 2017 Artem Artemev @awav
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
import uuid

from .. import misc

class Parentable:
    Very simple class for organizing GPflow objects in a tree.
    Each node contains a reference to the its parent. Links to children
    established implicitly via python object attributes.

    The Parentable class stores static variable which is used for assigning unique
    prefix name identification for each newly created object inherited from Parentable.

    Name is not required at `Parentable` object creation.
    But in this case, unique index identifier will be attached to the class name.

    :param name: String name.

    __global_index = 0

    def __init__(self, name=None):
        self._parent = None
        index = None
        if name is None:
            name = self._define_name(name)
            index = self._gen_index()
        self._name = name
        self._index = index

    def root(self):
        Top of the parentable tree.
        :return: Reference to top parentable object.
        if self._parent is None:
            return self
        return self._parent.root

    def parent(self):
        Parent for this node.
        :return: Reference to parent object.
        if self._parent is None:
            return self
        return self._parent

    def name(self):
        The name assigned to node at creation time. It can be referred also
        as original name.

        :return: Given name.
        return self._name

    def pathname(self):
        Path name is a recursive representation parent path name plus the name
        which was assigned to this object by its parent. In other words, it is
        stack of parent name where top is always parent's original name:
        `parent.pathname + parent.childname` and stop condition is root's

        For example, the pathname of an instance with the two parents may look
        like `parent0/parent1/childname_at_parent1`.
        Top parent's name equals its original name `parent0.name == parent0.pathname`.
        if self.parent is self:
            return self.name
        parent = self._parent
        return misc.tensor_name(parent.pathname, parent.childname(self))
    def index(self):
        return self._index

    def _children(self):
        Abstract property for getting pairs of names and children, respectively.

        :return: Dictionary where keys are names and values are Parentable objects.

    def _store_child(self, name, child):
        Abstract method for saving association between child and its name in
        parent's specific storage.

    def _remove_child(self, name, child):
        Abstract method for removing an association between child and its name in
        parent's specific storage.

    def _descendants(self):
        Scans full list of node descendants.

        :return: Generator of nodes.
        children = self._children
        if children is not None:
            for child in children.values():
                yield from child._descendants
                yield child
    def _contains(self, node):
        Checks either node already exist somewhere among descendants.

        :return: Boolean value.
        return node in list(self._descendants)

    def childname(self, child):
        if not isinstance(child, Parentable):
            raise ValueError('Parentable object expected, {child} passed.'.format(child=child))
        name = [ch[0] for ch in self._children.items() if ch[1] is child]
        if not name:
            raise KeyError('Parent {parent} does not have child {child}'.format(parent=self, child=child))
        return name[0]

    def _set_child(self, name, child):
        Set child.

        :param name: Child name.
        :param child: Parentable object.
        if not isinstance(child, Parentable):
            raise ValueError('Parentable child object expected, not {child}'.format(child=child))
        self._store_child(name, child)
    def _unset_child(self, name, child):
        Untie child from parent.

        :param name: Child name.
        :param child: Parentable object.
        if name not in self._children or self._children[name] is not child:
            msg = 'Child {child} with name "{name}" is not found'
            raise ValueError(msg.format(child=child, name=name))
        self._remove_child(name, child)

    def _set_parent(self, parent=None):
        Set parent.

        :param parent: Parentable object.
        :raises ValueError: Self-reference object passed.
        :raises ValueError: Non-Parentable object passed.
        if parent is not None:
            if not isinstance(parent, Parentable):
                raise ValueError('Parent object must implement Parentable interface.')
            if parent is self or parent._contains(self):
                raise ValueError('Self references are not allowed.')
        self._parent = parent if parent is not None else None
    def reset_name(self, name=None):
        self._name = self._define_name(name=name)
        self._index = self._gen_index()

    def _define_name(self, name=None):
        if name is not None:
            return name
        cls_name = self.__class__.__name__
        return cls_name

    def _gen_index(self):
        internal_index = Parentable._read_index()
        uuid_index = str(uuid.uuid4())[:8]
        pattern = '{uuid_index}-{internal_index}'
        return pattern.format(uuid_index=uuid_index, internal_index=internal_index)

    def _read_index(cls):
        Reads index number and increments it. No thread-safety guarantees.

        # TODO(@awav): index can grow indefinetly,
        # check boundaries or make another solution.
        index = cls.__global_index
        cls.__global_index += 1
        return index
back to top