Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ jobs:
id: app_version_bump
run: |
pip install pybump
echo "app_version=$(pybump bump --level patch --file pyproject.toml)" >> $GITHUB_OUTPUT
# Bump patch, then set commit SHA as metadata (+sha) for unique test.pypi.org versions
# Using --metadata flag for PEP 440 compatibility (+ instead of -)
pybump bump --level patch --file pyproject.toml
echo "app_version=$(pybump set --auto --metadata --file pyproject.toml)" >> $GITHUB_OUTPUT
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ dependencies = {file = ["requirements.txt"]}

[project]
name = "pybump"
version = "1.13.2"
version = "1.14.0"
dynamic = ["dependencies"]

authors = [
Expand Down
28 changes: 23 additions & 5 deletions src/pybump.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@
except ImportError:
from pybump_version import PybumpVersion

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


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

# Sub-parser for get version command
Expand Down Expand Up @@ -246,18 +259,23 @@ def main(): # pragma: no cover
# Case set-version argument passed, just set the new version with its value
if args['set_version']:
new_version = PybumpVersion(args['set_version'])
# Case the 'auto' flag was set, set release with current git branch name and metadata with hash
# Case the 'auto' flag was set, set release or metadata with git commit SHA
elif args['auto']:
from git import Repo, InvalidGitRepositoryError
# get the directory path of current working file
file_dirname_path = os.path.dirname(args['file'])
try:
repo = Repo(path=file_dirname_path, search_parent_directories=True)
# update current version release and metadata with relevant git values
# get commit SHA (try active branch first, fall back to HEAD)
try:
version_object.release = str(repo.active_branch.commit)
commit_sha = str(repo.active_branch.commit)
except TypeError:
version_object.release = str(repo.head.object.hexsha)
commit_sha = str(repo.head.object.hexsha)
# set metadata (+sha) if --metadata flag is set, otherwise set release (-sha)
if args.get('metadata'):
version_object.metadata = commit_sha
else:
version_object.release = commit_sha
new_version = version_object
except InvalidGitRepositoryError:
print("{} is not a valid git repo".format(file_dirname_path), file=stderr)
Expand Down
25 changes: 25 additions & 0 deletions test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,31 @@
dependencies = []
"""

# pyproject.toml with compound keys containing 'version' substring (issue #58)
# Should only match 'version = "2.0.0"' and NOT 'target-version' or other compound keys
valid_pyproject_toml_with_compound_keys = """
[project]
name = "test-project"
version = "2.0.0"
description = "A test project"

[tool.ruff]
target-version = "py312"

[tool.some]
this-is-a-version-containing-key = "1.1.7"
myversion = "should-not-match"
"""

# Content with version NOT at line start - should still match (inline in setup.py style)
valid_setup_py_inline_version = """
setuptools.setup(
name="pybump",
version="3.2.1",
author="Test",
)
"""

valid_version_file_1 = """0.12.4"""
valid_version_file_2 = """1.5.0-alpha+meta"""
invalid_version_file_1 = """
Expand Down
12 changes: 11 additions & 1 deletion test/test_pybump.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from . import valid_helm_chart, invalid_helm_chart, empty_helm_chart, \
valid_setup_py, invalid_setup_py_1, invalid_setup_py_multiple_ver, \
valid_version_file_1, valid_version_file_2, invalid_version_file_1, invalid_version_file_2, \
valid_pyproject_toml
valid_pyproject_toml, valid_pyproject_toml_with_compound_keys, valid_setup_py_inline_version


class PyBumpTest(unittest.TestCase):
Expand Down Expand Up @@ -178,6 +178,16 @@ def test_get_version_from_file(self):

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

# Test for issue #58: compound keys containing 'version' substring should not match
# File contains: version="2.0.0", target-version="py312", myversion="should-not-match"
# Only 'version' should be matched, not 'target-version' or 'myversion'
self.assertEqual(get_version_from_file(valid_pyproject_toml_with_compound_keys), '2.0.0',
msg="Should only match 'version', not compound keys like 'target-version'")

# Test that inline version (not at line start) still works
self.assertEqual(get_version_from_file(valid_setup_py_inline_version), '3.2.1',
msg="Should match version even when indented (setup.py style)")

def test_set_version_in_file(self):
# test the version replacement string, in a content
content_pre = 'some text before version="3.17.5", and some text after'
Expand Down
31 changes: 24 additions & 7 deletions test/test_simulate_pybump.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,27 @@ def simulate_get_version(file, app_version=False, sem_ver=False, release=False,
return run(["python", "src/pybump.py", "get", "--file", file], stdout=PIPE, stderr=PIPE)


def simulate_set_version(file, version='', app_version=False, auto=False):
def simulate_set_version(file, version='', app_version=False, auto=False, metadata=False):
"""
execute sub process to simulate real app execution,
set new version to a file
if auto is True, auto add git branch / hash
if metadata is True (with auto), set SHA as metadata (+sha) instead of release (-sha)
if app_version is True, then add the --app-version flag to execution
:param file: string
:param version: string
:param app_version: boolean
:param auto: boolean
:param metadata: boolean
:return: CompletedProcess object
"""
if auto:
cmd = ["python", "src/pybump.py", "set", "--file", file, "--auto"]
if metadata:
cmd.append("--metadata")
if app_version:
return run(["python", "src/pybump.py", "set", "--file", file, "--auto", "--app-version"],
stdout=PIPE, stderr=PIPE)
else:
return run(["python", "src/pybump.py", "set", "--file", file, "--auto"],
stdout=PIPE, stderr=PIPE)
cmd.append("--app-version")
return run(cmd, stdout=PIPE, stderr=PIPE)
else:
if app_version:
return run(["python", "src/pybump.py", "set", "--file", file, "--set-version", version, "--app-version"],
Expand Down Expand Up @@ -239,16 +241,31 @@ def test_get_flags(self):

def test_set_flags(self):
################################################
# simulate the 'set' command with version prefix
# simulate the 'set' command with --auto flag
################################################
# first set test_valid_setup.py a simple version
simulate_set_version("test/test_content_files/test_valid_setup.py", version="1.0.1")

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

########################################################
# simulate the 'set' command with --auto --metadata flag
########################################################
# reset to simple version
simulate_set_version("test/test_content_files/test_valid_setup.py", version="2.0.0")

# test --auto --metadata sets metadata (+sha) instead of release (-sha)
test_set_auto_metadata = simulate_set_version("test/test_content_files/test_valid_setup.py",
auto=True, metadata=True)
self.assertRegex(test_set_auto_metadata.stdout.decode('utf-8').strip(),
r'\b2.0.0\+[0-9a-f]{40}\b',
msg="test that '--auto --metadata' sets SHA as metadata (+sha), "
"output should match 2.0.0+<40-char-hex>")

# test invalid version set
test_set_auto = simulate_set_version("test/test_content_files/test_valid_setup.py", version='V123.x.4')
self.assertEqual('Invalid semantic version format: V123.x.4\nMake sure to comply with https://semver.org/ '
Expand Down
Loading