Skip to content

Commit 6cd99ab

Browse files
authored
Fix internal transaction method forwarding (#1017)
Forwarding methods shouldn't be decorated with `functools.wraps` as it not only copies the docs string and type annotations (desirable), but also `__name__`, `__qualname__`, and more (undesirable). While `wraps` can be configured with what properties to copy, it turns out that there is no need to keep the doc strings in the transaction base class for the forwarded methods. Instead, they are moved to the public (forwarding) methods. This still keeps the code DRY.
1 parent bd1f115 commit 6cd99ab

File tree

2 files changed

+102
-100
lines changed

2 files changed

+102
-100
lines changed

src/neo4j/_async/work/transaction.py

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import asyncio
2020
import typing as t
21-
from functools import wraps
2221

2322
from ..._async_compat.util import AsyncUtil
2423
from ..._work import Query
@@ -57,7 +56,7 @@ def __init__(self, connection, fetch_size, on_closed, on_error,
5756
self._on_cancel = on_cancel
5857
super().__init__()
5958

60-
async def _enter(self):
59+
async def _enter(self) -> te.Self:
6160
return self
6261

6362
@AsyncNonConcurrentMethodChecker.non_concurrent_method
@@ -113,7 +112,7 @@ async def run(
113112
parameters: t.Optional[t.Dict[str, t.Any]] = None,
114113
**kwparameters: t.Any
115114
) -> AsyncResult:
116-
""" Run a Cypher query within the context of this transaction.
115+
"""Run a Cypher query within the context of this transaction.
117116
118117
Cypher is typically expressed as a query template plus a
119118
set of named parameters. In Python, parameters may be expressed
@@ -172,10 +171,6 @@ async def run(
172171

173172
@AsyncNonConcurrentMethodChecker.non_concurrent_method
174173
async def _commit(self):
175-
"""Mark this transaction as successful and close in order to trigger a COMMIT.
176-
177-
:raise TransactionError: if the transaction is already closed
178-
"""
179174
if self._closed_flag:
180175
raise TransactionError(self, "Transaction closed")
181176
if self._last_error:
@@ -202,10 +197,6 @@ async def _commit(self):
202197

203198
@AsyncNonConcurrentMethodChecker.non_concurrent_method
204199
async def _rollback(self):
205-
"""Mark this transaction as unsuccessful and close in order to trigger a ROLLBACK.
206-
207-
:raise TransactionError: if the transaction is already closed
208-
"""
209200
if self._closed_flag:
210201
raise TransactionError(self, "Transaction closed")
211202

@@ -228,88 +219,98 @@ async def _rollback(self):
228219

229220
@AsyncNonConcurrentMethodChecker.non_concurrent_method
230221
async def _close(self):
231-
"""Close this transaction, triggering a ROLLBACK if not closed.
232-
"""
233222
if self._closed_flag:
234223
return
235224
await self._rollback()
236225

237226
if AsyncUtil.is_async_code:
238227
def _cancel(self) -> None:
239-
"""Cancel this transaction.
240-
241-
If the transaction is already closed, this method does nothing.
242-
Else, it will close the connection without ROLLBACK or COMMIT in
243-
a non-blocking manner.
244-
245-
The primary purpose of this function is to handle
246-
:class:`asyncio.CancelledError`.
247-
248-
::
249-
250-
tx = await session.begin_transaction()
251-
try:
252-
... # do some work
253-
except asyncio.CancelledError:
254-
tx.cancel()
255-
raise
256-
257-
"""
258228
if self._closed_flag:
259229
return
260230
try:
261231
self._on_cancel()
262232
finally:
263233
self._closed_flag = True
264234

265-
def _closed(self):
266-
"""Indicate whether the transaction has been closed or cancelled.
267-
268-
:returns:
269-
:data:`True` if closed or cancelled, :data:`False` otherwise.
270-
:rtype: bool
271-
"""
235+
def _closed(self) -> bool:
272236
return self._closed_flag
273237

274238

275239
class AsyncTransaction(AsyncTransactionBase):
276-
""" Container for multiple Cypher queries to be executed within a single
240+
"""Fully user-managed transaction.
241+
242+
Container for multiple Cypher queries to be executed within a single
277243
context. :class:`AsyncTransaction` objects can be used as a context
278-
managers (:py:const:`async with` block) where the transaction is committed
279-
or rolled back on based on whether an exception is raised::
244+
manager (:py:const:`async with` block) where the transaction is committed
245+
or rolled back based on whether an exception is raised::
280246
281247
async with await session.begin_transaction() as tx:
282248
...
283249
284250
"""
285251

286-
@wraps(AsyncTransactionBase._enter)
287252
async def __aenter__(self) -> AsyncTransaction:
288253
return await self._enter()
289254

290-
@wraps(AsyncTransactionBase._exit)
291-
async def __aexit__(self, exception_type, exception_value, traceback):
255+
async def __aexit__(
256+
self, exception_type, exception_value, traceback
257+
) -> None:
292258
await self._exit(exception_type, exception_value, traceback)
293259

294-
@wraps(AsyncTransactionBase._commit)
295260
async def commit(self) -> None:
261+
"""Commit the transaction and close it.
262+
263+
Marks this transaction as successful and closes in order to trigger a
264+
COMMIT.
265+
266+
:raise TransactionError: if the transaction is already closed
267+
"""
296268
return await self._commit()
297269

298-
@wraps(AsyncTransactionBase._rollback)
299270
async def rollback(self) -> None:
271+
"""Rollback the transaction and close it.
272+
273+
Marks the transaction as unsuccessful and closes in order to trigger
274+
a ROLLBACK.
275+
276+
:raise TransactionError: if the transaction is already closed
277+
"""
300278
return await self._rollback()
301279

302-
@wraps(AsyncTransactionBase._close)
303280
async def close(self) -> None:
281+
"""Close this transaction, triggering a ROLLBACK if not closed."""
304282
return await self._close()
305283

306-
@wraps(AsyncTransactionBase._closed)
307284
def closed(self) -> bool:
285+
"""Indicate whether the transaction has been closed or cancelled.
286+
287+
:returns:
288+
:data:`True` if closed or cancelled, :data:`False` otherwise.
289+
:rtype: bool
290+
"""
308291
return self._closed()
309292

310293
if AsyncUtil.is_async_code:
311-
@wraps(AsyncTransactionBase._cancel)
312294
def cancel(self) -> None:
295+
"""Cancel this transaction.
296+
297+
If the transaction is already closed, this method does nothing.
298+
Else, it will close the connection without ROLLBACK or COMMIT in
299+
a non-blocking manner.
300+
301+
The primary purpose of this function is to handle
302+
:class:`asyncio.CancelledError`.
303+
304+
::
305+
306+
tx = await session.begin_transaction()
307+
try:
308+
... # do some work
309+
except asyncio.CancelledError:
310+
tx.cancel()
311+
raise
312+
313+
"""
313314
return self._cancel()
314315

315316

src/neo4j/_sync/work/transaction.py

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import asyncio
2020
import typing as t
21-
from functools import wraps
2221

2322
from ..._async_compat.util import Util
2423
from ..._work import Query
@@ -57,7 +56,7 @@ def __init__(self, connection, fetch_size, on_closed, on_error,
5756
self._on_cancel = on_cancel
5857
super().__init__()
5958

60-
def _enter(self):
59+
def _enter(self) -> te.Self:
6160
return self
6261

6362
@NonConcurrentMethodChecker.non_concurrent_method
@@ -113,7 +112,7 @@ def run(
113112
parameters: t.Optional[t.Dict[str, t.Any]] = None,
114113
**kwparameters: t.Any
115114
) -> Result:
116-
""" Run a Cypher query within the context of this transaction.
115+
"""Run a Cypher query within the context of this transaction.
117116
118117
Cypher is typically expressed as a query template plus a
119118
set of named parameters. In Python, parameters may be expressed
@@ -172,10 +171,6 @@ def run(
172171

173172
@NonConcurrentMethodChecker.non_concurrent_method
174173
def _commit(self):
175-
"""Mark this transaction as successful and close in order to trigger a COMMIT.
176-
177-
:raise TransactionError: if the transaction is already closed
178-
"""
179174
if self._closed_flag:
180175
raise TransactionError(self, "Transaction closed")
181176
if self._last_error:
@@ -202,10 +197,6 @@ def _commit(self):
202197

203198
@NonConcurrentMethodChecker.non_concurrent_method
204199
def _rollback(self):
205-
"""Mark this transaction as unsuccessful and close in order to trigger a ROLLBACK.
206-
207-
:raise TransactionError: if the transaction is already closed
208-
"""
209200
if self._closed_flag:
210201
raise TransactionError(self, "Transaction closed")
211202

@@ -228,88 +219,98 @@ def _rollback(self):
228219

229220
@NonConcurrentMethodChecker.non_concurrent_method
230221
def _close(self):
231-
"""Close this transaction, triggering a ROLLBACK if not closed.
232-
"""
233222
if self._closed_flag:
234223
return
235224
self._rollback()
236225

237226
if Util.is_async_code:
238227
def _cancel(self) -> None:
239-
"""Cancel this transaction.
240-
241-
If the transaction is already closed, this method does nothing.
242-
Else, it will close the connection without ROLLBACK or COMMIT in
243-
a non-blocking manner.
244-
245-
The primary purpose of this function is to handle
246-
:class:`asyncio.CancelledError`.
247-
248-
::
249-
250-
tx = session.begin_transaction()
251-
try:
252-
... # do some work
253-
except asyncio.CancelledError:
254-
tx.cancel()
255-
raise
256-
257-
"""
258228
if self._closed_flag:
259229
return
260230
try:
261231
self._on_cancel()
262232
finally:
263233
self._closed_flag = True
264234

265-
def _closed(self):
266-
"""Indicate whether the transaction has been closed or cancelled.
267-
268-
:returns:
269-
:data:`True` if closed or cancelled, :data:`False` otherwise.
270-
:rtype: bool
271-
"""
235+
def _closed(self) -> bool:
272236
return self._closed_flag
273237

274238

275239
class Transaction(TransactionBase):
276-
""" Container for multiple Cypher queries to be executed within a single
240+
"""Fully user-managed transaction.
241+
242+
Container for multiple Cypher queries to be executed within a single
277243
context. :class:`Transaction` objects can be used as a context
278-
managers (:py:const:`with` block) where the transaction is committed
279-
or rolled back on based on whether an exception is raised::
244+
manager (:py:const:`with` block) where the transaction is committed
245+
or rolled back based on whether an exception is raised::
280246
281247
with session.begin_transaction() as tx:
282248
...
283249
284250
"""
285251

286-
@wraps(TransactionBase._enter)
287252
def __enter__(self) -> Transaction:
288253
return self._enter()
289254

290-
@wraps(TransactionBase._exit)
291-
def __exit__(self, exception_type, exception_value, traceback):
255+
def __exit__(
256+
self, exception_type, exception_value, traceback
257+
) -> None:
292258
self._exit(exception_type, exception_value, traceback)
293259

294-
@wraps(TransactionBase._commit)
295260
def commit(self) -> None:
261+
"""Commit the transaction and close it.
262+
263+
Marks this transaction as successful and closes in order to trigger a
264+
COMMIT.
265+
266+
:raise TransactionError: if the transaction is already closed
267+
"""
296268
return self._commit()
297269

298-
@wraps(TransactionBase._rollback)
299270
def rollback(self) -> None:
271+
"""Rollback the transaction and close it.
272+
273+
Marks the transaction as unsuccessful and closes in order to trigger
274+
a ROLLBACK.
275+
276+
:raise TransactionError: if the transaction is already closed
277+
"""
300278
return self._rollback()
301279

302-
@wraps(TransactionBase._close)
303280
def close(self) -> None:
281+
"""Close this transaction, triggering a ROLLBACK if not closed."""
304282
return self._close()
305283

306-
@wraps(TransactionBase._closed)
307284
def closed(self) -> bool:
285+
"""Indicate whether the transaction has been closed or cancelled.
286+
287+
:returns:
288+
:data:`True` if closed or cancelled, :data:`False` otherwise.
289+
:rtype: bool
290+
"""
308291
return self._closed()
309292

310293
if Util.is_async_code:
311-
@wraps(TransactionBase._cancel)
312294
def cancel(self) -> None:
295+
"""Cancel this transaction.
296+
297+
If the transaction is already closed, this method does nothing.
298+
Else, it will close the connection without ROLLBACK or COMMIT in
299+
a non-blocking manner.
300+
301+
The primary purpose of this function is to handle
302+
:class:`asyncio.CancelledError`.
303+
304+
::
305+
306+
tx = session.begin_transaction()
307+
try:
308+
... # do some work
309+
except asyncio.CancelledError:
310+
tx.cancel()
311+
raise
312+
313+
"""
313314
return self._cancel()
314315

315316

0 commit comments

Comments
 (0)