Source

graphalchemy / graphalchemy / elements.py

Full commit
# -*- coding: utf-8 -*-
'''graphalchemy classes'''

from __future__ import absolute_import

from appspace.ext import direct, factory

from stuf.utils import both, getcls, getter, lazy, lazy_class

from .core import Synced

conf = getattr(Synced, 'S')


class Model(Synced):

    '''graph model'''

    _g = direct(conf.manager.graph, conf.appspace)
    _h = factory(conf.helper.model, conf.appspace, '_sync')

    def __init__(self, element=None, **kw):
        '''
        init

        @param element: link element (default: None)

        For keywords:

        Allowed property values include strings, numbers, booleans, as well as
        arrays of those primitives. Within each array, all values must be of
        the same kind.
        '''
        self.direction = kw.pop('direction', 'outgoing')
        super(Model, self).__init__()
        # graph element
        self.element = element
        self._attrs = kw

    def _element(self, this):
        '''syncronized data structure'''
        return self._p.properties(super(Model, self)._element(this))

    @property
    def id(self):
        '''graph element id'''
        return self._p.id(self.element)

    @lazy_class
    def l(self):
        '''link wrapper'''
        return self._l()

    # prime access
    _p = l

    @both
    def links(self):
        '''sequence of all links'''
        return self._links(self, getcls(self))

    @links.expression
    def links(self):
        '''sequence of all links connected to this node'''
        return self.l

    @lazy
    def private(self):
        '''private graph element properties'''
        return self._sync.private

    @lazy
    def properties(self):
        '''all graph element properties'''
        return self._sync.properties

    @lazy
    def public(self):
        '''public graph element properties'''
        return self._sync.public

    @classmethod
    def _l(cls):
        '''link wrapper'''
        return cls._g.links.wrap(cls._link())

    def _refresh(self):
        '''refresh original'''
        self._sync.update_original(self._p.properties(self.element))
        self.links.reset()

    def copy(self, **kw):
        '''copy graph element properties'''
        return self._sync.copy(**kw)

    def delete(self, indices=None):
        '''delete element'''
        self._p.delete_element(self, indices)

    def index_under(self, index, key, value):
        '''
        index one property of a graph element

        @param index: graph index label
        @param key: graph element property key
        @param value: graph element property value
        '''
        self._p.index_one(index, key, value, self, True)

    def index_by(self, index=None, *indexed):
        '''
        index properties on a graph element

        @param index: graph index label (default: None)
        @param *indexed: properties to index
        '''
        self._p.index_many(index, self, indexed, True)

    def rollback(self):
        '''throw away uncommitted changes'''
        self._sync.reset()

    def update(self, **kw):
        '''
        update graph element properties

        For keywords:

        Allowed property values include strings, numbers, booleans, as well as
        arrays of those primitives. Within each array, all values must be of
        the same kind.
        '''
        self._sync.update_current(kw)


class Link(Model):

    '''link model'''

    def __init__(self, element=None, link=None, this=None, that=None, **kw):
        '''
        init

        @param element: link element (default: None)
        @param link: link kind name (default: None)
        @param this: one node in a link (default: None)
        @param that: another node in a link (default: None)

        For keywords:

        Allowed property values include strings, numbers, booleans, as well as
        arrays of those primitives. Within each array, all values must be of
        the same kind.
        '''
        super(Link, self).__init__(element, **kw)
        self.direction = kw.pop('direction', 'outgoing')
        # link kind name
        self.link = link
        # one end
        self.that = that if that is not None else self._node()(
            getter(element, 'end')
        )
        # the other end
        self.this = this if this is not None else self._node()(
            getter(element, 'start')
        )

    @lazy
    def deep_dump(self):
        '''deep dump link and both nodes'''
        return self.copy(
            end=self.end.deep_dump,
            id=self.id,
            kind=self.kind,
            start=self.start.deep_dump,
        )

    @lazy
    def dump(self):
        '''dump link and both nodes'''
        return self.copy(
            end=self.end.dump,
            id=self.id,
            start=self.start.dump,
            kind=self.kind,
        )

    @lazy
    def end(self):
        '''ending node of link'''
        return self.that if self.direction == 'incoming' else self.this

    @property
    def kind(self):
        '''kind of link'''
        return self.l.kind(self.element)

    @lazy
    def start(self):
        '''starting node of link'''
        return self.this if self.direction == 'incoming' else self.that


class Node(Model):

    '''graph node model'''

    @lazy
    def deep_dump(self):
        '''
        serialize this node, its outgoing related nodes, and their outgoing
        related nodes
        '''
        return self.copy(id=self.id, nodes=self.nodes.deep_dump)

    @lazy
    def dump(self):
        '''serialize this node and outgoing related nodes'''
        return self.copy(id=self.id, nodes=self.nodes.dump)

    @lazy_class
    def n(self):
        '''node wrapper'''
        return self._n()

    # prime node
    _p = n

    @both
    def nodes(self):
        '''sequence of all nodes with links with this node'''
        return self._nodes(self, self.__class__)

    @nodes.expression
    def nodes(self):
        '''sequence of all nodes with links with this node'''
        return self.n

    @classmethod
    def _l(cls):
        '''link wrapper'''
        return cls._g.links.wrap(cls._link())

    @classmethod
    def _n(cls):
        '''node wrapper'''
        return cls._g.nodes.wrap(cls)

    def _refresh(self):
        '''refresh node objects'''
        super(Node, self)._refresh()
        self.nodes.reset()

    def link(self, link, node, **kw):
        '''
        link this node to other node

        @param link: kind of link
        @param node: other node
        @param **kw: link properties
        '''
        self.l.create(link, self, node, **kw)