Neo4j Python Driver

repository·6.x·Indexed 21 days ago

https://github.com/neo4j/neo4j-python-driver

The official Neo4j Bolt driver for Python, providing a high-performance interface to interact with Neo4j graph databases using the Cypher query language. It includes features for connection pooling, TLS/SSL encryption, session and transaction management, and support for various authentication schemes and URI routing.

Tokens
24.4K
Snippets
63
Records
108
Agent score
77%

What's inside neo4j-python-driver

  1. Overview of Temporal data types in the Neo4j Python Driver

    6.x

    Temporal data types are managed by the neo4j.time module. These types are compliant with ISO-8601 and Cypher, providing nanosecond precision for sub-second values. They are designed to be used with pytz for timezone handling.

    Important Compatibility Note: The temporal types are specifically designed to work with pytz. Other datetime.tzinfo implementations, such as datetime.timezone, zoneinfo, or dateutil.tz, are not supported and may not function correctly.

  2. Select a target database in a session

    6.x

    You can specify a target database using the database argument in driver.session(database="name").

    If you do not specify a database, the driver's behavior depends on the connection URI scheme:

    • bolt schemes: Queries are dispatched without an explicit database name. The target database is determined by the server and may change during the session (e.g., if the user's home database changes).
    • neo4j schemes: The driver fetches the user's home database name on the first query and uses that name explicitly for all subsequent queries in the session, ensuring consistency.

    It is recommended to set an explicit database name if known to improve performance and ensure consistency.

    from neo4j import GraphDatabase
    
    driver = GraphDatabase.driver(uri, auth=(user, password))
    session = driver.session(database="system")
  3. Use Explicit Transactions (Unmanaged Transactions)

    6.x

    Explicit transactions allow you to run multiple statements within a single transaction and give you direct control over commit and rollback activity. They are created using neo4j.Session.begin_transaction(), which returns a neo4j.Transaction object.

    Use explicit transactions when you need to distribute Cypher execution across multiple functions for the same transaction or need to run multiple queries without the automatic retries provided by managed transactions.

    import neo4j
    
    def transfer_to_other_bank(driver, customer_id, other_bank_id, amount):
        with driver.session(
            database="neo4j",
            default_access_mode=neo4j.WRITE_ACCESS
        ) as session:
            tx = session.begin_transaction()
            try:
                if not customer_balance_check(tx, customer_id, amount):
                    return
                other_bank_transfer_api(customer_id, other_bank_id, amount)
                try:
                    decrease_customer_balance(tx, customer_id, amount)
                    tx.commit()
                except Exception as e:
                    raise
            finally:
                tx.close()  # rolls back if not yet committed
  4. How AsyncSessions and AsyncTransactions work

    6.x

    Database activity in the async driver is coordinated through two primary abstractions:

    1. AsyncSession: A logical container for one or more causally-related transactional units of work. Sessions provide top-level containment and automatically provide guarantees of causal consistency in a clustered environment.

      • Note: Session creation is lightweight, but sessions are not thread-safe and are not concurrency-safe. Avoid using asyncio utilities like asyncio.shield or asyncio.wait_for on individual session methods, as this can lead to multiple tasks handling the same session concurrently, resulting in undefined behavior.
      • Connections are drawn from the AsyncDriver connection pool as needed.
    2. Transactions (AsyncTransaction, AsyncManagedTransaction): A unit of work that is either committed in its entirety or rolled back on failure.

    To ensure safety when using asyncio.shield, shield the entire coroutine containing the session lifecycle rather than individual session calls.

    async def thats_better(driver):
        async def inner()
            async with driver.session() as session:
                await session.run("RETURN 1")
    
        await asyncio.shield(inner())
  5. Work with Graph Data Types (Node, Relationship, Path)

    6.x

    Cypher queries can return entire graph structures. The driver provides specific classes to model these entities. Unlike property values, graph entities cannot be passed in as parameters; you must pass their identity or properties explicitly.

    Node (neo4j.graph.Node)

    Represents a graph node.

    • Access properties: Use node[key] (raises KeyError if missing) or node.get(key).
    • Metadata: Access node.id, node.element_id, and node.labels.
    • Iteration: len(node) returns the number of properties; iterating over the node yields its properties.

    Relationship (neo4j.graph.Relationship)

    Represents a connection between nodes.

    • Access properties: Use relationship[key] or relationship.get(key).
    • Metadata: Access relationship.type, relationship.id, relationship.element_id, relationship.start_node, and relationship.end_node.

    Path (neo4j.graph.Path)

    Represents a sequence of nodes and relationships.

    • Metadata: Access path.nodes, path.relationships, path.start_node, and path.end_node.
    • Iteration: len(path) returns the number of relationships; iterating over the path yields its relationships.
  6. Use AsyncDriver for Neo4j connections

    6.x

    The neo4j.AsyncDriver is the central object for any Neo4j-backed asynchronous application. It manages a connection pool from which neo4j.AsyncSession objects borrow connections.

    Key Lifecycle Rules:

    • Lifetime: Create one top-level AsyncDriver instance that lives for the entire lifetime of your application.
    • Concurrency: Driver objects are safe to use in concurrent coroutines, but they are not thread-safe.
    • Immutability: Connection details (like the URI) are immutable. To change them, you must create a new driver instance.
    • Closing: Calling await driver.close() immediately shuts down all connections in the pool.
    • Connectivity: To verify the driver can communicate with the database without running a query, use await driver.verify_connectivity().
    from neo4j import AsyncGraphDatabase
    
    class Application:
        def __init__(self, uri, user, password):
            self.driver = AsyncGraphDatabase.driver(uri, auth=(user, password))
    
        async def close(self):
            await self.driver.close()
  7. Use Vector data types in the Neo4j Python Driver

    6.x
    The neo4j.vector module provides classes for handling vector data types within the driver. This includes the core Vector class for representing vector data, VectorDType for specifying the data type (e.g., float), and VectorEndian for managing byte order (endianness). These types are used when interacting with Neo4j databases that support vector capabilities.
  8. Use extended Python types as query parameters

    6.x

    The driver allows you to pass extended Python types (like tuple, bytearray, numpy.ndarray, or pandas.DataFrame) as query parameters. The driver will serialize these into standard Bolt types before sending them to the server.

    Note: The driver will never return these extended types in results; they will always be returned as their core Bolt type equivalents (e.g., a tuple passed in will return as a list).

    Parameter TypeBolt TypeResult Type
    tupleListlist
    bytearrayBytesbytes
    numpy.ndarray(nested) List(nested) list
    pandas.DataFrameMap[str, List[_]]dict
    pandas.SeriesListlist
    pandas.ArrayListlist
    import neo4j
    
    # Testing type conversion
    with neo4j.GraphDatabase.driver(URI, auth=AUTH) as driver:
        with driver.session() as session:
            type_in = ("foo", "bar")  # Passing a tuple
            result = session.run("RETURN $x", x=type_in)
            type_out = result.single()[0]
            print(type(type_out))  # <class 'list'>
            print(type_out)        # ['foo', 'bar']
  9. Map Cypher core data types to Python types

    6.x

    When executing Cypher queries, the driver automatically maps core Cypher data types to their corresponding Python built-in types. Use this mapping to understand how to process query results in your Python code.

    | Cypher Type | Python Type |
    | :--- | :--- |
    | Null | `None` |
    | Boolean | `bool` |
    | Integer | `int` |
    | Float | `float` |
    | String | `str` |
    | Bytes | `bytes` |
    | List | `list` |
    | Map | `dict` |
  10. Configure TLS/SSL encryption

    6.x

    There are three mutually exclusive ways to configure encryption:

    1. URI Suffixes (Easiest):

      • bolt+s:// or neo4j+s://: Uses TLS and trusts only system CAs.
      • bolt+ssc:// or neo4j+ssc://: Uses TLS and trusts any certificate (Self-Signed Certificate mode).
    2. Explicit Encryption Settings: Use a standard neo4j:// or bolt:// URI and set encrypted=True. You can then provide:

      • trusted_certificates: Using TrustSystemCAs, TrustAll, or TrustCustomCAs.
      • client_certificate: For mutual TLS (mTLS) authentication.
    3. Custom SSL Context: Provide a ssl_context (of type ssl.SSLContext) for full control. If this is provided, encrypted, trusted_certificates, and client_certificate are ignored.