pyorient icon indicating copy to clipboard operation
pyorient copied to clipboard

what is the correct way to write get_or_create_edge

Open rrmerugu opened this issue 7 years ago • 1 comments

I wrote get_or_create_vertex and it works fine (at least I can see the data correctly)

# this works great 
def orientdb_get_or_create_vertex(vertex=None, **kwargs):
    link_data = vertex.query(**kwargs).all()
    if link_data:
        link = link_data[0]
    else:
        link = vertex.create(
            **kwargs
        )
    return link

But I could not figure out a way to do get_or_create_edge. Usingedge.create(_from, _to) directly is creating duplications every time I run the script to update the data from mongo to orientdb(selected data only). how do I implement orientdb_relation_create_edge ?

def orientdb_relation_create_edge(g=None, edge=None, _from=None, _to=None):
    e = g.inE(_to, [edge]).outE(_from, [edge]).all() # not sure ?
    print (e)
    if e:
        e = e[0]
    else:
        e = edge.create(_from, _to) # using this directly is creating duplications, 
    return e

rrmerugu avatar Jan 31 '18 08:01 rrmerugu

This code quite dirty (you may need some adaptations), but it works well for me (I use it as base class for all Edge classes in OGM):

# Create Edge Class
class RelationshipGetOrCreateInterface(object):
    @classmethod
    def get_or_create(cls, from_node, to_node, **kwargs):
        """
        Get first or create according to criteria

        :param from_node:
        :param to_node:
        :param kwargs:
        :return: created or found edge object
        """
        get_query_dict = {
            "in": to_node,
            "out": from_node,
        }

        get_query_dict.update(kwargs)
        # import ipdb; ipdb.set_trace()

        # slow:
        # results = cls.objects.query(**get_query_dict)

        # optimized:
        results = cls.filter(from_node, to_node, **kwargs)
        count = len(results)

        if count>0:
            # get first
            if count > 1:
                LOG.debug("Multiple results for get_or_create filter query "
                       "return the first object only!!!")
                LOG.debug(get_query_dict)
            # found something, return first
            LOG.debug("Get the EDGE %s from DB! Data: %s" % (cls.__name__, results[0].__dict__))

            return results[0]
        else:
            # create
            return cls.create(from_node, to_node, **kwargs)

    @classmethod
    def filter(cls, from_node, to_node, **kwargs):
        rel_class_filter = cls.registry_name
        additional_filter_of_relation = ""
        if kwargs:
            # support only equality filters/ only strings!!!!
            for k,v in kwargs.items():
                additional_filter_of_relation += " and %s=%s" % (k, json.dumps(unicode(v)))

        if hasattr(to_node, "_id"):
            to_node_id = to_node._id
        elif hasattr(to_node, "id"):
            to_node_id = to_node.id
        else:
            raise Exception("Can not detect id for node: %s" % to_node)

        if hasattr(from_node, "_id"):
            from_node_id = from_node._id
        elif hasattr(from_node, "id"):
            from_node_id = from_node.id
        else:
            raise Exception("Can not detect id for node: %s" % from_node)
        command_str = "SELECT expand(outE('%s')[in=%s%s]) FROM %s" % (rel_class_filter,
                                                                      to_node_id,
                                                                      additional_filter_of_relation,
                                                                      from_node_id)
        LOG.info(command_str)
        try:
            orient_records = cls.objects.g.client.query(command_str)
            results = cls.objects.g.edges_from_records(orient_records)
            return results
        except Exception as e:           
            import socket
            LOG.warning(e)
            LOG.warning("Error happened during orient command: %s" % command_str)            
            return []

    @classmethod
    def filter_by_attributes(cls, **kwargs):
        """
        more flexible but slow method of filtering Edges in orientDB, must be used when it is impossible
        to filter by to_node and from_node (which makes query much faster)

        :param kwargs:
        :return: ORM edges
        """
        orient_records = cls.objects.query(**kwargs)
        return orient_records

    @classmethod
    def create(cls, from_node, to_node, **kwargs):
        edge = graph.create_edge(
            cls,
            from_node,
            to_node,
            **kwargs)
        LOG.debug("Creating a new Relationshisp in GraphDB: %s" % cls.__name__)
        str_from = str(from_node)
        str_to = str(to_node)        
        if kwargs:
            LOG.debug(kwargs)
        return edge

    # fast but hardcody method which makes possible to search relations with filtering
    # by string attribute
    @classmethod
    def get(cls, from_node, to_node, **kwargs):
        """
        Gets edge according to filter criteria
        Raises Exception if Not Found or Multiple Objects returned
        :param kwargs:
        :return:
        """
        results = cls.filter(from_node, to_node, **kwargs)        
        return cls.handle_results_for_get(results, from_node, to_node, **kwargs)

    def handle_results_for_get(self, results, from_node, to_node, **kwargs):
        count = len(results)
        if count==1:
            # found something
            LOG.info("Got the node from DB! %s" % kwargs)
            return results[0]
        elif count>1:
            LOG.exception("Multiple results for get filter query "
                          "kwargs: %s" % kwargs)
            for each_obj in results:
                LOG.debug(each_obj)
            raise Exception("Multiple Objects in get method!")
        else:
            # No objects found
            raise Exception("No objects according to filtering criteria: %s" % kwargs)

Would be happy to get feedback with improvements for the snippet class above

acriptis avatar Feb 02 '18 08:02 acriptis