@@ -33,6 +33,7 @@ The :class:`neo4j.AsyncDriver` construction is done via a ``classmethod`` on the
3333
3434 from neo4j import AsyncGraphDatabase
3535
36+
3637 async def main ():
3738 uri = " neo4j://example.com:7687"
3839 driver = AsyncGraphDatabase.driver(uri, auth = (" neo4j" , " password" ))
@@ -59,12 +60,12 @@ The :class:`neo4j.AsyncDriver` construction is done via a ``classmethod`` on the
5960
6061 from neo4j import AsyncGraphDatabase
6162
63+
6264 async def main ():
6365 uri = " neo4j://example.com:7687"
6466 auth = (" neo4j" , " password" )
6567 async with AsyncGraphDatabase.driver(uri, auth = auth) as driver:
66- # use the driver
67- ...
68+ ... # use the driver
6869
6970 asyncio.run(main())
7071
@@ -164,6 +165,7 @@ For example:
164165
165166 from neo4j import AsyncGraphDatabase
166167
168+
167169 async def custom_resolver (socket_address ):
168170 if socket_address == (" example.com" , 9999 ):
169171 yield " ::1" , 7687
@@ -172,16 +174,18 @@ For example:
172174 from socket import gaierror
173175 raise gaierror(" Unexpected socket address %r " % socket_address)
174176
177+
175178 # alternatively
176179 def custom_resolver (socket_address ):
177180 ...
178181
182+
179183 driver = AsyncGraphDatabase.driver(" neo4j://example.com:9999" ,
180184 auth = (" neo4j" , " password" ),
181185 resolver = custom_resolver)
182186
183187
184- :Default: `` None ` `
188+ :Default: :const: ` None `
185189
186190
187191
@@ -196,6 +200,7 @@ For example:
196200
197201 from neo4j import AsyncGraphDatabase
198202
203+
199204 class Application :
200205
201206 def __init__ (self , uri , user , password )
@@ -206,7 +211,7 @@ For example:
206211
207212 Connection details held by the :class: `neo4j.AsyncDriver ` are immutable.
208213Therefore if, for example, a password is changed, a replacement :class: `neo4j.AsyncDriver ` object must be created.
209- More than one :class: `.AsyncDriver ` may be required if connections to multiple databases , or connections as multiple users, are required,
214+ More than one :class: `.AsyncDriver ` may be required if connections to multiple remotes , or connections as multiple users, are required,
210215unless when using impersonation (:ref: `impersonated-user-ref `).
211216
212217:class: `neo4j.AsyncDriver ` objects are safe to be used in concurrent coroutines.
@@ -270,13 +275,18 @@ To construct a :class:`neo4j.AsyncSession` use the :meth:`neo4j.AsyncDriver.sess
270275
271276 from neo4j import AsyncGraphDatabase
272277
278+
273279 async def main ():
274- driver = AsyncGraphDatabase(uri, auth = (user, password))
275- session = driver.session()
276- result = await session.run(" MATCH (a:Person) RETURN a.name AS name" )
277- names = [record[" name" ] async for record in result]
278- await session.close()
279- await driver.close()
280+ async with AsyncGraphDatabase(uri, auth = (user, password)) as driver:
281+ session = driver.session()
282+ try :
283+ result = await session.run(" MATCH (a:Person) RETURN a.name AS name" )
284+ names = [record[" name" ] async for record in result]
285+ except asyncio.CancelledError:
286+ session.cancel()
287+ raise
288+ finally :
289+ await session.close()
280290
281291 asyncio.run(main())
282292
@@ -289,7 +299,7 @@ properly even when an exception is raised.
289299
290300 async with driver.session() as session:
291301 result = await session.run(" MATCH (a:Person) RETURN a.name AS name" )
292- # do something with the result...
302+ ... # do something with the result
293303
294304
295305 Sessions will often be created with some configuration settings, see :ref: `async-session-configuration-ref `.
@@ -299,7 +309,7 @@ Sessions will often be created with some configuration settings, see :ref:`async
299309 async with driver.session(database = " example_database" ,
300310 fetch_size = 100 ) as session:
301311 result = await session.run(" MATCH (a:Person) RETURN a.name AS name" )
302- # do something with the result...
312+ ... # do something with the result
303313
304314
305315************
@@ -315,7 +325,9 @@ AsyncSession
315325 This introduces concurrency and can lead to undefined behavior as
316326 :class: `AsyncSession ` is not concurrency-safe.
317327
318- Consider this **wrong ** example::
328+ Consider this **wrong ** example
329+
330+ .. code-block :: python
319331
320332 async def dont_do_this (driver ):
321333 async with driver.session() as session:
@@ -330,7 +342,9 @@ AsyncSession
330342
331343 In this particular example, the problem could be solved by shielding
332344 the whole coroutine ``dont_do_this `` instead of only the
333- ``session.run ``. Like so::
345+ ``session.run ``. Like so
346+
347+ .. code-block :: python
334348
335349 async def thats_better (driver ):
336350 async def inner ()
@@ -426,30 +440,32 @@ Auto-commit transactions are also the only way to run ``PERIODIC COMMIT``
426440newer) statements, since those Cypher clauses manage their own transactions
427441internally.
428442
429- Example :
443+ Write example :
430444
431445.. code-block :: python
432446
433447 import neo4j
434448
449+
435450 async def create_person (driver , name ):
436- async with driver.session(
437- default_access_mode = neo4j.WRITE_ACCESS
438- ) as session:
451+ # default_access_mode defaults to WRITE_ACCESS
452+ async with driver.session(database = " neo4j" ) as session:
439453 query = " CREATE (a:Person { name: $name }) RETURN id(a) AS node_id"
440454 result = await session.run(query, name = name)
441455 record = await result.single()
442456 return record[" node_id" ]
443457
444- Example :
458+ Read example :
445459
446460.. code-block :: python
447461
448462 import neo4j
449463
464+
450465 async def get_numbers (driver ):
451466 numbers = []
452467 async with driver.session(
468+ database = " neo4j" ,
453469 default_access_mode = neo4j.READ_ACCESS
454470 ) as session:
455471 result = await session.run(" UNWIND [1, 2, 3] AS x RETURN x" )
@@ -460,8 +476,8 @@ Example:
460476
461477 .. _async-explicit-transactions-ref :
462478
463- Explicit Async Transactions
464- ===========================
479+ Explicit Transactions (Unmanaged Transactions)
480+ ==============================================
465481Explicit transactions support multiple statements and must be created with an explicit :meth: `neo4j.AsyncSession.begin_transaction ` call.
466482
467483This creates a new :class: `neo4j.AsyncTransaction ` object that can be used to run Cypher.
@@ -485,41 +501,74 @@ It also gives applications the ability to directly control ``commit`` and ``roll
485501Closing an explicit transaction can either happen automatically at the end of a ``async with `` block,
486502or can be explicitly controlled through the :meth: `neo4j.AsyncTransaction.commit `, :meth: `neo4j.AsyncTransaction.rollback `, :meth: `neo4j.AsyncTransaction.close ` or :meth: `neo4j.AsyncTransaction.cancel ` methods.
487503
488- Explicit transactions are most useful for applications that need to distribute Cypher execution across multiple functions for the same transaction.
504+ Explicit transactions are most useful for applications that need to distribute Cypher execution across multiple functions for the same transaction or that need to run multiple queries within a single transaction but without the retries provided by managed transactions .
489505
490506Example:
491507
492508.. code-block :: python
493509
510+ import asyncio
511+
494512 import neo4j
495513
496- async def create_person (driver , name ):
514+
515+ async def transfer_to_other_bank (driver , customer_id , other_bank_id , amount ):
497516 async with driver.session(
517+ database = " neo4j" ,
518+ # optional, defaults to WRITE_ACCESS
498519 default_access_mode = neo4j.WRITE_ACCESS
499520 ) as session:
500521 tx = await session.begin_transaction()
501- node_id = await create_person_node(tx)
502- await set_person_name(tx, node_id, name)
503- await tx.commit()
504-
505- async def create_person_node (tx ):
506- query = " CREATE (a:Person { name: $name }) RETURN id(a) AS node_id"
507- name = " default_name"
508- result = await tx.run(query, name = name)
509- record = await result.single()
510- return record[" node_id" ]
511-
512- async def set_person_name (tx , node_id , name ):
513- query = " MATCH (a:Person) WHERE id(a) = $id SET a.name = $name"
514- result = await tx.run(query, id = node_id, name = name)
515- summary = await result.consume()
516- # use the summary for logging etc.
522+ # or just use a `with` context instead of try/excpet/finally
523+ try :
524+ if not await customer_balance_check(tx, customer_id, amount):
525+ # give up
526+ return
527+ await other_bank_transfer_api(customer_id, other_bank_id, amount)
528+ # Now the money has been transferred
529+ # => we can't retry or rollback anymore
530+ try :
531+ await decrease_customer_balance(tx, customer_id, amount)
532+ await tx.commit()
533+ except Exception as e:
534+ request_inspection(customer_id, other_bank_id, amount, e)
535+ raise
536+ except asyncio.CancelledError:
537+ tx.cancel()
538+ raise
539+ finally :
540+ await tx.close() # rolls back if not yet committed
541+
542+
543+ async def customer_balance_check (tx , customer_id , amount ):
544+ query = (" MATCH (c:Customer {id: $id} ) "
545+ " RETURN c.balance >= $amount AS sufficient" )
546+ result = await tx.run(query, id = customer_id, amount = amount)
547+ record = await result.single(strict = True )
548+ return record[" sufficient" ]
549+
550+
551+ async def other_bank_transfer_api (customer_id , other_bank_id , amount ):
552+ ... # make some API call to other bank
553+
554+
555+ async def decrease_customer_balance (tx , customer_id , amount ):
556+ query = (" MATCH (c:Customer {id: $id} ) "
557+ " SET c.balance = c.balance - $amount" )
558+ await tx.run(query, id = customer_id, amount = amount)
559+
560+
561+ def request_inspection (customer_id , other_bank_id , amount , e ):
562+ # manual cleanup required; log this or similar
563+ print (" WARNING: transaction rolled back due to exception:" , repr (e))
564+ print (" customer_id:" , customer_id, " other_bank_id:" , other_bank_id,
565+ " amount:" , amount)
517566
518567 .. _async-managed-transactions-ref :
519568
520569
521- Managed Async Transactions (`transaction functions `)
522- ====================================================
570+ Managed Transactions (`transaction functions `)
571+ ==============================================
523572Transaction functions are the most powerful form of transaction, providing access mode override and retry capabilities.
524573
525574+ :meth: `neo4j.AsyncSession.execute_write `
@@ -530,7 +579,7 @@ This function is called one or more times, within a configurable time limit, unt
530579Results should be fully consumed within the function and only aggregate or status values should be returned.
531580Returning a live result object would prevent the driver from correctly managing connections and would break retry guarantees.
532581
533- This function will receive a :class: `neo4j.AsyncManagedTransaction ` object as its first parameter.
582+ This function will receive a :class: `neo4j.AsyncManagedTransaction ` object as its first parameter. For more details see :meth: ` neo4j.AsyncSession.execute_write ` and :meth: ` neo4j.AsyncSession.execute_read `.
534583
535584.. autoclass :: neo4j.AsyncManagedTransaction()
536585
@@ -544,8 +593,10 @@ Example:
544593 async with driver.session() as session:
545594 node_id = await session.execute_write(create_person_tx, name)
546595
596+
547597 async def create_person_tx (tx , name ):
548- query = " CREATE (a:Person { name: $name }) RETURN id(a) AS node_id"
598+ query = (" CREATE (a:Person {name: $name, id: randomUUID()} ) "
599+ " RETURN a.id AS node_id" )
549600 result = await tx.run(query, name = name)
550601 record = await result.single()
551602 return record[" node_id" ]
0 commit comments