@@ -33,6 +33,7 @@ The :class:`neo4j.AsyncDriver` construction is done via a ``classmethod`` on the
33
33
34
34
from neo4j import AsyncGraphDatabase
35
35
36
+
36
37
async def main ():
37
38
uri = " neo4j://example.com:7687"
38
39
driver = AsyncGraphDatabase.driver(uri, auth = (" neo4j" , " password" ))
@@ -59,12 +60,12 @@ The :class:`neo4j.AsyncDriver` construction is done via a ``classmethod`` on the
59
60
60
61
from neo4j import AsyncGraphDatabase
61
62
63
+
62
64
async def main ():
63
65
uri = " neo4j://example.com:7687"
64
66
auth = (" neo4j" , " password" )
65
67
async with AsyncGraphDatabase.driver(uri, auth = auth) as driver:
66
- # use the driver
67
- ...
68
+ ... # use the driver
68
69
69
70
asyncio.run(main())
70
71
@@ -164,6 +165,7 @@ For example:
164
165
165
166
from neo4j import AsyncGraphDatabase
166
167
168
+
167
169
async def custom_resolver (socket_address ):
168
170
if socket_address == (" example.com" , 9999 ):
169
171
yield " ::1" , 7687
@@ -172,16 +174,18 @@ For example:
172
174
from socket import gaierror
173
175
raise gaierror(" Unexpected socket address %r " % socket_address)
174
176
177
+
175
178
# alternatively
176
179
def custom_resolver (socket_address ):
177
180
...
178
181
182
+
179
183
driver = AsyncGraphDatabase.driver(" neo4j://example.com:9999" ,
180
184
auth = (" neo4j" , " password" ),
181
185
resolver = custom_resolver)
182
186
183
187
184
- :Default: `` None ` `
188
+ :Default: :const: ` None `
185
189
186
190
187
191
@@ -196,6 +200,7 @@ For example:
196
200
197
201
from neo4j import AsyncGraphDatabase
198
202
203
+
199
204
class Application :
200
205
201
206
def __init__ (self , uri , user , password )
@@ -206,7 +211,7 @@ For example:
206
211
207
212
Connection details held by the :class: `neo4j.AsyncDriver ` are immutable.
208
213
Therefore 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,
210
215
unless when using impersonation (:ref: `impersonated-user-ref `).
211
216
212
217
: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
270
275
271
276
from neo4j import AsyncGraphDatabase
272
277
278
+
273
279
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()
280
290
281
291
asyncio.run(main())
282
292
@@ -289,7 +299,7 @@ properly even when an exception is raised.
289
299
290
300
async with driver.session() as session:
291
301
result = await session.run(" MATCH (a:Person) RETURN a.name AS name" )
292
- # do something with the result...
302
+ ... # do something with the result
293
303
294
304
295
305
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
299
309
async with driver.session(database = " example_database" ,
300
310
fetch_size = 100 ) as session:
301
311
result = await session.run(" MATCH (a:Person) RETURN a.name AS name" )
302
- # do something with the result...
312
+ ... # do something with the result
303
313
304
314
305
315
************
@@ -315,7 +325,9 @@ AsyncSession
315
325
This introduces concurrency and can lead to undefined behavior as
316
326
:class: `AsyncSession ` is not concurrency-safe.
317
327
318
- Consider this **wrong ** example::
328
+ Consider this **wrong ** example
329
+
330
+ .. code-block :: python
319
331
320
332
async def dont_do_this (driver ):
321
333
async with driver.session() as session:
@@ -330,7 +342,9 @@ AsyncSession
330
342
331
343
In this particular example, the problem could be solved by shielding
332
344
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
334
348
335
349
async def thats_better (driver ):
336
350
async def inner ()
@@ -426,30 +440,32 @@ Auto-commit transactions are also the only way to run ``PERIODIC COMMIT``
426
440
newer) statements, since those Cypher clauses manage their own transactions
427
441
internally.
428
442
429
- Example :
443
+ Write example :
430
444
431
445
.. code-block :: python
432
446
433
447
import neo4j
434
448
449
+
435
450
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:
439
453
query = " CREATE (a:Person { name: $name }) RETURN id(a) AS node_id"
440
454
result = await session.run(query, name = name)
441
455
record = await result.single()
442
456
return record[" node_id" ]
443
457
444
- Example :
458
+ Read example :
445
459
446
460
.. code-block :: python
447
461
448
462
import neo4j
449
463
464
+
450
465
async def get_numbers (driver ):
451
466
numbers = []
452
467
async with driver.session(
468
+ database = " neo4j" ,
453
469
default_access_mode = neo4j.READ_ACCESS
454
470
) as session:
455
471
result = await session.run(" UNWIND [1, 2, 3] AS x RETURN x" )
@@ -460,8 +476,8 @@ Example:
460
476
461
477
.. _async-explicit-transactions-ref :
462
478
463
- Explicit Async Transactions
464
- ===========================
479
+ Explicit Transactions (Unmanaged Transactions)
480
+ ==============================================
465
481
Explicit transactions support multiple statements and must be created with an explicit :meth: `neo4j.AsyncSession.begin_transaction ` call.
466
482
467
483
This 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
485
501
Closing an explicit transaction can either happen automatically at the end of a ``async with `` block,
486
502
or can be explicitly controlled through the :meth: `neo4j.AsyncTransaction.commit `, :meth: `neo4j.AsyncTransaction.rollback `, :meth: `neo4j.AsyncTransaction.close ` or :meth: `neo4j.AsyncTransaction.cancel ` methods.
487
503
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 .
489
505
490
506
Example:
491
507
492
508
.. code-block :: python
493
509
510
+ import asyncio
511
+
494
512
import neo4j
495
513
496
- async def create_person (driver , name ):
514
+
515
+ async def transfer_to_other_bank (driver , customer_id , other_bank_id , amount ):
497
516
async with driver.session(
517
+ database = " neo4j" ,
518
+ # optional, defaults to WRITE_ACCESS
498
519
default_access_mode = neo4j.WRITE_ACCESS
499
520
) as session:
500
521
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)
517
566
518
567
.. _async-managed-transactions-ref :
519
568
520
569
521
- Managed Async Transactions (`transaction functions `)
522
- ====================================================
570
+ Managed Transactions (`transaction functions `)
571
+ ==============================================
523
572
Transaction functions are the most powerful form of transaction, providing access mode override and retry capabilities.
524
573
525
574
+ :meth: `neo4j.AsyncSession.execute_write `
@@ -530,7 +579,7 @@ This function is called one or more times, within a configurable time limit, unt
530
579
Results should be fully consumed within the function and only aggregate or status values should be returned.
531
580
Returning a live result object would prevent the driver from correctly managing connections and would break retry guarantees.
532
581
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 `.
534
583
535
584
.. autoclass :: neo4j.AsyncManagedTransaction()
536
585
@@ -544,8 +593,10 @@ Example:
544
593
async with driver.session() as session:
545
594
node_id = await session.execute_write(create_person_tx, name)
546
595
596
+
547
597
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" )
549
600
result = await tx.run(query, name = name)
550
601
record = await result.single()
551
602
return record[" node_id" ]
0 commit comments