diff options
-rw-r--r-- | Doc/library/functools.rst | 5 | ||||
-rw-r--r-- | Include/pyerrors.h | 2 | ||||
-rw-r--r-- | Lib/functools.py | 43 | ||||
-rw-r--r-- | Lib/ipaddress.py | 2 | ||||
-rw-r--r-- | Lib/os.py | 16 | ||||
-rw-r--r-- | Lib/test/test_ipaddress.py | 1 | ||||
-rw-r--r-- | Lib/test/test_os.py | 25 | ||||
-rw-r--r-- | Misc/NEWS | 5 |
8 files changed, 74 insertions, 25 deletions
diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst index 1b0d82a..f5c6608 100644 --- a/Doc/library/functools.rst +++ b/Doc/library/functools.rst @@ -49,8 +49,9 @@ The :mod:`functools` module defines the following functions: Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable. - If *maxsize* is set to None, the LRU feature is disabled and the cache - can grow without bound. + If *maxsize* is set to None, the LRU feature is disabled and the cache can + grow without bound. The LRU feature performs best when *maxsize* is a + power-of-two. If *typed* is set to True, function arguments of different types will be cached separately. For example, ``f(3)`` and ``f(3.0)`` will be treated diff --git a/Include/pyerrors.h b/Include/pyerrors.h index fb6281c..cfae922 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -87,7 +87,7 @@ PyAPI_FUNC(void) PyErr_GetExcInfo(PyObject **, PyObject **, PyObject **); PyAPI_FUNC(void) PyErr_SetExcInfo(PyObject *, PyObject *, PyObject *); #if defined(__clang__) || \ - (defined(__GNUC__) && \ + (defined(__GNUC_MAJOR__) && \ ((__GNUC_MAJOR__ >= 3) || \ (__GNUC_MAJOR__ == 2) && (__GNUC_MINOR__ >= 5))) #define _Py_NO_RETURN __attribute__((__noreturn__)) diff --git a/Lib/functools.py b/Lib/functools.py index 9f024f1..226a46e 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -142,30 +142,35 @@ except ImportError: _CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) -class _CacheKey(list): - 'Make a cache key from optionally typed positional and keyword arguments' - +class _HashedSeq(list): __slots__ = 'hashvalue' - def __init__(self, args, kwds, typed, - kwd_mark = (object(),), - sorted=sorted, tuple=tuple, type=type, hash=hash): - key = args - if kwds: - sorted_items = sorted(kwds.items()) - key += kwd_mark - for item in sorted_items: - key += item - if typed: - key += tuple(type(v) for v in args) - if kwds: - key += tuple(type(v) for k, v in sorted_items) - self[:] = key - self.hashvalue = hash(key) # so we only have to hash just once + def __init__(self, tup, hash=hash): + self[:] = tup + self.hashvalue = hash(tup) def __hash__(self): return self.hashvalue +def _make_key(args, kwds, typed, + kwd_mark = (object(),), + fasttypes = {int, str, frozenset, type(None)}, + sorted=sorted, tuple=tuple, type=type, len=len): + 'Make a cache key from optionally typed positional and keyword arguments' + key = args + if kwds: + sorted_items = sorted(kwds.items()) + key += kwd_mark + for item in sorted_items: + key += item + if typed: + key += tuple(type(v) for v in args) + if kwds: + key += tuple(type(v) for k, v in sorted_items) + elif len(key) == 1 and type(key[0]) in fasttypes: + return key[0] + return _HashedSeq(key) + def lru_cache(maxsize=128, typed=False): """Least-recently-used cache decorator. @@ -193,7 +198,7 @@ def lru_cache(maxsize=128, typed=False): # Constants shared by all lru cache instances: sentinel = object() # unique object used to signal cache misses - make_key = _CacheKey # build a key from the function arguments + make_key = _make_key # build a key from the function arguments PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields def decorating_function(user_function): diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index a8edcd1..25bcccd 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -1130,7 +1130,7 @@ class _BaseV4: """ unspecified_address = IPv4Address('0.0.0.0') if isinstance(self, _BaseAddress): - return self in unspecified_address + return self == unspecified_address return (self.network_address == self.broadcast_address == unspecified_address) @@ -160,8 +160,20 @@ def makedirs(name, mode=0o777, exist_ok=False): try: mkdir(name, mode) except OSError as e: - if not (e.errno == errno.EEXIST and exist_ok and path.isdir(name) and - st.S_IMODE(lstat(name).st_mode) == _get_masked_mode(mode)): + dir_exists = path.isdir(name) + expected_mode = _get_masked_mode(mode) + if dir_exists: + # S_ISGID is automatically copied by the OS from parent to child + # directories on mkdir. Don't consider it being set to be a mode + # mismatch as mkdir does not unset it when not specified in mode. + actual_mode = st.S_IMODE(lstat(name).st_mode) & ~st.S_ISGID + else: + actual_mode = -1 + if not (e.errno == errno.EEXIST and exist_ok and dir_exists and + actual_mode == expected_mode): + if dir_exists and actual_mode != expected_mode: + e.strerror += ' (mode %o != expected mode %o)' % ( + actual_mode, expected_mode) raise def removedirs(name): diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index aff4ff9..5b7d013 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -837,6 +837,7 @@ class IpaddrUnitTest(unittest.TestCase): self.assertEqual(False, ipaddress.ip_network('128.0.0.0').is_loopback) # test addresses + self.assertEqual(True, ipaddress.ip_address('0.0.0.0').is_unspecified) self.assertEqual(True, ipaddress.ip_address('224.1.1.1').is_multicast) self.assertEqual(False, ipaddress.ip_address('240.0.0.0').is_multicast) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 9b29b37..3ee5a1e 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -838,6 +838,31 @@ class MakedirTests(unittest.TestCase): os.makedirs(path, mode=mode, exist_ok=True) os.umask(old_mask) + def test_exist_ok_s_isgid_directory(self): + path = os.path.join(support.TESTFN, 'dir1') + S_ISGID = stat.S_ISGID + mode = 0o777 + old_mask = os.umask(0o022) + try: + existing_testfn_mode = stat.S_IMODE( + os.lstat(support.TESTFN).st_mode) + os.chmod(support.TESTFN, existing_testfn_mode | S_ISGID) + if (os.lstat(support.TESTFN).st_mode & S_ISGID != S_ISGID): + raise unittest.SkipTest('No support for S_ISGID dir mode.') + # The os should apply S_ISGID from the parent dir for us, but + # this test need not depend on that behavior. Be explicit. + os.makedirs(path, mode | S_ISGID) + # http://bugs.python.org/issue14992 + # Should not fail when the bit is already set. + os.makedirs(path, mode, exist_ok=True) + # remove the bit. + os.chmod(path, stat.S_IMODE(os.lstat(path).st_mode) & ~S_ISGID) + with self.assertRaises(OSError): + # Should fail when the bit is not already set when demanded. + os.makedirs(path, mode | S_ISGID, exist_ok=True) + finally: + os.umask(old_mask) + def test_exist_ok_existing_regular_file(self): base = support.TESTFN path = os.path.join(support.TESTFN, 'dir1') @@ -10,6 +10,11 @@ What's New in Python 3.3.0 Beta 1? Library ------- +- Issue #14992: os.makedirs(path, exist_ok=True) would raise an OSError + when the path existed and had the S_ISGID mode bit set when it was + not explicitly asked for. This is no longer an exception as mkdir + cannot control if the OS sets that bit for it or not. + - Issue #14989: Make the CGI enable option to http.server available via command line. |