summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/functools.rst5
-rw-r--r--Include/pyerrors.h2
-rw-r--r--Lib/functools.py43
-rw-r--r--Lib/ipaddress.py2
-rw-r--r--Lib/os.py16
-rw-r--r--Lib/test/test_ipaddress.py1
-rw-r--r--Lib/test/test_os.py25
-rw-r--r--Misc/NEWS5
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)
diff --git a/Lib/os.py b/Lib/os.py
index a1a35cd..11e4d60 100644
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -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')
diff --git a/Misc/NEWS b/Misc/NEWS
index e732604..f0bac32 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.