A common thing to encounter in typed Python code is some generic user-extensible API interface like json.dumps
/json.loads
where we cannot provide a more specific annotation than Any
for the value type, because individual users can extend the serializer/deserializer with the ability to handle additional types, so when they want to increase strictness on an API of this kind they have no other choice than to write a wrapper and force everyone to use that wrapper instead of the actual API, if they want more accurate types. The other motivation for doing this is API ergonomics, so less asserts are necessary, but even here there may be users that rather would deal with a more accurate type, even if it is more annoying to use the API that way.
I think it would be nice to provide a common interface for library authors to export a set of placeholder types that can optionally be filled in with a different type by library users. This would also reduce the need for custom type checker plugins.
from typing import Any, TypePlaceholder
JSONSerializable = TypePlaceholder("JSONSerializable", Any)
JSON = TypePlaceholder("JSON", Any)
def dumps(value: JSONSerializable) -> str: ...
def loads(value: str) -> JSON: ...
Or alternatively for better backwards-compatibility[1]:
from typing import Any, TypePlaceholder
JSONSerializable: TypePlaceholder = Any
JSON: TypePlaceholder = Any
def dumps(value: JSONSerializable) -> str: ...
def loads(value: str) -> JSON: ...
I think it would be best for type checkers to provide their own way for how to fill in a TypePlaceholder
through their individual configuration formats, but if that’s a point of contention it could also be specified through code, although a per-project configuration option seems more sane to me (maybe we could also use py.typed
for this?).
This may be less compelling for high level libraries using low level libraries, since end-users may yet again override/extend the interface they themselves already extended, but it should definitely prove useful for application authors that have no downstream dependencies.
Sharing Placeholders
Sometimes multiple libraries will talk about essentially the same placeholder type, but they don’t necessarily want to introduce an explicit dependency on one another (or the placeholder they want to refer to is trapped in a stub file and not available at runtime, but they need it to be available at runtime). One possible way to resolve this, would be, to allow specifying a fully qualified name for the first parameter of a TypePlaceholder
.
Auditing internal/external consistency
This would also empower library authors by being able to internally use the types they’re supposed to support by default (even if it’s just object
instead of Any
) and making sure there aren’t internal consistencies where the implementation fails for certain types in unexpected ways, that would otherwise be hidden through use of Any
, without hampering library ergonomics for end-users.
As a library user you could audit the external library against your own type definitions and make sure the library is actually able to handle the types you want it to be able to support.
this way type checkers can just define
TypePlaceholder = TypeAlias
in their copy of typeshed to support the annotation without implementing the feature ↩︎