You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The two layouts have different invariants (per-key files vs. one .h5 per prefix), different lock granularity (per-key .lock vs.
per-bag .lock), and different list() semantics (whole-file vs.
walk-and-open). Any new backend-level method (e.g. a future size, atime, or migration hook) will require the same fork, and the two
layout invariants have to be re-learned from scratch each time.
PR #748's refix() and consolidate() already added a third
implicit layout concern (mixed-root consistency checks), and future
storage-level features would compound the branching.
Suggested refactor
Introduce a small _Layout strategy resolved in __post_init__ from prefix_length:
refix() / consolidate() construct the other layout explicitly
via dataclasses.replace(self, prefix_length=n), which already
rebuilds _layout in __post_init__.
Alternative shape: two sibling backend classes PerKeyBagOfHoldingH5File / MultiBagOfHoldingH5File selected by a
small module-level factory. Trade-off: cleaner classes, but the
config round-trip (storage_to_config) has to preserve the choice.
Pros
Eliminates five parallel if self.prefix_length == 0 branches.
Layout invariants (which files may appear in root, which lock file
is per-key vs. per-bag, what list() walks) become properties of one
object rather than being scattered.
Makes future features — atomic reindexing between layouts, a third
layout (nested prefixes?), migrations — extensions of the strategy
rather than a wider elif chain.
One more class hierarchy to learn; strategy pattern for a two-member
enumeration can feel over-engineered until the third case arrives.
Frozen-dataclass friendliness: the strategy object must be
reconstructible from prefix_length alone, so the config round-trip
stays a single field. Not a real constraint but worth calling out.
Blast radius touches every disk-op method — well covered by the
existing storage/test_bagofholding*.py suite and the smoke/test_optional_deps.py bagofholding round-trip, but the
parametrised value_storage/call_storage fixtures sweep both
layouts and will catch regressions.
No new external dependencies.
Complexity / maintainability
Medium. The refactor is behaviour-preserving, but the surface is
recent (PRs #746, #748, #749) and still evolving — worth doing before
a third layout variant lands, not after.
Location
src/fleche/storage/bagofholding_file.py:229-298—_path,_lock_path,_contains,_evict,listall switch onself.prefix_length == 0.Problem
Since PR #746 (closes #556),
BagOfHoldingH5FileBackendhosts twostructurally different disk layouts under one class:
prefix_length == 0: per-key files (root/{full_key}), inheritedbehaviour via
super()calls.prefix_length >= 1: multi-bag layout (root/{prefix}.h5, siblinggroups named by full key via bagofholding 0.1.12's
file.h5/grouppath addressing).
The branch on
prefix_length == 0is re-spelled in five methods:The two layouts have different invariants (per-key files vs. one
.h5per prefix), different lock granularity (per-key.lockvs.per-bag
.lock), and differentlist()semantics (whole-file vs.walk-and-open). Any new backend-level method (e.g. a future
size,atime, or migration hook) will require the same fork, and the twolayout invariants have to be re-learned from scratch each time.
PR #748's
refix()andconsolidate()already added a thirdimplicit layout concern (mixed-root consistency checks), and future
storage-level features would compound the branching.
Suggested refactor
Introduce a small
_Layoutstrategy resolved in__post_init__fromprefix_length:The five backend methods become straight delegators:
refix()/consolidate()construct the other layout explicitlyvia
dataclasses.replace(self, prefix_length=n), which alreadyrebuilds
_layoutin__post_init__.Alternative shape: two sibling backend classes
PerKeyBagOfHoldingH5File/MultiBagOfHoldingH5Fileselected by asmall module-level factory. Trade-off: cleaner classes, but the
config round-trip (
storage_to_config) has to preserve the choice.Pros
if self.prefix_length == 0branches.root, which lock fileis per-key vs. per-bag, what
list()walks) become properties of oneobject rather than being scattered.
layout (nested prefixes?), migrations — extensions of the strategy
rather than a wider
elifchain.check_consistency/refix/consolidateAPI: the layout object naturally owns the "which files are mine"
predicate, which right now leaks into
list()andrefix().Cons / risks
enumeration can feel over-engineered until the third case arrives.
reconstructible from
prefix_lengthalone, so the config round-tripstays a single field. Not a real constraint but worth calling out.
existing
storage/test_bagofholding*.pysuite and thesmoke/test_optional_deps.pybagofholding round-trip, but theparametrised
value_storage/call_storagefixtures sweep bothlayouts and will catch regressions.
Complexity / maintainability
Medium. The refactor is behaviour-preserving, but the surface is
recent (PRs #746, #748, #749) and still evolving — worth doing before
a third layout variant lands, not after.
Out of scope
check_consistency/refix/consolidateAPI itself(PR feat: refix() re-shards bagofholding prefix length; validate layout at init #748) — stays as-is; only the internals move behind the strategy.
2).Filed from a codebase-wide refactoring scan; not yet implemented.