Skip to content

feat: align with uv ecosystem, best-effort resolution, github action#12

Merged
pivoshenko merged 14 commits into
mainfrom
feat/improve-codebase
May 30, 2026
Merged

feat: align with uv ecosystem, best-effort resolution, github action#12
pivoshenko merged 14 commits into
mainfrom
feat/improve-codebase

Conversation

@pivoshenko

Copy link
Copy Markdown
Owner

Pull Request Checklist

Summary

  • Reframe uv-upsync as a uv-native tool: uv-style CLI flags/output, PEP 691 simple index (index-aware via [[tool.uv.index]]), packaging-based specifier parsing, and a uv lock round-trip with rollback
  • Best-effort resolution by default — held-back packages are reported with the conflicting peer; --resolve bisects for the highest version that locks; --strict and --no-lock opt-outs
  • Compound ranges, --max-bump patch|minor|major, --prerelease, --format text|json|markdown, [tool.uv-upsync] config, pre-commit hook, composite GitHub Action exposing a Markdown summary output
  • README cleanup: badge order, clearer overview/tagline, GitHub-aligned keywords

Checklist

  • My code follows the project style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings or errors

pivoshenko added 14 commits May 29, 2026 10:26
Reframe the tool as "uv lock --upgrade for your pyproject.toml specifiers"
and make it feel native to uv across the CLI, output, and internals.

CLI:
- uv-style options: --project, --directory, -P/--upgrade-package,
  --all-groups, --index-url, --offline, -n/--no-cache, -q/--quiet,
  -v/--verbose, --color, -V/--version
- add --check to fail CI when upgrades are available
- keep -f/--filepath as a hidden, backwards-compatible alias

Output:
- uv-style status lines (Updated <name> v<old> -> v<new>, Resolved/Audited),
  warning:/error: prefixes, and clap-like help (bold headers, cyan flags)

Internals:
- parse specifiers with packaging (PEP 440/508) instead of regex; only the
  version token is rewritten so formatting, extras and markers are preserved
- only raise lower bounds (>=, >, ~=); pinned/capped/excluded are skipped
- PyPIClient now uses the PEP 691 simple JSON API, is index-aware via
  [[tool.uv.index]], fetches concurrently, caches, and honors --offline
- run uv lock in the project directory and roll back on failure
- drop the httpx monkeypatch and the regex-based exception hierarchy

Tests rewritten for the new API; 100% coverage on covered modules.
- add a top-level entry-point wrapper that catches ClickException, Abort,
  BaseError and unexpected errors and renders them as `error: <message>`
  with the correct exit code, so users never see a raw Python traceback
  (use --verbose to re-raise unexpected errors for debugging)
- give the logger a uv-style `Caused by:` chain and use it for `uv lock`
  failures (with rollback) and for malformed/unreadable pyproject.toml
- usage errors now print uv's lowercase `error:` prefix
- ignore TRY400 project-wide; the custom logger uses error(cause=...)
  rather than stdlib logging.exception
- read defaults from a [tool.uv-upsync] table (exclude, group,
  upgrade-package, all-groups, index-url) with validation; resolution
  precedence is CLI > config > defaults, and the index layers as
  --index-url > [tool.uv-upsync] > [[tool.uv.index]] > PyPI
- ship .pre-commit-hooks.yaml with `uv-upsync` and `uv-upsync-check` hooks
- ship a composite action.yml (inputs: args, version, working-directory)
- document configuration, pre-commit and GitHub Action usage in the README
  and add a uv-style tagline; refresh CLAUDE.md
By default, when the full upgrade set does not lock, keep the maximal
subset that resolves instead of discarding every bump. A single
incompatible dependency no longer costs you the rest of the upgrades.

- split parsers into plan_updates (compute, no mutation) and apply_updates
  (return a deep-copied document with a chosen subset applied)
- __main__ tries the full set first (fast happy path), then greedily
  narrows to the maximal resolvable subset on failure, reporting which
  packages were held back; the working tree always ends in a lockable state
- add --strict (today's all-or-nothing rollback, exit 2) and --no-lock
  (write upgrades without running uv lock)
- document the resolution behavior and new flags in the README and CLAUDE.md
- handle compound specifiers like >=1.2,<2.0: raise the lower bound to the
  latest version that still satisfies the cap and any !=/exclusion clauses
  (previously multi-clause specifiers were skipped entirely)
- --max-bump patch|minor|major holds back larger jumps so minors can be
  auto-applied while majors are left for review; held-back majors are
  reported in verbose output
- --prerelease opts into pre-release versions (stable-only by default)
- PyPIClient now returns the full version list; version selection moved to
  parsers.select_new_version, which has the specifier context
- [tool.uv-upsync] gains max-bump and prerelease keys
- --format text|json|markdown renders the upgrade summary; json/markdown go
  to stdout only (status lines are suppressed) for clean scripting
- new report module renders the applied and held-back updates
- the GitHub Action captures the summary and exposes it as a `summary`
  output, so `--format markdown` produces a ready-made pull request body
- document --format and the action output in the README and CLAUDE.md
- --resolve: when a dependency does not lock at its latest version, bisect
  its eligible versions for the highest one that does, instead of holding it
  back entirely (e.g. numpy 2.4.6 -> 2.0.2 under requires-python >=3.9)
- held-back messages now name the conflicting peer when uv's resolver error
  mentions another project dependency (e.g. "conflicts with sphinx")
- parsers gains eligible_versions, at_version, find_conflicts and
  collect_all_names; Update carries the original text/operator so it can be
  rewritten to any version
- [tool.uv-upsync] gains a resolve key
- add a VHS demo (assets/demo.tape + sample project), a `just demo` recipe,
  and a workflow that renders and commits assets/demo.gif; reference it in
  the README
@pivoshenko pivoshenko merged commit d445a73 into main May 30, 2026
1 check passed
@pivoshenko pivoshenko deleted the feat/improve-codebase branch May 30, 2026 08:25
@codecov

codecov Bot commented May 30, 2026

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a3afb2df8c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread action.yml
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
summary="$(uvx ${{ inputs.version != '' && format('uv-upsync@{0}', inputs.version) || 'uv-upsync' }} ${{ inputs.args }})"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve action summaries on check failures

When users run the composite action with args: --check --format markdown, uv-upsync intentionally exits 1 after printing the summary, but this shell: bash step is executed by GitHub with -e, so the failing command substitution aborts the step before status=$? and the $GITHUB_OUTPUT write run. That makes the documented summary output unavailable exactly for the CI-gate case; disable errexit around this command or otherwise capture the status without letting the assignment terminate the step.

Useful? React with 👍 / 👎.

Comment thread src/uv_upsync/__main__.py
) -> exceptions.UVCommandError | None:
_write(pyproject, group, updates, filepath, all_groups=all_groups)
try:
uv.lock(offline=offline, cwd=filepath.parent)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass the CLI index to uv lock

When --index-url is supplied on the uv-upsync CLI, candidate versions are fetched from that index, but the subsequent lock is still invoked as plain uv lock in the project directory. I checked uv lock --help, and --index-url <INDEX_URL>/--default-index are the flags that change the package index for locking; without threading the selected index through here, a private-only version can be written and then resolved against PyPI/project defaults, causing best-effort to hold it back or strict mode to roll back even though it exists on the requested index.

Useful? React with 👍 / 👎.

Comment thread src/uv_upsync/pypi.py
Comment on lines +52 to +53
if indexes:
return str(indexes[0].get("url"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't treat extra indexes as the default index

For projects that declare [[tool.uv.index]] entries without default = true, this returns the first extra index as the sole Simple API base for every dependency. uv lock --help describes --index URLs as being used in addition to the default PyPI index, so a common config such as a PyTorch or internal supplemental index will make uv-upsync query that supplemental index for public packages like click or pytest and incorrectly report no upgrades, even though uv itself would still resolve them from PyPI.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant