Skip to content

Commit 3383f53

Browse files
srittauhugovkJelleZijlstra
authored
GitHub Action: Allow reading version from pyproject.toml (psf#4294)
Closes psf#4285 Co-authored-by: Hugo van Kemenade <[email protected]> Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent c8f1a55 commit 3383f53

File tree

4 files changed

+126
-8
lines changed

4 files changed

+126
-8
lines changed

CHANGES.md

+3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848

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

51+
- Add a new option `use_pyproject` to the GitHub Action `psf/black`. This will read the
52+
Black version from `pyproject.toml`. (#4294)
53+
5154
### Documentation
5255

5356
<!-- Major changes to documentation and policies. Small docs changes

action.yml

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ inputs:
2727
description: 'Python Version specifier (PEP440) - e.g. "21.5b1"'
2828
required: false
2929
default: ""
30+
use_pyproject:
31+
description: Read Black version specifier from pyproject.toml if `true`.
32+
required: false
33+
default: "false"
3034
summary:
3135
description: "Whether to add the output to the workflow summary"
3236
required: false
@@ -70,5 +74,6 @@ runs:
7074
INPUT_JUPYTER: ${{ inputs.jupyter }}
7175
INPUT_BLACK_ARGS: ${{ inputs.black_args }}
7276
INPUT_VERSION: ${{ inputs.version }}
77+
INPUT_USE_PYPROJECT: ${{ inputs.use_pyproject }}
7378
pythonioencoding: utf-8
7479
shell: bash

action/main.py

+100-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import os
2+
import re
23
import shlex
34
import shutil
45
import sys
56
from pathlib import Path
67
from subprocess import PIPE, STDOUT, run
8+
from typing import Union
79

810
ACTION_PATH = Path(os.environ["GITHUB_ACTION_PATH"])
911
ENV_PATH = ACTION_PATH / ".black-env"
@@ -13,12 +15,107 @@
1315
JUPYTER = os.getenv("INPUT_JUPYTER") == "true"
1416
BLACK_ARGS = os.getenv("INPUT_BLACK_ARGS", default="")
1517
VERSION = os.getenv("INPUT_VERSION", default="")
18+
USE_PYPROJECT = os.getenv("INPUT_USE_PYPROJECT") == "true"
19+
20+
BLACK_VERSION_RE = re.compile(r"^black([^A-Z0-9._-]+.*)$", re.IGNORECASE)
21+
EXTRAS_RE = re.compile(r"\[.*\]")
22+
23+
24+
def determine_version_specifier() -> str:
25+
"""Determine the version of Black to install.
26+
27+
The version can be specified either via the `with.version` input or via the
28+
pyproject.toml file if `with.use_pyproject` is set to `true`.
29+
"""
30+
if USE_PYPROJECT and VERSION:
31+
print(
32+
"::error::'with.version' and 'with.use_pyproject' inputs are "
33+
"mutually exclusive.",
34+
file=sys.stderr,
35+
flush=True,
36+
)
37+
sys.exit(1)
38+
if USE_PYPROJECT:
39+
return read_version_specifier_from_pyproject()
40+
elif VERSION and VERSION[0] in "0123456789":
41+
return f"=={VERSION}"
42+
else:
43+
return VERSION
44+
45+
46+
def read_version_specifier_from_pyproject() -> str:
47+
if sys.version_info < (3, 11):
48+
print(
49+
"::error::'with.use_pyproject' input requires Python 3.11 or later.",
50+
file=sys.stderr,
51+
flush=True,
52+
)
53+
sys.exit(1)
54+
55+
import tomllib # type: ignore[import-not-found,unreachable]
56+
57+
try:
58+
with Path("pyproject.toml").open("rb") as fp:
59+
pyproject = tomllib.load(fp)
60+
except FileNotFoundError:
61+
print(
62+
"::error::'with.use_pyproject' input requires a pyproject.toml file.",
63+
file=sys.stderr,
64+
flush=True,
65+
)
66+
sys.exit(1)
67+
68+
version = pyproject.get("tool", {}).get("black", {}).get("required-version")
69+
if version is not None:
70+
return f"=={version}"
71+
72+
arrays = [
73+
pyproject.get("project", {}).get("dependencies"),
74+
*pyproject.get("project", {}).get("optional-dependencies", {}).values(),
75+
]
76+
for array in arrays:
77+
version = find_black_version_in_array(array)
78+
if version is not None:
79+
break
80+
81+
if version is None:
82+
print(
83+
"::error::'black' dependency missing from pyproject.toml.",
84+
file=sys.stderr,
85+
flush=True,
86+
)
87+
sys.exit(1)
88+
89+
return version
90+
91+
92+
def find_black_version_in_array(array: object) -> Union[str, None]:
93+
if not isinstance(array, list):
94+
return None
95+
try:
96+
for item in array:
97+
# Rudimentary PEP 508 parsing.
98+
item = item.split(";")[0]
99+
item = EXTRAS_RE.sub("", item).strip()
100+
if item == "black":
101+
print(
102+
"::error::Version specifier missing for 'black' dependency in "
103+
"pyproject.toml.",
104+
file=sys.stderr,
105+
flush=True,
106+
)
107+
sys.exit(1)
108+
elif m := BLACK_VERSION_RE.match(item):
109+
return m.group(1).strip()
110+
except TypeError:
111+
pass
112+
113+
return None
114+
16115

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

19-
version_specifier = VERSION
20-
if VERSION and VERSION[0] in "0123456789":
21-
version_specifier = f"=={VERSION}"
118+
version_specifier = determine_version_specifier()
22119
if JUPYTER:
23120
extra_deps = "[colorama,jupyter]"
24121
else:

docs/integrations/github_actions.md

+18-5
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@ We recommend the use of the `@stable` tag, but per version tags also exist if yo
3232
that. Note that the action's version you select is independent of the version of _Black_
3333
the action will use.
3434

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

4245
If you want to include Jupyter Notebooks, _Black_ must be installed with the `jupyter`
4346
extra. Installing the extra and including Jupyter Notebook files can be configured via
@@ -70,3 +73,13 @@ If you want to match versions covered by Black's
7073
src: "./src"
7174
version: "~= 22.0"
7275
```
76+
77+
If you want to read the version from `pyproject.toml`, set `use_pyproject` to `true`:
78+
79+
```yaml
80+
- uses: psf/black@stable
81+
with:
82+
options: "--check --verbose"
83+
src: "./src"
84+
use_pyproject: true
85+
```

0 commit comments

Comments
 (0)