schema icon indicating copy to clipboard operation
schema copied to clipboard

Pretty String for __str__

Open rmorshea opened this issue 7 years ago • 11 comments

I'm working on documenting some of my schemas in sphinx however the string representation you get from Schema.__str__ is unusable. It would be nice if __str__ produced a more readable version of the schema - perhaps a shortened version of it?

Note: This could probably leverage the solution to #173.

rmorshea avatar Oct 23 '18 22:10 rmorshea

This is a great idea, agreed.

skorokithakis avatar Oct 23 '18 23:10 skorokithakis

We can leverage https://github.com/keleshev/schema/pull/182 into this:

from schema import Model

class User(Model):
    name = str
    bio = str

s = Schema(User)
s.validate({"name": "Ryan", "bio": "my-info"})

Which would behave identically to:


User = {"name": str, "bio": str}
s = Schema(User, name=“User”)
s.validate({"name": "Ryan", "bio": "my-info"})

By defining classes which can be converted into schemas anything worth documented can be documented like any normal class:

class User(Model):
    """A user schema"""

    name = str
    """the username"""

    bio = str
    """The user's background information"""

rmorshea avatar Dec 28 '18 20:12 rmorshea

That's an interesting solution, although I'm not sure I like the API. My two thoughts are:

  • We might want to look into using type annotations rather than assigning a type, if possible (although that might only work on 3.4+, hmm).
  • I'm not in love with the docstrings for documenting variables, can you really access those like that?
  • Bonus thought: Model.to_schema() class method.

What do you think?

skorokithakis avatar Dec 28 '18 23:12 skorokithakis

@skorokithakis the docstrings under the attributes is supported by sphinx to my knowlwedge.

Type annotations would be nice (there might be a better api assuming that). It depends on whether you want to be 2.7 compatible.

In terms of the API it could also be User.validate(value) or equivalently User(value). In general though, classes seem like the most logical way to document this stuff.

rmorshea avatar Dec 29 '18 01:12 rmorshea

the docstrings under the attributes is supported by sphinx to my knowlwedge.

Very interesting, I didn't know that, thanks.

It depends on whether you want to be 2.7 compatible.

Since 2.7 is getting EOLed next year, I'm less and less inclined to be compatible with it, in general.

In terms of the API it could also be User.validate(value) or equivalently User(value).

I think we'd still need to access the underlying schema, as it would be useful to include it in a higher-level schema as a subvalidator. Would Use work with this? I.e. could I define a field that was something like name = str | Use(lambda x: len(x) > 10) or something like that?

skorokithakis avatar Jan 09 '19 00:01 skorokithakis

We could definitely leverage type information while processing the Model. For example this model:

class User(Model):
    name : str = lambda x: len(x) < 10
    """The username."""

could translate to the schema

Schema({
 "name": And(str, lambda x: len(x) < 10)
})

You could take it a step further and allow people to define methods:

class User(Model):

    def name(self, value: str) -> str:
        """The username."""
        if not len(value) < 10:
            raise ValueError("Username must be fewer than 10 characters.")
        return value

    def bio(self, value: str = "Default bio...") -> str:
        """User's background information."""
        if len(value) > 500:
            raise ValueError("Bio must be less than 500 characters.")
        return value

I'm not really sure how self would be used here, but I could imagine that accessing self.bio would trigger bio() to be evaluated - this would allow users to cross reference keys on the same schema as in #164. This would require a clever metaclass, but I think its doable.

And this could all be backward compatible since all we'd have to do is check for the presence of __annotations__ in older version of Python.

rmorshea avatar Jan 09 '19 00:01 rmorshea

This sounds very good. I'm a bit worried that we're doing more API design than the medium of issues warrants, and that we're approaching other libraries' turf.

In general, I prefer a more functional approach to a declarative one, but that just means that my preferred model would look like:

class User(Model):
    name = And(str, lambda x: len(x) > 10, Use(lambda x: x.lower())

Etc. That might not be so bad, and we could certainly override that by defining a method, but I wonder if we're losing the flexibility of the library in that case (e.g. how are we going to have optional fields?).

skorokithakis avatar Jan 10 '19 23:01 skorokithakis

@skorokithakis good point. I took a moment yesterday to try and implement the API I proposed and it wasn't as straightforward as I wanted it to be. I think limiting it to attribute definitions for now is appropriate. Adding method support would be a feature add that could be done if enough people want it.

rmorshea avatar Jan 10 '19 23:01 rmorshea

Also I learned that sphinx supports "doc comments" which for short descriptions make it even cleaner:

class User(Model):
    name = str #: the username

rmorshea avatar Jan 10 '19 23:01 rmorshea

@skorokithakis I began implementing the Model class in my work and found it to be pretty clunky especially when things started to get complicated. It might be simpler to have __repr__ return the verbose string representation and __str__ return something that cuts off after a particular string length:

Schema({'a': int, 'b': str, 'c': ...})

That might be a reasonable solution for now at least since the more immediate problem is that for complicated schemas the string representation might take up the entire page before you ever got to its actual docstring.

rmorshea avatar Jan 19 '19 18:01 rmorshea

I agree, I think there's a lot of complexity here that isn't apparent at a first glance. I will defer to whatever way you want to handle this.

skorokithakis avatar Jan 20 '19 16:01 skorokithakis