From 7f1ea561a6d7fee15d74871bb7fc9ead1a23deaf Mon Sep 17 00:00:00 2001 From: Landon Bouma Date: Thu, 28 Dec 2023 20:47:06 -0600 Subject: [PATCH 1/4] Docs: Update comment --- .github/workflows/release-smoke-test.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-smoke-test.yml b/.github/workflows/release-smoke-test.yml index 48c89e5c..8224068e 100644 --- a/.github/workflows/release-smoke-test.yml +++ b/.github/workflows/release-smoke-test.yml @@ -33,12 +33,9 @@ on: # *** env: - # Lest: "The `python-version` input is not set. The version - # of Python currently in `PATH` will be used." and - # "Cache paths are empty. Please check the previous - # logs and make sure that the python version is specified" - # TRACK: https://github.com/actions/python-versions/releases - # USYNC: workflows/ (PYTHON_VERSION), tox.ini (basepython), Makefile (VENV_PYVER). + # USYNC: When you update Python versions, update the following: + # workflows/ (PYTHON_VERSION), tox.ini (basepython), Makefile (VENV_PYVER) + # - REFER: Track versions at https://github.com/actions/python-versions/releases # BWARE: Trailing zeroes disappear unless quoted. PYTHON_VERSION: "3.11" From 7080bbcb0cffaeccdbd9c24a9bbad3d0d1d0ac41 Mon Sep 17 00:00:00 2001 From: Landon Bouma Date: Thu, 28 Dec 2023 21:20:27 -0600 Subject: [PATCH 2/4] Refactor: CI: Reorder sections per convention - `if`, then `shell` or `uses`, before `with` or `run`, etc. --- .github/workflows/release-pypi.yml | 37 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index 4bd55f90..7ba76417 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -82,6 +82,8 @@ jobs: # REFER: GITHUB_REF is the branch ref or tag that triggered the workflow run. # https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables - name: Probe version tag (not workflow_dispatch) + if: github.event_name != 'workflow_dispatch' + shell: bash run: | VERSION_TAG="${GITHUB_REF#refs/tags/}" @@ -100,21 +102,19 @@ jobs: fi echo "VERSION_TAG=${VERSION_TAG}" >> "${GITHUB_ENV}" - shell: bash - if: github.event_name != 'workflow_dispatch' - name: Checkout repository (not workflow_dispatch) - uses: actions/checkout@v4 if: github.event_name != 'workflow_dispatch' + uses: actions/checkout@v4 - name: Checkout repository (is workflow_dispatch) + if: github.event_name == 'workflow_dispatch' uses: actions/checkout@v4 with: ref: ${{ github.event.repository.default_branch }} # Grab everything, because we might need to switch branches # in the next step. fetch-depth: 0 - if: github.event_name == 'workflow_dispatch' # Check the default branch HEAD for a version tag. # - If it's not found, try the pre-release branch. @@ -198,8 +198,8 @@ jobs: # ~/.kit/sh/sh-git-nubs/bin/git-nubs.sh # git_latest_version_tag - name: Fail if VERSION_TAG undiscoverable - run: exit 1 if: env.VERSION_TAG == '' + run: exit 1 # Double-check tag is version-formatted, esp. if 'workflow_dispatch' # (which got whatever was tagged on HEAD). @@ -221,6 +221,7 @@ jobs: shell: bash - name: Set pipx index URL + shell: bash run: | test_pypi_url="https://test.pypi.org/simple" # REFER: https://packaging.python.org/en/latest/guides/using-testpypi/ @@ -246,7 +247,6 @@ jobs: && index_url_pipx="" \ || index_url_pipx="--index-url ${test_pypi_url} ${pipx_pip_args}" echo INDEX_PIPX=${index_url_pipx} >> "${GITHUB_ENV}" - shell: bash # MAYBE/2023-11-11: Prefer `fromJSON(shell-bool)` vs. `== 'true|false'`? - name: Announcement — ${{ @@ -261,8 +261,8 @@ jobs: # github.repository is, e.g.,"doblabs/easy-as-pypi". - name: Probe package name - run: echo "PACKAGE_NAME=$(basename ${{ github.repository }})" >> "${GITHUB_ENV}" shell: bash + run: echo "PACKAGE_NAME=$(basename ${{ github.repository }})" >> "${GITHUB_ENV}" # # Local, and GHA: 1.2.0. # - name: pipx --version @@ -271,22 +271,22 @@ jobs: # On GHA: pipx: error: unrecognized arguments: easy-as-pypi==1.1.1a20 - name: Check if previously released + shell: bash run: | IS_RELEASED=false echo "pip install ${INDEX_PIP} ${PACKAGE_NAME}==${VERSION}" pip install ${INDEX_PIP} ${PACKAGE_NAME}==${VERSION} \ && IS_RELEASED=true echo "IS_RELEASED=${IS_RELEASED}" >> "${GITHUB_ENV}" - shell: bash # Determine if previously released (and skip re-releasing if so). - name: View release + if: env.IS_RELEASED == 'true' run: | echo "gh release view ${VERSION_TAG}" gh release view ${VERSION_TAG} env: GH_TOKEN: ${{ github.token }} - if: env.IS_RELEASED == 'true' # Here's how you might publish using Poetry, but this approach # requires a PyPI token. @@ -363,20 +363,17 @@ jobs: wrangle-ci-tags: name: Wrangle (read) CI tags - uses: ./.github/workflows/ci-tags-wrangle.yml needs: [prepare-publish] if: ${{ ! cancelled() && needs.prepare-publish.result == 'success' && needs.prepare-publish.outputs.IS_RELEASED == 'false' }} + uses: ./.github/workflows/ci-tags-wrangle.yml with: prerelease: ${{ needs.prepare-publish.outputs.PRERELEASE == 'true' }} # *** poetry-publish: - - runs-on: ubuntu-latest - needs: [prepare-publish, wrangle-ci-tags] # Easiest approach would be to skip if IS_RELEASED, e.g., @@ -390,6 +387,8 @@ jobs: && needs.prepare-publish.outputs.IS_RELEASED == 'false' && needs.wrangle-ci-tags.outputs.old-inhibit-release-pypi == 'false' }} + runs-on: ubuntu-latest + # CXREF: PyPI recommends using an environment ('release') # to keep non-admin organization members from publishing. # - BWARE: The environment configuration's branch protection rules @@ -460,9 +459,6 @@ jobs: # https://github.com/pypa/gh-action-pypi-publish - name: Publish package distributions to test.PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ # Only publish pre-release packages to test.PyPI. # # - It's not prudent to publish normal packages to test.PyPI, @@ -481,12 +477,15 @@ jobs: # - So for either type or release cascade, whether 'alpha' # or 'patch', we always publish to the same package index. if: env.PRERELEASE == 'true' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 # Unlike test.PyPI, only publish normal releases to PyPI proper, # never alphas. if: env.PRERELEASE == 'false' + uses: pypa/gh-action-pypi-publish@release/v1 # *** @@ -512,9 +511,9 @@ jobs: # immediately, but it takes a bit for pip to find it (~12 sec). # - SAVVY/2023-10-08: Huh, 17s not always enough? - name: Sleep for 13 seconds - run: sleep 13s - shell: bash if: needs.prepare-publish.outputs.IS_RELEASED == 'false' + shell: bash + run: sleep 13s # *** From 40bb586fea2b53a7005e89c8aac6167e925ce5fb Mon Sep 17 00:00:00 2001 From: Landon Bouma Date: Fri, 29 Dec 2023 05:21:30 -0600 Subject: [PATCH 3/4] Docs: Update README --- README.rst | 147 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 125 insertions(+), 22 deletions(-) diff --git a/README.rst b/README.rst index 1627557d..ba5dd321 100644 --- a/README.rst +++ b/README.rst @@ -49,39 +49,142 @@ Overview Boilerplate for modern, bathroom-tub-included Python projects. -The boilerplate itself is installable and includes minimalist Click CLI. +This project is installable and includes a minimalist +`Click `__ CLI:: -But most of the gold is buried within: + pip install easy-as-pypi + + easy-as-pypi + +But this project is so much more! + +This is *living* boilerplate. + +- Use this project to start a new Python project. + +- Use this project to manage your CI checks. + +- Use this project to automate your releases. + +- And keep your project up-to-date with the latest "boilerplate" by running:: + + bin/update-faithful + +- Because boilerplate is *never* static! + +Here are the selling points, because we're all here to make gold: - Modern Poetry and ``pyproject.toml`` setup. -- Supports cascading editable installs (install current project in - editable mode, as well as any dependencies you might have source - for locally; boilerplate manages alternative ``pyproject.toml`` - automatically). + - Much of any project's ``pyproject.toml`` is already prescribed, like, + 90% of your projects' ``pyproject.toml`` is the same between all your + projects. + + - So this project *generates* ``pyproject.toml``. + + It uses a simple template, located at ``.pyproject.project.tmpl``, + and applies it to a base ``pyproject.toml`` template + (named ``.pyproject.tmpl``). + + - Use ``.pyproject.project.tmpl`` to add the few bits that are unique + to your project, then run ``bin/update-faithful`` to generate + ``pyproject.toml`` for your project. + + - Most of ``pyproject.toml`` is already boilerplate — think of + ``pytest`` options, ``black`` options, ``flake8`` options, + ``isort`` options, as well as ``poetry install --with`` + dependencies, like those for testing, or creating docs, + etc. — these are usually the same for all of your projects! + So why repeat yourself? + + As such, we consider ``pyproject.toml`` to be essentially + boilerplate — it's half boilerplate, half project-specific, + and generated when you run ``bin/update-faithful``. + +- *Editable* installs. + + - Run ``make develop`` to install your project in editable mode, + as well as any dependencies that you have sources for locally. + + - This project (the boilerplate) will manage an alternative + ``.pyproject-editable/pyproject.toml`` automatically. + + - Edit ``Makefile`` to tell it which projects are *editable* + (specifically the ``EDITABLE_PJS`` environ). + +- Pre-release installs. + + - Publish pre-release builds to https://test.pypi.org + + - This project (the boilerplate) manages an alternative + ``.pyproject-prerelease/pyproject.toml`` and ``poetry.lock``, + as well as using the appropriate ``pip install --pre`` options, + so you can test changes to your stack before releasing to PyPI, + +- All the lints. + + - Run ``make lint`` to run all the lints: ``black``, ``flake8``, + ``isort``, ``pydocstyle``, ``doc8``, ``linkcheck``, + ``poetry check``, and ``twine check``. + +- All the tests. + + - Run ``tox`` to test against all supported Python versions. + +- All the other dev tasks. + + - Run ``make develop`` to create an editable virtualenv (using local sources). + + - Then you can run your app locally, against local sources, + by calling ``make test``, or ``pytest``. + + - You can also use ``pyenv`` and modify the Python version in + ``Makefile`` to test against different Python versions, e.g.,:: + + pyenv install -s 3.12 + + sed -i 's/^VENV_PYVER.*/VENV_PYVER ?= 3.12/' Makefile + + make develop + + workon + + make test + + - Run ``make install`` to create a release virtualenv (using PyPI sources), + so you can test what end-users will experience (though not the same as + publishing to PyPI and running ``pip install``, but close). + + - Run ``make docs`` to generate docs for *ReadTheDocs*. + + - Run ``make coverage`` to, ya know, run coverage. + + - Also Babel support, e.g., run ``make babel-compile`` to localize user + messages. -- All the lints: ``black``, ``flake8``, ``isort``, ``pydocstyle``, - ``doc8``, ``linkcheck``, ``poetry check``, and ``twine check``. +- All the CI. -- Test against all active Python versions and lint using ``tox``. + - Look under ``.github/workflows`` for what some might consider an + over-engineered GitHub Actions workflow. -- Run tasks, tests, and setup virtualenvs quickly using ``make`` - commands in your active virtualenv. + But that's really where's there gold: - - Generate docs for *ReadTheDocs*. + - When you push a branch, checks run. - - Localize user messages using ``Babel``. + - When you push a version tag, a release happens. - - Easily install to shared or isolated virtualenvs. + - After checks run, and after a release is published, + a "smoke test" runs: Both ``pip`` and ``pipx`` are + called to verify your package is viable. -- GitHub Actions linting, testing, and coverage upload. + - And lemme tell you, Poetry might work, publishing + to PyPI might work, but that still doesn't mean + the release works. The smoke test lets you know + it works for certain. -Most of the files are designed to be hard linked from the derived -projects themselves, as they won't need to be customized (such as -``Makefile``). + - And if you maintain multiple projects, the CI job + will dispatch and kick-off the release of the next + downstream project. -- Then when the boilerplate changes, you can just commit the - changes in the derived project, call them "dependency updates" - or something, and not have to worry about merging changes manually - (and running ``meld`` or something). +Point being, this is the Python "boilerplate" to end all boilerplate. From 9987135ef7e11bf00b6a47faecdfa10b3ac3751a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 21:57:45 +0000 Subject: [PATCH 4/4] Build(deps): Bump jinja2 from 3.1.2 to 3.1.3 in /.pyproject-prerelease Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3) --- updated-dependencies: - dependency-name: jinja2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .pyproject-prerelease/poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pyproject-prerelease/poetry.lock b/.pyproject-prerelease/poetry.lock index d16dc692..71d19ca8 100644 --- a/.pyproject-prerelease/poetry.lock +++ b/.pyproject-prerelease/poetry.lock @@ -807,13 +807,13 @@ trio = ["async_generator", "trio"] [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies]