Source code for polyglotdb.query.annotations.elements

from ..base.helper import key_for_cypher, value_for_cypher

from ..base.elements import (ClauseElement, EqualClauseElement as BaseEqualClauseElement,
                             GtClauseElement as BaseGtClauseElement, GteClauseElement as BaseGteClauseElement,
                             LtClauseElement as BaseLtClauseElement, LteClauseElement as BaseLteClauseElement,
                             NotEqualClauseElement as BaseNotEqualClauseElement,
                             InClauseElement as BaseInClauseElement, NotInClauseElement as BaseNotInClauseElement,
                             RegexClauseElement as BaseRegexClauseElement,
                             NullClauseElement as BaseNullClauseElement,
                             NotNullClauseElement as BaseNotNullClauseElement, )


class AnnotationClauseElement(ClauseElement):
    """
    Base class for filter elements that will be translated to Cypher.
    """

    def __repr__(self):
        return '<AnnotationClauseElement \'{}\'>'.format(self.for_cypher())


class PrecedenceClauseElement(AnnotationClauseElement):
    value_alias_prefix = ''
    template = "({})-[:precedes*]->({{id: {}}})"

    def __init__(self, annotation, other_annotation):
        self.node = annotation
        self.value = other_annotation.id

    @property
    def nodes(self):
        """
        Get all annotations involved in the clause.
        """
        return self.node.nodes

    @property
    def attributes(self):
        return []

    def cypher_value_string(self):
        """
        Create a Cypher parameter for the value of the clause.
        """
        return '$`%s%s`' % (self.value_alias_prefix.replace('`', ''), self.node.alias.replace('`', ''))

    def for_cypher(self):

        key = self.node.alias

        return self.template.format(node_alias=key, id_string=self.cypher_value_string())

    def is_matrix(self):
        from .attributes import SubPathAnnotation
        if isinstance(self.node, SubPathAnnotation):
            return False
        return True

    def involves(self, annotation):
        from .attributes import SubPathAnnotation
        to_match = 'alias'
        if isinstance(annotation, SubPathAnnotation):
            to_match = 'collection_alias'
        if getattr(self.node, to_match, None) == getattr(annotation, to_match):
            return True
        return False


class PrecedesClauseElement(PrecedenceClauseElement):
    value_alias_prefix = 'precedes_'
    template = "({node_alias})-[:precedes*]->({{id: {id_string}}})"


class FollowsClauseElement(PrecedenceClauseElement):
    value_alias_prefix = 'follows_'
    template = "({{id: {id_string}}})-[:precedes*]->({node_alias})"


class NotPrecedesClauseElement(PrecedenceClauseElement):
    value_alias_prefix = 'precedes_'
    template = "not ({node_alias})-[:precedes*]->({{id: {id_string}}})"


class NotFollowsClauseElement(PrecedenceClauseElement):
    value_alias_prefix = 'follows_'
    template = "not ({{id: {id_string}}})-[:precedes*]->({node_alias})"


class PausePrecedenceClauseElement(PrecedenceClauseElement):
    def __init__(self, annotation):
        self.node = annotation

    def for_cypher(self):
        key = self.node.alias
        return self.template.format(alias=key)


class FollowsPauseClauseElement(PausePrecedenceClauseElement):
    template = '(:pause)-[:precedes_pause]->({alias})'


class NotFollowsPauseClauseElement(PausePrecedenceClauseElement):
    template = 'not (:pause)-[:precedes_pause]->({alias})'


class PrecedesPauseClauseElement(PausePrecedenceClauseElement):
    template = '({alias})-[:precedes_pause]->(:pause)'


class NotPrecedesPauseClauseElement(PausePrecedenceClauseElement):
    template = 'not ({alias})-[:precedes_pause]->(:pause)'


class SubsetClauseElement(AnnotationClauseElement):
    template = "{}:{}"

    def for_cypher(self):
        """
        Return a Cypher representation of the clause.
        """
        value = key_for_cypher(self.value)
        if self.attribute.node.hierarchy.has_token_subset(self.attribute.node.node_type, self.value):
            key = self.attribute.node.alias
        else:
            key = self.attribute.node.type_alias
        return self.template.format(key,
                                    value)


class NotSubsetClauseElement(SubsetClauseElement):
    template = "NOT {}:{}"


[docs]class ContainsClauseElement(AnnotationClauseElement): """ Clause for filtering based on hierarchical relations. """ sign = 'contains' template = '''({alias})<-[:contained_by]-({token})-[:is_a]->({type} {{{label}: {value}}})''' def for_cypher(self): kwargs = {'alias': self.attribute.annotation.alias, 'value': value_for_cypher(self.value), 'label': key_for_cypher(self.attribute.label), 'type': ':{}_type'.format(self.attribute.node.node_type), 'token': ':{}'.format(self.attribute.node.node_type)} return self.template.format(**kwargs)
class AlignmentClauseElement(AnnotationClauseElement): """ Base class for filtering based on alignment. """ template = "{first}.label = {second}.label" side = '' aligned = True def __init__(self, first, second): from .attributes import HierarchicalAnnotation self.first = first if not isinstance(first, HierarchicalAnnotation) and not isinstance(second, HierarchicalAnnotation): second = getattr(self.first, second.node_type) self.second = second self.depth = 1 lower = self.first.node_type higher = self.second.node_type if lower not in self.first.hierarchy.get_lower_types(higher): lower, higher = higher, lower t = self.first.hierarchy.get_higher_types(lower) for i in t: if i == higher: break self.depth += 1 def __hash__(self): return hash((self.first, self.template, self.second)) def for_json(self): if self.side == 'left': att = 'begin' else: att = 'end' if self.aligned: op = '==' else: op = '!=' return [[[x for x in self.first.for_json()] + [att]], op, [[x for x in self.first.for_json()] + [att]]] @property def nodes(self): """ Returns ------- first and second annotations """ return self.first.nodes + self.second.nodes @property def attributes(self): """ Returns ------- the ID of the first annotation """ return [self.first.id] def for_cypher(self): """ Return a Cypher representation of the clause. """ kwargs = {'second_node_alias': self.second.alias, 'first_node_alias': self.first.alias} if self.depth != 1: kwargs['depth'] = '*' + str(self.depth) else: kwargs['depth'] = '' return self.template.format(**kwargs) def is_matrix(self): from .attributes import SubPathAnnotation if isinstance(self.first, SubPathAnnotation): return False if isinstance(self.second, SubPathAnnotation): return False return True def involves(self, annotation): from .attributes import SubPathAnnotation from ...exceptions import AnnotationAttributeError to_match = 'alias' if isinstance(annotation, SubPathAnnotation): to_match = 'collection_alias' try: if getattr(self.first, to_match, None) == getattr(annotation, to_match): return True if getattr(self.second, to_match, None) == getattr(annotation, to_match): return True except AnnotationAttributeError: pass return False class RightAlignedClauseElement(AlignmentClauseElement): """ Clause for filtering based on right alignment. """ template = '''not ({first_node_alias})-[:precedes]->()-[:contained_by{depth}]->({second_node_alias}) AND ({first_node_alias})-[:contained_by{depth}]->({second_node_alias})''' side = 'right' aligned = True class LeftAlignedClauseElement(AlignmentClauseElement): """ Clause for filtering based on left alignment. """ template = '''not ({first_node_alias})<-[:precedes]-()-[:contained_by{depth}]->({second_node_alias}) AND ({first_node_alias})-[:contained_by{depth}]->({second_node_alias})''' side = 'left' aligned = True class NotRightAlignedClauseElement(RightAlignedClauseElement): """ Clause for filtering based on not being right aligned. """ template = '''({first_node_alias})-[:precedes]->()-[:contained_by{depth}]->({second_node_alias}) AND ({first_node_alias})-[:contained_by{depth}]->({second_node_alias})''' side = 'right' aligned = False class NotLeftAlignedClauseElement(LeftAlignedClauseElement): """ Clause for filtering based on not being left aligned. """ template = '''({first_node_alias})<-[:precedes]-()-[:contained_by{depth}]->({second_node_alias}) AND ({first_node_alias})-[:contained_by{depth}]->({second_node_alias})''' side = 'left' aligned = False
[docs]class EqualClauseElement(AnnotationClauseElement, BaseEqualClauseElement): pass
[docs]class GtClauseElement(AnnotationClauseElement, BaseGtClauseElement): pass
[docs]class GteClauseElement(AnnotationClauseElement, BaseGteClauseElement): pass
[docs]class LtClauseElement(AnnotationClauseElement, BaseLtClauseElement): pass
[docs]class LteClauseElement(AnnotationClauseElement, BaseLteClauseElement): pass
[docs]class NotEqualClauseElement(AnnotationClauseElement, BaseNotEqualClauseElement): pass
[docs]class InClauseElement(AnnotationClauseElement, BaseInClauseElement): pass
class NotInClauseElement(AnnotationClauseElement, BaseNotInClauseElement): pass class RegexClauseElement(AnnotationClauseElement, BaseRegexClauseElement): pass class NullClauseElement(AnnotationClauseElement, BaseNullClauseElement): pass class NotNullClauseElement(AnnotationClauseElement, BaseNotNullClauseElement): pass