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
When a store is wrapped with a key transform (KeyCodecs.prefixed, wrap_kvs(id_of_key=...), Pipe of those), the wrapper's __getitem__/__setitem__ correctly map the key — but any other method delegates to the inner store with the outer, unmapped key. dol.base.Store.__getattr__ returns the bound leaf method, so the leaf receives a key it was never meant to see.
For a backend adapter this is severe, because the interesting capabilities are exactly the non-dunder methods.
Repro
fromdolimportKeyCodecsclassWithUrl(dict):
defurl_for(self, k): returnf"https://x/{k}"w=KeyCodecs.prefixed('a/')(WithUrl)({'a/b': 1})
w['b'] # -> 1 correct, prefix appliedw.url_for('b') # -> 'https://x/b' WRONG: should be 'https://x/a/b'
Measured on a realistic adapter (a store scoped to logs/, in a bucket that also holds logs2/leak and a root a.txt):
method
through the wrap
consequence
url_for('a.txt')
https://…/a.txt
signs a URL for the wrong object
sub('x/')
store over the rootx/
scope escape
handle('a.txt')
handle on root a.txt
wrong object
info('a.txt')
KeyError for a key that is present
manufactured "absent"
prefixes()
leaks the sibling prefix the scope exists to hide
delete_many(['a.txt'])
root a.txt destroyed
silent destruction of the wrong object
Nothing raises, and isinstance(w, SupportsUrlFor) stays True — a @runtime_checkable Protocol checks method presence only, so it cannot detect the breakage. (It is also wrap-dependent: since 3.12 isinstance uses getattr_static, which sees a class-wrapped capability but not an instance-wrapped one.)
The existing answer, and why it's easy to get wrong
dol.wrapped_self (shipped in 0.3.58) is the fix, but the natural formulation fails silently:
# inside a delegated method, `self` IS the unwrapped leaf (issue #18)inner_most_key(self, k) # -> None <- silentinner_most_key(wrapped_self(self), k) # -> 'a/b' correct
inner_most_key(self, k) returns None because store_trans_path yields nothing when the store lacks _id_of_key, and last_element returns None. So the URL becomes https://…/None with no exception.
Consider a declarative key-mapped delegation helper, so an adapter can say "these methods take a key as their first argument, map it on the way in" rather than each method remembering:
Every *dol adapter that adds a keyed capability method has this latent bug; a declarative form fixes the family at once. Related: inner_most_key(self, k) returning None rather than raising is itself worth changing — a silent None key is a bug amplifier.
Summary
When a store is wrapped with a key transform (
KeyCodecs.prefixed,wrap_kvs(id_of_key=...),Pipeof those), the wrapper's__getitem__/__setitem__correctly map the key — but any other method delegates to the inner store with the outer, unmapped key.dol.base.Store.__getattr__returns the bound leaf method, so the leaf receives a key it was never meant to see.For a backend adapter this is severe, because the interesting capabilities are exactly the non-dunder methods.
Repro
Measured on a realistic adapter (a store scoped to
logs/, in a bucket that also holdslogs2/leakand a roota.txt):url_for('a.txt')https://…/a.txtsub('x/')x/handle('a.txt')a.txtinfo('a.txt')KeyErrorfor a key that is presentprefixes()delete_many(['a.txt'])a.txtdestroyedNothing raises, and
isinstance(w, SupportsUrlFor)staysTrue— a@runtime_checkableProtocol checks method presence only, so it cannot detect the breakage. (It is also wrap-dependent: since 3.12isinstanceusesgetattr_static, which sees a class-wrapped capability but not an instance-wrapped one.)The existing answer, and why it's easy to get wrong
dol.wrapped_self(shipped in 0.3.58) is the fix, but the natural formulation fails silently:inner_most_key(self, k)returnsNonebecausestore_trans_pathyields nothing when the store lacks_id_of_key, andlast_elementreturnsNone. So the URL becomeshttps://…/Nonewith no exception.Ask
Two things, either of which would help a lot:
wrapped_selfas the answer for capability methods on wrapped stores, with theinner_most_key(wrapped_self(self), k)form spelled out — it is currently discoverable only if you already know issue wrap_kvs will wrap the instance but self of instance is not wrapped #18 exists.Every
*doladapter that adds a keyed capability method has this latent bug; a declarative form fixes the family at once. Related:inner_most_key(self, k)returningNonerather than raising is itself worth changing — a silentNonekey is a bug amplifier.