1212import re
1313import subprocess
1414import sys
15- import tempfile
1615import time
1716from bisect import bisect_left
1817from collections import defaultdict
2221from pathlib import Path
2322from typing import Optional , Union
2423
25- from packaging .requirements import Requirement
2624from packaging .specifiers import SpecifierSet
2725from packaging .version import Version
2826
@@ -165,12 +163,12 @@ def fetch_release(package: str, version: Version) -> Optional[dict]:
165163 return release
166164
167165
168- def _get_dependency_probe_constraints (
166+ def _get_additional_test_dependencies (
169167 integration : str ,
170168 release : Version ,
171169 python_version : ThreadedVersion ,
172170) -> tuple [str , ...]:
173- constraints = []
171+ additional_dependencies = []
174172 for rule , dependencies in TEST_SUITE_CONFIG [integration ].get ("deps" , {}).items ():
175173 # Skip if rule does not apply to current package or Python version
176174 if rule != "*" and (
@@ -182,17 +180,8 @@ def _get_dependency_probe_constraints(
182180 ):
183181 continue
184182
185- for dependency in dependencies :
186- requirement = Requirement (dependency )
187-
188- # Constraints are useful only when they actually constrain versions.
189- if (
190- requirement .specifier
191- and not requirement .extras
192- and requirement .url is None
193- ):
194- constraints .append (dependency )
195- return tuple (constraints )
183+ additional_dependencies += dependencies
184+ return tuple (additional_dependencies )
196185
197186
198187@functools .cache
@@ -201,56 +190,57 @@ def fetch_package_dependencies(
201190 package : str ,
202191 version : Version ,
203192 python_version : ThreadedVersion ,
193+ install_additional_targets : bool = False ,
204194) -> dict :
205- """Fetch package dependencies metadata from cache or, failing that, PyPI."""
195+ """
196+ Fetch package dependencies metadata from cache or, failing that, PyPI.
197+
198+ With `install_additional_targets`, dependencies in `config.py` are also included (test dependencies).
199+ """
206200 target = TEST_SUITE_CONFIG [integration ]["package" ]
207- constraints = _get_dependency_probe_constraints (
208- integration , version , python_version
209- )
210- constraints_hash = hashlib .md5 (
211- "\n " .join ([target , * constraints ]).encode ("utf-8" )
212- ).hexdigest ()
201+ targets = [f"{ target } =={ version } " ]
202+ if install_additional_targets :
203+ targets += _get_additional_test_dependencies (
204+ integration , version , python_version
205+ )
206+
207+ install_targets_hash = hashlib .md5 ("\n " .join (targets ).encode ("utf-8" )).hexdigest ()
208+
213209 package_dependencies = _fetch_package_dependencies_from_cache (
214- package , version , python_version , constraints_hash
210+ package , version , python_version , install_targets_hash
215211 )
216212 if package_dependencies is not None :
217213 return package_dependencies
218214
215+ print (f" Resolving dependencies: { package } =={ version } on Python { python_version } " )
216+
219217 # Removing non-report output with -qqq may be brittle, but avoids file I/O.
220218 # Currently -qqq supresses all non-report output that would break json.loads().
221- cmd = [
222- "uv" ,
223- "run" ,
224- "--no-project" ,
225- "--python" ,
226- str (python_version ),
227- "--with" ,
228- "pip" ,
229- "python" ,
230- "-m" ,
231- "pip" ,
232- "install" ,
233- f"{ target } =={ version } " ,
234- "--only-binary" ,
235- "grpcio-tools" , # Prevent source builds that hang CI. grpcio-tools is a build-time dependency pinned by apache-beam.
236- "--dry-run" ,
237- "--ignore-installed" ,
238- "--report" ,
239- "-" ,
240- "-qqq" ,
241- ]
242-
243- with tempfile .NamedTemporaryFile ("w" , encoding = "utf-8" ) as f :
244- f .write ("\n " .join (constraints ))
245- f .flush ()
246- print (
247- f" Resolving dependencies: { package } =={ version } on Python { python_version } "
248- )
249- result = subprocess .run (
250- [* cmd , "--constraint" , f .name ],
251- capture_output = True ,
252- text = True ,
253- )
219+ result = subprocess .run (
220+ [
221+ "uv" ,
222+ "run" ,
223+ "--no-project" ,
224+ "--python" ,
225+ str (python_version ),
226+ "--with" ,
227+ "pip" ,
228+ "python" ,
229+ "-m" ,
230+ "pip" ,
231+ "install" ,
232+ * targets ,
233+ "--only-binary" ,
234+ "grpcio-tools" , # Prevent source builds that hang CI. grpcio-tools is a build-time dependency pinned by apache-beam.
235+ "--dry-run" ,
236+ "--ignore-installed" ,
237+ "--report" ,
238+ "-" ,
239+ "-qqq" ,
240+ ],
241+ capture_output = True ,
242+ text = True ,
243+ )
254244
255245 if result .returncode != 0 :
256246 # Some failures are expected because uv installs packages which pip rejects for having bad metadata.
@@ -263,7 +253,7 @@ def fetch_package_dependencies(
263253 version ,
264254 python_version ,
265255 dependencies_info ,
266- constraints_hash ,
256+ install_targets_hash ,
267257 )
268258
269259 return dependencies_info
@@ -282,13 +272,11 @@ def _fetch_package_dependencies_from_cache(
282272 package : str ,
283273 version : Version ,
284274 python_version : ThreadedVersion ,
285- constraints_hash : str ,
275+ install_targets_hash : str ,
286276) -> Optional [dict ]:
287277 package = _normalize_name (package )
288- cache_entry = (
289- DEPENDENCIES_CACHE [package ].get (str (version ), {}).get (str (python_version ), None )
290- )
291- if cache_entry is not None and cache_entry ["constraints_hash" ] == constraints_hash :
278+ cache_entry = DEPENDENCIES_CACHE [str (python_version )].get (install_targets_hash )
279+ if cache_entry is not None :
292280 cache_entry ["_accessed" ] = True
293281 return cache_entry ["dependencies" ]
294282
@@ -308,29 +296,24 @@ def _save_to_package_dependencies_cache(
308296 version : Version ,
309297 python_version : ThreadedVersion ,
310298 release : Optional [dict ],
311- constraints_hash : str ,
299+ install_targets_hash : str ,
312300) -> None :
313301 normalized_dependencies = _normalize_package_dependencies (release )
314302
315303 with open (DEPENDENCIES_CACHE_FILE , "a" ) as releases_cache :
316304 releases_cache .write (
317305 json .dumps (
318306 {
319- "name" : package ,
320- "version" : str (version ),
321307 "python_version" : str (python_version ),
322308 "dependencies" : normalized_dependencies ,
323- "constraints_hash " : constraints_hash ,
309+ "install_targets_hash " : install_targets_hash ,
324310 }
325311 )
326312 + "\n "
327313 )
328314
329- DEPENDENCIES_CACHE [_normalize_name (package )].setdefault (str (version ), {})[
330- str (python_version )
331- ] = {
315+ DEPENDENCIES_CACHE [str (python_version )][install_targets_hash ] = {
332316 "dependencies" : normalized_dependencies ,
333- "constraints_hash" : constraints_hash ,
334317 "_accessed" : True ,
335318 }
336319
@@ -996,6 +979,7 @@ def _render_transitive_dependencies(
996979 package ,
997980 release ,
998981 python_version ,
982+ install_additional_targets = True ,
999983 ):
1000984 name = dependency ["metadata" ]["name" ]
1001985 version = dependency ["metadata" ]["version" ]
@@ -1286,12 +1270,9 @@ def main() -> dict[str, list]:
12861270 with open (DEPENDENCIES_CACHE_FILE ) as dependencies_cache :
12871271 for line in dependencies_cache :
12881272 release = json .loads (line )
1289- name = _normalize_name (release ["name" ])
1290- version = release ["version" ]
12911273 python_version = release ["python_version" ]
1292- DEPENDENCIES_CACHE [name ]. setdefault ( version , {})[ python_version ] = {
1274+ DEPENDENCIES_CACHE [python_version ][ release [ "install_targets_hash" ] ] = {
12931275 "dependencies" : release ["dependencies" ],
1294- "constraints_hash" : release ["constraints_hash" ],
12951276 "_accessed" : False ,
12961277 }
12971278
@@ -1379,13 +1360,13 @@ def main() -> dict[str, list]:
13791360 releases = []
13801361 with open (DEPENDENCIES_CACHE_FILE ) as releases_cache :
13811362 releases = [json .loads (line ) for line in releases_cache ]
1382- releases .sort (key = lambda r : (r ["name " ], r ["version " ]))
1363+ releases .sort (key = lambda r : (r ["python_version " ], r ["install_targets_hash " ]))
13831364 with open (DEPENDENCIES_CACHE_FILE , "w" ) as releases_cache :
13841365 for release in releases :
13851366 if (
1386- DEPENDENCIES_CACHE [_normalize_name ( release ["name" ]) ][
1387- release ["version " ]
1388- ][release [ "python_version" ]][ "_accessed" ]
1367+ DEPENDENCIES_CACHE [release ["python_version" ] ][
1368+ release ["install_targets_hash " ]
1369+ ]["_accessed" ]
13891370 is True
13901371 ):
13911372 releases_cache .write (json .dumps (release ) + "\n " )
0 commit comments