From 9103358bf83e8d8ed09c10db7c39a29d74ad0989 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 05:54:43 -0500 Subject: [PATCH 01/13] PYTHON-6125 Use python3 instead of bare python in cp_psl --- .evergreen/resync-specs.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index 4feca0aa5b..6bdee5a993 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -85,7 +85,9 @@ cp_psl () { echo "Could not find the public suffix list at $src" >&2 return 1 fi - python - "$src" "$PYMONGO"/pymongo/public_suffix_list.dat <<'EOF' + # Use python3 explicitly: bare python may resolve to Python 2 on CI hosts, + # which cannot parse the f-strings below. + python3 - "$src" "$PYMONGO"/pymongo/public_suffix_list.dat <<'EOF' import sys src, dst = sys.argv[1], sys.argv[2] From 68d7e603ab86c625248e9319b18ce4c005125b19 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 06:35:37 -0500 Subject: [PATCH 02/13] PYTHON-6125 Make cp_psl atomic and surface sync errors in task log --- .evergreen/resync-specs.sh | 60 ++++++++++++++++++-------- .evergreen/scripts/resync-all-specs.py | 5 +++ 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index 6bdee5a993..cd8a487001 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -88,27 +88,51 @@ cp_psl () { # Use python3 explicitly: bare python may resolve to Python 2 on CI hosts, # which cannot parse the f-strings below. python3 - "$src" "$PYMONGO"/pymongo/public_suffix_list.dat <<'EOF' +import os import sys src, dst = sys.argv[1], sys.argv[2] -with open(src, encoding="utf-8") as f, open(dst, "w", encoding="utf-8") as out: - for line in f: - rule = line.strip() - if rule and not rule.startswith("//") and not rule.isascii(): - # "!" and "*." prefixes are not valid IDNA, so encode only the - # domain part and re-attach the prefix afterwards. - prefix = "" - for p in ("!", "*."): - if rule.startswith(p): - prefix, rule = p, rule[len(p) :] - break - try: - rule = rule.encode("idna").decode("ascii") - except UnicodeError: - print(f"Could not convert rule to Punycode: {prefix}{rule}", file=sys.stderr) - sys.exit(1) - line = f"{prefix}{rule.lower()}\n" - out.write(line) + +# Read and convert everything in memory first, then write atomically: a +# truncated or failed conversion must never clobber the vendored list. +with open(src, encoding="utf-8") as f: + lines = f.readlines() +if not lines: + print(f"Source public suffix list is empty: {src}", file=sys.stderr) + sys.exit(1) + +converted = [] +for line in lines: + rule = line.strip() + if rule and not rule.startswith("//") and not rule.isascii(): + # "!" and "*." prefixes are not valid IDNA, so encode only the + # domain part and re-attach the prefix afterwards. + prefix = "" + for p in ("!", "*."): + if rule.startswith(p): + prefix, rule = p, rule[len(p) :] + break + try: + rule = rule.encode("idna").decode("ascii") + except UnicodeError: + print(f"Could not convert rule to Punycode: {prefix}{rule}", file=sys.stderr) + sys.exit(1) + line = f"{prefix}{rule.lower()}\n" + converted.append(line) + +# The conversion is line-for-line, so the output must have exactly as many +# lines as the input. +if len(converted) != len(lines): + print( + f"Conversion lost lines: {len(lines)} in, {len(converted)} out", + file=sys.stderr, + ) + sys.exit(1) + +tmp = dst + ".tmp" +with open(tmp, "w", encoding="utf-8") as out: + out.writelines(converted) +os.replace(tmp, dst) EOF } diff --git a/.evergreen/scripts/resync-all-specs.py b/.evergreen/scripts/resync-all-specs.py index 78afe8a4d8..090aae41a6 100644 --- a/.evergreen/scripts/resync-all-specs.py +++ b/.evergreen/scripts/resync-all-specs.py @@ -28,6 +28,10 @@ def resync_specs(directory: pathlib.Path, errored: dict[str, str]) -> None: ) except CalledProcessError as exc: errored[spec.name] = exc.stderr + # Surface the failure in the task log too: the PR-body summary is + # only written at the end, and is lost entirely if PR creation + # fails. + print(f"Spec sync failed for {spec.name}:\n{exc.stderr}", flush=True) print("Done syncing specs") @@ -60,6 +64,7 @@ def apply_patches(errored): ) except CalledProcessError as exc: errored[f"{patch}"] = exc.stderr + print(f"Patch application failed for {patch}:\n{exc.stderr}", flush=True) def check_new_spec_directories(directory: pathlib.Path) -> list[str]: From bfe5e9f15e9c0b45e3e6f807f41055c03df8004a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 06:38:03 -0500 Subject: [PATCH 03/13] PYTHON-6125 Attribute spec resync commits to the PR bot --- .evergreen/scripts/create-spec-pr.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.evergreen/scripts/create-spec-pr.sh b/.evergreen/scripts/create-spec-pr.sh index 0867ae5487..9545f3a34a 100755 --- a/.evergreen/scripts/create-spec-pr.sh +++ b/.evergreen/scripts/create-spec-pr.sh @@ -33,6 +33,18 @@ branch="spec-resync-"$(date '+%m-%d-%Y') git remote set-url origin https://x-access-token:${token}@github.com/$owner/$repo.git git checkout -b $branch "origin/main" + +# Attribute the commit to the bot user instead of the Evergreen host user. +# The noreply email follows GitHub's standard + form so the commit +# is linked to the bot account on GitHub. Cosmetic: fall back silently to the +# host identity if the lookup fails. +bot_login="mongodb-drivers-pr-bot[bot]" +bot_id=$(curl -gsm 10 "https://api.github.com/users/${bot_login}" | jq -r '.id // empty' || true) +if [ -n "$bot_id" ]; then + git config user.name "$bot_login" + git config user.email "${bot_id}+${bot_login}@users.noreply.github.com" +fi + git add ./test git commit -am "resyncing specs $(date '+%m-%d-%Y')" echo "Creating the git checkout... done." From 064bb1001a8c29837473a6d214aecf0bb9610dbd Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 06:42:03 -0500 Subject: [PATCH 04/13] PYTHON-6125 Make same-day spec resync PR attempts idempotent --- .evergreen/scripts/create-spec-pr.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.evergreen/scripts/create-spec-pr.sh b/.evergreen/scripts/create-spec-pr.sh index 9545f3a34a..98e4b23a7f 100755 --- a/.evergreen/scripts/create-spec-pr.sh +++ b/.evergreen/scripts/create-spec-pr.sh @@ -49,7 +49,25 @@ git add ./test git commit -am "resyncing specs $(date '+%m-%d-%Y')" echo "Creating the git checkout... done." -git push origin $branch +# Force-push: the branch name is date-stamped, so any existing remote branch +# is an artifact of an earlier same-day attempt and is always reproducible +# from origin/main plus a fresh commit. This makes same-day retries idempotent. +git push --force origin $branch + +# If a PR for this branch already exists (e.g. an earlier same-day attempt), +# reuse it rather than failing to create a duplicate. +existing_pr_url=$(curl -gs \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $token" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + --url "https://api.github.com/repos/$owner/$repo/pulls?head=$owner:$branch&state=open" \ + | jq -r '.[0].html_url // empty') +if [ -n "$existing_pr_url" ]; then + echo "$existing_pr_url" + echo "Creating the PR... done. (PR already existed; branch was force-pushed)" + rm -rf $tools + exit 0 +fi # Build the payload as a file so the body content is always properly # JSON-escaped, rather than interpolated into the command line as text. From 973f26c0fb82ed5922dca3c82d384fabaece6e0f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 06:42:27 -0500 Subject: [PATCH 05/13] PYTHON-6125 Refresh summary when reusing an existing spec resync PR --- .evergreen/scripts/create-spec-pr.sh | 38 +++++++++++++++++----------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/.evergreen/scripts/create-spec-pr.sh b/.evergreen/scripts/create-spec-pr.sh index 98e4b23a7f..41afa9324a 100755 --- a/.evergreen/scripts/create-spec-pr.sh +++ b/.evergreen/scripts/create-spec-pr.sh @@ -54,21 +54,6 @@ echo "Creating the git checkout... done." # from origin/main plus a fresh commit. This makes same-day retries idempotent. git push --force origin $branch -# If a PR for this branch already exists (e.g. an earlier same-day attempt), -# reuse it rather than failing to create a duplicate. -existing_pr_url=$(curl -gs \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $token" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - --url "https://api.github.com/repos/$owner/$repo/pulls?head=$owner:$branch&state=open" \ - | jq -r '.[0].html_url // empty') -if [ -n "$existing_pr_url" ]; then - echo "$existing_pr_url" - echo "Creating the PR... done. (PR already existed; branch was force-pushed)" - rm -rf $tools - exit 0 -fi - # Build the payload as a file so the body content is always properly # JSON-escaped, rather than interpolated into the command line as text. payload_file=$(mktemp) @@ -79,6 +64,29 @@ jq -n \ --rawfile body "$1" \ '{title: $title, body: $body, head: $head, base: "main"}' > "$payload_file" +# If a PR for this branch already exists (e.g. an earlier same-day attempt), +# reuse it rather than failing to create a duplicate. Refresh its title and +# body so the summary reflects this run. +existing_pr_json=$(curl -gs \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $token" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + --url "https://api.github.com/repos/$owner/$repo/pulls?head=$owner:$branch&state=open") +existing_pr_url=$(echo "$existing_pr_json" | jq -r '.[0].html_url // empty') +if [ -n "$existing_pr_url" ]; then + existing_pr_number=$(echo "$existing_pr_json" | jq -r '.[0].number // empty') + curl -sgX PATCH \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $token" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -d "{\"title\": \"[Spec Resync] $(date '+%m-%d-%Y')\", \"body\": $(jq -Rs . < "$1")}" \ + --url "https://api.github.com/repos/$owner/$repo/pulls/${existing_pr_number}" > /dev/null + echo "$existing_pr_url" + echo "Creating the PR... done. (PR already existed; branch and summary were updated)" + rm -rf $tools + exit 0 +fi + resp=$(curl -L \ -X POST \ -H "Accept: application/vnd.github+json" \ From f0ea4db1e59c941be6638a26636165eaa3f777f4 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 06:50:28 -0500 Subject: [PATCH 06/13] PYTHON-6125 Use toolchain python in cp_psl and write plain PR summary --- .evergreen/resync-specs.sh | 16 ++++++++++++---- .evergreen/scripts/resync-all-specs.py | 5 +++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index cd8a487001..654cec1613 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -85,9 +85,16 @@ cp_psl () { echo "Could not find the public suffix list at $src" >&2 return 1 fi - # Use python3 explicitly: bare python may resolve to Python 2 on CI hosts, - # which cannot parse the f-strings below. - python3 - "$src" "$PYMONGO"/pymongo/public_suffix_list.dat <<'EOF' + # Prefer the toolchain interpreter on Evergreen hosts: bare python may + # resolve to Python 2, and the default python3 there may be RHEL8's + # platform-python 3.6, which predates the APIs used below. Fall back to + # python3 locally. + if [ -x /opt/devtools/bin/python3.11 ]; then + PY=/opt/devtools/bin/python3.11 + else + PY=python3 + fi + "$PY" - "$src" "$PYMONGO"/pymongo/public_suffix_list.dat <<'EOF' import os import sys @@ -104,7 +111,8 @@ if not lines: converted = [] for line in lines: rule = line.strip() - if rule and not rule.startswith("//") and not rule.isascii(): + # str.isascii() is 3.7+; keep this 3.6-compatible via ord(). + if rule and not rule.startswith("//") and any(ord(ch) > 127 for ch in rule): # "!" and "*." prefixes are not valid IDNA, so encode only the # domain part and re-attach the prefix afterwards. prefix = "" diff --git a/.evergreen/scripts/resync-all-specs.py b/.evergreen/scripts/resync-all-specs.py index 090aae41a6..584c5b32bb 100644 --- a/.evergreen/scripts/resync-all-specs.py +++ b/.evergreen/scripts/resync-all-specs.py @@ -132,9 +132,10 @@ def write_summary(errored: dict[str, str], new: list[str], filename: str | None) if filename is None: print(f"\n{pr_body}") else: + # Plain text: the consumer embeds this file via jq --rawfile, + # which handles the JSON escaping itself. with open(filename, "w") as f: - # replacements made for proper json - f.write(pr_body.replace("\n", "\\n").replace("\t", "\\t")) + f.write(pr_body) def main(args: Namespace): From 109e8729da71d8b956c69457a05b5626e167561a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 07:44:43 -0500 Subject: [PATCH 07/13] PYTHON-6125 Retry spec PR branch push on transient 403s --- .evergreen/scripts/create-spec-pr.sh | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.evergreen/scripts/create-spec-pr.sh b/.evergreen/scripts/create-spec-pr.sh index 41afa9324a..d5f413f24d 100755 --- a/.evergreen/scripts/create-spec-pr.sh +++ b/.evergreen/scripts/create-spec-pr.sh @@ -52,7 +52,22 @@ echo "Creating the git checkout... done." # Force-push: the branch name is date-stamped, so any existing remote branch # is an artifact of an earlier same-day attempt and is always reproducible # from origin/main plus a fresh commit. This makes same-day retries idempotent. -git push --force origin $branch +# GitHub intermittently rejects automated pushes with a 403 (e.g. secondary +# rate limiting on the receive-pack endpoint), which self-heals after a short +# wait, so retry a few times before giving up. +push_ok=false +for _attempt in 1 2 3; do + if git push --force origin $branch; then + push_ok=true + break + fi + echo "Push attempt ${_attempt} failed; retrying in 30s..." + sleep 30 +done +if [ "$push_ok" != true ]; then + echo "Failed to push $branch after 3 attempts!" >&2 + exit 1 +fi # Build the payload as a file so the body content is always properly # JSON-escaped, rather than interpolated into the command line as text. From e159284d1803959809303ce150c9f34ca770928d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 08:01:42 -0500 Subject: [PATCH 08/13] PYTHON-6132 Trim comments --- .evergreen/resync-specs.sh | 12 +++--------- .evergreen/scripts/create-spec-pr.sh | 17 ++++------------- .evergreen/scripts/resync-all-specs.py | 4 +--- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index 654cec1613..0d7880ea28 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -85,10 +85,7 @@ cp_psl () { echo "Could not find the public suffix list at $src" >&2 return 1 fi - # Prefer the toolchain interpreter on Evergreen hosts: bare python may - # resolve to Python 2, and the default python3 there may be RHEL8's - # platform-python 3.6, which predates the APIs used below. Fall back to - # python3 locally. + # Prefer the CI toolchain interpreter; the default python3 may be 3.6 on RHEL8. if [ -x /opt/devtools/bin/python3.11 ]; then PY=/opt/devtools/bin/python3.11 else @@ -100,8 +97,7 @@ import sys src, dst = sys.argv[1], sys.argv[2] -# Read and convert everything in memory first, then write atomically: a -# truncated or failed conversion must never clobber the vendored list. +# Convert in memory and write atomically so failures can't truncate the list. with open(src, encoding="utf-8") as f: lines = f.readlines() if not lines: @@ -111,7 +107,7 @@ if not lines: converted = [] for line in lines: rule = line.strip() - # str.isascii() is 3.7+; keep this 3.6-compatible via ord(). + # ord() instead of str.isascii() for 3.6 compat. if rule and not rule.startswith("//") and any(ord(ch) > 127 for ch in rule): # "!" and "*." prefixes are not valid IDNA, so encode only the # domain part and re-attach the prefix afterwards. @@ -128,8 +124,6 @@ for line in lines: line = f"{prefix}{rule.lower()}\n" converted.append(line) -# The conversion is line-for-line, so the output must have exactly as many -# lines as the input. if len(converted) != len(lines): print( f"Conversion lost lines: {len(lines)} in, {len(converted)} out", diff --git a/.evergreen/scripts/create-spec-pr.sh b/.evergreen/scripts/create-spec-pr.sh index d5f413f24d..3254fd41e9 100755 --- a/.evergreen/scripts/create-spec-pr.sh +++ b/.evergreen/scripts/create-spec-pr.sh @@ -34,10 +34,7 @@ branch="spec-resync-"$(date '+%m-%d-%Y') git remote set-url origin https://x-access-token:${token}@github.com/$owner/$repo.git git checkout -b $branch "origin/main" -# Attribute the commit to the bot user instead of the Evergreen host user. -# The noreply email follows GitHub's standard + form so the commit -# is linked to the bot account on GitHub. Cosmetic: fall back silently to the -# host identity if the lookup fails. +# Attribute the commit to the bot user; fall back to the host identity. bot_login="mongodb-drivers-pr-bot[bot]" bot_id=$(curl -gsm 10 "https://api.github.com/users/${bot_login}" | jq -r '.id // empty' || true) if [ -n "$bot_id" ]; then @@ -49,12 +46,8 @@ git add ./test git commit -am "resyncing specs $(date '+%m-%d-%Y')" echo "Creating the git checkout... done." -# Force-push: the branch name is date-stamped, so any existing remote branch -# is an artifact of an earlier same-day attempt and is always reproducible -# from origin/main plus a fresh commit. This makes same-day retries idempotent. -# GitHub intermittently rejects automated pushes with a 403 (e.g. secondary -# rate limiting on the receive-pack endpoint), which self-heals after a short -# wait, so retry a few times before giving up. +# Date-stamped branch: force-push makes same-day reruns idempotent; retry +# absorbs GitHub's intermittent 403s on the receive-pack endpoint. push_ok=false for _attempt in 1 2 3; do if git push --force origin $branch; then @@ -79,9 +72,7 @@ jq -n \ --rawfile body "$1" \ '{title: $title, body: $body, head: $head, base: "main"}' > "$payload_file" -# If a PR for this branch already exists (e.g. an earlier same-day attempt), -# reuse it rather than failing to create a duplicate. Refresh its title and -# body so the summary reflects this run. +# Reuse an open PR for this branch (same-day rerun) instead of duplicating it. existing_pr_json=$(curl -gs \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer $token" \ diff --git a/.evergreen/scripts/resync-all-specs.py b/.evergreen/scripts/resync-all-specs.py index 584c5b32bb..da61366748 100644 --- a/.evergreen/scripts/resync-all-specs.py +++ b/.evergreen/scripts/resync-all-specs.py @@ -28,9 +28,7 @@ def resync_specs(directory: pathlib.Path, errored: dict[str, str]) -> None: ) except CalledProcessError as exc: errored[spec.name] = exc.stderr - # Surface the failure in the task log too: the PR-body summary is - # only written at the end, and is lost entirely if PR creation - # fails. + # The PR-body summary is lost if PR creation fails. print(f"Spec sync failed for {spec.name}:\n{exc.stderr}", flush=True) print("Done syncing specs") From 303d3f73a6f99d7bcb7713f1c05904d0f853d6fd Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 08:06:47 -0500 Subject: [PATCH 09/13] PYTHON-6132 Use newest mongodbtoolchain python for spec resync --- .evergreen/resync-specs.sh | 9 +++------ .evergreen/scripts/resync-all-specs.sh | 5 ++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index 0d7880ea28..9e1ee507e7 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -85,12 +85,9 @@ cp_psl () { echo "Could not find the public suffix list at $src" >&2 return 1 fi - # Prefer the CI toolchain interpreter; the default python3 may be 3.6 on RHEL8. - if [ -x /opt/devtools/bin/python3.11 ]; then - PY=/opt/devtools/bin/python3.11 - else - PY=python3 - fi + # Use the newest mongodbtoolchain python on CI hosts; python3 fallback locally. + PY=$(ls -1d /opt/mongodbtoolchain/v*/bin/python3 2>/dev/null | sort -V | tail -1) + [ -n "$PY" ] || PY=python3 "$PY" - "$src" "$PYMONGO"/pymongo/public_suffix_list.dat <<'EOF' import os import sys diff --git a/.evergreen/scripts/resync-all-specs.sh b/.evergreen/scripts/resync-all-specs.sh index 41e4a2bc73..41461b31a6 100755 --- a/.evergreen/scripts/resync-all-specs.sh +++ b/.evergreen/scripts/resync-all-specs.sh @@ -34,7 +34,10 @@ then # we're running locally python3 ./.evergreen/scripts/resync-all-specs.py else - /opt/devtools/bin/python3.11 ./.evergreen/scripts/resync-all-specs.py --filename "$PR_DESC" + # Use the newest mongodbtoolchain python; the default python3 may be too old. + PY=$(ls -1d /opt/mongodbtoolchain/v*/bin/python3 2>/dev/null | sort -V | tail -1) + [ -n "$PY" ] || PY=python3 + "$PY" ./.evergreen/scripts/resync-all-specs.py --filename "$PR_DESC" if [[ -f $PR_DESC ]]; then # changes were made -> call scrypt to create PR for us .evergreen/scripts/create-spec-pr.sh "$PR_DESC" From ef693af719f0317c1d1a148ef589b3a8dd7cd026 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 08:07:47 -0500 Subject: [PATCH 10/13] PYTHON-6132 Drop py3.6 compat from cp_psl --- .evergreen/resync-specs.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index 9e1ee507e7..da3b49732d 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -104,8 +104,7 @@ if not lines: converted = [] for line in lines: rule = line.strip() - # ord() instead of str.isascii() for 3.6 compat. - if rule and not rule.startswith("//") and any(ord(ch) > 127 for ch in rule): + if rule and not rule.startswith("//") and not rule.isascii(): # "!" and "*." prefixes are not valid IDNA, so encode only the # domain part and re-attach the prefix afterwards. prefix = "" From dec092ebb8358f1b06d92ef9a968bfbce103db25 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 08:16:35 -0500 Subject: [PATCH 11/13] PYTHON-6132 Address PR review: push retry messaging and PATCH error handling --- .evergreen/scripts/create-spec-pr.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.evergreen/scripts/create-spec-pr.sh b/.evergreen/scripts/create-spec-pr.sh index 3254fd41e9..f4fe4e3942 100755 --- a/.evergreen/scripts/create-spec-pr.sh +++ b/.evergreen/scripts/create-spec-pr.sh @@ -54,8 +54,10 @@ for _attempt in 1 2 3; do push_ok=true break fi - echo "Push attempt ${_attempt} failed; retrying in 30s..." - sleep 30 + if [ "$_attempt" -lt 3 ]; then + echo "Push attempt ${_attempt} failed; retrying in 30s..." + sleep 30 + fi done if [ "$push_ok" != true ]; then echo "Failed to push $branch after 3 attempts!" >&2 @@ -81,12 +83,16 @@ existing_pr_json=$(curl -gs \ existing_pr_url=$(echo "$existing_pr_json" | jq -r '.[0].html_url // empty') if [ -n "$existing_pr_url" ]; then existing_pr_number=$(echo "$existing_pr_json" | jq -r '.[0].number // empty') - curl -sgX PATCH \ + if ! curl -sfX PATCH \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer $token" \ -H "X-GitHub-Api-Version: 2022-11-28" \ -d "{\"title\": \"[Spec Resync] $(date '+%m-%d-%Y')\", \"body\": $(jq -Rs . < "$1")}" \ --url "https://api.github.com/repos/$owner/$repo/pulls/${existing_pr_number}" > /dev/null + then + echo "Failed to update PR summary!" >&2 + exit 1 + fi echo "$existing_pr_url" echo "Creating the PR... done. (PR already existed; branch and summary were updated)" rm -rf $tools From 7c1c9dce7f27378272dbdc8b816afeee183ec2c0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 08:39:00 -0500 Subject: [PATCH 12/13] PYTHON-6132 Address PR review: reject rule-less PSL sources and validate interpreter version --- .evergreen/resync-specs.sh | 7 +++++++ .evergreen/scripts/resync-all-specs.sh | 2 ++ 2 files changed, 9 insertions(+) diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index da3b49732d..840d05bf1e 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -88,6 +88,8 @@ cp_psl () { # Use the newest mongodbtoolchain python on CI hosts; python3 fallback locally. PY=$(ls -1d /opt/mongodbtoolchain/v*/bin/python3 2>/dev/null | sort -V | tail -1) [ -n "$PY" ] || PY=python3 + [ "$("$PY" -c 'import sys; print(sys.version_info >= (3, 7))' 2>/dev/null)" = True ] \ + || { echo "Error: $PY is not Python 3.7+, which this conversion requires." >&2; return 1; } "$PY" - "$src" "$PYMONGO"/pymongo/public_suffix_list.dat <<'EOF' import os import sys @@ -127,6 +129,11 @@ if len(converted) != len(lines): ) sys.exit(1) +# A blank or comment-only source must not replace the real list. +if not any(ln.strip() and not ln.strip().startswith("//") for ln in converted): + print(f"Source public suffix list has no rules: {src}", file=sys.stderr) + sys.exit(1) + tmp = dst + ".tmp" with open(tmp, "w", encoding="utf-8") as out: out.writelines(converted) diff --git a/.evergreen/scripts/resync-all-specs.sh b/.evergreen/scripts/resync-all-specs.sh index 41461b31a6..11dd2e65ba 100755 --- a/.evergreen/scripts/resync-all-specs.sh +++ b/.evergreen/scripts/resync-all-specs.sh @@ -37,6 +37,8 @@ else # Use the newest mongodbtoolchain python; the default python3 may be too old. PY=$(ls -1d /opt/mongodbtoolchain/v*/bin/python3 2>/dev/null | sort -V | tail -1) [ -n "$PY" ] || PY=python3 + [ "$("$PY" -c 'import sys; print(sys.version_info >= (3, 7))' 2>/dev/null)" = True ] \ + || { echo "Error: $PY is not Python 3.7+, which resync-all-specs.py requires." >&2; exit 1; } "$PY" ./.evergreen/scripts/resync-all-specs.py --filename "$PR_DESC" if [[ -f $PR_DESC ]]; then # changes were made -> call scrypt to create PR for us From e0423900a1bd53c5b5b34fa853e738fe4a66536f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Sep 2026 08:47:59 -0500 Subject: [PATCH 13/13] PYTHON-6132 Address PR review: reuse local branch and capture text stderr --- .evergreen/scripts/create-spec-pr.sh | 3 ++- .evergreen/scripts/resync-all-specs.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.evergreen/scripts/create-spec-pr.sh b/.evergreen/scripts/create-spec-pr.sh index f4fe4e3942..cffd8dc4de 100755 --- a/.evergreen/scripts/create-spec-pr.sh +++ b/.evergreen/scripts/create-spec-pr.sh @@ -32,7 +32,8 @@ echo "Creating the git checkout..." branch="spec-resync-"$(date '+%m-%d-%Y') git remote set-url origin https://x-access-token:${token}@github.com/$owner/$repo.git -git checkout -b $branch "origin/main" +# -B resets the branch if a reused checkout already has it, keeping reruns idempotent. +git checkout -B "$branch" "origin/main" # Attribute the commit to the bot user; fall back to the host identity. bot_login="mongodb-drivers-pr-bot[bot]" diff --git a/.evergreen/scripts/resync-all-specs.py b/.evergreen/scripts/resync-all-specs.py index da61366748..52c276b8b8 100644 --- a/.evergreen/scripts/resync-all-specs.py +++ b/.evergreen/scripts/resync-all-specs.py @@ -59,6 +59,7 @@ def apply_patches(errored): ], check=True, stderr=subprocess.PIPE, + text=True, ) except CalledProcessError as exc: errored[f"{patch}"] = exc.stderr