Skip to content

Drop plumbum round-trip in normalise_workspace_root (#101) - #110

Merged
leynos merged 4 commits into
mainfrom
issue-101-drop-plumbum-round-trip
Jun 10, 2026
Merged

Drop plumbum round-trip in normalise_workspace_root (#101)#110
leynos merged 4 commits into
mainfrom
issue-101-drop-plumbum-round-trip

Conversation

@lodyai

@lodyai lodyai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #101

  • Replace the plumbum local.path round-trip in normalise_workspace_root with a pure pathlib implementation (Path(value).expanduser().resolve(strict=False)).
  • Move plumbum from production dependencies to the dev group: only the end-to-end test helpers still use it; no production module imports it any longer.
  • Refresh uv.lock accordingly.
  • Update docs/lading-design.md and tick the corresponding docs/roadmap.md Phase 5 item.

Testing

  • New tests/unit/utils/test_path.py with unit tests plus Hypothesis property tests pinning parity with the previous behaviour: results are absolute, ~ expands, redundant separators normalise, None selects the resolved cwd.
  • make check-fmt, make lint, make typecheck, and make test (561 passed) all green.

🤖 Generated with Claude Code

Summary by Sourcery

Migrate workspace path normalisation to a pure pathlib implementation and adjust dependencies, docs, and tests accordingly.

Enhancements:

  • Replace plumbum-based workspace root normalisation with a direct pathlib.Path implementation in lading.utils.path.
  • Document the pathlib-based workspace root handling in the design docs and mark the roadmap item for this migration as complete.

Build:

  • Move plumbum from runtime dependencies to the dev dependency group and refresh the lockfile.

Documentation:

  • Update lading-design documentation to reflect the pathlib-only implementation of workspace root normalisation and mark the related roadmap task as done.

Tests:

  • Add unit and Hypothesis-based property tests for normalise_workspace_root to verify behaviour such as cwd defaulting, tilde expansion, and path normalisation.

Replace the pointless plumbum local.path round-trip with a pure
pathlib implementation. The plumbum conversion produced a string that
was immediately re-wrapped in Path, adding nothing while keeping a
production dependency the cuprum migration is retiring.

Move plumbum to the dev dependency group: the end-to-end test helpers
still shell out through it, but no production module imports it.

Add unit and Hypothesis property tests pinning behaviour parity with
the previous implementation (absolute results, tilde expansion,
separator normalisation, and the None-to-cwd default), and update the
design document and roadmap to record the migration.

Closes #101
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replace production plumbum path handling with pure pathlib in normalise_workspace_root, add Hypothesis and unit tests for its behaviour, move plumbum to dev dependencies, and update design, developers guide, and roadmap documentation.

Changes

Path normalisation modernisation

Layer / File(s) Summary
Path normalisation rewrite and design documentation
lading/utils/path.py, docs/lading-design.md, docs/developers-guide.md
normalise_workspace_root now uses Path(value).expanduser().resolve(strict=False) for strings/Paths and Path.cwd().resolve() for None. Docstring expanded; design and developers-guide text updated to state pathlib-only implementation and to describe load_cargo_metadata using normalise_workspace_root and CommandRunner invocation.
Property-based and unit tests for path normalisation
tests/unit/utils/test_path.py, tests/unit/test_bump_readme.py
Add Hypothesis strategies and _pathlib_reference helper; tests cover None→cwd, tilde expansion, Path vs str inputs, relative resolution, redundant separators, and ~/... expansions. Adjust fenced-code-block strategy to avoid backticks/tilde.
Dependency refactoring: plumbum to dev-only
pyproject.toml
Remove plumbum>=1.8 from production [project].dependencies, add it to [dependency-groups].dev with a test-only comment; reorder tomlkit after msgspec in production deps.
Roadmap completion marker
docs/roadmap.md
Mark Phase 5 task "Migrate lading/utils/path.py" as completed to reflect the pathlib migration.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant normalise_workspace_root
  participant pathlib_Path
  Caller->>normalise_workspace_root: pass value (None | str | Path)
  normalise_workspace_root->>pathlib_Path: Path(value).expanduser().resolve(strict=False)
  pathlib_Path-->>normalise_workspace_root: Path result
  normalise_workspace_root-->>Caller: return resolved Path
Loading

"Cast plumbum off and let Path preside,
Expand the tilde, resolve with pride,
Hypothesis checks each winding lane,
Dev deps shifted, docs explain,
Roadmap ticked — keep tests bona fide"

🚥 Pre-merge checks | ✅ 18 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Developer Documentation ⚠️ Warning Documentation covers the API change and roadmap is marked complete, but "normalizes" appears on lines 195, 388, 499 instead of en-GB "normalises". Replace "normalizes" with "normalises" on lines 195, 388, and 499 in docs/developers-guide.md to maintain consistent en-GB-oxendict spelling.
Testing (Property / Proof) ⚠️ Warning Property tests use tautological oracle: reference function shares identical pathlib code as implementation, not genuinely verifying parity with original. Use independent oracle (based on os.path or original plumbum code) or rely solely on independent invariants like is_absolute() and separator normalisation.
✅ Passed checks (18 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and directly references issue #101 and accurately summarises the main change: replacing plumbum with pathlib in normalise_workspace_root.
Description check ✅ Passed The description comprehensively covers the changeset: pathlib implementation, dependency migration, test additions, documentation updates, and verification that all checks pass.
Linked Issues check ✅ Passed All acceptance criteria from #101 are satisfied: pathlib-only implementation confirmed in lading/utils/path.py, plumbum moved to dev dependencies in pyproject.toml, and comprehensive Hypothesis property tests added in tests/unit/utils/test_path.py.
Out of Scope Changes check ✅ Passed All changes align directly with issue #101 objectives: core refactor, dependency migration, tests, and documentation updates. Only tangential change is tests/unit/test_bump_readme.py adjustment, which improves test robustness without scope creep.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Testing (Overall) ✅ Passed Tests with direct oracles and Hypothesis property tests cover all behaviour. Would fail if expanduser, resolve, or None logic removed. Reference function identity mitigated by is_absolute oracle.
User-Facing Documentation ✅ Passed Internal refactoring replacing plumbum with pathlib in normalise_workspace_root; function signature, behaviour, and CLI interface unchanged, requiring no user documentation updates.
Module-Level Documentation ✅ Passed All Python modules in this PR have proper module-level docstrings explaining purpose and relationships: lading/utils/path.py, tests/unit/utils/test_path.py, and tests/unit/test_bump_readme.py.
Testing (Unit And Behavioural) ✅ Passed Unit tests verify meaningful local behaviour, edge cases (None, tilde, paths), and invariants (absolute). Integration test via load_cargo_metadata. E2E tests verify CLI --workspace-root flag usage.
Testing (Compile-Time / Ui) ✅ Passed PR refactors a utility function without compile-time code generation or output serialisation. Comprehensive Hypothesis property tests verify behaviour parity; no trybuild or snapshot tests required.
Unit Architecture ✅ Passed Pure query function remains read-only with explicit parameters, tests verify without mocking, command paths remain composable with visible intent, dependencies are injectable at boundaries.
Domain Architecture ✅ Passed Path utilities in utils layer correctly isolated from domain models. Pure pathlib implementation removes dependency without mixing infrastructure concerns into business logic.
Observability ✅ Passed No observability needed: refactoring replaces plumbum with pathlib in pure utility function, maintaining identical input/output/error behaviour with no new failure modes or operational concerns.
Security And Privacy ✅ Passed Plumbum dependency removed safely; pathlib implementation is secure; subprocess uses parametrised commands (shell=False); no secrets in tests/docs; input validation present.
Performance And Resource Use ✅ Passed Pathlib-only implementation removes plumbum overhead; O(n) complexity bounded by filesystem limits; finite test bounds (max 12 chars/segment, 5 segments); non-hot initialization paths only.
Concurrency And State ✅ Passed The refactored normalise_workspace_root is a pure, synchronous function using idempotent pathlib operations. No concurrency, shared mutable state, async code, locks, or ordering concerns introduced.
Architectural Complexity And Maintainability ✅ Passed PR removes gratuitous plumbum round-trip with pure pathlib, moves plumbum to dev dependencies, adds property tests for behaviour parity, introduces no new abstractions or circular dependencies.
Rust Compiler Lint Integrity ✅ Passed PR modifies only Python code and documentation; no Rust files are changed. Rust Compiler Lint Integrity check is not applicable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-101-drop-plumbum-round-trip

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai

sourcery-ai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

Migrates workspace root normalisation to a pure pathlib implementation, removes plumbum from runtime dependencies, and adds tests and docs updates to lock in the new behaviour.

Flow diagram for the pathlib-based normalise_workspace_root

flowchart TD
    A["call normalise_workspace_root(value)"] --> B{"value is None"}
    B -- Yes --> C["Path.cwd().resolve()"]
    C --> D["return Path.cwd().resolve()"]
    B -- No --> E["Path(value)"]
    E --> F[".expanduser()"]
    F --> G[".resolve(strict=False)"]
    G --> H["return expanded Path"]
Loading

File-Level Changes

Change Details Files
Replace plumbum-based workspace root normalisation with a pure pathlib implementation.
  • Remove plumbum.local usage and associated import from the path utilities module.
  • Implement normalise_workspace_root using Path.cwd().resolve() for None and Path(value).expanduser().resolve(strict=False) for provided values.
  • Expand the function docstring with parameter/return descriptions and illustrative examples.
lading/utils/path.py
Adjust dependencies so plumbum is only used in tests, not production code.
  • Remove plumbum from the main dependencies list.
  • Add plumbum to the dev dependency group with a comment clarifying it is test-only and that production code uses pathlib and cuprum.
pyproject.toml
Update documentation to reflect the pathlib-only implementation and roadmap status.
  • Revise the design document to state that normalise_workspace_root is implemented with pathlib.Path alone instead of plumbum.local.
  • Mark the roadmap item for migrating lading/utils/path.py as complete (checked).
docs/lading-design.md
docs/roadmap.md
Introduce targeted unit and property-based tests for normalise_workspace_root.
  • Add deterministic tests covering None input (defaults to resolved cwd), tilde expansion, and Path vs string parity.
  • Add Hypothesis-based property tests ensuring relative paths resolve to absolute paths consistent with a pathlib-only reference implementation.
  • Add tests to verify redundant separators are normalised and arbitrary ~/suffix patterns anchor under the home directory.
tests/unit/utils/test_path.py
Refresh the lockfile to reflect the updated dependency graph.
  • Update uv.lock to match the new dependency set, particularly the move of plumbum to dev dependencies.
uv.lock

Assessment against linked issues

Issue Objective Addressed Explanation
#101 Update normalise_workspace_root to use only pathlib (no plumbum import) while preserving existing behaviour.
#101 Remove plumbum from production dependencies in pyproject.toml once no production consumer remains.
#101 Add property-based tests to pin behaviour parity of normalise_workspace_root with the previous implementation (including None -> cwd, tilde expansion, relative paths, and separator normalisation).

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
leynos marked this pull request as ready for review June 9, 2026 22:06
sourcery-ai[bot]

This comment was marked as resolved.

Build the property-test path inputs from segment lists joined via
`pathlib.Path` (and `os.sep` for separator doubling) instead of
hard-coded `/`, so the suite behaves identically on Windows.

Also constrain the `bump_readme` fenced-code-block property test so the
generated body cannot contain its own fence delimiters; a body that
opened a code block would be closed by `fence_header`, leaving the link
outside any fence and correctly rewritten — a flaw in the test, not the
production code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Strengthen `test_accepts_path_instances` with a concrete oracle
(`Path.home().resolve() / "ws"`) so it no longer passes against a
constant-returning implementation; the Path-equals-str check is retained
as a secondary assertion.

Document `normalise_workspace_root` in the developers' guide: its
`Path`/`str`/`None` contract, the pathlib-only implementation, and the
demotion of `plumbum` to a dev-only dependency. Correct the now-stale
`load_cargo_metadata` note that claimed it shelled out via `plumbum`; it
uses the active `CommandRunner`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 9, 2026

Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@leynos

leynos commented Jun 9, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following now been resolved?

  • The Hypothesis strategies in tests/unit/utils/test_path.py construct and manipulate paths using hard-coded / separators (including value.replace('/', '//')), which will behave differently or break on Windows; consider building paths with os.path.join or Path operations and using os.sep to keep the tests platform-agnostic.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cat tests/unit/utils/test_path.py

Length of output: 2591



@leynos Yes, this has been resolved. The current tests/unit/utils/test_path.py addresses the concern fully:

  • Path construction uses str(Path(*segments)) — no hard-coded / anywhere.
  • The redundant-separator test now uses value.replace(os.sep, os.sep * 2) instead of the previous value.replace('/', '//').
  • os is imported at the top of the file to support this.

The strategies and tests are now platform-agnostic.

coderabbitai[bot]

This comment was marked as resolved.

Replace the pathlib mirror oracle in the path property tests, which
restated the implementation almost verbatim, with genuinely independent
metamorphic invariants: results are absolute, retain no unresolved
`.`/`..` segments, anchor relative inputs at the cwd, are fixed points
of further normalisation, and expand `~` to exactly the home directory.

Correct US spellings ("normalizes"/"normalization") to en-GB
("normalises"/"normalisation") throughout the developers' guide, and
drop the stale claim in the design doc that workspace discovery shells
out via `plumbum`; it uses the active `CommandRunner`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@leynos

leynos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following now been resolved?

docs/lading-design.md (1)

228-231: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
Correct the stale metadata execution note in Step 1.2.
Update Line 229 to remove the plumbum claim. lading/workspace/metadata.py

now invokes cargo metadata through the active CommandRunner, so the current

design text is inaccurate.
Triage: [type:docstyle]

📝 Patch the wording
-- Workspace discovery is anchored in `lading.workspace.metadata`. The module
-  uses `plumbum` to construct `cargo metadata --format-version 1`, normalising
-  the workspace root via `lading.utils.normalise_workspace_root` before
-  invoking the command.
+- Workspace discovery is anchored in `lading.workspace.metadata`. The module
+  normalises the workspace root via `lading.utils.normalise_workspace_root`,
+  then invokes `cargo metadata --format-version 1` through the active
+  command-runner boundary.

As per coding guidelines, keep design documents synchronized with the

implemented system.

🤖 Detailed instructions

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/lading-design.md around lines 228 - 231, Update the wording in the
design doc to remove the stale claim that plumbum is used; instead state that
lading.workspace.metadata now invokes cargo metadata via the active
CommandRunner and still normalises the workspace root using
lading.utils.normalise_workspace_root before running the command, referencing
lading.workspace.metadata, CommandRunner, and
lading.utils.normalise_workspace_root so the doc matches the implemented
behavior.

Source: Coding guidelines

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '220,240p' docs/lading-design.md

Length of output: 1339



@leynos Yes, this has been resolved. Lines 228–231 of docs/lading-design.md now read:

Workspace discovery is anchored in lading.workspace.metadata. The module invokes cargo metadata --format-version 1 through the active CommandRunner, normalising the workspace root via lading.utils.normalise_workspace_root before invoking the command.

The stale plumbum claim is gone and the text correctly reflects the CommandRunner-based implementation.

@leynos

leynos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Have the following failed checks now been resolved?

If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures.

Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already.

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Developer Documentation ⚠️ Warning Documentation covers the API change and roadmap is marked complete, but "normalizes" appears on lines 195, 388, 499 instead of en-GB "normalises". Replace "normalizes" with "normalises" on lines 195, 388, and 499 in docs/developers-guide.md to maintain consistent en-GB-oxendict spelling.
Testing (Property / Proof) ⚠️ Warning Property tests use tautological oracle: reference function shares identical pathlib code as implementation, not genuinely verifying parity with original. Use independent oracle (based on os.path or original plumbum code) or rely solely on independent invariants like is_absolute() and separator normalisation.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Checking 'normalizes' vs 'normalises' in docs/developers-guide.md ==="
rg -n "normaliz" docs/developers-guide.md

echo ""
echo "=== Lines 190-200, 383-393, 494-504 ==="
sed -n '190,200p' docs/developers-guide.md
echo "---"
sed -n '383,393p' docs/developers-guide.md
echo "---"
sed -n '494,504p' docs/developers-guide.md

echo ""
echo "=== Full tests/unit/utils/test_path.py ==="
cat tests/unit/utils/test_path.py

Length of output: 5146



@leynos Both warnings are now resolved.

Developer Documentation
rg finds zero occurrences of normaliz in docs/developers-guide.md. Every instance now reads "normalises" (en-GB).

Testing (Property / Proof)
The _pathlib_reference mirror function is gone. The property tests now rely on independent structural invariants:

  • result.is_absolute() — output is always absolute.
  • ".." and "." absent from result.parts — path is fully resolved.
  • Fixed-point: normalise_workspace_root(result) == result.
  • CWD-anchoring: normalise_workspace_root(Path.cwd() / value) == result.
  • ~-expansion: equivalence with literal Path.home() substitution.

None of these re-implement the production logic, so the oracle is no longer tautological.

@leynos
leynos merged commit 6891e75 into main Jun 10, 2026
5 checks passed
@leynos
leynos deleted the issue-101-drop-plumbum-round-trip branch June 10, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Drop gratuitous plumbum round-trip in normalise_workspace_root

1 participant