From 3abd30078752f8e71ba59c4d049994dacff5358f Mon Sep 17 00:00:00 2001 From: Gerrod Ubben Date: Tue, 24 Mar 2026 15:19:06 -0400 Subject: [PATCH] Add new setting PYPI_PATH_PREFIX https://redhat.atlassian.net/browse/PULP-1318 --- CHANGES/+pypi-path-prefix.feature | 1 + docs/admin/reference/settings.md | 6 ++++++ pulp_python/app/pypi/views.py | 2 +- pulp_python/app/serializers.py | 6 ++++-- pulp_python/app/settings.py | 1 + pulp_python/app/urls.py | 5 +++-- 6 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 CHANGES/+pypi-path-prefix.feature diff --git a/CHANGES/+pypi-path-prefix.feature b/CHANGES/+pypi-path-prefix.feature new file mode 100644 index 000000000..4b00c814a --- /dev/null +++ b/CHANGES/+pypi-path-prefix.feature @@ -0,0 +1 @@ +Added new setting PYPI_PATH_PREFIX to allow for customizing the path prefix for the PyPI API. diff --git a/docs/admin/reference/settings.md b/docs/admin/reference/settings.md index d2a0cbb94..914e498c0 100644 --- a/docs/admin/reference/settings.md +++ b/docs/admin/reference/settings.md @@ -13,3 +13,9 @@ > This specifies the hostname where the PyPI API is served. It defaults to the fully qualified > hostname of the system where the process is running. This needs to be adjusted if running behind > a non local reverse proxy. + +## PYPI_PATH_PREFIX + +> This specifies where the PyPI endpoints can be found at. It defaults to `/pypi/`. The value is +> used along with `PYPI_API_HOSTNAME` to generate the links to the PyPI endpoints and should start +> and end in a slash. diff --git a/pulp_python/app/pypi/views.py b/pulp_python/app/pypi/views.py index b7808a9ec..70d16e5d6 100644 --- a/pulp_python/app/pypi/views.py +++ b/pulp_python/app/pypi/views.py @@ -59,7 +59,7 @@ ORIGIN_HOST = settings.CONTENT_ORIGIN if settings.CONTENT_ORIGIN else settings.PYPI_API_HOSTNAME BASE_CONTENT_URL = urljoin(ORIGIN_HOST, settings.CONTENT_PATH_PREFIX) -BASE_API_URL = urljoin(settings.PYPI_API_HOSTNAME, "pypi/") +BASE_API_URL = urljoin(settings.PYPI_API_HOSTNAME, settings.PYPI_PATH_PREFIX) PYPI_SIMPLE_V1_HTML = "application/vnd.pypi.simple.v1+html" PYPI_SIMPLE_V1_JSON = "application/vnd.pypi.simple.v1+json" diff --git a/pulp_python/app/serializers.py b/pulp_python/app/serializers.py index 6c716b9b3..d7beeff6e 100644 --- a/pulp_python/app/serializers.py +++ b/pulp_python/app/serializers.py @@ -9,6 +9,7 @@ from rest_framework import serializers from pypi_attestations import AttestationError from pydantic import TypeAdapter, ValidationError +from urllib.parse import urljoin from pulpcore.plugin import models as core_models from pulpcore.plugin import serializers as core_serializers @@ -31,6 +32,7 @@ ) log = logging.getLogger(__name__) +PYPI_BASE_URL = urljoin(settings.PYPI_API_HOSTNAME, settings.PYPI_PATH_PREFIX) @extend_schema_serializer( @@ -92,8 +94,8 @@ class PythonDistributionSerializer(core_serializers.DistributionSerializer): def get_base_url(self, obj): """Gets the base url.""" if settings.DOMAIN_ENABLED: - return f"{settings.PYPI_API_HOSTNAME}/pypi/{get_domain().name}/{obj.base_path}/" - return f"{settings.PYPI_API_HOSTNAME}/pypi/{obj.base_path}/" + return urljoin(PYPI_BASE_URL, f"{get_domain().name}/{obj.base_path}/") + return urljoin(PYPI_BASE_URL, f"{obj.base_path}/") class Meta: fields = core_serializers.DistributionSerializer.Meta.fields + ( diff --git a/pulp_python/app/settings.py b/pulp_python/app/settings.py index eab951a50..c45438d5a 100644 --- a/pulp_python/app/settings.py +++ b/pulp_python/app/settings.py @@ -2,6 +2,7 @@ PYTHON_GROUP_UPLOADS = False PYPI_API_HOSTNAME = "https://" + socket.getfqdn() +PYPI_PATH_PREFIX = "/pypi/" DRF_ACCESS_POLICY = { "dynaconf_merge_unique": True, diff --git a/pulp_python/app/urls.py b/pulp_python/app/urls.py index 513b6932b..5e3299a96 100644 --- a/pulp_python/app/urls.py +++ b/pulp_python/app/urls.py @@ -10,9 +10,10 @@ ) if settings.DOMAIN_ENABLED: - PYPI_API_URL = "pypi///" + PYPI_API_URL = "///" else: - PYPI_API_URL = "pypi//" + PYPI_API_URL = "//" +PYPI_API_URL = settings.PYPI_PATH_PREFIX.strip("/") + PYPI_API_URL # TODO: Implement remaining PyPI endpoints # path("project/", PackageProject.as_view()), # Endpoints to nicely see contents of index # path("search/", PackageSearch.as_view()),