def post(self, resource): self.__assert_unfinished() rs = resource.post({"statements": self.statements}) location = rs.location if location: self.__execute = Resource(location) j = rs.content rs.close() self.statements = [] if "commit" in j: self.__commit = Resource(j["commit"]) if "errors" in j: errors = j["errors"] if len(errors) >= 1: error = errors[0] raise self.error_class.hydrate(error) out = RecordListList() for result in j["results"]: columns = result["columns"] producer = RecordProducer(columns) out.append( RecordList(columns, [ producer.produce(self.graph.hydrate(r["rest"])) for r in result["data"] ])) return out
def test_can_handle_json_error_from_post(): new_node_resource = Resource("http://localhost:7474/db/data/node") try: new_node_resource.post(["foo"]) except GraphError: assert True else: assert False
def test_can_handle_json_error_from_get(): resource = Resource("http://localhost:7474/db/data/node/spam") try: resource.get() except ClientError: assert True else: assert False
def __init__(self, uri): log.info("begin") self.statements = [] self.__begin = Resource(uri) self.__begin_commit = Resource(uri + "/commit") self.__execute = None self.__commit = None self.__finished = False self.graph = self.__begin.graph
def test_can_handle_json_error_from_delete(graph): node, = graph.create({}) non_existent_property = Resource(node.properties.resource.uri.string + "/foo") try: non_existent_property.delete() except GraphError: assert True else: assert False
def test_can_handle_other_error_from_put(): with patch.object(_Resource, "put") as mocked: mocked.side_effect = DodgyServerError resource = Resource("http://localhost:7474/db/data/node/spam") try: resource.put() except GraphError as error: assert isinstance(error.__cause__, DodgyServerError) else: assert False
def test_can_handle_400(): resource = Resource("http://localhost:7474/db/data/cypher") try: resource.post() except GraphError as error: assert_error( error, (GraphError,), "org.neo4j.server.rest.repr.BadInputException", (_ClientError, _Response), 400) else: assert False
def test_can_handle_other_error_from_post(): with patch.object(_Resource, "post") as mocked: mocked.side_effect = DodgyServerError resource = Resource("http://localhost:7474/db/data/node/spam") try: resource.post() except DodgyServerError: assert True else: assert False
def test_can_handle_json_error_from_post(): new_node_resource = Resource("http://localhost:7474/db/data/node") try: new_node_resource.post(["foo"]) except GraphError as error: cause = error.__cause__ assert isinstance(cause, _ClientError) assert isinstance(cause, _Response) assert cause.status_code == 400 else: assert False
def test_can_handle_json_error_from_get(): resource = Resource("http://localhost:7474/db/data/node/spam") try: resource.get() except GraphError as error: cause = error.__cause__ assert isinstance(cause, _ClientError) assert isinstance(cause, _Response) assert cause.status_code == 404 else: assert False
def test_can_handle_409(graph): node_id = get_attached_node_id(graph) resource = Resource("http://localhost:7474/db/data/node/%s" % node_id) try: resource.delete() except GraphError as error: assert_error( error, (GraphError,), "org.neo4j.server.rest.web.OperationFailureException", (_ClientError, _Response), 409) else: assert False
def test_can_handle_404(graph): node_id = get_non_existent_node_id(graph) resource = Resource("http://localhost:7474/db/data/node/%s" % node_id) try: resource.get() except GraphError as error: assert_error( error, (GraphError,), "org.neo4j.server.rest.web.NodeNotFoundException", (_ClientError, _Response), 404) else: assert False
def test_can_handle_json_error_from_delete(graph): node, = graph.create({}) non_existent_property = Resource(node.properties.resource.uri.string + "/foo") try: non_existent_property.delete() except GraphError as error: cause = error.__cause__ assert isinstance(cause, _ClientError) assert isinstance(cause, _Response) assert cause.status_code == 404 else: assert False
def constraints(self): if not self.__constraints: constraints = [] constraints_resource = Resource(self.graph.uri.string + "schema/constraint") constraints_content = list(constraints_resource.get().content) for i in constraints_content: constraint = dict(i) constraint["property_keys"] = list(constraint["property_keys"]) constraints.append(constraint) self.__constraints = constraints return self.__constraints
def indexes(self): if not self.__indexes: indexes = [] for label in self.labels(): index_resource = Resource(self.graph.uri.string + "schema/index/" + label) indexes_content = list(index_resource.get().content) for i in indexes_content: index = dict(i) index["property_keys"] = list(index["property_keys"]) indexes.append(index) self.__indexes = indexes return self.__indexes
def test_can_init_server_plugin(self): remote_graph = remote(self.graph) metadata = remote_graph.metadata metadata["extensions"]["FakePlugin"] = {} self.graph.__remote__ = Resource(remote_graph.uri, metadata) plugin = FakePlugin(self.graph) assert plugin.resources == {}
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 Service.__init__(self) self.bind(uri)
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
def post(self, resource): self.__assert_unfinished() rs = resource.post({"statements": self.statements}) location = rs.location if location: self.__execute = Resource(location) j = rs.content rs.close() self.statements = [] if "commit" in j: self.__commit = Resource(j["commit"]) if "errors" in j: errors = j["errors"] if len(errors) >= 1: error = errors[0] raise self.error_class.hydrate(error) out = RecordListList() for result in j["results"]: columns = result["columns"] producer = RecordProducer(columns) out.append(RecordList(columns, [producer.produce(self.graph.hydrate(r["rest"])) for r in result["data"]])) return out
class CypherTransaction(object): """ A transaction is a transient resource that allows multiple Cypher statements to be executed within a single server transaction. """ error_class = CypherTransactionError def __init__(self, uri): log.info("begin") self.statements = [] self.__begin = Resource(uri) self.__begin_commit = Resource(uri + "/commit") self.__execute = None self.__commit = None self.__finished = False self.graph = self.__begin.graph def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.commit() def __assert_unfinished(self): if self.__finished: raise Finished(self) @property def _id(self): """ The internal server ID of this transaction, if available. """ if self.__execute is None: return None else: return int(self.__execute.uri.path.segments[-1]) @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): """ Add a statement to the current queue of statements to be executed. :param statement: the statement to append :param parameters: a dictionary of execution parameters """ self.__assert_unfinished() p = {} if parameters: for key, value in parameters.items(): if isinstance(value, (Node, Rel, Relationship)): value = value._id p[key] = value # OrderedDict is used here to avoid statement/parameters ordering bug log.info("append %r %r", statement, p) self.statements.append(OrderedDict([ ("statement", statement), ("parameters", p), ("resultDataContents", ["REST"]), ])) def post(self, resource): self.__assert_unfinished() rs = resource.post({"statements": self.statements}) location = rs.location if location: self.__execute = Resource(location) j = rs.content rs.close() self.statements = [] if "commit" in j: self.__commit = Resource(j["commit"]) if "errors" in j: errors = j["errors"] if len(errors) >= 1: error = errors[0] raise self.error_class.hydrate(error) out = RecordListList() for result in j["results"]: columns = result["columns"] producer = RecordProducer(columns) out.append(RecordList(columns, [producer.produce(self.graph.hydrate(r["rest"])) for r in result["data"]])) return out def process(self): """ Send all pending statements to the server for execution, leaving the transaction open for further statements. Along with :meth:`append <.CypherTransaction.append>`, this method can be used to batch up a number of individual statements into a single HTTP request:: from py2neo import Graph graph = Graph() statement = "MERGE (n:Person {name:{N}}) RETURN n" tx = graph.cypher.begin() def add_names(*names): for name in names: tx.append(statement, {"N": name}) tx.process() add_names("Homer", "Marge", "Bart", "Lisa", "Maggie") add_names("Peter", "Lois", "Chris", "Meg", "Stewie") tx.commit() :return: list of results from pending statements """ log.info("process") 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 """ log.info("commit") try: return self.post(self.__commit or self.__begin_commit) finally: self.__finished = True def rollback(self): """ Rollback the current transaction. """ self.__assert_unfinished() log.info("rollback") try: if self.__execute: self.__execute.delete() finally: self.__finished = True
def relationship_types(self): if not self.__relationship_types: relationship_types_resource = Resource(self.graph.uri.string + "relationship/types") self.__relationship_types = sorted(list(frozenset(relationship_types_resource.get().content))) return self.__relationship_types
def labels(self): if not self.__labels: labels_resource = Resource(self.graph.uri.string + "labels") self.__labels = sorted(list(frozenset(labels_resource.get().content))) return self.__labels
def test_can_remove_rewrite_uri(): rewrite(("https", "localtoast", 4747), ("http", "localhost", 7474)) rewrite(("https", "localtoast", 4747), None) assert Resource( "https://localtoast:4747/").uri == "https://localtoast:4747/"
class CypherTransaction(object): """ A transaction is a transient resource that allows multiple Cypher statements to be executed within a single server transaction. """ error_class = CypherTransactionError def __init__(self, uri): log.info("begin") self.statements = [] self.__begin = Resource(uri) self.__begin_commit = Resource(uri + "/commit") self.__execute = None self.__commit = None self.__finished = False self.graph = self.__begin.graph def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.commit() def __assert_unfinished(self): if self.__finished: raise Finished(self) @property def _id(self): """ The internal server ID of this transaction, if available. """ if self.__execute is None: return None else: return int(self.__execute.uri.path.segments[-1]) @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, **kwparameters): """ Add a statement to the current queue of statements to be executed. :arg statement: the statement to append :arg parameters: a dictionary of execution parameters """ self.__assert_unfinished() s = ustr(statement) p = {} def add_parameters(params): if params: for k, v in dict(params).items(): if isinstance(v, (Node, Rel, Relationship)): v = v._id p[k] = v try: add_parameters(statement.parameters) except AttributeError: pass add_parameters(dict(parameters or {}, **kwparameters)) s, p = presubstitute(s, p) # OrderedDict is used here to avoid statement/parameters ordering bug log.info("append %r %r", s, p) self.statements.append( OrderedDict([ ("statement", s), ("parameters", p), ("resultDataContents", ["REST"]), ])) def post(self, resource): self.__assert_unfinished() rs = resource.post({"statements": self.statements}) location = rs.location if location: self.__execute = Resource(location) j = rs.content rs.close() self.statements = [] if "commit" in j: self.__commit = Resource(j["commit"]) if "errors" in j: errors = j["errors"] if len(errors) >= 1: error = errors[0] raise self.error_class.hydrate(error) out = RecordListList() for result in j["results"]: columns = result["columns"] producer = RecordProducer(columns) out.append( RecordList(columns, [ producer.produce(self.graph.hydrate(r["rest"])) for r in result["data"] ])) return out def process(self): """ Send all pending statements to the server for execution, leaving the transaction open for further statements. Along with :meth:`append <.CypherTransaction.append>`, this method can be used to batch up a number of individual statements into a single HTTP request:: from py2neo import Graph graph = Graph() statement = "MERGE (n:Person {name:{N}}) RETURN n" tx = graph.cypher.begin() def add_names(*names): for name in names: tx.append(statement, {"N": name}) tx.process() add_names("Homer", "Marge", "Bart", "Lisa", "Maggie") add_names("Peter", "Lois", "Chris", "Meg", "Stewie") tx.commit() :return: list of results from pending statements """ log.info("process") 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 """ log.info("commit") try: return self.post(self.__commit or self.__begin_commit) finally: self.__finished = True def rollback(self): """ Rollback the current transaction. """ self.__assert_unfinished() log.info("rollback") try: if self.__execute: self.__execute.delete() finally: self.__finished = True