pulp-python 3.35.0__tar.gz → 3.36.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pulp_python-3.35.0 → pulp_python-3.36.1}/CHANGES.md +22 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/PKG-INFO +1 -1
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/__init__.py +3 -2
- pulp_python-3.36.1/pulp_python/app/pypi/feeds.py +252 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/pypi/views.py +11 -2
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/tasks/repair.py +20 -9
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/urls.py +16 -0
- pulp_python-3.36.1/pulp_python/tests/functional/api/test_pypi_feeds.py +132 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_pypi_simple_api.py +4 -2
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_simple_cache.py +7 -8
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python.egg-info/PKG-INFO +1 -1
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python.egg-info/SOURCES.txt +2 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pyproject.toml +2 -2
- {pulp_python-3.35.0 → pulp_python-3.36.1}/COMMITMENT +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/COPYRIGHT +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/LICENSE +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/MANIFEST.in +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/README.md +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/functest_requirements.txt +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/cache.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/exceptions.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/global_access_conditions.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/management/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/management/commands/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/management/commands/repair-python-metadata.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0001_initial.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0001_squashed_0010_update_json_field.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0002_pythonpackagecontent_python_version.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0003_new_sync_filters.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0004_DATA_swap_distribution_model.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0005_pythonpackagecontent_sha256.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0006_pythonrepository_autopublish.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0007_pythonpackagecontent_mv-2-1.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0008_pythonpackagecontent_unique_sha256.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0009_pythondistribution_allow_uploads.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0010_update_json_field.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0011_alter_pythondistribution_distribution_ptr_and_more.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0012_add_domain.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0013_add_rbac_permissions.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0014_pythonpackagecontent_dynamic_and_more.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0015_alter_pythonpackagecontent_options.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0016_pythonpackagecontent_metadata_sha256.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0017_pythonpackagecontent_size.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0018_packageprovenance.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0019_create_missing_metadata_artifacts.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0020_pythonpackagecontent_name_normalized.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0021_pythonrepository_upload_duplicate_filenames.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0022_pythonblocklistentry.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0023_packageyank.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0024_pythonrepository_error_on_reject.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/modelresource.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/models.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/provenance.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/pypi/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/pypi/serializers.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/replica.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/serializers.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/settings.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/tasks/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/tasks/publish.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/tasks/sync.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/tasks/upload.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/tasks/vulnerability_report.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/tasks/yank.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/utils.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/viewsets.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/webserver_snippets/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/webserver_snippets/apache.conf +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/webserver_snippets/nginx.conf +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/pytest_plugin.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_attestations.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_auto_publish.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_blocklist.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_consume_content.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_crud_content_unit.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_crud_publications.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_crud_remotes.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_domains.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_download_content.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_export_import.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_full_mirror.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_pypi_apis.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_rbac.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_repair.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_sync.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_upload.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_version_specifier_filter.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_vulnerability_report.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_yank.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/constants.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/utils.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/unit/__init__.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/unit/test_models.py +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python.egg-info/dependency_links.txt +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python.egg-info/entry_points.txt +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python.egg-info/requires.txt +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python.egg-info/top_level.txt +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/setup.cfg +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/test_requirements.txt +0 -0
- {pulp_python-3.35.0 → pulp_python-3.36.1}/unittest_requirements.txt +0 -0
|
@@ -8,6 +8,28 @@
|
|
|
8
8
|
|
|
9
9
|
[//]: # (towncrier release notes start)
|
|
10
10
|
|
|
11
|
+
## 3.36.1 (2026-09-09) {: #3.36.1 }
|
|
12
|
+
|
|
13
|
+
#### Bugfixes {: #3.36.1-bugfix }
|
|
14
|
+
|
|
15
|
+
- Prefer the PyPI Simple API JSON response when clients such as pip and uv advertise JSON alongside HTML.
|
|
16
|
+
[#1370](https://github.com/pulp/pulp_python/issues/1370)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 3.36.0 (2026-09-03) {: #3.36.0 }
|
|
21
|
+
|
|
22
|
+
#### Features {: #3.36.0-feature }
|
|
23
|
+
|
|
24
|
+
- Added PyPI-compatible RSS feeds on Python distributions at `rss/updates.xml`, `rss/packages.xml`, and `rss/project/<name>/releases.xml`.
|
|
25
|
+
[#1320](https://github.com/pulp/pulp_python/issues/1320)
|
|
26
|
+
|
|
27
|
+
#### Misc {: #3.36.0-misc }
|
|
28
|
+
|
|
29
|
+
- [#1352](https://github.com/pulp/pulp_python/issues/1352)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
11
33
|
## 3.35.0 (2026-08-26) {: #3.35.0 }
|
|
12
34
|
|
|
13
35
|
#### Features {: #3.35.0-feature }
|
|
@@ -12,7 +12,7 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
|
|
|
12
12
|
|
|
13
13
|
name = "pulp_python.app"
|
|
14
14
|
label = "python"
|
|
15
|
-
version = "3.
|
|
15
|
+
version = "3.36.1"
|
|
16
16
|
python_package_name = "pulp-python"
|
|
17
17
|
domain_compatible = True
|
|
18
18
|
|
|
@@ -28,6 +28,7 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
|
|
|
28
28
|
|
|
29
29
|
# TODO: Remove this when https://github.com/pulp/pulpcore/issues/5500 is resolved
|
|
30
30
|
def _populate_pypi_access_policies(sender, apps, verbosity, **kwargs):
|
|
31
|
+
from pulp_python.app.pypi.feeds import FeedView
|
|
31
32
|
from pulp_python.app.pypi.views import MetadataView, PyPIView, SimpleView, UploadView
|
|
32
33
|
|
|
33
34
|
try:
|
|
@@ -37,7 +38,7 @@ def _populate_pypi_access_policies(sender, apps, verbosity, **kwargs):
|
|
|
37
38
|
print(_("AccessPolicy model does not exist. Skipping initialization."))
|
|
38
39
|
return
|
|
39
40
|
|
|
40
|
-
for viewset in (PyPIView, SimpleView, UploadView, MetadataView):
|
|
41
|
+
for viewset in (PyPIView, SimpleView, UploadView, MetadataView, FeedView):
|
|
41
42
|
access_policy = getattr(viewset, "DEFAULT_ACCESS_POLICY", None)
|
|
42
43
|
if access_policy is not None:
|
|
43
44
|
viewset_name = viewset.urlpattern()
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from email.utils import getaddresses
|
|
3
|
+
from urllib.parse import urljoin
|
|
4
|
+
|
|
5
|
+
from django.db.models import F, FilteredRelation, Min, Q
|
|
6
|
+
from django.http.response import HttpResponse, HttpResponseNotFound
|
|
7
|
+
from django.utils.decorators import method_decorator
|
|
8
|
+
from django.utils.feedgenerator import Rss201rev2Feed
|
|
9
|
+
from django.views.decorators.cache import cache_control
|
|
10
|
+
from django.views.decorators.http import condition
|
|
11
|
+
from drf_spectacular.utils import extend_schema
|
|
12
|
+
from packaging.utils import canonicalize_name
|
|
13
|
+
from rest_framework.viewsets import ViewSet
|
|
14
|
+
|
|
15
|
+
from pulp_python.app.cache import PythonApiCache, find_base_path_cached
|
|
16
|
+
from pulp_python.app.pypi.views import PyPIMixin, _etag_func
|
|
17
|
+
|
|
18
|
+
UPDATES_LIMIT = 100
|
|
19
|
+
PACKAGES_LIMIT = 40
|
|
20
|
+
PROJECT_RELEASES_LIMIT = 40
|
|
21
|
+
RSS_CONTENT_TYPE = "application/rss+xml; charset=utf-8"
|
|
22
|
+
|
|
23
|
+
# XML 1.0 disallowed characters (Django still escapes <>&).
|
|
24
|
+
_INVALID_XML_CHARS = re.compile("[\x00-\x08\x0b\x0c\x0e-\x1f\ud800-\udfff\ufffe\uffff]")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def sanitize_xml_text(value):
|
|
28
|
+
"""Return a string safe to place in an RSS text field."""
|
|
29
|
+
if not value:
|
|
30
|
+
return ""
|
|
31
|
+
return _INVALID_XML_CHARS.sub("", str(value))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def format_author(author_email):
|
|
35
|
+
"""Return an RSS author email, or None if the value is not a usable address."""
|
|
36
|
+
if not author_email:
|
|
37
|
+
return None
|
|
38
|
+
emails = []
|
|
39
|
+
for _, email in getaddresses([author_email]):
|
|
40
|
+
if "@" not in email:
|
|
41
|
+
return None
|
|
42
|
+
emails.append(email)
|
|
43
|
+
return ", ".join(emails) or None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def annotate_added_at(content, repo_ver):
|
|
47
|
+
"""Annotate each package file with when it was added to this repository."""
|
|
48
|
+
return content.annotate(
|
|
49
|
+
active_membership=FilteredRelation(
|
|
50
|
+
"version_memberships",
|
|
51
|
+
condition=Q(
|
|
52
|
+
version_memberships__repository=repo_ver.repository,
|
|
53
|
+
version_memberships__version_removed=None,
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
file_added_at=F("active_membership__pulp_created"),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def iter_releases(content, repo_ver, name_normalized=None, limit=UPDATES_LIMIT):
|
|
61
|
+
"""Yield latest (name, version) releases in the served repository version."""
|
|
62
|
+
qs = annotate_added_at(content, repo_ver)
|
|
63
|
+
if name_normalized:
|
|
64
|
+
qs = qs.filter(name_normalized=name_normalized)
|
|
65
|
+
return (
|
|
66
|
+
qs.order_by()
|
|
67
|
+
.values("name_normalized", "version")
|
|
68
|
+
.annotate(
|
|
69
|
+
added_at=Min("file_added_at"),
|
|
70
|
+
name=Min("name"),
|
|
71
|
+
summary=Min("summary"),
|
|
72
|
+
author_email=Min("author_email"),
|
|
73
|
+
)
|
|
74
|
+
.order_by("-added_at", "name_normalized", "version")[:limit]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def iter_projects(content, repo_ver, limit=PACKAGES_LIMIT):
|
|
79
|
+
"""Yield latest newly added projects in the served repository version."""
|
|
80
|
+
qs = annotate_added_at(content, repo_ver)
|
|
81
|
+
return (
|
|
82
|
+
qs.order_by()
|
|
83
|
+
.values("name_normalized")
|
|
84
|
+
.annotate(
|
|
85
|
+
added_at=Min("file_added_at"),
|
|
86
|
+
name=Min("name"),
|
|
87
|
+
summary=Min("summary"),
|
|
88
|
+
author_email=Min("author_email"),
|
|
89
|
+
)
|
|
90
|
+
.order_by("-added_at", "name_normalized")[:limit]
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _item_dict(title, link, description, author_email, pubdate):
|
|
95
|
+
return {
|
|
96
|
+
"title": sanitize_xml_text(title),
|
|
97
|
+
"link": link,
|
|
98
|
+
"description": sanitize_xml_text(description),
|
|
99
|
+
"author_email": format_author(author_email),
|
|
100
|
+
"pubdate": pubdate,
|
|
101
|
+
"unique_id": link,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def render_rss(title, link, description, items):
|
|
106
|
+
"""Render an RSS 2.0 document from item dicts produced by `_item_dict`."""
|
|
107
|
+
feed = Rss201rev2Feed(
|
|
108
|
+
title=sanitize_xml_text(title),
|
|
109
|
+
link=link,
|
|
110
|
+
description=sanitize_xml_text(description),
|
|
111
|
+
language="en",
|
|
112
|
+
)
|
|
113
|
+
for item in items:
|
|
114
|
+
feed.add_item(
|
|
115
|
+
title=item["title"],
|
|
116
|
+
link=item["link"],
|
|
117
|
+
description=item["description"],
|
|
118
|
+
author_email=item["author_email"],
|
|
119
|
+
pubdate=item["pubdate"],
|
|
120
|
+
unique_id=item["unique_id"],
|
|
121
|
+
unique_id_is_permalink=True,
|
|
122
|
+
)
|
|
123
|
+
return feed.writeString("utf-8")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _release_link(index_url, name_normalized, version):
|
|
127
|
+
return urljoin(index_url, f"pypi/{name_normalized}/{version}/json")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _project_link(index_url, name_normalized):
|
|
131
|
+
return urljoin(index_url, f"pypi/{name_normalized}/json")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def render_updates_feed(index_url, releases):
|
|
135
|
+
items = [
|
|
136
|
+
_item_dict(
|
|
137
|
+
title=f"{release['name']} {release['version']}",
|
|
138
|
+
link=_release_link(index_url, release["name_normalized"], release["version"]),
|
|
139
|
+
description=release["summary"],
|
|
140
|
+
author_email=release["author_email"],
|
|
141
|
+
pubdate=release["added_at"],
|
|
142
|
+
)
|
|
143
|
+
for release in releases
|
|
144
|
+
]
|
|
145
|
+
return render_rss(
|
|
146
|
+
title="Recent updates",
|
|
147
|
+
link=index_url,
|
|
148
|
+
description="Recent updates to this Python package index",
|
|
149
|
+
items=items,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def render_packages_feed(index_url, projects):
|
|
154
|
+
items = [
|
|
155
|
+
_item_dict(
|
|
156
|
+
title=f"{project['name']} added to index",
|
|
157
|
+
link=_project_link(index_url, project["name_normalized"]),
|
|
158
|
+
description=project["summary"],
|
|
159
|
+
author_email=project["author_email"],
|
|
160
|
+
pubdate=project["added_at"],
|
|
161
|
+
)
|
|
162
|
+
for project in projects
|
|
163
|
+
]
|
|
164
|
+
return render_rss(
|
|
165
|
+
title="Newest packages",
|
|
166
|
+
link=index_url,
|
|
167
|
+
description="Newest packages registered on this Python package index",
|
|
168
|
+
items=items,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def render_project_releases_feed(index_url, project_name, releases):
|
|
173
|
+
project_link = _project_link(index_url, canonicalize_name(project_name))
|
|
174
|
+
items = [
|
|
175
|
+
_item_dict(
|
|
176
|
+
title=release["version"],
|
|
177
|
+
link=_release_link(index_url, release["name_normalized"], release["version"]),
|
|
178
|
+
description=release["summary"],
|
|
179
|
+
author_email=release["author_email"],
|
|
180
|
+
pubdate=release["added_at"],
|
|
181
|
+
)
|
|
182
|
+
for release in releases
|
|
183
|
+
]
|
|
184
|
+
return render_rss(
|
|
185
|
+
title=f"Recent updates for {project_name}",
|
|
186
|
+
link=project_link,
|
|
187
|
+
description=f"Recent updates to {project_name} on this Python package index",
|
|
188
|
+
items=items,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class FeedView(PyPIMixin, ViewSet):
|
|
193
|
+
"""View for PyPI-compatible RSS feeds on a distribution."""
|
|
194
|
+
|
|
195
|
+
endpoint_name = "rss"
|
|
196
|
+
DEFAULT_ACCESS_POLICY = {
|
|
197
|
+
"statements": [
|
|
198
|
+
{
|
|
199
|
+
"action": ["updates", "packages", "project_releases"],
|
|
200
|
+
"principal": "*",
|
|
201
|
+
"effect": "allow",
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
def _index_url(self, path):
|
|
207
|
+
return urljoin(self.base_api_url, f"{path}/")
|
|
208
|
+
|
|
209
|
+
def _rss_response(self, xml):
|
|
210
|
+
return HttpResponse(xml, content_type=RSS_CONTENT_TYPE)
|
|
211
|
+
|
|
212
|
+
@extend_schema(summary="Get latest updates RSS feed")
|
|
213
|
+
@method_decorator(cache_control(max_age=600, public=True))
|
|
214
|
+
@method_decorator(condition(etag_func=_etag_func))
|
|
215
|
+
@PythonApiCache(base_key=find_base_path_cached)
|
|
216
|
+
def updates(self, request, path):
|
|
217
|
+
"""Latest releases added to this index, analogous to PyPI `/rss/updates.xml`."""
|
|
218
|
+
repo_ver, content = self.get_rvc()
|
|
219
|
+
index_url = self._index_url(path)
|
|
220
|
+
releases = list(iter_releases(content, repo_ver)) if content is not None else []
|
|
221
|
+
return self._rss_response(render_updates_feed(index_url, releases))
|
|
222
|
+
|
|
223
|
+
@extend_schema(summary="Get newest packages RSS feed")
|
|
224
|
+
@method_decorator(cache_control(max_age=600, public=True))
|
|
225
|
+
@method_decorator(condition(etag_func=_etag_func))
|
|
226
|
+
@PythonApiCache(base_key=find_base_path_cached)
|
|
227
|
+
def packages(self, request, path):
|
|
228
|
+
"""Latest newly added projects, analogous to PyPI `/rss/packages.xml`."""
|
|
229
|
+
repo_ver, content = self.get_rvc()
|
|
230
|
+
index_url = self._index_url(path)
|
|
231
|
+
projects = list(iter_projects(content, repo_ver)) if content is not None else []
|
|
232
|
+
return self._rss_response(render_packages_feed(index_url, projects))
|
|
233
|
+
|
|
234
|
+
@extend_schema(summary="Get project releases RSS feed")
|
|
235
|
+
@method_decorator(cache_control(max_age=600, public=True))
|
|
236
|
+
@method_decorator(condition(etag_func=_etag_func))
|
|
237
|
+
@PythonApiCache(base_key=find_base_path_cached)
|
|
238
|
+
def project_releases(self, request, path, package):
|
|
239
|
+
"""Latest releases for one project, analogous to PyPI `/rss/project/<name>/releases.xml`."""
|
|
240
|
+
repo_ver, content = self.get_rvc()
|
|
241
|
+
normalized = canonicalize_name(package)
|
|
242
|
+
if content is None or not content.filter(name_normalized=normalized).exists():
|
|
243
|
+
return HttpResponseNotFound(f"{normalized} does not exist.")
|
|
244
|
+
|
|
245
|
+
index_url = self._index_url(path)
|
|
246
|
+
releases = list(
|
|
247
|
+
iter_releases(
|
|
248
|
+
content, repo_ver, name_normalized=normalized, limit=PROJECT_RELEASES_LIMIT
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
project_name = releases[0]["name"] if releases else package
|
|
252
|
+
return self._rss_response(render_project_releases_feed(index_url, project_name, releases))
|
|
@@ -318,8 +318,17 @@ class SimpleView(PackageUploadMixin, ViewSet):
|
|
|
318
318
|
Uses custom renderers for PyPI Simple API endpoints, defaulting to standard ones.
|
|
319
319
|
"""
|
|
320
320
|
if self.action in ["list", "retrieve"]:
|
|
321
|
-
#
|
|
322
|
-
|
|
321
|
+
# DRF resolves equally-specific media types in renderer order and does not
|
|
322
|
+
# account for q-values. Put the PyPI JSON renderer first when the client
|
|
323
|
+
# explicitly advertises it (as pip and uv do), otherwise retain HTML as the
|
|
324
|
+
# default for browser and legacy clients.
|
|
325
|
+
accept = self.request.META.get("HTTP_ACCEPT", "").lower()
|
|
326
|
+
renderers = [TemplateHTMLRenderer(), PyPISimpleHTMLRenderer()]
|
|
327
|
+
if PYPI_SIMPLE_V1_JSON in accept:
|
|
328
|
+
renderers.insert(0, PyPISimpleJSONRenderer())
|
|
329
|
+
else:
|
|
330
|
+
renderers.append(PyPISimpleJSONRenderer())
|
|
331
|
+
return renderers
|
|
323
332
|
else:
|
|
324
333
|
return [JSONRenderer(), BrowsableAPIRenderer()]
|
|
325
334
|
|
|
@@ -50,18 +50,29 @@ def repair(repository_pk: UUID) -> None:
|
|
|
50
50
|
num_repaired, pkgs_not_repaired, num_metadata_repaired, pkgs_metadata_not_repaired = (
|
|
51
51
|
repair_metadata(content)
|
|
52
52
|
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
|
|
54
|
+
def _format_pkg_set(pk_set):
|
|
55
|
+
"""Resolve a set of package PKs to 'name-version (pk)' strings."""
|
|
56
|
+
if not pk_set:
|
|
57
|
+
return "none"
|
|
58
|
+
packages = PythonPackageContent.objects.filter(pk__in=pk_set).values_list(
|
|
59
|
+
"name", "version", "pk"
|
|
60
|
+
)
|
|
61
|
+
return ", ".join(f"{name}-{version} ({pk})" for name, version, pk in packages)
|
|
58
62
|
|
|
59
63
|
log.info(
|
|
60
64
|
_(
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
+
"Repository: %s. "
|
|
66
|
+
"%d packages' metadata repaired. "
|
|
67
|
+
"Not repaired packages due to either inaccessible URL or mismatched sha256: [%s]. "
|
|
68
|
+
"%d metadata files repaired. "
|
|
69
|
+
"Packages whose metadata files could not be repaired: [%s]."
|
|
70
|
+
),
|
|
71
|
+
repository.name,
|
|
72
|
+
num_repaired,
|
|
73
|
+
_format_pkg_set(pkgs_not_repaired),
|
|
74
|
+
num_metadata_repaired,
|
|
75
|
+
_format_pkg_set(pkgs_metadata_not_repaired),
|
|
65
76
|
)
|
|
66
77
|
|
|
67
78
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
2
|
from django.urls import path
|
|
3
3
|
|
|
4
|
+
from pulp_python.app.pypi.feeds import FeedView
|
|
4
5
|
from pulp_python.app.pypi.views import (
|
|
5
6
|
MetadataView,
|
|
6
7
|
ProvenanceView,
|
|
@@ -43,5 +44,20 @@ urlpatterns = [
|
|
|
43
44
|
),
|
|
44
45
|
path(PYPI_API_URL + "yank/", YankView.as_view({"post": "yank"}), name="yank"),
|
|
45
46
|
path(PYPI_API_URL + "unyank/", YankView.as_view({"post": "unyank"}), name="unyank"),
|
|
47
|
+
path(
|
|
48
|
+
PYPI_API_URL + "rss/updates.xml",
|
|
49
|
+
FeedView.as_view({"get": "updates"}),
|
|
50
|
+
name="rss-updates",
|
|
51
|
+
),
|
|
52
|
+
path(
|
|
53
|
+
PYPI_API_URL + "rss/packages.xml",
|
|
54
|
+
FeedView.as_view({"get": "packages"}),
|
|
55
|
+
name="rss-packages",
|
|
56
|
+
),
|
|
57
|
+
path(
|
|
58
|
+
PYPI_API_URL + "rss/project/<str:package>/releases.xml",
|
|
59
|
+
FeedView.as_view({"get": "project_releases"}),
|
|
60
|
+
name="rss-project-releases",
|
|
61
|
+
),
|
|
46
62
|
path(PYPI_API_URL, PyPIView.as_view({"get": "retrieve"}), name="pypi-detail"),
|
|
47
63
|
]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from urllib.parse import urljoin, urlsplit
|
|
2
|
+
from xml.etree import ElementTree as ET
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from pulp_python.tests.functional.constants import (
|
|
8
|
+
PYTHON_EGG_FILENAME,
|
|
9
|
+
PYTHON_EGG_URL,
|
|
10
|
+
PYTHON_FIXTURES_URL,
|
|
11
|
+
PYTHON_WHEEL_FILENAME,
|
|
12
|
+
PYTHON_WHEEL_URL,
|
|
13
|
+
TWINE_EGG_FILENAME,
|
|
14
|
+
TWINE_EGG_URL,
|
|
15
|
+
TWINE_WHEEL_FILENAME,
|
|
16
|
+
TWINE_WHEEL_URL,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
TWINE_500_WHEEL_FILENAME = "twine-5.0.0-py3-none-any.whl"
|
|
20
|
+
TWINE_500_WHEEL_URL = urljoin(urljoin(PYTHON_FIXTURES_URL, "packages/"), TWINE_500_WHEEL_FILENAME)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _index_url(distro, bindings_cfg):
|
|
24
|
+
"""Build the index URL using the same origin the API client uses."""
|
|
25
|
+
path = urlsplit(distro.base_url).path
|
|
26
|
+
if not path.endswith("/"):
|
|
27
|
+
path += "/"
|
|
28
|
+
return bindings_cfg.host.rstrip("/") + path
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _get_feed(distro, relative, bindings_cfg):
|
|
32
|
+
return requests.get(urljoin(_index_url(distro, bindings_cfg), relative))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _parse_items(response):
|
|
36
|
+
assert response.status_code == 200, response.text
|
|
37
|
+
assert "application/rss+xml" in response.headers["Content-Type"]
|
|
38
|
+
root = ET.fromstring(response.content)
|
|
39
|
+
assert root.tag == "rss"
|
|
40
|
+
channel = root.find("channel")
|
|
41
|
+
assert channel is not None
|
|
42
|
+
return channel.findall("item")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _titles(items):
|
|
46
|
+
return [item.findtext("title") for item in items]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.parallel
|
|
50
|
+
def test_empty_feeds(bindings_cfg, python_empty_repo_distro):
|
|
51
|
+
"""Empty indexes return valid RSS documents with no items."""
|
|
52
|
+
_, distro = python_empty_repo_distro()
|
|
53
|
+
|
|
54
|
+
for relative in ("rss/updates.xml", "rss/packages.xml"):
|
|
55
|
+
items = _parse_items(_get_feed(distro, relative, bindings_cfg))
|
|
56
|
+
assert items == []
|
|
57
|
+
|
|
58
|
+
response = _get_feed(distro, "rss/project/shelf-reader/releases.xml", bindings_cfg)
|
|
59
|
+
assert response.status_code == 404
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.parallel
|
|
63
|
+
def test_feeds_projects_releases_and_files(
|
|
64
|
+
bindings_cfg, python_content_factory, python_empty_repo_distro
|
|
65
|
+
):
|
|
66
|
+
"""Feeds collapse files to releases, track new projects vs new versions, and stay per-index."""
|
|
67
|
+
repo, distro = python_empty_repo_distro()
|
|
68
|
+
_, other_distro = python_empty_repo_distro()
|
|
69
|
+
|
|
70
|
+
python_content_factory(PYTHON_EGG_FILENAME, url=PYTHON_EGG_URL, repository=repo)
|
|
71
|
+
python_content_factory(PYTHON_WHEEL_FILENAME, url=PYTHON_WHEEL_URL, repository=repo)
|
|
72
|
+
|
|
73
|
+
update_titles = _titles(_parse_items(_get_feed(distro, "rss/updates.xml", bindings_cfg)))
|
|
74
|
+
package_titles = _titles(_parse_items(_get_feed(distro, "rss/packages.xml", bindings_cfg)))
|
|
75
|
+
release_titles = _titles(
|
|
76
|
+
_parse_items(_get_feed(distro, "rss/project/shelf-reader/releases.xml", bindings_cfg))
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
assert update_titles == ["shelf-reader 0.1"]
|
|
80
|
+
assert package_titles == ["shelf-reader added to index"]
|
|
81
|
+
assert release_titles == ["0.1"]
|
|
82
|
+
assert _parse_items(_get_feed(other_distro, "rss/updates.xml", bindings_cfg)) == []
|
|
83
|
+
|
|
84
|
+
python_content_factory(TWINE_500_WHEEL_FILENAME, url=TWINE_500_WHEEL_URL, repository=repo)
|
|
85
|
+
|
|
86
|
+
update_titles = _titles(_parse_items(_get_feed(distro, "rss/updates.xml", bindings_cfg)))
|
|
87
|
+
package_titles = _titles(_parse_items(_get_feed(distro, "rss/packages.xml", bindings_cfg)))
|
|
88
|
+
assert update_titles == ["twine 5.0.0", "shelf-reader 0.1"]
|
|
89
|
+
assert package_titles == ["twine added to index", "shelf-reader added to index"]
|
|
90
|
+
|
|
91
|
+
python_content_factory(TWINE_WHEEL_FILENAME, url=TWINE_WHEEL_URL, repository=repo)
|
|
92
|
+
python_content_factory(TWINE_EGG_FILENAME, url=TWINE_EGG_URL, repository=repo)
|
|
93
|
+
|
|
94
|
+
update_titles = _titles(_parse_items(_get_feed(distro, "rss/updates.xml", bindings_cfg)))
|
|
95
|
+
package_titles = _titles(_parse_items(_get_feed(distro, "rss/packages.xml", bindings_cfg)))
|
|
96
|
+
twine_releases = _titles(
|
|
97
|
+
_parse_items(_get_feed(distro, "rss/project/twine/releases.xml", bindings_cfg))
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
assert update_titles == ["twine 5.1.0", "twine 5.0.0", "shelf-reader 0.1"]
|
|
101
|
+
assert package_titles == ["twine added to index", "shelf-reader added to index"]
|
|
102
|
+
assert twine_releases == ["5.1.0", "5.0.0"]
|
|
103
|
+
|
|
104
|
+
response = _get_feed(distro, "rss/project/does-not-exist/releases.xml", bindings_cfg)
|
|
105
|
+
assert response.status_code == 404
|
|
106
|
+
assert _parse_items(_get_feed(other_distro, "rss/updates.xml", bindings_cfg)) == []
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.parallel
|
|
110
|
+
def test_pinned_version_feeds(
|
|
111
|
+
bindings_cfg,
|
|
112
|
+
python_content_factory,
|
|
113
|
+
python_distribution_factory,
|
|
114
|
+
python_repo_factory,
|
|
115
|
+
):
|
|
116
|
+
"""Feeds for a pinned repository version stay frozen, as publication-backed indexes do."""
|
|
117
|
+
repo = python_repo_factory()
|
|
118
|
+
python_content_factory(PYTHON_EGG_FILENAME, url=PYTHON_EGG_URL, repository=repo)
|
|
119
|
+
distro = python_distribution_factory(repository=repo, version="1")
|
|
120
|
+
|
|
121
|
+
update_titles = _titles(_parse_items(_get_feed(distro, "rss/updates.xml", bindings_cfg)))
|
|
122
|
+
package_titles = _titles(_parse_items(_get_feed(distro, "rss/packages.xml", bindings_cfg)))
|
|
123
|
+
assert update_titles == ["shelf-reader 0.1"]
|
|
124
|
+
assert package_titles == ["shelf-reader added to index"]
|
|
125
|
+
|
|
126
|
+
item = _parse_items(_get_feed(distro, "rss/updates.xml", bindings_cfg))[0]
|
|
127
|
+
assert item.findtext("link").endswith("pypi/shelf-reader/0.1/json")
|
|
128
|
+
assert item.findtext("guid").endswith("pypi/shelf-reader/0.1/json")
|
|
129
|
+
|
|
130
|
+
python_content_factory(TWINE_WHEEL_FILENAME, url=TWINE_WHEEL_URL, repository=repo)
|
|
131
|
+
update_titles = _titles(_parse_items(_get_feed(distro, "rss/updates.xml", bindings_cfg)))
|
|
132
|
+
assert update_titles == ["shelf-reader 0.1"]
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_pypi_simple_api.py
RENAMED
|
@@ -169,8 +169,8 @@ def test_simple_json_detail_api(
|
|
|
169
169
|
(PYPI_TEXT_HTML, PYPI_TEXT_HTML),
|
|
170
170
|
(PYPI_SIMPLE_V1_HTML, PYPI_SIMPLE_V1_HTML),
|
|
171
171
|
(PYPI_SIMPLE_V1_JSON, PYPI_SIMPLE_V1_JSON),
|
|
172
|
-
#
|
|
173
|
-
(f"{PYPI_SIMPLE_V1_JSON}, {PYPI_SIMPLE_V1_HTML}",
|
|
172
|
+
# Clients such as pip and uv advertise JSON first, with HTML as a fallback.
|
|
173
|
+
(f"{PYPI_SIMPLE_V1_JSON}, {PYPI_SIMPLE_V1_HTML}", PYPI_SIMPLE_V1_JSON),
|
|
174
174
|
# Everything else should be html
|
|
175
175
|
("", PYPI_TEXT_HTML),
|
|
176
176
|
("application/json", PYPI_TEXT_HTML),
|
|
@@ -191,3 +191,5 @@ def test_simple_api_content_headers(
|
|
|
191
191
|
response = requests.get(url, headers={"Accept": header})
|
|
192
192
|
assert response.status_code == 200
|
|
193
193
|
assert result in response.headers["Content-Type"]
|
|
194
|
+
if url == detail_url and result == PYPI_SIMPLE_V1_JSON:
|
|
195
|
+
assert all(file["upload-time"] for file in response.json()["files"])
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_simple_cache.py
RENAMED
|
@@ -91,14 +91,13 @@ def test_simple_cache_separate_accept_headers(synced_distro):
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
@pytest.mark.parallel
|
|
94
|
-
def
|
|
94
|
+
def test_simple_cache_negotiated_media_types_are_separate(synced_distro):
|
|
95
95
|
"""
|
|
96
|
-
|
|
96
|
+
JSON and HTML responses must not poison each other in the cache.
|
|
97
97
|
|
|
98
|
-
Clients like uv/pip send an Accept that allows both JSON and HTML.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
the JSON entry is not served (and re-rendered) for the HTML request.
|
|
98
|
+
Clients like uv/pip send an Accept that allows both JSON and HTML. The
|
|
99
|
+
negotiated JSON response must be cached separately from an explicit HTML
|
|
100
|
+
response.
|
|
102
101
|
"""
|
|
103
102
|
url = f"{urljoin(synced_distro.base_url, 'simple/')}aiohttp"
|
|
104
103
|
# pip/uv-style Accept: JSON preferred, HTML still acceptable
|
|
@@ -112,13 +111,13 @@ def test_simple_cache_format_json_does_not_poison_html(synced_distro):
|
|
|
112
111
|
assert r_json.headers["X-PULP-CACHE"] == "MISS"
|
|
113
112
|
assert r_json.json()["name"] == "aiohttp"
|
|
114
113
|
|
|
115
|
-
r_html = requests.get(url, headers=
|
|
114
|
+
r_html = requests.get(url, headers={"Accept": PYPI_TEXT_HTML})
|
|
116
115
|
assert r_html.status_code == 200
|
|
117
116
|
assert PYPI_TEXT_HTML in r_html.headers["Content-Type"]
|
|
118
117
|
assert r_html.headers["X-PULP-CACHE"] == "MISS"
|
|
119
118
|
assert b"<a href=" in r_html.content
|
|
120
119
|
|
|
121
|
-
r_html_hit = requests.get(url, headers=
|
|
120
|
+
r_html_hit = requests.get(url, headers={"Accept": PYPI_TEXT_HTML})
|
|
122
121
|
assert r_html_hit.status_code == 200
|
|
123
122
|
assert r_html_hit.headers["X-PULP-CACHE"] == "HIT"
|
|
124
123
|
assert PYPI_TEXT_HTML in r_html_hit.headers["Content-Type"]
|
|
@@ -59,6 +59,7 @@ pulp_python/app/migrations/0023_packageyank.py
|
|
|
59
59
|
pulp_python/app/migrations/0024_pythonrepository_error_on_reject.py
|
|
60
60
|
pulp_python/app/migrations/__init__.py
|
|
61
61
|
pulp_python/app/pypi/__init__.py
|
|
62
|
+
pulp_python/app/pypi/feeds.py
|
|
62
63
|
pulp_python/app/pypi/serializers.py
|
|
63
64
|
pulp_python/app/pypi/views.py
|
|
64
65
|
pulp_python/app/tasks/__init__.py
|
|
@@ -88,6 +89,7 @@ pulp_python/tests/functional/api/test_download_content.py
|
|
|
88
89
|
pulp_python/tests/functional/api/test_export_import.py
|
|
89
90
|
pulp_python/tests/functional/api/test_full_mirror.py
|
|
90
91
|
pulp_python/tests/functional/api/test_pypi_apis.py
|
|
92
|
+
pulp_python/tests/functional/api/test_pypi_feeds.py
|
|
91
93
|
pulp_python/tests/functional/api/test_pypi_simple_api.py
|
|
92
94
|
pulp_python/tests/functional/api/test_rbac.py
|
|
93
95
|
pulp_python/tests/functional/api/test_repair.py
|
|
@@ -7,7 +7,7 @@ build-backend = 'setuptools.build_meta'
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "pulp-python"
|
|
10
|
-
version = "3.
|
|
10
|
+
version = "3.36.1"
|
|
11
11
|
description = "pulp-python plugin for the Pulp Project"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
authors = [
|
|
@@ -79,7 +79,7 @@ ignore = [
|
|
|
79
79
|
[tool.bumpversion]
|
|
80
80
|
# This section is managed by the plugin template. Do not edit manually.
|
|
81
81
|
|
|
82
|
-
current_version = "3.
|
|
82
|
+
current_version = "3.36.1"
|
|
83
83
|
commit = false
|
|
84
84
|
tag = false
|
|
85
85
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<alpha>0a)?(?P<patch>\\d+)(\\.(?P<release>[a-z]+))?"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0003_new_sync_filters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0010_update_json_field.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0013_add_rbac_permissions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0018_packageprovenance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/app/migrations/0022_pythonblocklistentry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_attestations.py
RENAMED
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_auto_publish.py
RENAMED
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_blocklist.py
RENAMED
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_consume_content.py
RENAMED
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_crud_content_unit.py
RENAMED
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_crud_publications.py
RENAMED
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_crud_remotes.py
RENAMED
|
File without changes
|
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_download_content.py
RENAMED
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_export_import.py
RENAMED
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_full_mirror.py
RENAMED
|
File without changes
|
{pulp_python-3.35.0 → pulp_python-3.36.1}/pulp_python/tests/functional/api/test_pypi_apis.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|