Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

trycast parses JSON-like values whose shape is defined by TypedDicts and other standard Python type hints.

License

NotificationsYou must be signed in to change notification settings

davidfstr/trycast

Repository files navigation

Trycast helps parses JSON-like values whose shape is defined bytyped dictionaries(TypedDicts) and other standard Python type hints.

You can use thetrycast(),checkcast(), orisassignable() functions belowfor parsing:

trycast()

Here is an example of parsing aPoint2D object defined as aTypedDictusingtrycast():

frombottleimportHTTPResponse,request,route# Bottle is a web frameworkfromtrycastimporttrycastfromtypingimportTypedDictclassPoint2D(TypedDict):x:floaty:floatname:str@route('/draw_point')defdraw_point_endpoint()->HTTPResponse:request_json=request.json# type: objectif (point:=trycast(Point2D,request_json))isNone:returnHTTPResponse(status=400)# Bad Requestdraw_point(point)# type is narrowed to Point2DreturnHTTPResponse(status=200)defdraw_point(point:Point2D)->None:    ...

In this example thetrycast function is asked to parse arequest_jsoninto aPoint2D object, returning the original object (with its type narrowedappropriately) if parsing was successful.

More complex types can be parsed as well, such as theShape in the followingexample, which is a tagged union that can be either aCircle orRect value:

frombottleimportHTTPResponse,request,routefromtrycastimporttrycastfromtypingimportLiteral,TypedDictclassPoint2D(TypedDict):x:floaty:floatclassCircle(TypedDict):type:Literal['circle']center:Point2D# a nested TypedDict!radius:floatclassRect(TypedDict):type:Literal['rect']x:floaty:floatwidth:floatheight:floatShape=Circle|Rect# a Tagged Union!@route('/draw_shape')defdraw_shape_endpoint()->HTTPResponse:request_json=request.json# type: objectif (shape:=trycast(Shape,request_json))isNone:returnHTTPResponse(status=400)# Bad Requestdraw_shape(shape)# type is narrowed to ShapereturnHTTPResponse(status=200)# OK

Important: Current limitations in the mypy typechecker require that youadd an extracast(Optional[Shape], ...) around the call totrycastin the example so that it is accepted by the typechecker without complaining:

shape=cast(Optional[Shape],trycast(Shape,request_json))ifshapeisNone:    ...

These limitations are in the process of being resolved byintroducing TypeForm support to mypy.

checkcast()

checkcast() is similar totrycast() but instead of returningNonewhen parsing fails it raises an exception explaining why and where theparsing failed.

Here is an example of parsing aCircle object usingcheckcast():

>>>fromtypingimportLiteral,TypedDict>>>fromtrycastimportcheckcast>>>>>>classPoint2D(TypedDict):...x:float...y:float...>>>classCircle(TypedDict):...type:Literal['circle']...center:Point2D# a nested TypedDict!...radius:float...>>>checkcast(Circle, {"type":"circle","center": {"x":1},"radius":10})Traceback (mostrecentcalllast):  ...trycast.ValidationError:ExpectedCirclebutfound {'type':'circle','center': {'x':1},'radius':10}Atkey'center':ExpectedPoint2Dbutfound {'x':1}Requiredkey'y'ismissing>>>

ValidationError only spends time generating a message if you try to print itor stringify it, so can be cheaply caught if you only want to use it forcontrol flow purposes.

isassignable()

Here is an example of parsing aShape object defined as a union ofTypedDicts usingisassignable():

classCircle(TypedDict):type:Literal['circle']    ...classRect(TypedDict):type:Literal['rect']    ...Shape=Circle|Rect# a Tagged Union!@route('/draw_shape')defdraw_shape_endpoint()->HTTPResponse:request_json=request.json# type: objectifnotisassignable(request_json,Shape):returnHTTPResponse(status=400)# Bad Requestdraw_shape(request_json)# type is narrowed to ShapereturnHTTPResponse(status=200)# OK

Important: Current limitations in the mypy typechecker prevent theautomatic narrowing of the type ofrequest_json in the above example toShape, so you must add an additionalcast() to narrow the type manually:

ifnotisassignable(request_json,Shape):    ...shape=cast(Shape,request_json)# type is manually narrowed to Shapedraw_shape(shape)

These limitations are in the process of being resolved byintroducing TypeForm support to mypy.

A betterisinstance()

isassignable(value, T) is similar to Python's builtinisinstance() butadditionally supports checking against arbitrary type annotation objectsincluding TypedDicts, Unions, Literals, and many others.

Formally,isassignable(value, T) checks whethervalue is consistent with avariable of typeT (usingPEP 484 statictypechecking rules), but atruntime.

Motivation & Alternatives

Why use trycast?

The trycast module is primarily designed forrecognizing JSON-like structuresthat can be described by Python's typing system. Secondarily, it can be usedforrecognizing arbitrary structures that can be described byPython's typing system.

Please seePhilosophy for more information about how trycastdiffers from similar libraries like pydantic.

Why use TypedDict?

Typed dictionaries are the natural form that JSON data comes in over the wire.They can be trivially serialized and deserialized without any additional logic.For applications that use a lot of JSON data - such as web applications -using typed dictionaries is very convenient for representing data structures.

If you just need a lightweight class structure that doesn't need excellentsupport for JSON-serialization you might consider other alternatives forrepresenting data structures in Python such asdataclasses (recommended),named tuples,attrs, or plain classes.

Installation

python -m pip install trycast

Recommendations while using trycast

  • So thattrycast() can recognize TypedDicts with mixed required andnot-required keys correctly:
    • Use Python 3.9+ if possible.
    • Prefer usingtyping.TypedDict, unless you must use Python 3.8.In Python 3.8 prefertyping_extensions.TypedDict instead.
    • Avoid usingmypy_extensions.TypedDict in general.

Presentations & Videos

A presentation aboutusing trycast to parse JSON was given at the2021 PyCon US Typing Summit:

2021 PyCon US Typing Summit Presentation

A presentation describingtools that use Python type annotations at runtime,including trycast, was given at the 2022 PyCon US Typing Summit:

2022 PyCon US Typing Summit Presentation

Contributing

Pull requests are welcome! ThePython Community Code of Conduct does apply.

You can checkout the code locally using:

git clone git@github.com:davidfstr/trycast.gitcd trycast

Create your local virtual environment to develop in usingPoetry:

poetry shellpoetry install

You can run the existing automated tests in the current version of Python with:

make test

You can also run the tests againstall supported Python versions with:

make testall

See additional development commands by running:

make help

License

MIT

Feature Reference

Typing Features Supported

  • Scalars
    • bool
    • int
    • float
    • None, type(None)
  • Strings
    • str
  • Raw Collections
    • list, List
    • tuple, Tuple
    • Sequence, MutableSequence
    • dict, Dict
    • Mapping, MutableMapping
  • Generic Collections(includingPEP 585)
    • list[T], List[T]
    • tuple[T, ...], Tuple[T, ...]
    • Sequence[T], MutableSequence[T]
    • dict[K, V], Dict[K, V]
    • Mapping[K, V], MutableMapping[K, V]
  • TypedDict
    • typing.TypedDict, typing_extensions.TypedDict(PEP 589)
    • mypy_extensions.TypedDict (when strict=False)
    • –––
    • Required, NotRequired(PEP 655)
    • ReadOnly(PEP 705)
  • Tuples (Heterogeneous)
    • tuple[T1], tuple[T1, T2], tuple[T1, T2, T3], etc
    • Tuple[T1], Tuple[T1, T2], Tuple[T1, T2, T3], etc
  • Unions
    • Union[X, Y]
    • Optional[T]
    • X | Y(PEP 604)
  • Literals
  • Callables
    • Callable
    • Callable[P, R] (where P=[Any]*N and R=Any)
  • NewTypes (when strict=False)
  • Special Types
    • Any
    • Never
    • NoReturn

Type Checkers Supported

Trycast does type check successfully with the following type checkers:

API Reference

trycast API

def trycast(    tp: TypeForm[T]† | TypeFormString[T]‡,    value: object,    /, failure: F = None,    *, strict: bool = True,    eval: bool = True) -> T | F: ...

Ifvalue is in the shape oftp (as accepted by a Python typecheckerconforming to PEP 484 "Type Hints") then returns it, otherwise returnsfailure (which is None by default).

This method logically performs an operation similar to:

return value if isinstance(tp, value) else failure

except that it supports many more types thanisinstance, including:

  • List[T]
  • Dict[K, V]
  • Optional[T]
  • Union[T1, T2, ...]
  • Literal[...]
  • T extends TypedDict

Similar to isinstance(), this method considers every bool value toalso be a valid int value, as consistent with Python typecheckers:

trycast(int, True) -> True
isinstance(True, int) -> True

Note that unlike isinstance(), this method considers every int value toalso be a valid float or complex value, as consistent with Python typecheckers:

trycast(float, 1) -> 1
trycast(complex, 1) -> 1
isinstance(1, float) -> False
isinstance(1, complex) -> False

Note that unlike isinstance(), this method considers every float value toalso be a valid complex value, as consistent with Python typecheckers:

trycast(complex, 1.0) -> 1
isinstance(1.0, complex) -> False

Parameters:

  • strict --
    • If strict=False then this function will additionally acceptmypy_extensions.TypedDict instances and Python 3.8 typing.TypedDictinstances for thetp parameter. Normally these kinds of types arerejected with a TypeNotSupportedError because thesetypes do not preserve enough information at runtime to reliablydetermine which keys are required and which are potentially-missing.
    • If strict=False thenNewType("Foo", T) will be treatedthe same asT. Normally NewTypes are rejected with aTypeNotSupportedError because values of NewTypes at runtimeare indistinguishable from their wrapped supertype.
  • eval --If eval=False then this function will not attempt to resolve stringtype references, which requires the use of the eval() function.Otherwise string type references will be accepted.

Raises:

  • TypeNotSupportedError --
    • If strict=True and either mypy_extensions.TypedDict or aPython 3.8 typing.TypedDict is found within thetp argument.
    • If strict=True and a NewType is found within thetp argument.
    • If a TypeVar is found within thetp argument.
    • If an unrecognized Generic type is found within thetp argument.
  • UnresolvedForwardRefError --Iftp is a type form which contains a ForwardRef.
  • UnresolvableTypeError --Iftp is a string that could not be resolved to a type.

Footnotes:

checkcast API

def checkcast(    tp: TypeForm[T]† | TypeFormString[T]‡,    value: object,    /, *, strict: bool = True,    eval: bool = True) -> T: ...

Ifvalue is in the shape oftp (as accepted by a Python typecheckerconforming to PEP 484 "Type Hints") then returns it, otherwiseraises ValidationError.

This method logically performs an operation similar to:

if isinstance(tp, value):    return valueelse:    raise ValidationError(tp, value)

except that it supports many more types thanisinstance, including:

  • List[T]
  • Dict[K, V]
  • Optional[T]
  • Union[T1, T2, ...]
  • Literal[...]
  • T extends TypedDict

Seetrycast.trycast() for information about parameters,raised exceptions, and other details.

Raises:

  • ValidationError -- Ifvalue is not in the shape oftp.
  • TypeNotSupportedError
  • UnresolvedForwardRefError
  • UnresolvableTypeError

isassignable API

def isassignable(    value: object,    tp: TypeForm[T]† | TypeFormString[T]‡,    /, *, eval: bool = True) -> TypeGuard[T]: ...

Returns whethervalue is in the shape oftp(as accepted by a Python typechecker conforming to PEP 484 "Type Hints").

This method logically performs an operation similar to:

return isinstance(value, tp)

except that it supports many more types thanisinstance, including:

  • List[T]
  • Dict[K, V]
  • Optional[T]
  • Union[T1, T2, ...]
  • Literal[...]
  • T extends TypedDict

Seetrycast.trycast(..., strict=True) for information about parameters,raised exceptions, and other details.

Changelog

Future

v1.2.0

  • Addcheckcast(), an alternative totrycast() which raises aValidationError upon failure instead of returningNone.(#16)
  • Add support for Python 3.13.
    • RecognizeReadOnly[] from PEP 705.(#25)
  • Add support for Python 3.12.
    • Recognizetype statements from PEP 695.(#29)
  • Enhance support for Python 3.11:
    • Recognize specialNever values.(#26)
  • Drop support for Python 3.7. (#21)
  • Enforce that calls totrycast() andisassignable() pass thefirst 2 arguments in positional fashion and not in a named fashion:(#18)(Breaking change)
    • Yes:trycast(T, value),isassignable(value, T)
    • No:trycast(tp=T, value=value),isassignable(value=value, tp=T)

v1.1.0

  • Fixtrycast() to recognize TypedDicts with extra keys. (#19)
    • This new behavior helps recognize JSON structures with arbitrary additional keysand is consistent with how static typecheckers treat additional keys.
  • Fix magic wand in logo to look more like a magic wand. (#20)

v1.0.0

  • Extendtrycast() to recognize more kinds of types:
    • Extendtrycast() to recognizeset[T] andSet[T] values.
    • Extendtrycast() to recognizefrozenset[T] andFrozenSet[T] values.
    • Extendtrycast() to recognizeCallable andCallable[P, R] types whenP andR only containAny.
    • Extendtrycast() to recognizeNewType types when strict=False.
    • Extendtrycast() to explicitly disallowTypeVar types.
    • Extendtrycast() to explicitly disallow unrecognizedGeneric types.
  • Fix issues with PEP 484 conformance:(Breaking change)
    • bool values are now correctly treated as assignable toint.
    • bool,int, andfloat values are now correctly treated as assignable tocomplex.
  • Add support for Python 3.11.
  • Documentation improvements:
    • Add installation instructions.
    • Improve differentiation from similar libraries.
    • Document supported typing features & type checkers.
    • Mention that trycast() and isassignable() accept TypeFormString[T]in addition to TypeForm[T].
    • Add developer documentation.

v0.7.3

  • Support X|Y syntax for Union types fromPEP 604.
  • Documentation improvements:
    • Improve introduction.
    • Add API reference.

v0.7.2

  • Add logo.

v0.7.1

  • Upgrade development status from Beta to Production/Stable: 🎉
    • trycast is thoroughly tested.
    • trycast has high code coverage (98%, across Python 3.7-3.10).
    • trycast has been in production use for over a yearatat least one company without issues.
    • trycast supports all major Python type checkers(Mypy, Pyright/Pylance, Pyre, Pytype).
    • trycast's initial API is finalized.
  • Fixcoverage to be a dev-dependency rather than a regular dependency.

v0.7.0

  • Finalize the initial API:
    • Altertrycast() to usestrict=True by default rather thanstrict=False.(Breaking change)
    • Define trycast's__all__ to export only thetrycast andisassignable functions.
  • Add support for additional type checkers, in addition toMypy:
    • Add support for thePyright type checker andPylance language server extension (for Visual Studio Code).
    • Add support for thePyre type checker.
    • Add support for thePytype type checker.
  • Extendtrycast() to recognize specialAny andNoReturn values.
  • Fixtrycast() to provide better diagnostic error when given a tupleof types as itstp argument. Was broken in v0.6.0.

v0.6.1

  • Fixtrycast(..., eval=False) to not usetyping.get_type_hints(),which internally callseval().
  • Fixtrycast() andisassignable() to avoid swallowing KeyboardInterruptand other non-Exception BaseExceptions.

v0.6.0

  • Extendtrycast() to recognize a stringified type argument.
  • Extendtrycast() to report a better error message when givena type argument with an unresolved forward reference (ForwardRef).
  • Fixstrict argument totrycast to be passed to inner calls oftrycastcorrectly.
    • This also fixesisassignable()'s use of strict matching to be correct.
  • Altertrycast() to interpret a type argument ofNone or"None" as analias fortype(None), as consistent withPEP 484.
  • AlterTypeNotSupportedError to extendTypeError rather thanValueError.(Breaking change)
    • This is consistent withtrycast's andisinstance's behavior of usingaTypeError rather than aValueError when there is a problem with itstp argument.
  • Drop support for Python 3.6.(Breaking change)
    • Python 3.6 is end-of-life.

v0.5.0

  • isassignable() is introduced to the API:
    • isassignable() leveragestrycast() to enable type-checkingof values against type objects (i.e. type forms) provided atruntime, using the same PEP 484 typechecking rules used bytypecheckers such as mypy.
  • Extendtrycast() to recognizeRequired[] andNotRequired[] fromPEP 655, as imported fromtyping_extensions.
  • Extendtrycast() to support astrict parameter that controls whether itacceptsmypy_extensions.TypedDict or Python 3.8typing.TypedDictinstances (which lack certain runtime type information necessary foraccurate runtime typechecking).
    • For nowstrict=False by default for backward compatibilitywith earlier versions oftrycast(), but this default is expectedto be altered tostrict=True when/before trycast v1.0.0 is released.
  • Rename primary development branch frommaster tomain.

v0.4.0

  • Upgrade development status from Alpha to Beta:
    • trycast is thoroughly tested.
    • trycast has high code coverage (92% on Python 3.9).
    • trycast has been in production use for over a yearatat least one company without issues.
  • Add support for Python 3.10.
  • Setup continuous integration with GitHub Actions, against Python 3.6 - 3.10.
  • Migrate to the Black code style.
  • Introduce Black and isort code formatters.
  • Introduce flake8 linter.
  • Introduce coverage.py code coverage reports.

v0.3.0

  • TypedDict improvements & fixes:
    • Fixtrycast() to recognize custom Mapping subclasses as TypedDicts.
  • Extendtrycast() to recognize more JSON-like values:
    • Extendtrycast() to recognizeMapping andMutableMapping values.
    • Extendtrycast() to recognizetuple[T, ...] andTuple[T, ...] values.
    • Extendtrycast() to recognizeSequence andMutableSequence values.
  • Extendtrycast() to recognizetuple[T1, T2, etc] andTuple[T1, T2, etc] values.
  • Documentation improvements:
    • Improve introduction.
    • Outline motivation to use trycast and note alternatives.

v0.2.0

  • TypedDict improvements & fixes:
    • Fixtrycast() to recognize TypedDicts frommypy_extensions.
    • Extendtrycast() to recognize TypedDicts that contain forward-referencesto other types.
      • Unfortunately there appears to be no easy way to support arbitrary kindsof types that contain forward-references.
      • In particular {Union, Optional} types and collection types (List, Dict)with forward-references remain unsupported bytrycast().
    • Recognize TypedDicts that have mixed required and not-required keys correctly.
      • Exception: Does not work for mypy_extensions.TypedDict orPython 3.8's typing.TypedDict due to insufficient runtimetype annotation information.
    • Fix recognition of a total=False TypedDict so that extra keys are disallowed.
  • Altertyping_extensions to be an optional dependency oftrycast.

v0.1.0

  • Add support for Python 3.6, 3.7, and 3.9, in addition to 3.8.

v0.0.2

  • Fix README to appear on PyPI.
  • Add other package metadata, such as the supported Python versions.

v0.0.1a

  • Initial release.
  • Supports typechecking all types found in JSON.

About

trycast parses JSON-like values whose shape is defined by TypedDicts and other standard Python type hints.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp