Skip to content

Commit 03f588d

Browse files
authored
Merge pull request #61 from ArieLevs/fix/issue-58-version-regex
Fix version regex to prevent false matches on compound keys
2 parents 60222c8 + 665edab commit 03f588d

File tree

6 files changed

+88
-15
lines changed

6 files changed

+88
-15
lines changed

.github/workflows/pythonpackage.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,10 @@ jobs:
110110
id: app_version_bump
111111
run: |
112112
pip install pybump
113-
echo "app_version=$(pybump bump --level patch --file pyproject.toml)" >> $GITHUB_OUTPUT
113+
# Bump patch, then set commit SHA as metadata (+sha) for unique test.pypi.org versions
114+
# Using --metadata flag for PEP 440 compatibility (+ instead of -)
115+
pybump bump --level patch --file pyproject.toml
116+
echo "app_version=$(pybump set --auto --metadata --file pyproject.toml)" >> $GITHUB_OUTPUT
114117
- name: Install uv
115118
uses: astral-sh/setup-uv@v5
116119
with:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ dependencies = {file = ["requirements.txt"]}
77

88
[project]
99
name = "pybump"
10-
version = "1.13.2"
10+
version = "1.14.0"
1111
dynamic = ["dependencies"]
1212

1313
authors = [

src/pybump.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,17 @@
1010
except ImportError:
1111
from pybump_version import PybumpVersion
1212

13-
regex_version_pattern = re.compile(r"((?:__)?version(?:__)? ?= ?[\"'])(.+?)([\"'])")
13+
# Regex to match version strings like: version = "1.0.0" or __version__ = '1.0.0'
14+
# (?<![a-zA-Z0-9_-]) - Negative lookbehind: 'version' must NOT be preceded by alphanumeric, underscore, or hyphen
15+
# This prevents matching 'target-version', 'myversion', etc.
16+
# (?:__)? - Optional non-capturing group for '__' prefix (matches __version__)
17+
# version - Literal 'version' string
18+
# (?:__)? - Optional non-capturing group for '__' suffix (matches __version__)
19+
# ?= ? - Optional spaces around the equals sign
20+
# [\"'] - Opening quote (single or double)
21+
# (.+?) - Capture group 2: version value (non-greedy)
22+
# [\"'] - Closing quote (single or double)
23+
regex_version_pattern = re.compile(r"((?<![a-zA-Z0-9_-])(?:__)?version(?:__)? ?= ?[\"'])(.+?)([\"'])")
1424

1525

1626
def is_valid_helm_chart(content):
@@ -176,6 +186,9 @@ def main(): # pragma: no cover
176186
help='Set automatic release / metadata from current git branch for current version')
177187
set_group.add_argument('--set-version',
178188
help='Semantic version to set as a combination of \'vX.Y.Z-release+metadata\'')
189+
parser_set.add_argument('--metadata', action='store_true',
190+
help='With --auto, set commit SHA as metadata (+sha) instead of release (-sha)',
191+
required=False)
179192
parser_set.add_argument('--quiet', action='store_true', help='Do not print new version', required=False)
180193

181194
# Sub-parser for get version command
@@ -246,18 +259,23 @@ def main(): # pragma: no cover
246259
# Case set-version argument passed, just set the new version with its value
247260
if args['set_version']:
248261
new_version = PybumpVersion(args['set_version'])
249-
# Case the 'auto' flag was set, set release with current git branch name and metadata with hash
262+
# Case the 'auto' flag was set, set release or metadata with git commit SHA
250263
elif args['auto']:
251264
from git import Repo, InvalidGitRepositoryError
252265
# get the directory path of current working file
253266
file_dirname_path = os.path.dirname(args['file'])
254267
try:
255268
repo = Repo(path=file_dirname_path, search_parent_directories=True)
256-
# update current version release and metadata with relevant git values
269+
# get commit SHA (try active branch first, fall back to HEAD)
257270
try:
258-
version_object.release = str(repo.active_branch.commit)
271+
commit_sha = str(repo.active_branch.commit)
259272
except TypeError:
260-
version_object.release = str(repo.head.object.hexsha)
273+
commit_sha = str(repo.head.object.hexsha)
274+
# set metadata (+sha) if --metadata flag is set, otherwise set release (-sha)
275+
if args.get('metadata'):
276+
version_object.metadata = commit_sha
277+
else:
278+
version_object.release = commit_sha
261279
new_version = version_object
262280
except InvalidGitRepositoryError:
263281
print("{} is not a valid git repo".format(file_dirname_path), file=stderr)

test/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,31 @@
9999
dependencies = []
100100
"""
101101

102+
# pyproject.toml with compound keys containing 'version' substring (issue #58)
103+
# Should only match 'version = "2.0.0"' and NOT 'target-version' or other compound keys
104+
valid_pyproject_toml_with_compound_keys = """
105+
[project]
106+
name = "test-project"
107+
version = "2.0.0"
108+
description = "A test project"
109+
110+
[tool.ruff]
111+
target-version = "py312"
112+
113+
[tool.some]
114+
this-is-a-version-containing-key = "1.1.7"
115+
myversion = "should-not-match"
116+
"""
117+
118+
# Content with version NOT at line start - should still match (inline in setup.py style)
119+
valid_setup_py_inline_version = """
120+
setuptools.setup(
121+
name="pybump",
122+
version="3.2.1",
123+
author="Test",
124+
)
125+
"""
126+
102127
valid_version_file_1 = """0.12.4"""
103128
valid_version_file_2 = """1.5.0-alpha+meta"""
104129
invalid_version_file_1 = """

test/test_pybump.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from . import valid_helm_chart, invalid_helm_chart, empty_helm_chart, \
77
valid_setup_py, invalid_setup_py_1, invalid_setup_py_multiple_ver, \
88
valid_version_file_1, valid_version_file_2, invalid_version_file_1, invalid_version_file_2, \
9-
valid_pyproject_toml
9+
valid_pyproject_toml, valid_pyproject_toml_with_compound_keys, valid_setup_py_inline_version
1010

1111

1212
class PyBumpTest(unittest.TestCase):
@@ -178,6 +178,16 @@ def test_get_version_from_file(self):
178178

179179
self.assertEqual(get_version_from_file(valid_pyproject_toml), '0.1.0')
180180

181+
# Test for issue #58: compound keys containing 'version' substring should not match
182+
# File contains: version="2.0.0", target-version="py312", myversion="should-not-match"
183+
# Only 'version' should be matched, not 'target-version' or 'myversion'
184+
self.assertEqual(get_version_from_file(valid_pyproject_toml_with_compound_keys), '2.0.0',
185+
msg="Should only match 'version', not compound keys like 'target-version'")
186+
187+
# Test that inline version (not at line start) still works
188+
self.assertEqual(get_version_from_file(valid_setup_py_inline_version), '3.2.1',
189+
msg="Should match version even when indented (setup.py style)")
190+
181191
def test_set_version_in_file(self):
182192
# test the version replacement string, in a content
183193
content_pre = 'some text before version="3.17.5", and some text after'

test/test_simulate_pybump.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,27 @@ def simulate_get_version(file, app_version=False, sem_ver=False, release=False,
2626
return run(["python", "src/pybump.py", "get", "--file", file], stdout=PIPE, stderr=PIPE)
2727

2828

29-
def simulate_set_version(file, version='', app_version=False, auto=False):
29+
def simulate_set_version(file, version='', app_version=False, auto=False, metadata=False):
3030
"""
3131
execute sub process to simulate real app execution,
3232
set new version to a file
3333
if auto is True, auto add git branch / hash
34+
if metadata is True (with auto), set SHA as metadata (+sha) instead of release (-sha)
3435
if app_version is True, then add the --app-version flag to execution
3536
:param file: string
3637
:param version: string
3738
:param app_version: boolean
3839
:param auto: boolean
40+
:param metadata: boolean
3941
:return: CompletedProcess object
4042
"""
4143
if auto:
44+
cmd = ["python", "src/pybump.py", "set", "--file", file, "--auto"]
45+
if metadata:
46+
cmd.append("--metadata")
4247
if app_version:
43-
return run(["python", "src/pybump.py", "set", "--file", file, "--auto", "--app-version"],
44-
stdout=PIPE, stderr=PIPE)
45-
else:
46-
return run(["python", "src/pybump.py", "set", "--file", file, "--auto"],
47-
stdout=PIPE, stderr=PIPE)
48+
cmd.append("--app-version")
49+
return run(cmd, stdout=PIPE, stderr=PIPE)
4850
else:
4951
if app_version:
5052
return run(["python", "src/pybump.py", "set", "--file", file, "--set-version", version, "--app-version"],
@@ -239,16 +241,31 @@ def test_get_flags(self):
239241

240242
def test_set_flags(self):
241243
################################################
242-
# simulate the 'set' command with version prefix
244+
# simulate the 'set' command with --auto flag
243245
################################################
244246
# first set test_valid_setup.py a simple version
245247
simulate_set_version("test/test_content_files/test_valid_setup.py", version="1.0.1")
246248

249+
# test --auto sets release (-sha)
247250
test_set_auto = simulate_set_version("test/test_content_files/test_valid_setup.py", auto=True)
248251
self.assertRegex(test_set_auto.stdout.decode('utf-8').strip(),
249252
r'\b1.0.1-[0-9a-f]{40}\b',
250253
msg="test that 'test_set_auto' contains an hexadecimal string with exactly 40 characters")
251254

255+
########################################################
256+
# simulate the 'set' command with --auto --metadata flag
257+
########################################################
258+
# reset to simple version
259+
simulate_set_version("test/test_content_files/test_valid_setup.py", version="2.0.0")
260+
261+
# test --auto --metadata sets metadata (+sha) instead of release (-sha)
262+
test_set_auto_metadata = simulate_set_version("test/test_content_files/test_valid_setup.py",
263+
auto=True, metadata=True)
264+
self.assertRegex(test_set_auto_metadata.stdout.decode('utf-8').strip(),
265+
r'\b2.0.0\+[0-9a-f]{40}\b',
266+
msg="test that '--auto --metadata' sets SHA as metadata (+sha), "
267+
"output should match 2.0.0+<40-char-hex>")
268+
252269
# test invalid version set
253270
test_set_auto = simulate_set_version("test/test_content_files/test_valid_setup.py", version='V123.x.4')
254271
self.assertEqual('Invalid semantic version format: V123.x.4\nMake sure to comply with https://semver.org/ '

0 commit comments

Comments
 (0)