diff --git a/CHANGES b/CHANGES index 995f9dea8..1aeb4c7c8 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,8 @@ Development version - Pull request #204: At `six.ensure_binary`, `six.ensure_text`, and `six.ensure_str`. +- Add `six.environ` and `six.environb` which operate similar to the Python3 + `os.environ` and `os.environb`. 1.11.0 ------ diff --git a/six.py b/six.py index 8d9ac41a8..43dee01f8 100644 --- a/six.py +++ b/six.py @@ -25,6 +25,7 @@ import functools import itertools import operator +import os import sys import types @@ -621,6 +622,67 @@ def iterlists(d, **kw): "Return an iterator over the (key, [values]) pairs of a dictionary.") +if PY3: + environb = os.environb + environ = os.environ +else: + environb = os.environ + + from collections import MutableMapping + + class _TextEnviron(MutableMapping): + """ + Utility class to return text strings from the environment instead of byte strings + + Mimics the behaviour of os.environ on Python3 + """ + # Mimic Python3's os.environ by using sys.getfilesystemencoding() + def __init__(self, env=None, encoding=sys.getfilesystemencoding()): + if env is None: + env = os.environ + self._raw_environ = env + self._value_cache = {} + self.encoding = encoding + + def __delitem__(self, key): + del self._raw_environ[key] + + def __getitem__(self, key): + # Note: For undecodable strings, Python3 will use surrogateescape. We don't have that + # on Python2 so we throw a ValueError instead + value = self._raw_environ[key] + + # Cache keys off of the undecoded values to handle any environment variables which + # change during a run + if value not in self._value_cache: + try: + self._value_cache[value] = ensure_text(value, encoding=self.encoding) + except UnicodeError: + raise ValueError('environ string for %s is undecodable in the' + ' filesystemencoding. Use six.environb and manually convert' + ' the value to text instead' % value) + return self._value_cache[value] + + def __setitem__(self, key, value): + if not isinstance(value, text_type): + raise TypeError("str expected, not %s" % type(value).__name__) + + try: + self._raw_environ[key] = ensure_binary(value, encoding=self.encoding) + except UnicodeError: + raise ValueError('The value, %s, is unencodable in the filesystemencoding.' + ' Use six.environb and manually convert the value to bytes' + ' instead' % value) + + def __iter__(self): + return self._raw_environ.__iter__() + + def __len__(self): + return len(self._raw_environ) + + environ = _TextEnviron() + + if PY3: def b(s): return s.encode("latin-1") @@ -906,7 +968,6 @@ def ensure_text(s, encoding='utf-8', errors='strict'): raise TypeError("not expecting type '%s'" % type(s)) - def python_2_unicode_compatible(klass): """ A decorator that defines __unicode__ and __str__ methods under Python 2.