11from __future__ import annotations
22
3+ import shlex
34from collections .abc import Mapping , MutableMapping , Sequence
5+ from dataclasses import dataclass
46from pathlib import Path , PurePath
57from typing import Any , Literal , TypeVar
68
79from packaging .utils import parse_wheel_filename
810
911from . import resources
1012from .cmd import call
13+ from .helpers import parse_key_value_string
1114
1215
16+ @dataclass ()
1317class DependencyConstraints :
14- def __init__ (self , base_file_path : Path ):
15- assert base_file_path .exists ()
16- self .base_file_path = base_file_path .resolve ()
18+ base_file_path : Path | None = None
19+ packages : list [str ] | None = None
20+
21+ def __post_init__ (self ) -> None :
22+ if self .packages is not None and self .base_file_path is not None :
23+ msg = "Cannot specify both a file and packages in the dependency constraints"
24+ raise ValueError (msg )
25+
26+ if self .base_file_path is not None :
27+ assert self .base_file_path .exists ()
28+ self .base_file_path = self .base_file_path .resolve ()
1729
1830 @staticmethod
1931 def with_defaults () -> DependencyConstraints :
2032 return DependencyConstraints (base_file_path = resources .CONSTRAINTS )
2133
34+ @staticmethod
35+ def from_config_string (config_string : str ) -> DependencyConstraints | None :
36+ config_dict = parse_key_value_string (config_string , ["file" ], ["packages" ])
37+ file_or_keywords = config_dict .get ("file" )
38+ packages = config_dict .get ("packages" )
39+
40+ if file_or_keywords and packages :
41+ msg = "Cannot specify both a file and packages in dependency-versions"
42+ raise ValueError (msg )
43+
44+ if packages :
45+ return DependencyConstraints (packages = packages )
46+
47+ if file_or_keywords and len (file_or_keywords ) > 1 :
48+ msg = "Only one file or keyword can be specified in dependency-versions"
49+ raise ValueError (msg )
50+
51+ file_or_keyword = file_or_keywords [0 ] if file_or_keywords else None
52+
53+ if file_or_keyword == "latest" :
54+ return None
55+
56+ if file_or_keyword == "pinned" or not file_or_keyword :
57+ return DependencyConstraints .with_defaults ()
58+
59+ return DependencyConstraints (base_file_path = Path (file_or_keyword ))
60+
2261 def get_for_python_version (
23- self , version : str , * , variant : Literal ["python" , "pyodide" ] = "python"
62+ self , * , version : str , variant : Literal ["python" , "pyodide" ] = "python" , tmp_dir : Path
2463 ) -> Path :
64+ if self .packages :
65+ constraint_file = tmp_dir / "constraints.txt"
66+ constraint_file .write_text ("\n " .join (self .packages ))
67+ return constraint_file
68+
69+ assert (
70+ self .base_file_path is not None
71+ ), "DependencyConstraints should have either a file or packages"
72+
2573 version_parts = version .split ("." )
2674
2775 # try to find a version-specific dependency file e.g. if
@@ -35,19 +83,15 @@ def get_for_python_version(
3583 else :
3684 return self .base_file_path
3785
38- def __repr__ (self ) -> str :
39- return f"{ self .__class__ .__name__ } ({ self .base_file_path !r} )"
40-
41- def __eq__ (self , o : object ) -> bool :
42- if not isinstance (o , DependencyConstraints ):
43- return False
44-
45- return self .base_file_path == o .base_file_path
46-
4786 def options_summary (self ) -> Any :
4887 if self == DependencyConstraints .with_defaults ():
4988 return "pinned"
89+ elif self .packages :
90+ return {"packages" : " " .join (shlex .quote (p ) for p in self .packages )}
5091 else :
92+ assert (
93+ self .base_file_path is not None
94+ ), "DependencyConstraints should have either a file or packages"
5195 return self .base_file_path .name
5296
5397
0 commit comments