Skip to content

Commit 20c61ee

Browse files
authored
Merge pull request #15 from qaspen-python/feature/new_cursor
Rewrote the whole implementation of Cursor. Made some performance changes in Transaction
2 parents 2123080 + 773b110 commit 20c61ee

File tree

9 files changed

+515
-311
lines changed

9 files changed

+515
-311
lines changed

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ async def main() -> None:
506506

507507
await transaction.begin()
508508
# Create new savepoint
509-
cursor = await transaction.cursor(
509+
cursor = transaction.cursor(
510510
querystring="SELECT * FROM users WHERE username = $1",
511511
parameters=["SomeUserName"],
512512
fetch_number=100,
@@ -525,6 +525,32 @@ async def main() -> None:
525525
await transaction.commit()
526526
```
527527

528+
### Cursor as an async context manager
529+
```python
530+
from typing import Any
531+
532+
from psqlpy import PSQLPool, IsolationLevel, QueryResult, Transaction, Cursor
533+
534+
535+
db_pool = PSQLPool()
536+
537+
538+
async def main() -> None:
539+
await db_pool.startup()
540+
541+
connection = await db_pool.connection()
542+
transaction: Transaction
543+
cursor: Cursor
544+
async with connection.transaction() as transaction:
545+
async with transaction.cursor(
546+
querystring="SELECT * FROM users WHERE username = $1",
547+
parameters=["SomeUserName"],
548+
fetch_number=100,
549+
) as cursor:
550+
async for fetched_result in cursor:
551+
print(fetched_result.result())
552+
```
553+
528554
### Cursor operations
529555

530556
Available cursor operations:

python/psqlpy/_internal/__init__.pyi

Lines changed: 45 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,25 @@ class Cursor:
160160
It can be used as an asynchronous iterator.
161161
"""
162162

163+
def __aiter__(self: Self) -> Self: ...
164+
async def __anext__(self: Self) -> QueryResult: ...
165+
async def __aenter__(self: Self) -> Self: ...
166+
async def __aexit__(
167+
self: Self,
168+
exception_type: type[BaseException] | None,
169+
exception: BaseException | None,
170+
traceback: types.TracebackType | None,
171+
) -> None: ...
172+
async def start(self: Self) -> None:
173+
"""Start the cursor.
174+
175+
Execute DECLARE command for the cursor.
176+
"""
177+
async def close(self: Self) -> None:
178+
"""Close the cursor.
179+
180+
Execute CLOSE command for the cursor.
181+
"""
163182
async def fetch(
164183
self: Self,
165184
fetch_number: int | None = None,
@@ -267,13 +286,6 @@ class Cursor:
267286
### Returns:
268287
result as `QueryResult`.
269288
"""
270-
async def close(self: Self) -> None:
271-
"""Close the cursor.
272-
273-
Execute CLOSE command for the cursor.
274-
"""
275-
def __aiter__(self: Self) -> Self: ...
276-
async def __anext__(self: Self) -> QueryResult: ...
277289

278290
class Transaction:
279291
"""Single connection for executing queries.
@@ -330,7 +342,8 @@ class Transaction:
330342
db_pool = PSQLPool()
331343
await db_pool.startup()
332344
333-
transaction = await db_pool.transaction()
345+
connection = await db_pool.connection()
346+
transaction = connection.transaction()
334347
await transaction.begin()
335348
query_result: QueryResult = await transaction.execute(
336349
"SELECT username FROM users WHERE id = $1",
@@ -339,21 +352,6 @@ class Transaction:
339352
dict_result: List[Dict[Any, Any]] = query_result.result()
340353
# You must call commit manually
341354
await transaction.commit()
342-
343-
# Or you can transaction as a async context manager
344-
345-
async def main() -> None:
346-
db_pool = PSQLPool()
347-
await psqlpy.startup()
348-
349-
transaction = await db_pool.transaction()
350-
async with transaction:
351-
query_result: QueryResult = await transaction.execute(
352-
"SELECT username FROM users WHERE id = $1",
353-
[100],
354-
)
355-
dict_result: List[Dict[Any, Any]] = query_result.result()
356-
# This way transaction begins and commits by itself.
357355
```
358356
"""
359357
async def execute_many(
@@ -381,7 +379,8 @@ class Transaction:
381379
db_pool = PSQLPool()
382380
await db_pool.startup()
383381
384-
transaction = await db_pool.transaction()
382+
connection = await db_pool.connection()
383+
transaction = connection.transaction()
385384
await transaction.begin()
386385
query_result: QueryResult = await transaction.execute_many(
387386
"INSERT INTO users (name, age) VALUES ($1, $2)",
@@ -390,21 +389,6 @@ class Transaction:
390389
dict_result: List[Dict[Any, Any]] = query_result.result()
391390
# You must call commit manually
392391
await transaction.commit()
393-
394-
# Or you can transaction as a async context manager
395-
396-
async def main() -> None:
397-
db_pool = PSQLPool()
398-
await psqlpy.startup()
399-
400-
transaction = await db_pool.transaction()
401-
async with transaction:
402-
query_result: QueryResult = await transaction.execute(
403-
"SELECT username FROM users WHERE id = $1",
404-
[100],
405-
)
406-
dict_result: List[Dict[Any, Any]] = query_result.result()
407-
# This way transaction begins and commits by itself.
408392
```
409393
"""
410394
async def fetch_row(
@@ -434,30 +418,16 @@ class Transaction:
434418
db_pool = PSQLPool()
435419
await db_pool.startup()
436420
437-
transaction = await db_pool.transaction()
421+
connection = await db_pool.connection()
422+
transaction = connection.transaction()
438423
await transaction.begin()
439-
query_result: SingleQueryResult = await transaction.execute(
424+
query_result: SingleQueryResult = await transaction.fetch_row(
440425
"SELECT username FROM users WHERE id = $1",
441426
[100],
442427
)
443428
dict_result: Dict[Any, Any] = query_result.result()
444429
# You must call commit manually
445430
await transaction.commit()
446-
447-
# Or you can transaction as a async context manager
448-
449-
async def main() -> None:
450-
db_pool = PSQLPool()
451-
await psqlpy.startup()
452-
453-
transaction = await db_pool.transaction()
454-
async with transaction:
455-
query_result: SingleQueryResult = await transaction.execute(
456-
"SELECT username FROM users WHERE id = $1 LIMIT 1",
457-
[100],
458-
)
459-
dict_result: Dict[Any, Any] = query_result.result()
460-
# This way transaction begins and commits by itself.
461431
```
462432
"""
463433
async def fetch_val(
@@ -485,27 +455,13 @@ class Transaction:
485455
db_pool = PSQLPool()
486456
await db_pool.startup()
487457
488-
transaction = await db_pool.transaction()
458+
connection = await db_pool.connection()
459+
transaction = connection.transaction()
489460
await transaction.begin()
490461
value: Any | None = await transaction.execute(
491462
"SELECT username FROM users WHERE id = $1",
492463
[100],
493464
)
494-
495-
# Or you can transaction as a async context manager
496-
497-
async def main() -> None:
498-
db_pool = PSQLPool()
499-
await psqlpy.startup()
500-
501-
transaction = await db_pool.transaction()
502-
async with transaction:
503-
query_result: SingleQueryResult = await transaction.execute(
504-
"SELECT username FROM users WHERE id = $1",
505-
[100],
506-
)
507-
dict_result: Dict[Any, Any] = query_result.result()
508-
# This way transaction begins and commits by itself.
509465
```
510466
"""
511467
async def pipeline(
@@ -547,7 +503,8 @@ class Transaction:
547503
db_pool = PSQLPool()
548504
await db_pool.startup()
549505
550-
transaction = await db_pool.transaction()
506+
connection = await db_pool.connection()
507+
transaction = connection.transaction()
551508
552509
results: list[QueryResult] = await transaction.pipeline(
553510
queries=[
@@ -591,7 +548,8 @@ class Transaction:
591548
db_pool = PSQLPool()
592549
await db_pool.startup()
593550
594-
transaction = await db_pool.transaction()
551+
connection = await db_pool.connection()
552+
transaction = connection.transaction()
595553
596554
await transaction.savepoint("my_savepoint")
597555
await transaction.execute(...)
@@ -615,7 +573,8 @@ class Transaction:
615573
db_pool = PSQLPool()
616574
await db_pool.startup()
617575
618-
transaction = await db_pool.transaction()
576+
connection = await db_pool.connection()
577+
transaction = connection.transaction()
619578
await transaction.execute(...)
620579
await transaction.rollback()
621580
```
@@ -640,7 +599,8 @@ class Transaction:
640599
db_pool = PSQLPool()
641600
await db_pool.startup()
642601
643-
transaction = await db_pool.transaction()
602+
connection = await db_pool.connection()
603+
transaction = connection.transaction()
644604
645605
await transaction.savepoint("my_savepoint")
646606
await transaction.execute(...)
@@ -667,13 +627,14 @@ class Transaction:
667627
db_pool = PSQLPool()
668628
await db_pool.startup()
669629
670-
transaction = await db_pool.transaction()
630+
connection = await db_pool.connection()
631+
transaction = connection.transaction()
671632
672633
await transaction.savepoint("my_savepoint")
673634
await transaction.release_savepoint
674635
```
675636
"""
676-
async def cursor(
637+
def cursor(
677638
self: Self,
678639
querystring: str,
679640
parameters: list[Any] | None = None,
@@ -707,15 +668,18 @@ class Transaction:
707668
connection = await db_pool.connection()
708669
transaction = await connection.transaction()
709670
710-
cursor = await transaction.cursor(
671+
cursor = transaction.cursor(
711672
querystring="SELECT * FROM users WHERE username = $1",
712673
parameters=["Some_Username"],
713674
fetch_number=5,
714675
)
676+
await cursor.start()
715677
716678
async for fetched_result in cursor:
717679
dict_result: List[Dict[Any, Any]] = fetched_result.result()
718680
... # do something with this result.
681+
682+
await cursor.close()
719683
```
720684
"""
721685

@@ -772,6 +736,7 @@ class Connection:
772736
### Parameters:
773737
- `isolation_level`: configure isolation level of the transaction.
774738
- `read_variant`: configure read variant of the transaction.
739+
- `deferrable`: configure deferrable of the transaction.
775740
"""
776741

777742
class PSQLPool:
@@ -842,7 +807,7 @@ class PSQLPool:
842807
843808
async def main() -> None:
844809
db_pool = PSQLPool()
845-
await psqlpy.startup()
810+
await db_pool.startup()
846811
query_result: QueryResult = await psqlpy.execute(
847812
"SELECT username FROM users WHERE id = $1",
848813
[100],

python/tests/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ async def test_cursor(
122122
connection = await psql_pool.connection()
123123
transaction = connection.transaction()
124124
await transaction.begin()
125-
cursor = await transaction.cursor(
125+
cursor = transaction.cursor(
126126
querystring=f"SELECT * FROM {table_name}",
127127
)
128+
await cursor.start()
128129
yield cursor
129130
await transaction.commit()

python/tests/test_cursor.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import math
2+
13
import pytest
24

3-
from psqlpy import Cursor
5+
from psqlpy import Cursor, PSQLPool, QueryResult, Transaction
46

57
pytestmark = pytest.mark.anyio
68

@@ -147,3 +149,25 @@ async def test_cursor_fetch_backward_all(
147149

148150
must_not_be_empty = await test_cursor.fetch_backward_all()
149151
assert len(must_not_be_empty.result()) == default_fetch_number - 1
152+
153+
154+
async def test_cursor_as_async_manager(
155+
psql_pool: PSQLPool,
156+
table_name: str,
157+
number_database_records: int,
158+
) -> None:
159+
"""Test cursor async manager and async iterator."""
160+
connection = await psql_pool.connection()
161+
transaction: Transaction
162+
cursor: Cursor
163+
all_results: list[QueryResult] = []
164+
expected_num_results = math.ceil(number_database_records / 3)
165+
fetch_number = 3
166+
async with connection.transaction() as transaction, transaction.cursor(
167+
querystring=f"SELECT * FROM {table_name}",
168+
fetch_number=fetch_number,
169+
) as cursor:
170+
async for result in cursor:
171+
all_results.append(result)
172+
173+
assert len(all_results) == expected_num_results

python/tests/test_transaction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ async def test_transaction_cursor(
200200
"""Test that transaction can create cursor."""
201201
connection = await psql_pool.connection()
202202
async with connection.transaction() as transaction:
203-
cursor = await transaction.cursor(f"SELECT * FROM {table_name}")
203+
cursor = transaction.cursor(f"SELECT * FROM {table_name}")
204204

205205
assert isinstance(cursor, Cursor)
206206

src/driver/connection.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,8 @@ impl Connection {
7676
isolation_level,
7777
read_variant,
7878
deferrable,
79-
Default::default(),
8079
);
8180

82-
Transaction {
83-
transaction: Arc::new(tokio::sync::RwLock::new(inner_transaction)),
84-
}
81+
Transaction::new(Arc::new(inner_transaction), Default::default())
8582
}
8683
}

0 commit comments

Comments
 (0)