diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 231a1dc8c..93b02c062 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,12 +30,14 @@ jobs: description: "Unit tests (Python 3.11)" - toxenv: "py312" description: "Unit tests (Python 3.12)" + - toxenv: "py313" + description: "Unit tests (Python 3.13)" - toxenv: "linters" - description: "Linters (Python 3.12)" + description: "Linters (Python 3.13)" - toxenv: "packaging" - description: "Packaging (Python 3.12)" + description: "Packaging (Python 3.13)" - toxenv: "migrations" - description: "Migrations (Python 3.12)" + description: "Migrations (Python 3.13)" name: ${{ matrix.description }} services: @@ -68,7 +70,7 @@ jobs: uses: actions/checkout@v4 - name: Build communication container - if: matrix.toxenv == 'py311' || matrix.toxenv == 'py312' + if: matrix.toxenv == 'py311' || matrix.toxenv == 'py312' || matrix.toxenv == 'py313' working-directory: ./resolwe/flow/docker_images run: docker build -f Dockerfile.communication -t ${{ env.RESOLWE_COMMUNICATOR_IMAGE }} ../../ @@ -92,10 +94,10 @@ jobs: - uses: actions/checkout@v4 - name: Install build - run: python3.12 -m pip install --user build + run: python3.13 -m pip install --user build - name: Build a binary wheel and a source tarball - run: python3.12 -m build --sdist --wheel --outdir dist/ . + run: python3.13 -m build --sdist --wheel --outdir dist/ . - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/docs/CHANGELOG.rst b/docs/CHANGELOG.rst index 7a405d2e2..f21c55531 100644 --- a/docs/CHANGELOG.rst +++ b/docs/CHANGELOG.rst @@ -10,10 +10,19 @@ This project adheres to `Semantic Versioning `_. Unreleased ========== +Changed +------- +- **BACKWARD INCOMPATIBLE:** Require ``Django 5.1.x`` and bump version of + dependencies + Fixed ----- - Fix error when deleting observer subscription +Added +----- +- Add support for ``Python 3.13`` + =================== 42.2.0 - 2025-01-13 diff --git a/pyproject.toml b/pyproject.toml index 91a88e082..ff786b4db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 70.0.0", "setuptools_scm >= 8.1.0"] +requires = ["setuptools >= 75.7.0", "setuptools_scm >= 8.1.0"] build-backend = "setuptools.build_meta" [project] @@ -9,7 +9,7 @@ readme = "README.rst" dynamic = ["version"] authors = [{ name = "Genialis, Inc", email = "dev-team@genialis.com" }] license = { text = "Apache License (2.0)" } -requires-python = ">=3.10, <3.13" +requires-python = ">=3.10, <3.14" keywords = ["resolwe", "dataflow", "django"] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -27,40 +27,40 @@ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ "asgiref~=3.8.1", - "asteval==0.9.33", - "async-timeout~=4.0.3", - "channels~=4.1.0", - "channels_redis~=4.2.0", + "asteval==1.0.5", + "beautifulsoup4~=4.12.3", + "channels~=4.2.0", + "channels_redis~=4.2.1", # Storage requirement for computing hashes. "crcmod", - "kubernetes~=30.1.0", - "docker~=7.1.0", - "Django~=4.2", - "djangorestframework~=3.15.2", - "django-filter~=24.2", - "django-versionfield~=1.0.3", + "Django~=5.1", "django-fernet-fields-v2~=0.9", - "drf-spectacular~=0.27.2", - "Jinja2~=3.1.4", - "jsonschema~=4.22.0", - "opentelemetry-api~=1.25.0", - "opentelemetry-exporter-otlp~=1.25.0", - "opentelemetry-sdk~=1.25.0", - "plumbum~=1.8.3", - "psycopg[binary]~=3.1.19", + "django-filter~=24.3", + "django-versionfield~=1.0.3", + "djangorestframework~=3.15.2", + "docker~=7.1.0", + "drf-spectacular~=0.28.0", + "Jinja2~=3.1.5", + "jsonschema~=4.23.0", + "kubernetes~=31.0.0", + "opentelemetry-api~=1.29.0", + "opentelemetry-exporter-otlp~=1.29.0", + "opentelemetry-sdk~=1.29.0", + "plumbum~=1.9.0", + "psycopg[binary]~=3.2.3", "python-decouple~=3.8", - "PyYAML~=6.0.1", - "redis~=5.0.6", + "pytz~=2024.2", + "PyYAML~=6.0.2", + "pyzmq~=26.2.0", + "redis~=5.2.1", "shellescape~=3.8.1", - "beautifulsoup4~=4.12.3", - "Sphinx~=7.3.7", - "wrapt~=1.16.0", - "pyzmq~=26.0.3", - "uvloop~=0.19.0", - "pytz~=2024.1", + "Sphinx~=8.1.3", + "uvloop~=0.21.0", + "wrapt~=1.17.0", ] @@ -70,23 +70,24 @@ storage-gcs = ["crcmod", "google-cloud-storage~=2.16.0"] docs = ["sphinx_rtd_theme", "pyasn1>=0.6.0", "daphne>=4.1.2"] package = ["twine", "wheel"] test = [ - "black==24.4.2", - "check-manifest>=0.49", - "coverage>=7.5.3", + "black==24.10.0", + "check-manifest>=0.50", + "coverage>=7.6.10", "daphne>=4.1.2", "django-filter-stubs>=0.1.3", - "django-stubs>=4.2.7", - "djangorestframework-stubs[compatible-mypy]>=3.15.0", - "flake8>=7.1.0", + "django-stubs>=5.1.2", + # Temporarily disabled due to compatibility issues with mypy. + # "djangorestframework-stubs[compatible-mypy]>=3.15.2", + "flake8>=7.1.1", "isort>=5.13.2", - "mypy>=1.10.0", + "mypy>=1.14.1", "pydocstyle>=6.3.0", "readme_renderer", "setuptools_scm", "tblib>=3.0.0", "testfixtures>=8.3.0", + "twine~=6.0.1", "types-setuptools", - "twine~=5.1.1", ] [project.urls] diff --git a/resolwe/flow/tests/test_utils.py b/resolwe/flow/tests/test_utils.py index c1f853169..4a28fbe9d 100644 --- a/resolwe/flow/tests/test_utils.py +++ b/resolwe/flow/tests/test_utils.py @@ -172,7 +172,7 @@ def retry_fail(): retry_fail() end = time() self.assertGreater(end - start, 0.14) - self.assertLess(end - start, 0.2) + self.assertLess(end - start, 0.2 + 0.1) self.assertEqual(self.count, 5) expected_logger_output = [ "ERROR:resolwe.flow.tests.test_utils:Retry 1/5 got exception, will retry in 0.01 seconds.\nTraceback", diff --git a/resolwe/observers/tests.py b/resolwe/observers/tests.py index f256a6b98..31df47da7 100644 --- a/resolwe/observers/tests.py +++ b/resolwe/observers/tests.py @@ -4,7 +4,6 @@ import json import uuid -import async_timeout from channels.db import database_sync_to_async from channels.routing import URLRouter from channels.testing import WebsocketCommunicator @@ -68,7 +67,7 @@ def setUp(self): async def assert_no_more_messages(self, client): """Assert there are no messages queued by a websocket client.""" with self.assertRaises(asyncio.TimeoutError): - async with async_timeout.timeout(0.01): + async with asyncio.timeout(0.01): raise ValueError("Unexpected message:", await client.receive_from()) async def await_subscription_observer_count(self, count): @@ -83,7 +82,7 @@ def get_subscription_count(): return total try: - async with async_timeout.timeout(1): + async with asyncio.timeout(1): while await get_subscription_count() != count: await asyncio.sleep(0.01) except asyncio.TimeoutError: @@ -101,7 +100,7 @@ def get_count(): return object.objects.count() try: - async with async_timeout.timeout(1): + async with asyncio.timeout(1): while await get_count() != count: await asyncio.sleep(0.01) except asyncio.TimeoutError: diff --git a/resolwe/test/testcases/__init__.py b/resolwe/test/testcases/__init__.py index 4c1902509..8118f658a 100644 --- a/resolwe/test/testcases/__init__.py +++ b/resolwe/test/testcases/__init__.py @@ -25,7 +25,6 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.models import Group -from django.contrib.contenttypes.models import ContentType from django.test import SimpleTestCase as DjangoSimpleTestCase from django.test import TestCase as DjangoTestCase from django.test import TransactionTestCase as DjangoTransactionTestCase @@ -39,13 +38,6 @@ class TestCaseHelpers(DjangoSimpleTestCase): """Mixin for test case helpers.""" - def _pre_setup(self, *args, **kwargs): - # NOTE: This is a work-around for Django issue #10827 - # (https://code.djangoproject.com/ticket/10827) that clears the - # ContentType cache before permissions are setup. - ContentType.objects.clear_cache() - super()._pre_setup(*args, **kwargs) - def _get_testing_directories(self): """Get the testing directories.""" dirs = [connector.path for connector in connectors.for_storage("data")] diff --git a/tox.ini b/tox.ini index d650e3cf0..59d31fed8 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,7 @@ envlist = py311{,-storage-credentials}, py312{,-storage-credentials}, + py313{,-storage-credentials}, docs, linters, packaging, @@ -12,13 +13,13 @@ minversion = 4.16 ignore_base_python_conflict = true [testenv] -# Use python 3.12 if it cannot be infered from the environment. +# Use python 3.13 if it cannot be infered from the environment. # See https://tox.wiki/en/latest/user_guide.html#test-environments for rules # how basepython is selected from the environment name. -basepython = python3.12 +basepython = python3.13 extras = # Always include storage extras or connectors related tests will fail. - py3{11,12}{,-storage-credentials}: + py3{11,12,13}{,-storage-credentials}: storage_s3 storage_gcs test @@ -30,7 +31,7 @@ extras = test passenv = # Pass environment variables controlling project's tests. - py{11,12}{,-storage-credentials},migrations: + py{11,12,13}{,-storage-credentials},migrations: RESOLWE_* DOCKER_* DJANGO_TEST_PROCESSES @@ -45,7 +46,7 @@ ignore_errors = # Run all linters to see their output even if one of them fails. linters: true -[testenv:py3{11,12}{,-storage-credentials}] +[testenv:py3{11,12,13}{,-storage-credentials}] commands = # General tests commands: # Print the environment and run tests.