Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-jaraco.develop for
openSUSE:Factory checked in at 2026-10-27 16:47:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jaraco.develop (Old)
and /work/SRC/openSUSE:Factory/.python-jaraco.develop.new.2252680 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jaraco.develop"
Tue Oct 27 16:47:18 2026 rev:8 rq:1392469 version:9.1.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-jaraco.develop/python-jaraco.develop.changes
2026-03-17 19:02:14.467071263 +0100
+++
/work/SRC/openSUSE:Factory/.python-jaraco.develop.new.2252680/python-jaraco.develop.changes
2026-10-27 16:47:49.836640090 +0100
@@ -1,0 +2,21 @@
+Sun Oct 25 16:10:23 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 9.1.0:
+ * Require Python 3.11 or later.
+ * Fragment types are now parsed from the right of the filename,
+ as towncrier itself does, so a dot in the fragment id no
+ longer causes finalize to fail with a KeyError. Fragment
+ names are also validated before the release kind is computed,
+ giving a clear error for unrecognized names.
+ * Secrets read from keyring are now wrapped in secrets.Secret,
+ whose repr elides the value, keeping credentials out of
+ rendered tracebacks and other repr-based output.
+ * Adopted the expanded Ruff 0.16 default rule selection, fixing
+ the violations it surfaced. Notably, the subprocess
+ invocations in init-azure and macos-build-python now check
+ their return codes, failing fast instead of proceeding after
+ a failed command.
+ * github.Repo.get_public_key() is now the Repo.public_key
+ cached property, which caches per repo instead of globally.
+
+-------------------------------------------------------------------
Old:
----
jaraco_develop-8.19.2.tar.gz
New:
----
jaraco_develop-9.1.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-jaraco.develop.spec ++++++
--- /var/tmp/diff_new_pack.cpKIRG/_old 2026-10-27 16:47:50.554670202 +0100
+++ /var/tmp/diff_new_pack.cpKIRG/_new 2026-10-27 16:47:50.555670244 +0100
@@ -18,14 +18,14 @@
%{?sle15_python_module_pythons}
Name: python-jaraco.develop
-Version: 8.19.2
+Version: 9.1.0
Release: 0
Summary: Development utilities by jaraco
License: MIT
URL: https://github.com/jaraco/jaraco.develop
Source:
https://files.pythonhosted.org/packages/source/j/jaraco.develop/jaraco_develop-%{version}.tar.gz
BuildRequires: %{python_module pip}
-BuildRequires: %{python_module setuptools >= 56}
+BuildRequires: %{python_module setuptools >= 77}
BuildRequires: %{python_module setuptools_scm >= 3.4.1}
BuildRequires: %{python_module wheel}
BuildRequires: python-rpm-macros
@@ -36,8 +36,8 @@
BuildRequires: %{python_module ini2toml}
BuildRequires: %{python_module jaraco.collections}
BuildRequires: %{python_module jaraco.context}
-BuildRequires: %{python_module jaraco.packaging}
-BuildRequires: %{python_module jaraco.ui}
+BuildRequires: %{python_module jaraco.packaging >= 10}
+BuildRequires: %{python_module jaraco.ui >= 2.4}
BuildRequires: %{python_module jaraco.vcs >= 1.1}
BuildRequires: %{python_module keyring}
BuildRequires: %{python_module packaging}
@@ -54,10 +54,10 @@
Requires: python-build
Requires: python-ini2toml
Requires: python-jaraco.context
-Requires: python-jaraco.ui
+Requires: python-jaraco.packaging >= 10
+Requires: python-jaraco.ui >= 2.4
Requires: python-jaraco.vcs >= 1.1
Requires: python-keyring
-Requires: python-keyring
Requires: python-packaging
Requires: python-path
Requires: python-requests
++++++ jaraco_develop-8.19.2.tar.gz -> jaraco_develop-9.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/(meta)/agents/skeleton.md
new/jaraco_develop-9.1.0/(meta)/agents/skeleton.md
--- old/jaraco_develop-8.19.2/(meta)/agents/skeleton.md 1970-01-01
01:00:00.000000000 +0100
+++ new/jaraco_develop-9.1.0/(meta)/agents/skeleton.md 2026-10-23
20:01:41.818654300 +0200
@@ -0,0 +1 @@
+Fetch and read the [skeleton](https://blog.jaraco.com/skeleton/) document in
its entirety. It is the authoritative source of truth for this project's
structure, conventions, testing, and release process, and is not duplicated
here.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jaraco_develop-8.19.2/.claude/skills/refresh-projects/SKILL.md
new/jaraco_develop-9.1.0/.claude/skills/refresh-projects/SKILL.md
--- old/jaraco_develop-8.19.2/.claude/skills/refresh-projects/SKILL.md
1970-01-01 01:00:00.000000000 +0100
+++ new/jaraco_develop-9.1.0/.claude/skills/refresh-projects/SKILL.md
2026-10-23 20:01:41.818833000 +0200
@@ -0,0 +1,373 @@
+---
+name: refresh-projects
+description: Refresh jaraco's skeleton-based projects - clean each checkout,
baseline with tox, merge jaraco/skeleton, repair ruff and other emergent
failures, push to main, and chase CI to green. Use when asked to "refresh the
projects", sync projects with skeleton, or get jaraco.com/projects green again.
+---
+
+# Refresh projects
+
+Brings jaraco's maintained projects up to date with
+[skeleton](https://github.com/jaraco/skeleton) and back to a green build.
+Before starting, read <https://blog.jaraco.com/skeleton/> (and the linked
+scm-approach and design pages). That document is the authoritative source for
+conventions.
+
+## Selecting projects
+
+- The list lives in `~/code/jaraco/dotfiles/projects.txt`. Entries starting
+ with `/` are `org/name`. Bare names are under `jaraco/`. Checkouts live at
+ `~/code/<org>/<name>`.
+- Skip entries tagged `[fork]` (not maintained here) and `[base]` (skeleton
+ and tidelift themselves).
+- `[lifted]` marks Tidelift-supported projects. Do these first.
+- Some projects have no `.github/workflows/main.yml`. These are cherrypy and
+ pypa projects with their own CI, plus a few non-packages such as dotfiles.
+- Coherent System projects aren't skeleton-based either. A project is one if
+ it has no tracked `pyproject.toml` (like jaraco.pdf), uses the
+ `coherent.build` backend, or runs CI from `coherent-oss/system` (like
+ tempora). Never merge skeleton into these, even when
+ `git merge-base HEAD FETCH_HEAD` succeeds, because the merge would
+ resurrect deleted skeleton files. Still run the baseline and fix failures.
+ To get a baseline for a Coherent project, run
+ `PYTHONSAFEPATH=1 uvx --python 3.X --from 'coherent.test>=0.7.0' python -m
coherent.test`.
+ coherent.build infers `Requires-Python` from the oldest CPython branch,
+ so a CPython end-of-life breaks every Coherent project at once. Fix that in
+ coherent-oss/system, not per project.
+ To see every violation, fetch skeleton's `ruff.toml` and run
+ `ruff check --config <it> --target-version py3XX .`, matching the inferred
+ Requires-Python. To test a change to coherent.test itself, run another
+ project with `uvx --from ~/code/coherent-oss/coherent.test python -m
coherent.test`.
+ UP036 after a CPython end-of-life means support is being dropped, so in
+ coherent.build/coherent.test deliver that through a PR.
+- Otherwise, merge skeleton only into projects that already share its
+ history (where `git merge-base HEAD FETCH_HEAD` succeeds). If neither
+ applies, leave the project alone and report it.
+
+Baseline CI state per repo:
+
+```bash
+gh run list -R ORG/NAME -w main.yml -b main -e push -L 1 --json
conclusion,createdAt
+```
+
+Use `-e push`, because `-b main` also matches fork PRs whose branch is named
+`main`. Runs older than a few months usually have expired logs. Treat the local
+`tox` run as the source of truth.
+
+## Per-project procedure
+
+1. **Clean.** Run `git status --short` (ignored files don't count). If there
+ are tracked changes or untracked files that look meaningful, such as
+ source, docs or newsfragments rather than `*.orig` or build leftovers,
+ **stop and report** instead of discarding them. Some files are always
+ disposable: report artifacts such as `diffcov.html` and `readme.html`,
+ and an untracked `pyproject.toml` that contains only the
+ `coherent.build` build-system stub (left over from building a
+ Coherent System project). Otherwise:
+ `git fetch origin && git switch main && git merge --ff-only origin/main
+ && git clean -fdx`. If main can't fast-forward, stop and report. The
exception is a local
+ main that's ahead only by a stale merge whose parents are both already
+ upstream. Move it to a backup branch and reset main.
+ Use `git clean -fdx -e .claude` to keep a user's
+ `.claude/settings.local.json`. Delete `diffcov.html` after running
+ diffcov.
+ Review `git clean -ndx` first. If the clean is denied by the permission
+ mode, skip it and use `tox -r` for a fresh environment.
+2. **Baseline.** Projects that read keys through keyring can hang on a
+ macOS Keychain prompt. Run with
+ `PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring` and
+ `TOX_OVERRIDE=testenv.pass_env+=PYTHON_KEYRING_BACKEND`. Run `tox` (current
Python) and note what fails before
+ touching anything. That tells you which failures the merge caused. Common
+ non-lint causes are dead pytest plugins in test extras (pytest-pep8
+ crashes under pytest 9 before collection) and `pkg_resources` imports
+ (see below). Tests keyed to `date.today()` can also fail because
+ calendar data expired. Classify those as data expiry, not merge fallout.
+3. **Merge skeleton.** `git pull --no-edit gh://jaraco/skeleton`.
+ - Keep the default merge message (`Merge
https://github.com/jaraco/skeleton`).
+ - "refusing to merge unrelated histories" means the project predates a
+ history collapse. Pull the matching handoff branch first
+ (`git pull gh://jaraco/skeleton 2023-handoff`), but only if the project
+ is otherwise skeleton-based.
+ It can also mean the project's history was rewritten (filter-repo
+ strips signatures, so the SHAs change). Find the skeleton commit with a
+ matching tree, `git merge-recursive <it> -- HEAD <skeleton>`, and
+ commit with skeleton as the second parent. Report it.
+ - Conflicts: try `git mergetool -t known-merge`
+ (`py -m jaraco.develop.merge`), which resolves placeholder and shebang
+ conflicts. Resolve the rest by hand:
+ - In `pyproject.toml`, the `# upstream` sections follow skeleton and the
+ `# local` sections stay with the project.
+ - In `ruff.toml`, take skeleton's upstream entries and keep the
+ project's `# local` entries. Drop local entries that skeleton has since
+ adopted upstream (B008, or `extend-select` entries such as `UP`), and
fix stray whitespace before
+ `# local`, which otherwise conflicts every time.
+ - Files the project deliberately customized or deleted keep the
+ project's version.
+ - A check the project disabled on purpose stays disabled, even when
+ skeleton edits that line. An example is a commented-out
+ `pytest-mypy` or `pytest-ruff` dependency.
+ - Local `actions/cache@v1`/`v2` steps are dead (those versions are
+ retired), so drop them.
+ - `.github/workflows/main.yml` now delegates to the reusable
+ `test-suite.yml`, so local matrix entries or steps can't carry over
+ as-is. Check whether the customization is still needed (often it's
+ stale) and take skeleton's version if not. If it is still needed,
+ raise it as a PR.
+ - Then `git commit --no-edit`.
+ - "Already up to date" is fine. Confirm `ruff check .` is clean and CI is
+ green, then move on.
+4. **Ruff (jaraco/skeleton#210).** Fix only what tox reports. pytest-ruff
+ shows only the first violation per file, so run `ruff check <file>` on
+ each file tox flags to see all of them. Some projects
+ deliberately exclude vendored sources (`collect_ignore` in `conftest.py`)
+ or disable pytest-ruff, and a bare `ruff check .` reports false alarms
+ there. A nested `ruff.toml` without `extend = "../ruff.toml"` silently
+ replaces the root config for its subtree, so check for those too. Run
+ `ruff check .` and
+ `ruff format --check .`. Fix with `ruff check --fix .` (and `ruff format .`
+ if needed), then **review the diff**. The autofixes are not always safe:
+ - PLC0414/F401 can delete `import X as X` re-exports. Skeleton ignores
+ PLC0414 now. Still check that no public name disappeared.
+ - B009 (`getattr(obj, 'const')` → attribute access) can upset mypy when
+ the attribute is dynamic. Run mypy (it runs under tox) to confirm.
+ - Skeleton enables `allow-dict-calls-with-keyword-arguments`, so keep the
+ `dict(a=1)` style. Don't rewrite it to literals.
+ - RUF012 (mutable class attributes): annotate with `ClassVar` (bare
+ `ClassVar` when the type is inferable). If the class attribute is an
+ intentional, never-mutated default, use `# noqa` with a reason.
+ - UP036 (outdated version block) often marks a `compat` shim for a Python
+ below `requires-python`. Remove the shim and its call sites in a
+ separate commit before the ruff commit.
+ - Some patterns are intentional. Give these a `# noqa` with a reason
+ instead of a code change:
+ - BLE001 in error-handler callbacks
+ - PLE0704 (bare `raise`) in `onerror` handlers and re-raise helpers
+ - SIM115 where a file deliberately outlives the block
+ - TRY004 where changing the exception type would change behavior
+ - EXE001: for a console-script entry-point module, remove the shebang. For
+ a script that runs through its shebang (e.g. mockproc), `chmod +x` it.
+ - SIM115 for an optional file or stdout:
+ `with (open(p) if p else contextlib.nullcontext(sys.stdout)) as f:`.
+ - SIM115: prefer a real `with` when the handle would otherwise never be
+ closed. Leaked handles can lose writes on PyPy. Use `noqa` only when the
+ file deliberately outlives the block.
+ - SIM905's autofix turns readable `"""...""".split()` word lists into
+ long literals. Reject it and use a `# noqa`.
+ - SIM102 often has no autofix when comments are present, so collapse
+ those by hand and keep the comments.
+ - Files synced from another upstream (CPython-derived code in
+ configparser, importlib_*, backports) shouldn't be mass-rewritten. Add
+ `[lint.per-file-ignores]` under `# local` for them, delivered as a PR
+ that names the mass-fix alternative.
+ - UP031 in log calls: when `log` may be a non-stdlib object (as in
+ cssutils/encutils), use f-strings rather than lazy logging arguments.
+ - Review "safe" autofixes too. G010 (`.warn` → `.warning`) assumes a
+ stdlib logger and broke cssutils' custom handler. RUF059's `_` renames
+ can expose F841. PLC0206 rewrites can shadow a variable the loop body
+ reassigns. Rerun ruff and the tests after each round of fixes.
+ - RUF012 sometimes exposes real shared-state bugs, such as a mutable class
+ attribute that instances mutate. Check before reaching for `ClassVar`,
+ and give a real bug its own commit and newsfragment.
+ - Ruff 0.17 dropped some 0.16 defaults (DTZ011, for example), so their
+ `# noqa` comments now trip RUF100 (unused noqa). Remove the codes and
+ keep any explanatory comment.
+ - RUF059 `_` renames: check that the name isn't consumed through
+ `locals()`/`vars()` (e.g. `.format(**locals())`).
+ - SIM118 (`in obj.keys()` → `in obj`) is only safe for real mappings.
+ Check the type first.
+ - PLW0602: removing a do-nothing `global x` can expose F821 when the
+ name is never bound at module level. Find the intended binding site
+ (often `main()`) rather than adding a `noqa`.
+ - RUF012 on a class attribute built from `locals()`: before Python 3.14,
+ any annotation in the class body adds `__annotations__` to `locals()`,
+ so don't use `ClassVar` there. Use a tuple if the attribute is only
+ used for membership checks.
+ - When there are more than ~100 violations, split the ruff commit
+ sensibly. Also confirm everything still parses on the minimum Python
+ (`python3.10 -c "import ast,sys; ast.parse(open(sys.argv[1]).read())"`),
+ since f-string conversions can introduce newer syntax.
+ - UP030/UP031 unsafe fixes can produce broken format strings (mixed
+ numbered and unnumbered fields → F525). Rewrite them by hand or recheck.
+ - `ruff check --fix .` also rewrites files excluded via `collect_ignore`.
+ Revert those, or pass explicit paths.
+ - Preserve line endings. Python `open().read()`/`write()` silently turns
+ CRLF into LF. Check `git ls-files --eol`.
+ - PLR0402's autofix (`import a.b as b` → `from a import b`) can break mypy
+ when `a.b` is untyped. Import the needed names directly instead.
+ - A `# noqa` for a multi-line string goes after the string's closing
+ line. If it's placed inside the string, it becomes part of the string.
+ - UP031 workflow: run `--unsafe-fixes --select UP031`, then
+ `--select UP032`. Do the `%d` sites by hand, adding `int()` where the
+ value may not be an int. Ruff won't autofix F401 in `__init__.py`, so
+ remove newly unused imports by hand.
+ - Use `--unsafe-fixes` only selectively, and only with review.
+ - Don't fix a rule by adding it to the project's ignore list unless the
+ rule is wrong for the project. If you do, put it under `# local` with a
+ comment.
+
+ When ruff fixes and other fixes touch the same file, keep them in
+ separate commits. `git add -p` is interactive, so stage with `git stash`
+ or by applying the fixes in sequence.
+
+ Commit as:
+
+ ```
+ Repair the violations from Ruff 0.16's expanded defaults.
+
+ Ref jaraco/skeleton#210.
+ ```
+ Before pushing, run `tox -e diffcov` and check its exit status. Piping it
+ through `tail` hides failures from `&& git push`. Lint rewrites touch lines
that were
+ already uncovered, which fails diffcov in CI. Add a test or simplify. Put
that
+ test or simplification in its own commit, ahead of the ruff commit.
+ Reordering local, unpushed commits (`git reset`, `--amend`) is fine.
+
+ For packages that register a pytest plugin, diffcov reports import-time
+ lines as uncovered. Ignore those.
+
+ diffcov compares against `origin/main`, so it only enforces on PRs. On a
+ direct push to main it passes trivially, so don't chase coverage of
+ untestable interactive or hardware code too far.
+
+ Doctests can use `getfixture('monkeypatch')` to cover side-effecting
+ lines cheaply, without a new test module.
+
+ Python 3.15 colorizes `--help` output under CI's `FORCE_COLOR`. Tests
+ that assert on help text need `PYTHON_COLORS=0`.
+
+ Some rule hits are real bugs and get their own bug-fix commits:
+ - C417 on a bare `map(...)` statement is usually dead code that never
+ runs.
+ - B015 (pointless comparison) in tests usually means a missing
+ `assert`. A missing `f` prefix on a continuation line of an implicitly
+ concatenated string is another real bug.
+ - `map(pattern.match, ...)` used where `filter` was meant, and PyMongo 4
+ removals (`Collection.update`/`insert`/`remove`).
+ - Python 2 leftovers such as `__unicode__`, `itertools.imap`/`ifilter`,
+ `zip(...).sort()`, `obj.next()`, `WindowsError`, and the optparse-style
+ `options, args = parser.parse_args()` used with argparse.
+ Adding tests for diffcov tends to expose these. When a new test exposes a
+ bug, commit the fix before the test so every commit passes. File an
+ issue when a module looks largely broken.
+
+ `pkg_resources` imports fail now that recent Setuptools no longer
+ provides it. Swap `resource_stream` for
+ `importlib.resources.files(pkg).joinpath(p).open('rb')`,
+ `resource_filename` for `str(files(pkg) / p)`, and
+ `require(x)[0].version` for `importlib.metadata.version(x)`. Treat the
+ swap as a bugfix with a newsfragment. `git clean` removes an empty
+ `newsfragments/` directory, so recreate it with `mkdir -p` first.
+
+5. **Other failures.** Re-run `tox` and fix what's left: deprecations, test
+ breakages from new Python or dependency releases, and mypy. Keep each
+ concern in its own commit with a descriptive message. Add a towncrier
+ newsfragment (`newsfragments/+<id>.bugfix.rst` or similar) only for
+ user-facing changes. Lint fixes need none.
+6. **Push** directly to main (`git push`), with no PR, then watch CI:
+
+ ```bash
+ gh run watch -R ORG/NAME <run-id> --exit-status
+ gh run view -R ORG/NAME <run-id> --json conclusion
+ ```
+
+ With many agents polling in parallel, GitHub's secondary rate limit
+ blocks `gh run ...` for minutes at a time. Poll sparingly (every 60s or
+ more) and fall back to
+ `gh api "repos/O/N/actions/workflows/main.yml/runs?branch=main&per_page=1"`,
+ matching `head_sha` to the pushed commit.
+
+ Confirm the conclusion with `gh run view`. Don't trust a wrapped
+ command's exit code. One failed job cancels the rest of the matrix, so a
+ second run may surface more failures.
+
+ For Windows-only code, run `mypy --platform win32 .` locally, and run
+ `ruff check` on platform-collected files to see what Windows CI will
+ report. First check whether the matrix disables windows-latest, in which
+ case CI won't verify those fixes.
+
+ mypy only checks platform branches (such as `sys.platform == 'win32'`) on
+ that platform. A `type: ignore` that is unused on Windows fails only in
+ Windows CI. Use `# type: ignore[code, unused-ignore]` there.
+
+ Fail-fast cancels the rest of the matrix after the first failure. To see
+ every failure, rerun once (`gh run rerun --failed`) or push a throwaway
+ branch. A cancelled job may already have passed, so read its log
+ before rerunning. Skeleton's matrix includes 3.14t and PyPy, which fail
when a
+ C-extension dependency has no wheel for them (psycopg2, pywin32). Treat
+ those separately from failures the merge caused. A local matrix
+ exclusion goes in a PR.
+
+ Lines that only run when a resource is missing, such as skip branches,
+ are never covered in CI. Avoid touching them for lint, or mark them
+ `# pragma: no cover`.
+
+ During the merge, check `tox.ini` for git-pinned forks, stale pins and
+ `extras` that no longer exist (fatal under tox 4).
+
+ Network tests broken by a third-party site: `xfail` them with a
+ reference to an issue.
+
+ Look up the run with `-w main.yml`. Otherwise the newest run is often
+ the "Dependency Graph" workflow.
+
+ Before pushing, check open PRs for overlap with the refresh. Don't
+ create a file a contributor PR also adds. Running
+ `git merge-tree --write-tree origin/main <pr-head>` before and after the
+ refresh shows whether the refresh introduces a conflict. Report any it
+ does.
+
+ When a lint rewrite touches rendering or formatting code, compare the
+ generated output before and after, using a `git worktree` of the
+ pre-change commit. That catches `vars()`/`locals()` breakage that tests
+ miss.
+
+ Before fixing a CI failure, search the project's open PRs. If a
+ contributor PR already fixes it, report that rather than duplicating it,
+ because merging someone else's PR is the maintainer's call. Also mention
+ whether the refresh commits will conflict with that PR.
+
+ Fix CI-only failures (other platforms or Python versions, diffcov, docs)
+ as follows:
+ - **Shared dependency or install failures** (one cause across many
+ projects, such as a missing wheel or a CI image change): fix them once
+ at the source (skeleton's `test-suite.yml`, or the upstream project)
+ instead of per project. A missing wheel can also show up as pip
+ backtracking to an ancient release of a dependency, which then fails
+ (e.g. `No module named 'pkg_resources'`), or as pip's
+ `resolution-too-deep`. Check the job log for
+ unexpectedly old versions. When batching, have the coordinator do it so
+ issues aren't duplicated.
+ - **Minor issues:** fix directly and push.
+ - **Significant issues:** file an issue on the project describing the
+ concern, then fix it in a commit that references the issue
+ (`Ref ORG/NAME#N` or `Closes ...`).
+ - **Anything needing the maintainer's judgment:** push it to a branch and
+ open a PR instead of committing to main. Examples are behavior changes,
+ dropping support, a large redesign, or a disputed lint rule.
+
+## Conventions
+
+- The user's shell is xonsh. Bash-isms such as `set -- $x` misbehave
+ silently. Wrap multi-step shell loops in `bash -c '...'`. macOS
+ `/bin/bash` is 3.2, so there's no `declare -A`.
+
+- Qualify cross-repo references (`jaraco/skeleton#210`, not `#210`).
+- Don't pin ruff, and don't trust `.pre-commit-config.yaml` (it's
+ best-effort). tox/pytest-ruff is authoritative.
+- Don't add `.gitignore` or `LICENSE` files (jaraco/skeleton#197).
+- Never force-push. Never rewrite main.
+
+## Scaling out
+
+With many projects, use one subagent per batch of about five projects, and
+run batches in parallel. Each project is a separate checkout, so there's no
+contention. Give each agent this procedure and its project list. Ask it to
+report, per project:
+
+- the baseline result
+- the merge outcome
+- commits pushed
+- the final CI state
+- any issues or PRs filed
+- anything it stopped on
+
+At the end, re-run the CI status query across all projects and summarize the
+remaining reds. The goal is <https://jaraco.com/projects> mostly green.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/.github/workflows/main.yml
new/jaraco_develop-9.1.0/.github/workflows/main.yml
--- old/jaraco_develop-8.19.2/.github/workflows/main.yml 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/.github/workflows/main.yml 2026-10-23
20:01:41.819189800 +0200
@@ -15,62 +15,35 @@
permissions:
contents: read
-env:
- # Environment variable to support color support (jaraco/skeleton#66)
- FORCE_COLOR: 1
-
- # Suppress noisy pip warnings
- PIP_DISABLE_PIP_VERSION_CHECK: 'true'
- PIP_NO_WARN_SCRIPT_LOCATION: 'true'
-
- # Ensure tests can sense settings about the environment
- TOX_OVERRIDE: >-
- testenv.pass_env+=GITHUB_*,FORCE_COLOR
-
-
jobs:
test:
strategy:
# https://blog.jaraco.com/efficient-use-of-ci-resources/
matrix:
python:
- - "3.9"
- - "3.13"
+ - "3.11"
+ - "3.15"
+ - "3.15t"
platform:
- ubuntu-latest
- macos-latest
- windows-latest
include:
- - python: "3.10"
- platform: ubuntu-latest
- - python: "3.11"
- platform: ubuntu-latest
- python: "3.12"
platform: ubuntu-latest
+ - python: "3.13"
+ platform: ubuntu-latest
- python: "3.14"
platform: ubuntu-latest
- - python: pypy3.10
+ - python: "3.16"
+ platform: ubuntu-latest
+ - python: pypy3.11
platform: ubuntu-latest
- runs-on: ${{ matrix.platform }}
- continue-on-error: ${{ matrix.python == '3.14' }}
- steps:
- - uses: actions/checkout@v4
- - name: Install build dependencies
- # Install dependencies for building packages on pre-release Pythons
- # jaraco/skeleton#161
- if: matrix.python == '3.14' && matrix.platform == 'ubuntu-latest'
- run: |
- sudo apt update
- sudo apt install -y libxml2-dev libxslt-dev
- - name: Setup Python
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python }}
- allow-prereleases: true
- - name: Install tox
- run: python -m pip install tox
- - name: Run
- run: tox
+ uses: ./.github/workflows/test-suite.yml
+ with:
+ python: ${{ matrix.python }}
+ platform: ${{ matrix.platform }}
+ secrets: inherit
collateral:
strategy:
@@ -79,19 +52,11 @@
job:
- diffcov
- docs
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
- - name: Setup Python
- uses: actions/setup-python@v5
- with:
- python-version: 3.x
- - name: Install tox
- run: python -m pip install tox
- - name: Eval ${{ matrix.job }}
- run: tox -e ${{ matrix.job }}
+ uses: ./.github/workflows/test-suite.yml
+ with:
+ platform: ubuntu-latest
+ tox-env: ${{ matrix.job }}
+ secrets: inherit
check: # This job does nothing and is only used for the branch protection
if: always()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jaraco_develop-8.19.2/.github/workflows/test-suite.yml
new/jaraco_develop-9.1.0/.github/workflows/test-suite.yml
--- old/jaraco_develop-8.19.2/.github/workflows/test-suite.yml 1970-01-01
01:00:00.000000000 +0100
+++ new/jaraco_develop-9.1.0/.github/workflows/test-suite.yml 2026-10-23
20:01:41.819275900 +0200
@@ -0,0 +1,69 @@
+name: Test Suite
+
+on:
+ workflow_call:
+ inputs:
+ python:
+ description: Python version
+ type: string
+ default: "3.x"
+ platform:
+ description: Runner platform
+ type: string
+ default: ubuntu-latest
+ tox-env:
+ description: >-
+ Tox environment to run; if empty, runs default tox
+ type: string
+ default: ""
+
+env:
+ # Environment variable to support color support (jaraco/skeleton#66)
+ FORCE_COLOR: 1
+
+ # Suppress noisy pip warnings
+ PIP_DISABLE_PIP_VERSION_CHECK: 'true'
+ PIP_NO_WARN_SCRIPT_LOCATION: 'true'
+
+ # Ensure tests can sense settings about the environment
+ TOX_OVERRIDE: >-
+ testenv.pass_env+=GITHUB_*,FORCE_COLOR
+
+jobs:
+ run:
+ runs-on: ${{ inputs.platform }}
+ # Tolerate failures on pre-release Pythons (jaraco/skeleton#199)
+ # local: and on free-threaded Windows, where PyNaCl publishes no
+ # cp315t wheels and can't build from source (jaraco/skeleton#227)
+ continue-on-error: >-
+ ${{ inputs.python == '3.16'
+ || (inputs.python == '3.15t' && inputs.platform == 'windows-latest') }}
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Install build dependencies
+ # Install dependencies for building packages on pre-release Pythons
+ # (jaraco/skeleton#161) and PyPy, which often lags on wheels
+ # (jaraco/skeleton#222).
+ if: >-
+ (inputs.python == '3.16' || startsWith(inputs.python, 'pypy'))
+ && inputs.platform == 'ubuntu-latest'
+ run: |
+ sudo apt update
+ sudo apt install -y libxml2-dev libxslt-dev
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ inputs.python }}
+ allow-prereleases: true
+ - name: Install tox
+ run: python -m pip install tox
+ - name: Run
+ if: ${{ !inputs.tox-env }}
+ run: tox
+ - name: Run ${{ inputs.tox-env }}
+ if: ${{ inputs.tox-env }}
+ run: tox -e ${{ inputs.tox-env }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/.pre-commit-config.yaml
new/jaraco_develop-9.1.0/.pre-commit-config.yaml
--- old/jaraco_develop-8.19.2/.pre-commit-config.yaml 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/.pre-commit-config.yaml 2026-10-23
20:01:41.819275900 +0200
@@ -2,6 +2,6 @@
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.0
hooks:
- - id: ruff
+ - id: ruff-check
args: [--fix, --unsafe-fixes]
- id: ruff-format
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/AGENTS.md
new/jaraco_develop-9.1.0/AGENTS.md
--- old/jaraco_develop-8.19.2/AGENTS.md 1970-01-01 01:00:00.000000000 +0100
+++ new/jaraco_develop-9.1.0/AGENTS.md 2026-10-23 20:01:41.819275900 +0200
@@ -0,0 +1 @@
+**Required first step:** Before reading further or making any changes, read
every file in `(meta)/agents/` in its entirety. Those files are the
authoritative source of truth for this project's conventions and are not
duplicated here. Do not act until you have ingested them.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/NEWS.rst
new/jaraco_develop-9.1.0/NEWS.rst
--- old/jaraco_develop-8.19.2/NEWS.rst 2026-02-07 23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/NEWS.rst 2026-10-23 20:01:41.819275900 +0200
@@ -1,3 +1,37 @@
+v9.1.0
+======
+
+Features
+--------
+
+- Require Python 3.11 or later.
+
+
+Bugfixes
+--------
+
+- Fragment types are now parsed from the right of the filename, as towncrier
+ itself does, so a dot in the fragment id no longer causes ``finalize`` to
+ fail with a ``KeyError``. Fragment names are also validated before the
+ release kind is computed, giving a clear error for unrecognized names. (#34)
+
+
+v9.0.0
+======
+
+Bugfixes
+--------
+
+- Secrets read from keyring are now wrapped in ``secrets.Secret``, whose repr
elides the value, keeping credentials out of rendered tracebacks and other
repr-based output. (#32)
+- Adopted the expanded Ruff 0.16 default rule selection, fixing the violations
it surfaced. Notably, the subprocess invocations in ``init-azure`` and
``macos-build-python`` now check their return codes, failing fast instead of
proceeding after a failed command.
+
+
+Deprecations and Removals
+-------------------------
+
+- ``github.Repo.get_public_key()`` is now the ``Repo.public_key`` cached
property, which caches per repo instead of globally.
+
+
v8.19.2
=======
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/PKG-INFO
new/jaraco_develop-9.1.0/PKG-INFO
--- old/jaraco_develop-8.19.2/PKG-INFO 2026-02-07 23:23:56.875347600 +0100
+++ new/jaraco_develop-9.1.0/PKG-INFO 2026-10-23 20:02:06.051179600 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: jaraco.develop
-Version: 8.19.2
+Version: 9.1.0
Summary: Development utilities by jaraco
Author-email: "Jason R. Coombs" <[email protected]>
License-Expression: MIT
@@ -9,7 +9,7 @@
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Requires-Python: >=3.9
+Requires-Python: >=3.11
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: jaraco.ui>=2.4
@@ -41,15 +41,14 @@
Requires-Dist: furo; extra == "doc"
Requires-Dist: sphinx-lint; extra == "doc"
Provides-Extra: check
-Requires-Dist: pytest-checkdocs>=2.4; extra == "check"
+Requires-Dist: pytest-checkdocs>=2.14; extra == "check"
Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra ==
"check"
Provides-Extra: cover
Requires-Dist: pytest-cov; extra == "cover"
Provides-Extra: enabler
Requires-Dist: pytest-enabler>=3.4; extra == "enabler"
Provides-Extra: type
-Requires-Dist: pytest-mypy>=1.0.1; extra == "type"
-Requires-Dist: mypy<1.19; platform_python_implementation == "PyPy" and extra
== "type"
+Requires-Dist: pytest-mypy>=1.0.1; platform_python_implementation != "PyPy"
and extra == "type"
Requires-Dist: types-setuptools; extra == "type"
Dynamic: license-file
@@ -69,7 +68,7 @@
.. image:: https://readthedocs.org/projects/jaracodevelop/badge/?version=latest
:target: https://jaracodevelop.readthedocs.io/en/latest/?badge=latest
-.. image:: https://img.shields.io/badge/skeleton-2025-informational
+.. image:: https://img.shields.io/badge/skeleton-2026-informational
:target: https://blog.jaraco.com/skeleton
This package includes a collection of libraries and scripts used by `jaraco
<https://www.jaraco.com>`_ and many of the `projects maintained by jaraco
<https://pypi.org/user/jaraco>`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/README.rst
new/jaraco_develop-9.1.0/README.rst
--- old/jaraco_develop-8.19.2/README.rst 2026-02-07 23:23:38.000000000
+0100
+++ new/jaraco_develop-9.1.0/README.rst 2026-10-23 20:01:41.819275900 +0200
@@ -14,7 +14,7 @@
.. image:: https://readthedocs.org/projects/jaracodevelop/badge/?version=latest
:target: https://jaracodevelop.readthedocs.io/en/latest/?badge=latest
-.. image:: https://img.shields.io/badge/skeleton-2025-informational
+.. image:: https://img.shields.io/badge/skeleton-2026-informational
:target: https://blog.jaraco.com/skeleton
This package includes a collection of libraries and scripts used by `jaraco
<https://www.jaraco.com>`_ and many of the `projects maintained by jaraco
<https://pypi.org/user/jaraco>`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jaraco_develop-8.19.2/jaraco/develop/add-github-secrets.py
new/jaraco_develop-9.1.0/jaraco/develop/add-github-secrets.py
--- old/jaraco_develop-8.19.2/jaraco/develop/add-github-secrets.py
2026-02-07 23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/add-github-secrets.py
2026-10-23 20:01:41.820014200 +0200
@@ -2,11 +2,10 @@
from typing import Annotated
import jaraco.context
-import keyring
import typer
from jaraco.ui.main import main
-from . import github
+from . import github, secrets
@jaraco.context.suppress(Exception)
@@ -37,5 +36,5 @@
):
for name in project.find_needed_secrets():
source = secret_sources[name]
- value = keyring.get_password(**source)
+ value = secrets.get_password(**source)
project.add_secret(name, value)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/jaraco/develop/compiler.py
new/jaraco_develop-9.1.0/jaraco/develop/compiler.py
--- old/jaraco_develop-8.19.2/jaraco/develop/compiler.py 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/compiler.py 2026-10-23
20:01:41.820274600 +0200
@@ -45,14 +45,14 @@
test_compiler_c = "#include <python.h>\n"
# make sure mimetypes knows the extension for c files
mimetypes.add_type('text/x-c', '.c')
- with EditableFile(test_compiler_c, 'text/x-c') as file:
- with temp_dir() as output_dir:
- try:
- compiler = ccompiler.new_compiler()
- compiler.compile(
- [file.name], output_dir=output_dir,
include_dirs=get_include_dirs()
- )
- result = True
- except Exception as e:
- result = FalseString(e)
+ with EditableFile(test_compiler_c, 'text/x-c') as file, temp_dir() as
output_dir:
+ try:
+ compiler = ccompiler.new_compiler()
+ compiler.compile(
+ [file.name], output_dir=output_dir,
include_dirs=get_include_dirs()
+ )
+ result = True
+ # any failure is a failure to compile
+ except Exception as e: # noqa: BLE001
+ result = FalseString(e)
return result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/jaraco/develop/cpython-sync.py
new/jaraco_develop-9.1.0/jaraco/develop/cpython-sync.py
--- old/jaraco_develop-8.19.2/jaraco/develop/cpython-sync.py 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/cpython-sync.py 2026-10-23
20:01:41.820274600 +0200
@@ -62,4 +62,5 @@
'-m',
f'cpython-{version} rev={tag["commit"]["sha"][:12]}',
]
- subprocess.run(cmd)
+ # tolerate a no-op sync, where there's nothing to commit
+ subprocess.run(cmd, check=False)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/jaraco/develop/git.py
new/jaraco_develop-9.1.0/jaraco/develop/git.py
--- old/jaraco_develop-8.19.2/jaraco/develop/git.py 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/git.py 2026-10-23
20:01:41.820274600 +0200
@@ -9,6 +9,7 @@
import subprocess
import types
import urllib.parse
+from typing import ClassVar
import path
import requests
@@ -49,7 +50,7 @@
return f'{self.__class__.__name__}({self.prefix!r}, {self.value!r})'
def matches(self, url):
- return url.startswith(self.prefix) or url.startswith(self.value)
+ return url.startswith((self.prefix, self.value))
@classmethod
def parse(cls, line):
@@ -68,7 +69,12 @@
def load(cls):
cmd = ['git', 'config', '--get-regexp', r'url\..*\.insteadof']
lines = subprocess.run(
- cmd, capture_output=True, text=True, encoding='utf-8'
+ cmd,
+ # git exits non-zero when no substitutions are configured
+ check=False,
+ capture_output=True,
+ text=True,
+ encoding='utf-8',
).stdout
return set(map(cls.parse, lines.splitlines()))
@@ -122,10 +128,10 @@
"""
pattern = re.compile(r'(?P<name>\S+)\s*(?P<rest>.*)$')
- tags: list[str] = []
+ tags: ClassVar[list[str]] = []
- def __new__(self, value, **kwargs):
- return super().__new__(self, value)
+ def __new__(cls, value, **kwargs):
+ return super().__new__(cls, value)
def __init__(self, value, **kwargs):
vars(self).update(kwargs)
@@ -170,7 +176,7 @@
def configure_fork(project, repo):
# special case for calendra - make sure not to fetch tags from upstream.
if project == 'calendra':
- cmd = 'git config remote.upstream.tagOpt --no-tags'.split()
+ cmd = ['git', 'config', 'remote.upstream.tagOpt', '--no-tags']
subprocess.check_output(cmd, cwd=repo)
if 'fork' not in project.tags:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/jaraco/develop/github.py
new/jaraco_develop-9.1.0/jaraco/develop/github.py
--- old/jaraco_develop-8.19.2/jaraco/develop/github.py 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/github.py 2026-10-23
20:01:41.820274600 +0200
@@ -6,14 +6,13 @@
import pathlib
import re
-import keyring
import nacl.encoding
import nacl.public
from jaraco.functools import apply
from more_itertools import unique_everseen
from requests_toolbelt import sessions
-from . import repo
+from . import repo, secrets
class Key(str):
@@ -36,26 +35,26 @@
@staticmethod
def load_token():
- token = os.environ.get("GITHUB_TOKEN") or keyring.get_password(
+ token = os.environ.get("GITHUB_TOKEN") or secrets.get_password(
'Github',
username(),
)
assert token, "Token not available"
- return token
+ return secrets.Secret(token)
@classmethod
def detect(cls):
return cls(repo.get_project_metadata().project)
- @functools.lru_cache
- def get_public_key(self):
+ @functools.cached_property
+ def public_key(self):
data = self.session.get(f'{self}/actions/secrets/public-key').json()
key = Key(data['key'])
key.id = data['key_id']
return key
def encrypt(self, value):
- src = self.get_public_key().encode('utf-8')
+ src = self.public_key.encode('utf-8')
pub_key = nacl.public.PublicKey(src, nacl.encoding.Base64Encoder())
box = nacl.public.SealedBox(pub_key)
cipher_text = box.encrypt(value.encode('utf-8'))
@@ -65,7 +64,7 @@
secret = f'{self}/actions/secrets/{name}'
params = dict(
encrypted_value=self.encrypt(value),
- key_id=self.get_public_key().id,
+ key_id=self.public_key.id,
)
resp = self.session.put(secret, json=params)
resp.raise_for_status()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/jaraco/develop/indent.py
new/jaraco_develop-9.1.0/jaraco/develop/indent.py
--- old/jaraco_develop-8.19.2/jaraco/develop/indent.py 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/indent.py 2026-10-23
20:01:41.820274600 +0200
@@ -37,7 +37,7 @@
Take a single spec and use the first part as the root and the latter
part as the spec.
"""
- root, sep, spec = spec.rpartition(os.pathsep)
+ root, _, spec = spec.rpartition(os.pathsep)
root = root or '.'
return _recursive_glob(root, spec)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/jaraco/develop/init-azure.py
new/jaraco_develop-9.1.0/jaraco/develop/init-azure.py
--- old/jaraco_develop-8.19.2/jaraco/develop/init-azure.py 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/init-azure.py 2026-10-23
20:01:41.820274600 +0200
@@ -2,9 +2,10 @@
import json
import subprocess
-import keyring
from jaraco.ui.main import main
+from . import secrets
+
def create_project(project, user):
cmd = [
@@ -19,11 +20,11 @@
'--visibility',
'public',
]
- subprocess.run(cmd)
+ subprocess.run(cmd, check=True)
def create_service_endpoint(project, user):
- github_token = keyring.get_password('Github', user)
+ github_token = secrets.get_password('Github', user)
env = dict(AZURE_DEVOPS_EXT_GITHUB_PAT=github_token)
cmd = [
'az',
@@ -38,7 +39,7 @@
'--project',
project,
]
- subprocess.run(cmd, env=env)
+ subprocess.run(cmd, env=env, check=True)
def find_endpoint_id(project):
@@ -62,7 +63,7 @@
'--yaml-path',
'azure-pipelines.yml',
]
- subprocess.run(cmd)
+ subprocess.run(cmd, check=True)
@main
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jaraco_develop-8.19.2/jaraco/develop/macos-build-python.py
new/jaraco_develop-9.1.0/jaraco/develop/macos-build-python.py
--- old/jaraco_develop-8.19.2/jaraco/develop/macos-build-python.py
2026-02-07 23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/macos-build-python.py
2026-10-23 20:01:41.820274600 +0200
@@ -42,5 +42,5 @@
)
cmd = ['./configure']
cmd += ['--with-pydebug'] * debug
- subprocess.run(cmd, env=env)
- subprocess.run(['make', '-j'])
+ subprocess.run(cmd, env=env, check=True)
+ subprocess.run(['make', '-j'], check=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jaraco_develop-8.19.2/jaraco/develop/migrate-config.py
new/jaraco_develop-9.1.0/jaraco/develop/migrate-config.py
--- old/jaraco_develop-8.19.2/jaraco/develop/migrate-config.py 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/migrate-config.py 2026-10-23
20:01:41.820274600 +0200
@@ -56,12 +56,12 @@
def run():
pyproject = pathlib.Path('pyproject.toml')
config = Translator().translate(pathlib.Path('setup.cfg').read_text(),
'setup.cfg')
- config_bs, config_rest = split_build_system(config)
+ _, config_rest = split_build_system(config)
existing_bs, existing_rest = split_build_system(pyproject.read_text())
pyproject.write_text(
bump_setuptools(existing_bs) + tweak(config_rest) + '\n' +
existing_rest
)
- subprocess.check_call('git rm setup.cfg'.split())
+ subprocess.check_call(['git', 'rm', 'setup.cfg'])
message = (
"Migrated config to pyproject.toml using jaraco.develop.migrate-config
"
"and ini2toml."
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/jaraco/develop/projects-run.py
new/jaraco_develop-9.1.0/jaraco/develop/projects-run.py
--- old/jaraco_develop-8.19.2/jaraco/develop/projects-run.py 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/projects-run.py 2026-10-23
20:01:41.820274600 +0200
@@ -19,13 +19,14 @@
),
)
def run(
+ # typer supplies the values, so these defaults are never mutated
tag: Annotated[
list[filters.Tag], typer.Option('--tag', '-t', parser=filters.Tag)
- ] = [],
+ ] = [], # noqa: B006
keyword: Annotated[
list[filters.Keyword],
typer.Option('--keyword', '-k', parser=filters.Keyword),
- ] = [],
+ ] = [], # noqa: B006
*,
ctx: typer.Context,
):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/jaraco/develop/rtd.py
new/jaraco_develop-9.1.0/jaraco/develop/rtd.py
--- old/jaraco_develop-8.19.2/jaraco/develop/rtd.py 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/rtd.py 2026-10-23
20:01:41.820274600 +0200
@@ -1,14 +1,15 @@
import functools
-import keyring
from requests_toolbelt import sessions
+from . import secrets
+
url = 'https://readthedocs.org/'
@functools.lru_cache
def session():
- auth = 'Token ' + keyring.get_password(url, 'token')
+ auth = secrets.Secret('Token ' + secrets.get_password(url, 'token'))
session = sessions.BaseUrlSession(url + 'api/v3/')
session.headers = dict(Authorization=auth)
return session
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/jaraco/develop/secrets.py
new/jaraco_develop-9.1.0/jaraco/develop/secrets.py
--- old/jaraco_develop-8.19.2/jaraco/develop/secrets.py 1970-01-01
01:00:00.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/secrets.py 2026-10-23
20:01:41.820274600 +0200
@@ -0,0 +1,44 @@
+"""
+Protection for secret values against incidental disclosure.
+"""
+
+import keyring
+from jaraco.functools import compose, pass_none
+
+
+class Secret(str):
+ """
+ A string whose repr elides the value.
+
+ Typer renders tracebacks through Rich, which presents the locals of
+ each frame using repr, disclosing any secret those locals hold
+ (jaraco/jaraco.develop#32). A Secret behaves as its value but
+ declines to reveal it when represented.
+
+ >>> token = Secret('sekrit')
+ >>> token
+ <Secret>
+ >>> token.encode('utf-8')
+ b'sekrit'
+ >>> {'token': token}
+ {'token': <Secret>}
+
+ The value remains available to anything that asks for the string
+ itself, so derived values are unprotected unless also wrapped.
+
+ >>> print(token)
+ sekrit
+ >>> f'Bearer {token}'
+ 'Bearer sekrit'
+ """
+
+ __slots__ = ()
+
+ def __repr__(self):
+ return '<Secret>'
+
+
+get_password = compose(pass_none(Secret), keyring.get_password)
+"""
+Retrieve a password from keyring as a Secret, or None if unset.
+"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/jaraco/develop/towncrier.py
new/jaraco_develop-9.1.0/jaraco/develop/towncrier.py
--- old/jaraco_develop-8.19.2/jaraco/develop/towncrier.py 2026-02-07
23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco/develop/towncrier.py 2026-10-23
20:01:41.820274600 +0200
@@ -1,4 +1,3 @@
-import collections
import pathlib
import subprocess
import sys
@@ -6,7 +5,7 @@
from jaraco.vcs import repo
from jaraco.versioning import semver
-_release_bumps = collections.defaultdict(
+_release_bumps = dict(
feature='minor',
bugfix='patch',
doc='patch',
@@ -18,13 +17,32 @@
"""
+def _fragment_type(file):
+ """
+ Given a towncrier fragment file, infer its type, or None if the
+ name declares no recognized type.
+
+ Parse from the right, as towncrier does, so a dot in the id
+ doesn't shift the fields.
+
+ >>> _fragment_type(pathlib.Path('18.feature.rst'))
+ 'feature'
+ >>> _fragment_type(pathlib.Path('+drop-python-3.10.feature.rst'))
+ 'feature'
+ >>> _fragment_type(pathlib.Path('18.feature'))
+ 'feature'
+ >>> _fragment_type(pathlib.Path('notes.rst'))
+ """
+ parts = file.name.rsplit('.', 2)
+ return next((part for part in parts[-2:] if part in _release_bumps), None)
+
+
def _release_bump(file):
"""
Given a towncrier fragment file, determine the
semver release bump.
"""
- _, type_, _ = file.name.split('.', 2)
- return _release_bumps[type_]
+ return _release_bumps[_fragment_type(file)]
def news_fragments():
@@ -38,6 +56,7 @@
Determine which release to make based on the files in the
changelog.
"""
+ check_changes()
bumps = map(_release_bump, news_fragments())
# use min here as 'major' < 'minor' < 'patch'
return min(bumps, default='patch')
@@ -48,9 +67,7 @@
Verify that all of the news fragments have the appropriate names.
"""
unrecognized = [
- str(file)
- for file in news_fragments()
- if not any(f".{key}" in file.suffixes for key in _release_bumps)
+ str(file) for file in news_fragments() if _fragment_type(file) is None
]
if unrecognized:
raise ValueError(f"Some news fragments have invalid names:
{unrecognized}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jaraco_develop-8.19.2/jaraco.develop.egg-info/PKG-INFO
new/jaraco_develop-9.1.0/jaraco.develop.egg-info/PKG-INFO
--- old/jaraco_develop-8.19.2/jaraco.develop.egg-info/PKG-INFO 2026-02-07
23:23:56.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco.develop.egg-info/PKG-INFO 2026-10-23
20:02:06.019178900 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: jaraco.develop
-Version: 8.19.2
+Version: 9.1.0
Summary: Development utilities by jaraco
Author-email: "Jason R. Coombs" <[email protected]>
License-Expression: MIT
@@ -9,7 +9,7 @@
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
-Requires-Python: >=3.9
+Requires-Python: >=3.11
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: jaraco.ui>=2.4
@@ -41,15 +41,14 @@
Requires-Dist: furo; extra == "doc"
Requires-Dist: sphinx-lint; extra == "doc"
Provides-Extra: check
-Requires-Dist: pytest-checkdocs>=2.4; extra == "check"
+Requires-Dist: pytest-checkdocs>=2.14; extra == "check"
Requires-Dist: pytest-ruff>=0.2.1; sys_platform != "cygwin" and extra ==
"check"
Provides-Extra: cover
Requires-Dist: pytest-cov; extra == "cover"
Provides-Extra: enabler
Requires-Dist: pytest-enabler>=3.4; extra == "enabler"
Provides-Extra: type
-Requires-Dist: pytest-mypy>=1.0.1; extra == "type"
-Requires-Dist: mypy<1.19; platform_python_implementation == "PyPy" and extra
== "type"
+Requires-Dist: pytest-mypy>=1.0.1; platform_python_implementation != "PyPy"
and extra == "type"
Requires-Dist: types-setuptools; extra == "type"
Dynamic: license-file
@@ -69,7 +68,7 @@
.. image:: https://readthedocs.org/projects/jaracodevelop/badge/?version=latest
:target: https://jaracodevelop.readthedocs.io/en/latest/?badge=latest
-.. image:: https://img.shields.io/badge/skeleton-2025-informational
+.. image:: https://img.shields.io/badge/skeleton-2026-informational
:target: https://blog.jaraco.com/skeleton
This package includes a collection of libraries and scripts used by `jaraco
<https://www.jaraco.com>`_ and many of the `projects maintained by jaraco
<https://pypi.org/user/jaraco>`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jaraco_develop-8.19.2/jaraco.develop.egg-info/SOURCES.txt
new/jaraco_develop-9.1.0/jaraco.develop.egg-info/SOURCES.txt
--- old/jaraco_develop-8.19.2/jaraco.develop.egg-info/SOURCES.txt
2026-02-07 23:23:56.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco.develop.egg-info/SOURCES.txt
2026-10-23 20:02:06.032109000 +0200
@@ -2,6 +2,7 @@
.editorconfig
.pre-commit-config.yaml
.readthedocs.yaml
+AGENTS.md
LICENSE
NEWS.rst
README.rst
@@ -13,7 +14,10 @@
todo.txt
towncrier.toml
tox.ini
+(meta)/agents/skeleton.md
+.claude/skills/refresh-projects/SKILL.md
.github/workflows/main.yml
+.github/workflows/test-suite.yml
docs/conf.py
docs/history.rst
docs/index.rst
@@ -21,6 +25,8 @@
jaraco.develop.egg-info/SOURCES.txt
jaraco.develop.egg-info/dependency_links.txt
jaraco.develop.egg-info/requires.txt
+jaraco.develop.egg-info/scm_file_list.json
+jaraco.develop.egg-info/scm_version.json
jaraco.develop.egg-info/top_level.txt
jaraco/develop/__init__.py
jaraco/develop/add-github-secret.py
@@ -45,6 +51,7 @@
jaraco/develop/repo.py
jaraco/develop/rst-header-replace.py
jaraco/develop/rtd.py
+jaraco/develop/secrets.py
jaraco/develop/towncrier.py
jaraco/develop/update-projects.py
jaraco/develop/compat/__init__.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jaraco_develop-8.19.2/jaraco.develop.egg-info/requires.txt
new/jaraco_develop-9.1.0/jaraco.develop.egg-info/requires.txt
--- old/jaraco_develop-8.19.2/jaraco.develop.egg-info/requires.txt
2026-02-07 23:23:56.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco.develop.egg-info/requires.txt
2026-10-23 20:02:06.020941700 +0200
@@ -18,7 +18,7 @@
build<1.3
[check]
-pytest-checkdocs>=2.4
+pytest-checkdocs>=2.14
[check:sys_platform != "cygwin"]
pytest-ruff>=0.2.1
@@ -42,8 +42,7 @@
pytest-home
[type]
-pytest-mypy>=1.0.1
types-setuptools
-[type:platform_python_implementation == "PyPy"]
-mypy<1.19
+[type:platform_python_implementation != "PyPy"]
+pytest-mypy>=1.0.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jaraco_develop-8.19.2/jaraco.develop.egg-info/scm_file_list.json
new/jaraco_develop-9.1.0/jaraco.develop.egg-info/scm_file_list.json
--- old/jaraco_develop-8.19.2/jaraco.develop.egg-info/scm_file_list.json
1970-01-01 01:00:00.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco.develop.egg-info/scm_file_list.json
2026-10-23 20:02:06.016273000 +0200
@@ -0,0 +1,54 @@
+{
+ "files": [
+ "(meta)/agents/skeleton.md",
+ ".claude/skills/refresh-projects/SKILL.md",
+ ".coveragerc",
+ ".editorconfig",
+ ".github/workflows/main.yml",
+ ".github/workflows/test-suite.yml",
+ ".pre-commit-config.yaml",
+ ".readthedocs.yaml",
+ "AGENTS.md",
+ "NEWS.rst",
+ "README.rst",
+ "conftest.py",
+ "docs/conf.py",
+ "docs/history.rst",
+ "docs/index.rst",
+ "jaraco/develop/__init__.py",
+ "jaraco/develop/add-github-secret.py",
+ "jaraco/develop/add-github-secrets.py",
+ "jaraco/develop/add-project.py",
+ "jaraco/develop/checkout-all.py",
+ "jaraco/develop/compat/__init__.py",
+ "jaraco/develop/compat/py38.py",
+ "jaraco/develop/compiler.py",
+ "jaraco/develop/cpython-sync.py",
+ "jaraco/develop/create-github-release.py",
+ "jaraco/develop/filters.py",
+ "jaraco/develop/finalize.py",
+ "jaraco/develop/git.py",
+ "jaraco/develop/github.py",
+ "jaraco/develop/indent.py",
+ "jaraco/develop/init-azure.py",
+ "jaraco/develop/macos-build-python.py",
+ "jaraco/develop/merge.py",
+ "jaraco/develop/migrate-config.py",
+ "jaraco/develop/print-meta.py",
+ "jaraco/develop/projects-run.py",
+ "jaraco/develop/pypi.py",
+ "jaraco/develop/repo.py",
+ "jaraco/develop/rst-header-replace.py",
+ "jaraco/develop/rtd.py",
+ "jaraco/develop/secrets.py",
+ "jaraco/develop/towncrier.py",
+ "jaraco/develop/update-projects.py",
+ "mypy.ini",
+ "pyproject.toml",
+ "pytest.ini",
+ "ruff.toml",
+ "todo.txt",
+ "towncrier.toml",
+ "tox.ini"
+ ]
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jaraco_develop-8.19.2/jaraco.develop.egg-info/scm_version.json
new/jaraco_develop-9.1.0/jaraco.develop.egg-info/scm_version.json
--- old/jaraco_develop-8.19.2/jaraco.develop.egg-info/scm_version.json
1970-01-01 01:00:00.000000000 +0100
+++ new/jaraco_develop-9.1.0/jaraco.develop.egg-info/scm_version.json
2026-10-23 20:02:06.009447000 +0200
@@ -0,0 +1,8 @@
+{
+ "tag": "9.1.0",
+ "distance": 0,
+ "node": "g0a55632133f1fa269a25132ecb455d3bf05fa8de",
+ "dirty": false,
+ "branch": "HEAD",
+ "node_date": "2026-10-23"
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/pyproject.toml
new/jaraco_develop-9.1.0/pyproject.toml
--- old/jaraco_develop-8.19.2/pyproject.toml 2026-02-07 23:23:38.000000000
+0100
+++ new/jaraco_develop-9.1.0/pyproject.toml 2026-10-23 20:01:41.820899000
+0200
@@ -20,7 +20,7 @@
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
]
-requires-python = ">=3.9"
+requires-python = ">=3.11"
license = "MIT"
dependencies = [
"jaraco.ui >= 2.4",
@@ -71,7 +71,7 @@
]
check = [
- "pytest-checkdocs >= 2.4",
+ "pytest-checkdocs >= 2.14",
"pytest-ruff >= 0.2.1; sys_platform != 'cygwin'",
]
@@ -85,10 +85,9 @@
type = [
# upstream
- "pytest-mypy >= 1.0.1",
-
- ## workaround for python/mypy#20454
- "mypy < 1.19; python_implementation == 'PyPy'",
+
+ # Exclude PyPy from type checks (python/mypy#20454 jaraco/skeleton#187)
+ "pytest-mypy >= 1.0.1; platform_python_implementation != 'PyPy'",
# local
"types-setuptools",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/ruff.toml
new/jaraco_develop-9.1.0/ruff.toml
--- old/jaraco_develop-8.19.2/ruff.toml 2026-02-07 23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/ruff.toml 2026-10-23 20:01:41.820899000 +0200
@@ -5,44 +5,58 @@
"C901", # complex-structure
"I", # isort
"PERF401", # manual-list-comprehension
+ "UP", # pyupgrade (Code modernization for strings, annotations,
conditions, ...)
# Ensure modern type annotation syntax and best practices
# Not including those covered by type-checkers or exclusive to Python
3.11+
"FA", # flake8-future-annotations
"F404", # late-future-import
"PYI", # flake8-pyi
- "UP006", # non-pep585-annotation
- "UP007", # non-pep604-annotation
- "UP010", # unnecessary-future-import
- "UP035", # deprecated-import
- "UP037", # quoted-annotation
- "UP043", # unnecessary-default-type-args
# local
]
ignore = [
# upstream
+ "UP015", # redundant-open-modes, explicit is preferred
# Typeshed rejects complex or non-literal defaults for maintenance and
testing reasons,
# irrelevant to this project.
"PYI011", # typed-argument-default-in-stub
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
"W191",
- "E111",
- "E114",
- "E117",
"D206",
"D300",
- "Q000",
- "Q001",
- "Q002",
- "Q003",
"COM812",
"COM819",
+ # N999 (invalid-module-name) yields false positives when a project's
+ # source directory is the distribution name and is redirected to a
+ # different import package (e.g. namespace layouts); the dotted
directory
+ # name is itself a valid module. See
+ # https://github.com/astral-sh/ruff/issues/27801.
+ "N999",
+
+ # B008 is over-reaching; see jaraco/skeleton#214
+ "B008", # function-call-in-default-argument
+
+ # PLC0414 flags `import X as X`, the idiom type checkers recognize as an
+ # explicit re-export, and its fix (with F401) silently drops public
names.
+ # See jaraco/skeleton#210.
+ "PLC0414", # useless-import-alias
+
+ # B018 flags bare attribute access, which is useful for its side effect
+ # (forcing a lazy import, probing a property) and more elegant in tests
+ # than assigning to an ignored name. See jaraco/skeleton#112.
+ "B018", # useless-expression
+
# local
]
+[lint.flake8-comprehensions]
+# Retain the elegant dict(a=1) keyword-call style; C408 still flags dict()
+# and dict([...]). See jaraco/skeleton#210.
+allow-dict-calls-with-keyword-arguments = true
+
[format]
# Enable preview to get hugged parenthesis unwrapping and other nice surprises
# See https://github.com/jaraco/skeleton/pull/133#issuecomment-2239538373
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jaraco_develop-8.19.2/tox.ini
new/jaraco_develop-9.1.0/tox.ini
--- old/jaraco_develop-8.19.2/tox.ini 2026-02-07 23:23:38.000000000 +0100
+++ new/jaraco_develop-9.1.0/tox.ini 2026-10-23 20:01:41.820899000 +0200
@@ -22,7 +22,7 @@
diff-cover
commands =
pytest {posargs} --cov-report xml
- diff-cover coverage.xml --compare-branch=origin/main --html-report
diffcov.html
+ diff-cover coverage.xml --compare-branch=origin/main --format
html:diffcov.html
diff-cover coverage.xml --compare-branch=origin/main --fail-under=100
[testenv:docs]