database-wrapper-pgsql 0.1.85__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- database_wrapper_pgsql/connector.py +109 -109
- database_wrapper_pgsql/db_wrapper_pgsql.py +10 -10
- database_wrapper_pgsql/db_wrapper_pgsql_async.py +10 -10
- database_wrapper_pgsql/db_wrapper_pgsql_mixin.py +51 -50
- {database_wrapper_pgsql-0.1.85.dist-info → database_wrapper_pgsql-0.2.2.dist-info}/METADATA +2 -2
- database_wrapper_pgsql-0.2.2.dist-info/RECORD +10 -0
- {database_wrapper_pgsql-0.1.85.dist-info → database_wrapper_pgsql-0.2.2.dist-info}/WHEEL +1 -1
- database_wrapper_pgsql-0.1.85.dist-info/RECORD +0 -10
- {database_wrapper_pgsql-0.1.85.dist-info → database_wrapper_pgsql-0.2.2.dist-info}/top_level.txt +0 -0
|
@@ -96,7 +96,7 @@ class PgSQL(DatabaseBackend):
|
|
|
96
96
|
user=self.config["username"],
|
|
97
97
|
password=self.config["password"],
|
|
98
98
|
dbname=self.config["database"],
|
|
99
|
-
connect_timeout=self.
|
|
99
|
+
connect_timeout=self.connection_timeout,
|
|
100
100
|
row_factory=PgDictRowFactory, # type: ignore
|
|
101
101
|
**self.config["kwargs"],
|
|
102
102
|
),
|
|
@@ -104,7 +104,7 @@ class PgSQL(DatabaseBackend):
|
|
|
104
104
|
self.cursor = self.connection.cursor(row_factory=PgDictRowFactory)
|
|
105
105
|
|
|
106
106
|
# Lets do some socket magic
|
|
107
|
-
self.
|
|
107
|
+
self.fix_socket_timeouts(self.connection.fileno())
|
|
108
108
|
|
|
109
109
|
def ping(self) -> bool:
|
|
110
110
|
try:
|
|
@@ -123,11 +123,11 @@ class PgSQL(DatabaseBackend):
|
|
|
123
123
|
@contextmanager
|
|
124
124
|
def transaction(
|
|
125
125
|
self,
|
|
126
|
-
|
|
126
|
+
db_conn: PgConnectionType | None = None,
|
|
127
127
|
) -> Iterator[Transaction]:
|
|
128
128
|
"""Transaction context manager"""
|
|
129
|
-
if
|
|
130
|
-
with
|
|
129
|
+
if db_conn:
|
|
130
|
+
with db_conn.transaction() as trans:
|
|
131
131
|
yield trans
|
|
132
132
|
|
|
133
133
|
assert self.connection, "Connection is not initialized"
|
|
@@ -138,7 +138,7 @@ class PgSQL(DatabaseBackend):
|
|
|
138
138
|
### Data ###
|
|
139
139
|
############
|
|
140
140
|
|
|
141
|
-
def
|
|
141
|
+
def affected_rows(self) -> int:
|
|
142
142
|
assert self.cursor, "Cursor is not initialized"
|
|
143
143
|
|
|
144
144
|
return self.cursor.rowcount
|
|
@@ -215,14 +215,14 @@ class PgSQLAsync(DatabaseBackend):
|
|
|
215
215
|
user=self.config["username"],
|
|
216
216
|
password=self.config["password"],
|
|
217
217
|
dbname=self.config["database"],
|
|
218
|
-
connect_timeout=self.
|
|
218
|
+
connect_timeout=self.connection_timeout,
|
|
219
219
|
row_factory=PgDictRowFactory, # type: ignore
|
|
220
220
|
**self.config["kwargs"],
|
|
221
221
|
)
|
|
222
222
|
self.cursor = self.connection.cursor(row_factory=PgDictRowFactory)
|
|
223
223
|
|
|
224
224
|
# Lets do some socket magic
|
|
225
|
-
self.
|
|
225
|
+
self.fix_socket_timeouts(self.connection.fileno())
|
|
226
226
|
|
|
227
227
|
async def close(self) -> Any:
|
|
228
228
|
"""Close connections"""
|
|
@@ -251,11 +251,11 @@ class PgSQLAsync(DatabaseBackend):
|
|
|
251
251
|
@asynccontextmanager
|
|
252
252
|
async def transaction(
|
|
253
253
|
self,
|
|
254
|
-
|
|
254
|
+
db_conn: PgConnectionTypeAsync | None = None,
|
|
255
255
|
) -> AsyncIterator[AsyncTransaction]:
|
|
256
256
|
"""Transaction context manager"""
|
|
257
|
-
if
|
|
258
|
-
async with
|
|
257
|
+
if db_conn:
|
|
258
|
+
async with db_conn.transaction() as trans:
|
|
259
259
|
yield trans
|
|
260
260
|
|
|
261
261
|
assert self.connection, "Connection is not initialized"
|
|
@@ -266,7 +266,7 @@ class PgSQLAsync(DatabaseBackend):
|
|
|
266
266
|
### Data ###
|
|
267
267
|
############
|
|
268
268
|
|
|
269
|
-
def
|
|
269
|
+
def affected_rows(self) -> int:
|
|
270
270
|
assert self.cursor, "Cursor is not initialized"
|
|
271
271
|
|
|
272
272
|
return self.cursor.rowcount
|
|
@@ -291,24 +291,24 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
291
291
|
PostgreSQL database implementation with connection pooling.
|
|
292
292
|
|
|
293
293
|
Instance is created without actually connecting to the database.
|
|
294
|
-
When you are ready to connect, call
|
|
294
|
+
When you are ready to connect, call open_pool() method.
|
|
295
295
|
|
|
296
|
-
Then you can use
|
|
297
|
-
|
|
296
|
+
Then you can use new_connection() to get connection from the pool and
|
|
297
|
+
return_connection() to return it back.
|
|
298
298
|
Or use context manager to get connection and return it back automatically,
|
|
299
299
|
for example:
|
|
300
300
|
|
|
301
301
|
pool = PgSQLWithPooling(config)
|
|
302
|
-
pool.
|
|
302
|
+
pool.open_pool()
|
|
303
303
|
with pool as (connection, cursor):
|
|
304
304
|
cursor.execute("SELECT 1")
|
|
305
305
|
|
|
306
306
|
:param config: Configuration for PostgreSQL
|
|
307
307
|
:type config: PgConfig
|
|
308
|
-
:param
|
|
309
|
-
:type
|
|
310
|
-
:param
|
|
311
|
-
:type
|
|
308
|
+
:param connection_timeout: Connection timeout
|
|
309
|
+
:type connection_timeout: int
|
|
310
|
+
:param instance_name: Name of the instance
|
|
311
|
+
:type instance_name: str
|
|
312
312
|
|
|
313
313
|
Defaults:
|
|
314
314
|
port = 5432
|
|
@@ -328,7 +328,7 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
328
328
|
cursor: PgCursorType | None
|
|
329
329
|
""" Cursor to database """
|
|
330
330
|
|
|
331
|
-
|
|
331
|
+
context_connection: ContextVar[tuple[PgConnectionType, PgCursorType] | None]
|
|
332
332
|
""" Connection used in context manager """
|
|
333
333
|
|
|
334
334
|
########################
|
|
@@ -337,19 +337,19 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
337
337
|
|
|
338
338
|
def __init__(
|
|
339
339
|
self,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
db_config: PgConfig,
|
|
341
|
+
connection_timeout: int = 5,
|
|
342
|
+
instance_name: str = "postgresql_pool",
|
|
343
343
|
) -> None:
|
|
344
344
|
"""
|
|
345
345
|
Main concept here is that in init we do not connect to database,
|
|
346
346
|
so that class instances can be safely made regardless of connection statuss.
|
|
347
347
|
|
|
348
|
-
Remember to call
|
|
349
|
-
and also
|
|
348
|
+
Remember to call open_pool() after creating instance to actually open the pool to the database
|
|
349
|
+
and also close_pool() to close the pool.
|
|
350
350
|
"""
|
|
351
351
|
|
|
352
|
-
super().__init__(
|
|
352
|
+
super().__init__(db_config, connection_timeout, instance_name)
|
|
353
353
|
|
|
354
354
|
# Set defaults
|
|
355
355
|
if not "port" in self.config or not self.config["port"]:
|
|
@@ -371,19 +371,19 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
371
371
|
if not "pool_kwargs" in self.config or not self.config["pool_kwargs"]:
|
|
372
372
|
self.config["pool_kwargs"] = {}
|
|
373
373
|
|
|
374
|
-
|
|
374
|
+
conn_str = (
|
|
375
375
|
f"postgresql://{self.config['username']}:{self.config['password']}@{self.config['hostname']}:{self.config['port']}"
|
|
376
|
-
f"/{self.config['database']}?connect_timeout={self.
|
|
376
|
+
f"/{self.config['database']}?connect_timeout={self.connection_timeout}&application_name={self.name}"
|
|
377
377
|
f"&sslmode={self.config['ssl']}"
|
|
378
378
|
)
|
|
379
379
|
self.pool = ConnectionPool(
|
|
380
|
-
|
|
380
|
+
conn_str,
|
|
381
381
|
open=False,
|
|
382
382
|
min_size=2,
|
|
383
383
|
max_size=self.config["maxconnections"],
|
|
384
384
|
max_lifetime=20 * 60,
|
|
385
385
|
max_idle=400,
|
|
386
|
-
timeout=self.
|
|
386
|
+
timeout=self.connection_timeout,
|
|
387
387
|
reconnect_timeout=0,
|
|
388
388
|
num_workers=4,
|
|
389
389
|
connection_class=PgConnectionType,
|
|
@@ -395,15 +395,15 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
395
395
|
### Connection ###
|
|
396
396
|
##################
|
|
397
397
|
|
|
398
|
-
def
|
|
399
|
-
self.pool.open(wait=True, timeout=self.
|
|
398
|
+
def open_pool(self) -> None:
|
|
399
|
+
self.pool.open(wait=True, timeout=self.connection_timeout)
|
|
400
400
|
|
|
401
|
-
def
|
|
401
|
+
def close_pool(self) -> None:
|
|
402
402
|
"""Close Pool"""
|
|
403
403
|
|
|
404
|
-
if self.
|
|
404
|
+
if self.shutdown_requested.is_set():
|
|
405
405
|
return
|
|
406
|
-
self.
|
|
406
|
+
self.shutdown_requested.set()
|
|
407
407
|
|
|
408
408
|
# Close pool
|
|
409
409
|
self.logger.debug("Closing connection pool")
|
|
@@ -417,11 +417,11 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
417
417
|
self.close()
|
|
418
418
|
|
|
419
419
|
# Create new connection
|
|
420
|
-
res = self.
|
|
420
|
+
res = self.new_connection()
|
|
421
421
|
if res:
|
|
422
422
|
(self.connection, self.cursor) = res
|
|
423
423
|
|
|
424
|
-
def
|
|
424
|
+
def new_connection(
|
|
425
425
|
self,
|
|
426
426
|
) -> tuple[PgConnectionType, PgCursorType] | None:
|
|
427
427
|
assert self.pool, "Pool is not initialized"
|
|
@@ -431,14 +431,14 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
431
431
|
|
|
432
432
|
# Get connection from the pool
|
|
433
433
|
tries = 0
|
|
434
|
-
while not self.
|
|
434
|
+
while not self.shutdown_requested.is_set():
|
|
435
435
|
connection = None
|
|
436
436
|
try:
|
|
437
|
-
connection = self.pool.getconn(timeout=self.
|
|
437
|
+
connection = self.pool.getconn(timeout=self.connection_timeout)
|
|
438
438
|
cursor = connection.cursor(row_factory=PgDictRowFactory)
|
|
439
439
|
|
|
440
440
|
# Lets do some socket magic
|
|
441
|
-
self.
|
|
441
|
+
self.fix_socket_timeouts(connection.fileno())
|
|
442
442
|
|
|
443
443
|
with connection.transaction():
|
|
444
444
|
cursor.execute("SELECT 1")
|
|
@@ -452,7 +452,7 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
452
452
|
self.pool.putconn(connection)
|
|
453
453
|
|
|
454
454
|
self.logger.error(f"Error while getting connection from the pool: {e}")
|
|
455
|
-
self.
|
|
455
|
+
self.shutdown_requested.wait(self.slow_down_timeout)
|
|
456
456
|
tries += 1
|
|
457
457
|
if tries >= 3:
|
|
458
458
|
break
|
|
@@ -460,7 +460,7 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
460
460
|
|
|
461
461
|
return None
|
|
462
462
|
|
|
463
|
-
def
|
|
463
|
+
def return_connection(self, connection: PgConnectionType) -> None:
|
|
464
464
|
"""Return connection to the pool"""
|
|
465
465
|
assert self.pool, "Pool is not initialized"
|
|
466
466
|
|
|
@@ -483,11 +483,11 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
483
483
|
"""Context manager"""
|
|
484
484
|
|
|
485
485
|
# Lets set the context var so that it is set even if we fail to get connection
|
|
486
|
-
self.
|
|
486
|
+
self.context_connection.set(None)
|
|
487
487
|
|
|
488
|
-
res = self.
|
|
488
|
+
res = self.new_connection()
|
|
489
489
|
if res:
|
|
490
|
-
self.
|
|
490
|
+
self.context_connection.set(res)
|
|
491
491
|
return res
|
|
492
492
|
|
|
493
493
|
return (
|
|
@@ -498,12 +498,12 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
498
498
|
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
|
499
499
|
"""Context manager"""
|
|
500
500
|
|
|
501
|
-
|
|
502
|
-
if
|
|
503
|
-
self.
|
|
501
|
+
test_data = self.context_connection.get()
|
|
502
|
+
if test_data:
|
|
503
|
+
self.return_connection(test_data[0])
|
|
504
504
|
|
|
505
505
|
# Reset context
|
|
506
|
-
self.
|
|
506
|
+
self.context_connection.set(None)
|
|
507
507
|
|
|
508
508
|
####################
|
|
509
509
|
### Transactions ###
|
|
@@ -512,11 +512,11 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
512
512
|
@contextmanager
|
|
513
513
|
def transaction(
|
|
514
514
|
self,
|
|
515
|
-
|
|
515
|
+
db_conn: PgConnectionType | None = None,
|
|
516
516
|
) -> Iterator[Transaction]:
|
|
517
517
|
"""Transaction context manager"""
|
|
518
|
-
if
|
|
519
|
-
with
|
|
518
|
+
if db_conn:
|
|
519
|
+
with db_conn.transaction() as trans:
|
|
520
520
|
yield trans
|
|
521
521
|
|
|
522
522
|
assert self.connection, "Connection is not initialized"
|
|
@@ -527,7 +527,7 @@ class PgSQLWithPooling(DatabaseBackend):
|
|
|
527
527
|
### Data ###
|
|
528
528
|
############
|
|
529
529
|
|
|
530
|
-
def
|
|
530
|
+
def affected_rows(self) -> int:
|
|
531
531
|
assert self.cursor, "Cursor is not initialized"
|
|
532
532
|
|
|
533
533
|
return self.cursor.rowcount
|
|
@@ -552,29 +552,29 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
552
552
|
PostgreSQL database implementation with async connection pooling.
|
|
553
553
|
|
|
554
554
|
Instance is created without actually connecting to the database.
|
|
555
|
-
When you are ready to connect, call await
|
|
555
|
+
When you are ready to connect, call await open_pool() method.
|
|
556
556
|
|
|
557
|
-
Then you can use
|
|
558
|
-
|
|
557
|
+
Then you can use new_connection() to get connection from the pool and
|
|
558
|
+
return_connection() to return it back.
|
|
559
559
|
|
|
560
560
|
Or use context manager to get connection and return it back automatically,
|
|
561
561
|
for example:
|
|
562
562
|
|
|
563
563
|
pool = PgSQLWithPoolingAsync(config)
|
|
564
|
-
await pool.
|
|
564
|
+
await pool.open_pool()
|
|
565
565
|
async with pool as (connection, cursor):
|
|
566
566
|
await cursor.execute("SELECT 1")
|
|
567
567
|
|
|
568
568
|
|
|
569
569
|
! Note: Close is not called automatically when class is destroyed.
|
|
570
|
-
! You need to call `await
|
|
570
|
+
! You need to call `await close_pool()` manually in async environment.
|
|
571
571
|
|
|
572
572
|
:param config: Configuration for PostgreSQL
|
|
573
573
|
:type config: PgConfig
|
|
574
|
-
:param
|
|
575
|
-
:type
|
|
576
|
-
:param
|
|
577
|
-
:type
|
|
574
|
+
:param connection_timeout: Connection timeout
|
|
575
|
+
:type connection_timeout: int
|
|
576
|
+
:param instance_name: Name of the instance
|
|
577
|
+
:type instance_name: str
|
|
578
578
|
|
|
579
579
|
Defaults:
|
|
580
580
|
port = 5432
|
|
@@ -585,7 +585,7 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
585
585
|
config: PgConfig
|
|
586
586
|
""" Configuration """
|
|
587
587
|
|
|
588
|
-
|
|
588
|
+
pool_async: AsyncConnectionPool[PgConnectionTypeAsync]
|
|
589
589
|
""" Connection pool """
|
|
590
590
|
|
|
591
591
|
connection: PgConnectionTypeAsync | None
|
|
@@ -594,7 +594,7 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
594
594
|
cursor: PgCursorTypeAsync | None
|
|
595
595
|
""" Cursor to database """
|
|
596
596
|
|
|
597
|
-
|
|
597
|
+
context_connection_async: ContextVar[
|
|
598
598
|
tuple[PgConnectionTypeAsync, PgCursorTypeAsync] | None
|
|
599
599
|
]
|
|
600
600
|
""" Connection used in async context manager """
|
|
@@ -605,19 +605,19 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
605
605
|
|
|
606
606
|
def __init__(
|
|
607
607
|
self,
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
608
|
+
db_config: PgConfig,
|
|
609
|
+
connection_timeout: int = 5,
|
|
610
|
+
instance_name: str = "async_postgresql",
|
|
611
611
|
) -> None:
|
|
612
612
|
"""
|
|
613
613
|
Main concept here is that in init we do not connect to database,
|
|
614
614
|
so that class instances can be safely made regardless of connection statuss.
|
|
615
615
|
|
|
616
|
-
Remember to call await
|
|
617
|
-
and also await
|
|
616
|
+
Remember to call await open_pool() after creating instance to actually open the pool to the database
|
|
617
|
+
and also await close_pool() to close the pool.
|
|
618
618
|
"""
|
|
619
619
|
|
|
620
|
-
super().__init__(
|
|
620
|
+
super().__init__(db_config, connection_timeout, instance_name)
|
|
621
621
|
|
|
622
622
|
# Set defaults
|
|
623
623
|
if not "port" in self.config or not self.config["port"]:
|
|
@@ -639,19 +639,19 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
639
639
|
if not "pool_kwargs" in self.config or not self.config["pool_kwargs"]:
|
|
640
640
|
self.config["pool_kwargs"] = {}
|
|
641
641
|
|
|
642
|
-
|
|
642
|
+
conn_str = (
|
|
643
643
|
f"postgresql://{self.config['username']}:{self.config['password']}@{self.config['hostname']}:{self.config['port']}"
|
|
644
|
-
f"/{self.config['database']}?connect_timeout={self.
|
|
644
|
+
f"/{self.config['database']}?connect_timeout={self.connection_timeout}&application_name={self.name}"
|
|
645
645
|
f"&sslmode={self.config['ssl']}"
|
|
646
646
|
)
|
|
647
|
-
self.
|
|
648
|
-
|
|
647
|
+
self.pool_async = AsyncConnectionPool(
|
|
648
|
+
conn_str,
|
|
649
649
|
open=False,
|
|
650
650
|
min_size=2,
|
|
651
651
|
max_size=self.config["maxconnections"],
|
|
652
652
|
max_lifetime=20 * 60,
|
|
653
653
|
max_idle=400,
|
|
654
|
-
timeout=self.
|
|
654
|
+
timeout=self.connection_timeout,
|
|
655
655
|
reconnect_timeout=0,
|
|
656
656
|
num_workers=4,
|
|
657
657
|
connection_class=PgConnectionTypeAsync,
|
|
@@ -663,27 +663,27 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
663
663
|
"""Destructor"""
|
|
664
664
|
del self.cursor
|
|
665
665
|
del self.connection
|
|
666
|
-
del self.
|
|
666
|
+
del self.pool_async
|
|
667
667
|
|
|
668
668
|
##################
|
|
669
669
|
### Connection ###
|
|
670
670
|
##################
|
|
671
671
|
|
|
672
|
-
async def
|
|
673
|
-
await self.
|
|
672
|
+
async def open_pool(self) -> None:
|
|
673
|
+
await self.pool_async.open(wait=True, timeout=self.connection_timeout)
|
|
674
674
|
|
|
675
|
-
async def
|
|
675
|
+
async def close_pool(self) -> None:
|
|
676
676
|
"""Close Pool"""
|
|
677
677
|
|
|
678
|
-
if self.
|
|
678
|
+
if self.shutdown_requested.is_set():
|
|
679
679
|
return
|
|
680
|
-
self.
|
|
680
|
+
self.shutdown_requested.set()
|
|
681
681
|
|
|
682
682
|
# Close async pool
|
|
683
683
|
self.logger.debug("Closing connection pool")
|
|
684
684
|
await self.close()
|
|
685
|
-
if hasattr(self, "poolAsync") and self.
|
|
686
|
-
await self.
|
|
685
|
+
if hasattr(self, "poolAsync") and self.pool_async.closed is False:
|
|
686
|
+
await self.pool_async.close()
|
|
687
687
|
|
|
688
688
|
async def open(self) -> None:
|
|
689
689
|
"""Get connection from the pool and keep it in the class"""
|
|
@@ -691,7 +691,7 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
691
691
|
await self.close()
|
|
692
692
|
|
|
693
693
|
# Create new connection
|
|
694
|
-
res = await self.
|
|
694
|
+
res = await self.new_connection()
|
|
695
695
|
if res:
|
|
696
696
|
(self.connection, self.cursor) = res
|
|
697
697
|
|
|
@@ -704,29 +704,29 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
704
704
|
self.cursor = None
|
|
705
705
|
|
|
706
706
|
if self.connection:
|
|
707
|
-
await self.
|
|
707
|
+
await self.return_connection(self.connection)
|
|
708
708
|
self.connection = None
|
|
709
709
|
|
|
710
|
-
async def
|
|
710
|
+
async def new_connection(
|
|
711
711
|
self,
|
|
712
712
|
) -> tuple[PgConnectionTypeAsync, PgCursorTypeAsync] | None:
|
|
713
|
-
assert self.
|
|
713
|
+
assert self.pool_async, "Async pool is not initialized"
|
|
714
714
|
|
|
715
715
|
# Log
|
|
716
716
|
self.logger.debug("Getting connection from the pool")
|
|
717
717
|
|
|
718
718
|
# Get connection from the pool
|
|
719
719
|
tries = 0
|
|
720
|
-
while not self.
|
|
720
|
+
while not self.shutdown_requested.is_set():
|
|
721
721
|
connection = None
|
|
722
722
|
try:
|
|
723
|
-
connection = await self.
|
|
724
|
-
timeout=self.
|
|
723
|
+
connection = await self.pool_async.getconn(
|
|
724
|
+
timeout=self.connection_timeout
|
|
725
725
|
)
|
|
726
726
|
cursor = connection.cursor(row_factory=PgDictRowFactory)
|
|
727
727
|
|
|
728
728
|
# Lets do some socket magic
|
|
729
|
-
self.
|
|
729
|
+
self.fix_socket_timeouts(connection.fileno())
|
|
730
730
|
|
|
731
731
|
async with connection.transaction():
|
|
732
732
|
await cursor.execute("SELECT 1")
|
|
@@ -737,10 +737,10 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
737
737
|
except Exception as e:
|
|
738
738
|
if connection:
|
|
739
739
|
await connection.close()
|
|
740
|
-
await self.
|
|
740
|
+
await self.pool_async.putconn(connection)
|
|
741
741
|
|
|
742
742
|
self.logger.error(f"Error while getting connection from the pool: {e}")
|
|
743
|
-
self.
|
|
743
|
+
self.shutdown_requested.wait(self.slow_down_timeout)
|
|
744
744
|
tries += 1
|
|
745
745
|
if tries >= 3:
|
|
746
746
|
break
|
|
@@ -748,18 +748,18 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
748
748
|
|
|
749
749
|
return None
|
|
750
750
|
|
|
751
|
-
async def
|
|
751
|
+
async def return_connection(self, connection: PgConnectionTypeAsync) -> None:
|
|
752
752
|
"""Return connection to the pool"""
|
|
753
|
-
assert self.
|
|
753
|
+
assert self.pool_async, "Async pool is not initialized"
|
|
754
754
|
|
|
755
755
|
# Log
|
|
756
756
|
self.logger.debug("Putting connection back to the pool")
|
|
757
757
|
|
|
758
758
|
# Put connection back to the pool
|
|
759
|
-
await self.
|
|
759
|
+
await self.pool_async.putconn(connection)
|
|
760
760
|
|
|
761
761
|
# Debug
|
|
762
|
-
self.logger.debug(self.
|
|
762
|
+
self.logger.debug(self.pool_async.get_stats())
|
|
763
763
|
|
|
764
764
|
###############
|
|
765
765
|
### Context ###
|
|
@@ -771,11 +771,11 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
771
771
|
"""Context manager"""
|
|
772
772
|
|
|
773
773
|
# Lets set the context var so that it is set even if we fail to get connection
|
|
774
|
-
self.
|
|
774
|
+
self.context_connection_async.set(None)
|
|
775
775
|
|
|
776
|
-
res = await self.
|
|
776
|
+
res = await self.new_connection()
|
|
777
777
|
if res:
|
|
778
|
-
self.
|
|
778
|
+
self.context_connection_async.set(res)
|
|
779
779
|
return res
|
|
780
780
|
|
|
781
781
|
return (
|
|
@@ -786,12 +786,12 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
786
786
|
async def __aexit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
|
787
787
|
"""Context manager"""
|
|
788
788
|
|
|
789
|
-
|
|
790
|
-
if
|
|
791
|
-
await self.
|
|
789
|
+
test_data = self.context_connection_async.get()
|
|
790
|
+
if test_data:
|
|
791
|
+
await self.return_connection(test_data[0])
|
|
792
792
|
|
|
793
793
|
# Reset context
|
|
794
|
-
self.
|
|
794
|
+
self.context_connection_async.set(None)
|
|
795
795
|
|
|
796
796
|
####################
|
|
797
797
|
### Transactions ###
|
|
@@ -800,11 +800,11 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
800
800
|
@asynccontextmanager
|
|
801
801
|
async def transaction(
|
|
802
802
|
self,
|
|
803
|
-
|
|
803
|
+
db_conn: PgConnectionTypeAsync | None = None,
|
|
804
804
|
) -> AsyncIterator[AsyncTransaction]:
|
|
805
805
|
"""Transaction context manager"""
|
|
806
|
-
if
|
|
807
|
-
async with
|
|
806
|
+
if db_conn:
|
|
807
|
+
async with db_conn.transaction() as trans:
|
|
808
808
|
yield trans
|
|
809
809
|
|
|
810
810
|
assert self.connection, "Connection is not initialized"
|
|
@@ -815,7 +815,7 @@ class PgSQLWithPoolingAsync(DatabaseBackend):
|
|
|
815
815
|
### Data ###
|
|
816
816
|
############
|
|
817
817
|
|
|
818
|
-
def
|
|
818
|
+
def affected_rows(self) -> int:
|
|
819
819
|
assert self.cursor, "Cursor is not initialized"
|
|
820
820
|
|
|
821
821
|
return self.cursor.rowcount
|
|
@@ -13,7 +13,7 @@ class DBWrapperPgSQL(DBWrapperPgSQLMixin, DBWrapper):
|
|
|
13
13
|
Sync database wrapper for postgres
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
db_cursor: PgCursorType | None
|
|
17
17
|
""" PostgreSQL cursor object """
|
|
18
18
|
|
|
19
19
|
#######################
|
|
@@ -24,36 +24,36 @@ class DBWrapperPgSQL(DBWrapperPgSQLMixin, DBWrapper):
|
|
|
24
24
|
# We are overriding the __init__ method for the type hinting
|
|
25
25
|
def __init__(
|
|
26
26
|
self,
|
|
27
|
-
|
|
27
|
+
db_cursor: PgCursorType | None = None,
|
|
28
28
|
logger: logging.Logger | None = None,
|
|
29
29
|
):
|
|
30
30
|
"""
|
|
31
31
|
Initializes a new instance of the DBWrapper class.
|
|
32
32
|
|
|
33
33
|
Args:
|
|
34
|
-
|
|
34
|
+
db_cursor (PgCursorType): The PostgreSQL database cursor object.
|
|
35
35
|
logger (logging.Logger, optional): The logger object. Defaults to None.
|
|
36
36
|
"""
|
|
37
|
-
super().__init__(
|
|
37
|
+
super().__init__(db_cursor, logger)
|
|
38
38
|
|
|
39
39
|
###############
|
|
40
40
|
### Setters ###
|
|
41
41
|
###############
|
|
42
42
|
|
|
43
|
-
def
|
|
43
|
+
def set_db_cursor(self, db_cursor: PgCursorType | None) -> None:
|
|
44
44
|
"""
|
|
45
45
|
Updates the database cursor object.
|
|
46
46
|
|
|
47
47
|
Args:
|
|
48
|
-
|
|
48
|
+
db_cursor (PgCursorType): The new database cursor object.
|
|
49
49
|
"""
|
|
50
|
-
super().
|
|
50
|
+
super().set_db_cursor(db_cursor)
|
|
51
51
|
|
|
52
52
|
######################
|
|
53
53
|
### Helper methods ###
|
|
54
54
|
######################
|
|
55
55
|
|
|
56
|
-
def
|
|
56
|
+
def log_query(
|
|
57
57
|
self,
|
|
58
58
|
cursor: Cursor[Any],
|
|
59
59
|
query: sql.SQL | sql.Composed,
|
|
@@ -67,5 +67,5 @@ class DBWrapperPgSQL(DBWrapperPgSQLMixin, DBWrapper):
|
|
|
67
67
|
query (Any): The query to log.
|
|
68
68
|
params (tuple[Any, ...]): The parameters to log.
|
|
69
69
|
"""
|
|
70
|
-
|
|
71
|
-
logging.getLogger().debug(f"Query: {
|
|
70
|
+
query_string = query.as_string(self.db_cursor)
|
|
71
|
+
logging.getLogger().debug(f"Query: {query_string} with params: {params}")
|
|
@@ -15,7 +15,7 @@ class DBWrapperPgSQLAsync(DBWrapperPgSQLMixin, DBWrapperAsync):
|
|
|
15
15
|
This is meant to be used in async environments.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
db_cursor: PgCursorTypeAsync | None
|
|
19
19
|
""" Async PostgreSQL cursor object """
|
|
20
20
|
|
|
21
21
|
#######################
|
|
@@ -26,36 +26,36 @@ class DBWrapperPgSQLAsync(DBWrapperPgSQLMixin, DBWrapperAsync):
|
|
|
26
26
|
# We are overriding the __init__ method for the type hinting
|
|
27
27
|
def __init__(
|
|
28
28
|
self,
|
|
29
|
-
|
|
29
|
+
db_cursor: PgCursorTypeAsync | None = None,
|
|
30
30
|
logger: logging.Logger | None = None,
|
|
31
31
|
):
|
|
32
32
|
"""
|
|
33
33
|
Initializes a new instance of the DBWrapper class.
|
|
34
34
|
|
|
35
35
|
Args:
|
|
36
|
-
|
|
36
|
+
db_cursor (PgCursorTypeAsync): The PostgreSQL database cursor object.
|
|
37
37
|
logger (logging.Logger, optional): The logger object. Defaults to None.
|
|
38
38
|
"""
|
|
39
|
-
super().__init__(
|
|
39
|
+
super().__init__(db_cursor, logger)
|
|
40
40
|
|
|
41
41
|
###############
|
|
42
42
|
### Setters ###
|
|
43
43
|
###############
|
|
44
44
|
|
|
45
|
-
def
|
|
45
|
+
def set_db_cursor(self, db_cursor: PgCursorTypeAsync | None) -> None:
|
|
46
46
|
"""
|
|
47
47
|
Updates the database cursor object.
|
|
48
48
|
|
|
49
49
|
Args:
|
|
50
|
-
|
|
50
|
+
db_cursor (PgCursorTypeAsync): The new database cursor object.
|
|
51
51
|
"""
|
|
52
|
-
super().
|
|
52
|
+
super().set_db_cursor(db_cursor)
|
|
53
53
|
|
|
54
54
|
######################
|
|
55
55
|
### Helper methods ###
|
|
56
56
|
######################
|
|
57
57
|
|
|
58
|
-
def
|
|
58
|
+
def log_query(
|
|
59
59
|
self,
|
|
60
60
|
cursor: PgCursorTypeAsync,
|
|
61
61
|
query: sql.SQL | sql.Composed,
|
|
@@ -69,5 +69,5 @@ class DBWrapperPgSQLAsync(DBWrapperPgSQLMixin, DBWrapperAsync):
|
|
|
69
69
|
query (Any): The query to log.
|
|
70
70
|
params (tuple[Any, ...]): The parameters to log.
|
|
71
71
|
"""
|
|
72
|
-
|
|
73
|
-
logging.getLogger().debug(f"Query: {
|
|
72
|
+
query_string = query.as_string(self.db_cursor)
|
|
73
|
+
logging.getLogger().debug(f"Query: {query_string} with params: {params}")
|
|
@@ -13,7 +13,7 @@ class DBWrapperPgSQLMixin:
|
|
|
13
13
|
### Helper methods ###
|
|
14
14
|
######################
|
|
15
15
|
|
|
16
|
-
def
|
|
16
|
+
def make_identifier(self, schema: str | None, name: str) -> sql.Identifier | str:
|
|
17
17
|
"""
|
|
18
18
|
Creates a SQL identifier object from the given name.
|
|
19
19
|
|
|
@@ -32,50 +32,50 @@ class DBWrapperPgSQLMixin:
|
|
|
32
32
|
### Query methods ###
|
|
33
33
|
#####################
|
|
34
34
|
|
|
35
|
-
def
|
|
35
|
+
def filter_query(
|
|
36
36
|
self,
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
schema_name: str | None,
|
|
38
|
+
table_name: str,
|
|
39
39
|
) -> sql.SQL | sql.Composed | str:
|
|
40
40
|
"""
|
|
41
41
|
Creates a SQL query to filter data from the given table.
|
|
42
42
|
|
|
43
43
|
Args:
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
schema_name (str): The name of the schema to filter data from.
|
|
45
|
+
table_name (str): The name of the table to filter data from.
|
|
46
46
|
|
|
47
47
|
Returns:
|
|
48
48
|
sql.SQL | sql.Composed: The created SQL query object.
|
|
49
49
|
"""
|
|
50
50
|
return sql.SQL("SELECT * FROM {table}").format(
|
|
51
|
-
table=self.
|
|
51
|
+
table=self.make_identifier(schema_name, table_name),
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
-
def
|
|
54
|
+
def order_query(
|
|
55
55
|
self,
|
|
56
|
-
|
|
56
|
+
order_by: OrderByItem | None = None,
|
|
57
57
|
) -> sql.SQL | sql.Composed | None:
|
|
58
58
|
"""
|
|
59
59
|
Creates a SQL query to order the results by the given column.
|
|
60
60
|
|
|
61
61
|
Args:
|
|
62
|
-
|
|
62
|
+
order_by (OrderByItem | None, optional): The column to order the results by. Defaults to None.
|
|
63
63
|
|
|
64
64
|
Returns:
|
|
65
65
|
Any: The created SQL query object.
|
|
66
66
|
|
|
67
67
|
TODO: Fix return type
|
|
68
68
|
"""
|
|
69
|
-
if
|
|
69
|
+
if order_by is None:
|
|
70
70
|
return None
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
order_list = [
|
|
73
73
|
f"{item[0]} {item[1] if len(item) > 1 and item[1] != None else 'ASC'}"
|
|
74
|
-
for item in
|
|
74
|
+
for item in order_by
|
|
75
75
|
]
|
|
76
|
-
return sql.SQL("ORDER BY %s" % ", ".join(
|
|
76
|
+
return sql.SQL("ORDER BY %s" % ", ".join(order_list)) # type: ignore
|
|
77
77
|
|
|
78
|
-
def
|
|
78
|
+
def limit_query(
|
|
79
79
|
self,
|
|
80
80
|
offset: int = 0,
|
|
81
81
|
limit: int = 100,
|
|
@@ -85,29 +85,29 @@ class DBWrapperPgSQLMixin:
|
|
|
85
85
|
|
|
86
86
|
return sql.SQL("LIMIT {} OFFSET {}").format(limit, offset)
|
|
87
87
|
|
|
88
|
-
def
|
|
88
|
+
def format_filter(self, key: str, filter: Any) -> tuple[Any, ...]:
|
|
89
89
|
# TODO: For now we assume that we have that method from DBWrapperMixin
|
|
90
90
|
# TODO: Its 5am and I am tired, I will fix this later
|
|
91
|
-
return super().
|
|
91
|
+
return super().format_filter(key, filter) # type: ignore
|
|
92
92
|
|
|
93
|
-
def
|
|
93
|
+
def create_filter(
|
|
94
94
|
self, filter: dict[str, Any] | None
|
|
95
95
|
) -> tuple[sql.Composed | None, tuple[Any, ...]]:
|
|
96
96
|
if filter is None or len(filter) == 0:
|
|
97
97
|
return (None, tuple())
|
|
98
98
|
|
|
99
|
-
raw = [self.
|
|
99
|
+
raw = [self.format_filter(key, filter[key]) for key in filter]
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
_query = sql.SQL("WHERE {queryItems}").format(
|
|
101
|
+
_query_items = sql.SQL(" AND ").join([sql.SQL(tup[0]) for tup in raw])
|
|
102
|
+
_query = sql.SQL("WHERE {queryItems}").format(query_items=_query_items)
|
|
103
103
|
_params = tuple([val for tup in raw for val in tup[1:] if val is not NoParam])
|
|
104
104
|
|
|
105
105
|
return (_query, _params)
|
|
106
106
|
|
|
107
|
-
def
|
|
107
|
+
def _format_filter_query(
|
|
108
108
|
self,
|
|
109
109
|
query: sql.SQL | sql.Composed | str,
|
|
110
|
-
|
|
110
|
+
q_filter: sql.SQL | sql.Composed | None,
|
|
111
111
|
order: sql.SQL | sql.Composed | None,
|
|
112
112
|
limit: sql.SQL | sql.Composed | None,
|
|
113
113
|
) -> sql.Composed:
|
|
@@ -115,57 +115,58 @@ class DBWrapperPgSQLMixin:
|
|
|
115
115
|
if isinstance(query, str):
|
|
116
116
|
query = sql.SQL(query) # type: ignore
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
if
|
|
120
|
-
# if isinstance(
|
|
121
|
-
#
|
|
122
|
-
|
|
118
|
+
query_parts: list[sql.Composable] = [query]
|
|
119
|
+
if q_filter is not None:
|
|
120
|
+
# if isinstance(q_filter, str):
|
|
121
|
+
# q_filter = sql.SQL(q_filter)
|
|
122
|
+
query_parts.append(q_filter)
|
|
123
123
|
if order is not None:
|
|
124
|
-
|
|
124
|
+
query_parts.append(order)
|
|
125
125
|
if limit is not None:
|
|
126
|
-
|
|
126
|
+
query_parts.append(limit)
|
|
127
127
|
|
|
128
|
-
return sql.SQL(" ").join(
|
|
128
|
+
return sql.SQL(" ").join(query_parts)
|
|
129
129
|
|
|
130
|
-
def
|
|
130
|
+
def _format_insert_query(
|
|
131
131
|
self,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
table_identifier: sql.Identifier | str,
|
|
133
|
+
store_data: dict[str, Any],
|
|
134
|
+
return_key: sql.Identifier | str,
|
|
135
135
|
) -> sql.Composed:
|
|
136
|
-
keys =
|
|
137
|
-
values = list(
|
|
136
|
+
keys = store_data.keys()
|
|
137
|
+
values = list(store_data.values())
|
|
138
138
|
|
|
139
139
|
return sql.SQL(
|
|
140
140
|
"INSERT INTO {table} ({columns}) VALUES ({values}) RETURNING {id_key}"
|
|
141
141
|
).format(
|
|
142
|
-
table=
|
|
142
|
+
table=table_identifier,
|
|
143
143
|
columns=sql.SQL(", ").join(map(sql.Identifier, keys)),
|
|
144
144
|
values=sql.SQL(", ").join(sql.Placeholder() * len(values)),
|
|
145
|
-
id_key=
|
|
145
|
+
id_key=return_key,
|
|
146
146
|
)
|
|
147
147
|
|
|
148
|
-
def
|
|
148
|
+
def _format_update_query(
|
|
149
149
|
self,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
table_identifier: sql.Identifier | str,
|
|
151
|
+
update_key: sql.Identifier | str,
|
|
152
|
+
update_data: dict[str, Any],
|
|
153
153
|
) -> sql.Composed:
|
|
154
|
-
keys =
|
|
154
|
+
keys = update_data.keys()
|
|
155
155
|
set_clause = sql.SQL(", ").join(
|
|
156
156
|
sql.Identifier(key) + sql.SQL(" = %s") for key in keys
|
|
157
157
|
)
|
|
158
158
|
return sql.SQL("UPDATE {table} SET {set_clause} WHERE {id_key} = %s").format(
|
|
159
|
-
table=
|
|
159
|
+
table=table_identifier,
|
|
160
160
|
set_clause=set_clause,
|
|
161
|
-
id_key=
|
|
161
|
+
id_key=update_key,
|
|
162
162
|
)
|
|
163
163
|
|
|
164
|
-
def
|
|
164
|
+
def _format_delete_query(
|
|
165
165
|
self,
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
table_identifier: sql.Identifier | str,
|
|
167
|
+
delete_key: sql.Identifier | str,
|
|
168
168
|
) -> sql.Composed:
|
|
169
169
|
return sql.SQL("DELETE FROM {table} WHERE {id_key} = %s").format(
|
|
170
|
-
table=
|
|
170
|
+
table=table_identifier,
|
|
171
|
+
id_key=delete_key,
|
|
171
172
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: database_wrapper_pgsql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: database_wrapper for PostgreSQL database
|
|
5
5
|
Author-email: Gints Murans <gm@gm.lv>
|
|
6
6
|
License: GNU General Public License v3.0 (GPL-3.0)
|
|
@@ -32,7 +32,7 @@ Classifier: Topic :: Software Development
|
|
|
32
32
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
33
33
|
Requires-Python: >=3.8
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
|
-
Requires-Dist: database_wrapper==0.
|
|
35
|
+
Requires-Dist: database_wrapper==0.2.2
|
|
36
36
|
Requires-Dist: psycopg[binary]>=3.2.0
|
|
37
37
|
Requires-Dist: psycopg[pool]>=3.2.0
|
|
38
38
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
database_wrapper_pgsql/__init__.py,sha256=9QvHYUq5EgEpqIaZArrm30v_USM5zIaf0UN-z6cHWhs,1058
|
|
2
|
+
database_wrapper_pgsql/connector.py,sha256=Hicf9DHxOjv6H77ySdTi26YmKjlSTzOIWQrjETq3LRg,25027
|
|
3
|
+
database_wrapper_pgsql/db_wrapper_pgsql.py,sha256=Tm18dzSOINz4Z8xHWynLCPf-HmD_SFP0PYVE8ZPd_A4,1921
|
|
4
|
+
database_wrapper_pgsql/db_wrapper_pgsql_async.py,sha256=d4AZp_dHdmob7oJlEst2DnhMqpeXeG6WOzI8k5OldKg,2024
|
|
5
|
+
database_wrapper_pgsql/db_wrapper_pgsql_mixin.py,sha256=KOlKXNpTeEGL_IkCIyh-kcoCvxUbUKAFjdeYlFe1iVs,5451
|
|
6
|
+
database_wrapper_pgsql/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
database_wrapper_pgsql-0.2.2.dist-info/METADATA,sha256=tsD8rkAo7w_UrW5Q6VxgXpDxOcM3wjggIjqlxhPGxFE,3229
|
|
8
|
+
database_wrapper_pgsql-0.2.2.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
9
|
+
database_wrapper_pgsql-0.2.2.dist-info/top_level.txt,sha256=EQhZLk12wRdsSp-Lo3Jc4cXmNfG8y5EouNv_7OBCSGo,23
|
|
10
|
+
database_wrapper_pgsql-0.2.2.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
database_wrapper_pgsql/__init__.py,sha256=9QvHYUq5EgEpqIaZArrm30v_USM5zIaf0UN-z6cHWhs,1058
|
|
2
|
-
database_wrapper_pgsql/connector.py,sha256=Qom_I8eVuq9iSHZEe06QW53oq16j0lmPLwPmcOrbKmc,24896
|
|
3
|
-
database_wrapper_pgsql/db_wrapper_pgsql.py,sha256=v4GGsheZljELurAuaQDTI6ZFoFGjThQ9MubRpRj3dVc,1906
|
|
4
|
-
database_wrapper_pgsql/db_wrapper_pgsql_async.py,sha256=sSCHLHvKnf7i_Dy8K8_XKa6hWMc26W9hJqU4nbN0v_c,2009
|
|
5
|
-
database_wrapper_pgsql/db_wrapper_pgsql_mixin.py,sha256=D0RKSGnZsyC3XbhQeGgblCP3W9530at2j9O5CaSc2GI,5377
|
|
6
|
-
database_wrapper_pgsql/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
database_wrapper_pgsql-0.1.85.dist-info/METADATA,sha256=mYMejIV0lsmsTreRiwb6XGxudu09LtScWQ4V03ob5cw,3231
|
|
8
|
-
database_wrapper_pgsql-0.1.85.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
9
|
-
database_wrapper_pgsql-0.1.85.dist-info/top_level.txt,sha256=EQhZLk12wRdsSp-Lo3Jc4cXmNfG8y5EouNv_7OBCSGo,23
|
|
10
|
-
database_wrapper_pgsql-0.1.85.dist-info/RECORD,,
|
{database_wrapper_pgsql-0.1.85.dist-info → database_wrapper_pgsql-0.2.2.dist-info}/top_level.txt
RENAMED
|
File without changes
|