From 40d141bc32793ee7cb69ee388323be16cd7e0153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 31 Mar 2020 13:33:20 +0200 Subject: [PATCH 01/25] Add packaging as direct dependency We are going to depend on packaging for converting to a PEP 440 compliant version. --- poetry.lock | 6 +++--- pyproject.toml | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4d90d0726..5777aac1d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -316,7 +316,7 @@ python-versions = "*" version = "0.6.1" [[package]] -category = "dev" +category = "main" description = "Core utilities for Python packages" name = "packaging" optional = false @@ -402,7 +402,7 @@ docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)", "hypothesis (>=3.27.0)"] [[package]] -category = "dev" +category = "main" description = "Python parsing module" name = "pyparsing" optional = false @@ -616,7 +616,7 @@ python-versions = "*" version = "1.11.2" [metadata] -content-hash = "66e650c644117d6522c5b4fd9af627479cf59a1f637093917ba5b3b97d009c34" +content-hash = "57c862dd78fcf1765166dd32e7a910fdcf19a9996d347fbf8c677870144e3f30" python-versions = "^3.5.2" [metadata.files] diff --git a/pyproject.toml b/pyproject.toml index 2834486e5..f3f6d3dbf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ paramiko = "^2.7.1" lxml = "^4.5.0" defusedxml = "^0.6.0" toml = "^0.10.0" +packaging = "^20.3" [tool.poetry.dev-dependencies] From e5ffa9ecd08927ed76d74b8e5e1eeb853945f9b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 31 Mar 2020 13:34:50 +0200 Subject: [PATCH 02/25] Use a PEP 440 compliant version string --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f3f6d3dbf..8b0adc018 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "python-gvm" -version = "20.4dev1" +version = "20.4.dev1" description = "Library to communicate with remote servers over GMP or OSP" license = "GPL-3.0-or-later" authors = ["Greenbone Networks GmbH "] From e2f9a990638dc644c4a6f8b61fbb65dbc8f34dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 31 Mar 2020 13:35:56 +0200 Subject: [PATCH 03/25] Add own function to convert to a PEP 440 compliant version --- gvm/utils.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/gvm/utils.py b/gvm/utils.py index 9178e329e..971a6020c 100644 --- a/gvm/utils.py +++ b/gvm/utils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2018 - 2019 Greenbone Networks GmbH +# Copyright (C) 2018 - 2020 Greenbone Networks GmbH # # SPDX-License-Identifier: GPL-3.0-or-later # @@ -16,13 +16,31 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import re import warnings +from packaging.version import Version, InvalidVersion + def deprecation(message: str): warnings.warn(message, DeprecationWarning, stacklevel=2) +def safe_version(version: str) -> str: + """ + Returns the version as a string in `PEP440`_ compliant + format. + + .. _PEP440: + https://www.python.org/dev/peps/pep-0440 + """ + try: + return str(Version(version)) + except InvalidVersion: + version = version.replace(' ', '.') + return re.sub('[^A-Za-z0-9.]+', '-', version) + + def get_version_string(version: tuple) -> str: """Create a version string from a version tuple From 45e9e542e1beb14581cee9036158ccc8ed676eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 31 Mar 2020 13:37:21 +0200 Subject: [PATCH 04/25] Move get_version_from_pyproject_toml func to utils Move a slightly modified version of get_version_from_pyproject_toml function to the utils module. --- gvm/__init__.py | 20 +------------------- gvm/utils.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/gvm/__init__.py b/gvm/__init__.py index 10e0909be..f01c958fa 100644 --- a/gvm/__init__.py +++ b/gvm/__init__.py @@ -18,23 +18,6 @@ """ Main module of python-gvm. """ -from pathlib import Path - -from pkg_resources import safe_version - -import toml - - -def get_version_from_pyproject_toml() -> str: - path = Path(__file__) - pyproject_toml_path = path.parent.parent / 'pyproject.toml' - - if pyproject_toml_path.exists(): - pyproject_toml = toml.loads(pyproject_toml_path.read_text()) - if 'tool' in pyproject_toml and 'poetry' in pyproject_toml['tool']: - return pyproject_toml['tool']['poetry']['version'] - - raise RuntimeError('Version information not found in pyproject.toml file.') def get_version() -> str: @@ -47,5 +30,4 @@ def get_version() -> str: .. _PEP440: https://www.python.org/dev/peps/pep-0440 """ - str_version = get_version_from_pyproject_toml() - return safe_version(str_version) + return "" diff --git a/gvm/utils.py b/gvm/utils.py index 971a6020c..a9cfe9886 100644 --- a/gvm/utils.py +++ b/gvm/utils.py @@ -19,6 +19,10 @@ import re import warnings +from pathlib import Path + +import toml + from packaging.version import Version, InvalidVersion @@ -41,6 +45,24 @@ def safe_version(version: str) -> str: return re.sub('[^A-Za-z0-9.]+', '-', version) +def get_version_from_pyproject_toml() -> str: + """ + Return the version information from the [tool.poetry] section of the + pyproject.toml file. The version may be in non standardized form. + """ + path = Path(__file__) + pyproject_toml_path = path.parent.parent / 'pyproject.toml' + + if not pyproject_toml_path.exists(): + raise RuntimeError('pyproject.toml file not found.') + + pyproject_toml = toml.loads(pyproject_toml_path.read_text()) + if 'tool' in pyproject_toml and 'poetry' in pyproject_toml['tool']: + return pyproject_toml['tool']['poetry']['version'] + + raise RuntimeError('Version information not found in pyproject.toml file.') + + def get_version_string(version: tuple) -> str: """Create a version string from a version tuple From a7198d0118dd8b70edcd672f559350a773f56b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 31 Mar 2020 13:38:37 +0200 Subject: [PATCH 05/25] Hardcode version in __version__.py The pyproject.toml file is **NOT** contained in the distributed python package and therefore the version can't be loaded from the pyproject.toml file at all. As a result the version information must be set at two places 1. In the [tool.poetry] section of the pyproject.toml file 2. In a file within the installed and distributed python package --- gvm/__init__.py | 3 ++- gvm/__version__.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 gvm/__version__.py diff --git a/gvm/__init__.py b/gvm/__init__.py index f01c958fa..bc05e7075 100644 --- a/gvm/__init__.py +++ b/gvm/__init__.py @@ -18,6 +18,7 @@ """ Main module of python-gvm. """ +from .__version__ import __version__ def get_version() -> str: @@ -30,4 +31,4 @@ def get_version() -> str: .. _PEP440: https://www.python.org/dev/peps/pep-0440 """ - return "" + return __version__ diff --git a/gvm/__version__.py b/gvm/__version__.py new file mode 100644 index 000000000..d7e1cbaff --- /dev/null +++ b/gvm/__version__.py @@ -0,0 +1,5 @@ +# pylint: disable=invalid-name + +# THIS IS AN AUTOGENERATED FILE. DO NOT TOUCH! + +__version__ = "20.4.dev1" From ff3f100ca72d19c6293357f011191482fc35dd46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 31 Mar 2020 13:52:30 +0200 Subject: [PATCH 06/25] Add utility to handle version information Convert verify-version.py into a more general utility to handle the version information in python-gvm. --- .circleci/config.yml | 5 +- verify-version.py | 47 ---------------- version.py | 126 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 48 deletions(-) delete mode 100644 verify-version.py create mode 100644 version.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 80c022c3a..23e788648 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -61,6 +61,9 @@ commands: - run: name: Check with pylint command: poetry run pylint --disable=R gvm + - run: + name: Check version information + command: poetry run python version.py verify current deploy: description: "Upload package to PyPI" steps: @@ -70,7 +73,7 @@ commands: command: poetry install --no-dev - run: name: Verify tag version - command: poetry run python verify-version.py ${CIRCLE_TAG} + command: poetry run python version.py verify ${CIRCLE_TAG} - run: name: Install twine command: poetry run pip install twine diff --git a/verify-version.py b/verify-version.py deleted file mode 100644 index 392a3471e..000000000 --- a/verify-version.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2020 Greenbone Networks GmbH -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import sys - -from gvm import get_version - - -def strip_version(version: str) -> str: - if not version: - return version - - if version[0] == 'v': - return version[1:] - - -def main(): - if len(sys.argv) < 2: - sys.exit('Missing argument for version.') - return - - p_version = strip_version(sys.argv[1]) - version = get_version() - if p_version != version: - sys.exit( - "Provided version: {} does not match the python-gvm " - "version: {}".format(p_version, version) - ) - - -if __name__ == '__main__': - main() diff --git a/version.py b/version.py new file mode 100644 index 000000000..d1be4b3df --- /dev/null +++ b/version.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Greenbone Networks GmbH +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import argparse +import subprocess +import sys + +from pathlib import Path + +from gvm import get_version +from gvm.utils import safe_version, get_version_from_pyproject_toml + + +def strip_version(version: str) -> str: + if version and version[0] == 'v': + return version[1:] + + return version + + +def verfiy(args): + pyproject_version = get_version_from_pyproject_toml() + version = get_version() + + if version != safe_version(version): + sys.exit("The version in gvm/__version__.py is not PEP 440 compliant.") + + if pyproject_version != version: + sys.exit( + "The version set in the pyproject.toml file \"{}\" doesn't match " + " the python-gvm version \"{}\"".format(pyproject_version, version) + ) + + if args.version != 'current': + provided_version = strip_version(args.version) + if provided_version != version: + sys.exit( + "Provided version \"{}\" does not match the python-gvm " + "version \"{}\"".format(provided_version, version) + ) + + +def show(_args): + pyproject_version = safe_version(get_version_from_pyproject_toml()) + print(pyproject_version) + + +def update(args): + version = safe_version(args.version) + python_gvm_version = get_version() + pyproject_version = get_version_from_pyproject_toml() + + if not args.force and python_gvm_version == version: + print('Version is already up-to-date.') + sys.exit(0) + + try: + subprocess.check_call( + ['poetry', 'version', version], + timeout=120, # wait 2 min and don't wait forever + ) + except subprocess.SubprocessError as e: + sys.exit(e) + + version_file_path = Path('gvm/__version__.py') + text = """# pylint: disable=invalid-name + +# THIS IS AN AUTOGENERATED FILE. DO NOT TOUCH! + +__version__ = "{}" + """.format( + version + ) + version_file_path.write_text(text) + + print('Updated version from {} to {}'.format(pyproject_version, version)) + + sys.exit(0) + + +def main(): + parser = argparse.ArgumentParser(description='Version handling utilities.') + + subparsers = parser.add_subparsers( + title='subcommands', + description='valid subcommands', + help='additional help', + ) + + verify_parser = subparsers.add_parser('verify') + verify_parser.add_argument('version', help='version string to compare') + verify_parser.set_defaults(func=verfiy) + + show_parser = subparsers.add_parser('show') + show_parser.set_defaults(func=show) + + update_parser = subparsers.add_parser('update') + update_parser.add_argument('version', help='version string to use') + update_parser.add_argument( + '--force', + help="don't check if version is already set", + action="store_true", + ) + update_parser.set_defaults(func=update) + + args = parser.parse_args() + args.func(args) + + +if __name__ == '__main__': + main() From af691c1802013a9682dd8f40df1dd1057d21d142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 31 Mar 2020 14:07:07 +0200 Subject: [PATCH 07/25] Move version script into gvm package Allow to call the module via python -m --- .circleci/config.yml | 4 ++-- version.py => gvm/version.py | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) rename version.py => gvm/version.py (97%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 23e788648..f04f6cbd0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -63,7 +63,7 @@ commands: command: poetry run pylint --disable=R gvm - run: name: Check version information - command: poetry run python version.py verify current + command: poetry run python -m gvm.version verify current deploy: description: "Upload package to PyPI" steps: @@ -73,7 +73,7 @@ commands: command: poetry install --no-dev - run: name: Verify tag version - command: poetry run python version.py verify ${CIRCLE_TAG} + command: poetry run python -m gvm.version verify ${CIRCLE_TAG} - run: name: Install twine command: poetry run pip install twine diff --git a/version.py b/gvm/version.py similarity index 97% rename from version.py rename to gvm/version.py index d1be4b3df..2d05f5a41 100644 --- a/version.py +++ b/gvm/version.py @@ -54,6 +54,8 @@ def verfiy(args): "version \"{}\"".format(provided_version, version) ) + print('OK') + def show(_args): pyproject_version = safe_version(get_version_from_pyproject_toml()) @@ -119,6 +121,11 @@ def main(): update_parser.set_defaults(func=update) args = parser.parse_args() + + if not hasattr(args, 'func'): + parser.print_usage() + sys.exit(0) + args.func(args) From 5b5d04b38b52efe1686952a37b82496266255d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 31 Mar 2020 14:58:13 +0200 Subject: [PATCH 08/25] Move version functions from utils to version module These function as version relates thus they should be in the new version module. --- gvm/utils.py | 62 -------------------------------------------------- gvm/version.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 63 deletions(-) diff --git a/gvm/utils.py b/gvm/utils.py index a9cfe9886..11f530a96 100644 --- a/gvm/utils.py +++ b/gvm/utils.py @@ -16,70 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re import warnings -from pathlib import Path - -import toml - -from packaging.version import Version, InvalidVersion - def deprecation(message: str): warnings.warn(message, DeprecationWarning, stacklevel=2) - - -def safe_version(version: str) -> str: - """ - Returns the version as a string in `PEP440`_ compliant - format. - - .. _PEP440: - https://www.python.org/dev/peps/pep-0440 - """ - try: - return str(Version(version)) - except InvalidVersion: - version = version.replace(' ', '.') - return re.sub('[^A-Za-z0-9.]+', '-', version) - - -def get_version_from_pyproject_toml() -> str: - """ - Return the version information from the [tool.poetry] section of the - pyproject.toml file. The version may be in non standardized form. - """ - path = Path(__file__) - pyproject_toml_path = path.parent.parent / 'pyproject.toml' - - if not pyproject_toml_path.exists(): - raise RuntimeError('pyproject.toml file not found.') - - pyproject_toml = toml.loads(pyproject_toml_path.read_text()) - if 'tool' in pyproject_toml and 'poetry' in pyproject_toml['tool']: - return pyproject_toml['tool']['poetry']['version'] - - raise RuntimeError('Version information not found in pyproject.toml file.') - - -def get_version_string(version: tuple) -> str: - """Create a version string from a version tuple - - Arguments: - version: version as tuple e.g. (1, 2, 0, dev, 5) - - Returns: - The version tuple converted into a string representation - """ - if len(version) > 4: - ver = ".".join(str(x) for x in version[:4]) - ver += str(version[4]) - - if len(version) > 5: - # support (1, 2, 3, 'beta', 2, 'dev', 1) - ver += ".{0}{1}".format(str(version[5]), str(version[6])) - - return ver - else: - return ".".join(str(x) for x in version) diff --git a/gvm/version.py b/gvm/version.py index 2d05f5a41..3ee6e5325 100644 --- a/gvm/version.py +++ b/gvm/version.py @@ -17,13 +17,18 @@ # along with this program. If not, see . import argparse +import re import subprocess import sys from pathlib import Path +import toml + +from packaging.version import Version, InvalidVersion + + from gvm import get_version -from gvm.utils import safe_version, get_version_from_pyproject_toml def strip_version(version: str) -> str: @@ -33,6 +38,61 @@ def strip_version(version: str) -> str: return version +def safe_version(version: str) -> str: + """ + Returns the version as a string in `PEP440`_ compliant + format. + + .. _PEP440: + https://www.python.org/dev/peps/pep-0440 + """ + try: + return str(Version(version)) + except InvalidVersion: + version = version.replace(' ', '.') + return re.sub('[^A-Za-z0-9.]+', '-', version) + + +def get_version_from_pyproject_toml() -> str: + """ + Return the version information from the [tool.poetry] section of the + pyproject.toml file. The version may be in non standardized form. + """ + path = Path(__file__) + pyproject_toml_path = path.parent.parent / 'pyproject.toml' + + if not pyproject_toml_path.exists(): + raise RuntimeError('pyproject.toml file not found.') + + pyproject_toml = toml.loads(pyproject_toml_path.read_text()) + if 'tool' in pyproject_toml and 'poetry' in pyproject_toml['tool']: + return pyproject_toml['tool']['poetry']['version'] + + raise RuntimeError('Version information not found in pyproject.toml file.') + + +def get_version_string(version: tuple) -> str: + """Create a version string from a version tuple + + Arguments: + version: version as tuple e.g. (1, 2, 0, dev, 5) + + Returns: + The version tuple converted into a string representation + """ + if len(version) > 4: + ver = ".".join(str(x) for x in version[:4]) + ver += str(version[4]) + + if len(version) > 5: + # support (1, 2, 3, 'beta', 2, 'dev', 1) + ver += ".{0}{1}".format(str(version[5]), str(version[6])) + + return ver + else: + return ".".join(str(x) for x in version) + + def verfiy(args): pyproject_version = get_version_from_pyproject_toml() version = get_version() From 9290ae042921ef46f8576c2604de145795a3bef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 11:53:51 +0200 Subject: [PATCH 09/25] Refactor version checks into several function Allow to reuse some of the code in external libraries and tools. --- gvm/version.py | 171 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 123 insertions(+), 48 deletions(-) diff --git a/gvm/version.py b/gvm/version.py index 3ee6e5325..ed7dff4a5 100644 --- a/gvm/version.py +++ b/gvm/version.py @@ -22,6 +22,7 @@ import sys from pathlib import Path +from typing import Union import toml @@ -53,13 +54,14 @@ def safe_version(version: str) -> str: return re.sub('[^A-Za-z0-9.]+', '-', version) -def get_version_from_pyproject_toml() -> str: +def get_version_from_pyproject_toml(pyproject_toml_path: Path = None) -> str: """ Return the version information from the [tool.poetry] section of the pyproject.toml file. The version may be in non standardized form. """ - path = Path(__file__) - pyproject_toml_path = path.parent.parent / 'pyproject.toml' + if not pyproject_toml_path: + path = Path(__file__) + pyproject_toml_path = path.parent.parent / 'pyproject.toml' if not pyproject_toml_path.exists(): raise RuntimeError('pyproject.toml file not found.') @@ -93,53 +95,50 @@ def get_version_string(version: tuple) -> str: return ".".join(str(x) for x in version) -def verfiy(args): - pyproject_version = get_version_from_pyproject_toml() - version = get_version() +def print_version(pyproject_toml_path: Path = None) -> None: + pyproject_version = get_version_from_pyproject_toml( + pyproject_toml_path=pyproject_toml_path + ) - if version != safe_version(version): - sys.exit("The version in gvm/__version__.py is not PEP 440 compliant.") + print(pyproject_version) - if pyproject_version != version: - sys.exit( - "The version set in the pyproject.toml file \"{}\" doesn't match " - " the python-gvm version \"{}\"".format(pyproject_version, version) - ) - if args.version != 'current': - provided_version = strip_version(args.version) - if provided_version != version: - sys.exit( - "Provided version \"{}\" does not match the python-gvm " - "version \"{}\"".format(provided_version, version) - ) +def check_version_equal(new_version: str, old_version: str) -> bool: + """ + Checks if new_version and old_version equal + """ + return safe_version(old_version) == safe_version(new_version) - print('OK') +def check_version_is_pep440_compliant(version: str) -> bool: + """ + Checks if the provided version is a PEP 440 compliant version string + """ + return version == safe_version(version) -def show(_args): - pyproject_version = safe_version(get_version_from_pyproject_toml()) - print(pyproject_version) +def update_pyproject_version( + new_version: str, cwd: Union[Path, str] = None, +) -> None: + """ + Update the version in the pyproject.toml file + """ + version = safe_version(new_version) -def update(args): - version = safe_version(args.version) - python_gvm_version = get_version() - pyproject_version = get_version_from_pyproject_toml() + subprocess.check_call( + ['poetry', 'version', version], + stdout=subprocess.DEVNULL, + timeout=120, # wait 2 min and don't wait forever + cwd=str(cwd), + ) - if not args.force and python_gvm_version == version: - print('Version is already up-to-date.') - sys.exit(0) - try: - subprocess.check_call( - ['poetry', 'version', version], - timeout=120, # wait 2 min and don't wait forever - ) - except subprocess.SubprocessError as e: - sys.exit(e) +def update_version_file(new_version: str, version_file_path: Path) -> None: + """ + Update the version file with the new version + """ + version = safe_version(new_version) - version_file_path = Path('gvm/__version__.py') text = """# pylint: disable=invalid-name # THIS IS AN AUTOGENERATED FILE. DO NOT TOUCH! @@ -150,26 +149,92 @@ def update(args): ) version_file_path.write_text(text) - print('Updated version from {} to {}'.format(pyproject_version, version)) - sys.exit(0) +def _update_python_gvm_version( + new_version: str, pyproject_toml_path: Path, *, force: bool = False +): + if not pyproject_toml_path.exists(): + sys.exit( + 'Could not find pyproject.toml file in the current working dir.' + ) + + cwd_path = Path.cwd() + python_gvm_version = get_version() + pyproject_version = get_version_from_pyproject_toml( + pyproject_toml_path=pyproject_toml_path + ) + version_file_path = cwd_path / 'gvm' / '__version__.py' + + if not pyproject_toml_path.exists(): + sys.exit( + 'Could not find __version__.py file at {}.'.format( + version_file_path + ) + ) + + if not force and not check_version_equal(new_version, python_gvm_version): + print('Version is already up-to-date.') + sys.exit(0) + + try: + update_pyproject_version(new_version=new_version, cwd=cwd_path) + except subprocess.SubprocessError as e: + sys.exit(e) + + update_version_file( + new_version=new_version, version_file_path=version_file_path, + ) + + print( + 'Updated version from {} to {}'.format( + pyproject_version, safe_version(new_version) + ) + ) + + +def _verify_version(version: str, pyproject_toml_path: Path) -> None: + python_gvm_version = get_version() + pyproject_version = get_version_from_pyproject_toml( + pyproject_toml_path=pyproject_toml_path + ) + if not check_version_is_pep440_compliant(python_gvm_version): + sys.exit("The version in gvm/__version__.py is not PEP 440 compliant.") + + if pyproject_version != python_gvm_version: + sys.exit( + "The version set in the pyproject.toml file \"{}\" doesn't " + "match the python-gvm version \"{}\"".format( + pyproject_version, python_gvm_version + ) + ) + + if version != 'current': + provided_version = strip_version(version) + if provided_version != python_gvm_version: + sys.exit( + "Provided version \"{}\" does not match the python-gvm " + "version \"{}\"".format(provided_version, python_gvm_version) + ) + + print('OK') def main(): - parser = argparse.ArgumentParser(description='Version handling utilities.') + parser = argparse.ArgumentParser( + description='Version handling utilities for python-gvm.', prog='version' + ) subparsers = parser.add_subparsers( title='subcommands', description='valid subcommands', help='additional help', + dest='command', ) verify_parser = subparsers.add_parser('verify') verify_parser.add_argument('version', help='version string to compare') - verify_parser.set_defaults(func=verfiy) - show_parser = subparsers.add_parser('show') - show_parser.set_defaults(func=show) + subparsers.add_parser('show') update_parser = subparsers.add_parser('update') update_parser.add_argument('version', help='version string to use') @@ -178,15 +243,25 @@ def main(): help="don't check if version is already set", action="store_true", ) - update_parser.set_defaults(func=update) args = parser.parse_args() - if not hasattr(args, 'func'): + if not getattr(args, 'command', None): parser.print_usage() sys.exit(0) - args.func(args) + pyproject_toml_path = Path.cwd() / 'pyproject.toml' + + if args.command == 'update': + _update_python_gvm_version( + args.version, + pyproject_toml_path=pyproject_toml_path, + force=args.force, + ) + elif args.command == 'show': + print_version(pyproject_toml_path=pyproject_toml_path) + elif args.command == 'verify': + _verify_version(args.version, pyproject_toml_path=pyproject_toml_path) if __name__ == '__main__': From 3ed3a9b142161a98d1677a618065be145cefb39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 11:56:16 +0200 Subject: [PATCH 10/25] Update and move get_version_string test module --- tests/version/__init__.py | 0 tests/{utils => version}/test_get_version_string.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/version/__init__.py rename tests/{utils => version}/test_get_version_string.py (97%) diff --git a/tests/version/__init__.py b/tests/version/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/utils/test_get_version_string.py b/tests/version/test_get_version_string.py similarity index 97% rename from tests/utils/test_get_version_string.py rename to tests/version/test_get_version_string.py index ba7f77f6c..444372d35 100644 --- a/tests/utils/test_get_version_string.py +++ b/tests/version/test_get_version_string.py @@ -18,7 +18,7 @@ import unittest -from gvm.utils import get_version_string +from gvm.version import get_version_string class TestGetVersionString(unittest.TestCase): From c4a6284f402acbd31eb89d8ea7826d98bb1f8c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 12:04:57 +0200 Subject: [PATCH 11/25] Add docstring for strip_version function --- gvm/version.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gvm/version.py b/gvm/version.py index ed7dff4a5..042ceeeda 100644 --- a/gvm/version.py +++ b/gvm/version.py @@ -33,6 +33,11 @@ def strip_version(version: str) -> str: + """ + Strips a leading 'v' from a version string + + E.g. v1.2.3 will be converted to 1.2.3 + """ if version and version[0] == 'v': return version[1:] From 1916abf9776c57bb66863e1acd0fa71e141ff41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 12:05:24 +0200 Subject: [PATCH 12/25] Add testcase for strip_version function --- tests/version/test_strip_version.py | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/version/test_strip_version.py diff --git a/tests/version/test_strip_version.py b/tests/version/test_strip_version.py new file mode 100644 index 000000000..e50dcbf44 --- /dev/null +++ b/tests/version/test_strip_version.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Greenbone Networks GmbH +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from gvm.version import strip_version + + +class StripVersionTestCase(unittest.TestCase): + def test_version_string_without_v(self): + self.assertEqual(strip_version('1.2.3'), '1.2.3') + self.assertEqual(strip_version('1.2.3dev'), '1.2.3dev') + + def test_version_string_with_v(self): + self.assertEqual(strip_version('v1.2.3'), '1.2.3') + self.assertEqual(strip_version('v1.2.3dev'), '1.2.3dev') From 66619e51032e9b57ce3daf9342d9e682c193ac9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 12:25:06 +0200 Subject: [PATCH 13/25] Add testcase for safe_version function --- tests/version/test_safe_version.py | 55 ++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/version/test_safe_version.py diff --git a/tests/version/test_safe_version.py b/tests/version/test_safe_version.py new file mode 100644 index 000000000..24ed456da --- /dev/null +++ b/tests/version/test_safe_version.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Greenbone Networks GmbH +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from gvm.version import safe_version + + +class SafeVersionTestCase(unittest.TestCase): + def test_dev_versions(self): + self.assertEqual(safe_version('1.2.3dev'), '1.2.3.dev0') + self.assertEqual(safe_version('1.2.3dev1'), '1.2.3.dev1') + self.assertEqual(safe_version('1.2.3.dev'), '1.2.3.dev0') + + def test_alpha_versions(self): + self.assertEqual(safe_version('1.2.3alpha'), '1.2.3a0') + self.assertEqual(safe_version('1.2.3.alpha'), '1.2.3a0') + self.assertEqual(safe_version('1.2.3a'), '1.2.3a0') + self.assertEqual(safe_version('1.2.3.a1'), '1.2.3a1') + self.assertEqual(safe_version('1.2.3a1'), '1.2.3a1') + + def test_beta_versions(self): + self.assertEqual(safe_version('1.2.3beta'), '1.2.3b0') + self.assertEqual(safe_version('1.2.3.beta'), '1.2.3b0') + self.assertEqual(safe_version('1.2.3b'), '1.2.3b0') + self.assertEqual(safe_version('1.2.3.b1'), '1.2.3b1') + self.assertEqual(safe_version('1.2.3b1'), '1.2.3b1') + + def test_caldav_versions(self): + self.assertEqual(safe_version('22.04'), '22.4') + self.assertEqual(safe_version('22.4'), '22.4') + self.assertEqual(safe_version('22.10'), '22.10') + self.assertEqual(safe_version('22.04dev1'), '22.4.dev1') + self.assertEqual(safe_version('22.10dev1'), '22.10.dev1') + + def test_release_versions(self): + self.assertEqual(safe_version('1'), '1') + self.assertEqual(safe_version('1.2'), '1.2') + self.assertEqual(safe_version('1.2.3'), '1.2.3') + self.assertEqual(safe_version('22.4'), '22.4') From bfc79d1805a41bcf79208cb8498a0d4ed46ea890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 12:44:08 +0200 Subject: [PATCH 14/25] Add testcase for get_version_from_pyproject_toml Also don't crash if version is not contained in [tool.poetry] section. --- gvm/version.py | 6 +- .../test_get_version_from_pyproject_toml.py | 79 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 tests/version/test_get_version_from_pyproject_toml.py diff --git a/gvm/version.py b/gvm/version.py index 042ceeeda..fc8d520d8 100644 --- a/gvm/version.py +++ b/gvm/version.py @@ -72,7 +72,11 @@ def get_version_from_pyproject_toml(pyproject_toml_path: Path = None) -> str: raise RuntimeError('pyproject.toml file not found.') pyproject_toml = toml.loads(pyproject_toml_path.read_text()) - if 'tool' in pyproject_toml and 'poetry' in pyproject_toml['tool']: + if ( + 'tool' in pyproject_toml + and 'poetry' in pyproject_toml['tool'] + and 'version' in pyproject_toml['tool']['poetry'] + ): return pyproject_toml['tool']['poetry']['version'] raise RuntimeError('Version information not found in pyproject.toml file.') diff --git a/tests/version/test_get_version_from_pyproject_toml.py b/tests/version/test_get_version_from_pyproject_toml.py new file mode 100644 index 000000000..917be3807 --- /dev/null +++ b/tests/version/test_get_version_from_pyproject_toml.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Greenbone Networks GmbH +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from pathlib import Path +from unittest.mock import MagicMock + +from gvm.version import get_version_from_pyproject_toml + + +class GetVersionFromPyprojectTomlTestCase(unittest.TestCase): + def test_pyproject_toml_file_not_exists(self): + fake_path_class = MagicMock(spec=Path) + fake_path = fake_path_class.return_value + fake_path.exists.return_value = False + + with self.assertRaisesRegex( + RuntimeError, 'pyproject.toml file not found' + ): + get_version_from_pyproject_toml(fake_path) + + fake_path.exists.assert_called_with() + + def test_no_poerty_section(self): + fake_path_class = MagicMock(spec=Path) + fake_path = fake_path_class.return_value + fake_path.exists.return_value = True + fake_path.read_text.return_value = '' + + with self.assertRaisesRegex( + RuntimeError, 'Version information not found in pyproject.toml file' + ): + get_version_from_pyproject_toml(fake_path) + + fake_path.exists.assert_called_with() + fake_path.read_text.assert_called_with() + + def test_empty_poerty_section(self): + fake_path_class = MagicMock(spec=Path) + fake_path = fake_path_class.return_value + fake_path.exists.return_value = True + fake_path.read_text.return_value = '[tool.poetry]' + + with self.assertRaisesRegex( + RuntimeError, 'Version information not found in pyproject.toml file' + ): + get_version_from_pyproject_toml(fake_path) + + fake_path.exists.assert_called_with() + fake_path.read_text.assert_called_with() + + def test_get_version(self): + fake_path_class = MagicMock(spec=Path) + fake_path = fake_path_class.return_value + fake_path.exists.return_value = True + fake_path.read_text.return_value = '[tool.poetry]\nversion = "1.2.3"' + + version = get_version_from_pyproject_toml(fake_path) + + self.assertEqual(version, '1.2.3') + + fake_path.exists.assert_called_with() + fake_path.read_text.assert_called_with() From 4fe7372362ebb583b0cce18528859a2b2b77f2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 12:51:39 +0200 Subject: [PATCH 15/25] Add testcase for check_version_equal function --- tests/version/test_check_version_equal.py | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/version/test_check_version_equal.py diff --git a/tests/version/test_check_version_equal.py b/tests/version/test_check_version_equal.py new file mode 100644 index 000000000..70be83686 --- /dev/null +++ b/tests/version/test_check_version_equal.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Greenbone Networks GmbH +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from gvm.version import check_version_equal + + +class CheckVersionEqualTestCase(unittest.TestCase): + def test_version_equal(self): + self.assertTrue(check_version_equal('1.2.3', '1.2.3')) + self.assertTrue(check_version_equal('1.2.3a', '1.2.3a0')) + self.assertTrue(check_version_equal('1.2.3a0', '1.2.3.a0')) + self.assertTrue(check_version_equal('1.2.3dev1', '1.2.3.dev1')) + + def test_version_not_equal(self): + self.assertFalse(check_version_equal('1.2.3', '1.2')) + self.assertFalse(check_version_equal('1.2.3a', '1.2.3a1')) + self.assertFalse(check_version_equal('1.2.3a0', '1.2.3.a1')) + self.assertFalse(check_version_equal('1.2.3dev', '1.2.3dev1')) + self.assertFalse(check_version_equal('1.2.3dev', '1.2.3.dev1')) From 362cc09c53a5c7d0f3b10c49d54b349fe2780997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 12:59:40 +0200 Subject: [PATCH 16/25] Rename function and add testcase Rename check_version_is_pep440_compliant to is_version_pep440_compliant and add a testcase. --- gvm/version.py | 4 +- .../test_is_version_pep440_compliant.py | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 tests/version/test_is_version_pep440_compliant.py diff --git a/gvm/version.py b/gvm/version.py index fc8d520d8..a9e6ccfd2 100644 --- a/gvm/version.py +++ b/gvm/version.py @@ -119,7 +119,7 @@ def check_version_equal(new_version: str, old_version: str) -> bool: return safe_version(old_version) == safe_version(new_version) -def check_version_is_pep440_compliant(version: str) -> bool: +def is_version_pep440_compliant(version: str) -> bool: """ Checks if the provided version is a PEP 440 compliant version string """ @@ -206,7 +206,7 @@ def _verify_version(version: str, pyproject_toml_path: Path) -> None: pyproject_version = get_version_from_pyproject_toml( pyproject_toml_path=pyproject_toml_path ) - if not check_version_is_pep440_compliant(python_gvm_version): + if not is_version_pep440_compliant(python_gvm_version): sys.exit("The version in gvm/__version__.py is not PEP 440 compliant.") if pyproject_version != python_gvm_version: diff --git a/tests/version/test_is_version_pep440_compliant.py b/tests/version/test_is_version_pep440_compliant.py new file mode 100644 index 000000000..908b9b239 --- /dev/null +++ b/tests/version/test_is_version_pep440_compliant.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Greenbone Networks GmbH +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from gvm.version import is_version_pep440_compliant + + +class IsVersionPep440CompliantTestCase(unittest.TestCase): + def test_is_compliant(self): + self.assertTrue(is_version_pep440_compliant('1.2.3.dev1')) + self.assertTrue(is_version_pep440_compliant('1.2.3.dev0')) + self.assertTrue(is_version_pep440_compliant('20.4')) + self.assertTrue(is_version_pep440_compliant('1.2')) + self.assertTrue(is_version_pep440_compliant('1.2.0a0')) + self.assertTrue(is_version_pep440_compliant('1.2.0a1')) + self.assertTrue(is_version_pep440_compliant('1.2.0b0')) + self.assertTrue(is_version_pep440_compliant('1.2.0b1')) + + def test_is_not_compliant(self): + self.assertFalse(is_version_pep440_compliant('1.2.3dev1')) + self.assertFalse(is_version_pep440_compliant('1.2.3dev')) + self.assertFalse(is_version_pep440_compliant('1.2.3dev0')) + self.assertFalse(is_version_pep440_compliant('1.2.3alpha')) + self.assertFalse(is_version_pep440_compliant('1.2.3alpha0')) + self.assertFalse(is_version_pep440_compliant('1.2.3.a0')) + self.assertFalse(is_version_pep440_compliant('1.2.3beta')) + self.assertFalse(is_version_pep440_compliant('1.2.3beta0')) + self.assertFalse(is_version_pep440_compliant('1.2.3.b0')) + self.assertFalse(is_version_pep440_compliant('20.04')) From 2a61101f3bf037650ca77001907ac8d49fd79ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 13:19:19 +0200 Subject: [PATCH 17/25] Add testcase for update_version_file function --- tests/version/test_update_version_file.py | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/version/test_update_version_file.py diff --git a/tests/version/test_update_version_file.py b/tests/version/test_update_version_file.py new file mode 100644 index 000000000..a1ba359b5 --- /dev/null +++ b/tests/version/test_update_version_file.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Greenbone Networks GmbH +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from pathlib import Path +from unittest.mock import MagicMock + +from gvm.version import update_version_file + + +class UpdateVersionFileTestCase(unittest.TestCase): + def test_update_version_file(self): + fake_path_class = MagicMock(spec=Path) + fake_path = fake_path_class.return_value + + update_version_file('22.04dev1', fake_path) + + text = fake_path.write_text.call_args[0][0] + + *_, version_line, _last_line = text.split('\n') + + self.assertEqual(version_line, '__version__ = "22.4.dev1"') From 86750e5f26645fe77dfa64f260e7867a03980f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 15:07:17 +0200 Subject: [PATCH 18/25] Remove extra spaces Don't add extra spaces at the end of the generated __version__.py file. --- gvm/version.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gvm/version.py b/gvm/version.py index a9e6ccfd2..b37e61041 100644 --- a/gvm/version.py +++ b/gvm/version.py @@ -152,8 +152,7 @@ def update_version_file(new_version: str, version_file_path: Path) -> None: # THIS IS AN AUTOGENERATED FILE. DO NOT TOUCH! -__version__ = "{}" - """.format( +__version__ = "{}"\n""".format( version ) version_file_path.write_text(text) From 0c88830639e1e37274d3e936a5803b218fe2ea51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 15:08:15 +0200 Subject: [PATCH 19/25] Add an additional test for different dev versions --- tests/version/test_check_version_equal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/version/test_check_version_equal.py b/tests/version/test_check_version_equal.py index 70be83686..5e96bd386 100644 --- a/tests/version/test_check_version_equal.py +++ b/tests/version/test_check_version_equal.py @@ -34,3 +34,4 @@ def test_version_not_equal(self): self.assertFalse(check_version_equal('1.2.3a0', '1.2.3.a1')) self.assertFalse(check_version_equal('1.2.3dev', '1.2.3dev1')) self.assertFalse(check_version_equal('1.2.3dev', '1.2.3.dev1')) + self.assertFalse(check_version_equal('1.2.3.dev1', '1.2.3.dev2')) From 75a11ed482b70b5ffceaac939294ebaad2d7fe58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 15:32:28 +0200 Subject: [PATCH 20/25] Replace toml with tomlkit tomlkit allos to preserver the current structure and layout when saving the toml file. --- gvm/version.py | 4 ++-- poetry.lock | 16 ++++++++++++++-- pyproject.toml | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/gvm/version.py b/gvm/version.py index b37e61041..9931f8a8e 100644 --- a/gvm/version.py +++ b/gvm/version.py @@ -24,7 +24,7 @@ from pathlib import Path from typing import Union -import toml +import tomlkit from packaging.version import Version, InvalidVersion @@ -71,7 +71,7 @@ def get_version_from_pyproject_toml(pyproject_toml_path: Path = None) -> str: if not pyproject_toml_path.exists(): raise RuntimeError('pyproject.toml file not found.') - pyproject_toml = toml.loads(pyproject_toml_path.read_text()) + pyproject_toml = tomlkit.parse(pyproject_toml_path.read_text()) if ( 'tool' in pyproject_toml and 'poetry' in pyproject_toml['tool'] diff --git a/poetry.lock b/poetry.lock index 5777aac1d..956d6e1d8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -578,13 +578,21 @@ lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] -category = "main" +category = "dev" description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" optional = false python-versions = "*" version = "0.10.0" +[[package]] +category = "main" +description = "Style preserving TOML library" +name = "tomlkit" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.5.11" + [[package]] category = "dev" description = "a fork of Python 2 and 3 ast modules with type comment support" @@ -616,7 +624,7 @@ python-versions = "*" version = "1.11.2" [metadata] -content-hash = "57c862dd78fcf1765166dd32e7a910fdcf19a9996d347fbf8c677870144e3f30" +content-hash = "a846b9a757bcafd97142a1db5667a2bd89e340c557b652952cb40147732a0e20" python-versions = "^3.5.2" [metadata.files] @@ -1024,6 +1032,10 @@ toml = [ {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, ] +tomlkit = [ + {file = "tomlkit-0.5.11-py2.py3-none-any.whl", hash = "sha256:4e1bd6c9197d984528f9ff0cc9db667c317d8881288db50db20eeeb0f6b0380b"}, + {file = "tomlkit-0.5.11.tar.gz", hash = "sha256:f044eda25647882e5ef22b43a1688fb6ab12af2fc50e8456cdfc751c873101cf"}, +] typed-ast = [ {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, diff --git a/pyproject.toml b/pyproject.toml index 8b0adc018..b01089bb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,8 +35,8 @@ python = "^3.5.2" paramiko = "^2.7.1" lxml = "^4.5.0" defusedxml = "^0.6.0" -toml = "^0.10.0" packaging = "^20.3" +tomlkit = "^0.5.11" [tool.poetry.dev-dependencies] From 88660349e1d352e4c0cc485acb5c7de2c3364846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 15:34:49 +0200 Subject: [PATCH 21/25] Don't call poetry for a version update Update the pyproject.toml version by reading and writing the file directly instread of calling poetry update. --- gvm/version.py | 30 ++++--- .../version/test_update_pyproject_version.py | 79 +++++++++++++++++++ 2 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 tests/version/test_update_pyproject_version.py diff --git a/gvm/version.py b/gvm/version.py index 9931f8a8e..65705ac43 100644 --- a/gvm/version.py +++ b/gvm/version.py @@ -18,11 +18,9 @@ import argparse import re -import subprocess import sys from pathlib import Path -from typing import Union import tomlkit @@ -127,19 +125,26 @@ def is_version_pep440_compliant(version: str) -> bool: def update_pyproject_version( - new_version: str, cwd: Union[Path, str] = None, + new_version: str, pyproject_toml_path: Path, ) -> None: """ Update the version in the pyproject.toml file """ version = safe_version(new_version) - subprocess.check_call( - ['poetry', 'version', version], - stdout=subprocess.DEVNULL, - timeout=120, # wait 2 min and don't wait forever - cwd=str(cwd), - ) + pyproject_toml = tomlkit.parse(pyproject_toml_path.read_text()) + + if 'tool' not in pyproject_toml: + tool_table = tomlkit.table() + pyproject_toml['tool'] = tool_table + + if 'poetry' not in pyproject_toml['tool']: + poetry_table = tomlkit.table() + pyproject_toml['tool'].add('poetry', poetry_table) + + pyproject_toml['tool']['poetry']['version'] = version + + pyproject_toml_path.write_text(tomlkit.dumps(pyproject_toml)) def update_version_file(new_version: str, version_file_path: Path) -> None: @@ -184,10 +189,9 @@ def _update_python_gvm_version( print('Version is already up-to-date.') sys.exit(0) - try: - update_pyproject_version(new_version=new_version, cwd=cwd_path) - except subprocess.SubprocessError as e: - sys.exit(e) + update_pyproject_version( + new_version=new_version, pyproject_toml_path=pyproject_toml_path + ) update_version_file( new_version=new_version, version_file_path=version_file_path, diff --git a/tests/version/test_update_pyproject_version.py b/tests/version/test_update_pyproject_version.py new file mode 100644 index 000000000..2144d3e8e --- /dev/null +++ b/tests/version/test_update_pyproject_version.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2020 Greenbone Networks GmbH +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from pathlib import Path +from unittest.mock import MagicMock + +import tomlkit +from gvm.version import update_pyproject_version + + +class UpdatePyprojectVersionTestCase(unittest.TestCase): + def test_empty_pyproject_toml(self): + fake_path_class = MagicMock(spec=Path) + fake_path = fake_path_class.return_value + fake_path.read_text.return_value = "" + + update_pyproject_version('20.04dev1', pyproject_toml_path=fake_path) + + text = fake_path.write_text.call_args[0][0] + + toml = tomlkit.parse(text) + + self.assertEqual(toml['tool']['poetry']['version'], '20.4.dev1') + + def test_empty_tool_section(self): + fake_path_class = MagicMock(spec=Path) + fake_path = fake_path_class.return_value + fake_path.read_text.return_value = "[tool]" + + update_pyproject_version('20.04dev1', pyproject_toml_path=fake_path) + + text = fake_path.write_text.call_args[0][0] + + toml = tomlkit.parse(text) + + self.assertEqual(toml['tool']['poetry']['version'], '20.4.dev1') + + def test_empty_tool_poetry_section(self): + fake_path_class = MagicMock(spec=Path) + fake_path = fake_path_class.return_value + fake_path.read_text.return_value = "[tool.poetry]" + + update_pyproject_version('20.04dev1', pyproject_toml_path=fake_path) + + text = fake_path.write_text.call_args[0][0] + + toml = tomlkit.parse(text) + + self.assertEqual(toml['tool']['poetry']['version'], '20.4.dev1') + + def test_override_existing_version(self): + fake_path_class = MagicMock(spec=Path) + fake_path = fake_path_class.return_value + fake_path.read_text.return_value = '[tool.poetry]\nversion = "1.2.3"' + + update_pyproject_version('20.04dev1', pyproject_toml_path=fake_path) + + text = fake_path.write_text.call_args[0][0] + + toml = tomlkit.parse(text) + + self.assertEqual(toml['tool']['poetry']['version'], '20.4.dev1') From 043331d958a2c565d218b9002bf09a8c7037c01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 15:46:57 +0200 Subject: [PATCH 22/25] Fix checking for equal version when updating the version --- gvm/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gvm/version.py b/gvm/version.py index 65705ac43..22ea98342 100644 --- a/gvm/version.py +++ b/gvm/version.py @@ -185,7 +185,7 @@ def _update_python_gvm_version( ) ) - if not force and not check_version_equal(new_version, python_gvm_version): + if not force and check_version_equal(new_version, python_gvm_version): print('Version is already up-to-date.') sys.exit(0) From df23dea86c731d57083a0718d123df506ec75508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 15:52:12 +0200 Subject: [PATCH 23/25] Rename check_version_equal to versions_equal --- gvm/version.py | 6 ++--- ...ersion_equal.py => test_versions_equal.py} | 24 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) rename tests/version/{test_check_version_equal.py => test_versions_equal.py} (52%) diff --git a/gvm/version.py b/gvm/version.py index 22ea98342..0f8d052e6 100644 --- a/gvm/version.py +++ b/gvm/version.py @@ -110,9 +110,9 @@ def print_version(pyproject_toml_path: Path = None) -> None: print(pyproject_version) -def check_version_equal(new_version: str, old_version: str) -> bool: +def versions_equal(new_version: str, old_version: str) -> bool: """ - Checks if new_version and old_version equal + Checks if new_version and old_version are equal """ return safe_version(old_version) == safe_version(new_version) @@ -185,7 +185,7 @@ def _update_python_gvm_version( ) ) - if not force and check_version_equal(new_version, python_gvm_version): + if not force and versions_equal(new_version, python_gvm_version): print('Version is already up-to-date.') sys.exit(0) diff --git a/tests/version/test_check_version_equal.py b/tests/version/test_versions_equal.py similarity index 52% rename from tests/version/test_check_version_equal.py rename to tests/version/test_versions_equal.py index 5e96bd386..3002ec405 100644 --- a/tests/version/test_check_version_equal.py +++ b/tests/version/test_versions_equal.py @@ -18,20 +18,20 @@ import unittest -from gvm.version import check_version_equal +from gvm.version import versions_equal -class CheckVersionEqualTestCase(unittest.TestCase): +class VersionEqualTestCase(unittest.TestCase): def test_version_equal(self): - self.assertTrue(check_version_equal('1.2.3', '1.2.3')) - self.assertTrue(check_version_equal('1.2.3a', '1.2.3a0')) - self.assertTrue(check_version_equal('1.2.3a0', '1.2.3.a0')) - self.assertTrue(check_version_equal('1.2.3dev1', '1.2.3.dev1')) + self.assertTrue(versions_equal('1.2.3', '1.2.3')) + self.assertTrue(versions_equal('1.2.3a', '1.2.3a0')) + self.assertTrue(versions_equal('1.2.3a0', '1.2.3.a0')) + self.assertTrue(versions_equal('1.2.3dev1', '1.2.3.dev1')) def test_version_not_equal(self): - self.assertFalse(check_version_equal('1.2.3', '1.2')) - self.assertFalse(check_version_equal('1.2.3a', '1.2.3a1')) - self.assertFalse(check_version_equal('1.2.3a0', '1.2.3.a1')) - self.assertFalse(check_version_equal('1.2.3dev', '1.2.3dev1')) - self.assertFalse(check_version_equal('1.2.3dev', '1.2.3.dev1')) - self.assertFalse(check_version_equal('1.2.3.dev1', '1.2.3.dev2')) + self.assertFalse(versions_equal('1.2.3', '1.2')) + self.assertFalse(versions_equal('1.2.3a', '1.2.3a1')) + self.assertFalse(versions_equal('1.2.3a0', '1.2.3.a1')) + self.assertFalse(versions_equal('1.2.3dev', '1.2.3dev1')) + self.assertFalse(versions_equal('1.2.3dev', '1.2.3.dev1')) + self.assertFalse(versions_equal('1.2.3.dev1', '1.2.3.dev2')) From 1c42bab8e4920811d6c436d3aae99fdacde7f3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 15:54:00 +0200 Subject: [PATCH 24/25] Update RELEASE doc for new version handling --- RELEASE.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 38127dd61..74b271451 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -21,9 +21,23 @@ release. We are following [Semantic Versioning](https://semver.org/) and git checkout -b create-new-release upstream/master ``` -* Open `pyproject.toml` and increment the version number. For example, if the - file contains `version = 22.4dev1`, the line should be changed to - `version = 22.4`. +* Get the current version number + + ```sh + poetry run python -m gvm.version show + ``` + +* Determine new release version number + + If the output is something like `22.4.dev1` or `21.10a1`, the new version + should be `22.4` or `21.10` respectively. + +* Update to new version number (`` must be replaced by the version + from the last step) + + ```sh + poetry run python -m gvm.version update + ``` * Update the `CHANGELOG.md` file: * Change `[unreleased]` to new release version. From 9ce94107e2169e4a641c91288d235e5e2188cd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 1 Apr 2020 16:07:54 +0200 Subject: [PATCH 25/25] Add changelog entry for new version handling --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 129d6e280..4dfe3be3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Added +* Added an API and CLI utilities for the version handling in python-gvm + [#198](https://github.com/greenbone/python-gvm/pull/198) + ### Changed -- Replaced `pipenv` with `poetry` for dependency management. `poetry install` +* Replaced `pipenv` with `poetry` for dependency management. `poetry install` works a bit different then `pipenv install`. It installs dev packages by default and also python-gvm in editable mode. This means after running `poetry install` gvm will directly be importable in the virtual python