diff --git a/.github/workflows/scripts/install.sh b/.github/workflows/scripts/install.sh index d4082b98..a512cbea 100755 --- a/.github/workflows/scripts/install.sh +++ b/.github/workflows/scripts/install.sh @@ -97,7 +97,7 @@ minio_access_key: "'$MINIO_ACCESS_KEY'"\ minio_secret_key: "'$MINIO_SECRET_KEY'"\ pulp_scenario_settings: {"domain_enabled": true}\ pulp_scenario_env: {}\ -test_storages_compat_layer: false\ +test_storages_compat_layer: true\ ' vars/main.yaml export PULP_API_ROOT="/rerouted/djnd/" fi diff --git a/CHANGES/+pulpcore-3.70.feature b/CHANGES/+pulpcore-3.70.feature new file mode 100644 index 00000000..b70054c1 --- /dev/null +++ b/CHANGES/+pulpcore-3.70.feature @@ -0,0 +1 @@ +Added pulpcore 3.70 compatibility diff --git a/pulp_python/app/fields.py b/pulp_python/app/fields.py deleted file mode 100644 index 110d2c55..00000000 --- a/pulp_python/app/fields.py +++ /dev/null @@ -1,12 +0,0 @@ -from drf_spectacular.utils import extend_schema_field -from drf_spectacular.types import OpenApiTypes -from rest_framework import serializers - - -@extend_schema_field(OpenApiTypes.OBJECT) -class JSONObjectField(serializers.JSONField): - """A drf JSONField override to force openapi schema to use 'object' type. - - Not strictly correct, but we relied on that for a long time. - See: https://github.com/tfranzel/drf-spectacular/issues/1095 - """ diff --git a/pulp_python/app/pypi/serializers.py b/pulp_python/app/pypi/serializers.py index 301d7a8b..dc99fc80 100644 --- a/pulp_python/app/pypi/serializers.py +++ b/pulp_python/app/pypi/serializers.py @@ -3,7 +3,6 @@ from rest_framework import serializers from pulp_python.app.utils import DIST_EXTENSIONS -from pulp_python.app import fields from pulpcore.plugin.models import Artifact from pulpcore.plugin.util import get_domain from django.db.utils import IntegrityError @@ -29,9 +28,9 @@ class PackageMetadataSerializer(serializers.Serializer): """ last_serial = serializers.IntegerField(help_text=_("Cache value from last PyPI sync")) - info = fields.JSONObjectField(help_text=_("Core metadata of the package")) - releases = fields.JSONObjectField(help_text=_("List of all the releases of the package")) - urls = fields.JSONObjectField() + info = serializers.JSONField(help_text=_("Core metadata of the package")) + releases = serializers.JSONField(help_text=_("List of all the releases of the package")) + urls = serializers.JSONField() class PackageUploadSerializer(serializers.Serializer): diff --git a/pulp_python/app/serializers.py b/pulp_python/app/serializers.py index 0c8c9070..dca90d27 100644 --- a/pulp_python/app/serializers.py +++ b/pulp_python/app/serializers.py @@ -8,7 +8,6 @@ from pulpcore.plugin.util import get_domain from pulp_python.app import models as python_models -from pulp_python.app import fields from pulp_python.app.utils import artifact_to_python_content_data @@ -158,7 +157,7 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa required=False, allow_blank=True, help_text=_('A browsable URL for the project and a label for it, separated by a comma.') ) - project_urls = fields.JSONObjectField( + project_urls = serializers.JSONField( required=False, default=dict, help_text=_('A dictionary of labels and URLs for the project.') ) @@ -171,28 +170,28 @@ class PythonPackageContentSerializer(core_serializers.SingleArtifactContentUploa required=False, allow_blank=True, help_text=_('Field to specify the OS and CPU for which the binary package was compiled. ') ) - requires_dist = fields.JSONObjectField( + requires_dist = serializers.JSONField( required=False, default=list, help_text=_('A JSON list containing names of some other distutils project ' 'required by this distribution.') ) - provides_dist = fields.JSONObjectField( + provides_dist = serializers.JSONField( required=False, default=list, help_text=_('A JSON list containing names of a Distutils project which is contained' ' within this distribution.') ) - obsoletes_dist = fields.JSONObjectField( + obsoletes_dist = serializers.JSONField( required=False, default=list, help_text=_('A JSON list containing names of a distutils project\'s distribution which ' 'this distribution renders obsolete, meaning that the two projects should not ' 'be installed at the same time.') ) - requires_external = fields.JSONObjectField( + requires_external = serializers.JSONField( required=False, default=list, help_text=_('A JSON list containing some dependency in the system that the distribution ' 'is to be used.') ) - classifiers = fields.JSONObjectField( + classifiers = serializers.JSONField( required=False, default=list, help_text=_('A JSON list containing classification values for a Python package.') ) diff --git a/pulp_python/pytest_plugin.py b/pulp_python/pytest_plugin.py index f04a7d4b..346cc951 100644 --- a/pulp_python/pytest_plugin.py +++ b/pulp_python/pytest_plugin.py @@ -145,7 +145,7 @@ def _download_python_file(relative_path, url): file_path = tmp_path / relative_path with open(file_path, mode="wb") as f: f.write(http_get(url)) - return file_path + return str(file_path) yield _download_python_file diff --git a/pulp_python/tests/functional/api/test_crud_content_unit.py b/pulp_python/tests/functional/api/test_crud_content_unit.py index 66439343..6dcbc450 100644 --- a/pulp_python/tests/functional/api/test_crud_content_unit.py +++ b/pulp_python/tests/functional/api/test_crud_content_unit.py @@ -26,23 +26,23 @@ def test_content_crud( content_body = {"relative_path": PYTHON_EGG_FILENAME, "artifact": artifact.pulp_href} response = python_bindings.ContentPackagesApi.create(**content_body) task = monitor_task(response.task) - content = python_bindings.ContentPackagesApi.read(task.created_resources[0]).to_dict() + content = python_bindings.ContentPackagesApi.read(task.created_resources[0]) for k, v in PYTHON_PACKAGE_DATA.items(): - assert content[k] == v + assert getattr(content, k) == v # Test read - result = python_bindings.ContentPackagesApi.list(filename=content["filename"]) + result = python_bindings.ContentPackagesApi.list(filename=content.filename) assert result.count == 1 - assert result.results[0].to_dict() == content + assert result.results[0] == content # Test partial update with pytest.raises(AttributeError) as e: - python_bindings.ContentPackagesApi.partial_update(content["pulp_href"], {"filename": "te"}) + python_bindings.ContentPackagesApi.partial_update(content.pulp_href, {"filename": "te"}) assert "object has no attribute 'partial_update'" in e.value.args[0] # Test delete with pytest.raises(AttributeError) as e: - python_bindings.ContentPackagesApi.delete(content["pulp_href"]) + python_bindings.ContentPackagesApi.delete(content.pulp_href) assert "object has no attribute 'delete'" in e.value.args[0] monitor_task(pulpcore_bindings.OrphansCleanupApi.cleanup({"orphan_protection_time": 0}).task) @@ -51,9 +51,9 @@ def test_content_crud( content_body = {"relative_path": PYTHON_EGG_FILENAME, "file": python_file} response = python_bindings.ContentPackagesApi.create(**content_body) task = monitor_task(response.task) - content = python_bindings.ContentPackagesApi.read(task.created_resources[0]).to_dict() + content = python_bindings.ContentPackagesApi.read(task.created_resources[0]) for k, v in PYTHON_PACKAGE_DATA.items(): - assert content[k] == v + assert getattr(content, k) == v monitor_task(pulpcore_bindings.OrphansCleanupApi.cleanup({"orphan_protection_time": 0}).task) @@ -65,15 +65,15 @@ def test_content_crud( content_search = python_bindings.ContentPackagesApi.list( repository_version_added=task.created_resources[0] ) - content = python_bindings.ContentPackagesApi.read(content_search.results[0].pulp_href).to_dict() + content = python_bindings.ContentPackagesApi.read(content_search.results[0].pulp_href) for k, v in PYTHON_PACKAGE_DATA.items(): - assert content[k] == v + assert getattr(content, k) == v # Test duplicate upload content_body = {"relative_path": PYTHON_EGG_FILENAME, "file": python_file} response = python_bindings.ContentPackagesApi.create(**content_body) task = monitor_task(response.task) - assert task.created_resources[0] == content["pulp_href"] + assert task.created_resources[0] == content.pulp_href # Test upload same filename w/ different artifact second_python_url = urljoin(urljoin(PYTHON_FIXTURES_URL, "packages/"), "aiohttp-3.3.0.tar.gz") @@ -81,8 +81,8 @@ def test_content_crud( content_body = {"relative_path": PYTHON_EGG_FILENAME, "file": second_python_file} response = python_bindings.ContentPackagesApi.create(**content_body) task = monitor_task(response.task) - content2 = python_bindings.ContentPackagesApi.read(task.created_resources[0]).to_dict() - assert content2["pulp_href"] != content["pulp_href"] + content2 = python_bindings.ContentPackagesApi.read(task.created_resources[0]) + assert content2.pulp_href != content.pulp_href # Test upload same filename w/ different artifacts in same repo # repo already has EGG_FILENAME w/ EGG_ARTIFACT, not upload EGG_FILENAME w/ AIO_ARTIFACT @@ -90,11 +90,11 @@ def test_content_crud( response = python_bindings.ContentPackagesApi.create(repository=repo.pulp_href, **content_body) task = monitor_task(response.task) assert len(task.created_resources) == 2 - assert content2["pulp_href"] in task.created_resources + assert content2.pulp_href in task.created_resources repo_ver2 = task.created_resources[0] content_list = python_bindings.ContentPackagesApi.list(repository_version=repo_ver2) assert content_list.count == 1 - assert content_list.results[0].pulp_href == content2["pulp_href"] + assert content_list.results[0].pulp_href == content2.pulp_href # Test upload w/ mismatched sha256 # If we don't perform orphan cleanup here, the upload will fail with a different error :hmm: diff --git a/pulp_python/tests/functional/api/test_crud_remotes.py b/pulp_python/tests/functional/api/test_crud_remotes.py index 2c8e681a..ace928fd 100644 --- a/pulp_python/tests/functional/api/test_crud_remotes.py +++ b/pulp_python/tests/functional/api/test_crud_remotes.py @@ -14,16 +14,16 @@ @pytest.mark.parametrize("kwargs", [{}, {"policy": "on_demand"}]) def test_remote_from_bandersnatch_config(kwargs, python_bindings, add_to_cleanup, tmp_path): """Verify whether it's possible to create a remote from a Bandersnatch config.""" - filename = tmp_path / "bandersnatch.conf" + filename = str(tmp_path / "bandersnatch.conf") with open(filename, mode="wb") as config: config.write(BANDERSNATCH_CONF) config.flush() name = str(uuid.uuid4()) - remote = python_bindings.RemotesPythonApi.from_bandersnatch(filename, name, **kwargs).to_dict() - add_to_cleanup(python_bindings.RemotesPythonApi, remote["pulp_href"]) + remote = python_bindings.RemotesPythonApi.from_bandersnatch(filename, name, **kwargs) + add_to_cleanup(python_bindings.RemotesPythonApi, remote.pulp_href) expected = _gen_expected_remote_body(name, **kwargs) for key, val in expected.items(): - assert remote[key] == val + assert getattr(remote, key) == val @pytest.mark.parallel @@ -40,7 +40,7 @@ def test_remote_default_policy(python_bindings, gen_object_with_cleanup, monitor @pytest.mark.parallel -def test_remote_invalid_project_specifier(python_bindings): +def test_remote_invalid_project_specifier(python_bindings, has_pulp_plugin): """Test that creating a remote with an invalid project specifier fails.""" # Test an include specifier without a "name" field. body = { @@ -48,8 +48,10 @@ def test_remote_invalid_project_specifier(python_bindings): "url": "https://test", "includes": PYTHON_INVALID_SPECIFIER_NO_NAME, } - with pytest.raises(python_bindings.ApiException): - python_bindings.RemotesPythonApi.create(body) + # Pydantic addition to bindings in 3.70 prevent this test from working + if has_pulp_plugin("core", max="3.70"): + with pytest.raises(python_bindings.ApiException): + python_bindings.RemotesPythonApi.create(body) # Test an include specifier with an invalid "version_specifier" field value. body["includes"] = PYTHON_INVALID_SPECIFIER_BAD_VERSION @@ -59,8 +61,9 @@ def test_remote_invalid_project_specifier(python_bindings): # Test an exclude specifier without a "name" field. body.pop("includes") body["excludes"] = PYTHON_INVALID_SPECIFIER_NO_NAME - with pytest.raises(python_bindings.ApiException): - python_bindings.RemotesPythonApi.create(body) + if has_pulp_plugin("core", max="3.70"): + with pytest.raises(python_bindings.ApiException): + python_bindings.RemotesPythonApi.create(body) # Test an exclude specifier with an invalid "version_specifier" field value. body["excludes"] = PYTHON_INVALID_SPECIFIER_BAD_VERSION @@ -95,14 +98,17 @@ def test_remote_version_specifier(python_bindings, add_to_cleanup): @pytest.mark.parallel -def test_remote_update_invalid_project_specifier(python_bindings, python_remote_factory): +def test_remote_update_invalid_project_specifier( + python_bindings, python_remote_factory, has_pulp_plugin +): """Test that updating a remote with an invalid project specifier fails non-destructively.""" remote = python_remote_factory() # Test an include specifier without a "name" field. body = {"includes": PYTHON_INVALID_SPECIFIER_NO_NAME} - with pytest.raises(python_bindings.ApiException): - python_bindings.RemotesPythonApi.partial_update(remote.pulp_href, body) + if has_pulp_plugin("core", max="3.70"): + with pytest.raises(python_bindings.ApiException): + python_bindings.RemotesPythonApi.partial_update(remote.pulp_href, body) # Test an include specifier with an invalid "version_specifier" field value. body = {"includes": PYTHON_INVALID_SPECIFIER_BAD_VERSION} @@ -111,8 +117,9 @@ def test_remote_update_invalid_project_specifier(python_bindings, python_remote_ # Test an exclude specifier without a "name" field. body = {"excludes": PYTHON_INVALID_SPECIFIER_NO_NAME} - with pytest.raises(python_bindings.ApiException): - python_bindings.RemotesPythonApi.partial_update(remote.pulp_href, body) + if has_pulp_plugin("core", max="3.70"): + with pytest.raises(python_bindings.ApiException): + python_bindings.RemotesPythonApi.partial_update(remote.pulp_href, body) # Test an exclude specifier with an invalid "version_specifier" field value. body = {"excludes": PYTHON_INVALID_SPECIFIER_BAD_VERSION} diff --git a/pulp_python/tests/functional/api/test_domains.py b/pulp_python/tests/functional/api/test_domains.py index d5db104c..786e58ec 100644 --- a/pulp_python/tests/functional/api/test_domains.py +++ b/pulp_python/tests/functional/api/test_domains.py @@ -6,7 +6,6 @@ from pulpcore.app import settings from pulp_python.tests.functional.constants import ( - PYTHON_URL, PYTHON_EGG_FILENAME, PYTHON_SM_PROJECT_SPECIFIER, PYTHON_SM_PACKAGE_COUNT, @@ -82,14 +81,6 @@ def test_domain_object_creation( } -@pytest.fixture -def python_file(tmp_path, http_get): - filename = tmp_path / PYTHON_EGG_FILENAME - with open(filename, mode="wb") as f: - f.write(http_get(PYTHON_URL)) - yield filename - - @pytest.mark.parallel def test_domain_content_upload( domain_factory, diff --git a/pulp_python/tests/functional/api/test_rbac.py b/pulp_python/tests/functional/api/test_rbac.py index c95ec543..125ef368 100644 --- a/pulp_python/tests/functional/api/test_rbac.py +++ b/pulp_python/tests/functional/api/test_rbac.py @@ -1,7 +1,6 @@ import pytest import uuid -from pulpcore.client.pulp_python import ApiException, AsyncOperationResponse from pulp_python.tests.functional.constants import ( PYTHON_EGG_FILENAME, PYTHON_EGG_SHA256, @@ -27,19 +26,28 @@ def _gen_users(role_names=list()): @pytest.fixture -def try_action(monitor_task): +def try_action(python_bindings, monitor_task): def _try_action(user, client, action, outcome, *args, **kwargs): action_api = getattr(client, f"{action}_with_http_info") try: with user: - response, status, _ = action_api(*args, **kwargs, _return_http_data_only=False) - if isinstance(response, AsyncOperationResponse): - response = monitor_task(response.task) - except ApiException as e: + response = action_api(*args, **kwargs) + if isinstance(response, tuple): + # old bindings + data, status_code, _ = response + else: + # new bindings + data = response.data + status_code = response.status_code + if isinstance(data, python_bindings.module.AsyncOperationResponse): + data = monitor_task(data.task) + except python_bindings.module.ApiException as e: assert e.status == outcome, f"{e}" else: - assert status == outcome, f"User performed {action} when they shouldn't been able to" - return response + assert ( + status_code == outcome + ), f"User performed {action} when they shouldn't been able to" + return data return _try_action diff --git a/pyproject.toml b/pyproject.toml index af1a1e4f..9cc858dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers=[ ] requires-python = ">=3.9" dependencies = [ - "pulpcore>=3.49.0,<3.70", + "pulpcore>=3.49.0,<3.85", "pkginfo>=1.10.0,<1.12.0", # Twine has <1.11 in their requirements "bandersnatch>=6.3,<7.0", # Anything >6.3 requires Python 3.10+ "pypi-simple>=1.5.0,<2.0", diff --git a/template_config.yml b/template_config.yml index ddc66452..d84bdba0 100644 --- a/template_config.yml +++ b/template_config.yml @@ -68,6 +68,6 @@ test_lowerbounds: true test_performance: false test_reroute: true test_s3: true -test_storages_compat_layer: false +test_storages_compat_layer: true use_issue_template: true