Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CHANGES/+pulpcore-3.70.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added pulpcore 3.70 compatibility
12 changes: 0 additions & 12 deletions pulp_python/app/fields.py

This file was deleted.

7 changes: 3 additions & 4 deletions pulp_python/app/pypi/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down
13 changes: 6 additions & 7 deletions pulp_python/app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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.')
)
Expand All @@ -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.')
)
Expand Down
2 changes: 1 addition & 1 deletion pulp_python/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
30 changes: 15 additions & 15 deletions pulp_python/tests/functional/api/test_crud_content_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -65,36 +65,36 @@ 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")
second_python_file = download_python_file("aiohttp-3.3.0.tar.gz", second_python_url)
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
# and see that repo will only have the second content unit inside after upload
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:
Expand Down
35 changes: 21 additions & 14 deletions pulp_python/tests/functional/api/test_crud_remotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,16 +40,18 @@ 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 = {
"name": str(uuid.uuid4()),
"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
Expand All @@ -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
Expand Down Expand Up @@ -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}
Expand All @@ -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}
Expand Down
9 changes: 0 additions & 9 deletions pulp_python/tests/functional/api/test_domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
24 changes: 16 additions & 8 deletions pulp_python/tests/functional/api/test_rbac.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion template_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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