From db9f1d3c0b0694051721baaae723a87df8935a4f Mon Sep 17 00:00:00 2001 From: "Kevin A. Mitchell" Date: Mon, 5 Oct 2026 23:08:55 -0500 Subject: [PATCH 1/6] pre-commit: Preserve fixture bytes during repository hygiene checks Keep service inputs out of mutating formatters and preserve their Git checkout bytes. Add explicit LF and whitespace defaults while retaining Markdown hard breaks and the existing Python attributes. Exclude the uv lockfile from TOML sorting. Assisted-by: Codex --- .editorconfig | 3 +++ .gitattributes | 56 ++++++++++++++++++++++++++++++----------- .pre-commit-config.yaml | 8 ++++-- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/.editorconfig b/.editorconfig index b780dfd7..8fb2983d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,11 +3,14 @@ root = true [*] charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.md] +trim_trailing_whitespace = false indent_size = 2 max_line_length = 80 diff --git a/.gitattributes b/.gitattributes index adc0809a..157225f5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,7 @@ +# BEGIN source: https://github.com/gitattributes/gitattributes/blob/2c20a14833a5ab196c7fc3effcc72ef4e895f4d4/Common.gitattributes # Common settings that generally should always be used with your language specific settings # Auto detect text files and perform LF normalization -# https://www.davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ * text=auto # @@ -10,26 +10,28 @@ # Documents *.bibtex text diff=bibtex -*.doc diff=astextplain -*.DOC diff=astextplain -*.docx diff=astextplain -*.DOCX diff=astextplain -*.dot diff=astextplain -*.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain -*.rtf diff=astextplain -*.RTF diff=astextplain -*.md text +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain +*.md text diff=markdown +*.mdx text diff=markdown *.tex text diff=tex *.adoc text *.textile text *.mustache text -*.csv text +*.csv text eol=crlf *.tab text *.tsv text *.txt text *.sql text +*.epub diff=astextplain # Graphics *.png binary @@ -39,7 +41,7 @@ *.tif binary *.tiff binary *.ico binary -# SVG treated as an asset (binary) by default. +# SVG treated as text by default. *.svg text # If you want to treat it as binary, # use the following line instead. @@ -49,7 +51,9 @@ # Scripts *.bash text eol=lf *.fish text eol=lf +*.ksh text eol=lf *.sh text eol=lf +*.zsh text eol=lf # These are explicitly windows files and should use crlf *.bat text eol=crlf *.cmd text eol=crlf @@ -64,10 +68,24 @@ # Archives *.7z binary +*.bz binary +*.bz2 binary +*.bzip2 binary *.gz binary +*.lz binary +*.lzma binary +*.rar binary *.tar binary +*.taz binary +*.tbz binary +*.tbz2 binary *.tgz binary +*.tlz binary +*.txz binary +*.xz binary +*.Z binary *.zip binary +*.zst binary # Text files where line endings should be preserved *.patch -text @@ -78,6 +96,9 @@ .gitattributes export-ignore .gitignore export-ignore +.gitkeep export-ignore +# END source: https://github.com/gitattributes/gitattributes/blob/2c20a14833a5ab196c7fc3effcc72ef4e895f4d4/Common.gitattributes + # Basic .gitattributes for a python repo. # Source files @@ -106,3 +127,10 @@ # with the python modules ``pickle``, ``dbm.*``, # ``shelve``, ``marshal``, ``anydbm``, & ``bsddb`` # (among others). + +# Service inputs must survive checkout byte-for-byte, including LF-only CSVs. +/tests/resources/** -text !eol +/examples/resources/** -text !eol +# Resource helpers are maintained Python, not immutable test data. +/tests/resources/**/*.py text eol=lf +/tests/resources/**/*.pyi text eol=lf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d69eb22..4b65b8a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,10 @@ repos: hooks: - id: trailing-whitespace args: [--markdown-linebreak-ext=md] + # Preserve uploaded service inputs; maintained Python stays in scope. + exclude: ^(tests/resources/|examples/resources/)(?!.*\.pyi?$) - id: end-of-file-fixer + exclude: ^(tests/resources/|examples/resources/)(?!.*\.pyi?$) - id: check-toml - id: check-yaml - id: check-added-large-files @@ -22,7 +25,7 @@ repos: rev: v3.6.2 hooks: - id: prettier - exclude: .md$ + exclude: (\.md$|^(tests/resources/|examples/resources/)(?!.*\.pyi?$)) - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.34.0 hooks: @@ -31,12 +34,13 @@ repos: rev: 3.4.0 hooks: - id: editorconfig-checker + exclude: ^(tests/resources/|examples/resources/)(?!.*\.pyi?$) - repo: https://github.com/executablebooks/mdformat rev: 0.7.22 hooks: - id: mdformat name: mdformat on non-.github files - exclude: ^(.github/|docs/) + exclude: ^(\.github/|docs/|tests/resources/|examples/resources/) args: ["--wrap", "80", "--number"] additional_dependencies: - mdformat-gfm From 64edcf130f7542236a9f4181cef009e1dd475efb Mon Sep 17 00:00:00 2001 From: "Kevin A. Mitchell" Date: Mon, 5 Oct 2026 23:09:15 -0500 Subject: [PATCH 2/6] pre-commit: Add repository structure and filename validation gates Check JSON syntax, case conflicts, merge markers, private keys, and GitHub Actions workflows. Exempt only the public signing-key fixture from private-key detection. Add a standalone NFC filename checker with an always-run hook and README usage. Resolve Git explicitly and report unavailable Git, invalid UTF-8 paths, and normalization collisions. Assisted-by: Codex --- .pre-commit-config.yaml | 18 +++++++ README.md | 9 ++++ scripts/check_nfc_filenames.py | 97 ++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 scripts/check_nfc_filenames.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4b65b8a9..65dfca47 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,8 +17,14 @@ repos: exclude: ^(tests/resources/|examples/resources/)(?!.*\.pyi?$) - id: end-of-file-fixer exclude: ^(tests/resources/|examples/resources/)(?!.*\.pyi?$) + - id: check-json - id: check-toml - id: check-yaml + - id: check-case-conflict + - id: check-merge-conflict + - id: detect-private-key + # Public test fixture for the self-signed pdfrest-test certificate. + exclude: ^tests/resources/signing_key\.pem$ - id: check-added-large-files args: [--maxkb=4000] - repo: https://github.com/JoC0de/pre-commit-prettier @@ -30,6 +36,10 @@ repos: rev: 0.34.0 hooks: - id: check-github-workflows + - repo: https://github.com/rhysd/actionlint + rev: v1.7.12 + hooks: + - id: actionlint - repo: https://github.com/editorconfig-checker/editorconfig-checker.python rev: 3.4.0 hooks: @@ -82,6 +92,14 @@ repos: rev: 0.8.24 hooks: - id: uv-lock + - repo: local + hooks: + - id: check-nfc-filenames + name: Check tracked filename NFC normalization + entry: uv run --script scripts/check_nfc_filenames.py + language: system + pass_filenames: false + always_run: true - repo: https://github.com/DetachHead/basedpyright-pre-commit-mirror rev: 1.34.0 # or whatever the latest version is at the time hooks: diff --git a/README.md b/README.md index bbf6b0a9..fcc0da43 100644 --- a/README.md +++ b/README.md @@ -140,3 +140,12 @@ asyncio.run(main()) ## For contributors Contributor workflows live in `CONTRIBUTING.md`. + +Check tracked filenames for UTF-8 NFC normalization and collisions: + +```bash +uv run --script scripts/check_nfc_filenames.py +``` + +This read-only check also runs through pre-commit. It requires Python 3.11 or +newer and reports offending filenames without renaming them. diff --git a/scripts/check_nfc_filenames.py b/scripts/check_nfc_filenames.py new file mode 100644 index 00000000..a417ce3a --- /dev/null +++ b/scripts/check_nfc_filenames.py @@ -0,0 +1,97 @@ +# /// script +# requires-python = ">=3.11" +# /// +"""Reject tracked Git paths that are not UTF-8 NFC, including collisions.""" + +from __future__ import annotations + +import argparse +import shutil +import subprocess +import sys +import unicodedata +from pathlib import Path +from typing import Any + + +def tracked_filename_report(root: Path) -> dict[str, Any]: + """Inspect raw index paths, not filesystem names or quoted Git status text.""" + git = shutil.which("git") + if git is None: + return { + "checked": False, + "error": "Git executable not found on PATH", + "issues": [], + "collisions": [], + } + process = subprocess.run( + args=["git", "ls-files", "--cached", "-z"], + executable=git, + cwd=root, + check=False, + capture_output=True, + ) + if process.returncode != 0: + return { + "checked": False, + "error": process.stderr.decode("utf-8", errors="replace").strip(), + "issues": [], + "collisions": [], + } + + paths: list[str] = [] + invalid_utf8: list[str] = [] + for raw_path in process.stdout.split(b"\0"): + if not raw_path: + continue + try: + paths.append(raw_path.decode("utf-8")) + except UnicodeDecodeError: + invalid_utf8.append(raw_path.decode("utf-8", errors="backslashreplace")) + + by_nfc: dict[str, list[str]] = {} + for path in set(paths): + by_nfc.setdefault(unicodedata.normalize("NFC", path), []).append(path) + collisions = [ + {"nfc_path": normalized, "paths": sorted(originals)} + for normalized, originals in sorted(by_nfc.items()) + if len(originals) > 1 + ] + issues = [ + { + "path": path, + "nfc_path": normalized, + "collides_with": sorted(other for other in originals if other != path), + } + for normalized, originals in sorted(by_nfc.items()) + for path in sorted(originals) + if path != normalized + ] + return { + "checked": True, + "issues": issues, + "collisions": collisions, + "invalid_utf8": sorted(invalid_utf8), + } + + +def main() -> int: + """Print actionable failures for a local pre-commit hook.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo", type=Path, default=Path.cwd()) + args = parser.parse_args() + report = tracked_filename_report(args.repo) + if not report["checked"]: + print(f"Cannot inspect tracked filenames: {report['error']}", file=sys.stderr) + return 2 + for issue in report["issues"]: + print(f"Non-NFC tracked path: {issue['path']!r} -> {issue['nfc_path']!r}") + if issue["collides_with"]: + print(f" Normalization collision with: {issue['collides_with']!r}") + for path in report["invalid_utf8"]: + print(f"Tracked path is not UTF-8: {path!r}") + return 1 if report["issues"] or report["invalid_utf8"] else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From c4c5d3111714413a5c79acccea4cb3d536c21887 Mon Sep 17 00:00:00 2001 From: "Kevin A. Mitchell" Date: Mon, 5 Oct 2026 23:10:02 -0500 Subject: [PATCH 3/6] pre-commit: Consolidate uv-managed type checking in quality CI Run basedpyright through the frozen project environment and retire its separate workflow. Run all quality hooks in the shared job with read-only repository permissions, a timeout, pinned actions, and a hook cache keyed by platform, tool versions, and configuration. Assisted-by: Codex --- .github/workflows/basedpyright.yml | 38 ------------------------------ .github/workflows/pre-commit.yml | 33 +++++++++++--------------- .pre-commit-config.yaml | 8 ++++--- 3 files changed, 19 insertions(+), 60 deletions(-) delete mode 100644 .github/workflows/basedpyright.yml diff --git a/.github/workflows/basedpyright.yml b/.github/workflows/basedpyright.yml deleted file mode 100644 index 6fbd60b2..00000000 --- a/.github/workflows/basedpyright.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: basedpyright - -on: - pull_request: - push: - branches: - - main - - develop - - feature-* - -jobs: - basedpyright: - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - packages: write - pull-requests: write - steps: - - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v6 - with: - version: 0.8.22 - python-version: 3.11 - enable-cache: true - cache-suffix: pre-commit - cache-dependency-glob: uv.lock - - name: Restore venv cache - uses: actions/cache@v4 - with: - path: | - .venv - key: ${{ runner.os }}-uv-${{ hashFiles('uv.lock') }} - - name: Synchronize project dependencies - run: uv sync --group dev - - name: Run basedpyright - run: uv run basedpyright diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index d73dcfd3..750b2788 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -8,38 +8,33 @@ on: - develop - feature-* +permissions: + contents: read + jobs: pre-commit: runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - packages: write - pull-requests: write + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Install uv - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 with: version: 0.8.22 python-version: 3.11 enable-cache: true cache-suffix: pre-commit cache-dependency-glob: uv.lock - - name: Restore venv cache - uses: actions/cache@v4 - with: - path: | - .venv - key: ${{ runner.os }}-uv-${{ hashFiles('uv.lock') }} - name: Restore pre-commit cache - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: - path: ~/.cache/pre-commit - key: pre-commit-4|${{ hashFiles('.pre-commit-config.yaml') }} + path: ${{ runner.temp }}/pre-commit-cache + key: pre-commit-${{ runner.os }}-${{ runner.arch }}-py3.11-uv0.8.22-pre-commit4.3.0-${{ hashFiles('uv.lock', '.pre-commit-config.yaml') }} + restore-keys: | + pre-commit-${{ runner.os }}-${{ runner.arch }}-py3.11-uv0.8.22-pre-commit4.3.0- - name: Synchronize project dependencies - run: uv sync --group dev + run: uv sync --frozen --group dev - name: Run pre-commit checks - run: uv run pre-commit run --show-diff-on-failure --all-files + run: uv run --frozen pre-commit run --show-diff-on-failure --all-files env: - SKIP: basedpyright + PRE_COMMIT_HOME: ${{ runner.temp }}/pre-commit-cache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 65dfca47..d8163fb6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -100,7 +100,9 @@ repos: language: system pass_filenames: false always_run: true - - repo: https://github.com/DetachHead/basedpyright-pre-commit-mirror - rev: 1.34.0 # or whatever the latest version is at the time - hooks: - id: basedpyright + name: basedpyright + entry: uv run --frozen basedpyright + language: system + pass_filenames: false + always_run: true From 357dde1e367018cf4e7dc2b5522a4d69450d811a Mon Sep 17 00:00:00 2001 From: "Kevin A. Mitchell" Date: Wed, 7 Oct 2026 16:41:21 -0500 Subject: [PATCH 4/6] pre-commit: Refresh repository quality scaffolding - Vendor the upstream NFC checker with provenance and byte exclusions. - Refresh complete ignore modules while preserving local amendments. - Pin compatible quality tools and CI actions. - Retain the established Ruff rule selection across the tool upgrade. Assisted-by: Codex --- .cit-quality/README.md | 59 ++++ .../vendor}/check_nfc_filenames.py | 12 +- .editorconfig-checker.json | 2 +- .gitattributes | 3 + .github/workflows/pre-commit.yml | 12 +- .gitignore | 320 +++++++++++------- .pre-commit-config.yaml | 28 +- README.md | 5 +- pyproject.toml | 13 +- uv.lock | 53 ++- 10 files changed, 325 insertions(+), 182 deletions(-) create mode 100644 .cit-quality/README.md rename {scripts => .cit-quality/vendor}/check_nfc_filenames.py (89%) diff --git a/.cit-quality/README.md b/.cit-quality/README.md new file mode 100644 index 00000000..5f3b6cdb --- /dev/null +++ b/.cit-quality/README.md @@ -0,0 +1,59 @@ +# CIT quality tooling + +This directory contains vendored contributor tooling, separate from project +source. It is maintained and tested in `cloud-innovation-team/cit-codex-skills`. +Executing the local copy requires no GitHub authentication or source-repository +access. + +## Provenance + +- Installed file: `.cit-quality/vendor/check_nfc_filenames.py`. +- Upstream file: + `skills/cit-repository-quality-scaffolding/scripts/check_nfc_filenames.py`. +- Source revision: `2dbf02d13b73799d2fcf1d3c7bab0a8371fd82ee`. +- Source SHA-256: + `83d3bb79ebf3a5d6cb4f213f3af4421bd258693057514637ce87f3fc00a79056`. +- Upstream suite: + `skills/cit-repository-quality-scaffolding/scripts/test_scaffolding.py`. + +The upstream behavioral suite tests rejection of non-NFC tracked filenames, +acceptance after a byte-preserving rename, and normalization collisions. It also +exercises the installed copy through the generated pre-commit hook. This +provenance identifies the source; rendering alone does not establish a passing +test run. Record the source revision and actual upstream validation in the PR. + +## Upstream validation + +On 2026-10-07, three focused upstream tests passed: + +- `test_vendored_checker_is_separate_from_project_source` (includes the + installed hook). +- `test_normalization_collision_is_reported_without_renaming`. +- `test_non_nfc_tracked_name_fails_hook_until_byte_preserving_rename`. + +Command: +`uv run --script /scripts/test_scaffolding.py -v -n 2 -k "vendored_checker_is_separate or normalization_collision_is_reported or non_nfc_tracked_name_fails_hook_until_byte_preserving_rename" --durations=5`. + +Result: `3 passed` on Python 3.13.11; rerun during this refresh. This is focused +validation, not a claim that the entire upstream suite or all checker failure +paths were exercised. + +## Review and updates + +Review source provenance, updates, and the repository's hook integration. +Implementation tests are maintained upstream; duplicate project tests are not +required for an unchanged, upstream-tested copy. Local behavior changes require +normal implementation review and appropriate regression coverage. + +Update the checker and this provenance note together from the scaffolding skill. +Preserve the copied bytes; contribute fixes upstream instead of editing the +vendored copy. Keep this directory out of project source, test discovery, +coverage, and distribution inputs where those tools would otherwise traverse it. +The `linguist-vendored` attribute classifies the code; it does not disable +GitHub review requirements. + +Run the installed hook from the repository root: + +```shell +uvx --from pre-commit==4.6.2 pre-commit run check-nfc-filenames --all-files +``` diff --git a/scripts/check_nfc_filenames.py b/.cit-quality/vendor/check_nfc_filenames.py similarity index 89% rename from scripts/check_nfc_filenames.py rename to .cit-quality/vendor/check_nfc_filenames.py index a417ce3a..1a17d33d 100644 --- a/scripts/check_nfc_filenames.py +++ b/.cit-quality/vendor/check_nfc_filenames.py @@ -6,7 +6,6 @@ from __future__ import annotations import argparse -import shutil import subprocess import sys import unicodedata @@ -16,17 +15,8 @@ def tracked_filename_report(root: Path) -> dict[str, Any]: """Inspect raw index paths, not filesystem names or quoted Git status text.""" - git = shutil.which("git") - if git is None: - return { - "checked": False, - "error": "Git executable not found on PATH", - "issues": [], - "collisions": [], - } process = subprocess.run( - args=["git", "ls-files", "--cached", "-z"], - executable=git, + ["git", "ls-files", "--cached", "-z"], cwd=root, check=False, capture_output=True, diff --git a/.editorconfig-checker.json b/.editorconfig-checker.json index 684cbc35..baefa2ef 100644 --- a/.editorconfig-checker.json +++ b/.editorconfig-checker.json @@ -1,5 +1,5 @@ { - "Version": "v3.4.0", + "Version": "v3.11.1", "Verbose": false, "Debug": false, "IgnoreDefaults": false, diff --git a/.gitattributes b/.gitattributes index 157225f5..6c7ffa50 100644 --- a/.gitattributes +++ b/.gitattributes @@ -134,3 +134,6 @@ # Resource helpers are maintained Python, not immutable test data. /tests/resources/**/*.py text eol=lf /tests/resources/**/*.pyi text eol=lf + +# CIT quality tooling is vendored, not project source. +/.cit-quality/vendor/** linguist-vendored diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 750b2788..0546f8a6 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -16,22 +16,22 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 with: - version: 0.8.22 + version: 0.12.18 python-version: 3.11 enable-cache: true cache-suffix: pre-commit cache-dependency-glob: uv.lock - name: Restore pre-commit cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ runner.temp }}/pre-commit-cache - key: pre-commit-${{ runner.os }}-${{ runner.arch }}-py3.11-uv0.8.22-pre-commit4.3.0-${{ hashFiles('uv.lock', '.pre-commit-config.yaml') }} + key: pre-commit-${{ runner.os }}-${{ runner.arch }}-py3.11-uv0.12.18-pre-commit4.6.2-${{ hashFiles('uv.lock', '.pre-commit-config.yaml') }} restore-keys: | - pre-commit-${{ runner.os }}-${{ runner.arch }}-py3.11-uv0.8.22-pre-commit4.3.0- + pre-commit-${{ runner.os }}-${{ runner.arch }}-py3.11-uv0.12.18-pre-commit4.6.2- - name: Synchronize project dependencies run: uv sync --frozen --group dev - name: Run pre-commit checks diff --git a/.gitignore b/.gitignore index f84925da..5cb242be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# Retained Toptal categories: Emacs, Git, Linux. # Created by https://www.toptal.com/developers/gitignore/api/vim,emacs,linux,macos,python,pycharm,windows,git # Edit at https://www.toptal.com/developers/gitignore?templates=vim,emacs,linux,macos,python,pycharm,windows,git @@ -81,20 +82,19 @@ flycheck_*.el # .nfs files are created when an open file is removed but is still being accessed .nfs* -### macOS ### +# BEGIN source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Global/macOS.gitignore # General .DS_Store +.localized +__MACOSX/ .AppleDouble .LSOverride +Icon[ ] -# Icon must end with two \r -Icon - - -# Thumbnails +# Resource forks ._* -# Files that might appear in the root of a volume +# Files and directories that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 @@ -102,6 +102,15 @@ Icon .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent +.com.apple.timemachine.supported +.PKInstallSandboxManager +.PKInstallSandboxManager-SystemSoftware +.hotfiles.btree +.vol +.file +.disk_label* +lost+found +.HFS+ Private Directory Data[ ] # Directories potentially created on remote AFP share .AppleDB @@ -110,12 +119,81 @@ Network Trash Folder Temporary Items .apdisk -### macOS Patch ### -# iCloud generated files -*.icloud +# Mac OS 6 to 9 +Desktop DB +Desktop DF +TheFindByContentFolder +TheVolumeSettingsFolder +.FBCIndex +.FBCSemaphoreFile +.FBCLockFolder + +# Quota system +.quota.group +.quota.user +.quota.ops.group +.quota.ops.user + +# TimeMachine +Backups.backupdb +.MobileBackups +.MobileBackups.trash +MobileBackups.trash +tmbootpicker.efi +# END source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Global/macOS.gitignore + +# BEGIN source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk +# END source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Global/Windows.gitignore + +# BEGIN source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Global/Vim.gitignore +# Swap +[._]*.s[a-v][a-z] +# comment out the next line if you don't need vector files +!*.svg +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ +# END source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Global/Vim.gitignore -### PyCharm ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# BEGIN source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Global/JetBrains.gitignore +# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff @@ -180,6 +258,8 @@ atlassian-ide-plugin.xml # SonarLint plugin .idea/sonarlint/ +# see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119 +.idea/sonarlint.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml @@ -187,50 +267,39 @@ crashlytics.properties crashlytics-build.properties fabric.properties -# Editor-based Rest Client +# Editor-based HTTP Client .idea/httpRequests +http-client.private.env.json # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser -### PyCharm Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - -# *.iml -# modules.xml -# .idea/misc.xml -# *.ipr - -# Sonarlint plugin -# https://plugins.jetbrains.com/plugin/7973-sonarlint -.idea/**/sonarlint/ - -# SonarQube Plugin -# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin -.idea/**/sonarIssues.xml - -# Markdown Navigator plugin -# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced -.idea/**/markdown-navigator.xml -.idea/**/markdown-navigator-enh.xml -.idea/**/markdown-navigator/ - -# Cache file creation bug -# See https://youtrack.jetbrains.com/issue/JBR-2257 -.idea/$CACHE_FILE$ - -# CodeStream plugin -# https://plugins.jetbrains.com/plugin/12206-codestream -.idea/codestream.xml - -# Azure Toolkit for IntelliJ plugin -# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij -.idea/**/azureSettings.xml - -### Python ### +# Apifox Helper cache +.idea/.cache/.Apifox_Helper +.idea/ApifoxUploaderProjectSetting.xml + +# Github Copilot persisted session migrations, see: https://github.com/microsoft/copilot-intellij-feedback/issues/712#issuecomment-3322062215 +.idea/**/copilot.data.migration.*.xml +# END source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Global/JetBrains.gitignore + +# BEGIN source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Global/VisualStudioCode.gitignore +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets +!*.code-workspace + +# Built Visual Studio Code Extensions +*.vsix +# END source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Global/VisualStudioCode.gitignore + +# BEGIN source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Python.gitignore # Byte-compiled / optimized / DLL files __pycache__/ -*.py[cod] +*.py[codz] *$py.class # C extensions @@ -240,6 +309,7 @@ __pycache__/ .Python build/ develop-eggs/ +dist/ downloads/ eggs/ .eggs/ @@ -256,8 +326,8 @@ share/python-wheels/ MANIFEST # PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec @@ -275,7 +345,8 @@ htmlcov/ nosetests.xml coverage.xml *.cover -*.py,cover +*.py.cover +*.lcov .hypothesis/ .pytest_cache/ cover/ @@ -321,28 +392,65 @@ ipython_config.py # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. -#Pipfile.lock +# Pipfile.lock + +# uv +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# in version control. - +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi/* +!.pixi/config.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff -celerybeat-schedule +celerybeat-schedule* celerybeat.pid +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + # SageMath parsed files *.sage.py # Environments .env +.envrc .venv env/ venv/ @@ -357,7 +465,7 @@ venv.bak/ # Rope project settings .ropeproject -# mkdocs documentation +# mkdocs/Zensical documentation /site # mypy @@ -375,70 +483,43 @@ dmypy.json cython_debug/ # PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -### Python Patch ### -# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration -poetry.toml - -# ruff +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that +# can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, you +# could uncomment the following to ignore the entire vscode folder +# .vscode/ +# Temporary file for partial code execution +tempCodeRunnerFile.py + +# Ruff stuff: .ruff_cache/ -# LSP config files -pyrightconfig.json - -### Vim ### -# Swap -[._]*.s[a-v][a-z] -!*.svg # comment out if you don't need vector files -[._]*.sw[a-p] -[._]s[a-rt-v][a-z] -[._]ss[a-gi-z] -[._]sw[a-p] +# PyPI configuration file +.pypirc -# Session -Session.vim -Sessionx.vim +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ -# Temporary -.netrwhist -# Auto-generated tag files -tags -# Persistent undo -[._]*.un~ - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# End of https://www.toptal.com/developers/gitignore/api/vim,emacs,linux,macos,python,pycharm,windows,git +# Streamlit +.streamlit/secrets.toml +# END source: https://github.com/github/gitignore/blob/0e5d690153ca3da8a4a1aef2d053406f408f531c/Python.gitignore +# Repository-specific amendments # Files for building Docker containers that don't have to get checked in /installers/ @@ -455,3 +536,6 @@ $RECYCLE.BIN/ !pyrightconfig.json /coverage/ /coverage.json + +# Existing coverage.py annotate outputs +*.py,cover diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d8163fb6..e47bcf64 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,10 +13,10 @@ repos: hooks: - id: trailing-whitespace args: [--markdown-linebreak-ext=md] - # Preserve uploaded service inputs; maintained Python stays in scope. - exclude: ^(tests/resources/|examples/resources/)(?!.*\.pyi?$) + # Preserve service inputs, vendored bytes, and immutable ignore modules. + exclude: (^\.cit-quality/vendor/|^\.gitignore$|^(tests/resources/|examples/resources/)(?!.*\.pyi?$)) - id: end-of-file-fixer - exclude: ^(tests/resources/|examples/resources/)(?!.*\.pyi?$) + exclude: (^\.cit-quality/vendor/|^\.gitignore$|^(tests/resources/|examples/resources/)(?!.*\.pyi?$)) - id: check-json - id: check-toml - id: check-yaml @@ -33,7 +33,7 @@ repos: - id: prettier exclude: (\.md$|^(tests/resources/|examples/resources/)(?!.*\.pyi?$)) - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.34.0 + rev: 0.38.1 hooks: - id: check-github-workflows - repo: https://github.com/rhysd/actionlint @@ -41,10 +41,11 @@ repos: hooks: - id: actionlint - repo: https://github.com/editorconfig-checker/editorconfig-checker.python - rev: 3.4.0 + rev: 3.11.1 hooks: - id: editorconfig-checker - exclude: ^(tests/resources/|examples/resources/)(?!.*\.pyi?$) + # The upstream macOS ignore module contains literal carriage returns. + exclude: (^\.cit-quality/vendor/|^\.gitignore$|^(tests/resources/|examples/resources/)(?!.*\.pyi?$)) - repo: https://github.com/executablebooks/mdformat rev: 0.7.22 hooks: @@ -71,32 +72,35 @@ repos: hooks: - id: shellcheck args: [-x] - - repo: https://github.com/maxwinterstein/shfmt-py - rev: v3.12.0.1 + - repo: https://github.com/scop/pre-commit-shfmt + rev: v3.14.1-1 hooks: - id: shfmt + args: [--write] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.3 + rev: v0.16.8 hooks: - id: ruff-check + exclude: ^\.cit-quality/vendor/ types_or: [python, pyi, jupyter] - id: ruff-format + exclude: ^\.cit-quality/vendor/ types_or: [python, pyi, jupyter] - repo: https://github.com/pappasam/toml-sort - rev: v0.24.3 + rev: v0.25.0 hooks: - id: toml-sort-fix exclude: ^uv\.lock$ - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.8.24 + rev: 0.12.18 hooks: - id: uv-lock - repo: local hooks: - id: check-nfc-filenames name: Check tracked filename NFC normalization - entry: uv run --script scripts/check_nfc_filenames.py + entry: uv run --script .cit-quality/vendor/check_nfc_filenames.py language: system pass_filenames: false always_run: true diff --git a/README.md b/README.md index fcc0da43..1a09cf0a 100644 --- a/README.md +++ b/README.md @@ -144,8 +144,9 @@ Contributor workflows live in `CONTRIBUTING.md`. Check tracked filenames for UTF-8 NFC normalization and collisions: ```bash -uv run --script scripts/check_nfc_filenames.py +uv run --script .cit-quality/vendor/check_nfc_filenames.py ``` This read-only check also runs through pre-commit. It requires Python 3.11 or -newer and reports offending filenames without renaming them. +newer and reports offending filenames without renaming them. Source provenance +and upstream validation are recorded in `.cit-quality/README.md`. diff --git a/pyproject.toml b/pyproject.toml index 2bdc5ded..a0dea141 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,12 +2,12 @@ name = "pdfrest" version = "1.1.0" description = "Python client library for interacting with the pdfRest API" -readme = {file = "README.md", content-type = "text/markdown"} +readme = { file = "README.md", content-type = "text/markdown" } authors = [ - {name = "Datalogics"}, + { name = "Datalogics" }, ] maintainers = [ - {name = "Datalogics"}, + { name = "Datalogics" }, ] keywords = [ "api", @@ -49,8 +49,8 @@ build-backend = "uv_build" [dependency-groups] dev = [ - "pre-commit>=3.7.0", - "ruff>=0.6.9", + "pre-commit==4.6.2", + "ruff==0.16.8", "pytest>=8.3.3", "pytest-cov>=5.0.0", "pip-audit>=2.7.3", @@ -79,10 +79,13 @@ markers = [ ] [tool.ruff] +extend-exclude = [".cit-quality/vendor"] extend-include = ["*.ipynb"] target-version = "py310" [tool.ruff.lint] +# Keep the established Ruff defaults explicit across tool upgrades. +select = ["E4", "E7", "E9", "F"] # Enable the pycodestyle (`E`) and Pyflakes (`F`) rules by default. # Rules cribbed from the PDM sources themselves. extend-select = [ diff --git a/uv.lock b/uv.lock index a48fd046..e315631b 100644 --- a/uv.lock +++ b/uv.lock @@ -1013,7 +1013,7 @@ dev = [ { name = "mkdocstrings", extras = ["python"], specifier = ">=1.0.3" }, { name = "nox", specifier = ">=2025.5.1" }, { name = "pip-audit", specifier = ">=2.7.3" }, - { name = "pre-commit", specifier = ">=3.7.0" }, + { name = "pre-commit", specifier = "==4.6.2" }, { name = "pytest", specifier = ">=8.3.3" }, { name = "pytest-asyncio", specifier = ">=1.2.0" }, { name = "pytest-cov", specifier = ">=5.0.0" }, @@ -1024,7 +1024,7 @@ dev = [ { name = "pytest-xdist", specifier = ">=3.8.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "rich", specifier = ">=14.1.0" }, - { name = "ruff", specifier = ">=0.6.9" }, + { name = "ruff", specifier = "==0.16.8" }, ] [[package]] @@ -1101,7 +1101,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.3.0" +version = "4.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -1110,9 +1110,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/89/1f3e8e1fc3e97de0fa963495832f581f025f29471602a309e48808244292/pre_commit-4.6.2.tar.gz", hash = "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441", size = 198670, upload-time = "2026-08-10T22:07:18.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, + { url = "https://files.pythonhosted.org/packages/45/e2/bbb7129c9e7999a6b8ee9cca3b66486c25c423ab5a75f34071798b74ce94/pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e", size = 226202, upload-time = "2026-08-10T22:07:16.942Z" }, ] [[package]] @@ -1519,28 +1519,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/df/8d7d8c515d33adfc540e2edf6c6021ea1c5a58a678d8cfce9fae59aabcab/ruff-0.13.2.tar.gz", hash = "sha256:cb12fffd32fb16d32cef4ed16d8c7cdc27ed7c944eaa98d99d01ab7ab0b710ff", size = 5416417, upload-time = "2025-09-25T14:54:09.936Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/84/5716a7fa4758e41bf70e603e13637c42cfb9dbf7ceb07180211b9bbf75ef/ruff-0.13.2-py3-none-linux_armv6l.whl", hash = "sha256:3796345842b55f033a78285e4f1641078f902020d8450cade03aad01bffd81c3", size = 12343254, upload-time = "2025-09-25T14:53:27.784Z" }, - { url = "https://files.pythonhosted.org/packages/9b/77/c7042582401bb9ac8eff25360e9335e901d7a1c0749a2b28ba4ecb239991/ruff-0.13.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ff7e4dda12e683e9709ac89e2dd436abf31a4d8a8fc3d89656231ed808e231d2", size = 13040891, upload-time = "2025-09-25T14:53:31.38Z" }, - { url = "https://files.pythonhosted.org/packages/c6/15/125a7f76eb295cb34d19c6778e3a82ace33730ad4e6f28d3427e134a02e0/ruff-0.13.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c75e9d2a2fafd1fdd895d0e7e24b44355984affdde1c412a6f6d3f6e16b22d46", size = 12243588, upload-time = "2025-09-25T14:53:33.543Z" }, - { url = "https://files.pythonhosted.org/packages/9e/eb/0093ae04a70f81f8be7fd7ed6456e926b65d238fc122311293d033fdf91e/ruff-0.13.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cceac74e7bbc53ed7d15d1042ffe7b6577bf294611ad90393bf9b2a0f0ec7cb6", size = 12491359, upload-time = "2025-09-25T14:53:35.892Z" }, - { url = "https://files.pythonhosted.org/packages/43/fe/72b525948a6956f07dad4a6f122336b6a05f2e3fd27471cea612349fedb9/ruff-0.13.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae3f469b5465ba6d9721383ae9d49310c19b452a161b57507764d7ef15f4b07", size = 12162486, upload-time = "2025-09-25T14:53:38.171Z" }, - { url = "https://files.pythonhosted.org/packages/6a/e3/0fac422bbbfb2ea838023e0d9fcf1f30183d83ab2482800e2cb892d02dfe/ruff-0.13.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f8f9e3cd6714358238cd6626b9d43026ed19c0c018376ac1ef3c3a04ffb42d8", size = 13871203, upload-time = "2025-09-25T14:53:41.943Z" }, - { url = "https://files.pythonhosted.org/packages/6b/82/b721c8e3ec5df6d83ba0e45dcf00892c4f98b325256c42c38ef136496cbf/ruff-0.13.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c6ed79584a8f6cbe2e5d7dbacf7cc1ee29cbdb5df1172e77fbdadc8bb85a1f89", size = 14929635, upload-time = "2025-09-25T14:53:43.953Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a0/ad56faf6daa507b83079a1ad7a11694b87d61e6bf01c66bd82b466f21821/ruff-0.13.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aed130b2fde049cea2019f55deb939103123cdd191105f97a0599a3e753d61b0", size = 14338783, upload-time = "2025-09-25T14:53:46.205Z" }, - { url = "https://files.pythonhosted.org/packages/47/77/ad1d9156db8f99cd01ee7e29d74b34050e8075a8438e589121fcd25c4b08/ruff-0.13.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1887c230c2c9d65ed1b4e4cfe4d255577ea28b718ae226c348ae68df958191aa", size = 13355322, upload-time = "2025-09-25T14:53:48.164Z" }, - { url = "https://files.pythonhosted.org/packages/64/8b/e87cfca2be6f8b9f41f0bb12dc48c6455e2d66df46fe61bb441a226f1089/ruff-0.13.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bcb10276b69b3cfea3a102ca119ffe5c6ba3901e20e60cf9efb53fa417633c3", size = 13354427, upload-time = "2025-09-25T14:53:50.486Z" }, - { url = "https://files.pythonhosted.org/packages/7f/df/bf382f3fbead082a575edb860897287f42b1b3c694bafa16bc9904c11ed3/ruff-0.13.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:afa721017aa55a555b2ff7944816587f1cb813c2c0a882d158f59b832da1660d", size = 13537637, upload-time = "2025-09-25T14:53:52.887Z" }, - { url = "https://files.pythonhosted.org/packages/51/70/1fb7a7c8a6fc8bd15636288a46e209e81913b87988f26e1913d0851e54f4/ruff-0.13.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1dbc875cf3720c64b3990fef8939334e74cb0ca65b8dbc61d1f439201a38101b", size = 12340025, upload-time = "2025-09-25T14:53:54.88Z" }, - { url = "https://files.pythonhosted.org/packages/4c/27/1e5b3f1c23ca5dd4106d9d580e5c13d9acb70288bff614b3d7b638378cc9/ruff-0.13.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939a1b2a960e9742e9a347e5bbc9b3c3d2c716f86c6ae273d9cbd64f193f22", size = 12133449, upload-time = "2025-09-25T14:53:57.089Z" }, - { url = "https://files.pythonhosted.org/packages/2d/09/b92a5ccee289f11ab128df57d5911224197d8d55ef3bd2043534ff72ca54/ruff-0.13.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:50e2d52acb8de3804fc5f6e2fa3ae9bdc6812410a9e46837e673ad1f90a18736", size = 13051369, upload-time = "2025-09-25T14:53:59.124Z" }, - { url = "https://files.pythonhosted.org/packages/89/99/26c9d1c7d8150f45e346dc045cc49f23e961efceb4a70c47dea0960dea9a/ruff-0.13.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3196bc13ab2110c176b9a4ae5ff7ab676faaa1964b330a1383ba20e1e19645f2", size = 13523644, upload-time = "2025-09-25T14:54:01.622Z" }, - { url = "https://files.pythonhosted.org/packages/f7/00/e7f1501e81e8ec290e79527827af1d88f541d8d26151751b46108978dade/ruff-0.13.2-py3-none-win32.whl", hash = "sha256:7c2a0b7c1e87795fec3404a485096bcd790216c7c146a922d121d8b9c8f1aaac", size = 12245990, upload-time = "2025-09-25T14:54:03.647Z" }, - { url = "https://files.pythonhosted.org/packages/ee/bd/d9f33a73de84fafd0146c6fba4f497c4565fe8fa8b46874b8e438869abc2/ruff-0.13.2-py3-none-win_amd64.whl", hash = "sha256:17d95fb32218357c89355f6f6f9a804133e404fc1f65694372e02a557edf8585", size = 13324004, upload-time = "2025-09-25T14:54:06.05Z" }, - { url = "https://files.pythonhosted.org/packages/c3/12/28fa2f597a605884deb0f65c1b1ae05111051b2a7030f5d8a4ff7f4599ba/ruff-0.13.2-py3-none-win_arm64.whl", hash = "sha256:da711b14c530412c827219312b7d7fbb4877fb31150083add7e8c5336549cea7", size = 12484437, upload-time = "2025-09-25T14:54:08.022Z" }, +version = "0.16.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/78/449cb84790bd5cc3823b2652ee405a4558856e5c4195aee3a16bf7b3eb5d/ruff-0.16.8.tar.gz", hash = "sha256:9247bf92b5f04d825c8639a4fe423ec2e4222acd9222e58412b0dab7e442798b", size = 4938814, upload-time = "2026-09-16T15:54:46.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/25/6071aabc530e9be7e2c195e8fe3f7aea2735405b6cf447212832d7811831/ruff-0.16.8-py3-none-linux_armv6l.whl", hash = "sha256:6ffbd6d87383c1edf5f6fa890f10200950240d7c1a16052a19a09d3a2307dd38", size = 10048966, upload-time = "2026-09-16T15:53:57.605Z" }, + { url = "https://files.pythonhosted.org/packages/54/98/07f90ecbc74dd5fb5764f11f2bc774d6a7cffef92d2ff5f5b4e9e23c754e/ruff-0.16.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:42ed6b878ed61e3acca92f2730a17acff39286944ea82398544696366a6f925e", size = 10165498, upload-time = "2026-09-16T15:54:01.14Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1f/e6a712e3b47cad4a40600134105ed193cb773f618a42eb7ba323cb812cc0/ruff-0.16.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ea781c7f2afba8c6a505ea0fb3f994020249e0c450635f5381286fea6b46170", size = 9830004, upload-time = "2026-09-16T15:54:03.998Z" }, + { url = "https://files.pythonhosted.org/packages/23/f2/311a08776d75d81c7676e20b6b020ae63cbe881fcdc7a8dd64e6e18bdd93/ruff-0.16.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8efeae3bbe414a5efefda11a792dfb51ef90ac48d50c4830de2f644caf3e8659", size = 9986558, upload-time = "2026-09-16T15:54:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ed/37b6cb3d3ba8c73e68ae3eb1d502383beb5aa05a582bb7bb3a922f929f54/ruff-0.16.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a79b795469fef7fc6e908b218eed2eb17332afd85031db6480dc864560e69b2", size = 9877332, upload-time = "2026-09-16T15:54:09.552Z" }, + { url = "https://files.pythonhosted.org/packages/22/cc/40873a8f36ad084cc540d55fcca7077264d5b13b24659e9180c176fb2b08/ruff-0.16.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fdc5563cdc50555e6fba39322850860e9267c1b3d12c26a74729d8604c3c812", size = 10507125, upload-time = "2026-09-16T15:54:12.152Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e4/fc91a642b78ccbab6b9477720f3644ae7a10a9bcce69a934679cd64f62bc/ruff-0.16.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34508983c70665578dab88f5223d8e6228307e1135398ca8bfc8b7e9501e282b", size = 11336694, upload-time = "2026-09-16T15:54:15.489Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/bbd2a9a600a4e73dc3e7548a249c8d1671273464b55822c6fae50f602dff/ruff-0.16.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644bb578569e0ffc575741232bd385dacdd6fbe123f1a729e7a225f54aa3957f", size = 10774448, upload-time = "2026-09-16T15:54:18.16Z" }, + { url = "https://files.pythonhosted.org/packages/1a/41/d83af9879a7b6e8bf5fe16b1da0b134049d2f5d3afac12defb0897cb84bd/ruff-0.16.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e7d226246961db9235098333caa13063906d3851136b84c2900b82f5daa1df", size = 10323796, upload-time = "2026-09-16T15:54:20.743Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/cefd07bfe914b84943ea769ade8d607bd22750b965d3228eefd7cebd15d0/ruff-0.16.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a2bf6bc3e9ebdd4449abc6f06cf64b98051a2c61cf94d2fe9596518c881f1a1e", size = 10514115, upload-time = "2026-09-16T15:54:23.497Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9d/76a2e26c79a23be6e6e3664c57bec9e9fc8de155cfb9e4b67ea91b64f9d7/ruff-0.16.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ca111ba0849539165e9e59d2b442542f3c1e8060ebbdea82494f1ffbccb1e1f", size = 10072582, upload-time = "2026-09-16T15:54:26.185Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d4/f42edddb39668af1a559ceafa3823aedd65633a48dc9768e775485faa2c1/ruff-0.16.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:359a1e5b495448ee1e91018064382ebc86f90e8aac2fed222c7d0e4e8df85fd2", size = 9879644, upload-time = "2026-09-16T15:54:29.278Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/913e3195d95e0378786c6656945c865f534a3560e29139da4882aff630d1/ruff-0.16.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:59e8f5681349474110b24d62e93cfda6593f5fa3473446ca3705200cac1a08b9", size = 10231569, upload-time = "2026-09-16T15:54:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c4/8aa6ea0bdcedbd1bf87397e2fc4ed8406448ea5842f8660bc6e5f163039d/ruff-0.16.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:efa3e7a16d1baaa79957888dfdf8be9ef2e44db81cb032af06d76632ab59e773", size = 10663666, upload-time = "2026-09-16T15:54:34.838Z" }, + { url = "https://files.pythonhosted.org/packages/3d/02/7f10ef4700bc223c30a3fdd10631a29830c45524b810a3c7ed947af64591/ruff-0.16.8-py3-none-win32.whl", hash = "sha256:55793ba85c69921e89be061426d91a78652d6e50317c962240922747a4eb713f", size = 10093472, upload-time = "2026-09-16T15:54:37.47Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5d/a509c07d714b6da88f2c518b4637cf6f1d46b074be8f0f1e5fb9ff5126fe/ruff-0.16.8-py3-none-win_amd64.whl", hash = "sha256:a6b85621fd3c81e31fc5f5add09c9c078b430db3595ca632efafdec9e64ebfaa", size = 10586899, upload-time = "2026-09-16T15:54:40.488Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a0/50787329e4f20bf9dc9f6230015d46ec69c51a97ace5bc202dae4755365d/ruff-0.16.8-py3-none-win_arm64.whl", hash = "sha256:d075e820af612102ce217f07cc93e69f9490b10ec13ea85fa87bd03d996cef8a", size = 10386316, upload-time = "2026-09-16T15:54:43.332Z" }, ] [[package]] From bfa6af51a4d6a1fe8456413bb7764ce977a21c94 Mon Sep 17 00:00:00 2001 From: "Kevin A. Mitchell" Date: Wed, 7 Oct 2026 17:15:06 -0500 Subject: [PATCH 5/6] pre-commit: Format Python examples in Markdown - Include Markdown in Ruff formatting while preserving fixture bytes. - Format Python examples in the file-usage guide. Assisted-by: Codex --- .pre-commit-config.yaml | 5 +++-- docs/using-files.md | 22 +++++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e47bcf64..45eca81a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -84,8 +84,9 @@ repos: exclude: ^\.cit-quality/vendor/ types_or: [python, pyi, jupyter] - id: ruff-format - exclude: ^\.cit-quality/vendor/ - types_or: [python, pyi, jupyter] + # Format Python examples in Markdown while preserving service inputs. + exclude: (^\.cit-quality/vendor/|^(tests/resources/|examples/resources/)(?!.*\.pyi?$)) + types_or: [python, pyi, jupyter, markdown] - repo: https://github.com/pappasam/toml-sort rev: v0.25.0 hooks: diff --git a/docs/using-files.md b/docs/using-files.md index 13afbde9..15b1044b 100644 --- a/docs/using-files.md +++ b/docs/using-files.md @@ -36,6 +36,7 @@ is documented on ```python from pdfrest import AsyncPdfRestClient + async def process_pdf() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./input.pdf"]) @@ -64,6 +65,7 @@ multipart tuples. from pathlib import Path from pdfrest import AsyncPdfRestClient + async def upload() -> None: async with AsyncPdfRestClient() as client, Path("input.pdf").open("rb") as fh: uploaded = await client.files.create([("input.pdf", fh, "application/pdf")]) @@ -99,6 +101,7 @@ Best default for local files. Each entry can be: ```python from pdfrest import AsyncPdfRestClient + async def upload_from_paths() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths( @@ -118,15 +121,14 @@ Use this when pdfRest should fetch files from `http`/`https` URLs. from pdfrest import PdfRestClient with PdfRestClient() as client: - uploaded = client.files.create_from_urls( - ["https://example.com/document.pdf"] - ) + uploaded = client.files.create_from_urls(["https://example.com/document.pdf"]) ``` === "Async" ```python from pdfrest import AsyncPdfRestClient + async def upload_from_urls() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_urls( @@ -172,6 +174,7 @@ not need metadata first. existing_id = "1de305d2-b6a0-4b5d-9a55-4e4e6d8c2d39" + async def work_with_existing_id() -> None: async with AsyncPdfRestClient() as client: # Hydrate metadata into PdfRestFile @@ -195,6 +198,7 @@ not need metadata first. ```python from pdfrest import AsyncPdfRestClient + async def fetch_metadata() -> None: async with AsyncPdfRestClient() as client: file_meta = await client.files.get("1de305d2-b6a0-4b5d-9a55-4e4e6d8c2d39") @@ -233,6 +237,7 @@ or a file ID string for all methods below. ```python from pdfrest import AsyncPdfRestClient + async def read_as_bytes() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./input.pdf"]) @@ -254,6 +259,7 @@ or a file ID string for all methods below. ```python from pdfrest import AsyncPdfRestClient + async def read_as_text() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./notes.txt"]) @@ -275,6 +281,7 @@ or a file ID string for all methods below. ```python from pdfrest import AsyncPdfRestClient + async def read_as_json() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./metadata.json"]) @@ -296,6 +303,7 @@ or a file ID string for all methods below. ```python from pdfrest import AsyncPdfRestClient + async def write_to_disk() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./input.pdf"]) @@ -322,6 +330,7 @@ Use this when files are large or you need chunk/line-level processing. ```python from pdfrest import AsyncPdfRestClient + async def stream_download() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./input.pdf"]) @@ -365,6 +374,7 @@ Both stream wrappers also expose text/line/raw iterators: ```python from pdfrest import AsyncPdfRestClient + async def stream_bytes() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./input.pdf"]) @@ -392,6 +402,7 @@ Both stream wrappers also expose text/line/raw iterators: ```python from pdfrest import AsyncPdfRestClient + async def stream_text() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./notes.txt"]) @@ -419,6 +430,7 @@ Both stream wrappers also expose text/line/raw iterators: ```python from pdfrest import AsyncPdfRestClient + async def stream_lines() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./events.ndjson"]) @@ -446,6 +458,7 @@ Both stream wrappers also expose text/line/raw iterators: ```python from pdfrest import AsyncPdfRestClient + async def stream_raw() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./input.pdf"]) @@ -482,6 +495,7 @@ upload calls can be passed directly into many endpoint helpers. ```python from pdfrest import AsyncPdfRestClient + async def chain_calls() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./input.pdf"]) @@ -510,6 +524,7 @@ Delete uploaded files when you no longer need them: ```python from pdfrest import AsyncPdfRestClient + async def delete_files() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./input.pdf"]) @@ -547,6 +562,7 @@ that failed, so you can report each file-level failure precisely. ```python from pdfrest import AsyncPdfRestClient, PdfRestDeleteError, PdfRestErrorGroup + async def delete_with_error_handling() -> None: async with AsyncPdfRestClient() as client: uploaded = await client.files.create_from_paths(["./input.pdf", "./second.pdf"]) From f29fe21540a0e95fdfeb6df23ff61556a65da7f0 Mon Sep 17 00:00:00 2001 From: "Kevin A. Mitchell" Date: Thu, 8 Oct 2026 13:16:21 -0500 Subject: [PATCH 6/6] docs: Remove GitHub Pages deployment workflow - Remove the Docs workflow and its GitHub Pages deployment. - Update CI guidance for Docs Check and Netlify hosting. Assisted-by: Codex --- .github/workflows/docs.yml | 51 -------------------------------------- AGENTS.md | 7 +++--- 2 files changed, 4 insertions(+), 54 deletions(-) delete mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 50c88c8a..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Docs - -on: - push: - branches: - - main - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Pages - uses: actions/configure-pages@v5 - - name: Install uv - uses: astral-sh/setup-uv@v6 - with: - version: 0.9.18 - python-version: "3.11" - enable-cache: true - cache-suffix: docs - cache-dependency-glob: uv.lock - - name: Synchronize project dependencies - run: uv sync --group dev - - name: Build documentation - run: uv run mkdocs build --strict - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 - with: - path: site - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - needs: build - runs-on: ubuntu-latest - steps: - - id: deployment - name: Deploy to GitHub Pages - uses: actions/deploy-pages@v4 diff --git a/AGENTS.md b/AGENTS.md index 06a36200..b029b08f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -449,9 +449,10 @@ ## CI & Publishing Notes -- GitHub Actions run three workflows: `pre-commit` (no AWS credentials), - `Test and Publish` (Python 3.10–3.14 matrix), and `Docs` (GitHub Pages build - and deploy on `main` push/manual dispatch). +- GitHub Actions run `pre-commit`, `basedpyright`, and `Test and Publish` + (Python 3.10–3.14 test and example matrices). `Test and Publish` also runs + `Docs Check` on Python 3.11 with `uv run mkdocs build --strict`; PRs upload a + docs preview artifact. Netlify hosts the documentation site. - Only the release job assumes the AWS OIDC role to `uv build` and publish with `uv publish`. - Keep CodeArtifact credentials out of source control; day-to-day development