Skip to content

feat: improve --verbose diff output for modified device types#61

Merged
marcinpsk merged 6 commits intomainfrom
feat/diff
Apr 23, 2026
Merged

feat: improve --verbose diff output for modified device types#61
marcinpsk merged 6 commits intomainfrom
feat/diff

Conversation

@marcinpsk
Copy link
Copy Markdown
Owner

@marcinpsk marcinpsk commented Apr 22, 2026

Show property changes in diff-u style (- old / + new) with aligned names under a Properties: header. List added and changed components individually, with per-field diffs for changed components.

Summary by CodeRabbit

  • Chores

    • Improved device modification reporting with aligned property diffs, clear separation of image vs non-image changes, and richer component details.
    • CI test workflow now runs across multiple Python versions.
    • Added repository ignore rule for a planning document.
  • New Features

    • Shared normalization for comparing configuration vs system values, improving equality across whitespace, empty values, and numeric/string mismatches.
  • Documentation

    • Added guidance on import-path handling.
  • Tests

    • New and updated tests covering normalization and image-path resolution.

Show property changes in diff-u style (- old / + new) with aligned
names under a Properties: header. List added and changed components
individually, with per-field diffs for changed components.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: eb66521e-206e-4b6e-bad6-9e689efc1f94

📥 Commits

Reviewing files that changed from the base of the PR and between 64c383c and 6b83a7d.

📒 Files selected for processing (3)
  • core/netbox_api.py
  • tests/test_netbox_api.py
  • tests/test_normalization.py

Walkthrough

Introduces a shared normalization module, centralizes YAML/NetBox value comparisons, refactors image-directory discovery and device/module image handling, improves change-detector logging for properties and components, removes trivial __new__ overrides, and updates tests plus CI/docs/gitignore entries.

Changes

Cohort / File(s) Summary
Normalization helpers
core/normalization.py
New module with normalize_values(yaml_val, nb_val) and values_equal(...) to standardize comparisons (unwrap .value, trim/empty→None, numeric coercion rules).
Change logging
core/change_detector.py
Replaced local normalization with shared helpers; added _log_property_diffs; split image vs non-image property logging; expanded component add/modify output to include component type/name and property diffs.
NetBox API & image handling
core/netbox_api.py
Added _image_dir_for_yaml(...) and switched image discovery/counting to use it; replaced local _values_equal calls with values_equal; removed NetBox.__new__ and DeviceTypes.__new__; simplified existing-device-type handling and default src sentinel usage.
Repository allocation
core/repo.py
Removed trivial DTLRepo.__new__ override (now uses default allocation).
Tests
tests/test_normalization.py, tests/test_netbox_api.py, tests/test_change_detector.py
Added new normalization tests; removed old in-file normalization tests; added _image_dir_for_yaml tests; removed _values_equal tests.
CI / Docs / Misc
.github/workflows/tests.yml, README.md, .gitignore
CI: Python-version matrix and matrix-scoped coverage artifacts; README: docs on import branching and image handling; .gitignore: added CODE_REVIEW_PLAN.md.

Sequence Diagram(s)

(Skipped)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
I hop through code with whiskers bright,
I trim the trailing spaces right.
I spot the images, name each part,
I line the diffs with tidy art.
A grateful rabbit cheers the nightly merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: improving verbose diff output for modified device types with formatted property diffs, which is the primary focus of the core/change_detector.py modifications and directly reflects the PR's objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feat/diff

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

@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 `@core/change_detector.py`:
- Around line 543-546: The current logging for property diffs (in the loop over
prop_changes using pc.property_name, pc.old_value and pc.new_value and
handle.verbose_log) writes multi-line values directly, which loses the leading
"-"/"+" prefix on continuation lines; fix by splitting pc.old_value and
pc.new_value on newline and emit each line separately with the same prefix and
formatted name (use the existing name and pad logic), e.g., for old_value do for
i, line in enumerate(pc.old_value.splitlines() or [""]) and call
handle.verbose_log(f"      - {name if i==0 else '':{...}} {line}") (and
similarly for new_value with "      +"), and apply the same change to the other
occurrence handling lines 561-564 so all multi-line property values retain diff
prefixes on every line.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f24bd4b9-46ed-4525-ad66-1413e8fab325

📥 Commits

Reviewing files that changed from the base of the PR and between 5a340e3 and deb10df.

📒 Files selected for processing (1)
  • core/change_detector.py

Comment thread core/change_detector.py Outdated
- Add core/normalization.py with unified normalize_values() and
  values_equal() replacing the drifted _normalize_values (change_detector)
  and _values_equal (netbox_api) implementations.
  Behavioral improvements in the unified version vs what each caller had:
  · pynetbox .value unwrapping applied to rack-type comparisons (latent
    bug: weight_unit and similar choice fields were never unwrapped)
  · all trailing whitespace stripped, not just \n (rack types)
  · numeric coercion in both directions (rack types gained nb→yaml path)
  · int type preserved when non-string side is int (device types now show
    166 instead of 166.0 in diff output)
- Consolidate tests into tests/test_normalization.py; add regression for
  bool-as-int trap (True must not equal "1" or "1.0").
- Expand CI matrix to ["3.12", "3.14"] so Docker Python version is
  exercised on every PR.
- Update CODE_REVIEW_PLAN.md: item 1 verified non-issue, items 2–3 done.
Item 5 — Delete no-op __new__ overrides
  NetBox.__new__, DeviceTypes.__new__, DTLRepo.__new__ each just called
  super().__new__(cls) with no added behaviour.

Item 6 — Extract _image_dir_for_yaml module-level helper
  Replaces three near-identical "last-occurrence segment swap" blocks in
  _resolve_image_paths, count_device_type_images, and
  _discover_module_image_files. Returns Path | None (None for empty /
  "Unknown" src or missing segment). 6 direct tests added.

Item 7 — Drop dead bool return from _handle_existing_device_type
  Both return True statements removed; function now returns None.
  Caller unconditionally continues after the call.

Item 11 — Document image-comparison semantics
  Added a note to _compare_image_properties docstring: only missing images
  are detected; local file modifications with the same name are not
  redetected (NetBox stores URL, not content hash).

Item 12 — Contributor note: keep --update / --only-new / default paths in sync
  Added a paragraph to the Contributing section of README.md pointing at
  the three-branch pattern in create_device_types and its siblings.

Also extracted _log_property_diffs helper from _log_modified_device_details
to bring McCabe complexity from 17 back under the 15 threshold; change_detector
coverage is now 100 %.
@marcinpsk
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

✅ Actions performed

Review triggered.

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.

@marcinpsk
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

✅ Actions performed

Review triggered.

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.

Copy link
Copy Markdown

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
core/netbox_api.py (1)

988-995: 🧹 Nitpick | 🔵 Trivial

Minor: src_path = Path(src_file) computed before the early-return guard.

When src_file is empty or "Unknown", _image_dir_for_yaml returns None and src_path is discarded unused. Not a bug, but moving the Path(src_file) construction below the None check makes the intent clearer and saves a throwaway object on the skip path.

♻️ Proposed tidy-up
-        src_path = Path(src_file)
         image_dir = _image_dir_for_yaml(src_file, "module-types", "module-images")
         if image_dir is None:
             return []
+        src_path = Path(src_file)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/netbox_api.py` around lines 988 - 995, Move the Path(src_file) creation
so it only runs after the early-return guard: call _image_dir_for_yaml(src_file,
"module-types", "module-images") first, return early if it is None, and then
construct src_path = Path(src_file) before using src_path.stem to build the
glob; update the block around the _image_dir_for_yaml call and the image_files
computation (which filters by IMAGE_EXTENSIONS) so no Path object is created
when _image_dir_for_yaml returns None.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/tests.yml:
- Around line 19-21: The workflow's matrix uses python-version values that cause
multiple jobs to upload the same artifact name "coverage-report", leading to 409
conflicts; update the upload-artifact step to give a unique name per matrix job
(for example change the artifact name to include the matrix variable like
coverage-report-${{ matrix.python-version }} or a sanitized variant such as
coverage-report-${{ matrix.python-version//./- }}), ensuring each matrix job
uploads a distinct artifact name.

In @.gitignore:
- Line 141: Move the CODE_REVIEW_PLAN.md entry so it is grouped with the other
documentation-related ignores (near the existing .claude/ and CLAUDE.md entries)
and remove the extra blank line to keep the .gitignore organized and compact;
locate the line containing "CODE_REVIEW_PLAN.md" and relocate it just above or
below the "CLAUDE.md"/.claude/ block, then delete the adjacent blank line.

In `@core/normalization.py`:
- Around line 27-37: Currently the code converts empty strings to None before
trimming, so strings that are only whitespace (e.g., "   ") become "" after
rstrip but are not converted to None; move or duplicate the empty-string
normalization to occur after trimming (i.e., call rstrip() on yaml_val and
nb_val first when they are str, then if yaml_val == "" set yaml_val = None and
similarly for nb_val) so that both yaml_val and nb_val are normalized to None
when they contain only whitespace (referencing the yaml_val and nb_val variables
in core/normalization.py).

In `@README.md`:
- Around line 156-160: Update the README paragraph describing the three-branch
image-progress pattern to clarify that only create_device_types and
create_module_types include the three image-progress branches (only_new
early-return, update, and create) and that create_rack_types follows an
existing/update/create pattern without image-progress blocks; adjust the wording
to remove the blanket “all three” claim and explicitly state that rack types do
not have image-progress branches so future image-handling changes need only be
applied to create_device_types and create_module_types.

---

Outside diff comments:
In `@core/netbox_api.py`:
- Around line 988-995: Move the Path(src_file) creation so it only runs after
the early-return guard: call _image_dir_for_yaml(src_file, "module-types",
"module-images") first, return early if it is None, and then construct src_path
= Path(src_file) before using src_path.stem to build the glob; update the block
around the _image_dir_for_yaml call and the image_files computation (which
filters by IMAGE_EXTENSIONS) so no Path object is created when
_image_dir_for_yaml returns None.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0f3b2d10-ed86-401e-8afc-dfb5c60edac7

📥 Commits

Reviewing files that changed from the base of the PR and between deb10df and e80d8c2.

📒 Files selected for processing (10)
  • .github/workflows/tests.yml
  • .gitignore
  • README.md
  • core/change_detector.py
  • core/netbox_api.py
  • core/normalization.py
  • core/repo.py
  • tests/test_change_detector.py
  • tests/test_netbox_api.py
  • tests/test_normalization.py
💤 Files with no reviewable changes (2)
  • tests/test_change_detector.py
  • core/repo.py

Comment thread .github/workflows/tests.yml
Comment thread .gitignore Outdated
Comment thread core/normalization.py Outdated
Comment thread README.md Outdated
… guard order)

- tests.yml: unique artifact name per matrix job (coverage-report-{py-version})
  to avoid 409 conflicts when both Python versions upload simultaneously
- .gitignore: relocate CODE_REVIEW_PLAN.md entry next to CLAUDE.md / .claude/,
  remove extra trailing blank lines
- normalization.py: strip whitespace before empty-string→None check so
  whitespace-only strings ("   ", "\t\n") also normalize to None; add test
- netbox_api.py: defer Path(src_file) construction until after
  _image_dir_for_yaml early-return guard in _discover_module_image_files
- README.md: clarify that create_rack_types has no image-progress branches;
  only create_device_types and create_module_types need updating for
  image-handling changes
@marcinpsk
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

✅ Actions performed

Review triggered.

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.

Copy link
Copy Markdown

@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: 2

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

Inline comments:
In `@core/netbox_api.py`:
- Around line 79-95: _helper _image_dir_for_yaml treats the literal string
"Unknown" as a sentinel which couples it to callers like create_rack_types and
create_module_types; make this coupling explicit by centralizing the sentinel
(e.g., define a module-level constant such as UNKNOWN_SENTINEL) or add a short
comment at each caller noting that they pass "Unknown" by default, then update
_image_dir_for_yaml to check against that constant (or document the caller
behavior) so future callers won't accidentally pass a legitimate path equal to
the literal "Unknown" without awareness; reference symbols: _image_dir_for_yaml,
create_rack_types, create_module_types.

In `@tests/test_normalization.py`:
- Around line 119-132: The test test_type_error_in_coercion_returns_false
currently never triggers the numeric coercion path because Uncoercible() is
compared to strings, so Uncoercible.__float__ is never called; update the
assertions to pass a numeric (int or float) on one side and Uncoercible() on the
other so normalize_values / values_equal actually invokes float() and returns
False when __float__ raises (e.g., replace or add assertions using 1 or 1.0 with
Uncoercible()), or alternatively rename the test to reflect it only checks
fallback equality behavior if you prefer not to change assertions.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6143cf00-bf37-4299-a13b-4f1dfe2f4c3d

📥 Commits

Reviewing files that changed from the base of the PR and between e80d8c2 and 64c383c.

📒 Files selected for processing (6)
  • .github/workflows/tests.yml
  • .gitignore
  • README.md
  • core/netbox_api.py
  • core/normalization.py
  • tests/test_normalization.py

Comment thread core/netbox_api.py
Comment thread tests/test_normalization.py Outdated
- Define _UNKNOWN_SRC = "Unknown" module-level constant in netbox_api.py;
  update _image_dir_for_yaml, create_rack_types, and create_module_types to
  use it so the sentinel coupling is explicit rather than repeated literals
- Update TestImageDirForYaml to import and use _UNKNOWN_SRC
- Fix test_type_error_in_coercion_is_swallowed: previous Uncoercible() class
  was not int/float so the numeric coercion branch never ran and __float__ was
  never called; replace with BadStr(str) subclass so float(nb_val) actually
  invokes __float__ and the TypeError catch is exercised
@marcinpsk marcinpsk merged commit 0d74d42 into main Apr 23, 2026
7 checks passed
@marcinpsk marcinpsk deleted the feat/diff branch April 23, 2026 07:11
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