1010from pip ._internal .resolution .resolvelib .candidates import REQUIRES_PYTHON_IDENTIFIER
1111from pip ._internal .resolution .resolvelib .factory import Factory
1212from pip ._internal .resolution .resolvelib .provider import PipProvider
13- from pip ._internal .resolution .resolvelib .requirements import SpecifierRequirement
13+ from pip ._internal .resolution .resolvelib .requirements import (
14+ ExplicitRequirement ,
15+ SpecifierRequirement ,
16+ )
1417
1518if TYPE_CHECKING :
1619 from pip ._vendor .resolvelib .providers import Preference
2023 PreferenceInformation = RequirementInformation [Requirement , Candidate ]
2124
2225
26+ class FakeCandidate (Candidate ):
27+ """A minimal fake candidate for testing purposes."""
28+
29+ def __init__ (self , * args : object , ** kwargs : object ) -> None : ...
30+
31+
2332def build_req_info (
2433 name : str , parent : Optional [Candidate ] = None
2534) -> "PreferenceInformation" :
@@ -33,6 +42,14 @@ def build_req_info(
3342 return requirement_information
3443
3544
45+ def build_explicit_req_info (
46+ url : str , parent : Optional [Candidate ] = None
47+ ) -> "PreferenceInformation" :
48+ """Build a direct requirement using a minimal FakeCandidate."""
49+ direct_requirement = ExplicitRequirement (FakeCandidate (url ))
50+ return RequirementInformation (requirement = direct_requirement , parent = parent )
51+
52+
3653@pytest .mark .parametrize (
3754 "identifier, information, backtrack_causes, user_requested, expected" ,
3855 [
@@ -42,47 +59,55 @@ def build_req_info(
4259 {"pinned-package" : [build_req_info ("pinned-package==1.0" )]},
4360 [],
4461 {},
45- (False , False , True , math .inf , False , "pinned-package" ),
62+ (True , False , True , math .inf , False , "pinned-package" ),
4663 ),
4764 # Star-specified package, i.e. with "*"
4865 (
4966 "star-specified-package" ,
5067 {"star-specified-package" : [build_req_info ("star-specified-package==1.*" )]},
5168 [],
5269 {},
53- (False , True , False , math .inf , False , "star-specified-package" ),
70+ (True , True , False , math .inf , False , "star-specified-package" ),
5471 ),
5572 # Package that caused backtracking
5673 (
5774 "backtrack-package" ,
5875 {"backtrack-package" : [build_req_info ("backtrack-package" )]},
5976 [build_req_info ("backtrack-package" )],
6077 {},
61- (False , True , True , math .inf , True , "backtrack-package" ),
78+ (True , True , True , math .inf , True , "backtrack-package" ),
6279 ),
6380 # Root package requested by user
6481 (
6582 "root-package" ,
6683 {"root-package" : [build_req_info ("root-package" )]},
6784 [],
6885 {"root-package" : 1 },
69- (False , True , True , 1 , True , "root-package" ),
86+ (True , True , True , 1 , True , "root-package" ),
7087 ),
7188 # Unfree package (with specifier operator)
7289 (
7390 "unfree-package" ,
7491 {"unfree-package" : [build_req_info ("unfree-package!=1" )]},
7592 [],
7693 {},
77- (False , True , True , math .inf , False , "unfree-package" ),
94+ (True , True , True , math .inf , False , "unfree-package" ),
7895 ),
7996 # Free package (no operator)
8097 (
8198 "free-package" ,
8299 {"free-package" : [build_req_info ("free-package" )]},
83100 [],
84101 {},
85- (False , True , True , math .inf , True , "free-package" ),
102+ (True , True , True , math .inf , True , "free-package" ),
103+ ),
104+ # Test case for "direct" preference (explicit URL)
105+ (
106+ "direct-package" ,
107+ {"direct-package" : [build_explicit_req_info ("direct-package" )]},
108+ [],
109+ {},
110+ (False , True , True , math .inf , True , "direct-package" ),
86111 ),
87112 # Upper bounded with <= operator
88113 (
@@ -94,15 +119,15 @@ def build_req_info(
94119 },
95120 [],
96121 {},
97- (False , True , False , math .inf , False , "upper-bound-lte-package" ),
122+ (True , True , False , math .inf , False , "upper-bound-lte-package" ),
98123 ),
99124 # Upper bounded with < operator
100125 (
101126 "upper-bound-lt-package" ,
102127 {"upper-bound-lt-package" : [build_req_info ("upper-bound-lt-package<2.0" )]},
103128 [],
104129 {},
105- (False , True , False , math .inf , False , "upper-bound-lt-package" ),
130+ (True , True , False , math .inf , False , "upper-bound-lt-package" ),
106131 ),
107132 # Upper bounded with ~= operator
108133 (
@@ -114,15 +139,15 @@ def build_req_info(
114139 },
115140 [],
116141 {},
117- (False , True , False , math .inf , False , "upper-bound-compatible-package" ),
142+ (True , True , False , math .inf , False , "upper-bound-compatible-package" ),
118143 ),
119144 # Not upper bounded, using only >= operator
120145 (
121146 "lower-bound-package" ,
122147 {"lower-bound-package" : [build_req_info ("lower-bound-package>=1.0" )]},
123148 [],
124149 {},
125- (False , True , True , math .inf , False , "lower-bound-package" ),
150+ (True , True , True , math .inf , False , "lower-bound-package" ),
126151 ),
127152 ],
128153)
0 commit comments