def run(self, query, parameters=None, mode=None, bookmarks=None, metadata=None, timeout=None, db=None, **handlers): if db is not None: raise ConfigurationError("Database name parameter for selecting database is not supported in Bolt Protocol {!r}. Database name {!r}.".format(Bolt3.PROTOCOL_VERSION, db)) if not parameters: parameters = {} extra = {} if mode in (READ_ACCESS, "r"): extra["mode"] = "r" # It will default to mode "w" if nothing is specified if bookmarks: try: extra["bookmarks"] = list(bookmarks) except TypeError: raise TypeError("Bookmarks must be provided within an iterable") if metadata: try: extra["tx_metadata"] = dict(metadata) except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout: try: extra["tx_timeout"] = int(1000 * timeout) except TypeError: raise TypeError("Timeout must be specified as a number of seconds") fields = (query, parameters, extra) log.debug("[#%04X] C: RUN %s", self.local_port, " ".join(map(repr, fields))) if query.upper() == u"COMMIT": self._append(b"\x10", fields, CommitResponse(self, **handlers)) else: self._append(b"\x10", fields, Response(self, **handlers)) self._is_reset = False
def __init__(self, opener, pool_config, workspace_config, routing_context, addresses): """ :param opener: :param pool_config: :param workspace_config: :param routing_context: Dictionary with routing information :param addresses: """ super(Neo4jPool, self).__init__(opener, pool_config, workspace_config) # Each database have a routing table, the default database is a special case. log.debug("[#0000] C: <NEO4J POOL> routing addresses %r", addresses) self.init_address = addresses[0] self.routing_tables = { workspace_config.database: RoutingTable(database=workspace_config.database, routers=addresses) } self.routing_context = routing_context if self.routing_context is None: self.routing_context = {} elif "address" in self.routing_context: raise ConfigurationError( "The key 'address' is reserved for routing context.") self.routing_context["address"] = str(self.init_address) self.refresh_lock = Lock()
def route(self, database=None, bookmarks=None): if database is not None: # default database raise ConfigurationError("Database name parameter for selecting database is not " "supported in Bolt Protocol {!r}. Database name {!r}. " "Server Agent {!r}.".format(Bolt3.PROTOCOL_VERSION, database, self.server_info.agent)) metadata = {} records = [] def fail(md): from neo4j._exceptions import BoltRoutingError if md.get("code") == "Neo.ClientError.Procedure.ProcedureNotFound": raise BoltRoutingError("Server does not support routing", self.unresolved_address) else: raise BoltRoutingError("Routing support broken on server", self.unresolved_address) # Ignoring database and bookmarks because there is no multi-db support. # The bookmarks are only relevant for making sure a previously created # db exists before querying a routing table for it. self.run( "CALL dbms.cluster.routing.getRoutingTable($context)", # This is an internal procedure call. Only available if the Neo4j 3.5 is setup with clustering. {"context": self.routing_context}, mode="r", # Bolt Protocol Version(3, 0) supports mode="r" on_success=metadata.update, on_failure=fail ) self.pull(on_success=metadata.update, on_records=records.extend) self.send_all() self.fetch_all() routing_info = [dict(zip(metadata.get("fields", ()), values)) for values in records] return routing_info
def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None, db=None, **handlers): if db is not None: raise ConfigurationError( "Database name parameter for selecting database is not supported in Bolt Protocol {!r}. Database name {!r}." .format(Bolt3.PROTOCOL_VERSION, db)) extra = {} if mode in (READ_ACCESS, "r"): extra[ "mode"] = "r" # It will default to mode "w" if nothing is specified if bookmarks: try: extra["bookmarks"] = list(bookmarks) except TypeError: raise TypeError( "Bookmarks must be provided within an iterable") if metadata: try: extra["tx_metadata"] = dict(metadata) except TypeError: raise TypeError("Metadata must be coercible to a dict") if timeout: try: extra["tx_timeout"] = int(1000 * timeout) except TypeError: raise TypeError( "Timeout must be specified as a number of seconds") log.debug("[#%04X] C: BEGIN %r", self.local_port, extra) self._append(b"\x11", (extra, ), Response(self, **handlers)) self._is_reset = False
def open(cls, *addresses, auth, pool_config, workspace_config, routing_context=None): """Create a new Neo4jPool :param addresses: one or more address as positional argument :param auth: :param pool_config: :param workspace_config: :param routing_context: :return: Neo4jPool """ address = addresses[0] if routing_context is None: routing_context = {} elif "address" in routing_context: raise ConfigurationError( "The key 'address' is reserved for routing context.") routing_context["address"] = str(address) def opener(addr, timeout): return Bolt.open(addr, auth=auth, timeout=timeout, routing_context=routing_context, **pool_config) pool = cls(opener, pool_config, workspace_config, routing_context, address) return pool
def open(cls, address, *, auth, pool_config, workspace_config, routing_context=None): """Create a new BoltPool :param address: :param auth: :param pool_config: :param workspace_config: :param routing_context: :return: BoltPool """ if routing_context is None: routing_context = {} elif "address" in routing_context: raise ConfigurationError( "The key 'address' is reserved for routing context.") routing_context["address"] = str(address) def opener(addr, timeout): return Bolt.open(addr, auth=auth, timeout=timeout, routing_context=routing_context, **pool_config) pool = cls(opener, pool_config, workspace_config, routing_context, address) return pool
def consume_chain(data, *config_classes): values = [] for config_class in config_classes: if not issubclass(config_class, Config): raise TypeError("%r is not a Config subclass" % config_class) values.append(config_class._consume(data)) if data: raise ConfigurationError("Unexpected config keys: %s" % ", ".join(data.keys())) return values
def run_get_routing_table(self, on_success, on_failure, database=DEFAULT_DATABASE): if database != DEFAULT_DATABASE: raise ConfigurationError("Database name parameter for selecting database is not supported in Bolt Protocol {!r}. Database name {!r}. Server Agent {!r}.".format(Bolt3.PROTOCOL_VERSION, database, self.server_info.agent)) self.run( "CALL dbms.cluster.routing.getRoutingTable($context)", # This is an internal procedure call. Only available if the Neo4j 3.5 is setup with clustering. {"context": self.routing_context}, mode="r", # Bolt Protocol Version(3, 0) supports mode="r" on_success=on_success, on_failure=on_failure, )
def fetch_routing_info(self, *, address, timeout, database): """ Fetch raw routing info from a given router address. :param address: router address :param timeout: seconds :param database: the data base name to get routing table for :return: list of routing records or None if no connection could be established :raise ServiceUnavailable: if the server does not support routing or if routing support is broken """ # The name of system database is fixed and named as "system". # It cannot be changed for a single instance or a cluster. (We can reliably assume that the system db exists on each instance.) # # Database name is NOT case sensitive. # # Each cluster member will host the exact same databases. For example, if a cluster member A has databases "foo" and # "system", then all other members in the cluster should also have and only have "foo" and "system". # However at a certain time, the cluster members may or may not up-to-date, as a result, cluster members may contain different databases. # # Maintain a routing table for each database. # # Default database is named "neo4j", (this can be renamed on the Neo4j server). # # Any core member in a cluster can provide a routing table for any database inside the cluster. # The seed_url can be used to find all databases in the cluster. # # If the driver failed to refresh routing table with all known routers, then the driver should retry a few times before it raises a ServiceUnavailable. # # A valid routing table should at least have one router and one reader. # # To prevent the routing tables from growing infinitely. # Stale/Aged routing tables is removed when there is a failure to obtain a routing table. # Remove a routing table if it have been aged, timeout = TTL + RoutingConfig.routing_table_purge_delay # Carry out Bolt subclass imports locally to avoid circular dependency issues. from neo4j.io._bolt3 import Bolt3 from neo4j.io._bolt4x0 import Bolt4x0 from neo4j.api import ( SYSTEM_DATABASE, DEFAULT_DATABASE, READ_ACCESS, ) metadata = {} records = [] def fail(md): if md.get("code") == "Neo.ClientError.Procedure.ProcedureNotFound": raise BoltRoutingError("Server does not support routing", address) else: raise BoltRoutingError("Routing support broken on server", address) try: with self._acquire(address, timeout) as cx: _, _, server_version = (cx.server_info.agent or "").partition("/") log.debug("[#%04X] C: <ROUTING> query=%r", cx.local_port, self.routing_context or {}) if database is None: database = self.workspace_config.database # TODO: This logic should be inside the Bolt subclasses, because it can change depending on Bolt Protocol Version. if cx.PROTOCOL_VERSION == Bolt3.PROTOCOL_VERSION: if database != DEFAULT_DATABASE: raise ConfigurationError("Database name parameter for selecting database is not supported in Bolt Protocol {!r}. Database name {!r}. Server Agent {!r}.".format( Bolt3.PROTOCOL_VERSION, database, cx.server_info.agent)) cx.run( "CALL dbms.cluster.routing.getRoutingTable($context)", # This is an internal procedure call. Only available if the Neo4j 3.5 is setup with clustering. {"context": self.routing_context}, mode="r", # Bolt Protocol Version(3, 0) supports mode on_success=metadata.update, on_failure=fail, ) elif cx.PROTOCOL_VERSION == Bolt4x0.PROTOCOL_VERSION: if database == DEFAULT_DATABASE: cx.run( "CALL dbms.routing.getRoutingTable($context)", {"context": self.routing_context}, mode="r", db=SYSTEM_DATABASE, on_success=metadata.update, on_failure=fail, ) else: cx.run( "CALL dbms.routing.getRoutingTable($context, $database)", {"context": self.routing_context, "database": database}, mode="r", db=SYSTEM_DATABASE, on_success=metadata.update, on_failure=fail, ) cx.pull(on_success=metadata.update, on_records=records.extend) cx.send_all() cx.fetch_all() routing_info = [dict(zip(metadata.get("fields", ()), values)) for values in records] log.debug("[#%04X] S: <ROUTING> info=%r", cx.local_port, routing_info) return routing_info except BoltRoutingError as error: raise ServiceUnavailable(*error.args) except ServiceUnavailable: self.deactivate(address=address) return None
def driver(cls, uri, *, auth=None, **config): """Create a driver. :param uri: the connection URI for the driver, see :ref:`uri-ref` for available URIs. :param auth: the authentication details, see :ref:`auth-ref` for available authentication details. :param config: driver configuration key-word arguments, see :ref:`driver-configuration-ref` for available key-word arguments. :return: :ref:`neo4j-driver-ref` or :ref:`bolt-driver-ref` """ from neo4j.api import ( parse_neo4j_uri, parse_routing_context, DRIVER_BOLT, DRIVER_NEO4j, SECURITY_TYPE_NOT_SECURE, SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE, URI_SCHEME_BOLT, URI_SCHEME_NEO4J, URI_SCHEME_BOLT_SELF_SIGNED_CERTIFICATE, URI_SCHEME_BOLT_SECURE, URI_SCHEME_NEO4J_SELF_SIGNED_CERTIFICATE, URI_SCHEME_NEO4J_SECURE, ) driver_type, security_type, parsed = parse_neo4j_uri(uri) if "trust" in config.keys(): if config.get("trust") not in [ TRUST_ALL_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES ]: from neo4j.exceptions import ConfigurationError raise ConfigurationError( "The config setting `trust` values are {!r}".format([ TRUST_ALL_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES, ])) if security_type in [ SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE ] and ("encrypted" in config.keys() or "trust" in config.keys()): from neo4j.exceptions import ConfigurationError raise ConfigurationError( "The config settings 'encrypted' and 'trust' can only be used with the URI schemes {!r}. Use the other URI schemes {!r} for setting encryption settings." .format([ URI_SCHEME_BOLT, URI_SCHEME_NEO4J, ], [ URI_SCHEME_BOLT_SELF_SIGNED_CERTIFICATE, URI_SCHEME_BOLT_SECURE, URI_SCHEME_NEO4J_SELF_SIGNED_CERTIFICATE, URI_SCHEME_NEO4J_SECURE, ])) if security_type == SECURITY_TYPE_SECURE: config["encrypted"] = True elif security_type == SECURITY_TYPE_SELF_SIGNED_CERTIFICATE: config["encrypted"] = True config["trust"] = TRUST_ALL_CERTIFICATES if driver_type == DRIVER_BOLT: return cls.bolt_driver(parsed.netloc, auth=auth, **config) elif driver_type == DRIVER_NEO4j: routing_context = parse_routing_context(parsed.query) return cls.neo4j_driver(parsed.netloc, auth=auth, routing_context=routing_context, **config)
def driver(cls, uri, *, auth=None, **config): """ Create a Neo4j driver that uses socket I/O and thread-based concurrency. :param uri: ``bolt://host[:port]`` **Settings:** BoltDriver with no encryption. ``bolt+ssc://host[:port]`` **Settings:** BoltDriver with encryption (accepts self signed certificates). ``bolt+s://host[:port]`` **Settings:** BoltDriver with encryption (accepts only certificates signed by an certificate authority), full certificate checks. ``neo4j://host[:port][?routing_context]`` **Settings:** Neo4jDriver with no encryption. ``neo4j+ssc://host[:port][?routing_context]`` **Settings:** Neo4jDriver with encryption (accepts self signed certificates). ``neo4j+s://host[:port][?routing_context]`` **Settings:** Neo4jDriver with encryption (accepts only certificates signed by an certificate authority), full certificate checks. :param auth: :param config: connection configuration settings """ from neo4j.api import ( parse_neo4j_uri, parse_routing_context, DRIVER_BOLT, DRIVER_NEO4j, SECURITY_TYPE_NOT_SECURE, SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE, URI_SCHEME_BOLT, URI_SCHEME_NEO4J, URI_SCHEME_BOLT_SELF_SIGNED_CERTIFICATE, URI_SCHEME_BOLT_SECURE, URI_SCHEME_NEO4J_SELF_SIGNED_CERTIFICATE, URI_SCHEME_NEO4J_SECURE, ) driver_type, security_type, parsed = parse_neo4j_uri(uri) if "trust" in config.keys(): if config.get("trust") not in [ TRUST_ALL_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES ]: from neo4j.exceptions import ConfigurationError raise ConfigurationError( "The config setting `trust` values are {!r}".format([ TRUST_ALL_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES, ])) if security_type in [ SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE ] and ("encrypted" in config.keys() or "trust" in config.keys()): from neo4j.exceptions import ConfigurationError raise ConfigurationError( "The config settings 'encrypted' and 'trust' can only be used with the URI schemes {!r}. Use the other URI schemes {!r} for setting encryption settings." .format([ URI_SCHEME_BOLT, URI_SCHEME_NEO4J, ], [ URI_SCHEME_BOLT_SELF_SIGNED_CERTIFICATE, URI_SCHEME_BOLT_SECURE, URI_SCHEME_NEO4J_SELF_SIGNED_CERTIFICATE, URI_SCHEME_NEO4J_SECURE, ])) if security_type == SECURITY_TYPE_SECURE: config["encrypted"] = True elif security_type == SECURITY_TYPE_SELF_SIGNED_CERTIFICATE: config["encrypted"] = True config["trust"] = TRUST_ALL_CERTIFICATES if driver_type == DRIVER_BOLT: return cls.bolt_driver(parsed.netloc, auth=auth, **config) elif driver_type == DRIVER_NEO4j: routing_context = parse_routing_context(parsed.query) return cls.neo4j_driver(parsed.netloc, auth=auth, routing_context=routing_context, **config)