Skip to content
Closed
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
50 changes: 50 additions & 0 deletions docs/source/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,56 @@ The module interface respects the DB-API 2.0 standard defined in :pep:`249`.
See :ref:`lazy-connections` section to learn about
advantages and limitations of using the ``lazy`` parameter.

#####################
Client-side routing
#####################

When connecting to a Memgraph high-availability cluster, passing
``routing=True`` to :func:`connect` makes the connection routing-aware: the
given ``host``/``port`` must point at a cluster coordinator, from which the
current cluster topology is fetched (via a Bolt ``ROUTE`` message) and a data
instance matching the requested ``access_mode`` is selected and connected to.

.. data:: mgclient.ACCESS_MODE_WRITE

``access_mode`` value selecting a server that accepts writes (the cluster
main). This is the default.

.. data:: mgclient.ACCESS_MODE_READ

``access_mode`` value selecting a server that serves reads (a replica).

If the addresses a cluster advertises are not directly reachable by the client
(for example when the cluster is reached through a proxy or a port-forward),
pass a ``resolver`` callable that maps an advertised ``"host:port"`` address to
an iterable of ``"host:port"`` targets to try.

``connect(routing=True, ...)`` performs a fresh routing lookup on every call.
For a long-lived router that caches the routing table (honouring its TTL),
balances reads across replicas and fails over across coordinators, use the
:class:`Router` class:

.. autoclass:: mgclient.Router
:members: connect, execute_read, execute_write, refresh, routing_table

:meth:`Router.execute_read` and :meth:`Router.execute_write` are *managed
transactions*: they run your unit of work against the right instance and
automatically retry transient failover conditions (a new main being elected, a
replica catching up, an instance dropping mid-request) with a routing refresh
and capped exponential backoff. A write that committed on the main but could
not reach a synchronous replica is treated as success rather than retried
(retrying would duplicate it).

The classification used for retries is also exposed for building your own retry
loops:

.. autofunction:: mgclient.is_transient_error

.. autofunction:: mgclient.is_committed_on_main_error

For lower-level access to the routing table itself, see
:meth:`Connection.get_routing_table`.

################
Module constants
################
Expand Down
60 changes: 60 additions & 0 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,63 @@ will be immediately committed and no rollback is possible. A few commands
(``CREATE INDEX``, ``CREATE USER`` and similar) require to be run outside any
transaction. To set the connection in *autocommit* mode, set
:attr:`.autocommit` property of the connection to ``True``.

##############################################
Connecting to a high-availability cluster
##############################################

When connecting to a Memgraph high-availability (HA) cluster you connect to a
*coordinator* and let :mod:`mgclient` route each query to the right data
instance: writes to the main, reads to a replica. Pass ``routing=True`` to
:func:`.connect` and choose an ``access_mode``::

>>> import mgclient

# Routed to the main (writes)
>>> conn = mgclient.connect(host="coordinator-1", port=7687,
... username="user", password="pass",
... routing=True,
... access_mode=mgclient.ACCESS_MODE_WRITE)
>>> cursor = conn.cursor()
>>> cursor.execute("CREATE (:Person {name: $name})", {"name": "Ada"})
>>> conn.commit()

# Routed to a replica (reads)
>>> reader = mgclient.connect(host="coordinator-1", port=7687,
... username="user", password="pass",
... routing=True,
... access_mode=mgclient.ACCESS_MODE_READ)

Each :func:`.connect` call above performs a fresh routing lookup. For a
long-lived client, use a :class:`Router` instead: it caches the routing table,
balances reads across replicas, and fails over across coordinators::

>>> router = mgclient.Router(host="coordinator-1", port=7687,
... username="user", password="pass")
>>> writer = router.connect(access_mode=mgclient.ACCESS_MODE_WRITE)
>>> reader = router.connect(access_mode=mgclient.ACCESS_MODE_READ)

To keep working through failovers (a new main being elected, a replica catching
up, an instance dropping), run your queries through the *managed* transaction
helpers, which retry transient conditions automatically::

>>> def create_person(cursor):
... cursor.execute("CREATE (p:Person {name: $n}) RETURN id(p)", {"n": "Ada"})
... return cursor.fetchall()[0][0]
>>> new_id = router.execute_write(create_person) # committed for you

>>> def count_people(cursor):
... cursor.execute("MATCH (:Person) RETURN count(*)")
... return cursor.fetchall()[0][0]
>>> total = router.execute_read(count_people)

If the addresses the cluster advertises are not directly reachable from the
client (for example in-cluster Kubernetes names reached from outside), pass a
``resolver`` that maps an advertised ``"host:port"`` to reachable targets::

>>> def resolver(advertised):
... mapping = {"memgraph-data-0.default.svc.cluster.local:7687": "10.0.0.5:7687"}
... return [mapping.get(advertised, advertised)]
>>> router = mgclient.Router(host="coordinator-1", port=7687, resolver=resolver)

See the :doc:`module` reference for the full routing API.
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ Documentation = "https://memgraph.github.io/pymgclient"

[tool.setuptools]
py-modules = ["tools.pymgclient_build_meta"]

[tool.setuptools.packages.find]
include = ["tools*"]
packages = ["tools", "mgclient"]
package-dir = { mgclient = "python/mgclient" }

[tool.setuptools.dynamic]
version = { attr = "tools.pymgclient_build_meta.__version__" }
31 changes: 31 additions & 0 deletions python/mgclient/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright (c) 2016-2026 Memgraph Ltd. [https://memgraph.com]
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Memgraph database adapter for Python, compliant with the DB-API 2.0
specification described by :pep:`249`.
"""

from mgclient._mgclient import * # noqa: F401,F403

# The routing-aware ``connect`` wrapper replaces the C ``connect`` imported
# above; with routing disabled (the default) it delegates straight to the C
# ``connect`` function.
from mgclient.routing import ( # noqa: F401,E402
ACCESS_MODE_READ,
ACCESS_MODE_WRITE,
Router,
connect,
is_committed_on_main_error,
is_transient_error,
)
Loading
Loading