|
1 | 1 | import asyncio |
| 2 | +import json |
2 | 3 | import logging |
3 | 4 | from functools import partial |
4 | | -from urllib.parse import urljoin, urlparse |
| 5 | +from urllib.parse import urlparse |
5 | 6 |
|
6 | | -from aiohttp import ClientError |
7 | 7 | from bandersnatch.configuration import BandersnatchConfig |
8 | 8 | from bandersnatch.master import Master |
9 | 9 | from bandersnatch.mirror import Mirror |
10 | 10 | from packaging.requirements import Requirement |
11 | | -from pypi_simple import IndexPage |
| 11 | +from pypi_simple import ACCEPT_ANY, IndexPage, UnsupportedContentTypeError |
12 | 12 |
|
13 | 13 | from pulpcore.plugin.download import HttpDownloader |
14 | 14 | from pulpcore.plugin.exceptions import SyncError |
@@ -168,36 +168,28 @@ def __init__(self, serial, master, workers, deferred_download, python_stage, pro |
168 | 168 |
|
169 | 169 | async def determine_packages_to_sync(self): |
170 | 170 | """ |
171 | | - Called when includes wasn't specified. List all projects from the remote |
172 | | - via the PEP 691 Simple JSON API, falling back to HTML /simple/. |
| 171 | + Called when includes wasn't specified. List all projects from the remote's simple index. |
173 | 172 | """ |
174 | 173 | logger.info("Syncing all packages from %s", self.master.url) |
175 | | - try: |
176 | | - simple_index = await self.master.fetch_simple_index() |
177 | | - if not isinstance(simple_index, dict) or "projects" not in simple_index: |
178 | | - raise ValueError("Simple JSON index is missing a projects list") |
179 | | - for project in simple_index["projects"]: |
180 | | - name = project.get("name") |
181 | | - if name is None: |
182 | | - continue |
183 | | - # _last-serial is a PyPI extension; default to 0 when absent |
184 | | - self.packages_to_sync[name] = project.get("_last-serial", 0) |
185 | | - self.target_serial = max( |
186 | | - [self.synced_serial or 0] + [int(v) for v in self.packages_to_sync.values()] |
187 | | - ) |
188 | | - except (ClientError, ValueError, TypeError, AttributeError) as exc: |
189 | | - logger.info( |
190 | | - "Failed to list packages via Simple JSON API (%s); " |
191 | | - "falling back to HTML simple index.", |
192 | | - exc, |
193 | | - ) |
194 | | - url = urljoin(self.remote.url, "simple/") |
195 | | - downloader = self.remote.get_downloader(url=url) |
196 | | - result = await downloader.run() |
197 | | - with open(result.path) as f: |
198 | | - index = IndexPage.from_html(f.read()) |
199 | | - self.packages_to_sync.update({p: 0 for p in index.projects}) |
200 | | - self.target_serial = result.headers.get(PYPI_LAST_SERIAL, 0) |
| 174 | + url = f"{self.remote.url.rstrip('/')}/simple/" |
| 175 | + downloader = self.remote.get_downloader(url=url) |
| 176 | + result = await downloader.run( |
| 177 | + extra_data={"request_kwargs": {"headers": {"Accept": ACCEPT_ANY}}} |
| 178 | + ) |
| 179 | + |
| 180 | + content_type = result.headers.get("Content-Type", "text/html").partition(";")[0].lower() |
| 181 | + with open(result.path, "rb") as f: |
| 182 | + content = f.read() |
| 183 | + |
| 184 | + if content_type == "application/vnd.pypi.simple.v1+json": |
| 185 | + index = IndexPage.from_json_data(json.loads(content)) |
| 186 | + elif content_type in {"application/vnd.pypi.simple.v1+html", "text/html"}: |
| 187 | + index = IndexPage.from_html(content) |
| 188 | + else: |
| 189 | + raise UnsupportedContentTypeError(url, content_type) |
| 190 | + |
| 191 | + self.packages_to_sync = {project: 0 for project in index.projects} |
| 192 | + self.target_serial = index.last_serial or result.headers.get(PYPI_LAST_SERIAL, 0) |
201 | 193 |
|
202 | 194 | self._filter_packages() |
203 | 195 | if self.target_serial: |
|
0 commit comments