Skip to content

Commit 480c2bc

Browse files
acul71web3-bot
andauthored
Fix release process (#27)
* fix: update pyupgrade to v3.21.2 for Python 3.14 compatibility Fixes #24 Updated pyupgrade from v3.15.0 to v3.21.2 to resolve Python 3.14 compatibility issues in linting. The previous version had a bug with Python 3.14's tokenize module that caused TypeError when processing files. - Updated .pre-commit-config.yaml - Added newsfragment for issue #24 - All Python 3.14 lint tests now pass * Fix release process (closes #26) - Add release automation targets to Makefile (validate-newsfragments, build-docs, package-test, notes, release, check-bump, check-git) - Add sphinx.ext.doctest extension to docs/conf.py for doctest support - Fix version string quotes in pyproject.toml bumpversion config - Create comprehensive package test script (scripts/release/test_package.py) This aligns the release process with py-multihash PR 31 improvements. --------- Co-authored-by: web3-bot <[email protected]>
1 parent 5d78683 commit 480c2bc

File tree

5 files changed

+230
-3
lines changed

5 files changed

+230
-3
lines changed

Makefile

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,55 @@ dist: clean
9292

9393
pr: clean fix lint typecheck test
9494
@echo "PR preparation complete! All checks passed."
95+
96+
# docs helpers
97+
98+
validate-newsfragments:
99+
python ./newsfragments/validate_files.py
100+
towncrier build --draft --version preview
101+
102+
build-docs:
103+
sphinx-apidoc -o docs/ . "*conftest*" tests/
104+
$(MAKE) -C docs clean
105+
$(MAKE) -C docs html
106+
$(MAKE) -C docs doctest
107+
108+
# release commands
109+
110+
package-test: clean
111+
python -m build
112+
python scripts/release/test_package.py
113+
114+
notes: check-bump validate-newsfragments
115+
# Let UPCOMING_VERSION be the version that is used for the current bump
116+
$(eval UPCOMING_VERSION=$(shell bump-my-version bump --dry-run $(bump) -v | awk -F"'" '/New version will be / {print $$2}'))
117+
# Now generate the release notes to have them included in the release commit
118+
towncrier build --yes --version $(UPCOMING_VERSION)
119+
# Before we bump the version, make sure that the towncrier-generated docs will build
120+
make build-docs
121+
git commit -m "Compile release notes for v$(UPCOMING_VERSION)"
122+
123+
release: check-bump check-git clean
124+
# verify that notes command ran correctly
125+
./newsfragments/validate_files.py is-empty
126+
CURRENT_SIGN_SETTING=$(git config commit.gpgSign)
127+
git config commit.gpgSign true
128+
bump-my-version bump $(bump)
129+
python -m build
130+
git config commit.gpgSign "$(CURRENT_SIGN_SETTING)"
131+
git push upstream && git push upstream --tags
132+
twine upload dist/*
133+
134+
# release helpers
135+
136+
check-bump:
137+
ifndef bump
138+
$(error bump must be set, typically: major, minor, patch, or devnum)
139+
endif
140+
141+
check-git:
142+
# require that upstream is configured for multiformats/py-multibase
143+
@if ! git remote -v | grep "upstream[[:space:]][email protected]:multiformats/py-multibase.git (push)\|upstream[[:space:]]https://github.com/multiformats/py-multibase (push)"; then \
144+
echo "Error: You must have a remote named 'upstream' that points to 'py-multibase'"; \
145+
exit 1; \
146+
fi

docs/conf.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@
4040

4141
# Add any Sphinx extension module names here, as strings. They can be
4242
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
43-
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
43+
extensions = [
44+
'sphinx.ext.autodoc',
45+
'sphinx.ext.viewcode',
46+
'sphinx.ext.doctest',
47+
]
4448

4549
# Add any paths that contain templates here, relative to this directory.
4650
templates_path = ['_templates']

newsfragments/26.internal.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed release process by adding automated release targets to Makefile, adding doctest support to documentation builds, fixing version string configuration, and creating comprehensive package test script. This aligns the release process with py-multihash PR 31 improvements.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ replace = "version = \"{new_version}\""
164164

165165
[[tool.bumpversion.files]]
166166
filename = "multibase/__init__.py"
167-
search = "__version__ = '{current_version}'"
168-
replace = "__version__ = '{new_version}'"
167+
search = "__version__ = \"{current_version}\""
168+
replace = "__version__ = \"{new_version}\""
169169

170170
[[tool.bumpversion.files]]
171171
filename = "pyproject.toml"

scripts/release/test_package.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#!/usr/bin/env python
2+
"""Test that the built package can be installed and works correctly."""
3+
4+
import subprocess
5+
import sys
6+
from pathlib import Path
7+
8+
# Find the dist directory
9+
dist_dir = Path(__file__).parent.parent.parent / "dist"
10+
if not dist_dir.exists():
11+
print(f"Error: dist directory not found at {dist_dir}")
12+
sys.exit(1)
13+
14+
# Find wheel or source distribution files
15+
dist_files = list(dist_dir.glob("*.whl")) + list(dist_dir.glob("*.tar.gz"))
16+
if not dist_files:
17+
print(f"Error: No distribution files found in {dist_dir}")
18+
sys.exit(1)
19+
20+
# Install the package
21+
dist_file = dist_files[0]
22+
print(f"Installing {dist_file.name}...")
23+
result = subprocess.run(
24+
[sys.executable, "-m", "pip", "install", "--force-reinstall", str(dist_file)],
25+
capture_output=True,
26+
text=True,
27+
)
28+
if result.returncode != 0:
29+
print(f"Error installing package: {result.stderr}")
30+
sys.exit(1)
31+
32+
# Test that the package can be imported and basic functionality works
33+
print("Testing package import and basic functionality...")
34+
try:
35+
import multibase
36+
37+
# Test that version is accessible
38+
assert hasattr(multibase, "__version__"), "Package should have __version__"
39+
assert multibase.__version__, "Version should not be empty"
40+
print(f"✓ Version accessible: {multibase.__version__}")
41+
42+
# Test that main functions are available
43+
assert callable(multibase.encode), "encode should be callable"
44+
assert callable(multibase.decode), "decode should be callable"
45+
assert callable(multibase.is_encoded), "is_encoded should be callable"
46+
assert callable(multibase.list_encodings), "list_encodings should be callable"
47+
assert callable(multibase.is_encoding_supported), "is_encoding_supported should be callable"
48+
assert callable(multibase.get_encoding_info), "get_encoding_info should be callable"
49+
print("✓ Main functions are callable")
50+
51+
# Test that exceptions are importable
52+
assert hasattr(multibase, "UnsupportedEncodingError"), "UnsupportedEncodingError should be available"
53+
assert hasattr(multibase, "InvalidMultibaseStringError"), "InvalidMultibaseStringError should be available"
54+
assert hasattr(multibase, "DecodingError"), "DecodingError should be available"
55+
print("✓ Exceptions are importable")
56+
57+
# Test that classes are importable
58+
assert hasattr(multibase, "Encoder"), "Encoder class should be available"
59+
assert hasattr(multibase, "Decoder"), "Decoder class should be available"
60+
print("✓ Classes are importable")
61+
62+
# Test basic encode/decode with multiple encodings
63+
test_data = "hello world"
64+
65+
# Test base64
66+
encoded = multibase.encode("base64", test_data)
67+
assert encoded.startswith(b"m"), "Base64 encoding should start with 'm'"
68+
decoded = multibase.decode(encoded)
69+
assert decoded == b"hello world", "Base64 decoded data should match original"
70+
71+
# Test base16
72+
encoded = multibase.encode("base16", test_data)
73+
assert encoded.startswith(b"f"), "Base16 encoding should start with 'f'"
74+
decoded = multibase.decode(encoded)
75+
assert decoded == b"hello world", "Base16 decoded data should match original"
76+
77+
# Test base32
78+
encoded = multibase.encode("base32", test_data)
79+
assert encoded.startswith(b"b"), "Base32 encoding should start with 'b'"
80+
decoded = multibase.decode(encoded)
81+
assert decoded == b"hello world", "Base32 decoded data should match original"
82+
print("✓ Multiple encoding types work (base64, base16, base32)")
83+
84+
# Test is_encoded
85+
assert multibase.is_encoded(encoded), "is_encoded should return True for valid multibase string"
86+
assert not multibase.is_encoded("invalid"), "is_encoded should return False for invalid string"
87+
print("✓ is_encoded function works")
88+
89+
# Test is_encoding_supported
90+
assert multibase.is_encoding_supported("base64"), "base64 should be supported"
91+
assert multibase.is_encoding_supported("base16"), "base16 should be supported"
92+
assert not multibase.is_encoding_supported("base999"), "base999 should not be supported"
93+
print("✓ is_encoding_supported function works")
94+
95+
# Test list_encodings
96+
encodings = multibase.list_encodings()
97+
assert isinstance(encodings, list), "list_encodings should return a list"
98+
assert "base64" in encodings, "base64 should be in encodings list"
99+
assert "base16" in encodings, "base16 should be in encodings list"
100+
assert "base32" in encodings, "base32 should be in encodings list"
101+
assert len(encodings) >= 20, "Should have at least 20 encodings"
102+
print(f"✓ list_encodings works (found {len(encodings)} encodings)")
103+
104+
# Test get_encoding_info
105+
info = multibase.get_encoding_info("base64")
106+
assert info.encoding == "base64", "get_encoding_info should return correct encoding name"
107+
assert info.code == b"m", "base64 code should be 'm'"
108+
assert info.converter is not None, "converter should not be None"
109+
print("✓ get_encoding_info function works")
110+
111+
# Test decode with return_encoding
112+
test_encoded = multibase.encode("base16", "test")
113+
encoding, decoded = multibase.decode(test_encoded, return_encoding=True)
114+
assert encoding == "base16", "return_encoding should return correct encoding"
115+
assert decoded == b"test", "decoded data should match"
116+
print("✓ decode with return_encoding works")
117+
118+
# Test Encoder class
119+
encoder = multibase.Encoder("base64")
120+
assert encoder.encoding == "base64", "Encoder should have correct encoding"
121+
encoded = encoder.encode("test")
122+
assert encoded.startswith(b"m"), "Encoder.encode should work"
123+
print("✓ Encoder class works")
124+
125+
# Test Decoder class
126+
decoder = multibase.Decoder()
127+
test_encoded = multibase.encode("base64", "test")
128+
decoded = decoder.decode(test_encoded)
129+
assert decoded == b"test", "Decoder.decode should work"
130+
encoding, decoded = decoder.decode(test_encoded, return_encoding=True)
131+
assert encoding == "base64", "Decoder.decode with return_encoding should work"
132+
print("✓ Decoder class works")
133+
134+
# Test error handling
135+
try:
136+
multibase.encode("base999", "test")
137+
assert False, "Should raise UnsupportedEncodingError"
138+
except multibase.UnsupportedEncodingError:
139+
pass
140+
print("✓ Error handling works (UnsupportedEncodingError)")
141+
142+
try:
143+
multibase.decode("invalid")
144+
assert False, "Should raise InvalidMultibaseStringError"
145+
except multibase.InvalidMultibaseStringError:
146+
pass
147+
print("✓ Error handling works (InvalidMultibaseStringError)")
148+
149+
print(f"\n✓ Package installed successfully (version {multibase.__version__})")
150+
print("✓ All functionality tests passed")
151+
print("Package is ready for release!")
152+
153+
except ImportError as e:
154+
print(f"Error importing package: {e}")
155+
import traceback
156+
157+
traceback.print_exc()
158+
sys.exit(1)
159+
except AssertionError as e:
160+
print(f"Test failed: {e}")
161+
import traceback
162+
163+
traceback.print_exc()
164+
sys.exit(1)
165+
except Exception as e:
166+
print(f"Unexpected error: {e}")
167+
import traceback
168+
169+
traceback.print_exc()
170+
sys.exit(1)

0 commit comments

Comments
 (0)