def fetch_routing_table(self, address): """ Fetch a routing table from a given router address. :param address: router address :return: a new RoutingTable instance or None if the given router is currently unable to provide routing information :raise ServiceUnavailable: if no writers are available :raise ProtocolError: if the routing information received is unusable """ new_routing_info = self.fetch_routing_info(address) if new_routing_info is None: return None # Parse routing info and count the number of each type of server new_routing_table = RoutingTable.parse_routing_info(new_routing_info) num_routers = len(new_routing_table.routers) num_readers = len(new_routing_table.readers) num_writers = len(new_routing_table.writers) # No writers are available. This likely indicates a temporary state, # such as leader switching, so we should not signal an error. # When no writers available, then we flag we are reading in absence of writer self.missing_writer = (num_writers == 0) # No routers if num_routers == 0: raise ProtocolError("No routing servers returned from server %r" % (address,)) # No readers if num_readers == 0: raise ProtocolError("No read servers returned from server %r" % (address,)) # At least one of each is fine, so return this table return new_routing_table
def parse_routing_info(cls, records): """ Parse the records returned from a getServers call and return a new RoutingTable instance. """ if len(records) != 1: raise ProtocolError("Expected exactly one record") record = records[0] routers = [] readers = [] writers = [] try: servers = record["servers"] for server in servers: role = server["role"] addresses = [] for address in server["addresses"]: addresses.extend(resolve(SocketAddress.parse(address, DEFAULT_PORT))) if role == "ROUTE": routers.extend(addresses) elif role == "READ": readers.extend(addresses) elif role == "WRITE": writers.extend(addresses) ttl = record["ttl"] except (KeyError, TypeError): raise ProtocolError("Cannot parse routing info") else: return cls(routers, readers, writers, ttl)
def driver(cls, uri, **config): """ Acquire a :class:`.Driver` instance for the given URI and configuration. The URI scheme determines the Driver implementation that will be returned. Options are: ``bolt`` Returns a :class:`.DirectDriver`. ``bolt+routing`` Returns a :class:`.RoutingDriver`. :param uri: URI for a graph database service :param config: configuration and authentication details (valid keys are listed below) `auth` An authentication token for the server, for example ``("neo4j", "password")``. `der_encoded_server_certificate` The server certificate in DER format, if required. `encrypted` A boolean flag to determine whether encryption should be used. Defaults to :const:`True`. `trust` Trust level: one of :attr:`.TRUST_ALL_CERTIFICATES` (default) or :attr:`.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES`. `user_agent` A custom user agent string, if required. """ parsed = urlparse(uri) try: driver_class = cls.uri_schemes[parsed.scheme] except KeyError: raise ProtocolError("URI scheme %r not supported" % parsed.scheme) else: return driver_class(uri, **config)