Skip to content

Commit 4e2f53d

Browse files
Harden GitHub Actions workflows with zizmor (#144)
* [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.14 → v0.15.2](astral-sh/ruff-pre-commit@v0.14.14...v0.15.2) - [github.com/kynan/nbstripout: 0.9.0 → 0.9.1](kynan/nbstripout@0.9.0...0.9.1) * Fix ty check for CoiledFunction construction * Harden GitHub Actions workflows with zizmor * Fix typing regressions after merging main --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent f891a0b commit 4e2f53d

File tree

8 files changed

+103
-28
lines changed

8 files changed

+103
-28
lines changed

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ updates:
55
directory: "/"
66
schedule:
77
interval: "weekly"
8+
cooldown:
9+
default-days: 7
810
groups:
911
github-actions:
1012
patterns:
@@ -13,3 +15,5 @@ updates:
1315
directory: "/"
1416
schedule:
1517
interval: "weekly"
18+
cooldown:
19+
default-days: 7

.github/workflows/main.yml

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,35 @@ on:
1515
- '*'
1616
merge_group:
1717

18+
permissions: {}
19+
1820
jobs:
1921

2022
run-type-checking:
2123

2224
name: Run tests for type-checking
2325
runs-on: ubuntu-latest
26+
permissions:
27+
contents: read
2428

2529
steps:
26-
- uses: actions/checkout@v6
27-
- uses: astral-sh/setup-uv@v7
30+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
31+
with:
32+
persist-credentials: false
33+
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
2834
with:
2935
enable-cache: true
3036
- name: Install just
31-
uses: extractions/setup-just@v3
37+
uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3
3238
- name: Run type checking
3339
run: just typing
3440

3541
run-tests:
3642

3743
name: Run tests for ${{ matrix.os }} on ${{ matrix.python-version }}
3844
runs-on: ${{ matrix.os }}
45+
permissions:
46+
contents: read
3947

4048
strategy:
4149
fail-fast: false
@@ -44,15 +52,17 @@ jobs:
4452
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
4553

4654
steps:
47-
- uses: actions/checkout@v6
48-
- uses: astral-sh/setup-uv@v7
55+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
56+
with:
57+
persist-credentials: false
58+
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
4959
with:
5060
python-version: ${{ matrix.python-version }}
5161
enable-cache: true
5262
- name: Install just
53-
uses: extractions/setup-just@v3
63+
uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3
5464
- name: Run tests
5565
run: just test
5666

5767
- name: Upload test coverage reports to Codecov with GitHub Action
58-
uses: codecov/codecov-action@v5
68+
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5

.github/workflows/publish-to-pypi.yml

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ name: Publish Python 🐍 distribution 📦 to PyPI
22

33
on: push
44

5+
permissions: {}
6+
57
jobs:
68
build:
79
name: Build distribution 📦
810
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
913

1014
steps:
11-
- uses: actions/checkout@v6
15+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
16+
with:
17+
persist-credentials: false
1218
- name: Set up Python
13-
uses: actions/setup-python@v6
19+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
1420
with:
1521
python-version: "3.x"
1622
- name: Install pypa/build
@@ -22,7 +28,7 @@ jobs:
2228
- name: Build a binary wheel and a source tarball
2329
run: python3 -m build
2430
- name: Store the distribution packages
25-
uses: actions/upload-artifact@v7
31+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
2632
with:
2733
name: python-package-distributions
2834
path: dist/
@@ -41,12 +47,12 @@ jobs:
4147

4248
steps:
4349
- name: Download all the dists
44-
uses: actions/download-artifact@v8
50+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
4551
with:
4652
name: python-package-distributions
4753
path: dist/
4854
- name: Publish distribution 📦 to PyPI
49-
uses: pypa/gh-action-pypi-publish@release/v1
55+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1
5056

5157
github-release:
5258
name: >-
@@ -62,30 +68,32 @@ jobs:
6268

6369
steps:
6470
- name: Download all the dists
65-
uses: actions/download-artifact@v8
71+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
6672
with:
6773
name: python-package-distributions
6874
path: dist/
6975
- name: Sign the dists with Sigstore
70-
uses: sigstore/gh-action-sigstore-python@v3.2.0
76+
uses: sigstore/gh-action-sigstore-python@a5caf349bc536fbef3668a10ed7f5cd309a4b53d # v3.2.0
7177
with:
7278
inputs: >-
7379
./dist/*.tar.gz
7480
./dist/*.whl
7581
- name: Create GitHub Release
7682
env:
7783
GITHUB_TOKEN: ${{ github.token }}
84+
RELEASE_TAG: ${{ github.ref_name }}
85+
REPOSITORY: ${{ github.repository }}
7886
run: >-
7987
gh release create
80-
'${{ github.ref_name }}'
81-
--repo '${{ github.repository }}'
88+
"$RELEASE_TAG"
89+
--repo "$REPOSITORY"
8290
--notes ""
8391
- name: Upload artifact signatures to GitHub Release
8492
env:
8593
GITHUB_TOKEN: ${{ github.token }}
86-
# Upload to GitHub Release using the `gh` CLI. `dist/` contains the built
87-
# packages, and the sigstore-produced signatures and certificates.
94+
RELEASE_TAG: ${{ github.ref_name }}
95+
REPOSITORY: ${{ github.repository }}
8896
run: >-
8997
gh release upload
90-
'${{ github.ref_name }}' dist/**
91-
--repo '${{ github.repository }}'
98+
"$RELEASE_TAG" dist/**
99+
--repo "$REPOSITORY"

.github/workflows/zizmor.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: zizmor
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- '*'
10+
schedule:
11+
- cron: '0 7 * * 1'
12+
workflow_dispatch:
13+
14+
permissions: {}
15+
16+
jobs:
17+
zizmor:
18+
name: Scan GitHub Actions
19+
runs-on: ubuntu-latest
20+
permissions:
21+
contents: read
22+
security-events: write
23+
24+
steps:
25+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
26+
with:
27+
persist-credentials: false
28+
29+
- uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
30+
with:
31+
enable-cache: true
32+
python-version: '3.13'
33+
34+
- name: Run zizmor
35+
run: uvx --from zizmor zizmor --format=github .
36+
37+
- name: Generate SARIF report
38+
if: always()
39+
run: uvx --from zizmor zizmor --format=sarif --no-exit-codes . > zizmor.sarif
40+
41+
- name: Upload SARIF report
42+
if: >
43+
always() &&
44+
(github.event_name != 'pull_request' ||
45+
github.event.pull_request.head.repo.full_name == github.repository)
46+
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4
47+
with:
48+
sarif_file: zizmor.sarif

src/pytask_parallel/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def pytask_parse_config(config: dict[str, Any]) -> None:
2727
raise ValueError(msg) from None
2828

2929
if config["n_workers"] == "auto":
30-
config["n_workers"] = max(os.cpu_count() - 1, 1) # type: ignore[operator]
30+
cpu_count = os.cpu_count() or 1
31+
config["n_workers"] = max(cpu_count - 1, 1)
3132

3233
# If more than one worker is used, and no backend is set, use loky.
3334
if config["n_workers"] > 1 and config["parallel_backend"] == ParallelBackend.NONE:

src/pytask_parallel/execute.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def pytask_execute_build(session: Session) -> bool | None: # noqa: C901, PLR091
137137
newly_collected_reports.append(
138138
ExecutionReport.from_task_and_exception(
139139
task,
140-
wrapper_result.exc_info, # type: ignore[arg-type]
140+
cast("Any", wrapper_result.exc_info),
141141
)
142142
)
143143
running_tasks.pop(task_name)
@@ -213,7 +213,7 @@ def pytask_execute_task(session: Session, task: PTask) -> Future[WrapperResult]:
213213
# cloudpickle will pickle it with the function. See cloudpickle#417, pytask#373
214214
# and pytask#374.
215215
task_module = get_module(task.function, getattr(task, "path", None))
216-
if should_pickle_module_by_value(task_module):
216+
if task_module is not None and should_pickle_module_by_value(task_module):
217217
cloudpickle.register_pickle_by_value(task_module)
218218

219219
return cast("Any", wrapper_func).submit(
@@ -237,7 +237,7 @@ def pytask_execute_task(session: Session, task: PTask) -> Future[WrapperResult]:
237237
# cloudpickle will pickle it with the function. See cloudpickle#417, pytask#373
238238
# and pytask#374.
239239
task_module = get_module(task.function, getattr(task, "path", None))
240-
if should_pickle_module_by_value(task_module):
240+
if task_module is not None and should_pickle_module_by_value(task_module):
241241
cloudpickle.register_pickle_by_value(task_module)
242242

243243
return session.config["_parallel_executor"].submit(

src/pytask_parallel/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def _parse_future_exception(
136136
return None if exc is None else (type(exc), exc, exc.__traceback__)
137137

138138

139-
def get_module(func: Callable[..., Any], path: Path | None) -> ModuleType:
139+
def get_module(func: Callable[..., Any], path: Path | None) -> ModuleType | None:
140140
"""Get the module of a python function.
141141
142142
``functools.partial`` obfuscates the module of the function and
@@ -151,8 +151,8 @@ def get_module(func: Callable[..., Any], path: Path | None) -> ModuleType:
151151
func = func.func
152152

153153
if path:
154-
return inspect.getmodule(func, path.as_posix()) # type: ignore[return-value]
155-
return inspect.getmodule(func) # type: ignore[return-value]
154+
return inspect.getmodule(func, path.as_posix())
155+
return inspect.getmodule(func)
156156

157157

158158
def strip_annotation_locals(task: PTask) -> None:

src/pytask_parallel/wrappers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,11 @@ def wrap_task_in_process( # noqa: PLR0913
138138
except Exception: # noqa: BLE001
139139
exc_info = sys.exc_info()
140140
processed_exc_info = _render_traceback_to_string(
141-
exc_info, # type: ignore[arg-type]
141+
(
142+
cast("type[BaseException]", exc_info[0]),
143+
cast("BaseException", exc_info[1]),
144+
exc_info[2],
145+
),
142146
show_locals,
143147
console_options,
144148
)

0 commit comments

Comments
 (0)