No project description provided
Project description
trycast
The trycast module defines 2 functions, trycast()
and isassignable()
:
trycast()
trycast() parses JSON-like values whose shape is defined by typed dictionaries (TypedDicts) and other standard Python type hints.
Here is an example of parsing a Point2D
object defined as a TypedDict
:
from bottle import HTTPResponse, request, route
from trycast import trycast
from typing import TypedDict
class Point2D(TypedDict):
x: float
y: float
name: str
@route('/draw_point')
def draw_point_endpoint() -> HTTPResponse:
request_json = request.json # type: object
if (point := trycast(Point2D, request_json)) is not None:
draw_point(point) # type is narrowed to Point2D
return HTTPResponse(status=200)
else:
return HTTPResponse(status=400) # Bad Request
def draw_point(point: Point2D) -> None:
...
In this example the trycast
function is asked to parse a request_json
into a Point2D
object, returning the original object (with its type narrowed
appropriately) if parsing was successful.
More complex types can be parsed as well, such as the Shape
in the following
example, which is a tagged union that can be either a Circle
or Rect
value:
from bottle import HTTPResponse, request, route
from trycast import trycast
from typing import Literal, TypedDict, Union
class Point2D(TypedDict):
x: float
y: float
class Circle(TypedDict):
type: Literal['circle']
center: Point2D # 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!
@route('/draw_shape')
def draw_shape_endpoint() -> HTTPResponse:
request_json = request.json # type: object
if (shape := trycast(Shape, request_json)) is not None:
draw_shape(shape) # type is narrowed to Shape
return HTTPResponse(status=200) # OK
else:
return HTTPResponse(status=400) # Bad Request
Important: Current limitations in the mypy typechecker require that you add an extra
cast(Optional[Shape], ...)
around the call totrycast
in the example so that it is accepted by the typechecker without complaining:shape = cast(Optional[Shape], trycast(Shape, request_json)) if shape is not None: ...These limitations are in the process of being resolved by introducing TypeForm support to mypy.
isassignable()
isassignable(value, T)
checks whether value
is assignable to a variable
of type T
(using PEP 484 static typechecking rules), but at runtime.
It is similar to Python's builtin isinstance()
method but
additionally supports checking against TypedDict types, Union types,
Literal types, and many others.
Here is an example of checking assignability to a Shape
object defined as a
Union of TypedDicts:
class Circle(TypedDict):
type: Literal['circle']
...
class Rect(TypedDict):
type: Literal['rect']
...
Shape = Union[Circle, Rect] # a Tagged Union!
@route('/draw_shape')
def draw_shape_endpoint() -> HTTPResponse:
request_json = request.json # type: object
if isassignable(request_json, Shape):
draw_shape(request_json) # type is narrowed to Shape
return HTTPResponse(status=200) # OK
else:
return HTTPResponse(status=400) # Bad Request
Important: Current limitations in the mypy typechecker prevent the automatic narrowing of the type of
request_json
in the above example toShape
, so you must add an additionalcast()
to narrow the type manually:if isassignable(request_json, Shape): shape = cast(Shape, request_json) # type is manually narrowed to Shape draw_shape(shape)These limitations are in the process of being resolved by introducing TypeForm support to mypy.
Motivation & Alternatives
Why use typed dictionaries to represent data structures instead of classes, named tuples, or other formats?
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.
Other alternatives for representing data structures in Python include dataclasses, named tuples, attrs, and plain classes.
Recommendations while using trycast
- So that
trycast()
can recognize TypedDicts with mixed required and not-required keys correctly:- Use Python 3.9+ if possible.
- Prefer using
typing.TypedDict
, unless you must use Python 3.8. In Python 3.8 prefertyping_extensions.TypedDict
instead. - Avoid using
mypy_extensions.TypedDict
in general.
More Information
A presentation about trycast was given at the 2021 PyCon Typing Summit:
License
Changelog
Future
- See the Roadmap.
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 year at at least one company without issues.
- trycast supports all major Python type checkers (Mypy, Pyright/Pylance, Pyre, Pytype).
- trycast's initial API is finalized.
- Fix
coverage
to be a dev-dependency rather than a regular dependency.
v0.7.0
- Finalize the initial API:
- Alter
trycast()
to usestrict=True
by default rather thanstrict=False
. (Breaking change) - Define trycast's
__all__
to export only thetrycast
andisassignable
functions.
- Alter
- Add support for additional type checkers, in addition to Mypy:
- Extend
trycast()
to recognize specialAny
andNoReturn
values. - Fix
trycast()
to provide better diagnostic error when given a tuple of types as itstp
argument. Was broken in v0.6.0.
v0.6.1
- Fix
trycast(..., eval=False)
to not usetyping.get_type_hints()
, which internally callseval()
. - Fix
trycast()
andisassignable()
to avoid swallowing KeyboardInterrupt and other non-Exception BaseExceptions.
v0.6.0
- Extend
trycast()
to recognize a stringified type argument. - Extend
trycast()
to report a better error message when given a type argument with an unresolved forward reference (ForwardRef
). - Fix
strict
argument totrycast
to be passed to inner calls oftrycast
correctly.- This also fixes
isassignable()
's use of strict matching to be correct.
- This also fixes
- Alter
trycast()
to interpret a type argument ofNone
or"None"
as an alias fortype(None)
, as consistent with PEP 484. - Alter
TypeNotSupportedError
to extendTypeError
rather thanValueError
. (Breaking change)- This is consistent with
trycast
's andisinstance
's behavior of using aTypeError
rather than aValueError
when there is a problem with itstp
argument.
- This is consistent with
- 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-checking of values against type objects (i.e. type forms) provided at runtime, using the same PEP 484 typechecking rules used by typecheckers such as mypy.
- Extend
trycast()
to recognizeRequired[]
andNotRequired[]
from PEP 655, as imported fromtyping_extensions
. - Extend
trycast()
to support astrict
parameter that controls whether it acceptsmypy_extensions.TypedDict
or Python 3.8typing.TypedDict
instances (which lack certain runtime type information necessary for accurate runtime typechecking).- For now
strict=False
by default for backward compatibility with earlier versions oftrycast()
, but this default is expected to be altered tostrict=True
when/before trycast v1.0.0 is released.
- For now
- Rename primary development branch from
master
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 year at at 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:
- Fix
trycast()
to recognize custom Mapping subclasses as TypedDicts.
- Fix
- Extend
trycast()
to recognize more JSON-like values:- Extend
trycast()
to recognizeMapping
andMutableMapping
values. - Extend
trycast()
to recognizetuple[T, ...]
andTuple[T, ...]
values. - Extend
trycast()
to recognizeSequence
andMutableSequence
values.
- Extend
- Extend
trycast()
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:
- Fix
trycast()
to recognize TypedDicts frommypy_extensions
. - Extend
trycast()
to recognize TypedDicts that contain forward-references to other types.- Unfortunately there appears to be no easy way to support arbitrary kinds of types that contain forward-references.
- In particular {Union, Optional} types and collection types (List, Dict)
with forward-references remain unsupported by
trycast()
.
- Recognize TypedDicts that have mixed required and not-required keys correctly.
- Exception: Does not work for mypy_extensions.TypedDict or Python 3.8's typing.TypedDict due to insufficient runtime type annotation information.
- Fix recognition of a total=False TypedDict so that extra keys are disallowed.
- Fix
- Alter
typing_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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file trycast-0.7.1.tar.gz
.
File metadata
- Download URL: trycast-0.7.1.tar.gz
- Upload date:
- Size: 16.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.1.6 CPython/3.10.1 Darwin/18.7.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4fc69a8d6434ea3ab185b0d323ddbdc1197d239a80028ff18de373512abf2ba8 |
|
MD5 | 7bbd5bc63004ab77d140cf37dbc5fcbb |
|
BLAKE2b-256 | 521a7b5f140a5d051b94d3f5bca7d39394e3cd9481ef2dae2741b0290144e261 |
File details
Details for the file trycast-0.7.1-py3-none-any.whl
.
File metadata
- Download URL: trycast-0.7.1-py3-none-any.whl
- Upload date:
- Size: 11.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.1.6 CPython/3.10.1 Darwin/18.7.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1a7a52e5d30cafa44375d7b4a780b1f72d35b760d4a195f65f37be5a7a6958a5 |
|
MD5 | 53899b883220fc4a2690c79d25ef894e |
|
BLAKE2b-256 | 3670043153b730b8ae227cbc8f3d905c1518c435273255aeebaa26b4c61fb1db |