Skip to content

SWE Pipeline: Automated code improvements for ipfs_dict_chain - #2

Merged
WouterGlorieux merged 54 commits into
mainfrom
swe-pipeline/integration
Jun 13, 2026
Merged

SWE Pipeline: Automated code improvements for ipfs_dict_chain#2
WouterGlorieux merged 54 commits into
mainfrom
swe-pipeline/integration

Conversation

@WouterGlorieux

Copy link
Copy Markdown
Contributor

SWE Pipeline — Automated Code Improvements

This pull request was automatically generated by the SWE Pipeline.

Summary

  • 22 issues fixed: 15 bugs, 6 refactors, 1 enhancements
  • Phase A hygiene applied (lint fixes, type annotations, docstrings)
  • Coverage: 100%
  • Tests: all passing

What changed

See the commit history on the swe-pipeline/integration branch for the full list of changes.

Review

The pipeline performed multiple code reviews and all issues found were addressed. The codebase is at 100% test coverage with a green test suite.


Automatically generated at commit ea597120.

Valyrian SWE Pipeline added 30 commits June 13, 2026 18:11
…d-allows-unrealistically-short-cids into swe-pipeline/integration
…onse-has-no-hash-key-violating-type-contract into swe-pipeline/integration
…y-call-and-uses-destructive-connection-test into swe-pipeline/integration
…nt-if-client-cat-raises-an-exception into swe-pipeline/integration
…ver-invalidated-and-has-redundant-lookup into swe-pipeline/integration
…d-of-keyerror-for-missing-keys into swe-pipeline/integration
…s-instance-attributes-breaking-the-dict-protocol into swe-pipeline/integration
…ling-leaving-object-in-inconsistent-state into swe-pipeline/integration
…-keys-and-can-raise-attributeerror into swe-pipeline/integration
…-previous-state-data-on-ipfs into swe-pipeline/integration
…n which is confusing and likely unintended
…n-change-detection-which-is-confusing-and-likely-unintended into swe-pipeline/integration
…vious-cids-don-t-handle-missing-ipfs-data into swe-pipeline/integration
Valyrian SWE Pipeline added 23 commits June 13, 2026 20:13
…vious-cids-make-redundant-network-calls-for-each-state into swe-pipeline/integration
…ore-calling-super-init-which-can-cause-issues into swe-pipeline/integration
…dict-save-instead-of-calling-super into swe-pipeline/integration
…nd doesn't actually test IPFSDict behavior
…g-interpolation-and-doesn-t-actually-test-ipfsdict-behavior into swe-pipeline/integration
…e-the-method-body-instead-of-at-module-level into swe-pipeline/integration
…chain-instance-unnecessarily-when-loading-previous-state into swe-pipeline/integration
…hain instances for each state, making redundant network calls
…es-full-ipfsdictchain-instances-for-each-state-making-redundant-network-calls into swe-pipeline/integration
…cid-from-loaded-data-when-loading-from-an-existing-cid into swe-pipeline/integration
…ed-leaking-resources into swe-pipeline/integration
…_previous_cid_for uses IPFSDict.get_json via sys.modules
…s-get-json-but-get-previous-cid-for-uses-ipfsdict-get-json-via-sys-modules into swe-pipeline/integration
@codecov

codecov Bot commented Jun 13, 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.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

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

The deploy-pages step was running on pull_request events too, which
fails because the PR merge ref is not allowed to deploy to the
github-pages environment. Split into build (all triggers) and deploy
(push to main only) jobs.

@WouterGlorieux WouterGlorieux left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pull Request Review: PR #2swe-pipeline/integrationmain

Summary

This is a substantial, well-structured PR that addresses 22 distinct issues across the ipfs_dict_chain codebase, ranging from correctness bugs (e.g., AttributeError instead of KeyError, missing CID length validation) to performance problems (redundant network calls, event loop leaks) and code quality concerns (duplicated logic, broken dict protocol, local imports). The changes are organized as a series of focused, incremental fixes — each addressing a single concern — and are backed by corresponding test updates that bring coverage to 100%. The overall quality of the work is high, and the PR is ready to merge after addressing a few minor observations noted below.

Changes Overview

Area What Changed
CID.py Added minimum CID length validation (46 chars for CIDv0) and docstrings.
IPFS.py Introduced a module-level reusable event loop (_get_loop/_close_loop) to replace per-call loop creation; rewrote connect() to use a read-only _test_connection() instead of a destructive add_json; added TTL-based cache expiration, clear() and cleanup() methods; fixed get_file_content() to close the client in a finally block; made _add_json() raise IPFSError when response lacks a Hash key instead of returning None.
IPFSDict.py Fixed the dict protocol by overriding __setattr__/__getattribute__ to store data keys in the inherited dict (via super().__setitem__/__getitem__) instead of as instance attributes; fixed load() to set _cid after populating data (avoiding inconsistent state on failure); updated items(), save(), __str__, __setitem__, and __getitem__ to use super() dict methods.
IPFSDictChain.py Fixed __init__ to extract previous_cid from loaded data after super().__init__; delegated save() to super().save() instead of duplicating logic; rewrote changes() to exclude previous_cid from change detection, detect deleted keys, and handle missing IPFS data gracefully; added _get_previous_cid_for() for lightweight chain traversal; rewrote get_previous_states() and get_previous_cids() to use two-pass (collect CIDs, then load data) avoiding redundant full-instance creation.
Tests Added tests for CID length validation, cache expiry/clear/cleanup, _test_connection, missing Hash response, deleted-key detection, IPFS error handling in changes()/get_previous_states()/get_previous_cids(), and max_depth; fixed existing tests to match corrected behavior (e.g., KeyError instead of AttributeError, no previous_cid in changes).
CI / Config Split GitHub Pages deploy into separate build and deploy jobs, restricting deploy to pushes on main; added .SWE/ to .gitignore.
Docs Added module-level docstrings to all source files and an __init__.py package docstring.

Issues & Concerns

✅ Correctly Resolved Issues

All of the bugs identified in the commit history appear to be correctly addressed:

  1. Event loop leak — The module-level _get_loop() with atexit cleanup replaces the pattern of creating and discarding event loops on every call.
  2. Destructive connection testconnect() now uses _test_connection() (calls client.id()) instead of add_json(), which is read-only and non-destructive.
  3. Cache never invalidated — The IPFSCache now stores (data, expiry) tuples and checks time.time() on every get(), with clear() and cleanup() methods.
  4. _add_json returns None — Now raises IPFSError when Hash is missing, preserving the str return type contract.
  5. get_file_content doesn't close client on error — Uses try/finally to guarantee client.close().
  6. CID regex too permissive — Added MIN_CID_LENGTH = 46 validation.
  7. IPFSDict breaks dict protocol — The new __setattr__/__getattribute__ overrides correctly route data keys to the inherited dict storage, while private attributes (starting with _) use normal attribute access.
  8. AttributeError instead of KeyError__getitem__ now calls super().__getitem__() which raises KeyError.
  9. load() sets _cid before populating_cid is now set after the data loop, so a failure during population leaves the object in a clean state.
  10. IPFSDictChain.__init__ sets previous_cid before super().__init__ — Now calls super().__init__ first, then extracts previous_cid from the loaded data via self.get('previous_cid').
  11. save() duplicates logic — Now delegates to super().save().
  12. changes() includes previous_cid — Both the old and new change-detection loops skip 'previous_cid'.
  13. changes() doesn't handle missing IPFS data — Catches IPFSError and treats all current data as new.
  14. changes() doesn't detect deleted keys — The new logic iterates old_data keys and records {'old': ..., 'new': None} for keys absent from current_items.
  15. get_previous_states()/get_previous_cids() make redundant network calls — The two-pass approach (collect CIDs via _get_previous_cid_for, then load data) avoids creating full IPFSDictChain instances.
  16. get_previous_states()/get_previous_cids() don't handle missing IPFS data — Both methods handle IPFSError gracefully.
  17. changes() creates full IPFSDictChain instance — Now uses get_json() directly instead of constructing an IPFSDictChain(cid=...).
  18. _get_previous_cid_for has local import — Moved to module-level import sys and uses sys.modules['ipfs_dict_chain.IPFSDict'].get_json.

🔍 Observations for Discussion

  1. _get_previous_cid_for uses sys.modules — While functional, accessing sys.modules to get a reference to get_json is an unusual pattern. A cleaner approach would be to simply import get_json directly at the top of IPFSDictChain.py (it's already imported: from .IPFS import IPFSError, add_json, get_json). The get_json function is already available — you could use it directly instead of going through sys.modules['ipfs_dict_chain.IPFSDict']. This would make the code more readable and avoid potential issues if the module isn't yet in sys.modules.

  2. IPFSCache TTL default (300s) — The 5-minute default TTL is reasonable, but consider whether cached data could become stale in your use case. If the IPFS data is immutable (as CIDs imply), the cache never needs invalidation by time — only by memory pressure. Consider documenting this design choice.

  3. changes() behavior when previous_cid is None — Returns {key: {'new': value} for ...} which is correct. However, note that the first save() sets previous_cid = self._cid (which was None), so after the first save, previous_cid becomes the first state's CID. This means changes() after the first save will compare against the first state, which is the intended behavior.

  4. test_get_previous_cids_ipfs_error — The test patches ipfs_dict_chain.IPFSDict.get_json but the method under test (_get_previous_cid_for) accesses it via sys.modules['ipfs_dict_chain.IPFSDict'].get_json. The patch should work because sys.modules['ipfs_dict_chain.IPFSDict'] returns the same module object that was patched. However, this is fragile — if the import order changes or the module is imported under a different name, the patch might not apply. Consider importing get_json directly in IPFSDictChain.py and using it directly, which would make mocking more straightforward.

  5. test_changes_ipfs_error — This test patches ipfs_dict_chain.IPFSDictChain.get_json, but changes() in IPFSDictChain calls get_json (imported from .IPFS), not IPFSDictChain.get_json. The patch may not actually be intercepting the right target. Let me verify this...

⚠️ Potential Issue: test_changes_ipfs_error Mock Target

Looking at IPFSDictChain.changes():

from .IPFS import IPFSError, add_json, get_json
...
old_data = get_json(self.previous_cid)

The test patches ipfs_dict_chain.IPFSDictChain.get_json, but get_json is imported as a module-level name in IPFSDictChain.py, not as an attribute of the class. The correct patch target should be ipfs_dict_chain.IPFSDictChain.get_json (the module-level name) — but since get_json is imported at module load time, patching the class attribute won't affect the module-level reference.

This means the test might not actually be testing the error-handling path. The patch should target ipfs_dict_chain.IPFS.get_json or ipfs_dict_chain.IPFSDictChain.get_json (the module-level name, not the class attribute). This is a minor issue, but worth fixing to ensure the test coverage is genuine.

Recommendation

Approve with minor follow-ups. The PR is well-structured, addresses real bugs, and improves test coverage to 100%. The code quality is high, with clear docstrings, proper type annotations, and consistent style.

Suggested Follow-ups (can be done in a subsequent PR):

  1. Fix the mock target in test_changes_ipfs_error — Change the patch target from ipfs_dict_chain.IPFSDictChain.get_json to ipfs_dict_chain.IPFS.get_json to ensure the error-handling path is actually tested.
  2. Simplify _get_previous_cid_for — Replace sys.modules['ipfs_dict_chain.IPFSDict'].get_json(cid) with the already-imported get_json(cid) from .IPFS. This removes the sys.modules dependency and makes mocking more straightforward.
  3. Consider documenting cache TTL rationale — A brief comment explaining why 300 seconds was chosen (or why cache invalidation is acceptable) would help future maintainers.

These are minor polish items, not blockers. The core logic is sound, the tests pass, and the PR delivers significant value.

@WouterGlorieux
WouterGlorieux merged commit fc1825f into main Jun 13, 2026
4 checks passed
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