>
> Probably the best you can do is write your own recursive
> isinstance-lookalike that has the behavior you need for validating JSON.
>

This is already done in the form of pydantic
<https://pydantic-docs.helpmanual.io/> which is the backbone of one of the
fastest growing python web frameworks, fastAPI
<https://fastapi.tiangolo.com/>. (Admission: I'm one of the authors of
pydantic)

Pydantic doesn't yet support TypedDict, but there's an issue
<https://github.com/samuelcolvin/pydantic/issues/760> to implement it.

---

More generally, I think runtime type checking was never the intention for
type hints, in fact I think Guido specifically said somewhere that runtime
type checking was not an intended use case (am I right?). However
pydantic and a few other projects seem to me to have been very successful
in using them for that anyway. I'd love it if python's developers could be
a bit more supportive to runtime type inspection in future.

Match statements look very interesting, thanks for alerting me to the PEP.

Samuel

--

Samuel Colvin


On Sun, 22 Nov 2020 at 18:17, Guido van Rossum <gu...@python.org> wrote:

> You nerd-sniped me there. :-)
>
> I think this is perhaps too complicated to attempt to make it all work.
>
> - We intentionally don't support things like `isinstance(x, List[str])`
> because that would require checking all the items with `isinstance(item,
> str)`, and that seems a speed trap. Reverting this decision would be hard
> work.
>
> - `isinstance(3, float)` returns False, because at runtime int and float
> are distinct types, even though static type checkers regard int as a
> subtype of float (with some caveats, at least in mypy's case, but that's
> basically how it works). Changing this behavior at runtime will break tons
> of existing code that tries to distinguish ints from floats but checks for
> float first (since everyone "knows" they are distinct types), so this would
> be even harder to get through than the previous bullet.
>
> - Currently static type checkers don't allow defining any methods (even
> class methods) in TypedDict instances, so you can't manually an
> `__instancecheck__` method to a TypedDict class. (But we could add it to
> typing.TypedDict of course.)
>
> - There's also the issue that bool is a subtype of int. Again, very hard
> to change that without breaking code.
>
> - Some static type checkers (mypy, but not pyright -- haven't tried others
> yet) disallow `isinstance(x, SomeTypedDict)` -- presumably because they are
> aware of the problems above.
>
> Probably the best you can do is write your own recursive
> isinstance-lookalike that has the behavior you need for validating JSON.
> But then you're no better off than any other JSON validation framework (and
> I expect there already to be some that introspect TypedDict subclasses).
>
> I suppose you could come up with some mechanism whereby you can create a
> parallel hierarchy of classes that do support isinstance(), so you could
> write e.g.
>
> ICircle = make_interface(Circle)
> IRect = make_interface(Rect)
> # etc.
>
> def draw_shape():
>     match request.json:
>         case ICircle(center, radius): ...
>         case IRect(x, y, width, height): ...
>         ...
>
> but this loses much of the original attractiveness.
>
> --Guido
>
> On Sat, Nov 21, 2020 at 10:46 PM David Foster <davidf...@gmail.com> wrote:
>
>> I am excited about the potential of the new PEP 634-636 "match" statement
>> to
>> match JSON data received by Python web applications. Often this JSON data
>> is
>> in the form of structured dictionaries (TypedDicts) containing Lists and
>> other primitives (str, float, bool, None).
>>
>> PEP 634-636 already contain the ability to match all of those underlying
>> data
>> types except for TypedDicts, so I'd like to explore what it might look
>> like to
>> match a TypedDict...
>>
>> Consider an example web application that wants to provide a service to
>> draw
>> shapes, perhaps on a connected physical billboard.
>>
>> The service has a '/draw_shape' endpoint which takes a JSON object
>> (a Shape TypedDict) describing a shape to draw:
>>
>>      from bottle import HTTPResponse, request, route
>>      from typing import Literal, TypedDict, Union
>>
>>      class Point2D(TypedDict):
>>          x: float
>>          y: float
>>
>>      class Circle(TypedDict):
>>          type: Literal['circle']
>>          center: Point2D  # has a nested TypedDict!
>>          radius: float
>>
>>      class Rect(TypedDict):
>>          type: Literal['rect']
>>          x: float
>>          y: float
>>          width: float
>>          height: float
>>
>>      Shape = Union[Circle, Rect]  # a Tagged Union / Discriminated Union
>>
>>      @route('/draw_shape')
>>      def draw_shape() -> None:
>>          match request.json:  # a Shape?
>>              ...
>>              case _:
>>                  return HTTPResponse(status=400)  # Bad Request
>>
>> Now, what syntax could we have at the ... inside the "match" statement to
>> effectively pull apart a Shape?
>>
>> The current version of PEP 634-636 would require duplicating all the keys
>> and value types that are defined in Shape's underlying Circle and Rect
>> types:
>>
>>          match request.json:  # a Shape?
>>              case {'type': 'circle', 'center': {'x': float(), 'y':
>> float()}, \
>>                      radius: float()} as circle:
>>                  draw_circle(circle)  # type is inferred as Circle
>>              case {'type': 'rect', 'x': float(), 'y': float(), \
>>                      'width': float(), 'height': float()} as rect:
>>                  draw_rect(rect)  # type is inferred as Rect
>>              case _:
>>                  return HTTPResponse(status=400)  # Bad Request
>>
>> Wouldn't it be nicer if we could use class patterns instead?
>>
>>          match request.json:  # a Shape?
>>              case Circle() as circle:
>>                  draw_circle(circle)
>>              case Rect() as rect:
>>                  draw_rect(rect)
>>              case _:
>>                  return HTTPResponse(status=400)  # Bad Request
>>
>> Now that syntax almost works except that Circle and Rect, being
>> TypedDicts,
>> do not support isinstance() checks. PEP 589 ("TypedDict") did not define
>> how
>> such isinstance() checks should work initially because it's somewhat
>> complex
>> to specify. From the PEP:
>>
>>  > In particular, TypedDict type objects cannot be used in isinstance()
>> tests
>>  > such as isinstance(d, Movie). The reason is that there is no existing
>>  > support for checking types of dictionary item values, since
>> isinstance()
>>  > does not work with many PEP 484 types, including common ones like
>> List[str].
>>  > [...]
>>  > This is consistent with how isinstance() is not supported for
>> List[str].
>>
>> Well, what if we (or I) took the time to specify how isinstance() worked
>> with
>> TypedDict? Then the match syntax above with TypedDict as a class pattern
>> would work!
>>
>> Refining the example above even further, it would be nice if we didn't
>> have to
>> enumerate all the different types of Shapes directly in the
>> match-statement.
>> What if we could match on a Shape directly?
>>
>>          match request.json:  # a Shape?
>>              case Shape() as shape:
>>                  draw_shape(shape)
>>              case _:
>>                  return HTTPResponse(status=400)  # Bad Request
>>
>> Now for that syntax to work it must be possible for an isinstance() check
>> to
>> work on a Shape, which is defined to be a Union[Circle, Rect], and
>> isinstance()
>> checks also aren't currently defined for Union types. So it would be
>> useful
>> to define isinstance() for Union types as well.
>>
>> Of course that match-statement is now simple enough to just be rewriten
>> as an
>> if-statement:
>>
>>          if isinstance(shape := request.json, Shape):
>>              draw_shape(shape)
>>          else:
>>              return HTTPResponse(status=400)  # Bad Request
>>
>> Now *that* is a wonderfully short bit of parsing code that results in
>> well-typed objects as output. 🎉
>>
>> So to summarize, I believe it's possible to support really powerful
>> matching
>> on JSON objects if we just define how isinstance() should work with a
>> handful
>> of new types.
>>
>> In particular the above example would work if isinstance() was defined
>> for:
>>      * Union[T1, T2], Optional[T]
>>      * T extends TypedDict
>>      * Literal['foo']
>>
>> For arbitrary JSON beyond the example, we'd also want to support
>> isinstance() for:
>>      * List[T]
>>
>> We already support isinstance() for the other JSON primitive types:
>>      * str
>>      * float
>>      * bool
>>      * type(None)
>>
>> So what do folks think? If I were to start writing a PEP to extend
>> isinstance()
>> to cover at least the above cases, would that be welcome?
>>
>> --
>> David Foster | Seattle, WA, USA
>> Contributor to TypedDict support for mypy
>> _______________________________________________
>> Python-ideas mailing list -- python-ideas@python.org
>> To unsubscribe send an email to python-ideas-le...@python.org
>> https://mail.python.org/mailman3/lists/python-ideas.python.org/
>> Message archived at
>> https://mail.python.org/archives/list/python-ideas@python.org/message/Y2EJEZXYRKCXH7SP5MDF3PT2TYIB7SJS/
>> Code of Conduct: http://python.org/psf/codeofconduct/
>>
>
>
> --
> --Guido van Rossum (python.org/~guido)
> *Pronouns: he/him **(why is my pronoun here?)*
> <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-change-the-world/>
> _______________________________________________
> Python-ideas mailing list -- python-ideas@python.org
> To unsubscribe send an email to python-ideas-le...@python.org
> https://mail.python.org/mailman3/lists/python-ideas.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-ideas@python.org/message/AZ2JBLZGMA4ZIDTTC5J3F55XZTV43HOS/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/ME3PWLDBYLQBA7KQJF44NKDP6NYVE3S7/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to