traitlets icon indicating copy to clipboard operation
traitlets copied to clipboard

Introduce @preprocess decorator?

Open pganssle opened this issue 8 years ago • 4 comments

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 avatar Aug 27 '17 13:08 pganssle

@pganssle, would this be better solved by a trait specified in this manner Encode("utf-8")?

rmorshea avatar Jan 25 '18 16:01 rmorshea

@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 avatar Jan 25 '18 18:01 pganssle

@pganssle I was imagining it as a new trait. I see your point though, and may be able to implement this shortly.

rmorshea avatar Jan 26 '18 07:01 rmorshea

@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").

rmorshea avatar Feb 06 '18 19:02 rmorshea