1+ #!/usr/bin/env python3
2+ """
3+ Release script for async-kinesis
4+
5+ Usage:
6+ python scripts/release.py --version 1.2.0
7+ python scripts/release.py --check # Check current version
8+ python scripts/release.py --next-patch # Auto-increment patch version
9+ python scripts/release.py --next-minor # Auto-increment minor version
10+ python scripts/release.py --next-major # Auto-increment major version
11+ """
12+
13+ import argparse
14+ import re
15+ import subprocess
16+ import sys
17+ from pathlib import Path
18+
19+
20+ def get_current_version ():
21+ """Extract current version from setup.py"""
22+ setup_py = Path ("setup.py" )
23+ if not setup_py .exists ():
24+ raise FileNotFoundError ("setup.py not found" )
25+
26+ content = setup_py .read_text ()
27+ match = re .search (r'version="([^"]+)"' , content )
28+ if not match :
29+ raise ValueError ("Version not found in setup.py" )
30+
31+ return match .group (1 )
32+
33+
34+ def update_version (new_version ):
35+ """Update version in setup.py"""
36+ setup_py = Path ("setup.py" )
37+ content = setup_py .read_text ()
38+
39+ # Update version in setup.py
40+ new_content = re .sub (
41+ r'version="[^"]+"' ,
42+ f'version="{ new_version } "' ,
43+ content
44+ )
45+
46+ if content == new_content :
47+ raise ValueError ("Version pattern not found in setup.py" )
48+
49+ setup_py .write_text (new_content )
50+ print (f"Updated setup.py version to { new_version } " )
51+
52+
53+ def increment_version (current_version , bump_type ):
54+ """Increment version based on bump type"""
55+ parts = current_version .split ('.' )
56+ if len (parts ) != 3 :
57+ raise ValueError (f"Invalid version format: { current_version } " )
58+
59+ major , minor , patch = map (int , parts )
60+
61+ if bump_type == "major" :
62+ major += 1
63+ minor = 0
64+ patch = 0
65+ elif bump_type == "minor" :
66+ minor += 1
67+ patch = 0
68+ elif bump_type == "patch" :
69+ patch += 1
70+ else :
71+ raise ValueError (f"Invalid bump type: { bump_type } " )
72+
73+ return f"{ major } .{ minor } .{ patch } "
74+
75+
76+ def run_tests ():
77+ """Run tests to ensure everything works before release"""
78+ print ("Running tests..." )
79+ result = subprocess .run (["python" , "-m" , "pytest" , "-x" ], capture_output = True , text = True )
80+ if result .returncode != 0 :
81+ print ("Tests failed!" )
82+ print (result .stdout )
83+ print (result .stderr )
84+ return False
85+ print ("Tests passed!" )
86+ return True
87+
88+
89+ def run_linting ():
90+ """Run linting checks"""
91+ print ("Running linting checks..." )
92+
93+ # Black
94+ result = subprocess .run (["black" , "--check" , "kinesis" , "tests" ], capture_output = True )
95+ if result .returncode != 0 :
96+ print ("Black formatting check failed!" )
97+ return False
98+
99+ # isort
100+ result = subprocess .run (["isort" , "--check-only" , "kinesis" , "tests" ], capture_output = True )
101+ if result .returncode != 0 :
102+ print ("isort check failed!" )
103+ return False
104+
105+ # flake8
106+ result = subprocess .run ([
107+ "flake8" , "kinesis" , "tests" ,
108+ "--max-line-length=88" ,
109+ "--extend-ignore=E203,W503,E501"
110+ ], capture_output = True )
111+ if result .returncode != 0 :
112+ print ("flake8 check failed!" )
113+ return False
114+
115+ print ("All linting checks passed!" )
116+ return True
117+
118+
119+ def create_tag (version ):
120+ """Create and push git tag"""
121+ print (f"Creating git tag { version } ..." )
122+
123+ # Check if working directory is clean
124+ result = subprocess .run (["git" , "status" , "--porcelain" ], capture_output = True , text = True )
125+ if result .stdout .strip ():
126+ print ("Working directory is not clean. Please commit changes first." )
127+ return False
128+
129+ # Create tag
130+ subprocess .run (["git" , "tag" , version ], check = True )
131+ print (f"Created tag { version } " )
132+
133+ # Ask user if they want to push
134+ push = input ("Push tag to origin? (y/N): " ).lower ().strip ()
135+ if push in ('y' , 'yes' ):
136+ subprocess .run (["git" , "push" , "origin" , version ], check = True )
137+ print (f"Pushed tag { version } to origin" )
138+ print (f"GitHub Actions will now build and publish version { version } to PyPI" )
139+ else :
140+ print (f"Tag { version } created locally. Push manually with: git push origin { version } " )
141+
142+ return True
143+
144+
145+ def main ():
146+ parser = argparse .ArgumentParser (description = "Release management for async-kinesis" )
147+ group = parser .add_mutually_exclusive_group (required = True )
148+ group .add_argument ("--version" , help = "Set specific version" )
149+ group .add_argument ("--check" , action = "store_true" , help = "Check current version" )
150+ group .add_argument ("--next-patch" , action = "store_true" , help = "Increment patch version" )
151+ group .add_argument ("--next-minor" , action = "store_true" , help = "Increment minor version" )
152+ group .add_argument ("--next-major" , action = "store_true" , help = "Increment major version" )
153+
154+ parser .add_argument ("--skip-tests" , action = "store_true" , help = "Skip running tests" )
155+ parser .add_argument ("--skip-lint" , action = "store_true" , help = "Skip linting checks" )
156+ parser .add_argument ("--no-tag" , action = "store_true" , help = "Don't create git tag" )
157+
158+ args = parser .parse_args ()
159+
160+ try :
161+ current_version = get_current_version ()
162+ print (f"Current version: { current_version } " )
163+
164+ if args .check :
165+ return
166+
167+ # Determine new version
168+ if args .version :
169+ new_version = args .version
170+ elif args .next_patch :
171+ new_version = increment_version (current_version , "patch" )
172+ elif args .next_minor :
173+ new_version = increment_version (current_version , "minor" )
174+ elif args .next_major :
175+ new_version = increment_version (current_version , "major" )
176+
177+ print (f"New version: { new_version } " )
178+
179+ # Run checks
180+ if not args .skip_lint and not run_linting ():
181+ sys .exit (1 )
182+
183+ if not args .skip_tests and not run_tests ():
184+ sys .exit (1 )
185+
186+ # Update version
187+ update_version (new_version )
188+
189+ # Create tag if requested
190+ if not args .no_tag :
191+ if not create_tag (new_version ):
192+ sys .exit (1 )
193+ else :
194+ print (f"Version updated to { new_version } . Create tag manually with: git tag { new_version } " )
195+
196+ except Exception as e :
197+ print (f"Error: { e } " )
198+ sys .exit (1 )
199+
200+
201+ if __name__ == "__main__" :
202+ main ()
0 commit comments