示例#1
0
 def __init__(self, graph, hydrate=True):
     self.graph = graph
     # TODO: make function for subresource pattern below
     self._batch = Resource(graph.resource.metadata["batch"])
     self._cypher = Resource(graph.resource.metadata["cypher"])
     self.clear()
     self.hydrate = hydrate
示例#2
0
 def __init__(self, uri):
     self._begin = Resource(uri)
     self._begin_commit = Resource(uri + "/commit")
     self._execute = None
     self._commit = None
     self._statements = []
     self._finished = False
示例#3
0
 def __init__(self, graph):
     Bindable.__init__(self, graph.service_root.uri.resolve("load2neo"))
     try:
         self.__load2neo_version = self.resource.metadata[
             "load2neo_version"]
     except ClientError:
         raise NotImplementedError("Load2neo extension not available")
     self.__geoff_loader = Resource(self.resource.metadata["geoff_loader"])
示例#4
0
 def __init__(self, uri=None):
     if uri is None:
         service_root = ServiceRoot()
         manager = Resource(service_root.resource.metadata["management"])
         monitor = Monitor(manager.metadata["services"]["monitor"])
         uri = monitor.resource.uri
     Bindable.__init__(self, uri)
示例#5
0
 def __init__(self, graph):
     Bindable.__init__(self, graph.service_root.uri.resolve("load2neo"))
     try:
         self.__load2neo_version = self.resource.metadata["load2neo_version"]
     except ClientError:
         raise NotImplementedError("Load2neo extension not available")
     self.__geoff_loader = Resource(self.resource.metadata["geoff_loader"])
示例#6
0
class Loader(Bindable):
    def __init__(self, graph):
        Bindable.__init__(self, graph.service_root.uri.resolve("load2neo"))
        try:
            self.__load2neo_version = self.resource.metadata[
                "load2neo_version"]
        except ClientError:
            raise NotImplementedError("Load2neo extension not available")
        self.__geoff_loader = Resource(self.resource.metadata["geoff_loader"])

    @property
    def load2neo_version(self):
        return self.__load2neo_version

    def load_geoff(self, geoff):
        """ Load Geoff data via the load2neo extension.

        >>> from py2neo import Graph
        >>> from py2neo.ext.geoff import Loader
        >>> graph = Graph()
        >>> loader = Loader(graph)
        >>> loader.load_geoff("(alice)<-[:KNOWS]->(bob)")
        [{'alice': (N1), 'bob': (N2)}]

        :param geoff: geoff data to load
        :return: list of node mappings

        """
        return [{
            key: self.graph.node(value)
            for key, value in json.loads(line).items()
        } for line in self.__geoff_loader.post(geoff).content.splitlines(
            keepends=False)]
示例#7
0
class Loader(Bindable):

    def __init__(self, graph):
        Bindable.__init__(self, graph.service_root.uri.resolve("load2neo"))
        try:
            self.__load2neo_version = self.resource.metadata["load2neo_version"]
        except ClientError:
            raise NotImplementedError("Load2neo extension not available")
        self.__geoff_loader = Resource(self.resource.metadata["geoff_loader"])

    @property
    def load2neo_version(self):
        return self.__load2neo_version

    def load_geoff(self, geoff):
        """ Load Geoff data via the load2neo extension.

        >>> from py2neo import Graph
        >>> from py2neo.ext.geoff import Loader
        >>> graph = Graph()
        >>> loader = Loader(graph)
        >>> loader.load_geoff("(alice)<-[:KNOWS]->(bob)")
        [{'alice': (N1), 'bob': (N2)}]

        :param geoff: geoff data to load
        :return: list of node mappings

        """
        return [
            {key: self.graph.node(value) for key, value in json.loads(line).items()}
            for line in self.__geoff_loader.post(geoff).content.splitlines(keepends=False)
        ]
示例#8
0
 def __init__(self, graph, hydrate=True):
     self.graph = graph
     # TODO: make function for subresource pattern below
     self._batch = Resource(graph.resource.metadata["batch"])
     self._cypher = Resource(graph.resource.metadata["cypher"])
     self.clear()
     self.hydrate = hydrate
示例#9
0
    def create(self, abstract):
        """ Create a node or relationship based on the abstract entity
        provided. For example::

            batch = WriteBatch(graph)
            a = batch.create(node(name="Alice"))
            b = batch.create(node(name="Bob"))
            batch.create(rel(a, "KNOWS", b))
            results = batch.submit()

        :param abstract: node or relationship
        :type abstract: abstract
        :return: batch request object
        """
        entity = _cast(abstract, abstract=True)
        if isinstance(entity, Node):
            uri = self._uri_for(Resource(self.graph.resource.metadata["node"]))
            body = entity.properties
        elif isinstance(entity, Relationship):
            uri = self._uri_for(entity.start_node, "relationships")
            body = {"type": entity.type, "to": self._uri_for(entity.end_node)}
            if entity.properties:
                body["data"] = entity.properties
        else:
            raise TypeError(entity)
        return self.append_post(uri, body)
示例#10
0
 def __init__(self, content_type, uri, name=None):
     Bindable.__init__(self)
     self._content_type = content_type
     key_value_pos = uri.find("/{key}/{value}")
     if key_value_pos >= 0:
         self._searcher = ResourceTemplate(uri)
         self.bind(uri[:key_value_pos])
     else:
         self.bind(uri)
         self._searcher = ResourceTemplate(uri.string + "/{key}/{value}")
     uri = self.resource.uri
     if self.graph.neo4j_version >= (1, 9):
         self._create_or_fail = Resource(uri.resolve("?uniqueness=create_or_fail"))
         self._get_or_create = Resource(uri.resolve("?uniqueness=get_or_create"))
     else:
         self._create_or_fail = None
         self._get_or_create = Resource(uri.resolve("?unique"))
     self._query_template = ResourceTemplate(uri.string + "{?query,order}")
     self._name = name or uri.path.segments[-1]
     self.__searcher_stem_cache = {}
示例#11
0
    def _index_manager(self, content_type):
        """ Fetch the index management resource for the given `content_type`.

        :param content_type:
        :return:
        """
        if content_type is Node:
            uri = self.resource.metadata["node_index"]
        elif content_type is Relationship:
            uri = self.resource.metadata["relationship_index"]
        else:
            raise TypeError("Indexes can manage either Nodes or Relationships")
        return Resource(uri)
示例#12
0
 def __init__(self, content_type, uri, name=None):
     Bindable.__init__(self)
     self._content_type = content_type
     key_value_pos = uri.find("/{key}/{value}")
     if key_value_pos >= 0:
         self._searcher = ResourceTemplate(uri)
         self.bind(uri[:key_value_pos])
     else:
         self.bind(uri)
         self._searcher = ResourceTemplate(uri.string + "/{key}/{value}")
     uri = self.resource.uri
     if self.graph.neo4j_version >= (1, 9):
         self._create_or_fail = Resource(
             uri.resolve("?uniqueness=create_or_fail"))
         self._get_or_create = Resource(
             uri.resolve("?uniqueness=get_or_create"))
     else:
         self._create_or_fail = None
         self._get_or_create = Resource(uri.resolve("?unique"))
     self._query_template = ResourceTemplate(uri.string + "{?query,order}")
     self._name = name or uri.path.segments[-1]
     self.__searcher_stem_cache = {}
示例#13
0
 def _post(self, resource):
     self._assert_unfinished()
     rs = resource.post({"statements": self._statements})
     location = dict(rs.headers).get("location")
     if location:
         self._execute = Resource(location)
     j = rs.content
     rs.close()
     self._clear()
     if "commit" in j:
         self._commit = Resource(j["commit"])
     if "errors" in j:
         errors = j["errors"]
         if len(errors) >= 1:
             error = errors[0]
             raise TransactionError.new(error["code"], error["message"])
     out = []
     for result in j["results"]:
         producer = RecordProducer(result["columns"])
         out.append([
             producer.produce(self._begin.service_root.graph.hydrate(r["rest"]))
             for r in result["data"]
         ])
     return out
示例#14
0
 def fetch_latest_stats(self):
     """ Fetch the latest server statistics as a list of 2-tuples, each
     holding a `datetime` object and a named tuple of node, relationship and
     property counts.
     """
     counts = namedtuple(
         "Stats", ("node_count", "relationship_count", "property_count"))
     uri = self.resource.metadata["resources"]["latest_data"]
     latest_data = Resource(uri).get().content
     timestamps = latest_data["timestamps"]
     data = latest_data["data"]
     data = zip(
         (datetime.fromtimestamp(t) for t in timestamps),
         (counts(*x) for x in zip(
             (numberise(n) for n in data["node_count"]),
             (numberise(n) for n in data["relationship_count"]),
             (numberise(n) for n in data["property_count"]),
         )),
     )
     return data
示例#15
0
class BatchRequestList(object):

    def __init__(self, graph, hydrate=True):
        self.graph = graph
        # TODO: make function for subresource pattern below
        self._batch = Resource(graph.resource.metadata["batch"])
        self._cypher = Resource(graph.resource.metadata["cypher"])
        self.clear()
        self.hydrate = hydrate

    def __len__(self):
        return len(self._requests)

    def __nonzero__(self):
        return bool(self._requests)

    def append(self, request):
        self._requests.append(request)
        return request

    def append_get(self, uri):
        return self.append(BatchRequest("GET", uri))

    def append_put(self, uri, body=None):
        return self.append(BatchRequest("PUT", uri, body))

    def append_post(self, uri, body=None):
        return self.append(BatchRequest("POST", uri, body))

    def append_delete(self, uri):
        return self.append(BatchRequest("DELETE", uri))

    def append_cypher(self, query, params=None):
        """ Append a Cypher query to this batch. Resources returned from Cypher
        queries cannot be referenced by other batch requests.

        :param query: Cypher query
        :type query: :py:class:`str`
        :param params: query parameters
        :type params: :py:class:`dict`
        :return: batch request object
        :rtype: :py:class:`_Batch.Request`
        """
        if params:
            body = {"query": str(query), "params": dict(params)}
        else:
            body = {"query": str(query)}
        return self.append_post(self._uri_for(self._cypher), body)

    @property
    def _body(self):
        return [
            {
                "id": i,
                "method": request.method,
                "to": str(request.uri),
                "body": request.body,
            }
            for i, request in enumerate(self._requests)
        ]

    def clear(self):
        """ Clear all requests from this batch.
        """
        self._requests = []

    def find(self, request):
        """ Find the position of a request within this batch.
        """
        for i, req in pendulate(self._requests):
            if req == request:
                return i
        raise ValueError("Request not found")

    # TODO merge with Graph.relative_uri
    def _uri_for(self, resource, *segments, **kwargs):
        """ Return a relative URI in string format for the entity specified
        plus extra path segments.
        """
        if isinstance(resource, int):
            uri = "{{{0}}}".format(resource)
        elif isinstance(resource, NodePointer):
            uri = "{{{0}}}".format(resource.address)
        elif isinstance(resource, BatchRequest):
            uri = "{{{0}}}".format(self.find(resource))
        elif isinstance(resource, Node):
            # TODO: remove when Rel is also Bindable
            offset = len(resource.graph.resource.uri.string)
            uri = resource.resource.uri.string[offset:]
        else:
            offset = len(resource.service_root.graph.resource.uri)
            uri = str(resource.uri)[offset:]
        if segments:
            if not uri.endswith("/"):
                uri += "/"
            uri += "/".join(map(percent_encode, segments))
        query = kwargs.get("query")
        if query is not None:
            uri += "?" + query
        return uri

    def _execute(self):
        request_count = len(self)
        request_text = "request" if request_count == 1 else "requests"
        batch_log.info("Executing batch with {0} {1}".format(request_count, request_text))
        if __debug__:
            for id_, request in enumerate(self._requests):
                batch_log.debug(">>> {{{0}}} {1} {2} {3}".format(id_, request.method, request.uri, request.body))
        try:
            response = self._batch.post(self._body)
        except (ClientError, ServerError) as e:
            if e.exception:
                # A CustomBatchError is a dynamically created subclass of
                # BatchError with the same name as the underlying server
                # exception
                CustomBatchError = type(str(e.exception), (BatchError,), {})
                raise CustomBatchError(e)
            else:
                raise BatchError(e)
        else:
            return response

    def run(self):
        """ Execute the batch on the server and discard the results. If the
        batch results are not required, this will generally be the fastest
        execution method.
        """
        return self._execute().close()

    def stream(self):
        """ Execute the batch on the server and return iterable results. This
        method allows handling of results as they are received from the server.

        :return: iterable results
        :rtype: :py:class:`BatchResponseList`
        """
        response_list = BatchResponseList(self.graph, self._execute(), hydrate=self.hydrate)
        for response in response_list:
            yield response.body
        response_list.close()

    def submit(self):
        """ Execute the batch on the server and return a list of results. This
        method blocks until all results are received.

        :return: result records
        :rtype: :py:class:`list`
        """
        response_list = BatchResponseList(self.graph, self._execute(), hydrate=self.hydrate)
        return [response.body for response in response_list.responses]
示例#16
0
class BatchRequestList(object):
    def __init__(self, graph, hydrate=True):
        self.graph = graph
        # TODO: make function for subresource pattern below
        self._batch = Resource(graph.resource.metadata["batch"])
        self._cypher = Resource(graph.resource.metadata["cypher"])
        self.clear()
        self.hydrate = hydrate

    def __len__(self):
        return len(self._requests)

    def __nonzero__(self):
        return bool(self._requests)

    def append(self, request):
        self._requests.append(request)
        return request

    def append_get(self, uri):
        return self.append(BatchRequest("GET", uri))

    def append_put(self, uri, body=None):
        return self.append(BatchRequest("PUT", uri, body))

    def append_post(self, uri, body=None):
        return self.append(BatchRequest("POST", uri, body))

    def append_delete(self, uri):
        return self.append(BatchRequest("DELETE", uri))

    def append_cypher(self, query, params=None):
        """ Append a Cypher query to this batch. Resources returned from Cypher
        queries cannot be referenced by other batch requests.

        :param query: Cypher query
        :type query: :py:class:`str`
        :param params: query parameters
        :type params: :py:class:`dict`
        :return: batch request object
        :rtype: :py:class:`_Batch.Request`
        """
        if params:
            body = {"query": str(query), "params": dict(params)}
        else:
            body = {"query": str(query)}
        return self.append_post(self._uri_for(self._cypher), body)

    @property
    def _body(self):
        return [{
            "id": i,
            "method": request.method,
            "to": str(request.uri),
            "body": request.body,
        } for i, request in enumerate(self._requests)]

    def clear(self):
        """ Clear all requests from this batch.
        """
        self._requests = []

    def find(self, request):
        """ Find the position of a request within this batch.
        """
        for i, req in pendulate(self._requests):
            if req == request:
                return i
        raise ValueError("Request not found")

    # TODO merge with Graph.relative_uri
    def _uri_for(self, resource, *segments, **kwargs):
        """ Return a relative URI in string format for the entity specified
        plus extra path segments.
        """
        if isinstance(resource, int):
            uri = "{{{0}}}".format(resource)
        elif isinstance(resource, NodePointer):
            uri = "{{{0}}}".format(resource.address)
        elif isinstance(resource, BatchRequest):
            uri = "{{{0}}}".format(self.find(resource))
        elif isinstance(resource, Node):
            # TODO: remove when Rel is also Bindable
            offset = len(resource.graph.resource.uri.string)
            uri = resource.resource.uri.string[offset:]
        else:
            offset = len(resource.service_root.graph.resource.uri)
            uri = str(resource.uri)[offset:]
        if segments:
            if not uri.endswith("/"):
                uri += "/"
            uri += "/".join(map(percent_encode, segments))
        query = kwargs.get("query")
        if query is not None:
            uri += "?" + query
        return uri

    def _execute(self):
        request_count = len(self)
        request_text = "request" if request_count == 1 else "requests"
        batch_log.info("Executing batch with {0} {1}".format(
            request_count, request_text))
        if __debug__:
            for id_, request in enumerate(self._requests):
                batch_log.debug(">>> {{{0}}} {1} {2} {3}".format(
                    id_, request.method, request.uri, request.body))
        try:
            response = self._batch.post(self._body)
        except (ClientError, ServerError) as e:
            if e.exception:
                # A CustomBatchError is a dynamically created subclass of
                # BatchError with the same name as the underlying server
                # exception
                CustomBatchError = type(str(e.exception), (BatchError, ), {})
                raise CustomBatchError(e)
            else:
                raise BatchError(e)
        else:
            return response

    def run(self):
        """ Execute the batch on the server and discard the results. If the
        batch results are not required, this will generally be the fastest
        execution method.
        """
        return self._execute().close()

    def stream(self):
        """ Execute the batch on the server and return iterable results. This
        method allows handling of results as they are received from the server.

        :return: iterable results
        :rtype: :py:class:`BatchResponseList`
        """
        response_list = BatchResponseList(self.graph,
                                          self._execute(),
                                          hydrate=self.hydrate)
        for response in response_list:
            yield response.body
        response_list.close()

    def submit(self):
        """ Execute the batch on the server and return a list of results. This
        method blocks until all results are received.

        :return: result records
        :rtype: :py:class:`list`
        """
        response_list = BatchResponseList(self.graph,
                                          self._execute(),
                                          hydrate=self.hydrate)
        return [response.body for response in response_list.responses]
示例#17
0
class Index(Bindable):
    """ Searchable database index which can contain either nodes or
    relationships.

    .. seealso:: :py:func:`Graph.get_or_create_index`
    """

    __instances = {}

    def __new__(cls, content_type, uri, name=None):
        """ Fetch a cached instance if one is available, otherwise create,
        cache and return a new instance.

        :param uri: URI of the cached resource
        :return: a resource instance
        """
        inst = super(Index, cls).__new__(cls)
        return cls.__instances.setdefault(uri, inst)

    def __init__(self, content_type, uri, name=None):
        Bindable.__init__(self)
        self._content_type = content_type
        key_value_pos = uri.find("/{key}/{value}")
        if key_value_pos >= 0:
            self._searcher = ResourceTemplate(uri)
            self.bind(uri[:key_value_pos])
        else:
            self.bind(uri)
            self._searcher = ResourceTemplate(uri.string + "/{key}/{value}")
        uri = self.resource.uri
        if self.graph.neo4j_version >= (1, 9):
            self._create_or_fail = Resource(
                uri.resolve("?uniqueness=create_or_fail"))
            self._get_or_create = Resource(
                uri.resolve("?uniqueness=get_or_create"))
        else:
            self._create_or_fail = None
            self._get_or_create = Resource(uri.resolve("?unique"))
        self._query_template = ResourceTemplate(uri.string + "{?query,order}")
        self._name = name or uri.path.segments[-1]
        self.__searcher_stem_cache = {}

    def __repr__(self):
        return "{0}({1}, {2})".format(self.__class__.__name__,
                                      self._content_type.__name__,
                                      repr(URI(self).string))

    def _searcher_stem_for_key(self, key):
        if key not in self.__searcher_stem_cache:
            stem = self._searcher.uri_template.string.partition("{key}")[0]
            self.__searcher_stem_cache[key] = stem + percent_encode(key) + "/"
        return self.__searcher_stem_cache[key]

    def add(self, key, value, entity):
        """ Add an entity to this index under the `key`:`value` pair supplied::

            # create a node and obtain a reference to the "People" node index
            alice, = graph.create({"name": "Alice Smith"})
            people = graph.get_or_create_index(neo4j.Node, "People")

            # add the node to the index
            people.add("family_name", "Smith", alice)

        Note that while Neo4j indexes allow multiple entities to be added under
        a particular key:value, the same entity may only be represented once;
        this method is therefore idempotent.
        """
        self.resource.post({
            "key": key,
            "value": value,
            "uri": str(URI(entity))
        })
        return entity

    def add_if_none(self, key, value, entity):
        """ Add an entity to this index under the `key`:`value` pair
        supplied if no entry already exists at that point::

            # obtain a reference to the "Rooms" node index and
            # add node `alice` to room 100 if empty
            rooms = graph.get_or_create_index(neo4j.Node, "Rooms")
            rooms.add_if_none("room", 100, alice)

        If added, this method returns the entity, otherwise :py:const:`None`
        is returned.
        """
        rs = self._get_or_create.post({
            "key": key,
            "value": value,
            "uri": str(URI(entity))
        })
        if rs.status_code == CREATED:
            return entity
        else:
            return None

    @property
    def content_type(self):
        """ Return the type of entity contained within this index. Will return
        either :py:class:`Node` or :py:class:`Relationship`.
        """
        return self._content_type

    @property
    def name(self):
        """ Return the name of this index.
        """
        return self._name

    def get(self, key, value):
        """ Fetch a list of all entities from the index which are associated
        with the `key`:`value` pair supplied::

            # obtain a reference to the "People" node index and
            # get all nodes where `family_name` equals "Smith"
            people = graph.get_or_create_index(neo4j.Node, "People")
            smiths = people.get("family_name", "Smith")

        ..
        """
        return [
            self.graph.hydrate(assembled(result)) for i, result in grouped(
                self._searcher.expand(key=key, value=value).get())
        ]

    def create(self, key, value, abstract):
        """ Create and index a new node or relationship using the abstract
        provided.
        """
        batch = LegacyWriteBatch(self.graph)
        if self._content_type is Node:
            batch.create(abstract)
            batch.add_indexed_node(self, key, value, 0)
        elif self._content_type is Relationship:
            batch.create(abstract)
            batch.add_indexed_relationship(self, key, value, 0)
        else:
            raise TypeError(self._content_type)
        entity, index_entry = batch.submit()
        return entity

    def _create_unique(self, key, value, abstract):
        """ Internal method to support `get_or_create` and `create_if_none`.
        """
        if self._content_type is Node:
            body = {"key": key, "value": value, "properties": abstract}
        elif self._content_type is Relationship:
            body = {
                "key": key,
                "value": value,
                "start": str(abstract[0].__uri__),
                "type": abstract[1],
                "end": str(abstract[2].__uri__),
                "properties": abstract[3] if len(abstract) > 3 else None
            }
        else:
            raise TypeError(self._content_type)
        return self._get_or_create.post(body)

    def get_or_create(self, key, value, abstract):
        """ Fetch a single entity from the index which is associated with the
        `key`:`value` pair supplied, creating a new entity with the supplied
        details if none exists::

            # obtain a reference to the "Contacts" node index and
            # ensure that Alice exists therein
            contacts = graph.get_or_create_index(neo4j.Node, "Contacts")
            alice = contacts.get_or_create("name", "SMITH, Alice", {
                "given_name": "Alice Jane", "family_name": "Smith",
                "phone": "01234 567 890", "mobile": "07890 123 456"
            })

            # obtain a reference to the "Friendships" relationship index and
            # ensure that Alice and Bob's friendship is registered (`alice`
            # and `bob` refer to existing nodes)
            friendships = graph.get_or_create_index(neo4j.Relationship, "Friendships")
            alice_and_bob = friendships.get_or_create(
                "friends", "Alice & Bob", (alice, "KNOWS", bob)
            )

        ..
        """
        return self.graph.hydrate(
            assembled(self._create_unique(key, value, abstract)))

    def create_if_none(self, key, value, abstract):
        """ Create a new entity with the specified details within the current
        index, under the `key`:`value` pair supplied, if no such entity already
        exists. If creation occurs, the new entity will be returned, otherwise
        :py:const:`None` will be returned::

            # obtain a reference to the "Contacts" node index and
            # create a node for Alice if one does not already exist
            contacts = graph.get_or_create_index(neo4j.Node, "Contacts")
            alice = contacts.create_if_none("name", "SMITH, Alice", {
                "given_name": "Alice Jane", "family_name": "Smith",
                "phone": "01234 567 890", "mobile": "07890 123 456"
            })

        ..
        """
        rs = self._create_unique(key, value, abstract)
        if rs.status_code == CREATED:
            return self.graph.hydrate(assembled(rs))
        else:
            return None

    def remove(self, key=None, value=None, entity=None):
        """ Remove any entries from the index which match the parameters
        supplied. The allowed parameter combinations are:

        `key`, `value`, `entity`
            remove a specific entity indexed under a given key-value pair

        `key`, `value`
            remove all entities indexed under a given key-value pair

        `key`, `entity`
            remove a specific entity indexed against a given key but with
            any value

        `entity`
            remove all occurrences of a specific entity regardless of
            key and value

        """
        if key and value and entity:
            t = ResourceTemplate(self.resource.uri.string +
                                 "/{key}/{value}/{entity}")
            t.expand(key=key, value=value, entity=entity._id).delete()
        elif key and value:
            uris = [
                URI(entity.resource.metadata["indexed"])
                for entity in self.get(key, value)
            ]
            batch = LegacyWriteBatch(self.graph)
            for uri in uris:
                batch.append_delete(uri)
            batch.run()
        elif key and entity:
            t = ResourceTemplate(self.resource.uri.string + "/{key}/{entity}")
            t.expand(key=key, entity=entity._id).delete()
        elif entity:
            t = ResourceTemplate(self.resource.uri.string + "/{entity}")
            t.expand(entity=entity._id).delete()
        else:
            raise TypeError("Illegal parameter combination for index removal")

    def query(self, query):
        """ Query the index according to the supplied query criteria, returning
        a list of matched entities::

            # obtain a reference to the "People" node index and
            # get all nodes where `family_name` equals "Smith"
            people = graph.get_or_create_index(neo4j.Node, "People")
            s_people = people.query("family_name:S*")

        The query syntax used should be appropriate for the configuration of
        the index being queried. For indexes with default configuration, this
        should be Apache Lucene query syntax.
        """
        resource = self._query_template.expand(query=query)
        for i, result in grouped(resource.get()):
            yield self.graph.hydrate(assembled(result))

    def _query_with_score(self, query, order):
        resource = self._query_template.expand(query=query, order=order)
        for i, result in grouped(resource.get()):
            meta = assembled(result)
            yield self.graph.hydrate(meta), meta["score"]

    def query_by_index(self, query):
        return self._query_with_score(query, "index")

    def query_by_relevance(self, query):
        return self._query_with_score(query, "relevance")

    def query_by_score(self, query):
        return self._query_with_score(query, "score")
示例#18
0
class Transaction(object):
    """ A transaction is a transient resource that allows multiple Cypher
    statements to be executed within a single server transaction.
    """

    def __init__(self, uri):
        self._begin = Resource(uri)
        self._begin_commit = Resource(uri + "/commit")
        self._execute = None
        self._commit = None
        self._statements = []
        self._finished = False

    def _clear(self):
        self._statements = []

    def _assert_unfinished(self):
        if self._finished:
            raise TransactionFinished()

    @property
    def finished(self):
        """ Indicates whether or not this transaction has been completed or is
        still open.

        :return: :py:const:`True` if this transaction has finished,
                 :py:const:`False` otherwise
        """
        return self._finished

    def append(self, statement, parameters=None):
        """ Append a statement to the current queue of statements to be
        executed.

        :param statement: the statement to execute
        :param parameters: a dictionary of execution parameters
        """
        self._assert_unfinished()
        # OrderedDict is used here to avoid statement/parameters ordering bug
        self._statements.append(OrderedDict([
            ("statement", statement),
            ("parameters", dict(parameters or {})),
            ("resultDataContents", ["REST"]),
        ]))

    def _post(self, resource):
        self._assert_unfinished()
        rs = resource.post({"statements": self._statements})
        location = dict(rs.headers).get("location")
        if location:
            self._execute = Resource(location)
        j = rs.content
        rs.close()
        self._clear()
        if "commit" in j:
            self._commit = Resource(j["commit"])
        if "errors" in j:
            errors = j["errors"]
            if len(errors) >= 1:
                error = errors[0]
                raise TransactionError.new(error["code"], error["message"])
        out = []
        for result in j["results"]:
            producer = RecordProducer(result["columns"])
            out.append([
                producer.produce(self._begin.service_root.graph.hydrate(r["rest"]))
                for r in result["data"]
            ])
        return out

    def execute(self):
        """ Send all pending statements to the server for execution, leaving
        the transaction open for further statements.

        :return: list of results from pending statements
        """
        return self._post(self._execute or self._begin)

    def commit(self):
        """ Send all pending statements to the server for execution and commit
        the transaction.

        :return: list of results from pending statements
        """
        try:
            return self._post(self._commit or self._begin_commit)
        finally:
            self._finished = True

    def rollback(self):
        """ Rollback the current transaction.
        """
        self._assert_unfinished()
        try:
            if self._execute:
                self._execute.delete()
        finally:
            self._finished = True
示例#19
0
class Index(Bindable):
    """ Searchable database index which can contain either nodes or
    relationships.

    .. seealso:: :py:func:`Graph.get_or_create_index`
    """

    __instances = {}

    def __new__(cls, content_type, uri, name=None):
        """ Fetch a cached instance if one is available, otherwise create,
        cache and return a new instance.

        :param uri: URI of the cached resource
        :return: a resource instance
        """
        inst = super(Index, cls).__new__(cls)
        return cls.__instances.setdefault(uri, inst)

    def __init__(self, content_type, uri, name=None):
        Bindable.__init__(self)
        self._content_type = content_type
        key_value_pos = uri.find("/{key}/{value}")
        if key_value_pos >= 0:
            self._searcher = ResourceTemplate(uri)
            self.bind(uri[:key_value_pos])
        else:
            self.bind(uri)
            self._searcher = ResourceTemplate(uri.string + "/{key}/{value}")
        uri = self.resource.uri
        if self.graph.neo4j_version >= (1, 9):
            self._create_or_fail = Resource(uri.resolve("?uniqueness=create_or_fail"))
            self._get_or_create = Resource(uri.resolve("?uniqueness=get_or_create"))
        else:
            self._create_or_fail = None
            self._get_or_create = Resource(uri.resolve("?unique"))
        self._query_template = ResourceTemplate(uri.string + "{?query,order}")
        self._name = name or uri.path.segments[-1]
        self.__searcher_stem_cache = {}

    def __repr__(self):
        return "{0}({1}, {2})".format(
            self.__class__.__name__,
            self._content_type.__name__,
            repr(URI(self).string)
        )

    def _searcher_stem_for_key(self, key):
        if key not in self.__searcher_stem_cache:
            stem = self._searcher.uri_template.string.partition("{key}")[0]
            self.__searcher_stem_cache[key] = stem + percent_encode(key) + "/"
        return self.__searcher_stem_cache[key]

    def add(self, key, value, entity):
        """ Add an entity to this index under the `key`:`value` pair supplied::

            # create a node and obtain a reference to the "People" node index
            alice, = graph.create({"name": "Alice Smith"})
            people = graph.get_or_create_index(neo4j.Node, "People")

            # add the node to the index
            people.add("family_name", "Smith", alice)

        Note that while Neo4j indexes allow multiple entities to be added under
        a particular key:value, the same entity may only be represented once;
        this method is therefore idempotent.
        """
        self.resource.post({
            "key": key,
            "value": value,
            "uri": str(URI(entity))
        })
        return entity

    def add_if_none(self, key, value, entity):
        """ Add an entity to this index under the `key`:`value` pair
        supplied if no entry already exists at that point::

            # obtain a reference to the "Rooms" node index and
            # add node `alice` to room 100 if empty
            rooms = graph.get_or_create_index(neo4j.Node, "Rooms")
            rooms.add_if_none("room", 100, alice)

        If added, this method returns the entity, otherwise :py:const:`None`
        is returned.
        """
        rs = self._get_or_create.post({
            "key": key,
            "value": value,
            "uri": str(URI(entity))
        })
        if rs.status_code == CREATED:
            return entity
        else:
            return None

    @property
    def content_type(self):
        """ Return the type of entity contained within this index. Will return
        either :py:class:`Node` or :py:class:`Relationship`.
        """
        return self._content_type

    @property
    def name(self):
        """ Return the name of this index.
        """
        return self._name

    def get(self, key, value):
        """ Fetch a list of all entities from the index which are associated
        with the `key`:`value` pair supplied::

            # obtain a reference to the "People" node index and
            # get all nodes where `family_name` equals "Smith"
            people = graph.get_or_create_index(neo4j.Node, "People")
            smiths = people.get("family_name", "Smith")

        ..
        """
        return [
            self.graph.hydrate(assembled(result))
            for i, result in grouped(self._searcher.expand(key=key, value=value).get())
        ]

    def create(self, key, value, abstract):
        """ Create and index a new node or relationship using the abstract
        provided.
        """
        batch = LegacyWriteBatch(self.graph)
        if self._content_type is Node:
            batch.create(abstract)
            batch.add_indexed_node(self, key, value, 0)
        elif self._content_type is Relationship:
            batch.create(abstract)
            batch.add_indexed_relationship(self, key, value, 0)
        else:
            raise TypeError(self._content_type)
        entity, index_entry = batch.submit()
        return entity

    def _create_unique(self, key, value, abstract):
        """ Internal method to support `get_or_create` and `create_if_none`.
        """
        if self._content_type is Node:
            body = {
                "key": key,
                "value": value,
                "properties": abstract
            }
        elif self._content_type is Relationship:
            body = {
                "key": key,
                "value": value,
                "start": str(abstract[0].__uri__),
                "type": abstract[1],
                "end": str(abstract[2].__uri__),
                "properties": abstract[3] if len(abstract) > 3 else None
            }
        else:
            raise TypeError(self._content_type)
        return self._get_or_create.post(body)

    def get_or_create(self, key, value, abstract):
        """ Fetch a single entity from the index which is associated with the
        `key`:`value` pair supplied, creating a new entity with the supplied
        details if none exists::

            # obtain a reference to the "Contacts" node index and
            # ensure that Alice exists therein
            contacts = graph.get_or_create_index(neo4j.Node, "Contacts")
            alice = contacts.get_or_create("name", "SMITH, Alice", {
                "given_name": "Alice Jane", "family_name": "Smith",
                "phone": "01234 567 890", "mobile": "07890 123 456"
            })

            # obtain a reference to the "Friendships" relationship index and
            # ensure that Alice and Bob's friendship is registered (`alice`
            # and `bob` refer to existing nodes)
            friendships = graph.get_or_create_index(neo4j.Relationship, "Friendships")
            alice_and_bob = friendships.get_or_create(
                "friends", "Alice & Bob", (alice, "KNOWS", bob)
            )

        ..
        """
        return self.graph.hydrate(assembled(self._create_unique(key, value, abstract)))

    def create_if_none(self, key, value, abstract):
        """ Create a new entity with the specified details within the current
        index, under the `key`:`value` pair supplied, if no such entity already
        exists. If creation occurs, the new entity will be returned, otherwise
        :py:const:`None` will be returned::

            # obtain a reference to the "Contacts" node index and
            # create a node for Alice if one does not already exist
            contacts = graph.get_or_create_index(neo4j.Node, "Contacts")
            alice = contacts.create_if_none("name", "SMITH, Alice", {
                "given_name": "Alice Jane", "family_name": "Smith",
                "phone": "01234 567 890", "mobile": "07890 123 456"
            })

        ..
        """
        rs = self._create_unique(key, value, abstract)
        if rs.status_code == CREATED:
            return self.graph.hydrate(assembled(rs))
        else:
            return None

    def remove(self, key=None, value=None, entity=None):
        """ Remove any entries from the index which match the parameters
        supplied. The allowed parameter combinations are:

        `key`, `value`, `entity`
            remove a specific entity indexed under a given key-value pair

        `key`, `value`
            remove all entities indexed under a given key-value pair

        `key`, `entity`
            remove a specific entity indexed against a given key but with
            any value

        `entity`
            remove all occurrences of a specific entity regardless of
            key and value

        """
        if key and value and entity:
            t = ResourceTemplate(self.resource.uri.string + "/{key}/{value}/{entity}")
            t.expand(key=key, value=value, entity=entity._id).delete()
        elif key and value:
            uris = [
                URI(entity.resource.metadata["indexed"])
                for entity in self.get(key, value)
            ]
            batch = LegacyWriteBatch(self.graph)
            for uri in uris:
                batch.append_delete(uri)
            batch.run()
        elif key and entity:
            t = ResourceTemplate(self.resource.uri.string + "/{key}/{entity}")
            t.expand(key=key, entity=entity._id).delete()
        elif entity:
            t = ResourceTemplate(self.resource.uri.string + "/{entity}")
            t.expand(entity=entity._id).delete()
        else:
            raise TypeError("Illegal parameter combination for index removal")

    def query(self, query):
        """ Query the index according to the supplied query criteria, returning
        a list of matched entities::

            # obtain a reference to the "People" node index and
            # get all nodes where `family_name` equals "Smith"
            people = graph.get_or_create_index(neo4j.Node, "People")
            s_people = people.query("family_name:S*")

        The query syntax used should be appropriate for the configuration of
        the index being queried. For indexes with default configuration, this
        should be Apache Lucene query syntax.
        """
        resource = self._query_template.expand(query=query)
        for i, result in grouped(resource.get()):
            yield self.graph.hydrate(assembled(result))

    def _query_with_score(self, query, order):
        resource = self._query_template.expand(query=query, order=order)
        for i, result in grouped(resource.get()):
            meta = assembled(result)
            yield self.graph.hydrate(meta), meta["score"]

    def query_by_index(self, query):
        return self._query_with_score(query, "index")

    def query_by_relevance(self, query):
        return self._query_with_score(query, "relevance")

    def query_by_score(self, query):
        return self._query_with_score(query, "score")