diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 35c1410..e379895 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,11 +1,10 @@ # This workflow will install Python dependencies, run tests and lint with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Tests +name: Mainline tests/verifications on: push: - tags: '*' branches: [ master, main ] pull_request: branches: [ master, main ] @@ -25,7 +24,7 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade pip invoke + python -m pip install --upgrade pip invoke typing-extensions invoke setup --devel --tests - name: Lint with flake8 @@ -50,64 +49,15 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade pip invoke + python -m pip install --upgrade pip invoke typing-extensions invoke setup --devel --tests - name: validate types run: | python/bin/python -m mypy instruct - update-changelog: - needs: [verify_style, verify_types] - runs-on: 'ubuntu-latest' - permissions: - contents: write - steps: - - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Python 3.11 - uses: actions/setup-python@v5 - with: - python-version: '3.11' - architecture: 'x64' - - - name: Setup - run: | - python -m pip install --upgrade pip invoke typing-extensions - invoke setup --devel --no-project - - - - name: Warn changelog out of sync - if: ${{ github.ref_type != 'tag' }} - run: | - - invoke update-changes - if ! git diff-index --quiet HEAD -- - then - echo '::warn file=CHANGES.rst,line=1,title=CHANGES.rst out of sync::Expected invoke update-changes to have zero changes, got instead '"$(git diff CHANGES.rst)"'. - - If you make a tag from this, it *will* error out.' - - fi - - - - name: Assert changelog in sync - if: ${{ github.ref_type == 'tag' }} - run: | - invoke update-changes - if ! git diff-index --quiet HEAD -- - then - echo '::error file=CHANGES.rst,line=1,title=CHANGES.rst out of sync::Update the changelog. - Suggest you fix that and delete the tag.' - exit 254 - fi - - test_matrix: - needs: [verify_style, verify_types, update-changelog] + needs: [verify_style, verify_types, check-versions] strategy: fail-fast: false matrix: @@ -148,85 +98,52 @@ jobs: name: Install dependencies run: | python -m pip install --upgrade pip invoke typing-extensions - invoke setup --tests + invoke setup --devel --tests --no-project + invoke python-path + python/bin/python -m pip install -r setup-requirements.txt + - + name: Prepare build artifacts + run: | + invoke build --validate + python/bin/python -m pip install --no-index dist/instruct*.whl + - name: Test with pytest run: | - python/bin/python -m pytest + invoke test - pypi-publish: - needs: [test_matrix, verify_style, update-changelog] + check-versions: runs-on: 'ubuntu-latest' - environment: - name: pypi - url: https://pypi.org/p/instruct - permissions: - id-token: write - contents: write steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + - name: Set up Python 3.11 uses: actions/setup-python@v5 with: python-version: '3.11' architecture: 'x64' + - + name: Setup + run: | + python -m pip install --upgrade pip invoke typing-extensions + invoke setup --devel --no-project - name: Set some variables... id: version run: | echo "CURRENT_VERSION=$(grep -vE '^#' CURRENT_VERSION.txt | head -1)" >> "$GITHUB_OUTPUT" - if [ "x${{ github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v') }}" = 'xtrue' ]; then - echo "GIT_VERSION=$(echo '${{ github.ref_name }}' | sed 's/^.//')" >> "$GITHUB_OUTPUT" - fi + echo "CHANGELOG_VERSION=$(invoke last-logged-changes | head -1 | cut -f2 -d' ')" >> "$GITHUB_OUTPUT" - - name: Warn tag version matches the source version - if: ${{ github.ref_type != 'tag' }} + name: Warn if branch version already matches an existing tag run: | - if [ 'x${{ steps.version.outputs.GIT_VERSION }}' != 'x${{ steps.version.outputs.CURRENT_VERSION }}' ]; then - echo '::warn file=CURRENT_VERSION.txt,line=2,title=Version mismatch::Expected ${{ steps.version.outputs.GIT_VERSION }} but got ${{ steps.version.outputs.CURRENT_VERSION }} instead. - If you make a tag from this, it *will* error out.' + if [ "x$(invoke local-tag-exists --format json 'v${{ steps.version.outputs.CURRENT_VERSION }}')" = 'xtrue' ]; then + echo '::warning file=CURRENT_VERSION.txt,line=2,title=Version already exists in tags::Tag v${{ steps.version.outputs.CURRENT_VERSION }} already exists.' fi - - - - name: Assert tag version matches the source version - if: ${{ github.ref_type == 'tag' }} - run: | - if [ 'x${{ steps.version.outputs.GIT_VERSION }}' != 'x${{ steps.version.outputs.CURRENT_VERSION }}' ]; then - echo '::error file=CURRENT_VERSION.txt,line=2,title=Version mismatch::Expected ${{ steps.version.outputs.GIT_VERSION }} but got ${{ steps.version.outputs.CURRENT_VERSION }} instead. - Suggest you fix that and delete the tag.' - exit 254 - fi - - - name: Setup - run: | - python -m pip install --upgrade pip invoke typing-extensions - invoke setup --devel --no-project - - - name: Create artifacts - id: artifacts - run: | - invoke build --validate - invoke last_logged_changes | tee CHANGES.rst.${{ steps.version.outputs.CURRENT_VERSION }} - - - - name: Create Release - if: ${{ github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v') }} - id: upload-release-asset - uses: softprops/action-gh-release@v1 - with: - body_path: CHANGES.rst.${{ steps.version.outputs.CURRENT_VERSION }} - name: Release ${{ steps.version.outputs.CURRENT_VERSION }} - files: - dist/instruct* - # ARJ: disable for now - # - - # name: Publish package distributions to PyPI - # uses: pypa/gh-action-pypi-publish@release/v1 - # if: ${{ github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v') }} - # with: - # skip-existing: false diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 2a1bf42..fb7b237 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -53,11 +53,18 @@ jobs: name: Install dependencies run: | python -m pip install --upgrade pip invoke typing-extensions - invoke setup --tests + invoke setup --devel --tests --no-project + invoke python-path + python/bin/python -m pip install -r setup-requirements.txt + - + name: Prepare build artifacts + run: | + invoke build --validate + python/bin/python -m pip install --no-index dist/instruct*.whl - name: Test with pytest run: | - python/bin/python -m pytest + invoke test verify_style: runs-on: 'ubuntu-latest' @@ -78,14 +85,11 @@ jobs: - name: Lint with flake8 run: | - # stop the build if there are Python syntax errors or undefined names - python/bin/python -m flake8 instruct/ --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - python/bin/python -m flake8 instruct/ --ignore=E203,W503 --count --exit-zero --max-complexity=103 --max-line-length=127 --statistics + invoke lint - name: Check style with black run: | - python/bin/python -m black --check instruct + invoke black --check verify_types: runs-on: 'ubuntu-latest' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..01bb7c9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,176 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Release + +on: + push: + tags: 'v*' + +jobs: + verify_style: + runs-on: 'ubuntu-latest' + steps: + - + uses: actions/checkout@v4 + - + name: Set up Python 3 + uses: actions/setup-python@v5 + with: + python-version: '3.x' + architecture: 'x64' + - + name: Install dependencies + run: | + python -m pip install --upgrade pip invoke + invoke setup --devel --tests + - + name: Lint with flake8 + run: | + invoke lint + - + name: Check style with black + run: | + invoke black --check + + verify_types: + runs-on: 'ubuntu-latest' + steps: + - + uses: actions/checkout@v4 + - + name: Set up Python 3 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + architecture: 'x64' + - + name: Install dependencies + run: | + python -m pip install --upgrade pip invoke + invoke setup --devel --tests + - + name: validate types + run: | + python/bin/python -m mypy instruct + + test_matrix: + needs: [verify_style, verify_types] + strategy: + fail-fast: false + matrix: + experimental: [false] + arch: + - 'x64' + python_version: + - '3.7' + - '3.8' + - '3.9' + - '3.10' + - '3.11' + - '3.12' + - 'pypy3.7' + - 'pypy3.8' + - 'pypy3.9' + os: + - 'ubuntu-latest' + include: + - + python_version: 'pypy3.10' + experimental: true + arch: 'x64' + os: ubuntu-latest + + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.experimental }} + steps: + - + uses: actions/checkout@v4 + - + name: Set up Python ${{ matrix.python_version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python_version }} + architecture: ${{ matrix.arch }} + - + name: Install dependencies + run: | + python -m pip install --upgrade pip invoke typing-extensions + invoke setup --tests + - + name: Test with pytest + run: | + python/bin/python -m pytest + + + pypi-publish: + needs: [test_matrix, verify_style] + runs-on: 'ubuntu-latest' + environment: + name: pypi + url: https://pypi.org/p/instruct + permissions: + id-token: write + contents: write + steps: + - + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - + name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + architecture: 'x64' + - + name: Setup + run: | + python -m pip install --upgrade pip invoke typing-extensions + invoke setup --devel --no-project + - + name: Set some variables... + id: version + run: | + echo "CURRENT_VERSION=$(grep -vE '^#' CURRENT_VERSION.txt | head -1)" >> "$GITHUB_OUTPUT" + echo "CHANGELOG_VERSION=$(invoke last-logged-changes | head -1 | cut -f2 -d' ')" >> "$GITHUB_OUTPUT" + echo "GIT_VERSION=$(echo '${{ github.ref_name }}' | sed 's/^.//')" >> "$GITHUB_OUTPUT" + + - + name: Assert tag version matches the source version + run: | + if [ 'x${{ steps.version.outputs.GIT_VERSION }}' != 'x${{ steps.version.outputs.CURRENT_VERSION }}' ]; then + echo '::error file=CURRENT_VERSION.txt,line=2,title=Version mismatch::Expected ${{ steps.version.outputs.GIT_VERSION }} but got ${{ steps.version.outputs.CURRENT_VERSION }} instead. + Suggest you fix that and delete the tag.' + exit 254 + fi + if [ 'x${{ steps.version.outputs.CHANGELOG_VERSION }}' != 'x${{ github.ref_name }}' ]; then + echo '::error title=Release note version mismatch:: Expected release notes to be for ${{ github.ref_name }} however got ${{ steps.version.outputs.CHANGELOG_VERSION }} instead.' + exit 253 + fi + + - + name: Create artifacts + id: artifacts + run: | + invoke build --validate + invoke last-logged-changes | tee CHANGES.rst.${{ steps.version.outputs.CURRENT_VERSION }} + + - + name: Create Release + id: upload-release-asset + uses: softprops/action-gh-release@v1 + with: + body_path: CHANGES.rst.${{ steps.version.outputs.CURRENT_VERSION }} + name: Release ${{ steps.version.outputs.CURRENT_VERSION }} + files: + dist/instruct* + # ARJ: disable for now + # - + # name: Publish package distributions to PyPI + # uses: pypa/gh-action-pypi-publish@release/v1 + # if: ${{ github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v') }} + # with: + # skip-existing: false diff --git a/tasks.py b/tasks.py index 0ae7c87..1a8ee51 100644 --- a/tasks.py +++ b/tasks.py @@ -8,6 +8,7 @@ import sys import tempfile import tarfile +import typing import zipfile from contextlib import suppress, contextmanager, closing from pathlib import Path @@ -17,6 +18,38 @@ from typing import Literal else: from typing_extensions import Literal +if sys.version_info[:2] >= (3, 10): + from typing import TypeGuard, TypeAlias +else: + from typing_extensions import TypeGuard, TypeAlias +if sys.version_info[:2] >= (3, 11): + from typing import Never, assert_never +else: + from typing_extensions import Never, assert_never + +if typing.TYPE_CHECKING: + from packaging.version import Version + from packaging.version import parse as parse_version + + has_packaging = True + +else: + try: + import packaging.version + except ImportError: + has_packaging = False + else: + packaging.version + has_packaging = True + + if has_packaging: + from packaging.version import Version + from packaging.version import parse as parse_version + else: + Version: TypeAlias = Never + + def parse_version(v: str) -> Version: + raise ImportError("packaging not found!") from invoke.context import Context @@ -130,16 +163,60 @@ def window( @task -def update_changes(context: Context, version: str = ""): +def last_logged_changes(context: Context) -> str: + """ + Return the latest entry in CHANGES.rst. + """ python_bin = _.python_path(str, silent=True) - extra = "" + cli = f"{python_bin} -m git_changelog" + result = context.run(f"{cli} -R", warn=True, hide="both") + assert result is not None + if result: + return result.stdout + perror(f"Unable to emit last release changes due to:\n{result.stderr}") + raise SystemExit(result.return_code) + + +@task( + help={ + "with_unreleased": "Add unreleased section if missing and populate", + "version": "override version", + } +) +def update_changelog( + context: Context, version: str = "", output: Optional[str] = None, with_unreleased: bool = False +): + """ + Update the change log (output defaults to CHANGES.rst) + """ + python_bin = _.python_path(str, silent=True) + root = _.project_root(Path, silent=True) + default_changelog = root / "CHANGES.rst" + extra = f"-i -o {default_changelog!s}" + if output is not None: + output_file = Path(output).resolve() + else: + output_file = default_changelog + if output in ("-", "/dev/stdout"): + output = "-" + extra = "" + elif output_file != default_changelog: + extra = f"-o {output_file!s}" if version: - extra = f"-B {version}" - result = context.run(f"{python_bin} -m git_changelog {extra}", warn=True) + extra = f"{extra} -B {version}" + if with_unreleased: + extra = f"{extra} -j emit_unreleased=true" + cli = f"{python_bin} -m git_changelog" + result = context.run(f"{cli} {extra}", warn=True, hide="both") assert result is not None - if "already in changelog" in result.stderr: - perror("No new changes to create changelog with") - return + if not result: + if "already in changelog" in result.stderr: + perror("No new changes to create changelog with") + return + perror(f"Unable to run {cli!r} due to:\n{result.stderr}") + raise SystemExit(result.return_code) + if output == "-": + return result.stdout with open(_.project_root(Path, silent=True) / "CHANGES.rst", "r+") as fh: body = fh.read() index = body.index(".. |Changes|") @@ -153,15 +230,65 @@ def update_changes(context: Context, version: str = ""): body = body[:index] + body[index + 1 :] in_between = body[index : index + 2] deleted_newline_count += 1 + while body[-2:] in ("\n", "\n"): + body = body[:-1] + deleted_newline_count += 1 if deleted_newline_count: perror(f"deleted {deleted_newline_count} extra newlines") fh.seek(0) + fh.truncate(0) fh.write(body) + return + + +@task +def lint(context: Context): + root = _.project_root(Path, silent=True) + python_bin = _.python_path(str, silent=True) + context.run( + f"{python_bin} -m flake8 {root / 'instruct'!s} " + "--count --select=E9,F63,F7,F82 --show-source --statistics" + ) + context.run( + f"{python_bin} -m flake8 {root / 'instruct'!s} " + "--ignore=E203,W503 --count --exit-zero " + "--max-complexity=103 --max-line-length=127 --statistics" + ) + + +@task +def black(context: Context, check: bool = False): + python_bin = _.python_path(str, silent=True) + root = _.project_root(Path, silent=True) + extra = "" + if check: + extra = f"{extra} --check" + context.run(f"{python_bin} -m black --version") + pyproject = root / "pyproject.toml" + generate_version = root / "generate_version.py" + cli = f"{python_bin} -m black" + context.run(f"{cli} {extra} {root!s}") + if check: + perror( + f"Checking the results of {generate_version!s} " + "by writing to a tempfile and running black on it..." + ) + version_cli = f"{cli} --config {pyproject!s}" + with tempfile.NamedTemporaryFile(mode="w+") as fh: + context.run(f"{python_bin} {generate_version!s} {fh.name}") + if not context.run(f"{version_cli} --check {fh.name}", warn=True): + perror("generate_version returns black issues!") + with tempfile.NamedTemporaryFile(mode="w+") as new: + new.write(fh.read()) + context.run(f"{version_cli} {new.name}") + context.run(f"diff -Naur {fh.name} {new.name}") + raise SystemExit(1) + perror("success!") @task def test(context: Context): - python_bin = _.python_path(str) + python_bin = _.python_path(str, silent=True) context.run(f"{python_bin} -m pytest") @@ -275,6 +402,9 @@ def build(context: Context, validate: bool = False) -> Tuple[Path, ...]: """ python_bin = _.python_path(str, silent=True) dist = _.project_root(Path, silent=True) / "dist" + if not dist.exists(): + perror(f"Creating {dist!s}") + dist.mkdir() public_version = None files = [] with tempfile.TemporaryDirectory() as tmp: @@ -534,3 +664,218 @@ def setup( if requirements: context.run(f"{venv!s}/bin/python -m pip install {requirements}") return venv + + +@task +def dirty_repo(context) -> bool: + root = _.project_root(Path, silent=True) + if not (root / ".git").exists(): + perror(f"{root!s} is not a git repo, assuming not dirty!") + return False + result = context.run("git diff-index --quiet HEAD --", warn=True) + assert result is not None + if not result: + if result.return_code == 1 and not result.stderr: + return True + perror("git threw an error!") + raise SystemExit(result.return_code) + return False + + +@task +def remote_tag_exists(context: Context, tag: str) -> bool: + root = _.project_root(Path, silent=True) + result = context.run(f"git -C {root!s} ls-remote origin refs/tags/{tag!s}") + assert result is not None + if result.stdout.strip(): + return True + return False + + +@task +def local_tag_exists(tag: str) -> bool: + root = _.project_root(Path, silent=True) + tag_ref = root / ".git" / "refs" / "tags" / tag + if tag_ref.exists(): + if not tag_ref.is_file(): + raise ValueError(f"{tag!r} is not a valid tag") + return True + return False + + +@task +def create_release(context: Context, next_version: Union[str, Version]) -> Version: + assert has_packaging + branch = _.branch_name(context) + assert branch == "master" + root = _.project_root(Path, silent=True) + if _.dirty_repo(context, silent=True): + perror( + f"Repository {root!s} is dirty! " + "Please commit any and all changes *before* creating a release!" + ) + raise SystemExit(22) + next_version = _.bump_version(context, next_version, dry_run=True, silent=True) + perror(f"Next version will be {next_version!s}") + assert isinstance(next_version, Version) + version = _.show_version(context, silent=True) + assert next_version > version, f"{next_version!s} must be greater than {version!s}!" + perror(f"Checking if tag v{version!s} exists on remote!") + if _.remote_tag_exists(context, f"v{version!s}"): + perror(f"tag v{version!s} already exists on remote!") + raise SystemExit(2) + perror("Checking if tag exists locally (i.e. partial release)") + if _.local_tag_exists(f"v{version!s}"): + print("found local tag, Removing") + context.run(f"git tag -d v{version!s}") + assert not _.dirty_repo(context, silent=True), "repo dirty~?" + perror(f"Creating a lightweight tag v{version!s} for changelog updating...") + context.run(f"git tag v{version!s}") + _.update_changelog(context, version=str(version)) + perror(f"deleting lightweight tag v{version!s}") + context.run(f"git tag -d v{version!s}") + if _.dirty_repo(context, silent=True): + perror("changelog did change, committing it.") + context.run(f"git -C {root!s} add {root / 'CHANGES.rst'}") + context.run(f'git -C {root!s} commit -m "doc: update CHANGES.rst"') + if _.dirty_repo(context, silent=True): + perror("Repository is still dirty despite committing CHANGES.rst!") + raise SystemExit(123) + perror(f"Creating annotated tag v{version!s}") + with io.StringIO() as fh: + fh.write(_.last_logged_changes(context)) + fh.seek(0) + context.run(f"git -C {root!s} tag -a v{version!s} -F -", in_stream=fh) + assert not _.dirty_repo(context, silent=True) + return _.bump_version(context, to_version=next_version, silent=True) + + +@task +def show_version(context: Context) -> Version: + assert has_packaging + root = _.project_root(Path, silent=True) + with open(root / "CURRENT_VERSION.txt") as fh: + for line in (x.strip() for x in fh): + if line.startswith("#"): + continue + if "#" in line: + line, ignore = line.split("#", 1) + version = parse_version(line) + return version + + +def _is_bump_type( + s: str, +) -> TypeGuard[Literal["a", "b", "dev", "rc", "major", "minor", "patch", "post"]]: + return s in ("a", "b", "dev", "rc", "major", "minor", "patch", "post") + + +PRE_RELEASE_TYPES = ("a", "b", "rc") + + +@task +def bump_version( + context: Context, to_version: Union[str, Version] = "", dry_run: bool = False +) -> Version: + assert has_packaging + root = _.project_root(Path, silent=True) + branch = _.branch_name(context, silent=True) + assert branch == "master" + root = _.project_root(Path, silent=True) + if not dry_run and _.dirty_repo(context, silent=True): + perror( + f"Repository {root!s} is dirty! " + "Please commit any and all changes *before* creating a release!" + ) + raise SystemExit(22) + + bump_type: Literal["a", "b", "dev", "rc", "major", "minor", "patch", "post", ""] = "" + if isinstance(to_version, str) and to_version in ( + "a", + "alpha", + "beta", + "b", + "dev", + "rc", + "patch", + "minor", + "major", + "post", + ): + if to_version in ("alpha", "beta"): + to_version = to_version[:1] + assert _is_bump_type(to_version) + bump_type = to_version + to_version = "" + + if not to_version: + current_version = _.show_version(context, silent=True) + if not bump_type: + if current_version.is_prerelease: + bump_type = current_version.pre[0] + elif current_version.is_postrelease: + bump_type = "post" + elif current_version.is_devrelease: + bump_type = "dev" + else: + bump_type = "patch" + if bump_type in PRE_RELEASE_TYPES: + pre_type, pre_ord = current_version.pre + assert PRE_RELEASE_TYPES.index(bump_type) >= PRE_RELEASE_TYPES.index(pre_type) + if bump_type != pre_type: + pre_ord = -1 + to_version = f"{current_version.base_version}{bump_type}{pre_ord + 1}" + elif bump_type == "post": + to_version = f"{current_version.base_version}post{current_version.post + 1}" + elif bump_type == "dev": + to_version = f"{current_version.base_version}dev{current_version.dev + 1}" + elif bump_type == "major": + to_version = f"{current_version.major + 1}.0.0" + elif bump_type == "minor": + to_version = f"{current_version.major}.{current_version.minor + 1}.0" + elif bump_type == "patch": + patch = current_version.micro + 1 + if current_version.is_prerelease: + patch = current_version.micro + to_version = f"{current_version.major}.{current_version.minor}.{patch}" + else: + assert False, "unreachable" + + if isinstance(to_version, str): + next_version = parse_version(to_version) + elif isinstance(to_version, Version): + next_version = to_version + else: + assert_never(to_version) + + with open(root / "CURRENT_VERSION.txt", "r+") as fh: + index = 0 + for line in fh: + index += len(line) + line_s = line.strip() + if line_s.startswith("#"): + continue + if "#" in line_s: + line_s, ignore = line_s.split("#", 1) + version = parse_version(line_s) + break + assert next_version > version + if not dry_run: + if index: + fh.seek(index - len(line)) + fh.truncate(fh.tell()) + fh.write(f"{next_version!s}\n") + else: + perror(f"{fh.name!r} is empty?! Readding comment") + fh.seek(0) + fh.write("# bump the below version on release\n") + assert _.dirty_repo( + context, silent=True + ), "Repo is not dirty despite modifying the version??" + if not dry_run: + context.run(f"git -C {root!s} add CURRENT_VERSION.txt") + with io.StringIO() as fh: + fh.write(f"build(release): bump version to {next_version!s}") + fh.seek(0) + context.run(f"git -C {root!s} commit -F -", in_stream=fh) + return next_version diff --git a/tasksupport.py b/tasksupport.py index 022b6fe..ec30624 100644 --- a/tasksupport.py +++ b/tasksupport.py @@ -42,14 +42,19 @@ else: has_typing_extensions = True -try: +if sys.version_info[:2] >= (3, 8): from typing import Literal -except ImportError: + from typing import get_origin, get_args +else: from typing_extensions import Literal -try: - from typing import get_origin, get_args, get_overloads -except ImportError: - from typing_extensions import get_overloads, get_args, get_origin + from typing_extensions import get_origin, get_args + +if sys.version_info[:2] >= (3, 11): + from typing import Never, assert_never + from typing import get_overloads +else: + from typing_extensions import Never, assert_never + from typing_extensions import get_overloads T = TypeVar("T") U = TypeVar("U") @@ -267,7 +272,7 @@ def get_types_from( if is_literal(annotation): return - if annotation in (Any, Ellipsis): + if annotation in (Any, Ellipsis, Never): return type_name = None with suppress(AttributeError):