@@ -1264,17 +1264,14 @@ class Package:
1264
1264
1265
1265
name : str
1266
1266
version : str
1267
+ filename : str
1267
1268
metadata : MetadataKind
1268
1269
# This will override any dependencies specified in the actual dist's METADATA.
1269
1270
requires_dist : Tuple [str , ...] = ()
1270
1271
1271
- def filename (self ) -> str :
1272
- """We will only be pointed at .tar.gz packages, so we can guess the name."""
1273
- return f"{ self .name } -{ self .version } .tar.gz"
1274
-
1275
1272
def metadata_filename (self ) -> str :
1276
1273
"""This is specified by PEP 658."""
1277
- return f"{ self .filename () } .metadata"
1274
+ return f"{ self .filename } .metadata"
1278
1275
1279
1276
def generate_additional_tag (self ) -> str :
1280
1277
"""This gets injected into the <a> tag in the generated PyPI index page for this
@@ -1289,26 +1286,27 @@ def generate_additional_tag(self) -> str:
1289
1286
checksum = sha256 (self .generate_metadata ()).hexdigest ()
1290
1287
return f'data-dist-info-metadata="sha256={ checksum } "'
1291
1288
1289
+ def requires_str (self ) -> str :
1290
+ if not self .requires_dist :
1291
+ return ""
1292
+ joined = " and " .join (self .requires_dist )
1293
+ return f"Requires-Dist: { joined } "
1294
+
1292
1295
def generate_metadata (self ) -> bytes :
1293
1296
"""This is written to `self.metadata_filename()` and will override the actual
1294
1297
dist's METADATA, unless `self.metadata == MetadataKind.NoFile`."""
1295
- if self .requires_dist :
1296
- joined = " and " .join (self .requires_dist )
1297
- requires_str = f"Requires-Dist: { joined } "
1298
- else :
1299
- requires_str = ""
1300
1298
return dedent (
1301
1299
f"""\
1302
1300
Metadata-Version: 2.1
1303
1301
Name: { self .name }
1304
1302
Version: { self .version }
1305
- { requires_str }
1303
+ { self . requires_str () }
1306
1304
"""
1307
1305
).encode ("utf-8" )
1308
1306
1309
1307
1310
1308
@pytest .fixture (scope = "function" )
1311
- def index_html_content (tmpdir : Path ) -> Callable [... , Path ]:
1309
+ def write_index_html_content (tmpdir : Path ) -> Callable [[ str ] , Path ]:
1312
1310
"""Generate a PyPI package index.html within a temporary local directory."""
1313
1311
html_dir = tmpdir / "index_html_content"
1314
1312
html_dir .mkdir ()
@@ -1327,13 +1325,14 @@ def generate_index_html_subdir(index_html: str) -> Path:
1327
1325
1328
1326
1329
1327
@pytest .fixture (scope = "function" )
1330
- def index_for_packages (
1328
+ def html_index_for_packages (
1331
1329
shared_data : TestData ,
1332
- index_html_content : Callable [... , Path ],
1330
+ write_index_html_content : Callable [[ str ] , Path ],
1333
1331
) -> Callable [..., Path ]:
1334
- """Generate a PyPI package index within a local directory pointing to blank data."""
1332
+ """Generate a PyPI HTML package index within a local directory pointing to
1333
+ blank data."""
1335
1334
1336
- def generate_index_for_packages (packages : Dict [str , List [Package ]]) -> Path :
1335
+ def generate_html_index_for_packages (packages : Dict [str , List [Package ]]) -> Path :
1337
1336
"""
1338
1337
Produce a PyPI directory structure pointing to the specified packages.
1339
1338
"""
@@ -1353,7 +1352,7 @@ def generate_index_for_packages(packages: Dict[str, List[Package]]) -> Path:
1353
1352
</body>
1354
1353
</html>"""
1355
1354
# (2) Generate the index.html in a new subdirectory of the temp directory.
1356
- index_html_subdir = index_html_content (index_html )
1355
+ index_html_subdir = write_index_html_content (index_html )
1357
1356
1358
1357
# (3) Generate subdirectories for individual packages, each with their own
1359
1358
# index.html.
@@ -1366,12 +1365,12 @@ def generate_index_for_packages(packages: Dict[str, List[Package]]) -> Path:
1366
1365
# (3.1) Generate the <a> tag which pip can crawl pointing to this
1367
1366
# specific package version.
1368
1367
download_links .append (
1369
- f' <a href="{ package_link .filename () } " { package_link .generate_additional_tag ()} >{ package_link .filename () } </a><br/>' # noqa: E501
1368
+ f' <a href="{ package_link .filename } " { package_link .generate_additional_tag ()} >{ package_link .filename } </a><br/>' # noqa: E501
1370
1369
)
1371
1370
# (3.2) Copy over the corresponding file in `shared_data.packages`.
1372
1371
shutil .copy (
1373
- shared_data .packages / package_link .filename () ,
1374
- pkg_subdir / package_link .filename () ,
1372
+ shared_data .packages / package_link .filename ,
1373
+ pkg_subdir / package_link .filename ,
1375
1374
)
1376
1375
# (3.3) Write a metadata file, if applicable.
1377
1376
if package_link .metadata != MetadataKind .NoFile :
@@ -1399,13 +1398,13 @@ def generate_index_for_packages(packages: Dict[str, List[Package]]) -> Path:
1399
1398
1400
1399
return index_html_subdir
1401
1400
1402
- return generate_index_for_packages
1401
+ return generate_html_index_for_packages
1403
1402
1404
1403
1405
1404
@pytest .fixture (scope = "function" )
1406
- def download_generated_index (
1405
+ def download_generated_html_index (
1407
1406
script : PipTestEnvironment ,
1408
- index_for_packages : Callable [... , Path ],
1407
+ html_index_for_packages : Callable [[ Dict [ str , List [ Package ]]] , Path ],
1409
1408
tmpdir : Path ,
1410
1409
) -> Callable [..., Tuple [TestPipResult , Path ]]:
1411
1410
"""Execute `pip download` against a generated PyPI index."""
@@ -1420,7 +1419,7 @@ def run_for_generated_index(
1420
1419
Produce a PyPI directory structure pointing to the specified packages, then
1421
1420
execute `pip download -i ...` pointing to our generated index.
1422
1421
"""
1423
- index_dir = index_for_packages (packages )
1422
+ index_dir = html_index_for_packages (packages )
1424
1423
pip_args = [
1425
1424
"download" ,
1426
1425
"-d" ,
@@ -1438,17 +1437,61 @@ def run_for_generated_index(
1438
1437
# The package database we generate for testing PEP 658 support.
1439
1438
_simple_packages : Dict [str , List [Package ]] = {
1440
1439
"simple" : [
1441
- Package ("simple" , "1.0" , MetadataKind .Sha256 ),
1442
- Package ("simple" , "2.0" , MetadataKind .No ),
1440
+ Package ("simple" , "1.0" , "simple-1.0.tar.gz" , MetadataKind .Sha256 ),
1441
+ Package ("simple" , "2.0" , "simple-2.0.tar.gz" , MetadataKind .No ),
1443
1442
# This will raise a hashing error.
1444
- Package ("simple" , "3.0" , MetadataKind .WrongHash ),
1443
+ Package ("simple" , "3.0" , "simple-3.0.tar.gz" , MetadataKind .WrongHash ),
1445
1444
],
1446
1445
"simple2" : [
1447
1446
# Override the dependencies here in order to force pip to download
1448
1447
# simple-1.0.tar.gz as well.
1449
- Package ("simple2" , "1.0" , MetadataKind .Unhashed , ("simple==1.0" ,)),
1448
+ Package (
1449
+ "simple2" ,
1450
+ "1.0" ,
1451
+ "simple2-1.0.tar.gz" ,
1452
+ MetadataKind .Unhashed ,
1453
+ ("simple==1.0" ,),
1454
+ ),
1450
1455
# This will raise an error when pip attempts to fetch the metadata file.
1451
- Package ("simple2" , "2.0" , MetadataKind .NoFile ),
1456
+ Package ("simple2" , "2.0" , "simple2-2.0.tar.gz" , MetadataKind .NoFile ),
1457
+ ],
1458
+ "colander" : [
1459
+ # Ensure we can read the dependencies from a metadata file within a wheel
1460
+ # *without* PEP 658 metadata.
1461
+ Package (
1462
+ "colander" , "0.9.9" , "colander-0.9.9-py2.py3-none-any.whl" , MetadataKind .No
1463
+ ),
1464
+ ],
1465
+ "compilewheel" : [
1466
+ # Ensure we can override the dependencies of a wheel file by injecting PEP
1467
+ # 658 metadata.
1468
+ Package (
1469
+ "compilewheel" ,
1470
+ "1.0" ,
1471
+ "compilewheel-1.0-py2.py3-none-any.whl" ,
1472
+ MetadataKind .Unhashed ,
1473
+ ("simple==1.0" ,),
1474
+ ),
1475
+ ],
1476
+ "has-script" : [
1477
+ # Ensure we check PEP 658 metadata hashing errors for wheel files.
1478
+ Package (
1479
+ "has-script" ,
1480
+ "1.0" ,
1481
+ "has.script-1.0-py2.py3-none-any.whl" ,
1482
+ MetadataKind .WrongHash ,
1483
+ ),
1484
+ ],
1485
+ "translationstring" : [
1486
+ Package (
1487
+ "translationstring" , "1.1" , "translationstring-1.1.tar.gz" , MetadataKind .No
1488
+ ),
1489
+ ],
1490
+ "priority" : [
1491
+ # Ensure we check for a missing metadata file for wheels.
1492
+ Package (
1493
+ "priority" , "1.0" , "priority-1.0-py2.py3-none-any.whl" , MetadataKind .NoFile
1494
+ ),
1452
1495
],
1453
1496
}
1454
1497
@@ -1458,16 +1501,24 @@ def run_for_generated_index(
1458
1501
[
1459
1502
("simple2==1.0" , ["simple-1.0.tar.gz" , "simple2-1.0.tar.gz" ]),
1460
1503
("simple==2.0" , ["simple-2.0.tar.gz" ]),
1504
+ (
1505
+ "colander" ,
1506
+ ["colander-0.9.9-py2.py3-none-any.whl" , "translationstring-1.1.tar.gz" ],
1507
+ ),
1508
+ (
1509
+ "compilewheel" ,
1510
+ ["compilewheel-1.0-py2.py3-none-any.whl" , "simple-1.0.tar.gz" ],
1511
+ ),
1461
1512
],
1462
1513
)
1463
1514
def test_download_metadata (
1464
- download_generated_index : Callable [..., Tuple [TestPipResult , Path ]],
1515
+ download_generated_html_index : Callable [..., Tuple [TestPipResult , Path ]],
1465
1516
requirement_to_download : str ,
1466
1517
expected_outputs : List [str ],
1467
1518
) -> None :
1468
1519
"""Verify that if a data-dist-info-metadata attribute is present, then it is used
1469
1520
instead of the actual dist's METADATA."""
1470
- _ , download_dir = download_generated_index (
1521
+ _ , download_dir = download_generated_html_index (
1471
1522
_simple_packages ,
1472
1523
[requirement_to_download ],
1473
1524
)
@@ -1480,17 +1531,21 @@ def test_download_metadata(
1480
1531
(
1481
1532
"simple==3.0" ,
1482
1533
"95e0f200b6302989bcf2cead9465cf229168295ea330ca30d1ffeab5c0fed996" ,
1483
- )
1534
+ ),
1535
+ (
1536
+ "has-script" ,
1537
+ "16ba92d7f6f992f6de5ecb7d58c914675cf21f57f8e674fb29dcb4f4c9507e5b" ,
1538
+ ),
1484
1539
],
1485
1540
)
1486
1541
def test_incorrect_metadata_hash (
1487
- download_generated_index : Callable [..., Tuple [TestPipResult , Path ]],
1542
+ download_generated_html_index : Callable [..., Tuple [TestPipResult , Path ]],
1488
1543
requirement_to_download : str ,
1489
1544
real_hash : str ,
1490
1545
) -> None :
1491
1546
"""Verify that if a hash for data-dist-info-metadata is provided, it must match the
1492
1547
actual hash of the metadata file."""
1493
- result , _ = download_generated_index (
1548
+ result , _ = download_generated_html_index (
1494
1549
_simple_packages ,
1495
1550
[requirement_to_download ],
1496
1551
allow_error = True ,
@@ -1503,23 +1558,28 @@ def test_incorrect_metadata_hash(
1503
1558
1504
1559
1505
1560
@pytest .mark .parametrize (
1506
- "requirement_to_download" ,
1507
- ["simple2==2.0" ],
1561
+ "requirement_to_download, expected_url" ,
1562
+ [
1563
+ ("simple2==2.0" , "simple2-2.0.tar.gz.metadata" ),
1564
+ ("priority" , "priority-1.0-py2.py3-none-any.whl.metadata" ),
1565
+ ],
1508
1566
)
1509
1567
def test_metadata_not_found (
1510
- download_generated_index : Callable [..., Tuple [TestPipResult , Path ]],
1568
+ download_generated_html_index : Callable [..., Tuple [TestPipResult , Path ]],
1511
1569
requirement_to_download : str ,
1570
+ expected_url : str ,
1512
1571
) -> None :
1513
1572
"""Verify that if a data-dist-info-metadata attribute is provided, that pip will
1514
1573
fetch the .metadata file at the location specified by PEP 658, and error
1515
1574
if unavailable."""
1516
- result , _ = download_generated_index (
1575
+ result , _ = download_generated_html_index (
1517
1576
_simple_packages ,
1518
1577
[requirement_to_download ],
1519
1578
allow_error = True ,
1520
1579
)
1521
1580
assert result .returncode != 0
1581
+ expected_re = re .escape (expected_url )
1522
1582
pattern = re .compile (
1523
- r "ERROR: 404 Client Error: FileNotFoundError for url:.*simple2-2\.0\.tar\.gz\.metadata" # noqa: E501
1583
+ f "ERROR: 404 Client Error: FileNotFoundError for url:.*{ expected_re } "
1524
1584
)
1525
1585
assert pattern .search (result .stderr ), (pattern , result .stderr )
0 commit comments