From c993a64c9c15a5021108d5a549f8bacd15b0adbe Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Mon, 6 May 2024 14:59:54 -0400 Subject: [PATCH] V2 (#8) * testing v2 * back to uuid * change path * map site packages * remove ending wildcard * disable pip version * trim down * cleanup * tweaks * update default * fix * remove inline comment * readme --- .github/dependabot.yml | 6 +- .github/workflows/test-dependents.yml | 17 +-- .github/workflows/test-pyrepo.yml | 152 ++++++++++++++------------ .github/workflows/upload-coverage.yml | 51 +++++---- .pre-commit-config.yaml | 17 +++ README.md | 43 ++++---- update_readme.py | 4 +- 7 files changed, 158 insertions(+), 132 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5c03104..0d8368e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,7 @@ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: github-actions + directory: / schedule: - interval: "weekly" + interval: weekly diff --git a/.github/workflows/test-dependents.yml b/.github/workflows/test-dependents.yml index 696ea3e..ac79eb6 100644 --- a/.github/workflows/test-dependents.yml +++ b/.github/workflows/test-dependents.yml @@ -11,43 +11,38 @@ on: dependency-ref: required: false type: string - default: "" description: Ref to checkout in dependency-repo. Defaults to HEAD in default branch. python-version: required: false type: string - default: "3.x" - description: "Python version to use. Passed to `actions/setup-python`." + default: 3.x + description: Python version to use. Passed to `actions/setup-python`. os: required: false type: string default: ubuntu-latest - description: "Operating system to use. Passed to `runs-on:`." + description: Operating system to use. Passed to `runs-on:`. host-extras: required: false type: string - default: "" description: Extras to use when installing host (package running this workflow). dependency-extras: required: false type: string - default: "test" + default: test description: Extras to use when installing dependency-repo. qt: required: false type: string - default: "" - description: "Version of Qt to install." + description: Version of Qt to install. post-install-cmd: required: false type: string - default: "" description: Command(s) to run after installing dependencies. pytest-args: required: false type: string - default: "" - description: "Additional arguments to pass to pytest. Can be used to specify paths or for for `-k`, `-x`, etc." + description: Additional arguments to pass to pytest. Can be used to specify paths or for for `-k`, `-x`, etc. jobs: test: diff --git a/.github/workflows/test-pyrepo.yml b/.github/workflows/test-pyrepo.yml index b3a7428..cbfdb99 100644 --- a/.github/workflows/test-pyrepo.yml +++ b/.github/workflows/test-pyrepo.yml @@ -7,109 +7,122 @@ on: python-version: required: false type: string - default: "3.x" - description: "Python version to use. Passed to `actions/setup-python`." + default: 3.x + description: Python version to use. Passed to `actions/setup-python`. os: required: false type: string default: ubuntu-latest - description: "Operating system to use. Passed to `runs-on:`." + description: Operating system to use. Passed to `runs-on:`. extras: required: false type: string default: test - description: "Package extras to install (may use commas for multiples `'test,docs'`). If you don't have an extra named 'test' you should change this." + description: > + Package extras to install (may use commas for multiples `'test,docs'`). + If you don't have an extra named 'test' you should change this. pip-install-flags: required: false type: string - default: "" - description: "Additional flags to pass to pip install. Can be used for `--editable`, `--no-deps`, etc." + description: > + Additional flags to pass to pip install. + Can be used for `--editable`, `--no-deps`, etc. pip-install-pre-release: required: false type: boolean default: false - description: "Whether to install pre-releases in the pip install phase with `--pre`." + description: Whether to install pre-releases in the pip install phase with `--pre`. # example >>> pip-install-pre-release: github.event_name == 'schedule' pip-install-min-reqs: required: false type: boolean default: false - description: "Whether to install the *minimum* declared dependency versions." + description: > + Whether to install the *minimum* declared dependency versions. pip-pre-installs: required: false type: string - default: "" - description: "Packages to install *before* calling `pip install .`" + default: '' + description: > + Packages to install *before* calling `pip install .` pip-post-installs: required: false type: string - default: "" - description: "Packages to install *after* `pip install .`. (these are called with `--force-reinstall`.)" + description: > + Packages to install *after* `pip install .`. + (these are called with `--force-reinstall`.) qt: required: false type: string - default: "" - description: "Version of qt to install (or none if blank). Will also install qt-libs and run tests headlessly if not blank." + description: > + Version of qt to install (or none if blank). + Will also install qt-libs and run tests headlessly if not blank. fetch-depth: required: false type: number default: 1 - description: "The number of commits to fetch. 0 indicates all history for all branches and tags." + description: > + The number of commits to fetch. 0 indicates all history for all branches and tags. python-cache-dependency-path: required: false type: string - default: "pyproject.toml" - description: "passed to `actions/setup-python`" + default: pyproject.toml + description: > + passed to `actions/setup-python` pytest-args: required: false type: string - default: "" - description: "Additional arguments to pass to pytest. Can be used to specify paths or for for `-k`, `-x`, etc." - pytest-cov-flags: - required: false - type: string - default: "--cov --cov-report=xml --cov-report=term-missing" - description: "Flags to pass to pytest-cov. Can be used for `--cov-fail-under`, `--cov-branch`, etc. Note: it's best to specify `[tool.coverage.run] source = ['your_package']`." + description: > + Additional arguments to pass to pytest. + Can be used to specify paths or for for `-k`, `-x`, etc. fail-on-coverage-error: required: false type: boolean default: true - description: "Fail the build if codecov action fails." + description: > + Fail the build if codecov action fails. hatch-build-hooks-enable: required: false type: boolean default: false - description: "Value for [`HATCH_BUILD_HOOKS_ENABLE`](https://hatch.pypa.io/latest/config/build/#environment-variables)." + description: > + Value for + [`HATCH_BUILD_HOOKS_ENABLE`](https://hatch.pypa.io/latest/config/build/#environment-variables). report-failures: required: false - type: boolean - default: false - description: "Whether to create a GitHub issue when a test fails. Good for cron jobs." - # example >>> report-failures: github.event_name == 'schedule' + type: string + description: > + (true or false). Whether to create a GitHub issue when a test fails. + If not set, will be true for scheduled runs, and false otherwise. cache-key: required: false type: string - default: "" - description: "Cache key to use for caching. If not set, no caching will be used." + description: > + Cache key to use for caching. If not set, no caching will be used. cache-path: required: false type: string - default: "" - description: "Path to cache. If not set, no caching will be used." + description: > + Path to cache. If not set, no caching will be used. cache-script: required: false type: string - default: "" - description: "Script to run to create the cache. If not set, no caching will be used." + description: > + Script to run to create the cache. If not set, no caching will be used. coverage-upload: required: false type: string - default: "codecov" - description: "Where to upload coverage data. Options: `'artifact'`, `'codecov'`. If using 'artifact', coverage must be sent to codecov in a separate workflow. (*see upload-coverage.yml*)" + default: codecov + description: > + How to upload coverage data. Options are `'artifact'`, `'codecov'`. + If using 'artifact', coverage must be sent to codecov in a separate + workflow. (*see upload-coverage.yml*) secrets: - codecov-token: + codecov_token: required: false - description: "Token for codecov-action. Only used if `pytest-cov-flags` is not empty and coverage-upload is 'codecov'." + description: > + Token for codecov-action. Only used if `pytest-cov-flags` + is not empty and coverage-upload is 'codecov'. jobs: test: @@ -119,29 +132,9 @@ jobs: HATCH_BUILD_HOOKS_ENABLE: ${{ inputs.hatch-build-hooks-enable }} MIN_REQS_TEST: ${{ inputs.pip-install-min-reqs }} USE_CACHE: ${{ inputs.cache-key != '' && inputs.cache-path != '' && 'true' || 'false' }} + PIP_DISABLE_PIP_VERSION_CHECK: 1 + REPORT_FAIL: ${{ inputs.report-failures && inputs.report-failures || (github.event_name == 'schedule' && 'true' || 'false')}} steps: - - name: Generate UUID - shell: bash - run: | - if [[ "$RUNNER_OS" == "Windows" ]]; then - echo "UUID=$(powershell -Command "[guid]::NewGuid().ToString()")" >> $GITHUB_ENV - else - echo "UUID=$(uuidgen)" >> $GITHUB_ENV - fi - - - name: Set COVERAGE_FILE - shell: bash - run: echo "COVERAGE_FILE=.coverage.$UUID" >> $GITHUB_ENV - - - name: Set CODECOV_TOKEN - shell: bash - run: | - if [ -n "${{ secrets.codecov-token }}" ]; then - echo "CODECOV_TOKEN=${{ secrets.codecov-token }}" >> $GITHUB_ENV - else - echo "CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }}" >> $GITHUB_ENV - fi - - name: Checkout ${{ github.repository }} uses: actions/checkout@v4 with: @@ -152,7 +145,7 @@ jobs: with: python-version: ${{ inputs.python-version }} cache-dependency-path: ${{ inputs.python-cache-dependency-path }} - cache: "pip" + cache: pip - run: python -m pip install --upgrade pip @@ -198,19 +191,21 @@ jobs: if: ${{ inputs.pip-post-installs != '' }} run: python -m pip install ${{ inputs.pip-post-installs }} --force-reinstall + - name: Install coverage + run: python -m pip install coverage + - name: Run tests if: ${{ inputs.qt == '' }} - run: python -m pytest --color=yes ${{ inputs.pytest-cov-flags }} ${{ inputs.pytest-args }} + run: coverage run -p -m pytest --color=yes ${{ inputs.pytest-args }} - name: Run tests (headless) if: ${{ inputs.qt != '' }} uses: aganders3/headless-gui@v2 with: - run: python -m pytest --color=yes ${{ inputs.pytest-cov-flags }} ${{ inputs.pytest-args }} + run: coverage run -p -m pytest --color=yes ${{ inputs.pytest-args }} - name: Report failure - # common usage might be: report-failures: github.event_name == 'schedule' - if: ${{ failure() && inputs.report-failures }} + if: ${{ failure() && env.REPORT_FAIL == 'true'}} uses: actions/github-script@v7 env: ACTION_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} @@ -246,17 +241,34 @@ jobs: console.log('A new issue was created to report a failing test.'); } + - name: Generage coverage report + if: inputs.coverage-upload == 'codecov' + run: | + python -m coverage combine --append + python -m coverage xml -o coverage.xml + python -m coverage report + - name: codecov v4 - if: inputs.pytest-cov-flags != '' && inputs.coverage-upload == 'codecov' + if: inputs.coverage-upload == 'codecov' uses: codecov/codecov-action@v4 with: fail_ci_if_error: ${{ inputs.fail-on-coverage-error }} verbose: true - token: ${{ env.CODECOV_TOKEN }} + token: ${{ secrets.codecov_token }} + + - name: Generate UUID + if: inputs.coverage-upload == 'artifact' + shell: bash + run: | + if [[ "$RUNNER_OS" == "Windows" ]]; then + echo "UUID=$(powershell -Command "[guid]::NewGuid().ToString()")" >> $GITHUB_ENV + else + echo "UUID=$(uuidgen)" >> $GITHUB_ENV + fi - name: Upload coverage data uses: actions/upload-artifact@v4 - if: inputs.pytest-cov-flags != '' && inputs.coverage-upload == 'artifact' + if: inputs.coverage-upload == 'artifact' with: name: covreport-${{ inputs.os }}-py${{ inputs.python-version }}-${{ env.UUID }} path: ./.coverage* diff --git a/.github/workflows/upload-coverage.yml b/.github/workflows/upload-coverage.yml index d8d30de..9842c40 100644 --- a/.github/workflows/upload-coverage.yml +++ b/.github/workflows/upload-coverage.yml @@ -7,38 +7,33 @@ on: required: false type: boolean default: true - description: "Fail if codecov action fails." + description: Fail if codecov action fails. artifact-pattern: required: false type: string - default: "covreport-*" - description: "glob pattern to the artifacts that should be downloaded for coverage reports. This should match the `name` you used for the `upload-artifact` step in the job that generates the coverage reports. (*This default matches the name in test-pyrepo.yml*)" + default: covreport-* + description: > + glob pattern to the artifacts that should be downloaded for coverage reports. + This should match the `name` you used for the `upload-artifact` step in the + job that generates the coverage reports. + (*This default matches the name in test-pyrepo.yml*) secrets: - codecov-token: + codecov_token: required: false - description: "Token for codecov-action." + description: Token for codecov-action. jobs: upload_coverage: name: Upload coverage runs-on: ubuntu-latest steps: - - name: Set CODECOV_TOKEN - shell: bash - run: | - # merge inherited secret with explicit secret - if [ -n "${{ secrets.codecov-token }}" ]; then - echo "CODECOV_TOKEN=${{ secrets.codecov-token }}" >> $GITHUB_ENV - else - echo "CODECOV_TOKEN=${{ secrets.CODECOV_TOKEN }}" >> $GITHUB_ENV - fi - name: Checkout ${{ github.repository }} uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: "3.x" + python-version: 3.x - name: Install coverage run: pip install coverage @@ -46,7 +41,7 @@ jobs: - name: Download coverage data uses: actions/download-artifact@v4 with: - pattern: ${{ inputs.artifact-pattern }} + pattern: ${{ inputs.artifact-pattern }} path: covreports merge-multiple: true @@ -55,15 +50,19 @@ jobs: REPO_NAME="${GITHUB_REPOSITORY##*/}" echo "[paths]" > ci_coveragerc echo "source =" >> ci_coveragerc - echo " D:\\a\\$REPO_NAME\\$REPO_NAME" >> ci_coveragerc - echo " D:\\a\\$REPO_NAME\\$REPO_NAME\\src" >> ci_coveragerc - echo " D:\\a\\$REPO_NAME\\$REPO_NAME\\src\\$REPO_NAME" >> ci_coveragerc - echo " /home/runner/work/$REPO_NAME/$REPO_NAME" >> ci_coveragerc - echo " /home/runner/work/$REPO_NAME/$REPO_NAME/src" >> ci_coveragerc - echo " /home/runner/work/$REPO_NAME/$REPO_NAME/src/$REPO_NAME" >> ci_coveragerc - echo " /Users/runner/work/$REPO_NAME/$REPO_NAME" >> ci_coveragerc - echo " /Users/runner/work/$REPO_NAME/$REPO_NAME/src" >> ci_coveragerc - echo " /Users/runner/work/$REPO_NAME/$REPO_NAME/src/$REPO_NAME" >> ci_coveragerc + # if src exists, add it to the pathmap + if [ -d "src" ]; then + echo " src/" >> ci_coveragerc + echo " D:\\a\\$REPO_NAME\\$REPO_NAME\\src" >> ci_coveragerc + echo " /home/runner/work/$REPO_NAME/$REPO_NAME/src" >> ci_coveragerc + echo " /Users/runner/work/$REPO_NAME/$REPO_NAME/src" >> ci_coveragerc + else + echo " ${GITHUB_WORKSPACE}" >> ci_coveragerc + echo " D:\\a\\$REPO_NAME\\$REPO_NAME" >> ci_coveragerc + echo " /home/runner/work/$REPO_NAME/$REPO_NAME" >> ci_coveragerc + echo " /Users/runner/work/$REPO_NAME/$REPO_NAME" >> ci_coveragerc + fi + echo " */site-packages/" >> ci_coveragerc cat ci_coveragerc python -Im coverage combine --rcfile=ci_coveragerc covreports python -Im coverage xml -o coverage.xml @@ -75,4 +74,4 @@ jobs: with: fail_ci_if_error: ${{ inputs.fail-on-coverage-error }} verbose: true - token: ${{ env.CODECOV_TOKEN }} + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..576afff --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +repos: + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.13.0 + hooks: + - id: pretty-format-yaml + args: [--autofix, --offset, '2'] + + - repo: https://github.com/rhysd/actionlint + rev: v1.6.27 + hooks: + - id: actionlint + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.40.0 + hooks: + - id: markdownlint + args: [--disable=MD013, --disable=MD024] diff --git a/README.md b/README.md index f59fac4..d1deeab 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ as well if you find them useful for your own projects. ## Run python tests -[`uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v1`](.github/workflows/test-pyrepo.yml) +[`uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2`](.github/workflows/test-pyrepo.yml) Standard workflow to setup python and test a python package, in the following order: @@ -39,24 +39,23 @@ Standard workflow to setup python and test a python package, in the following or | pip-install-min-reqs | boolean | False | Whether to install the *minimum* declared dependency versions. | | pip-pre-installs | string | | Packages to install *before* calling `pip install .` | | pip-post-installs | string | | Packages to install *after* `pip install .`. (these are called with `--force-reinstall`.) | -| qt | string | | Version of qt to install (or none if blank). Will also install qt-libs and run tests headlessly if not blank. | +| qt | string | | Version of qt to install (or none if blank). Will also install qt-libs and run tests headlessly if not blank. | | fetch-depth | number | 1 | The number of commits to fetch. 0 indicates all history for all branches and tags. | | python-cache-dependency-path | string | pyproject.toml | passed to `actions/setup-python` | | pytest-args | string | | Additional arguments to pass to pytest. Can be used to specify paths or for for `-k`, `-x`, etc. | -| pytest-cov-flags | string | --cov --cov-report=xml --cov-report=term-missing | Flags to pass to pytest-cov. Can be used for `--cov-fail-under`, `--cov-branch`, etc. Note: it's best to specify `[tool.coverage.run] source = ['your_package']`. | | fail-on-coverage-error | boolean | True | Fail the build if codecov action fails. | | hatch-build-hooks-enable | boolean | False | Value for [`HATCH_BUILD_HOOKS_ENABLE`](https://hatch.pypa.io/latest/config/build/#environment-variables). | -| report-failures | boolean | False | Whether to create a GitHub issue when a test fails. Good for cron jobs. | +| report-failures | string | | (true or false). Whether to create a GitHub issue when a test fails. If not set, will be true for scheduled runs, and false otherwise. | | cache-key | string | | Cache key to use for caching. If not set, no caching will be used. | | cache-path | string | | Path to cache. If not set, no caching will be used. | | cache-script | string | | Script to run to create the cache. If not set, no caching will be used. | -| coverage-upload | string | codecov | Where to upload coverage data. Options: `'artifact'`, `'codecov'`. If using 'artifact', coverage must be sent to codecov in a separate workflow. (*see upload-coverage.yml*) | +| coverage-upload | string | codecov | How to upload coverage data. Options are `'artifact'`, `'codecov'`. If using 'artifact', coverage must be sent to codecov in a separate workflow. (*see upload-coverage.yml*) | **Secrets:** | Input | Description | | --- | --- | -| codecov-token | Token for codecov-action. Only used if `pytest-cov-flags` is not empty and coverage-upload is 'codecov'. | +| codecov_token | Token for codecov-action. Only used if `pytest-cov-flags` is not empty and coverage-upload is 'codecov'. | See complete up-to-date list of options in [`test-pyrepo.yml`](.github/workflows/test-pyrepo.yml#L5) @@ -73,7 +72,7 @@ name: CI jobs: run_tests: - uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v1 + uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2 with: os: ${{ matrix.os }} python-version: ${{ matrix.python-version }} @@ -87,6 +86,11 @@ jobs: #### Testing depenency pre-releases on a schedule +Note that the default value for `report-failures` is `${{ github.event_name == 'schedule' }}`, so you don't need to specify it unless you want to override the +default. However, you may wish to use +`pip-install-pre-release: ${{ github.event_name == 'schedule' }}` +to test pre-release versions when triggered by a cron job. + ```yaml name: CI @@ -96,14 +100,13 @@ on: jobs: run_tests: - uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v1 + uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2 with: os: ${{ matrix.os }} python-version: ${{ matrix.python-version }} # Test pre-release versions when triggered by a schedule # and open an issue if the tests fail pip-install-pre-release: ${{ github.event_name == 'schedule' }} - report-failures: ${{ github.event_name == 'schedule' }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] @@ -123,7 +126,7 @@ name: CI jobs: run_tests: - uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v1 + uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2 with: os: ${{ matrix.os }} python-version: ${{ matrix.python-version }} @@ -147,12 +150,12 @@ name: CI jobs: tests: - uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v1 + uses: pyapp-kit/workflows/.github/workflows/test-pyrepo.yml@v2 with: os: ${{ matrix.os }} python-version: ${{ matrix.python-version }} # changing this to "artifact" prevents uploading to codecov here, - # instead it creates and uploads an artifact with the coverage data + # instead it uploads an artifact with the coverage data coverage-upload: artifact strategy: fail-fast: false @@ -160,18 +163,18 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.10", "3.11", "3.12"] - # now add an additional job that needs the previous job - # which uses the 'upload-coverage.yml' workflow + # now add an additional job to combine and upload the coverage upload_coverage: + if: always() needs: [tests] - uses: pyapp-kit/workflows/.github/workflows/upload-coverage.yml@v1 + uses: pyapp-kit/workflows/.github/workflows/upload-coverage.yml@v2 secrets: - codecov-token: ${{ secrets.CODECOV_TOKEN }} + codecov_token: ${{ secrets.CODECOV_TOKEN }} ``` ## Test Dependent Packages -[`uses: pyapp-kit/workflows/.github/workflows/test-dependents.yml@v1`](.github/workflows/test-dependents.yml) +[`uses: pyapp-kit/workflows/.github/workflows/test-dependents.yml@v2`](.github/workflows/test-dependents.yml) This workflow is useful when your package is a dependency of other packages, and you would like to ensure that your changes don't break those packages. @@ -214,7 +217,7 @@ name: CI jobs: test-package-b: - uses: pyapp-kit/workflows/.github/workflows/test-dependents.yml@v1 + uses: pyapp-kit/workflows/.github/workflows/test-dependents.yml@v2 with: os: ${{ matrix.os }} python-version: ${{ matrix.python-version }} @@ -230,7 +233,7 @@ jobs: ## Combine and Upload Coverage Artifacts -[`uses: pyapp-kit/workflows/.github/workflows/upload-coverage.yml@v1`](.github/workflows/upload-coverage.yml) +[`uses: pyapp-kit/workflows/.github/workflows/upload-coverage.yml@v2`](.github/workflows/upload-coverage.yml) This workflow is designed to be used in conjunction with the `test-pyrepo.yml` workflow when the `coverage-upload` input is set to `artifact`. @@ -245,5 +248,5 @@ when the `coverage-upload` input is set to `artifact`. | Input | Description | | --- | --- | -| codecov-token | Token for codecov-action. | +| codecov_token | Token for codecov-action. | diff --git a/update_readme.py b/update_readme.py index 45ce7d6..d70cd40 100644 --- a/update_readme.py +++ b/update_readme.py @@ -18,7 +18,7 @@ def _input_table(inputs: dict) -> str: default = str(v.get("default", "")).lstrip("'").rstrip("'") lines.append( f"| {k} | {v.get('type', '')} | " - f"{default} | {v.get('description', '')} |" + f"{default} | {v.get('description', '').rstrip()} |" ) return "\n".join(lines) @@ -26,7 +26,7 @@ def _input_table(inputs: dict) -> str: def _secrets_table(inputs: dict) -> str: lines = ["| Input | Description |", "| --- | --- |"] for k, v in inputs.items(): - lines.append(f"| {k} | {v.get('description', '')} |") + lines.append(f"| {k} | {v.get('description', '').rstrip()} |") return "\n".join(lines)