diff --git a/docs/source/_static/nature_custom.css_t b/docs/source/_static/nature_custom.css_t index eedcdc740..f1f54a2ef 100644 --- a/docs/source/_static/nature_custom.css_t +++ b/docs/source/_static/nature_custom.css_t @@ -311,3 +311,7 @@ li > p:first-child { li > p:last-child { margin-top: 10px; } + +dl { + align-items: baseline; +} diff --git a/docs/source/api.rst b/docs/source/api.rst index 509bbfbac..e522425fe 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -265,8 +265,8 @@ The maximum total number of connections allowed, per host (i.e. cluster nodes), ``resolver`` ------------ -A custom resolver function to resolve host and port values ahead of DNS resolution. -This function is called with a 2-tuple of (host, port) and should return an iterable of 2-tuples (host, port). +A custom resolver function to resolve any addresses the driver receives ahead of DNS resolution. +This function is called with an :class:`.Address` and should return an iterable of :class:`.Address` objects or values that can be used to construct :class:`.Address` objects. If no custom resolver function is supplied, the internal resolver moves straight to regular DNS resolution. @@ -274,24 +274,36 @@ For example: .. code-block:: python - from neo4j import GraphDatabase + import neo4j - def custom_resolver(socket_address): - if socket_address == ("example.com", 9999): - yield "::1", 7687 - yield "127.0.0.1", 7687 - else: - from socket import gaierror - raise gaierror("Unexpected socket address %r" % socket_address) + def custom_resolver(socket_address): + # assert isinstance(socket_address, neo4j.Address) + if socket_address != ("example.com", 9999): + raise OSError(f"Unexpected socket address {socket_address!r}") + # You can return any neo4j.Address object + yield neo4j.Address(("localhost", 7687)) # IPv4 + yield neo4j.Address(("::1", 7687, 0, 0)) # IPv6 + yield neo4j.Address.parse("localhost:7687") + yield neo4j.Address.parse("[::1]:7687") - driver = GraphDatabase.driver("neo4j://example.com:9999", - auth=("neo4j", "password"), - resolver=custom_resolver) + # or any tuple that can be passed to neo4j.Address(...). + # Initially, this will be interpreted as IPv4, but DNS resolution + # will turn it into IPv6 if appropriate. + yield "::1", 7687 + # This will be interpreted as IPv6 directly, but DNS resolution will + # still happen. + yield "::1", 7687, 0, 0 + yield "127.0.0.1", 7687 -:Default: :const:`None` + driver = neo4j.GraphDatabase.driver("neo4j://example.com:9999", + auth=("neo4j", "password"), + resolver=custom_resolver) + + +:Default: :data:`None` .. _trust-ref: @@ -337,8 +349,8 @@ If given, ``encrypted`` and ``trusted_certificates`` have no effect. Its usage is strongly discouraged and comes without any guarantees. -:Type: :class:`ssl.SSLContext` or :const:`None` -:Default: :const:`None` +:Type: :class:`ssl.SSLContext` or :data:`None` +:Default: :data:`None` .. versionadded:: 5.0 @@ -656,7 +668,7 @@ context of the impersonated user. For this, the user for which the :Type: ``str``, None -:Default: :const:`None` +:Default: :data:`None` .. _default-access-mode-ref: @@ -721,8 +733,8 @@ See :class:`.BookmarkManager` for more information. For simple use-cases, it often suffices that work within a single session is automatically causally consistent. -:Type: :const:`None` or :class:`.BookmarkManager` -:Default: :const:`None` +:Type: :data:`None` or :class:`.BookmarkManager` +:Default: :data:`None` .. versionadded:: 5.0 @@ -1069,7 +1081,7 @@ The core types with their general mappings are listed below: +------------------------+---------------------------------------------------------------------------------------------------------------------------+ | Cypher Type | Python Type | +========================+===========================================================================================================================+ -| Null | :const:`None` | +| Null | :data:`None` | +------------------------+---------------------------------------------------------------------------------------------------------------------------+ | Boolean | :class:`bool` | +------------------------+---------------------------------------------------------------------------------------------------------------------------+ @@ -1295,6 +1307,23 @@ BookmarkManager :members: +************************* +Constants, Enums, Helpers +************************* + +.. autoclass:: neo4j.Address + :show-inheritance: + :members: + + +.. autoclass:: neo4j.IPv4Address() + :show-inheritance: + + +.. autoclass:: neo4j.IPv6Address() + :show-inheritance: + + .. _errors-ref: ****** diff --git a/docs/source/async_api.rst b/docs/source/async_api.rst index 20c5746f3..dc18d23b8 100644 --- a/docs/source/async_api.rst +++ b/docs/source/async_api.rst @@ -152,8 +152,8 @@ driver accepts an async custom resolver function: ``resolver`` ------------ -A custom resolver function to resolve host and port values ahead of DNS resolution. -This function is called with a 2-tuple of (host, port) and should return an iterable of 2-tuples (host, port). +A custom resolver function to resolve any addresses the driver receives ahead of DNS resolution. +This function is called with an :class:`.Address` and should return an iterable of :class:`.Address` objects or values that can be used to construct :class:`.Address` objects. If no custom resolver function is supplied, the internal resolver moves straight to regular DNS resolution. @@ -163,16 +163,28 @@ For example: .. code-block:: python - from neo4j import AsyncGraphDatabase + import neo4j async def custom_resolver(socket_address): - if socket_address == ("example.com", 9999): - yield "::1", 7687 - yield "127.0.0.1", 7687 - else: - from socket import gaierror - raise gaierror("Unexpected socket address %r" % socket_address) + # assert isinstance(socket_address, neo4j.Address) + if socket_address != ("example.com", 9999): + raise OSError(f"Unexpected socket address {socket_address!r}") + + # You can return any neo4j.Address object + yield neo4j.Address(("localhost", 7687)) # IPv4 + yield neo4j.Address(("::1", 7687, 0, 0)) # IPv6 + yield neo4j.Address.parse("localhost:7687") + yield neo4j.Address.parse("[::1]:7687") + + # or any tuple that can be passed to neo4j.Address(...). + # Initially, this will be interpreted as IPv4, but DNS resolution + # will turn it into IPv6 if appropriate. + yield "::1", 7687 + # This will be interpreted as IPv6 directly, but DNS resolution will + # still happen. + yield "::1", 7687, 0, 0 + yield "127.0.0.1", 7687 # alternatively @@ -180,12 +192,12 @@ For example: ... - driver = AsyncGraphDatabase.driver("neo4j://example.com:9999", + driver = neo4j.GraphDatabase.driver("neo4j://example.com:9999", auth=("neo4j", "password"), resolver=custom_resolver) -:Default: :const:`None` +:Default: :data:`None` @@ -406,8 +418,8 @@ See :class:`BookmarkManager` for more information. group a series of queries together that will be causally chained automatically. -:Type: :const:`None`, :class:`BookmarkManager`, or :class:`AsyncBookmarkManager` -:Default: :const:`None` +:Type: :data:`None`, :class:`BookmarkManager`, or :class:`AsyncBookmarkManager` +:Default: :data:`None` **This is experimental.** (See :ref:`filter-warnings-ref`) It might be changed or removed any time even without prior notice. diff --git a/src/neo4j/_async/driver.py b/src/neo4j/_async/driver.py index 953ec7d20..b60440ef4 100644 --- a/src/neo4j/_async/driver.py +++ b/src/neo4j/_async/driver.py @@ -274,7 +274,7 @@ def bookmark_manager( Function which will be called whenever the set of bookmarks handled by the bookmark manager gets updated with the new internal bookmark set. It will receive the new set of bookmarks - as a :class:`.Bookmarks` object and return :const:`None`. + as a :class:`.Bookmarks` object and return :data:`None`. :returns: A default implementation of :class:`AsyncBookmarkManager`. diff --git a/src/neo4j/_async/io/_pool.py b/src/neo4j/_async/io/_pool.py index d1c0f7c9d..8376d5610 100644 --- a/src/neo4j/_async/io/_pool.py +++ b/src/neo4j/_async/io/_pool.py @@ -679,7 +679,7 @@ async def update_connection_pool(self, *, database): routing_table = await self.get_or_create_routing_table(database) servers = routing_table.servers() for address in list(self.connections): - if address.unresolved not in servers: + if address._unresolved not in servers: await super(AsyncNeo4jPool, self).deactivate(address) async def ensure_routing_table_is_fresh( diff --git a/src/neo4j/_async/work/result.py b/src/neo4j/_async/work/result.py index 1cbb9270b..9cfde69a9 100644 --- a/src/neo4j/_async/work/result.py +++ b/src/neo4j/_async/work/result.py @@ -418,7 +418,7 @@ async def single(self, strict: bool = False) -> t.Optional[Record]: there is not exactly one record left. If ``strict`` is :const:`False`, fewer than one record will make this - method return :const:`None`, more than one record will make this method + method return :data:`None`, more than one record will make this method emit a warning and return the first record. :param strict: @@ -427,7 +427,7 @@ async def single(self, strict: bool = False) -> t.Optional[Record]: warning if there are more than 1 record. :const:`False` by default. - :returns: the next :class:`neo4j.Record` or :const:`None` if none remain + :returns: the next :class:`neo4j.Record` or :data:`None` if none remain :warns: if more than one record is available and ``strict`` is :const:`False` @@ -496,7 +496,7 @@ async def peek(self) -> t.Optional[Record]: This leaves the record in the buffer for further processing. - :returns: the next :class:`neo4j.Record` or :const:`None` if none + :returns: the next :class:`neo4j.Record` or :data:`None` if none remain. :raises ResultConsumedError: if the transaction from which this result @@ -683,7 +683,7 @@ async def to_df( :param parse_dates: If :const:`True`, columns that exclusively contain :class:`time.DateTime` objects, :class:`time.Date` objects, or - :const:`None`, will be converted to :class:`pandas.Timestamp`. + :data:`None`, will be converted to :class:`pandas.Timestamp`. :raises ImportError: if `pandas` library is not available. :raises ResultConsumedError: if the transaction from which this result diff --git a/src/neo4j/_async/work/transaction.py b/src/neo4j/_async/work/transaction.py index 40b546323..7baf8387f 100644 --- a/src/neo4j/_async/work/transaction.py +++ b/src/neo4j/_async/work/transaction.py @@ -111,10 +111,10 @@ async def run( arguments, or as a mixture of both. For example, the `run` queries below are all equivalent:: - >>> query = "CREATE (a:Person { name: $name, age: $age })" - >>> result = await tx.run(query, {"name": "Alice", "age": 33}) - >>> result = await tx.run(query, {"name": "Alice"}, age=33) - >>> result = await tx.run(query, name="Alice", age=33) + query = "CREATE (a:Person { name: $name, age: $age })" + result = await tx.run(query, {"name": "Alice", "age": 33}) + result = await tx.run(query, {"name": "Alice"}, age=33) + result = await tx.run(query, name="Alice", age=33) Parameter values can be of any type supported by the Neo4j type system. In Python, this includes :class:`bool`, :class:`int`, diff --git a/src/neo4j/_async_compat/network/_bolt_socket.py b/src/neo4j/_async_compat/network/_bolt_socket.py index cdbe28524..da0aae661 100644 --- a/src/neo4j/_async_compat/network/_bolt_socket.py +++ b/src/neo4j/_async_compat/network/_bolt_socket.py @@ -207,7 +207,7 @@ async def _connect_secure(cls, resolved_address, timeout, keep_alive, ssl): ssl_kwargs = {} if ssl is not None: - hostname = resolved_address.host_name or None + hostname = resolved_address._host_name or None ssl_kwargs.update( ssl=ssl, server_hostname=hostname if HAS_SNI else None ) @@ -232,7 +232,7 @@ async def _connect_secure(cls, resolved_address, timeout, keep_alive, ssl): raise BoltProtocolError( "When using an encrypted socket, the server should " "always provide a certificate", - address=(resolved_address.host_name, local_port) + address=(resolved_address._host_name, local_port) ) return cls(reader, protocol, writer) @@ -257,7 +257,7 @@ async def _connect_secure(cls, resolved_address, timeout, keep_alive, ssl): await cls.close_socket(s) raise BoltSecurityError( message="Failed to establish encrypted connection.", - address=(resolved_address.host_name, local_port) + address=(resolved_address._host_name, local_port) ) from error except Exception as error: log.debug("[#0000] S: %s %s", type(error).__name__, @@ -650,7 +650,7 @@ def connect(cls, address, *, timeout, custom_resolver, ssl_context, s = None try: s = BoltSocket._connect(resolved_address, timeout, keep_alive) - s = BoltSocket._secure(s, resolved_address.host_name, + s = BoltSocket._secure(s, resolved_address._host_name, ssl_context) return BoltSocket._handshake(s, resolved_address) except (BoltError, DriverError, OSError) as error: diff --git a/src/neo4j/_async_compat/network/_util.py b/src/neo4j/_async_compat/network/_util.py index 708d3eb9b..c292cd221 100644 --- a/src/neo4j/_async_compat/network/_util.py +++ b/src/neo4j/_async_compat/network/_util.py @@ -47,7 +47,7 @@ async def _dns_resolver(address, family=0): ) except OSError: raise ValueError("Cannot resolve address {}".format(address)) - return list(_resolved_addresses_from_info(info, address.host_name)) + return list(_resolved_addresses_from_info(info, address._host_name)) @staticmethod async def resolve_address(address, family=0, resolver=None): @@ -117,7 +117,7 @@ def _dns_resolver(address, family=0): ) except OSError: raise ValueError("Cannot resolve address {}".format(address)) - return _resolved_addresses_from_info(info, address.host_name) + return _resolved_addresses_from_info(info, address._host_name) @staticmethod def resolve_address(address, family=0, resolver=None): diff --git a/src/neo4j/_data.py b/src/neo4j/_data.py index 9a680f77d..0046fd122 100644 --- a/src/neo4j/_data.py +++ b/src/neo4j/_data.py @@ -246,7 +246,7 @@ def data(self, *keys: _K) -> t.Dict[str, t.Any]: """ Return the keys and values of this record as a dictionary, optionally including only certain values by index or key. Keys provided in the items that are not in the record will be - inserted with a value of :const:`None`; indexes provided + inserted with a value of :data:`None`; indexes provided that are out of bounds will trigger an :exc:`IndexError`. :param keys: indexes or keys of the items to include; if none diff --git a/src/neo4j/_sync/driver.py b/src/neo4j/_sync/driver.py index 22c4d11ed..5aace35b7 100644 --- a/src/neo4j/_sync/driver.py +++ b/src/neo4j/_sync/driver.py @@ -271,7 +271,7 @@ def bookmark_manager( Function which will be called whenever the set of bookmarks handled by the bookmark manager gets updated with the new internal bookmark set. It will receive the new set of bookmarks - as a :class:`.Bookmarks` object and return :const:`None`. + as a :class:`.Bookmarks` object and return :data:`None`. :returns: A default implementation of :class:`BookmarkManager`. diff --git a/src/neo4j/_sync/io/_pool.py b/src/neo4j/_sync/io/_pool.py index 0d0faad17..9fe584284 100644 --- a/src/neo4j/_sync/io/_pool.py +++ b/src/neo4j/_sync/io/_pool.py @@ -679,7 +679,7 @@ def update_connection_pool(self, *, database): routing_table = self.get_or_create_routing_table(database) servers = routing_table.servers() for address in list(self.connections): - if address.unresolved not in servers: + if address._unresolved not in servers: super(Neo4jPool, self).deactivate(address) def ensure_routing_table_is_fresh( diff --git a/src/neo4j/_sync/work/result.py b/src/neo4j/_sync/work/result.py index 1077116df..c881a19f2 100644 --- a/src/neo4j/_sync/work/result.py +++ b/src/neo4j/_sync/work/result.py @@ -418,7 +418,7 @@ def single(self, strict: bool = False) -> t.Optional[Record]: there is not exactly one record left. If ``strict`` is :const:`False`, fewer than one record will make this - method return :const:`None`, more than one record will make this method + method return :data:`None`, more than one record will make this method emit a warning and return the first record. :param strict: @@ -427,7 +427,7 @@ def single(self, strict: bool = False) -> t.Optional[Record]: warning if there are more than 1 record. :const:`False` by default. - :returns: the next :class:`neo4j.Record` or :const:`None` if none remain + :returns: the next :class:`neo4j.Record` or :data:`None` if none remain :warns: if more than one record is available and ``strict`` is :const:`False` @@ -496,7 +496,7 @@ def peek(self) -> t.Optional[Record]: This leaves the record in the buffer for further processing. - :returns: the next :class:`neo4j.Record` or :const:`None` if none + :returns: the next :class:`neo4j.Record` or :data:`None` if none remain. :raises ResultConsumedError: if the transaction from which this result @@ -683,7 +683,7 @@ def to_df( :param parse_dates: If :const:`True`, columns that exclusively contain :class:`time.DateTime` objects, :class:`time.Date` objects, or - :const:`None`, will be converted to :class:`pandas.Timestamp`. + :data:`None`, will be converted to :class:`pandas.Timestamp`. :raises ImportError: if `pandas` library is not available. :raises ResultConsumedError: if the transaction from which this result diff --git a/src/neo4j/_sync/work/transaction.py b/src/neo4j/_sync/work/transaction.py index fdccbf3d9..7ed47e6d9 100644 --- a/src/neo4j/_sync/work/transaction.py +++ b/src/neo4j/_sync/work/transaction.py @@ -111,10 +111,10 @@ def run( arguments, or as a mixture of both. For example, the `run` queries below are all equivalent:: - >>> query = "CREATE (a:Person { name: $name, age: $age })" - >>> result = tx.run(query, {"name": "Alice", "age": 33}) - >>> result = tx.run(query, {"name": "Alice"}, age=33) - >>> result = tx.run(query, name="Alice", age=33) + query = "CREATE (a:Person { name: $name, age: $age })" + result = tx.run(query, {"name": "Alice", "age": 33}) + result = tx.run(query, {"name": "Alice"}, age=33) + result = tx.run(query, name="Alice", age=33) Parameter values can be of any type supported by the Neo4j type system. In Python, this includes :class:`bool`, :class:`int`, diff --git a/src/neo4j/addressing.py b/src/neo4j/addressing.py index 6e26fc6d7..364365ff3 100644 --- a/src/neo4j/addressing.py +++ b/src/neo4j/addressing.py @@ -79,22 +79,83 @@ def ipv6_cls(self): class Address(tuple, metaclass=_AddressMeta): + """Base class to represent server addresses within the driver. + + A tuple of two (IPv4) or four (IPv6) elements, representing the address + parts. See also python's :mod:`socket` module for more information. + + >>> Address(("example.com", 7687)) + IPv4Address(('example.com', 7687)) + >>> Address(("127.0.0.1", 7687)) + IPv4Address(('127.0.0.1', 7687)) + >>> Address(("::1", 7687, 0, 0)) + IPv6Address(('::1', 7687, 0, 0)) + + :param iterable: A collection of two or four elements creating an + :class:`.IPv4Address` or :class:`.IPv6Address` instance respectively. + """ + + #: Address family (:data:`socket.AF_INET` or :data:`socket.AF_INET6`). + family: t.Optional[AddressFamily] = None + + def __new__(cls, iterable: t.Collection) -> Address: + if isinstance(iterable, cls): + return iterable + n_parts = len(iterable) + inst = tuple.__new__(cls, iterable) + if n_parts == 2: + inst.__class__ = cls.ipv4_cls + elif n_parts == 4: + inst.__class__ = cls.ipv6_cls + else: + raise ValueError("Addresses must consist of either " + "two parts (IPv4) or four parts (IPv6)") + return inst @classmethod def from_socket( - cls: t.Type[_TAddress], + cls, socket: _WithPeerName - ) -> _TAddress: + ) -> Address: + """Create an address from a socket object. + + Uses the socket's ``getpeername`` method to retrieve the remote + address the socket is connected to. + """ address = socket.getpeername() return cls(address) @classmethod def parse( - cls: t.Type[_TAddress], + cls, s: str, default_host: t.Optional[str] = None, default_port: t.Optional[int] = None - ) -> _TAddress: + ) -> Address: + """Parse a string into an address. + + The string must be in the format ``host:port`` (IPv4) or + ``[host]:port`` (IPv6). + If no port is specified, or is empty, ``default_port`` will be used. + If no host is specified, or is empty, ``default_host`` will be used. + + >>> Address.parse("localhost:7687") + IPv4Address(('localhost', 7687)) + >>> Address.parse("[::1]:7687") + IPv6Address(('::1', 7687, 0, 0)) + >>> Address.parse("localhost") + IPv4Address(('localhost', 0)) + >>> Address.parse("localhost", default_port=1234) + IPv4Address(('localhost', 1234)) + + :param s: The string to parse. + :param default_host: The default host to use if none is specified. + :data:`None` indicates to use ``"localhost"`` as default. + :param default_port: The default port to use if none is specified. + :data:`None` indicates to use ``0`` as default. + + :return: The parsed address. + """ if not isinstance(s, str): raise TypeError("Address.parse requires a string argument") if s.startswith("["): @@ -120,72 +181,134 @@ def parse( @classmethod def parse_list( - cls: t.Type[_TAddress], + cls, *s: str, default_host: t.Optional[str] = None, default_port: t.Optional[int] = None - ) -> t.List[_TAddress]: - """ Parse a string containing one or more socket addresses, each - separated by whitespace. + ) -> t.List[Address]: + """Parse multiple addresses into a list. + + See :meth:`.parse` for details on the string format. + + Either a whitespace-separated list of strings or multiple strings + can be used. + + >>> Address.parse_list("localhost:7687", "[::1]:7687") + [IPv4Address(('localhost', 7687)), IPv6Address(('::1', 7687, 0, 0))] + >>> Address.parse_list("localhost:7687 [::1]:7687") + [IPv4Address(('localhost', 7687)), IPv6Address(('::1', 7687, 0, 0))] + + :param s: The string(s) to parse. + :param default_host: The default host to use if none is specified. + :data:`None` indicates to use ``"localhost"`` as default. + :param default_port: The default port to use if none is specified. + :data:`None` indicates to use ``0`` as default. + + :return: The list of parsed addresses. """ if not all(isinstance(s0, str) for s0 in s): raise TypeError("Address.parse_list requires a string argument") return [cls.parse(a, default_host, default_port) for a in " ".join(s).split()] - def __new__(cls, iterable: t.Collection) -> Address: - if isinstance(iterable, cls): - return iterable - n_parts = len(iterable) - inst = tuple.__new__(cls, iterable) - if n_parts == 2: - inst.__class__ = cls.ipv4_cls - elif n_parts == 4: - inst.__class__ = cls.ipv6_cls - else: - raise ValueError("Addresses must consist of either " - "two parts (IPv4) or four parts (IPv6)") - return inst - - #: Address family (AF_INET or AF_INET6) - family: t.Optional[AddressFamily] = None - def __repr__(self): return "{}({!r})".format(self.__class__.__name__, tuple(self)) @property - def host_name(self) -> str: + def _host_name(self) -> t.Any: return self[0] @property - def host(self) -> str: + def host(self) -> t.Any: + """The host part of the address. + + This is the first part of the address tuple. + + >>> Address(("localhost", 7687)).host + 'localhost' + """ return self[0] @property - def port(self) -> int: + def port(self) -> t.Any: + """The port part of the address. + + This is the second part of the address tuple. + + >>> Address(("localhost", 7687)).port + 7687 + >>> Address(("localhost", 7687, 0, 0)).port + 7687 + >>> Address(("localhost", "7687")).port + '7687' + >>> Address(("localhost", "http")).port + 'http' + """ return self[1] @property - def unresolved(self) -> Address: + def _unresolved(self) -> Address: return self @property def port_number(self) -> int: + """The port part of the address as an integer. + + First try to resolve the port as an integer, using + :meth:`socket.getservbyname`. If that fails, fall back to parsing the + port as an integer. + + >>> Address(("localhost", 7687)).port_number + 7687 + >>> Address(("localhost", "http")).port_number + 80 + >>> Address(("localhost", "7687")).port_number + 7687 + >>> Address(("localhost", [])).port_number + Traceback (most recent call last): + ... + TypeError: Unknown port value [] + >>> Address(("localhost", "banana-protocol")).port_number + Traceback (most recent call last): + ... + ValueError: Unknown port value 'banana-protocol' + + :returns: The resolved port number. + + :raise ValueError: If the port cannot be resolved. + :raise TypeError: If the port cannot be resolved. + """ + error_cls: t.Type = TypeError + try: return getservbyname(self[1]) - except (OSError, TypeError): + except OSError: # OSError: service/proto not found + error_cls = ValueError + except TypeError: # TypeError: getservbyname() argument 1 must be str, not X - try: - return int(self[1]) - except (TypeError, ValueError) as e: - raise type(e)("Unknown port value %r" % self[1]) + pass + try: + return int(self[1]) + except ValueError: + error_cls = ValueError + except TypeError: + pass + raise error_cls("Unknown port value %r" % self[1]) + +class IPv4Address(Address): + """An IPv4 address (family ``AF_INET``). -_TAddress = t.TypeVar("_TAddress", bound=Address) + This class is also used for addresses that specify a host name instead of + an IP address. E.g., + >>> Address(("example.com", 7687)) + IPv4Address(('example.com', 7687)) -class IPv4Address(Address): + This class should not be instantiated directly. Instead, use + :class:`.Address` or one of its factory methods. + """ family = AF_INET @@ -194,6 +317,11 @@ def __str__(self) -> str: class IPv6Address(Address): + """An IPv6 address (family ``AF_INETl``). + + This class should not be instantiated directly. Instead, use + :class:`.Address` or one of its factory methods. + """ family = AF_INET6 @@ -203,20 +331,20 @@ def __str__(self) -> str: class ResolvedAddress(Address): - _host_name: str + _unresolved_host_name: str @property - def host_name(self) -> str: - return self._host_name + def _host_name(self) -> str: + return self._unresolved_host_name @property - def unresolved(self) -> Address: + def _unresolved(self) -> Address: return super().__new__(Address, (self._host_name, *self[1:])) def __new__(cls, iterable, *, host_name: str) -> ResolvedAddress: new = super().__new__(cls, iterable) new = t.cast(ResolvedAddress, new) - new._host_name = host_name + new._unresolved_host_name = host_name return new diff --git a/src/neo4j/debug.py b/src/neo4j/debug.py index 5af57a395..579f45e74 100644 --- a/src/neo4j/debug.py +++ b/src/neo4j/debug.py @@ -155,9 +155,9 @@ def watch( """Enable logging for all loggers. :param level: Minimum log level to show. - If :const:`None`, the ``default_level`` is used. + If :data:`None`, the ``default_level`` is used. :param out: Output stream for all loggers. - If :const:`None`, the ``default_out`` is used. + If :data:`None`, the ``default_out`` is used. :type out: stream or file-like object """ if level is None: diff --git a/src/neo4j/time/__init__.py b/src/neo4j/time/__init__.py index 7ad53a40c..0ddc84289 100644 --- a/src/neo4j/time/__init__.py +++ b/src/neo4j/time/__init__.py @@ -1988,7 +1988,7 @@ class DateTime(date_time_base_class, metaclass=DateTimeType): Regular construction of a :class:`.DateTime` object requires at least the `year`, `month` and `day` arguments to be supplied. The optional `hour`, `minute` and `second` arguments default to zero and - `tzinfo` defaults to :const:`None`. + `tzinfo` defaults to :data:`None`. `year`, `month`, and `day` are passed to the constructor of :class:`.Date`. `hour`, `minute`, `second`, `nanosecond`, and `tzinfo` are passed to the @@ -1997,7 +1997,7 @@ class DateTime(date_time_base_class, metaclass=DateTimeType): >>> dt = DateTime(2018, 4, 30, 12, 34, 56, 789123456); dt neo4j.time.DateTime(2018, 4, 30, 12, 34, 56, 789123456) >>> dt.second - 56.789123456 + 56 """ __date: Date diff --git a/tests/unit/async_/test_addressing.py b/tests/unit/async_/test_addressing.py index 75a036f9c..ca5eb0f01 100644 --- a/tests/unit/async_/test_addressing.py +++ b/tests/unit/async_/test_addressing.py @@ -107,7 +107,7 @@ def custom_resolver(_): custom_resolved = [("127.0.0.1", 7687), ("localhost", 4321)] address = Address(("foobar", 1234)) - unresolved = address.unresolved + unresolved = address._unresolved assert address.__class__ == unresolved.__class__ assert address == unresolved resolved = AsyncNetworkUtil.resolve_address( @@ -115,7 +115,7 @@ def custom_resolver(_): ) resolved_list = await AsyncUtil.list(resolved) custom_resolved_addresses = sorted(Address(a) for a in custom_resolved) - unresolved_list = sorted(a.unresolved for a in resolved_list) + unresolved_list = sorted(a._unresolved for a in resolved_list) assert custom_resolved_addresses == unresolved_list assert (list(map(lambda a: a.__class__, custom_resolved_addresses)) == list(map(lambda a: a.__class__, unresolved_list))) diff --git a/tests/unit/sync/test_addressing.py b/tests/unit/sync/test_addressing.py index 444b289d4..190ac4169 100644 --- a/tests/unit/sync/test_addressing.py +++ b/tests/unit/sync/test_addressing.py @@ -107,7 +107,7 @@ def custom_resolver(_): custom_resolved = [("127.0.0.1", 7687), ("localhost", 4321)] address = Address(("foobar", 1234)) - unresolved = address.unresolved + unresolved = address._unresolved assert address.__class__ == unresolved.__class__ assert address == unresolved resolved = NetworkUtil.resolve_address( @@ -115,7 +115,7 @@ def custom_resolver(_): ) resolved_list = Util.list(resolved) custom_resolved_addresses = sorted(Address(a) for a in custom_resolved) - unresolved_list = sorted(a.unresolved for a in resolved_list) + unresolved_list = sorted(a._unresolved for a in resolved_list) assert custom_resolved_addresses == unresolved_list assert (list(map(lambda a: a.__class__, custom_resolved_addresses)) == list(map(lambda a: a.__class__, unresolved_list))) diff --git a/tox.ini b/tox.ini index ee21d3ce4..82fb5767d 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ usedevelop = true commands = coverage erase unit: coverage run -m pytest -W error -v {posargs} tests/unit + unit: coverage run -m pytest -v --doctest-modules {posargs} src integration: coverage run -m pytest -W error -v {posargs} tests/integration performance: python -m pytest --benchmark-autosave -v {posargs} tests/performance unit,integration: coverage report