diff --git a/.coveragerc b/.coveragerc index 07234c9..a2d6d6e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -13,7 +13,7 @@ omit = version.py [report] -include = src/* +include = snippets2changelog/* # Regexes for lines to exclude from consideration ignore_errors = True diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d6867e7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,64 @@ +--- + +# this file is *not* meant to cover or endorse the use of GitHub Actions, but +# rather to help make automated releases for this project + +name: Upload Python Package + +on: + push: + branches: + - main + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.11' + - name: Install build and deploy dependencies + run: | + python -m pip install -U poetry twine + poetry self add "poetry-dynamic-versioning[plugin]" + poetry install + - name: Parse changelog + id: parse_changelog + run: | + poetry run changelog2version --changelog_file changelog.md --print | python -c "import sys, json; print(json.load(sys.stdin)['info']['description'])" > latest_description.txt + echo 'LATEST_DESCRIPTION<<"EOT"' >> $GITHUB_OUTPUT + cat latest_description.txt >> $GITHUB_OUTPUT + echo '"EOT"' >> $GITHUB_OUTPUT + latest_version=$(poetry run changelog2version --changelog_file changelog.md --print | python -c "import sys, json; print(json.load(sys.stdin)['info']['version'])") + echo "latest_version=$latest_version" >> $GITHUB_ENV + - name: Build package + run: | + poetry run changelog2version \ + --changelog_file changelog.md \ + --version_file snippets2changelog/version.py \ + --version_file_type py \ + --debug + poetry build + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1.5 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + skip_existing: true + verbose: true + print_hash: true + - name: Create Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.latest_version }} + release_name: ${{ env.latest_version }} + body: ${{ steps.parse_changelog.outputs.LATEST_DESCRIPTION }} + draft: false + prerelease: false diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml new file mode 100644 index 0000000..d21120b --- /dev/null +++ b/.github/workflows/test-release.yaml @@ -0,0 +1,74 @@ +--- + +# this file is *not* meant to cover or endorse the use of GitHub Actions, but +# rather to help make automated releases for this project + +name: Upload Python Package to test.pypi.org + +on: [pull_request] + +permissions: + contents: write + +jobs: + test-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.11' + - name: Install deploy dependencies + run: | + python -m pip install -U poetry twine + poetry self add "poetry-dynamic-versioning[plugin]" + poetry install + - name: Parse changelog + id: parse_changelog + run: | + poetry run changelog2version --changelog_file changelog.md --print | python -c "import sys, json; print(json.load(sys.stdin)['info']['description'])" > latest_description.txt + echo 'LATEST_DESCRIPTION<<"EOT"' >> $GITHUB_OUTPUT + cat latest_description.txt >> $GITHUB_OUTPUT + echo '"EOT"' >> $GITHUB_OUTPUT + latest_version=$(poetry run changelog2version --changelog_file changelog.md --print | python -c "import sys, json; print(json.load(sys.stdin)['info']['version'])") + echo "latest_version=$latest_version" >> $GITHUB_ENV + - name: Build package + run: | + poetry run changelog2version \ + --changelog_file changelog.md \ + --version_file snippets2changelog/version.py \ + --version_file_type py \ + --additional_version_info="-rc${{ github.run_number }}.dev${{ github.event.number }}" \ + --debug + poetry build + - name: Test built package + run: | + twine check dist/*.tar.gz + - name: Archive build package artifact + uses: actions/upload-artifact@v3 + with: + # https://docs.github.com/en/actions/learn-github-actions/contexts#github-context + # ${{ github.repository }} and ${{ github.ref_name }} can't be used for artifact name due to unallowed '/' + name: dist_py.${{ github.event.repository.name }}_sha.${{ github.sha }}_build.${{ github.run_number }} + path: dist/*.tar.gz + retention-days: 14 + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1.5 + with: + repository_url: https://test.pypi.org/legacy/ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + skip_existing: true + verbose: true + print_hash: true + - name: Create Prerelease + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.latest_version }}-rc${{ github.run_number }}.dev${{ github.event.number }} + release_name: ${{ env.latest_version }}-rc${{ github.run_number }}.dev${{ github.event.number }} + body: ${{ steps.parse_changelog.outputs.LATEST_DESCRIPTION }} + draft: false + prerelease: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..042f777 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,56 @@ +--- + +# This workflow will install Python dependencies, run tests and lint with a +# specific Python version +# For more information see: +# https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Test Python package + +on: + push: + # branches: [ $default-branch ] + branches-ignore: + - 'main' + - 'develop' + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # python-version: ['3.8', '3.9', '3.10', '3.11'] + # # os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install -U poetry + poetry install + - name: Test with pytest + run: | + poetry run pytest -v + - name: Install deploy dependencies + run: | + python -m pip install -U twine + poetry self add "poetry-dynamic-versioning[plugin]" + - name: Build package + run: | + poetry run changelog2version \ + --changelog_file changelog.md \ + --version_file snippets2changelog/version.py \ + --version_file_type py \ + --debug + poetry build + - name: Test built package + run: | + twine check dist/*.tar.gz diff --git a/.github/workflows/unittest.yaml b/.github/workflows/unittest.yaml new file mode 100644 index 0000000..9d77254 --- /dev/null +++ b/.github/workflows/unittest.yaml @@ -0,0 +1,38 @@ +--- + +# this file is *not* meant to cover or endorse the use of GitHub Actions, but +# rather to help make automated releases for this project + +name: Unittest Python Package + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + test-and-coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v3 + with: + python-version: '3.11' + - name: Execute tests + run: | + python -m pip install -U poetry + poetry install + python create_report_dirs.py + poetry run coverage run -m pytest -v + - name: Create coverage report + run: | + poetry run coverage xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./reports/coverage/coverage.xml + flags: unittests + fail_ci_if_error: true + # path_to_write_report: ./reports/coverage/codecov_report.txt + verbose: true diff --git a/README.md b/README.md index b1fe4f9..aa9bef3 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Create version info files based on the latest changelog entry. - [Parse](#parse) - [Contributing](#contributing) - [Setup](#setup) + - [Testing](#testing) - [Credits](#credits) @@ -103,7 +104,7 @@ changelog-generator parse example_snippets/123.md \ For active development you need to have `poetry` and `pre-commit` installed -```sh +```bash python3 -m pip install --upgrade --user poetry pre-commit git clone https://github.com/brainelectronics/snippets2changelog.git cd snippets2changelog @@ -111,6 +112,25 @@ pre-commit install poetry install ``` +### Testing + +```bash +# run all tests +poetry run coverage run -m pytest -v + +# run only one specific tests +poetry run coverage run -m pytest -v -k "test_read_save_json" +``` + +Generate the coverage files with + +```bash +python create_report_dirs.py +coverage html +``` + +The coverage report is placed at `reports/coverage/html/index.html` + ## Credits A big thank you to the creators and maintainers of [SemVer.org][ref-semver] diff --git a/changelog.md b/changelog.md index 7bf7143..910ba94 100644 --- a/changelog.md +++ b/changelog.md @@ -17,7 +17,7 @@ r"^\#\# \[\d{1,}[.]\d{1,}[.]\d{1,}\] \- \d{4}\-\d{2}-\d{2}$" --> ## Released -## [0.1.0] - 2204-05-30 +## [0.1.0] - 2024-05-30 ### Added - This changelog file - [`.coveragerc`](.coveragerc) file diff --git a/poetry.lock b/poetry.lock index 07e527f..912dad5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -66,6 +66,25 @@ files = [ {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] +[[package]] +name = "changelog2version" +version = "0.10.0" +description = "Update version info file with latest changelog version entry" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "changelog2version-0.10.0-py3-none-any.whl", hash = "sha256:b6388815415a1bae6101458bef46754420f1ae4b4666ddf53a55fd8d1307823e"}, + {file = "changelog2version-0.10.0.tar.gz", hash = "sha256:1652564cead72d5d71aaf8866e6a39faa57d7e8ea6c6684537da253c07afe54e"}, +] + +[package.dependencies] +jinja2 = ">=3.1.0,<4" +semver = ">=2.13.0,<3" + +[package.extras] +dev = ["tox (>=3.25.1,<4)"] +test = ["coverage (>=6.4.2,<7)", "flake8 (>=5.0.0,<6)", "nose2 (>=0.12.0,<1)"] + [[package]] name = "click" version = "8.1.7" @@ -700,6 +719,17 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "semver" +version = "2.13.0" +description = "Python helper for Semantic Versioning (http://semver.org/)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, + {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, +] + [[package]] name = "smmap" version = "5.0.1" @@ -816,4 +846,4 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "2cadc7bcfd8038aaf81db54cbb13c9a0acba326f20ec49afd7e474a1051754b1" +content-hash = "040e37cb7494fdb355d979dee00732d62562c55573c89ac11c6977b2348e0092" diff --git a/pyproject.toml b/pyproject.toml index ad5e4a5..becb889 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "snippets2changelog" -version = "0.0.0+will-be-updated-automatically" +version = "0.0.0" # will-be-updated-automatically description = "Generate a changelog from individual snippets" authors = ["brainelectronics "] repository = "https://github.com/brainelectronics/snippets2changelog" @@ -10,7 +10,7 @@ packages = [ { include = "snippets2changelog/**/*.py" } ] include = [ - { path = 'snippets2changelog/templates/snippet.md.template', format = 'sdist' } + { path = "snippets2changelog/templates/snippet.md.template", format = ["sdist", "wheel"] } ] exclude = ["snippets2changelog/out"] classifiers = [ @@ -33,7 +33,7 @@ enable = true format-jinja-imports = [ { module = "subprocess", item = "check_output" }, ] -format-jinja = """{{ check_output(["python", "-c", "from pathlib import Path; exec(Path('snippets2changelog/version.py').read_text()); print(__version__)"]).decode().strip() }}""" +format-jinja = """{{ check_output(["python3", "-c", "from pathlib import Path; exec(Path('snippets2changelog/version.py').read_text()); print(__version__)"]).decode().strip() }}""" # format-jinja = "{{ env.get('PROJECT_VERSION') }}" [tool.poetry.scripts] @@ -43,9 +43,11 @@ changelog-generator = 'snippets2changelog.cli:main' python = "^3.11" pyyaml = "~6.0" GitPython = "~3.1.43" +jinja2 = "^3.1.4" [tool.poetry.group.dev.dependencies] black = "*" +changelog2version = "^0.10" flake8 = "*" isort = "*" mypy = "*" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_common.py b/tests/test_common.py new file mode 100644 index 0000000..92c24fd --- /dev/null +++ b/tests/test_common.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +import json +import logging +from pathlib import Path +from typing import Any + +import pytest +from pytest import MonkeyPatch + +from snippets2changelog.common import ( + LOG_LEVELS, + collect_user_choice, + read_file, + save_file, +) + + +def test_log_levels() -> None: + assert LOG_LEVELS[0] == logging.CRITICAL + assert LOG_LEVELS[1] == logging.ERROR + assert LOG_LEVELS[2] == logging.WARNING + assert LOG_LEVELS[3] == logging.INFO + assert LOG_LEVELS[4] == logging.DEBUG + + +def test_something_that_involves_user_input(monkeypatch: MonkeyPatch) -> None: + # monkeypatch the "input" function, so that it returns "feature". + # This simulates the user entering "feature" in the terminal: + monkeypatch.setattr('builtins.input', lambda _: "feature") + + assert "feature" == collect_user_choice(question="what's it", options=["bugfix", "feature", "breaking"]) + + +@pytest.mark.parametrize( + "name, data", + [ + ("json", {"a": 1, "b": ["2", 3], "c": {"d": 4}}), + ("yml", {"a": 1, "b": ["2", 3], "c": {"d": 4}}), + ("yaml", {"a": 1, "b": ["2", 3], "c": {"d": 4}}), + ("txt", "Hello World"), + ("txt", "Hello World\nFrom testing"), + ("raise", "Something"), + ] +) +def test_read_save_json(name: str, data: dict[str, Any] | str, tmp_path: Path) -> None: + p = tmp_path / f"data.{name}" + + if name == "raise": + with pytest.raises(NotImplementedError): + save_file(content=data, path=p, render=name) + else: + save_file(content=data, path=p, render=name) + + if name == "txt": + name = "read" + if "\n" in data: + name = "readline" + + if name == "raise": + with pytest.raises(NotImplementedError): + assert read_file(path=p, parse=name) == data + else: + assert read_file(path=p, parse=name) == data if name != "readline" else data.split() + assert len(list(tmp_path.iterdir())) == 1