From 0b7cb848db421001f20b9063631000e589f12caa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 00:48:02 -0600 Subject: [PATCH 1/7] build(deps-dev): bump mypy from 1.10.1 to 1.11.2 (#1009) * build(deps-dev): bump mypy from 1.10.1 to 1.11.2 * chore(pre-commit): bump mypy from 1.10.0 to 1.11.2 --- .pre-commit-config.yaml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4acf01e2e..7b559cea1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,7 +50,7 @@ repos: name: ruff (format) - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.10.0" + rev: "v1.11.2" hooks: - id: mypy additional_dependencies: diff --git a/pyproject.toml b/pyproject.toml index f7785e7e5..18193a5a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ dev = [ "ruff == 0.5.0" ] mypy = [ - "mypy == 1.10.1", + "mypy == 1.11.2", "types-requests ~= 2.32.0", ] From 297f612cdafc7c54efe843f6a911a3a3c9fbf312 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 00:48:53 -0600 Subject: [PATCH 2/7] build(deps-dev): bump ruff from 0.5.0 to 0.6.1 (#1006) * build(deps-dev): bump ruff from 0.5.0 to 0.6.1 * chore(pre-commit): bump ruff plugin from 0.5.0 to 0.6.1 --- .pre-commit-config.yaml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7b559cea1..842903553 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,7 +37,7 @@ repos: # Linters and validation - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.0 + rev: v0.6.1 hooks: - id: ruff name: ruff (lint) diff --git a/pyproject.toml b/pyproject.toml index 18193a5a7..242021dfe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ test = [ dev = [ "pre-commit ~= 3.5", "tox ~= 4.11", - "ruff == 0.5.0" + "ruff == 0.6.1" ] mypy = [ "mypy == 1.11.2", From d84efc7719a8679e6979d513d1c8c60904af7384 Mon Sep 17 00:00:00 2001 From: Will Yardley Date: Tue, 24 Sep 2024 18:55:25 -0700 Subject: [PATCH 3/7] docs: update docstrings to resolve sphinx failures (#1030) set `ignore-module-all` for `autodoc_default_options` to resolve some Sphinx errors about duplicate / ambiguous references https://github.com/sphinx-doc/sphinx/issues/4961#issuecomment-1543858623 Standardize some non-standard (Google-ish) docstrings to Sphinx format, to avoid ruff and Sphinx arguing about underline length. Fix indents and other minor whitespace / formatting changes. Fixes #1029 --- docs/conf.py | 2 + semantic_release/cli/commands/version.py | 17 ++--- semantic_release/hvcs/gitea.py | 9 ++- semantic_release/hvcs/gitlab.py | 81 ++++++++---------------- semantic_release/hvcs/util.py | 8 ++- 5 files changed, 45 insertions(+), 72 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9c026c4b8..da9f2cda5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,6 +16,8 @@ "sphinxcontrib.apidoc", ] +autodoc_default_options = {"ignore-module-all": True} + templates_path = ["_templates"] source_suffix = ".rst" master_doc = "index" diff --git a/semantic_release/cli/commands/version.py b/semantic_release/cli/commands/version.py index 503d7bf1e..5461cdd2d 100644 --- a/semantic_release/cli/commands/version.py +++ b/semantic_release/cli/commands/version.py @@ -219,19 +219,12 @@ def build_distributions( """ Run the build command to build the distributions. - Arguments: - --------- - build_command: str | None - The build command to run - build_command_env: Mapping[str, str] | None - The environment variables to use when running the build command - noop: bool - Whether or not to run the build command - - Raises: - ------ - BuildDistributionsError: if the build command fails + :param build_command: The build command to run. + :param build_command_env: The environment variables to use when running the + build command. + :param noop: Whether or not to run the build command. + :raises: BuildDistributionsError: if the build command fails """ if not build_command: rprint("[green]No build command specified, skipping") diff --git a/semantic_release/hvcs/gitea.py b/semantic_release/hvcs/gitea.py index 12f52e850..3ae2e6ab4 100644 --- a/semantic_release/hvcs/gitea.py +++ b/semantic_release/hvcs/gitea.py @@ -95,11 +95,9 @@ def create_release( Ref: https://gitea.com/api/swagger#/repository/repoCreateRelease :param tag: Tag to create release for - :param release_notes: The release notes for this version - :param prerelease: Whether or not this release should be specified as a - prerelease + prerelease :return: Whether the request succeeded """ @@ -181,6 +179,7 @@ def get_release_id_by_tag(self, tag: str) -> int | None: Get a release by its tag name https://gitea.com/api/swagger#/repository/repoGetReleaseByTag :param tag: Tag to get release for + :return: ID of found release """ tag_endpoint = self.create_api_url( @@ -206,6 +205,7 @@ def edit_release_notes(self, release_id: int, release_notes: str) -> int: https://gitea.com/api/swagger#/repository/repoEditRelease :param id: ID of release to update :param release_notes: The release notes for this version + :return: The ID of the release that was edited """ log.info("Updating release %s", release_id) @@ -231,6 +231,7 @@ def create_or_update_release( Post release changelog :param version: The version number :param changelog: The release notes for this version + :return: The status of the request """ log.info("Creating release for %s", tag) @@ -274,6 +275,7 @@ def upload_release_asset( :param release_id: ID of the release to upload to :param file: Path of the file to upload :param label: this parameter has no effect + :return: The status of the request """ url = self.asset_upload_url(release_id) @@ -312,6 +314,7 @@ def upload_dists(self, tag: str, dist_glob: str) -> int: Upload distributions to a release :param tag: Tag to upload for :param path: Path to the dist directory + :return: The number of distributions successfully uploaded """ # Find the release corresponding to this tag diff --git a/semantic_release/hvcs/gitlab.py b/semantic_release/hvcs/gitlab.py index f5452c4ca..1a9437b76 100644 --- a/semantic_release/hvcs/gitlab.py +++ b/semantic_release/hvcs/gitlab.py @@ -106,23 +106,16 @@ def create_release( """ Create a release in a remote VCS, adding any release notes and assets to it - Arguments: - --------- - tag(str): The tag to create the release for - release_notes(str): The changelog description for this version only - prerelease(bool): This parameter has no effect in GitLab - assets(list[str]): A list of paths to files to upload as assets (TODO: not implemented) - noop(bool): If True, do not perform any actions, only log intents - - Returns: - ------- - str: The tag of the release - - Raises: - ------ - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request + :param tag: The tag to create the release for + :param release_notes: The changelog description for this version only + :param prerelease: This parameter has no effect in GitLab + :param assets: A list of paths to files to upload as assets (TODO: not implemented) + :param noop: If True, do not perform any actions, only log intents + :return: The tag of the release + + :raises: GitlabAuthenticationError: If authentication is not correct + :raises: GitlabCreateError: If the server cannot perform the request """ if noop: noop_report(f"would have created a release for tag {tag}") @@ -145,20 +138,13 @@ def create_release( @suppress_not_found def get_release_by_tag(self, tag: str) -> gitlab.v4.objects.ProjectRelease | None: """ - Get a release by its tag name - - Arguments: - --------- - tag(str): The tag name to get the release for + Get a release by its tag name. - Returns: - ------- - gitlab.v4.objects.ProjectRelease | None: The release object or None if not found + :param tag: The tag name to get the release for - Raises: - ------ - gitlab.exceptions.GitlabAuthenticationError: If the user is not authenticated + :return: gitlab.v4.objects.ProjectRelease or None if not found + :raises: gitlab.exceptions.GitlabAuthenticationError: If the user is not authenticated """ try: return self.project.releases.get(tag) @@ -175,21 +161,15 @@ def edit_release_notes( # type: ignore[override] release_notes: str, ) -> str: """ - Update the release notes for a given release + Update the release notes for a given release. - Arguments: - --------- - release(gitlab.v4.objects.ProjectRelease): The release object to update - release_notes(str): The new release notes + :param release: The release object to update + :param release_notes: The new release notes - Returns: - ------- - str: The release id + :return: The release id - Raises: - ------ - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request + :raises: GitlabAuthenticationError: If authentication is not correct + :raises: GitlabUpdateError: If the server cannot perform the request """ log.info( @@ -206,24 +186,17 @@ def create_or_update_release( self, tag: str, release_notes: str, prerelease: bool = False ) -> str: """ - Create or update a release for the given tag in a remote VCS - - Arguments: - --------- - tag(str): The tag to create or update the release for - release_notes(str): The changelog description for this version only - prerelease(bool): This parameter has no effect in GitLab + Create or update a release for the given tag in a remote VCS. - Returns: - ------- - str: The release id + :param tag: The tag to create or update the release for + :param release_notes: The changelog description for this version only + :param prerelease: This parameter has no effect in GitLab - Raises: - ------ - ValueError: If the release could not be created or updated - gitlab.exceptions.GitlabAuthenticationError: If the user is not authenticated - GitlabUpdateError: If the server cannot perform the request + :return: The release id + :raises ValueError: If the release could not be created or updated + :raises gitlab.exceptions.GitlabAuthenticationError: If the user is not authenticated + :raises GitlabUpdateError: If the server cannot perform the request """ try: return self.create_release( diff --git a/semantic_release/hvcs/util.py b/semantic_release/hvcs/util.py index fd0d66ebc..3c4f78888 100644 --- a/semantic_release/hvcs/util.py +++ b/semantic_release/hvcs/util.py @@ -21,12 +21,14 @@ def build_requests_session( ) -> Session: """ Create a requests session. + :param raise_for_status: If True, a hook to invoke raise_for_status be installed :param retry: If true, it will use default Retry configuration. if an integer, it - will use default Retry configuration with given integer as total retry - count. if Retry instance, it will use this instance. + will use default Retry configuration with given integer as total retry + count. if Retry instance, it will use this instance. :param auth: Optional TokenAuth instance to be used to provide the Authorization - header to the session + header to the session + :return: configured requests Session """ session = Session() From 1fa0e70c1fd5117f802b3a5f1c02809f987f5ccb Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Wed, 25 Sep 2024 18:27:59 -0600 Subject: [PATCH 4/7] ci(pr): add file type filter to regulate testing jobs (#1031) --- .github/workflows/pr.yml | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d8ae2fb03..e412f4b3f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -4,10 +4,68 @@ name: Checks on: pull_request: +permissions: + contents: read + jobs: + + eval-changes: + name: Evaluate changes + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Evaluate | Check specific file types for changes + id: changed-files + uses: tj-actions/changed-files@v45.0.2 + with: + files_yaml: | + build: + - MANIFEST.in + - Dockerfile + - .dockerignore + - scripts/** + ci: + - .github/workflows/** + docs: + - docs/** + - README.rst + - AUTHORS.rst + - CONTRIBUTING.rst + - CHANGELOG.rst + src: + - semantic_release/** + - pyproject.toml + tests: + - tests/** + + - name: Evaluate | Detect if any of the combinations of file sets have changed + id: all-changes + run: | + printf '%s\n' "any_changed=false" >> $GITHUB_OUTPUT + if [ "${{ steps.changed-files.outputs.build_any_changed }}" == "true" ] || \ + [ "${{ steps.changed-files.outputs.ci_any_changed }}" == "true" ] || \ + [ "${{ steps.changed-files.outputs.docs_any_changed }}" == "true" ] || \ + [ "${{ steps.changed-files.outputs.src_any_changed }}" == "true" ] || \ + [ "${{ steps.changed-files.outputs.tests_any_changed }}" == "true" ]; then + printf '%s\n' "any_changed=true" >> $GITHUB_OUTPUT + fi + + outputs: + any-file-changes: ${{ steps.all-changes.outputs.any_changed }} + build-changes: ${{ steps.changed-files.outputs.build_any_changed }} + ci-changes: ${{ steps.changed-files.outputs.ci_any_changed }} + doc-changes: ${{ steps.changed-files.outputs.docs_any_changed }} + src-changes: ${{ steps.changed-files.outputs.src_any_changed }} + test-changes: ${{ steps.changed-files.outputs.tests_any_changed }} + + test-linux: name: Python ${{ matrix.python-version }} on ${{ matrix.os }} tests runs-on: ${{ matrix.os }} + needs: eval-changes + if: ${{ needs.eval-changes.outputs.src-changes == 'true' || needs.eval-changes.outputs.test-changes == 'true' || needs.eval-changes.outputs.ci-changes == 'true' }} strategy: matrix: python-version: @@ -66,6 +124,8 @@ jobs: test-windows: name: Python ${{ matrix.python-version }} on ${{ matrix.os }} tests runs-on: ${{ matrix.os }} + needs: eval-changes + if: ${{ needs.eval-changes.outputs.src-changes == 'true' || needs.eval-changes.outputs.test-changes == 'true' || needs.eval-changes.outputs.ci-changes == 'true' }} strategy: # Since the current test suite takes 10-15 minutes to complete on windows, we are # only going to run it on the oldest version of python we support. The older version @@ -122,7 +182,10 @@ jobs: report_paths: ./tests/reports/*.xml annotate_only: true + lint: + needs: eval-changes + if: ${{ needs.eval-changes.outputs.any-file-changes == 'true' }} runs-on: ubuntu-latest steps: @@ -147,6 +210,7 @@ jobs: - name: mypy run: python -m mypy --ignore-missing-imports semantic_release + commitlint: runs-on: ubuntu-latest From 2307ed29d9990bf1b6821403a4b8db3365ef8bb5 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Fri, 27 Sep 2024 01:05:17 -0600 Subject: [PATCH 5/7] chore(gha): update action details for marketplace publishing (#1032) * chore(gha): update action details for marketplace publishing * docs(homepage): re-structure homepage to be separate from project readme * docs(README): simplify README to point at official docs --- README.rst | 54 ++++++++++++-------------------------------------- action.yml | 13 +++++++----- docs/index.rst | 52 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 47 deletions(-) diff --git a/README.rst b/README.rst index 70167f553..c8fdd9f14 100644 --- a/README.rst +++ b/README.rst @@ -1,50 +1,22 @@ Python Semantic Release *********************** -|Ruff| |Test Status| |PyPI Version| |conda-forge version| |Read the Docs Status| |Pre-Commit Enabled| +*Automating Releases via SemVer and Commit Message Conventions* -Automatic Semantic Versioning for Python projects. This is a Python -implementation of `semantic-release`_ for JS by Stephan Bönnemann. If -you find this topic interesting you should check out his `talk from -JSConf Budapest`_. +---- -The general idea is to be able to detect what the next version of the -project should be based on the commits. This tool will use that to -automate the whole release, upload to an artifact repository and post changelogs to -GitHub. You can run the tool on a CI service, or just run it locally. +The official documentation for Python Semantic Release can be found at +`python-semantic-release.readthedocs.io`_. -Installation -============ +GitHub Action +============= -:: +When using the Python Semantic Release GitHub Action, it executes the command +``semantic-release version`` using `python-semantic-release`_. - python3 -m pip install python-semantic-release - semantic-release --help +The usage information and examples for this GitHub Action is available under +the `GitHub Actions section`_ of `python-semantic-release.readthedocs.io`_. -Python Semantic Release is also available from `conda-forge`_ or as a `GitHub Action`_. -Read more about the setup and configuration in our `getting started guide`_. - -.. _semantic-release: https://github.com/semantic-release/semantic-release -.. _talk from JSConf Budapest: https://www.youtube.com/watch?v=tc2UgG5L7WM -.. _getting started guide: https://python-semantic-release.readthedocs.io/en/latest/#getting-started -.. _GitHub Action: https://python-semantic-release.readthedocs.io/en/latest/automatic-releases/github-actions.html -.. _conda-forge: https://anaconda.org/conda-forge/python-semantic-release - -.. |Test Status| image:: https://img.shields.io/github/actions/workflow/status/python-semantic-release/python-semantic-release/main.yml?branch=master&label=Test%20Status&logo=github - :target: https://github.com/python-semantic-release/python-semantic-release/actions/workflows/main.yml - :alt: test-status -.. |PyPI Version| image:: https://img.shields.io/pypi/v/python-semantic-release?label=PyPI&logo=pypi - :target: https://pypi.org/project/python-semantic-release/ - :alt: pypi -.. |conda-forge Version| image:: https://img.shields.io/conda/vn/conda-forge/python-semantic-release?logo=anaconda - :target: https://anaconda.org/conda-forge/python-semantic-release - :alt: conda-forge -.. |Read the Docs Status| image:: https://img.shields.io/readthedocs/python-semantic-release?label=Read%20the%20Docs&logo=Read%20the%20Docs - :target: https://python-semantic-release.readthedocs.io/en/latest/ - :alt: docs -.. |Pre-Commit Enabled| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit - :target: https://github.com/pre-commit/pre-commit - :alt: pre-commit -.. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json - :target: https://github.com/astral-sh/ruff - :alt: Ruff +.. _python-semantic-release: https://pypi.org/project/python-semantic-release/ +.. _python-semantic-release.readthedocs.io: https://python-semantic-release.readthedocs.io/en/latest/ +.. _GitHub Actions section: https://python-semantic-release.readthedocs.io/en/latest/automatic-releases/github-actions.html diff --git a/action.yml b/action.yml index 13ea3593b..dd3c1547f 100644 --- a/action.yml +++ b/action.yml @@ -1,7 +1,10 @@ --- name: Python Semantic Release -description: Automatic Semantic Versioning for Python projects +description: Automated Releases via SemVer and Commit Message Conventions + +branding: + color: orange inputs: root_options: @@ -103,14 +106,14 @@ outputs: description: | "true" if a release was made, "false" otherwise - version: - description: | - The newly released version if one was made, otherwise the current version - tag: description: | The Git tag corresponding to the version output + version: + description: | + The newly released version if one was made, otherwise the current version + runs: using: docker image: Dockerfile diff --git a/docs/index.rst b/docs/index.rst index 210d820a9..b51b47128 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,54 @@ -.. include:: ../README.rst +Python Semantic Release +*********************** + +|Ruff| |Test Status| |PyPI Version| |conda-forge version| |Read the Docs Status| |Pre-Commit Enabled| + +Automatic Semantic Versioning for Python projects. This is a Python +implementation of `semantic-release`_ for JS by Stephan Bönnemann. If +you find this topic interesting you should check out his `talk from +JSConf Budapest`_. + +The general idea is to be able to detect what the next version of the +project should be based on the commits. This tool will use that to +automate the whole release, upload to an artifact repository and post changelogs to +GitHub. You can run the tool on a CI service, or just run it locally. + +Installation +============ + +:: + + python3 -m pip install python-semantic-release + semantic-release --help + +Python Semantic Release is also available from `conda-forge`_ or as a `GitHub Action`_. +Read more about the setup and configuration in our `getting started guide`_. + +.. _semantic-release: https://github.com/semantic-release/semantic-release +.. _talk from JSConf Budapest: https://www.youtube.com/watch?v=tc2UgG5L7WM +.. _getting started guide: https://python-semantic-release.readthedocs.io/en/latest/#getting-started +.. _GitHub Action: https://python-semantic-release.readthedocs.io/en/latest/automatic-releases/github-actions.html +.. _conda-forge: https://anaconda.org/conda-forge/python-semantic-release + +.. |Test Status| image:: https://img.shields.io/github/actions/workflow/status/python-semantic-release/python-semantic-release/main.yml?branch=master&label=Test%20Status&logo=github + :target: https://github.com/python-semantic-release/python-semantic-release/actions/workflows/main.yml + :alt: test-status +.. |PyPI Version| image:: https://img.shields.io/pypi/v/python-semantic-release?label=PyPI&logo=pypi + :target: https://pypi.org/project/python-semantic-release/ + :alt: pypi +.. |conda-forge Version| image:: https://img.shields.io/conda/vn/conda-forge/python-semantic-release?logo=anaconda + :target: https://anaconda.org/conda-forge/python-semantic-release + :alt: conda-forge +.. |Read the Docs Status| image:: https://img.shields.io/readthedocs/python-semantic-release?label=Read%20the%20Docs&logo=Read%20the%20Docs + :target: https://python-semantic-release.readthedocs.io/en/latest/ + :alt: docs +.. |Pre-Commit Enabled| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit + :target: https://github.com/pre-commit/pre-commit + :alt: pre-commit +.. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Ruff + Documentation Contents ====================== From 156915c7d759098f65cf9de7c4e980b40b38d5f1 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Fri, 27 Sep 2024 01:15:02 -0600 Subject: [PATCH 6/7] fix(version-cmd): improve `version_variables` flexibility w/ quotes (ie. json, yaml, etc) (#1028) * fix(version-cmd): increase `version_variable` flexibility with quotations (ie. json, yaml, etc) Previously json would not work due to the key being wrapped in quotes, yaml also has issues when it does not usually use quotes. The regex we created originally only wrapped the version to be replaced in quotes but now both the key and version can optionally be wrapped in different kind of quotations. Resolves: #601, #706, #962, #1026 * docs(configuration): add clarity to `version_variables` usage & limitations Ref: #941 * fix(version-cmd): ensure `version_variables` do not match partial variable names * build(deps-test): add `PyYAML` as a test dependency * test(fixtures): refactor location of fixture for global use of cli runner * test(stamp-version): add test cases to stamp json, python, & yaml files --- docs/configuration.rst | 29 ++++ pyproject.toml | 5 +- semantic_release/cli/config.py | 13 +- tests/command_line/conftest.py | 6 - tests/conftest.py | 6 + tests/scenario/test_version_stamp.py | 219 +++++++++++++++++++++++++++ 6 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 tests/scenario/test_version_stamp.py diff --git a/docs/configuration.rst b/docs/configuration.rst index e2bfe3bbc..ee63a99b0 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1102,4 +1102,33 @@ specified in ``file:variable`` format. For example: "docs/conf.py:version", ] +Each version variable will be transformed into a Regular Expression that will be used +to substitute the version number in the file. The replacement algorithm is **ONLY** a +pattern match and replace. It will **NOT** evaluate the code nor will PSR understand +any internal object structures (ie. ``file:object.version`` will not work). + +.. important:: + The Regular Expression expects a version value to exist in the file to be replaced. + It cannot be an empty string or a non-semver compliant string. If this is the very + first time you are using PSR, we recommend you set the version to ``0.0.0``. This + may become more flexible in the future with resolution of issue `#941`_. + +.. _#941: https://github.com/python-semantic-release/python-semantic-release/issues/941 + +Given the pattern matching nature of this feature, the Regular Expression is able to +support most file formats as a variable declaration in most languages is very similar. +We specifically support Python, YAML, and JSON as these have been the most common +requests. This configuration option will also work regardless of file extension +because its only a pattern match. + +.. note:: + This will also work for TOML but we recommend using :ref:`config-version_toml` for + TOML files as it actually will interpret the TOML file and replace the version + number before writing the file back to disk. + +.. warning:: + If the file (ex. JSON) you are replacing has two of the same variable name in it, + this pattern match will not be able to differentiate between the two and will replace + both. This is a limitation of the pattern matching and not a bug. + **Default:** ``[]`` diff --git a/pyproject.toml b/pyproject.toml index 242021dfe..36be341bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ docs = [ ] test = [ "coverage[toml] ~= 7.0", + "pyyaml ~= 6.0", "pytest ~= 8.3", "pytest-env ~= 1.0", "pytest-xdist ~= 3.0", @@ -86,8 +87,8 @@ env = [ ] addopts = [ # TO DEBUG in single process, swap auto to 0 - "-nauto", - # "-n0", + # "-nauto", + "-n0", "-ra", "--diff-symbols", "--cache-clear", diff --git a/semantic_release/cli/config.py b/semantic_release/cli/config.py index ba12f4ce4..5c9f31ef7 100644 --- a/semantic_release/cli/config.py +++ b/semantic_release/cli/config.py @@ -540,7 +540,18 @@ def from_raw_config( # noqa: C901 try: path, variable = decl.split(":", maxsplit=1) # VersionDeclarationABC handles path existence check - search_text = rf"(?x){variable}\s*(:=|[:=])\s*(?P['\"])(?P{SEMVER_REGEX.pattern})(?P=quote)" # noqa: E501 + search_text = str.join( + "", + [ + # Supports optional matching quotations around variable name + # Negative lookbehind to ensure we don't match part of a variable name + f"""(?x)(?P['"])?(?['"])?(?P{SEMVER_REGEX.pattern})(?P=quote2)?""", + ], + ) pd = PatternVersionDeclaration(path, search_text) except ValueError as exc: log.exception("Invalid variable declaration %r", decl) diff --git a/tests/command_line/conftest.py b/tests/command_line/conftest.py index b8160373d..46d68d46e 100644 --- a/tests/command_line/conftest.py +++ b/tests/command_line/conftest.py @@ -6,7 +6,6 @@ from unittest.mock import MagicMock import pytest -from click.testing import CliRunner from requests_mock import ANY from semantic_release.cli import config as cli_config_module @@ -40,11 +39,6 @@ class RetrieveRuntimeContextFn(Protocol): def __call__(self, repo: Repo) -> RuntimeContext: ... -@pytest.fixture -def cli_runner() -> CliRunner: - return CliRunner(mix_stderr=False) - - @pytest.fixture def post_mocker(requests_mock: Mocker) -> Mocker: """Patch all POST requests, mocking a response body for VCS release creation.""" diff --git a/tests/conftest.py b/tests/conftest.py index 3465b236a..911906a7d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING import pytest +from click.testing import CliRunner from git import Commit, Repo from tests.fixtures import * @@ -28,6 +29,11 @@ class TeardownCachedDirFn(Protocol): def __call__(self, directory: Path) -> Path: ... +@pytest.fixture +def cli_runner() -> CliRunner: + return CliRunner(mix_stderr=False) + + @pytest.fixture(scope="session") def default_netrc_username() -> str: return "username" diff --git a/tests/scenario/test_version_stamp.py b/tests/scenario/test_version_stamp.py new file mode 100644 index 000000000..60a408e67 --- /dev/null +++ b/tests/scenario/test_version_stamp.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING + +import pytest +import yaml + +from semantic_release.cli.commands.main import main + +from tests.const import MAIN_PROG_NAME, VERSION_SUBCMD +from tests.fixtures.repos.trunk_based_dev.repo_w_no_tags import ( + repo_with_no_tags_angular_commits, +) +from tests.util import assert_successful_exit_code + +if TYPE_CHECKING: + from click.testing import CliRunner + + from tests.fixtures.example_project import UpdatePyprojectTomlFn + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_python( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + new_version = "0.1.0" + target_file = Path("src/example/_version.py") + + # Set configuration to modify the python file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:__version__"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Load python module for reading the version (ensures the file is valid) + spec = importlib.util.spec_from_file_location("example._version", str(target_file)) + module = importlib.util.module_from_spec(spec) # type: ignore + spec.loader.exec_module(module) # type: ignore + + # Check the version was updated + assert new_version == module.__version__ + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_yaml( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + orig_version = "0.0.0" + new_version = "0.1.0" + target_file = Path("example.yml") + orig_yaml = dedent( + f"""\ + --- + package: example + version: {orig_version} + date-released: 1970-01-01 + """ + ) + # Write initial text in file + target_file.write_text(orig_yaml) + + # Set configuration to modify the yaml file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:version"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Read content + resulting_yaml_obj = yaml.safe_load(target_file.read_text()) + + # Check the version was updated + assert new_version == resulting_yaml_obj["version"] + + # Check the rest of the content is the same (by reseting the version & comparing) + resulting_yaml_obj["version"] = orig_version + + assert yaml.safe_load(orig_yaml) == resulting_yaml_obj + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_yaml_cff( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + orig_version = "0.0.0" + new_version = "0.1.0" + target_file = Path("CITATION.cff") + # Derived format from python-semantic-release/python-semantic-release#962 + orig_yaml = dedent( + f"""\ + --- + cff-version: 1.2.0 + message: "If you use this software, please cite it as below." + authors: + - family-names: Doe + given-names: Jon + orcid: https://orcid.org/1234-6666-2222-5555 + title: "My Research Software" + version: {orig_version} + date-released: 1970-01-01 + """ + ) + # Write initial text in file + target_file.write_text(orig_yaml) + + # Set configuration to modify the yaml file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:version"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Read content + resulting_yaml_obj = yaml.safe_load(target_file.read_text()) + + # Check the version was updated + assert new_version == resulting_yaml_obj["version"] + + # Check the rest of the content is the same (by reseting the version & comparing) + resulting_yaml_obj["version"] = orig_version + + assert yaml.safe_load(orig_yaml) == resulting_yaml_obj + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_json( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + orig_version = "0.0.0" + new_version = "0.1.0" + target_file = Path("plugins.json") + orig_json = { + "id": "test-plugin", + "version": orig_version, + "meta": { + "description": "Test plugin", + }, + } + # Write initial text in file + target_file.write_text(json.dumps(orig_json, indent=4)) + + # Set configuration to modify the json file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:version"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Read content + resulting_json_obj = json.loads(target_file.read_text()) + + # Check the version was updated + assert new_version == resulting_json_obj["version"] + + # Check the rest of the content is the same (by reseting the version & comparing) + resulting_json_obj["version"] = orig_version + + assert orig_json == resulting_json_obj From cbe8eaaa7a06ef218fce69bd1bc01dd16483dc6d Mon Sep 17 00:00:00 2001 From: semantic-release Date: Fri, 27 Sep 2024 07:35:01 +0000 Subject: [PATCH 7/7] 9.8.9 Automatically generated by python-semantic-release --- CHANGELOG.md | 42 ++++++++++++++++++++++ docs/automatic-releases/github-actions.rst | 6 ++-- docs/github-action.rst | 2 +- pyproject.toml | 2 +- semantic_release/__init__.py | 2 +- 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83681b66b..9fc9c6892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # CHANGELOG +## v9.8.9 (2024-09-27) + +### Documentation + +* docs: update docstrings to resolve sphinx failures (#1030) + +set `ignore-module-all` for `autodoc_default_options` to resolve some +Sphinx errors about duplicate / ambiguous references +https://github.com/sphinx-doc/sphinx/issues/4961#issuecomment-1543858623 + +Standardize some non-standard (Google-ish) docstrings to Sphinx +format, to avoid ruff and Sphinx arguing about underline length. + +Fix indents and other minor whitespace / formatting changes. + +Fixes #1029 ([`d84efc7`](https://github.com/python-semantic-release/python-semantic-release/commit/d84efc7719a8679e6979d513d1c8c60904af7384)) + +### Fix + +* fix(version-cmd): improve `version_variables` flexibility w/ quotes (ie. json, yaml, etc) (#1028) + +* fix(version-cmd): increase `version_variable` flexibility with quotations (ie. json, yaml, etc) + + Previously json would not work due to the key being wrapped in quotes, yaml also has issues + when it does not usually use quotes. The regex we created originally only wrapped the version + to be replaced in quotes but now both the key and version can optionally be wrapped in + different kind of quotations. + + Resolves: #601, #706, #962, #1026 + +* docs(configuration): add clarity to `version_variables` usage & limitations + + Ref: #941 + +* fix(version-cmd): ensure `version_variables` do not match partial variable names + +* build(deps-test): add `PyYAML` as a test dependency + +* test(fixtures): refactor location of fixture for global use of cli runner + +* test(stamp-version): add test cases to stamp json, python, & yaml files ([`156915c`](https://github.com/python-semantic-release/python-semantic-release/commit/156915c7d759098f65cf9de7c4e980b40b38d5f1)) + ## v9.8.8 (2024-09-01) ### Documentation diff --git a/docs/automatic-releases/github-actions.rst b/docs/automatic-releases/github-actions.rst index a02e7ab1a..ca6b8661f 100644 --- a/docs/automatic-releases/github-actions.rst +++ b/docs/automatic-releases/github-actions.rst @@ -53,7 +53,7 @@ Example Workflow - name: Python Semantic Release # Adjust tag with desired version if applicable. Version shorthand # is NOT available, e.g. vX or vX.X will not work. - uses: python-semantic-release/python-semantic-release@v9.8.8 + uses: python-semantic-release/python-semantic-release@v9.8.9 with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -94,13 +94,13 @@ multiple projects. .. code:: yaml - name: Release Project 1 - uses: python-semantic-release/python-semantic-release@v9.8.8 + uses: python-semantic-release/python-semantic-release@v9.8.9 with: directory: ./project1 github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release Project 2 - uses: python-semantic-release/python-semantic-release@v9.8.8 + uses: python-semantic-release/python-semantic-release@v9.8.9 with: directory: ./project2 github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/github-action.rst b/docs/github-action.rst index 35faf9deb..24a539f77 100644 --- a/docs/github-action.rst +++ b/docs/github-action.rst @@ -131,7 +131,7 @@ provide the following inputs: - name: Python Semantic Release # Adjust tag with desired version if applicable. Version shorthand # is NOT available, e.g. vX or vX.X will not work. - uses: python-semantic-release/python-semantic-release@v9.8.8 + uses: python-semantic-release/python-semantic-release@v9.8.9 with: # ... other options force: "patch" diff --git a/pyproject.toml b/pyproject.toml index 36be341bd..d70c1ecc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "9.8.8" +version = "9.8.9" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } diff --git a/semantic_release/__init__.py b/semantic_release/__init__.py index a1b08b69b..30801902c 100644 --- a/semantic_release/__init__.py +++ b/semantic_release/__init__.py @@ -24,7 +24,7 @@ tags_and_versions, ) -__version__ = "9.8.8" +__version__ = "9.8.9" __all__ = [ "CommitParser",