jcrist / msgspec

A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML
https://jcristharif.com/msgspec/
BSD 3-Clause "New" or "Revised" License
2.23k stars 64 forks source link

Should we integrate with `annotated-types`? #177

Open jcrist opened 2 years ago

jcrist commented 2 years ago

Follow up from #176 and #154.

We currently support adding constraints to types by annotating them with msgspec.Meta. annotated-types provides it's own annotations describing the same constraints - we could potentially integrate with them in 2 ways:

I'm not convinced there's a real use case for either of these integrations, and would want to hear from multiple real users before expending any effort supporting this. See https://github.com/jcrist/msgspec/pull/176#issuecomment-1229265714 for a longer write up of my thoughts.

adriangb commented 2 years ago

So I'm obviously somewhat biased here but I do want to share some thoughts on the pros:

rjdbcm commented 1 year ago

In my personal projects I have been using a fork with iter implemented (via crude hack but seems to work alright)

I have seen pyparsing used to parse a constraint string minilang into actual annotated types as in AndreaCensi/PyContracts. I would imagine implementing a stack-based language is beyond the scope of msgspec but I wanted to mention it because I had fiddled with the idea of the minilang as an actual C extension but found it a bit reinventing the wheel-y

akotlar commented 7 months ago

I am happy with the current system, if it works with static typing. If it doesn't, I would love for it to either be updated to do so, or if the performance hit weren't large, to support annotated-types. Here is my test case, I may be doing something wrong (constraint documentation specifies that the current system is supported by mypy):

from typing import Annotated

from msgspec import Struct, Meta

UnitFloatValidator = Annotated[
    float, Meta(ge=0.0, le=1.0)
]

Length = Annotated[list[int], Meta(max_length=3)]

class ProbabilityInterval(Struct):
    """Represent an interval of probabilities."""

    lower_bound: UnitFloatValidator
    upper_bound: UnitFloatValidator

class Length3String(Struct):
    """ A string of length 3"""

    some_string: Length

if __name__ == "__main__":

    # These don't fail, but should
    print(ProbabilityInterval(lower_bound=-1, upper_bound=1.1))
    print(Length3String(some_string=[1,2,3,4]))

    # Arguably this is ok not to fail, because Python ints and floats are compatible
    print(ProbabilityInterval(lower_bound=int(1), upper_bound=1.0))

    # These fail
    print(Length3String(some_string=["a", "b", "c"]))
    print(ProbabilityInterval(lower_bound=[1], upper_bound=1.1))

The cases that don't fail are missed by both mypy and pyright, in my hands at least.

Grateful for all the work you've done on this btw, and your willingness to engage the community.

jcrist commented 7 months ago

To my knowledge no static analysis tool (mypy, pyright, or otherwise) currently has support for doing anything with annotations added via an Annotated[...] (annotated-types or otherwise). One of the arguments in the PEP proposing typing.Annotated was that type checkers could ignore these extra annotations and have them still be available at runtime.

To support these kind of constructs in mypy you'd need to write a custom mypy plugin. AFAIK there's no way to add a similar plugin to pyright.

akotlar commented 7 months ago

Ah I misunderstood annotated-types.

Iā€™m going to give this solution a try: https://stackoverflow.com/questions/32787411/how-to-declare-python-constraint-on-generic-type-to-support-lt

adriangb commented 3 months ago

Hi @jcrist a followup discussion here: https://peps.python.org/pep-0746/ and https://discuss.python.org/t/pep-746-typedmetadata-for-type-checking-of-pep-593-annotated/53834.

Even if msgspec doesn't use annotated-types I'd still really appreciate your input on that PEP since msgspec can adopt it even without annotated-types. In particular on the signature of the method (type vs. value). Thanks!

tlambert03 commented 2 months ago

came looking to see whether there had been discussion of using annotated-types here, and found this. Just wanted to put in my support for msgspec supporting annotated-types annotations. Wouldn't mean depending on it, just basically "honoring" it such that Annotated[int, annotated_types.Ge(0)] had the same semantics as Annotated[int, msgspec.Meta(ge=0)], etc...

I'm someone who definitely likes to go back and forth between msgspec and pydantic, and also generally likes to support concepts that are re-usable and shareable. annotated-types is a nice initiative for merely marking the concept of a constraint, in a simple-to-depend-on package, while imposing almost no assumptions about implementation details. šŸ‘

PEP 746 certainly looks nice as well :)