Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.

Commit 8cbcccb

Browse files
author
ansipunk
committed
Rename PostgresBackend to AsyncpgBackend
1 parent 37c450c commit 8cbcccb

File tree

5 files changed

+154
-36
lines changed

5 files changed

+154
-36
lines changed

databases/backends/postgres.py renamed to databases/backends/asyncpg.py

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from sqlalchemy.sql.ddl import DDLElement
88

99
from databases.backends.common.records import Record, create_column_maps
10-
from databases.backends.dialects.psycopg import dialect as psycopg_dialect
10+
from databases.backends.dialects.psycopg import get_dialect
1111
from databases.core import LOG_EXTRA, DatabaseURL
1212
from databases.interfaces import (
1313
ConnectionBackend,
@@ -19,28 +19,15 @@
1919
logger = logging.getLogger("databases")
2020

2121

22-
class PostgresBackend(DatabaseBackend):
22+
class AsyncpgBackend(DatabaseBackend):
2323
def __init__(
2424
self, database_url: typing.Union[DatabaseURL, str], **options: typing.Any
2525
) -> None:
2626
self._database_url = DatabaseURL(database_url)
2727
self._options = options
28-
self._dialect = self._get_dialect()
28+
self._dialect = get_dialect()
2929
self._pool = None
3030

31-
def _get_dialect(self) -> Dialect:
32-
dialect = psycopg_dialect(paramstyle="pyformat")
33-
34-
dialect.implicit_returning = True
35-
dialect.supports_native_enum = True
36-
dialect.supports_smallserial = True # 9.2+
37-
dialect._backslash_escapes = False
38-
dialect.supports_sane_multi_rowcount = True # psycopg 2.0.9+
39-
dialect._has_native_hstore = True
40-
dialect.supports_native_decimal = True
41-
42-
return dialect
43-
4431
def _get_connection_kwargs(self) -> dict:
4532
url_options = self._database_url.options
4633

@@ -78,12 +65,12 @@ async def disconnect(self) -> None:
7865
await self._pool.close()
7966
self._pool = None
8067

81-
def connection(self) -> "PostgresConnection":
82-
return PostgresConnection(self, self._dialect)
68+
def connection(self) -> "AsyncpgConnection":
69+
return AsyncpgConnection(self, self._dialect)
8370

8471

85-
class PostgresConnection(ConnectionBackend):
86-
def __init__(self, database: PostgresBackend, dialect: Dialect):
72+
class AsyncpgConnection(ConnectionBackend):
73+
def __init__(self, database: AsyncpgBackend, dialect: Dialect):
8774
self._database = database
8875
self._dialect = dialect
8976
self._connection: typing.Optional[asyncpg.connection.Connection] = None
@@ -159,7 +146,7 @@ async def iterate(
159146
yield Record(row, result_columns, self._dialect, column_maps)
160147

161148
def transaction(self) -> TransactionBackend:
162-
return PostgresTransaction(connection=self)
149+
return AsyncpgTransaction(connection=self)
163150

164151
def _compile(self, query: ClauseElement) -> typing.Tuple[str, list, tuple]:
165152
compiled = query.compile(
@@ -197,8 +184,8 @@ def raw_connection(self) -> asyncpg.connection.Connection:
197184
return self._connection
198185

199186

200-
class PostgresTransaction(TransactionBackend):
201-
def __init__(self, connection: PostgresConnection):
187+
class AsyncpgTransaction(TransactionBackend):
188+
def __init__(self, connection: AsyncpgConnection):
202189
self._connection = connection
203190
self._transaction: typing.Optional[asyncpg.transaction.Transaction] = None
204191

databases/backends/dialects/psycopg.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,13 @@ class PGDialect_psycopg(PGDialect):
4343
execution_ctx_cls = PGExecutionContext_psycopg
4444

4545

46-
dialect = PGDialect_psycopg
46+
def get_dialect() -> PGDialect_psycopg:
47+
dialect = PGDialect_psycopg(paramstyle="pyformat")
48+
dialect.implicit_returning = True
49+
dialect.supports_native_enum = True
50+
dialect.supports_smallserial = True # 9.2+
51+
dialect._backslash_escapes = False
52+
dialect.supports_sane_multi_rowcount = True # psycopg 2.0.9+
53+
dialect._has_native_hstore = True
54+
dialect.supports_native_decimal = True
55+
return dialect

databases/backends/psycopg.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import typing
2+
from collections.abc import Sequence
3+
4+
import psycopg_pool
5+
from sqlalchemy.sql import ClauseElement
6+
7+
from databases.backends.dialects.psycopg import get_dialect
8+
from databases.core import DatabaseURL
9+
from databases.interfaces import (
10+
ConnectionBackend,
11+
DatabaseBackend,
12+
TransactionBackend,
13+
)
14+
15+
16+
class PsycopgBackend(DatabaseBackend):
17+
def __init__(
18+
self, database_url: typing.Union[DatabaseURL, str], **options: typing.Any
19+
) -> None:
20+
self._database_url = DatabaseURL(database_url)
21+
self._options = options
22+
self._dialect = get_dialect()
23+
self._pool: typing.Optional[psycopg_pool.AsyncConnectionPool] = None
24+
25+
async def connect(self) -> None:
26+
if self._pool is not None:
27+
return
28+
29+
self._pool = psycopg_pool.AsyncConnectionPool(
30+
self._database_url.url, open=False, **self._options)
31+
await self._pool.open()
32+
33+
async def disconnect(self) -> None:
34+
if self._pool is None:
35+
return
36+
37+
await self._pool.close()
38+
self._pool = None
39+
40+
def connection(self) -> "PsycopgConnection":
41+
return PsycopgConnection(self)
42+
43+
44+
class PsycopgConnection(ConnectionBackend):
45+
def __init__(self, database: PsycopgBackend) -> None:
46+
self._database = database
47+
48+
async def acquire(self) -> None:
49+
if self._connection is not None:
50+
return
51+
52+
if self._database._pool is None:
53+
raise RuntimeError("PsycopgBackend is not running")
54+
55+
# TODO: Add configurable timeouts
56+
self._connection = await self._database._pool.getconn()
57+
58+
async def release(self) -> None:
59+
if self._connection is None:
60+
return
61+
62+
await self._database._pool.putconn(self._connection)
63+
self._connection = None
64+
65+
async def fetch_all(self, query: ClauseElement) -> typing.List["Record"]:
66+
raise NotImplementedError() # pragma: no cover
67+
68+
async def fetch_one(self, query: ClauseElement) -> typing.Optional["Record"]:
69+
raise NotImplementedError() # pragma: no cover
70+
71+
async def fetch_val(
72+
self, query: ClauseElement, column: typing.Any = 0
73+
) -> typing.Any:
74+
row = await self.fetch_one(query)
75+
return None if row is None else row[column]
76+
77+
async def execute(self, query: ClauseElement) -> typing.Any:
78+
raise NotImplementedError() # pragma: no cover
79+
80+
async def execute_many(self, queries: typing.List[ClauseElement]) -> None:
81+
raise NotImplementedError() # pragma: no cover
82+
83+
async def iterate(
84+
self, query: ClauseElement
85+
) -> typing.AsyncGenerator[typing.Mapping, None]:
86+
raise NotImplementedError() # pragma: no cover
87+
# mypy needs async iterators to contain a `yield`
88+
# https://github.com/python/mypy/issues/5385#issuecomment-407281656
89+
yield True # pragma: no cover
90+
91+
def transaction(self) -> "TransactionBackend":
92+
raise NotImplementedError() # pragma: no cover
93+
94+
@property
95+
def raw_connection(self) -> typing.Any:
96+
raise NotImplementedError() # pragma: no cover
97+
98+
99+
class PsycopgTransaction(TransactionBackend):
100+
async def start(
101+
self, is_root: bool, extra_options: typing.Dict[typing.Any, typing.Any]
102+
) -> None:
103+
raise NotImplementedError() # pragma: no cover
104+
105+
async def commit(self) -> None:
106+
raise NotImplementedError() # pragma: no cover
107+
108+
async def rollback(self) -> None:
109+
raise NotImplementedError() # pragma: no cover
110+
111+
112+
class Record(Sequence):
113+
@property
114+
def _mapping(self) -> typing.Mapping:
115+
raise NotImplementedError() # pragma: no cover
116+
117+
def __getitem__(self, key: typing.Any) -> typing.Any:
118+
raise NotImplementedError() # pragma: no cover

databases/core.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,16 @@
4343

4444
class Database:
4545
SUPPORTED_BACKENDS = {
46-
"postgresql": "databases.backends.postgres:PostgresBackend",
46+
"postgres": "databases.backends.asyncpg:AsyncpgBackend",
47+
"postgresql": "databases.backends.asyncpg:AsyncpgBackend",
4748
"postgresql+aiopg": "databases.backends.aiopg:AiopgBackend",
48-
"postgres": "databases.backends.postgres:PostgresBackend",
49+
"postgresql+asyncpg": "databases.backends.asyncpg:AsyncpgBackend",
50+
"postgresql+psycopg": "databases.backends.psycopg:PsycopgBackend",
4951
"mysql": "databases.backends.mysql:MySQLBackend",
52+
"mysql+aiomysql": "databases.backends.asyncmy:MySQLBackend",
5053
"mysql+asyncmy": "databases.backends.asyncmy:AsyncMyBackend",
5154
"sqlite": "databases.backends.sqlite:SQLiteBackend",
55+
"sqlite+aiosqlite": "databases.backends.sqlite:SQLiteBackend",
5256
}
5357

5458
_connection_map: "weakref.WeakKeyDictionary[asyncio.Task, 'Connection']"

tests/test_connection_options.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pytest
77

88
from databases.backends.aiopg import AiopgBackend
9-
from databases.backends.postgres import PostgresBackend
9+
from databases.backends.asyncpg import AsyncpgBackend
1010
from databases.core import DatabaseURL
1111
from tests.test_databases import DATABASE_URLS, async_adapter
1212

@@ -19,7 +19,7 @@
1919

2020

2121
def test_postgres_pool_size():
22-
backend = PostgresBackend("postgres://localhost/database?min_size=1&max_size=20")
22+
backend = AsyncpgBackend("postgres://localhost/database?min_size=1&max_size=20")
2323
kwargs = backend._get_connection_kwargs()
2424
assert kwargs == {"min_size": 1, "max_size": 20}
2525

@@ -29,43 +29,43 @@ async def test_postgres_pool_size_connect():
2929
for url in DATABASE_URLS:
3030
if DatabaseURL(url).dialect != "postgresql":
3131
continue
32-
backend = PostgresBackend(url + "?min_size=1&max_size=20")
32+
backend = AsyncpgBackend(url + "?min_size=1&max_size=20")
3333
await backend.connect()
3434
await backend.disconnect()
3535

3636

3737
def test_postgres_explicit_pool_size():
38-
backend = PostgresBackend("postgres://localhost/database", min_size=1, max_size=20)
38+
backend = AsyncpgBackend("postgres://localhost/database", min_size=1, max_size=20)
3939
kwargs = backend._get_connection_kwargs()
4040
assert kwargs == {"min_size": 1, "max_size": 20}
4141

4242

4343
def test_postgres_ssl():
44-
backend = PostgresBackend("postgres://localhost/database?ssl=true")
44+
backend = AsyncpgBackend("postgres://localhost/database?ssl=true")
4545
kwargs = backend._get_connection_kwargs()
4646
assert kwargs == {"ssl": True}
4747

4848

4949
def test_postgres_ssl_verify_full():
50-
backend = PostgresBackend("postgres://localhost/database?ssl=verify-full")
50+
backend = AsyncpgBackend("postgres://localhost/database?ssl=verify-full")
5151
kwargs = backend._get_connection_kwargs()
5252
assert kwargs == {"ssl": "verify-full"}
5353

5454

5555
def test_postgres_explicit_ssl():
56-
backend = PostgresBackend("postgres://localhost/database", ssl=True)
56+
backend = AsyncpgBackend("postgres://localhost/database", ssl=True)
5757
kwargs = backend._get_connection_kwargs()
5858
assert kwargs == {"ssl": True}
5959

6060

6161
def test_postgres_explicit_ssl_verify_full():
62-
backend = PostgresBackend("postgres://localhost/database", ssl="verify-full")
62+
backend = AsyncpgBackend("postgres://localhost/database", ssl="verify-full")
6363
kwargs = backend._get_connection_kwargs()
6464
assert kwargs == {"ssl": "verify-full"}
6565

6666

6767
def test_postgres_no_extra_options():
68-
backend = PostgresBackend("postgres://localhost/database")
68+
backend = AsyncpgBackend("postgres://localhost/database")
6969
kwargs = backend._get_connection_kwargs()
7070
assert kwargs == {}
7171

@@ -74,7 +74,7 @@ def test_postgres_password_as_callable():
7474
def gen_password():
7575
return "Foo"
7676

77-
backend = PostgresBackend(
77+
backend = AsyncpgBackend(
7878
"postgres://:password@localhost/database", password=gen_password
7979
)
8080
kwargs = backend._get_connection_kwargs()

0 commit comments

Comments
 (0)