Skip to content

API docs: elaborate address resolver docs #874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/source/_static/nature_custom.css_t
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,7 @@ li > p:first-child {
li > p:last-child {
margin-top: 10px;
}

dl {
align-items: baseline;
}
69 changes: 49 additions & 20 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -265,33 +265,45 @@ 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.

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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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` |
+------------------------+---------------------------------------------------------------------------------------------------------------------------+
Expand Down Expand Up @@ -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:

******
Expand Down
38 changes: 25 additions & 13 deletions docs/source/async_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -163,29 +163,41 @@ 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
def custom_resolver(socket_address):
...


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`



Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/neo4j/_async/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
2 changes: 1 addition & 1 deletion src/neo4j/_async/io/_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 4 additions & 4 deletions src/neo4j/_async/work/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/neo4j/_async/work/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
8 changes: 4 additions & 4 deletions src/neo4j/_async_compat/network/_bolt_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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)
Expand All @@ -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: <ERROR> %s %s", type(error).__name__,
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions src/neo4j/_async_compat/network/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion src/neo4j/_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/neo4j/_sync/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
2 changes: 1 addition & 1 deletion src/neo4j/_sync/io/_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 4 additions & 4 deletions src/neo4j/_sync/work/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading