def test_reload_external_changes(manager, connection, static_types):
    Thing = static_types['Thing']

    manager.save(Thing)
    manager.reload_types()  # cache type registry

    # update the graph as an external manager would
    # (change a value and bump the typesystem version)
    match_clauses = (
        get_match_clause(Thing, 'Thing', manager.type_registry),
        get_match_clause(manager.type_system, 'ts', manager.type_registry),
    )
    query = join_lines(
        'MATCH',
        (match_clauses, ','),
        'SET ts.version = {version}',
        'SET Thing.cls_attr = {cls_attr}',
        'RETURN Thing'
    )
    query_params = {
        'cls_attr': 'placeholder',
        'version': str(uuid.uuid4())
    }
    cypher.execute(connection, query, query_params)

    # reloading types should see the difference
    manager.reload_types()
    descriptor = manager.type_registry.get_descriptor(Thing)
    assert "cls_attr" in descriptor.class_attributes
Example #2
0
def test_get_match_clause_for_relationship_non_unique_endpoint(cls):
    a = cls()
    b = cls()

    rel = Connects(start=a, end=b)
    with pytest.raises(NoUniqueAttributeError) as exc:
        get_match_clause(rel, 'rel', type_registry)
    assert "doesn't have any unique attributes" in str(exc)
Example #3
0
    def delete(self, obj):
        """ Deletes an object from the store.

        Args:
            obj: The object to delete.

        Returns:
            A tuple: with (number of nodes removed, number of rels removed)
        """
        invalidates_types = False

        if isinstance(obj, Relationship):
            query = join_lines(
                'MATCH {}, {},',
                'n1 -[rel]-> n2',
                'DELETE rel',
                'RETURN 0, count(rel)'
            ).format(
                get_match_clause(obj.start, 'n1', self.type_registry),
                get_match_clause(obj.end, 'n2', self.type_registry),
            )
            rel_type = type(obj)
            if rel_type in (IsA, DeclaredOn):
                invalidates_types = True

        elif isinstance(obj, PersistableType):
            query = join_lines(
                'MATCH {}',
                'OPTIONAL MATCH attr -[:DECLAREDON]-> obj',
                'DELETE attr',
                'WITH obj',
                'MATCH obj -[rel]- ()',
                'DELETE obj, rel',
                'RETURN count(obj), count(rel)'
            ).format(
                get_match_clause(obj, 'obj', self.type_registry)
            )
            invalidates_types = True
        else:
            query = join_lines(
                'MATCH {},',
                'obj -[rel]- ()',
                'DELETE obj, rel',
                'RETURN count(obj), count(rel)'
            ).format(
                get_match_clause(obj, 'obj', self.type_registry)
            )

        # TODO: delete node/rel from indexes
        res = next(self._execute(query))
        if invalidates_types:
            self.invalidate_type_system()
        return res
Example #4
0
    def update_type(self, tpe, bases):
        """ Change the bases of the given ``tpe``
        """
        if not isinstance(tpe, PersistableType):
            raise UnsupportedTypeError("Object is not a PersistableType")

        if self.type_registry.is_static_type(tpe):
            raise CannotUpdateType("Type '{}' is defined in code and cannot"
                                   "be updated.".format(get_type_id(tpe)))

        descriptor = self.type_registry.get_descriptor(tpe)
        existing_attrs = dict_difference(descriptor.attributes,
                                         descriptor.declared_attributes)
        base_attrs = {}
        for base in bases:
            desc = self.type_registry.get_descriptor(base)
            base_attrs.update(desc.attributes)
        base_attrs = dict_difference(base_attrs,
                                     descriptor.declared_attributes)

        if existing_attrs != base_attrs:
            raise CannotUpdateType("Inherited attributes are not identical")

        match_clauses = [get_match_clause(tpe, 'type', self.type_registry)]
        create_clauses = []
        query_args = {}

        for index, base in enumerate(bases):
            name = 'base_{}'.format(index)
            match = get_match_clause(base, name, self.type_registry)
            create = "type -[:ISA {%s_props}]-> %s" % (name, name)

            query_args["{}_props".format(name)] = {'base_index': index}
            match_clauses.append(match)
            create_clauses.append(create)

        query = join_lines(
            "MATCH",
            (match_clauses, ','),
            ", type -[r:ISA]-> ()",
            "DELETE r",
            "CREATE",
            (create_clauses, ','),
            "RETURN type")

        try:
            next(self._execute(query, **query_args))
            self.invalidate_type_system()
        except StopIteration:
            raise CannotUpdateType("Type or bases not found in the database.")

        self.reload_types()
def test_class_attr_class_serialization(manager):
    with collector() as classes:
        class A(Entity):
            id = Uuid()
            cls_attr = "spam"

        class B(A):
            cls_attr = "ham"

        class C(B):
            pass

    manager.save_collected_classes(classes)

    # we want inherited attributes when we serialize
    assert manager.serialize(C) == {
        '__type__': 'PersistableType',
        'id': 'C',
        'cls_attr': 'ham',
    }

    # we don't want inherited attributes in the db
    query_str = "MATCH {} RETURN C".format(
        get_match_clause(C, 'C', manager.type_registry),
    )

    (db_attrs,) = next(manager._execute(query_str))
    properties = db_attrs.get_properties()
    assert 'cls_attr' not in properties
Example #6
0
def has_property(manager, obj, prop):
    query = "MATCH {} RETURN node.{}".format(
        get_match_clause(obj, 'node', manager.type_registry),
        prop,
    )

    properties = list(manager.query(query))
    return not (properties == [(None,)])
Example #7
0
def test_get_match_clause_for_relationship():
    a = IndexableThing(indexable_attr='a')
    b = IndexableThing(indexable_attr='b')
    rel = Connects(start=a, end=b)
    match_clause = get_match_clause(rel, 'rel', type_registry)
    expected = """
        (rel__start:IndexableThing {indexable_attr: "a"}),
        (rel__end:IndexableThing {indexable_attr: "b"}),
        (rel__start)-[rel:CONNECTS]->(rel__end)
    """
    assert match_clause == dedent(expected)
Example #8
0
def get_instance_of_relationship(manager, obj):
    query = """
        MATCH
        {},
        (node)-[instance_of:INSTANCEOF]->()
        RETURN instance_of
    """.format(
        get_match_clause(obj, 'node', manager.type_registry),
    )
    instance_of = manager.query_single(query)
    return instance_of
Example #9
0
def test_get_match_clause_mutiple_uniques():
    obj = TwoUniquesThing(
        indexable_attr="bar",
        also_unique="baz"
    )

    match_clause = get_match_clause(obj, "foo", type_registry)
    # order if labels and properties are undefined, so try all possibilities
    possible_labels = ['IndexableThing', 'TwoUniquesThing']
    possible_attrs = ['indexable_attr: "bar"', 'also_unique: "baz"']
    possible_clauses = set()
    for labels in permutations(possible_labels, 2):
        for attrs in permutations(possible_attrs, 2):
            clause = '(foo:{} {{{}}})'.format(
                ':'.join(labels), ', '.join(attrs)
            )
            possible_clauses.add(clause)

    assert match_clause in possible_clauses
Example #10
0
    def get_related_objects(self, rel_cls, ref_cls, obj):

        if ref_cls is Outgoing:
            rel_query = '(n)-[relation:{}]->(related)'
        elif ref_cls is Incoming:
            rel_query = '(n)<-[relation:{}]-(related)'

        # TODO: should get the rel name from descriptor?
        rel_query = rel_query.format(get_neo4j_relationship_name(rel_cls))

        query = join_lines(
            'MATCH {idx_lookup}, {rel_query}'
            'RETURN related, relation'
        )

        query = query.format(
            idx_lookup=get_match_clause(obj, 'n', self.type_registry),
            rel_query=rel_query
        )

        return self.query(query)
def test_class_att_overriding(manager):
    with collector() as classes:
        class A(Entity):
            id = Uuid()
            cls_attr = "spam"

        class B(A):
            cls_attr = "ham"

        class C(B):
            pass

    manager.save_collected_classes(classes)
    manager.reload_types()

    a = A()
    b = B()
    c = C()

    assert a.cls_attr == "spam"
    assert b.cls_attr == "ham"
    assert c.cls_attr == "ham"

    manager.save(a)
    manager.save(b)
    manager.save(c)

    query_str = """
        MATCH
        {},
        (node)-[:INSTANCEOF]->()-[:ISA*0..]->A
        RETURN node
    """.format(
        get_match_clause(A, 'A', manager.type_registry),
    )

    results = list(manager.query(query_str))

    for col, in results:
        assert col.cls_attr == col.__class__.cls_attr
Example #12
0
def test_get_match_clause_bad_unique_value():
    with pytest.raises(NoUniqueAttributeError):
        get_match_clause(IndexableThing(
            indexable_attr=None), 'foo', type_registry)
Example #13
0
def test_get_match_clause_no_uniques():
    with pytest.raises(NoUniqueAttributeError):
        get_match_clause(NotIndexable(), 'foo', type_registry)
Example #14
0
def test_get_match_clause_for_instance():
    obj = IndexableThing(indexable_attr="bar")

    clause = get_match_clause(obj, "foo", type_registry)
    assert clause == '(foo:IndexableThing {indexable_attr: "bar"})'
Example #15
0
def test_get_match_clause_for_type():
    clause = get_match_clause(IndexableThing, "foo", type_registry)
    assert clause == '(foo:PersistableType {id: "IndexableThing"})'
Example #16
0
def test_get_match_clause_for_relationship_missing_endpoint():
    rel = Connects()
    with pytest.raises(NoUniqueAttributeError) as exc:
        get_match_clause(rel, 'rel', type_registry)
    assert 'is missing a start or end node' in str(exc)
Example #17
0
    def change_instance_type(self, obj, type_id, updated_values=None):
        if updated_values is None:
            updated_values = {}

        type_registry = self.type_registry

        if type_id not in type_registry._types_in_db:
            raise TypeNotPersistedError(type_id)

        properties = self.serialize(obj, for_db=True)
        properties['__type__'] = type_id
        properties.update(updated_values)

        # get rid of any attributes not supported by the new type
        properties = self.serialize(self.deserialize(properties), for_db=True)

        old_type = type(obj)
        new_type = type_registry.get_class_by_id(type_id)

        rel_props = type_registry.object_to_dict(InstanceOf(), for_db=True)

        old_labels = set(type_registry.get_labels_for_type(old_type))
        new_labels = set(type_registry.get_labels_for_type(new_type))
        removed_labels = old_labels - new_labels
        added_labels = new_labels - old_labels

        if removed_labels:
            remove_labels_statement = 'REMOVE obj:' + ':'.join(removed_labels)
        else:
            remove_labels_statement = ''

        if added_labels:
            add_labels_statement = 'SET obj :' + ':'.join(added_labels)
        else:
            add_labels_statement = ''

        match_clauses = (
            get_match_clause(obj, 'obj', type_registry),
            get_match_clause(new_type, 'type', type_registry)
        )

        query = join_lines(
            'MATCH',
            (match_clauses, ','),
            ', (obj)-[old_rel:INSTANCEOF]->()',
            'DELETE old_rel',
            'CREATE (obj)-[new_rel:INSTANCEOF {rel_props}]->(type)',
            'SET obj={properties}',
            remove_labels_statement,
            add_labels_statement,
            'RETURN obj',
        )

        new_obj = self.query_single(
            query, properties=properties, rel_props=rel_props)

        if new_obj is None:
            raise NoResultFound(
                "{} not found in db".format(repr(obj))
            )

        set_store_for_object(new_obj, self)
        return new_obj
Example #18
0
    def _update(self, persistable, existing, changes):

        registry = self.type_registry

        set_clauses = ', '.join([
            'n.%s={%s}' % (key, key) for key, value in changes.items()
            if not isinstance(value, dict)
        ])

        if set_clauses:
            set_clauses = 'SET %s' % set_clauses
        else:
            set_clauses = ''

        if isinstance(persistable, type):

            query_args = {'type_id': get_type_id(persistable)}
            class_attr_changes = {k: v for k, v in changes.items()
                                  if k != 'attributes'}
            query_args.update(class_attr_changes)

            where = []

            descr = registry.get_descriptor(persistable)
            for attr_name in descr.declared_attributes.keys():
                where.append('attr.name = {attr_%s}' % attr_name)
                query_args['attr_%s' % attr_name] = attr_name

            if where:
                where = ' OR '.join(where)
                where = 'WHERE not(%s)' % where
            else:
                where = ''

            query = join_lines(
                'MATCH (n:PersistableType)',
                'WHERE n.id = {type_id}',
                set_clauses,
                'WITH n',
                'MATCH attr -[r:DECLAREDON]-> n',
                where,
                'DELETE attr, r',
                'RETURN n',
            )
            self._update_types(persistable)
        else:
            match_clause = get_match_clause(existing, 'n', registry)
            query = join_lines(
                'MATCH %s' % match_clause,
                set_clauses,
                'RETURN n'
            )
            query_args = changes

        try:
            (result,) = next(self._execute(query, **query_args))
        except StopIteration:
            # this can happen, if no attributes where changed on a type
            result = persistable

        return result
Example #19
0
    def _get_changes(self, persistable):
        changes = {}
        existing = None

        registry = self.type_registry

        if isinstance(persistable, PersistableType):

            if issubclass(persistable, Relationship):
                # not stored in the db; must be static
                return None, {}

            query = """
                MATCH
                    {}
                OPTIONAL MATCH
                    (attr)-[:DECLAREDON*0..]->(cls)
                RETURN
                    cls, collect(attr.name)
            """.format(get_match_clause(persistable, 'cls', registry))

            # don't use self.query since we don't want to convert the py2neo
            # node into an object
            rows = self._execute(query)
            cls_node, attrs = next(rows, (None, None))

            if cls_node is None:
                # have not found the cls
                return None, {}

            existing_cls_attrs = cls_node._properties

            # Make sure we get a clean view of current data.
            registry.refresh_type(persistable)

            new_cls_attrs = registry.object_to_dict(persistable)

            # If any existing keys in "new" are missing in "old", add `None`s.
            # Unlike instance attributes, we just need to remove the properties
            # from the node, which we can achieve by setting the values to None
            for key in set(existing_cls_attrs) - set(new_cls_attrs):
                new_cls_attrs[key] = None
            changes = get_changes(old=existing_cls_attrs, new=new_cls_attrs)

            attrs = set(attrs)

            modified_attrs = {}

            descr = registry.get_descriptor(persistable)
            for name, attr in descr.declared_attributes.items():
                if name not in attrs:
                    modified_attrs[name] = attr

            del_attrs = set(attrs)

            for name in Descriptor(persistable).attributes.keys():
                del_attrs.discard(name)

            for name in del_attrs:
                modified_attrs[name] = None

            if modified_attrs:
                changes['attributes'] = modified_attrs

            # we want to return the existing class
            type_id = get_type_id(persistable)
            existing = registry.get_descriptor_by_id(type_id).cls
        else:
            try:
                query = 'MATCH {} RETURN obj'.format(
                    get_match_clause(persistable, 'obj', registry)
                )
            except NoUniqueAttributeError:
                existing = None
            else:
                existing = self.query_single(query)

            if existing is not None:
                existing_props = registry.object_to_dict(existing)
                props = registry.object_to_dict(persistable)

                if existing_props == props:
                    return existing, {}

                changes = get_changes(old=existing_props, new=props)

        return existing, changes