traitlets
traitlets copied to clipboard
Introduce @preprocess decorator?
As part of ipython/ipywidgets#1676, @SylvainCorlay and I were discussing a sort of validator of this sort:
@validate('value)
def validate_value(self, proposal):
if isinstance(proposal, six.text_types):
self.format = 'url'
proposal = proposal.encode('utf-8')
return proposal
The problem with this is that value is a Bytes trait, and validate calls the trait validator before the @validate decorator, so the only way to implement something like this is to declare the trait Any (even though in the message it should always be Bytes.
This could be solved by moving the user-specified validation up the stack, but to me this sort of seems like a separate action from validation it seems like it should go Input->Preprocessing->Validation->Observer.
That said, on the Python side, this could introduce a distinction between the types that can be assigned to a trait vs. the types that can be emitted as a message - I don't see a major problem here (it seems the Unicode type class already does this), but it might be worth introducing a mechanism for documenting which types are allowed as input to the preprocessor.
@pganssle, would this be better solved by a trait specified in this manner Encode("utf-8")?
@rmorshea I don't think I know what that is, I don't see it in the list of traits. Are you proposing a new trait type called Encode that will automatically dispatch like this?
In any case, it doesn't seem like a good idea to solve this by creating special-case traits for all input type -> output type mappings. Consider a trait that might dispatch how a value is set by dispatching on type in a more complicated way:
@preprocess('value')
def preprocess_value(self, proposal):
if isinstance(proposal, URLWrapperType):
value = URLWrapperType.download_content()
elif isinstance(proposal, str):
import os
if os.path.exists(proposal):
with open(proposal, 'rt') as f:
value = f.read()
else:
value = proposal
elif getattr(proposal, 'read', None) is not None:
value = proposal.read()
else:
value = proposal
if isinstance(value, six.text_type):
value = value.decode('utf-8')
return value
Presumably you could write a custom trait like URLorFilePathorStreamorString_ReturnsBytes, or you could just declare the trait as Bytes and use the hook for preprocessing logic (clean, fits in with the existing hooks).
@pganssle I was imagining it as a new trait. I see your point though, and may be able to implement this shortly.
@pganssle https://github.com/ipython/traitlets/pull/466 proposes an Instance.cast method that could be generalized in TraitType and used to capture methods decorated with @cast("my_trait").