diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..61a93e3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# pyOpenSci packaging template Changelog + +## [Unreleased] + +* Update release workflow to follow PyPA / PyPI recommended practices (@lwasser, #48) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..857a206 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing to the pyOpenSci Python package template + +To work on the template locally, you can call the copier template directly. Note that by default, `copier` uses the latest tag in your commit history. To ensure it uses the latest commit on your current active branch use: + +`copier copy -r HEAD /path/to/your/template destination-dir` + +If you want to test it against the latest tag in your local commit history, you can use: + +`copier copy /path/to/your/template destination-dir` + +## Run the tests + +You can use Hatch to run all of the tests for the template: + +`hatch run test:run` diff --git a/README.md b/README.md index 24873f1..a2b4a70 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ To use this template: 1. Install copier using [pipx](https://pipx.pypa.io/stable/) or pip preferably with a [virtual environment](https://www.pyopensci.org/python-package-guide/CONTRIBUTING.html#create-a-virtual-environment). Global Installation: - + ```console pipx install copier ``` - + or Environment specific installation: ```console @@ -37,8 +37,9 @@ To use this template: ```console copier copy gh:pyopensci/pyos-package-template path/here ``` - - The command below will create the package directory in your current working directory. + + The command below will create the package directory in your current working directory. + ```console copier copy gh:pyopensci/pyos-package-template . ``` @@ -48,14 +49,13 @@ To use this template: as your source. You can read more about generating your project in the [copier documentation](https://copier.readthedocs.io/en/stable/generating/). - ## Run the template workflow -Once you have installed copier, you are ready to create your Python package template. -First, run the command below from your favorite shell. Note that this is copying our template from GitHub so it +Once you have installed copier, you are ready to create your Python package template. +First, run the command below from your favorite shell. Note that this is copying our template from GitHub so it will require internet access to run properly. -The command below will create the package directory in your current working directory. +The command below will create the package directory in your current working directory. `copier copy gh:pyopensci/pyos-package-template .` @@ -64,13 +64,13 @@ If you wish to create the package directory in another directory you can specify `copier copy gh:pyopensci/pyos-package-template dirname-here` ## Template overview -The copier template will ask you a series of questions which you can respond to. The questions will -help you customize the template. + +The copier template will ask you a series of questions which you can respond to. The questions will +help you customize the template. Below is what the template workflow will look like when you run it. In the example below, you "fully customize" the template. - ```console ➜ copier copy gh:pyopensci/pyos-package-template . 🎤 Who is the copyright holder, for example, yourself or your organization? Used in the license diff --git a/copier.yml b/copier.yml index fb1b199..dc07813 100644 --- a/copier.yml +++ b/copier.yml @@ -77,7 +77,7 @@ documentation: type: str help: "Do you want to include documentation for your project and which framework do you want to use?" choices: - "Sphinx (https://www.pyopensci.org/pyos-sphinx-theme)": sphinx + "Sphinx (https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html)": sphinx "mkdocs-material (https://squidfunk.github.io/mkdocs-material)": mkdocs No: "" default: "{% if use_default != 'minimal' %}sphinx{% else %}{% endif %}" @@ -111,7 +111,7 @@ license: type: str help: | Which license do you want to use? Includes a LICENSE file in the repository root. - For more information, see: + For more information, see: - https://www.pyopensci.org/python-package-guide/documentation/repository-files/license-files.html - https://opensource.org/licenses choices: diff --git a/template/pyproject.toml.jinja b/template/pyproject.toml.jinja index 42aea9b..c3f90c4 100644 --- a/template/pyproject.toml.jinja +++ b/template/pyproject.toml.jinja @@ -208,7 +208,7 @@ dependencies = [ detached = true [tool.hatch.envs.style.scripts] -docstrings = "pydoclint" +docstrings = "pydoclint src/ tests/" code = "ruff check {args}" format = "ruff format {args}" check = ["docstrings", "code"] diff --git a/template/{% if use_git and dev_platform == 'GitHub' %}.github{% endif %}/workflows/release.yml b/template/{% if use_git and dev_platform == 'GitHub' %}.github{% endif %}/workflows/release.yml index 34fb5af..0c2c107 100644 --- a/template/{% if use_git and dev_platform == 'GitHub' %}.github{% endif %}/workflows/release.yml +++ b/template/{% if use_git and dev_platform == 'GitHub' %}.github{% endif %}/workflows/release.yml @@ -1,53 +1,77 @@ -name: CD +name: Publish to PyPI on: - push: - tags: - - 'v?[0-9]+.[0-9]+.[0-9]+' - - 'v?[0-9]+.[0-9]+.[0-9]+(a|b|rc|post|dev)[0-9]+' + release: + types: [published] jobs: prerequisites: uses: ./.github/workflows/test.yml - - release: + # Setup build separate from publish for added security + # See https://github.com/pypa/gh-action-pypi-publish/issues/217#issuecomment-1965727093 + build: needs: [prerequisites] - strategy: - matrix: - os: [ubuntu-latest] - python-version: ["3.12"] - runs-on: ${{ matrix.os }} - permissions: - # Write permissions are needed to create OIDC tokens. - id-token: write - # Write permissions are needed to make GitHub releases. - contents: write - + runs-on: ubuntu-latest + # Environment is encouraged for increased security + environment: build steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} + - name: Checkout + uses: actions/checkout@v4 + with: + # This fetch element is only important if you are use SCM based + # versioning (that looks at git tags to gather the version) + fetch-depth: 100 + # Need the tags so that setuptools-scm can form a valid version number + - name: Fetch git tags + run: git fetch origin 'refs/tags/*:refs/tags/*' - - name: Install hatch - uses: pypa/hatch@install + - name: Setup Python + uses: actions/setup-python@v5 + with: + # You can modify what version of Python you want to use for your release + python-version: "3.11" - - name: Build package - run: hatch build + # Security recommends we should pin deps. Should we pin the workflow version? + - name: Install hatch + uses: pypa/hatch@a3c83ab3d481fbc2dc91dd0088628817488dd1d5 - # We rely on a trusted publisher configuration being present on PyPI, - # see https://docs.pypi.org/trusted-publishers/. - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + - name: Build package using Hatch + run: | + hatch build + echo "" + echo "Generated files:" + ls -lh dist/ - - name: GH release - uses: softprops/action-gh-release@v2 - with: - body: > - Please see - https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/CHANGELOG.md - for the full release notes. - draft: false - prerelease: false + # Store an artifact of the build to use in the publish step below + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + if-no-files-found: error + publish: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + # Modify the repo name below to be your project's repo name. + if: github.repository_owner == "{{ username }}" + needs: + - build + runs-on: ubuntu-latest + # Environment required here for trusted publisher + environment: + name: pypi + # Modify the url to be the name of your package + url: https://pypi.org/p/${{ package_name }} + permissions: + id-token: write # this permission is mandatory for PyPI publishing + steps: + - name: Download dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + merge-multiple: true + - name: Publish package to PyPI + # Only publish to real PyPI on release + if: github.event_name == 'release' + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/tests/conftest.py b/tests/conftest.py index 888f6dd..3b70fa8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ -"""Ruff is forcing me to write a docstring for conftest.py.""" +"""Fixtures used in our test suite for the pyOpenSci Python package +template.""" import shutil from pathlib import Path @@ -16,6 +17,7 @@ COPIER_CONFIG_PATH = Path(__file__).parents[1] / "copier.yml" INCLUDES_PATH = Path(__file__).parents[1] / "includes" + def _load_copier_config() -> dict: yaml = YAML(typ="safe") with COPIER_CONFIG_PATH.open("r") as yfile: @@ -29,6 +31,7 @@ def _load_copier_config() -> dict: # pytest hooks # -------------------------------------------------- + def pytest_addoption(parser: "Parser") -> None: """Add options to pytest.""" parser.addoption( @@ -40,10 +43,12 @@ def pytest_addoption(parser: "Parser") -> None: "otherwise, use a temporary directoy and remove it afterwards.", ) + # -------------------------------------------------- # Fixtures - autouse # -------------------------------------------------- + @pytest.fixture(scope="session", autouse=True) def cleanup_hatch_envs( pytestconfig: pytest.Config, @@ -67,10 +72,12 @@ def cleanup_hatch_envs( finally: shutil.rmtree(hatch_dir, ignore_errors=True) + # --------------------------------------------- # Fixtures - exports # --------------------------------------------- + @pytest.fixture(scope="session") def monkeypatch_session() -> Generator[MonkeyPatch, None, None]: """Monkeypatch you can use with a session scoped fixture."""