diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e8932fd..cd90af3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: inputs: requested_release_tag: - description: 'The tag to use for this developmental release (without `.dev` suffix) (e.g., `v2.0.1`)' + description: "The tag to use for this developmental release (without `.dev` suffix) (e.g., `v2.0.1`)" required: true jobs: @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.12' + python-version: "3.12" - run: | pip install packaging @@ -62,14 +62,10 @@ jobs: run: | [[ ${{ env.version_from_release_tag }} == ${{ env.backports_version }} ]] - - name: Get the latest developmental release for this version from Test PyPI - run: | - curl https://test.pypi.org/pypi/backports-datetime-fromisoformat/json | python -c 'import json, sys; from packaging import version; contents=sys.stdin.read(); parsed = json.loads(contents); new_release_number = version.parse("${{ env.version_from_release_tag }}"); existing_releases = [version.parse(x) for x in parsed["releases"].keys()]; existing_developmental_release_version = max([x for x in existing_releases if x.release == new_release_number.release and x.is_devrelease], default=version.parse("0.0.0")); print(f"test_pypi_developmental_version={existing_developmental_release_version}")' >> $GITHUB_ENV - - - name: Generate the developmental release version - # If there is a developmental release in Test PyPI for the version in pyproject.toml, increment the number. Else 1. Save in $GITHUB_ENV + - name: Generate the next developmental release version + # If there is a developmental release in Test PyPI for the requested version, increment the number. Else 1. Save in $GITHUB_ENV run: | - python -c 'from packaging import version; new = version.parse("${{ env.version_from_release_tag }}"); existing = version.parse("${{ env.test_pypi_developmental_version }}"); dev_number = existing.dev + 1 if existing.is_devrelease and new.release == existing.release else 1; epoch = f"{new.epoch}!" if new.epoch else ""; release = ".".join([str(x) for x in new.release]); pre = f"{new.pre[0]}{new.pre[1]}" if new.pre else ""; post = f".post{new.post}" if new.post else ""; dev = f".dev{dev_number}"; developmental_release_version=f"{epoch}{release}{pre}{post}{dev}"; print(f"developmental_release_version={developmental_release_version}")' >> $GITHUB_ENV + curl https://test.pypi.org/pypi/backports-datetime-fromisoformat/json | python release/developmental_release.py ${{ env.version_from_release_tag }} | sed 's/^/developmental_release_version=/' >> $GITHUB_ENV - name: Determine which version to use run: echo "version_to_use=`if [ '${{ github.event_name }}' == 'workflow_dispatch' ]; then echo '${{ env.developmental_release_version }}'; else echo '${{ env.version_from_release_tag }}'; fi`" >> $GITHUB_ENV @@ -80,7 +76,6 @@ jobs: echo 'release event tag `${{ env.release_tag }}`' echo 'release event version `${{ env.version_from_release_tag }}`' echo 'Version in pyproject.toml `${{ env.backports_version }}`' - echo 'Developmental release version in Test PyPI `${{ env.test_pypi_developmental_version }}`' echo 'New developmental version `${{ env.developmental_release_version }}`' echo 'Version to use `${{ env.version_to_use }}`' @@ -182,7 +177,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.12' + python-version: "3.12" - uses: actions/download-artifact@v3 with: @@ -212,7 +207,8 @@ jobs: path: dist/*.tar.gz publish_to_test_pypi: - needs: [pre_build_sanity_check, build_wheels, build_wheels_windows, build_sdist] + needs: + [pre_build_sanity_check, build_wheels, build_wheels_windows, build_sdist] runs-on: ubuntu-latest steps: @@ -302,7 +298,15 @@ jobs: publish: if: github.event_name == 'release' - needs: [pre_build_sanity_check, build_wheels, build_wheels_windows, build_sdist, publish_to_test_pypi, pre_publish_sanity_check] + needs: + [ + pre_build_sanity_check, + build_wheels, + build_wheels_windows, + build_sdist, + publish_to_test_pypi, + pre_publish_sanity_check, + ] runs-on: ubuntu-latest steps: diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 071561e..791c965 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -59,4 +59,5 @@ Once you have sucessfully uploaded and tested a developmental release, it's time ## What if something went wrong with the final release? -TODO. +1. Debug and correct the problem. +2. Publish a new release with an incremented version number diff --git a/pyproject.toml b/pyproject.toml index c1cb395..32f85df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,27 +1,25 @@ [project] name = "backports-datetime-fromisoformat" version = "2.0.1" -authors = [ - { name="Michael Overmeyer", email="backports@movermeyer.com" }, -] +authors = [{ name = "Michael Overmeyer", email = "backports@movermeyer.com" }] description = "Backport of Python 3.11's datetime.fromisoformat" readme = "README.rst" requires-python = ">3" -license = {file = "LICENSE"} +license = { file = "LICENSE" } classifiers = [ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Topic :: Software Development :: Libraries :: Python Modules', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Topic :: Software Development :: Libraries :: Python Modules', ] [project.urls] @@ -34,3 +32,14 @@ Changelog = "https://github.com/movermeyer/backports.datetime_fromisoformat/CHAN [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" + +[tool.pylint.'MESSAGES CONTROL'] +max-line-length = 120 +disable = "C0114, C0115, C0116, C0301" + +[tool.autopep8] +max_line_length = 120 +ignore = ["E501"] +in-place = true +recursive = true +aggressive = 3 diff --git a/release/__init__.py b/release/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/release/developmental_release.py b/release/developmental_release.py new file mode 100644 index 0000000..9c34cd2 --- /dev/null +++ b/release/developmental_release.py @@ -0,0 +1,47 @@ +import argparse +import json +import sys + +from packaging.version import parse, Version +from typing import Iterator + +def _releases(contents) -> Iterator[Version]: + parsed = json.loads(contents) + existing_releases = [parse(x) for x in parsed["releases"].keys()] + return existing_releases + +def non_developmental_version(version: Version): + if not version.is_devrelease: + return version + + epoch = f"{version.epoch}!" if version.epoch else "" + release = ".".join([str(x) for x in version.release]) + pre = f"{version.pre[0]}{version.pre[1]}" if version.pre else "" + post = f".post{version.post}" if version.post else "" + return parse(f"{epoch}{release}{pre}{post}") + + +def new_developmental_release_version(new: Version, existing_releases: Iterator[Version]) -> Version: + existing = max([x for x in existing_releases if x.is_devrelease and non_developmental_version(x) == new], default=parse("0.0.0")) + dev_number = existing.dev + 1 if existing.is_devrelease and new.release == existing.release else 1 + + epoch = f"{new.epoch}!" if new.epoch else "" + release = ".".join([str(x) for x in new.release]) + pre = f"{new.pre[0]}{new.pre[1]}" if new.pre else "" + post = f".post{new.post}" if new.post else "" + dev = f".dev{dev_number}" + return parse(f"{epoch}{release}{pre}{post}{dev}") + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Generates the next developmental release version number based on existing releases' + ) + + parser.add_argument('new_version', help='The new version number you want to use (without any `.dev` suffix)') + args = parser.parse_args() + + contents = sys.stdin.read() + existing_releases = _releases(contents) + dev_version = new_developmental_release_version(parse(args.new_version), existing_releases) + print(dev_version) diff --git a/release/test_developmental_release.py b/release/test_developmental_release.py new file mode 100644 index 0000000..c724fb0 --- /dev/null +++ b/release/test_developmental_release.py @@ -0,0 +1,40 @@ +import itertools +import unittest + +from packaging.version import parse, Version +from typing import Iterator + +from developmental_release import new_developmental_release_version + +def generate_versions() -> Iterator[Version]: + # https://peps.python.org/pep-0440/ + epoch_options = ["", "1!"] + release_options = ['2', '2.0', '2.0.0', '2.0.0.0', '2.0.0.0.0'] + pre_release_options = ["", 'a1', 'b1', 'rc1'] + post_release_options = ["", ".post1"] + dev_options = ["", ".dev1"] + for v in (parse(f"{epoch}{release}{pre_release}{post_release}{dev}") for (epoch, release, pre_release, post_release, dev) in itertools.product(epoch_options, release_options, pre_release_options, post_release_options, dev_options)): + yield v + +class TestDevelopmentalVersionGeneration(unittest.TestCase): + def test_no_releases(self): + for version in generate_versions(): + if version.is_devrelease: + continue + new_dev_version = new_developmental_release_version(version, []) + self.assertRegex(f"{new_dev_version}", r"\.dev1$", f"Input: {version}, Output: {new_dev_version}") + + def test_existing_dev_release(self): + for version in generate_versions(): + if version.is_devrelease: + continue + new_dev_version = new_developmental_release_version(version, [parse(f"{version}.dev1")]) + self.assertRegex(f"{new_dev_version}", r"\.dev2$", f"Input: {version}, Output: {new_dev_version}") + + def test_dev_release_exists_for_more_specific_version(self): + version = parse("2.0.0") + new_dev_version = new_developmental_release_version(version, [parse("2.0.0b1.dev1")]) + self.assertRegex(f"{new_dev_version}", r"\.dev1$", f"Input: {version}, Output: {new_dev_version}") + +if __name__ == '__main__': + unittest.main()