Skip to content

Commit 584659f

Browse files
authored
fix: fixtures must not require asyncpg (#35)
fix #34
1 parent 145c498 commit 584659f

8 files changed

Lines changed: 71 additions & 20 deletions

File tree

.circleci/config.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ workflows:
2323
matrix:
2424
parameters:
2525
sqlalchemy_version: ["1.3", "1.4"]
26+
asyncpg: ["asyncpg", "noasyncpg"]
2627
- release/release:
2728
name: release
2829
requires:
@@ -49,19 +50,25 @@ workflows:
4950
jobs:
5051

5152
test:
53+
environment:
54+
CACHE_VERSION: "2021-05-02T10:18:17.640582"
5255
parameters:
5356
sqlalchemy_version:
5457
type: enum
5558
enum: ["1.4", "1.3"]
5659
description: |
5760
Specify which version of sqlalchemy to run the tests against
61+
asyncpg:
62+
type: enum
63+
enum: ["asyncpg", "noasyncpg"]
64+
description: To run tests with and without asyncpg installed.
5865
executor: python-postgres
5966
working_directory: ~/project/.
6067
steps:
6168
- base/setup
6269
- python/setup
6370
- utils/with_cache:
64-
key: 'sqlalchemy<<parameters.sqlalchemy_version>>-{{ checksum "pyproject.toml" }}-{{ checksum "poetry.lock" }}'
71+
key: 'sqlalchemy<<parameters.sqlalchemy_version>>-<<parameters.asyncpg>>-{{ checksum "pyproject.toml" }}-{{ checksum "poetry.lock" }}'
6572
namespace: tox
6673
path: ~/project/.tox
6774
steps:
@@ -70,11 +77,13 @@ jobs:
7077
command: |
7178
poetry run pip install -U tox
7279
- run:
73-
name: "run tox using sqlalchemy <<parameters.sqlalchemy_version>>.*"
80+
name: "run tox using sqlalchemy <<parameters.sqlalchemy_version>>.* and -<<parameters.asyncpg>>"
7481
command: |
75-
poetry run tox -e sqlalchemy<<parameters.sqlalchemy_version>>
82+
poetry run tox -e sqlalchemy<<parameters.sqlalchemy_version>>-<<parameters.asyncpg>>
83+
- store_test_results:
84+
path: test-reports
7685
- utils/send_coverage_to_codecov:
77-
codecov_flag: sqlalchemy<<parameters.sqlalchemy_version>>
86+
codecov_flag: sqlalchemy<<parameters.sqlalchemy_version>>-<<parameters.asyncpg>>
7887

7988
publish:
8089
docker:

fastapi_sqla/_pytest_plugin.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import os
22
from unittest.mock import patch
3+
from urllib.parse import urlsplit, urlunsplit
34

45
from alembic import command
56
from alembic.config import Config
67
from pytest import fixture
78
from sqlalchemy import create_engine, text
89

910
try:
11+
import asyncpg # noqa
1012
from sqlalchemy.ext.asyncio import create_async_engine
1113

1214
asyncio_support = True
@@ -102,16 +104,21 @@ def session(sqla_transaction, sqla_connection):
102104
session.close()
103105

104106

105-
if asyncio_support:
107+
def format_async_async_sqlalchemy_url(url):
108+
scheme, location, path, query, fragment = urlsplit(url)
109+
return urlunsplit([f"{scheme}+asyncpg", location, path, query, fragment])
110+
106111

107-
@fixture(scope="session")
108-
def async_sqlalchemy_url(db_url):
109-
"""Default async db url.
112+
@fixture(scope="session")
113+
def async_sqlalchemy_url(db_url):
114+
"""Default async db url.
110115
111-
It is the same as `db_url` with `postgresql+asynpg://` as scheme.
112-
"""
113-
scheme, parts = db_url.split(":")
114-
return f"{scheme}+asyncpg:{parts}"
116+
It is the same as `db_url` with `postgresql+asyncpg://` as scheme.
117+
"""
118+
return format_async_async_sqlalchemy_url(db_url)
119+
120+
121+
if asyncio_support:
115122

116123
@fixture
117124
async def async_engine(async_sqlalchemy_url):

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ tox = { version = "^3.23.0", optional = true}
6464
tests = [
6565
"alembic",
6666
"asgi_lifespan",
67-
"asyncpg",
6867
"black",
6968
"Faker",
7069
"httpx",
@@ -97,15 +96,15 @@ fastapi-sqla = "fastapi_sqla._pytest_plugin"
9796
legacy_tox_ini = """
9897
[tox]
9998
isolated_build = True
100-
envlist = sqlalchemy{1.3,1.4}
99+
envlist = sqlalchemy{ 1.3, 1.4 }-{ asyncpg, noasyncpg }
101100
102101
[testenv]
103102
passenv = CI
104103
deps =
105104
sqlalchemy1.3: sqlalchemy<1.4
106105
sqlalchemy1.4: sqlalchemy>=1.4,<2
106+
asyncpg: asyncpg
107107
extras =
108-
asyncpg
109108
tests
110109
commands = pytest -vv --cov={envsitepackagesdir}/fastapi_sqla --cov-report xml --cov-report html --junitxml=test-reports/pytest/junit.xml
111110
"""

tests/conftest.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ def pytest_configure(config):
1717
"sqlalchemy version"
1818
),
1919
)
20+
config.addinivalue_line(
21+
"markers", "require_asyncpg: skip test if asyncpg is not installed"
22+
)
2023

2124

2225
@fixture(scope="session")
@@ -26,13 +29,21 @@ def sqla_version_tuple():
2629
return tuple(int(i) for i in __version__.split("."))
2730

2831

32+
def is_asyncpg_installed():
33+
try:
34+
import asyncpg # noqa
35+
except ImportError:
36+
return False
37+
else:
38+
return True
39+
40+
2941
@fixture(scope="session", autouse=True)
30-
def environ(db_url, sqla_version_tuple):
42+
def environ(db_url, sqla_version_tuple, async_sqlalchemy_url):
3143
values = {"sqlalchemy_url": db_url, "PYTHONASYNCIODEBUG": "1"}
3244

33-
if sqla_version_tuple >= (1, 4, 0):
34-
scheme, parts = db_url.split(":")
35-
values["async_sqlalchemy_url"] = f"{scheme}+asyncpg:{parts}"
45+
if sqla_version_tuple >= (1, 4, 0) and is_asyncpg_installed():
46+
values["async_sqlalchemy_url"] = async_sqlalchemy_url
3647

3748
with patch.dict("os.environ", values=values, clear=True):
3849
yield values
@@ -76,6 +87,14 @@ def check_sqlalchemy_version(request, sqla_version_tuple):
7687
)
7788

7889

90+
@fixture(autouse=True)
91+
def check_asyncpg(request):
92+
"Skip test marked with mark.require_asyncpg if asyncpg is not installed."
93+
marker = request.node.get_closest_marker("require_asyncpg")
94+
if marker and not is_asyncpg_installed():
95+
skip("This test requires asyncpg. Skipping as asyncpg is not installed.")
96+
97+
7998
@fixture(scope="session")
8099
def faker():
81100
return Faker()

tests/test_asyncio_support.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from pytest import fixture, mark
22
from sqlalchemy import text
33

4-
pytestmark = [mark.asyncio, mark.sqlalchemy("1.4")]
4+
pytestmark = [mark.asyncio, mark.sqlalchemy("1.4"), mark.require_asyncpg]
55

66

77
@fixture(autouse=True)

tests/test_middleware.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ async def test_session_dependency(client, faker, session):
9595
assert row == (userid, first_name, last_name)
9696

9797

98+
@mark.require_asyncpg
9899
@mark.sqlalchemy("1.4")
99100
async def test_async_session_dependency(client, faker, async_session):
100101
userid = faker.unique.random_int()

tests/test_pagination.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ async def client(app):
222222
yield client
223223

224224

225+
@mark.require_asyncpg
225226
@mark.asyncio
226227
@mark.parametrize(
227228
"offset,items_number,path",

tests/test_pytest_plugin.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def test_session_fixture_does_not_write_in_db(session, singer_cls, engine):
4141
assert connection.execute(text("select count(*) from singer")).scalar() == 0
4242

4343

44+
@mark.require_asyncpg
4445
@mark.asyncio
4546
@mark.sqlalchemy("1.4")
4647
async def test_async_session_fixture_does_not_write_in_db(
@@ -66,6 +67,7 @@ def test_all_opened_sessions_are_within_the_same_transaction(
6667
assert other_session.query(singer_cls).get(1)
6768

6869

70+
@mark.require_asyncpg
6971
@mark.asyncio
7072
@mark.sqlalchemy("1.4")
7173
async def test_all_opened_async_sessions_are_within_the_same_transaction(
@@ -126,3 +128,16 @@ def test_anything(session):
126128
result = testdir.runpytest()
127129
result.assert_outcomes(errors=1)
128130
result.stdout.fnmatch_lines(["*sqla_modules fixture is not defined*"])
131+
132+
133+
@mark.parametrize(
134+
"url,expected",
135+
[
136+
("postgresql://localhost/db", "postgresql+asyncpg://localhost/db"),
137+
("postgresql://u:p@localhost/db", "postgresql+asyncpg://u:p@localhost/db"),
138+
],
139+
)
140+
def test_format_async_sqlalchemy_url(monkeypatch, conftest, testdir, url, expected):
141+
from fastapi_sqla._pytest_plugin import format_async_async_sqlalchemy_url
142+
143+
assert format_async_async_sqlalchemy_url(url) == expected

0 commit comments

Comments
 (0)