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 = [] 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 __init__(self, uri): self.statements = [] self.__begin = Resource(uri) self.__begin_commit = Resource(uri + "/commit") self.__execute = None self.__commit = None self.__finished = False
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 GraphError: 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) ]
class GeoffLoader(UnmanagedExtension): """ A wrapper for the `load2neo <http://nigelsmall.com/load2neo>`_ unmanaged extension. """ DEFAULT_PATH = "/load2neo/" def __init__(self, graph, path=DEFAULT_PATH): UnmanagedExtension.__init__(self, graph, path) self.geoff_loader = Resource(self.resource.metadata["geoff_loader"]) @property def load2neo_version(self): """ The version of server extension currently installed. """ return version_tuple(self.resource.metadata["load2neo_version"]) def load(self, string): """ Load Geoff data from a string. """ rs = self.geoff_loader.post(string) return [ NodeDictionary(self.graph, json.loads(line)) for line in rs.content.splitlines(False) ] def load_xml(self, string): """ Load XML data by first converting to Geoff format. """ return self.load(xml_to_geoff(string))
class GeoffLoader(UnmanagedExtension): """ A wrapper for the `load2neo <http://nigelsmall.com/load2neo>`_ unmanaged extension. """ DEFAULT_PATH = "/load2neo/" def __init__(self, graph, path=DEFAULT_PATH): UnmanagedExtension.__init__(self, graph, path) self.geoff_loader = Resource(self.resource.metadata["geoff_loader"]) @property def load2neo_version(self): """ The version of server extension currently installed. """ return version_tuple(self.resource.metadata["load2neo_version"]) def load(self, string): """ Load Geoff data from a string. """ rs = self.geoff_loader.post(string) return [NodeDictionary(self.graph, json.loads(line)) for line in rs.content.splitlines(False)] def load_xml(self, string): """ Load XML data by first converting to Geoff format. """ return self.load(xml_to_geoff(string))
def __init__(self, graph): Bindable.__init__(self, graph.service_root.uri.resolve("load2neo")) try: self.__load2neo_version = self.resource.metadata["load2neo_version"] except GraphError: raise NotImplementedError("Load2neo extension not available") self.__geoff_loader = Resource(self.resource.metadata["geoff_loader"])
def test_implicit_authentication_through_resource_constructor(): from py2neo.core import _headers, Resource _headers.clear() _headers.update({None: [("X-Stream", "true")]}) resource = Resource("http://*****:*****@localhost:7474/") headers = _get_headers("localhost:7474") assert headers['Authorization'] == 'Basic YXJ0aHVyOmV4Y2FsaWJ1cg==' assert resource.headers['Authorization'] == 'Basic YXJ0aHVyOmV4Y2FsaWJ1cg=='
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 __init__(self, content_type, uri, name=None): Service.__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 __init__(self, content_type, uri, name=None): Service.__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 _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)
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 = dict(rs.headers).get("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 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
class Index(Service): """ 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): Service.__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(self.uri.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": entity.uri.string, }) 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": entity.uri.string, }) 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_to_index(Node, self, key, value, 0) elif self._content_type is Relationship: batch.create(abstract) batch.add_to_index(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": abstract[0].uri.string, "type": abstract[1], "end": abstract[2].uri.string, "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")
class Index(Service): """ 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): Service.__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(self.uri.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": entity.uri.string, }) 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": entity.uri.string, }) 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_to_index(Node, self, key, value, 0) elif self._content_type is Relationship: batch.create(abstract) batch.add_to_index(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": abstract[0].uri.string, "type": abstract[1], "end": abstract[2].uri.string, "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")
def __init__(self, graph, path=DEFAULT_PATH): UnmanagedExtension.__init__(self, graph, path) self.geoff_loader = Resource(self.resource.metadata["geoff_loader"])
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.statements = [] self.__begin = Resource(uri) self.__begin_commit = Resource(uri + "/commit") self.__execute = None self.__commit = None self.__finished = False 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.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 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
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): self.statements = [] self.__begin = Resource(uri) self.__begin_commit = Resource(uri + "/commit") self.__execute = None self.__commit = None self.__finished = False def __assert_unfinished(self): if self.__finished: raise TransactionFinished() @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): """ 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 = 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 = [] 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