7

SimpleCookie is apparently a generic type and thus the following code (test.py) gives an error when checked with mypy:

from http.cookies import SimpleCookie

cookie = SimpleCookie()

test.py:3: error: Need type annotation for 'cookie'

Now if I change test.py line 3 to:

cookie: SimpleCookie = SimpleCookie()

I get the following error:

test.py:3: error: Missing type parameters for generic type "SimpleCookie"

SimpleCookie inherits from dict, has str keys and Morsel values, so I'd assume that the correct generic type annotation is something like this:

from http.cookies import Morsel, SimpleCookie

cookie: SimpleCookie[str, Morsel] = SimpleCookie()

But now the error is:

test.py:3: error: "SimpleCookie" expects 1 type argument, but 2 given

Changing line 3 to

cookie: SimpleCookie[str] = SimpleCookie()

suddenly makes mypy happy, but leaves me very confused why this is the correct solution, so I have two questions:

  1. Why is SimpleCookie a generic type with one argument?
  2. What's the best way to handle this in my code? Should I annotate SimpleCookie variables with SimpleCookie[str] (which seems like a lie to me) or should I just annotate them with Any and hope this will be cleaned up in future Python versions?

mypy version 0.750 and Python 3.8.0

Agost Biro
  • 2,251
  • 14
  • 26

1 Answers1

7

Explanation

str in SimpleCookie[str] actually refers to the type _T of coded_value in Morsel.

mypy uses https://github.com/python/typeshed/blob/master/stdlib/3/http/cookies.pyi:

class Morsel(Dict[str, Any], Generic[_T]):
    value: str
    coded_value: _T
    key: str
    def set(self, key: str, val: str, coded_val: _T) -> None: ...
    # ...

class BaseCookie(Dict[str, Morsel[_T]], Generic[_T]):
    # ...
    def value_decode(self, val: str) -> _T: ...
    def value_encode(self, val: _T) -> str: ...
    # ...
    def __setitem__(self, key: str, value: Union[str, Morsel[_T]]) -> None: ...

class SimpleCookie(BaseCookie[_T], Generic[_T]): ...

Correct typing

_T should be Any, i.e. SimpleCookie[Any], as explained in python/typeshed#3060:

Morsel does cast any value to string ... max-age can take an integer (unix time) and http-only a boolean.

Actually, I could not reproduce the error you get with this:

from http.cookies import SimpleCookie

cookie: SimpleCookie = SimpleCookie()
aaron
  • 17,311
  • 4
  • 27
  • 63
  • 1
    Thanks, great answer! Weird that you can't reproduce, I double checked now and I get the same error with mypy==0.750 and Python 3.8.0 (Conda distribution). – Agost Biro Dec 19 '19 at 15:33
  • 1
    There is one thing I don't get about the type signature of `SimpleCookie`: why does it intherit from `Generic[_T]` again? Wouldn't it be generic by virtue of inheriting from `BaseCookie`? – Agost Biro Dec 19 '19 at 15:34
  • 1
    Yes, it would. Seems like just being explicit from the start: [python/typeshed@e97638d#diff-9c3523d2a7593f7bb448bb4c0d504f5a](https://github.com/python/typeshed/commit/e97638d4bd03b1e5fc5a09904a17018f389ec673#diff-9c3523d2a7593f7bb448bb4c0d504f5a) – aaron Dec 19 '19 at 17:07