Neo4j Python Driver: Queries and Record Streams

The official Neo4j Python driver serves as the primary bridge between Python applications and the Neo4j graph database, managing network communication, authentication, and connection pooling. This article explores how the driver manages the execution of Cypher queries through robust transaction boundaries and how it efficiently handles the resulting data using stream-based record consumption patterns.

Connecting and Managing Transactions

The driver operates on the Bolt protocol, establishing a pool of socket connections to the database. Query execution begins by acquiring a session from the driver instance. Within this session, the driver supports three modes of query execution:

  1. Auto-commit transactions: Direct execution using session.run(). This approach does not provide automatic retry logic and commits immediately, making it best suited for administrative tasks or non-critical operations.
  2. Transaction functions (execute_read / execute_write): The idiomatic and recommended approach. These functions automatically manage transaction boundaries and include built-in retry mechanisms for transient routing and network failures.
  3. Explicit transactions: Manually managed via session.begin_transaction(), requiring the developer to explicitly call commit() or rollback().

Parameters are passed directly alongside Cypher queries rather than concatenated into strings. The driver serializes these parameters into the Bolt protocol, which prevents Cypher injection attacks and allows Neo4j to cache query execution plans.

Executing Cypher Queries

When a query is dispatched, the driver sends the Cypher statement and its parameters to the Neo4j server across an active connection. The server compiles and executes the query, then immediately begins returning metadata and results back over the socket.

Because the driver offloads workload processing to the server, query dispatch is non-blocking until results are demanded, laying the groundwork for how data is streamed back to the client.

Consuming Record Streams

Query execution produces a Result object, which acts as an iterator over a stream of Record objects rather than loading the entire dataset into application memory at once.

Lifecycle and Clean-up Considerations

A record stream is tied to the lifecycle of the transaction and session that created it. If a session or transaction is closed before the Result iterator is exhausted, unconsumed records are discarded. To preserve data outside the transaction scope, the application must explicitly consume the stream—either by iterating through it completely or by converting it into local data structures before the context manager exits.