6
6
Additions by Barry Warsaw, Georg Brandl and Benjamin Peterson
7
7
"""
8
8
9
+ from __future__ import annotations
10
+
9
11
import datetime
10
12
import glob
11
13
import hashlib
18
20
import sys
19
21
import tempfile
20
22
from contextlib import contextmanager
23
+ from typing import Any , Callable , Generator , Self
21
24
22
25
COMMASPACE = ", "
23
26
SPACE = " "
28
31
29
32
30
33
class Tag :
31
- def __init__ (self , tag_name ) :
34
+ def __init__ (self , tag_name : str ) -> None :
32
35
# if tag is ".", use current directory name as tag
33
36
# e.g. if current directory name is "3.4.6",
34
37
# "release.py --bump 3.4.6" and "release.py --bump ." are the same
@@ -37,6 +40,7 @@ def __init__(self, tag_name):
37
40
result = tag_cre .match (tag_name )
38
41
if result is None :
39
42
error (f"tag { tag_name } is not valid" )
43
+ assert result is not None
40
44
data = list (result .groups ())
41
45
if data [3 ] is None :
42
46
# A final release.
@@ -56,47 +60,49 @@ def __init__(self, tag_name):
56
60
# This has the effect of normalizing the version.
57
61
self .text = self .normalized ()
58
62
if self .level != "f" :
63
+ assert self .level is not None
59
64
self .text += self .level + str (self .serial )
60
65
self .basic_version = f"{ self .major } .{ self .minor } "
61
66
62
- def __str__ (self ):
67
+ def __str__ (self ) -> str :
63
68
return self .text
64
69
65
- def normalized (self ):
70
+ def normalized (self ) -> str :
66
71
return f"{ self .major } .{ self .minor } .{ self .patch } "
67
72
68
73
@property
69
- def branch (self ):
74
+ def branch (self ) -> str :
70
75
return "main" if self .is_alpha_release else f"{ self .major } .{ self .minor } "
71
76
72
77
@property
73
- def is_alpha_release (self ):
78
+ def is_alpha_release (self ) -> bool :
74
79
return self .level == "a"
75
80
76
81
@property
77
- def is_release_candidate (self ):
82
+ def is_release_candidate (self ) -> bool :
78
83
return self .level == "rc"
79
84
80
85
@property
81
- def is_feature_freeze_release (self ):
86
+ def is_feature_freeze_release (self ) -> bool :
82
87
return self .level == "b" and self .serial == 1
83
88
84
89
@property
85
- def nickname (self ):
90
+ def nickname (self ) -> str :
86
91
return self .text .replace ("." , "" )
87
92
88
93
@property
89
- def gitname (self ):
94
+ def gitname (self ) -> str :
90
95
return "v" + self .text
91
96
92
- def next_minor_release (self ):
97
+ def next_minor_release (self ) -> Self :
93
98
return self .__class__ (f"{ self .major } .{ int (self .minor )+ 1 } .0a0" )
94
99
95
- def as_tuple (self ):
96
- return (self .major , self .minor , self .patch , self .level , self .serial )
100
+ def as_tuple (self ) -> tuple [int , int , int , str , int ]:
101
+ assert isinstance (self .level , str )
102
+ return self .major , self .minor , self .patch , self .level , self .serial
97
103
98
104
@property
99
- def committed_at (self ):
105
+ def committed_at (self ) -> datetime . datetime :
100
106
# Fetch the epoch of the tagged commit for build reproducibility.
101
107
proc = subprocess .run (
102
108
["git" , "log" , self .gitname , "-1" , "--pretty=%ct" ], stdout = subprocess .PIPE
@@ -108,14 +114,16 @@ def committed_at(self):
108
114
)
109
115
110
116
111
- def error (* msgs ) :
117
+ def error (* msgs : str ) -> None :
112
118
print ("**ERROR**" , file = sys .stderr )
113
119
for msg in msgs :
114
120
print (msg , file = sys .stderr )
115
121
sys .exit (1 )
116
122
117
123
118
- def run_cmd (cmd , silent = False , shell = False , ** kwargs ):
124
+ def run_cmd (
125
+ cmd : list [str ] | str , silent : bool = False , shell : bool = False , ** kwargs : Any
126
+ ) -> None :
119
127
if shell :
120
128
cmd = SPACE .join (cmd )
121
129
if not silent :
@@ -134,7 +142,7 @@ def run_cmd(cmd, silent=False, shell=False, **kwargs):
134
142
root = None
135
143
136
144
137
- def chdir_to_repo_root ():
145
+ def chdir_to_repo_root () -> str :
138
146
global root
139
147
140
148
# find the root of the local CPython repo
@@ -152,7 +160,10 @@ def chdir_to_repo_root():
152
160
153
161
os .chdir (path )
154
162
155
- def test_first_line (filename , test ):
163
+ def test_first_line (
164
+ filename : str ,
165
+ test : Callable [[str ], re .Match [str ] | None ] | Callable [[object ], bool ],
166
+ ) -> bool :
156
167
if not os .path .exists (filename ):
157
168
return False
158
169
with open (filename ) as f :
@@ -180,18 +191,18 @@ def test_first_line(filename, test):
180
191
return root
181
192
182
193
183
- def get_output (args ) :
194
+ def get_output (args : list [ str ]) -> bytes :
184
195
return subprocess .check_output (args )
185
196
186
197
187
- def check_env ():
198
+ def check_env () -> None :
188
199
if "EDITOR" not in os .environ :
189
200
error ("editor not detected." , "Please set your EDITOR environment variable" )
190
201
if not os .path .exists (".git" ):
191
202
error ("CWD is not a git clone" )
192
203
193
204
194
- def get_arg_parser ():
205
+ def get_arg_parser () -> optparse . OptionParser :
195
206
usage = "%prog [options] tagname"
196
207
p = optparse .OptionParser (usage = usage )
197
208
p .add_option (
@@ -244,7 +255,9 @@ def get_arg_parser():
244
255
return p
245
256
246
257
247
- def constant_replace (fn , updated_constants , comment_start = "/*" , comment_end = "*/" ):
258
+ def constant_replace (
259
+ fn : str , updated_constants : str , comment_start : str = "/*" , comment_end : str = "*/"
260
+ ) -> None :
248
261
"""Inserts in between --start constant-- and --end constant-- in a file"""
249
262
start_tag = comment_start + "--start constants--" + comment_end
250
263
end_tag = comment_start + "--end constants--" + comment_end
@@ -271,7 +284,7 @@ def constant_replace(fn, updated_constants, comment_start="/*", comment_end="*/"
271
284
os .rename (fn + ".new" , fn )
272
285
273
286
274
- def tweak_patchlevel (tag , done = False ):
287
+ def tweak_patchlevel (tag : Tag , done : bool = False ) -> None :
275
288
print ("Updating Include/patchlevel.h..." , end = " " )
276
289
template = '''
277
290
#define PY_MAJOR_VERSION\t {tag.major}
@@ -282,6 +295,7 @@ def tweak_patchlevel(tag, done=False):
282
295
283
296
/* Version as a string */
284
297
#define PY_VERSION \t \" {tag.text}{plus}"''' .strip ()
298
+ assert tag .level is str
285
299
level_def = {
286
300
"a" : "PY_RELEASE_LEVEL_ALPHA" ,
287
301
"b" : "PY_RELEASE_LEVEL_BETA" ,
@@ -297,7 +311,7 @@ def tweak_patchlevel(tag, done=False):
297
311
print ("done" )
298
312
299
313
300
- def bump (tag ) :
314
+ def bump (tag : Tag ) -> None :
301
315
print (f"Bumping version to { tag } " )
302
316
303
317
tweak_patchlevel (tag )
@@ -338,7 +352,7 @@ def manual_edit(fn: str) -> None:
338
352
339
353
340
354
@contextmanager
341
- def pushd (new ) :
355
+ def pushd (new : str ) -> Generator [ None , None , None ] :
342
356
print (f"chdir'ing to { new } " )
343
357
old = os .getcwd ()
344
358
os .chdir (new )
@@ -348,7 +362,7 @@ def pushd(new):
348
362
os .chdir (old )
349
363
350
364
351
- def make_dist (name ) :
365
+ def make_dist (name : str ) -> None :
352
366
try :
353
367
os .mkdir (name )
354
368
except OSError :
@@ -360,7 +374,7 @@ def make_dist(name):
360
374
print (f"created dist directory { name } " )
361
375
362
376
363
- def tarball (source , clamp_mtime ) :
377
+ def tarball (source : str , clamp_mtime : str ) -> None :
364
378
"""Build tarballs for a directory."""
365
379
print ("Making .tgz" )
366
380
base = os .path .basename (source )
@@ -408,7 +422,7 @@ def tarball(source, clamp_mtime):
408
422
print (" %s %8s %s" % (checksum_xz .hexdigest (), int (os .path .getsize (xz )), xz ))
409
423
410
424
411
- def export (tag , silent = False , skip_docs = False ):
425
+ def export (tag : Tag , silent : bool = False , skip_docs : bool = False ) -> None :
412
426
make_dist (tag .text )
413
427
print ("Exporting tag:" , tag .text )
414
428
archivename = f"Python-{ tag .text } "
@@ -531,7 +545,7 @@ def export(tag, silent=False, skip_docs=False):
531
545
print ("**You may also want to run make install and re-test**" )
532
546
533
547
534
- def build_docs ():
548
+ def build_docs () -> str :
535
549
"""Build and tarball the documentation"""
536
550
print ("Building docs" )
537
551
with tempfile .TemporaryDirectory () as venv :
@@ -548,11 +562,11 @@ def build_docs():
548
562
return os .path .abspath ("dist" )
549
563
550
564
551
- def upload (tag , username ) :
565
+ def upload (tag : Tag , username : str ) -> None :
552
566
"""scp everything to dinsdale"""
553
567
address = f'"{ username } @dinsdale.python.org:'
554
568
555
- def scp (from_loc , to_loc ) :
569
+ def scp (from_loc : str , to_loc : str ) -> None :
556
570
run_cmd (["scp" , from_loc , address + to_loc ])
557
571
558
572
with pushd (tag .text ):
@@ -566,7 +580,7 @@ def scp(from_loc, to_loc):
566
580
)
567
581
568
582
569
- def make_tag (tag ) :
583
+ def make_tag (tag : Tag ) -> bool :
570
584
# make sure we've run blurb export
571
585
good_files = glob .glob ("Misc/NEWS.d/" + str (tag ) + ".rst" )
572
586
bad_files = list (glob .glob ("Misc/NEWS.d/next/*/0*.rst" ))
@@ -604,11 +618,11 @@ def make_tag(tag):
604
618
return True
605
619
606
620
607
- def done (tag ) :
621
+ def done (tag : Tag ) -> None :
608
622
tweak_patchlevel (tag , done = True )
609
623
610
624
611
- def main (argv ) :
625
+ def main (argv : Any ) -> None :
612
626
chdir_to_repo_root ()
613
627
parser = get_arg_parser ()
614
628
options , args = parser .parse_args (argv )
0 commit comments