From b8b225b5072cef9d69f0f1fe950ff577f8216b76 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Sun, 31 Mar 2024 15:44:50 -0400 Subject: [PATCH] Revisit versioning and streamline maintenance adding "bump" and "tag_release" nox sessions (#13) * feat: simplify versioning by hard-coding value in pyproject.toml This major version associated with the package is now consistent with the IDC table version hard-coded in `scripts/sql/idc_index.sql`. Co-authored-by: Henry Schreiner * chore: Add "bump" nox session to streamline IDC index version update Co-authored-by: Henry Schreiner * chore: Add nox session and instructions for tagging a release Co-authored-by: Henry Schreiner --------- Co-authored-by: Henry Schreiner --- .github/CONTRIBUTING.md | 21 +++++ noxfile.py | 90 ++++++++++++++++++++- pyproject.toml | 12 +-- scripts/python/update_idc_index_version.py | 91 ++++++++++++++++++++++ src/idc_index_data/_version.pyi | 1 - tests/test_package.py | 8 ++ 6 files changed, 215 insertions(+), 8 deletions(-) create mode 100755 scripts/python/update_idc_index_version.py diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 11cc123..2292c15 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -99,3 +99,24 @@ pre-commit run -a ``` to check all files. + +# Updating the IDC index version + +You can update the version using: + +```bash +export GCP_PROJECT=idc-external-025 +export GOOGLE_APPLICATION_CREDENTIALS=/path/to/keyfile.json +nox -s bump -- +``` + +And follow the instructions it gives you. Leave off the version to bump to the +latest version. Add `-–commit` to run the commit procedure. + +# Tagging a release + +You can print the instructions for tagging a release using: + +```bash +nox -s tag_release +``` diff --git a/noxfile.py b/noxfile.py index 31ba46e..a9a9f86 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse +import re import shutil from pathlib import Path @@ -8,7 +9,7 @@ DIR = Path(__file__).parent.resolve() -nox.options.sessions = ["lint", "pylint", "tests"] +nox.options.sessions = ["lint", "pylint", "tests"] # Session run by default @nox.session @@ -115,3 +116,90 @@ def build(session: nox.Session) -> None: session.install("build") session.run("python", "-m", "build") + + +def _bump(session: nox.Session, name: str, script: str, files) -> None: + parser = argparse.ArgumentParser() + parser.add_argument( + "--commit", action="store_true", help="Make a branch and commit." + ) + parser.add_argument( + "version", nargs="?", help="The version to process - leave off for latest." + ) + args = parser.parse_args(session.posargs) + + session.install("db-dtypes") + session.install("google-cloud-bigquery") + session.install("pandas") + session.install("pyarrow") + + if args.version is None: + gcp_project = "idc-external-025" + idc_index_version = session.run( + "python", + "scripts/python/idc_index_data_manager.py", + "--project", + gcp_project, + "--retrieve-latest-idc-release-version", + external=True, + silent=True, + ).strip() + + else: + idc_index_version = args.version + + extra = ["--quiet"] if args.commit else [] + session.run("python", script, idc_index_version, *extra) + + if args.commit: + session.run( + "git", + "switch", + "-c", + f"update-to-{name.replace(' ', '-').lower()}-{idc_index_version}", + external=True, + ) + session.run("git", "add", "-u", *files, external=True) + session.run( + "git", + "commit", + "-m", + f"Update to {name} {idc_index_version}", + external=True, + ) + session.log( + f'Complete! Now run: gh pr create --fill --body "Created by running `nox -s {session.name} -- --commit`"' + ) + + +@nox.session +def bump(session: nox.Session) -> None: + """ + Set to a new IDC index version, use -- , otherwise will use the latest version. + """ + files = ( + "pyproject.toml", + "scripts/sql/idc_index.sql", + "tests/test_package.py", + ) + _bump( + session, + "IDC index", + "scripts/python/update_idc_index_version.py", + files, + ) + + +@nox.session(venv_backend="none") +def tag_release(session: nox.Session) -> None: + """ + Print instructions for tagging a release and pushing it to GitHub. + """ + + session.log("Run the following commands to make a release:") + txt = Path("pyproject.toml").read_text() + current_version = next(iter(re.finditer(r'^version = "([\d\.]+)$"', txt))).group(1) + print( + f"git tag --sign -m 'idc-index-data {current_version}' {current_version} main" + ) + print(f"git push origin {current_version}") diff --git a/pyproject.toml b/pyproject.toml index 2737df0..aed46eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ build-backend = "scikit_build_core.build" [project] name = "idc-index-data" +version = "17.0.0" authors = [ { name = "Andrey Fedorov", email = "andrey.fedorov@gmail.com" }, { name = "Vamsi Thiriveedhi", email = "vthiriveedhi@mgh.harvard.edu" }, @@ -39,7 +40,6 @@ classifiers = [ "Topic :: Scientific/Engineering", "Typing :: Typed", ] -dynamic = ["version"] dependencies = [] [project.optional-dependencies] @@ -69,15 +69,15 @@ Changelog = "https://github.com/ImagingDataCommons/idc-index-data/releases" [tool.scikit-build] minimum-version = "0.8.2" build-dir = "build/{wheel_tag}" -metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" -sdist.include = ["src/idc_index_data/_version.py"] wheel.platlib = false wheel.py-api = "py3" -[tool.setuptools_scm] -write_to = "src/idc_index_data/_version.py" -version_scheme = "no-guess-dev" +[[tool.scikit-build.generate]] +path = "idc_index_data/_version.py" +template = ''' +version = "${version}" +''' [tool.pytest.ini_options] diff --git a/scripts/python/update_idc_index_version.py b/scripts/python/update_idc_index_version.py new file mode 100755 index 0000000..d2f1513 --- /dev/null +++ b/scripts/python/update_idc_index_version.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +""" +Command line executable allowing to update source files given a IDC index version. +""" + +from __future__ import annotations + +import argparse +import contextlib +import os +import re +import textwrap +from pathlib import Path + +ROOT_DIR = Path(__file__).parent / "../.." + + +@contextlib.contextmanager +def _log(txt, verbose=True): + if verbose: + print(txt) # noqa: T201 + yield + if verbose: + print(f"{txt} - done") # noqa: T201 + + +def _update_file(filepath, regex, replacement): + msg = "Updating %s" % os.path.relpath(str(filepath), ROOT_DIR) + with _log(msg): + pattern = re.compile(regex) + with filepath.open() as doc_file: + lines = doc_file.readlines() + updated_content = [] + for line in lines: + updated_content.append(re.sub(pattern, replacement, line)) + with filepath.open("w") as doc_file: + doc_file.writelines(updated_content) + + +def update_pyproject_toml(idc_index_version): + pattern = re.compile(r'^version = "[\w\.]+"$') + replacement = f'version = "{idc_index_version}.0.0"' + _update_file(ROOT_DIR / "pyproject.toml", pattern, replacement) + + +def update_sql_scripts(idc_index_version): + pattern = re.compile(r"idc_v\d+") + replacement = f"idc_v{idc_index_version}" + _update_file(ROOT_DIR / "scripts/sql/idc_index.sql", pattern, replacement) + + +def update_tests(idc_index_version): + pattern = re.compile(r"EXPECTED_IDC_INDEX_VERSION = \d+") + replacement = f"EXPECTED_IDC_INDEX_VERSION = {idc_index_version}" + _update_file(ROOT_DIR / "tests/test_package.py", pattern, replacement) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "idc_index_version", + metavar="IDC_INDEX_VERSION", + type=int, + help="IDC index version of the form NN", + ) + parser.add_argument( + "--quiet", + action="store_true", + help="Hide the output", + ) + + args = parser.parse_args() + + update_pyproject_toml(args.idc_index_version) + update_sql_scripts(args.idc_index_version) + update_tests(args.idc_index_version) + + if not args.quiet: + msg = """\ + Complete! Now run: + + git switch -c update-to-idc-index-{release} + git add -u pyproject.toml scripts/sql/idc_index.sql tests/test_package.py + git commit -m "Update to IDC index {release}" + gh pr create --fill --body "Created by update_idc_index_version.py" + """ + print(textwrap.dedent(msg.format(release=args.idc_index_version))) # noqa: T201 + + +if __name__ == "__main__": + main() diff --git a/src/idc_index_data/_version.pyi b/src/idc_index_data/_version.pyi index 91744f9..502a8ee 100644 --- a/src/idc_index_data/_version.pyi +++ b/src/idc_index_data/_version.pyi @@ -1,4 +1,3 @@ from __future__ import annotations version: str -version_tuple: tuple[int, int, int] | tuple[int, int, int, str, str] diff --git a/tests/test_package.py b/tests/test_package.py index 3870f64..d27a51d 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -2,13 +2,21 @@ import importlib.metadata +from packaging.version import Version + import idc_index_data as m +EXPECTED_IDC_INDEX_VERSION = 17 + def test_version(): assert importlib.metadata.version("idc_index_data") == m.__version__ +def test_idc_index_version(): + assert Version(m.__version__).major == EXPECTED_IDC_INDEX_VERSION + + def test_filepath(): if m.IDC_INDEX_CSV_ARCHIVE_FILEPATH is not None: assert m.IDC_INDEX_CSV_ARCHIVE_FILEPATH.is_file()