feat: add SHA-1 idempotency primitives for CoreFileObject#963
Open
feat: add SHA-1 idempotency primitives for CoreFileObject#963
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o start Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a no-network, no-mutation primitive that callers can use to compare a local bytes/Path/BinaryIO source against the server-stored SHA-1 checksum on a CoreFileObject node, without triggering a transfer. Also adds MATCHES_LOCAL_CHECKSUM_FEATURE_NOT_SUPPORTED_MESSAGE constant and re-exports it from infrahub_sdk/node/__init__.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds InfrahubNode.upload_if_changed() which composes sha1_of_source, upload_from_path/upload_from_bytes, and save() to perform uploads only when local content differs from the server-side checksum. Returns an UploadResult with the locally-computed digest as the post-upload checksum. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirror the async InfrahubNode.upload_if_changed on the sync class, extending TestUploadIfChanged to parametrize over both client types (standard + sync) for all 6 test scenarios. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a `skip_if_unchanged: bool = False` kwarg to `InfrahubNode.download_file`. When True and dest is provided, SHA-1 of the local file is compared against the node's server checksum; a match returns 0 immediately without a network request. Includes @overload signatures and 5 new parametrized tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ircuit Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the async InfrahubNode.download_file skip-if-unchanged logic on InfrahubNodeSync, including overloads. Extend TestDownloadSkipIfUnchanged to parametrize over both client types (53 total tests pass). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deploying infrahub-sdk-python with
|
| Latest commit: |
65f7c71
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://86dab61c.infrahub-sdk-python.pages.dev |
| Branch Preview URL: | https://feat-idempotent-file-ops.infrahub-sdk-python.pages.dev |
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## stable #963 +/- ##
==========================================
+ Coverage 80.77% 80.86% +0.09%
==========================================
Files 132 119 -13
Lines 10999 10433 -566
Branches 1681 1578 -103
==========================================
- Hits 8884 8437 -447
+ Misses 1566 1471 -95
+ Partials 549 525 -24
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 14 files with indirect coverage changes 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds SHA-1 compare-and-skip primitives to
InfrahubNode/InfrahubNodeSyncso downstream libraries stop maintaining parallel copies of this logic. Four public additions:sha1_of_source(source: bytes | Path | BinaryIO) -> strininfrahub_sdk.file_handler— streaming SHA-1 helper (64 KiB chunks; rewindsBinaryIOto original position). Canonical import path:from infrahub_sdk.file_handler import sha1_of_source.matches_local_checksum(source) -> bool— atomic compare against the node's server-storedchecksumattribute. No network, no mutation.upload_if_changed(source, name=None) -> UploadResult— composes compare +upload_from_*+save(). Returns a frozenUploadResult(uploaded, checksum)dataclass. Skips the transfer when the local SHA-1 matches.download_file(..., skip_if_unchanged=True)— short-circuits the download whendestexists on disk with a matching SHA-1. Returns0bytes written on skip.Both async and sync twins. Full async↔sync symmetry.
Motivation
Both opsmill/nornir-infrahub#71 and opsmill/infrahub-ansible#317 shipped near-identical
hashlib.sha1(...).hexdigest()+ compare logic. Centralising it in the SDK gives one source of truth and — because nornir-infrahub already extended idempotency to the download side — brings that capability to the Ansible collection too.Usage — nornir-infrahub example
Concretely, the nornir-infrahub tasks in
nornir_infrahub/plugins/tasks/file_object.pycollapse from ~30 lines of hand-rolled idempotency into a single method call per operation.Upload
Before (today, on nornir-infrahub
main):After (with this PR):
No
_sha1helper, nohashlibimport, no two-branchupload_from_path/upload_from_bytesdispatch — the SDK handles the compare, the staging, and the save in one call. The returnedUploadResult.uploadedmaps directly to Nornir'sResult(changed=...).Download
Before (today):
After:
Two branches collapse into one, the manual SHA-1 compare and file-exists handling move into the SDK, and the
changedsignal falls out of the return value — no more tracking it imperatively across branches.Notes
upload_if_changedcomputessha1_of_source(source)once up front and reuses it for both the compare and the returnedUploadResult.checksum. This is the semantically correct value (the server storessha1(received_bytes), which equals the local digest), and it avoids double-hashing or reading aBinaryIOtwice.download_file(skip_if_unchanged=True)validates the saved-node precondition before the skip-check, so an unsaved node with a coincidentally-matchingchecksum.valuestill raisesValueErrorrather than silently returning0. See thefixcommit in the branch and the accompanying.fixed.mdchangelog fragment.*_FEATURE_NOT_SUPPORTED_MESSAGEconstants were added alongside the existingFILE_DOWNLOAD_FEATURE_NOT_SUPPORTED_MESSAGE, following the same convention.Follow-ups (not in this PR)
Once this lands and a release ships with the new lower bound:
infrahub-sdkfloor and replace local SHA-1 code innornir_infrahub/plugins/tasks/file_object.pywith the new methods (per the usage example above).infrahub-sdkfloor and replace local SHA-1 code inplugins/module_utils/node.py(get_file_object_local_checksum,_update_object_with_file) with the new methods. Also gains download-side idempotency for free by wiringskip_if_unchanged=Trueinto theobject_file_fetchaction.Test plan
InfrahubNodeandInfrahubNodeSync(85 file-related tests acrosstest_file_handler.pyandtest_file_object.py, up from ~41 baseline)ruff checkcleanmypycleanAdded+Fixedfragments validateChangelog fragments
changelog/+idempotent-file-ops.added.md— describes the four new primitiveschangelog/+idempotent-file-ops-unsaved-node.fixed.md— describes the unsaved-node guard indownload_file(skip_if_unchanged=True)