Skip to content

Commit

Permalink
GitHub Action: Allow reading version from pyproject.toml (#4294)
Browse files Browse the repository at this point in the history
Closes #4285

Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
  • Loading branch information
3 people committed Apr 5, 2024
1 parent c8f1a55 commit 3383f53
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Expand Up @@ -48,6 +48,9 @@

<!-- For example, Docker, GitHub Actions, pre-commit, editors -->

- Add a new option `use_pyproject` to the GitHub Action `psf/black`. This will read the
Black version from `pyproject.toml`. (#4294)

### Documentation

<!-- Major changes to documentation and policies. Small docs changes
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Expand Up @@ -27,6 +27,10 @@ inputs:
description: 'Python Version specifier (PEP440) - e.g. "21.5b1"'
required: false
default: ""
use_pyproject:
description: Read Black version specifier from pyproject.toml if `true`.
required: false
default: "false"
summary:
description: "Whether to add the output to the workflow summary"
required: false
Expand Down Expand Up @@ -70,5 +74,6 @@ runs:
INPUT_JUPYTER: ${{ inputs.jupyter }}
INPUT_BLACK_ARGS: ${{ inputs.black_args }}
INPUT_VERSION: ${{ inputs.version }}
INPUT_USE_PYPROJECT: ${{ inputs.use_pyproject }}
pythonioencoding: utf-8
shell: bash
103 changes: 100 additions & 3 deletions action/main.py
@@ -1,9 +1,11 @@
import os
import re
import shlex
import shutil
import sys
from pathlib import Path
from subprocess import PIPE, STDOUT, run
from typing import Union

ACTION_PATH = Path(os.environ["GITHUB_ACTION_PATH"])
ENV_PATH = ACTION_PATH / ".black-env"
Expand All @@ -13,12 +15,107 @@
JUPYTER = os.getenv("INPUT_JUPYTER") == "true"
BLACK_ARGS = os.getenv("INPUT_BLACK_ARGS", default="")
VERSION = os.getenv("INPUT_VERSION", default="")
USE_PYPROJECT = os.getenv("INPUT_USE_PYPROJECT") == "true"

BLACK_VERSION_RE = re.compile(r"^black([^A-Z0-9._-]+.*)$", re.IGNORECASE)
EXTRAS_RE = re.compile(r"\[.*\]")


def determine_version_specifier() -> str:
"""Determine the version of Black to install.
The version can be specified either via the `with.version` input or via the
pyproject.toml file if `with.use_pyproject` is set to `true`.
"""
if USE_PYPROJECT and VERSION:
print(
"::error::'with.version' and 'with.use_pyproject' inputs are "
"mutually exclusive.",
file=sys.stderr,
flush=True,
)
sys.exit(1)
if USE_PYPROJECT:
return read_version_specifier_from_pyproject()
elif VERSION and VERSION[0] in "0123456789":
return f"=={VERSION}"
else:
return VERSION


def read_version_specifier_from_pyproject() -> str:
if sys.version_info < (3, 11):
print(
"::error::'with.use_pyproject' input requires Python 3.11 or later.",
file=sys.stderr,
flush=True,
)
sys.exit(1)

import tomllib # type: ignore[import-not-found,unreachable]

try:
with Path("pyproject.toml").open("rb") as fp:
pyproject = tomllib.load(fp)
except FileNotFoundError:
print(
"::error::'with.use_pyproject' input requires a pyproject.toml file.",
file=sys.stderr,
flush=True,
)
sys.exit(1)

version = pyproject.get("tool", {}).get("black", {}).get("required-version")
if version is not None:
return f"=={version}"

arrays = [
pyproject.get("project", {}).get("dependencies"),
*pyproject.get("project", {}).get("optional-dependencies", {}).values(),
]
for array in arrays:
version = find_black_version_in_array(array)
if version is not None:
break

if version is None:
print(
"::error::'black' dependency missing from pyproject.toml.",
file=sys.stderr,
flush=True,
)
sys.exit(1)

return version


def find_black_version_in_array(array: object) -> Union[str, None]:
if not isinstance(array, list):
return None
try:
for item in array:
# Rudimentary PEP 508 parsing.
item = item.split(";")[0]
item = EXTRAS_RE.sub("", item).strip()
if item == "black":
print(
"::error::Version specifier missing for 'black' dependency in "
"pyproject.toml.",
file=sys.stderr,
flush=True,
)
sys.exit(1)
elif m := BLACK_VERSION_RE.match(item):
return m.group(1).strip()
except TypeError:
pass

return None


run([sys.executable, "-m", "venv", str(ENV_PATH)], check=True)

version_specifier = VERSION
if VERSION and VERSION[0] in "0123456789":
version_specifier = f"=={VERSION}"
version_specifier = determine_version_specifier()
if JUPYTER:
extra_deps = "[colorama,jupyter]"
else:
Expand Down
23 changes: 18 additions & 5 deletions docs/integrations/github_actions.md
Expand Up @@ -32,12 +32,15 @@ We recommend the use of the `@stable` tag, but per version tags also exist if yo
that. Note that the action's version you select is independent of the version of _Black_
the action will use.

The version of _Black_ the action will use can be configured via `version`. This can be
any
The version of _Black_ the action will use can be configured via `version` or read from
the `pyproject.toml` file. `version` can be any
[valid version specifier](https://packaging.python.org/en/latest/glossary/#term-Version-Specifier)
or just the version number if you want an exact version. The action defaults to the
latest release available on PyPI. Only versions available from PyPI are supported, so no
commit SHAs or branch names.
or just the version number if you want an exact version. To read the version from the
`pyproject.toml` file instead, set `use_pyproject` to `true`. This will first look into
the `tool.black.required-version` field, then the `project.dependencies` array and
finally the `project.optional-dependencies` table. The action defaults to the latest
release available on PyPI. Only versions available from PyPI are supported, so no commit
SHAs or branch names.

If you want to include Jupyter Notebooks, _Black_ must be installed with the `jupyter`
extra. Installing the extra and including Jupyter Notebook files can be configured via
Expand Down Expand Up @@ -70,3 +73,13 @@ If you want to match versions covered by Black's
src: "./src"
version: "~= 22.0"
```

If you want to read the version from `pyproject.toml`, set `use_pyproject` to `true`:

```yaml
- uses: psf/black@stable
with:
options: "--check --verbose"
src: "./src"
use_pyproject: true
```

0 comments on commit 3383f53

Please sign in to comment.