Skip to content

Consider making Serializer generic in t.AnyStr for type checking to avoid overly ambiguous return types #347

Description

@Daverball

Currently Serializer.dumps has an ambiguous return type (str | bytes) due to how the Serializer determines its own return type in __init__ however for some of the derived classes such as URLSafeSerializer we know statically that the return type will be str and not bytes.

If Serializer were generic we could perform this type narrowing when we inherit from Serializer by instead inheriting from Serializer[str]. That way we don't need to type:ignore or assert isinstance(a, str) everywhere we use a URLSafeSerializer and correctly expect to get a str back from dumps.

If you wanted to be a bit more fancy you could even define a generic Protocol for the custom serializer that's passed in __init__ (rather than use Any) and create overloads to return the correct Serializer type, i.e. if dumps on the Serializer that's passed in returns bytes, then it will return Serializer[bytes], the default with None can then return Serializer[str].

i.e. something like this: (it would require a bit more care due to the order of arguments, to properly match every possible case, but the basic idea should come across)

class Serializer(_t.Generic[_t.AnyStr]):

   @_t.overload
    def __init__(
        self: Serializer[str],
        secret_key: _t_secret_key,
        salt: _t_opt_str_bytes = ...,
        serializer: _t.Optional[_SerializerProtocol[str]] = ...,
        serializer_kwargs: _t_opt_kwargs = ...,
        signer: _t.Optional[_t_signer] = ...,
        signer_kwargs: _t_opt_kwargs = ...,
        fallback_signers: _t.Optional[_t_fallbacks] = ...,
    ): ...

   @_t.overload
    def __init__(
        self: Serializer[bytes],
        secret_key: _t_secret_key,
        salt: _t_opt_str_bytes,
        serializer: _SerializerProtocol[bytes],
        serializer_kwargs: _t_opt_kwargs = ...,
        signer: _t.Optional[_t_signer] = ...,
        signer_kwargs: _t_opt_kwargs = ...,
        fallback_signers: _t.Optional[_t_fallbacks] = ...,
    ): ...

   # fallback for when the passed in serializer can't be pattern matched
   @_t.overload
    def __init__(
        self,
        secret_key: _t_secret_key,
        salt: _t_opt_str_bytes,
        serializer: Any,
        serializer_kwargs: _t_opt_kwargs = ...,
        signer: _t.Optional[_t_signer] = ...,
        signer_kwargs: _t_opt_kwargs = ...,
        fallback_signers: _t.Optional[_t_fallbacks] = ...,
    ): ...

Environment:

  • Python version: 3.10
  • ItsDangerous version: 2.1.2

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions