Skip to content

Werkzeug EnvironHeaders is not considered a Mapping #3569

Closed

Description

@libre-man

It seems that the EnvironHeaders class of werkzeug (and probably the base class Headers too) is not compatible with Mapping, however it looks like it should be compatible (it implements all needed methods, and functions like one).

Activity

  1. srittau commented on Jan 3, 2020

    @srittau
    Collaborator

    Do you have an example that you think should type check but doesn't?

  2. libre-man commented on Jan 3, 2020

    @libre-man
    ContributorAuthor

    Here is small example:

    from flask import request
    import typing as t
    
    T = t.TypeVar('T')
    Z = t.TypeVar('Z')
    
    def get(kv: t.Mapping[T, Z], key: T) -> Z:
        return kv[key]
    
    get(request.headers, 'a_header')

    On which mypy errors with: test.py:10: error: Argument 1 to "get" has incompatible type "EnvironHeaders"; expected "Mapping[str, <nothing>]".

  3. srittau commented on Jan 5, 2020

    @srittau
    Collaborator

    I opened #3576 for the underlying issue. My best suggestion for now is to use a custom protocol for your own code and - depending on the outcome of the discussion in #3576 - submit PRs for code in typeshed.

  4. gward commented on Jul 7, 2020

    @gward
    Contributor

    For posterity, here is a custom protocol that works for me in one particular place:

    from typing import overload
    import typing_extensions
    
    class HeadersType(typing_extensions.Protocol):
        """common subtype of Mapping[str, str] and werkzeug.datastructures.EnvironHeaders"""
        @overload
        def get(self, k: str) -> Optional[str]:
            ...
    
        @overload
        def get(self, k: str, default: str) -> str:
            ...
    

    This is not a general solution to the problem! But I just spent at least an hour banging my head against this until I got it working. Hopefully this can save someone else some time in the future.

    I just had to change my code from

    headers: Mapping[str, str]
    

    to

    headers: HeadersType
    

    and now mypy is happy.

  5. added a commit that references this issue on Jul 10, 2020
  6. srittau commented on Jul 10, 2020

    @srittau
    Collaborator

    A brief addendum on why we can't just derive Headers from Mapping: Since Mapping is not a protocol, the following code returns False at runtime:

    from collections.abc import Mapping
    from werkzeug.datastructures import Headers
    
    isinstance(Headers(), Mapping)  # False

    Deriving Headers from Mapping would introduce a false negatives. For example, cgi.FieldStorage takes a headers arguments, but checks for Mapping explicitly:

    from cgi import FieldStorage
    from werkzeug.datastructures import Headers
    
    FieldStorage(headers=Headers())  # throws a TypeError
  7. srittau commented on May 18, 2021

    @srittau
    Collaborator

    Closing as the werkzeug stubs will be removed in a few months.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions