1212The types stubs live in https://github.com/python/typeshed/tree/master/stubs,
1313all fixes for types and metadata should be contributed there, see
1414https://github.com/python/typeshed/blob/master/CONTRIBUTING.md for more details.
15+
16+ This file also contains some helper functions related to wheel validation and upload.
1517"""
1618
1719import argparse
2022import shutil
2123import tempfile
2224import subprocess
25+ from collections import defaultdict
26+ from functools import cmp_to_key
2327from textwrap import dedent
24- from typing import List , Dict , Any , Tuple
28+ from typing import List , Dict , Any , Tuple , Set
29+
30+ from scripts import get_version
2531
2632import toml
2733
6672""" ).lstrip ()
6773
6874
75+ def strip_types_prefix (dependency : str ) -> str :
76+ assert dependency .startswith ("types-" ), "Currently only dependencies on stub packages are supported"
77+ return dependency [len ("types-" ):]
78+
79+
6980def find_stub_files (top : str ) -> List [str ]:
7081 """Find all stub files for a given package, relative to package root.
7182
@@ -165,6 +176,82 @@ def collect_setup_entries(
165176 return packages , package_data
166177
167178
179+ def verify_dependency (typeshed_dir : str , dependency : str , uploaded : str ) -> None :
180+ """Verify this is a valid dependency, i.e. a stub package uploaded by us."""
181+ known_distributions = set (os .listdir (os .path .join (typeshed_dir , THIRD_PARTY_NAMESPACE )))
182+ assert ";" not in dependency , "Semicolons in dependencies are not supported"
183+ dependency = get_version .strip_dep_version (dependency )
184+ assert strip_types_prefix (dependency ) in known_distributions , "Only dependencies on typeshed stubs are allowed"
185+ with open (uploaded ) as f :
186+ uploaded_distributions = set (f .read ().splitlines ())
187+
188+ msg = f"{ dependency } looks like a foreign distribution."
189+ uploaded_distributions_lower = [d .lower () for d in uploaded_distributions ]
190+ if dependency not in uploaded_distributions and dependency .lower () in uploaded_distributions_lower :
191+ msg += " Note: list is case sensitive"
192+ assert dependency in uploaded_distributions , msg
193+
194+
195+ def update_uploaded (uploaded : str , distribution : str ) -> None :
196+ with open (uploaded ) as f :
197+ current = set (f .read ().splitlines ())
198+ if f"types-{ distribution } " not in current :
199+ with open (uploaded , "w" ) as f :
200+ f .writelines (sorted (current | {f"types-{ distribution } " }))
201+
202+
203+ def make_dependency_map (typeshed_dir : str , distributions : List [str ]) -> Dict [str , Set [str ]]:
204+ """Return relative dependency map among distributions.
205+
206+ Important: this only includes dependencies *within* the given
207+ list of distributions.
208+ """
209+ result : Dict [str , Set [str ]] = {d : set () for d in distributions }
210+ for distribution in distributions :
211+ data = read_matadata (
212+ os .path .join (typeshed_dir , THIRD_PARTY_NAMESPACE , distribution , META )
213+ )
214+ for dependency in data .get ("requires" , []):
215+ dependency = strip_types_prefix (get_version .strip_dep_version (dependency ))
216+ if dependency in distributions :
217+ result [distribution ].add (dependency )
218+ return result
219+
220+
221+ def transitive_deps (dep_map : Dict [str , Set [str ]]) -> Dict [str , Set [str ]]:
222+ """Propagate dependencies to compute a transitive dependency map.
223+
224+ Note: this algorithm is O(N**2) in general case, but we don't worry,
225+ because N is small (less than 1000). So it will take few seconds at worst,
226+ while building/uploading 1000 packages will take minutes.
227+ """
228+ transitive : Dict [str , Set [str ]] = defaultdict (set )
229+ for distribution in dep_map :
230+ to_add = {distribution }
231+ while to_add :
232+ new = to_add .pop ()
233+ extra = dep_map [new ]
234+ transitive [distribution ] |= extra
235+ assert distribution not in transitive [distribution ], f"Cyclic dependency { distribution } -> { distribution } "
236+ to_add |= extra
237+ return transitive
238+
239+
240+ def sort_by_dependency (dep_map : Dict [str , Set [str ]]) -> List [str ]:
241+ """Sort distributions by dependency order (those depending on nothing appear first)."""
242+ trans_map = transitive_deps (dep_map )
243+
244+ def compare (d1 : str , d2 : str ) -> int :
245+ if d1 in trans_map [d2 ]:
246+ return - 1
247+ if d2 in trans_map [d1 ]:
248+ return 1
249+ return 0
250+
251+ # Return independent packages sorted by name for stability.
252+ return sorted (sorted (dep_map ), key = cmp_to_key (compare ))
253+
254+
168255def generate_setup_file (typeshed_dir : str , distribution : str , increment : str ) -> str :
169256 """Auto-generate a setup.py file for given distribution using a template."""
170257 base_dir = os .path .join (typeshed_dir , THIRD_PARTY_NAMESPACE , distribution )
@@ -178,17 +265,11 @@ def generate_setup_file(typeshed_dir: str, distribution: str, increment: str) ->
178265 packages += py2_packages
179266 package_data .update (py2_package_data )
180267 version = metadata ["version" ]
181- requires = metadata .get ("requires" , [])
182- known_distributions = set (os .listdir (os .path .join (typeshed_dir , THIRD_PARTY_NAMESPACE )))
183- for dependency in requires :
184- assert dependency .startswith ("types-" ), "Only dependencies on stub packages are allowed"
185- dep_name = dependency [len ("types-" ):]
186- assert dep_name in known_distributions , "Only dependencies on typeshed stubs are allowed"
187268 assert version .count ("." ) == 1 , f"Version must be major.minor, not { version } "
188269 return SETUP_TEMPLATE .format (
189270 distribution = distribution ,
190271 version = f"{ version } .{ increment } " ,
191- requires = requires ,
272+ requires = metadata . get ( " requires" , []) ,
192273 packages = packages ,
193274 package_data = package_data ,
194275 )
0 commit comments