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

Commit 09e2dd7

Browse files
author
ansipunk
committed
Add and test psycopg3 support
1 parent ae3fb16 commit 09e2dd7

File tree

8 files changed

+50
-22
lines changed

8 files changed

+50
-22
lines changed

.github/workflows/test-suite.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ jobs:
1818

1919
services:
2020
mysql:
21-
image: mysql:5.7
21+
image: mariadb:11
2222
env:
2323
MYSQL_USER: username
2424
MYSQL_PASSWORD: password
2525
MYSQL_ROOT_PASSWORD: password
2626
MYSQL_DATABASE: testsuite
2727
ports:
2828
- 3306:3306
29-
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
29+
options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=3
3030

3131
postgres:
32-
image: postgres:14
32+
image: postgres:16
3333
env:
3434
POSTGRES_USER: username
3535
POSTGRES_PASSWORD: password
@@ -59,5 +59,6 @@ jobs:
5959
mysql+asyncmy://username:password@localhost:3306/testsuite,
6060
postgresql://username:password@localhost:5432/testsuite,
6161
postgresql+aiopg://username:[email protected]:5432/testsuite,
62-
postgresql+asyncpg://username:password@localhost:5432/testsuite
62+
postgresql+asyncpg://username:password@localhost:5432/testsuite,
63+
postgresql+psycopg://username:password@localhost:5432/testsuite
6364
run: "scripts/test"

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Database drivers supported are:
3333

3434
* [asyncpg][asyncpg]
3535
* [aiopg][aiopg]
36+
* [psycopg3][psycopg3]
3637
* [aiomysql][aiomysql]
3738
* [asyncmy][asyncmy]
3839
* [aiosqlite][aiosqlite]
@@ -42,6 +43,7 @@ You can install the required database drivers with:
4243
```shell
4344
$ pip install databases[asyncpg]
4445
$ pip install databases[aiopg]
46+
$ pip install databases[psycopg3]
4547
$ pip install databases[aiomysql]
4648
$ pip install databases[asyncmy]
4749
$ pip install databases[aiosqlite]
@@ -105,6 +107,7 @@ for examples of how to start using databases together with SQLAlchemy core expre
105107
[pymysql]: https://github.com/PyMySQL/PyMySQL
106108
[asyncpg]: https://github.com/MagicStack/asyncpg
107109
[aiopg]: https://github.com/aio-libs/aiopg
110+
[psycopg3]: https://github.com/psycopg/psycopg
108111
[aiomysql]: https://github.com/aio-libs/aiomysql
109112
[asyncmy]: https://github.com/long2ice/asyncmy
110113
[aiosqlite]: https://github.com/omnilib/aiosqlite

databases/backends/common/records.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import enum
21
import typing
3-
from datetime import date, datetime, time
42

53
from sqlalchemy.engine.interfaces import Dialect
64
from sqlalchemy.engine.row import Row as SQLRow

docs/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Database drivers supported are:
3131

3232
* [asyncpg][asyncpg]
3333
* [aiopg][aiopg]
34+
* [psycopg3][psycopg3]
3435
* [aiomysql][aiomysql]
3536
* [asyncmy][asyncmy]
3637
* [aiosqlite][aiosqlite]
@@ -40,6 +41,7 @@ You can install the required database drivers with:
4041
```shell
4142
$ pip install databases[asyncpg]
4243
$ pip install databases[aiopg]
44+
$ pip install databases[psycopg3]
4345
$ pip install databases[aiomysql]
4446
$ pip install databases[asyncmy]
4547
$ pip install databases[aiosqlite]
@@ -103,6 +105,7 @@ for examples of how to start using databases together with SQLAlchemy core expre
103105
[pymysql]: https://github.com/PyMySQL/PyMySQL
104106
[asyncpg]: https://github.com/MagicStack/asyncpg
105107
[aiopg]: https://github.com/aio-libs/aiopg
108+
[psycopg3]: https://github.com/psycopg/psycopg
106109
[aiomysql]: https://github.com/aio-libs/aiomysql
107110
[asyncmy]: https://github.com/long2ice/asyncmy
108111
[aiosqlite]: https://github.com/omnilib/aiosqlite

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ aiomysql==0.2.0
66
aiopg==1.4.0
77
aiosqlite==0.20.0
88
asyncpg==0.29.0
9+
psycopg==3.1.18
10+
psycopg-binary==3.1.18
911

1012
# Sync database drivers for standard tooling around setup/teardown/migrations.
1113
psycopg==3.1.18
14+
psycopg-binary==3.1.18
1215
pymysql==1.1.0
1316

1417
# Testing

setup.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ def get_packages(package):
4747
author_email="[email protected]",
4848
packages=get_packages("databases"),
4949
package_data={"databases": ["py.typed"]},
50-
install_requires=["sqlalchemy>=2.0.7"],
50+
install_requires=["sqlalchemy>=2.0.11"],
5151
extras_require={
52-
"postgresql": ["asyncpg"],
53-
"asyncpg": ["asyncpg"],
54-
"aiopg": ["aiopg"],
5552
"mysql": ["aiomysql"],
5653
"aiomysql": ["aiomysql"],
5754
"asyncmy": ["asyncmy"],
55+
"postgresql": ["asyncpg"],
56+
"aiopg": ["aiopg"],
57+
"asyncpg": ["asyncpg"],
58+
"psycopg3": ["psycopg"],
5859
"sqlite": ["aiosqlite"],
5960
"aiosqlite": ["aiosqlite"],
6061
},

tests/test_databases.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ def create_test_database():
134134
"postgresql+aiopg",
135135
"sqlite+aiosqlite",
136136
"postgresql+asyncpg",
137+
"postgresql+psycopg",
137138
]:
138139
url = str(database_url.replace(driver=None))
139140
engine = sqlalchemy.create_engine(url)
@@ -151,6 +152,7 @@ def create_test_database():
151152
"postgresql+aiopg",
152153
"sqlite+aiosqlite",
153154
"postgresql+asyncpg",
155+
"postgresql+psycopg",
154156
]:
155157
url = str(database_url.replace(driver=None))
156158
engine = sqlalchemy.create_engine(url)
@@ -1354,7 +1356,11 @@ async def test_queries_with_expose_backend_connection(database_url):
13541356
elif database.url.scheme == "mysql+asyncmy":
13551357
async with raw_connection.cursor() as cursor:
13561358
await cursor.execute(insert_query, values)
1357-
elif database.url.scheme in ["postgresql", "postgresql+asyncpg"]:
1359+
elif database.url.scheme in [
1360+
"postgresql",
1361+
"postgresql+asyncpg",
1362+
"postgresql+psycopg",
1363+
]:
13581364
await raw_connection.execute(insert_query, *values)
13591365
elif database.url.scheme in ["sqlite", "sqlite+aiosqlite"]:
13601366
await raw_connection.execute(insert_query, values)
@@ -1392,7 +1398,11 @@ async def test_queries_with_expose_backend_connection(database_url):
13921398
async with raw_connection.cursor() as cursor:
13931399
await cursor.execute(select_query)
13941400
results = await cursor.fetchall()
1395-
elif database.url.scheme in ["postgresql", "postgresql+asyncpg"]:
1401+
elif database.url.scheme in [
1402+
"postgresql",
1403+
"postgresql+asyncpg",
1404+
"postgresql+psycopg",
1405+
]:
13961406
results = await raw_connection.fetch(select_query)
13971407
elif database.url.scheme in ["sqlite", "sqlite+aiosqlite"]:
13981408
results = await raw_connection.execute_fetchall(select_query)
@@ -1407,7 +1417,11 @@ async def test_queries_with_expose_backend_connection(database_url):
14071417
assert results[2][2] == True
14081418

14091419
# fetch_one()
1410-
if database.url.scheme in ["postgresql", "postgresql+asyncpg"]:
1420+
if database.url.scheme in [
1421+
"postgresql",
1422+
"postgresql+asyncpg",
1423+
"postgresql+psycopg",
1424+
]:
14111425
result = await raw_connection.fetchrow(select_query)
14121426
elif database.url.scheme == "mysql+asyncmy":
14131427
async with raw_connection.cursor() as cursor:

tests/test_integration.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import contextlib
2+
13
import pytest
24
import sqlalchemy
35
from starlette.applications import Starlette
46
from starlette.responses import JSONResponse
7+
from starlette.routing import Route
58
from starlette.testclient import TestClient
69

710
from databases import Database, DatabaseURL
@@ -29,6 +32,7 @@ def create_test_database():
2932
"postgresql+aiopg",
3033
"sqlite+aiosqlite",
3134
"postgresql+asyncpg",
35+
"postgresql+psycopg",
3236
]:
3337
url = str(database_url.replace(driver=None))
3438
engine = sqlalchemy.create_engine(url)
@@ -45,6 +49,7 @@ def create_test_database():
4549
"postgresql+aiopg",
4650
"sqlite+aiosqlite",
4751
"postgresql+asyncpg",
52+
"postgresql+psycopg",
4853
]:
4954
url = str(database_url.replace(driver=None))
5055
engine = sqlalchemy.create_engine(url)
@@ -53,17 +58,13 @@ def create_test_database():
5358

5459
def get_app(database_url):
5560
database = Database(database_url, force_rollback=True)
56-
app = Starlette()
5761

58-
@app.on_event("startup")
59-
async def startup():
62+
@contextlib.asynccontextmanager
63+
async def lifespan(app):
6064
await database.connect()
61-
62-
@app.on_event("shutdown")
63-
async def shutdown():
65+
yield
6466
await database.disconnect()
6567

66-
@app.route("/notes", methods=["GET"])
6768
async def list_notes(request):
6869
query = notes.select()
6970
results = await database.fetch_all(query)
@@ -73,14 +74,18 @@ async def list_notes(request):
7374
]
7475
return JSONResponse(content)
7576

76-
@app.route("/notes", methods=["POST"])
7777
async def add_note(request):
7878
data = await request.json()
7979
query = notes.insert().values(text=data["text"], completed=data["completed"])
8080
await database.execute(query)
8181
return JSONResponse({"text": data["text"], "completed": data["completed"]})
8282

83-
return app
83+
routes = [
84+
Route("/notes", list_notes, methods=["GET"]),
85+
Route("/notes", add_note, methods=["POST"]),
86+
]
87+
88+
return Starlette(routes=routes, lifespan=lifespan)
8489

8590

8691
@pytest.mark.parametrize("database_url", DATABASE_URLS)

0 commit comments

Comments
 (0)