From 31fb9ba5638ff3870fd3b7826df6f6842069741a Mon Sep 17 00:00:00 2001 From: Chris Guidry Date: Thu, 18 Jul 2024 08:25:12 -0400 Subject: [PATCH 1/3] Uses `testcontainers` to provide a redis instance for the unit tests We can't assume that a dev would want the unit tests to connect to the locally running redis at `localhost:6379`, so I'd rather shift that to assuming that the user has a Docker-compatible system available. This makes the test suite easier to reason about and lets us control the redis version with more precision, and it makes the initial startup experience easier for new devs. --- .github/workflows/ci.yml | 100 +++++++++++++++++-------------------- .gitignore | 1 + requirements/docs.txt | 8 +-- requirements/pyproject.txt | 2 +- requirements/testing.in | 1 + requirements/testing.txt | 20 ++++++++ tests/conftest.py | 50 +++++++++++++++---- tests/test_cli.py | 6 +++ tests/test_jobs.py | 4 +- tests/test_main.py | 10 ++-- tests/test_utils.py | 13 +++-- tests/test_worker.py | 19 +++---- 12 files changed, 144 insertions(+), 90 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c51aca00..f8c3aecb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: branches: - main tags: - - '**' + - "**" pull_request: {} jobs: @@ -13,40 +13,40 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: set up python - uses: actions/setup-python@v5 - with: - python-version: '3.11' + - name: set up python + uses: actions/setup-python@v5 + with: + python-version: "3.11" - - run: pip install -r requirements/linting.txt -r requirements/pyproject.txt pre-commit + - run: pip install -r requirements/linting.txt -r requirements/pyproject.txt pre-commit - - run: pre-commit run -a --verbose - env: - SKIP: no-commit-to-branch + - run: pre-commit run -a --verbose + env: + SKIP: no-commit-to-branch docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: set up python - uses: actions/setup-python@v5 - with: - python-version: '3.11' + - name: set up python + uses: actions/setup-python@v5 + with: + python-version: "3.11" - - run: pip install -r requirements/docs.txt -r requirements/pyproject.txt - - run: pip install . + - run: pip install -r requirements/docs.txt -r requirements/pyproject.txt + - run: pip install . - - run: make docs + - run: make docs - - name: Store docs site - uses: actions/upload-artifact@v4 - with: - name: docs - path: docs/_build/ + - name: Store docs site + uses: actions/upload-artifact@v4 + with: + name: docs + path: docs/_build/ test: name: test py${{ matrix.python }} with redis:${{ matrix.redis }} on ${{ matrix.os }} @@ -54,47 +54,41 @@ jobs: fail-fast: false matrix: os: [ubuntu] - python: ['3.8', '3.9', '3.10', '3.11', '3.12'] - redis: ['5'] + python: ["3.8", "3.9", "3.10", "3.11", "3.12"] + redis: ["5"] include: - - python: '3.11' - redis: '6' - os: 'ubuntu' - - python: '3.11' - redis: '7' - os: 'ubuntu' + - python: "3.11" + redis: "6" + os: "ubuntu" + - python: "3.11" + redis: "7" + os: "ubuntu" env: PYTHON: ${{ matrix.python }} OS: ${{ matrix.os }} + ARQ_TEST_REDIS_VERSION: ${{ matrix.redis }} runs-on: ${{ matrix.os }}-latest - services: - redis: - image: redis:${{ matrix.redis }} - ports: - - 6379:6379 - options: --entrypoint redis-server - steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: set up python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} + - name: set up python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} - - run: pip install -r requirements/testing.txt -r requirements/pyproject.txt + - run: pip install -r requirements/testing.txt -r requirements/pyproject.txt - - run: make test + - run: make test - - run: coverage xml + - run: coverage xml - - uses: codecov/codecov-action@v4 - with: - file: ./coverage.xml - env_vars: PYTHON,OS + - uses: codecov/codecov-action@v4 + with: + file: ./coverage.xml + env_vars: PYTHON,OS check: if: always() @@ -130,7 +124,7 @@ jobs: - name: set up python uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: "3.11" - name: install run: pip install -U build @@ -139,7 +133,7 @@ jobs: id: check-version uses: samuelcolvin/check-python-version@v3.2 with: - version_file_path: 'arq/version.py' + version_file_path: "arq/version.py" - name: build run: python -m build @@ -148,7 +142,7 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 - name: publish docs - if: '!fromJSON(steps.check-version.outputs.IS_PRERELEASE)' + if: "!fromJSON(steps.check-version.outputs.IS_PRERELEASE)" run: make publish-docs env: NETLIFY: ${{ secrets.netlify_token }} diff --git a/.gitignore b/.gitignore index e2d3e183..feb4a6ad 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ __pycache__/ .venv/ /.auto-format /scratch/ +.python-version diff --git a/requirements/docs.txt b/requirements/docs.txt index 2ce9f8c2..ad4ccc2f 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -8,13 +8,13 @@ alabaster==0.7.16 # via sphinx babel==2.14.0 # via sphinx -certifi==2024.2.2 +certifi==2024.7.4 # via requests charset-normalizer==3.3.2 # via requests docutils==0.19 # via sphinx -idna==3.6 +idna==3.7 # via requests imagesize==1.4.1 # via sphinx @@ -26,7 +26,7 @@ packaging==24.0 # via sphinx pygments==2.17.2 # via sphinx -requests==2.31.0 +requests==2.32.3 # via sphinx snowballstemmer==2.2.0 # via sphinx @@ -44,5 +44,5 @@ sphinxcontrib-qthelp==1.0.7 # via sphinx sphinxcontrib-serializinghtml==1.1.10 # via sphinx -urllib3==2.2.1 +urllib3==2.2.2 # via requests diff --git a/requirements/pyproject.txt b/requirements/pyproject.txt index bc79cb18..041adfac 100644 --- a/requirements/pyproject.txt +++ b/requirements/pyproject.txt @@ -10,7 +10,7 @@ click==8.1.7 # via arq (pyproject.toml) hiredis==2.3.2 # via redis -idna==3.6 +idna==3.7 # via anyio redis==4.6.0 # via arq (pyproject.toml) diff --git a/requirements/testing.in b/requirements/testing.in index eb019d9d..bc9f2a2a 100644 --- a/requirements/testing.in +++ b/requirements/testing.in @@ -8,3 +8,4 @@ pytest-mock pytest-pretty pytest-timeout pytz +testcontainers diff --git a/requirements/testing.txt b/requirements/testing.txt index 77e186a6..88a825c2 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -6,10 +6,18 @@ # annotated-types==0.6.0 # via pydantic +certifi==2024.7.4 + # via requests +charset-normalizer==3.3.2 + # via requests coverage==7.4.4 # via -r requirements/testing.in dirty-equals==0.7.1.post0 # via -r requirements/testing.in +docker==7.1.0 + # via testcontainers +idna==3.7 + # via requests iniconfig==2.0.0 # via pytest markdown-it-py==3.0.0 @@ -47,9 +55,21 @@ pytz==2024.1 # via # -r requirements/testing.in # dirty-equals +requests==2.32.3 + # via docker rich==13.7.1 # via pytest-pretty +testcontainers==4.7.2 + # via -r requirements/testing.in typing-extensions==4.10.0 # via # pydantic # pydantic-core + # testcontainers +urllib3==2.2.2 + # via + # docker + # requests + # testcontainers +wrapt==1.16.0 + # via testcontainers diff --git a/tests/conftest.py b/tests/conftest.py index b9332eed..99b33599 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,27 +2,55 @@ import functools import os import sys +from typing import Generator import msgpack import pytest import redis.exceptions from redis.asyncio.retry import Retry from redis.backoff import NoBackoff +from testcontainers.redis import RedisContainer -from arq.connections import ArqRedis, create_pool +from arq.connections import ArqRedis, RedisSettings, create_pool from arq.worker import Worker @pytest.fixture(name='loop') -def _fix_loop(event_loop): +def _fix_loop(event_loop: asyncio.AbstractEventLoop) -> asyncio.AbstractEventLoop: return event_loop +@pytest.fixture(scope='session') +def redis_version() -> str: + return os.getenv('ARQ_TEST_REDIS_VERSION', 'latest') + + +@pytest.fixture(scope='session') +def redis_container(redis_version: str) -> Generator[RedisContainer, None, None]: + with RedisContainer(f'redis:{redis_version}') as redis: + yield redis + + +@pytest.fixture(scope='session') +def test_redis_host(redis_container: RedisContainer) -> str: + return redis_container.get_container_host_ip() + + +@pytest.fixture(scope='session') +def test_redis_port(redis_container: RedisContainer) -> int: + return redis_container.get_exposed_port(redis_container.port) + + +@pytest.fixture(scope='session') +def test_redis_settings(test_redis_host: str, test_redis_port: int) -> RedisSettings: + return RedisSettings(host=test_redis_host, port=test_redis_port) + + @pytest.fixture -async def arq_redis(loop): +async def arq_redis(test_redis_host: str, test_redis_port: int): redis_ = ArqRedis( - host='localhost', - port=6379, + host=test_redis_host, + port=test_redis_port, encoding='utf-8', ) @@ -34,10 +62,10 @@ async def arq_redis(loop): @pytest.fixture -async def arq_redis_msgpack(loop): +async def arq_redis_msgpack(test_redis_host: str, test_redis_port: int): redis_ = ArqRedis( - host='localhost', - port=6379, + host=test_redis_host, + port=test_redis_port, encoding='utf-8', job_serializer=msgpack.packb, job_deserializer=functools.partial(msgpack.unpackb, raw=False), @@ -48,10 +76,10 @@ async def arq_redis_msgpack(loop): @pytest.fixture -async def arq_redis_retry(loop): +async def arq_redis_retry(test_redis_host: str, test_redis_port: int): redis_ = ArqRedis( - host='localhost', - port=6379, + host=test_redis_host, + port=test_redis_port, encoding='utf-8', retry=Retry(backoff=NoBackoff(), retries=3), retry_on_timeout=True, diff --git a/tests/test_cli.py b/tests/test_cli.py index dbe60af0..8cd98028 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,6 +3,7 @@ from arq import logs from arq.cli import cli +from arq.connections import RedisSettings async def foobar(ctx): @@ -14,6 +15,11 @@ class WorkerSettings: functions = [foobar] +@pytest.fixture(scope='module', autouse=True) +def setup_worker_connection(test_redis_host: str, test_redis_port: int): + WorkerSettings.redis_settings = RedisSettings(host=test_redis_host, port=test_redis_port) + + def test_help(): runner = CliRunner() result = runner.invoke(cli, ['--help']) diff --git a/tests/test_jobs.py b/tests/test_jobs.py index c30113d7..7266a9b3 100644 --- a/tests/test_jobs.py +++ b/tests/test_jobs.py @@ -114,9 +114,9 @@ async def test_enqueue_job_alt_queue(arq_redis: ArqRedis, worker): await test_enqueue_job(arq_redis, worker, queue_name='custom_queue') -async def test_enqueue_job_nondefault_queue(worker): +async def test_enqueue_job_nondefault_queue(test_redis_settings: RedisSettings, worker): """Test initializing arq_redis with a queue name, and the worker using it.""" - arq_redis = await create_pool(RedisSettings(), default_queue_name='test_queue') + arq_redis = await create_pool(test_redis_settings, default_queue_name='test_queue') await test_enqueue_job( arq_redis, lambda functions, **_: worker(functions=functions, arq_redis=arq_redis, queue_name=None), diff --git a/tests/test_main.py b/tests/test_main.py index 198c815b..baf03ee8 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -11,7 +11,7 @@ import pytest from dirty_equals import IsInt, IsNow -from arq.connections import ArqRedis +from arq.connections import ArqRedis, RedisSettings from arq.constants import default_queue_name from arq.jobs import Job, JobDef, SerializationError from arq.utils import timestamp_ms @@ -65,7 +65,9 @@ async def parent_job(ctx): assert inner_result == 42 -async def test_enqueue_job_nested_custom_serializer(arq_redis_msgpack: ArqRedis, worker): +async def test_enqueue_job_nested_custom_serializer( + arq_redis_msgpack: ArqRedis, test_redis_settings: RedisSettings, worker +): async def foobar(ctx): return 42 @@ -78,6 +80,7 @@ async def parent_job(ctx): worker: Worker = worker( functions=[func(parent_job, name='parent_job'), func(foobar, name='foobar')], arq_redis=None, + redis_settings=test_redis_settings, job_serializer=msgpack.packb, job_deserializer=functools.partial(msgpack.unpackb, raw=False), ) @@ -90,7 +93,7 @@ async def parent_job(ctx): assert inner_result == 42 -async def test_enqueue_job_custom_queue(arq_redis: ArqRedis, worker): +async def test_enqueue_job_custom_queue(arq_redis: ArqRedis, test_redis_settings: RedisSettings, worker): async def foobar(ctx): return 42 @@ -103,6 +106,7 @@ async def parent_job(ctx): worker: Worker = worker( functions=[func(parent_job, name='parent_job'), func(foobar, name='foobar')], arq_redis=None, + redis_settings=test_redis_settings, queue_name='spanner', ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 9de66f88..96c9a25c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -55,20 +55,19 @@ async def test_redis_sentinel_failure(create_pool, cancel_remaining_task, mocker await create_pool(settings) -async def test_redis_success_log(caplog, create_pool): +async def test_redis_success_log(test_redis_settings: RedisSettings, caplog, create_pool): caplog.set_level(logging.INFO) - settings = RedisSettings() - pool = await create_pool(settings) + pool = await create_pool(test_redis_settings) assert 'redis connection successful' not in [r.message for r in caplog.records] await pool.close(close_connection_pool=True) - pool = await create_pool(settings, retry=1) + pool = await create_pool(test_redis_settings, retry=1) assert 'redis connection successful' in [r.message for r in caplog.records] await pool.close(close_connection_pool=True) -async def test_redis_log(create_pool): - redis = await create_pool(RedisSettings()) +async def test_redis_log(test_redis_settings: RedisSettings, create_pool): + redis = await create_pool(test_redis_settings) await redis.flushall() await redis.set(b'a', b'1') await redis.set(b'b', b'2') @@ -110,7 +109,7 @@ def test_typing(): assert 'OptionType' in arq.typing.__all__ -def test_redis_settings_validation(): +def redis_settings_validation(): class Settings(BaseModel, arbitrary_types_allowed=True): redis_settings: RedisSettings diff --git a/tests/test_worker.py b/tests/test_worker.py index a25f0f1d..141257c1 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -35,12 +35,13 @@ async def fails(ctx): raise TypeError('my type error') -def test_no_jobs(arq_redis: ArqRedis, loop, mocker): +def test_no_jobs(test_redis_settings: RedisSettings, arq_redis: ArqRedis, loop, mocker): class Settings: functions = [func(foobar, name='foobar')] burst = True poll_delay = 0 queue_read_limit = 10 + redis_settings = test_redis_settings loop.run_until_complete(arq_redis.enqueue_job('foobar')) mocker.patch('asyncio.get_event_loop', lambda: loop) @@ -49,21 +50,21 @@ class Settings: assert str(worker) == '' -def test_health_check_direct(loop): +def test_health_check_direct(test_redis_settings: RedisSettings, loop): class Settings: - pass + redis_settings = test_redis_settings asyncio.set_event_loop(loop) assert check_health(Settings) == 1 -async def test_health_check_fails(): - assert 1 == await async_check_health(None) +async def test_health_check_fails(test_redis_settings: RedisSettings): + assert 1 == await async_check_health(test_redis_settings) -async def test_health_check_pass(arq_redis): +async def test_health_check_pass(test_redis_settings: RedisSettings, arq_redis: ArqRedis): await arq_redis.set(default_queue_name + health_check_key_suffix, b'1') - assert 0 == await async_check_health(None) + assert 0 == await async_check_health(test_redis_settings) async def test_set_health_check_key(arq_redis: ArqRedis, worker): @@ -479,8 +480,8 @@ async def test_log_health_check(arq_redis: ArqRedis, worker, caplog): assert 'recording health' in caplog.text -async def test_remain_keys(arq_redis: ArqRedis, worker, create_pool): - redis2 = await create_pool(RedisSettings()) +async def test_remain_keys(test_redis_settings: RedisSettings, arq_redis: ArqRedis, worker, create_pool): + redis2 = await create_pool(test_redis_settings) await arq_redis.enqueue_job('foobar', _job_id='testing') assert sorted(await redis2.keys('*')) == [b'arq:job:testing', b'arq:queue'] worker: Worker = worker(functions=[foobar]) From d2a552cd017c48d7273482c41631a20192957148 Mon Sep 17 00:00:00 2001 From: Chris Guidry Date: Thu, 18 Jul 2024 08:41:34 -0400 Subject: [PATCH 2/3] Downgrade `testcontainers` to a 3.8-compatible version --- requirements/testing.in | 2 +- requirements/testing.txt | 18 +++++++++++++----- tests/conftest.py | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/requirements/testing.in b/requirements/testing.in index bc9f2a2a..7f12cc70 100644 --- a/requirements/testing.in +++ b/requirements/testing.in @@ -8,4 +8,4 @@ pytest-mock pytest-pretty pytest-timeout pytz -testcontainers +testcontainers<4 # until we remove 3.8 support diff --git a/requirements/testing.txt b/requirements/testing.txt index 88a825c2..87461fd6 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.9 # by the following command: # # pip-compile --output-file=requirements/testing.txt --strip-extras requirements/testing.in @@ -12,10 +12,14 @@ charset-normalizer==3.3.2 # via requests coverage==7.4.4 # via -r requirements/testing.in +deprecation==2.1.0 + # via testcontainers dirty-equals==0.7.1.post0 # via -r requirements/testing.in docker==7.1.0 # via testcontainers +exceptiongroup==1.2.2 + # via pytest idna==3.7 # via requests iniconfig==2.0.0 @@ -27,7 +31,9 @@ mdurl==0.1.2 msgpack==1.0.8 # via -r requirements/testing.in packaging==24.0 - # via pytest + # via + # deprecation + # pytest pluggy==1.4.0 # via pytest pydantic==2.6.4 @@ -59,17 +65,19 @@ requests==2.32.3 # via docker rich==13.7.1 # via pytest-pretty -testcontainers==4.7.2 +testcontainers==3.7.1 # via -r requirements/testing.in +tomli==2.0.1 + # via + # coverage + # pytest typing-extensions==4.10.0 # via # pydantic # pydantic-core - # testcontainers urllib3==2.2.2 # via # docker # requests - # testcontainers wrapt==1.16.0 # via testcontainers diff --git a/tests/conftest.py b/tests/conftest.py index 99b33599..9b6b7f5b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,7 +38,7 @@ def test_redis_host(redis_container: RedisContainer) -> str: @pytest.fixture(scope='session') def test_redis_port(redis_container: RedisContainer) -> int: - return redis_container.get_exposed_port(redis_container.port) + return redis_container.get_exposed_port(redis_container.port_to_expose) @pytest.fixture(scope='session') From 3302134f2b3cb9e0c88496eda14be4cac3542f41 Mon Sep 17 00:00:00 2001 From: Chris Guidry Date: Mon, 22 Jul 2024 14:59:05 -0400 Subject: [PATCH 3/3] Restoring formatting for `ci.yml` as of `main` --- .github/workflows/ci.yml | 92 ++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8c3aecb..9f9a9958 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: branches: - main tags: - - "**" + - '**' pull_request: {} jobs: @@ -13,40 +13,40 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: set up python - uses: actions/setup-python@v5 - with: - python-version: "3.11" + - name: set up python + uses: actions/setup-python@v5 + with: + python-version: '3.11' - - run: pip install -r requirements/linting.txt -r requirements/pyproject.txt pre-commit + - run: pip install -r requirements/linting.txt -r requirements/pyproject.txt pre-commit - - run: pre-commit run -a --verbose - env: - SKIP: no-commit-to-branch + - run: pre-commit run -a --verbose + env: + SKIP: no-commit-to-branch docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: set up python - uses: actions/setup-python@v5 - with: - python-version: "3.11" + - name: set up python + uses: actions/setup-python@v5 + with: + python-version: '3.11' - - run: pip install -r requirements/docs.txt -r requirements/pyproject.txt - - run: pip install . + - run: pip install -r requirements/docs.txt -r requirements/pyproject.txt + - run: pip install . - - run: make docs + - run: make docs - - name: Store docs site - uses: actions/upload-artifact@v4 - with: - name: docs - path: docs/_build/ + - name: Store docs site + uses: actions/upload-artifact@v4 + with: + name: docs + path: docs/_build/ test: name: test py${{ matrix.python }} with redis:${{ matrix.redis }} on ${{ matrix.os }} @@ -54,15 +54,15 @@ jobs: fail-fast: false matrix: os: [ubuntu] - python: ["3.8", "3.9", "3.10", "3.11", "3.12"] - redis: ["5"] + python: ['3.8', '3.9', '3.10', '3.11', '3.12'] + redis: ['5'] include: - - python: "3.11" - redis: "6" - os: "ubuntu" - - python: "3.11" - redis: "7" - os: "ubuntu" + - python: '3.11' + redis: '6' + os: 'ubuntu' + - python: '3.11' + redis: '7' + os: 'ubuntu' env: PYTHON: ${{ matrix.python }} @@ -72,23 +72,23 @@ jobs: runs-on: ${{ matrix.os }}-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: set up python - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python }} + - name: set up python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} - - run: pip install -r requirements/testing.txt -r requirements/pyproject.txt + - run: pip install -r requirements/testing.txt -r requirements/pyproject.txt - - run: make test + - run: make test - - run: coverage xml + - run: coverage xml - - uses: codecov/codecov-action@v4 - with: - file: ./coverage.xml - env_vars: PYTHON,OS + - uses: codecov/codecov-action@v4 + with: + file: ./coverage.xml + env_vars: PYTHON,OS check: if: always() @@ -124,7 +124,7 @@ jobs: - name: set up python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: '3.11' - name: install run: pip install -U build @@ -133,7 +133,7 @@ jobs: id: check-version uses: samuelcolvin/check-python-version@v3.2 with: - version_file_path: "arq/version.py" + version_file_path: 'arq/version.py' - name: build run: python -m build @@ -142,7 +142,7 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 - name: publish docs - if: "!fromJSON(steps.check-version.outputs.IS_PRERELEASE)" + if: '!fromJSON(steps.check-version.outputs.IS_PRERELEASE)' run: make publish-docs env: NETLIFY: ${{ secrets.netlify_token }}