Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ first: an interrupted upload is completed only if its existing artifact matches.
A reused version with changed source, unexpected build configuration or yanked
files fails visibly and needs a new version.

After an upload, a separate clean checkout of the tested commit verifies the
public wheel and source archive again, including source, metadata and PyPI
hashes. Their filenames, hashes and download URLs must also appear in PyPI's
public Simple index. This catches a stale preflight 404 followed by an upload
that skipped an already existing artifact with different contents. It also
waits for the release to become visible to ordinary package installers.
The existing-release skip path performs the same bounded public confirmation in
the read-only gate, so a later scheduled retry can recover from an earlier
visibility timeout without a build, upload or publishing token.

Missing or partially visible publication data can retry six times, with waits
of 5, 10, 20, 40 and 60 seconds (135 seconds total backoff). Visible source,
metadata, hash or index identity mismatches fail immediately. The verification
step has an eight-minute hard timeout including network calls. A timeout or
mismatch fails the publisher even when PyPI accepted the upload; it never
deletes or replaces immutable files. Scheduled retries can finish a matching
partial release after visibility recovers. Client-side checks cannot make the
preflight, upload and public index update one atomic transaction, or prevent a
later owner action from changing publication availability.

## One-time PyPI configuration

The PyPI `csdid` project must register this GitHub Trusted Publisher:
Expand Down
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ directory, and `FASTER_MODE.md` for scope and reproduction instructions.
### Key Conventions

- Version is defined in `csdid/_version.py` as `__version`; update it there for releases.
- The README is auto-generated: edit `README.qmd` (Quarto), not `readme.md` directly. CI renders and commits the output on push to `main`.
- The README is auto-generated: edit `README.qmd` (Quarto), not `README.md` directly. CI renders it on pushes to `main` and proposes a PR for generated changes. Keep badges, examples and citation instructions in `README.qmd`; do not create a second case-variant README file.
- PyPI publishing is automated via CI on push to `main` using `python setup.py sdist` + Twine.

## Subprojects
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ jobs:
python-version: ${{ matrix.python-version }}
architecture: x64

- name: Check README source and generated content
run: |
python scripts/check_readme.py
python -m unittest discover -s scripts/tests -p 'test_*.py'

- name: Read reviewed Python DRDID source
id: peer
run: |
Expand Down
105 changes: 80 additions & 25 deletions .github/workflows/gen_readme.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,106 @@ on:
- main
workflow_dispatch:

concurrency:
group: readme-generation-${{ github.ref }}
cancel-in-progress: true

jobs:
CreateQuarto:
runs-on: ubuntu-latest
# Manual runs on other branches validate a preview; only main publishes PRs.
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
# `contents: write` lets the action create the branch; `pull-requests: write`
# lets it open the PR. We no longer push to `main` directly because `main`
# is a protected branch (direct pushes are rejected with GH006).
contents: write
pull-requests: write
defaults:
run:
working-directory: csdid
env:
MPLBACKEND: Agg
PYTHONHASHSEED: '0'
OPENBLAS_NUM_THREADS: '1'
OMP_NUM_THREADS: '1'
MKL_NUM_THREADS: '1'
steps:
- name: Chequear el código
uses: actions/checkout@v4
- name: Check out the source being documented
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
path: csdid

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.12'
cache: pip
cache-dependency-path: csdid/docs/requirements-readme.txt

- name: Check README source content
run: python scripts/check_readme.py --source-only

- name: Read the reviewed Python DRDID source
id: peer
run: >-
python scripts/check_release_ci.py lock
--lock csdid/test_csdid/joint_sync/python_drdid.json

- name: Check out the reviewed Python DRDID
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
repository: ${{ steps.peer.outputs.repository }}
ref: ${{ steps.peer.outputs.commit }}
path: python-drdid
persist-credentials: false

- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2

- name: Configurar Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
cache: "pip"
version: '1.8.25'

- name: Install csdid and dependencies
- name: Install the checked-out package and rendering dependencies
run: |
pip uninstall -y csdid || true
pip install git+https://github.com/d2cml-ai/csdid/
pip install nbclient nbformat PyYAML
pip install git+https://github.com/d2cml-ai/DRDID
pip install -r requirements.txt
python -m venv "$RUNNER_TEMP/readme-venv"
echo "$RUNNER_TEMP/readme-venv/bin" >> "$GITHUB_PATH"
echo "QUARTO_PYTHON=$RUNNER_TEMP/readme-venv/bin/python" >> "$GITHUB_ENV"
"$RUNNER_TEMP/readme-venv/bin/python" -m pip install \
-r docs/requirements-readme.txt "$GITHUB_WORKSPACE/python-drdid" .
"$RUNNER_TEMP/readme-venv/bin/python" -m pip check

- name: Generar quarto doc
run: quarto render README.qmd
- name: Render twice and reject unstable output
run: |
quarto render README.qmd
cp README.md "$RUNNER_TEMP/README-first.md"
find README_files/figure-commonmark -type f -name '*.png' -print0 \
| sort -z | xargs -0 sha256sum > "$RUNNER_TEMP/readme-first.sha256"
quarto render README.qmd
cmp "$RUNNER_TEMP/README-first.md" README.md
find README_files/figure-commonmark -type f -name '*.png' -print0 \
| sort -z | xargs -0 sha256sum > "$RUNNER_TEMP/readme-second.sha256"
cmp "$RUNNER_TEMP/readme-first.sha256" "$RUNNER_TEMP/readme-second.sha256"

- name: Check generated content and regression guards
run: |
python scripts/check_readme.py
python -m unittest discover -s scripts/tests -p 'test_*.py'

- name: Open a PR with the regenerated README
if: github.ref == 'refs/heads/main'
uses: peter-evans/create-pull-request@v7
with:
path: csdid
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "docs: update generated README"
title: "docs: update generated README"
add-paths: |
README.md
README_files/figure-commonmark/*.png
commit-message: 'docs: update generated README'
title: 'docs: update generated README'
body: |
Automated regeneration of `readme.md` from `README.qmd` by the
**Actualizar Readme** workflow.
Automated regeneration of `README.md` from `README.qmd` using
the checked-out package, pinned rendering dependencies, and a fixed
example seed. Content guards and two identical renders passed.

Merge this PR to publish the updated README. If there are no changes
to the rendered README, no PR is created.
Edit `README.qmd` to change authored documentation. This PR contains
only the rendered README and its generated figures.
branch: auto/update-readme
delete-branch: true
author: "github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
- name: Skip an identical release or wait for its backend
if: steps.verify.outputs.ready == 'true'
id: release
timeout-minutes: 8
working-directory: source
run: python3 scripts/check_release_ci.py release

Expand Down Expand Up @@ -88,6 +89,7 @@ jobs:
- name: Recheck release state after any environment delay
if: steps.verify.outputs.ready == 'true'
id: release
timeout-minutes: 8
working-directory: source
run: python3 scripts/check_release_ci.py release

Expand Down Expand Up @@ -146,9 +148,25 @@ jobs:

- name: Publish to PyPI
if: steps.final-ci.outputs.ready == 'true'
id: publish
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
with:
packages-dir: source/dist/
# Safely finish a partial upload. The gate verifies existing artifacts
# instead of silently accepting changed code under an existing version.
skip-existing: true

- name: Check out clean reviewed source for public verification
if: steps.publish.outcome == 'success'
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
ref: ${{ needs.gate.outputs.sha }}
path: publication-source
persist-credentials: false

- name: Verify public artifacts and installer index after upload
if: steps.publish.outcome == 'success'
# Includes network time, in addition to the bounded 135s of backoff.
timeout-minutes: 8
working-directory: publication-source
run: python3 scripts/check_release_ci.py published
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
include readme.md
include README.md
include LICENSE
include CHANGELOG_v0.3.0.md
include CHANGELOG_v0.4.3.md
Expand Down
Loading
Loading