Skip to content

Simplify manifest class#1111

Merged
spoorcc merged 4 commits intomainfrom
simplify-manifest-class
Apr 8, 2026
Merged

Simplify manifest class#1111
spoorcc merged 4 commits intomainfrom
simplify-manifest-class

Conversation

@spoorcc
Copy link
Copy Markdown
Contributor

@spoorcc spoorcc commented Apr 6, 2026

Summary by CodeRabbit

  • Improvements

    • Manifest formatting normalized (consistent blank-line spacing) and string quoting made more reliable.
    • Manifest persistence simplified and made consistent across add/import/freeze operations; manifest writing behavior unified.
    • Project input handling improved with stricter validation and more predictable project selection and remote resolution.
    • Manifest construction and serialization made more robust, producing stable YAML output.
  • Bug Fixes

    • Import now omits a redundant remote entry when exactly one remote is discovered.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 6, 2026

Walkthrough

Replaced Manifest.update_dump() usages with Manifest.dump(); refactored Manifest to derive projects/remotes from the live ruamel YAML document; introduced ManifestBuilder for manifest construction/formatting; added ProjectEntry.from_raw; adjusted manifest dumping API, string normalization, and tests.

Changes

Cohort / File(s) Summary
Command callers
dfetch/commands/add.py, dfetch/commands/freeze.py, dfetch/commands/import_.py
Replaced superproject.manifest.update_dump() calls with superproject.manifest.dump(). import_.py now constructs manifests via ManifestBuilder, adding remotes and per-project dicts (removing per-project "remote" when single remote).
Manifest implementation
dfetch/manifest/manifest.py
Major refactor: removed cached _projects/_remotes, rebuilds projects/remotes from ruamel doc on access, added string normalization, changed `dump(path: str
Project entry API
dfetch/manifest/project.py
Added ProjectEntry.from_raw(...) factory to accept dict, ProjectEntry, or YAML-like dict; validates name and delegates to existing constructors or copy.
Tests
tests/test_add.py, tests/test_manifest.py, tests/test_import.py
Updated assertions to expect manifest.dump() instead of update_dump(). Tests use public manifest.remotes instead of private _remotes. Added tests for ManifestBuilder formatting and access. Adjusted import tests to patch dfetch.manifest.manifest.Manifest.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as "CLI (add/freeze/import)"
  participant Manifest as "Manifest / ManifestBuilder"
  participant FS as "Filesystem"

  CLI->>Manifest: append_project_entry(...) / build()
  Note right of Manifest: mutate ruamel YAML doc or assemble YAML string
  Manifest->>Manifest: normalize string scalars & adjust spacing
  Manifest->>FS: dump(path) — write YAML to file
  FS-->>Manifest: write OK
  Manifest-->>CLI: return success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

development

Suggested reviewers

  • ben-edna
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Simplify manifest class' accurately captures the main objective of the pull request, which involves refactoring the Manifest class to reduce complexity, remove deprecated methods (update_dump), and improve the internal architecture.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch simplify-manifest-class

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dfetch/manifest/manifest.py`:
- Around line 343-358: The projects property rebuilds the project list on every
access causing duplicate work because selected_projects calls self.projects and
then _check_all_names_found calls it again; modify selected_projects to reuse
the already-computed list by passing the local all_projects into the checker (or
update _check_all_names_found to accept an optional projects sequence) instead
of letting _check_all_names_found call self.projects again; touch the
selected_projects method and the _check_all_names_found signature/usage to
accept and use the passed projects to avoid double rebuilds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6fd6b95c-2739-46c7-8506-049b4a093de1

📥 Commits

Reviewing files that changed from the base of the PR and between 405a891 and 5b9b3a2.

📒 Files selected for processing (6)
  • dfetch/commands/add.py
  • dfetch/commands/freeze.py
  • dfetch/commands/import_.py
  • dfetch/manifest/manifest.py
  • tests/test_add.py
  • tests/test_manifest.py

Comment thread dfetch/manifest/manifest.py Outdated
@spoorcc spoorcc force-pushed the simplify-manifest-class branch from 5b9b3a2 to 6e29cdb Compare April 6, 2026 21:09
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dfetch/manifest/manifest.py`:
- Around line 262-266: Re-run the spacing and quoting normalizers right before
any serialization so mutations (from append_project_entry and
update_project_version) can't bypass them: call self._ensure_section_spacing()
and self._normalize_string_scalars() at the start of dump() (and similarly
before the immediate serialization path around the other serialize call at the
425-435 area), and/or invoke those two helpers at the end of
append_project_entry() and update_project_version() so the document is
normalized before being written.
- Around line 250-252: The current check calls _ensure_unique(projects_raw,
"dst", "manifest.projects") which only inspects explicit dst keys and misses
collisions where ProjectEntry.__init__ defaults dst to name; update the
validation to compute each project's effective destination (use dst if present
else name) from projects_raw and pass that list or a synthetic field to
_ensure_unique (or call _ensure_unique with the computed values) so duplicates
like a project with no dst and another with dst equal to that name are detected
and rejected.
- Around line 86-92: When clearing potential spurious blank tokens from
parent.ca.items for a given key (the code using parent.ca.items.get(key) and
touching indices [2] and [3]), first inspect the token objects at those
positions and only set them to None if they contain purely whitespace/newlines
(no '#' character) to avoid deleting user comments; similarly update the
analogous logic around the later block that touches indices at lines ~112-115.
Locate the CommentToken (or whatever token object is present) at parent_items[2]
and parent_items[3], examine their string content for a '#' (or non-whitespace)
and preserve the token if it contains a user comment, otherwise clear it as
before. Ensure both places in manifest.py use this same guarded check so user
comments between keys (e.g., between integrity/hash or patch/first item) are
retained.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e0f8cd0e-039f-4abd-a803-a261d8f6b215

📥 Commits

Reviewing files that changed from the base of the PR and between 5b9b3a2 and 6e29cdb.

📒 Files selected for processing (7)
  • dfetch/commands/add.py
  • dfetch/commands/freeze.py
  • dfetch/commands/import_.py
  • dfetch/manifest/manifest.py
  • dfetch/manifest/project.py
  • tests/test_add.py
  • tests/test_manifest.py

Comment thread dfetch/manifest/manifest.py Outdated
Comment thread dfetch/manifest/manifest.py Outdated
Comment thread dfetch/manifest/manifest.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_manifest.py (1)

420-425: 🧹 Nitpick | 🔵 Trivial

Test dump(), not _doc.as_yaml().

The renamed test still bypasses the public API, so it will not catch regressions in Manifest.dump() itself. Using mock_open() keeps the assertion aligned with the behavior that changed in this PR.

♻️ Suggested change
 def test_dump_writes_version_as_quoted_string(manifest_text: str) -> None:
     """dump must always write version as a quoted YAML string."""
     manifest = Manifest.from_yaml(manifest_text)
-    result = manifest._doc.as_yaml()
+    with patch("dfetch.manifest.manifest.open", mock_open()) as mocked_open:
+        manifest.dump(DEFAULT_MANIFEST_NAME)
+    result = "".join(call.args[0] for call in mocked_open().write.call_args_list)
     # The version value must appear quoted so that YAML parsers read it as a string.
     assert f"version: '{manifest.version}'" in result
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_manifest.py` around lines 420 - 425, The test currently calls
manifest._doc.as_yaml() instead of the public API; change
test_dump_writes_version_as_quoted_string to call Manifest.dump() and capture
its output using mock_open (or a tmp file fixture) so you assert against what
dump actually writes; locate the test function
test_dump_writes_version_as_quoted_string and replace the _doc.as_yaml() usage
with a call to manifest.dump() while using mock_open()/patch on builtins.open
(or the test's tmp file writer) to read the written content and assert that
"version: '<manifest.version>'" appears in that captured output.
♻️ Duplicate comments (2)
dfetch/manifest/manifest.py (2)

354-364: ⚠️ Potential issue | 🟠 Major

Normalize the live document immediately before dump().

_normalize_string_scalars() only runs during construction. After later mutations like append_project_entry(), new YAML values are plain strs again, so dump() can write "176"/"%feature" back unquoted and change how they parse on the next load.

♻️ Proposed fix
     def dump(self, path: str | None = None) -> None:
         """Write the manifest to *path*, preserving formatting and comments.
@@
+        self._normalize_string_scalars()
+
         target = path or self.__path
         if not target:
             raise RuntimeError("Cannot dump manifest with no path")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dfetch/manifest/manifest.py` around lines 354 - 364, The dump method may
write newly added/modified string scalars without the normalization applied at
construction, causing values like "176" or "%feature" to be emitted unquoted;
before writing in dump (method dump in class Manifest), call the existing
normalization routine (e.g. _normalize_string_scalars) to re-normalize the live
YAML document so string scalars get proper tagging/quoting; ensure this is
applied right before manifest_file.write(self._doc.as_yaml()) so mutations from
methods like append_project_entry are normalized prior to serializing.

182-184: ⚠️ Potential issue | 🟠 Major

Check uniqueness on the effective dst, not just explicit keys.

_ensure_unique(projects_raw, "dst", ...) skips entries that omit dst, but those still resolve to name. A manifest containing - name: foo and - name: bar; dst: foo passes validation and still makes both projects target the same destination.

♻️ Proposed fix
         _ensure_unique(remotes_raw, "name", "manifest.remotes")
         _ensure_unique(projects_raw, "name", "manifest.projects")
-        _ensure_unique(projects_raw, "dst", "manifest.projects")
+        _ensure_unique(
+            [
+                {"dst": project.get("dst", project["name"])}
+                for project in projects_raw
+            ],
+            "dst",
+            "manifest.projects",
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dfetch/manifest/manifest.py` around lines 182 - 184, The uniqueness check
currently calls _ensure_unique(projects_raw, "dst", "manifest.projects") which
ignores entries that lack an explicit "dst" (they effectively use their "name"),
so compute the effective destination for each project (e.g., effective_dst =
project.get("dst") or project["name"]) and run the uniqueness check against
those effective values; update the validation to either build a list of
effective dsts and call _ensure_unique on that list or modify _ensure_unique to
accept a fallback key/resolver for projects_raw so that collisions between
explicit dst and defaulted name are detected for manifest.projects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dfetch/manifest/manifest.py`:
- Around line 395-403: The function _normalize_string_scalars currently only
wraps top-level mapping values under manifest_mu.get("projects") and
manifest_mu.get("remotes") with _yaml_str, missing nested strings (e.g.,
integrity.hash and list items like ignore); update _normalize_string_scalars to
recursively traverse mappings and sequences within each entry so every string
value encountered is passed to _yaml_str (not just direct values), preserving
existing calls for project and remote entries while ensuring nested dict keys
and list items are normalized too.

---

Outside diff comments:
In `@tests/test_manifest.py`:
- Around line 420-425: The test currently calls manifest._doc.as_yaml() instead
of the public API; change test_dump_writes_version_as_quoted_string to call
Manifest.dump() and capture its output using mock_open (or a tmp file fixture)
so you assert against what dump actually writes; locate the test function
test_dump_writes_version_as_quoted_string and replace the _doc.as_yaml() usage
with a call to manifest.dump() while using mock_open()/patch on builtins.open
(or the test's tmp file writer) to read the written content and assert that
"version: '<manifest.version>'" appears in that captured output.

---

Duplicate comments:
In `@dfetch/manifest/manifest.py`:
- Around line 354-364: The dump method may write newly added/modified string
scalars without the normalization applied at construction, causing values like
"176" or "%feature" to be emitted unquoted; before writing in dump (method dump
in class Manifest), call the existing normalization routine (e.g.
_normalize_string_scalars) to re-normalize the live YAML document so string
scalars get proper tagging/quoting; ensure this is applied right before
manifest_file.write(self._doc.as_yaml()) so mutations from methods like
append_project_entry are normalized prior to serializing.
- Around line 182-184: The uniqueness check currently calls
_ensure_unique(projects_raw, "dst", "manifest.projects") which ignores entries
that lack an explicit "dst" (they effectively use their "name"), so compute the
effective destination for each project (e.g., effective_dst = project.get("dst")
or project["name"]) and run the uniqueness check against those effective values;
update the validation to either build a list of effective dsts and call
_ensure_unique on that list or modify _ensure_unique to accept a fallback
key/resolver for projects_raw so that collisions between explicit dst and
defaulted name are detected for manifest.projects.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4b29e911-e230-4cae-8472-14419d10b4ad

📥 Commits

Reviewing files that changed from the base of the PR and between 6e29cdb and 0b09022.

📒 Files selected for processing (4)
  • dfetch/commands/import_.py
  • dfetch/manifest/manifest.py
  • tests/test_import.py
  • tests/test_manifest.py

Comment thread dfetch/manifest/manifest.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dfetch/manifest/manifest.py`:
- Around line 594-614: The substitution in _render that inserts blank lines
between list entries is too permissive (parts loop using re.sub(r"\n(  - )",
...)), which can match `  - ` inside multi-line scalar values; tighten the regex
to only target actual sequence-item line starts—e.g. replace that re.sub with
one using a multiline/anchored pattern such as re.sub(r"(?m)^\s{2}- ", r"\n\n  -
", parts[i]) or use a lookahead like r"\n(  - (?=\S))" so only true list-item
lines in the parts variable are matched and modified.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2286969b-260f-4832-8225-5d4b057e7ee2

📥 Commits

Reviewing files that changed from the base of the PR and between 0b09022 and affd7a0.

📒 Files selected for processing (2)
  • dfetch/manifest/manifest.py
  • tests/test_manifest.py

Comment thread dfetch/manifest/manifest.py
@spoorcc spoorcc merged commit 8fe9e39 into main Apr 8, 2026
41 checks passed
@spoorcc spoorcc deleted the simplify-manifest-class branch April 8, 2026 20:48
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