Commits

Martin von Löwis  committed 275796a Merge

merge with default

  • Participants
  • Parent commits f038153, e763999
  • Branches pep-382

Comments (0)

Files changed (28)

File Doc/library/datetime.rst

 
    Most implementations of :meth:`dst` will probably look like one of these two::
 
-      def dst(self):
+      def dst(self, dt):
           # a fixed-offset class:  doesn't account for DST
           return timedelta(0)
 
    or ::
 
-      def dst(self):
+      def dst(self, dt):
           # Code to set dston and dstoff to the time zone's DST
           # transition times based on the input dt.year, and expressed
           # in standard local time.  Then

File Doc/library/functions.rst

    :meth:`__index__` method that returns an integer.
 
 
-.. function:: open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True)
+.. function:: open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
 
    Open *file* and return a corresponding stream.  If the file cannot be opened,
    an :exc:`OSError` is raised.
    closed.  If a filename is given *closefd* has no effect and must be ``True``
    (the default).
 
+   A custom opener can be used by passing a callable as *opener*. The underlying
+   file descriptor for the file object is then obtained by calling *opener* with
+   (*file*, *flags*). *opener* must return an open file descriptor (passing
+   :mod:`os.open` as *opener* results in functionality similar to passing
+   ``None``).
+
+   .. versionchanged:: 3.3
+      The *opener* parameter was added.
+
    The type of file object returned by the :func:`open` function depends on the
    mode.  When :func:`open` is used to open a file in a text mode (``'w'``,
    ``'r'``, ``'wt'``, ``'rt'``, etc.), it returns a subclass of

File Doc/library/html.parser.rst

 
 .. method:: HTMLParser.handle_data(data)
 
-   This method is called to process arbitrary data.  It is intended to be
+   This method is called to process arbitrary data (e.g. the content of
+   ``<script>...</script>`` and ``<style>...</style>``).  It is intended to be
    overridden by a derived class; the base class implementation does nothing.
 
 

File Doc/library/io.rst

 Raw File I/O
 ^^^^^^^^^^^^
 
-.. class:: FileIO(name, mode='r', closefd=True)
+.. class:: FileIO(name, mode='r', closefd=True, opener=None)
 
    :class:`FileIO` represents an OS-level file containing bytes data.
    It implements the :class:`RawIOBase` interface (and therefore the
    The :meth:`read` (when called with a positive argument), :meth:`readinto`
    and :meth:`write` methods on this class will only make one system call.
 
+   A custom opener can be used by passing a callable as *opener*. The underlying
+   file descriptor for the file object is then obtained by calling *opener* with
+   (*name*, *flags*). *opener* must return an open file descriptor (passing
+   :mod:`os.open` as *opener* results in functionality similar to passing
+   ``None``).
+
+   .. versionchanged:: 3.3
+      The *opener* parameter was added.
+
    In addition to the attributes and methods from :class:`IOBase` and
    :class:`RawIOBase`, :class:`FileIO` provides the following data
    attributes and methods:

File Doc/tutorial/interpreter.rst

 
 When a script file is used, it is sometimes useful to be able to run the script
 and enter interactive mode afterwards.  This can be done by passing :option:`-i`
-before the script.  (This does not work if the script is read from standard
-input, for the same reason as explained in the previous paragraph.)
+before the script.
 
 
 .. _tut-argpassing:

File Doc/using/windows.rst

       "7 Minutes to "Hello World!""
       by Richard Dooling, 2006
 
-   `Installing on Windows <http://diveintopython.org/installing_python/windows.html>`_
+   `Installing on Windows <http://diveintopython.net/installing_python/windows.html>`_
       in "`Dive into Python: Python from novice to pro
-      <http://diveintopython.org/index.html>`_"
+      <http://diveintopython.net/index.html>`_"
       by Mark Pilgrim, 2004,
       ISBN 1-59059-356-1
 

File Lib/_pyio.py

 
 
 def open(file, mode="r", buffering=-1, encoding=None, errors=None,
-         newline=None, closefd=True):
+         newline=None, closefd=True, opener=None):
 
     r"""Open file and return a stream.  Raise IOError upon failure.
 
     be kept open when the file is closed. This does not work when a file name is
     given and must be True in that case.
 
+    A custom opener can be used by passing a callable as *opener*. The
+    underlying file descriptor for the file object is then obtained by calling
+    *opener* with (*file*, *flags*). *opener* must return an open file
+    descriptor (passing os.open as *opener* results in functionality similar to
+    passing None).
+
     open() returns a file object whose type depends on the mode, and
     through which the standard file operations such as reading and writing
     are performed. When open() is used to open a file in a text mode ('w',
                  (writing and "w" or "") +
                  (appending and "a" or "") +
                  (updating and "+" or ""),
-                 closefd)
+                 closefd, opener=opener)
     line_buffering = False
     if buffering == 1 or buffering < 0 and raw.isatty():
         buffering = -1

File Lib/html/parser.py

   \s*                                # trailing whitespace
 """, re.VERBOSE)
 endendtag = re.compile('>')
+# the HTML 5 spec, section 8.1.2.2, doesn't allow spaces between
+# </ and the tag name, so maybe this should be fixed
 endtagfind = re.compile('</\s*([a-zA-Z][-.a-zA-Z0-9:_]*)\s*>')
 
 
         self.rawdata = ''
         self.lasttag = '???'
         self.interesting = interesting_normal
+        self.cdata_elem = None
         _markupbase.ParserBase.reset(self)
 
     def feed(self, data):
         """Return full source of start tag: '<...>'."""
         return self.__starttag_text
 
-    def set_cdata_mode(self):
+    def set_cdata_mode(self, elem):
         self.interesting = interesting_cdata
+        self.cdata_elem = elem.lower()
 
     def clear_cdata_mode(self):
         self.interesting = interesting_normal
+        self.cdata_elem = None
 
     # Internal -- handle data as far as reasonable.  May leave state
     # and data to be processed by a subsequent call.  If 'end' is
         else:
             self.handle_starttag(tag, attrs)
             if tag in self.CDATA_CONTENT_ELEMENTS:
-                self.set_cdata_mode()
+                self.set_cdata_mode(tag)
         return endpos
 
     # Internal -- check to see if we have a complete starttag; return end
         j = match.end()
         match = endtagfind.match(rawdata, i) # </ + tag + >
         if not match:
+            if self.cdata_elem is not None:
+                self.handle_data(rawdata[i:j])
+                return j
             if self.strict:
                 self.error("bad end tag: %r" % (rawdata[i:j],))
             k = rawdata.find('<', i + 1, j)
                 j = i + 1
             self.handle_data(rawdata[i:j])
             return j
-        tag = match.group(1)
-        self.handle_endtag(tag.lower())
+
+        elem = match.group(1).lower() # script or style
+        if self.cdata_elem is not None:
+            if elem != self.cdata_elem:
+                self.handle_data(rawdata[i:j])
+                return j
+
+        self.handle_endtag(elem.lower())
         self.clear_cdata_mode()
         return j
 

File Lib/importlib/_bootstrap.py

     Be prepared to handle a FileExistsError if concurrent writing of the
     temporary file is attempted."""
     if not sys.platform.startswith('win'):
-        # On POSIX-like platforms, renaming is atomic
-        path_tmp = path + '.tmp'
+        # On POSIX-like platforms, renaming is atomic. id() is used to generate
+        # a pseudo-random filename.
+        path_tmp = '{}.{}'.format(path, id(path))
+        fd = _os.open(path_tmp, _os.O_EXCL | _os.O_CREAT | _os.O_WRONLY)
         try:
-            fd = _os.open(path_tmp, _os.O_EXCL | _os.O_CREAT | _os.O_WRONLY)
             with _io.FileIO(fd, 'wb') as file:
                 file.write(data)
             _os.rename(path_tmp, path)

File Lib/locale.py

     grouping = conv[monetary and 'mon_grouping' or 'grouping']
     if not grouping:
         return (s, 0)
-    result = ""
-    seps = 0
     if s[-1] == ' ':
         stripped = s.rstrip()
         right_spaces = s[len(stripped):]

File Lib/test/datetimetester.py

 
     def test_strftime_y2k(self):
         for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
-            self.assertIn(self.theclass(y, 1, 1).strftime("%Y"),
-                          [str(y),'%04d' % y])
+            d = self.theclass(y, 1, 1)
+            # Issue 13305:  For years < 1000, the value is not always
+            # padded to 4 digits across platforms.  The C standard
+            # assumes year >= 1900, so it does not specify the number
+            # of digits.
+            if d.strftime("%Y") != '%04d' % y:
+                # Year 42 returns '42', not padded
+                self.assertEqual(d.strftime("%Y"), '%d' % y)
+                # '0042' is obtained anyway
+                self.assertEqual(d.strftime("%4Y"), '%04d' % y)
 
     def test_replace(self):
         cls = self.theclass

File Lib/test/test_htmlparser.py

             ("starttag_text", s)])
 
     def test_cdata_content(self):
-        s = """<script> <!-- not a comment --> &not-an-entity-ref; </script>"""
-        self._run_check(s, [
-            ("starttag", "script", []),
-            ("data", " <!-- not a comment --> &not-an-entity-ref; "),
-            ("endtag", "script"),
-            ])
-        s = """<script> <not a='start tag'> </script>"""
-        self._run_check(s, [
-            ("starttag", "script", []),
-            ("data", " <not a='start tag'> "),
-            ("endtag", "script"),
-            ])
+        contents = [
+            '<!-- not a comment --> &not-an-entity-ref;',
+            "<not a='start tag'>",
+            '<a href="" /> <p> <span></span>',
+            'foo = "</scr" + "ipt>";',
+            'foo = "</SCRIPT" + ">";',
+            'foo = <\n/script> ',
+            '<!-- document.write("</scr" + "ipt>"); -->',
+            ('\n//<![CDATA[\n'
+             'document.write(\'<s\'+\'cript type="text/javascript" '
+             'src="http://www.example.org/r=\'+new '
+             'Date().getTime()+\'"><\\/s\'+\'cript>\');\n//]]>'),
+            '\n<!-- //\nvar foo = 3.14;\n// -->\n',
+            'foo = "</sty" + "le>";',
+            '<!-- \u2603 -->',
+            # these two should be invalid according to the HTML 5 spec,
+            # section 8.1.2.2
+            #'foo = </\nscript>',
+            #'foo = </ script>',
+        ]
+        elements = ['script', 'style', 'SCRIPT', 'STYLE', 'Script', 'Style']
+        for content in contents:
+            for element in elements:
+                element_lower = element.lower()
+                s = '<{element}>{content}</{element}>'.format(element=element,
+                                                               content=content)
+                self._run_check(s, [("starttag", element_lower, []),
+                                    ("data", content),
+                                    ("endtag", element_lower)])
+
 
     def test_entityrefs_in_attributes(self):
         self._run_check("<html foo='&euro;&amp;&#97;&#x61;&unsupported;'>", [
 
 class HTMLParserTolerantTestCase(TestCaseBase):
 
-    def setUp(self):
-        self.collector = EventCollector(strict=False)
+    def get_collector(self):
+        return EventCollector(strict=False)
 
     def test_tolerant_parsing(self):
         self._run_check('<html <html>te>>xt&a<<bc</a></html>\n'
                              ('endtag', 'html'),
                              ('data', '\n<img src="URL><//img></html'),
                              ('endtag', 'html')],
-                        collector = self.collector)
+                        collector=self.get_collector())
+
+    def test_with_unquoted_attributes(self):
+        # see #12008
+        html = ("<html><body bgcolor=d0ca90 text='181008'>"
+                "<table cellspacing=0 cellpadding=1 width=100% ><tr>"
+                "<td align=left><font size=-1>"
+                "- <a href=/rabota/><span class=en> software-and-i</span></a>"
+                "- <a href='/1/'><span class=en> library</span></a></table>")
+        expected = [
+            ('starttag', 'html', []),
+            ('starttag', 'body', [('bgcolor', 'd0ca90'), ('text', '181008')]),
+            ('starttag', 'table',
+                [('cellspacing', '0'), ('cellpadding', '1'), ('width', '100%')]),
+            ('starttag', 'tr', []),
+            ('starttag', 'td', [('align', 'left')]),
+            ('starttag', 'font', [('size', '-1')]),
+            ('data', '- '), ('starttag', 'a', [('href', '/rabota/')]),
+            ('starttag', 'span', [('class', 'en')]), ('data', ' software-and-i'),
+            ('endtag', 'span'), ('endtag', 'a'),
+            ('data', '- '), ('starttag', 'a', [('href', '/1/')]),
+            ('starttag', 'span', [('class', 'en')]), ('data', ' library'),
+            ('endtag', 'span'), ('endtag', 'a'), ('endtag', 'table')
+        ]
+
+        self._run_check(html, expected, collector=self.get_collector())
 
     def test_comma_between_attributes(self):
         self._run_check('<form action="/xxx.php?a=1&amp;b=2&amp", '
                             ('starttag', 'form',
                                 [('action', '/xxx.php?a=1&b=2&amp'),
                                  ('method', 'post')])],
-                        collector = self.collector)
+                        collector=self.get_collector())
 
     def test_weird_chars_in_unquoted_attribute_values(self):
         self._run_check('<form action=bogus|&#()value>', [
                             ('starttag', 'form',
                                 [('action', 'bogus|&#()value')])],
-                        collector = self.collector)
+                        collector=self.get_collector())
 
-    def test_issue13273(self):
+    def test_correct_detection_of_start_tags(self):
+        # see #13273
         html = ('<div style=""    ><b>The <a href="some_url">rain</a> '
                 '<br /> in <span>Spain</span></b></div>')
         expected = [
             ('endtag', 'b'),
             ('endtag', 'div')
         ]
-        self._run_check(html, expected, collector=self.collector)
+        self._run_check(html, expected, collector=self.get_collector())
 
-    def test_issue13273_2(self):
         html = '<div style="", foo = "bar" ><b>The <a href="some_url">rain</a>'
         expected = [
             ('starttag', 'div', [('style', ''), ('foo', 'bar')]),
             ('data', 'rain'),
             ('endtag', 'a'),
         ]
-        self._run_check(html, expected, collector=self.collector)
+        self._run_check(html, expected, collector=self.get_collector())
 
     def test_unescape_function(self):
         p = html.parser.HTMLParser()

File Lib/test/test_io.py

         for obj in test:
             self.assertTrue(hasattr(obj, "__dict__"))
 
+    def test_opener(self):
+        with self.open(support.TESTFN, "w") as f:
+            f.write("egg\n")
+        fd = os.open(support.TESTFN, os.O_RDONLY)
+        def opener(path, flags):
+            return fd
+        with self.open("non-existent", "r", opener=opener) as f:
+            self.assertEqual(f.read(), "egg\n")
+
 class CIOTest(IOTest):
 
     def test_IOBase_finalize(self):

File Lib/test/test_site.py

 else:
     raise unittest.SkipTest("importation of site.py suppressed")
 
-if not os.path.isdir(site.USER_SITE):
+if site.ENABLE_USER_SITE and not os.path.isdir(site.USER_SITE):
     # need to add user site directory for tests
     os.makedirs(site.USER_SITE)
     site.addsitedir(site.USER_SITE)
         finally:
             pth_file.cleanup()
 
+    @unittest.skipUnless(site.ENABLE_USER_SITE, "requires access to PEP 370 "
+                          "user-site (site.ENABLE_USER_SITE)")
     def test_s_option(self):
         usersite = site.USER_SITE
         self.assertIn(usersite, sys.path)

File Lib/test/test_time.py

 import sys
 import warnings
 
+# Max year is only limited by the size of C int.
+SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4
+TIME_MAXYEAR = (1 << 8 * SIZEOF_INT - 1) - 1
+TIME_MINYEAR = -TIME_MAXYEAR - 1
+
+
 class TimeTestCase(unittest.TestCase):
 
     def setUp(self):
             with self.assertRaises(ValueError):
                 time.strftime('%f')
 
-    def _bounds_checking(self, func=time.strftime):
+    def _bounds_checking(self, func):
         # Make sure that strftime() checks the bounds of the various parts
-        #of the time tuple (0 is valid for *all* values).
+        # of the time tuple (0 is valid for *all* values).
 
         # The year field is tested by other test cases above
 
         # Check month [1, 12] + zero support
+        func((1900, 0, 1, 0, 0, 0, 0, 1, -1))
+        func((1900, 12, 1, 0, 0, 0, 0, 1, -1))
         self.assertRaises(ValueError, func,
                             (1900, -1, 1, 0, 0, 0, 0, 1, -1))
         self.assertRaises(ValueError, func,
                             (1900, 13, 1, 0, 0, 0, 0, 1, -1))
         # Check day of month [1, 31] + zero support
+        func((1900, 1, 0, 0, 0, 0, 0, 1, -1))
+        func((1900, 1, 31, 0, 0, 0, 0, 1, -1))
         self.assertRaises(ValueError, func,
                             (1900, 1, -1, 0, 0, 0, 0, 1, -1))
         self.assertRaises(ValueError, func,
                             (1900, 1, 32, 0, 0, 0, 0, 1, -1))
         # Check hour [0, 23]
+        func((1900, 1, 1, 23, 0, 0, 0, 1, -1))
         self.assertRaises(ValueError, func,
                             (1900, 1, 1, -1, 0, 0, 0, 1, -1))
         self.assertRaises(ValueError, func,
                             (1900, 1, 1, 24, 0, 0, 0, 1, -1))
         # Check minute [0, 59]
+        func((1900, 1, 1, 0, 59, 0, 0, 1, -1))
         self.assertRaises(ValueError, func,
                             (1900, 1, 1, 0, -1, 0, 0, 1, -1))
         self.assertRaises(ValueError, func,
                             (1900, 1, 1, 0, 0, -1, 0, 1, -1))
         # C99 only requires allowing for one leap second, but Python's docs say
         # allow two leap seconds (0..61)
+        func((1900, 1, 1, 0, 0, 60, 0, 1, -1))
+        func((1900, 1, 1, 0, 0, 61, 0, 1, -1))
         self.assertRaises(ValueError, func,
                             (1900, 1, 1, 0, 0, 62, 0, 1, -1))
         # No check for upper-bound day of week;
         #  value forced into range by a ``% 7`` calculation.
         # Start check at -2 since gettmarg() increments value before taking
         #  modulo.
+        self.assertEqual(func((1900, 1, 1, 0, 0, 0, -1, 1, -1)),
+                         func((1900, 1, 1, 0, 0, 0, +6, 1, -1)))
         self.assertRaises(ValueError, func,
                             (1900, 1, 1, 0, 0, 0, -2, 1, -1))
         # Check day of the year [1, 366] + zero support
+        func((1900, 1, 1, 0, 0, 0, 0, 0, -1))
+        func((1900, 1, 1, 0, 0, 0, 0, 366, -1))
         self.assertRaises(ValueError, func,
                             (1900, 1, 1, 0, 0, 0, 0, -1, -1))
         self.assertRaises(ValueError, func,
                 self.fail("conversion specifier %r failed with '%s' input." %
                           (format, strf_output))
 
+    # XXX Temporary tests to troubleshoot issue #13309 on buildbots
+    test_maa_strptime = test_strptime
+    test_mzz_strptime = test_strptime
+
     def test_strptime_bytes(self):
         # Make sure only strings are accepted as arguments to strptime.
         self.assertRaises(TypeError, time.strptime, b'2009', "%Y")
         time.asctime(time.gmtime(self.t))
 
         # Max year is only limited by the size of C int.
-        sizeof_int = sysconfig.get_config_var('SIZEOF_INT') or 4
-        bigyear = (1 << 8 * sizeof_int - 1) - 1
-        asc = time.asctime((bigyear, 6, 1) + (0,)*6)
-        self.assertEqual(asc[-len(str(bigyear)):], str(bigyear))
-        self.assertRaises(OverflowError, time.asctime, (bigyear + 1,) + (0,)*8)
+        for bigyear in TIME_MAXYEAR, TIME_MINYEAR:
+            asc = time.asctime((bigyear, 6, 1) + (0,) * 6)
+            self.assertEqual(asc[-len(str(bigyear)):], str(bigyear))
+        self.assertRaises(OverflowError, time.asctime,
+                          (TIME_MAXYEAR + 1,) + (0,) * 8)
+        self.assertRaises(OverflowError, time.asctime,
+                          (TIME_MINYEAR - 1,) + (0,) * 8)
         self.assertRaises(TypeError, time.asctime, 0)
         self.assertRaises(TypeError, time.asctime, ())
         self.assertRaises(TypeError, time.asctime, (0,) * 10)
         t1 = time.mktime(lt1)
         self.assertAlmostEqual(t1, t0, delta=0.2)
 
+    def test_mktime(self):
+        # Issue #1726687
+        for t in (-2, -1, 0, 1):
+            try:
+                tt = time.localtime(t)
+            except (OverflowError, ValueError):
+                pass
+            else:
+                self.assertEqual(time.mktime(tt), t)
+            self.assertNotEqual(time.strftime('%Z', time.gmtime(self.t)), 'LMT',
+                                "strftime bug after processing t = %s" % t)
+        # It may not be possible to reliably make mktime return error
+        # on all platfom.  This will make sure that no other exception
+        # than OverflowError is raised for an extreme value.
+        try:
+            time.mktime((-1, 1, 1, 0, 0, 0, -1, -1, -1))
+        except OverflowError:
+            pass
+
+
 class TestLocale(unittest.TestCase):
     def setUp(self):
         self.oldloc = locale.setlocale(locale.LC_ALL)
         raise NotImplementedError()
 
 class _TestAsctimeYear:
+    _format = '%d'
+
     def yearstr(self, y):
         return time.asctime((y,) + (0,) * 8).split()[-1]
 
         self.assertEqual(self.yearstr(123456789), '123456789')
 
 class _TestStrftimeYear:
+
+    # Issue 13305:  For years < 1000, the value is not always
+    # padded to 4 digits across platforms.  The C standard
+    # assumes year >= 1900, so it does not specify the number
+    # of digits.
+
+    if time.strftime('%Y', (1,) + (0,) * 8) == '0001':
+        _format = '%04d'
+    else:
+        _format = '%d'
+
     def yearstr(self, y):
-        return time.strftime('%Y', (y,) + (0,) * 8).split()[-1]
+        return time.strftime('%Y', (y,) + (0,) * 8)
+
+    def test_4dyear(self):
+        # Check that we can return the zero padded value.
+        if self._format == '%04d':
+            self.test_year('%04d')
+        else:
+            def year4d(y):
+                return time.strftime('%4Y', (y,) + (0,) * 8)
+            self.test_year('%04d', func=year4d)
+
+    def skip_if_not_supported(y):
+        msg = "strftime() is limited to [1; 9999] with Visual Studio"
+        # Check that it doesn't crash for year > 9999
+        try:
+            time.strftime('%Y', (y,) + (0,) * 8)
+        except ValueError:
+            cond = False
+        else:
+            cond = True
+        return unittest.skipUnless(cond, msg)
+
+    @skip_if_not_supported(10000)
+    def test_large_year(self):
+        return super().test_large_year()
+
+    @skip_if_not_supported(0)
+    def test_negative(self):
+        return super().test_negative()
+
+    del skip_if_not_supported
+
+
+class _Test4dYear(_BaseYearTest):
+    _format = '%d'
+
+    def test_year(self, fmt=None, func=None):
+        fmt = fmt or self._format
+        func = func or self.yearstr
+        self.assertEqual(func(1),    fmt % 1)
+        self.assertEqual(func(68),   fmt % 68)
+        self.assertEqual(func(69),   fmt % 69)
+        self.assertEqual(func(99),   fmt % 99)
+        self.assertEqual(func(999),  fmt % 999)
+        self.assertEqual(func(9999), fmt % 9999)
 
     def test_large_year(self):
-        # Check that it doesn't crash for year > 9999
-        try:
-            text = self.yearstr(12345)
-        except ValueError:
-            # strftime() is limited to [1; 9999] with Visual Studio
-            return
-        self.assertEqual(text, '12345')
+        self.assertEqual(self.yearstr(12345), '12345')
         self.assertEqual(self.yearstr(123456789), '123456789')
-
-class _Test4dYear(_BaseYearTest):
-
-    def test_year(self):
-        self.assertIn(self.yearstr(1),     ('1', '0001'))
-        self.assertIn(self.yearstr(68),   ('68', '0068'))
-        self.assertIn(self.yearstr(69),   ('69', '0069'))
-        self.assertIn(self.yearstr(99),   ('99', '0099'))
-        self.assertIn(self.yearstr(999), ('999', '0999'))
-        self.assertEqual(self.yearstr(9999), '9999')
+        self.assertEqual(self.yearstr(TIME_MAXYEAR), str(TIME_MAXYEAR))
+        self.assertRaises(OverflowError, self.yearstr, TIME_MAXYEAR + 1)
 
     def test_negative(self):
-        try:
-            text = self.yearstr(-1)
-        except ValueError:
-            # strftime() is limited to [1; 9999] with Visual Studio
-            return
-        self.assertIn(text, ('-1', '-001'))
-
+        self.assertEqual(self.yearstr(-1), self._format % -1)
         self.assertEqual(self.yearstr(-1234), '-1234')
         self.assertEqual(self.yearstr(-123456), '-123456')
+        self.assertEqual(self.yearstr(TIME_MINYEAR), str(TIME_MINYEAR))
+        self.assertRaises(OverflowError, self.yearstr, TIME_MINYEAR - 1)
 
 
-    def test_mktime(self):
-        # Issue #1726687
-        for t in (-2, -1, 0, 1):
-            try:
-                tt = time.localtime(t)
-            except (OverflowError, ValueError):
-                pass
-            else:
-                self.assertEqual(time.mktime(tt), t)
-        # It may not be possible to reliably make mktime return error
-        # on all platfom.  This will make sure that no other exception
-        # than OverflowError is raised for an extreme value.
-        try:
-            time.mktime((-1, 1, 1, 0, 0, 0, -1, -1, -1))
-        except OverflowError:
-            pass
-
 class TestAsctime4dyear(_TestAsctimeYear, _Test4dYear):
     pass
 

File Lib/test/test_urllib.py

     else:
         return opener.open(url, data)
 
+
+class FakeHTTPMixin(object):
+    def fakehttp(self, fakedata):
+        class FakeSocket(io.BytesIO):
+            io_refs = 1
+
+            def sendall(self, str):
+                pass
+
+            def makefile(self, *args, **kwds):
+                self.io_refs += 1
+                return self
+
+            def read(self, amt=None):
+                if self.closed:
+                    return b""
+                return io.BytesIO.read(self, amt)
+
+            def readline(self, length=None):
+                if self.closed:
+                    return b""
+                return io.BytesIO.readline(self, length)
+
+            def close(self):
+                self.io_refs -= 1
+                if self.io_refs == 0:
+                    io.BytesIO.close(self)
+
+        class FakeHTTPConnection(http.client.HTTPConnection):
+            def connect(self):
+                self.sock = FakeSocket(fakedata)
+        self._connection_class = http.client.HTTPConnection
+        http.client.HTTPConnection = FakeHTTPConnection
+
+    def unfakehttp(self):
+        http.client.HTTPConnection = self._connection_class
+
+
 class urlopen_FileTests(unittest.TestCase):
     """Test urlopen() opening a temporary file.
 
         self.env.set('NO_PROXY', 'localhost, anotherdomain.com, newdomain.com')
         self.assertTrue(urllib.request.proxy_bypass_environment('anotherdomain.com'))
 
-class urlopen_HttpTests(unittest.TestCase):
+class urlopen_HttpTests(unittest.TestCase, FakeHTTPMixin):
     """Test urlopen() opening a fake http connection."""
 
-    def fakehttp(self, fakedata):
-        class FakeSocket(io.BytesIO):
-            io_refs = 1
-            def sendall(self, str): pass
-            def makefile(self, *args, **kwds):
-                self.io_refs += 1
-                return self
-            def read(self, amt=None):
-                if self.closed: return b""
-                return io.BytesIO.read(self, amt)
-            def readline(self, length=None):
-                if self.closed: return b""
-                return io.BytesIO.readline(self, length)
-            def close(self):
-                self.io_refs -= 1
-                if self.io_refs == 0:
-                    io.BytesIO.close(self)
-        class FakeHTTPConnection(http.client.HTTPConnection):
-            def connect(self):
-                self.sock = FakeSocket(fakedata)
-        self._connection_class = http.client.HTTPConnection
-        http.client.HTTPConnection = FakeHTTPConnection
-
-    def unfakehttp(self):
-        http.client.HTTPConnection = self._connection_class
-
     def check_read(self, ver):
         self.fakehttp(b"HTTP/" + ver + b" 200 OK\r\n\r\nHello!")
         try:
         self.assertEqual(report[0][1], 8192)
         self.assertEqual(report[0][2], 8193)
 
+
+class urlretrieve_HttpTests(unittest.TestCase, FakeHTTPMixin):
+    """Test urllib.urlretrieve() using fake http connections"""
+
+    def test_short_content_raises_ContentTooShortError(self):
+        self.fakehttp(b'''HTTP/1.1 200 OK
+Date: Wed, 02 Jan 2008 03:03:54 GMT
+Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e
+Connection: close
+Content-Length: 100
+Content-Type: text/html; charset=iso-8859-1
+
+FF
+''')
+
+        def _reporthook(par1, par2, par3):
+            pass
+
+        with self.assertRaises(urllib.error.ContentTooShortError):
+            try:
+                urllib.request.urlretrieve('http://example.com/',
+                                           reporthook=_reporthook)
+            finally:
+                self.unfakehttp()
+
+    def test_short_content_raises_ContentTooShortError_without_reporthook(self):
+        self.fakehttp(b'''HTTP/1.1 200 OK
+Date: Wed, 02 Jan 2008 03:03:54 GMT
+Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e
+Connection: close
+Content-Length: 100
+Content-Type: text/html; charset=iso-8859-1
+
+FF
+''')
+        with self.assertRaises(urllib.error.ContentTooShortError):
+            try:
+                urllib.request.urlretrieve('http://example.com/')
+            finally:
+                self.unfakehttp()
+
+
 class QuotingTests(unittest.TestCase):
     """Tests for urllib.quote() and urllib.quote_plus()
 
         urlopen_FileTests,
         urlopen_HttpTests,
         urlretrieve_FileTests,
+        urlretrieve_HttpTests,
         ProxyTests,
         QuotingTests,
         UnquotingTests,

File Lib/test/test_urllib2.py

 # parse_keqv_list, parse_http_list, HTTPDigestAuthHandler
 
 class TrivialTests(unittest.TestCase):
+
+    def test___all__(self):
+        # Verify which names are exposed
+        for module in 'request', 'response', 'parse', 'error', 'robotparser':
+            context = {}
+            exec('from urllib.%s import *' % module, context)
+            del context['__builtins__']
+            for k, v in context.items():
+                self.assertEqual(v.__module__, 'urllib.%s' % module,
+                    "%r is exposed in 'urllib.%s' but defined in %r" %
+                    (k, module, v.__module__))
+
     def test_trivial(self):
         # A couple trivial tests
 

File Lib/urllib/error.py

 
 import urllib.response
 
+__all__ = ['URLError', 'HTTPError', 'ContentTooShortError']
+
+
 # do these error classes make sense?
 # make sure all of the IOError stuff is overridden.  we just want to be
 # subtypes.

File Lib/urllib/request.py

 import io
 import os
 import posixpath
-import random
 import re
 import socket
 import sys
 else:
     _have_ssl = True
 
+__all__ = [
+    # Classes
+    'Request', 'OpenerDirector', 'BaseHandler', 'HTTPDefaultErrorHandler',
+    'HTTPRedirectHandler', 'HTTPCookieProcessor', 'ProxyHandler',
+    'HTTPPasswordMgr', 'HTTPPasswordMgrWithDefaultRealm',
+    'AbstractBasicAuthHandler', 'HTTPBasicAuthHandler', 'ProxyBasicAuthHandler',
+    'AbstractDigestAuthHandler', 'HTTPDigestAuthHandler', 'ProxyDigestAuthHandler',
+    'HTTPHandler', 'FileHandler', 'FTPHandler', 'CacheFTPHandler',
+    'UnknownHandler', 'HTTPErrorProcessor',
+    # Functions
+    'urlopen', 'install_opener', 'build_opener',
+    'pathname2url', 'url2pathname', 'getproxies',
+    # Legacy interface
+    'urlretrieve', 'urlcleanup', 'URLopener', 'FancyURLopener',
+]
+
 # used in User-Agent header sent
 __version__ = sys.version[:3]
 
         return response
 
 
-def randombytes(n):
-    """Return n random bytes."""
-    return os.urandom(n)
+# Return n random bytes.
+_randombytes = os.urandom
+
 
 class AbstractDigestAuthHandler:
     # Digest authentication is specified in RFC 2617.
         # authentication, and to provide some message integrity protection.
         # This isn't a fabulous effort, but it's probably Good Enough.
         s = "%s:%s:%s:" % (self.nonce_count, nonce, time.ctime())
-        b = s.encode("ascii") + randombytes(8)
+        b = s.encode("ascii") + _randombytes(8)
         dig = hashlib.sha1(b).hexdigest()
         return dig[:16]
 
     http_request = AbstractHTTPHandler.do_request_
 
 if hasattr(http.client, 'HTTPSConnection'):
-    import ssl
 
     class HTTPSHandler(AbstractHTTPHandler):
 
 
         https_request = AbstractHTTPHandler.do_request_
 
+    __all__.append(HTTPSHandler)
+
 class HTTPCookieProcessor(BaseHandler):
     def __init__(self, cookiejar=None):
         import http.cookiejar
                 size = -1
                 read = 0
                 blocknum = 0
+                if "content-length" in headers:
+                    size = int(headers["Content-Length"])
                 if reporthook:
-                    if "content-length" in headers:
-                        size = int(headers["Content-Length"])
                     reporthook(blocknum, bs, size)
                 while 1:
                     block = fp.read(bs)
         if not host: raise IOError('http error', 'no host given')
 
         if proxy_passwd:
-            import base64
             proxy_auth = base64.b64encode(proxy_passwd.encode()).decode('ascii')
         else:
             proxy_auth = None
 
         if user_passwd:
-            import base64
             auth = base64.b64encode(user_passwd.encode()).decode('ascii')
         else:
             auth = None
 
     def open_local_file(self, url):
         """Use local file."""
-        import mimetypes, email.utils
-        from io import StringIO
+        import email.utils
+        import mimetypes
         host, file = splithost(url)
         localname = url2pathname(file)
         try:
         if not isinstance(url, str):
             raise URLError('ftp error', 'proxy support for ftp protocol currently not implemented')
         import mimetypes
-        from io import StringIO
         host, path = splithost(url)
         if not host: raise URLError('ftp error', 'no host given')
         host, port = splitport(host)
                                             time.gmtime(time.time())))
         msg.append('Content-type: %s' % type)
         if encoding == 'base64':
-            import base64
             # XXX is this encoding/decoding ok?
             data = base64.decodebytes(data.encode('ascii')).decode('latin-1')
         else:
             URLopener.http_error_default(self, url, fp,
                                          errcode, errmsg, headers)
         stuff = headers['www-authenticate']
-        import re
         match = re.match('[ \t]*([^ \t]+)[ \t]+realm="([^"]*)"', stuff)
         if not match:
             URLopener.http_error_default(self, url, fp,
             URLopener.http_error_default(self, url, fp,
                                          errcode, errmsg, headers)
         stuff = headers['proxy-authenticate']
-        import re
         match = re.match('[ \t]*([^ \t]+)[ \t]+realm="([^"]*)"', stuff)
         if not match:
             URLopener.http_error_default(self, url, fp,
       'exceptions': ['foo.bar', '*.bar.com', '127.0.0.1', '10.1', '10.0/16']
     }
     """
-    import re
-    import socket
     from fnmatch import fnmatch
 
     hostonly, port = splitport(host)
                     for p in proxyServer.split(';'):
                         protocol, address = p.split('=', 1)
                         # See if address has a type:// prefix
-                        import re
                         if not re.match('^([^/:]+)://', address):
                             address = '%s://%s' % (protocol, address)
                         proxies[protocol] = address
     def proxy_bypass_registry(host):
         try:
             import winreg
-            import re
         except ImportError:
             # Std modules, so should be around - but you never know!
             return 0

File Lib/xmlrpc/client.py

 # Wrapper for XML-RPC DateTime values.  This converts a time value to
 # the format used by XML-RPC.
 # <p>
-# The value can be given as a string in the format
-# "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by
+# The value can be given as a datetime object, as a string in the
+# format "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by
 # time.localtime()), or an integer value (as returned by time.time()).
 # The wrapper uses time.localtime() to convert an integer to a time
 # tuple.
 #
-# @param value The time, given as an ISO 8601 string, a time
-#              tuple, or a integer time value.
+# @param value The time, given as a datetime object, an ISO 8601 string,
+#              a time tuple, or an integer time value.
+
+
+# Issue #13305: different format codes across platforms
+_day0 = datetime(1, 1, 1)
+if _day0.strftime('%Y') == '0001':      # Mac OS X
+    def _iso8601_format(value):
+        return value.strftime("%Y%m%dT%H:%M:%S")
+elif _day0.strftime('%4Y') == '0001':   # Linux
+    def _iso8601_format(value):
+        return value.strftime("%4Y%m%dT%H:%M:%S")
+else:
+    def _iso8601_format(value):
+        return value.strftime("%Y%m%dT%H:%M:%S").zfill(17)
+del _day0
+
 
 def _strftime(value):
     if isinstance(value, datetime):
-        return value.strftime("%Y%m%dT%H:%M:%S")
+        return _iso8601_format(value)
 
     if not isinstance(value, (tuple, time.struct_time)):
         if value == 0:
             o = other.value
         elif isinstance(other, datetime):
             s = self.value
-            o = other.strftime("%Y%m%dT%H:%M:%S")
+            o = _iso8601_format(other)
         elif isinstance(other, str):
             s = self.value
             o = other

File Misc/ACKS

File contents unchanged.
 Core and Builtins
 -----------------
 
+- Issue #12797: Added custom opener parameter to builtin open() and
+  FileIO.open().
+
 - Issue #10519: Avoid unnecessary recursive function calls in
   setobject.c.
 
 Library
 -------
 
-- Issue 13296: Fix IDLE to clear compile __future__ flags on shell restart.
+- Issue #13287: urllib.request and urllib.error now contains a __all__ and
+  exposes only relevant Classes, Functions. Patch by Florent Xicluna.
+
+- Issue #670664: Fix HTMLParser to correctly handle the content of
+  ``<script>...</script>`` and ``<style>...</style>``.
+
+- Issue #10817: Fix urlretrieve function to raise ContentTooShortError even
+  when reporthook is None. Patch by Jyrki Pulliainen.
+
+- Issue #13296: Fix IDLE to clear compile __future__ flags on shell restart.
   (Patch by Roger Serwy)
 
 - Fix the xmlrpc.client user agent to return something similar to
 Tests
 -----
 
+- Issue #13304: Skip test case if user site-packages disabled (-s or
+  PYTHONNOUSERSITE).  (Patch by Carl Meyer)
+
 - Issue #5661: Add a test for ECONNRESET/EPIPE handling to test_asyncore. Patch
   by Xavier de Gaye.
 

File Modules/_io/_iomodule.c

  */
 PyDoc_STRVAR(open_doc,
 "open(file, mode='r', buffering=-1, encoding=None,\n"
-"     errors=None, newline=None, closefd=True) -> file object\n"
+"     errors=None, newline=None, closefd=True, opener=None) -> file object\n"
 "\n"
 "Open file and return a stream.  Raise IOError upon failure.\n"
 "\n"
 "when the file is closed. This does not work when a file name is given\n"
 "and must be True in that case.\n"
 "\n"
+"A custom opener can be used by passing a callable as *opener*. The\n"
+"underlying file descriptor for the file object is then obtained by\n"
+"calling *opener* with (*file*, *flags*). *opener* must return an open\n"
+"file descriptor (passing os.open as *opener* results in functionality\n"
+"similar to passing None).\n"
+"\n"
 "open() returns a file object whose type depends on the mode, and\n"
 "through which the standard file operations such as reading and writing\n"
 "are performed. When open() is used to open a file in a text mode ('w',\n"
 {
     char *kwlist[] = {"file", "mode", "buffering",
                       "encoding", "errors", "newline",
-                      "closefd", NULL};
-    PyObject *file;
+                      "closefd", "opener", NULL};
+    PyObject *file, *opener = Py_None;
     char *mode = "r";
     int buffering = -1, closefd = 1;
     char *encoding = NULL, *errors = NULL, *newline = NULL;
 
     _Py_IDENTIFIER(isatty);
     _Py_IDENTIFIER(fileno);
+    _Py_IDENTIFIER(mode);
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|sizzzi:open", kwlist,
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|sizzziO:open", kwlist,
                                      &file, &mode, &buffering,
                                      &encoding, &errors, &newline,
-                                     &closefd)) {
+                                     &closefd, &opener)) {
         return NULL;
     }
 
 
     /* Create the Raw file stream */
     raw = PyObject_CallFunction((PyObject *)&PyFileIO_Type,
-				"Osi", file, rawmode, closefd);
+                                "OsiO", file, rawmode, closefd, opener);
     if (raw == NULL)
         return NULL;
 
     if (wrapper == NULL)
         goto error;
 
-    if (PyObject_SetAttrString(wrapper, "mode", modeobj) < 0)
+    if (_PyObject_SetAttrId(wrapper, &PyId_mode, modeobj) < 0)
         goto error;
     Py_DECREF(modeobj);
     return wrapper;

File Modules/_io/bufferedio.c

 _Py_IDENTIFIER(_dealloc_warn);
 _Py_IDENTIFIER(flush);
 _Py_IDENTIFIER(isatty);
+_Py_IDENTIFIER(mode);
+_Py_IDENTIFIER(name);
 _Py_IDENTIFIER(peek);
 _Py_IDENTIFIER(read);
 _Py_IDENTIFIER(read1);
 buffered_name_get(buffered *self, void *context)
 {
     CHECK_INITIALIZED(self)
-    return PyObject_GetAttrString(self->raw, "name");
+    return _PyObject_GetAttrId(self->raw, &PyId_name);
 }
 
 static PyObject *
 buffered_mode_get(buffered *self, void *context)
 {
     CHECK_INITIALIZED(self)
-    return PyObject_GetAttrString(self->raw, "mode");
+    return _PyObject_GetAttrId(self->raw, &PyId_mode);
 }
 
 /* Lower-level APIs */
 {
     PyObject *nameobj, *res;
 
-    nameobj = PyObject_GetAttrString((PyObject *) self, "name");
+    nameobj = _PyObject_GetAttrId((PyObject *) self, &PyId_name);
     if (nameobj == NULL) {
         if (PyErr_ExceptionMatches(PyExc_AttributeError))
             PyErr_Clear();

File Modules/_io/fileio.c

 fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
 {
     fileio *self = (fileio *) oself;
-    static char *kwlist[] = {"file", "mode", "closefd", NULL};
+    static char *kwlist[] = {"file", "mode", "closefd", "opener", NULL};
     const char *name = NULL;
-    PyObject *nameobj, *stringobj = NULL;
+    PyObject *nameobj, *stringobj = NULL, *opener = Py_None;
     char *mode = "r";
     char *s;
 #ifdef MS_WINDOWS
             return -1;
     }
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|si:fileio",
-                                     kwlist, &nameobj, &mode, &closefd))
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|siO:fileio",
+                                     kwlist, &nameobj, &mode, &closefd,
+                                     &opener))
         return -1;
 
     if (PyFloat_Check(nameobj)) {
             goto error;
         }
 
-        Py_BEGIN_ALLOW_THREADS
         errno = 0;
+        if (opener == Py_None) {
+            Py_BEGIN_ALLOW_THREADS
 #ifdef MS_WINDOWS
-        if (widename != NULL)
-            self->fd = _wopen(widename, flags, 0666);
-        else
+            if (widename != NULL)
+                self->fd = _wopen(widename, flags, 0666);
+            else
 #endif
-            self->fd = open(name, flags, 0666);
-        Py_END_ALLOW_THREADS
+                self->fd = open(name, flags, 0666);
+            Py_END_ALLOW_THREADS
+        } else {
+            PyObject *fdobj = PyObject_CallFunction(
+                                  opener, "Oi", nameobj, flags);
+            if (fdobj == NULL)
+                goto error;
+            if (!PyLong_Check(fdobj)) {
+                Py_DECREF(fdobj);
+                PyErr_SetString(PyExc_TypeError,
+                        "expected integer from opener");
+                goto error;
+            }
+
+            self->fd = PyLong_AsLong(fdobj);
+            Py_DECREF(fdobj);
+            if (self->fd == -1) {
+                goto error;
+            }
+        }
+
         if (self->fd < 0) {
 #ifdef MS_WINDOWS
             if (widename != NULL)
 static PyObject *
 fileio_repr(fileio *self)
 {
+    _Py_IDENTIFIER(name);
     PyObject *nameobj, *res;
 
     if (self->fd < 0)
         return PyUnicode_FromFormat("<_io.FileIO [closed]>");
 
-    nameobj = PyObject_GetAttrString((PyObject *) self, "name");
+    nameobj = _PyObject_GetAttrId((PyObject *) self, &PyId_name);
     if (nameobj == NULL) {
         if (PyErr_ExceptionMatches(PyExc_AttributeError))
             PyErr_Clear();
 
 
 PyDoc_STRVAR(fileio_doc,
-"file(name: str[, mode: str]) -> file IO object\n"
+"file(name: str[, mode: str][, opener: None]) -> file IO object\n"
 "\n"
 "Open a file.  The mode can be 'r', 'w' or 'a' for reading (default),\n"
 "writing or appending.  The file will be created if it doesn't exist\n"
 "when opened for writing or appending; it will be truncated when\n"
 "opened for writing.  Add a '+' to the mode to allow simultaneous\n"
-"reading and writing.");
+"reading and writing. A custom opener can be used by passing a\n"
+"callable as *opener*. The underlying file descriptor for the file\n"
+"object is then obtained by calling opener with (*name*, *flags*).\n"
+"*opener* must return an open file descriptor (passing os.open as\n"
+"*opener* results in functionality similar to passing None).");
 
 PyDoc_STRVAR(read_doc,
 "read(size: int) -> bytes.  read at most size bytes, returned as bytes.\n"

File Modules/_io/iobase.c

    of the IOBase object rather than the virtual `closed` attribute as returned
    by whatever subclass. */
 
+_Py_IDENTIFIER(__IOBase_closed);
 #define IS_CLOSED(self) \
-    PyObject_HasAttrString(self, "__IOBase_closed")
+    _PyObject_HasAttrId(self, &PyId___IOBase_closed)
 
 /* Internal methods */
 static PyObject *
 iobase_close(PyObject *self, PyObject *args)
 {
     PyObject *res;
+    _Py_IDENTIFIER(__IOBase_closed);
 
     if (IS_CLOSED(self))
         Py_RETURN_NONE;
 
     res = PyObject_CallMethodObjArgs(self, _PyIO_str_flush, NULL);
-    PyObject_SetAttrString(self, "__IOBase_closed", Py_True);
+    _PyObject_SetAttrId(self, &PyId___IOBase_closed, Py_True);
     if (res == NULL) {
         return NULL;
     }
     PyObject *buffer, *result;
     Py_ssize_t old_size = -1;
     _Py_IDENTIFIER(read);
+    _Py_IDENTIFIER(peek);
 
     if (!PyArg_ParseTuple(args, "|O&:readline", &_PyIO_ConvertSsize_t, &limit)) {
         return NULL;
     }
 
-    if (PyObject_HasAttrString(self, "peek"))
+    if (_PyObject_HasAttrId(self, &PyId_peek))
         has_peek = 1;
 
     buffer = PyByteArray_FromStringAndSize(NULL, 0);

File Modules/_io/textio.c

 _Py_IDENTIFIER(flush);
 _Py_IDENTIFIER(getpreferredencoding);
 _Py_IDENTIFIER(isatty);
+_Py_IDENTIFIER(mode);
+_Py_IDENTIFIER(name);
+_Py_IDENTIFIER(raw);
 _Py_IDENTIFIER(read);
+_Py_IDENTIFIER(read1);
 _Py_IDENTIFIER(readable);
 _Py_IDENTIFIER(replace);
 _Py_IDENTIFIER(reset);
         ci = _PyCodec_Lookup(encoding);
         if (ci == NULL)
             goto error;
-        res = PyObject_GetAttrString(ci, "name");
+        res = _PyObject_GetAttrId(ci, &PyId_name);
         Py_DECREF(ci);
         if (res == NULL) {
             if (PyErr_ExceptionMatches(PyExc_AttributeError))
     if (Py_TYPE(buffer) == &PyBufferedReader_Type ||
         Py_TYPE(buffer) == &PyBufferedWriter_Type ||
         Py_TYPE(buffer) == &PyBufferedRandom_Type) {
-        raw = PyObject_GetAttrString(buffer, "raw");
+        raw = _PyObject_GetAttrId(buffer, &PyId_raw);
         /* Cache the raw FileIO object to speed up 'closed' checks */
         if (raw == NULL) {
             if (PyErr_ExceptionMatches(PyExc_AttributeError))
     self->seekable = self->telling = PyObject_IsTrue(res);
     Py_DECREF(res);
 
-    self->has_read1 = PyObject_HasAttrString(buffer, "read1");
+    self->has_read1 = _PyObject_HasAttrId(buffer, &PyId_read1);
 
     self->encoding_start_of_stream = 0;
     if (self->seekable && self->encoder) {
     res = PyUnicode_FromString("<_io.TextIOWrapper");
     if (res == NULL)
         return NULL;
-    nameobj = PyObject_GetAttrString((PyObject *) self, "name");
+    nameobj = _PyObject_GetAttrId((PyObject *) self, &PyId_name);
     if (nameobj == NULL) {
         if (PyErr_ExceptionMatches(PyExc_AttributeError))
             PyErr_Clear();
         if (res == NULL)
             return NULL;
     }
-    modeobj = PyObject_GetAttrString((PyObject *) self, "mode");
+    modeobj = _PyObject_GetAttrId((PyObject *) self, &PyId_mode);
     if (modeobj == NULL) {
         if (PyErr_ExceptionMatches(PyExc_AttributeError))
             PyErr_Clear();
 textiowrapper_name_get(textio *self, void *context)
 {
     CHECK_INITIALIZED(self);
-    return PyObject_GetAttrString(self->buffer, "name");
+    return _PyObject_GetAttrId(self->buffer, &PyId_name);
 }
 
 static PyObject *

File Python/import.c

     return co;
 }
 
-/* Helper to open a bytecode file for writing in exclusive mode */
-
-#ifndef MS_WINDOWS
-static FILE *
-open_exclusive(char *filename, mode_t mode)
-{
-#if defined(O_EXCL)&&defined(O_CREAT)&&defined(O_WRONLY)&&defined(O_TRUNC)
-    /* Use O_EXCL to avoid a race condition when another process tries to
-       write the same file.  When that happens, our open() call fails,
-       which is just fine (since it's only a cache).
-       XXX If the file exists and is writable but the directory is not
-       writable, the file will never be written.  Oh well.
-    */
-    int fd;
-    (void) unlink(filename);
-    fd = open(filename, O_EXCL|O_CREAT|O_WRONLY|O_TRUNC
-#ifdef O_BINARY
-                            |O_BINARY   /* necessary for Windows */
-#endif
-#ifdef __VMS
-            , mode, "ctxt=bin", "shr=nil"
-#else
-            , mode
-#endif
-          );
-    if (fd < 0)
-        return NULL;
-    return fdopen(fd, "wb");
-#else
-    /* Best we can do -- on Windows this can't happen anyway */
-    return fopen(filename, "wb");
-#endif
-}
-#endif
-
-
 /* Write a compiled module to a file, placing the time of last
    modification of its source into the header.
    Errors are ignored, if a write error occurs an attempt is made to
 #ifdef MS_WINDOWS   /* since Windows uses different permissions  */
     mode_t mode = srcstat->st_mode & ~S_IEXEC;
 #else
-    mode_t mode = srcstat->st_mode & ~S_IXUSR & ~S_IXGRP & ~S_IXOTH;
     mode_t dirmode = (srcstat->st_mode |
                       S_IXUSR | S_IXGRP | S_IXOTH |
                       S_IWUSR | S_IWGRP | S_IWOTH);
     PyObject *dirbytes;
 #endif
-#ifdef MS_WINDOWS
     int fd;
-#else
+#ifndef MS_WINDOWS
     PyObject *cpathbytes, *cpathbytes_tmp;
     Py_ssize_t cpathbytes_len;
 #endif
         return;
     }
     cpathbytes_len = PyBytes_GET_SIZE(cpathbytes);
-    cpathbytes_tmp = PyBytes_FromStringAndSize(NULL, cpathbytes_len + 4);
+    cpathbytes_tmp = PyBytes_FromStringAndSize(NULL, cpathbytes_len + 6);
     if (cpathbytes_tmp == NULL) {
         Py_DECREF(cpathbytes);
         PyErr_Clear();
     }
     memcpy(PyBytes_AS_STRING(cpathbytes_tmp), PyBytes_AS_STRING(cpathbytes),
            cpathbytes_len);
-    memcpy(PyBytes_AS_STRING(cpathbytes_tmp) + cpathbytes_len, ".tmp", 4);
-
-    fp = open_exclusive(PyBytes_AS_STRING(cpathbytes_tmp), mode);
+    memcpy(PyBytes_AS_STRING(cpathbytes_tmp) + cpathbytes_len, "XXXXXX", 6);
+
+    fd = mkstemp(PyBytes_AS_STRING(cpathbytes_tmp));
+    if (0 <= fd)
+        fp = fdopen(fd, "wb");
+    else
+        fp = NULL;
 #endif
     if (fp == NULL) {
         if (Py_VerboseFlag)