diff --git a/galaxy_release_util/bootstrap_history.py b/galaxy_release_util/bootstrap_history.py index 4021d76..ebcbf57 100644 --- a/galaxy_release_util/bootstrap_history.py +++ b/galaxy_release_util/bootstrap_history.py @@ -9,7 +9,7 @@ import sys import textwrap from pathlib import Path -from typing import Optional +from typing import Optional, List import click from github.PullRequest import PullRequest @@ -17,6 +17,8 @@ from .cli.options import ( ClickVersion, + #next_version_option, + freeze_date_option, galaxy_root_option, group_options, ) @@ -34,6 +36,7 @@ RELEASE_DELTA_MONTHS = 4 # Number of months between releases. MINOR_TO_MONTH = {0: 2, 1: 6, 2: 10} +FREEZE_TO_RELEASE_DELTA_DAYS = 21 # Default number of days from freeze to release TEMPLATE = """ @@ -327,6 +330,11 @@ release_version_argument = click.argument("release-version", type=ClickVersion()) +next_version_option = click.option( + "--next-version", + type=ClickVersion(), + help="Next release version.", +) @click.group(help="Subcommands of this script can perform various tasks around creating Galaxy releases") def cli(): @@ -334,24 +342,35 @@ def cli(): @cli.command(help="Create release checklist issue on GitHub") -@group_options(release_version_argument, galaxy_root_option) -def create_release_issue(release_version: Version, galaxy_root: Path): - previous_release = _previous_release(galaxy_root, release_version) - new_version_params = _next_version_params(release_version) - next_version = new_version_params["version"] - freeze_date, _ = _release_dates(release_version) +@group_options(release_version_argument, next_version_option, freeze_date_option, galaxy_root_option) +def create_release_issue(release_version: Version, next_version: Version, freeze_date, galaxy_root: Path): + """ + TODO + """ # TODO ADD COMMENT + + previous_version = _get_previous_release_version(galaxy_root, release_version) + next_version = next_version or _get_next_release_version(release_version) + assert next_version > release_version, "Next release version should be greater than release version" + release_issue_template_params = dict( version=release_version, next_version=next_version, - previous_version=previous_release, + previous_version=previous_version, freeze_date=freeze_date, ) + release_issue_contents = RELEASE_ISSUE_TEMPLATE.safe_substitute(**release_issue_template_params) + #print(release_issue_contents) + github = github_client() repo = github.get_repo(f"{PROJECT_OWNER}/{PROJECT_NAME}") + + # TODO error: must enable github tokern !!!!! + assert token????? + release_issue = repo.create_issue( - title=f"Publication of Galaxy Release v {release_version}", - body=release_issue_contents, + title=f"Publication of Galaxy Release v {release_version}", + body=release_issue_contents, ) return release_issue @@ -449,34 +468,55 @@ def _issue_to_str(pr): return f"Issue #{pr.number} ({pr.title}) {pr.html_url}" -def _next_version_params(release_version: Version): +def _get_next_version_params(release_version: Version): # we'll just hardcode this to 3 "minor" versions per year - if release_version.minor < 2: - next_major = release_version.major - next_minor = release_version.minor + 1 - else: - next_major = release_version.major + 1 - next_minor = 0 - next_month_name = calendar.month_name[MINOR_TO_MONTH[next_minor]] - next_version = Version(f"{next_major}.{next_minor}") - freeze_date, release_date = _release_dates(next_version) - return dict( - version=next_version, - year=next_major, - month_name=next_month_name, - freeze_date=freeze_date, - release_date=release_date, - ) - - -def _release_dates(version: Version): - # hardcoded to 3 releases a year, freeze dates kind of random - year = version.major - month = MINOR_TO_MONTH[version.minor] - first_of_month = datetime.date(year + 2000, month, 1) - freeze_date = next_weekday(first_of_month, 0) - release_date = next_weekday(first_of_month, 0) + datetime.timedelta(21) - return freeze_date, release_date + #if release_version.minor < 2: + # next_major = release_version.major + # next_minor = release_version.minor + 1 + #else: + # next_major = release_version.major + 1 + # next_minor = 0 + #next_month_name = calendar.month_name[MINOR_TO_MONTH[next_minor]] + #next_version = Version(f"{next_major}.{next_minor}") + #freeze_date, release_date = _release_dates(next_version) + #return dict( + # version=next_version, + # year=next_major, + # month_name=next_month_name, + # freeze_date=freeze_date, + # release_date=release_date, + #) + return None + + +#def _next_version_params(release_version: Version): +# # we'll just hardcode this to 3 "minor" versions per year +# if release_version.minor < 2: +# next_major = release_version.major +# next_minor = release_version.minor + 1 +# else: +# next_major = release_version.major + 1 +# next_minor = 0 +# next_month_name = calendar.month_name[MINOR_TO_MONTH[next_minor]] +# next_version = Version(f"{next_major}.{next_minor}") +# freeze_date, release_date = _release_dates(next_version) +# return dict( +# version=next_version, +# year=next_major, +# month_name=next_month_name, +# freeze_date=freeze_date, +# release_date=release_date, +# ) + + +#def _release_dates(version: Version): +# # hardcoded to 3 releases a year, freeze dates kind of random +# year = version.major +# month = MINOR_TO_MONTH[version.minor] +# first_of_month = datetime.date(year + 2000, month, 1) +# freeze_date = next_weekday(first_of_month, 0) +# release_date = next_weekday(first_of_month, 0) + datetime.timedelta(21) +# return freeze_date, release_date def _get_prs(release_version: str, state="closed"): @@ -554,30 +594,59 @@ def _write_file(path, contents, skip_if_exists=False): f.write(contents) -def _previous_release(galaxy_root, to: Version): - previous_release = None - for release in _releases(galaxy_root): - if release == str(to): - break - previous_release = release - return previous_release -def _releases(galaxy_root): +#def _get_release_date(freeze_date: click.DateTime): # TODO do we need this? +# """ +# Return release date based on freeze date and FREEZE_TO_RELEASE_DELTA_DAYS. +# If the date falls on the weekend, return the next Monday. +# Do not account for holidays. +# """ +# release_date = freeze_date + datetime.timedelta(FREEZE_TO_RELEASE_DELTA_DAYS) +# if release_date.weekday() > 4: +# release_date += datetime.timedelta(7 - release_date.weekday()) +# return release_date + + + +def _get_next_release_version(version: Version): + return Version(f"{version.major}.{version.minor + 1}") + + +def _get_previous_release_version(galaxy_root: Path, version: Version): + """ Return previous release version if it exists. """ + # NOTE: We convert strings to Version objects to compare apples to apples: + # str(Version(foo)) is not the same as the string foo: str(Version("22.05")) == "22.5" + prev = None + for release in _get_release_version_strings(galaxy_root): + release_version = Version(release) + if release_version >= version: + return prev + prev = release_version + return prev + + +def _get_release_version_strings(galaxy_root: Path) -> List[str]: + """ + Return sorted list of release version strings. + """ + all_files = _get_release_notes_filenames(galaxy_root) + release_notes_file_pattern = re.compile(r"\d+\.\d+.rst") + filenames = [f.rstrip(".rst") for f in all_files if release_notes_file_pattern.match(f)] + return sorted(filenames) + + +def _get_release_notes_filenames(galaxy_root): releases_path = galaxy_root / "doc" / "source" / "releases" if not os.path.exists(releases_path): msg = f"Path to releases documentation not found: {releases_path}. If you are running this script outside of galaxy root directory, you should specify the '--galaxy-root' argument" raise Exception(msg) - - all_files = sorted(os.listdir(releases_path)) - release_note_file_pattern = re.compile(r"\d+\.\d+.rst") - release_note_files = [f for f in all_files if release_note_file_pattern.match(f)] - return sorted(f.rstrip(".rst") for f in release_note_files) + return sorted(os.listdir(releases_path)) -def _release_file(galaxy_root: Path, release: Optional[str]) -> str: +def _release_file(galaxy_root: Path, release: Optional[str]) -> str: # TODO: check this! releases_path = galaxy_root / "doc" / "source" / "releases" if release is None: release = sorted(os.listdir(releases_path))[-1] @@ -585,12 +654,7 @@ def _release_file(galaxy_root: Path, release: Optional[str]) -> str: return history_path -def get_first_sentence(message: str) -> str: - first_line = message.split("\n")[0] - return first_line - - -def process_sentence(message): +def process_sentence(message): # TODO: check this! # Strip tags like [15.07]. message = strip_release(message=message) # Link issues and pull requests... @@ -599,7 +663,7 @@ def process_sentence(message): return message -def wrap(message): +def wrap(message): # TODO: check this! message = process_sentence(message) wrapper = textwrap.TextWrapper(initial_indent="* ") wrapper.subsequent_indent = " " @@ -611,9 +675,5 @@ def wrap(message): return first_lines + ("\n" + rest_lines if rest_lines else "") -def next_weekday(d, weekday): - """Return the next week day (0 for Monday, 6 for Sunday) starting from ``d``.""" - days_ahead = weekday - d.weekday() - if days_ahead <= 0: # Target day already happened this week - days_ahead += 7 - return d + datetime.timedelta(days_ahead) +def next_weekday(d, weekday): # TODO remobve this + pass diff --git a/galaxy_release_util/cli/options.py b/galaxy_release_util/cli/options.py index 6437cf8..a4dadb8 100644 --- a/galaxy_release_util/cli/options.py +++ b/galaxy_release_util/cli/options.py @@ -1,3 +1,4 @@ +import datetime import pathlib from typing import ( Any, @@ -19,6 +20,18 @@ ) +release_date_option = click.option( + "--release-date", + type=click.DateTime(), +) + +freeze_date_option = click.option( + "--freeze-date", + type=click.DateTime(), + default=datetime.datetime.today(), +) + + def group_options(*options): def wrapper(function): for option in reversed(options): diff --git a/tests/test_bootstrap_history.py b/tests/test_bootstrap_history.py index e06ec1d..bbfdd27 100644 --- a/tests/test_bootstrap_history.py +++ b/tests/test_bootstrap_history.py @@ -1,22 +1,65 @@ import datetime +from packaging.version import Version import pytest -from galaxy_release_util.bootstrap_history import get_release_date +from galaxy_release_util import bootstrap_history +from galaxy_release_util.bootstrap_history import ( + #_get_release_date, + _get_next_release_version, + _get_previous_release_version, + _get_next_release_version, + _get_release_version_strings, +) -# test data tuples: freeze date, expected release date -# (assumption: FREEZE_TO_RELEASE_DELTA_DAYS = 21) -release_date_test_data = [ - (datetime.datetime(2024, 11, 1), datetime.datetime(2024, 11, 22)), # expect Friday - (datetime.datetime(2024, 11, 2), datetime.datetime(2024, 11, 25)), # expect Monday (not Saturday) - (datetime.datetime(2024, 11, 3), datetime.datetime(2024, 11, 25)), # expect Monday (not Sunday) - (datetime.datetime(2024, 11, 4), datetime.datetime(2024, 11, 25)), # expect Monday - (datetime.datetime(2024, 11, 5), datetime.datetime(2024, 11, 26)), # expect Tuesday - (datetime.datetime(2024, 11, 6), datetime.datetime(2024, 11, 27)), # expect Wednesday - (datetime.datetime(2024, 11, 7), datetime.datetime(2024, 11, 28)), # expect Thursday -] +# TODO do we need this??? +## test data tuples: freeze date, expected release date +## (assumption: FREEZE_TO_RELEASE_DELTA_DAYS = 21) +#release_date_test_data = [ +# (datetime.datetime(2024, 11, 1), datetime.datetime(2024, 11, 22)), # expect Friday +# (datetime.datetime(2024, 11, 2), datetime.datetime(2024, 11, 25)), # expect Monday (not Saturday) +# (datetime.datetime(2024, 11, 3), datetime.datetime(2024, 11, 25)), # expect Monday (not Sunday) +# (datetime.datetime(2024, 11, 4), datetime.datetime(2024, 11, 25)), # expect Monday +# (datetime.datetime(2024, 11, 5), datetime.datetime(2024, 11, 26)), # expect Tuesday +# (datetime.datetime(2024, 11, 6), datetime.datetime(2024, 11, 27)), # expect Wednesday +# (datetime.datetime(2024, 11, 7), datetime.datetime(2024, 11, 28)), # expect Thursday +#] +# +# +#@pytest.mark.parametrize("freeze_date, expected_release_date", release_date_test_data) +#def test_get_release_date(freeze_date, expected_release_date): +# assert _get_release_date(freeze_date) == expected_release_date +# + + +def test_get_previous_release_version(monkeypatch): + monkeypatch.setattr(bootstrap_history, "_get_release_version_strings", lambda x: sorted(["22.01", "22.05", "23.0", "23.1"])) + + assert _get_previous_release_version(None, Version("15.1")) == None + assert _get_previous_release_version(None, Version("22.01")) == None + assert _get_previous_release_version(None, Version("22.05")) == Version("22.01") + assert _get_previous_release_version(None, Version("23.0")) == Version("22.05") + assert _get_previous_release_version(None, Version("23.1")) == Version("23.0") + assert _get_previous_release_version(None, Version("23.2")) == Version("23.1") + assert _get_previous_release_version(None, Version("99.99")) == Version("23.1") + + +def test_get_next_release_version(): + assert _get_next_release_version(Version("25.0")) == Version("25.1") + assert _get_next_release_version(Version("26.1")) == Version("26.2") + + +def test_get_release_version_strings(monkeypatch): + filenames = [ + "15.0.not_rst", + "22.01.rst", + "22.05.rst", + "23.0.rst", + "23.1.rst", + "23.not_a_release.rst", + "not_a_release.23.rst", + ] + monkeypatch.setattr(bootstrap_history, "_get_release_notes_filenames", lambda x: sorted(filenames)) + assert _get_release_version_strings(None) == ["22.01", "22.05", "23.0", "23.1"] -@pytest.mark.parametrize("freeze_date, expected_release_date", release_date_test_data) -def test_get_release_date(freeze_date, expected_release_date): - assert get_release_date(freeze_date) == expected_release_date