1818from pip ._internal .locations import get_scheme
1919from pip ._internal .models .direct_url import (
2020 DIRECT_URL_METADATA_NAME ,
21+ PROVENANCE_URL_METADATA_NAME ,
2122 ArchiveInfo ,
2223 DirectUrl ,
2324)
@@ -330,6 +331,17 @@ def main():
330331 "gui_scripts" : ["sample2 = sample:main" ],
331332 },
332333 ).save_to_dir (tmpdir )
334+ self .download_info = DirectUrl (
335+ url = "https://localhost:8080/sample/sample-1.2.0-py3-none-any.whl" ,
336+ info = ArchiveInfo (
337+ hash = "sha256=257ded4ea1fafa475f099e544b2d7560f674d42917e096d462e"
338+ "8a46a64f51245" ,
339+ hashes = {
340+ "sha256" : "257ded4ea1fafa475f099e544b2d7560f674d42917e096d46"
341+ "2e8a46a64f51245" ,
342+ },
343+ ),
344+ )
333345 self .req = Requirement ("sample" )
334346 self .src = os .path .join (tmpdir , "src" )
335347 self .dest = os .path .join (tmpdir , "dest" )
@@ -370,6 +382,8 @@ def test_std_install(self, data: TestData, tmpdir: Path) -> None:
370382 self .name ,
371383 self .wheelpath ,
372384 scheme = self .scheme ,
385+ download_info = self .download_info ,
386+ is_direct = False ,
373387 req_description = str (self .req ),
374388 )
375389 self .assert_installed (0o644 )
@@ -388,6 +402,8 @@ def test_std_install_with_custom_umask(
388402 self .name ,
389403 self .wheelpath ,
390404 scheme = self .scheme ,
405+ download_info = self .download_info ,
406+ is_direct = False ,
391407 req_description = str (self .req ),
392408 )
393409 self .assert_installed (expected_permission )
@@ -400,6 +416,8 @@ def test_std_install_requested(self, data: TestData, tmpdir: Path) -> None:
400416 self .name ,
401417 self .wheelpath ,
402418 scheme = self .scheme ,
419+ download_info = self .download_info ,
420+ is_direct = False ,
403421 req_description = str (self .req ),
404422 requested = True ,
405423 )
@@ -415,27 +433,100 @@ def test_std_install_with_direct_url(self, data: TestData, tmpdir: Path) -> None
415433 because wheelpath is typically the result of a local build.
416434 """
417435 self .prep (data , tmpdir )
418- direct_url = DirectUrl (
436+ download_info = DirectUrl (
419437 url = "file:///home/user/archive.tgz" ,
420438 info = ArchiveInfo (),
421439 )
422440 wheel .install_wheel (
423441 self .name ,
424442 self .wheelpath ,
425443 scheme = self .scheme ,
444+ download_info = download_info ,
445+ is_direct = True ,
426446 req_description = str (self .req ),
427- direct_url = direct_url ,
428447 )
429448 direct_url_path = os .path .join (self .dest_dist_info , DIRECT_URL_METADATA_NAME )
430449 self .assert_permission (direct_url_path , 0o644 )
431450 with open (direct_url_path , "rb" ) as f1 :
432- expected_direct_url_json = direct_url .to_json ()
451+ expected_direct_url_json = download_info .to_json ()
433452 direct_url_json = f1 .read ().decode ("utf-8" )
434453 assert direct_url_json == expected_direct_url_json
435- # check that the direc_url file is part of RECORDS
454+ # check that the direct_url file is part of RECORDS
436455 with open (os .path .join (self .dest_dist_info , "RECORD" )) as f2 :
437456 assert DIRECT_URL_METADATA_NAME in f2 .read ()
438457
458+ def test_std_install_with_provenance_url (
459+ self , data : TestData , tmpdir : Path
460+ ) -> None :
461+ """Test that install_wheel creates provenance_url.json metadata."""
462+ self .prep (data , tmpdir )
463+ download_info = DirectUrl (
464+ url = "https://pypi.org/simple/sample/sample-1.2.0-py3-none-any.whl" ,
465+ info = ArchiveInfo (
466+ hash = "sha256=257ded4ea1fafa475f099e544b2d7560f674d42"
467+ "917e096d462e8a46a64f51245" ,
468+ hashes = {
469+ "sha256" : "257ded4ea1fafa475f099e544b2d7560f674d"
470+ "42917e096d462e8a46a64f51245" ,
471+ },
472+ ),
473+ )
474+ wheel .install_wheel (
475+ self .name ,
476+ self .wheelpath ,
477+ scheme = self .scheme ,
478+ download_info = download_info ,
479+ is_direct = False ,
480+ req_description = str (self .req ),
481+ )
482+ provenance_url_path = os .path .join (
483+ self .dest_dist_info , PROVENANCE_URL_METADATA_NAME
484+ )
485+ self .assert_permission (provenance_url_path , 0o644 )
486+ with open (provenance_url_path , "rb" ) as f1 :
487+ expected_provenance_url_json = download_info .to_json (keep_hash_key = False )
488+ provenance_url_json = f1 .read ().decode ("utf-8" )
489+ assert provenance_url_json == expected_provenance_url_json
490+ # check that the provenance_url.json file is part of RECORDS
491+ with open (os .path .join (self .dest_dist_info , "RECORD" )) as f2 :
492+ assert PROVENANCE_URL_METADATA_NAME in f2 .read ()
493+
494+ @pytest .mark .parametrize (
495+ "hashes" ,
496+ [
497+ pytest .param (None , id = "None" ),
498+ pytest .param ({}, id = "empty" ),
499+ ],
500+ )
501+ def test_std_install_with_provenance_url_no_hashes (
502+ self , data : TestData , tmpdir : Path , hashes : Optional [Dict [str , str ]]
503+ ) -> None :
504+ """Test that install_wheel does not create provenance_url.json
505+ when hashes are missing.
506+ """
507+ self .prep (data , tmpdir )
508+ download_info = DirectUrl (
509+ url = "https://pypi.org/simple/sample/sample-1.2.0-py3-none-any.whl" ,
510+ info = ArchiveInfo (
511+ hash = None ,
512+ hashes = hashes ,
513+ ),
514+ )
515+ wheel .install_wheel (
516+ self .name ,
517+ self .wheelpath ,
518+ scheme = self .scheme ,
519+ download_info = download_info ,
520+ is_direct = False ,
521+ req_description = str (self .req ),
522+ )
523+ provenance_url_path = os .path .join (
524+ self .dest_dist_info , PROVENANCE_URL_METADATA_NAME
525+ )
526+ assert not os .path .exists (provenance_url_path )
527+ with open (os .path .join (self .dest_dist_info , "RECORD" )) as f2 :
528+ assert PROVENANCE_URL_METADATA_NAME not in f2 .read ()
529+
439530 def test_install_prefix (self , data : TestData , tmpdir : Path ) -> None :
440531 prefix = os .path .join (os .path .sep , "some" , "path" )
441532 self .prep (data , tmpdir )
@@ -451,6 +542,8 @@ def test_install_prefix(self, data: TestData, tmpdir: Path) -> None:
451542 self .name ,
452543 self .wheelpath ,
453544 scheme = scheme ,
545+ download_info = self .download_info ,
546+ is_direct = False ,
454547 req_description = str (self .req ),
455548 )
456549
@@ -468,6 +561,8 @@ def test_dist_info_contains_empty_dir(self, data: TestData, tmpdir: Path) -> Non
468561 self .name ,
469562 self .wheelpath ,
470563 scheme = self .scheme ,
564+ download_info = self .download_info ,
565+ is_direct = False ,
471566 req_description = str (self .req ),
472567 )
473568 self .assert_installed (0o644 )
@@ -486,6 +581,8 @@ def test_wheel_install_rejects_bad_paths(
486581 "simple" ,
487582 str (wheel_path ),
488583 scheme = self .scheme ,
584+ download_info = self .download_info ,
585+ is_direct = False ,
489586 req_description = "simple" ,
490587 )
491588
@@ -508,6 +605,8 @@ def test_invalid_entrypoints_fail(
508605 "simple" ,
509606 str (wheel_path ),
510607 scheme = self .scheme ,
608+ download_info = self .download_info ,
609+ is_direct = False ,
511610 req_description = "simple" ,
512611 )
513612
0 commit comments