Description
The following code is valid and idiomatic Python 2 code, but does not typecheck under mypy --py2
:
from __future__ import unicode_literals
import os
os.environ['key'] = 'value'
It yields the error:
os_environ.py:4: error: Invalid index type "unicode" for "MutableMapping"
os_environ.py:4: error: Incompatible types in assignment (expression has type "unicode", target has type "str")
From a bit of poking at it in the repl, it seems that os.environ
is a dict-like object which functions a bit like cStringIO
: it accepts either str
or unicode
objects, but throws a runtime encoding error when passed a unicode
object which cannot be encoded to the ascii
codec:
>>> os.environ[u'💣'] = u'🔥'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/lucaswiman/.pyenv/versions/2.7/lib/python2.7/os.py", line 473, in __setitem__
putenv(key, item)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
In #869, I suggested replacing the type signature with MutableMapping[Union[str, unicode], Union[str, unicode]]
, to which @JukkaL replied:
This is potentially problematic, since this may break a lot of existing code using os.environ that assumes that it only has str keys and values (and there are other potential issues as well).
I think the type I suggested there is accurate, since annoyingly, these unicode
objects' type information does seem to be retained and actually can break code at runtime that assumes it's only getting str
:
>>> import io
>>> os.environ[u'key'] = u'value'
>>> set(map(type, os.environ.values()))
set([<type 'str'>, <type 'unicode'>])
>>> io.BytesIO(os.environ['key'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'unicode' does not have the buffer interface
That said, if there is interest in something more backwards-compatible with previously annotated code, we could allow assignment of str
key/values, but assume the actual contents of the mapping are str
. I think the following update to os/__init__.pyi
would achieve this effect, and might be a good compromise:
class _Environ(MutableMapping[str, str]):
def __setitem__(self, key:Union[str, unicode], value:Union[str, unicode]) -> None: ...
def copy(self) -> Dict[str, str]: ...
environ = ... # type: _Environ
Please advise, and I'd be happy to submit a pull request once a decision has been reached.