From 98effe6e55387fd74f493e7f78f356b49748d44f Mon Sep 17 00:00:00 2001 From: Daniel Thorn Date: Wed, 17 Aug 2022 12:59:02 -0700 Subject: [PATCH 1/2] Add support for Sanic 21+ --- src/dockerflow/sanic/checks.py | 7 +++- tests/constraints/sanic-20.txt | 4 +- tests/constraints/sanic-21.txt | 1 + tests/constraints/sanic-22.txt | 1 + tests/requirements/sanic-20.txt | 7 ++++ tests/requirements/sanic.txt | 5 ++- tests/sanic/test_sanic.py | 72 ++++++++++++++++++++------------- tox.ini | 10 +++-- 8 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 tests/constraints/sanic-21.txt create mode 100644 tests/constraints/sanic-22.txt create mode 100644 tests/requirements/sanic-20.txt diff --git a/src/dockerflow/sanic/checks.py b/src/dockerflow/sanic/checks.py index 332c865..50c8925 100644 --- a/src/dockerflow/sanic/checks.py +++ b/src/dockerflow/sanic/checks.py @@ -56,12 +56,17 @@ async def check_redis_connected(redis): """ import aioredis + if aioredis.__version__.startswith("1."): + RedisConnectionError = aioredis.ConnectionClosedError + else: + RedisConnectionError = aioredis.ConnectionError + errors = [] try: with await redis.conn as r: result = await r.ping() - except aioredis.ConnectionClosedError as e: + except RedisConnectionError as e: msg = "Could not connect to redis: {!s}".format(e) errors.append(Error(msg, id=health.ERROR_CANNOT_CONNECT_REDIS)) except aioredis.RedisError as e: diff --git a/tests/constraints/sanic-20.txt b/tests/constraints/sanic-20.txt index d450454..7c6c3ea 100644 --- a/tests/constraints/sanic-20.txt +++ b/tests/constraints/sanic-20.txt @@ -1 +1,3 @@ -Sanic<21 +Sanic>=20,<21 +aioredis<2 +sanic_redis<0.3.0 diff --git a/tests/constraints/sanic-21.txt b/tests/constraints/sanic-21.txt new file mode 100644 index 0000000..0d223de --- /dev/null +++ b/tests/constraints/sanic-21.txt @@ -0,0 +1 @@ +Sanic>=21,<22 diff --git a/tests/constraints/sanic-22.txt b/tests/constraints/sanic-22.txt new file mode 100644 index 0000000..97479fa --- /dev/null +++ b/tests/constraints/sanic-22.txt @@ -0,0 +1 @@ +Sanic>=22,<23 diff --git a/tests/requirements/sanic-20.txt b/tests/requirements/sanic-20.txt new file mode 100644 index 0000000..e11c6f2 --- /dev/null +++ b/tests/requirements/sanic-20.txt @@ -0,0 +1,7 @@ +# these are constrained by the files in tests/constraints/*.txt +# to support a triple stack Django/Flask/Sanic +aiohttp +aioredis +Sanic +sanic_redis +uvloop>=0.14.0rc1 diff --git a/tests/requirements/sanic.txt b/tests/requirements/sanic.txt index 41ec1d2..20ffec4 100644 --- a/tests/requirements/sanic.txt +++ b/tests/requirements/sanic.txt @@ -1,7 +1,8 @@ # these are constrained by the files in tests/constraints/*.txt # to support a triple stack Django/Flask/Sanic aiohttp -aioredis<2.0.0 +aioredis Sanic -sanic_redis<0.3.0 +sanic_redis +sanic-testing uvloop>=0.14.0rc1 diff --git a/tests/sanic/test_sanic.py b/tests/sanic/test_sanic.py index f3682a2..e31b285 100644 --- a/tests/sanic/test_sanic.py +++ b/tests/sanic/test_sanic.py @@ -3,11 +3,11 @@ # file, you can obtain one at http://mozilla.org/MPL/2.0/. import functools import logging -import socket +import uuid import aioredis import pytest -import sanic.testing +import sanic import sanic_redis.core from sanic import Sanic, response from sanic_redis import SanicRedis @@ -15,9 +15,14 @@ from dockerflow import health from dockerflow.sanic import Dockerflow, checks +if sanic.__version__.startswith("20."): + from sanic.testing import SanicTestClient +else: + from sanic_testing.testing import SanicTestClient + class FakeRedis: - def __init__(self, error=None, **kw): + def __init__(self, *args, error=None, **kw): self.error = error def __await__(self): @@ -30,7 +35,7 @@ def __enter__(self): def __exit__(self, *exc_info): pass - def close(self): + async def close(self): pass async def wait_closed(self): @@ -38,7 +43,11 @@ async def wait_closed(self): async def ping(self): if self.error == "connection": - raise aioredis.ConnectionClosedError("fake") + if aioredis.__version__.startswith("1."): + RedisConnectionError = aioredis.ConnectionClosedError + else: + RedisConnectionError = aioredis.ConnectionError + raise RedisConnectionError("fake") elif self.error == "redis": raise aioredis.RedisError("fake") elif self.error == "malformed": @@ -47,13 +56,20 @@ async def ping(self): return b"PONG" -async def fake_redis(**kw): - return FakeRedis(**kw) +class FakeRedis1(FakeRedis): + def close(self): + pass + + +async def fake_redis(*args, **kw): + if aioredis.__version__.startswith("1."): + return FakeRedis1(*args, **kw) + return FakeRedis(*args, **kw) @pytest.fixture(scope="function") def app(): - app = Sanic("dockerflow") + app = Sanic(f"dockerflow-{uuid.uuid4().hex}") @app.route("/") async def root(request): @@ -77,28 +93,20 @@ def dockerflow_redis(app): @pytest.fixture def test_client(app): - # Create SanicTestClient manually and provide a socket object instead of host - # and port when calling Sanic.run in order to avoid parallel test failures - # caused by Sanic.test_client bindngs to a static port - s = socket.socket() - s.bind((sanic.testing.HOST, 0)) - try: - # initialize test_client with socket's port - test_client = sanic.testing.SanicTestClient(app, s.getsockname()[1]) - # override app.run to drop host and port in favor of socket - run = app.run - app.run = lambda host, port, **kw: run(sock=s, **kw) - # yield test_client - yield test_client - finally: - s.close() + return SanicTestClient(app) def test_instantiating(app): - dockerflow = Dockerflow() - assert "dockerflow.heartbeat" not in app.router.routes_names - dockerflow.init_app(app) - assert "dockerflow.heartbeat" in app.router.routes_names + Dockerflow() + if sanic.__version__.startswith("20."): + assert "dockerflow.heartbeat" not in app.router.routes_names + else: + assert ("__heartbeat__",) not in app.router.routes_all + Dockerflow(app) + if sanic.__version__.startswith("20."): + assert "dockerflow.heartbeat" in app.router.routes_names + else: + assert ("__heartbeat__",) in app.router.routes_all def test_version_exists(dockerflow, mocker, test_client, version_content): @@ -178,7 +186,10 @@ async def warning_check2(): def test_redis_check(dockerflow_redis, mocker, test_client): assert "check_redis_connected" in dockerflow_redis.checks - mocker.patch.object(sanic_redis.core, "create_redis_pool", fake_redis) + if aioredis.__version__.startswith("1."): + mocker.patch.object(sanic_redis.core, "create_redis_pool", fake_redis) + else: + mocker.patch.object(sanic_redis.core, "from_url", fake_redis) _, response = test_client.get("/__heartbeat__") assert response.status == 200 assert response.json["status"] == "ok" @@ -198,7 +209,10 @@ def test_redis_check(dockerflow_redis, mocker, test_client): def test_redis_check_error(dockerflow_redis, mocker, test_client, error, messages): assert "check_redis_connected" in dockerflow_redis.checks fake_redis_error = functools.partial(fake_redis, error=error) - mocker.patch.object(sanic_redis.core, "create_redis_pool", fake_redis_error) + if aioredis.__version__.startswith("1."): + mocker.patch.object(sanic_redis.core, "create_redis_pool", fake_redis_error) + else: + mocker.patch.object(sanic_redis.core, "from_url", fake_redis_error) _, response = test_client.get("/__heartbeat__") assert response.status == 500 assert response.json["status"] == "error" diff --git a/tox.ini b/tox.ini index 7f04c32..378e2a4 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,8 @@ envlist = py{37,38,39,310}-dj32 py{38,39,310}-dj{40} py{37,38,39,310}-fl{012,10,11,20,21} - py{37,38,39}-s{20} + py{37,38,39}-s20 + py{37,38,39,310}-s{21,22} [gh-actions] python = @@ -26,7 +27,8 @@ deps = -rtests/requirements/default.txt dj{32,40}: -rtests/requirements/django.txt fl{012,10,11,20,21}: -rtests/requirements/flask.txt - s{20}: -rtests/requirements/sanic.txt + s20: -rtests/requirements/sanic-20.txt + s{21,22}: -rtests/requirements/sanic.txt dj32: -ctests/constraints/django-3.2.txt dj40: -ctests/constraints/django-4.0.txt fl012: -ctests/constraints/flask-0.12.txt @@ -35,11 +37,13 @@ deps = fl20: -ctests/constraints/flask-2.0.txt fl21: -ctests/constraints/flask-2.0.txt s20: -ctests/constraints/sanic-20.txt + s21: -ctests/constraints/sanic-21.txt + s22: -ctests/constraints/sanic-22.txt commands = python --version dj{32,40}: pytest tests/core/ tests/django --nomigrations {posargs:} fl{012,10,11,20,21}: pytest tests/core/ tests/flask/ {posargs:} - s{20}: pytest tests/core/ tests/sanic/ {posargs:} + s{20,21,22}: pytest tests/core/ tests/sanic/ {posargs:} [testenv:py38-docs] basepython = python3.8 From 8e68ddf824a113657b781294834d5842bce80342 Mon Sep 17 00:00:00 2001 From: Daniel Thorn Date: Thu, 18 Aug 2022 09:40:46 -0700 Subject: [PATCH 2/2] address review --- tests/sanic/test_sanic.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/sanic/test_sanic.py b/tests/sanic/test_sanic.py index e31b285..b3c4be5 100644 --- a/tests/sanic/test_sanic.py +++ b/tests/sanic/test_sanic.py @@ -96,17 +96,22 @@ def test_client(app): return SanicTestClient(app) +@pytest.mark.skipif(not sanic.__version__.startswith("20."), reason="requires sanic 20") +def test_instantiating_sanic_20(app): + dockerflow = Dockerflow() + assert "dockerflow.heartbeat" not in app.router.routes_names + dockerflow.init_app(app) + assert "dockerflow.heartbeat" in app.router.routes_names + + +@pytest.mark.skipif( + sanic.__version__.startswith("20."), reason="requires sanic 21 or later" +) def test_instantiating(app): Dockerflow() - if sanic.__version__.startswith("20."): - assert "dockerflow.heartbeat" not in app.router.routes_names - else: - assert ("__heartbeat__",) not in app.router.routes_all + assert ("__heartbeat__",) not in app.router.routes_all Dockerflow(app) - if sanic.__version__.startswith("20."): - assert "dockerflow.heartbeat" in app.router.routes_names - else: - assert ("__heartbeat__",) in app.router.routes_all + assert ("__heartbeat__",) in app.router.routes_all def test_version_exists(dockerflow, mocker, test_client, version_content):