whytrail 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- whytrail-0.1.0/.github/actions/whytrail-run/README.md +20 -0
- whytrail-0.1.0/.github/actions/whytrail-run/action.yml +50 -0
- whytrail-0.1.0/.github/workflows/ci.yml +333 -0
- whytrail-0.1.0/.github/workflows/docs.yml +51 -0
- whytrail-0.1.0/.github/workflows/publish.yml +50 -0
- whytrail-0.1.0/.gitignore +18 -0
- whytrail-0.1.0/CHANGELOG.md +423 -0
- whytrail-0.1.0/LICENSE +21 -0
- whytrail-0.1.0/PKG-INFO +185 -0
- whytrail-0.1.0/README.md +144 -0
- whytrail-0.1.0/benchmarks/test_overhead.py +58 -0
- whytrail-0.1.0/docs/adr/0001-whytrail-architecture.md +159 -0
- whytrail-0.1.0/docs/adr/0002-category-strategy.md +100 -0
- whytrail-0.1.0/docs/adr/0003-ecosystem-scale-triage.md +248 -0
- whytrail-0.1.0/docs/adr/0004-rename-to-whytrail.md +81 -0
- whytrail-0.1.0/docs/adr/0005-vscode-extension-scope.md +112 -0
- whytrail-0.1.0/docs/changelog.md +1 -0
- whytrail-0.1.0/docs/index.md +1 -0
- whytrail-0.1.0/docs/plugin-guide.md +256 -0
- whytrail-0.1.0/docs/testing-maturity.md +215 -0
- whytrail-0.1.0/examples/ex_cli_crash.py +15 -0
- whytrail-0.1.0/examples/ex_tracked_pipeline.py +35 -0
- whytrail-0.1.0/mkdocs.yml +83 -0
- whytrail-0.1.0/plugins/whytrail-aiohttp/README.md +16 -0
- whytrail-0.1.0/plugins/whytrail-aiohttp/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-aiohttp/src/whytrail_aiohttp/__init__.py +48 -0
- whytrail-0.1.0/plugins/whytrail-anthropic/README.md +15 -0
- whytrail-0.1.0/plugins/whytrail-anthropic/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-anthropic/src/whytrail_anthropic/__init__.py +61 -0
- whytrail-0.1.0/plugins/whytrail-asyncpg/README.md +15 -0
- whytrail-0.1.0/plugins/whytrail-asyncpg/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-asyncpg/src/whytrail_asyncpg/__init__.py +62 -0
- whytrail-0.1.0/plugins/whytrail-boto3/README.md +23 -0
- whytrail-0.1.0/plugins/whytrail-boto3/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-boto3/src/whytrail_boto3/__init__.py +62 -0
- whytrail-0.1.0/plugins/whytrail-celery/README.md +17 -0
- whytrail-0.1.0/plugins/whytrail-celery/pyproject.toml +15 -0
- whytrail-0.1.0/plugins/whytrail-celery/src/whytrail_celery/__init__.py +75 -0
- whytrail-0.1.0/plugins/whytrail-ddtrace/README.md +25 -0
- whytrail-0.1.0/plugins/whytrail-ddtrace/pyproject.toml +15 -0
- whytrail-0.1.0/plugins/whytrail-ddtrace/src/whytrail_ddtrace/__init__.py +64 -0
- whytrail-0.1.0/plugins/whytrail-django/README.md +31 -0
- whytrail-0.1.0/plugins/whytrail-django/pyproject.toml +15 -0
- whytrail-0.1.0/plugins/whytrail-django/src/whytrail_django/__init__.py +79 -0
- whytrail-0.1.0/plugins/whytrail-dramatiq/README.md +20 -0
- whytrail-0.1.0/plugins/whytrail-dramatiq/pyproject.toml +15 -0
- whytrail-0.1.0/plugins/whytrail-dramatiq/src/whytrail_dramatiq/__init__.py +77 -0
- whytrail-0.1.0/plugins/whytrail-fastapi/README.md +36 -0
- whytrail-0.1.0/plugins/whytrail-fastapi/pyproject.toml +24 -0
- whytrail-0.1.0/plugins/whytrail-fastapi/src/whytrail_fastapi/__init__.py +83 -0
- whytrail-0.1.0/plugins/whytrail-flask/README.md +26 -0
- whytrail-0.1.0/plugins/whytrail-flask/pyproject.toml +15 -0
- whytrail-0.1.0/plugins/whytrail-flask/src/whytrail_flask/__init__.py +65 -0
- whytrail-0.1.0/plugins/whytrail-google-cloud/README.md +18 -0
- whytrail-0.1.0/plugins/whytrail-google-cloud/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-google-cloud/src/whytrail_google_cloud/__init__.py +51 -0
- whytrail-0.1.0/plugins/whytrail-grpcio/README.md +13 -0
- whytrail-0.1.0/plugins/whytrail-grpcio/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-grpcio/src/whytrail_grpcio/__init__.py +43 -0
- whytrail-0.1.0/plugins/whytrail-httpx/README.md +15 -0
- whytrail-0.1.0/plugins/whytrail-httpx/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-httpx/src/whytrail_httpx/__init__.py +52 -0
- whytrail-0.1.0/plugins/whytrail-huggingface-hub/README.md +16 -0
- whytrail-0.1.0/plugins/whytrail-huggingface-hub/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-huggingface-hub/src/whytrail_huggingface_hub/__init__.py +44 -0
- whytrail-0.1.0/plugins/whytrail-jsonschema/README.md +26 -0
- whytrail-0.1.0/plugins/whytrail-jsonschema/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-jsonschema/src/whytrail_jsonschema/__init__.py +46 -0
- whytrail-0.1.0/plugins/whytrail-langchain/README.md +32 -0
- whytrail-0.1.0/plugins/whytrail-langchain/pyproject.toml +15 -0
- whytrail-0.1.0/plugins/whytrail-langchain/src/whytrail_langchain/__init__.py +244 -0
- whytrail-0.1.0/plugins/whytrail-marshmallow/README.md +18 -0
- whytrail-0.1.0/plugins/whytrail-marshmallow/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-marshmallow/src/whytrail_marshmallow/__init__.py +61 -0
- whytrail-0.1.0/plugins/whytrail-openai/README.md +15 -0
- whytrail-0.1.0/plugins/whytrail-openai/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-openai/src/whytrail_openai/__init__.py +68 -0
- whytrail-0.1.0/plugins/whytrail-pandas/README.md +41 -0
- whytrail-0.1.0/plugins/whytrail-pandas/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-pandas/src/whytrail_pandas/__init__.py +101 -0
- whytrail-0.1.0/plugins/whytrail-polars/README.md +16 -0
- whytrail-0.1.0/plugins/whytrail-polars/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-polars/src/whytrail_polars/__init__.py +89 -0
- whytrail-0.1.0/plugins/whytrail-prefect/README.md +19 -0
- whytrail-0.1.0/plugins/whytrail-prefect/pyproject.toml +15 -0
- whytrail-0.1.0/plugins/whytrail-prefect/src/whytrail_prefect/__init__.py +54 -0
- whytrail-0.1.0/plugins/whytrail-pydantic/README.md +26 -0
- whytrail-0.1.0/plugins/whytrail-pydantic/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-pydantic/src/whytrail_pydantic/__init__.py +58 -0
- whytrail-0.1.0/plugins/whytrail-pymongo/README.md +25 -0
- whytrail-0.1.0/plugins/whytrail-pymongo/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-pymongo/src/whytrail_pymongo/__init__.py +60 -0
- whytrail-0.1.0/plugins/whytrail-pytest/README.md +15 -0
- whytrail-0.1.0/plugins/whytrail-pytest/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-pytest/src/whytrail_pytest/plugin.py +53 -0
- whytrail-0.1.0/plugins/whytrail-pyyaml/README.md +21 -0
- whytrail-0.1.0/plugins/whytrail-pyyaml/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-pyyaml/src/whytrail_pyyaml/__init__.py +61 -0
- whytrail-0.1.0/plugins/whytrail-requests/README.md +24 -0
- whytrail-0.1.0/plugins/whytrail-requests/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-requests/src/whytrail_requests/__init__.py +85 -0
- whytrail-0.1.0/plugins/whytrail-rq/README.md +22 -0
- whytrail-0.1.0/plugins/whytrail-rq/pyproject.toml +22 -0
- whytrail-0.1.0/plugins/whytrail-rq/src/whytrail_rq/__init__.py +66 -0
- whytrail-0.1.0/plugins/whytrail-scrapy/README.md +17 -0
- whytrail-0.1.0/plugins/whytrail-scrapy/pyproject.toml +15 -0
- whytrail-0.1.0/plugins/whytrail-scrapy/src/whytrail_scrapy/__init__.py +61 -0
- whytrail-0.1.0/plugins/whytrail-sentry/README.md +29 -0
- whytrail-0.1.0/plugins/whytrail-sentry/pyproject.toml +15 -0
- whytrail-0.1.0/plugins/whytrail-sentry/src/whytrail_sentry/__init__.py +82 -0
- whytrail-0.1.0/plugins/whytrail-sqlalchemy/README.md +25 -0
- whytrail-0.1.0/plugins/whytrail-sqlalchemy/pyproject.toml +18 -0
- whytrail-0.1.0/plugins/whytrail-sqlalchemy/src/whytrail_sqlalchemy/__init__.py +75 -0
- whytrail-0.1.0/pyproject.toml +99 -0
- whytrail-0.1.0/scripts/new_plugin.py +244 -0
- whytrail-0.1.0/src/whytrail/__init__.py +210 -0
- whytrail-0.1.0/src/whytrail/_repr.py +27 -0
- whytrail-0.1.0/src/whytrail/cli/__init__.py +0 -0
- whytrail-0.1.0/src/whytrail/cli/__main__.py +74 -0
- whytrail-0.1.0/src/whytrail/core/__init__.py +0 -0
- whytrail-0.1.0/src/whytrail/core/explanation.py +187 -0
- whytrail-0.1.0/src/whytrail/core/graph.py +160 -0
- whytrail-0.1.0/src/whytrail/core/node.py +84 -0
- whytrail-0.1.0/src/whytrail/core/serialize.py +117 -0
- whytrail-0.1.0/src/whytrail/explainers/__init__.py +0 -0
- whytrail-0.1.0/src/whytrail/explainers/builtin.py +125 -0
- whytrail-0.1.0/src/whytrail/otel.py +87 -0
- whytrail-0.1.0/src/whytrail/propagation.py +104 -0
- whytrail-0.1.0/src/whytrail/protocols.py +48 -0
- whytrail-0.1.0/src/whytrail/py.typed +0 -0
- whytrail-0.1.0/src/whytrail/registry.py +107 -0
- whytrail-0.1.0/src/whytrail/runtime/__init__.py +0 -0
- whytrail-0.1.0/src/whytrail/runtime/capture.py +162 -0
- whytrail-0.1.0/src/whytrail/runtime/context.py +128 -0
- whytrail-0.1.0/src/whytrail/runtime/monitoring.py +154 -0
- whytrail-0.1.0/tests/conftest.py +21 -0
- whytrail-0.1.0/tests/integration/test_cli.py +73 -0
- whytrail-0.1.0/tests/integration/test_deep_trace.py +88 -0
- whytrail-0.1.0/tests/integration/test_otel.py +109 -0
- whytrail-0.1.0/tests/integration/test_propagation.py +55 -0
- whytrail-0.1.0/tests/integration/test_protocol_and_registry.py +101 -0
- whytrail-0.1.0/tests/integration/test_why_exceptions.py +110 -0
- whytrail-0.1.0/tests/integration/test_why_tracking.py +118 -0
- whytrail-0.1.0/tests/plugin_contract/test_aiohttp_plugin.py +74 -0
- whytrail-0.1.0/tests/plugin_contract/test_anthropic_plugin.py +64 -0
- whytrail-0.1.0/tests/plugin_contract/test_asyncpg_plugin.py +62 -0
- whytrail-0.1.0/tests/plugin_contract/test_boto3_plugin.py +61 -0
- whytrail-0.1.0/tests/plugin_contract/test_celery_plugin.py +75 -0
- whytrail-0.1.0/tests/plugin_contract/test_ddtrace_plugin.py +61 -0
- whytrail-0.1.0/tests/plugin_contract/test_django_plugin.py +111 -0
- whytrail-0.1.0/tests/plugin_contract/test_dramatiq_plugin.py +72 -0
- whytrail-0.1.0/tests/plugin_contract/test_fastapi_plugin.py +88 -0
- whytrail-0.1.0/tests/plugin_contract/test_flask_plugin.py +97 -0
- whytrail-0.1.0/tests/plugin_contract/test_google_cloud_plugin.py +61 -0
- whytrail-0.1.0/tests/plugin_contract/test_grpcio_plugin.py +76 -0
- whytrail-0.1.0/tests/plugin_contract/test_httpx_plugin.py +89 -0
- whytrail-0.1.0/tests/plugin_contract/test_huggingface_hub_plugin.py +66 -0
- whytrail-0.1.0/tests/plugin_contract/test_jsonschema_plugin.py +67 -0
- whytrail-0.1.0/tests/plugin_contract/test_langchain_plugin.py +94 -0
- whytrail-0.1.0/tests/plugin_contract/test_marshmallow_plugin.py +64 -0
- whytrail-0.1.0/tests/plugin_contract/test_openai_plugin.py +70 -0
- whytrail-0.1.0/tests/plugin_contract/test_pandas_plugin.py +61 -0
- whytrail-0.1.0/tests/plugin_contract/test_polars_plugin.py +57 -0
- whytrail-0.1.0/tests/plugin_contract/test_prefect_plugin.py +49 -0
- whytrail-0.1.0/tests/plugin_contract/test_pydantic_plugin.py +67 -0
- whytrail-0.1.0/tests/plugin_contract/test_pymongo_plugin.py +72 -0
- whytrail-0.1.0/tests/plugin_contract/test_pytest_plugin.py +70 -0
- whytrail-0.1.0/tests/plugin_contract/test_pyyaml_plugin.py +69 -0
- whytrail-0.1.0/tests/plugin_contract/test_redaction_fuzz.py +289 -0
- whytrail-0.1.0/tests/plugin_contract/test_requests_plugin.py +77 -0
- whytrail-0.1.0/tests/plugin_contract/test_rq_plugin.py +104 -0
- whytrail-0.1.0/tests/plugin_contract/test_scrapy_plugin.py +60 -0
- whytrail-0.1.0/tests/plugin_contract/test_sentry_plugin.py +114 -0
- whytrail-0.1.0/tests/plugin_contract/test_sqlalchemy_plugin.py +91 -0
- whytrail-0.1.0/tests/plugin_contract/test_web_concurrency.py +157 -0
- whytrail-0.1.0/tests/property/test_graph_invariants.py +57 -0
- whytrail-0.1.0/tests/unit/test_explanation.py +93 -0
- whytrail-0.1.0/tests/unit/test_graph.py +111 -0
- whytrail-0.1.0/tests/unit/test_node.py +38 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# whytrail run (GitHub Action)
|
|
2
|
+
|
|
3
|
+
Wraps `whytrail run` (see `src/whytrail/cli/__main__.py`) so a CI job's failure
|
|
4
|
+
output shows a causal explanation instead of a bare traceback -- the same
|
|
5
|
+
CLI a developer would run locally, packaged for a workflow step.
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
- uses: <owner>/whytrail/.github/actions/whytrail-run@main
|
|
9
|
+
with:
|
|
10
|
+
script: scripts/nightly_import.py
|
|
11
|
+
graph: "true" # optional: include a Mermaid graph
|
|
12
|
+
extra-packages: whytrail-requests # optional: plugin distributions the script needs
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
On an uncaught exception, the step fails (matching `whytrail run`'s own exit
|
|
16
|
+
code) and the explanation appears in the job log at the point of failure --
|
|
17
|
+
no extra step needed to surface it.
|
|
18
|
+
|
|
19
|
+
Inputs: `script` (required), `args`, `python-version` (default `3.x`),
|
|
20
|
+
`graph` (default `false`), `extra-packages`.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: "whytrail run"
|
|
2
|
+
description: "Run a Python script and explain any uncaught exception with whytrail instead of a bare traceback."
|
|
3
|
+
branding:
|
|
4
|
+
icon: "help-circle"
|
|
5
|
+
color: "orange"
|
|
6
|
+
|
|
7
|
+
inputs:
|
|
8
|
+
script:
|
|
9
|
+
description: "Path to the Python script to run"
|
|
10
|
+
required: true
|
|
11
|
+
args:
|
|
12
|
+
description: "Arguments to pass to the script"
|
|
13
|
+
required: false
|
|
14
|
+
default: ""
|
|
15
|
+
python-version:
|
|
16
|
+
description: "Python version to set up before running"
|
|
17
|
+
required: false
|
|
18
|
+
default: "3.x"
|
|
19
|
+
graph:
|
|
20
|
+
description: "Include a Mermaid provenance graph in the explanation output"
|
|
21
|
+
required: false
|
|
22
|
+
default: "false"
|
|
23
|
+
extra-packages:
|
|
24
|
+
description: "Space-separated extra packages to install alongside whytrail (e.g. plugin distributions your script needs, like whytrail-requests)"
|
|
25
|
+
required: false
|
|
26
|
+
default: ""
|
|
27
|
+
|
|
28
|
+
runs:
|
|
29
|
+
using: "composite"
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/setup-python@v5
|
|
32
|
+
with:
|
|
33
|
+
python-version: ${{ inputs.python-version }}
|
|
34
|
+
|
|
35
|
+
- name: Install whytrail
|
|
36
|
+
shell: bash
|
|
37
|
+
run: |
|
|
38
|
+
pip install whytrail
|
|
39
|
+
if [ -n "${{ inputs.extra-packages }}" ]; then
|
|
40
|
+
pip install ${{ inputs.extra-packages }}
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
- name: Run script with whytrail
|
|
44
|
+
shell: bash
|
|
45
|
+
run: |
|
|
46
|
+
FLAGS=""
|
|
47
|
+
if [ "${{ inputs.graph }}" = "true" ]; then
|
|
48
|
+
FLAGS="--graph"
|
|
49
|
+
fi
|
|
50
|
+
whytrail run $FLAGS "${{ inputs.script }}" -- ${{ inputs.args }}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install whytrail (core + dev)
|
|
24
|
+
run: pip install -e ".[dev]"
|
|
25
|
+
|
|
26
|
+
- name: Type-check with mypy --strict
|
|
27
|
+
run: mypy
|
|
28
|
+
|
|
29
|
+
- name: Run core test suite
|
|
30
|
+
run: pytest tests -q
|
|
31
|
+
|
|
32
|
+
plugin-contract-tests:
|
|
33
|
+
# Each plugin has its own third-party dependency (pandas, sqlalchemy,
|
|
34
|
+
# fastapi, ...); installing all of them into the core matrix above
|
|
35
|
+
# would make every core-only change pay for every plugin's install
|
|
36
|
+
# time. Kept separate so a plugin dependency bump can't block a
|
|
37
|
+
# core-only PR, and vice versa.
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
strategy:
|
|
40
|
+
fail-fast: false
|
|
41
|
+
matrix:
|
|
42
|
+
plugin:
|
|
43
|
+
- whytrail-requests
|
|
44
|
+
- whytrail-pytest
|
|
45
|
+
- whytrail-sentry
|
|
46
|
+
- whytrail-pandas
|
|
47
|
+
- whytrail-sqlalchemy
|
|
48
|
+
- whytrail-fastapi
|
|
49
|
+
- whytrail-django
|
|
50
|
+
- whytrail-celery
|
|
51
|
+
- whytrail-langchain
|
|
52
|
+
- whytrail-pydantic
|
|
53
|
+
- whytrail-boto3
|
|
54
|
+
- whytrail-httpx
|
|
55
|
+
- whytrail-openai
|
|
56
|
+
- whytrail-anthropic
|
|
57
|
+
- whytrail-asyncpg
|
|
58
|
+
- whytrail-pymongo
|
|
59
|
+
- whytrail-marshmallow
|
|
60
|
+
- whytrail-jsonschema
|
|
61
|
+
- whytrail-polars
|
|
62
|
+
- whytrail-rq
|
|
63
|
+
- whytrail-dramatiq
|
|
64
|
+
- whytrail-flask
|
|
65
|
+
- whytrail-pyyaml
|
|
66
|
+
- whytrail-grpcio
|
|
67
|
+
- whytrail-scrapy
|
|
68
|
+
- whytrail-aiohttp
|
|
69
|
+
- whytrail-huggingface-hub
|
|
70
|
+
- whytrail-google-cloud
|
|
71
|
+
- whytrail-ddtrace
|
|
72
|
+
- whytrail-prefect
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v4
|
|
75
|
+
|
|
76
|
+
- uses: actions/setup-python@v5
|
|
77
|
+
with:
|
|
78
|
+
python-version: "3.12"
|
|
79
|
+
|
|
80
|
+
- name: Install whytrail + plugin under test + test deps
|
|
81
|
+
run: |
|
|
82
|
+
pip install -e ".[dev]"
|
|
83
|
+
# [test] is requested for every plugin uniformly; pip just warns
|
|
84
|
+
# ("does not provide the extra") and continues for the ~28 that
|
|
85
|
+
# don't declare one. The two that do (whytrail-fastapi needs the
|
|
86
|
+
# real fastapi package + httpx's TestClient; whytrail-rq needs
|
|
87
|
+
# fakeredis) are test-only tools neither plugin depends on at
|
|
88
|
+
# runtime, so they don't belong in the plugin's own
|
|
89
|
+
# `dependencies` -- found when both failed on the first real CI
|
|
90
|
+
# run with pytest exit 5 ("no tests collected"), since their
|
|
91
|
+
# test files' module-level `pytest.importorskip` guards turned a
|
|
92
|
+
# missing test-only dependency into a silent zero-test skip
|
|
93
|
+
# rather than a visible failure.
|
|
94
|
+
pip install -e "./plugins/${{ matrix.plugin }}[test]"
|
|
95
|
+
|
|
96
|
+
- name: Run this plugin's contract tests
|
|
97
|
+
run: |
|
|
98
|
+
# Strip the "whytrail-" prefix, then convert any remaining
|
|
99
|
+
# dashes to underscores -- needed for plugins like
|
|
100
|
+
# whytrail-google-cloud and whytrail-huggingface-hub, whose test
|
|
101
|
+
# files (test_google_cloud_plugin.py,
|
|
102
|
+
# test_huggingface_hub_plugin.py) use underscores throughout.
|
|
103
|
+
# A prefix-only strip would look for a literal dash in the
|
|
104
|
+
# filename and silently match zero tests.
|
|
105
|
+
name="${{ matrix.plugin }}"
|
|
106
|
+
test_name="${name#whytrail-}"
|
|
107
|
+
test_name="${test_name//-/_}"
|
|
108
|
+
pytest "tests/plugin_contract/test_${test_name}_plugin.py" -q
|
|
109
|
+
|
|
110
|
+
plugin-version-matrix:
|
|
111
|
+
# docs/testing-maturity.md gap #1: every plugin above installs
|
|
112
|
+
# whatever `pip` resolves today, never the specific floor each
|
|
113
|
+
# pyproject.toml claims to support. Checking that floor for real
|
|
114
|
+
# found real version-compatibility bugs in two rounds: first locally
|
|
115
|
+
# in this sandbox (Windows, before this job existed), then again --
|
|
116
|
+
# differently -- on the actual ubuntu-latest runners this job
|
|
117
|
+
# targets, the first time it ever executed for real. Neither round
|
|
118
|
+
# alone would have found everything the other did; several "confirmed
|
|
119
|
+
# fine" conclusions from local-only testing turned out wrong once a
|
|
120
|
+
# real CI run actually exercised each plugin's contract tests against
|
|
121
|
+
# its pinned floor, not just "does it install and import."
|
|
122
|
+
#
|
|
123
|
+
# No 3.13 wheel / build failure (found locally, still holds on Linux):
|
|
124
|
+
# - pydantic==2.0.3: pydantic-core fails to build from source
|
|
125
|
+
# (ForwardRef._evaluate() signature changed). First working: 2.8.0.
|
|
126
|
+
# - pyyaml==6.0: source build fails (Cython/setuptools mismatch).
|
|
127
|
+
# First working: 6.0.2.
|
|
128
|
+
# - grpcio==1.60.0: no 3.13 wheel on any platform. First working: 1.66.2.
|
|
129
|
+
# - pandas==2.0.0: no 3.13 wheel. First working: 2.2.3.
|
|
130
|
+
#
|
|
131
|
+
# Genuine Python 3.13 runtime incompatibility in the dependency's own
|
|
132
|
+
# code, not a packaging gap:
|
|
133
|
+
# - sqlalchemy==2.0.0: TypingOnly assertion predates two dunders
|
|
134
|
+
# 3.13 added to every class. First working: 2.0.36.
|
|
135
|
+
# - asyncpg==0.29.0: pgproto.c calls a C API function with the
|
|
136
|
+
# pre-3.13 argument count. First working: 0.30.0.
|
|
137
|
+
# - sentry-sdk==2.0.0: copies frame locals via copy.copy(), which
|
|
138
|
+
# breaks on 3.13's FrameLocalsProxy (PEP 667) -- passed a local
|
|
139
|
+
# install-and-import check, only failed when its actual capture
|
|
140
|
+
# path ran against a real exception in a real CI run. First
|
|
141
|
+
# working, found by bisection: 2.11.0.
|
|
142
|
+
# - prefect==2.14.0-2.20.0: not fixable within the 2.x line at all --
|
|
143
|
+
# `GatherTaskGroup` (prefect's own asyncio.TaskGroup subclass)
|
|
144
|
+
# doesn't implement an abstract method 3.13's asyncio added,
|
|
145
|
+
# independent of which pydantic/griffe versions are present. The
|
|
146
|
+
# floor moved to the 3.x line entirely: 3.7.8 (earlier 3.x
|
|
147
|
+
# releases have their own pydantic-version-drift issues below).
|
|
148
|
+
#
|
|
149
|
+
# Standard-library removal, unrelated to any single dependency version:
|
|
150
|
+
# - marshmallow==3.0.0: imports `distutils.version.LooseVersion`,
|
|
151
|
+
# removed in 3.12 (PEP 632). First working, by bisection: 3.15.0.
|
|
152
|
+
#
|
|
153
|
+
# Transitive-dependency drift -- the floor's own unpinned/loosely-pinned
|
|
154
|
+
# sub-dependency moved on and broke it, nothing to do with Python 3.13:
|
|
155
|
+
# - requests==2.20.0: imports `urllib3.packages.six.moves`, a shim
|
|
156
|
+
# urllib3 2.0 removed. First working, by bisection: 2.25.0.
|
|
157
|
+
# - flask==2.3.0: testing.py reads `werkzeug.__version__`, which
|
|
158
|
+
# werkzeug 3.x removed. First working: 2.3.3.
|
|
159
|
+
# - prefect (see above): also hit a `pydantic.ConfigWrapper`
|
|
160
|
+
# TypeError against pydantic's newest 2.x releases, and separately
|
|
161
|
+
# a `griffe.Docstring` import removed by a later griffe release --
|
|
162
|
+
# three independent drift issues layered on the same plugin.
|
|
163
|
+
#
|
|
164
|
+
# Plugin/test written against a newer version of the SDK's own API
|
|
165
|
+
# than the stated floor actually provides -- not a Python-version or
|
|
166
|
+
# transitive-drift issue, just an old client library:
|
|
167
|
+
# - ddtrace==2.0.0: `ddtrace.trace.tracer` (a module-level singleton
|
|
168
|
+
# the test imports) didn't exist yet. First working: 2.20.0.
|
|
169
|
+
# - openai==1.0.0: exception `.body` shape at this version doesn't
|
|
170
|
+
# match what the plugin reads. First working, by bisection: 1.30.0.
|
|
171
|
+
# - anthropic==0.30.0: same shape issue. First working: 0.34.0.
|
|
172
|
+
# - huggingface_hub==0.20.0: `huggingface_hub.errors` (the module the
|
|
173
|
+
# plugin imports) didn't exist yet, and even once it did (0.22+),
|
|
174
|
+
# `hf_raise_for_status()`'s response-shape handling didn't match
|
|
175
|
+
# until the 1.x line. First working: 1.0.0 -- a major-version
|
|
176
|
+
# jump, not a patch bump, the same way prefect needed one.
|
|
177
|
+
#
|
|
178
|
+
# Not independently version-testable the way this job assumes:
|
|
179
|
+
# - whytrail-fastapi tests build a real FastAPI app, so `starlette`
|
|
180
|
+
# alone isn't a meaningful axis to pin -- forcing an old starlette
|
|
181
|
+
# floor (0.36.0) against whatever fastapi the test-tools step
|
|
182
|
+
# installs breaks on an unrelated TestClient API mismatch the
|
|
183
|
+
# moment the two are versioned independently. `fastapi` is the
|
|
184
|
+
# pinned dependency instead (floor 0.115.0, the oldest version
|
|
185
|
+
# confirmed to both resolve a starlette satisfying the plugin's
|
|
186
|
+
# real >=0.36 floor *and* pass); the test-tools step below installs
|
|
187
|
+
# only httpx for this plugin now, not fastapi.
|
|
188
|
+
#
|
|
189
|
+
# Every other plugin's stated floor (boto3, httpx, django, celery,
|
|
190
|
+
# dramatiq, rq, langchain-core, google-api-core, pytest, jsonschema,
|
|
191
|
+
# scrapy, polars, aiohttp, pymongo) was checked the same way on real
|
|
192
|
+
# ubuntu-latest CI and confirmed to already work -- not assumed, just
|
|
193
|
+
# not (yet) found broken. See docs/testing-maturity.md for why "found
|
|
194
|
+
# broken so far" and "confirmed working" are different-strength claims,
|
|
195
|
+
# and CHANGELOG.md for the full account of both rounds of findings.
|
|
196
|
+
#
|
|
197
|
+
# This job pins each plugin's *actual* Python-3.13-compatible floor
|
|
198
|
+
# (not the aspirational one in pyproject.toml, which is still correct
|
|
199
|
+
# for older Python versions) against the latest release, so a future
|
|
200
|
+
# dependency bump that reintroduces a gap like this fails CI instead
|
|
201
|
+
# of being discovered by a user.
|
|
202
|
+
# Each entry below is fully explicit (plugin, dependency, floor,
|
|
203
|
+
# dependency-version) rather than a `plugin`x`dependency-version`
|
|
204
|
+
# cross-product via a separate `include:` list. The cross-product
|
|
205
|
+
# form was tried first and is wrong: `include` entries only merge
|
|
206
|
+
# into a base matrix when they share a key with it, and these entries
|
|
207
|
+
# share no key with a bare `dependency-version: [floor, latest]` axis
|
|
208
|
+
# -- so instead of the intended 60 jobs (30 plugins x 2 versions),
|
|
209
|
+
# only 2 generic jobs ran, both missing plugin context entirely. Not
|
|
210
|
+
# caught by local `mkdocs`/`mypy`/`pytest` verification, since none
|
|
211
|
+
# of those parse this workflow file -- only found by watching the
|
|
212
|
+
# first real push actually run it and seeing "plugin-version-matrix
|
|
213
|
+
# (floor)" and "(latest)" instead of 60 named jobs.
|
|
214
|
+
runs-on: ubuntu-latest
|
|
215
|
+
strategy:
|
|
216
|
+
fail-fast: false
|
|
217
|
+
matrix:
|
|
218
|
+
include:
|
|
219
|
+
- { plugin: whytrail-pydantic, dependency: pydantic, floor: "2.8.0", dependency-version: floor }
|
|
220
|
+
- { plugin: whytrail-pydantic, dependency: pydantic, floor: "2.8.0", dependency-version: latest }
|
|
221
|
+
- { plugin: whytrail-sqlalchemy, dependency: sqlalchemy, floor: "2.0.36", dependency-version: floor }
|
|
222
|
+
- { plugin: whytrail-sqlalchemy, dependency: sqlalchemy, floor: "2.0.36", dependency-version: latest }
|
|
223
|
+
- { plugin: whytrail-asyncpg, dependency: asyncpg, floor: "0.30.0", dependency-version: floor }
|
|
224
|
+
- { plugin: whytrail-asyncpg, dependency: asyncpg, floor: "0.30.0", dependency-version: latest }
|
|
225
|
+
- { plugin: whytrail-pyyaml, dependency: pyyaml, floor: "6.0.2", dependency-version: floor }
|
|
226
|
+
- { plugin: whytrail-pyyaml, dependency: pyyaml, floor: "6.0.2", dependency-version: latest }
|
|
227
|
+
- { plugin: whytrail-grpcio, dependency: grpcio, floor: "1.66.2", dependency-version: floor }
|
|
228
|
+
- { plugin: whytrail-grpcio, dependency: grpcio, floor: "1.66.2", dependency-version: latest }
|
|
229
|
+
- { plugin: whytrail-ddtrace, dependency: ddtrace, floor: "2.20.0", dependency-version: floor }
|
|
230
|
+
- { plugin: whytrail-ddtrace, dependency: ddtrace, floor: "2.20.0", dependency-version: latest }
|
|
231
|
+
- { plugin: whytrail-pandas, dependency: pandas, floor: "2.2.3", dependency-version: floor }
|
|
232
|
+
- { plugin: whytrail-pandas, dependency: pandas, floor: "2.2.3", dependency-version: latest }
|
|
233
|
+
- { plugin: whytrail-marshmallow, dependency: marshmallow, floor: "3.15.0", dependency-version: floor }
|
|
234
|
+
- { plugin: whytrail-marshmallow, dependency: marshmallow, floor: "3.15.0", dependency-version: latest }
|
|
235
|
+
- { plugin: whytrail-requests, dependency: requests, floor: "2.25.0", dependency-version: floor }
|
|
236
|
+
- { plugin: whytrail-requests, dependency: requests, floor: "2.25.0", dependency-version: latest }
|
|
237
|
+
- { plugin: whytrail-prefect, dependency: prefect, floor: "3.7.8", dependency-version: floor }
|
|
238
|
+
- { plugin: whytrail-prefect, dependency: prefect, floor: "3.7.8", dependency-version: latest }
|
|
239
|
+
- { plugin: whytrail-polars, dependency: polars, floor: "0.20.0", dependency-version: floor }
|
|
240
|
+
- { plugin: whytrail-polars, dependency: polars, floor: "0.20.0", dependency-version: latest }
|
|
241
|
+
- { plugin: whytrail-aiohttp, dependency: aiohttp, floor: "3.9.0", dependency-version: floor }
|
|
242
|
+
- { plugin: whytrail-aiohttp, dependency: aiohttp, floor: "3.9.0", dependency-version: latest }
|
|
243
|
+
- { plugin: whytrail-boto3, dependency: boto3, floor: "1.34.0", dependency-version: floor }
|
|
244
|
+
- { plugin: whytrail-boto3, dependency: boto3, floor: "1.34.0", dependency-version: latest }
|
|
245
|
+
- { plugin: whytrail-httpx, dependency: httpx, floor: "0.27.0", dependency-version: floor }
|
|
246
|
+
- { plugin: whytrail-httpx, dependency: httpx, floor: "0.27.0", dependency-version: latest }
|
|
247
|
+
- { plugin: whytrail-flask, dependency: flask, floor: "2.3.3", dependency-version: floor }
|
|
248
|
+
- { plugin: whytrail-flask, dependency: flask, floor: "2.3.3", dependency-version: latest }
|
|
249
|
+
- { plugin: whytrail-django, dependency: django, floor: "4.2.0", dependency-version: floor }
|
|
250
|
+
- { plugin: whytrail-django, dependency: django, floor: "4.2.0", dependency-version: latest }
|
|
251
|
+
- { plugin: whytrail-celery, dependency: celery, floor: "5.3.0", dependency-version: floor }
|
|
252
|
+
- { plugin: whytrail-celery, dependency: celery, floor: "5.3.0", dependency-version: latest }
|
|
253
|
+
- { plugin: whytrail-dramatiq, dependency: dramatiq, floor: "1.14.0", dependency-version: floor }
|
|
254
|
+
- { plugin: whytrail-dramatiq, dependency: dramatiq, floor: "1.14.0", dependency-version: latest }
|
|
255
|
+
- { plugin: whytrail-rq, dependency: rq, floor: "1.15.0", dependency-version: floor }
|
|
256
|
+
- { plugin: whytrail-rq, dependency: rq, floor: "1.15.0", dependency-version: latest }
|
|
257
|
+
- { plugin: whytrail-sentry, dependency: sentry-sdk, floor: "2.11.0", dependency-version: floor }
|
|
258
|
+
- { plugin: whytrail-sentry, dependency: sentry-sdk, floor: "2.11.0", dependency-version: latest }
|
|
259
|
+
- { plugin: whytrail-langchain, dependency: langchain-core, floor: "0.2.0", dependency-version: floor }
|
|
260
|
+
- { plugin: whytrail-langchain, dependency: langchain-core, floor: "0.2.0", dependency-version: latest }
|
|
261
|
+
- { plugin: whytrail-huggingface-hub, dependency: huggingface_hub, floor: "1.0.0", dependency-version: floor }
|
|
262
|
+
- { plugin: whytrail-huggingface-hub, dependency: huggingface_hub, floor: "1.0.0", dependency-version: latest }
|
|
263
|
+
- { plugin: whytrail-google-cloud, dependency: google-api-core, floor: "2.15.0", dependency-version: floor }
|
|
264
|
+
- { plugin: whytrail-google-cloud, dependency: google-api-core, floor: "2.15.0", dependency-version: latest }
|
|
265
|
+
- { plugin: whytrail-anthropic, dependency: anthropic, floor: "0.34.0", dependency-version: floor }
|
|
266
|
+
- { plugin: whytrail-anthropic, dependency: anthropic, floor: "0.34.0", dependency-version: latest }
|
|
267
|
+
- { plugin: whytrail-openai, dependency: openai, floor: "1.30.0", dependency-version: floor }
|
|
268
|
+
- { plugin: whytrail-openai, dependency: openai, floor: "1.30.0", dependency-version: latest }
|
|
269
|
+
- { plugin: whytrail-pytest, dependency: pytest, floor: "7.0.0", dependency-version: floor }
|
|
270
|
+
- { plugin: whytrail-pytest, dependency: pytest, floor: "7.0.0", dependency-version: latest }
|
|
271
|
+
- { plugin: whytrail-jsonschema, dependency: jsonschema, floor: "4.0.0", dependency-version: floor }
|
|
272
|
+
- { plugin: whytrail-jsonschema, dependency: jsonschema, floor: "4.0.0", dependency-version: latest }
|
|
273
|
+
- { plugin: whytrail-scrapy, dependency: scrapy, floor: "2.8.0", dependency-version: floor }
|
|
274
|
+
- { plugin: whytrail-scrapy, dependency: scrapy, floor: "2.8.0", dependency-version: latest }
|
|
275
|
+
- { plugin: whytrail-fastapi, dependency: fastapi, floor: "0.115.0", dependency-version: floor }
|
|
276
|
+
- { plugin: whytrail-fastapi, dependency: fastapi, floor: "0.115.0", dependency-version: latest }
|
|
277
|
+
- { plugin: whytrail-pymongo, dependency: pymongo, floor: "4.0.0", dependency-version: floor }
|
|
278
|
+
- { plugin: whytrail-pymongo, dependency: pymongo, floor: "4.0.0", dependency-version: latest }
|
|
279
|
+
steps:
|
|
280
|
+
- uses: actions/checkout@v4
|
|
281
|
+
|
|
282
|
+
- uses: actions/setup-python@v5
|
|
283
|
+
with:
|
|
284
|
+
python-version: "3.13"
|
|
285
|
+
|
|
286
|
+
- name: Install whytrail + plugin (without its dependency pin)
|
|
287
|
+
run: |
|
|
288
|
+
pip install -e ".[dev]"
|
|
289
|
+
pip install -e "./plugins/${{ matrix.plugin }}" --no-deps
|
|
290
|
+
|
|
291
|
+
- name: Install this plugin's test-only tools, if any
|
|
292
|
+
# --no-deps above (deliberately, so the next step controls the
|
|
293
|
+
# dependency-under-test's exact version) also blocks pip from
|
|
294
|
+
# resolving [test]-extra packages via the normal
|
|
295
|
+
# "pkg[test]" syntax, since --no-deps skips all dependency
|
|
296
|
+
# metadata, not just the core `dependencies` list -- so these
|
|
297
|
+
# are installed directly by name instead, same tools as
|
|
298
|
+
# plugin-contract-tests' [test] extra above.
|
|
299
|
+
# whytrail-fastapi installs only httpx here, not fastapi itself --
|
|
300
|
+
# fastapi is the dependency under test for this plugin (see the
|
|
301
|
+
# entries above and the note by them), handled by the next step.
|
|
302
|
+
run: |
|
|
303
|
+
case "${{ matrix.plugin }}" in
|
|
304
|
+
whytrail-fastapi) pip install httpx ;;
|
|
305
|
+
whytrail-rq) pip install fakeredis ;;
|
|
306
|
+
esac
|
|
307
|
+
|
|
308
|
+
- name: Install the dependency at the version under test
|
|
309
|
+
run: |
|
|
310
|
+
if [ "${{ matrix.dependency-version }}" = "floor" ]; then
|
|
311
|
+
pip install "${{ matrix.dependency }}==${{ matrix.floor }}"
|
|
312
|
+
else
|
|
313
|
+
pip install --upgrade "${{ matrix.dependency }}"
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
- name: Run this plugin's contract tests
|
|
317
|
+
run: |
|
|
318
|
+
name="${{ matrix.plugin }}"
|
|
319
|
+
test_name="${name#whytrail-}"
|
|
320
|
+
test_name="${test_name//-/_}"
|
|
321
|
+
pytest "tests/plugin_contract/test_${test_name}_plugin.py" -q
|
|
322
|
+
|
|
323
|
+
benchmarks:
|
|
324
|
+
runs-on: ubuntu-latest
|
|
325
|
+
steps:
|
|
326
|
+
- uses: actions/checkout@v4
|
|
327
|
+
- uses: actions/setup-python@v5
|
|
328
|
+
with:
|
|
329
|
+
python-version: "3.12"
|
|
330
|
+
- run: pip install -e ".[dev]"
|
|
331
|
+
- name: Run performance benchmarks (informational, not a merge gate yet)
|
|
332
|
+
run: pytest benchmarks/ --benchmark-only
|
|
333
|
+
continue-on-error: true
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- "docs/**"
|
|
8
|
+
- "mkdocs.yml"
|
|
9
|
+
- "README.md"
|
|
10
|
+
- "CHANGELOG.md"
|
|
11
|
+
- ".github/workflows/docs.yml"
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
pages: write
|
|
17
|
+
id-token: write
|
|
18
|
+
|
|
19
|
+
concurrency:
|
|
20
|
+
group: pages
|
|
21
|
+
cancel-in-progress: false
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
build:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: "3.12"
|
|
32
|
+
|
|
33
|
+
- name: Install docs dependencies
|
|
34
|
+
run: pip install -e ".[docs]"
|
|
35
|
+
|
|
36
|
+
- name: Build with mkdocs --strict
|
|
37
|
+
run: mkdocs build --strict
|
|
38
|
+
|
|
39
|
+
- uses: actions/upload-pages-artifact@v3
|
|
40
|
+
with:
|
|
41
|
+
path: site
|
|
42
|
+
|
|
43
|
+
deploy:
|
|
44
|
+
needs: build
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
environment:
|
|
47
|
+
name: github-pages
|
|
48
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
49
|
+
steps:
|
|
50
|
+
- id: deployment
|
|
51
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Triggers on a GitHub Release being published, not on every push -- a
|
|
4
|
+
# release is a deliberate, human action, and PyPI publishes are
|
|
5
|
+
# effectively irreversible (a version number can never be reused, even
|
|
6
|
+
# if yanked). Tag the release vX.Y.Z to match pyproject.toml's version.
|
|
7
|
+
on:
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
|
|
21
|
+
- name: Install build tooling
|
|
22
|
+
run: pip install build twine
|
|
23
|
+
|
|
24
|
+
- name: Build sdist and wheel
|
|
25
|
+
run: python -m build
|
|
26
|
+
|
|
27
|
+
- name: Validate metadata
|
|
28
|
+
run: twine check dist/*
|
|
29
|
+
|
|
30
|
+
- uses: actions/upload-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: dist
|
|
33
|
+
path: dist/
|
|
34
|
+
|
|
35
|
+
publish:
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment: pypi
|
|
39
|
+
permissions:
|
|
40
|
+
# id-token: write is what makes this "trusted publishing" -- PyPI
|
|
41
|
+
# verifies this workflow's OIDC identity directly, no API token
|
|
42
|
+
# stored as a repo secret, nothing to rotate or leak.
|
|
43
|
+
id-token: write
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/download-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: dist
|
|
48
|
+
path: dist/
|
|
49
|
+
|
|
50
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|