6
6
import sys
7
7
from concurrent .futures import ThreadPoolExecutor
8
8
from datetime import datetime , timedelta
9
- from typing import Dict , Iterator , Tuple
9
+ from typing import Dict , Iterator , Optional , Tuple
10
10
11
11
import yaml
12
12
@@ -34,10 +34,14 @@ def error(msg: str) -> None:
34
34
print ("ERROR:" , msg )
35
35
36
36
37
- def parse_requirements (fname ) -> Iterator [Tuple [str , int , int ]]:
37
+ def warning (msg : str ) -> None :
38
+ print ("WARNING:" , msg )
39
+
40
+
41
+ def parse_requirements (fname ) -> Iterator [Tuple [str , int , int , Optional [int ]]]:
38
42
"""Load requirements/py36-min-all-deps.yml
39
43
40
- Yield (package name, major version, minor version)
44
+ Yield (package name, major version, minor version, [patch version] )
41
45
"""
42
46
global has_errors
43
47
@@ -52,15 +56,18 @@ def parse_requirements(fname) -> Iterator[Tuple[str, int, int]]:
52
56
if pkg .endswith ("<" ) or pkg .endswith (">" ) or eq != "=" :
53
57
error ("package should be pinned with exact version: " + row )
54
58
continue
59
+
55
60
try :
56
- major , minor = version .split ("." )
57
- except ValueError :
58
- error ("expected major.minor (without patch): " + row )
59
- continue
60
- try :
61
- yield pkg , int (major ), int (minor )
61
+ version_tup = tuple (int (x ) for x in version .split ("." ))
62
62
except ValueError :
63
- error ("failed to parse version: " + row )
63
+ raise ValueError ("non-numerical version: " + row )
64
+
65
+ if len (version_tup ) == 2 :
66
+ yield (pkg , * version_tup , None ) # type: ignore
67
+ elif len (version_tup ) == 3 :
68
+ yield (pkg , * version_tup ) # type: ignore
69
+ else :
70
+ raise ValueError ("expected major.minor or major.minor.patch: " + row )
64
71
65
72
66
73
def query_conda (pkg : str ) -> Dict [Tuple [int , int ], datetime ]:
@@ -80,9 +87,9 @@ def query_conda(pkg: str) -> Dict[Tuple[int, int], datetime]:
80
87
label = label .strip ()
81
88
if label == "file name" :
82
89
value = value .strip ()[len (pkg ) :]
83
- major , minor = value .split ("-" )[1 ].split ("." )[:2 ]
84
- major = int (major )
85
- minor = int (minor )
90
+ smajor , sminor = value .split ("-" )[1 ].split ("." )[:2 ]
91
+ major = int (smajor )
92
+ minor = int (sminor )
86
93
if label == "timestamp" :
87
94
assert major is not None
88
95
assert minor is not None
@@ -109,17 +116,15 @@ def query_conda(pkg: str) -> Dict[Tuple[int, int], datetime]:
109
116
110
117
111
118
def process_pkg (
112
- pkg : str , req_major : int , req_minor : int
113
- ) -> Tuple [str , int , int , str , int , int , str , str ]:
119
+ pkg : str , req_major : int , req_minor : int , req_patch : Optional [ int ]
120
+ ) -> Tuple [str , str , str , str , str , str ]:
114
121
"""Compare package version from requirements file to available versions in conda.
115
122
Return row to build pandas dataframe:
116
123
117
124
- package name
118
- - major version in requirements file
119
- - minor version in requirements file
125
+ - major.minor.[patch] version in requirements file
120
126
- publication date of version in requirements file (YYYY-MM-DD)
121
- - major version suggested by policy
122
- - minor version suggested by policy
127
+ - major.minor version suggested by policy
123
128
- publication date of version suggested by policy (YYYY-MM-DD)
124
129
- status ("<", "=", "> (!)")
125
130
"""
@@ -130,7 +135,7 @@ def process_pkg(
130
135
req_published = versions [req_major , req_minor ]
131
136
except KeyError :
132
137
error ("not found in conda: " + pkg )
133
- return pkg , req_major , req_minor , "-" , 0 , 0 , "-" , "(!)"
138
+ return pkg , fmt_version ( req_major , req_minor , req_patch ), "-" , "-" , "-" , "(!)"
134
139
135
140
policy_months = POLICY_MONTHS .get (pkg , POLICY_MONTHS_DEFAULT )
136
141
policy_published = datetime .now () - timedelta (days = policy_months * 30 )
@@ -153,30 +158,39 @@ def process_pkg(
153
158
else :
154
159
status = "="
155
160
161
+ if req_patch is not None :
162
+ warning ("patch version should not appear in requirements file: " + pkg )
163
+ status += " (w)"
164
+
156
165
return (
157
166
pkg ,
158
- req_major ,
159
- req_minor ,
167
+ fmt_version (req_major , req_minor , req_patch ),
160
168
req_published .strftime ("%Y-%m-%d" ),
161
- policy_major ,
162
- policy_minor ,
169
+ fmt_version (policy_major , policy_minor ),
163
170
policy_published_actual .strftime ("%Y-%m-%d" ),
164
171
status ,
165
172
)
166
173
167
174
175
+ def fmt_version (major : int , minor : int , patch : int = None ) -> str :
176
+ if patch is None :
177
+ return f"{ major } .{ minor } "
178
+ else :
179
+ return f"{ major } .{ minor } .{ patch } "
180
+
181
+
168
182
def main () -> None :
169
183
fname = sys .argv [1 ]
170
184
with ThreadPoolExecutor (8 ) as ex :
171
185
futures = [
172
- ex .submit (process_pkg , pkg , major , minor )
173
- for pkg , major , minor in parse_requirements (fname )
186
+ ex .submit (process_pkg , pkg , major , minor , patch )
187
+ for pkg , major , minor , patch in parse_requirements (fname )
174
188
]
175
189
rows = [f .result () for f in futures ]
176
190
177
- print ("Package Required Policy Status" )
178
- print ("------------- ----------------- ----------------- ------" )
179
- fmt = "{:13} {:>1d}.{:<2d} ({:10}) {:>1d}.{:<2d } ({:10}) {}"
191
+ print ("Package Required Policy Status" )
192
+ print ("------------- -------------------- --- ----------------- ------" )
193
+ fmt = "{:13} {:7} ({:10}) {:7 } ({:10}) {}"
180
194
for row in rows :
181
195
print (fmt .format (* row ))
182
196
0 commit comments