Hi all,
I've been reading about the TypeGuards coming in 3.10 (and I'm excited!). I have a question which is related to guards and casts.
One use-case I have is parsing data from some external source (e.g. JSON from a web API), but then presenting the result as a well-typed-value. For example:
def get_error_code(error_data: Dict[str, Any]) -> str:
code = error_data["code"]
if isinstance(code, str):
return code
raise ValueError("bah! bad code!")
The type of a value from an untrustworthy source should be checked at runtime, and we raise an error if the type is not correct.
There are a few other ways of writing this, but with pitfalls of varying levels of obviousness. e.g.
return cast(str, error_data["code"])
or
code = error_data["code"]
assert isinstance(code, str)
return code
The cast has no runtime check, and any `assert` gets removed if optimization is used. The `cast` "says what I mean" most succinctly, but by design doesn't include a runtime check.
This seems like something which could be made the responsibility of `typing` in 3.11 or later. For example, an 'asserting_cast' variant which takes a (TypeGuard | Type) and lets the above examples be written as
code = error_data["code"]
return asserting_cast(code, str)
or, to clarify the TypeGuard case,
def is_str(x: Any) -> TypeGuard[str]:
return isinstance(x, str)
...
code = error_data["code"]
return asserting_cast(code, is_str)
I think the implementation would be quite simple, but IMO there's value in stdlib `typing` providing a pre-built solution which has thought about odd cases like non-runtime-checkable protocols.
I'd be more than happy to work on adding something like this to `typing` if it seems reasonable. It would be a "safer but slower" alternative to cast and can raise a custom error, e.g. FailedTypeCastError(ValueError) .
My biggest two questions are...
Have I missed something obvious in `typing` or maybe `typing-extensions` which already covers this?
Is this a bad idea, and if so can someone help me understand why?
Thanks in advance,
-Stephen