Skip to content

Commit

Permalink
Add and test psycopg3 support
Browse files Browse the repository at this point in the history
  • Loading branch information
ansipunk committed Mar 2, 2024
1 parent ae3fb16 commit 09e2dd7
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 22 deletions.
9 changes: 5 additions & 4 deletions .github/workflows/test-suite.yml
Expand Up @@ -18,18 +18,18 @@ jobs:

services:
mysql:
image: mysql:5.7
image: mariadb:11
env:
MYSQL_USER: username
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testsuite
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=3

postgres:
image: postgres:14
image: postgres:16
env:
POSTGRES_USER: username
POSTGRES_PASSWORD: password
Expand Down Expand Up @@ -59,5 +59,6 @@ jobs:
mysql+asyncmy://username:password@localhost:3306/testsuite,
postgresql://username:password@localhost:5432/testsuite,
postgresql+aiopg://username:password@127.0.0.1:5432/testsuite,
postgresql+asyncpg://username:password@localhost:5432/testsuite
postgresql+asyncpg://username:password@localhost:5432/testsuite,
postgresql+psycopg://username:password@localhost:5432/testsuite
run: "scripts/test"
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -33,6 +33,7 @@ Database drivers supported are:

* [asyncpg][asyncpg]
* [aiopg][aiopg]
* [psycopg3][psycopg3]
* [aiomysql][aiomysql]
* [asyncmy][asyncmy]
* [aiosqlite][aiosqlite]
Expand All @@ -42,6 +43,7 @@ You can install the required database drivers with:
```shell
$ pip install databases[asyncpg]
$ pip install databases[aiopg]
$ pip install databases[psycopg3]
$ pip install databases[aiomysql]
$ pip install databases[asyncmy]
$ pip install databases[aiosqlite]
Expand Down Expand Up @@ -105,6 +107,7 @@ for examples of how to start using databases together with SQLAlchemy core expre
[pymysql]: https://github.com/PyMySQL/PyMySQL
[asyncpg]: https://github.com/MagicStack/asyncpg
[aiopg]: https://github.com/aio-libs/aiopg
[psycopg3]: https://github.com/psycopg/psycopg
[aiomysql]: https://github.com/aio-libs/aiomysql
[asyncmy]: https://github.com/long2ice/asyncmy
[aiosqlite]: https://github.com/omnilib/aiosqlite
Expand Down
2 changes: 0 additions & 2 deletions databases/backends/common/records.py
@@ -1,6 +1,4 @@
import enum
import typing
from datetime import date, datetime, time

from sqlalchemy.engine.interfaces import Dialect
from sqlalchemy.engine.row import Row as SQLRow
Expand Down
3 changes: 3 additions & 0 deletions docs/index.md
Expand Up @@ -31,6 +31,7 @@ Database drivers supported are:

* [asyncpg][asyncpg]
* [aiopg][aiopg]
* [psycopg3][psycopg3]
* [aiomysql][aiomysql]
* [asyncmy][asyncmy]
* [aiosqlite][aiosqlite]
Expand All @@ -40,6 +41,7 @@ You can install the required database drivers with:
```shell
$ pip install databases[asyncpg]
$ pip install databases[aiopg]
$ pip install databases[psycopg3]
$ pip install databases[aiomysql]
$ pip install databases[asyncmy]
$ pip install databases[aiosqlite]
Expand Down Expand Up @@ -103,6 +105,7 @@ for examples of how to start using databases together with SQLAlchemy core expre
[pymysql]: https://github.com/PyMySQL/PyMySQL
[asyncpg]: https://github.com/MagicStack/asyncpg
[aiopg]: https://github.com/aio-libs/aiopg
[psycopg3]: https://github.com/psycopg/psycopg
[aiomysql]: https://github.com/aio-libs/aiomysql
[asyncmy]: https://github.com/long2ice/asyncmy
[aiosqlite]: https://github.com/omnilib/aiosqlite
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Expand Up @@ -6,9 +6,12 @@ aiomysql==0.2.0
aiopg==1.4.0
aiosqlite==0.20.0
asyncpg==0.29.0
psycopg==3.1.18
psycopg-binary==3.1.18

# Sync database drivers for standard tooling around setup/teardown/migrations.
psycopg==3.1.18
psycopg-binary==3.1.18
pymysql==1.1.0

# Testing
Expand Down
9 changes: 5 additions & 4 deletions setup.py
Expand Up @@ -47,14 +47,15 @@ def get_packages(package):
author_email="tom@tomchristie.com",
packages=get_packages("databases"),
package_data={"databases": ["py.typed"]},
install_requires=["sqlalchemy>=2.0.7"],
install_requires=["sqlalchemy>=2.0.11"],
extras_require={
"postgresql": ["asyncpg"],
"asyncpg": ["asyncpg"],
"aiopg": ["aiopg"],
"mysql": ["aiomysql"],
"aiomysql": ["aiomysql"],
"asyncmy": ["asyncmy"],
"postgresql": ["asyncpg"],
"aiopg": ["aiopg"],
"asyncpg": ["asyncpg"],
"psycopg3": ["psycopg"],
"sqlite": ["aiosqlite"],
"aiosqlite": ["aiosqlite"],
},
Expand Down
20 changes: 17 additions & 3 deletions tests/test_databases.py
Expand Up @@ -134,6 +134,7 @@ def create_test_database():
"postgresql+aiopg",
"sqlite+aiosqlite",
"postgresql+asyncpg",
"postgresql+psycopg",
]:
url = str(database_url.replace(driver=None))
engine = sqlalchemy.create_engine(url)
Expand All @@ -151,6 +152,7 @@ def create_test_database():
"postgresql+aiopg",
"sqlite+aiosqlite",
"postgresql+asyncpg",
"postgresql+psycopg",
]:
url = str(database_url.replace(driver=None))
engine = sqlalchemy.create_engine(url)
Expand Down Expand Up @@ -1354,7 +1356,11 @@ async def test_queries_with_expose_backend_connection(database_url):
elif database.url.scheme == "mysql+asyncmy":
async with raw_connection.cursor() as cursor:
await cursor.execute(insert_query, values)
elif database.url.scheme in ["postgresql", "postgresql+asyncpg"]:
elif database.url.scheme in [
"postgresql",
"postgresql+asyncpg",
"postgresql+psycopg",
]:
await raw_connection.execute(insert_query, *values)
elif database.url.scheme in ["sqlite", "sqlite+aiosqlite"]:
await raw_connection.execute(insert_query, values)
Expand Down Expand Up @@ -1392,7 +1398,11 @@ async def test_queries_with_expose_backend_connection(database_url):
async with raw_connection.cursor() as cursor:
await cursor.execute(select_query)
results = await cursor.fetchall()
elif database.url.scheme in ["postgresql", "postgresql+asyncpg"]:
elif database.url.scheme in [
"postgresql",
"postgresql+asyncpg",
"postgresql+psycopg",
]:
results = await raw_connection.fetch(select_query)
elif database.url.scheme in ["sqlite", "sqlite+aiosqlite"]:
results = await raw_connection.execute_fetchall(select_query)
Expand All @@ -1407,7 +1417,11 @@ async def test_queries_with_expose_backend_connection(database_url):
assert results[2][2] == True

# fetch_one()
if database.url.scheme in ["postgresql", "postgresql+asyncpg"]:
if database.url.scheme in [
"postgresql",
"postgresql+asyncpg",
"postgresql+psycopg",
]:
result = await raw_connection.fetchrow(select_query)
elif database.url.scheme == "mysql+asyncmy":
async with raw_connection.cursor() as cursor:
Expand Down
23 changes: 14 additions & 9 deletions tests/test_integration.py
@@ -1,7 +1,10 @@
import contextlib

import pytest
import sqlalchemy
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from starlette.testclient import TestClient

from databases import Database, DatabaseURL
Expand Down Expand Up @@ -29,6 +32,7 @@ def create_test_database():
"postgresql+aiopg",
"sqlite+aiosqlite",
"postgresql+asyncpg",
"postgresql+psycopg",
]:
url = str(database_url.replace(driver=None))
engine = sqlalchemy.create_engine(url)
Expand All @@ -45,6 +49,7 @@ def create_test_database():
"postgresql+aiopg",
"sqlite+aiosqlite",
"postgresql+asyncpg",
"postgresql+psycopg",
]:
url = str(database_url.replace(driver=None))
engine = sqlalchemy.create_engine(url)
Expand All @@ -53,17 +58,13 @@ def create_test_database():

def get_app(database_url):
database = Database(database_url, force_rollback=True)
app = Starlette()

@app.on_event("startup")
async def startup():
@contextlib.asynccontextmanager
async def lifespan(app):
await database.connect()

@app.on_event("shutdown")
async def shutdown():
yield
await database.disconnect()

@app.route("/notes", methods=["GET"])
async def list_notes(request):
query = notes.select()
results = await database.fetch_all(query)
Expand All @@ -73,14 +74,18 @@ async def list_notes(request):
]
return JSONResponse(content)

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

return app
routes = [
Route("/notes", list_notes, methods=["GET"]),
Route("/notes", add_note, methods=["POST"]),
]

return Starlette(routes=routes, lifespan=lifespan)


@pytest.mark.parametrize("database_url", DATABASE_URLS)
Expand Down

0 comments on commit 09e2dd7

Please sign in to comment.