diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 09d66222..17f88ea4 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -20,7 +20,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: [3.9, '3.10', '3.11', '3.12', '3.13'] + python-version: [3.9, '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -77,7 +77,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: [3.9, '3.10', '3.11', '3.12', '3.13'] + python-version: [3.9, '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ef0d7a2..0adae373 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ List of the most important changes for each release. +## 0.8.8 +- Adds support for Python 3.14 + ## 0.8.7 - Adds flexibility for customizing deserialization behavior using sync filter to `SyncableModel` methods diff --git a/morango/__init__.py b/morango/__init__.py index 686e10f8..aa00ec3d 100644 --- a/morango/__init__.py +++ b/morango/__init__.py @@ -1 +1 @@ -__version__ = "0.8.7" +__version__ = "0.8.8" diff --git a/requirements/test.txt b/requirements/test.txt index 80c94094..a4d253ee 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,8 +1,6 @@ # These are for testing -factory-boy==2.7.0 -fake-factory==0.5.7 -mock==2.0.0 -pytest==6.2.5 +factory-boy>=3.0,<4 +mock>=4.0,<6 +pytest>=6.2.5,<8 pytest-django==4.5.2 -more-itertools<=8.10.0 -typing +more-itertools<=10.0.0 diff --git a/setup.py b/setup.py index 1126f585..69140a2a 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', ], - python_requires=">=3.6, <3.14", + python_requires=">=3.6, <3.15", ) diff --git a/tests/testapp/tests/compat.py b/tests/testapp/tests/compat.py index 284ca2b8..881f379e 100644 --- a/tests/testapp/tests/compat.py +++ b/tests/testapp/tests/compat.py @@ -1,11 +1,21 @@ -try: - # In the Python EOL GH workflows, we have to install backported version - # as the Docker container images we use does not have the test module installed. - from backports.test.support import EnvironmentVarGuard # noqa F401 -except ImportError: - try: - # For python >3.8 and <3.10 - from test.support import EnvironmentVarGuard # noqa F401 - except ImportError: - # In Python 3.10, this has been moved to test.support.os_helper - from test.support.os_helper import EnvironmentVarGuard # noqa F401 +import os +from unittest.mock import patch as _patch + + +class EnvironmentVarGuard: + """ + Vendored replacement for the removed test.support EnvironmentVarGuard. + Uses unittest.mock.patch.dict(os.environ) under the hood. + Supports the context-manager-with-dict-assignment pattern: + with EnvironmentVarGuard() as env: env[k] = v + """ + + def __init__(self): + self._patcher = _patch.dict(os.environ) + + def __enter__(self): + self._patcher.start() + return os.environ + + def __exit__(self, *args): + self._patcher.stop() diff --git a/tests/testapp/tests/helpers.py b/tests/testapp/tests/helpers.py index 88f4f9b3..1dbe1168 100644 --- a/tests/testapp/tests/helpers.py +++ b/tests/testapp/tests/helpers.py @@ -36,14 +36,14 @@ from morango.sync.syncsession import TransferClient -class FacilityFactory(factory.DjangoModelFactory): +class FacilityModelFactory(factory.django.DjangoModelFactory): class Meta: model = Facility name = factory.Sequence(lambda n: "Fac %d" % n) -class AbstractStoreFactory(factory.DjangoModelFactory): +class AbstractStoreFactory(factory.django.DjangoModelFactory): class Meta: model = AbstractStore @@ -61,12 +61,12 @@ class Meta: model = Store -class RecordMaxCounterBufferFactory(factory.DjangoModelFactory): +class RecordMaxCounterBufferFactory(factory.django.DjangoModelFactory): class Meta: model = RecordMaxCounterBuffer -class RecordMaxCounterFactory(factory.DjangoModelFactory): +class RecordMaxCounterFactory(factory.django.DjangoModelFactory): class Meta: model = RecordMaxCounter @@ -102,11 +102,11 @@ def create_dummy_store_data(): data["mc"].serialize_into_store() # counter is at 1 # create group of facilities and first serialization - data["group1_c1"] = [FacilityFactory() for _ in range(5)] + data["group1_c1"] = [FacilityModelFactory() for _ in range(5)] data["mc"].serialize_into_store() # counter is at 2 # create group of facilities and second serialization - data["group1_c2"] = [FacilityFactory() for _ in range(5)] + data["group1_c2"] = [FacilityModelFactory() for _ in range(5)] # create users and logs associated with user data["user1"] = MyUser.objects.create(username="bob") @@ -127,7 +127,7 @@ def create_dummy_store_data(): ] # new counter is at 0 data["mc"].serialize_into_store() # new counter is at 1 - data["group2_c1"] = [FacilityFactory() for _ in range(5)] + data["group2_c1"] = [FacilityModelFactory() for _ in range(5)] # create users and logs associated with user data["user2"] = MyUser.objects.create(username="rob") diff --git a/tests/testapp/tests/integration/test_signals.py b/tests/testapp/tests/integration/test_signals.py index 1d830cd9..e245a271 100644 --- a/tests/testapp/tests/integration/test_signals.py +++ b/tests/testapp/tests/integration/test_signals.py @@ -1,20 +1,12 @@ -import factory from django.test import TestCase from facility_profile.models import Facility +from ..helpers import FacilityModelFactory from morango.models.core import DeletedModels from morango.models.core import InstanceIDModel from morango.sync.controller import MorangoProfileController -class FacilityModelFactory(factory.DjangoModelFactory): - - class Meta: - model = Facility - - name = factory.Sequence(lambda n: "Fac %d" % n) - - class PostDeleteSignalsTestCase(TestCase): def setUp(self): diff --git a/tests/testapp/tests/models/test_core.py b/tests/testapp/tests/models/test_core.py index de25201f..f760eb25 100644 --- a/tests/testapp/tests/models/test_core.py +++ b/tests/testapp/tests/models/test_core.py @@ -20,7 +20,7 @@ from morango.sync.controller import MorangoProfileController -class DatabaseMaxCounterFactory(factory.DjangoModelFactory): +class DatabaseMaxCounterFactory(factory.django.DjangoModelFactory): class Meta: model = DatabaseMaxCounter diff --git a/tests/testapp/tests/sync/test_controller.py b/tests/testapp/tests/sync/test_controller.py index f009e216..b0cdf6a5 100644 --- a/tests/testapp/tests/sync/test_controller.py +++ b/tests/testapp/tests/sync/test_controller.py @@ -12,6 +12,7 @@ from facility_profile.models import SummaryLog from ..compat import EnvironmentVarGuard +from ..helpers import FacilityModelFactory from ..helpers import serialized_facility_factory from ..helpers import TestSessionContext from morango.constants import transfer_stages @@ -26,14 +27,7 @@ from morango.sync.controller import SessionController -class FacilityModelFactory(factory.DjangoModelFactory): - class Meta: - model = Facility - - name = factory.Sequence(lambda n: "Fac %d" % n) - - -class StoreModelFacilityFactory(factory.DjangoModelFactory): +class StoreModelFacilityFactory(factory.django.DjangoModelFactory): class Meta: model = Store diff --git a/tests/testapp/tests/sync/test_operations.py b/tests/testapp/tests/sync/test_operations.py index 6aae39e8..8ad29b10 100644 --- a/tests/testapp/tests/sync/test_operations.py +++ b/tests/testapp/tests/sync/test_operations.py @@ -3,7 +3,6 @@ import uuid from time import sleep -import factory import mock import pytest from django.conf import settings @@ -53,13 +52,6 @@ DBBackend = load_backend(connection) -class FacilityModelFactory(factory.DjangoModelFactory): - class Meta: - model = Facility - - name = factory.Sequence(lambda n: "Fac %d" % n) - - def assertRecordsBuffered(records): buffer_ids = Buffer.objects.values_list("model_uuid", flat=True) rmcb_ids = RecordMaxCounterBuffer.objects.values_list("model_uuid", flat=True) diff --git a/tests/testapp/tests/test_compat.py b/tests/testapp/tests/test_compat.py new file mode 100644 index 00000000..425ba022 --- /dev/null +++ b/tests/testapp/tests/test_compat.py @@ -0,0 +1,37 @@ +import os +import uuid + +from django.test import SimpleTestCase + +from .compat import EnvironmentVarGuard + + +class EnvironmentVarGuardTestCase(SimpleTestCase): + """Tests for the vendored EnvironmentVarGuard.""" + + def test_sets_env_var_inside_context(self): + key = "MORANGO_TEST_" + uuid.uuid4().hex[:8] + with EnvironmentVarGuard() as env: + env[key] = "test_value" + self.assertEqual(os.environ[key], "test_value") + + def test_reverts_env_var_on_exit(self): + key = "MORANGO_TEST_" + uuid.uuid4().hex[:8] + with EnvironmentVarGuard() as env: + env[key] = "test_value" + self.assertNotIn(key, os.environ) + + def test_reverts_modified_env_var_on_exit(self): + key = "MORANGO_TEST_" + uuid.uuid4().hex[:8] + os.environ[key] = "original" + try: + with EnvironmentVarGuard() as env: + env[key] = "modified" + self.assertEqual(os.environ[key], "modified") + self.assertEqual(os.environ[key], "original") + finally: + os.environ.pop(key, None) + + def test_returns_os_environ(self): + with EnvironmentVarGuard() as env: + self.assertIs(env, os.environ) diff --git a/tox.ini b/tox.ini index 5a99e067..ca85397e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py{3.6,3.7,3.8,3.9,3.10,3.11,3.12,3.13}-cryptography{40.0.2} + py{3.6,3.7,3.8,3.9,3.10,3.11,3.12,3.13,3.14}-cryptography{40.0.2} postgres windows @@ -21,15 +21,13 @@ basepython = py3.11: python3.11 py3.12: python3.12 py3.13: python3.13 + py3.14: python3.14 postgres: python3.9 windows: python3.8 deps = -r{toxinidir}/requirements/test.txt cryptography40.0.2: cryptography==40.0.2 - py3.6: backports.test.support==0.1.1 - py3.7: backports.test.support==0.1.1 - py3.8: backports.test.support==0.1.1 commands = sh -c 'tests/testapp/manage.py makemigrations --check' python -O -m pytest {posargs: --color=no}