Skip to content

Commit 226c2bb

Browse files
authored
publish on tag (#43)
1 parent b1d4f47 commit 226c2bb

File tree

2 files changed

+349
-0
lines changed

2 files changed

+349
-0
lines changed

.github/workflows/publish.yml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
python-version: ["3.10", "3.11", "3.12"]
14+
15+
services:
16+
kinesis:
17+
image: localstack/localstack:latest
18+
ports:
19+
- 4566:4566
20+
env:
21+
SERVICES: kinesis
22+
KINESIS_ERROR_PROBABILITY: 0.0
23+
DEBUG: 1
24+
25+
redis:
26+
image: redis:latest
27+
ports:
28+
- 6379:6379
29+
options: >-
30+
--health-cmd "redis-cli ping"
31+
--health-interval 10s
32+
--health-timeout 5s
33+
--health-retries 5
34+
35+
steps:
36+
- uses: actions/checkout@v4
37+
38+
- name: Set up Python ${{ matrix.python-version }}
39+
uses: actions/setup-python@v4
40+
with:
41+
python-version: ${{ matrix.python-version }}
42+
43+
- name: Cache pip dependencies
44+
uses: actions/cache@v3
45+
with:
46+
path: ~/.cache/pip
47+
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/test-requirements.txt') }}
48+
restore-keys: |
49+
${{ runner.os }}-pip-
50+
51+
- name: Install dependencies
52+
run: |
53+
python -m pip install --upgrade pip
54+
pip install -r requirements.txt
55+
pip install -r test-requirements.txt
56+
sudo apt-get update && sudo apt-get install -y netcat-openbsd redis-tools curl
57+
58+
- name: Wait for services
59+
run: |
60+
# Give services time to start
61+
sleep 20
62+
# Wait for LocalStack port to be available (skip health check due to S3 errors)
63+
timeout 120 bash -c 'until nc -z localhost 4566; do echo "Waiting for localstack port..."; sleep 3; done'
64+
echo "LocalStack port is ready"
65+
# Wait for Redis
66+
timeout 60 bash -c 'until redis-cli -h localhost ping; do echo "Waiting for redis..."; sleep 2; done'
67+
echo "Redis is ready"
68+
69+
- name: Run tests
70+
run: |
71+
timeout 600 pytest -v --cov=kinesis --cov-report=xml --cov-report=term-missing
72+
env:
73+
ENDPOINT_URL: http://localhost:4566
74+
REDIS_HOST: localhost
75+
REDIS_PORT: 6379
76+
AWS_DEFAULT_REGION: ap-southeast-2
77+
AWS_ACCESS_KEY_ID: test
78+
AWS_SECRET_ACCESS_KEY: test
79+
80+
lint:
81+
runs-on: ubuntu-latest
82+
steps:
83+
- uses: actions/checkout@v4
84+
85+
- name: Set up Python
86+
uses: actions/setup-python@v4
87+
with:
88+
python-version: "3.12"
89+
90+
- name: Install dependencies
91+
run: |
92+
python -m pip install --upgrade pip
93+
pip install black isort flake8
94+
95+
- name: Check code formatting with black
96+
run: black --check --diff kinesis tests
97+
98+
- name: Check import sorting with isort
99+
run: isort --check-only --diff kinesis tests
100+
101+
- name: Lint with flake8
102+
run: flake8 kinesis tests --max-line-length=88 --extend-ignore=E203,W503,E501
103+
104+
publish:
105+
needs: [test, lint]
106+
runs-on: ubuntu-latest
107+
environment: pypi
108+
permissions:
109+
id-token: write # For trusted publishing
110+
111+
steps:
112+
- uses: actions/checkout@v4
113+
114+
- name: Set up Python
115+
uses: actions/setup-python@v4
116+
with:
117+
python-version: "3.12"
118+
119+
- name: Install build dependencies
120+
run: |
121+
python -m pip install --upgrade pip
122+
pip install build twine
123+
124+
- name: Extract tag version
125+
id: get_version
126+
run: |
127+
echo "tag_version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
128+
129+
- name: Update version in setup.py
130+
run: |
131+
sed -i 's/version="[^"]*"/version="${{ steps.get_version.outputs.tag_version }}"/' setup.py
132+
133+
- name: Verify version update
134+
run: |
135+
echo "Updated version in setup.py:"
136+
grep 'version=' setup.py
137+
138+
- name: Build package
139+
run: python -m build
140+
141+
- name: Check package
142+
run: twine check dist/*
143+
144+
- name: Publish to PyPI
145+
uses: pypa/gh-action-pypi-publish@release/v1
146+
with:
147+
repository-url: https://upload.pypi.org/legacy/

scripts/release.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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

Comments
 (0)