3838if TYPE_CHECKING :
3939 from tomlkit .toml_document import TOMLDocument
4040
41+ from poetry .core .version .markers import BaseMarker
4142 from poetry .repositories import Repository
4243
4344logger = logging .getLogger (__name__ )
@@ -204,102 +205,104 @@ def locked_repository(self, with_dev_reqs: bool = False) -> "Repository":
204205
205206 @staticmethod
206207 def __get_locked_package (
207- _dependency : Dependency , packages_by_name : Dict [str , List [Package ]]
208+ dependency : Dependency ,
209+ packages_by_name : Dict [str , List [Package ]],
210+ decided : Optional [Dict [Package , Dependency ]] = None ,
208211 ) -> Optional [Package ]:
209212 """
210213 Internal helper to identify corresponding locked package using dependency
211214 version constraints.
212215 """
213- for _package in packages_by_name .get (_dependency .name , []):
214- if _dependency .constraint .allows (_package .version ):
215- return _package
216- return None
216+ decided = decided or {}
217+
218+ # Get the packages that are consistent with this dependency.
219+ packages = [
220+ package
221+ for package in packages_by_name .get (dependency .name , [])
222+ if package .python_constraint .allows_all (dependency .python_constraint )
223+ and dependency .constraint .allows (package .version )
224+ ]
225+
226+ # If we've previously made a choice that is compatible with the current
227+ # requirement, stick with it.
228+ for package in packages :
229+ old_decision = decided .get (package )
230+ if (
231+ old_decision is not None
232+ and not old_decision .marker .intersect (dependency .marker ).is_empty ()
233+ ):
234+ return package
235+
236+ return next (iter (packages ), None )
217237
218238 @classmethod
219- def __walk_dependency_level (
239+ def __walk_dependencies (
220240 cls ,
221241 dependencies : List [Dependency ],
222- level : int ,
223- pinned_versions : bool ,
224242 packages_by_name : Dict [str , List [Package ]],
225- project_level_dependencies : Set [str ],
226- nested_dependencies : Dict [Tuple [str , str ], Dependency ],
227- ) -> Dict [Tuple [str , str ], Dependency ]:
228- if not dependencies :
229- return nested_dependencies
230-
231- next_level_dependencies = []
232-
233- for requirement in dependencies :
234- key = (requirement .name , requirement .pretty_constraint )
235- locked_package = cls .__get_locked_package (requirement , packages_by_name )
236-
237- if locked_package :
238- # create dependency from locked package to retain dependency metadata
239- # if this is not done, we can end-up with incorrect nested dependencies
240- constraint = requirement .constraint
241- pretty_constraint = requirement .pretty_constraint
242- marker = requirement .marker
243- requirement = locked_package .to_dependency ()
244- requirement .marker = requirement .marker .intersect (marker )
243+ ) -> Dict [Package , Dependency ]:
244+ nested_dependencies : Dict [Package , Dependency ] = {}
245245
246- key = (requirement .name , pretty_constraint )
247-
248- if not pinned_versions :
249- requirement .set_constraint (constraint )
246+ visited : Set [Tuple [Dependency , "BaseMarker" ]] = set ()
247+ while dependencies :
248+ requirement = dependencies .pop (0 )
249+ if (requirement , requirement .marker ) in visited :
250+ continue
251+ visited .add ((requirement , requirement .marker ))
250252
251- for require in locked_package .requires :
252- if require .marker .is_empty ():
253- require .marker = requirement .marker
254- else :
255- require .marker = require .marker .intersect (requirement .marker )
253+ locked_package = cls .__get_locked_package (
254+ requirement , packages_by_name , nested_dependencies
255+ )
256256
257- require .marker = require .marker .intersect (locked_package .marker )
257+ if not locked_package :
258+ # Should normally be able to satisfy all requirements, but this case is
259+ # permissible eg if we encounter a dev dependency when walking the
260+ # non-dev dependencies.
261+ continue
258262
259- if key not in nested_dependencies :
260- next_level_dependencies .append (require )
263+ # create dependency from locked package to retain dependency metadata
264+ # if this is not done, we can end-up with incorrect nested dependencies
265+ constraint = requirement .constraint
266+ marker = requirement .marker
267+ requirement = locked_package .to_dependency ()
268+ requirement .marker = requirement .marker .intersect (marker )
261269
262- if requirement .name in project_level_dependencies and level == 0 :
263- # project level dependencies take precedence
264- continue
270+ requirement .set_constraint (constraint )
265271
266- if not locked_package :
267- # we make a copy to avoid any side-effects
268- requirement = deepcopy (requirement )
272+ for require in locked_package .requires :
273+ require = deepcopy (require )
274+ require .marker = require .marker .intersect (requirement .marker )
275+ if not require .marker .is_empty ():
276+ dependencies .append (require )
269277
278+ key = locked_package
270279 if key not in nested_dependencies :
271280 nested_dependencies [key ] = requirement
272281 else :
273282 nested_dependencies [key ].marker = nested_dependencies [key ].marker .union (
274283 requirement .marker
275284 )
276285
277- return cls .__walk_dependency_level (
278- dependencies = next_level_dependencies ,
279- level = level + 1 ,
280- pinned_versions = pinned_versions ,
281- packages_by_name = packages_by_name ,
282- project_level_dependencies = project_level_dependencies ,
283- nested_dependencies = nested_dependencies ,
284- )
286+ return nested_dependencies
285287
286288 @classmethod
287289 def get_project_dependencies (
288290 cls ,
289291 project_requires : List [Dependency ],
290292 locked_packages : List [Package ],
291- pinned_versions : bool = False ,
292- with_nested : bool = False ,
293- ) -> Iterable [Dependency ]:
293+ ) -> Iterable [Tuple [Package , Dependency ]]:
294294 # group packages entries by name, this is required because requirement might use
295- # different constraints
295+ # different constraints.
296296 packages_by_name = {}
297297 for pkg in locked_packages :
298298 if pkg .name not in packages_by_name :
299299 packages_by_name [pkg .name ] = []
300300 packages_by_name [pkg .name ].append (pkg )
301301
302- project_level_dependencies = set ()
302+ # Put higher versions first so that we prefer them.
303+ for packages in packages_by_name .values ():
304+ packages .sort (key = lambda package : package .version , reverse = True )
305+
303306 dependencies = []
304307
305308 for dependency in project_requires :
@@ -311,38 +314,18 @@ def get_project_dependencies(
311314 locked_package .marker
312315 )
313316
314- if not pinned_versions :
315- locked_dependency .set_constraint (dependency .constraint )
317+ locked_dependency .set_constraint (dependency .constraint )
316318
317319 dependency = locked_dependency
318320
319- project_level_dependencies .add (dependency .name )
320321 dependencies .append (dependency )
321322
322- if not with_nested :
323- # return only with project level dependencies
324- return dependencies
325-
326- nested_dependencies = cls .__walk_dependency_level (
323+ nested_dependencies = cls .__walk_dependencies (
327324 dependencies = dependencies ,
328- level = 0 ,
329- pinned_versions = pinned_versions ,
330325 packages_by_name = packages_by_name ,
331- project_level_dependencies = project_level_dependencies ,
332- nested_dependencies = {},
333326 )
334327
335- # Merge same dependencies using marker union
336- for requirement in dependencies :
337- key = (requirement .name , requirement .pretty_constraint )
338- if key not in nested_dependencies :
339- nested_dependencies [key ] = requirement
340- else :
341- nested_dependencies [key ].marker = nested_dependencies [key ].marker .union (
342- requirement .marker
343- )
344-
345- return sorted (nested_dependencies .values (), key = lambda x : x .name .lower ())
328+ return nested_dependencies .items ()
346329
347330 def get_project_dependency_packages (
348331 self ,
@@ -382,16 +365,10 @@ def get_project_dependency_packages(
382365
383366 selected .append (dependency )
384367
385- for dependency in self .get_project_dependencies (
368+ for package , dependency in self .get_project_dependencies (
386369 project_requires = selected ,
387370 locked_packages = repository .packages ,
388- with_nested = True ,
389371 ):
390- try :
391- package = repository .find_packages (dependency = dependency )[0 ]
392- except IndexError :
393- continue
394-
395372 for extra in dependency .extras :
396373 package .requires_extras .append (extra )
397374
0 commit comments