Beispiel #1
0
def get_ancestors(cur, node, sort=True):
    """
    Return an iterator which yields a ``NodeData`` object for every
    node in the hierarchy chain from ``node`` to root node.

    :param node:
    :type node: Node or uuid4
    :param bool sort: Start with closest node and end with root node.
                      (default: True). Set to False if order is
                      unimportant.
    """
    # TODO: benchmark if vectorize_nodes() or WITH RECURSIVE is faster
    sql = """
        SELECT
          nodes.*
        FROM
          ancestors
        INNER JOIN
          nodes
        ON
          ancestors.ancestor=nodes.id
        WHERE
          ancestors.node=%s;
    """
    cur.execute(sql, (str(node), ))

    if sort:
        make_node = lambda r: NodeData(**r)
        for node in vectorize_nodes(map(make_node, cur))[::-1]:
            yield node
    else:
        for result in cur:
            yield NodeData(**result)
Beispiel #2
0
def get_ancestors(cur, node, sort=True):
    """
    Return an iterator that yields a ``NodeData`` object of every
    element while traversing from ``node`` to the root node.

    :param node:
    :type node: Node or int
    :param bool sort: Start with closest node and end with root node.
                      (default: True)
    """
    # TODO: benchmark if vectorize_nodes() or WITH RECURSIVE is faster
    sql = """
        SELECT
          nodes.*
        FROM
          ancestors
        INNER JOIN
          nodes
        ON
          ancestors.ancestor=nodes.id
        WHERE
          ancestors.node=%s;
    """
    cur.execute(sql, (int(node), ))

    if sort:
        make_node = lambda r: NodeData(**r)
        for node in vectorize_nodes(map(make_node, cur)):
            yield node
    else:
        for result in cur:
            yield NodeData(**result)
Beispiel #3
0
def test_to_dict_conversion():
    kwargs = {
        'id': 11,
        'parent': 22,
        'position': 4,
        'properties': {'a': 1}
    }
    node = NodeData(**kwargs)
    assert node.to_dict() == kwargs
Beispiel #4
0
def get_node(cur, id):
    """
    Return ``NodeData`` object for given ``id``. Raises ``ValueError``
    if ID doesn't exist.

    :param uuid4 id: Database ID
    """
    sql = """
        SELECT
          *
        FROM
          nodes
        WHERE
          id = %s;
    """
    if not isinstance(id, str):
        raise TypeError('ID must be type string (UUID4).')

    cur.execute(sql, (id, ))
    result = cur.fetchone()

    if result is None:
        raise exceptions.NodeNotFound(id)
    else:
        return NodeData(**result)
Beispiel #5
0
def get_node_at_position(cur, node, position):
    """
    Return node at ``position`` in the children of ``node``.

    :param node:
    :type node: Node or int
    :param int position:
    """
    sql = """
      SELECT
        *
      FROM
        nodes
      WHERE
        parent=%s
      AND
        position=%s
    """

    cur.execute(sql, (int(node), position))
    result = cur.fetchone()

    if result is None:
        raise ValueError('Node does not exist.')
    else:
        return NodeData(**result)
Beispiel #6
0
def set_properties(cur, node, new_properties):
    """
    Set the property dictionary to ``new_properties``.
    Return ``NodeData`` object with updated properties.

    :param node:
    :type node: Node or uuid4
    :param new_properties: dict
    """
    if not isinstance(new_properties, dict):
        raise TypeError('Only dictionaries are supported.')

    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    sql = """
        UPDATE
          nodes
        SET
          properties=%s
        WHERE
          id=%s;
    """
    cur.execute(sql, (json.dumps(new_properties), str(node)))

    kwargs = node.to_dict()
    kwargs['properties'] = new_properties
    return NodeData(**kwargs)
Beispiel #7
0
def get_node(cur, id):
    """
    Return ``NodeData`` object for given ``id``. Raises ``ValueError``
    if ID doesn't exist.

    :param int id: Database ID
    """
    if type(id) != int:
        raise TypeError('Need numerical id.')

    sql = """
        SELECT
          *
        FROM
          nodes
        WHERE
          id = %s;
    """
    cur.execute(sql, (id, ))
    result = cur.fetchone()

    if result is None:
        raise ValueError('Node does not exist.')
    else:
        return NodeData(**result)
Beispiel #8
0
def insert_node(cur,
                parent,
                properties=None,
                position=None,
                auto_position=True,
                id=None):
    """
    Create a ``Node`` object, insert it into the tree and then return
    it.

    :param parent: Reference to its parent node. If `None`, this will
                   be the root node.
    :type parent: Node or uuid4
    :param dict properties: Inheritable key/value pairs
                            (see :ref:`core-properties`)
    :param int position: Position in between siblings. If 0, the node
                         will be inserted at the beginning of the
                         parents children. If -1, the node will be
                         inserted the the end of the parents children.
                         If `auto_position` is disabled, this is just a
                         value.
    :param bool auto_position: See :ref:`core-positioning`
    :param uuid4 id: Use this ID instead of automatically generating
                     one.
    """
    if id is None:
        id = str(uuid.uuid4())

    parent_id = None
    if parent is not None:
        parent_id = str(parent)

    if properties is None:
        properties = {}

    # Can't run set_position() because the node doesn't exist yet
    if auto_position:
        if isinstance(position, int) and position >= 0:
            ensure_free_position(cur, parent, position)
        else:
            position = find_highest_position(cur, parent) + 1

    sql = """
        INSERT INTO
          nodes
          (id, parent, position, properties)
        VALUES
          (%s, %s, %s, %s);
    """
    cur.execute(sql, (id, parent_id, position, json.dumps(properties)))

    return NodeData(id, parent_id, position, properties)
Beispiel #9
0
def change_parent(cur, node, new_parent, position=None, auto_position=True):
    """
    Move node and its subtree from its current to another parent node.
    Return updated ``Node`` object with new parent set. Raise
    ``ValueError`` if ``new_parent`` is inside ``node`` s subtree.

    :param node:
    :type node: Node or int
    :param new_parent: Reference to the new parent node
    :type new_parent: Node or int
    :param int position: Position in between siblings. If 0, the node
                         will be inserted at the beginning of the
                         parents children. If -1, the node will be
                         inserted the the end of the parents children.
                         If `auto_position` is disabled, this is just a
                         value.
    :param bool auto_position: See :ref:`core-positioning`.
    """
    new_parent_id = int(new_parent)
    if new_parent_id in get_descendant_ids(cur, node):
        raise ValueError('Cannot move node into its own subtree.')

    # Can't run set_position() here because the node hasn't been moved yet,
    # must do it manually
    if auto_position:
        if type(position) == int and position >= 0:
            ensure_free_position(cur, new_parent_id, position)
        else:
            position = find_highest_position(cur, new_parent_id) + 1

    sql = """
        UPDATE
          nodes
        SET
          parent=%s,
          position=%s
        WHERE
          id=%s;
    """
    cur.execute(sql, (new_parent_id, position, int(node)))

    if type(node) == int:
        node = get_node(cur, node)

    kwargs = node.to_dict()
    kwargs['parent'] = new_parent_id
    kwargs['position'] = position
    return NodeData(**kwargs)
Beispiel #10
0
def insert_node(cur, parent, properties=None, position=None,
                auto_position=True):
    """
    Create a ``Node`` object, insert it into the tree and then return
    it.

    :param parent: Reference to its parent node. If `None`, this will
                   be the root node.
    :type parent: Node or int
    :param dict properties: Inheritable key/value pairs
                            (see :ref:`core-properties`)
    :param int position: Position in between siblings. If 0, the node
                         will be inserted at the beginning of the
                         parents children. If -1, the node will be
                         inserted the the end of the parents children.
                         If `auto_position` is disabled, this is just a
                         value.
    :param bool auto_position: See :ref:`core-positioning`
    """
    parent_id = None
    if parent is not None:
        parent_id = int(parent)

    if properties is None:
        properties = {}

    # Can't run set_position() because the node doesn't exist yet
    if auto_position:
        if type(position) == int and position >= 0:
            ensure_free_position(cur, parent, position)
        else:
            position = find_highest_position(cur, parent) + 1

    sql = """
        INSERT INTO
          nodes
          (parent, position, properties)
        VALUES
          (%s, %s, %s);
    """
    cur.execute(sql, (parent_id, position, json.dumps(properties)))

    cur.execute("SELECT LASTVAL();")
    id = cur.fetchone()['lastval']
    node = NodeData(id, parent_id, position, properties)

    return node
Beispiel #11
0
def get_nodes_by_property_dict(cur, query):
    """
    Return an iterator that yields a ``NodeData`` object of every node
    which contains all key/value pairs of ``query`` in its property
    dictionary. Inherited keys are not considered.

    :param dict query: The dictionary to search for
    """
    sql = """
        SELECT
          *
        FROM
          nodes
        WHERE
          properties @> %s;
    """
    cur.execute(sql, (json.dumps(query), ))
    for result in cur:
        yield NodeData(**result)
Beispiel #12
0
def get_nodes_by_property_key(cur, key):
    """
    Return an iterator that yields a ``NodeData`` object of every node
    which contains ``key`` in its property dictionary. Inherited keys
    are not considered.

    :param str key: The key to search for
    """
    sql = """
        SELECT
          *
        FROM
          nodes
        WHERE
          properties ? %s;
    """
    cur.execute(sql, (key, ))
    for result in cur:
        yield NodeData(**result)
Beispiel #13
0
def get_root_node(cur):
    """
    Return root node. Raise ``ValueError`` if root node doesn't exist.
    """
    sql = """
        SELECT
          *
        FROM
          nodes
        WHERE
          parent IS NULL;
    """
    cur.execute(sql)
    result = cur.fetchone()

    if result is None:
        raise ValueError('No root node.')
    else:
        return NodeData(**result)
Beispiel #14
0
def set_property_value(cur, node, key, value):
    """
    Set the value for a single property key.
    Return ``NodeData`` object with updated properties.

    :param node:
    :type node: Node or uuid4
    :param key: str
    :param value: object
    """
    id = str(node)
    if isinstance(node, str):
        node = get_node(cur, id)

    properties = node.properties.copy()
    properties[key] = value
    set_properties(cur, node, properties)

    kwargs = node.to_dict()
    kwargs['properties'] = properties
    return NodeData(**kwargs)
Beispiel #15
0
def get_children(cur, node):
    """
    Return an iterator that yields a ``NodeData`` object of every
    immediate child.

    :param node:
    :type node: Node or int
    """
    sql = """
        SELECT
          *
        FROM
          nodes
        WHERE
          parent=%s
        ORDER BY
          position;
    """
    cur.execute(sql, (int(node), ))
    for result in cur:
        yield NodeData(**result)
Beispiel #16
0
def get_descendants(cur, node):
    """
    Return an iterator that yields the ID of every element while
    traversing from ``node`` to the root node.

    :param node:
    :type node: Node or int
    """
    sql = """
        SELECT
          nodes.*
        FROM
          ancestors
        INNER JOIN
          nodes
        ON
          ancestors.node=nodes.id
        WHERE
          ancestors.ancestor=%s;
    """
    cur.execute(sql, (int(node), ))
    for result in cur:
        yield NodeData(**result)
Beispiel #17
0
def test_basic_representation():
    node = NodeData(11, 22)
    assert repr(node) == "<NodeData id=11>"
Beispiel #18
0
def test_title_representation():
    node = NodeData(11, 22, properties={'title': 'my test'})
    assert repr(node) == "<NodeData id=11, title='my test'>"