11#!/usr/bin/env python3
22
3- # For various reasons we need the contents of certain files to be
3+ # For security (and simplicity) reasons, only a limited kind of files can be
4+ # present in /stdlib and /stubs directories, see README for detail. Here we
5+ # verify these constraints.
6+
7+ # In addition, for various reasons we need the contents of certain files to be
48# duplicated in two places, for example stdlib/@python2/builtins.pyi and
59# stdlib/@python2/__builtin__.pyi must be identical. In the past we used
610# symlinks but that doesn't always work on Windows, so now you must
1014import filecmp
1115import os
1216
17+ import toml
18+
1319consistent_files = [
1420 {"stdlib/@python2/builtins.pyi" , "stdlib/@python2/__builtin__.pyi" },
1521 {"stdlib/threading.pyi" , "stdlib/_dummy_threading.pyi" },
1622]
1723
1824
19- def main ():
25+ def assert_stubs_only (directory ):
26+ """Check that given directory contains only valid stub files."""
27+ top = directory .split (os .sep )[- 1 ]
28+ assert top .isidentifier (), f"Bad directory name: { top } "
29+ for _ , dirs , files in os .walk (directory ):
30+ for file in files :
31+ name , ext = os .path .splitext (file )
32+ assert name .isidentifier (), f"Files must be valid modules, got: { name } "
33+ assert ext == ".pyi" , f"Only stub flies allowed. Got: { file } in { directory } "
34+ for subdir in dirs :
35+ assert subdir .isidentifier (), f"Directories must be valid packages, got: { subdir } "
36+
37+
38+ def check_stdlib ():
39+ for entry in os .listdir ("stdlib" ):
40+ if os .path .isfile (os .path .join ("stdlib" , entry )):
41+ name , ext = os .path .splitext (entry )
42+ if ext != ".pyi" :
43+ assert entry == "VERSIONS" , f"Unexpected file in stdlib root: { entry } "
44+ assert name .isidentifier (), "Bad file name in stdlib"
45+ else :
46+ if entry == "@python2" :
47+ continue
48+ assert_stubs_only (os .path .join ("stdlib" , entry ))
49+ for entry in os .listdir ("stdlib/@python2" ):
50+ if os .path .isfile (os .path .join ("stdlib/@python2" , entry )):
51+ name , ext = os .path .splitext (entry )
52+ assert name .isidentifier (), "Bad file name in stdlib"
53+ assert ext == ".pyi" , "Unexpected file in stdlib/@python2 root"
54+ else :
55+ assert_stubs_only (os .path .join ("stdlib/@python2" , entry ))
56+
57+
58+ def check_stubs ():
59+ for distribution in os .listdir ("stubs" ):
60+ assert not os .path .isfile (distribution ), f"Only directories allowed in stubs, got { distribution } "
61+ for entry in os .listdir (os .path .join ("stubs" , distribution )):
62+ if os .path .isfile (os .path .join ("stubs" , distribution , entry )):
63+ name , ext = os .path .splitext (entry )
64+ if ext != ".pyi" :
65+ assert entry in {"METADATA.toml" , "README" , "README.md" , "README.rst" }, entry
66+ else :
67+ assert name .isidentifier (), f"Bad file name '{ entry } ' in stubs"
68+ else :
69+ if entry == "@python2" :
70+ continue
71+ assert_stubs_only (os .path .join ("stubs" , distribution , entry ))
72+ if os .path .isdir (os .path .join ("stubs" , distribution , "@python2" )):
73+ for entry in os .listdir (os .path .join ("stubs" , distribution , "@python2" )):
74+ if os .path .isfile (os .path .join ("stubs" , distribution , "@python2" , entry )):
75+ name , ext = os .path .splitext (entry )
76+ assert name .isidentifier (), f"Bad file name '{ entry } ' in stubs"
77+ assert ext == ".pyi" , f"Unexpected file { entry } in @python2 stubs"
78+ else :
79+ assert_stubs_only (os .path .join ("stubs" , distribution , "@python2" , entry ))
80+
81+
82+ def check_same_files ():
2083 files = [os .path .join (root , file ) for root , dir , files in os .walk ("." ) for file in files ]
2184 no_symlink = "You cannot use symlinks in typeshed, please copy {} to its link."
2285 for file in files :
@@ -34,5 +97,63 @@ def main():
3497 )
3598
3699
100+ def check_versions ():
101+ versions = {}
102+ with open ("stdlib/VERSIONS" ) as f :
103+ data = f .read ().splitlines ()
104+ for line in data :
105+ if not line or line .lstrip ().startswith ("#" ):
106+ continue
107+ assert ": " in line , f"Bad line in VERSIONS: { line } "
108+ module , version = line .split (": " )
109+ msg = f"Unsupported Python version{ version } "
110+ assert version .count ("." ) == 1 , msg
111+ major , minor = version .split ("." )
112+ assert major in {"2" , "3" }, msg
113+ assert minor .isdigit (), msg
114+ assert module not in versions , f"Duplicate module { module } in VERSIONS"
115+ versions [module ] = (int (major ), int (minor ))
116+ modules = set ()
117+ for entry in os .listdir ("stdlib" ):
118+ if entry == "@python2" or entry == "VERSIONS" :
119+ continue
120+ if os .path .isfile (os .path .join ("stdlib" , entry )):
121+ mod , _ = os .path .splitext (entry )
122+ modules .add (mod )
123+ else :
124+ modules .add (entry )
125+ extra = modules - set (versions )
126+ assert not extra , f"Modules not in versions: { extra } "
127+ extra = set (versions ) - modules
128+ assert not extra , f"Versions not in modules: { extra } "
129+
130+
131+ def check_metadata ():
132+ for distribution in os .listdir ("stubs" ):
133+ with open (os .path .join ("stubs" , distribution , "METADATA.toml" )) as f :
134+ data = toml .loads (f .read ())
135+ assert "version" in data , f"Missing version for { distribution } "
136+ version = data ["version" ]
137+ msg = f"Unsupported Python version { version } "
138+ assert version .count ("." ) == 1 , msg
139+ major , minor = version .split ("." )
140+ assert major .isdigit () and minor .isdigit (), msg
141+ for key in data :
142+ assert key in {
143+ "version" , "python2" , "python3" , "requires"
144+ }, f"Unexpected key { key } for { distribution } "
145+ assert isinstance (data .get ("python2" , False ), bool ), f"Invalid python2 value for { distribution } "
146+ assert isinstance (data .get ("python3" , True ), bool ), f"Invalid python3 value for { distribution } "
147+ assert isinstance (data .get ("requires" , []), list ), f"Invalid requires value for { distribution } "
148+ for dep in data .get ("requires" , []):
149+ # TODO: add more validation here.
150+ assert isinstance (dep , str ), f"Invalid dependency { dep } for { distribution } "
151+ assert dep .startswith ("types-" ), f"Only stub dependencies supported, got { dep } "
152+
153+
37154if __name__ == "__main__" :
38- main ()
155+ check_stdlib ()
156+ check_versions ()
157+ check_stubs ()
158+ check_metadata ()
159+ check_same_files ()
0 commit comments