4
4
import operator
5
5
from abc import ABC , abstractmethod
6
6
from collections import defaultdict
7
+ from functools import partial
7
8
from pathlib import Path
8
- from typing import TYPE_CHECKING , Any , Callable , Sequence
9
+ from typing import TYPE_CHECKING , Any , Callable , Sequence , cast
9
10
10
11
from packaging .requirements import Requirement
11
12
15
16
from tox .tox_env .installer import Installer
16
17
from tox .tox_env .python .api import Python
17
18
from tox .tox_env .python .package import EditableLegacyPackage , EditablePackage , SdistPackage , WheelPackage
18
- from tox .tox_env .python .pip .req_file import PythonDeps
19
+ from tox .tox_env .python .pip .req_file import PythonConstraints , PythonDeps
19
20
20
21
if TYPE_CHECKING :
21
22
from tox .config .main import Config
@@ -52,6 +53,7 @@ class Pip(PythonInstallerListDependencies):
52
53
53
54
def _register_config (self ) -> None :
54
55
super ()._register_config ()
56
+ root = self ._env .core ["toxinidir" ]
55
57
self ._env .conf .add_config (
56
58
keys = ["pip_pre" ],
57
59
of_type = bool ,
@@ -65,6 +67,13 @@ def _register_config(self) -> None:
65
67
post_process = self .post_process_install_command ,
66
68
desc = "command used to install packages" ,
67
69
)
70
+ self ._env .conf .add_config (
71
+ keys = ["constraints" ],
72
+ of_type = PythonConstraints ,
73
+ factory = partial (PythonConstraints .factory , root ),
74
+ default = PythonConstraints ("" , root ),
75
+ desc = "constraints to apply to installed python dependencies" ,
76
+ )
68
77
self ._env .conf .add_config (
69
78
keys = ["constrain_package_deps" ],
70
79
of_type = bool ,
@@ -110,6 +119,10 @@ def install(self, arguments: Any, section: str, of_type: str) -> None:
110
119
logging .warning ("pip cannot install %r" , arguments )
111
120
raise SystemExit (1 )
112
121
122
+ @property
123
+ def constraints (self ) -> PythonConstraints :
124
+ return cast ("PythonConstraints" , self ._env .conf ["constraints" ])
125
+
113
126
def constraints_file (self ) -> Path :
114
127
return Path (self ._env .env_dir ) / "constraints.txt"
115
128
@@ -121,16 +134,25 @@ def constrain_package_deps(self) -> bool:
121
134
def use_frozen_constraints (self ) -> bool :
122
135
return bool (self ._env .conf ["use_frozen_constraints" ])
123
136
124
- def _install_requirement_file (self , arguments : PythonDeps , section : str , of_type : str ) -> None : # noqa: C901
137
+ def _install_requirement_file (self , arguments : PythonDeps , section : str , of_type : str ) -> None :
138
+ new_requirements : list [str ] = []
139
+ new_constraints : list [str ] = []
140
+
125
141
try :
126
142
new_options , new_reqs = arguments .unroll ()
127
143
except ValueError as exception :
128
144
msg = f"{ exception } for tox env py within deps"
129
145
raise Fail (msg ) from exception
130
- new_requirements : list [str ] = []
131
- new_constraints : list [str ] = []
132
146
for req in new_reqs :
133
147
(new_constraints if req .startswith ("-c " ) else new_requirements ).append (req )
148
+
149
+ try :
150
+ _ , new_reqs = self .constraints .unroll ()
151
+ except ValueError as exception :
152
+ msg = f"{ exception } for tox env py within constraints"
153
+ raise Fail (msg ) from exception
154
+ new_constraints .extend (new_reqs )
155
+
134
156
constraint_options = {
135
157
"constrain_package_deps" : self .constrain_package_deps ,
136
158
"use_frozen_constraints" : self .use_frozen_constraints ,
@@ -159,17 +181,10 @@ def _install_requirement_file(self, arguments: PythonDeps, section: str, of_type
159
181
raise Recreate (msg )
160
182
args = arguments .as_root_args
161
183
if args : # pragma: no branch
184
+ args .extend (self .constraints .as_root_args )
162
185
self ._execute_installer (args , of_type )
163
186
if self .constrain_package_deps and not self .use_frozen_constraints :
164
- # when we drop Python 3.8 we can use the builtin `.removeprefix`
165
- def remove_prefix (text : str , prefix : str ) -> str :
166
- if text .startswith (prefix ):
167
- return text [len (prefix ) :]
168
- return text
169
-
170
- combined_constraints = new_requirements + [
171
- remove_prefix (text = c , prefix = "-c " ) for c in new_constraints
172
- ]
187
+ combined_constraints = new_requirements + [c .removeprefix ("-c " ) for c in new_constraints ]
173
188
self .constraints_file ().write_text ("\n " .join (combined_constraints ))
174
189
175
190
@staticmethod
@@ -215,13 +230,18 @@ def _install_list_of_deps( # noqa: C901
215
230
raise Recreate (msg ) # pragma: no branch
216
231
new_deps = sorted (set (groups ["req" ]) - set (old or []))
217
232
if new_deps : # pragma: no branch
233
+ new_deps .extend (self .constraints .as_root_args )
218
234
self ._execute_installer (new_deps , req_of_type )
219
235
install_args = ["--force-reinstall" , "--no-deps" ]
220
236
if groups ["pkg" ]:
237
+ # we intentionally ignore constraints when installing the package itself
238
+ # https://github.com/tox-dev/tox/issues/3550
221
239
self ._execute_installer (install_args + groups ["pkg" ], of_type )
222
240
if groups ["dev_pkg" ]:
223
241
for entry in groups ["dev_pkg" ]:
224
242
install_args .extend (("-e" , str (entry )))
243
+ # we intentionally ignore constraints when installing the package itself
244
+ # https://github.com/tox-dev/tox/issues/3550
225
245
self ._execute_installer (install_args , of_type )
226
246
227
247
def _execute_installer (self , deps : Sequence [Any ], of_type : str ) -> None :
0 commit comments