完成微波仿真、点云构网、模型导出、仿真成像功能移植

master
剑古敛锋 2024-04-11 22:01:25 +08:00
parent 51341c8e72
commit 796fac0821
2331 changed files with 90189 additions and 652837 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -59,6 +59,17 @@ direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
=============================================================== ===============================================================
Python software and documentation are licensed under the
Python Software Foundation License Version 2.
Starting with Python 3.8.6, examples, recipes, and other code in
the documentation are dual licensed under the PSF License Version 2
and the Zero-Clause BSD license.
Some software incorporated into Python is under different licenses.
The licenses are listed with code falling under that license.
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
-------------------------------------------- --------------------------------------------
@ -73,8 +84,8 @@ analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version, distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright, provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; All 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
Rights Reserved" are retained in Python alone or in any derivative version All Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee. prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on 3. In the event Licensee prepares a derivative work that is based on
@ -253,351 +264,16 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
Additional Conditions for this Windows binary build
---------------------------------------------------
This program is linked with and uses Microsoft Distributable Code,
copyrighted by Microsoft Corporation. The Microsoft Distributable Code
is embedded in each .exe, .dll and .pyd file as a result of running
the code through a linker.
If you further distribute programs that include the Microsoft
Distributable Code, you must comply with the restrictions on
distribution specified by Microsoft. In particular, you must require
distributors and external end users to agree to terms that protect the
Microsoft Distributable Code at least as much as Microsoft's own
requirements for the Distributable Code. See Microsoft's documentation
(included in its developer tools and on its website at microsoft.com)
for specific details.
Redistribution of the Windows binary build of the Python interpreter
complies with this agreement, provided that you do not:
- alter any copyright, trademark or patent notice in Microsoft's
Distributable Code;
- use Microsoft's trademarks in your programs' names or in a way that
suggests your programs come from or are endorsed by Microsoft;
- distribute Microsoft's Distributable Code to run on a platform other
than Microsoft operating systems, run-time technologies or application
platforms; or
- include Microsoft Distributable Code in malicious, deceptive or
unlawful programs.
These restrictions apply only to the Microsoft Distributable Code as
defined above, not to Python itself or any programs running on the
Python interpreter. The redistribution of the Python interpreter and
libraries is governed by the Python Software License included with this
file, or by other licenses as marked.
--------------------------------------------------------------------------
This program, "bzip2", the associated library "libbzip2", and all
documentation, are copyright (C) 1996-2010 Julian R Seward. All
rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
3. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
4. The name of the author may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Julian Seward, jseward@bzip.org
bzip2/libbzip2 version 1.0.6 of 6 September 2010
--------------------------------------------------------------------------
LICENSE ISSUES
==============
The OpenSSL toolkit stays under a double license, i.e. both the conditions of
the OpenSSL License and the original SSLeay license apply to the toolkit.
See below for the actual license texts.
OpenSSL License
---------------
/* ====================================================================
* Copyright (c) 1998-2018 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* openssl-core@openssl.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This product includes cryptographic software written by Eric Young
* (eay@cryptsoft.com). This product includes software written by Tim
* Hudson (tjh@cryptsoft.com).
*
*/
Original SSLeay License
-----------------------
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
* All rights reserved.
*
* This package is an SSL implementation written
* by Eric Young (eay@cryptsoft.com).
* The implementation was written so as to conform with Netscapes SSL.
*
* This library is free for commercial and non-commercial use as long as
* the following conditions are aheared to. The following conditions
* apply to all code found in this distribution, be it the RC4, RSA,
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
* included with this distribution is covered by the same copyright terms
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
*
* Copyright remains Eric Young's, and as such any Copyright notices in
* the code are not to be removed.
* If this package is used in a product, Eric Young should be given attribution
* as the author of the parts of the library used.
* This can be in the form of a textual message at program startup or
* in documentation (online or textual) provided with the package.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* "This product includes cryptographic software written by
* Eric Young (eay@cryptsoft.com)"
* The word 'cryptographic' can be left out if the rouines from the library
* being used are not cryptographic related :-).
* 4. If you include any Windows specific code (or a derivative thereof) from
* the apps directory (application code) you must include an acknowledgement:
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
*
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* The licence and distribution terms for any publically available version or
* derivative of this code cannot be changed. i.e. this code cannot simply be
* copied and put under another distribution licence
* [including the GNU Public Licence.]
*/
This software is copyrighted by the Regents of the University of
California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
Corporation and other parties. The following terms apply to all files
associated with the software unless explicitly disclaimed in
individual files.
The authors hereby grant permission to use, copy, modify, distribute,
and license this software and its documentation for any purpose, provided
that existing copyright notices are retained in all copies and that this
notice is included verbatim in any distributions. No written agreement,
license, or royalty fee is required for any of the authorized uses.
Modifications to this software may be copyrighted by their authors
and need not follow the licensing terms described here, provided that
the new terms are clearly indicated on the first page of each file where
they apply.
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
MODIFICATIONS.
GOVERNMENT USE: If you are acquiring this software on behalf of the
U.S. government, the Government shall have only "Restricted Rights"
in the software and related documentation as defined in the Federal
Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
are acquiring the software on behalf of the Department of Defense, the
software shall be classified as "Commercial Computer Software" and the
Government shall have only "Restricted Rights" as defined in Clause
252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the
authors grant the U.S. Government and others acting in its behalf
permission to use and distribute the software in accordance with the
terms specified in this license.
This software is copyrighted by the Regents of the University of
California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
Corporation, Apple Inc. and other parties. The following terms apply to
all files associated with the software unless explicitly disclaimed in
individual files.
The authors hereby grant permission to use, copy, modify, distribute,
and license this software and its documentation for any purpose, provided
that existing copyright notices are retained in all copies and that this
notice is included verbatim in any distributions. No written agreement,
license, or royalty fee is required for any of the authorized uses.
Modifications to this software may be copyrighted by their authors
and need not follow the licensing terms described here, provided that
the new terms are clearly indicated on the first page of each file where
they apply.
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
MODIFICATIONS.
GOVERNMENT USE: If you are acquiring this software on behalf of the
U.S. government, the Government shall have only "Restricted Rights"
in the software and related documentation as defined in the Federal
Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
are acquiring the software on behalf of the Department of Defense, the
software shall be classified as "Commercial Computer Software" and the
Government shall have only "Restricted Rights" as defined in Clause
252.227-7013 (b) (3) of DFARs. Notwithstanding the foregoing, the
authors grant the U.S. Government and others acting in its behalf
permission to use and distribute the software in accordance with the
terms specified in this license.
Copyright (c) 1993-1999 Ioi Kim Lam.
Copyright (c) 2000-2001 Tix Project Group.
Copyright (c) 2004 ActiveState
This software is copyrighted by the above entities
and other parties. The following terms apply to all files associated
with the software unless explicitly disclaimed in individual files.
The authors hereby grant permission to use, copy, modify, distribute,
and license this software and its documentation for any purpose, provided
that existing copyright notices are retained in all copies and that this
notice is included verbatim in any distributions. No written agreement,
license, or royalty fee is required for any of the authorized uses.
Modifications to this software may be copyrighted by their authors
and need not follow the licensing terms described here, provided that
the new terms are clearly indicated on the first page of each file where
they apply.
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
MODIFICATIONS.
GOVERNMENT USE: If you are acquiring this software on behalf of the
U.S. government, the Government shall have only "Restricted Rights"
in the software and related documentation as defined in the Federal
Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
are acquiring the software on behalf of the Department of Defense, the
software shall be classified as "Commercial Computer Software" and the
Government shall have only "Restricted Rights" as defined in Clause
252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the
authors grant the U.S. Government and others acting in its behalf
permission to use and distribute the software in accordance with the
terms specified in this license.
---------------------------------------------------------------------- ----------------------------------------------------------------------
Parts of this software are based on the Tcl/Tk software copyrighted by Permission to use, copy, modify, and/or distribute this software for any
the Regents of the University of California, Sun Microsystems, Inc., purpose with or without fee is hereby granted.
and other parties. The original license terms of the Tcl/Tk software
distribution is included in the file docs/license.tcltk.
Parts of this software are based on the HTML Library software
copyrighted by Sun Microsystems, Inc. The original license terms of
the HTML Library software distribution is included in the file
docs/license.html_lib.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

View File

@ -68,16 +68,18 @@ __all__ = ["all_feature_names"] + all_feature_names
# this module. # this module.
CO_NESTED = 0x0010 # nested_scopes CO_NESTED = 0x0010 # nested_scopes
CO_GENERATOR_ALLOWED = 0 # generators (obsolete, was 0x1000) CO_GENERATOR_ALLOWED = 0 # generators (obsolete, was 0x1000)
CO_FUTURE_DIVISION = 0x2000 # division CO_FUTURE_DIVISION = 0x20000 # division
CO_FUTURE_ABSOLUTE_IMPORT = 0x4000 # perform absolute imports by default CO_FUTURE_ABSOLUTE_IMPORT = 0x40000 # perform absolute imports by default
CO_FUTURE_WITH_STATEMENT = 0x8000 # with statement CO_FUTURE_WITH_STATEMENT = 0x80000 # with statement
CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function CO_FUTURE_PRINT_FUNCTION = 0x100000 # print function
CO_FUTURE_UNICODE_LITERALS = 0x20000 # unicode string literals CO_FUTURE_UNICODE_LITERALS = 0x200000 # unicode string literals
CO_FUTURE_BARRY_AS_BDFL = 0x40000 CO_FUTURE_BARRY_AS_BDFL = 0x400000
CO_FUTURE_GENERATOR_STOP = 0x80000 # StopIteration becomes RuntimeError in generators CO_FUTURE_GENERATOR_STOP = 0x800000 # StopIteration becomes RuntimeError in generators
CO_FUTURE_ANNOTATIONS = 0x100000 # annotations become strings at runtime CO_FUTURE_ANNOTATIONS = 0x1000000 # annotations become strings at runtime
class _Feature: class _Feature:
def __init__(self, optionalRelease, mandatoryRelease, compiler_flag): def __init__(self, optionalRelease, mandatoryRelease, compiler_flag):
self.optional = optionalRelease self.optional = optionalRelease
self.mandatory = mandatoryRelease self.mandatory = mandatoryRelease
@ -88,7 +90,6 @@ class _Feature:
This is a 5-tuple, of the same form as sys.version_info. This is a 5-tuple, of the same form as sys.version_info.
""" """
return self.optional return self.optional
def getMandatoryRelease(self): def getMandatoryRelease(self):
@ -97,7 +98,6 @@ class _Feature:
This is a 5-tuple, of the same form as sys.version_info, or, if This is a 5-tuple, of the same form as sys.version_info, or, if
the feature was dropped, is None. the feature was dropped, is None.
""" """
return self.mandatory return self.mandatory
def __repr__(self): def __repr__(self):
@ -105,6 +105,7 @@ class _Feature:
self.mandatory, self.mandatory,
self.compiler_flag)) self.compiler_flag))
nested_scopes = _Feature((2, 1, 0, "beta", 1), nested_scopes = _Feature((2, 1, 0, "beta", 1),
(2, 2, 0, "alpha", 0), (2, 2, 0, "alpha", 0),
CO_NESTED) CO_NESTED)
@ -134,7 +135,7 @@ unicode_literals = _Feature((2, 6, 0, "alpha", 2),
CO_FUTURE_UNICODE_LITERALS) CO_FUTURE_UNICODE_LITERALS)
barry_as_FLUFL = _Feature((3, 1, 0, "alpha", 2), barry_as_FLUFL = _Feature((3, 1, 0, "alpha", 2),
(3, 9, 0, "alpha", 0), (4, 0, 0, "alpha", 0),
CO_FUTURE_BARRY_AS_BDFL) CO_FUTURE_BARRY_AS_BDFL)
generator_stop = _Feature((3, 5, 0, "beta", 1), generator_stop = _Feature((3, 5, 0, "beta", 1),
@ -142,5 +143,5 @@ generator_stop = _Feature((3, 5, 0, "beta", 1),
CO_FUTURE_GENERATOR_STOP) CO_FUTURE_GENERATOR_STOP)
annotations = _Feature((3, 7, 0, "beta", 1), annotations = _Feature((3, 7, 0, "beta", 1),
(4, 0, 0, "alpha", 0), (3, 10, 0, "alpha", 0),
CO_FUTURE_ANNOTATIONS) CO_FUTURE_ANNOTATIONS)

View File

@ -9,6 +9,12 @@ Unit tests are in test_collections.
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import sys import sys
GenericAlias = type(list[int])
EllipsisType = type(...)
def _f(): pass
FunctionType = type(_f)
del _f
__all__ = ["Awaitable", "Coroutine", __all__ = ["Awaitable", "Coroutine",
"AsyncIterable", "AsyncIterator", "AsyncGenerator", "AsyncIterable", "AsyncIterator", "AsyncGenerator",
"Hashable", "Iterable", "Iterator", "Generator", "Reversible", "Hashable", "Iterable", "Iterator", "Generator", "Reversible",
@ -110,6 +116,8 @@ class Awaitable(metaclass=ABCMeta):
return _check_methods(C, "__await__") return _check_methods(C, "__await__")
return NotImplemented return NotImplemented
__class_getitem__ = classmethod(GenericAlias)
class Coroutine(Awaitable): class Coroutine(Awaitable):
@ -169,6 +177,8 @@ class AsyncIterable(metaclass=ABCMeta):
return _check_methods(C, "__aiter__") return _check_methods(C, "__aiter__")
return NotImplemented return NotImplemented
__class_getitem__ = classmethod(GenericAlias)
class AsyncIterator(AsyncIterable): class AsyncIterator(AsyncIterable):
@ -255,6 +265,8 @@ class Iterable(metaclass=ABCMeta):
return _check_methods(C, "__iter__") return _check_methods(C, "__iter__")
return NotImplemented return NotImplemented
__class_getitem__ = classmethod(GenericAlias)
class Iterator(Iterable): class Iterator(Iterable):
@ -274,6 +286,7 @@ class Iterator(Iterable):
return _check_methods(C, '__iter__', '__next__') return _check_methods(C, '__iter__', '__next__')
return NotImplemented return NotImplemented
Iterator.register(bytes_iterator) Iterator.register(bytes_iterator)
Iterator.register(bytearray_iterator) Iterator.register(bytearray_iterator)
#Iterator.register(callable_iterator) #Iterator.register(callable_iterator)
@ -353,6 +366,7 @@ class Generator(Iterator):
'send', 'throw', 'close') 'send', 'throw', 'close')
return NotImplemented return NotImplemented
Generator.register(generator) Generator.register(generator)
@ -385,6 +399,9 @@ class Container(metaclass=ABCMeta):
return _check_methods(C, "__contains__") return _check_methods(C, "__contains__")
return NotImplemented return NotImplemented
__class_getitem__ = classmethod(GenericAlias)
class Collection(Sized, Iterable, Container): class Collection(Sized, Iterable, Container):
__slots__ = () __slots__ = ()
@ -395,6 +412,87 @@ class Collection(Sized, Iterable, Container):
return _check_methods(C, "__len__", "__iter__", "__contains__") return _check_methods(C, "__len__", "__iter__", "__contains__")
return NotImplemented return NotImplemented
class _CallableGenericAlias(GenericAlias):
""" Represent `Callable[argtypes, resulttype]`.
This sets ``__args__`` to a tuple containing the flattened``argtypes``
followed by ``resulttype``.
Example: ``Callable[[int, str], float]`` sets ``__args__`` to
``(int, str, float)``.
"""
__slots__ = ()
def __new__(cls, origin, args):
try:
return cls.__create_ga(origin, args)
except TypeError as exc:
import warnings
warnings.warn(f'{str(exc)} '
f'(This will raise a TypeError in Python 3.10.)',
DeprecationWarning)
return GenericAlias(origin, args)
@classmethod
def __create_ga(cls, origin, args):
if not isinstance(args, tuple) or len(args) != 2:
raise TypeError(
"Callable must be used as Callable[[arg, ...], result].")
t_args, t_result = args
if isinstance(t_args, (list, tuple)):
ga_args = tuple(t_args) + (t_result,)
# This relaxes what t_args can be on purpose to allow things like
# PEP 612 ParamSpec. Responsibility for whether a user is using
# Callable[...] properly is deferred to static type checkers.
else:
ga_args = args
return super().__new__(cls, origin, ga_args)
def __repr__(self):
if len(self.__args__) == 2 and self.__args__[0] is Ellipsis:
return super().__repr__()
return (f'collections.abc.Callable'
f'[[{", ".join([_type_repr(a) for a in self.__args__[:-1]])}], '
f'{_type_repr(self.__args__[-1])}]')
def __reduce__(self):
args = self.__args__
if not (len(args) == 2 and args[0] is Ellipsis):
args = list(args[:-1]), args[-1]
return _CallableGenericAlias, (Callable, args)
def __getitem__(self, item):
# Called during TypeVar substitution, returns the custom subclass
# rather than the default types.GenericAlias object.
ga = super().__getitem__(item)
args = ga.__args__
t_result = args[-1]
t_args = args[:-1]
args = (t_args, t_result)
return _CallableGenericAlias(Callable, args)
def _type_repr(obj):
"""Return the repr() of an object, special-casing types (internal helper).
Copied from :mod:`typing` since collections.abc
shouldn't depend on that module.
"""
if isinstance(obj, GenericAlias):
return repr(obj)
if isinstance(obj, type):
if obj.__module__ == 'builtins':
return obj.__qualname__
return f'{obj.__module__}.{obj.__qualname__}'
if obj is Ellipsis:
return '...'
if isinstance(obj, FunctionType):
return obj.__name__
return repr(obj)
class Callable(metaclass=ABCMeta): class Callable(metaclass=ABCMeta):
__slots__ = () __slots__ = ()
@ -409,6 +507,8 @@ class Callable(metaclass=ABCMeta):
return _check_methods(C, "__call__") return _check_methods(C, "__call__")
return NotImplemented return NotImplemented
__class_getitem__ = classmethod(_CallableGenericAlias)
### SETS ### ### SETS ###
@ -542,6 +642,7 @@ class Set(Collection):
hx = hash(x) hx = hash(x)
h ^= (hx ^ (hx << 16) ^ 89869747) * 3644798167 h ^= (hx ^ (hx << 16) ^ 89869747) * 3644798167
h &= MASK h &= MASK
h ^= (h >> 11) ^ (h >> 25)
h = h * 69069 + 907133923 h = h * 69069 + 907133923
h &= MASK h &= MASK
if h > MAX: if h > MAX:
@ -550,6 +651,7 @@ class Set(Collection):
h = 590923713 h = 590923713
return h return h
Set.register(frozenset) Set.register(frozenset)
@ -632,6 +734,7 @@ class MutableSet(Set):
self.discard(value) self.discard(value)
return self return self
MutableSet.register(set) MutableSet.register(set)
@ -688,6 +791,7 @@ class Mapping(Collection):
__reversed__ = None __reversed__ = None
Mapping.register(mappingproxy) Mapping.register(mappingproxy)
@ -704,13 +808,15 @@ class MappingView(Sized):
def __repr__(self): def __repr__(self):
return '{0.__class__.__name__}({0._mapping!r})'.format(self) return '{0.__class__.__name__}({0._mapping!r})'.format(self)
__class_getitem__ = classmethod(GenericAlias)
class KeysView(MappingView, Set): class KeysView(MappingView, Set):
__slots__ = () __slots__ = ()
@classmethod @classmethod
def _from_iterable(self, it): def _from_iterable(cls, it):
return set(it) return set(it)
def __contains__(self, key): def __contains__(self, key):
@ -719,6 +825,7 @@ class KeysView(MappingView, Set):
def __iter__(self): def __iter__(self):
yield from self._mapping yield from self._mapping
KeysView.register(dict_keys) KeysView.register(dict_keys)
@ -727,7 +834,7 @@ class ItemsView(MappingView, Set):
__slots__ = () __slots__ = ()
@classmethod @classmethod
def _from_iterable(self, it): def _from_iterable(cls, it):
return set(it) return set(it)
def __contains__(self, item): def __contains__(self, item):
@ -743,6 +850,7 @@ class ItemsView(MappingView, Set):
for key in self._mapping: for key in self._mapping:
yield (key, self._mapping[key]) yield (key, self._mapping[key])
ItemsView.register(dict_items) ItemsView.register(dict_items)
@ -761,6 +869,7 @@ class ValuesView(MappingView, Collection):
for key in self._mapping: for key in self._mapping:
yield self._mapping[key] yield self._mapping[key]
ValuesView.register(dict_values) ValuesView.register(dict_values)
@ -821,21 +930,12 @@ class MutableMapping(Mapping):
except KeyError: except KeyError:
pass pass
def update(*args, **kwds): def update(self, other=(), /, **kwds):
''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F.
If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and has a .keys() method, does: for k in E: D[k] = E[k]
If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
In either case, this is followed by: for k, v in F.items(): D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v
''' '''
if not args:
raise TypeError("descriptor 'update' of 'MutableMapping' object "
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('update expected at most 1 arguments, got %d' %
len(args))
if args:
other = args[0]
if isinstance(other, Mapping): if isinstance(other, Mapping):
for key in other: for key in other:
self[key] = other[key] self[key] = other[key]
@ -856,6 +956,7 @@ class MutableMapping(Mapping):
self[key] = default self[key] = default
return default return default
MutableMapping.register(dict) MutableMapping.register(dict)
@ -923,6 +1024,7 @@ class Sequence(Reversible, Collection):
'S.count(value) -> integer -- return number of occurrences of value' 'S.count(value) -> integer -- return number of occurrences of value'
return sum(1 for v in self if v is value or v == value) return sum(1 for v in self if v is value or v == value)
Sequence.register(tuple) Sequence.register(tuple)
Sequence.register(str) Sequence.register(str)
Sequence.register(range) Sequence.register(range)
@ -986,6 +1088,8 @@ class MutableSequence(Sequence):
def extend(self, values): def extend(self, values):
'S.extend(iterable) -- extend sequence by appending elements from the iterable' 'S.extend(iterable) -- extend sequence by appending elements from the iterable'
if values is self:
values = list(values)
for v in values: for v in values:
self.append(v) self.append(v)
@ -1007,5 +1111,6 @@ class MutableSequence(Sequence):
self.extend(values) self.extend(values)
return self return self
MutableSequence.register(list) MutableSequence.register(list)
MutableSequence.register(bytearray) # Multiply inheriting, see ByteString MutableSequence.register(bytearray) # Multiply inheriting, see ByteString

View File

@ -1,163 +0,0 @@
"""Drop-in replacement for the thread module.
Meant to be used as a brain-dead substitute so that threaded code does
not need to be rewritten for when the thread module is not present.
Suggested usage is::
try:
import _thread
except ImportError:
import _dummy_thread as _thread
"""
# Exports only things specified by thread documentation;
# skipping obsolete synonyms allocate(), start_new(), exit_thread().
__all__ = ['error', 'start_new_thread', 'exit', 'get_ident', 'allocate_lock',
'interrupt_main', 'LockType']
# A dummy value
TIMEOUT_MAX = 2**31
# NOTE: this module can be imported early in the extension building process,
# and so top level imports of other modules should be avoided. Instead, all
# imports are done when needed on a function-by-function basis. Since threads
# are disabled, the import lock should not be an issue anyway (??).
error = RuntimeError
def start_new_thread(function, args, kwargs={}):
"""Dummy implementation of _thread.start_new_thread().
Compatibility is maintained by making sure that ``args`` is a
tuple and ``kwargs`` is a dictionary. If an exception is raised
and it is SystemExit (which can be done by _thread.exit()) it is
caught and nothing is done; all other exceptions are printed out
by using traceback.print_exc().
If the executed function calls interrupt_main the KeyboardInterrupt will be
raised when the function returns.
"""
if type(args) != type(tuple()):
raise TypeError("2nd arg must be a tuple")
if type(kwargs) != type(dict()):
raise TypeError("3rd arg must be a dict")
global _main
_main = False
try:
function(*args, **kwargs)
except SystemExit:
pass
except:
import traceback
traceback.print_exc()
_main = True
global _interrupt
if _interrupt:
_interrupt = False
raise KeyboardInterrupt
def exit():
"""Dummy implementation of _thread.exit()."""
raise SystemExit
def get_ident():
"""Dummy implementation of _thread.get_ident().
Since this module should only be used when _threadmodule is not
available, it is safe to assume that the current process is the
only thread. Thus a constant can be safely returned.
"""
return 1
def allocate_lock():
"""Dummy implementation of _thread.allocate_lock()."""
return LockType()
def stack_size(size=None):
"""Dummy implementation of _thread.stack_size()."""
if size is not None:
raise error("setting thread stack size not supported")
return 0
def _set_sentinel():
"""Dummy implementation of _thread._set_sentinel()."""
return LockType()
class LockType(object):
"""Class implementing dummy implementation of _thread.LockType.
Compatibility is maintained by maintaining self.locked_status
which is a boolean that stores the state of the lock. Pickling of
the lock, though, should not be done since if the _thread module is
then used with an unpickled ``lock()`` from here problems could
occur from this class not having atomic methods.
"""
def __init__(self):
self.locked_status = False
def acquire(self, waitflag=None, timeout=-1):
"""Dummy implementation of acquire().
For blocking calls, self.locked_status is automatically set to
True and returned appropriately based on value of
``waitflag``. If it is non-blocking, then the value is
actually checked and not set if it is already acquired. This
is all done so that threading.Condition's assert statements
aren't triggered and throw a little fit.
"""
if waitflag is None or waitflag:
self.locked_status = True
return True
else:
if not self.locked_status:
self.locked_status = True
return True
else:
if timeout > 0:
import time
time.sleep(timeout)
return False
__enter__ = acquire
def __exit__(self, typ, val, tb):
self.release()
def release(self):
"""Release the dummy lock."""
# XXX Perhaps shouldn't actually bother to test? Could lead
# to problems for complex, threaded code.
if not self.locked_status:
raise error
self.locked_status = False
return True
def locked(self):
return self.locked_status
def __repr__(self):
return "<%s %s.%s object at %s>" % (
"locked" if self.locked_status else "unlocked",
self.__class__.__module__,
self.__class__.__qualname__,
hex(id(self))
)
# Used to signal that interrupt_main was called in a "thread"
_interrupt = False
# True when not executing in a "thread"
_main = True
def interrupt_main():
"""Set _interrupt flag to True to have start_new_thread raise
KeyboardInterrupt upon exiting."""
if _main:
raise KeyboardInterrupt
else:
global _interrupt
_interrupt = True

View File

@ -157,6 +157,7 @@ class ParserBase:
match= _msmarkedsectionclose.search(rawdata, i+3) match= _msmarkedsectionclose.search(rawdata, i+3)
else: else:
self.error('unknown status keyword %r in marked section' % rawdata[i+3:j]) self.error('unknown status keyword %r in marked section' % rawdata[i+3:j])
match = None
if not match: if not match:
return -1 return -1
if report: if report:

View File

@ -17,7 +17,7 @@ __all__ = [
_UNIVERSAL_CONFIG_VARS = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', 'BASECFLAGS', _UNIVERSAL_CONFIG_VARS = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', 'BASECFLAGS',
'BLDSHARED', 'LDSHARED', 'CC', 'CXX', 'BLDSHARED', 'LDSHARED', 'CC', 'CXX',
'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS', 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS',
'PY_CORE_CFLAGS') 'PY_CORE_CFLAGS', 'PY_CORE_LDFLAGS')
# configuration variables that may contain compiler calls # configuration variables that may contain compiler calls
_COMPILER_CONFIG_VARS = ('BLDSHARED', 'LDSHARED', 'CC', 'CXX') _COMPILER_CONFIG_VARS = ('BLDSHARED', 'LDSHARED', 'CC', 'CXX')
@ -52,7 +52,7 @@ def _find_executable(executable, path=None):
return executable return executable
def _read_output(commandstring): def _read_output(commandstring, capture_stderr=False):
"""Output from successful command execution or None""" """Output from successful command execution or None"""
# Similar to os.popen(commandstring, "r").read(), # Similar to os.popen(commandstring, "r").read(),
# but without actually using os.popen because that # but without actually using os.popen because that
@ -67,6 +67,9 @@ def _read_output(commandstring):
os.getpid(),), "w+b") os.getpid(),), "w+b")
with contextlib.closing(fp) as fp: with contextlib.closing(fp) as fp:
if capture_stderr:
cmd = "%s >'%s' 2>&1" % (commandstring, fp.name)
else:
cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name) cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name)
return fp.read().decode('utf-8').strip() if not os.system(cmd) else None return fp.read().decode('utf-8').strip() if not os.system(cmd) else None
@ -110,6 +113,26 @@ def _get_system_version():
return _SYSTEM_VERSION return _SYSTEM_VERSION
_SYSTEM_VERSION_TUPLE = None
def _get_system_version_tuple():
"""
Return the macOS system version as a tuple
The return value is safe to use to compare
two version numbers.
"""
global _SYSTEM_VERSION_TUPLE
if _SYSTEM_VERSION_TUPLE is None:
osx_version = _get_system_version()
if osx_version:
try:
_SYSTEM_VERSION_TUPLE = tuple(int(i) for i in osx_version.split('.'))
except ValueError:
_SYSTEM_VERSION_TUPLE = ()
return _SYSTEM_VERSION_TUPLE
def _remove_original_values(_config_vars): def _remove_original_values(_config_vars):
"""Remove original unmodified values for testing""" """Remove original unmodified values for testing"""
# This is needed for higher-level cross-platform tests of get_platform. # This is needed for higher-level cross-platform tests of get_platform.
@ -125,6 +148,33 @@ def _save_modified_value(_config_vars, cv, newvalue):
_config_vars[_INITPRE + cv] = oldvalue _config_vars[_INITPRE + cv] = oldvalue
_config_vars[cv] = newvalue _config_vars[cv] = newvalue
_cache_default_sysroot = None
def _default_sysroot(cc):
""" Returns the root of the default SDK for this system, or '/' """
global _cache_default_sysroot
if _cache_default_sysroot is not None:
return _cache_default_sysroot
contents = _read_output('%s -c -E -v - </dev/null' % (cc,), True)
in_incdirs = False
for line in contents.splitlines():
if line.startswith("#include <...>"):
in_incdirs = True
elif line.startswith("End of search list"):
in_incdirs = False
elif in_incdirs:
line = line.strip()
if line == '/usr/include':
_cache_default_sysroot = '/'
elif line.endswith(".sdk/usr/include"):
_cache_default_sysroot = line[:-12]
if _cache_default_sysroot is None:
_cache_default_sysroot = '/'
return _cache_default_sysroot
def _supports_universal_builds(): def _supports_universal_builds():
"""Returns True if universal builds are supported on this system""" """Returns True if universal builds are supported on this system"""
# As an approximation, we assume that if we are running on 10.4 or above, # As an approximation, we assume that if we are running on 10.4 or above,
@ -132,14 +182,18 @@ def _supports_universal_builds():
# builds, in particular -isysroot and -arch arguments to the compiler. This # builds, in particular -isysroot and -arch arguments to the compiler. This
# is in support of allowing 10.4 universal builds to run on 10.3.x systems. # is in support of allowing 10.4 universal builds to run on 10.3.x systems.
osx_version = _get_system_version() osx_version = _get_system_version_tuple()
if osx_version:
try:
osx_version = tuple(int(i) for i in osx_version.split('.'))
except ValueError:
osx_version = ''
return bool(osx_version >= (10, 4)) if osx_version else False return bool(osx_version >= (10, 4)) if osx_version else False
def _supports_arm64_builds():
"""Returns True if arm64 builds are supported on this system"""
# There are two sets of systems supporting macOS/arm64 builds:
# 1. macOS 11 and later, unconditionally
# 2. macOS 10.15 with Xcode 12.2 or later
# For now the second category is ignored.
osx_version = _get_system_version_tuple()
return osx_version >= (11, 0) if osx_version else False
def _find_appropriate_compiler(_config_vars): def _find_appropriate_compiler(_config_vars):
"""Find appropriate C compiler for extension module builds""" """Find appropriate C compiler for extension module builds"""
@ -211,7 +265,7 @@ def _remove_universal_flags(_config_vars):
if cv in _config_vars and cv not in os.environ: if cv in _config_vars and cv not in os.environ:
flags = _config_vars[cv] flags = _config_vars[cv]
flags = re.sub(r'-arch\s+\w+\s', ' ', flags, flags=re.ASCII) flags = re.sub(r'-arch\s+\w+\s', ' ', flags, flags=re.ASCII)
flags = re.sub('-isysroot [^ \t]*', ' ', flags) flags = re.sub(r'-isysroot\s*\S+', ' ', flags)
_save_modified_value(_config_vars, cv, flags) _save_modified_value(_config_vars, cv, flags)
return _config_vars return _config_vars
@ -287,7 +341,7 @@ def _check_for_unavailable_sdk(_config_vars):
# to /usr and /System/Library by either a standalone CLT # to /usr and /System/Library by either a standalone CLT
# package or the CLT component within Xcode. # package or the CLT component within Xcode.
cflags = _config_vars.get('CFLAGS', '') cflags = _config_vars.get('CFLAGS', '')
m = re.search(r'-isysroot\s+(\S+)', cflags) m = re.search(r'-isysroot\s*(\S+)', cflags)
if m is not None: if m is not None:
sdk = m.group(1) sdk = m.group(1)
if not os.path.exists(sdk): if not os.path.exists(sdk):
@ -295,7 +349,7 @@ def _check_for_unavailable_sdk(_config_vars):
# Do not alter a config var explicitly overridden by env var # Do not alter a config var explicitly overridden by env var
if cv in _config_vars and cv not in os.environ: if cv in _config_vars and cv not in os.environ:
flags = _config_vars[cv] flags = _config_vars[cv]
flags = re.sub(r'-isysroot\s+\S+(?:\s|$)', ' ', flags) flags = re.sub(r'-isysroot\s*\S+(?:\s|$)', ' ', flags)
_save_modified_value(_config_vars, cv, flags) _save_modified_value(_config_vars, cv, flags)
return _config_vars return _config_vars
@ -320,7 +374,7 @@ def compiler_fixup(compiler_so, cc_args):
stripArch = stripSysroot = True stripArch = stripSysroot = True
else: else:
stripArch = '-arch' in cc_args stripArch = '-arch' in cc_args
stripSysroot = '-isysroot' in cc_args stripSysroot = any(arg for arg in cc_args if arg.startswith('-isysroot'))
if stripArch or 'ARCHFLAGS' in os.environ: if stripArch or 'ARCHFLAGS' in os.environ:
while True: while True:
@ -331,6 +385,12 @@ def compiler_fixup(compiler_so, cc_args):
except ValueError: except ValueError:
break break
elif not _supports_arm64_builds():
# Look for "-arch arm64" and drop that
for idx in reversed(range(len(compiler_so))):
if compiler_so[idx] == '-arch' and compiler_so[idx+1] == "arm64":
del compiler_so[idx:idx+2]
if 'ARCHFLAGS' in os.environ and not stripArch: if 'ARCHFLAGS' in os.environ and not stripArch:
# User specified different -arch flags in the environ, # User specified different -arch flags in the environ,
# see also distutils.sysconfig # see also distutils.sysconfig
@ -338,23 +398,34 @@ def compiler_fixup(compiler_so, cc_args):
if stripSysroot: if stripSysroot:
while True: while True:
try: indices = [i for i,x in enumerate(compiler_so) if x.startswith('-isysroot')]
index = compiler_so.index('-isysroot') if not indices:
break
index = indices[0]
if compiler_so[index] == '-isysroot':
# Strip this argument and the next one: # Strip this argument and the next one:
del compiler_so[index:index+2] del compiler_so[index:index+2]
except ValueError: else:
break # It's '-isysroot/some/path' in one arg
del compiler_so[index:index+1]
# Check if the SDK that is used during compilation actually exists, # Check if the SDK that is used during compilation actually exists,
# the universal build requires the usage of a universal SDK and not all # the universal build requires the usage of a universal SDK and not all
# users have that installed by default. # users have that installed by default.
sysroot = None sysroot = None
if '-isysroot' in cc_args: argvar = cc_args
idx = cc_args.index('-isysroot') indices = [i for i,x in enumerate(cc_args) if x.startswith('-isysroot')]
sysroot = cc_args[idx+1] if not indices:
elif '-isysroot' in compiler_so: argvar = compiler_so
idx = compiler_so.index('-isysroot') indices = [i for i,x in enumerate(compiler_so) if x.startswith('-isysroot')]
sysroot = compiler_so[idx+1]
for idx in indices:
if argvar[idx] == '-isysroot':
sysroot = argvar[idx+1]
break
else:
sysroot = argvar[idx][len('-isysroot'):]
break
if sysroot and not os.path.isdir(sysroot): if sysroot and not os.path.isdir(sysroot):
from distutils import log from distutils import log
@ -411,7 +482,7 @@ def customize_compiler(_config_vars):
This customization is performed when the first This customization is performed when the first
extension module build is requested extension module build is requested
in distutils.sysconfig.customize_compiler). in distutils.sysconfig.customize_compiler.
""" """
# Find a compiler to use for extension module builds # Find a compiler to use for extension module builds
@ -470,6 +541,8 @@ def get_platform_osx(_config_vars, osname, release, machine):
if len(archs) == 1: if len(archs) == 1:
machine = archs[0] machine = archs[0]
elif archs == ('arm64', 'x86_64'):
machine = 'universal2'
elif archs == ('i386', 'ppc'): elif archs == ('i386', 'ppc'):
machine = 'fat' machine = 'fat'
elif archs == ('i386', 'x86_64'): elif archs == ('i386', 'x86_64'):

View File

@ -32,7 +32,7 @@ class ABCMeta(type):
# external code. # external code.
_abc_invalidation_counter = 0 _abc_invalidation_counter = 0
def __new__(mcls, name, bases, namespace, **kwargs): def __new__(mcls, name, bases, namespace, /, **kwargs):
cls = super().__new__(mcls, name, bases, namespace, **kwargs) cls = super().__new__(mcls, name, bases, namespace, **kwargs)
# Compute set of abstract method names # Compute set of abstract method names
abstracts = {name abstracts = {name

View File

@ -140,8 +140,11 @@ __all__ = [
# Limits for the C version for compatibility # Limits for the C version for compatibility
'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY', 'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY',
# C version: compile time choice that enables the thread local context # C version: compile time choice that enables the thread local context (deprecated, now always true)
'HAVE_THREADS' 'HAVE_THREADS',
# C version: compile time choice that enables the coroutine local context
'HAVE_CONTEXTVAR'
] ]
__xname__ = __name__ # sys.modules lookup (--without-threads) __xname__ = __name__ # sys.modules lookup (--without-threads)
@ -172,6 +175,7 @@ ROUND_05UP = 'ROUND_05UP'
# Compatibility with the C version # Compatibility with the C version
HAVE_THREADS = True HAVE_THREADS = True
HAVE_CONTEXTVAR = True
if sys.maxsize == 2**63-1: if sys.maxsize == 2**63-1:
MAX_PREC = 999999999999999999 MAX_PREC = 999999999999999999
MAX_EMAX = 999999999999999999 MAX_EMAX = 999999999999999999
@ -5631,8 +5635,6 @@ class _WorkRep(object):
def __repr__(self): def __repr__(self):
return "(%r, %r, %r)" % (self.sign, self.int, self.exp) return "(%r, %r, %r)" % (self.sign, self.int, self.exp)
__str__ = __repr__
def _normalize(op1, op2, prec = 0): def _normalize(op1, op2, prec = 0):

View File

@ -33,6 +33,12 @@ DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes
# Rebind for compatibility # Rebind for compatibility
BlockingIOError = BlockingIOError BlockingIOError = BlockingIOError
# Does io.IOBase finalizer log the exception if the close() method fails?
# The exception is ignored silently by default in release build.
_IOBASE_EMITS_UNRAISABLE = (hasattr(sys, "gettotalrefcount") or sys.flags.dev_mode)
# Does open() check its 'errors' argument?
_CHECK_ERRORS = _IOBASE_EMITS_UNRAISABLE
def open(file, mode="r", buffering=-1, encoding=None, errors=None, def open(file, mode="r", buffering=-1, encoding=None, errors=None,
newline=None, closefd=True, opener=None): newline=None, closefd=True, opener=None):
@ -198,6 +204,11 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
raise ValueError("binary mode doesn't take an errors argument") raise ValueError("binary mode doesn't take an errors argument")
if binary and newline is not None: if binary and newline is not None:
raise ValueError("binary mode doesn't take a newline argument") raise ValueError("binary mode doesn't take a newline argument")
if binary and buffering == 1:
import warnings
warnings.warn("line buffering (buffering=1) isn't supported in binary "
"mode, the default buffer size will be used",
RuntimeWarning, 2)
raw = FileIO(file, raw = FileIO(file,
(creating and "x" or "") + (creating and "x" or "") +
(reading and "r" or "") + (reading and "r" or "") +
@ -245,11 +256,34 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
result.close() result.close()
raise raise
# Define a default pure-Python implementation for open_code()
# that does not allow hooks. Warn on first use. Defined for tests.
def _open_code_with_warning(path):
"""Opens the provided file with mode ``'rb'``. This function
should be used when the intent is to treat the contents as
executable code.
``path`` should be an absolute path.
When supported by the runtime, this function can be hooked
in order to allow embedders more control over code files.
This functionality is not supported on the current runtime.
"""
import warnings
warnings.warn("_pyio.open_code() may not be using hooks",
RuntimeWarning, 2)
return open(path, "rb")
try:
open_code = io.open_code
except AttributeError:
open_code = _open_code_with_warning
class DocDescriptor: class DocDescriptor:
"""Helper for builtins.open.__doc__ """Helper for builtins.open.__doc__
""" """
def __get__(self, obj, typ): def __get__(self, obj, typ=None):
return ( return (
"open(file, mode='r', buffering=-1, encoding=None, " "open(file, mode='r', buffering=-1, encoding=None, "
"errors=None, newline=None, closefd=True)\n\n" + "errors=None, newline=None, closefd=True)\n\n" +
@ -280,23 +314,21 @@ except AttributeError:
class IOBase(metaclass=abc.ABCMeta): class IOBase(metaclass=abc.ABCMeta):
"""The abstract base class for all I/O classes, acting on streams of """The abstract base class for all I/O classes.
bytes. There is no public constructor.
This class provides dummy implementations for many methods that This class provides dummy implementations for many methods that
derived classes can override selectively; the default implementations derived classes can override selectively; the default implementations
represent a file that cannot be read, written or seeked. represent a file that cannot be read, written or seeked.
Even though IOBase does not declare read, readinto, or write because Even though IOBase does not declare read or write because
their signatures will vary, implementations and clients should their signatures will vary, implementations and clients should
consider those methods part of the interface. Also, implementations consider those methods part of the interface. Also, implementations
may raise UnsupportedOperation when operations they do not support are may raise UnsupportedOperation when operations they do not support are
called. called.
The basic type used for binary data read from or written to a file is The basic type used for binary data read from or written to a file is
bytes. Other bytes-like objects are accepted as method arguments too. In bytes. Other bytes-like objects are accepted as method arguments too.
some cases (such as readinto), a writable object is required. Text I/O Text I/O classes work with str data.
classes work with str data.
Note that calling any method (even inquiries) on a closed stream is Note that calling any method (even inquiries) on a closed stream is
undefined. Implementations may raise OSError in this case. undefined. Implementations may raise OSError in this case.
@ -374,6 +406,19 @@ class IOBase(metaclass=abc.ABCMeta):
def __del__(self): def __del__(self):
"""Destructor. Calls close().""" """Destructor. Calls close()."""
try:
closed = self.closed
except AttributeError:
# If getting closed fails, then the object is probably
# in an unusable state, so ignore.
return
if closed:
return
if _IOBASE_EMITS_UNRAISABLE:
self.close()
else:
# The try/except block is in case this is called at program # The try/except block is in case this is called at program
# exit time, when it's possible that globals have already been # exit time, when it's possible that globals have already been
# deleted, and then the close() call might fail. Since # deleted, and then the close() call might fail. Since
@ -547,6 +592,11 @@ class IOBase(metaclass=abc.ABCMeta):
return lines return lines
def writelines(self, lines): def writelines(self, lines):
"""Write a list of lines to the stream.
Line separators are not added, so it is usual for each of the lines
provided to have a line separator at the end.
"""
self._checkClosed() self._checkClosed()
for line in lines: for line in lines:
self.write(line) self.write(line)
@ -753,6 +803,9 @@ class _BufferedIOMixin(BufferedIOBase):
return pos return pos
def truncate(self, pos=None): def truncate(self, pos=None):
self._checkClosed()
self._checkWritable()
# Flush the stream. We're mixing buffered I/O with lower-level I/O, # Flush the stream. We're mixing buffered I/O with lower-level I/O,
# and a flush may be necessary to synch both views of the current # and a flush may be necessary to synch both views of the current
# file state. # file state.
@ -809,15 +862,14 @@ class _BufferedIOMixin(BufferedIOBase):
return self.raw.mode return self.raw.mode
def __getstate__(self): def __getstate__(self):
raise TypeError("can not serialize a '{0}' object" raise TypeError(f"cannot pickle {self.__class__.__name__!r} object")
.format(self.__class__.__name__))
def __repr__(self): def __repr__(self):
modname = self.__class__.__module__ modname = self.__class__.__module__
clsname = self.__class__.__qualname__ clsname = self.__class__.__qualname__
try: try:
name = self.name name = self.name
except Exception: except AttributeError:
return "<{}.{}>".format(modname, clsname) return "<{}.{}>".format(modname, clsname)
else: else:
return "<{}.{} name={!r}>".format(modname, clsname, name) return "<{}.{} name={!r}>".format(modname, clsname, name)
@ -835,6 +887,10 @@ class BytesIO(BufferedIOBase):
"""Buffered I/O implementation using an in-memory bytes buffer.""" """Buffered I/O implementation using an in-memory bytes buffer."""
# Initialize _buffer as soon as possible since it's used by __del__()
# which calls close()
_buffer = None
def __init__(self, initial_bytes=None): def __init__(self, initial_bytes=None):
buf = bytearray() buf = bytearray()
if initial_bytes is not None: if initial_bytes is not None:
@ -862,6 +918,7 @@ class BytesIO(BufferedIOBase):
return memoryview(self._buffer) return memoryview(self._buffer)
def close(self): def close(self):
if self._buffer is not None:
self._buffer.clear() self._buffer.clear()
super().close() super().close()
@ -1518,7 +1575,7 @@ class FileIO(RawIOBase):
raise IsADirectoryError(errno.EISDIR, raise IsADirectoryError(errno.EISDIR,
os.strerror(errno.EISDIR), file) os.strerror(errno.EISDIR), file)
except AttributeError: except AttributeError:
# Ignore the AttribueError if stat.S_ISDIR or errno.EISDIR # Ignore the AttributeError if stat.S_ISDIR or errno.EISDIR
# don't exist. # don't exist.
pass pass
self._blksize = getattr(fdfstat, 'st_blksize', 0) self._blksize = getattr(fdfstat, 'st_blksize', 0)
@ -1534,7 +1591,11 @@ class FileIO(RawIOBase):
# For consistent behaviour, we explicitly seek to the # For consistent behaviour, we explicitly seek to the
# end of file (otherwise, it might be done only on the # end of file (otherwise, it might be done only on the
# first write()). # first write()).
try:
os.lseek(fd, 0, SEEK_END) os.lseek(fd, 0, SEEK_END)
except OSError as e:
if e.errno != errno.ESPIPE:
raise
except: except:
if owned_fd is not None: if owned_fd is not None:
os.close(owned_fd) os.close(owned_fd)
@ -1549,7 +1610,7 @@ class FileIO(RawIOBase):
self.close() self.close()
def __getstate__(self): def __getstate__(self):
raise TypeError("cannot serialize '%s' object", self.__class__.__name__) raise TypeError(f"cannot pickle {self.__class__.__name__!r} object")
def __repr__(self): def __repr__(self):
class_name = '%s.%s' % (self.__class__.__module__, class_name = '%s.%s' % (self.__class__.__module__,
@ -1759,8 +1820,7 @@ class TextIOBase(IOBase):
"""Base class for text I/O. """Base class for text I/O.
This class provides a character and line based interface to stream This class provides a character and line based interface to stream
I/O. There is no readinto method because Python's character strings I/O.
are immutable. There is no public constructor.
""" """
def read(self, size=-1): def read(self, size=-1):
@ -1933,6 +1993,10 @@ class TextIOWrapper(TextIOBase):
_CHUNK_SIZE = 2048 _CHUNK_SIZE = 2048
# Initialize _buffer as soon as possible since it's used by __del__()
# which calls close()
_buffer = None
# The write_through argument has no effect here since this # The write_through argument has no effect here since this
# implementation always writes through. The argument is present only # implementation always writes through. The argument is present only
# so that the signature can match the signature of the C version. # so that the signature can match the signature of the C version.
@ -1966,6 +2030,8 @@ class TextIOWrapper(TextIOBase):
else: else:
if not isinstance(errors, str): if not isinstance(errors, str):
raise ValueError("invalid errors: %r" % errors) raise ValueError("invalid errors: %r" % errors)
if _CHECK_ERRORS:
codecs.lookup_error(errors)
self._buffer = buffer self._buffer = buffer
self._decoded_chars = '' # buffer for text returned from decoder self._decoded_chars = '' # buffer for text returned from decoder
@ -2023,13 +2089,13 @@ class TextIOWrapper(TextIOBase):
self.__class__.__qualname__) self.__class__.__qualname__)
try: try:
name = self.name name = self.name
except Exception: except AttributeError:
pass pass
else: else:
result += " name={0!r}".format(name) result += " name={0!r}".format(name)
try: try:
mode = self.mode mode = self.mode
except Exception: except AttributeError:
pass pass
else: else:
result += " mode={0!r}".format(mode) result += " mode={0!r}".format(mode)
@ -2149,6 +2215,7 @@ class TextIOWrapper(TextIOBase):
self.buffer.write(b) self.buffer.write(b)
if self._line_buffering and (haslf or "\r" in s): if self._line_buffering and (haslf or "\r" in s):
self.flush() self.flush()
self._set_decoded_chars('')
self._snapshot = None self._snapshot = None
if self._decoder: if self._decoder:
self._decoder.reset() self._decoder.reset()
@ -2234,7 +2301,7 @@ class TextIOWrapper(TextIOBase):
return not eof return not eof
def _pack_cookie(self, position, dec_flags=0, def _pack_cookie(self, position, dec_flags=0,
bytes_to_feed=0, need_eof=0, chars_to_skip=0): bytes_to_feed=0, need_eof=False, chars_to_skip=0):
# The meaning of a tell() cookie is: seek to position, set the # The meaning of a tell() cookie is: seek to position, set the
# decoder flags to dec_flags, read bytes_to_feed bytes, feed them # decoder flags to dec_flags, read bytes_to_feed bytes, feed them
# into the decoder with need_eof as the EOF flag, then skip # into the decoder with need_eof as the EOF flag, then skip
@ -2248,7 +2315,7 @@ class TextIOWrapper(TextIOBase):
rest, dec_flags = divmod(rest, 1<<64) rest, dec_flags = divmod(rest, 1<<64)
rest, bytes_to_feed = divmod(rest, 1<<64) rest, bytes_to_feed = divmod(rest, 1<<64)
need_eof, chars_to_skip = divmod(rest, 1<<64) need_eof, chars_to_skip = divmod(rest, 1<<64)
return position, dec_flags, bytes_to_feed, need_eof, chars_to_skip return position, dec_flags, bytes_to_feed, bool(need_eof), chars_to_skip
def tell(self): def tell(self):
if not self._seekable: if not self._seekable:
@ -2282,7 +2349,7 @@ class TextIOWrapper(TextIOBase):
# current pos. # current pos.
# Rationale: calling decoder.decode() has a large overhead # Rationale: calling decoder.decode() has a large overhead
# regardless of chunk size; we want the number of such calls to # regardless of chunk size; we want the number of such calls to
# be O(1) in most situations (common decoders, non-crazy input). # be O(1) in most situations (common decoders, sensible input).
# Actually, it will be exactly 1 for fixed-size codecs (all # Actually, it will be exactly 1 for fixed-size codecs (all
# 8-bit codecs, also UTF-16 and UTF-32). # 8-bit codecs, also UTF-16 and UTF-32).
skip_bytes = int(self._b2cratio * chars_to_skip) skip_bytes = int(self._b2cratio * chars_to_skip)
@ -2322,7 +2389,7 @@ class TextIOWrapper(TextIOBase):
# (a point where the decoder has nothing buffered, so seek() # (a point where the decoder has nothing buffered, so seek()
# can safely start from there and advance to this location). # can safely start from there and advance to this location).
bytes_fed = 0 bytes_fed = 0
need_eof = 0 need_eof = False
# Chars decoded since `start_pos` # Chars decoded since `start_pos`
chars_decoded = 0 chars_decoded = 0
for i in range(skip_bytes, len(next_input)): for i in range(skip_bytes, len(next_input)):
@ -2339,7 +2406,7 @@ class TextIOWrapper(TextIOBase):
else: else:
# We didn't get enough decoded data; signal EOF to get more. # We didn't get enough decoded data; signal EOF to get more.
chars_decoded += len(decoder.decode(b'', final=True)) chars_decoded += len(decoder.decode(b'', final=True))
need_eof = 1 need_eof = True
if chars_decoded < chars_to_skip: if chars_decoded < chars_to_skip:
raise OSError("can't reconstruct logical file position") raise OSError("can't reconstruct logical file position")
@ -2381,18 +2448,18 @@ class TextIOWrapper(TextIOBase):
raise ValueError("tell on closed file") raise ValueError("tell on closed file")
if not self._seekable: if not self._seekable:
raise UnsupportedOperation("underlying stream is not seekable") raise UnsupportedOperation("underlying stream is not seekable")
if whence == 1: # seek relative to current position if whence == SEEK_CUR:
if cookie != 0: if cookie != 0:
raise UnsupportedOperation("can't do nonzero cur-relative seeks") raise UnsupportedOperation("can't do nonzero cur-relative seeks")
# Seeking to the current position should attempt to # Seeking to the current position should attempt to
# sync the underlying buffer with the current position. # sync the underlying buffer with the current position.
whence = 0 whence = 0
cookie = self.tell() cookie = self.tell()
if whence == 2: # seek relative to end of file elif whence == SEEK_END:
if cookie != 0: if cookie != 0:
raise UnsupportedOperation("can't do nonzero end-relative seeks") raise UnsupportedOperation("can't do nonzero end-relative seeks")
self.flush() self.flush()
position = self.buffer.seek(0, 2) position = self.buffer.seek(0, whence)
self._set_decoded_chars('') self._set_decoded_chars('')
self._snapshot = None self._snapshot = None
if self._decoder: if self._decoder:

View File

@ -77,15 +77,6 @@ class LocaleTime(object):
if time.tzname != self.tzname or time.daylight != self.daylight: if time.tzname != self.tzname or time.daylight != self.daylight:
raise ValueError("timezone changed during initialization") raise ValueError("timezone changed during initialization")
def __pad(self, seq, front):
# Add '' to seq to either the front (is True), else the back.
seq = list(seq)
if front:
seq.insert(0, '')
else:
seq.append('')
return seq
def __calc_weekday(self): def __calc_weekday(self):
# Set self.a_weekday and self.f_weekday using the calendar # Set self.a_weekday and self.f_weekday using the calendar
# module. # module.
@ -191,7 +182,7 @@ class TimeRE(dict):
self.locale_time = LocaleTime() self.locale_time = LocaleTime()
base = super() base = super()
base.__init__({ base.__init__({
# The " \d" part of the regex is to make %c from ANSI C work # The " [1-9]" part of the regex is to make %c from ANSI C work
'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])", 'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
'f': r"(?P<f>[0-9]{1,6})", 'f': r"(?P<f>[0-9]{1,6})",
'H': r"(?P<H>2[0-3]|[0-1]\d|\d)", 'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
@ -210,7 +201,7 @@ class TimeRE(dict):
#XXX: Does 'Y' need to worry about having less or more than #XXX: Does 'Y' need to worry about having less or more than
# 4 digits? # 4 digits?
'Y': r"(?P<Y>\d\d\d\d)", 'Y': r"(?P<Y>\d\d\d\d)",
'z': r"(?P<z>[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|Z)", 'z': r"(?P<z>[+-]\d\d:?[0-5]\d(:?[0-5]\d(\.\d{1,6})?)?|(?-i:Z))",
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'), 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'), 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'), 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),

View File

@ -56,7 +56,7 @@ You can create custom local objects by subclassing the local class:
>>> class MyLocal(local): >>> class MyLocal(local):
... number = 2 ... number = 2
... def __init__(self, **kw): ... def __init__(self, /, **kw):
... self.__dict__.update(kw) ... self.__dict__.update(kw)
... def squared(self): ... def squared(self):
... return self.number ** 2 ... return self.number ** 2
@ -204,7 +204,7 @@ def _patch(self):
class local: class local:
__slots__ = '_local__impl', '__dict__' __slots__ = '_local__impl', '__dict__'
def __new__(cls, *args, **kw): def __new__(cls, /, *args, **kw):
if (args or kw) and (cls.__init__ is object.__init__): if (args or kw) and (cls.__init__ is object.__init__):
raise TypeError("Initialization arguments are not supported") raise TypeError("Initialization arguments are not supported")
self = object.__new__(cls) self = object.__new__(cls)

View File

@ -3,6 +3,7 @@
# by abc.py to load everything else at startup. # by abc.py to load everything else at startup.
from _weakref import ref from _weakref import ref
from types import GenericAlias
__all__ = ['WeakSet'] __all__ = ['WeakSet']
@ -50,10 +51,14 @@ class WeakSet:
self.update(data) self.update(data)
def _commit_removals(self): def _commit_removals(self):
l = self._pending_removals pop = self._pending_removals.pop
discard = self.data.discard discard = self.data.discard
while l: while True:
discard(l.pop()) try:
item = pop()
except IndexError:
return
discard(item)
def __iter__(self): def __iter__(self):
with _IterationGuard(self): with _IterationGuard(self):
@ -194,3 +199,8 @@ class WeakSet:
def isdisjoint(self, other): def isdisjoint(self, other):
return len(self.intersection(other)) == 0 return len(self.intersection(other)) == 0
def __repr__(self):
return repr(self.data)
__class_getitem__ = classmethod(GenericAlias)

View File

@ -11,7 +11,8 @@ def abstractmethod(funcobj):
class that has a metaclass derived from ABCMeta cannot be class that has a metaclass derived from ABCMeta cannot be
instantiated unless all of its abstract methods are overridden. instantiated unless all of its abstract methods are overridden.
The abstract methods can be called using any of the normal The abstract methods can be called using any of the normal
'super' call mechanisms. 'super' call mechanisms. abstractmethod() may be used to declare
abstract methods for properties and descriptors.
Usage: Usage:
@ -27,17 +28,14 @@ def abstractmethod(funcobj):
class abstractclassmethod(classmethod): class abstractclassmethod(classmethod):
"""A decorator indicating abstract classmethods. """A decorator indicating abstract classmethods.
Similar to abstractmethod. Deprecated, use 'classmethod' with 'abstractmethod' instead:
Usage: class C(ABC):
@classmethod
class C(metaclass=ABCMeta): @abstractmethod
@abstractclassmethod
def my_abstract_classmethod(cls, ...): def my_abstract_classmethod(cls, ...):
... ...
'abstractclassmethod' is deprecated. Use 'classmethod' with
'abstractmethod' instead.
""" """
__isabstractmethod__ = True __isabstractmethod__ = True
@ -50,17 +48,14 @@ class abstractclassmethod(classmethod):
class abstractstaticmethod(staticmethod): class abstractstaticmethod(staticmethod):
"""A decorator indicating abstract staticmethods. """A decorator indicating abstract staticmethods.
Similar to abstractmethod. Deprecated, use 'staticmethod' with 'abstractmethod' instead:
Usage: class C(ABC):
@staticmethod
class C(metaclass=ABCMeta): @abstractmethod
@abstractstaticmethod
def my_abstract_staticmethod(...): def my_abstract_staticmethod(...):
... ...
'abstractstaticmethod' is deprecated. Use 'staticmethod' with
'abstractmethod' instead.
""" """
__isabstractmethod__ = True __isabstractmethod__ = True
@ -73,29 +68,14 @@ class abstractstaticmethod(staticmethod):
class abstractproperty(property): class abstractproperty(property):
"""A decorator indicating abstract properties. """A decorator indicating abstract properties.
Requires that the metaclass is ABCMeta or derived from it. A Deprecated, use 'property' with 'abstractmethod' instead:
class that has a metaclass derived from ABCMeta cannot be
instantiated unless all of its abstract properties are overridden.
The abstract properties can be called using any of the normal
'super' call mechanisms.
Usage: class C(ABC):
@property
class C(metaclass=ABCMeta): @abstractmethod
@abstractproperty
def my_abstract_property(self): def my_abstract_property(self):
... ...
This defines a read-only property; you can also define a read-write
abstract property using the 'long' form of property declaration:
class C(metaclass=ABCMeta):
def getx(self): ...
def setx(self, value): ...
x = abstractproperty(getx, setx)
'abstractproperty' is deprecated. Use 'property' with 'abstractmethod'
instead.
""" """
__isabstractmethod__ = True __isabstractmethod__ = True

View File

@ -138,7 +138,7 @@ import struct
import builtins import builtins
import warnings import warnings
__all__ = ["Error", "open", "openfp"] __all__ = ["Error", "open"]
class Error(Exception): class Error(Exception):
pass pass
@ -920,10 +920,6 @@ def open(f, mode=None):
else: else:
raise Error("mode must be 'r', 'rb', 'w', or 'wb'") raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
def openfp(f, mode=None):
warnings.warn("aifc.openfp is deprecated since Python 3.7. "
"Use aifc.open instead.", DeprecationWarning, stacklevel=2)
return open(f, mode=mode)
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys

View File

@ -11,7 +11,7 @@ def geohash(latitude, longitude, datedow):
37.857713 -122.544543 37.857713 -122.544543
''' '''
# http://xkcd.com/426/ # https://xkcd.com/426/
h = hashlib.md5(datedow).hexdigest() h = hashlib.md5(datedow, usedforsecurity=False).hexdigest()
p, q = [('%f' % float.fromhex('0.' + x)) for x in (h[:16], h[16:32])] p, q = [('%f' % float.fromhex('0.' + x)) for x in (h[:16], h[16:32])]
print('%d%s %d%s' % (latitude, p[1:], longitude, q[1:])) print('%d%s %d%s' % (latitude, p[1:], longitude, q[1:]))

View File

@ -1,4 +1,5 @@
# Author: Steven J. Bethard <steven.bethard@gmail.com>. # Author: Steven J. Bethard <steven.bethard@gmail.com>.
# New maintainer as of 29 August 2019: Raymond Hettinger <raymond.hettinger@gmail.com>
"""Command-line parsing library """Command-line parsing library
@ -66,6 +67,7 @@ __all__ = [
'ArgumentParser', 'ArgumentParser',
'ArgumentError', 'ArgumentError',
'ArgumentTypeError', 'ArgumentTypeError',
'BooleanOptionalAction',
'FileType', 'FileType',
'HelpFormatter', 'HelpFormatter',
'ArgumentDefaultsHelpFormatter', 'ArgumentDefaultsHelpFormatter',
@ -127,7 +129,7 @@ class _AttributeHolder(object):
return '%s(%s)' % (type_name, ', '.join(arg_strings)) return '%s(%s)' % (type_name, ', '.join(arg_strings))
def _get_kwargs(self): def _get_kwargs(self):
return sorted(self.__dict__.items()) return list(self.__dict__.items())
def _get_args(self): def _get_args(self):
return [] return []
@ -164,15 +166,12 @@ class HelpFormatter(object):
# default setting for width # default setting for width
if width is None: if width is None:
try: import shutil
width = int(_os.environ['COLUMNS']) width = shutil.get_terminal_size().columns
except (KeyError, ValueError):
width = 80
width -= 2 width -= 2
self._prog = prog self._prog = prog
self._indent_increment = indent_increment self._indent_increment = indent_increment
self._max_help_position = max_help_position
self._max_help_position = min(max_help_position, self._max_help_position = min(max_help_position,
max(width - 20, indent_increment * 2)) max(width - 20, indent_increment * 2))
self._width = width self._width = width
@ -265,7 +264,7 @@ class HelpFormatter(object):
invocations.append(get_invocation(subaction)) invocations.append(get_invocation(subaction))
# update the maximum item length # update the maximum item length
invocation_length = max([len(s) for s in invocations]) invocation_length = max(map(len, invocations))
action_length = invocation_length + self._current_indent action_length = invocation_length + self._current_indent
self._action_max_length = max(self._action_max_length, self._action_max_length = max(self._action_max_length,
action_length) action_length)
@ -393,6 +392,9 @@ class HelpFormatter(object):
group_actions = set() group_actions = set()
inserts = {} inserts = {}
for group in groups: for group in groups:
if not group._group_actions:
raise ValueError(f'empty group {group}')
try: try:
start = actions.index(group._group_actions[0]) start = actions.index(group._group_actions[0])
except ValueError: except ValueError:
@ -407,12 +409,18 @@ class HelpFormatter(object):
inserts[start] += ' [' inserts[start] += ' ['
else: else:
inserts[start] = '[' inserts[start] = '['
if end in inserts:
inserts[end] += ']'
else:
inserts[end] = ']' inserts[end] = ']'
else: else:
if start in inserts: if start in inserts:
inserts[start] += ' (' inserts[start] += ' ('
else: else:
inserts[start] = '(' inserts[start] = '('
if end in inserts:
inserts[end] += ')'
else:
inserts[end] = ')' inserts[end] = ')'
for i in range(start + 1, end): for i in range(start + 1, end):
inserts[i] = '|' inserts[i] = '|'
@ -450,7 +458,7 @@ class HelpFormatter(object):
# if the Optional doesn't take a value, format is: # if the Optional doesn't take a value, format is:
# -s or --long # -s or --long
if action.nargs == 0: if action.nargs == 0:
part = '%s' % option_string part = action.format_usage()
# if the Optional takes a value, format is: # if the Optional takes a value, format is:
# -s ARGS or --long ARGS # -s ARGS or --long ARGS
@ -521,8 +529,9 @@ class HelpFormatter(object):
parts = [action_header] parts = [action_header]
# if there was help for the action, add lines of help text # if there was help for the action, add lines of help text
if action.help: if action.help and action.help.strip():
help_text = self._expand_help(action) help_text = self._expand_help(action)
if help_text:
help_lines = self._split_lines(help_text, help_width) help_lines = self._split_lines(help_text, help_width)
parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
for line in help_lines[1:]: for line in help_lines[1:]:
@ -586,7 +595,11 @@ class HelpFormatter(object):
elif action.nargs == OPTIONAL: elif action.nargs == OPTIONAL:
result = '[%s]' % get_metavar(1) result = '[%s]' % get_metavar(1)
elif action.nargs == ZERO_OR_MORE: elif action.nargs == ZERO_OR_MORE:
result = '[%s [%s ...]]' % get_metavar(2) metavar = get_metavar(1)
if len(metavar) == 2:
result = '[%s [%s ...]]' % metavar
else:
result = '[%s ...]' % metavar
elif action.nargs == ONE_OR_MORE: elif action.nargs == ONE_OR_MORE:
result = '%s [%s ...]' % get_metavar(2) result = '%s [%s ...]' % get_metavar(2)
elif action.nargs == REMAINDER: elif action.nargs == REMAINDER:
@ -596,7 +609,10 @@ class HelpFormatter(object):
elif action.nargs == SUPPRESS: elif action.nargs == SUPPRESS:
result = '' result = ''
else: else:
try:
formats = ['%s' for _ in range(action.nargs)] formats = ['%s' for _ in range(action.nargs)]
except TypeError:
raise ValueError("invalid nargs value") from None
result = ' '.join(formats) % get_metavar(action.nargs) result = ' '.join(formats) % get_metavar(action.nargs)
return result return result
@ -715,6 +731,8 @@ def _get_action_name(argument):
return argument.metavar return argument.metavar
elif argument.dest not in (None, SUPPRESS): elif argument.dest not in (None, SUPPRESS):
return argument.dest return argument.dest
elif argument.choices:
return '{' + ','.join(argument.choices) + '}'
else: else:
return None return None
@ -830,14 +848,58 @@ class Action(_AttributeHolder):
'default', 'default',
'type', 'type',
'choices', 'choices',
'required',
'help', 'help',
'metavar', 'metavar',
] ]
return [(name, getattr(self, name)) for name in names] return [(name, getattr(self, name)) for name in names]
def format_usage(self):
return self.option_strings[0]
def __call__(self, parser, namespace, values, option_string=None): def __call__(self, parser, namespace, values, option_string=None):
raise NotImplementedError(_('.__call__() not defined')) raise NotImplementedError(_('.__call__() not defined'))
class BooleanOptionalAction(Action):
def __init__(self,
option_strings,
dest,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar=None):
_option_strings = []
for option_string in option_strings:
_option_strings.append(option_string)
if option_string.startswith('--'):
option_string = '--no-' + option_string[2:]
_option_strings.append(option_string)
if help is not None and default is not None and default is not SUPPRESS:
help += " (default: %(default)s)"
super().__init__(
option_strings=_option_strings,
dest=dest,
nargs=0,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
if option_string in self.option_strings:
setattr(namespace, self.dest, not option_string.startswith('--no-'))
def format_usage(self):
return ' | '.join(self.option_strings)
class _StoreAction(Action): class _StoreAction(Action):
@ -853,7 +915,7 @@ class _StoreAction(Action):
help=None, help=None,
metavar=None): metavar=None):
if nargs == 0: if nargs == 0:
raise ValueError('nargs for store actions must be > 0; if you ' raise ValueError('nargs for store actions must be != 0; if you '
'have nothing to store, actions such as store ' 'have nothing to store, actions such as store '
'true or store const may be more appropriate') 'true or store const may be more appropriate')
if const is not None and nargs != OPTIONAL: if const is not None and nargs != OPTIONAL:
@ -945,7 +1007,7 @@ class _AppendAction(Action):
help=None, help=None,
metavar=None): metavar=None):
if nargs == 0: if nargs == 0:
raise ValueError('nargs for append actions must be > 0; if arg ' raise ValueError('nargs for append actions must be != 0; if arg '
'strings are not supplying the value to append, ' 'strings are not supplying the value to append, '
'the append const action may be more appropriate') 'the append const action may be more appropriate')
if const is not None and nargs != OPTIONAL: if const is not None and nargs != OPTIONAL:
@ -1157,6 +1219,12 @@ class _SubParsersAction(Action):
vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, []) vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
class _ExtendAction(_AppendAction):
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest, None)
items = _copy_items(items)
items.extend(values)
setattr(namespace, self.dest, items)
# ============== # ==============
# Type classes # Type classes
@ -1189,9 +1257,9 @@ class FileType(object):
# the special argument "-" means sys.std{in,out} # the special argument "-" means sys.std{in,out}
if string == '-': if string == '-':
if 'r' in self._mode: if 'r' in self._mode:
return _sys.stdin return _sys.stdin.buffer if 'b' in self._mode else _sys.stdin
elif 'w' in self._mode: elif any(c in self._mode for c in 'wax'):
return _sys.stdout return _sys.stdout.buffer if 'b' in self._mode else _sys.stdout
else: else:
msg = _('argument "-" with mode %r') % self._mode msg = _('argument "-" with mode %r') % self._mode
raise ValueError(msg) raise ValueError(msg)
@ -1201,8 +1269,9 @@ class FileType(object):
return open(string, self._mode, self._bufsize, self._encoding, return open(string, self._mode, self._bufsize, self._encoding,
self._errors) self._errors)
except OSError as e: except OSError as e:
message = _("can't open '%s': %s") args = {'filename': string, 'error': e}
raise ArgumentTypeError(message % (string, e)) message = _("can't open '%(filename)s': %(error)s")
raise ArgumentTypeError(message % args)
def __repr__(self): def __repr__(self):
args = self._mode, self._bufsize args = self._mode, self._bufsize
@ -1265,6 +1334,7 @@ class _ActionsContainer(object):
self.register('action', 'help', _HelpAction) self.register('action', 'help', _HelpAction)
self.register('action', 'version', _VersionAction) self.register('action', 'version', _VersionAction)
self.register('action', 'parsers', _SubParsersAction) self.register('action', 'parsers', _SubParsersAction)
self.register('action', 'extend', _ExtendAction)
# raise an exception if the conflict handler is invalid # raise an exception if the conflict handler is invalid
self._get_handler() self._get_handler()
@ -1357,6 +1427,10 @@ class _ActionsContainer(object):
if not callable(type_func): if not callable(type_func):
raise ValueError('%r is not callable' % (type_func,)) raise ValueError('%r is not callable' % (type_func,))
if type_func is FileType:
raise ValueError('%r is a FileType class object, instance of it'
' must be passed' % (type_func,))
# raise an error if the metavar does not match the type # raise an error if the metavar does not match the type
if hasattr(self, "_get_formatter"): if hasattr(self, "_get_formatter"):
try: try:
@ -1471,9 +1545,7 @@ class _ActionsContainer(object):
# strings starting with two prefix characters are long options # strings starting with two prefix characters are long options
option_strings.append(option_string) option_strings.append(option_string)
if option_string[0] in self.prefix_chars: if len(option_string) > 1 and option_string[1] in self.prefix_chars:
if len(option_string) > 1:
if option_string[1] in self.prefix_chars:
long_option_strings.append(option_string) long_option_strings.append(option_string)
# infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
@ -1614,6 +1686,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
- conflict_handler -- String indicating how to handle conflicts - conflict_handler -- String indicating how to handle conflicts
- add_help -- Add a -h/-help option - add_help -- Add a -h/-help option
- allow_abbrev -- Allow long options to be abbreviated unambiguously - allow_abbrev -- Allow long options to be abbreviated unambiguously
- exit_on_error -- Determines whether or not ArgumentParser exits with
error info when an error occurs
""" """
def __init__(self, def __init__(self,
@ -1628,7 +1702,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
argument_default=None, argument_default=None,
conflict_handler='error', conflict_handler='error',
add_help=True, add_help=True,
allow_abbrev=True): allow_abbrev=True,
exit_on_error=True):
superinit = super(ArgumentParser, self).__init__ superinit = super(ArgumentParser, self).__init__
superinit(description=description, superinit(description=description,
@ -1647,6 +1722,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
self.fromfile_prefix_chars = fromfile_prefix_chars self.fromfile_prefix_chars = fromfile_prefix_chars
self.add_help = add_help self.add_help = add_help
self.allow_abbrev = allow_abbrev self.allow_abbrev = allow_abbrev
self.exit_on_error = exit_on_error
add_group = self.add_argument_group add_group = self.add_argument_group
self._positionals = add_group(_('positional arguments')) self._positionals = add_group(_('positional arguments'))
@ -1777,15 +1853,19 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
setattr(namespace, dest, self._defaults[dest]) setattr(namespace, dest, self._defaults[dest])
# parse the arguments and exit if there are any errors # parse the arguments and exit if there are any errors
if self.exit_on_error:
try: try:
namespace, args = self._parse_known_args(args, namespace) namespace, args = self._parse_known_args(args, namespace)
except ArgumentError:
err = _sys.exc_info()[1]
self.error(str(err))
else:
namespace, args = self._parse_known_args(args, namespace)
if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
return namespace, args return namespace, args
except ArgumentError:
err = _sys.exc_info()[1]
self.error(str(err))
def _parse_known_args(self, arg_strings, namespace): def _parse_known_args(self, arg_strings, namespace):
# replace arg strings that are file references # replace arg strings that are file references
@ -2074,10 +2154,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
OPTIONAL: _('expected at most one argument'), OPTIONAL: _('expected at most one argument'),
ONE_OR_MORE: _('expected at least one argument'), ONE_OR_MORE: _('expected at least one argument'),
} }
default = ngettext('expected %s argument', msg = nargs_errors.get(action.nargs)
if msg is None:
msg = ngettext('expected %s argument',
'expected %s arguments', 'expected %s arguments',
action.nargs) % action.nargs action.nargs) % action.nargs
msg = nargs_errors.get(action.nargs, default)
raise ArgumentError(action, msg) raise ArgumentError(action, msg)
# return the number of arguments matched # return the number of arguments matched
@ -2124,7 +2205,6 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
action = self._option_string_actions[option_string] action = self._option_string_actions[option_string]
return action, option_string, explicit_arg return action, option_string, explicit_arg
if self.allow_abbrev:
# search through all possible prefixes of the option string # search through all possible prefixes of the option string
# and all actions in the parser for possible interpretations # and all actions in the parser for possible interpretations
option_tuples = self._get_option_tuples(arg_string) option_tuples = self._get_option_tuples(arg_string)
@ -2165,6 +2245,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# split at the '=' # split at the '='
chars = self.prefix_chars chars = self.prefix_chars
if option_string[0] in chars and option_string[1] in chars: if option_string[0] in chars and option_string[1] in chars:
if self.allow_abbrev:
if '=' in option_string: if '=' in option_string:
option_prefix, explicit_arg = option_string.split('=', 1) option_prefix, explicit_arg = option_string.split('=', 1)
else: else:

File diff suppressed because it is too large Load Diff

View File

@ -117,7 +117,7 @@ class async_chat(asyncore.dispatcher):
data = self.recv(self.ac_in_buffer_size) data = self.recv(self.ac_in_buffer_size)
except BlockingIOError: except BlockingIOError:
return return
except OSError as why: except OSError:
self.handle_error() self.handle_error()
return return

View File

@ -8,6 +8,7 @@ import sys
from .base_events import * from .base_events import *
from .coroutines import * from .coroutines import *
from .events import * from .events import *
from .exceptions import *
from .futures import * from .futures import *
from .locks import * from .locks import *
from .protocols import * from .protocols import *
@ -16,6 +17,7 @@ from .queues import *
from .streams import * from .streams import *
from .subprocess import * from .subprocess import *
from .tasks import * from .tasks import *
from .threads import *
from .transports import * from .transports import *
# Exposed for _asynciomodule.c to implement now deprecated # Exposed for _asynciomodule.c to implement now deprecated
@ -25,6 +27,7 @@ from .tasks import _all_tasks_compat # NoQA
__all__ = (base_events.__all__ + __all__ = (base_events.__all__ +
coroutines.__all__ + coroutines.__all__ +
events.__all__ + events.__all__ +
exceptions.__all__ +
futures.__all__ + futures.__all__ +
locks.__all__ + locks.__all__ +
protocols.__all__ + protocols.__all__ +
@ -33,6 +36,7 @@ __all__ = (base_events.__all__ +
streams.__all__ + streams.__all__ +
subprocess.__all__ + subprocess.__all__ +
tasks.__all__ + tasks.__all__ +
threads.__all__ +
transports.__all__) transports.__all__)
if sys.platform == 'win32': # pragma: no cover if sys.platform == 'win32': # pragma: no cover

View File

@ -16,11 +16,12 @@ to modify the meaning of the API call itself.
import collections import collections
import collections.abc import collections.abc
import concurrent.futures import concurrent.futures
import functools
import heapq import heapq
import itertools import itertools
import logging
import os import os
import socket import socket
import stat
import subprocess import subprocess
import threading import threading
import time import time
@ -37,15 +38,18 @@ except ImportError: # pragma: no cover
from . import constants from . import constants
from . import coroutines from . import coroutines
from . import events from . import events
from . import exceptions
from . import futures from . import futures
from . import protocols from . import protocols
from . import sslproto from . import sslproto
from . import staggered
from . import tasks from . import tasks
from . import transports from . import transports
from . import trsock
from .log import logger from .log import logger
__all__ = 'BaseEventLoop', __all__ = 'BaseEventLoop','Server',
# Minimum number of _scheduled timer handles before cleanup of # Minimum number of _scheduled timer handles before cleanup of
@ -56,10 +60,15 @@ _MIN_SCHEDULED_TIMER_HANDLES = 100
# before cleanup of cancelled handles is performed. # before cleanup of cancelled handles is performed.
_MIN_CANCELLED_TIMER_HANDLES_FRACTION = 0.5 _MIN_CANCELLED_TIMER_HANDLES_FRACTION = 0.5
# Exceptions which must not call the exception handler in fatal error
# methods (_fatal_error()) _HAS_IPv6 = hasattr(socket, 'AF_INET6')
_FATAL_ERROR_IGNORE = (BrokenPipeError,
ConnectionResetError, ConnectionAbortedError) # Maximum timeout passed to select to avoid OS limitations
MAXIMUM_SELECT_TIMEOUT = 24 * 3600
# Used for deprecation and removal of `loop.create_datagram_endpoint()`'s
# *reuse_address* parameter
_unset = object()
def _format_handle(handle): def _format_handle(handle):
@ -91,7 +100,7 @@ def _set_reuseport(sock):
'SO_REUSEPORT defined but not implemented.') 'SO_REUSEPORT defined but not implemented.')
def _ipaddr_info(host, port, family, type, proto): def _ipaddr_info(host, port, family, type, proto, flowinfo=0, scopeid=0):
# Try to skip getaddrinfo if "host" is already an IP. Users might have # Try to skip getaddrinfo if "host" is already an IP. Users might have
# handled name resolution in their own code and pass in resolved IPs. # handled name resolution in their own code and pass in resolved IPs.
if not hasattr(socket, 'inet_pton'): if not hasattr(socket, 'inet_pton'):
@ -123,7 +132,7 @@ def _ipaddr_info(host, port, family, type, proto):
if family == socket.AF_UNSPEC: if family == socket.AF_UNSPEC:
afs = [socket.AF_INET] afs = [socket.AF_INET]
if hasattr(socket, 'AF_INET6'): if _HAS_IPv6:
afs.append(socket.AF_INET6) afs.append(socket.AF_INET6)
else: else:
afs = [family] afs = [family]
@ -139,6 +148,9 @@ def _ipaddr_info(host, port, family, type, proto):
try: try:
socket.inet_pton(af, host) socket.inet_pton(af, host)
# The host has already been resolved. # The host has already been resolved.
if _HAS_IPv6 and af == socket.AF_INET6:
return af, type, proto, '', (host, port, flowinfo, scopeid)
else:
return af, type, proto, '', (host, port) return af, type, proto, '', (host, port)
except OSError: except OSError:
pass pass
@ -147,16 +159,54 @@ def _ipaddr_info(host, port, family, type, proto):
return None return None
def _interleave_addrinfos(addrinfos, first_address_family_count=1):
"""Interleave list of addrinfo tuples by family."""
# Group addresses by family
addrinfos_by_family = collections.OrderedDict()
for addr in addrinfos:
family = addr[0]
if family not in addrinfos_by_family:
addrinfos_by_family[family] = []
addrinfos_by_family[family].append(addr)
addrinfos_lists = list(addrinfos_by_family.values())
reordered = []
if first_address_family_count > 1:
reordered.extend(addrinfos_lists[0][:first_address_family_count - 1])
del addrinfos_lists[0][:first_address_family_count - 1]
reordered.extend(
a for a in itertools.chain.from_iterable(
itertools.zip_longest(*addrinfos_lists)
) if a is not None)
return reordered
def _run_until_complete_cb(fut): def _run_until_complete_cb(fut):
if not fut.cancelled(): if not fut.cancelled():
exc = fut.exception() exc = fut.exception()
if isinstance(exc, BaseException) and not isinstance(exc, Exception): if isinstance(exc, (SystemExit, KeyboardInterrupt)):
# Issue #22429: run_forever() already finished, no need to # Issue #22429: run_forever() already finished, no need to
# stop it. # stop it.
return return
futures._get_loop(fut).stop() futures._get_loop(fut).stop()
if hasattr(socket, 'TCP_NODELAY'):
def _set_nodelay(sock):
if (sock.family in {socket.AF_INET, socket.AF_INET6} and
sock.type == socket.SOCK_STREAM and
sock.proto == socket.IPPROTO_TCP):
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
else:
def _set_nodelay(sock):
pass
def _check_ssl_socket(sock):
if ssl is not None and isinstance(sock, ssl.SSLSocket):
raise TypeError("Socket cannot be of type SSLSocket")
class _SendfileFallbackProtocol(protocols.Protocol): class _SendfileFallbackProtocol(protocols.Protocol):
def __init__(self, transp): def __init__(self, transp):
if not isinstance(transp, transports._FlowControlMixin): if not isinstance(transp, transports._FlowControlMixin):
@ -279,8 +329,8 @@ class Server(events.AbstractServer):
@property @property
def sockets(self): def sockets(self):
if self._sockets is None: if self._sockets is None:
return [] return ()
return list(self._sockets) return tuple(trsock.TransportSocket(s) for s in self._sockets)
def close(self): def close(self):
sockets = self._sockets sockets = self._sockets
@ -305,7 +355,7 @@ class Server(events.AbstractServer):
self._start_serving() self._start_serving()
# Skip one loop iteration so that all 'loop.add_reader' # Skip one loop iteration so that all 'loop.add_reader'
# go through. # go through.
await tasks.sleep(0, loop=self._loop) await tasks.sleep(0)
async def serve_forever(self): async def serve_forever(self):
if self._serving_forever_fut is not None: if self._serving_forever_fut is not None:
@ -319,7 +369,7 @@ class Server(events.AbstractServer):
try: try:
await self._serving_forever_fut await self._serving_forever_fut
except futures.CancelledError: except exceptions.CancelledError:
try: try:
self.close() self.close()
await self.wait_closed() await self.wait_closed()
@ -365,6 +415,8 @@ class BaseEventLoop(events.AbstractEventLoop):
self._asyncgens = weakref.WeakSet() self._asyncgens = weakref.WeakSet()
# Set to True when `loop.shutdown_asyncgens` is called. # Set to True when `loop.shutdown_asyncgens` is called.
self._asyncgens_shutdown_called = False self._asyncgens_shutdown_called = False
# Set to True when `loop.shutdown_default_executor` is called.
self._executor_shutdown_called = False
def __repr__(self): def __repr__(self):
return ( return (
@ -376,18 +428,20 @@ class BaseEventLoop(events.AbstractEventLoop):
"""Create a Future object attached to the loop.""" """Create a Future object attached to the loop."""
return futures.Future(loop=self) return futures.Future(loop=self)
def create_task(self, coro): def create_task(self, coro, *, name=None):
"""Schedule a coroutine object. """Schedule a coroutine object.
Return a task object. Return a task object.
""" """
self._check_closed() self._check_closed()
if self._task_factory is None: if self._task_factory is None:
task = tasks.Task(coro, loop=self) task = tasks.Task(coro, loop=self, name=name)
if task._source_traceback: if task._source_traceback:
del task._source_traceback[-1] del task._source_traceback[-1]
else: else:
task = self._task_factory(self, coro) task = self._task_factory(self, coro)
tasks._set_task_name(task, name)
return task return task
def set_task_factory(self, factory): def set_task_factory(self, factory):
@ -460,13 +514,14 @@ class BaseEventLoop(events.AbstractEventLoop):
if self._closed: if self._closed:
raise RuntimeError('Event loop is closed') raise RuntimeError('Event loop is closed')
def _check_default_executor(self):
if self._executor_shutdown_called:
raise RuntimeError('Executor shutdown has been called')
def _asyncgen_finalizer_hook(self, agen): def _asyncgen_finalizer_hook(self, agen):
self._asyncgens.discard(agen) self._asyncgens.discard(agen)
if not self.is_closed(): if not self.is_closed():
self.create_task(agen.aclose()) self.call_soon_threadsafe(self.create_task, agen.aclose())
# Wake up the loop if the finalizer was called from
# a different thread.
self._write_to_self()
def _asyncgen_firstiter_hook(self, agen): def _asyncgen_firstiter_hook(self, agen):
if self._asyncgens_shutdown_called: if self._asyncgens_shutdown_called:
@ -489,7 +544,7 @@ class BaseEventLoop(events.AbstractEventLoop):
closing_agens = list(self._asyncgens) closing_agens = list(self._asyncgens)
self._asyncgens.clear() self._asyncgens.clear()
results = await tasks.gather( results = await tasks._gather(
*[ag.aclose() for ag in closing_agens], *[ag.aclose() for ag in closing_agens],
return_exceptions=True, return_exceptions=True,
loop=self) loop=self)
@ -503,14 +558,37 @@ class BaseEventLoop(events.AbstractEventLoop):
'asyncgen': agen 'asyncgen': agen
}) })
def run_forever(self): async def shutdown_default_executor(self):
"""Run until stop() is called.""" """Schedule the shutdown of the default executor."""
self._check_closed() self._executor_shutdown_called = True
if self._default_executor is None:
return
future = self.create_future()
thread = threading.Thread(target=self._do_shutdown, args=(future,))
thread.start()
try:
await future
finally:
thread.join()
def _do_shutdown(self, future):
try:
self._default_executor.shutdown(wait=True)
self.call_soon_threadsafe(future.set_result, None)
except Exception as ex:
self.call_soon_threadsafe(future.set_exception, ex)
def _check_running(self):
if self.is_running(): if self.is_running():
raise RuntimeError('This event loop is already running') raise RuntimeError('This event loop is already running')
if events._get_running_loop() is not None: if events._get_running_loop() is not None:
raise RuntimeError( raise RuntimeError(
'Cannot run the event loop while another loop is running') 'Cannot run the event loop while another loop is running')
def run_forever(self):
"""Run until stop() is called."""
self._check_closed()
self._check_running()
self._set_coroutine_origin_tracking(self._debug) self._set_coroutine_origin_tracking(self._debug)
self._thread_id = threading.get_ident() self._thread_id = threading.get_ident()
@ -542,6 +620,7 @@ class BaseEventLoop(events.AbstractEventLoop):
Return the Future's result, or raise its exception. Return the Future's result, or raise its exception.
""" """
self._check_closed() self._check_closed()
self._check_running()
new_task = not futures.isfuture(future) new_task = not futures.isfuture(future)
future = tasks.ensure_future(future, loop=self) future = tasks.ensure_future(future, loop=self)
@ -592,6 +671,7 @@ class BaseEventLoop(events.AbstractEventLoop):
self._closed = True self._closed = True
self._ready.clear() self._ready.clear()
self._scheduled.clear() self._scheduled.clear()
self._executor_shutdown_called = True
executor = self._default_executor executor = self._default_executor
if executor is not None: if executor is not None:
self._default_executor = None self._default_executor = None
@ -601,10 +681,9 @@ class BaseEventLoop(events.AbstractEventLoop):
"""Returns True if the event loop was closed.""" """Returns True if the event loop was closed."""
return self._closed return self._closed
def __del__(self): def __del__(self, _warn=warnings.warn):
if not self.is_closed(): if not self.is_closed():
warnings.warn(f"unclosed event loop {self!r}", ResourceWarning, _warn(f"unclosed event loop {self!r}", ResourceWarning, source=self)
source=self)
if not self.is_running(): if not self.is_running():
self.close() self.close()
@ -729,13 +808,23 @@ class BaseEventLoop(events.AbstractEventLoop):
self._check_callback(func, 'run_in_executor') self._check_callback(func, 'run_in_executor')
if executor is None: if executor is None:
executor = self._default_executor executor = self._default_executor
# Only check when the default executor is being used
self._check_default_executor()
if executor is None: if executor is None:
executor = concurrent.futures.ThreadPoolExecutor() executor = concurrent.futures.ThreadPoolExecutor(
thread_name_prefix='asyncio'
)
self._default_executor = executor self._default_executor = executor
return futures.wrap_future( return futures.wrap_future(
executor.submit(func, *args), loop=self) executor.submit(func, *args), loop=self)
def set_default_executor(self, executor): def set_default_executor(self, executor):
if not isinstance(executor, concurrent.futures.ThreadPoolExecutor):
warnings.warn(
'Using the default executor that is not an instance of '
'ThreadPoolExecutor is deprecated and will be prohibited '
'in Python 3.9',
DeprecationWarning, 2)
self._default_executor = executor self._default_executor = executor
def _getaddrinfo_debug(self, host, port, family, type, proto, flags): def _getaddrinfo_debug(self, host, port, family, type, proto, flags):
@ -780,11 +869,12 @@ class BaseEventLoop(events.AbstractEventLoop):
*, fallback=True): *, fallback=True):
if self._debug and sock.gettimeout() != 0: if self._debug and sock.gettimeout() != 0:
raise ValueError("the socket must be non-blocking") raise ValueError("the socket must be non-blocking")
_check_ssl_socket(sock)
self._check_sendfile_params(sock, file, offset, count) self._check_sendfile_params(sock, file, offset, count)
try: try:
return await self._sock_sendfile_native(sock, file, return await self._sock_sendfile_native(sock, file,
offset, count) offset, count)
except events.SendfileNotAvailableError as exc: except exceptions.SendfileNotAvailableError as exc:
if not fallback: if not fallback:
raise raise
return await self._sock_sendfile_fallback(sock, file, return await self._sock_sendfile_fallback(sock, file,
@ -793,9 +883,9 @@ class BaseEventLoop(events.AbstractEventLoop):
async def _sock_sendfile_native(self, sock, file, offset, count): async def _sock_sendfile_native(self, sock, file, offset, count):
# NB: sendfile syscall is not supported for SSL sockets and # NB: sendfile syscall is not supported for SSL sockets and
# non-mmap files even if sendfile is supported by OS # non-mmap files even if sendfile is supported by OS
raise events.SendfileNotAvailableError( raise exceptions.SendfileNotAvailableError(
f"syscall sendfile is not available for socket {sock!r} " f"syscall sendfile is not available for socket {sock!r} "
"and file {file!r} combination") f"and file {file!r} combination")
async def _sock_sendfile_fallback(self, sock, file, offset, count): async def _sock_sendfile_fallback(self, sock, file, offset, count):
if offset: if offset:
@ -816,7 +906,7 @@ class BaseEventLoop(events.AbstractEventLoop):
read = await self.run_in_executor(None, file.readinto, view) read = await self.run_in_executor(None, file.readinto, view)
if not read: if not read:
break # EOF break # EOF
await self.sock_sendall(sock, view) await self.sock_sendall(sock, view[:read])
total_sent += read total_sent += read
return total_sent return total_sent
finally: finally:
@ -844,12 +934,49 @@ class BaseEventLoop(events.AbstractEventLoop):
"offset must be a non-negative integer (got {!r})".format( "offset must be a non-negative integer (got {!r})".format(
offset)) offset))
async def _connect_sock(self, exceptions, addr_info, local_addr_infos=None):
"""Create, bind and connect one socket."""
my_exceptions = []
exceptions.append(my_exceptions)
family, type_, proto, _, address = addr_info
sock = None
try:
sock = socket.socket(family=family, type=type_, proto=proto)
sock.setblocking(False)
if local_addr_infos is not None:
for _, _, _, _, laddr in local_addr_infos:
try:
sock.bind(laddr)
break
except OSError as exc:
msg = (
f'error while attempting to bind on '
f'address {laddr!r}: '
f'{exc.strerror.lower()}'
)
exc = OSError(exc.errno, msg)
my_exceptions.append(exc)
else: # all bind attempts failed
raise my_exceptions.pop()
await self.sock_connect(sock, address)
return sock
except OSError as exc:
my_exceptions.append(exc)
if sock is not None:
sock.close()
raise
except:
if sock is not None:
sock.close()
raise
async def create_connection( async def create_connection(
self, protocol_factory, host=None, port=None, self, protocol_factory, host=None, port=None,
*, ssl=None, family=0, *, ssl=None, family=0,
proto=0, flags=0, sock=None, proto=0, flags=0, sock=None,
local_addr=None, server_hostname=None, local_addr=None, server_hostname=None,
ssl_handshake_timeout=None): ssl_handshake_timeout=None,
happy_eyeballs_delay=None, interleave=None):
"""Connect to a TCP server. """Connect to a TCP server.
Create a streaming transport connection to a given Internet host and Create a streaming transport connection to a given Internet host and
@ -884,6 +1011,13 @@ class BaseEventLoop(events.AbstractEventLoop):
raise ValueError( raise ValueError(
'ssl_handshake_timeout is only meaningful with ssl') 'ssl_handshake_timeout is only meaningful with ssl')
if sock is not None:
_check_ssl_socket(sock)
if happy_eyeballs_delay is not None and interleave is None:
# If using happy eyeballs, default to interleave addresses by family
interleave = 1
if host is not None or port is not None: if host is not None or port is not None:
if sock is not None: if sock is not None:
raise ValueError( raise ValueError(
@ -902,43 +1036,31 @@ class BaseEventLoop(events.AbstractEventLoop):
flags=flags, loop=self) flags=flags, loop=self)
if not laddr_infos: if not laddr_infos:
raise OSError('getaddrinfo() returned empty list') raise OSError('getaddrinfo() returned empty list')
else:
laddr_infos = None
if interleave:
infos = _interleave_addrinfos(infos, interleave)
exceptions = [] exceptions = []
for family, type, proto, cname, address in infos: if happy_eyeballs_delay is None:
# not using happy eyeballs
for addrinfo in infos:
try: try:
sock = socket.socket(family=family, type=type, proto=proto) sock = await self._connect_sock(
sock.setblocking(False) exceptions, addrinfo, laddr_infos)
if local_addr is not None:
for _, _, _, _, laddr in laddr_infos:
try:
sock.bind(laddr)
break break
except OSError as exc: except OSError:
msg = (
f'error while attempting to bind on '
f'address {laddr!r}: '
f'{exc.strerror.lower()}'
)
exc = OSError(exc.errno, msg)
exceptions.append(exc)
else:
sock.close()
sock = None
continue continue
if self._debug: else: # using happy eyeballs
logger.debug("connect %r to %r", sock, address) sock, _, _ = await staggered.staggered_race(
await self.sock_connect(sock, address) (functools.partial(self._connect_sock,
except OSError as exc: exceptions, addrinfo, laddr_infos)
if sock is not None: for addrinfo in infos),
sock.close() happy_eyeballs_delay, loop=self)
exceptions.append(exc)
except: if sock is None:
if sock is not None: exceptions = [exc for sub in exceptions for exc in sub]
sock.close()
raise
else:
break
else:
if len(exceptions) == 1: if len(exceptions) == 1:
raise exceptions[0] raise exceptions[0]
else: else:
@ -1037,7 +1159,7 @@ class BaseEventLoop(events.AbstractEventLoop):
try: try:
return await self._sendfile_native(transport, file, return await self._sendfile_native(transport, file,
offset, count) offset, count)
except events.SendfileNotAvailableError as exc: except exceptions.SendfileNotAvailableError as exc:
if not fallback: if not fallback:
raise raise
@ -1050,7 +1172,7 @@ class BaseEventLoop(events.AbstractEventLoop):
offset, count) offset, count)
async def _sendfile_native(self, transp, file, offset, count): async def _sendfile_native(self, transp, file, offset, count):
raise events.SendfileNotAvailableError( raise exceptions.SendfileNotAvailableError(
"sendfile syscall is not supported") "sendfile syscall is not supported")
async def _sendfile_fallback(self, transp, file, offset, count): async def _sendfile_fallback(self, transp, file, offset, count):
@ -1067,11 +1189,11 @@ class BaseEventLoop(events.AbstractEventLoop):
if blocksize <= 0: if blocksize <= 0:
return total_sent return total_sent
view = memoryview(buf)[:blocksize] view = memoryview(buf)[:blocksize]
read = file.readinto(view) read = await self.run_in_executor(None, file.readinto, view)
if not read: if not read:
return total_sent # EOF return total_sent # EOF
await proto.drain() await proto.drain()
transp.write(view) transp.write(view[:read])
total_sent += read total_sent += read
finally: finally:
if total_sent > 0 and hasattr(file, 'seek'): if total_sent > 0 and hasattr(file, 'seek'):
@ -1116,7 +1238,7 @@ class BaseEventLoop(events.AbstractEventLoop):
try: try:
await waiter await waiter
except Exception: except BaseException:
transport.close() transport.close()
conmade_cb.cancel() conmade_cb.cancel()
resume_cb.cancel() resume_cb.cancel()
@ -1127,7 +1249,7 @@ class BaseEventLoop(events.AbstractEventLoop):
async def create_datagram_endpoint(self, protocol_factory, async def create_datagram_endpoint(self, protocol_factory,
local_addr=None, remote_addr=None, *, local_addr=None, remote_addr=None, *,
family=0, proto=0, flags=0, family=0, proto=0, flags=0,
reuse_address=None, reuse_port=None, reuse_address=_unset, reuse_port=None,
allow_broadcast=None, sock=None): allow_broadcast=None, sock=None):
"""Create datagram connection.""" """Create datagram connection."""
if sock is not None: if sock is not None:
@ -1136,7 +1258,7 @@ class BaseEventLoop(events.AbstractEventLoop):
f'A UDP Socket was expected, got {sock!r}') f'A UDP Socket was expected, got {sock!r}')
if (local_addr or remote_addr or if (local_addr or remote_addr or
family or proto or flags or family or proto or flags or
reuse_address or reuse_port or allow_broadcast): reuse_port or allow_broadcast):
# show the problematic kwargs in exception msg # show the problematic kwargs in exception msg
opts = dict(local_addr=local_addr, remote_addr=remote_addr, opts = dict(local_addr=local_addr, remote_addr=remote_addr,
family=family, proto=proto, flags=flags, family=family, proto=proto, flags=flags,
@ -1157,15 +1279,28 @@ class BaseEventLoop(events.AbstractEventLoop):
for addr in (local_addr, remote_addr): for addr in (local_addr, remote_addr):
if addr is not None and not isinstance(addr, str): if addr is not None and not isinstance(addr, str):
raise TypeError('string is expected') raise TypeError('string is expected')
if local_addr and local_addr[0] not in (0, '\x00'):
try:
if stat.S_ISSOCK(os.stat(local_addr).st_mode):
os.remove(local_addr)
except FileNotFoundError:
pass
except OSError as err:
# Directory may have permissions only to create socket.
logger.error('Unable to check or remove stale UNIX '
'socket %r: %r',
local_addr, err)
addr_pairs_info = (((family, proto), addr_pairs_info = (((family, proto),
(local_addr, remote_addr)), ) (local_addr, remote_addr)), )
else: else:
# join address by (family, protocol) # join address by (family, protocol)
addr_infos = collections.OrderedDict() addr_infos = {} # Using order preserving dict
for idx, addr in ((0, local_addr), (1, remote_addr)): for idx, addr in ((0, local_addr), (1, remote_addr)):
if addr is not None: if addr is not None:
assert isinstance(addr, tuple) and len(addr) == 2, ( if not (isinstance(addr, tuple) and len(addr) == 2):
'2-tuple is expected') raise TypeError('2-tuple is expected')
infos = await self._ensure_resolved( infos = await self._ensure_resolved(
addr, family=family, type=socket.SOCK_DGRAM, addr, family=family, type=socket.SOCK_DGRAM,
@ -1190,8 +1325,18 @@ class BaseEventLoop(events.AbstractEventLoop):
exceptions = [] exceptions = []
if reuse_address is None: # bpo-37228
reuse_address = os.name == 'posix' and sys.platform != 'cygwin' if reuse_address is not _unset:
if reuse_address:
raise ValueError("Passing `reuse_address=True` is no "
"longer supported, as the usage of "
"SO_REUSEPORT in UDP poses a significant "
"security concern.")
else:
warnings.warn("The *reuse_address* parameter has been "
"deprecated as of 3.5.10 and is scheduled "
"for removal in 3.11.", DeprecationWarning,
stacklevel=2)
for ((family, proto), for ((family, proto),
(local_address, remote_address)) in addr_pairs_info: (local_address, remote_address)) in addr_pairs_info:
@ -1200,9 +1345,6 @@ class BaseEventLoop(events.AbstractEventLoop):
try: try:
sock = socket.socket( sock = socket.socket(
family=family, type=socket.SOCK_DGRAM, proto=proto) family=family, type=socket.SOCK_DGRAM, proto=proto)
if reuse_address:
sock.setsockopt(
socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if reuse_port: if reuse_port:
_set_reuseport(sock) _set_reuseport(sock)
if allow_broadcast: if allow_broadcast:
@ -1213,6 +1355,7 @@ class BaseEventLoop(events.AbstractEventLoop):
if local_addr: if local_addr:
sock.bind(local_address) sock.bind(local_address)
if remote_addr: if remote_addr:
if not allow_broadcast:
await self.sock_connect(sock, remote_address) await self.sock_connect(sock, remote_address)
r_addr = remote_address r_addr = remote_address
except OSError as exc: except OSError as exc:
@ -1254,7 +1397,7 @@ class BaseEventLoop(events.AbstractEventLoop):
family=0, type=socket.SOCK_STREAM, family=0, type=socket.SOCK_STREAM,
proto=0, flags=0, loop): proto=0, flags=0, loop):
host, port = address[:2] host, port = address[:2]
info = _ipaddr_info(host, port, family, type, proto) info = _ipaddr_info(host, port, family, type, proto, *address[2:])
if info is not None: if info is not None:
# "host" is already a resolved IP. # "host" is already a resolved IP.
return [info] return [info]
@ -1304,12 +1447,14 @@ class BaseEventLoop(events.AbstractEventLoop):
raise ValueError( raise ValueError(
'ssl_handshake_timeout is only meaningful with ssl') 'ssl_handshake_timeout is only meaningful with ssl')
if sock is not None:
_check_ssl_socket(sock)
if host is not None or port is not None: if host is not None or port is not None:
if sock is not None: if sock is not None:
raise ValueError( raise ValueError(
'host/port and sock can not be specified at the same time') 'host/port and sock can not be specified at the same time')
AF_INET6 = getattr(socket, 'AF_INET6', 0)
if reuse_address is None: if reuse_address is None:
reuse_address = os.name == 'posix' and sys.platform != 'cygwin' reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
sockets = [] sockets = []
@ -1324,7 +1469,7 @@ class BaseEventLoop(events.AbstractEventLoop):
fs = [self._create_server_getaddrinfo(host, port, family=family, fs = [self._create_server_getaddrinfo(host, port, family=family,
flags=flags) flags=flags)
for host in hosts] for host in hosts]
infos = await tasks.gather(*fs, loop=self) infos = await tasks._gather(*fs, loop=self)
infos = set(itertools.chain.from_iterable(infos)) infos = set(itertools.chain.from_iterable(infos))
completed = False completed = False
@ -1349,7 +1494,9 @@ class BaseEventLoop(events.AbstractEventLoop):
# Disable IPv4/IPv6 dual stack support (enabled by # Disable IPv4/IPv6 dual stack support (enabled by
# default on Linux) which makes a single socket # default on Linux) which makes a single socket
# listen on both address families. # listen on both address families.
if af == AF_INET6 and hasattr(socket, 'IPPROTO_IPV6'): if (_HAS_IPv6 and
af == socket.AF_INET6 and
hasattr(socket, 'IPPROTO_IPV6')):
sock.setsockopt(socket.IPPROTO_IPV6, sock.setsockopt(socket.IPPROTO_IPV6,
socket.IPV6_V6ONLY, socket.IPV6_V6ONLY,
True) True)
@ -1380,7 +1527,7 @@ class BaseEventLoop(events.AbstractEventLoop):
server._start_serving() server._start_serving()
# Skip one loop iteration so that all 'loop.add_reader' # Skip one loop iteration so that all 'loop.add_reader'
# go through. # go through.
await tasks.sleep(0, loop=self) await tasks.sleep(0)
if self._debug: if self._debug:
logger.info("%r is serving", server) logger.info("%r is serving", server)
@ -1405,6 +1552,9 @@ class BaseEventLoop(events.AbstractEventLoop):
raise ValueError( raise ValueError(
'ssl_handshake_timeout is only meaningful with ssl') 'ssl_handshake_timeout is only meaningful with ssl')
if sock is not None:
_check_ssl_socket(sock)
transport, protocol = await self._create_connection_transport( transport, protocol = await self._create_connection_transport(
sock, protocol_factory, ssl, '', server_side=True, sock, protocol_factory, ssl, '', server_side=True,
ssl_handshake_timeout=ssl_handshake_timeout) ssl_handshake_timeout=ssl_handshake_timeout)
@ -1466,6 +1616,7 @@ class BaseEventLoop(events.AbstractEventLoop):
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=False, universal_newlines=False,
shell=True, bufsize=0, shell=True, bufsize=0,
encoding=None, errors=None, text=None,
**kwargs): **kwargs):
if not isinstance(cmd, (bytes, str)): if not isinstance(cmd, (bytes, str)):
raise ValueError("cmd must be a string") raise ValueError("cmd must be a string")
@ -1475,6 +1626,13 @@ class BaseEventLoop(events.AbstractEventLoop):
raise ValueError("shell must be True") raise ValueError("shell must be True")
if bufsize != 0: if bufsize != 0:
raise ValueError("bufsize must be 0") raise ValueError("bufsize must be 0")
if text:
raise ValueError("text must be False")
if encoding is not None:
raise ValueError("encoding must be None")
if errors is not None:
raise ValueError("errors must be None")
protocol = protocol_factory() protocol = protocol_factory()
debug_log = None debug_log = None
if self._debug: if self._debug:
@ -1491,19 +1649,23 @@ class BaseEventLoop(events.AbstractEventLoop):
async def subprocess_exec(self, protocol_factory, program, *args, async def subprocess_exec(self, protocol_factory, program, *args,
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, universal_newlines=False, stderr=subprocess.PIPE, universal_newlines=False,
shell=False, bufsize=0, **kwargs): shell=False, bufsize=0,
encoding=None, errors=None, text=None,
**kwargs):
if universal_newlines: if universal_newlines:
raise ValueError("universal_newlines must be False") raise ValueError("universal_newlines must be False")
if shell: if shell:
raise ValueError("shell must be False") raise ValueError("shell must be False")
if bufsize != 0: if bufsize != 0:
raise ValueError("bufsize must be 0") raise ValueError("bufsize must be 0")
if text:
raise ValueError("text must be False")
if encoding is not None:
raise ValueError("encoding must be None")
if errors is not None:
raise ValueError("errors must be None")
popen_args = (program,) + args popen_args = (program,) + args
for arg in popen_args:
if not isinstance(arg, (str, bytes)):
raise TypeError(
f"program arguments must be a bytes or text string, "
f"not {type(arg).__name__}")
protocol = protocol_factory() protocol = protocol_factory()
debug_log = None debug_log = None
if self._debug: if self._debug:
@ -1615,7 +1777,9 @@ class BaseEventLoop(events.AbstractEventLoop):
if self._exception_handler is None: if self._exception_handler is None:
try: try:
self.default_exception_handler(context) self.default_exception_handler(context)
except Exception: except (SystemExit, KeyboardInterrupt):
raise
except BaseException:
# Second protection layer for unexpected errors # Second protection layer for unexpected errors
# in the default implementation, as well as for subclassed # in the default implementation, as well as for subclassed
# event loops with overloaded "default_exception_handler". # event loops with overloaded "default_exception_handler".
@ -1624,7 +1788,9 @@ class BaseEventLoop(events.AbstractEventLoop):
else: else:
try: try:
self._exception_handler(self, context) self._exception_handler(self, context)
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
# Exception in the user set custom exception handler. # Exception in the user set custom exception handler.
try: try:
# Let's try default handler. # Let's try default handler.
@ -1633,7 +1799,9 @@ class BaseEventLoop(events.AbstractEventLoop):
'exception': exc, 'exception': exc,
'context': context, 'context': context,
}) })
except Exception: except (SystemExit, KeyboardInterrupt):
raise
except BaseException:
# Guard 'default_exception_handler' in case it is # Guard 'default_exception_handler' in case it is
# overloaded. # overloaded.
logger.error('Exception in default exception handler ' logger.error('Exception in default exception handler '
@ -1696,29 +1864,8 @@ class BaseEventLoop(events.AbstractEventLoop):
elif self._scheduled: elif self._scheduled:
# Compute the desired timeout. # Compute the desired timeout.
when = self._scheduled[0]._when when = self._scheduled[0]._when
timeout = max(0, when - self.time()) timeout = min(max(0, when - self.time()), MAXIMUM_SELECT_TIMEOUT)
if self._debug and timeout != 0:
t0 = self.time()
event_list = self._selector.select(timeout)
dt = self.time() - t0
if dt >= 1.0:
level = logging.INFO
else:
level = logging.DEBUG
nevent = len(event_list)
if timeout is None:
logger.log(level, 'poll took %.3f ms: %s events',
dt * 1e3, nevent)
elif nevent:
logger.log(level,
'poll %.3f ms took %.3f ms: %s events',
timeout * 1e3, dt * 1e3, nevent)
elif dt >= 1.0:
logger.log(level,
'poll %.3f ms took %.3f ms: timeout',
timeout * 1e3, dt * 1e3)
else:
event_list = self._selector.select(timeout) event_list = self._selector.select(timeout)
self._process_events(event_list) self._process_events(event_list)

View File

@ -1,19 +1,10 @@
__all__ = () __all__ = ()
import concurrent.futures._base
import reprlib import reprlib
from _thread import get_ident
from . import format_helpers from . import format_helpers
Error = concurrent.futures._base.Error
CancelledError = concurrent.futures.CancelledError
TimeoutError = concurrent.futures.TimeoutError
class InvalidStateError(Error):
"""The operation is not allowed in this state."""
# States for Future. # States for Future.
_PENDING = 'PENDING' _PENDING = 'PENDING'
_CANCELLED = 'CANCELLED' _CANCELLED = 'CANCELLED'
@ -51,6 +42,16 @@ def _format_callbacks(cb):
return f'cb=[{cb}]' return f'cb=[{cb}]'
# bpo-42183: _repr_running is needed for repr protection
# when a Future or Task result contains itself directly or indirectly.
# The logic is borrowed from @reprlib.recursive_repr decorator.
# Unfortunately, the direct decorator usage is impossible because of
# AttributeError: '_asyncio.Task' object has no attribute '__module__' error.
#
# After fixing this thing we can return to the decorator based approach.
_repr_running = set()
def _future_repr_info(future): def _future_repr_info(future):
# (Future) -> str # (Future) -> str
"""helper function for Future.__repr__""" """helper function for Future.__repr__"""
@ -59,9 +60,17 @@ def _future_repr_info(future):
if future._exception is not None: if future._exception is not None:
info.append(f'exception={future._exception!r}') info.append(f'exception={future._exception!r}')
else: else:
key = id(future), get_ident()
if key in _repr_running:
result = '...'
else:
_repr_running.add(key)
try:
# use reprlib to limit the length of the output, especially # use reprlib to limit the length of the output, especially
# for very long strings # for very long strings
result = reprlib.repr(future._result) result = reprlib.repr(future._result)
finally:
_repr_running.discard(key)
info.append(f'result={result}') info.append(f'result={result}')
if future._callbacks: if future._callbacks:
info.append(_format_callbacks(future._callbacks)) info.append(_format_callbacks(future._callbacks))

View File

@ -120,10 +120,9 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
# Don't clear the _proc reference yet: _post_init() may still run # Don't clear the _proc reference yet: _post_init() may still run
def __del__(self): def __del__(self, _warn=warnings.warn):
if not self._closed: if not self._closed:
warnings.warn(f"unclosed transport {self!r}", ResourceWarning, _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
source=self)
self.close() self.close()
def get_pid(self): def get_pid(self):
@ -183,7 +182,9 @@ class BaseSubprocessTransport(transports.SubprocessTransport):
for callback, data in self._pending_calls: for callback, data in self._pending_calls:
loop.call_soon(callback, *data) loop.call_soon(callback, *data)
self._pending_calls = None self._pending_calls = None
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
if waiter is not None and not waiter.cancelled(): if waiter is not None and not waiter.cancelled():
waiter.set_exception(exc) waiter.set_exception(exc)
else: else:

View File

@ -12,21 +12,30 @@ def _task_repr_info(task):
# replace status # replace status
info[0] = 'cancelling' info[0] = 'cancelling'
info.insert(1, 'name=%r' % task.get_name())
coro = coroutines._format_coroutine(task._coro) coro = coroutines._format_coroutine(task._coro)
info.insert(1, f'coro=<{coro}>') info.insert(2, f'coro=<{coro}>')
if task._fut_waiter is not None: if task._fut_waiter is not None:
info.insert(2, f'wait_for={task._fut_waiter!r}') info.insert(3, f'wait_for={task._fut_waiter!r}')
return info return info
def _task_get_stack(task, limit): def _task_get_stack(task, limit):
frames = [] frames = []
try: if hasattr(task._coro, 'cr_frame'):
# 'async def' coroutines # case 1: 'async def' coroutines
f = task._coro.cr_frame f = task._coro.cr_frame
except AttributeError: elif hasattr(task._coro, 'gi_frame'):
# case 2: legacy coroutines
f = task._coro.gi_frame f = task._coro.gi_frame
elif hasattr(task._coro, 'ag_frame'):
# case 3: async generators
f = task._coro.ag_frame
else:
# case 4: unknown objects
f = None
if f is not None: if f is not None:
while f is not None: while f is not None:
if limit is not None: if limit is not None:

View File

@ -7,6 +7,7 @@ import os
import sys import sys
import traceback import traceback
import types import types
import warnings
from . import base_futures from . import base_futures
from . import constants from . import constants
@ -107,6 +108,9 @@ def coroutine(func):
If the coroutine is not yielded from before it is destroyed, If the coroutine is not yielded from before it is destroyed,
an error message is logged. an error message is logged.
""" """
warnings.warn('"@coroutine" decorator is deprecated since Python 3.8, use "async def" instead',
DeprecationWarning,
stacklevel=2)
if inspect.iscoroutinefunction(func): if inspect.iscoroutinefunction(func):
# In Python 3.5 that's all we need to do for coroutines # In Python 3.5 that's all we need to do for coroutines
# defined with "async def". # defined with "async def".

View File

@ -3,7 +3,7 @@
__all__ = ( __all__ = (
'AbstractEventLoopPolicy', 'AbstractEventLoopPolicy',
'AbstractEventLoop', 'AbstractServer', 'AbstractEventLoop', 'AbstractServer',
'Handle', 'TimerHandle', 'SendfileNotAvailableError', 'Handle', 'TimerHandle',
'get_event_loop_policy', 'set_event_loop_policy', 'get_event_loop_policy', 'set_event_loop_policy',
'get_event_loop', 'set_event_loop', 'new_event_loop', 'get_event_loop', 'set_event_loop', 'new_event_loop',
'get_child_watcher', 'set_child_watcher', 'get_child_watcher', 'set_child_watcher',
@ -21,14 +21,6 @@ import threading
from . import format_helpers from . import format_helpers
class SendfileNotAvailableError(RuntimeError):
"""Sendfile syscall is not available.
Raised if OS does not support sendfile syscall for given socket or
file type.
"""
class Handle: class Handle:
"""Object returned by callback registration methods.""" """Object returned by callback registration methods."""
@ -86,7 +78,9 @@ class Handle:
def _run(self): def _run(self):
try: try:
self._context.run(self._callback, *self._args) self._context.run(self._callback, *self._args)
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
cb = format_helpers._format_callback_source( cb = format_helpers._format_callback_source(
self._callback, self._args) self._callback, self._args)
msg = f'Exception in callback {cb}' msg = f'Exception in callback {cb}'
@ -124,20 +118,24 @@ class TimerHandle(Handle):
return hash(self._when) return hash(self._when)
def __lt__(self, other): def __lt__(self, other):
if isinstance(other, TimerHandle):
return self._when < other._when return self._when < other._when
return NotImplemented
def __le__(self, other): def __le__(self, other):
if self._when < other._when: if isinstance(other, TimerHandle):
return True return self._when < other._when or self.__eq__(other)
return self.__eq__(other) return NotImplemented
def __gt__(self, other): def __gt__(self, other):
if isinstance(other, TimerHandle):
return self._when > other._when return self._when > other._when
return NotImplemented
def __ge__(self, other): def __ge__(self, other):
if self._when > other._when: if isinstance(other, TimerHandle):
return True return self._when > other._when or self.__eq__(other)
return self.__eq__(other) return NotImplemented
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, TimerHandle): if isinstance(other, TimerHandle):
@ -147,10 +145,6 @@ class TimerHandle(Handle):
self._cancelled == other._cancelled) self._cancelled == other._cancelled)
return NotImplemented return NotImplemented
def __ne__(self, other):
equal = self.__eq__(other)
return NotImplemented if equal is NotImplemented else not equal
def cancel(self): def cancel(self):
if not self._cancelled: if not self._cancelled:
self._loop._timer_handle_cancelled(self) self._loop._timer_handle_cancelled(self)
@ -254,19 +248,23 @@ class AbstractEventLoop:
"""Shutdown all active asynchronous generators.""" """Shutdown all active asynchronous generators."""
raise NotImplementedError raise NotImplementedError
async def shutdown_default_executor(self):
"""Schedule the shutdown of the default executor."""
raise NotImplementedError
# Methods scheduling callbacks. All these return Handles. # Methods scheduling callbacks. All these return Handles.
def _timer_handle_cancelled(self, handle): def _timer_handle_cancelled(self, handle):
"""Notification that a TimerHandle has been cancelled.""" """Notification that a TimerHandle has been cancelled."""
raise NotImplementedError raise NotImplementedError
def call_soon(self, callback, *args): def call_soon(self, callback, *args, context=None):
return self.call_later(0, callback, *args) return self.call_later(0, callback, *args, context=context)
def call_later(self, delay, callback, *args): def call_later(self, delay, callback, *args, context=None):
raise NotImplementedError raise NotImplementedError
def call_at(self, when, callback, *args): def call_at(self, when, callback, *args, context=None):
raise NotImplementedError raise NotImplementedError
def time(self): def time(self):
@ -277,15 +275,15 @@ class AbstractEventLoop:
# Method scheduling a coroutine object: create a task. # Method scheduling a coroutine object: create a task.
def create_task(self, coro): def create_task(self, coro, *, name=None):
raise NotImplementedError raise NotImplementedError
# Methods for interacting with threads. # Methods for interacting with threads.
def call_soon_threadsafe(self, callback, *args): def call_soon_threadsafe(self, callback, *args, context=None):
raise NotImplementedError raise NotImplementedError
async def run_in_executor(self, executor, func, *args): def run_in_executor(self, executor, func, *args):
raise NotImplementedError raise NotImplementedError
def set_default_executor(self, executor): def set_default_executor(self, executor):
@ -305,7 +303,8 @@ class AbstractEventLoop:
*, ssl=None, family=0, proto=0, *, ssl=None, family=0, proto=0,
flags=0, sock=None, local_addr=None, flags=0, sock=None, local_addr=None,
server_hostname=None, server_hostname=None,
ssl_handshake_timeout=None): ssl_handshake_timeout=None,
happy_eyeballs_delay=None, interleave=None):
raise NotImplementedError raise NotImplementedError
async def create_server( async def create_server(
@ -397,7 +396,7 @@ class AbstractEventLoop:
The return value is a Server object, which can be used to stop The return value is a Server object, which can be used to stop
the service. the service.
path is a str, representing a file systsem path to bind the path is a str, representing a file system path to bind the
server socket to. server socket to.
sock can optionally be specified in order to use a preexisting sock can optionally be specified in order to use a preexisting
@ -466,7 +465,7 @@ class AbstractEventLoop:
# The reason to accept file-like object instead of just file descriptor # The reason to accept file-like object instead of just file descriptor
# is: we need to own pipe and close it at transport finishing # is: we need to own pipe and close it at transport finishing
# Can got complicated errors if pass f.fileno(), # Can got complicated errors if pass f.fileno(),
# close fd in pipe transport then close f and vise versa. # close fd in pipe transport then close f and vice versa.
raise NotImplementedError raise NotImplementedError
async def connect_write_pipe(self, protocol_factory, pipe): async def connect_write_pipe(self, protocol_factory, pipe):
@ -479,7 +478,7 @@ class AbstractEventLoop:
# The reason to accept file-like object instead of just file descriptor # The reason to accept file-like object instead of just file descriptor
# is: we need to own pipe and close it at transport finishing # is: we need to own pipe and close it at transport finishing
# Can got complicated errors if pass f.fileno(), # Can got complicated errors if pass f.fileno(),
# close fd in pipe transport then close f and vise versa. # close fd in pipe transport then close f and vice versa.
raise NotImplementedError raise NotImplementedError
async def subprocess_shell(self, protocol_factory, cmd, *, async def subprocess_shell(self, protocol_factory, cmd, *,
@ -630,13 +629,13 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
self._local = self._Local() self._local = self._Local()
def get_event_loop(self): def get_event_loop(self):
"""Get the event loop. """Get the event loop for the current context.
This may be None or an instance of EventLoop. Returns an instance of EventLoop or raises an exception.
""" """
if (self._local._loop is None and if (self._local._loop is None and
not self._local._set_called and not self._local._set_called and
isinstance(threading.current_thread(), threading._MainThread)): threading.current_thread() is threading.main_thread()):
self.set_event_loop(self.new_event_loop()) self.set_event_loop(self.new_event_loop())
if self._local._loop is None: if self._local._loop is None:

View File

@ -1,7 +1,6 @@
"""A Future class similar to the one in PEP 3148.""" """A Future class similar to the one in PEP 3148."""
__all__ = ( __all__ = (
'CancelledError', 'TimeoutError', 'InvalidStateError',
'Future', 'wrap_future', 'isfuture', 'Future', 'wrap_future', 'isfuture',
) )
@ -9,15 +8,14 @@ import concurrent.futures
import contextvars import contextvars
import logging import logging
import sys import sys
from types import GenericAlias
from . import base_futures from . import base_futures
from . import events from . import events
from . import exceptions
from . import format_helpers from . import format_helpers
CancelledError = base_futures.CancelledError
InvalidStateError = base_futures.InvalidStateError
TimeoutError = base_futures.TimeoutError
isfuture = base_futures.isfuture isfuture = base_futures.isfuture
@ -54,6 +52,9 @@ class Future:
_exception = None _exception = None
_loop = None _loop = None
_source_traceback = None _source_traceback = None
_cancel_message = None
# A saved CancelledError for later chaining as an exception context.
_cancelled_exc = None
# This field is used for a dual purpose: # This field is used for a dual purpose:
# - Its presence is a marker to declare that a class implements # - Its presence is a marker to declare that a class implements
@ -106,6 +107,8 @@ class Future:
context['source_traceback'] = self._source_traceback context['source_traceback'] = self._source_traceback
self._loop.call_exception_handler(context) self._loop.call_exception_handler(context)
__class_getitem__ = classmethod(GenericAlias)
@property @property
def _log_traceback(self): def _log_traceback(self):
return self.__log_traceback return self.__log_traceback
@ -118,9 +121,27 @@ class Future:
def get_loop(self): def get_loop(self):
"""Return the event loop the Future is bound to.""" """Return the event loop the Future is bound to."""
return self._loop loop = self._loop
if loop is None:
raise RuntimeError("Future object is not initialized.")
return loop
def cancel(self): def _make_cancelled_error(self):
"""Create the CancelledError to raise if the Future is cancelled.
This should only be called once when handling a cancellation since
it erases the saved context exception value.
"""
if self._cancel_message is None:
exc = exceptions.CancelledError()
else:
exc = exceptions.CancelledError(self._cancel_message)
exc.__context__ = self._cancelled_exc
# Remove the reference since we don't need this anymore.
self._cancelled_exc = None
return exc
def cancel(self, msg=None):
"""Cancel the future and schedule callbacks. """Cancel the future and schedule callbacks.
If the future is already done or cancelled, return False. Otherwise, If the future is already done or cancelled, return False. Otherwise,
@ -131,6 +152,7 @@ class Future:
if self._state != _PENDING: if self._state != _PENDING:
return False return False
self._state = _CANCELLED self._state = _CANCELLED
self._cancel_message = msg
self.__schedule_callbacks() self.__schedule_callbacks()
return True return True
@ -170,9 +192,10 @@ class Future:
the future is done and has an exception set, this exception is raised. the future is done and has an exception set, this exception is raised.
""" """
if self._state == _CANCELLED: if self._state == _CANCELLED:
raise CancelledError exc = self._make_cancelled_error()
raise exc
if self._state != _FINISHED: if self._state != _FINISHED:
raise InvalidStateError('Result is not ready.') raise exceptions.InvalidStateError('Result is not ready.')
self.__log_traceback = False self.__log_traceback = False
if self._exception is not None: if self._exception is not None:
raise self._exception raise self._exception
@ -187,9 +210,10 @@ class Future:
InvalidStateError. InvalidStateError.
""" """
if self._state == _CANCELLED: if self._state == _CANCELLED:
raise CancelledError exc = self._make_cancelled_error()
raise exc
if self._state != _FINISHED: if self._state != _FINISHED:
raise InvalidStateError('Exception is not set.') raise exceptions.InvalidStateError('Exception is not set.')
self.__log_traceback = False self.__log_traceback = False
return self._exception return self._exception
@ -231,7 +255,7 @@ class Future:
InvalidStateError. InvalidStateError.
""" """
if self._state != _PENDING: if self._state != _PENDING:
raise InvalidStateError('{}: {!r}'.format(self._state, self)) raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
self._result = result self._result = result
self._state = _FINISHED self._state = _FINISHED
self.__schedule_callbacks() self.__schedule_callbacks()
@ -243,7 +267,7 @@ class Future:
InvalidStateError. InvalidStateError.
""" """
if self._state != _PENDING: if self._state != _PENDING:
raise InvalidStateError('{}: {!r}'.format(self._state, self)) raise exceptions.InvalidStateError(f'{self._state}: {self!r}')
if isinstance(exception, type): if isinstance(exception, type):
exception = exception() exception = exception()
if type(exception) is StopIteration: if type(exception) is StopIteration:
@ -288,6 +312,18 @@ def _set_result_unless_cancelled(fut, result):
fut.set_result(result) fut.set_result(result)
def _convert_future_exc(exc):
exc_class = type(exc)
if exc_class is concurrent.futures.CancelledError:
return exceptions.CancelledError(*exc.args)
elif exc_class is concurrent.futures.TimeoutError:
return exceptions.TimeoutError(*exc.args)
elif exc_class is concurrent.futures.InvalidStateError:
return exceptions.InvalidStateError(*exc.args)
else:
return exc
def _set_concurrent_future_state(concurrent, source): def _set_concurrent_future_state(concurrent, source):
"""Copy state from a future to a concurrent.futures.Future.""" """Copy state from a future to a concurrent.futures.Future."""
assert source.done() assert source.done()
@ -297,7 +333,7 @@ def _set_concurrent_future_state(concurrent, source):
return return
exception = source.exception() exception = source.exception()
if exception is not None: if exception is not None:
concurrent.set_exception(exception) concurrent.set_exception(_convert_future_exc(exception))
else: else:
result = source.result() result = source.result()
concurrent.set_result(result) concurrent.set_result(result)
@ -317,7 +353,7 @@ def _copy_future_state(source, dest):
else: else:
exception = source.exception() exception = source.exception()
if exception is not None: if exception is not None:
dest.set_exception(exception) dest.set_exception(_convert_future_exc(exception))
else: else:
result = source.result() result = source.result()
dest.set_result(result) dest.set_result(result)

View File

@ -6,88 +6,10 @@ import collections
import warnings import warnings
from . import events from . import events
from . import futures from . import exceptions
from .coroutines import coroutine
class _ContextManager:
"""Context manager.
This enables the following idiom for acquiring and releasing a
lock around a block:
with (yield from lock):
<block>
while failing loudly when accidentally using:
with lock:
<block>
Deprecated, use 'async with' statement:
async with lock:
<block>
"""
def __init__(self, lock):
self._lock = lock
def __enter__(self):
# We have no use for the "as ..." clause in the with
# statement for locks.
return None
def __exit__(self, *args):
try:
self._lock.release()
finally:
self._lock = None # Crudely prevent reuse.
class _ContextManagerMixin: class _ContextManagerMixin:
def __enter__(self):
raise RuntimeError(
'"yield from" should be used as context manager expression')
def __exit__(self, *args):
# This must exist because __enter__ exists, even though that
# always raises; that's how the with-statement works.
pass
@coroutine
def __iter__(self):
# This is not a coroutine. It is meant to enable the idiom:
#
# with (yield from lock):
# <block>
#
# as an alternative to:
#
# yield from lock.acquire()
# try:
# <block>
# finally:
# lock.release()
# Deprecated, use 'async with' statement:
# async with lock:
# <block>
warnings.warn("'with (yield from lock)' is deprecated "
"use 'async with lock' instead",
DeprecationWarning, stacklevel=2)
yield from self.acquire()
return _ContextManager(self)
async def __acquire_ctx(self):
await self.acquire()
return _ContextManager(self)
def __await__(self):
warnings.warn("'with await lock' is deprecated "
"use 'async with lock' instead",
DeprecationWarning, stacklevel=2)
# To make "with await lock" work.
return self.__acquire_ctx().__await__()
async def __aenter__(self): async def __aenter__(self):
await self.acquire() await self.acquire()
# We have no use for the "as ..." clause in the with # We have no use for the "as ..." clause in the with
@ -153,12 +75,15 @@ class Lock(_ContextManagerMixin):
""" """
def __init__(self, *, loop=None): def __init__(self, *, loop=None):
self._waiters = collections.deque() self._waiters = None
self._locked = False self._locked = False
if loop is not None: if loop is None:
self._loop = loop
else:
self._loop = events.get_event_loop() self._loop = events.get_event_loop()
else:
self._loop = loop
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
def __repr__(self): def __repr__(self):
res = super().__repr__() res = super().__repr__()
@ -177,10 +102,13 @@ class Lock(_ContextManagerMixin):
This method blocks until the lock is unlocked, then sets it to This method blocks until the lock is unlocked, then sets it to
locked and returns True. locked and returns True.
""" """
if not self._locked and all(w.cancelled() for w in self._waiters): if (not self._locked and (self._waiters is None or
all(w.cancelled() for w in self._waiters))):
self._locked = True self._locked = True
return True return True
if self._waiters is None:
self._waiters = collections.deque()
fut = self._loop.create_future() fut = self._loop.create_future()
self._waiters.append(fut) self._waiters.append(fut)
@ -192,7 +120,7 @@ class Lock(_ContextManagerMixin):
await fut await fut
finally: finally:
self._waiters.remove(fut) self._waiters.remove(fut)
except futures.CancelledError: except exceptions.CancelledError:
if not self._locked: if not self._locked:
self._wake_up_first() self._wake_up_first()
raise raise
@ -219,6 +147,8 @@ class Lock(_ContextManagerMixin):
def _wake_up_first(self): def _wake_up_first(self):
"""Wake up the first waiter if it isn't done.""" """Wake up the first waiter if it isn't done."""
if not self._waiters:
return
try: try:
fut = next(iter(self._waiters)) fut = next(iter(self._waiters))
except StopIteration: except StopIteration:
@ -243,10 +173,13 @@ class Event:
def __init__(self, *, loop=None): def __init__(self, *, loop=None):
self._waiters = collections.deque() self._waiters = collections.deque()
self._value = False self._value = False
if loop is not None: if loop is None:
self._loop = loop
else:
self._loop = events.get_event_loop() self._loop = events.get_event_loop()
else:
self._loop = loop
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
def __repr__(self): def __repr__(self):
res = super().__repr__() res = super().__repr__()
@ -307,13 +240,16 @@ class Condition(_ContextManagerMixin):
""" """
def __init__(self, lock=None, *, loop=None): def __init__(self, lock=None, *, loop=None):
if loop is not None: if loop is None:
self._loop = loop
else:
self._loop = events.get_event_loop() self._loop = events.get_event_loop()
else:
self._loop = loop
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
if lock is None: if lock is None:
lock = Lock(loop=self._loop) lock = Lock(loop=loop)
elif lock._loop is not self._loop: elif lock._loop is not self._loop:
raise ValueError("loop argument must agree with lock") raise ValueError("loop argument must agree with lock")
@ -363,11 +299,11 @@ class Condition(_ContextManagerMixin):
try: try:
await self.acquire() await self.acquire()
break break
except futures.CancelledError: except exceptions.CancelledError:
cancelled = True cancelled = True
if cancelled: if cancelled:
raise futures.CancelledError raise exceptions.CancelledError
async def wait_for(self, predicate): async def wait_for(self, predicate):
"""Wait until a predicate becomes true. """Wait until a predicate becomes true.
@ -435,10 +371,14 @@ class Semaphore(_ContextManagerMixin):
raise ValueError("Semaphore initial value must be >= 0") raise ValueError("Semaphore initial value must be >= 0")
self._value = value self._value = value
self._waiters = collections.deque() self._waiters = collections.deque()
if loop is not None: if loop is None:
self._loop = loop
else:
self._loop = events.get_event_loop() self._loop = events.get_event_loop()
else:
self._loop = loop
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
self._wakeup_scheduled = False
def __repr__(self): def __repr__(self):
res = super().__repr__() res = super().__repr__()
@ -452,6 +392,7 @@ class Semaphore(_ContextManagerMixin):
waiter = self._waiters.popleft() waiter = self._waiters.popleft()
if not waiter.done(): if not waiter.done():
waiter.set_result(None) waiter.set_result(None)
self._wakeup_scheduled = True
return return
def locked(self): def locked(self):
@ -467,15 +408,16 @@ class Semaphore(_ContextManagerMixin):
called release() to make it larger than 0, and then return called release() to make it larger than 0, and then return
True. True.
""" """
while self._value <= 0: # _wakeup_scheduled is set if *another* task is scheduled to wakeup
# but its acquire() is not resumed yet
while self._wakeup_scheduled or self._value <= 0:
fut = self._loop.create_future() fut = self._loop.create_future()
self._waiters.append(fut) self._waiters.append(fut)
try: try:
await fut await fut
except: # reset _wakeup_scheduled *after* waiting for a future
# See the similar code in Queue.get. self._wakeup_scheduled = False
fut.cancel() except exceptions.CancelledError:
if self._value > 0 and not fut.cancelled():
self._wake_up_next() self._wake_up_next()
raise raise
self._value -= 1 self._value -= 1
@ -498,6 +440,11 @@ class BoundedSemaphore(Semaphore):
""" """
def __init__(self, value=1, *, loop=None): def __init__(self, value=1, *, loop=None):
if loop:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
self._bound_value = value self._bound_value = value
super().__init__(value, loop=loop) super().__init__(value, loop=loop)

View File

@ -10,17 +10,39 @@ import io
import os import os
import socket import socket
import warnings import warnings
import signal
import threading
import collections
from . import base_events from . import base_events
from . import constants from . import constants
from . import events
from . import futures from . import futures
from . import exceptions
from . import protocols from . import protocols
from . import sslproto from . import sslproto
from . import transports from . import transports
from . import trsock
from .log import logger from .log import logger
def _set_socket_extra(transport, sock):
transport._extra['socket'] = trsock.TransportSocket(sock)
try:
transport._extra['sockname'] = sock.getsockname()
except socket.error:
if transport._loop.get_debug():
logger.warning(
"getsockname() failed on %r", sock, exc_info=True)
if 'peername' not in transport._extra:
try:
transport._extra['peername'] = sock.getpeername()
except socket.error:
# UDP sockets may not have a peer name
transport._extra['peername'] = None
class _ProactorBasePipeTransport(transports._FlowControlMixin, class _ProactorBasePipeTransport(transports._FlowControlMixin,
transports.BaseTransport): transports.BaseTransport):
"""Base class for pipe and socket transports.""" """Base class for pipe and socket transports."""
@ -88,15 +110,14 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
self._read_fut.cancel() self._read_fut.cancel()
self._read_fut = None self._read_fut = None
def __del__(self): def __del__(self, _warn=warnings.warn):
if self._sock is not None: if self._sock is not None:
warnings.warn(f"unclosed transport {self!r}", ResourceWarning, _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
source=self)
self.close() self.close()
def _fatal_error(self, exc, message='Fatal error on pipe transport'): def _fatal_error(self, exc, message='Fatal error on pipe transport'):
try: try:
if isinstance(exc, base_events._FATAL_ERROR_IGNORE): if isinstance(exc, OSError):
if self._loop.get_debug(): if self._loop.get_debug():
logger.debug("%r: %s", self, message, exc_info=True) logger.debug("%r: %s", self, message, exc_info=True)
else: else:
@ -110,7 +131,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
self._force_close(exc) self._force_close(exc)
def _force_close(self, exc): def _force_close(self, exc):
if self._empty_waiter is not None: if self._empty_waiter is not None and not self._empty_waiter.done():
if exc is None: if exc is None:
self._empty_waiter.set_result(None) self._empty_waiter.set_result(None)
else: else:
@ -137,7 +158,7 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin,
# end then it may fail with ERROR_NETNAME_DELETED if we # end then it may fail with ERROR_NETNAME_DELETED if we
# just close our end. First calling shutdown() seems to # just close our end. First calling shutdown() seems to
# cure it, but maybe using DisconnectEx() would be better. # cure it, but maybe using DisconnectEx() would be better.
if hasattr(self._sock, 'shutdown'): if hasattr(self._sock, 'shutdown') and self._sock.fileno() != -1:
self._sock.shutdown(socket.SHUT_RDWR) self._sock.shutdown(socket.SHUT_RDWR)
self._sock.close() self._sock.close()
self._sock = None self._sock = None
@ -212,7 +233,9 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
try: try:
keep_open = self._protocol.eof_received() keep_open = self._protocol.eof_received()
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error( self._fatal_error(
exc, 'Fatal error: protocol.eof_received() call failed.') exc, 'Fatal error: protocol.eof_received() call failed.')
return return
@ -235,7 +258,9 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
if isinstance(self._protocol, protocols.BufferedProtocol): if isinstance(self._protocol, protocols.BufferedProtocol):
try: try:
protocols._feed_data_to_buffered_proto(self._protocol, data) protocols._feed_data_to_buffered_proto(self._protocol, data)
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error(exc, self._fatal_error(exc,
'Fatal error: protocol.buffer_updated() ' 'Fatal error: protocol.buffer_updated() '
'call failed.') 'call failed.')
@ -282,7 +307,7 @@ class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
self._force_close(exc) self._force_close(exc)
except OSError as exc: except OSError as exc:
self._fatal_error(exc, 'Fatal read error on pipe transport') self._fatal_error(exc, 'Fatal read error on pipe transport')
except futures.CancelledError: except exceptions.CancelledError:
if not self._closing: if not self._closing:
raise raise
else: else:
@ -343,6 +368,10 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
def _loop_writing(self, f=None, data=None): def _loop_writing(self, f=None, data=None):
try: try:
if f is not None and self._write_fut is None and self._closing:
# XXX most likely self._force_close() has been called, and
# it has set self._write_fut to None.
return
assert f is self._write_fut assert f is self._write_fut
self._write_fut = None self._write_fut = None
self._pending_write = 0 self._pending_write = 0
@ -421,6 +450,135 @@ class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport):
self.close() self.close()
class _ProactorDatagramTransport(_ProactorBasePipeTransport,
transports.DatagramTransport):
max_size = 256 * 1024
def __init__(self, loop, sock, protocol, address=None,
waiter=None, extra=None):
self._address = address
self._empty_waiter = None
# We don't need to call _protocol.connection_made() since our base
# constructor does it for us.
super().__init__(loop, sock, protocol, waiter=waiter, extra=extra)
# The base constructor sets _buffer = None, so we set it here
self._buffer = collections.deque()
self._loop.call_soon(self._loop_reading)
def _set_extra(self, sock):
_set_socket_extra(self, sock)
def get_write_buffer_size(self):
return sum(len(data) for data, _ in self._buffer)
def abort(self):
self._force_close(None)
def sendto(self, data, addr=None):
if not isinstance(data, (bytes, bytearray, memoryview)):
raise TypeError('data argument must be bytes-like object (%r)',
type(data))
if not data:
return
if self._address is not None and addr not in (None, self._address):
raise ValueError(
f'Invalid address: must be None or {self._address}')
if self._conn_lost and self._address:
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
logger.warning('socket.sendto() raised exception.')
self._conn_lost += 1
return
# Ensure that what we buffer is immutable.
self._buffer.append((bytes(data), addr))
if self._write_fut is None:
# No current write operations are active, kick one off
self._loop_writing()
# else: A write operation is already kicked off
self._maybe_pause_protocol()
def _loop_writing(self, fut=None):
try:
if self._conn_lost:
return
assert fut is self._write_fut
self._write_fut = None
if fut:
# We are in a _loop_writing() done callback, get the result
fut.result()
if not self._buffer or (self._conn_lost and self._address):
# The connection has been closed
if self._closing:
self._loop.call_soon(self._call_connection_lost, None)
return
data, addr = self._buffer.popleft()
if self._address is not None:
self._write_fut = self._loop._proactor.send(self._sock,
data)
else:
self._write_fut = self._loop._proactor.sendto(self._sock,
data,
addr=addr)
except OSError as exc:
self._protocol.error_received(exc)
except Exception as exc:
self._fatal_error(exc, 'Fatal write error on datagram transport')
else:
self._write_fut.add_done_callback(self._loop_writing)
self._maybe_resume_protocol()
def _loop_reading(self, fut=None):
data = None
try:
if self._conn_lost:
return
assert self._read_fut is fut or (self._read_fut is None and
self._closing)
self._read_fut = None
if fut is not None:
res = fut.result()
if self._closing:
# since close() has been called we ignore any read data
data = None
return
if self._address is not None:
data, addr = res, self._address
else:
data, addr = res
if self._conn_lost:
return
if self._address is not None:
self._read_fut = self._loop._proactor.recv(self._sock,
self.max_size)
else:
self._read_fut = self._loop._proactor.recvfrom(self._sock,
self.max_size)
except OSError as exc:
self._protocol.error_received(exc)
except exceptions.CancelledError:
if not self._closing:
raise
else:
if self._read_fut is not None:
self._read_fut.add_done_callback(self._loop_reading)
finally:
if data:
self._protocol.datagram_received(data, addr)
class _ProactorDuplexPipeTransport(_ProactorReadPipeTransport, class _ProactorDuplexPipeTransport(_ProactorReadPipeTransport,
_ProactorBaseWritePipeTransport, _ProactorBaseWritePipeTransport,
transports.Transport): transports.Transport):
@ -440,23 +598,13 @@ class _ProactorSocketTransport(_ProactorReadPipeTransport,
_sendfile_compatible = constants._SendfileMode.TRY_NATIVE _sendfile_compatible = constants._SendfileMode.TRY_NATIVE
def __init__(self, loop, sock, protocol, waiter=None,
extra=None, server=None):
super().__init__(loop, sock, protocol, waiter, extra, server)
base_events._set_nodelay(sock)
def _set_extra(self, sock): def _set_extra(self, sock):
self._extra['socket'] = sock _set_socket_extra(self, sock)
try:
self._extra['sockname'] = sock.getsockname()
except (socket.error, AttributeError):
if self._loop.get_debug():
logger.warning(
"getsockname() failed on %r", sock, exc_info=True)
if 'peername' not in self._extra:
try:
self._extra['peername'] = sock.getpeername()
except (socket.error, AttributeError):
if self._loop.get_debug():
logger.warning("getpeername() failed on %r",
sock, exc_info=True)
def can_write_eof(self): def can_write_eof(self):
return True return True
@ -480,6 +628,9 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
self._accept_futures = {} # socket file descriptor => Future self._accept_futures = {} # socket file descriptor => Future
proactor.set_loop(self) proactor.set_loop(self)
self._make_self_pipe() self._make_self_pipe()
if threading.current_thread() is threading.main_thread():
# wakeup fd can only be installed to a file descriptor from the main thread
signal.set_wakeup_fd(self._csock.fileno())
def _make_socket_transport(self, sock, protocol, waiter=None, def _make_socket_transport(self, sock, protocol, waiter=None,
extra=None, server=None): extra=None, server=None):
@ -499,6 +650,11 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
extra=extra, server=server) extra=extra, server=server)
return ssl_protocol._app_transport return ssl_protocol._app_transport
def _make_datagram_transport(self, sock, protocol,
address=None, waiter=None, extra=None):
return _ProactorDatagramTransport(self, sock, protocol, address,
waiter, extra)
def _make_duplex_pipe_transport(self, sock, protocol, waiter=None, def _make_duplex_pipe_transport(self, sock, protocol, waiter=None,
extra=None): extra=None):
return _ProactorDuplexPipeTransport(self, return _ProactorDuplexPipeTransport(self,
@ -520,6 +676,8 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
if self.is_closed(): if self.is_closed():
return return
if threading.current_thread() is threading.main_thread():
signal.set_wakeup_fd(-1)
# Call these methods before closing the event loop (before calling # Call these methods before closing the event loop (before calling
# BaseEventLoop.close), because they can schedule callbacks with # BaseEventLoop.close), because they can schedule callbacks with
# call_soon(), which is forbidden when the event loop is closed. # call_soon(), which is forbidden when the event loop is closed.
@ -551,11 +709,11 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
try: try:
fileno = file.fileno() fileno = file.fileno()
except (AttributeError, io.UnsupportedOperation) as err: except (AttributeError, io.UnsupportedOperation) as err:
raise events.SendfileNotAvailableError("not a regular file") raise exceptions.SendfileNotAvailableError("not a regular file")
try: try:
fsize = os.fstat(fileno).st_size fsize = os.fstat(fileno).st_size
except OSError as err: except OSError:
raise events.SendfileNotAvailableError("not a regular file") raise exceptions.SendfileNotAvailableError("not a regular file")
blocksize = count if count else fsize blocksize = count if count else fsize
if not blocksize: if not blocksize:
return 0 # empty file return 0 # empty file
@ -604,17 +762,26 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
self._ssock.setblocking(False) self._ssock.setblocking(False)
self._csock.setblocking(False) self._csock.setblocking(False)
self._internal_fds += 1 self._internal_fds += 1
self.call_soon(self._loop_self_reading)
def _loop_self_reading(self, f=None): def _loop_self_reading(self, f=None):
try: try:
if f is not None: if f is not None:
f.result() # may raise f.result() # may raise
if self._self_reading_future is not f:
# When we scheduled this Future, we assigned it to
# _self_reading_future. If it's not there now, something has
# tried to cancel the loop while this callback was still in the
# queue (see windows_events.ProactorEventLoop.run_forever). In
# that case stop here instead of continuing to schedule a new
# iteration.
return
f = self._proactor.recv(self._ssock, 4096) f = self._proactor.recv(self._ssock, 4096)
except futures.CancelledError: except exceptions.CancelledError:
# _close_self_pipe() has been called, stop waiting for data # _close_self_pipe() has been called, stop waiting for data
return return
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self.call_exception_handler({ self.call_exception_handler({
'message': 'Error on reading from the event loop self pipe', 'message': 'Error on reading from the event loop self pipe',
'exception': exc, 'exception': exc,
@ -625,7 +792,22 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
f.add_done_callback(self._loop_self_reading) f.add_done_callback(self._loop_self_reading)
def _write_to_self(self): def _write_to_self(self):
self._csock.send(b'\0') # This may be called from a different thread, possibly after
# _close_self_pipe() has been called or even while it is
# running. Guard for self._csock being None or closed. When
# a socket is closed, send() raises OSError (with errno set to
# EBADF, but let's not rely on the exact error code).
csock = self._csock
if csock is None:
return
try:
csock.send(b'\0')
except OSError:
if self._debug:
logger.debug("Fail to write a null byte into the "
"self-pipe socket",
exc_info=True)
def _start_serving(self, protocol_factory, sock, def _start_serving(self, protocol_factory, sock,
sslcontext=None, server=None, backlog=100, sslcontext=None, server=None, backlog=100,
@ -656,13 +838,13 @@ class BaseProactorEventLoop(base_events.BaseEventLoop):
self.call_exception_handler({ self.call_exception_handler({
'message': 'Accept failed on a socket', 'message': 'Accept failed on a socket',
'exception': exc, 'exception': exc,
'socket': sock, 'socket': trsock.TransportSocket(sock),
}) })
sock.close() sock.close()
elif self._debug: elif self._debug:
logger.debug("Accept failed on socket %r", logger.debug("Accept failed on socket %r",
sock, exc_info=True) sock, exc_info=True)
except futures.CancelledError: except exceptions.CancelledError:
sock.close() sock.close()
else: else:
self._accept_futures[sock.fileno()] = f self._accept_futures[sock.fileno()] = f

View File

@ -16,6 +16,8 @@ class BaseProtocol:
write-only transport like write pipe write-only transport like write pipe
""" """
__slots__ = ()
def connection_made(self, transport): def connection_made(self, transport):
"""Called when a connection is made. """Called when a connection is made.
@ -87,6 +89,8 @@ class Protocol(BaseProtocol):
* CL: connection_lost() * CL: connection_lost()
""" """
__slots__ = ()
def data_received(self, data): def data_received(self, data):
"""Called when some data is received. """Called when some data is received.
@ -105,10 +109,6 @@ class Protocol(BaseProtocol):
class BufferedProtocol(BaseProtocol): class BufferedProtocol(BaseProtocol):
"""Interface for stream protocol with manual buffer control. """Interface for stream protocol with manual buffer control.
Important: this has been added to asyncio in Python 3.7
*on a provisional basis*! Consider it as an experimental API that
might be changed or removed in Python 3.8.
Event methods, such as `create_server` and `create_connection`, Event methods, such as `create_server` and `create_connection`,
accept factories that return protocols that implement this interface. accept factories that return protocols that implement this interface.
@ -130,6 +130,8 @@ class BufferedProtocol(BaseProtocol):
* CL: connection_lost() * CL: connection_lost()
""" """
__slots__ = ()
def get_buffer(self, sizehint): def get_buffer(self, sizehint):
"""Called to allocate a new receive buffer. """Called to allocate a new receive buffer.
@ -160,6 +162,8 @@ class BufferedProtocol(BaseProtocol):
class DatagramProtocol(BaseProtocol): class DatagramProtocol(BaseProtocol):
"""Interface for datagram protocol.""" """Interface for datagram protocol."""
__slots__ = ()
def datagram_received(self, data, addr): def datagram_received(self, data, addr):
"""Called when some datagram is received.""" """Called when some datagram is received."""
@ -173,6 +177,8 @@ class DatagramProtocol(BaseProtocol):
class SubprocessProtocol(BaseProtocol): class SubprocessProtocol(BaseProtocol):
"""Interface for protocol for subprocess calls.""" """Interface for protocol for subprocess calls."""
__slots__ = ()
def pipe_data_received(self, fd, data): def pipe_data_received(self, fd, data):
"""Called when the subprocess writes data into stdout/stderr pipe. """Called when the subprocess writes data into stdout/stderr pipe.

View File

@ -2,6 +2,8 @@ __all__ = ('Queue', 'PriorityQueue', 'LifoQueue', 'QueueFull', 'QueueEmpty')
import collections import collections
import heapq import heapq
import warnings
from types import GenericAlias
from . import events from . import events
from . import locks from . import locks
@ -34,6 +36,9 @@ class Queue:
self._loop = events.get_event_loop() self._loop = events.get_event_loop()
else: else:
self._loop = loop self._loop = loop
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
self._maxsize = maxsize self._maxsize = maxsize
# Futures. # Futures.
@ -41,7 +46,7 @@ class Queue:
# Futures. # Futures.
self._putters = collections.deque() self._putters = collections.deque()
self._unfinished_tasks = 0 self._unfinished_tasks = 0
self._finished = locks.Event(loop=self._loop) self._finished = locks.Event(loop=loop)
self._finished.set() self._finished.set()
self._init(maxsize) self._init(maxsize)
@ -72,6 +77,8 @@ class Queue:
def __str__(self): def __str__(self):
return f'<{type(self).__name__} {self._format()}>' return f'<{type(self).__name__} {self._format()}>'
__class_getitem__ = classmethod(GenericAlias)
def _format(self): def _format(self):
result = f'maxsize={self._maxsize!r}' result = f'maxsize={self._maxsize!r}'
if getattr(self, '_queue', None): if getattr(self, '_queue', None):

View File

@ -5,8 +5,8 @@ from . import events
from . import tasks from . import tasks
def run(main, *, debug=False): def run(main, *, debug=None):
"""Run a coroutine. """Execute the coroutine and return the result.
This function runs the passed coroutine, taking care of This function runs the passed coroutine, taking care of
managing the asyncio event loop and finalizing asynchronous managing the asyncio event loop and finalizing asynchronous
@ -39,12 +39,14 @@ def run(main, *, debug=False):
loop = events.new_event_loop() loop = events.new_event_loop()
try: try:
events.set_event_loop(loop) events.set_event_loop(loop)
if debug is not None:
loop.set_debug(debug) loop.set_debug(debug)
return loop.run_until_complete(main) return loop.run_until_complete(main)
finally: finally:
try: try:
_cancel_all_tasks(loop) _cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens()) loop.run_until_complete(loop.shutdown_asyncgens())
loop.run_until_complete(loop.shutdown_default_executor())
finally: finally:
events.set_event_loop(None) events.set_event_loop(None)
loop.close() loop.close()
@ -59,7 +61,7 @@ def _cancel_all_tasks(loop):
task.cancel() task.cancel()
loop.run_until_complete( loop.run_until_complete(
tasks.gather(*to_cancel, loop=loop, return_exceptions=True)) tasks._gather(*to_cancel, loop=loop, return_exceptions=True))
for task in to_cancel: for task in to_cancel:
if task.cancelled(): if task.cancelled():

View File

@ -25,6 +25,7 @@ from . import futures
from . import protocols from . import protocols
from . import sslproto from . import sslproto
from . import transports from . import transports
from . import trsock
from .log import logger from .log import logger
@ -39,17 +40,6 @@ def _test_selector_event(selector, fd, event):
return bool(key.events & event) return bool(key.events & event)
if hasattr(socket, 'TCP_NODELAY'):
def _set_nodelay(sock):
if (sock.family in {socket.AF_INET, socket.AF_INET6} and
sock.type == socket.SOCK_STREAM and
sock.proto == socket.IPPROTO_TCP):
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
else:
def _set_nodelay(sock):
pass
class BaseSelectorEventLoop(base_events.BaseEventLoop): class BaseSelectorEventLoop(base_events.BaseEventLoop):
"""Selector event loop. """Selector event loop.
@ -138,7 +128,9 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
# a socket is closed, send() raises OSError (with errno set to # a socket is closed, send() raises OSError (with errno set to
# EBADF, but let's not rely on the exact error code). # EBADF, but let's not rely on the exact error code).
csock = self._csock csock = self._csock
if csock is not None: if csock is None:
return
try: try:
csock.send(b'\0') csock.send(b'\0')
except OSError: except OSError:
@ -182,7 +174,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
self.call_exception_handler({ self.call_exception_handler({
'message': 'socket.accept() out of system resource', 'message': 'socket.accept() out of system resource',
'exception': exc, 'exception': exc,
'socket': sock, 'socket': trsock.TransportSocket(sock),
}) })
self._remove_reader(sock.fileno()) self._remove_reader(sock.fileno())
self.call_later(constants.ACCEPT_RETRY_DELAY, self.call_later(constants.ACCEPT_RETRY_DELAY,
@ -219,12 +211,14 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
try: try:
await waiter await waiter
except: except BaseException:
transport.close() transport.close()
raise raise
# It's now up to the protocol to handle the connection. # It's now up to the protocol to handle the connection.
except Exception as exc:
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
if self._debug: if self._debug:
context = { context = {
'message': 'message':
@ -269,6 +263,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
(handle, writer)) (handle, writer))
if reader is not None: if reader is not None:
reader.cancel() reader.cancel()
return handle
def _remove_reader(self, fd): def _remove_reader(self, fd):
if self.is_closed(): if self.is_closed():
@ -305,6 +300,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
(reader, handle)) (reader, handle))
if writer is not None: if writer is not None:
writer.cancel() writer.cancel()
return handle
def _remove_writer(self, fd): def _remove_writer(self, fd):
"""Remove a writer callback.""" """Remove a writer callback."""
@ -332,7 +328,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
def add_reader(self, fd, callback, *args): def add_reader(self, fd, callback, *args):
"""Add a reader callback.""" """Add a reader callback."""
self._ensure_fd_no_transport(fd) self._ensure_fd_no_transport(fd)
return self._add_reader(fd, callback, *args) self._add_reader(fd, callback, *args)
def remove_reader(self, fd): def remove_reader(self, fd):
"""Remove a reader callback.""" """Remove a reader callback."""
@ -342,7 +338,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
def add_writer(self, fd, callback, *args): def add_writer(self, fd, callback, *args):
"""Add a writer callback..""" """Add a writer callback.."""
self._ensure_fd_no_transport(fd) self._ensure_fd_no_transport(fd)
return self._add_writer(fd, callback, *args) self._add_writer(fd, callback, *args)
def remove_writer(self, fd): def remove_writer(self, fd):
"""Remove a writer callback.""" """Remove a writer callback."""
@ -356,29 +352,37 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
The maximum amount of data to be received at once is specified by The maximum amount of data to be received at once is specified by
nbytes. nbytes.
""" """
base_events._check_ssl_socket(sock)
if self._debug and sock.gettimeout() != 0: if self._debug and sock.gettimeout() != 0:
raise ValueError("the socket must be non-blocking") raise ValueError("the socket must be non-blocking")
try:
return sock.recv(n)
except (BlockingIOError, InterruptedError):
pass
fut = self.create_future() fut = self.create_future()
self._sock_recv(fut, None, sock, n) fd = sock.fileno()
self._ensure_fd_no_transport(fd)
handle = self._add_reader(fd, self._sock_recv, fut, sock, n)
fut.add_done_callback(
functools.partial(self._sock_read_done, fd, handle=handle))
return await fut return await fut
def _sock_recv(self, fut, registered_fd, sock, n): def _sock_read_done(self, fd, fut, handle=None):
if handle is None or not handle.cancelled():
self.remove_reader(fd)
def _sock_recv(self, fut, sock, n):
# _sock_recv() can add itself as an I/O callback if the operation can't # _sock_recv() can add itself as an I/O callback if the operation can't
# be done immediately. Don't use it directly, call sock_recv(). # be done immediately. Don't use it directly, call sock_recv().
if registered_fd is not None: if fut.done():
# Remove the callback early. It should be rare that the
# selector says the fd is ready but the call still returns
# EAGAIN, and I am willing to take a hit in that case in
# order to simplify the common case.
self.remove_reader(registered_fd)
if fut.cancelled():
return return
try: try:
data = sock.recv(n) data = sock.recv(n)
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
fd = sock.fileno() return # try again next time
self.add_reader(fd, self._sock_recv, fut, fd, sock, n) except (SystemExit, KeyboardInterrupt):
except Exception as exc: raise
except BaseException as exc:
fut.set_exception(exc) fut.set_exception(exc)
else: else:
fut.set_result(data) fut.set_result(data)
@ -389,30 +393,34 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
The received data is written into *buf* (a writable buffer). The received data is written into *buf* (a writable buffer).
The return value is the number of bytes written. The return value is the number of bytes written.
""" """
base_events._check_ssl_socket(sock)
if self._debug and sock.gettimeout() != 0: if self._debug and sock.gettimeout() != 0:
raise ValueError("the socket must be non-blocking") raise ValueError("the socket must be non-blocking")
try:
return sock.recv_into(buf)
except (BlockingIOError, InterruptedError):
pass
fut = self.create_future() fut = self.create_future()
self._sock_recv_into(fut, None, sock, buf) fd = sock.fileno()
self._ensure_fd_no_transport(fd)
handle = self._add_reader(fd, self._sock_recv_into, fut, sock, buf)
fut.add_done_callback(
functools.partial(self._sock_read_done, fd, handle=handle))
return await fut return await fut
def _sock_recv_into(self, fut, registered_fd, sock, buf): def _sock_recv_into(self, fut, sock, buf):
# _sock_recv_into() can add itself as an I/O callback if the operation # _sock_recv_into() can add itself as an I/O callback if the operation
# can't be done immediately. Don't use it directly, call # can't be done immediately. Don't use it directly, call
# sock_recv_into(). # sock_recv_into().
if registered_fd is not None: if fut.done():
# Remove the callback early. It should be rare that the
# selector says the FD is ready but the call still returns
# EAGAIN, and I am willing to take a hit in that case in
# order to simplify the common case.
self.remove_reader(registered_fd)
if fut.cancelled():
return return
try: try:
nbytes = sock.recv_into(buf) nbytes = sock.recv_into(buf)
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
fd = sock.fileno() return # try again next time
self.add_reader(fd, self._sock_recv_into, fut, fd, sock, buf) except (SystemExit, KeyboardInterrupt):
except Exception as exc: raise
except BaseException as exc:
fut.set_exception(exc) fut.set_exception(exc)
else: else:
fut.set_result(nbytes) fut.set_result(nbytes)
@ -426,48 +434,65 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
raised, and there is no way to determine how much data, if any, was raised, and there is no way to determine how much data, if any, was
successfully processed by the receiving end of the connection. successfully processed by the receiving end of the connection.
""" """
base_events._check_ssl_socket(sock)
if self._debug and sock.gettimeout() != 0: if self._debug and sock.gettimeout() != 0:
raise ValueError("the socket must be non-blocking") raise ValueError("the socket must be non-blocking")
fut = self.create_future()
if data:
self._sock_sendall(fut, None, sock, data)
else:
fut.set_result(None)
return await fut
def _sock_sendall(self, fut, registered_fd, sock, data):
if registered_fd is not None:
self.remove_writer(registered_fd)
if fut.cancelled():
return
try: try:
n = sock.send(data) n = sock.send(data)
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
n = 0 n = 0
except Exception as exc:
if n == len(data):
# all data sent
return
fut = self.create_future()
fd = sock.fileno()
self._ensure_fd_no_transport(fd)
# use a trick with a list in closure to store a mutable state
handle = self._add_writer(fd, self._sock_sendall, fut, sock,
memoryview(data), [n])
fut.add_done_callback(
functools.partial(self._sock_write_done, fd, handle=handle))
return await fut
def _sock_sendall(self, fut, sock, view, pos):
if fut.done():
# Future cancellation can be scheduled on previous loop iteration
return
start = pos[0]
try:
n = sock.send(view[start:])
except (BlockingIOError, InterruptedError):
return
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
fut.set_exception(exc) fut.set_exception(exc)
return return
if n == len(data): start += n
if start == len(view):
fut.set_result(None) fut.set_result(None)
else: else:
if n: pos[0] = start
data = data[n:]
fd = sock.fileno()
self.add_writer(fd, self._sock_sendall, fut, fd, sock, data)
async def sock_connect(self, sock, address): async def sock_connect(self, sock, address):
"""Connect to a remote socket at address. """Connect to a remote socket at address.
This method is a coroutine. This method is a coroutine.
""" """
base_events._check_ssl_socket(sock)
if self._debug and sock.gettimeout() != 0: if self._debug and sock.gettimeout() != 0:
raise ValueError("the socket must be non-blocking") raise ValueError("the socket must be non-blocking")
if not hasattr(socket, 'AF_UNIX') or sock.family != socket.AF_UNIX: if sock.family == socket.AF_INET or (
base_events._HAS_IPv6 and sock.family == socket.AF_INET6):
resolved = await self._ensure_resolved( resolved = await self._ensure_resolved(
address, family=sock.family, proto=sock.proto, loop=self) address, family=sock.family, type=sock.type, proto=sock.proto,
loop=self,
)
_, _, _, _, address = resolved[0] _, _, _, _, address = resolved[0]
fut = self.create_future() fut = self.create_future()
@ -483,19 +508,24 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
# connection runs in background. We have to wait until the socket # connection runs in background. We have to wait until the socket
# becomes writable to be notified when the connection succeed or # becomes writable to be notified when the connection succeed or
# fails. # fails.
self._ensure_fd_no_transport(fd)
handle = self._add_writer(
fd, self._sock_connect_cb, fut, sock, address)
fut.add_done_callback( fut.add_done_callback(
functools.partial(self._sock_connect_done, fd)) functools.partial(self._sock_write_done, fd, handle=handle))
self.add_writer(fd, self._sock_connect_cb, fut, sock, address) except (SystemExit, KeyboardInterrupt):
except Exception as exc: raise
except BaseException as exc:
fut.set_exception(exc) fut.set_exception(exc)
else: else:
fut.set_result(None) fut.set_result(None)
def _sock_connect_done(self, fd, fut): def _sock_write_done(self, fd, fut, handle=None):
if handle is None or not handle.cancelled():
self.remove_writer(fd) self.remove_writer(fd)
def _sock_connect_cb(self, fut, sock, address): def _sock_connect_cb(self, fut, sock, address):
if fut.cancelled(): if fut.done():
return return
try: try:
@ -506,7 +536,9 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
# socket is still registered, the callback will be retried later # socket is still registered, the callback will be retried later
pass pass
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
fut.set_exception(exc) fut.set_exception(exc)
else: else:
fut.set_result(None) fut.set_result(None)
@ -519,24 +551,26 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
object usable to send and receive data on the connection, and address object usable to send and receive data on the connection, and address
is the address bound to the socket on the other end of the connection. is the address bound to the socket on the other end of the connection.
""" """
base_events._check_ssl_socket(sock)
if self._debug and sock.gettimeout() != 0: if self._debug and sock.gettimeout() != 0:
raise ValueError("the socket must be non-blocking") raise ValueError("the socket must be non-blocking")
fut = self.create_future() fut = self.create_future()
self._sock_accept(fut, False, sock) self._sock_accept(fut, sock)
return await fut return await fut
def _sock_accept(self, fut, registered, sock): def _sock_accept(self, fut, sock):
fd = sock.fileno() fd = sock.fileno()
if registered:
self.remove_reader(fd)
if fut.cancelled():
return
try: try:
conn, address = sock.accept() conn, address = sock.accept()
conn.setblocking(False) conn.setblocking(False)
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
self.add_reader(fd, self._sock_accept, fut, True, sock) self._ensure_fd_no_transport(fd)
except Exception as exc: handle = self._add_reader(fd, self._sock_accept, fut, sock)
fut.add_done_callback(
functools.partial(self._sock_read_done, fd, handle=handle))
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
fut.set_exception(exc) fut.set_exception(exc)
else: else:
fut.set_result((conn, address)) fut.set_result((conn, address))
@ -588,8 +622,11 @@ class _SelectorTransport(transports._FlowControlMixin,
def __init__(self, loop, sock, protocol, extra=None, server=None): def __init__(self, loop, sock, protocol, extra=None, server=None):
super().__init__(extra, loop) super().__init__(extra, loop)
self._extra['socket'] = sock self._extra['socket'] = trsock.TransportSocket(sock)
try:
self._extra['sockname'] = sock.getsockname() self._extra['sockname'] = sock.getsockname()
except OSError:
self._extra['sockname'] = None
if 'peername' not in self._extra: if 'peername' not in self._extra:
try: try:
self._extra['peername'] = sock.getpeername() self._extra['peername'] = sock.getpeername()
@ -660,15 +697,14 @@ class _SelectorTransport(transports._FlowControlMixin,
self._loop._remove_writer(self._sock_fd) self._loop._remove_writer(self._sock_fd)
self._loop.call_soon(self._call_connection_lost, None) self._loop.call_soon(self._call_connection_lost, None)
def __del__(self): def __del__(self, _warn=warnings.warn):
if self._sock is not None: if self._sock is not None:
warnings.warn(f"unclosed transport {self!r}", ResourceWarning, _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
source=self)
self._sock.close() self._sock.close()
def _fatal_error(self, exc, message='Fatal error on transport'): def _fatal_error(self, exc, message='Fatal error on transport'):
# Should be called from exception handler only. # Should be called from exception handler only.
if isinstance(exc, base_events._FATAL_ERROR_IGNORE): if isinstance(exc, OSError):
if self._loop.get_debug(): if self._loop.get_debug():
logger.debug("%r: %s", self, message, exc_info=True) logger.debug("%r: %s", self, message, exc_info=True)
else: else:
@ -733,7 +769,7 @@ class _SelectorSocketTransport(_SelectorTransport):
# Disable the Nagle algorithm -- small writes will be # Disable the Nagle algorithm -- small writes will be
# sent without waiting for the TCP ACK. This generally # sent without waiting for the TCP ACK. This generally
# decreases the latency (in some cases significantly.) # decreases the latency (in some cases significantly.)
_set_nodelay(self._sock) base_events._set_nodelay(self._sock)
self._loop.call_soon(self._protocol.connection_made, self) self._loop.call_soon(self._protocol.connection_made, self)
# only start reading when connection_made() has been called # only start reading when connection_made() has been called
@ -782,7 +818,9 @@ class _SelectorSocketTransport(_SelectorTransport):
buf = self._protocol.get_buffer(-1) buf = self._protocol.get_buffer(-1)
if not len(buf): if not len(buf):
raise RuntimeError('get_buffer() returned an empty buffer') raise RuntimeError('get_buffer() returned an empty buffer')
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error( self._fatal_error(
exc, 'Fatal error: protocol.get_buffer() call failed.') exc, 'Fatal error: protocol.get_buffer() call failed.')
return return
@ -791,7 +829,9 @@ class _SelectorSocketTransport(_SelectorTransport):
nbytes = self._sock.recv_into(buf) nbytes = self._sock.recv_into(buf)
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
return return
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error(exc, 'Fatal read error on socket transport') self._fatal_error(exc, 'Fatal read error on socket transport')
return return
@ -801,7 +841,9 @@ class _SelectorSocketTransport(_SelectorTransport):
try: try:
self._protocol.buffer_updated(nbytes) self._protocol.buffer_updated(nbytes)
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error( self._fatal_error(
exc, 'Fatal error: protocol.buffer_updated() call failed.') exc, 'Fatal error: protocol.buffer_updated() call failed.')
@ -812,7 +854,9 @@ class _SelectorSocketTransport(_SelectorTransport):
data = self._sock.recv(self.max_size) data = self._sock.recv(self.max_size)
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
return return
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error(exc, 'Fatal read error on socket transport') self._fatal_error(exc, 'Fatal read error on socket transport')
return return
@ -822,7 +866,9 @@ class _SelectorSocketTransport(_SelectorTransport):
try: try:
self._protocol.data_received(data) self._protocol.data_received(data)
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error( self._fatal_error(
exc, 'Fatal error: protocol.data_received() call failed.') exc, 'Fatal error: protocol.data_received() call failed.')
@ -832,7 +878,9 @@ class _SelectorSocketTransport(_SelectorTransport):
try: try:
keep_open = self._protocol.eof_received() keep_open = self._protocol.eof_received()
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error( self._fatal_error(
exc, 'Fatal error: protocol.eof_received() call failed.') exc, 'Fatal error: protocol.eof_received() call failed.')
return return
@ -868,7 +916,9 @@ class _SelectorSocketTransport(_SelectorTransport):
n = self._sock.send(data) n = self._sock.send(data)
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
pass pass
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error(exc, 'Fatal write error on socket transport') self._fatal_error(exc, 'Fatal write error on socket transport')
return return
else: else:
@ -891,7 +941,9 @@ class _SelectorSocketTransport(_SelectorTransport):
n = self._sock.send(self._buffer) n = self._sock.send(self._buffer)
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
pass pass
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._loop._remove_writer(self._sock_fd) self._loop._remove_writer(self._sock_fd)
self._buffer.clear() self._buffer.clear()
self._fatal_error(exc, 'Fatal write error on socket transport') self._fatal_error(exc, 'Fatal write error on socket transport')
@ -967,7 +1019,9 @@ class _SelectorDatagramTransport(_SelectorTransport):
pass pass
except OSError as exc: except OSError as exc:
self._protocol.error_received(exc) self._protocol.error_received(exc)
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error(exc, 'Fatal read error on datagram transport') self._fatal_error(exc, 'Fatal read error on datagram transport')
else: else:
self._protocol.datagram_received(data, addr) self._protocol.datagram_received(data, addr)
@ -979,9 +1033,11 @@ class _SelectorDatagramTransport(_SelectorTransport):
if not data: if not data:
return return
if self._address and addr not in (None, self._address): if self._address:
if addr not in (None, self._address):
raise ValueError( raise ValueError(
f'Invalid address: must be None or {self._address}') f'Invalid address: must be None or {self._address}')
addr = self._address
if self._conn_lost and self._address: if self._conn_lost and self._address:
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES: if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
@ -992,7 +1048,7 @@ class _SelectorDatagramTransport(_SelectorTransport):
if not self._buffer: if not self._buffer:
# Attempt to send it right away first. # Attempt to send it right away first.
try: try:
if self._address: if self._extra['peername']:
self._sock.send(data) self._sock.send(data)
else: else:
self._sock.sendto(data, addr) self._sock.sendto(data, addr)
@ -1002,7 +1058,9 @@ class _SelectorDatagramTransport(_SelectorTransport):
except OSError as exc: except OSError as exc:
self._protocol.error_received(exc) self._protocol.error_received(exc)
return return
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error( self._fatal_error(
exc, 'Fatal write error on datagram transport') exc, 'Fatal write error on datagram transport')
return return
@ -1015,7 +1073,7 @@ class _SelectorDatagramTransport(_SelectorTransport):
while self._buffer: while self._buffer:
data, addr = self._buffer.popleft() data, addr = self._buffer.popleft()
try: try:
if self._address: if self._extra['peername']:
self._sock.send(data) self._sock.send(data)
else: else:
self._sock.sendto(data, addr) self._sock.sendto(data, addr)
@ -1025,7 +1083,9 @@ class _SelectorDatagramTransport(_SelectorTransport):
except OSError as exc: except OSError as exc:
self._protocol.error_received(exc) self._protocol.error_received(exc)
return return
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._fatal_error( self._fatal_error(
exc, 'Fatal write error on datagram transport') exc, 'Fatal write error on datagram transport')
return return

View File

@ -5,7 +5,6 @@ try:
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
ssl = None ssl = None
from . import base_events
from . import constants from . import constants
from . import protocols from . import protocols
from . import transports from . import transports
@ -316,10 +315,9 @@ class _SSLProtocolTransport(transports._FlowControlMixin,
self._closed = True self._closed = True
self._ssl_protocol._start_shutdown() self._ssl_protocol._start_shutdown()
def __del__(self): def __del__(self, _warn=warnings.warn):
if not self._closed: if not self._closed:
warnings.warn(f"unclosed transport {self!r}", ResourceWarning, _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
source=self)
self.close() self.close()
def is_reading(self): def is_reading(self):
@ -369,6 +367,12 @@ class _SSLProtocolTransport(transports._FlowControlMixin,
"""Return the current size of the write buffer.""" """Return the current size of the write buffer."""
return self._ssl_protocol._transport.get_write_buffer_size() return self._ssl_protocol._transport.get_write_buffer_size()
def get_write_buffer_limits(self):
"""Get the high and low watermarks for write flow control.
Return a tuple (low, high) where low and high are
positive number of bytes."""
return self._ssl_protocol._transport.get_write_buffer_limits()
@property @property
def _protocol_paused(self): def _protocol_paused(self):
# Required for sendfile fallback pause_writing/resume_writing logic # Required for sendfile fallback pause_writing/resume_writing logic
@ -499,7 +503,11 @@ class SSLProtocol(protocols.Protocol):
self._app_transport._closed = True self._app_transport._closed = True
self._transport = None self._transport = None
self._app_transport = None self._app_transport = None
if getattr(self, '_handshake_timeout_handle', None):
self._handshake_timeout_handle.cancel()
self._wakeup_waiter(exc) self._wakeup_waiter(exc)
self._app_protocol = None
self._sslpipe = None
def pause_writing(self): def pause_writing(self):
"""Called when the low-level transport's buffer goes over """Called when the low-level transport's buffer goes over
@ -524,7 +532,9 @@ class SSLProtocol(protocols.Protocol):
try: try:
ssldata, appdata = self._sslpipe.feed_ssldata(data) ssldata, appdata = self._sslpipe.feed_ssldata(data)
except Exception as e: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as e:
self._fatal_error(e, 'SSL error in data received') self._fatal_error(e, 'SSL error in data received')
return return
@ -539,7 +549,9 @@ class SSLProtocol(protocols.Protocol):
self._app_protocol, chunk) self._app_protocol, chunk)
else: else:
self._app_protocol.data_received(chunk) self._app_protocol.data_received(chunk)
except Exception as ex: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as ex:
self._fatal_error( self._fatal_error(
ex, 'application protocol failed to receive SSL data') ex, 'application protocol failed to receive SSL data')
return return
@ -625,7 +637,9 @@ class SSLProtocol(protocols.Protocol):
raise handshake_exc raise handshake_exc
peercert = sslobj.getpeercert() peercert = sslobj.getpeercert()
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
if isinstance(exc, ssl.CertificateError): if isinstance(exc, ssl.CertificateError):
msg = 'SSL handshake failed on verifying the certificate' msg = 'SSL handshake failed on verifying the certificate'
else: else:
@ -688,7 +702,9 @@ class SSLProtocol(protocols.Protocol):
# delete it and reduce the outstanding buffer size. # delete it and reduce the outstanding buffer size.
del self._write_backlog[0] del self._write_backlog[0]
self._write_buffer_size -= len(data) self._write_buffer_size -= len(data)
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
if self._in_handshake: if self._in_handshake:
# Exceptions will be re-raised in _on_handshake_complete. # Exceptions will be re-raised in _on_handshake_complete.
self._on_handshake_complete(exc) self._on_handshake_complete(exc)
@ -696,7 +712,7 @@ class SSLProtocol(protocols.Protocol):
self._fatal_error(exc, 'Fatal error on SSL transport') self._fatal_error(exc, 'Fatal error on SSL transport')
def _fatal_error(self, exc, message='Fatal error on transport'): def _fatal_error(self, exc, message='Fatal error on transport'):
if isinstance(exc, base_events._FATAL_ERROR_IGNORE): if isinstance(exc, OSError):
if self._loop.get_debug(): if self._loop.get_debug():
logger.debug("%r: %s", self, message, exc_info=True) logger.debug("%r: %s", self, message, exc_info=True)
else: else:

View File

@ -1,16 +1,19 @@
__all__ = ( __all__ = (
'StreamReader', 'StreamWriter', 'StreamReaderProtocol', 'StreamReader', 'StreamWriter', 'StreamReaderProtocol',
'open_connection', 'start_server', 'open_connection', 'start_server')
'IncompleteReadError', 'LimitOverrunError',
)
import socket import socket
import sys
import warnings
import weakref
if hasattr(socket, 'AF_UNIX'): if hasattr(socket, 'AF_UNIX'):
__all__ += ('open_unix_connection', 'start_unix_server') __all__ += ('open_unix_connection', 'start_unix_server')
from . import coroutines from . import coroutines
from . import events from . import events
from . import exceptions
from . import format_helpers
from . import protocols from . import protocols
from .log import logger from .log import logger
from .tasks import sleep from .tasks import sleep
@ -19,37 +22,6 @@ from .tasks import sleep
_DEFAULT_LIMIT = 2 ** 16 # 64 KiB _DEFAULT_LIMIT = 2 ** 16 # 64 KiB
class IncompleteReadError(EOFError):
"""
Incomplete read error. Attributes:
- partial: read bytes string before the end of stream was reached
- expected: total number of expected bytes (or None if unknown)
"""
def __init__(self, partial, expected):
super().__init__(f'{len(partial)} bytes read on a total of '
f'{expected!r} expected bytes')
self.partial = partial
self.expected = expected
def __reduce__(self):
return type(self), (self.partial, self.expected)
class LimitOverrunError(Exception):
"""Reached the buffer limit while looking for a separator.
Attributes:
- consumed: total number of to be consumed bytes.
"""
def __init__(self, message, consumed):
super().__init__(message)
self.consumed = consumed
def __reduce__(self):
return type(self), (self.args[0], self.consumed)
async def open_connection(host=None, port=None, *, async def open_connection(host=None, port=None, *,
loop=None, limit=_DEFAULT_LIMIT, **kwds): loop=None, limit=_DEFAULT_LIMIT, **kwds):
"""A wrapper for create_connection() returning a (reader, writer) pair. """A wrapper for create_connection() returning a (reader, writer) pair.
@ -71,6 +43,10 @@ async def open_connection(host=None, port=None, *,
""" """
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_event_loop()
else:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
reader = StreamReader(limit=limit, loop=loop) reader = StreamReader(limit=limit, loop=loop)
protocol = StreamReaderProtocol(reader, loop=loop) protocol = StreamReaderProtocol(reader, loop=loop)
transport, _ = await loop.create_connection( transport, _ = await loop.create_connection(
@ -104,6 +80,10 @@ async def start_server(client_connected_cb, host=None, port=None, *,
""" """
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_event_loop()
else:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
def factory(): def factory():
reader = StreamReader(limit=limit, loop=loop) reader = StreamReader(limit=limit, loop=loop)
@ -122,6 +102,10 @@ if hasattr(socket, 'AF_UNIX'):
"""Similar to `open_connection` but works with UNIX Domain Sockets.""" """Similar to `open_connection` but works with UNIX Domain Sockets."""
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_event_loop()
else:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
reader = StreamReader(limit=limit, loop=loop) reader = StreamReader(limit=limit, loop=loop)
protocol = StreamReaderProtocol(reader, loop=loop) protocol = StreamReaderProtocol(reader, loop=loop)
transport, _ = await loop.create_unix_connection( transport, _ = await loop.create_unix_connection(
@ -134,6 +118,10 @@ if hasattr(socket, 'AF_UNIX'):
"""Similar to `start_server` but works with UNIX Domain Sockets.""" """Similar to `start_server` but works with UNIX Domain Sockets."""
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_event_loop()
else:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
def factory(): def factory():
reader = StreamReader(limit=limit, loop=loop) reader = StreamReader(limit=limit, loop=loop)
@ -208,6 +196,9 @@ class FlowControlMixin(protocols.Protocol):
self._drain_waiter = waiter self._drain_waiter = waiter
await waiter await waiter
def _get_close_waiter(self, stream):
raise NotImplementedError
class StreamReaderProtocol(FlowControlMixin, protocols.Protocol): class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
"""Helper class to adapt between Protocol and StreamReader. """Helper class to adapt between Protocol and StreamReader.
@ -218,46 +209,86 @@ class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
call inappropriate methods of the protocol.) call inappropriate methods of the protocol.)
""" """
_source_traceback = None
def __init__(self, stream_reader, client_connected_cb=None, loop=None): def __init__(self, stream_reader, client_connected_cb=None, loop=None):
super().__init__(loop=loop) super().__init__(loop=loop)
self._stream_reader = stream_reader if stream_reader is not None:
self._stream_reader_wr = weakref.ref(stream_reader)
self._source_traceback = stream_reader._source_traceback
else:
self._stream_reader_wr = None
if client_connected_cb is not None:
# This is a stream created by the `create_server()` function.
# Keep a strong reference to the reader until a connection
# is established.
self._strong_reader = stream_reader
self._reject_connection = False
self._stream_writer = None self._stream_writer = None
self._transport = None
self._client_connected_cb = client_connected_cb self._client_connected_cb = client_connected_cb
self._over_ssl = False self._over_ssl = False
self._closed = self._loop.create_future() self._closed = self._loop.create_future()
@property
def _stream_reader(self):
if self._stream_reader_wr is None:
return None
return self._stream_reader_wr()
def connection_made(self, transport): def connection_made(self, transport):
self._stream_reader.set_transport(transport) if self._reject_connection:
context = {
'message': ('An open stream was garbage collected prior to '
'establishing network connection; '
'call "stream.close()" explicitly.')
}
if self._source_traceback:
context['source_traceback'] = self._source_traceback
self._loop.call_exception_handler(context)
transport.abort()
return
self._transport = transport
reader = self._stream_reader
if reader is not None:
reader.set_transport(transport)
self._over_ssl = transport.get_extra_info('sslcontext') is not None self._over_ssl = transport.get_extra_info('sslcontext') is not None
if self._client_connected_cb is not None: if self._client_connected_cb is not None:
self._stream_writer = StreamWriter(transport, self, self._stream_writer = StreamWriter(transport, self,
self._stream_reader, reader,
self._loop) self._loop)
res = self._client_connected_cb(self._stream_reader, res = self._client_connected_cb(reader,
self._stream_writer) self._stream_writer)
if coroutines.iscoroutine(res): if coroutines.iscoroutine(res):
self._loop.create_task(res) self._loop.create_task(res)
self._strong_reader = None
def connection_lost(self, exc): def connection_lost(self, exc):
if self._stream_reader is not None: reader = self._stream_reader
if reader is not None:
if exc is None: if exc is None:
self._stream_reader.feed_eof() reader.feed_eof()
else: else:
self._stream_reader.set_exception(exc) reader.set_exception(exc)
if not self._closed.done(): if not self._closed.done():
if exc is None: if exc is None:
self._closed.set_result(None) self._closed.set_result(None)
else: else:
self._closed.set_exception(exc) self._closed.set_exception(exc)
super().connection_lost(exc) super().connection_lost(exc)
self._stream_reader = None self._stream_reader_wr = None
self._stream_writer = None self._stream_writer = None
self._transport = None
def data_received(self, data): def data_received(self, data):
self._stream_reader.feed_data(data) reader = self._stream_reader
if reader is not None:
reader.feed_data(data)
def eof_received(self): def eof_received(self):
self._stream_reader.feed_eof() reader = self._stream_reader
if reader is not None:
reader.feed_eof()
if self._over_ssl: if self._over_ssl:
# Prevent a warning in SSLProtocol.eof_received: # Prevent a warning in SSLProtocol.eof_received:
# "returning true from eof_received() # "returning true from eof_received()
@ -265,6 +296,9 @@ class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
return False return False
return True return True
def _get_close_waiter(self, stream):
return self._closed
def __del__(self): def __del__(self):
# Prevent reports about unhandled exceptions. # Prevent reports about unhandled exceptions.
# Better than self._closed._log_traceback = False hack # Better than self._closed._log_traceback = False hack
@ -290,6 +324,8 @@ class StreamWriter:
assert reader is None or isinstance(reader, StreamReader) assert reader is None or isinstance(reader, StreamReader)
self._reader = reader self._reader = reader
self._loop = loop self._loop = loop
self._complete_fut = self._loop.create_future()
self._complete_fut.set_result(None)
def __repr__(self): def __repr__(self):
info = [self.__class__.__name__, f'transport={self._transport!r}'] info = [self.__class__.__name__, f'transport={self._transport!r}']
@ -320,7 +356,7 @@ class StreamWriter:
return self._transport.is_closing() return self._transport.is_closing()
async def wait_closed(self): async def wait_closed(self):
await self._protocol._closed await self._protocol._get_close_waiter(self)
def get_extra_info(self, name, default=None): def get_extra_info(self, name, default=None):
return self._transport.get_extra_info(name, default) return self._transport.get_extra_info(name, default)
@ -338,18 +374,23 @@ class StreamWriter:
if exc is not None: if exc is not None:
raise exc raise exc
if self._transport.is_closing(): if self._transport.is_closing():
# Wait for protocol.connection_lost() call
# Raise connection closing error if any,
# ConnectionResetError otherwise
# Yield to the event loop so connection_lost() may be # Yield to the event loop so connection_lost() may be
# called. Without this, _drain_helper() would return # called. Without this, _drain_helper() would return
# immediately, and code that calls # immediately, and code that calls
# write(...); await drain() # write(...); await drain()
# in a loop would never call connection_lost(), so it # in a loop would never call connection_lost(), so it
# would not see an error when the socket is closed. # would not see an error when the socket is closed.
await sleep(0, loop=self._loop) await sleep(0)
await self._protocol._drain_helper() await self._protocol._drain_helper()
class StreamReader: class StreamReader:
_source_traceback = None
def __init__(self, limit=_DEFAULT_LIMIT, loop=None): def __init__(self, limit=_DEFAULT_LIMIT, loop=None):
# The line length limit is a security feature; # The line length limit is a security feature;
# it also doubles as half the buffer limit. # it also doubles as half the buffer limit.
@ -368,6 +409,9 @@ class StreamReader:
self._exception = None self._exception = None
self._transport = None self._transport = None
self._paused = False self._paused = False
if self._loop.get_debug():
self._source_traceback = format_helpers.extract_stack(
sys._getframe(1))
def __repr__(self): def __repr__(self):
info = ['StreamReader'] info = ['StreamReader']
@ -494,9 +538,9 @@ class StreamReader:
seplen = len(sep) seplen = len(sep)
try: try:
line = await self.readuntil(sep) line = await self.readuntil(sep)
except IncompleteReadError as e: except exceptions.IncompleteReadError as e:
return e.partial return e.partial
except LimitOverrunError as e: except exceptions.LimitOverrunError as e:
if self._buffer.startswith(sep, e.consumed): if self._buffer.startswith(sep, e.consumed):
del self._buffer[:e.consumed + seplen] del self._buffer[:e.consumed + seplen]
else: else:
@ -571,7 +615,7 @@ class StreamReader:
# see upper comment for explanation. # see upper comment for explanation.
offset = buflen + 1 - seplen offset = buflen + 1 - seplen
if offset > self._limit: if offset > self._limit:
raise LimitOverrunError( raise exceptions.LimitOverrunError(
'Separator is not found, and chunk exceed the limit', 'Separator is not found, and chunk exceed the limit',
offset) offset)
@ -582,13 +626,13 @@ class StreamReader:
if self._eof: if self._eof:
chunk = bytes(self._buffer) chunk = bytes(self._buffer)
self._buffer.clear() self._buffer.clear()
raise IncompleteReadError(chunk, None) raise exceptions.IncompleteReadError(chunk, None)
# _wait_for_data() will resume reading if stream was paused. # _wait_for_data() will resume reading if stream was paused.
await self._wait_for_data('readuntil') await self._wait_for_data('readuntil')
if isep > self._limit: if isep > self._limit:
raise LimitOverrunError( raise exceptions.LimitOverrunError(
'Separator is found, but chunk is longer than limit', isep) 'Separator is found, but chunk is longer than limit', isep)
chunk = self._buffer[:isep + seplen] chunk = self._buffer[:isep + seplen]
@ -674,7 +718,7 @@ class StreamReader:
if self._eof: if self._eof:
incomplete = bytes(self._buffer) incomplete = bytes(self._buffer)
self._buffer.clear() self._buffer.clear()
raise IncompleteReadError(incomplete, n) raise exceptions.IncompleteReadError(incomplete, n)
await self._wait_for_data('readexactly') await self._wait_for_data('readexactly')

View File

@ -1,6 +1,7 @@
__all__ = 'create_subprocess_exec', 'create_subprocess_shell' __all__ = 'create_subprocess_exec', 'create_subprocess_shell'
import subprocess import subprocess
import warnings
from . import events from . import events
from . import protocols from . import protocols
@ -25,6 +26,7 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
self._transport = None self._transport = None
self._process_exited = False self._process_exited = False
self._pipe_fds = [] self._pipe_fds = []
self._stdin_closed = self._loop.create_future()
def __repr__(self): def __repr__(self):
info = [self.__class__.__name__] info = [self.__class__.__name__]
@ -76,6 +78,10 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
if pipe is not None: if pipe is not None:
pipe.close() pipe.close()
self.connection_lost(exc) self.connection_lost(exc)
if exc is None:
self._stdin_closed.set_result(None)
else:
self._stdin_closed.set_exception(exc)
return return
if fd == 1: if fd == 1:
reader = self.stdout reader = self.stdout
@ -102,6 +108,10 @@ class SubprocessStreamProtocol(streams.FlowControlMixin,
self._transport.close() self._transport.close()
self._transport = None self._transport = None
def _get_close_waiter(self, stream):
if stream is self.stdin:
return self._stdin_closed
class Process: class Process:
def __init__(self, transport, protocol, loop): def __init__(self, transport, protocol, loop):
@ -183,7 +193,7 @@ class Process:
stderr = self._read_stream(2) stderr = self._read_stream(2)
else: else:
stderr = self._noop() stderr = self._noop()
stdin, stdout, stderr = await tasks.gather(stdin, stdout, stderr, stdin, stdout, stderr = await tasks._gather(stdin, stdout, stderr,
loop=self._loop) loop=self._loop)
await self.wait() await self.wait()
return (stdout, stderr) return (stdout, stderr)
@ -194,6 +204,13 @@ async def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None,
**kwds): **kwds):
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_event_loop()
else:
warnings.warn("The loop argument is deprecated since Python 3.8 "
"and scheduled for removal in Python 3.10.",
DeprecationWarning,
stacklevel=2
)
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit, protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
loop=loop) loop=loop)
transport, protocol = await loop.subprocess_shell( transport, protocol = await loop.subprocess_shell(
@ -208,6 +225,12 @@ async def create_subprocess_exec(program, *args, stdin=None, stdout=None,
limit=streams._DEFAULT_LIMIT, **kwds): limit=streams._DEFAULT_LIMIT, **kwds):
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_event_loop()
else:
warnings.warn("The loop argument is deprecated since Python 3.8 "
"and scheduled for removal in Python 3.10.",
DeprecationWarning,
stacklevel=2
)
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit, protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
loop=loop) loop=loop)
transport, protocol = await loop.subprocess_exec( transport, protocol = await loop.subprocess_exec(

View File

@ -13,15 +13,23 @@ import concurrent.futures
import contextvars import contextvars
import functools import functools
import inspect import inspect
import itertools
import types import types
import warnings import warnings
import weakref import weakref
from types import GenericAlias
from . import base_tasks from . import base_tasks
from . import coroutines from . import coroutines
from . import events from . import events
from . import exceptions
from . import futures from . import futures
from .coroutines import coroutine from .coroutines import _is_coroutine
# Helper to generate new task names
# This uses itertools.count() instead of a "+= 1" operation because the latter
# is not thread safe. See bpo-11866 for a longer explanation.
_task_name_counter = itertools.count(1).__next__
def current_task(loop=None): def current_task(loop=None):
@ -35,7 +43,22 @@ def all_tasks(loop=None):
"""Return a set of all tasks for the loop.""" """Return a set of all tasks for the loop."""
if loop is None: if loop is None:
loop = events.get_running_loop() loop = events.get_running_loop()
return {t for t in _all_tasks # Looping over a WeakSet (_all_tasks) isn't safe as it can be updated from another
# thread while we do so. Therefore we cast it to list prior to filtering. The list
# cast itself requires iteration, so we repeat it several times ignoring
# RuntimeErrors (which are not very likely to occur). See issues 34970 and 36607 for
# details.
i = 0
while True:
try:
tasks = list(_all_tasks)
except RuntimeError:
i += 1
if i >= 1000:
raise
else:
break
return {t for t in tasks
if futures._get_loop(t) is loop and not t.done()} if futures._get_loop(t) is loop and not t.done()}
@ -45,7 +68,32 @@ def _all_tasks_compat(loop=None):
# method. # method.
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_event_loop()
return {t for t in _all_tasks if futures._get_loop(t) is loop} # Looping over a WeakSet (_all_tasks) isn't safe as it can be updated from another
# thread while we do so. Therefore we cast it to list prior to filtering. The list
# cast itself requires iteration, so we repeat it several times ignoring
# RuntimeErrors (which are not very likely to occur). See issues 34970 and 36607 for
# details.
i = 0
while True:
try:
tasks = list(_all_tasks)
except RuntimeError:
i += 1
if i >= 1000:
raise
else:
break
return {t for t in tasks if futures._get_loop(t) is loop}
def _set_task_name(task, name):
if name is not None:
try:
set_name = task.set_name
except AttributeError:
pass
else:
set_name(name)
class Task(futures._PyFuture): # Inherit Python Task implementation class Task(futures._PyFuture): # Inherit Python Task implementation
@ -66,35 +114,7 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
# status is still pending # status is still pending
_log_destroy_pending = True _log_destroy_pending = True
@classmethod def __init__(self, coro, *, loop=None, name=None):
def current_task(cls, loop=None):
"""Return the currently running task in an event loop or None.
By default the current task for the current event loop is returned.
None is returned when called not in the context of a Task.
"""
warnings.warn("Task.current_task() is deprecated, "
"use asyncio.current_task() instead",
PendingDeprecationWarning,
stacklevel=2)
if loop is None:
loop = events.get_event_loop()
return current_task(loop)
@classmethod
def all_tasks(cls, loop=None):
"""Return a set of all tasks for an event loop.
By default all tasks for the current event loop are returned.
"""
warnings.warn("Task.all_tasks() is deprecated, "
"use asyncio.all_tasks() instead",
PendingDeprecationWarning,
stacklevel=2)
return _all_tasks_compat(loop)
def __init__(self, coro, *, loop=None):
super().__init__(loop=loop) super().__init__(loop=loop)
if self._source_traceback: if self._source_traceback:
del self._source_traceback[-1] del self._source_traceback[-1]
@ -104,6 +124,11 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
self._log_destroy_pending = False self._log_destroy_pending = False
raise TypeError(f"a coroutine was expected, got {coro!r}") raise TypeError(f"a coroutine was expected, got {coro!r}")
if name is None:
self._name = f'Task-{_task_name_counter()}'
else:
self._name = str(name)
self._must_cancel = False self._must_cancel = False
self._fut_waiter = None self._fut_waiter = None
self._coro = coro self._coro = coro
@ -123,9 +148,20 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
self._loop.call_exception_handler(context) self._loop.call_exception_handler(context)
super().__del__() super().__del__()
__class_getitem__ = classmethod(GenericAlias)
def _repr_info(self): def _repr_info(self):
return base_tasks._task_repr_info(self) return base_tasks._task_repr_info(self)
def get_coro(self):
return self._coro
def get_name(self):
return self._name
def set_name(self, value):
self._name = str(value)
def set_result(self, result): def set_result(self, result):
raise RuntimeError('Task does not support set_result operation') raise RuntimeError('Task does not support set_result operation')
@ -166,7 +202,7 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
""" """
return base_tasks._task_print_stack(self, limit, file) return base_tasks._task_print_stack(self, limit, file)
def cancel(self): def cancel(self, msg=None):
"""Request that this task cancel itself. """Request that this task cancel itself.
This arranges for a CancelledError to be thrown into the This arranges for a CancelledError to be thrown into the
@ -190,22 +226,23 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
if self.done(): if self.done():
return False return False
if self._fut_waiter is not None: if self._fut_waiter is not None:
if self._fut_waiter.cancel(): if self._fut_waiter.cancel(msg=msg):
# Leave self._fut_waiter; it may be a Task that # Leave self._fut_waiter; it may be a Task that
# catches and ignores the cancellation so we may have # catches and ignores the cancellation so we may have
# to cancel it again later. # to cancel it again later.
return True return True
# It must be the case that self.__step is already scheduled. # It must be the case that self.__step is already scheduled.
self._must_cancel = True self._must_cancel = True
self._cancel_message = msg
return True return True
def __step(self, exc=None): def __step(self, exc=None):
if self.done(): if self.done():
raise futures.InvalidStateError( raise exceptions.InvalidStateError(
f'_step(): already done: {self!r}, {exc!r}') f'_step(): already done: {self!r}, {exc!r}')
if self._must_cancel: if self._must_cancel:
if not isinstance(exc, futures.CancelledError): if not isinstance(exc, exceptions.CancelledError):
exc = futures.CancelledError() exc = self._make_cancelled_error()
self._must_cancel = False self._must_cancel = False
coro = self._coro coro = self._coro
self._fut_waiter = None self._fut_waiter = None
@ -223,16 +260,18 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
if self._must_cancel: if self._must_cancel:
# Task is cancelled right before coro stops. # Task is cancelled right before coro stops.
self._must_cancel = False self._must_cancel = False
super().set_exception(futures.CancelledError()) super().cancel(msg=self._cancel_message)
else: else:
super().set_result(exc.value) super().set_result(exc.value)
except futures.CancelledError: except exceptions.CancelledError as exc:
# Save the original exception so we can chain it later.
self._cancelled_exc = exc
super().cancel() # I.e., Future.cancel(self). super().cancel() # I.e., Future.cancel(self).
except Exception as exc: except (KeyboardInterrupt, SystemExit) as exc:
super().set_exception(exc)
except BaseException as exc:
super().set_exception(exc) super().set_exception(exc)
raise raise
except BaseException as exc:
super().set_exception(exc)
else: else:
blocking = getattr(result, '_asyncio_future_blocking', None) blocking = getattr(result, '_asyncio_future_blocking', None)
if blocking is not None: if blocking is not None:
@ -255,7 +294,8 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
self.__wakeup, context=self._context) self.__wakeup, context=self._context)
self._fut_waiter = result self._fut_waiter = result
if self._must_cancel: if self._must_cancel:
if self._fut_waiter.cancel(): if self._fut_waiter.cancel(
msg=self._cancel_message):
self._must_cancel = False self._must_cancel = False
else: else:
new_exc = RuntimeError( new_exc = RuntimeError(
@ -286,7 +326,7 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
def __wakeup(self, future): def __wakeup(self, future):
try: try:
future.result() future.result()
except Exception as exc: except BaseException as exc:
# This may also be a cancellation. # This may also be a cancellation.
self.__step(exc) self.__step(exc)
else: else:
@ -312,13 +352,15 @@ else:
Task = _CTask = _asyncio.Task Task = _CTask = _asyncio.Task
def create_task(coro): def create_task(coro, *, name=None):
"""Schedule the execution of a coroutine object in a spawn task. """Schedule the execution of a coroutine object in a spawn task.
Return a Task object. Return a Task object.
""" """
loop = events.get_running_loop() loop = events.get_running_loop()
return loop.create_task(coro) task = loop.create_task(coro)
_set_task_name(task, name)
return task
# wait() and as_completed() similar to those in PEP 3148. # wait() and as_completed() similar to those in PEP 3148.
@ -331,7 +373,7 @@ ALL_COMPLETED = concurrent.futures.ALL_COMPLETED
async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED): async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
"""Wait for the Futures and coroutines given by fs to complete. """Wait for the Futures and coroutines given by fs to complete.
The sequence futures must not be empty. The fs iterable must not be empty.
Coroutines will be wrapped in Tasks. Coroutines will be wrapped in Tasks.
@ -352,9 +394,21 @@ async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
raise ValueError(f'Invalid return_when value: {return_when}') raise ValueError(f'Invalid return_when value: {return_when}')
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_running_loop()
else:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
fs = {ensure_future(f, loop=loop) for f in set(fs)} fs = set(fs)
if any(coroutines.iscoroutine(f) for f in fs):
warnings.warn("The explicit passing of coroutine objects to "
"asyncio.wait() is deprecated since Python 3.8, and "
"scheduled for removal in Python 3.11.",
DeprecationWarning, stacklevel=2)
fs = {ensure_future(f, loop=loop) for f in fs}
return await _wait(fs, timeout, return_when, loop) return await _wait(fs, timeout, return_when, loop)
@ -378,7 +432,11 @@ async def wait_for(fut, timeout, *, loop=None):
This function is a coroutine. This function is a coroutine.
""" """
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_running_loop()
else:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
if timeout is None: if timeout is None:
return await fut return await fut
@ -389,8 +447,11 @@ async def wait_for(fut, timeout, *, loop=None):
if fut.done(): if fut.done():
return fut.result() return fut.result()
fut.cancel() await _cancel_and_wait(fut, loop=loop)
raise futures.TimeoutError() try:
return fut.result()
except exceptions.CancelledError as exc:
raise exceptions.TimeoutError() from exc
waiter = loop.create_future() waiter = loop.create_future()
timeout_handle = loop.call_later(timeout, _release_waiter, waiter) timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
@ -403,9 +464,15 @@ async def wait_for(fut, timeout, *, loop=None):
# wait until the future completes or the timeout # wait until the future completes or the timeout
try: try:
await waiter await waiter
except futures.CancelledError: except exceptions.CancelledError:
if fut.done():
return fut.result()
else:
fut.remove_done_callback(cb) fut.remove_done_callback(cb)
fut.cancel() # We must ensure that the task is not running
# after wait_for() returns.
# See https://bugs.python.org/issue32751
await _cancel_and_wait(fut, loop=loop)
raise raise
if fut.done(): if fut.done():
@ -416,7 +483,13 @@ async def wait_for(fut, timeout, *, loop=None):
# after wait_for() returns. # after wait_for() returns.
# See https://bugs.python.org/issue32751 # See https://bugs.python.org/issue32751
await _cancel_and_wait(fut, loop=loop) await _cancel_and_wait(fut, loop=loop)
raise futures.TimeoutError() # In case task cancellation failed with some
# exception, we should re-raise it
# See https://bugs.python.org/issue40607
try:
return fut.result()
except exceptions.CancelledError as exc:
raise exceptions.TimeoutError() from exc
finally: finally:
timeout_handle.cancel() timeout_handle.cancel()
@ -453,10 +526,11 @@ async def _wait(fs, timeout, return_when, loop):
finally: finally:
if timeout_handle is not None: if timeout_handle is not None:
timeout_handle.cancel() timeout_handle.cancel()
for f in fs:
f.remove_done_callback(_on_completion)
done, pending = set(), set() done, pending = set(), set()
for f in fs: for f in fs:
f.remove_done_callback(_on_completion)
if f.done(): if f.done():
done.add(f) done.add(f)
else: else:
@ -500,11 +574,19 @@ def as_completed(fs, *, loop=None, timeout=None):
Note: The futures 'f' are not necessarily members of fs. Note: The futures 'f' are not necessarily members of fs.
""" """
if futures.isfuture(fs) or coroutines.iscoroutine(fs): if futures.isfuture(fs) or coroutines.iscoroutine(fs):
raise TypeError(f"expect a list of futures, not {type(fs).__name__}") raise TypeError(f"expect an iterable of futures, not {type(fs).__name__}")
loop = loop if loop is not None else events.get_event_loop()
todo = {ensure_future(f, loop=loop) for f in set(fs)} if loop is not None:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
from .queues import Queue # Import here to avoid circular import problem. from .queues import Queue # Import here to avoid circular import problem.
done = Queue(loop=loop) done = Queue(loop=loop)
if loop is None:
loop = events.get_event_loop()
todo = {ensure_future(f, loop=loop) for f in set(fs)}
timeout_handle = None timeout_handle = None
def _on_timeout(): def _on_timeout():
@ -525,7 +607,7 @@ def as_completed(fs, *, loop=None, timeout=None):
f = await done.get() f = await done.get()
if f is None: if f is None:
# Dummy value from _on_timeout(). # Dummy value from _on_timeout().
raise futures.TimeoutError raise exceptions.TimeoutError
return f.result() # May raise f.exception(). return f.result() # May raise f.exception().
for f in todo: for f in todo:
@ -550,12 +632,18 @@ def __sleep0():
async def sleep(delay, result=None, *, loop=None): async def sleep(delay, result=None, *, loop=None):
"""Coroutine that completes after a given time (in seconds).""" """Coroutine that completes after a given time (in seconds)."""
if loop is not None:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
if delay <= 0: if delay <= 0:
await __sleep0() await __sleep0()
return result return result
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_running_loop()
future = loop.create_future() future = loop.create_future()
h = loop.call_later(delay, h = loop.call_later(delay,
futures._set_result_unless_cancelled, futures._set_result_unless_cancelled,
@ -580,7 +668,8 @@ def ensure_future(coro_or_future, *, loop=None):
return task return task
elif futures.isfuture(coro_or_future): elif futures.isfuture(coro_or_future):
if loop is not None and loop is not futures._get_loop(coro_or_future): if loop is not None and loop is not futures._get_loop(coro_or_future):
raise ValueError('loop argument must agree with Future') raise ValueError('The future belongs to a different loop than '
'the one specified as the loop argument')
return coro_or_future return coro_or_future
elif inspect.isawaitable(coro_or_future): elif inspect.isawaitable(coro_or_future):
return ensure_future(_wrap_awaitable(coro_or_future), loop=loop) return ensure_future(_wrap_awaitable(coro_or_future), loop=loop)
@ -589,7 +678,7 @@ def ensure_future(coro_or_future, *, loop=None):
'required') 'required')
@coroutine @types.coroutine
def _wrap_awaitable(awaitable): def _wrap_awaitable(awaitable):
"""Helper for asyncio.ensure_future(). """Helper for asyncio.ensure_future().
@ -598,6 +687,8 @@ def _wrap_awaitable(awaitable):
""" """
return (yield from awaitable.__await__()) return (yield from awaitable.__await__())
_wrap_awaitable._is_coroutine = _is_coroutine
class _GatheringFuture(futures.Future): class _GatheringFuture(futures.Future):
"""Helper for gather(). """Helper for gather().
@ -612,12 +703,12 @@ class _GatheringFuture(futures.Future):
self._children = children self._children = children
self._cancel_requested = False self._cancel_requested = False
def cancel(self): def cancel(self, msg=None):
if self.done(): if self.done():
return False return False
ret = False ret = False
for child in self._children: for child in self._children:
if child.cancel(): if child.cancel(msg=msg):
ret = True ret = True
if ret: if ret:
# If any child tasks were actually cancelled, we should # If any child tasks were actually cancelled, we should
@ -649,7 +740,23 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
the outer Future is *not* cancelled in this case. (This is to the outer Future is *not* cancelled in this case. (This is to
prevent the cancellation of one child to cause other children to prevent the cancellation of one child to cause other children to
be cancelled.) be cancelled.)
If *return_exceptions* is False, cancelling gather() after it
has been marked done won't cancel any submitted awaitables.
For instance, gather can be marked done after propagating an
exception to the caller, therefore, calling ``gather.cancel()``
after catching an exception (raised by one of the awaitables) from
gather won't cancel any other awaitables.
""" """
if loop is not None:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
return _gather(*coros_or_futures, loop=loop, return_exceptions=return_exceptions)
def _gather(*coros_or_futures, loop=None, return_exceptions=False):
if not coros_or_futures: if not coros_or_futures:
if loop is None: if loop is None:
loop = events.get_event_loop() loop = events.get_event_loop()
@ -661,7 +768,7 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
nonlocal nfinished nonlocal nfinished
nfinished += 1 nfinished += 1
if outer.done(): if outer is None or outer.done():
if not fut.cancelled(): if not fut.cancelled():
# Mark exception retrieved. # Mark exception retrieved.
fut.exception() fut.exception()
@ -672,7 +779,7 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
# Check if 'fut' is cancelled first, as # Check if 'fut' is cancelled first, as
# 'fut.exception()' will *raise* a CancelledError # 'fut.exception()' will *raise* a CancelledError
# instead of returning it. # instead of returning it.
exc = futures.CancelledError() exc = fut._make_cancelled_error()
outer.set_exception(exc) outer.set_exception(exc)
return return
else: else:
@ -688,10 +795,15 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
for fut in children: for fut in children:
if fut.cancelled(): if fut.cancelled():
# Check if 'fut' is cancelled first, as # Check if 'fut' is cancelled first, as 'fut.exception()'
# 'fut.exception()' will *raise* a CancelledError # will *raise* a CancelledError instead of returning it.
# instead of returning it. # Also, since we're adding the exception return value
res = futures.CancelledError() # to 'results' instead of raising it, don't bother
# setting __context__. This also lets us preserve
# calling '_make_cancelled_error()' at most once.
res = exceptions.CancelledError(
'' if fut._cancel_message is None else
fut._cancel_message)
else: else:
res = fut.exception() res = fut.exception()
if res is None: if res is None:
@ -702,7 +814,8 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
# If gather is being cancelled we must propagate the # If gather is being cancelled we must propagate the
# cancellation regardless of *return_exceptions* argument. # cancellation regardless of *return_exceptions* argument.
# See issue 32684. # See issue 32684.
outer.set_exception(futures.CancelledError()) exc = fut._make_cancelled_error()
outer.set_exception(exc)
else: else:
outer.set_result(results) outer.set_result(results)
@ -710,6 +823,7 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
children = [] children = []
nfuts = 0 nfuts = 0
nfinished = 0 nfinished = 0
outer = None # bpo-46672
for arg in coros_or_futures: for arg in coros_or_futures:
if arg not in arg_to_fut: if arg not in arg_to_fut:
fut = ensure_future(arg, loop=loop) fut = ensure_future(arg, loop=loop)
@ -762,6 +876,10 @@ def shield(arg, *, loop=None):
except CancelledError: except CancelledError:
res = None res = None
""" """
if loop is not None:
warnings.warn("The loop argument is deprecated since Python 3.8, "
"and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
inner = ensure_future(arg, loop=loop) inner = ensure_future(arg, loop=loop)
if inner.done(): if inner.done():
# Shortcut. # Shortcut.
@ -769,7 +887,7 @@ def shield(arg, *, loop=None):
loop = futures._get_loop(inner) loop = futures._get_loop(inner)
outer = loop.create_future() outer = loop.create_future()
def _done_callback(inner): def _inner_done_callback(inner):
if outer.cancelled(): if outer.cancelled():
if not inner.cancelled(): if not inner.cancelled():
# Mark inner's result as retrieved. # Mark inner's result as retrieved.
@ -785,7 +903,13 @@ def shield(arg, *, loop=None):
else: else:
outer.set_result(inner.result()) outer.set_result(inner.result())
inner.add_done_callback(_done_callback)
def _outer_done_callback(outer):
if not inner.done():
inner.remove_done_callback(_inner_done_callback)
inner.add_done_callback(_inner_done_callback)
outer.add_done_callback(_outer_done_callback)
return outer return outer
@ -801,7 +925,9 @@ def run_coroutine_threadsafe(coro, loop):
def callback(): def callback():
try: try:
futures._chain_future(ensure_future(coro, loop=loop), future) futures._chain_future(ensure_future(coro, loop=loop), future)
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
if future.set_running_or_notify_cancel(): if future.set_running_or_notify_cancel():
future.set_exception(exc) future.set_exception(exc)
raise raise

View File

@ -9,6 +9,8 @@ __all__ = (
class BaseTransport: class BaseTransport:
"""Base class for transports.""" """Base class for transports."""
__slots__ = ('_extra',)
def __init__(self, extra=None): def __init__(self, extra=None):
if extra is None: if extra is None:
extra = {} extra = {}
@ -27,8 +29,8 @@ class BaseTransport:
Buffered data will be flushed asynchronously. No more data Buffered data will be flushed asynchronously. No more data
will be received. After all buffered data is flushed, the will be received. After all buffered data is flushed, the
protocol's connection_lost() method will (eventually) called protocol's connection_lost() method will (eventually) be
with None as its argument. called with None as its argument.
""" """
raise NotImplementedError raise NotImplementedError
@ -44,6 +46,8 @@ class BaseTransport:
class ReadTransport(BaseTransport): class ReadTransport(BaseTransport):
"""Interface for read-only transports.""" """Interface for read-only transports."""
__slots__ = ()
def is_reading(self): def is_reading(self):
"""Return True if the transport is receiving.""" """Return True if the transport is receiving."""
raise NotImplementedError raise NotImplementedError
@ -68,6 +72,8 @@ class ReadTransport(BaseTransport):
class WriteTransport(BaseTransport): class WriteTransport(BaseTransport):
"""Interface for write-only transports.""" """Interface for write-only transports."""
__slots__ = ()
def set_write_buffer_limits(self, high=None, low=None): def set_write_buffer_limits(self, high=None, low=None):
"""Set the high- and low-water limits for write flow control. """Set the high- and low-water limits for write flow control.
@ -93,6 +99,12 @@ class WriteTransport(BaseTransport):
"""Return the current size of the write buffer.""" """Return the current size of the write buffer."""
raise NotImplementedError raise NotImplementedError
def get_write_buffer_limits(self):
"""Get the high and low watermarks for write flow control.
Return a tuple (low, high) where low and high are
positive number of bytes."""
raise NotImplementedError
def write(self, data): def write(self, data):
"""Write some data bytes to the transport. """Write some data bytes to the transport.
@ -154,10 +166,14 @@ class Transport(ReadTransport, WriteTransport):
except writelines(), which calls write() in a loop. except writelines(), which calls write() in a loop.
""" """
__slots__ = ()
class DatagramTransport(BaseTransport): class DatagramTransport(BaseTransport):
"""Interface for datagram (UDP) transports.""" """Interface for datagram (UDP) transports."""
__slots__ = ()
def sendto(self, data, addr=None): def sendto(self, data, addr=None):
"""Send data to the transport. """Send data to the transport.
@ -180,6 +196,8 @@ class DatagramTransport(BaseTransport):
class SubprocessTransport(BaseTransport): class SubprocessTransport(BaseTransport):
__slots__ = ()
def get_pid(self): def get_pid(self):
"""Get subprocess id.""" """Get subprocess id."""
raise NotImplementedError raise NotImplementedError
@ -247,6 +265,8 @@ class _FlowControlMixin(Transport):
resume_writing() may be called. resume_writing() may be called.
""" """
__slots__ = ('_loop', '_protocol_paused', '_high_water', '_low_water')
def __init__(self, extra=None, loop=None): def __init__(self, extra=None, loop=None):
super().__init__(extra) super().__init__(extra)
assert loop is not None assert loop is not None
@ -262,7 +282,9 @@ class _FlowControlMixin(Transport):
self._protocol_paused = True self._protocol_paused = True
try: try:
self._protocol.pause_writing() self._protocol.pause_writing()
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._loop.call_exception_handler({ self._loop.call_exception_handler({
'message': 'protocol.pause_writing() failed', 'message': 'protocol.pause_writing() failed',
'exception': exc, 'exception': exc,
@ -276,7 +298,9 @@ class _FlowControlMixin(Transport):
self._protocol_paused = False self._protocol_paused = False
try: try:
self._protocol.resume_writing() self._protocol.resume_writing()
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._loop.call_exception_handler({ self._loop.call_exception_handler({
'message': 'protocol.resume_writing() failed', 'message': 'protocol.resume_writing() failed',
'exception': exc, 'exception': exc,

View File

@ -2,6 +2,7 @@
import errno import errno
import io import io
import itertools
import os import os
import selectors import selectors
import signal import signal
@ -12,12 +13,12 @@ import sys
import threading import threading
import warnings import warnings
from . import base_events from . import base_events
from . import base_subprocess from . import base_subprocess
from . import constants from . import constants
from . import coroutines from . import coroutines
from . import events from . import events
from . import exceptions
from . import futures from . import futures
from . import selector_events from . import selector_events
from . import tasks from . import tasks
@ -28,7 +29,9 @@ from .log import logger
__all__ = ( __all__ = (
'SelectorEventLoop', 'SelectorEventLoop',
'AbstractChildWatcher', 'SafeChildWatcher', 'AbstractChildWatcher', 'SafeChildWatcher',
'FastChildWatcher', 'DefaultEventLoopPolicy', 'FastChildWatcher', 'PidfdChildWatcher',
'MultiLoopChildWatcher', 'ThreadedChildWatcher',
'DefaultEventLoopPolicy',
) )
@ -98,7 +101,7 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
try: try:
# Register a dummy signal handler to ask Python to write the signal # Register a dummy signal handler to ask Python to write the signal
# number in the wakup file descriptor. _process_self_data() will # number in the wakeup file descriptor. _process_self_data() will
# read signal numbers from this file descriptor to handle signals. # read signal numbers from this file descriptor to handle signals.
signal.signal(sig, _sighandler_noop) signal.signal(sig, _sighandler_noop)
@ -168,8 +171,8 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
if not isinstance(sig, int): if not isinstance(sig, int):
raise TypeError(f'sig must be an int, not {sig!r}') raise TypeError(f'sig must be an int, not {sig!r}')
if not (1 <= sig < signal.NSIG): if sig not in signal.valid_signals():
raise ValueError(f'sig {sig} out of range(1, {signal.NSIG})') raise ValueError(f'invalid signal number {sig}')
def _make_read_pipe_transport(self, pipe, protocol, waiter=None, def _make_read_pipe_transport(self, pipe, protocol, waiter=None,
extra=None): extra=None):
@ -183,6 +186,13 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
stdin, stdout, stderr, bufsize, stdin, stdout, stderr, bufsize,
extra=None, **kwargs): extra=None, **kwargs):
with events.get_child_watcher() as watcher: with events.get_child_watcher() as watcher:
if not watcher.is_active():
# Check early.
# Raising exception before process creation
# prevents subprocess execution if the watcher
# is not ready to handle it.
raise RuntimeError("asyncio.get_child_watcher() is not activated, "
"subprocess support is not installed.")
waiter = self.create_future() waiter = self.create_future()
transp = _UnixSubprocessTransport(self, protocol, args, shell, transp = _UnixSubprocessTransport(self, protocol, args, shell,
stdin, stdout, stderr, bufsize, stdin, stdout, stderr, bufsize,
@ -193,7 +203,9 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
self._child_watcher_callback, transp) self._child_watcher_callback, transp)
try: try:
await waiter await waiter
except Exception: except (SystemExit, KeyboardInterrupt):
raise
except BaseException:
transp.close() transp.close()
await transp._wait() await transp._wait()
raise raise
@ -311,24 +323,24 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
server._start_serving() server._start_serving()
# Skip one loop iteration so that all 'loop.add_reader' # Skip one loop iteration so that all 'loop.add_reader'
# go through. # go through.
await tasks.sleep(0, loop=self) await tasks.sleep(0)
return server return server
async def _sock_sendfile_native(self, sock, file, offset, count): async def _sock_sendfile_native(self, sock, file, offset, count):
try: try:
os.sendfile os.sendfile
except AttributeError as exc: except AttributeError:
raise events.SendfileNotAvailableError( raise exceptions.SendfileNotAvailableError(
"os.sendfile() is not available") "os.sendfile() is not available")
try: try:
fileno = file.fileno() fileno = file.fileno()
except (AttributeError, io.UnsupportedOperation) as err: except (AttributeError, io.UnsupportedOperation) as err:
raise events.SendfileNotAvailableError("not a regular file") raise exceptions.SendfileNotAvailableError("not a regular file")
try: try:
fsize = os.fstat(fileno).st_size fsize = os.fstat(fileno).st_size
except OSError as err: except OSError:
raise events.SendfileNotAvailableError("not a regular file") raise exceptions.SendfileNotAvailableError("not a regular file")
blocksize = count if count else fsize blocksize = count if count else fsize
if not blocksize: if not blocksize:
return 0 # empty file return 0 # empty file
@ -382,14 +394,16 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
# one being 'file' is not a regular mmap(2)-like # one being 'file' is not a regular mmap(2)-like
# file, in which case we'll fall back on using # file, in which case we'll fall back on using
# plain send(). # plain send().
err = events.SendfileNotAvailableError( err = exceptions.SendfileNotAvailableError(
"os.sendfile call failed") "os.sendfile call failed")
self._sock_sendfile_update_filepos(fileno, offset, total_sent) self._sock_sendfile_update_filepos(fileno, offset, total_sent)
fut.set_exception(err) fut.set_exception(err)
else: else:
self._sock_sendfile_update_filepos(fileno, offset, total_sent) self._sock_sendfile_update_filepos(fileno, offset, total_sent)
fut.set_exception(exc) fut.set_exception(exc)
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._sock_sendfile_update_filepos(fileno, offset, total_sent) self._sock_sendfile_update_filepos(fileno, offset, total_sent)
fut.set_exception(exc) fut.set_exception(exc)
else: else:
@ -431,6 +445,7 @@ class _UnixReadPipeTransport(transports.ReadTransport):
self._fileno = pipe.fileno() self._fileno = pipe.fileno()
self._protocol = protocol self._protocol = protocol
self._closing = False self._closing = False
self._paused = False
mode = os.fstat(self._fileno).st_mode mode = os.fstat(self._fileno).st_mode
if not (stat.S_ISFIFO(mode) or if not (stat.S_ISFIFO(mode) or
@ -492,10 +507,20 @@ class _UnixReadPipeTransport(transports.ReadTransport):
self._loop.call_soon(self._call_connection_lost, None) self._loop.call_soon(self._call_connection_lost, None)
def pause_reading(self): def pause_reading(self):
if self._closing or self._paused:
return
self._paused = True
self._loop._remove_reader(self._fileno) self._loop._remove_reader(self._fileno)
if self._loop.get_debug():
logger.debug("%r pauses reading", self)
def resume_reading(self): def resume_reading(self):
if self._closing or not self._paused:
return
self._paused = False
self._loop._add_reader(self._fileno, self._read_ready) self._loop._add_reader(self._fileno, self._read_ready)
if self._loop.get_debug():
logger.debug("%r resumes reading", self)
def set_protocol(self, protocol): def set_protocol(self, protocol):
self._protocol = protocol self._protocol = protocol
@ -510,10 +535,9 @@ class _UnixReadPipeTransport(transports.ReadTransport):
if not self._closing: if not self._closing:
self._close(None) self._close(None)
def __del__(self): def __del__(self, _warn=warnings.warn):
if self._pipe is not None: if self._pipe is not None:
warnings.warn(f"unclosed transport {self!r}", ResourceWarning, _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
source=self)
self._pipe.close() self._pipe.close()
def _fatal_error(self, exc, message='Fatal error on pipe transport'): def _fatal_error(self, exc, message='Fatal error on pipe transport'):
@ -641,7 +665,9 @@ class _UnixWritePipeTransport(transports._FlowControlMixin,
n = os.write(self._fileno, data) n = os.write(self._fileno, data)
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
n = 0 n = 0
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._conn_lost += 1 self._conn_lost += 1
self._fatal_error(exc, 'Fatal write error on pipe transport') self._fatal_error(exc, 'Fatal write error on pipe transport')
return return
@ -661,7 +687,9 @@ class _UnixWritePipeTransport(transports._FlowControlMixin,
n = os.write(self._fileno, self._buffer) n = os.write(self._fileno, self._buffer)
except (BlockingIOError, InterruptedError): except (BlockingIOError, InterruptedError):
pass pass
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
self._buffer.clear() self._buffer.clear()
self._conn_lost += 1 self._conn_lost += 1
# Remove writer here, _fatal_error() doesn't it # Remove writer here, _fatal_error() doesn't it
@ -706,10 +734,9 @@ class _UnixWritePipeTransport(transports._FlowControlMixin,
# write_eof is all what we needed to close the write pipe # write_eof is all what we needed to close the write pipe
self.write_eof() self.write_eof()
def __del__(self): def __del__(self, _warn=warnings.warn):
if self._pipe is not None: if self._pipe is not None:
warnings.warn(f"unclosed transport {self!r}", ResourceWarning, _warn(f"unclosed transport {self!r}", ResourceWarning, source=self)
source=self)
self._pipe.close() self._pipe.close()
def abort(self): def abort(self):
@ -717,7 +744,7 @@ class _UnixWritePipeTransport(transports._FlowControlMixin,
def _fatal_error(self, exc, message='Fatal error on pipe transport'): def _fatal_error(self, exc, message='Fatal error on pipe transport'):
# should be called by exception handler only # should be called by exception handler only
if isinstance(exc, base_events._FATAL_ERROR_IGNORE): if isinstance(exc, OSError):
if self._loop.get_debug(): if self._loop.get_debug():
logger.debug("%r: %s", self, message, exc_info=True) logger.debug("%r: %s", self, message, exc_info=True)
else: else:
@ -758,12 +785,18 @@ class _UnixSubprocessTransport(base_subprocess.BaseSubprocessTransport):
# other end). Notably this is needed on AIX, and works # other end). Notably this is needed on AIX, and works
# just fine on other platforms. # just fine on other platforms.
stdin, stdin_w = socket.socketpair() stdin, stdin_w = socket.socketpair()
try:
self._proc = subprocess.Popen( self._proc = subprocess.Popen(
args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr, args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
universal_newlines=False, bufsize=bufsize, **kwargs) universal_newlines=False, bufsize=bufsize, **kwargs)
if stdin_w is not None: if stdin_w is not None:
stdin.close() stdin.close()
self._proc.stdin = open(stdin_w.detach(), 'wb', buffering=bufsize) self._proc.stdin = open(stdin_w.detach(), 'wb', buffering=bufsize)
stdin_w = None
finally:
if stdin_w is not None:
stdin.close()
stdin_w.close()
class AbstractChildWatcher: class AbstractChildWatcher:
@ -825,6 +858,15 @@ class AbstractChildWatcher:
""" """
raise NotImplementedError() raise NotImplementedError()
def is_active(self):
"""Return ``True`` if the watcher is active and is used by the event loop.
Return True if the watcher is installed and ready to handle process exit
notifications.
"""
raise NotImplementedError()
def __enter__(self): def __enter__(self):
"""Enter the watcher's context and allow starting new processes """Enter the watcher's context and allow starting new processes
@ -836,6 +878,98 @@ class AbstractChildWatcher:
raise NotImplementedError() raise NotImplementedError()
class PidfdChildWatcher(AbstractChildWatcher):
"""Child watcher implementation using Linux's pid file descriptors.
This child watcher polls process file descriptors (pidfds) to await child
process termination. In some respects, PidfdChildWatcher is a "Goldilocks"
child watcher implementation. It doesn't require signals or threads, doesn't
interfere with any processes launched outside the event loop, and scales
linearly with the number of subprocesses launched by the event loop. The
main disadvantage is that pidfds are specific to Linux, and only work on
recent (5.3+) kernels.
"""
def __init__(self):
self._loop = None
self._callbacks = {}
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
pass
def is_active(self):
return self._loop is not None and self._loop.is_running()
def close(self):
self.attach_loop(None)
def attach_loop(self, loop):
if self._loop is not None and loop is None and self._callbacks:
warnings.warn(
'A loop is being detached '
'from a child watcher with pending handlers',
RuntimeWarning)
for pidfd, _, _ in self._callbacks.values():
self._loop._remove_reader(pidfd)
os.close(pidfd)
self._callbacks.clear()
self._loop = loop
def add_child_handler(self, pid, callback, *args):
existing = self._callbacks.get(pid)
if existing is not None:
self._callbacks[pid] = existing[0], callback, args
else:
pidfd = os.pidfd_open(pid)
self._loop._add_reader(pidfd, self._do_wait, pid)
self._callbacks[pid] = pidfd, callback, args
def _do_wait(self, pid):
pidfd, callback, args = self._callbacks.pop(pid)
self._loop._remove_reader(pidfd)
try:
_, status = os.waitpid(pid, 0)
except ChildProcessError:
# The child process is already reaped
# (may happen if waitpid() is called elsewhere).
returncode = 255
logger.warning(
"child process pid %d exit status already read: "
" will report returncode 255",
pid)
else:
returncode = _compute_returncode(status)
os.close(pidfd)
callback(pid, returncode, *args)
def remove_child_handler(self, pid):
try:
pidfd, _, _ = self._callbacks.pop(pid)
except KeyError:
return False
self._loop._remove_reader(pidfd)
os.close(pidfd)
return True
def _compute_returncode(status):
if os.WIFSIGNALED(status):
# The child process died because of a signal.
return -os.WTERMSIG(status)
elif os.WIFEXITED(status):
# The child process exited (e.g sys.exit()).
return os.WEXITSTATUS(status)
else:
# The child exited, but we don't understand its status.
# This shouldn't happen, but if it does, let's just
# return that status; perhaps that helps debug it.
return status
class BaseChildWatcher(AbstractChildWatcher): class BaseChildWatcher(AbstractChildWatcher):
def __init__(self): def __init__(self):
@ -845,6 +979,9 @@ class BaseChildWatcher(AbstractChildWatcher):
def close(self): def close(self):
self.attach_loop(None) self.attach_loop(None)
def is_active(self):
return self._loop is not None and self._loop.is_running()
def _do_waitpid(self, expected_pid): def _do_waitpid(self, expected_pid):
raise NotImplementedError() raise NotImplementedError()
@ -874,7 +1011,9 @@ class BaseChildWatcher(AbstractChildWatcher):
def _sig_chld(self): def _sig_chld(self):
try: try:
self._do_waitpid_all() self._do_waitpid_all()
except Exception as exc: except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
# self._loop should always be available here # self._loop should always be available here
# as '_sig_chld' is added as a signal handler # as '_sig_chld' is added as a signal handler
# in 'attach_loop' # in 'attach_loop'
@ -883,19 +1022,6 @@ class BaseChildWatcher(AbstractChildWatcher):
'exception': exc, 'exception': exc,
}) })
def _compute_returncode(self, status):
if os.WIFSIGNALED(status):
# The child process died because of a signal.
return -os.WTERMSIG(status)
elif os.WIFEXITED(status):
# The child process exited (e.g sys.exit()).
return os.WEXITSTATUS(status)
else:
# The child exited, but we don't understand its status.
# This shouldn't happen, but if it does, let's just
# return that status; perhaps that helps debug it.
return status
class SafeChildWatcher(BaseChildWatcher): class SafeChildWatcher(BaseChildWatcher):
"""'Safe' child watcher implementation. """'Safe' child watcher implementation.
@ -919,11 +1045,6 @@ class SafeChildWatcher(BaseChildWatcher):
pass pass
def add_child_handler(self, pid, callback, *args): def add_child_handler(self, pid, callback, *args):
if self._loop is None:
raise RuntimeError(
"Cannot add child handler, "
"the child watcher does not have a loop attached")
self._callbacks[pid] = (callback, args) self._callbacks[pid] = (callback, args)
# Prevent a race condition in case the child is already terminated. # Prevent a race condition in case the child is already terminated.
@ -959,7 +1080,7 @@ class SafeChildWatcher(BaseChildWatcher):
# The child process is still alive. # The child process is still alive.
return return
returncode = self._compute_returncode(status) returncode = _compute_returncode(status)
if self._loop.get_debug(): if self._loop.get_debug():
logger.debug('process %s exited with returncode %s', logger.debug('process %s exited with returncode %s',
expected_pid, returncode) expected_pid, returncode)
@ -1020,11 +1141,6 @@ class FastChildWatcher(BaseChildWatcher):
def add_child_handler(self, pid, callback, *args): def add_child_handler(self, pid, callback, *args):
assert self._forks, "Must use the context manager" assert self._forks, "Must use the context manager"
if self._loop is None:
raise RuntimeError(
"Cannot add child handler, "
"the child watcher does not have a loop attached")
with self._lock: with self._lock:
try: try:
returncode = self._zombies.pop(pid) returncode = self._zombies.pop(pid)
@ -1057,7 +1173,7 @@ class FastChildWatcher(BaseChildWatcher):
# A child process is still alive. # A child process is still alive.
return return
returncode = self._compute_returncode(status) returncode = _compute_returncode(status)
with self._lock: with self._lock:
try: try:
@ -1086,6 +1202,220 @@ class FastChildWatcher(BaseChildWatcher):
callback(pid, returncode, *args) callback(pid, returncode, *args)
class MultiLoopChildWatcher(AbstractChildWatcher):
"""A watcher that doesn't require running loop in the main thread.
This implementation registers a SIGCHLD signal handler on
instantiation (which may conflict with other code that
install own handler for this signal).
The solution is safe but it has a significant overhead when
handling a big number of processes (*O(n)* each time a
SIGCHLD is received).
"""
# Implementation note:
# The class keeps compatibility with AbstractChildWatcher ABC
# To achieve this it has empty attach_loop() method
# and doesn't accept explicit loop argument
# for add_child_handler()/remove_child_handler()
# but retrieves the current loop by get_running_loop()
def __init__(self):
self._callbacks = {}
self._saved_sighandler = None
def is_active(self):
return self._saved_sighandler is not None
def close(self):
self._callbacks.clear()
if self._saved_sighandler is None:
return
handler = signal.getsignal(signal.SIGCHLD)
if handler != self._sig_chld:
logger.warning("SIGCHLD handler was changed by outside code")
else:
signal.signal(signal.SIGCHLD, self._saved_sighandler)
self._saved_sighandler = None
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def add_child_handler(self, pid, callback, *args):
loop = events.get_running_loop()
self._callbacks[pid] = (loop, callback, args)
# Prevent a race condition in case the child is already terminated.
self._do_waitpid(pid)
def remove_child_handler(self, pid):
try:
del self._callbacks[pid]
return True
except KeyError:
return False
def attach_loop(self, loop):
# Don't save the loop but initialize itself if called first time
# The reason to do it here is that attach_loop() is called from
# unix policy only for the main thread.
# Main thread is required for subscription on SIGCHLD signal
if self._saved_sighandler is not None:
return
self._saved_sighandler = signal.signal(signal.SIGCHLD, self._sig_chld)
if self._saved_sighandler is None:
logger.warning("Previous SIGCHLD handler was set by non-Python code, "
"restore to default handler on watcher close.")
self._saved_sighandler = signal.SIG_DFL
# Set SA_RESTART to limit EINTR occurrences.
signal.siginterrupt(signal.SIGCHLD, False)
def _do_waitpid_all(self):
for pid in list(self._callbacks):
self._do_waitpid(pid)
def _do_waitpid(self, expected_pid):
assert expected_pid > 0
try:
pid, status = os.waitpid(expected_pid, os.WNOHANG)
except ChildProcessError:
# The child process is already reaped
# (may happen if waitpid() is called elsewhere).
pid = expected_pid
returncode = 255
logger.warning(
"Unknown child process pid %d, will report returncode 255",
pid)
debug_log = False
else:
if pid == 0:
# The child process is still alive.
return
returncode = _compute_returncode(status)
debug_log = True
try:
loop, callback, args = self._callbacks.pop(pid)
except KeyError: # pragma: no cover
# May happen if .remove_child_handler() is called
# after os.waitpid() returns.
logger.warning("Child watcher got an unexpected pid: %r",
pid, exc_info=True)
else:
if loop.is_closed():
logger.warning("Loop %r that handles pid %r is closed", loop, pid)
else:
if debug_log and loop.get_debug():
logger.debug('process %s exited with returncode %s',
expected_pid, returncode)
loop.call_soon_threadsafe(callback, pid, returncode, *args)
def _sig_chld(self, signum, frame):
try:
self._do_waitpid_all()
except (SystemExit, KeyboardInterrupt):
raise
except BaseException:
logger.warning('Unknown exception in SIGCHLD handler', exc_info=True)
class ThreadedChildWatcher(AbstractChildWatcher):
"""Threaded child watcher implementation.
The watcher uses a thread per process
for waiting for the process finish.
It doesn't require subscription on POSIX signal
but a thread creation is not free.
The watcher has O(1) complexity, its performance doesn't depend
on amount of spawn processes.
"""
def __init__(self):
self._pid_counter = itertools.count(0)
self._threads = {}
def is_active(self):
return True
def close(self):
self._join_threads()
def _join_threads(self):
"""Internal: Join all non-daemon threads"""
threads = [thread for thread in list(self._threads.values())
if thread.is_alive() and not thread.daemon]
for thread in threads:
thread.join()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def __del__(self, _warn=warnings.warn):
threads = [thread for thread in list(self._threads.values())
if thread.is_alive()]
if threads:
_warn(f"{self.__class__} has registered but not finished child processes",
ResourceWarning,
source=self)
def add_child_handler(self, pid, callback, *args):
loop = events.get_running_loop()
thread = threading.Thread(target=self._do_waitpid,
name=f"waitpid-{next(self._pid_counter)}",
args=(loop, pid, callback, args),
daemon=True)
self._threads[pid] = thread
thread.start()
def remove_child_handler(self, pid):
# asyncio never calls remove_child_handler() !!!
# The method is no-op but is implemented because
# abstract base classes require it.
return True
def attach_loop(self, loop):
pass
def _do_waitpid(self, loop, expected_pid, callback, args):
assert expected_pid > 0
try:
pid, status = os.waitpid(expected_pid, 0)
except ChildProcessError:
# The child process is already reaped
# (may happen if waitpid() is called elsewhere).
pid = expected_pid
returncode = 255
logger.warning(
"Unknown child process pid %d, will report returncode 255",
pid)
else:
returncode = _compute_returncode(status)
if loop.get_debug():
logger.debug('process %s exited with returncode %s',
expected_pid, returncode)
if loop.is_closed():
logger.warning("Loop %r that handles pid %r is closed", loop, pid)
else:
loop.call_soon_threadsafe(callback, pid, returncode, *args)
self._threads.pop(expected_pid)
class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy): class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
"""UNIX event loop policy with a watcher for child processes.""" """UNIX event loop policy with a watcher for child processes."""
_loop_factory = _UnixSelectorEventLoop _loop_factory = _UnixSelectorEventLoop
@ -1097,9 +1427,8 @@ class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
def _init_watcher(self): def _init_watcher(self):
with events._lock: with events._lock:
if self._watcher is None: # pragma: no branch if self._watcher is None: # pragma: no branch
self._watcher = SafeChildWatcher() self._watcher = ThreadedChildWatcher()
if isinstance(threading.current_thread(), if threading.current_thread() is threading.main_thread():
threading._MainThread):
self._watcher.attach_loop(self._local._loop) self._watcher.attach_loop(self._local._loop)
def set_event_loop(self, loop): def set_event_loop(self, loop):
@ -1113,13 +1442,13 @@ class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
super().set_event_loop(loop) super().set_event_loop(loop)
if (self._watcher is not None and if (self._watcher is not None and
isinstance(threading.current_thread(), threading._MainThread)): threading.current_thread() is threading.main_thread()):
self._watcher.attach_loop(loop) self._watcher.attach_loop(loop)
def get_child_watcher(self): def get_child_watcher(self):
"""Get the watcher for child processes. """Get the watcher for child processes.
If not yet set, a SafeChildWatcher object is automatically created. If not yet set, a ThreadedChildWatcher object is automatically created.
""" """
if self._watcher is None: if self._watcher is None:
self._init_watcher() self._init_watcher()

View File

@ -1,5 +1,10 @@
"""Selector and proactor event loops for Windows.""" """Selector and proactor event loops for Windows."""
import sys
if sys.platform != 'win32': # pragma: no cover
raise ImportError('win32 only')
import _overlapped import _overlapped
import _winapi import _winapi
import errno import errno
@ -7,11 +12,13 @@ import math
import msvcrt import msvcrt
import socket import socket
import struct import struct
import time
import weakref import weakref
from . import events from . import events
from . import base_subprocess from . import base_subprocess
from . import futures from . import futures
from . import exceptions
from . import proactor_events from . import proactor_events
from . import selector_events from . import selector_events
from . import tasks from . import tasks
@ -73,9 +80,9 @@ class _OverlappedFuture(futures.Future):
self._loop.call_exception_handler(context) self._loop.call_exception_handler(context)
self._ov = None self._ov = None
def cancel(self): def cancel(self, msg=None):
self._cancel_overlapped() self._cancel_overlapped()
return super().cancel() return super().cancel(msg=msg)
def set_exception(self, exception): def set_exception(self, exception):
super().set_exception(exception) super().set_exception(exception)
@ -147,9 +154,9 @@ class _BaseWaitHandleFuture(futures.Future):
self._unregister_wait_cb(None) self._unregister_wait_cb(None)
def cancel(self): def cancel(self, msg=None):
self._unregister_wait() self._unregister_wait()
return super().cancel() return super().cancel(msg=msg)
def set_exception(self, exception): def set_exception(self, exception):
self._unregister_wait() self._unregister_wait()
@ -307,6 +314,25 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
proactor = IocpProactor() proactor = IocpProactor()
super().__init__(proactor) super().__init__(proactor)
def run_forever(self):
try:
assert self._self_reading_future is None
self.call_soon(self._loop_self_reading)
super().run_forever()
finally:
if self._self_reading_future is not None:
ov = self._self_reading_future._ov
self._self_reading_future.cancel()
# self_reading_future was just cancelled so if it hasn't been
# finished yet, it never will be (it's possible that it has
# already finished and its callback is waiting in the queue,
# where it could still happen if the event loop is restarted).
# Unregister it otherwise IocpProactor.close will wait for it
# forever
if ov is not None:
self._proactor._unregister(ov)
self._self_reading_future = None
async def create_pipe_connection(self, protocol_factory, address): async def create_pipe_connection(self, protocol_factory, address):
f = self._proactor.connect_pipe(address) f = self._proactor.connect_pipe(address)
pipe = await f pipe = await f
@ -351,7 +377,7 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
elif self._debug: elif self._debug:
logger.warning("Accept pipe failed on pipe %r", logger.warning("Accept pipe failed on pipe %r",
pipe, exc_info=True) pipe, exc_info=True)
except futures.CancelledError: except exceptions.CancelledError:
if pipe: if pipe:
pipe.close() pipe.close()
else: else:
@ -371,7 +397,9 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
**kwargs) **kwargs)
try: try:
await waiter await waiter
except Exception: except (SystemExit, KeyboardInterrupt):
raise
except BaseException:
transp.close() transp.close()
await transp._wait() await transp._wait()
raise raise
@ -392,10 +420,16 @@ class IocpProactor:
self._unregistered = [] self._unregistered = []
self._stopped_serving = weakref.WeakSet() self._stopped_serving = weakref.WeakSet()
def _check_closed(self):
if self._iocp is None:
raise RuntimeError('IocpProactor is closed')
def __repr__(self): def __repr__(self):
return ('<%s overlapped#=%s result#=%s>' info = ['overlapped#=%s' % len(self._cache),
% (self.__class__.__name__, len(self._cache), 'result#=%s' % len(self._results)]
len(self._results))) if self._iocp is None:
info.append('closed')
return '<%s %s>' % (self.__class__.__name__, " ".join(info))
def set_loop(self, loop): def set_loop(self, loop):
self._loop = loop self._loop = loop
@ -444,7 +478,7 @@ class IocpProactor:
else: else:
ov.ReadFileInto(conn.fileno(), buf) ov.ReadFileInto(conn.fileno(), buf)
except BrokenPipeError: except BrokenPipeError:
return self._result(b'') return self._result(0)
def finish_recv(trans, key, ov): def finish_recv(trans, key, ov):
try: try:
@ -458,6 +492,44 @@ class IocpProactor:
return self._register(ov, conn, finish_recv) return self._register(ov, conn, finish_recv)
def recvfrom(self, conn, nbytes, flags=0):
self._register_with_iocp(conn)
ov = _overlapped.Overlapped(NULL)
try:
ov.WSARecvFrom(conn.fileno(), nbytes, flags)
except BrokenPipeError:
return self._result((b'', None))
def finish_recv(trans, key, ov):
try:
return ov.getresult()
except OSError as exc:
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
_overlapped.ERROR_OPERATION_ABORTED):
raise ConnectionResetError(*exc.args)
else:
raise
return self._register(ov, conn, finish_recv)
def sendto(self, conn, buf, flags=0, addr=None):
self._register_with_iocp(conn)
ov = _overlapped.Overlapped(NULL)
ov.WSASendTo(conn.fileno(), buf, flags, addr)
def finish_send(trans, key, ov):
try:
return ov.getresult()
except OSError as exc:
if exc.winerror in (_overlapped.ERROR_NETNAME_DELETED,
_overlapped.ERROR_OPERATION_ABORTED):
raise ConnectionResetError(*exc.args)
else:
raise
return self._register(ov, conn, finish_send)
def send(self, conn, buf, flags=0): def send(self, conn, buf, flags=0):
self._register_with_iocp(conn) self._register_with_iocp(conn)
ov = _overlapped.Overlapped(NULL) ov = _overlapped.Overlapped(NULL)
@ -497,7 +569,7 @@ class IocpProactor:
# Coroutine closing the accept socket if the future is cancelled # Coroutine closing the accept socket if the future is cancelled
try: try:
await future await future
except futures.CancelledError: except exceptions.CancelledError:
conn.close() conn.close()
raise raise
@ -507,6 +579,14 @@ class IocpProactor:
return future return future
def connect(self, conn, address): def connect(self, conn, address):
if conn.type == socket.SOCK_DGRAM:
# WSAConnect will complete immediately for UDP sockets so we don't
# need to register any IOCP operation
_overlapped.WSAConnect(conn.fileno(), address)
fut = self._loop.create_future()
fut.set_result(None)
return fut
self._register_with_iocp(conn) self._register_with_iocp(conn)
# The socket needs to be locally bound before we call ConnectEx(). # The socket needs to be locally bound before we call ConnectEx().
try: try:
@ -582,7 +662,7 @@ class IocpProactor:
# ConnectPipe() failed with ERROR_PIPE_BUSY: retry later # ConnectPipe() failed with ERROR_PIPE_BUSY: retry later
delay = min(delay * 2, CONNECT_PIPE_MAX_DELAY) delay = min(delay * 2, CONNECT_PIPE_MAX_DELAY)
await tasks.sleep(delay, loop=self._loop) await tasks.sleep(delay)
return windows_utils.PipeHandle(handle) return windows_utils.PipeHandle(handle)
@ -602,6 +682,8 @@ class IocpProactor:
return fut return fut
def _wait_for_handle(self, handle, timeout, _is_cancel): def _wait_for_handle(self, handle, timeout, _is_cancel):
self._check_closed()
if timeout is None: if timeout is None:
ms = _winapi.INFINITE ms = _winapi.INFINITE
else: else:
@ -644,6 +726,8 @@ class IocpProactor:
# that succeed immediately. # that succeed immediately.
def _register(self, ov, obj, callback): def _register(self, ov, obj, callback):
self._check_closed()
# Return a future which will be set with the result of the # Return a future which will be set with the result of the
# operation when it completes. The future's value is actually # operation when it completes. The future's value is actually
# the value returned by callback(). # the value returned by callback().
@ -680,6 +764,7 @@ class IocpProactor:
already be signalled (pending in the proactor event queue). It is also already be signalled (pending in the proactor event queue). It is also
safe if the event is never signalled (because it was cancelled). safe if the event is never signalled (because it was cancelled).
""" """
self._check_closed()
self._unregistered.append(ov) self._unregistered.append(ov)
def _get_accept_socket(self, family): def _get_accept_socket(self, family):
@ -749,6 +834,10 @@ class IocpProactor:
self._stopped_serving.add(obj) self._stopped_serving.add(obj)
def close(self): def close(self):
if self._iocp is None:
# already closed
return
# Cancel remaining registered operations. # Cancel remaining registered operations.
for address, (fut, ov, obj, callback) in list(self._cache.items()): for address, (fut, ov, obj, callback) in list(self._cache.items()):
if fut.cancelled(): if fut.cancelled():
@ -771,12 +860,23 @@ class IocpProactor:
context['source_traceback'] = fut._source_traceback context['source_traceback'] = fut._source_traceback
self._loop.call_exception_handler(context) self._loop.call_exception_handler(context)
# Wait until all cancelled overlapped complete: don't exit with running
# overlapped to prevent a crash. Display progress every second if the
# loop is still running.
msg_update = 1.0
start_time = time.monotonic()
next_msg = start_time + msg_update
while self._cache: while self._cache:
if not self._poll(1): if next_msg <= time.monotonic():
logger.debug('taking long time to close proactor') logger.debug('%r is running after closing for %.1f seconds',
self, time.monotonic() - start_time)
next_msg = time.monotonic() + msg_update
# handle a few events, or timeout
self._poll(msg_update)
self._results = [] self._results = []
if self._iocp is not None:
_winapi.CloseHandle(self._iocp) _winapi.CloseHandle(self._iocp)
self._iocp = None self._iocp = None
@ -810,4 +910,4 @@ class WindowsProactorEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
_loop_factory = ProactorEventLoop _loop_factory = ProactorEventLoop
DefaultEventLoopPolicy = WindowsSelectorEventLoopPolicy DefaultEventLoopPolicy = WindowsProactorEventLoopPolicy

View File

@ -107,10 +107,9 @@ class PipeHandle:
CloseHandle(self._handle) CloseHandle(self._handle)
self._handle = None self._handle = None
def __del__(self): def __del__(self, _warn=warnings.warn):
if self._handle is not None: if self._handle is not None:
warnings.warn(f"unclosed {self!r}", ResourceWarning, _warn(f"unclosed {self!r}", ResourceWarning, source=self)
source=self)
self.close() self.close()
def __enter__(self): def __enter__(self):

View File

@ -228,7 +228,7 @@ class dispatcher:
if sock: if sock:
# Set to nonblocking just to make sure for cases where we # Set to nonblocking just to make sure for cases where we
# get a socket from a blocking source. # get a socket from a blocking source.
sock.setblocking(0) sock.setblocking(False)
self.set_socket(sock, map) self.set_socket(sock, map)
self.connected = True self.connected = True
# The constructor no longer requires that the socket # The constructor no longer requires that the socket
@ -262,8 +262,6 @@ class dispatcher:
status.append(repr(self.addr)) status.append(repr(self.addr))
return '<%s at %#x>' % (' '.join(status), id(self)) return '<%s at %#x>' % (' '.join(status), id(self))
__str__ = __repr__
def add_channel(self, map=None): def add_channel(self, map=None):
#self.log_info('adding channel %s' % self) #self.log_info('adding channel %s' % self)
if map is None: if map is None:
@ -282,7 +280,7 @@ class dispatcher:
def create_socket(self, family=socket.AF_INET, type=socket.SOCK_STREAM): def create_socket(self, family=socket.AF_INET, type=socket.SOCK_STREAM):
self.family_and_type = family, type self.family_and_type = family, type
sock = socket.socket(family, type) sock = socket.socket(family, type)
sock.setblocking(0) sock.setblocking(False)
self.set_socket(sock) self.set_socket(sock)
def set_socket(self, sock, map=None): def set_socket(self, sock, map=None):

View File

@ -82,7 +82,7 @@ def b64decode(s, altchars=None, validate=False):
altchars = _bytes_from_decode_data(altchars) altchars = _bytes_from_decode_data(altchars)
assert len(altchars) == 2, repr(altchars) assert len(altchars) == 2, repr(altchars)
s = s.translate(bytes.maketrans(altchars, b'+/')) s = s.translate(bytes.maketrans(altchars, b'+/'))
if validate and not re.match(b'^[A-Za-z0-9+/]*={0,2}$', s): if validate and not re.fullmatch(b'[A-Za-z0-9+/]*={0,2}', s):
raise binascii.Error('Non-base64 digit found') raise binascii.Error('Non-base64 digit found')
return binascii.a2b_base64(s) return binascii.a2b_base64(s)
@ -231,23 +231,16 @@ def b32decode(s, casefold=False, map01=None):
raise binascii.Error('Non-base32 digit found') from None raise binascii.Error('Non-base32 digit found') from None
decoded += acc.to_bytes(5, 'big') decoded += acc.to_bytes(5, 'big')
# Process the last, partial quanta # Process the last, partial quanta
if padchars: if l % 8 or padchars not in {0, 1, 3, 4, 6}:
raise binascii.Error('Incorrect padding')
if padchars and decoded:
acc <<= 5 * padchars acc <<= 5 * padchars
last = acc.to_bytes(5, 'big') last = acc.to_bytes(5, 'big')
if padchars == 1: leftover = (43 - 5 * padchars) // 8 # 1: 4, 3: 3, 4: 2, 6: 1
decoded[-5:] = last[:-1] decoded[-5:] = last[:leftover]
elif padchars == 3:
decoded[-5:] = last[:-2]
elif padchars == 4:
decoded[-5:] = last[:-3]
elif padchars == 6:
decoded[-5:] = last[:-4]
else:
raise binascii.Error('Incorrect padding')
return bytes(decoded) return bytes(decoded)
# RFC 3548, Base 16 Alphabet specifies uppercase, but hexlify() returns # RFC 3548, Base 16 Alphabet specifies uppercase, but hexlify() returns
# lowercase. The RFC also recommends against accepting input case # lowercase. The RFC also recommends against accepting input case
# insensitively. # insensitively.
@ -327,7 +320,7 @@ def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False):
global _a85chars, _a85chars2 global _a85chars, _a85chars2
# Delay the initialization of tables to not waste memory # Delay the initialization of tables to not waste memory
# if the function is never called # if the function is never called
if _a85chars is None: if _a85chars2 is None:
_a85chars = [bytes((i,)) for i in range(33, 118)] _a85chars = [bytes((i,)) for i in range(33, 118)]
_a85chars2 = [(a + b) for a in _a85chars for b in _a85chars] _a85chars2 = [(a + b) for a in _a85chars for b in _a85chars]
@ -435,7 +428,7 @@ def b85encode(b, pad=False):
global _b85chars, _b85chars2 global _b85chars, _b85chars2
# Delay the initialization of tables to not waste memory # Delay the initialization of tables to not waste memory
# if the function is never called # if the function is never called
if _b85chars is None: if _b85chars2 is None:
_b85chars = [bytes((i,)) for i in _b85alphabet] _b85chars = [bytes((i,)) for i in _b85alphabet]
_b85chars2 = [(a + b) for a in _b85chars for b in _b85chars] _b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
return _85encode(b, _b85chars, _b85chars2, pad) return _85encode(b, _b85chars, _b85chars2, pad)
@ -538,28 +531,12 @@ def encodebytes(s):
pieces.append(binascii.b2a_base64(chunk)) pieces.append(binascii.b2a_base64(chunk))
return b"".join(pieces) return b"".join(pieces)
def encodestring(s):
"""Legacy alias of encodebytes()."""
import warnings
warnings.warn("encodestring() is a deprecated alias since 3.1, "
"use encodebytes()",
DeprecationWarning, 2)
return encodebytes(s)
def decodebytes(s): def decodebytes(s):
"""Decode a bytestring of base-64 data into a bytes object.""" """Decode a bytestring of base-64 data into a bytes object."""
_input_type_check(s) _input_type_check(s)
return binascii.a2b_base64(s) return binascii.a2b_base64(s)
def decodestring(s):
"""Legacy alias of decodebytes()."""
import warnings
warnings.warn("decodestring() is a deprecated alias since Python 3.1, "
"use decodebytes()",
DeprecationWarning, 2)
return decodebytes(s)
# Usable as a script... # Usable as a script...
def main(): def main():

View File

@ -38,7 +38,7 @@ class Bdb:
"""Return canonical form of filename. """Return canonical form of filename.
For real filenames, the canonical form is a case-normalized (on For real filenames, the canonical form is a case-normalized (on
case insenstive filesystems) absolute path. 'Filenames' with case insensitive filesystems) absolute path. 'Filenames' with
angle brackets, such as "<stdin>", generated in interactive angle brackets, such as "<stdin>", generated in interactive
mode, are returned unchanged. mode, are returned unchanged.
""" """
@ -74,7 +74,7 @@ class Bdb:
return: A function or other code block is about to return. return: A function or other code block is about to return.
exception: An exception has occurred. exception: An exception has occurred.
c_call: A C function is about to be called. c_call: A C function is about to be called.
c_return: A C functon has returned. c_return: A C function has returned.
c_exception: A C function has raised an exception. c_exception: A C function has raised an exception.
For the Python events, specialized functions (see the dispatch_*() For the Python events, specialized functions (see the dispatch_*()
@ -117,7 +117,7 @@ class Bdb:
"""Invoke user function and return trace function for call event. """Invoke user function and return trace function for call event.
If the debugger stops on this function call, invoke If the debugger stops on this function call, invoke
self.user_call(). Raise BbdQuit if self.quitting is set. self.user_call(). Raise BdbQuit if self.quitting is set.
Return self.trace_dispatch to continue tracing in this scope. Return self.trace_dispatch to continue tracing in this scope.
""" """
# XXX 'arg' is no longer used # XXX 'arg' is no longer used
@ -190,6 +190,8 @@ class Bdb:
def is_skipped_module(self, module_name): def is_skipped_module(self, module_name):
"Return True if module_name matches any skip pattern." "Return True if module_name matches any skip pattern."
if module_name is None: # some modules do not have names
return False
for pattern in self.skip: for pattern in self.skip:
if fnmatch.fnmatch(module_name, pattern): if fnmatch.fnmatch(module_name, pattern):
return True return True
@ -382,7 +384,7 @@ class Bdb:
return None return None
def _prune_breaks(self, filename, lineno): def _prune_breaks(self, filename, lineno):
"""Prune breakpoints for filname:lineno. """Prune breakpoints for filename:lineno.
A list of breakpoints is maintained in the Bdb instance and in A list of breakpoints is maintained in the Bdb instance and in
the Breakpoint class. If a breakpoint in the Bdb instance no the Breakpoint class. If a breakpoint in the Bdb instance no
@ -546,13 +548,6 @@ class Bdb:
s += frame.f_code.co_name s += frame.f_code.co_name
else: else:
s += "<lambda>" s += "<lambda>"
if '__args__' in frame.f_locals:
args = frame.f_locals['__args__']
else:
args = None
if args:
s += reprlib.repr(args)
else:
s += '()' s += '()'
if '__return__' in frame.f_locals: if '__return__' in frame.f_locals:
rv = frame.f_locals['__return__'] rv = frame.f_locals['__return__']
@ -616,7 +611,7 @@ class Bdb:
# This method is more useful to debug a single function call. # This method is more useful to debug a single function call.
def runcall(self, func, *args, **kwds): def runcall(self, func, /, *args, **kwds):
"""Debug a single function call. """Debug a single function call.
Return the result of the function call. Return the result of the function call.

View File

@ -21,10 +21,16 @@ hexbin(inputfilename, outputfilename)
# input. The resulting code (xx 90 90) would appear to be interpreted as an # input. The resulting code (xx 90 90) would appear to be interpreted as an
# escaped *value* of 0x90. All coders I've seen appear to ignore this nicety... # escaped *value* of 0x90. All coders I've seen appear to ignore this nicety...
# #
import binascii
import contextlib
import io import io
import os import os
import struct import struct
import binascii import warnings
warnings.warn('the binhex module is deprecated', DeprecationWarning,
stacklevel=2)
__all__ = ["binhex","hexbin","Error"] __all__ = ["binhex","hexbin","Error"]
@ -76,6 +82,16 @@ class openrsrc:
def close(self): def close(self):
pass pass
# DeprecationWarning is already emitted on "import binhex". There is no need
# to repeat the warning at each call to deprecated binascii functions.
@contextlib.contextmanager
def _ignore_deprecation_warning():
with warnings.catch_warnings():
warnings.filterwarnings('ignore', '', DeprecationWarning)
yield
class _Hqxcoderengine: class _Hqxcoderengine:
"""Write data to the coder in 3-byte chunks""" """Write data to the coder in 3-byte chunks"""
@ -93,6 +109,7 @@ class _Hqxcoderengine:
self.data = self.data[todo:] self.data = self.data[todo:]
if not data: if not data:
return return
with _ignore_deprecation_warning():
self.hqxdata = self.hqxdata + binascii.b2a_hqx(data) self.hqxdata = self.hqxdata + binascii.b2a_hqx(data)
self._flush(0) self._flush(0)
@ -100,15 +117,16 @@ class _Hqxcoderengine:
first = 0 first = 0
while first <= len(self.hqxdata) - self.linelen: while first <= len(self.hqxdata) - self.linelen:
last = first + self.linelen last = first + self.linelen
self.ofp.write(self.hqxdata[first:last] + b'\n') self.ofp.write(self.hqxdata[first:last] + b'\r')
self.linelen = LINELEN self.linelen = LINELEN
first = last first = last
self.hqxdata = self.hqxdata[first:] self.hqxdata = self.hqxdata[first:]
if force: if force:
self.ofp.write(self.hqxdata + b':\n') self.ofp.write(self.hqxdata + b':\r')
def close(self): def close(self):
if self.data: if self.data:
with _ignore_deprecation_warning():
self.hqxdata = self.hqxdata + binascii.b2a_hqx(self.data) self.hqxdata = self.hqxdata + binascii.b2a_hqx(self.data)
self._flush(1) self._flush(1)
self.ofp.close() self.ofp.close()
@ -125,12 +143,14 @@ class _Rlecoderengine:
self.data = self.data + data self.data = self.data + data
if len(self.data) < REASONABLY_LARGE: if len(self.data) < REASONABLY_LARGE:
return return
with _ignore_deprecation_warning():
rledata = binascii.rlecode_hqx(self.data) rledata = binascii.rlecode_hqx(self.data)
self.ofp.write(rledata) self.ofp.write(rledata)
self.data = b'' self.data = b''
def close(self): def close(self):
if self.data: if self.data:
with _ignore_deprecation_warning():
rledata = binascii.rlecode_hqx(self.data) rledata = binascii.rlecode_hqx(self.data)
self.ofp.write(rledata) self.ofp.write(rledata)
self.ofp.close() self.ofp.close()
@ -276,6 +296,7 @@ class _Hqxdecoderengine:
# #
while True: while True:
try: try:
with _ignore_deprecation_warning():
decdatacur, self.eof = binascii.a2b_hqx(data) decdatacur, self.eof = binascii.a2b_hqx(data)
break break
except binascii.Incomplete: except binascii.Incomplete:
@ -312,6 +333,7 @@ class _Rledecoderengine:
def _fill(self, wtd): def _fill(self, wtd):
self.pre_buffer = self.pre_buffer + self.ifp.read(wtd + 4) self.pre_buffer = self.pre_buffer + self.ifp.read(wtd + 4)
if self.ifp.eof: if self.ifp.eof:
with _ignore_deprecation_warning():
self.post_buffer = self.post_buffer + \ self.post_buffer = self.post_buffer + \
binascii.rledecode_hqx(self.pre_buffer) binascii.rledecode_hqx(self.pre_buffer)
self.pre_buffer = b'' self.pre_buffer = b''
@ -340,6 +362,7 @@ class _Rledecoderengine:
else: else:
mark = mark - 1 mark = mark - 1
with _ignore_deprecation_warning():
self.post_buffer = self.post_buffer + \ self.post_buffer = self.post_buffer + \
binascii.rledecode_hqx(self.pre_buffer[:mark]) binascii.rledecode_hqx(self.pre_buffer[:mark])
self.pre_buffer = self.pre_buffer[mark:] self.pre_buffer = self.pre_buffer[mark:]

View File

@ -9,14 +9,7 @@ def insort_right(a, x, lo=0, hi=None):
slice of a to be searched. slice of a to be searched.
""" """
if lo < 0: lo = bisect_right(a, x, lo, hi)
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
if x < a[mid]: hi = mid
else: lo = mid+1
a.insert(lo, x) a.insert(lo, x)
def bisect_right(a, x, lo=0, hi=None): def bisect_right(a, x, lo=0, hi=None):
@ -36,6 +29,7 @@ def bisect_right(a, x, lo=0, hi=None):
hi = len(a) hi = len(a)
while lo < hi: while lo < hi:
mid = (lo+hi)//2 mid = (lo+hi)//2
# Use __lt__ to match the logic in list.sort() and in heapq
if x < a[mid]: hi = mid if x < a[mid]: hi = mid
else: lo = mid+1 else: lo = mid+1
return lo return lo
@ -49,14 +43,7 @@ def insort_left(a, x, lo=0, hi=None):
slice of a to be searched. slice of a to be searched.
""" """
if lo < 0: lo = bisect_left(a, x, lo, hi)
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
if a[mid] < x: lo = mid+1
else: hi = mid
a.insert(lo, x) a.insert(lo, x)
@ -77,6 +64,7 @@ def bisect_left(a, x, lo=0, hi=None):
hi = len(a) hi = len(a)
while lo < hi: while lo < hi:
mid = (lo+hi)//2 mid = (lo+hi)//2
# Use __lt__ to match the logic in list.sort() and in heapq
if a[mid] < x: lo = mid+1 if a[mid] < x: lo = mid+1
else: hi = mid else: hi = mid
return lo return lo

View File

@ -12,7 +12,6 @@ __author__ = "Nadeem Vawda <nadeem.vawda@gmail.com>"
from builtins import open as _builtin_open from builtins import open as _builtin_open
import io import io
import os import os
import warnings
import _compression import _compression
from threading import RLock from threading import RLock
@ -36,7 +35,7 @@ class BZ2File(_compression.BaseStream):
returned as bytes, and data to be written should be given as bytes. returned as bytes, and data to be written should be given as bytes.
""" """
def __init__(self, filename, mode="r", buffering=None, compresslevel=9): def __init__(self, filename, mode="r", *, compresslevel=9):
"""Open a bzip2-compressed file. """Open a bzip2-compressed file.
If filename is a str, bytes, or PathLike object, it gives the If filename is a str, bytes, or PathLike object, it gives the
@ -47,8 +46,6 @@ class BZ2File(_compression.BaseStream):
'x' for creating exclusively, or 'a' for appending. These can 'x' for creating exclusively, or 'a' for appending. These can
equivalently be given as 'rb', 'wb', 'xb', and 'ab'. equivalently be given as 'rb', 'wb', 'xb', and 'ab'.
buffering is ignored. Its use is deprecated.
If mode is 'w', 'x' or 'a', compresslevel can be a number between 1 If mode is 'w', 'x' or 'a', compresslevel can be a number between 1
and 9 specifying the level of compression: 1 produces the least and 9 specifying the level of compression: 1 produces the least
compression, and 9 (default) produces the most compression. compression, and 9 (default) produces the most compression.
@ -63,10 +60,6 @@ class BZ2File(_compression.BaseStream):
self._closefp = False self._closefp = False
self._mode = _MODE_CLOSED self._mode = _MODE_CLOSED
if buffering is not None:
warnings.warn("Use of 'buffering' argument is deprecated",
DeprecationWarning)
if not (1 <= compresslevel <= 9): if not (1 <= compresslevel <= 9):
raise ValueError("compresslevel must be between 1 and 9") raise ValueError("compresslevel must be between 1 and 9")
@ -233,15 +226,23 @@ class BZ2File(_compression.BaseStream):
"""Write a byte string to the file. """Write a byte string to the file.
Returns the number of uncompressed bytes written, which is Returns the number of uncompressed bytes written, which is
always len(data). Note that due to buffering, the file on disk always the length of data in bytes. Note that due to buffering,
may not reflect the data written until close() is called. the file on disk may not reflect the data written until close()
is called.
""" """
with self._lock: with self._lock:
self._check_can_write() self._check_can_write()
if isinstance(data, (bytes, bytearray)):
length = len(data)
else:
# accept any data that supports the buffer protocol
data = memoryview(data)
length = data.nbytes
compressed = self._compressor.compress(data) compressed = self._compressor.compress(data)
self._fp.write(compressed) self._fp.write(compressed)
self._pos += len(data) self._pos += length
return len(data) return length
def writelines(self, seq): def writelines(self, seq):
"""Write a sequence of byte strings to the file. """Write a sequence of byte strings to the file.

View File

@ -7,6 +7,7 @@
__all__ = ["run", "runctx", "Profile"] __all__ = ["run", "runctx", "Profile"]
import _lsprof import _lsprof
import io
import profile as _pyprofile import profile as _pyprofile
# ____________________________________________________________ # ____________________________________________________________
@ -25,11 +26,11 @@ runctx.__doc__ = _pyprofile.runctx.__doc__
# ____________________________________________________________ # ____________________________________________________________
class Profile(_lsprof.Profiler): class Profile(_lsprof.Profiler):
"""Profile(custom_timer=None, time_unit=None, subcalls=True, builtins=True) """Profile(timer=None, timeunit=None, subcalls=True, builtins=True)
Builds a profiler object using the specified timer function. Builds a profiler object using the specified timer function.
The default timer is a fast built-in one based on real time. The default timer is a fast built-in one based on real time.
For custom timer functions returning integers, time_unit can For custom timer functions returning integers, timeunit can
be a float specifying a scale (i.e. how long each integer unit be a float specifying a scale (i.e. how long each integer unit
is, in seconds). is, in seconds).
""" """
@ -103,13 +104,20 @@ class Profile(_lsprof.Profiler):
return self return self
# This method is more useful to profile a single function call. # This method is more useful to profile a single function call.
def runcall(self, func, *args, **kw): def runcall(self, func, /, *args, **kw):
self.enable() self.enable()
try: try:
return func(*args, **kw) return func(*args, **kw)
finally: finally:
self.disable() self.disable()
def __enter__(self):
self.enable()
return self
def __exit__(self, *exc_info):
self.disable()
# ____________________________________________________________ # ____________________________________________________________
def label(code): def label(code):
@ -124,6 +132,7 @@ def main():
import os import os
import sys import sys
import runpy import runpy
import pstats
from optparse import OptionParser from optparse import OptionParser
usage = "cProfile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..." usage = "cProfile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..."
parser = OptionParser(usage=usage) parser = OptionParser(usage=usage)
@ -132,7 +141,8 @@ def main():
help="Save stats to <outfile>", default=None) help="Save stats to <outfile>", default=None)
parser.add_option('-s', '--sort', dest="sort", parser.add_option('-s', '--sort', dest="sort",
help="Sort order when printing to stdout, based on pstats.Stats class", help="Sort order when printing to stdout, based on pstats.Stats class",
default=-1) default=-1,
choices=sorted(pstats.Stats.sort_arg_dict_default))
parser.add_option('-m', dest="module", action="store_true", parser.add_option('-m', dest="module", action="store_true",
help="Profile a library module", default=False) help="Profile a library module", default=False)
@ -143,6 +153,11 @@ def main():
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
sys.argv[:] = args sys.argv[:] = args
# The script that we're profiling may chdir, so capture the absolute path
# to the output file at startup.
if options.outfile is not None:
options.outfile = os.path.abspath(options.outfile)
if len(args) > 0: if len(args) > 0:
if options.module: if options.module:
code = "run_module(modname, run_name='__main__')" code = "run_module(modname, run_name='__main__')"
@ -153,7 +168,7 @@ def main():
else: else:
progname = args[0] progname = args[0]
sys.path.insert(0, os.path.dirname(progname)) sys.path.insert(0, os.path.dirname(progname))
with open(progname, 'rb') as fp: with io.open_code(progname) as fp:
code = compile(fp.read(), progname, 'exec') code = compile(fp.read(), progname, 'exec')
globs = { globs = {
'__file__': progname, '__file__': progname,
@ -161,7 +176,12 @@ def main():
'__package__': None, '__package__': None,
'__cached__': None, '__cached__': None,
} }
try:
runctx(code, globs, None, options.outfile, options.sort) runctx(code, globs, None, options.outfile, options.sort)
except BrokenPipeError as exc:
# Prevent "Exception ignored" during interpreter shutdown.
sys.stdout = None
sys.exit(exc.errno)
else: else:
parser.print_usage() parser.print_usage()
return parser return parser

View File

@ -127,18 +127,18 @@ def monthrange(year, month):
return day1, ndays return day1, ndays
def monthlen(year, month): def _monthlen(year, month):
return mdays[month] + (month == February and isleap(year)) return mdays[month] + (month == February and isleap(year))
def prevmonth(year, month): def _prevmonth(year, month):
if month == 1: if month == 1:
return year-1, 12 return year-1, 12
else: else:
return year, month-1 return year, month-1
def nextmonth(year, month): def _nextmonth(year, month):
if month == 12: if month == 12:
return year+1, 1 return year+1, 1
else: else:
@ -207,13 +207,13 @@ class Calendar(object):
day1, ndays = monthrange(year, month) day1, ndays = monthrange(year, month)
days_before = (day1 - self.firstweekday) % 7 days_before = (day1 - self.firstweekday) % 7
days_after = (self.firstweekday - day1 - ndays) % 7 days_after = (self.firstweekday - day1 - ndays) % 7
y, m = prevmonth(year, month) y, m = _prevmonth(year, month)
end = monthlen(y, m) + 1 end = _monthlen(y, m) + 1
for d in range(end-days_before, end): for d in range(end-days_before, end):
yield y, m, d yield y, m, d
for d in range(1, ndays + 1): for d in range(1, ndays + 1):
yield year, month, d yield year, month, d
y, m = nextmonth(year, month) y, m = _nextmonth(year, month)
for d in range(1, days_after + 1): for d in range(1, days_after + 1):
yield y, m, d yield y, m, d

View File

@ -38,16 +38,14 @@ import os
import urllib.parse import urllib.parse
from email.parser import FeedParser from email.parser import FeedParser
from email.message import Message from email.message import Message
from warnings import warn
import html import html
import locale import locale
import tempfile import tempfile
__all__ = ["MiniFieldStorage", "FieldStorage", __all__ = ["MiniFieldStorage", "FieldStorage", "parse", "parse_multipart",
"parse", "parse_qs", "parse_qsl", "parse_multipart",
"parse_header", "test", "print_exception", "print_environ", "parse_header", "test", "print_exception", "print_environ",
"print_form", "print_directory", "print_arguments", "print_form", "print_directory", "print_arguments",
"print_environ_usage", "escape"] "print_environ_usage"]
# Logging support # Logging support
# =============== # ===============
@ -117,7 +115,8 @@ log = initlog # The current logging function
# 0 ==> unlimited input # 0 ==> unlimited input
maxlen = 0 maxlen = 0
def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0): def parse(fp=None, environ=os.environ, keep_blank_values=0,
strict_parsing=0, separator='&'):
"""Parse a query in the environment or from a file (default stdin) """Parse a query in the environment or from a file (default stdin)
Arguments, all optional: Arguments, all optional:
@ -136,6 +135,9 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
strict_parsing: flag indicating what to do with parsing errors. strict_parsing: flag indicating what to do with parsing errors.
If false (the default), errors are silently ignored. If false (the default), errors are silently ignored.
If true, errors raise a ValueError exception. If true, errors raise a ValueError exception.
separator: str. The symbol to use for separating the query arguments.
Defaults to &.
""" """
if fp is None: if fp is None:
fp = sys.stdin fp = sys.stdin
@ -156,7 +158,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
if environ['REQUEST_METHOD'] == 'POST': if environ['REQUEST_METHOD'] == 'POST':
ctype, pdict = parse_header(environ['CONTENT_TYPE']) ctype, pdict = parse_header(environ['CONTENT_TYPE'])
if ctype == 'multipart/form-data': if ctype == 'multipart/form-data':
return parse_multipart(fp, pdict) return parse_multipart(fp, pdict, separator=separator)
elif ctype == 'application/x-www-form-urlencoded': elif ctype == 'application/x-www-form-urlencoded':
clength = int(environ['CONTENT_LENGTH']) clength = int(environ['CONTENT_LENGTH'])
if maxlen and clength > maxlen: if maxlen and clength > maxlen:
@ -180,25 +182,10 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
qs = "" qs = ""
environ['QUERY_STRING'] = qs # XXX Shouldn't, really environ['QUERY_STRING'] = qs # XXX Shouldn't, really
return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing, return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing,
encoding=encoding) encoding=encoding, separator=separator)
# parse query string function called from urlparse, def parse_multipart(fp, pdict, encoding="utf-8", errors="replace", separator='&'):
# this is done in order to maintain backward compatibility.
def parse_qs(qs, keep_blank_values=0, strict_parsing=0):
"""Parse a query given as a string argument."""
warn("cgi.parse_qs is deprecated, use urllib.parse.parse_qs instead",
DeprecationWarning, 2)
return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing)
def parse_qsl(qs, keep_blank_values=0, strict_parsing=0):
"""Parse a query given as a string argument."""
warn("cgi.parse_qsl is deprecated, use urllib.parse.parse_qsl instead",
DeprecationWarning, 2)
return urllib.parse.parse_qsl(qs, keep_blank_values, strict_parsing)
def parse_multipart(fp, pdict, encoding="utf-8", errors="replace"):
"""Parse multipart input. """Parse multipart input.
Arguments: Arguments:
@ -217,9 +204,12 @@ def parse_multipart(fp, pdict, encoding="utf-8", errors="replace"):
ctype = "multipart/form-data; boundary={}".format(boundary) ctype = "multipart/form-data; boundary={}".format(boundary)
headers = Message() headers = Message()
headers.set_type(ctype) headers.set_type(ctype)
try:
headers['Content-Length'] = pdict['CONTENT-LENGTH'] headers['Content-Length'] = pdict['CONTENT-LENGTH']
except KeyError:
pass
fs = FieldStorage(fp, headers=headers, encoding=encoding, errors=errors, fs = FieldStorage(fp, headers=headers, encoding=encoding, errors=errors,
environ={'REQUEST_METHOD': 'POST'}) environ={'REQUEST_METHOD': 'POST'}, separator=separator)
return {k: fs.getlist(k) for k in fs} return {k: fs.getlist(k) for k in fs}
def _parseparam(s): def _parseparam(s):
@ -328,7 +318,8 @@ class FieldStorage:
""" """
def __init__(self, fp=None, headers=None, outerboundary=b'', def __init__(self, fp=None, headers=None, outerboundary=b'',
environ=os.environ, keep_blank_values=0, strict_parsing=0, environ=os.environ, keep_blank_values=0, strict_parsing=0,
limit=None, encoding='utf-8', errors='replace'): limit=None, encoding='utf-8', errors='replace',
max_num_fields=None, separator='&'):
"""Constructor. Read multipart/* until last part. """Constructor. Read multipart/* until last part.
Arguments, all optional: Arguments, all optional:
@ -368,10 +359,15 @@ class FieldStorage:
for the page sending the form (content-type : meta http-equiv or for the page sending the form (content-type : meta http-equiv or
header) header)
max_num_fields: int. If set, then __init__ throws a ValueError
if there are more than n fields read by parse_qsl().
""" """
method = 'GET' method = 'GET'
self.keep_blank_values = keep_blank_values self.keep_blank_values = keep_blank_values
self.strict_parsing = strict_parsing self.strict_parsing = strict_parsing
self.max_num_fields = max_num_fields
self.separator = separator
if 'REQUEST_METHOD' in environ: if 'REQUEST_METHOD' in environ:
method = environ['REQUEST_METHOD'].upper() method = environ['REQUEST_METHOD'].upper()
self.qs_on_post = None self.qs_on_post = None
@ -473,7 +469,7 @@ class FieldStorage:
if maxlen and clen > maxlen: if maxlen and clen > maxlen:
raise ValueError('Maximum content length exceeded') raise ValueError('Maximum content length exceeded')
self.length = clen self.length = clen
if self.limit is None and clen: if self.limit is None and clen >= 0:
self.limit = clen self.limit = clen
self.list = self.file = None self.list = self.file = None
@ -595,12 +591,11 @@ class FieldStorage:
qs = qs.decode(self.encoding, self.errors) qs = qs.decode(self.encoding, self.errors)
if self.qs_on_post: if self.qs_on_post:
qs += '&' + self.qs_on_post qs += '&' + self.qs_on_post
self.list = []
query = urllib.parse.parse_qsl( query = urllib.parse.parse_qsl(
qs, self.keep_blank_values, self.strict_parsing, qs, self.keep_blank_values, self.strict_parsing,
encoding=self.encoding, errors=self.errors) encoding=self.encoding, errors=self.errors,
for key, value in query: max_num_fields=self.max_num_fields, separator=self.separator)
self.list.append(MiniFieldStorage(key, value)) self.list = [MiniFieldStorage(key, value) for key, value in query]
self.skip_lines() self.skip_lines()
FieldStorageClass = None FieldStorageClass = None
@ -614,9 +609,9 @@ class FieldStorage:
if self.qs_on_post: if self.qs_on_post:
query = urllib.parse.parse_qsl( query = urllib.parse.parse_qsl(
self.qs_on_post, self.keep_blank_values, self.strict_parsing, self.qs_on_post, self.keep_blank_values, self.strict_parsing,
encoding=self.encoding, errors=self.errors) encoding=self.encoding, errors=self.errors,
for key, value in query: max_num_fields=self.max_num_fields, separator=self.separator)
self.list.append(MiniFieldStorage(key, value)) self.list.extend(MiniFieldStorage(key, value) for key, value in query)
klass = self.FieldStorageClass or self.__class__ klass = self.FieldStorageClass or self.__class__
first_line = self.fp.readline() # bytes first_line = self.fp.readline() # bytes
@ -631,6 +626,11 @@ class FieldStorage:
first_line = self.fp.readline() first_line = self.fp.readline()
self.bytes_read += len(first_line) self.bytes_read += len(first_line)
# Propagate max_num_fields into the sub class appropriately
max_num_fields = self.max_num_fields
if max_num_fields is not None:
max_num_fields -= len(self.list)
while True: while True:
parser = FeedParser() parser = FeedParser()
hdr_text = b"" hdr_text = b""
@ -650,9 +650,19 @@ class FieldStorage:
if 'content-length' in headers: if 'content-length' in headers:
del headers['content-length'] del headers['content-length']
limit = None if self.limit is None \
else self.limit - self.bytes_read
part = klass(self.fp, headers, ib, environ, keep_blank_values, part = klass(self.fp, headers, ib, environ, keep_blank_values,
strict_parsing,self.limit-self.bytes_read, strict_parsing, limit,
self.encoding, self.errors) self.encoding, self.errors, max_num_fields, self.separator)
if max_num_fields is not None:
max_num_fields -= 1
if part.list:
max_num_fields -= len(part.list)
if max_num_fields < 0:
raise ValueError('Max number of fields exceeded')
self.bytes_read += part.bytes_read self.bytes_read += part.bytes_read
self.list.append(part) self.list.append(part)
if part.done or self.bytes_read >= self.length > 0: if part.done or self.bytes_read >= self.length > 0:
@ -734,7 +744,8 @@ class FieldStorage:
last_line_lfend = True last_line_lfend = True
_read = 0 _read = 0
while 1: while 1:
if _read >= self.limit:
if self.limit is not None and 0 <= self.limit <= _read:
break break
line = self.fp.readline(1<<16) # bytes line = self.fp.readline(1<<16) # bytes
self.bytes_read += len(line) self.bytes_read += len(line)
@ -974,18 +985,6 @@ environment as well. Here are some common variable names:
# Utilities # Utilities
# ========= # =========
def escape(s, quote=None):
"""Deprecated API."""
warn("cgi.escape is deprecated, use html.escape instead",
DeprecationWarning, stacklevel=2)
s = s.replace("&", "&amp;") # Must be done first!
s = s.replace("<", "&lt;")
s = s.replace(">", "&gt;")
if quote:
s = s.replace('"', "&quot;")
return s
def valid_boundary(s): def valid_boundary(s):
import re import re
if isinstance(s, bytes): if isinstance(s, bytes):

View File

@ -124,8 +124,9 @@ function calls leading up to the error, in the order they occurred.</p>'''
args, varargs, varkw, locals = inspect.getargvalues(frame) args, varargs, varkw, locals = inspect.getargvalues(frame)
call = '' call = ''
if func != '?': if func != '?':
call = 'in ' + strong(pydoc.html.escape(func)) + \ call = 'in ' + strong(pydoc.html.escape(func))
inspect.formatargvalues(args, varargs, varkw, locals, if func != "<module>":
call += inspect.formatargvalues(args, varargs, varkw, locals,
formatvalue=lambda value: '=' + pydoc.html.repr(value)) formatvalue=lambda value: '=' + pydoc.html.repr(value))
highlight = {} highlight = {}
@ -207,8 +208,9 @@ function calls leading up to the error, in the order they occurred.
args, varargs, varkw, locals = inspect.getargvalues(frame) args, varargs, varkw, locals = inspect.getargvalues(frame)
call = '' call = ''
if func != '?': if func != '?':
call = 'in ' + func + \ call = 'in ' + func
inspect.formatargvalues(args, varargs, varkw, locals, if func != "<module>":
call += inspect.formatargvalues(args, varargs, varkw, locals,
formatvalue=lambda value: '=' + pydoc.text.repr(value)) formatvalue=lambda value: '=' + pydoc.text.repr(value))
highlight = {} highlight = {}

View File

@ -40,7 +40,7 @@ class InteractiveInterpreter:
Arguments are as for compile_command(). Arguments are as for compile_command().
One several things can happen: One of several things can happen:
1) The input is incorrect; compile_command() raised an 1) The input is incorrect; compile_command() raised an
exception (SyntaxError or OverflowError). A syntax traceback exception (SyntaxError or OverflowError). A syntax traceback

View File

@ -386,7 +386,7 @@ class StreamWriter(Codec):
def reset(self): def reset(self):
""" Flushes and resets the codec buffers used for keeping state. """ Resets the codec buffers used for keeping internal state.
Calling this method should ensure that the data on the Calling this method should ensure that the data on the
output is put into a clean state, that allows appending output is put into a clean state, that allows appending
@ -620,7 +620,7 @@ class StreamReader(Codec):
def reset(self): def reset(self):
""" Resets the codec buffers used for keeping state. """ Resets the codec buffers used for keeping internal state.
Note that no stream repositioning should take place. Note that no stream repositioning should take place.
This method is primarily intended to be able to recover This method is primarily intended to be able to recover
@ -838,7 +838,7 @@ class StreamRecoder:
def writelines(self, list): def writelines(self, list):
data = ''.join(list) data = b''.join(list)
data, bytesdecoded = self.decode(data, self.errors) data, bytesdecoded = self.decode(data, self.errors)
return self.writer.write(data) return self.writer.write(data)
@ -847,6 +847,12 @@ class StreamRecoder:
self.reader.reset() self.reader.reset()
self.writer.reset() self.writer.reset()
def seek(self, offset, whence=0):
# Seeks must be propagated to both the readers and writers
# as they might need to reset their internal buffers.
self.reader.seek(offset, whence)
self.writer.seek(offset, whence)
def __getattr__(self, name, def __getattr__(self, name,
getattr=getattr): getattr=getattr):
@ -862,7 +868,7 @@ class StreamRecoder:
### Shortcuts ### Shortcuts
def open(filename, mode='r', encoding=None, errors='strict', buffering=1): def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):
""" Open an encoded file using the given mode and return """ Open an encoded file using the given mode and return
a wrapped version providing transparent encoding/decoding. a wrapped version providing transparent encoding/decoding.
@ -883,7 +889,8 @@ def open(filename, mode='r', encoding=None, errors='strict', buffering=1):
encoding error occurs. encoding error occurs.
buffering has the same meaning as for the builtin open() API. buffering has the same meaning as for the builtin open() API.
It defaults to line buffered. It defaults to -1 which means that the default buffer size will
be used.
The returned wrapped file object provides an extra attribute The returned wrapped file object provides an extra attribute
.encoding which allows querying the used encoding. This .encoding which allows querying the used encoding. This
@ -898,11 +905,16 @@ def open(filename, mode='r', encoding=None, errors='strict', buffering=1):
file = builtins.open(filename, mode, buffering) file = builtins.open(filename, mode, buffering)
if encoding is None: if encoding is None:
return file return file
try:
info = lookup(encoding) info = lookup(encoding)
srw = StreamReaderWriter(file, info.streamreader, info.streamwriter, errors) srw = StreamReaderWriter(file, info.streamreader, info.streamwriter, errors)
# Add attributes to simplify introspection # Add attributes to simplify introspection
srw.encoding = encoding srw.encoding = encoding
return srw return srw
except:
file.close()
raise
def EncodedFile(file, data_encoding, file_encoding=None, errors='strict'): def EncodedFile(file, data_encoding, file_encoding=None, errors='strict'):

View File

@ -57,6 +57,7 @@ Compile():
""" """
import __future__ import __future__
import warnings
_features = [getattr(__future__, fname) _features = [getattr(__future__, fname)
for fname in __future__.all_feature_names] for fname in __future__.all_feature_names]
@ -80,9 +81,14 @@ def _maybe_compile(compiler, source, filename, symbol):
try: try:
code = compiler(source, filename, symbol) code = compiler(source, filename, symbol)
except SyntaxError as err: except SyntaxError:
pass pass
# Catch syntax warnings after the first compile
# to emit warnings (SyntaxWarning, DeprecationWarning) at most once.
with warnings.catch_warnings():
warnings.simplefilter("error")
try: try:
code1 = compiler(source + "\n", filename, symbol) code1 = compiler(source + "\n", filename, symbol)
except SyntaxError as e: except SyntaxError as e:
@ -93,10 +99,13 @@ def _maybe_compile(compiler, source, filename, symbol):
except SyntaxError as e: except SyntaxError as e:
err2 = e err2 = e
try:
if code: if code:
return code return code
if not code1 and repr(err1) == repr(err2): if not code1 and repr(err1) == repr(err2):
raise err1 raise err1
finally:
err1 = err2 = None
def _compile(source, filename, symbol): def _compile(source, filename, symbol):
return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT) return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT)
@ -109,7 +118,8 @@ def compile_command(source, filename="<input>", symbol="single"):
source -- the source string; may contain \n characters source -- the source string; may contain \n characters
filename -- optional filename from which source was read; default filename -- optional filename from which source was read; default
"<input>" "<input>"
symbol -- optional grammar start symbol; "single" (default) or "eval" symbol -- optional grammar start symbol; "single" (default), "exec"
or "eval"
Return value / exceptions raised: Return value / exceptions raised:
@ -130,7 +140,7 @@ class Compile:
self.flags = PyCF_DONT_IMPLY_DEDENT self.flags = PyCF_DONT_IMPLY_DEDENT
def __call__(self, source, filename, symbol): def __call__(self, source, filename, symbol):
codeob = compile(source, filename, symbol, self.flags, 1) codeob = compile(source, filename, symbol, self.flags, True)
for feature in _features: for feature in _features:
if codeob.co_flags & feature.compiler_flag: if codeob.co_flags & feature.compiler_flag:
self.flags |= feature.compiler_flag self.flags |= feature.compiler_flag

View File

@ -14,17 +14,30 @@ list, set, and tuple.
''' '''
__all__ = ['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList', __all__ = [
'UserString', 'Counter', 'OrderedDict', 'ChainMap'] 'ChainMap',
'Counter',
'OrderedDict',
'UserDict',
'UserList',
'UserString',
'defaultdict',
'deque',
'namedtuple',
]
import _collections_abc import _collections_abc
from operator import itemgetter as _itemgetter, eq as _eq
from keyword import iskeyword as _iskeyword
import sys as _sys
import heapq as _heapq import heapq as _heapq
from _weakref import proxy as _proxy import sys as _sys
from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
from itertools import chain as _chain
from itertools import repeat as _repeat
from itertools import starmap as _starmap
from keyword import iskeyword as _iskeyword
from operator import eq as _eq
from operator import itemgetter as _itemgetter
from reprlib import recursive_repr as _recursive_repr from reprlib import recursive_repr as _recursive_repr
from _weakref import proxy as _proxy
try: try:
from _collections import deque from _collections import deque
@ -47,13 +60,14 @@ def __getattr__(name):
obj = getattr(_collections_abc, name) obj = getattr(_collections_abc, name)
import warnings import warnings
warnings.warn("Using or importing the ABCs from 'collections' instead " warnings.warn("Using or importing the ABCs from 'collections' instead "
"of from 'collections.abc' is deprecated, " "of from 'collections.abc' is deprecated since Python 3.3, "
"and in 3.8 it will stop working", "and in 3.10 it will stop working",
DeprecationWarning, stacklevel=2) DeprecationWarning, stacklevel=2)
globals()[name] = obj globals()[name] = obj
return obj return obj
raise AttributeError(f'module {__name__!r} has no attribute {name!r}') raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
################################################################################ ################################################################################
### OrderedDict ### OrderedDict
################################################################################ ################################################################################
@ -93,16 +107,10 @@ class OrderedDict(dict):
# Individual links are kept alive by the hard reference in self.__map. # Individual links are kept alive by the hard reference in self.__map.
# Those hard references disappear when a key is deleted from an OrderedDict. # Those hard references disappear when a key is deleted from an OrderedDict.
def __init__(*args, **kwds): def __init__(self, other=(), /, **kwds):
'''Initialize an ordered dictionary. The signature is the same as '''Initialize an ordered dictionary. The signature is the same as
regular dictionaries. Keyword argument order is preserved. regular dictionaries. Keyword argument order is preserved.
''' '''
if not args:
raise TypeError("descriptor '__init__' of 'OrderedDict' object "
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try: try:
self.__root self.__root
except AttributeError: except AttributeError:
@ -110,7 +118,7 @@ class OrderedDict(dict):
self.__root = root = _proxy(self.__hardroot) self.__root = root = _proxy(self.__hardroot)
root.prev = root.next = root root.prev = root.next = root
self.__map = {} self.__map = {}
self.__update(*args, **kwds) self.__update(other, **kwds)
def __setitem__(self, key, value, def __setitem__(self, key, value,
dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link): dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link):
@ -299,6 +307,24 @@ class OrderedDict(dict):
return dict.__eq__(self, other) and all(map(_eq, self, other)) return dict.__eq__(self, other) and all(map(_eq, self, other))
return dict.__eq__(self, other) return dict.__eq__(self, other)
def __ior__(self, other):
self.update(other)
return self
def __or__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = self.__class__(self)
new.update(other)
return new
def __ror__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = self.__class__(other)
new.update(self)
return new
try: try:
from _collections import OrderedDict from _collections import OrderedDict
@ -311,7 +337,10 @@ except ImportError:
### namedtuple ### namedtuple
################################################################################ ################################################################################
_nt_itemgetters = {} try:
from _collections import _tuplegetter
except ImportError:
_tuplegetter = lambda index, doc: property(_itemgetter(index), doc=doc)
def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None): def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None):
"""Returns a new subclass of tuple with named fields. """Returns a new subclass of tuple with named fields.
@ -384,18 +413,23 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
# Variables used in the methods and docstrings # Variables used in the methods and docstrings
field_names = tuple(map(_sys.intern, field_names)) field_names = tuple(map(_sys.intern, field_names))
num_fields = len(field_names) num_fields = len(field_names)
arg_list = repr(field_names).replace("'", "")[1:-1] arg_list = ', '.join(field_names)
if num_fields == 1:
arg_list += ','
repr_fmt = '(' + ', '.join(f'{name}=%r' for name in field_names) + ')' repr_fmt = '(' + ', '.join(f'{name}=%r' for name in field_names) + ')'
tuple_new = tuple.__new__ tuple_new = tuple.__new__
_len = len _dict, _tuple, _len, _map, _zip = dict, tuple, len, map, zip
# Create all the named tuple methods to be added to the class namespace # Create all the named tuple methods to be added to the class namespace
s = f'def __new__(_cls, {arg_list}): return _tuple_new(_cls, ({arg_list}))' namespace = {
namespace = {'_tuple_new': tuple_new, '__name__': f'namedtuple_{typename}'} '_tuple_new': tuple_new,
# Note: exec() has the side-effect of interning the field names '__builtins__': {},
exec(s, namespace) '__name__': f'namedtuple_{typename}',
__new__ = namespace['__new__'] }
code = f'lambda _cls, {arg_list}: _tuple_new(_cls, ({arg_list}))'
__new__ = eval(code, namespace)
__new__.__name__ = '__new__'
__new__.__doc__ = f'Create new instance of {typename}({arg_list})' __new__.__doc__ = f'Create new instance of {typename}({arg_list})'
if defaults is not None: if defaults is not None:
__new__.__defaults__ = defaults __new__.__defaults__ = defaults
@ -410,8 +444,8 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
_make.__func__.__doc__ = (f'Make a new {typename} object from a sequence ' _make.__func__.__doc__ = (f'Make a new {typename} object from a sequence '
'or iterable') 'or iterable')
def _replace(_self, **kwds): def _replace(self, /, **kwds):
result = _self._make(map(kwds.pop, field_names, _self)) result = self._make(_map(kwds.pop, field_names, self))
if kwds: if kwds:
raise ValueError(f'Got unexpected field names: {list(kwds)!r}') raise ValueError(f'Got unexpected field names: {list(kwds)!r}')
return result return result
@ -424,17 +458,22 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
return self.__class__.__name__ + repr_fmt % self return self.__class__.__name__ + repr_fmt % self
def _asdict(self): def _asdict(self):
'Return a new OrderedDict which maps field names to their values.' 'Return a new dict which maps field names to their values.'
return OrderedDict(zip(self._fields, self)) return _dict(_zip(self._fields, self))
def __getnewargs__(self): def __getnewargs__(self):
'Return self as a plain tuple. Used by copy and pickle.' 'Return self as a plain tuple. Used by copy and pickle.'
return tuple(self) return _tuple(self)
# Modify function metadata to help with introspection and debugging # Modify function metadata to help with introspection and debugging
for method in (
for method in (__new__, _make.__func__, _replace, __new__,
__repr__, _asdict, __getnewargs__): _make.__func__,
_replace,
__repr__,
_asdict,
__getnewargs__,
):
method.__qualname__ = f'{typename}.{method.__name__}' method.__qualname__ = f'{typename}.{method.__name__}'
# Build-up the class namespace dictionary # Build-up the class namespace dictionary
@ -443,7 +482,7 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
'__doc__': f'{typename}({arg_list})', '__doc__': f'{typename}({arg_list})',
'__slots__': (), '__slots__': (),
'_fields': field_names, '_fields': field_names,
'_fields_defaults': field_defaults, '_field_defaults': field_defaults,
'__new__': __new__, '__new__': __new__,
'_make': _make, '_make': _make,
'_replace': _replace, '_replace': _replace,
@ -451,15 +490,9 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
'_asdict': _asdict, '_asdict': _asdict,
'__getnewargs__': __getnewargs__, '__getnewargs__': __getnewargs__,
} }
cache = _nt_itemgetters
for index, name in enumerate(field_names): for index, name in enumerate(field_names):
try: doc = _sys.intern(f'Alias for field number {index}')
itemgetter_object, doc = cache[index] class_namespace[name] = _tuplegetter(index, doc)
except KeyError:
itemgetter_object = _itemgetter(index)
doc = f'Alias for field number {index}'
cache[index] = itemgetter_object, doc
class_namespace[name] = property(itemgetter_object, doc=doc)
result = type(typename, (tuple,), class_namespace) result = type(typename, (tuple,), class_namespace)
@ -545,7 +578,7 @@ class Counter(dict):
# http://code.activestate.com/recipes/259174/ # http://code.activestate.com/recipes/259174/
# Knuth, TAOCP Vol. II section 4.6.3 # Knuth, TAOCP Vol. II section 4.6.3
def __init__(*args, **kwds): def __init__(self, iterable=None, /, **kwds):
'''Create a new, empty Counter object. And if given, count elements '''Create a new, empty Counter object. And if given, count elements
from an input iterable. Or, initialize the count from another mapping from an input iterable. Or, initialize the count from another mapping
of elements to their counts. of elements to their counts.
@ -556,14 +589,8 @@ class Counter(dict):
>>> c = Counter(a=4, b=2) # a new counter from keyword args >>> c = Counter(a=4, b=2) # a new counter from keyword args
''' '''
if not args: super().__init__()
raise TypeError("descriptor '__init__' of 'Counter' object " self.update(iterable, **kwds)
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
super(Counter, self).__init__()
self.update(*args, **kwds)
def __missing__(self, key): def __missing__(self, key):
'The count of elements not in the Counter is zero.' 'The count of elements not in the Counter is zero.'
@ -574,8 +601,8 @@ class Counter(dict):
'''List the n most common elements and their counts from the most '''List the n most common elements and their counts from the most
common to the least. If n is None, then list all element counts. common to the least. If n is None, then list all element counts.
>>> Counter('abcdeabcdabcaba').most_common(3) >>> Counter('abracadabra').most_common(3)
[('a', 5), ('b', 4), ('c', 3)] [('a', 5), ('b', 2), ('r', 2)]
''' '''
# Emulate Bag.sortedByCount from Smalltalk # Emulate Bag.sortedByCount from Smalltalk
@ -609,12 +636,17 @@ class Counter(dict):
@classmethod @classmethod
def fromkeys(cls, iterable, v=None): def fromkeys(cls, iterable, v=None):
# There is no equivalent method for counters because setting v=1 # There is no equivalent method for counters because the semantics
# means that no element can have a count greater than one. # would be ambiguous in cases such as Counter.fromkeys('aaabbc', v=2).
# Initializing counters to zero values isn't necessary because zero
# is already the default value for counter lookups. Initializing
# to one is easily accomplished with Counter(set(iterable)). For
# more exotic cases, create a dictionary first using a dictionary
# comprehension or dict.fromkeys().
raise NotImplementedError( raise NotImplementedError(
'Counter.fromkeys() is undefined. Use Counter(iterable) instead.') 'Counter.fromkeys() is undefined. Use Counter(iterable) instead.')
def update(*args, **kwds): def update(self, iterable=None, /, **kwds):
'''Like dict.update() but add counts instead of replacing them. '''Like dict.update() but add counts instead of replacing them.
Source can be an iterable, a dictionary, or another Counter instance. Source can be an iterable, a dictionary, or another Counter instance.
@ -634,13 +666,6 @@ class Counter(dict):
# contexts. Instead, we implement straight-addition. Both the inputs # contexts. Instead, we implement straight-addition. Both the inputs
# and outputs are allowed to contain zero and negative counts. # and outputs are allowed to contain zero and negative counts.
if not args:
raise TypeError("descriptor 'update' of 'Counter' object "
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
iterable = args[0] if args else None
if iterable is not None: if iterable is not None:
if isinstance(iterable, _collections_abc.Mapping): if isinstance(iterable, _collections_abc.Mapping):
if self: if self:
@ -648,13 +673,14 @@ class Counter(dict):
for elem, count in iterable.items(): for elem, count in iterable.items():
self[elem] = count + self_get(elem, 0) self[elem] = count + self_get(elem, 0)
else: else:
super(Counter, self).update(iterable) # fast path when counter is empty # fast path when counter is empty
super().update(iterable)
else: else:
_count_elements(self, iterable) _count_elements(self, iterable)
if kwds: if kwds:
self.update(kwds) self.update(kwds)
def subtract(*args, **kwds): def subtract(self, iterable=None, /, **kwds):
'''Like dict.update() but subtracts counts instead of replacing them. '''Like dict.update() but subtracts counts instead of replacing them.
Counts can be reduced below zero. Both the inputs and outputs are Counts can be reduced below zero. Both the inputs and outputs are
allowed to contain zero and negative counts. allowed to contain zero and negative counts.
@ -670,13 +696,6 @@ class Counter(dict):
-1 -1
''' '''
if not args:
raise TypeError("descriptor 'subtract' of 'Counter' object "
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
iterable = args[0] if args else None
if iterable is not None: if iterable is not None:
self_get = self.get self_get = self.get
if isinstance(iterable, _collections_abc.Mapping): if isinstance(iterable, _collections_abc.Mapping):
@ -702,13 +721,14 @@ class Counter(dict):
def __repr__(self): def __repr__(self):
if not self: if not self:
return '%s()' % self.__class__.__name__ return f'{self.__class__.__name__}()'
try: try:
items = ', '.join(map('%r: %r'.__mod__, self.most_common())) # dict() preserves the ordering returned by most_common()
return '%s({%s})' % (self.__class__.__name__, items) d = dict(self.most_common())
except TypeError: except TypeError:
# handle case where values are not orderable # handle case where values are not orderable
return '{0}({1!r})'.format(self.__class__.__name__, dict(self)) d = dict(self)
return f'{self.__class__.__name__}({d!r})'
# Multiset-style mathematical operations discussed in: # Multiset-style mathematical operations discussed in:
# Knuth TAOCP Volume II section 4.6.3 exercise 19 # Knuth TAOCP Volume II section 4.6.3 exercise 19
@ -718,6 +738,13 @@ class Counter(dict):
# #
# To strip negative and zero counts, add-in an empty counter: # To strip negative and zero counts, add-in an empty counter:
# c += Counter() # c += Counter()
#
# Rich comparison operators for multiset subset and superset tests
# are deliberately omitted due to semantic conflicts with the
# existing inherited dict equality method. Subset and superset
# semantics ignore zero counts and require that p≤q ∧ p≥q → p=q;
# however, that would not be the case for p=Counter(a=1, b=0)
# and q=Counter(a=1) where the dictionaries are not equal.
def __add__(self, other): def __add__(self, other):
'''Add counts from two counters. '''Add counts from two counters.
@ -922,7 +949,7 @@ class ChainMap(_collections_abc.MutableMapping):
def __iter__(self): def __iter__(self):
d = {} d = {}
for mapping in reversed(self.maps): for mapping in reversed(self.maps):
d.update(mapping) # reuses stored hash values if possible d.update(dict.fromkeys(mapping)) # reuses stored hash values if possible
return iter(d) return iter(d)
def __contains__(self, key): def __contains__(self, key):
@ -933,8 +960,7 @@ class ChainMap(_collections_abc.MutableMapping):
@_recursive_repr() @_recursive_repr()
def __repr__(self): def __repr__(self):
return '{0.__class__.__name__}({1})'.format( return f'{self.__class__.__name__}({", ".join(map(repr, self.maps))})'
self, ', '.join(map(repr, self.maps)))
@classmethod @classmethod
def fromkeys(cls, iterable, *args): def fromkeys(cls, iterable, *args):
@ -967,7 +993,7 @@ class ChainMap(_collections_abc.MutableMapping):
try: try:
del self.maps[0][key] del self.maps[0][key]
except KeyError: except KeyError:
raise KeyError('Key not found in the first mapping: {!r}'.format(key)) raise KeyError(f'Key not found in the first mapping: {key!r}')
def popitem(self): def popitem(self):
'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
@ -981,12 +1007,31 @@ class ChainMap(_collections_abc.MutableMapping):
try: try:
return self.maps[0].pop(key, *args) return self.maps[0].pop(key, *args)
except KeyError: except KeyError:
raise KeyError('Key not found in the first mapping: {!r}'.format(key)) raise KeyError(f'Key not found in the first mapping: {key!r}')
def clear(self): def clear(self):
'Clear maps[0], leaving maps[1:] intact.' 'Clear maps[0], leaving maps[1:] intact.'
self.maps[0].clear() self.maps[0].clear()
def __ior__(self, other):
self.maps[0].update(other)
return self
def __or__(self, other):
if not isinstance(other, _collections_abc.Mapping):
return NotImplemented
m = self.copy()
m.maps[0].update(other)
return m
def __ror__(self, other):
if not isinstance(other, _collections_abc.Mapping):
return NotImplemented
m = dict(other)
for child in reversed(self.maps):
m.update(child)
return self.__class__(m)
################################################################################ ################################################################################
### UserDict ### UserDict
@ -995,36 +1040,29 @@ class ChainMap(_collections_abc.MutableMapping):
class UserDict(_collections_abc.MutableMapping): class UserDict(_collections_abc.MutableMapping):
# Start by filling-out the abstract methods # Start by filling-out the abstract methods
def __init__(*args, **kwargs): def __init__(self, dict=None, /, **kwargs):
if not args:
raise TypeError("descriptor '__init__' of 'UserDict' object "
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
if args:
dict = args[0]
elif 'dict' in kwargs:
dict = kwargs.pop('dict')
import warnings
warnings.warn("Passing 'dict' as keyword argument is deprecated",
DeprecationWarning, stacklevel=2)
else:
dict = None
self.data = {} self.data = {}
if dict is not None: if dict is not None:
self.update(dict) self.update(dict)
if len(kwargs): if kwargs:
self.update(kwargs) self.update(kwargs)
def __len__(self): return len(self.data)
def __len__(self):
return len(self.data)
def __getitem__(self, key): def __getitem__(self, key):
if key in self.data: if key in self.data:
return self.data[key] return self.data[key]
if hasattr(self.__class__, "__missing__"): if hasattr(self.__class__, "__missing__"):
return self.__class__.__missing__(self, key) return self.__class__.__missing__(self, key)
raise KeyError(key) raise KeyError(key)
def __setitem__(self, key, item): self.data[key] = item
def __delitem__(self, key): del self.data[key] def __setitem__(self, key, item):
self.data[key] = item
def __delitem__(self, key):
del self.data[key]
def __iter__(self): def __iter__(self):
return iter(self.data) return iter(self.data)
@ -1033,7 +1071,37 @@ class UserDict(_collections_abc.MutableMapping):
return key in self.data return key in self.data
# Now, add the methods in dicts but not in MutableMapping # Now, add the methods in dicts but not in MutableMapping
def __repr__(self): return repr(self.data) def __repr__(self):
return repr(self.data)
def __or__(self, other):
if isinstance(other, UserDict):
return self.__class__(self.data | other.data)
if isinstance(other, dict):
return self.__class__(self.data | other)
return NotImplemented
def __ror__(self, other):
if isinstance(other, UserDict):
return self.__class__(other.data | self.data)
if isinstance(other, dict):
return self.__class__(other | self.data)
return NotImplemented
def __ior__(self, other):
if isinstance(other, UserDict):
self.data |= other.data
else:
self.data |= other
return self
def __copy__(self):
inst = self.__class__.__new__(self.__class__)
inst.__dict__.update(self.__dict__)
# Create a copy and avoid triggering descriptors
inst.__dict__["data"] = self.__dict__["data"].copy()
return inst
def copy(self): def copy(self):
if self.__class__ is UserDict: if self.__class__ is UserDict:
return UserDict(self.data.copy()) return UserDict(self.data.copy())
@ -1046,6 +1114,7 @@ class UserDict(_collections_abc.MutableMapping):
self.data = data self.data = data
c.update(self) c.update(self)
return c return c
@classmethod @classmethod
def fromkeys(cls, iterable, value=None): def fromkeys(cls, iterable, value=None):
d = cls() d = cls()
@ -1054,13 +1123,13 @@ class UserDict(_collections_abc.MutableMapping):
return d return d
################################################################################ ################################################################################
### UserList ### UserList
################################################################################ ################################################################################
class UserList(_collections_abc.MutableSequence): class UserList(_collections_abc.MutableSequence):
"""A more or less complete user-defined wrapper around list objects.""" """A more or less complete user-defined wrapper around list objects."""
def __init__(self, initlist=None): def __init__(self, initlist=None):
self.data = [] self.data = []
if initlist is not None: if initlist is not None:
@ -1071,31 +1140,60 @@ class UserList(_collections_abc.MutableSequence):
self.data[:] = initlist.data[:] self.data[:] = initlist.data[:]
else: else:
self.data = list(initlist) self.data = list(initlist)
def __repr__(self): return repr(self.data)
def __lt__(self, other): return self.data < self.__cast(other) def __repr__(self):
def __le__(self, other): return self.data <= self.__cast(other) return repr(self.data)
def __eq__(self, other): return self.data == self.__cast(other)
def __gt__(self, other): return self.data > self.__cast(other) def __lt__(self, other):
def __ge__(self, other): return self.data >= self.__cast(other) return self.data < self.__cast(other)
def __le__(self, other):
return self.data <= self.__cast(other)
def __eq__(self, other):
return self.data == self.__cast(other)
def __gt__(self, other):
return self.data > self.__cast(other)
def __ge__(self, other):
return self.data >= self.__cast(other)
def __cast(self, other): def __cast(self, other):
return other.data if isinstance(other, UserList) else other return other.data if isinstance(other, UserList) else other
def __contains__(self, item): return item in self.data
def __len__(self): return len(self.data) def __contains__(self, item):
def __getitem__(self, i): return self.data[i] return item in self.data
def __setitem__(self, i, item): self.data[i] = item
def __delitem__(self, i): del self.data[i] def __len__(self):
return len(self.data)
def __getitem__(self, i):
if isinstance(i, slice):
return self.__class__(self.data[i])
else:
return self.data[i]
def __setitem__(self, i, item):
self.data[i] = item
def __delitem__(self, i):
del self.data[i]
def __add__(self, other): def __add__(self, other):
if isinstance(other, UserList): if isinstance(other, UserList):
return self.__class__(self.data + other.data) return self.__class__(self.data + other.data)
elif isinstance(other, type(self.data)): elif isinstance(other, type(self.data)):
return self.__class__(self.data + other) return self.__class__(self.data + other)
return self.__class__(self.data + list(other)) return self.__class__(self.data + list(other))
def __radd__(self, other): def __radd__(self, other):
if isinstance(other, UserList): if isinstance(other, UserList):
return self.__class__(other.data + self.data) return self.__class__(other.data + self.data)
elif isinstance(other, type(self.data)): elif isinstance(other, type(self.data)):
return self.__class__(other + self.data) return self.__class__(other + self.data)
return self.__class__(list(other) + self.data) return self.__class__(list(other) + self.data)
def __iadd__(self, other): def __iadd__(self, other):
if isinstance(other, UserList): if isinstance(other, UserList):
self.data += other.data self.data += other.data
@ -1104,22 +1202,53 @@ class UserList(_collections_abc.MutableSequence):
else: else:
self.data += list(other) self.data += list(other)
return self return self
def __mul__(self, n): def __mul__(self, n):
return self.__class__(self.data * n) return self.__class__(self.data * n)
__rmul__ = __mul__ __rmul__ = __mul__
def __imul__(self, n): def __imul__(self, n):
self.data *= n self.data *= n
return self return self
def append(self, item): self.data.append(item)
def insert(self, i, item): self.data.insert(i, item) def __copy__(self):
def pop(self, i=-1): return self.data.pop(i) inst = self.__class__.__new__(self.__class__)
def remove(self, item): self.data.remove(item) inst.__dict__.update(self.__dict__)
def clear(self): self.data.clear() # Create a copy and avoid triggering descriptors
def copy(self): return self.__class__(self) inst.__dict__["data"] = self.__dict__["data"][:]
def count(self, item): return self.data.count(item) return inst
def index(self, item, *args): return self.data.index(item, *args)
def reverse(self): self.data.reverse() def append(self, item):
def sort(self, *args, **kwds): self.data.sort(*args, **kwds) self.data.append(item)
def insert(self, i, item):
self.data.insert(i, item)
def pop(self, i=-1):
return self.data.pop(i)
def remove(self, item):
self.data.remove(item)
def clear(self):
self.data.clear()
def copy(self):
return self.__class__(self)
def count(self, item):
return self.data.count(item)
def index(self, item, *args):
return self.data.index(item, *args)
def reverse(self):
self.data.reverse()
def sort(self, /, *args, **kwds):
self.data.sort(*args, **kwds)
def extend(self, other): def extend(self, other):
if isinstance(other, UserList): if isinstance(other, UserList):
self.data.extend(other.data) self.data.extend(other.data)
@ -1127,12 +1256,12 @@ class UserList(_collections_abc.MutableSequence):
self.data.extend(other) self.data.extend(other)
################################################################################ ################################################################################
### UserString ### UserString
################################################################################ ################################################################################
class UserString(_collections_abc.Sequence): class UserString(_collections_abc.Sequence):
def __init__(self, seq): def __init__(self, seq):
if isinstance(seq, str): if isinstance(seq, str):
self.data = seq self.data = seq
@ -1140,12 +1269,25 @@ class UserString(_collections_abc.Sequence):
self.data = seq.data[:] self.data = seq.data[:]
else: else:
self.data = str(seq) self.data = str(seq)
def __str__(self): return str(self.data)
def __repr__(self): return repr(self.data) def __str__(self):
def __int__(self): return int(self.data) return str(self.data)
def __float__(self): return float(self.data)
def __complex__(self): return complex(self.data) def __repr__(self):
def __hash__(self): return hash(self.data) return repr(self.data)
def __int__(self):
return int(self.data)
def __float__(self):
return float(self.data)
def __complex__(self):
return complex(self.data)
def __hash__(self):
return hash(self.data)
def __getnewargs__(self): def __getnewargs__(self):
return (self.data[:],) return (self.data[:],)
@ -1153,18 +1295,22 @@ class UserString(_collections_abc.Sequence):
if isinstance(string, UserString): if isinstance(string, UserString):
return self.data == string.data return self.data == string.data
return self.data == string return self.data == string
def __lt__(self, string): def __lt__(self, string):
if isinstance(string, UserString): if isinstance(string, UserString):
return self.data < string.data return self.data < string.data
return self.data < string return self.data < string
def __le__(self, string): def __le__(self, string):
if isinstance(string, UserString): if isinstance(string, UserString):
return self.data <= string.data return self.data <= string.data
return self.data <= string return self.data <= string
def __gt__(self, string): def __gt__(self, string):
if isinstance(string, UserString): if isinstance(string, UserString):
return self.data > string.data return self.data > string.data
return self.data > string return self.data > string
def __ge__(self, string): def __ge__(self, string):
if isinstance(string, UserString): if isinstance(string, UserString):
return self.data >= string.data return self.data >= string.data
@ -1175,105 +1321,188 @@ class UserString(_collections_abc.Sequence):
char = char.data char = char.data
return char in self.data return char in self.data
def __len__(self): return len(self.data) def __len__(self):
def __getitem__(self, index): return self.__class__(self.data[index]) return len(self.data)
def __getitem__(self, index):
return self.__class__(self.data[index])
def __add__(self, other): def __add__(self, other):
if isinstance(other, UserString): if isinstance(other, UserString):
return self.__class__(self.data + other.data) return self.__class__(self.data + other.data)
elif isinstance(other, str): elif isinstance(other, str):
return self.__class__(self.data + other) return self.__class__(self.data + other)
return self.__class__(self.data + str(other)) return self.__class__(self.data + str(other))
def __radd__(self, other): def __radd__(self, other):
if isinstance(other, str): if isinstance(other, str):
return self.__class__(other + self.data) return self.__class__(other + self.data)
return self.__class__(str(other) + self.data) return self.__class__(str(other) + self.data)
def __mul__(self, n): def __mul__(self, n):
return self.__class__(self.data * n) return self.__class__(self.data * n)
__rmul__ = __mul__ __rmul__ = __mul__
def __mod__(self, args): def __mod__(self, args):
return self.__class__(self.data % args) return self.__class__(self.data % args)
def __rmod__(self, format):
return self.__class__(format % args) def __rmod__(self, template):
return self.__class__(str(template) % self)
# the following methods are defined in alphabetical order: # the following methods are defined in alphabetical order:
def capitalize(self): return self.__class__(self.data.capitalize()) def capitalize(self):
return self.__class__(self.data.capitalize())
def casefold(self): def casefold(self):
return self.__class__(self.data.casefold()) return self.__class__(self.data.casefold())
def center(self, width, *args): def center(self, width, *args):
return self.__class__(self.data.center(width, *args)) return self.__class__(self.data.center(width, *args))
def count(self, sub, start=0, end=_sys.maxsize): def count(self, sub, start=0, end=_sys.maxsize):
if isinstance(sub, UserString): if isinstance(sub, UserString):
sub = sub.data sub = sub.data
return self.data.count(sub, start, end) return self.data.count(sub, start, end)
def encode(self, encoding=None, errors=None): # XXX improve this?
if encoding: def removeprefix(self, prefix, /):
if errors: if isinstance(prefix, UserString):
return self.__class__(self.data.encode(encoding, errors)) prefix = prefix.data
return self.__class__(self.data.encode(encoding)) return self.__class__(self.data.removeprefix(prefix))
return self.__class__(self.data.encode())
def removesuffix(self, suffix, /):
if isinstance(suffix, UserString):
suffix = suffix.data
return self.__class__(self.data.removesuffix(suffix))
def encode(self, encoding='utf-8', errors='strict'):
encoding = 'utf-8' if encoding is None else encoding
errors = 'strict' if errors is None else errors
return self.data.encode(encoding, errors)
def endswith(self, suffix, start=0, end=_sys.maxsize): def endswith(self, suffix, start=0, end=_sys.maxsize):
return self.data.endswith(suffix, start, end) return self.data.endswith(suffix, start, end)
def expandtabs(self, tabsize=8): def expandtabs(self, tabsize=8):
return self.__class__(self.data.expandtabs(tabsize)) return self.__class__(self.data.expandtabs(tabsize))
def find(self, sub, start=0, end=_sys.maxsize): def find(self, sub, start=0, end=_sys.maxsize):
if isinstance(sub, UserString): if isinstance(sub, UserString):
sub = sub.data sub = sub.data
return self.data.find(sub, start, end) return self.data.find(sub, start, end)
def format(self, *args, **kwds):
def format(self, /, *args, **kwds):
return self.data.format(*args, **kwds) return self.data.format(*args, **kwds)
def format_map(self, mapping): def format_map(self, mapping):
return self.data.format_map(mapping) return self.data.format_map(mapping)
def index(self, sub, start=0, end=_sys.maxsize): def index(self, sub, start=0, end=_sys.maxsize):
return self.data.index(sub, start, end) return self.data.index(sub, start, end)
def isalpha(self): return self.data.isalpha()
def isalnum(self): return self.data.isalnum() def isalpha(self):
def isascii(self): return self.data.isascii() return self.data.isalpha()
def isdecimal(self): return self.data.isdecimal()
def isdigit(self): return self.data.isdigit() def isalnum(self):
def isidentifier(self): return self.data.isidentifier() return self.data.isalnum()
def islower(self): return self.data.islower()
def isnumeric(self): return self.data.isnumeric() def isascii(self):
def isprintable(self): return self.data.isprintable() return self.data.isascii()
def isspace(self): return self.data.isspace()
def istitle(self): return self.data.istitle() def isdecimal(self):
def isupper(self): return self.data.isupper() return self.data.isdecimal()
def join(self, seq): return self.data.join(seq)
def isdigit(self):
return self.data.isdigit()
def isidentifier(self):
return self.data.isidentifier()
def islower(self):
return self.data.islower()
def isnumeric(self):
return self.data.isnumeric()
def isprintable(self):
return self.data.isprintable()
def isspace(self):
return self.data.isspace()
def istitle(self):
return self.data.istitle()
def isupper(self):
return self.data.isupper()
def join(self, seq):
return self.data.join(seq)
def ljust(self, width, *args): def ljust(self, width, *args):
return self.__class__(self.data.ljust(width, *args)) return self.__class__(self.data.ljust(width, *args))
def lower(self): return self.__class__(self.data.lower())
def lstrip(self, chars=None): return self.__class__(self.data.lstrip(chars)) def lower(self):
return self.__class__(self.data.lower())
def lstrip(self, chars=None):
return self.__class__(self.data.lstrip(chars))
maketrans = str.maketrans maketrans = str.maketrans
def partition(self, sep): def partition(self, sep):
return self.data.partition(sep) return self.data.partition(sep)
def replace(self, old, new, maxsplit=-1): def replace(self, old, new, maxsplit=-1):
if isinstance(old, UserString): if isinstance(old, UserString):
old = old.data old = old.data
if isinstance(new, UserString): if isinstance(new, UserString):
new = new.data new = new.data
return self.__class__(self.data.replace(old, new, maxsplit)) return self.__class__(self.data.replace(old, new, maxsplit))
def rfind(self, sub, start=0, end=_sys.maxsize): def rfind(self, sub, start=0, end=_sys.maxsize):
if isinstance(sub, UserString): if isinstance(sub, UserString):
sub = sub.data sub = sub.data
return self.data.rfind(sub, start, end) return self.data.rfind(sub, start, end)
def rindex(self, sub, start=0, end=_sys.maxsize): def rindex(self, sub, start=0, end=_sys.maxsize):
return self.data.rindex(sub, start, end) return self.data.rindex(sub, start, end)
def rjust(self, width, *args): def rjust(self, width, *args):
return self.__class__(self.data.rjust(width, *args)) return self.__class__(self.data.rjust(width, *args))
def rpartition(self, sep): def rpartition(self, sep):
return self.data.rpartition(sep) return self.data.rpartition(sep)
def rstrip(self, chars=None): def rstrip(self, chars=None):
return self.__class__(self.data.rstrip(chars)) return self.__class__(self.data.rstrip(chars))
def split(self, sep=None, maxsplit=-1): def split(self, sep=None, maxsplit=-1):
return self.data.split(sep, maxsplit) return self.data.split(sep, maxsplit)
def rsplit(self, sep=None, maxsplit=-1): def rsplit(self, sep=None, maxsplit=-1):
return self.data.rsplit(sep, maxsplit) return self.data.rsplit(sep, maxsplit)
def splitlines(self, keepends=False): return self.data.splitlines(keepends)
def splitlines(self, keepends=False):
return self.data.splitlines(keepends)
def startswith(self, prefix, start=0, end=_sys.maxsize): def startswith(self, prefix, start=0, end=_sys.maxsize):
return self.data.startswith(prefix, start, end) return self.data.startswith(prefix, start, end)
def strip(self, chars=None): return self.__class__(self.data.strip(chars))
def swapcase(self): return self.__class__(self.data.swapcase()) def strip(self, chars=None):
def title(self): return self.__class__(self.data.title()) return self.__class__(self.data.strip(chars))
def swapcase(self):
return self.__class__(self.data.swapcase())
def title(self):
return self.__class__(self.data.title())
def translate(self, *args): def translate(self, *args):
return self.__class__(self.data.translate(*args)) return self.__class__(self.data.translate(*args))
def upper(self): return self.__class__(self.data.upper())
def zfill(self, width): return self.__class__(self.data.zfill(width)) def upper(self):
return self.__class__(self.data.upper())
def zfill(self, width):
return self.__class__(self.data.zfill(width))

View File

@ -1,2 +1,3 @@
from _collections_abc import * from _collections_abc import *
from _collections_abc import __all__ from _collections_abc import __all__
from _collections_abc import _CallableGenericAlias

View File

@ -15,16 +15,14 @@ import sys
import importlib.util import importlib.util
import py_compile import py_compile
import struct import struct
import filecmp
try:
from concurrent.futures import ProcessPoolExecutor
except ImportError:
ProcessPoolExecutor = None
from functools import partial from functools import partial
from pathlib import Path
__all__ = ["compile_dir","compile_file","compile_path"] __all__ = ["compile_dir","compile_file","compile_path"]
def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0): def _walk_dir(dir, maxlevels, quiet=0):
if quiet < 2 and isinstance(dir, os.PathLike): if quiet < 2 and isinstance(dir, os.PathLike):
dir = os.fspath(dir) dir = os.fspath(dir)
if not quiet: if not quiet:
@ -40,43 +38,64 @@ def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0):
if name == '__pycache__': if name == '__pycache__':
continue continue
fullname = os.path.join(dir, name) fullname = os.path.join(dir, name)
if ddir is not None:
dfile = os.path.join(ddir, name)
else:
dfile = None
if not os.path.isdir(fullname): if not os.path.isdir(fullname):
yield fullname yield fullname
elif (maxlevels > 0 and name != os.curdir and name != os.pardir and elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
os.path.isdir(fullname) and not os.path.islink(fullname)): os.path.isdir(fullname) and not os.path.islink(fullname)):
yield from _walk_dir(fullname, ddir=dfile, yield from _walk_dir(fullname, maxlevels=maxlevels - 1,
maxlevels=maxlevels - 1, quiet=quiet) quiet=quiet)
def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None, def compile_dir(dir, maxlevels=None, ddir=None, force=False,
quiet=0, legacy=False, optimize=-1, workers=1, rx=None, quiet=0, legacy=False, optimize=-1, workers=1,
invalidation_mode=py_compile.PycInvalidationMode.TIMESTAMP): invalidation_mode=None, *, stripdir=None,
prependdir=None, limit_sl_dest=None, hardlink_dupes=False):
"""Byte-compile all modules in the given directory tree. """Byte-compile all modules in the given directory tree.
Arguments (only dir is required): Arguments (only dir is required):
dir: the directory to byte-compile dir: the directory to byte-compile
maxlevels: maximum recursion level (default 10) maxlevels: maximum recursion level (default `sys.getrecursionlimit()`)
ddir: the directory that will be prepended to the path to the ddir: the directory that will be prepended to the path to the
file as it is compiled into each byte-code file. file as it is compiled into each byte-code file.
force: if True, force compilation, even if timestamps are up-to-date force: if True, force compilation, even if timestamps are up-to-date
quiet: full output with False or 0, errors only with 1, quiet: full output with False or 0, errors only with 1,
no output with 2 no output with 2
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
optimize: optimization level or -1 for level of the interpreter optimize: int or list of optimization levels or -1 for level of
the interpreter. Multiple levels leads to multiple compiled
files each with one optimization level.
workers: maximum number of parallel workers workers: maximum number of parallel workers
invalidation_mode: how the up-to-dateness of the pyc will be checked invalidation_mode: how the up-to-dateness of the pyc will be checked
stripdir: part of path to left-strip from source file path
prependdir: path to prepend to beginning of original file path, applied
after stripdir
limit_sl_dest: ignore symlinks if they are pointing outside of
the defined path
hardlink_dupes: hardlink duplicated pyc files
""" """
if workers is not None and workers < 0: ProcessPoolExecutor = None
if ddir is not None and (stripdir is not None or prependdir is not None):
raise ValueError(("Destination dir (ddir) cannot be used "
"in combination with stripdir or prependdir"))
if ddir is not None:
stripdir = dir
prependdir = ddir
ddir = None
if workers < 0:
raise ValueError('workers must be greater or equal to 0') raise ValueError('workers must be greater or equal to 0')
if workers != 1:
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels, try:
ddir=ddir) # Only import when needed, as low resource platforms may
# fail to import it
from concurrent.futures import ProcessPoolExecutor
except ImportError:
workers = 1
if maxlevels is None:
maxlevels = sys.getrecursionlimit()
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels)
success = True success = True
if workers is not None and workers != 1 and ProcessPoolExecutor is not None: if workers != 1 and ProcessPoolExecutor is not None:
# If workers == 0, let ProcessPoolExecutor choose
workers = workers or None workers = workers or None
with ProcessPoolExecutor(max_workers=workers) as executor: with ProcessPoolExecutor(max_workers=workers) as executor:
results = executor.map(partial(compile_file, results = executor.map(partial(compile_file,
@ -84,19 +103,27 @@ def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
rx=rx, quiet=quiet, rx=rx, quiet=quiet,
legacy=legacy, legacy=legacy,
optimize=optimize, optimize=optimize,
invalidation_mode=invalidation_mode), invalidation_mode=invalidation_mode,
stripdir=stripdir,
prependdir=prependdir,
limit_sl_dest=limit_sl_dest,
hardlink_dupes=hardlink_dupes),
files) files)
success = min(results, default=True) success = min(results, default=True)
else: else:
for file in files: for file in files:
if not compile_file(file, ddir, force, rx, quiet, if not compile_file(file, ddir, force, rx, quiet,
legacy, optimize, invalidation_mode): legacy, optimize, invalidation_mode,
stripdir=stripdir, prependdir=prependdir,
limit_sl_dest=limit_sl_dest,
hardlink_dupes=hardlink_dupes):
success = False success = False
return success return success
def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
legacy=False, optimize=-1, legacy=False, optimize=-1,
invalidation_mode=py_compile.PycInvalidationMode.TIMESTAMP): invalidation_mode=None, *, stripdir=None, prependdir=None,
limit_sl_dest=None, hardlink_dupes=False):
"""Byte-compile one file. """Byte-compile one file.
Arguments (only fullname is required): Arguments (only fullname is required):
@ -108,51 +135,114 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
quiet: full output with False or 0, errors only with 1, quiet: full output with False or 0, errors only with 1,
no output with 2 no output with 2
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
optimize: optimization level or -1 for level of the interpreter optimize: int or list of optimization levels or -1 for level of
the interpreter. Multiple levels leads to multiple compiled
files each with one optimization level.
invalidation_mode: how the up-to-dateness of the pyc will be checked invalidation_mode: how the up-to-dateness of the pyc will be checked
stripdir: part of path to left-strip from source file path
prependdir: path to prepend to beginning of original file path, applied
after stripdir
limit_sl_dest: ignore symlinks if they are pointing outside of
the defined path.
hardlink_dupes: hardlink duplicated pyc files
""" """
if ddir is not None and (stripdir is not None or prependdir is not None):
raise ValueError(("Destination dir (ddir) cannot be used "
"in combination with stripdir or prependdir"))
success = True success = True
if quiet < 2 and isinstance(fullname, os.PathLike): if quiet < 2 and isinstance(fullname, os.PathLike):
fullname = os.fspath(fullname) fullname = os.fspath(fullname)
name = os.path.basename(fullname) name = os.path.basename(fullname)
dfile = None
if ddir is not None: if ddir is not None:
dfile = os.path.join(ddir, name) dfile = os.path.join(ddir, name)
if stripdir is not None:
fullname_parts = fullname.split(os.path.sep)
stripdir_parts = stripdir.split(os.path.sep)
ddir_parts = list(fullname_parts)
for spart, opart in zip(stripdir_parts, fullname_parts):
if spart == opart:
ddir_parts.remove(spart)
dfile = os.path.join(*ddir_parts)
if prependdir is not None:
if dfile is None:
dfile = os.path.join(prependdir, fullname)
else: else:
dfile = None dfile = os.path.join(prependdir, dfile)
if isinstance(optimize, int):
optimize = [optimize]
# Use set() to remove duplicates.
# Use sorted() to create pyc files in a deterministic order.
optimize = sorted(set(optimize))
if hardlink_dupes and len(optimize) < 2:
raise ValueError("Hardlinking of duplicated bytecode makes sense "
"only for more than one optimization level")
if rx is not None: if rx is not None:
mo = rx.search(fullname) mo = rx.search(fullname)
if mo: if mo:
return success return success
if limit_sl_dest is not None and os.path.islink(fullname):
if Path(limit_sl_dest).resolve() not in Path(fullname).resolve().parents:
return success
opt_cfiles = {}
if os.path.isfile(fullname): if os.path.isfile(fullname):
for opt_level in optimize:
if legacy: if legacy:
cfile = fullname + 'c' opt_cfiles[opt_level] = fullname + 'c'
else: else:
if optimize >= 0: if opt_level >= 0:
opt = optimize if optimize >= 1 else '' opt = opt_level if opt_level >= 1 else ''
cfile = importlib.util.cache_from_source( cfile = (importlib.util.cache_from_source(
fullname, optimization=opt) fullname, optimization=opt))
opt_cfiles[opt_level] = cfile
else: else:
cfile = importlib.util.cache_from_source(fullname) cfile = importlib.util.cache_from_source(fullname)
cache_dir = os.path.dirname(cfile) opt_cfiles[opt_level] = cfile
head, tail = name[:-3], name[-3:] head, tail = name[:-3], name[-3:]
if tail == '.py': if tail == '.py':
if not force: if not force:
try: try:
mtime = int(os.stat(fullname).st_mtime) mtime = int(os.stat(fullname).st_mtime)
expect = struct.pack('<4sll', importlib.util.MAGIC_NUMBER, expect = struct.pack('<4sLL', importlib.util.MAGIC_NUMBER,
0, mtime) 0, mtime & 0xFFFF_FFFF)
for cfile in opt_cfiles.values():
with open(cfile, 'rb') as chandle: with open(cfile, 'rb') as chandle:
actual = chandle.read(12) actual = chandle.read(12)
if expect == actual: if expect != actual:
break
else:
return success return success
except OSError: except OSError:
pass pass
if not quiet: if not quiet:
print('Compiling {!r}...'.format(fullname)) print('Compiling {!r}...'.format(fullname))
try: try:
for index, opt_level in enumerate(optimize):
cfile = opt_cfiles[opt_level]
ok = py_compile.compile(fullname, cfile, dfile, True, ok = py_compile.compile(fullname, cfile, dfile, True,
optimize=optimize, optimize=opt_level,
invalidation_mode=invalidation_mode) invalidation_mode=invalidation_mode)
if index > 0 and hardlink_dupes:
previous_cfile = opt_cfiles[optimize[index - 1]]
if filecmp.cmp(cfile, previous_cfile, shallow=False):
os.unlink(cfile)
os.link(previous_cfile, cfile)
except py_compile.PyCompileError as err: except py_compile.PyCompileError as err:
success = False success = False
if quiet >= 2: if quiet >= 2:
@ -162,9 +252,8 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
else: else:
print('*** ', end='') print('*** ', end='')
# escape non-printable characters in msg # escape non-printable characters in msg
msg = err.msg.encode(sys.stdout.encoding, encoding = sys.stdout.encoding or sys.getdefaultencoding()
errors='backslashreplace') msg = err.msg.encode(encoding, errors='backslashreplace').decode(encoding)
msg = msg.decode(sys.stdout.encoding)
print(msg) print(msg)
except (SyntaxError, UnicodeError, OSError) as e: except (SyntaxError, UnicodeError, OSError) as e:
success = False success = False
@ -182,7 +271,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0, def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
legacy=False, optimize=-1, legacy=False, optimize=-1,
invalidation_mode=py_compile.PycInvalidationMode.TIMESTAMP): invalidation_mode=None):
"""Byte-compile all module on sys.path. """Byte-compile all module on sys.path.
Arguments (all optional): Arguments (all optional):
@ -221,7 +310,7 @@ def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Utilities to support installing Python libraries.') description='Utilities to support installing Python libraries.')
parser.add_argument('-l', action='store_const', const=0, parser.add_argument('-l', action='store_const', const=0,
default=10, dest='maxlevels', default=None, dest='maxlevels',
help="don't recurse into subdirectories") help="don't recurse into subdirectories")
parser.add_argument('-r', type=int, dest='recursion', parser.add_argument('-r', type=int, dest='recursion',
help=('control the maximum recursion level. ' help=('control the maximum recursion level. '
@ -239,6 +328,20 @@ def main():
'compile-time tracebacks and in runtime ' 'compile-time tracebacks and in runtime '
'tracebacks in cases where the source file is ' 'tracebacks in cases where the source file is '
'unavailable')) 'unavailable'))
parser.add_argument('-s', metavar='STRIPDIR', dest='stripdir',
default=None,
help=('part of path to left-strip from path '
'to source file - for example buildroot. '
'`-d` and `-s` options cannot be '
'specified together.'))
parser.add_argument('-p', metavar='PREPENDDIR', dest='prependdir',
default=None,
help=('path to add as prefix to path '
'to source file - for example / to make '
'it absolute when some part is removed '
'by `-s` option. '
'`-d` and `-p` options cannot be '
'specified together.'))
parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None, parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None,
help=('skip files matching the regular expression; ' help=('skip files matching the regular expression; '
'the regexp is searched for in the full path ' 'the regexp is searched for in the full path '
@ -255,9 +358,21 @@ def main():
type=int, help='Run compileall concurrently') type=int, help='Run compileall concurrently')
invalidation_modes = [mode.name.lower().replace('_', '-') invalidation_modes = [mode.name.lower().replace('_', '-')
for mode in py_compile.PycInvalidationMode] for mode in py_compile.PycInvalidationMode]
parser.add_argument('--invalidation-mode', default='timestamp', parser.add_argument('--invalidation-mode',
choices=sorted(invalidation_modes), choices=sorted(invalidation_modes),
help='How the pycs will be invalidated at runtime') help=('set .pyc invalidation mode; defaults to '
'"checked-hash" if the SOURCE_DATE_EPOCH '
'environment variable is set, and '
'"timestamp" otherwise.'))
parser.add_argument('-o', action='append', type=int, dest='opt_levels',
help=('Optimization levels to run compilation with. '
'Default is -1 which uses the optimization level '
'of the Python interpreter itself (see -O).'))
parser.add_argument('-e', metavar='DIR', dest='limit_sl_dest',
help='Ignore symlinks pointing outsite of the DIR')
parser.add_argument('--hardlink-dupes', action='store_true',
dest='hardlink_dupes',
help='Hardlink duplicated pyc files')
args = parser.parse_args() args = parser.parse_args()
compile_dests = args.compile_dest compile_dests = args.compile_dest
@ -266,12 +381,26 @@ def main():
import re import re
args.rx = re.compile(args.rx) args.rx = re.compile(args.rx)
if args.limit_sl_dest == "":
args.limit_sl_dest = None
if args.recursion is not None: if args.recursion is not None:
maxlevels = args.recursion maxlevels = args.recursion
else: else:
maxlevels = args.maxlevels maxlevels = args.maxlevels
if args.opt_levels is None:
args.opt_levels = [-1]
if len(args.opt_levels) == 1 and args.hardlink_dupes:
parser.error(("Hardlinking of duplicated bytecode makes sense "
"only for more than one optimization level."))
if args.ddir is not None and (
args.stripdir is not None or args.prependdir is not None
):
parser.error("-d cannot be used in combination with -s or -p")
# if flist is provided then load it # if flist is provided then load it
if args.flist: if args.flist:
try: try:
@ -283,11 +412,11 @@ def main():
print("Error reading file list {}".format(args.flist)) print("Error reading file list {}".format(args.flist))
return False return False
if args.workers is not None: if args.invalidation_mode:
args.workers = args.workers or None
ivl_mode = args.invalidation_mode.replace('-', '_').upper() ivl_mode = args.invalidation_mode.replace('-', '_').upper()
invalidation_mode = py_compile.PycInvalidationMode[ivl_mode] invalidation_mode = py_compile.PycInvalidationMode[ivl_mode]
else:
invalidation_mode = None
success = True success = True
try: try:
@ -296,13 +425,23 @@ def main():
if os.path.isfile(dest): if os.path.isfile(dest):
if not compile_file(dest, args.ddir, args.force, args.rx, if not compile_file(dest, args.ddir, args.force, args.rx,
args.quiet, args.legacy, args.quiet, args.legacy,
invalidation_mode=invalidation_mode): invalidation_mode=invalidation_mode,
stripdir=args.stripdir,
prependdir=args.prependdir,
optimize=args.opt_levels,
limit_sl_dest=args.limit_sl_dest,
hardlink_dupes=args.hardlink_dupes):
success = False success = False
else: else:
if not compile_dir(dest, maxlevels, args.ddir, if not compile_dir(dest, maxlevels, args.ddir,
args.force, args.rx, args.quiet, args.force, args.rx, args.quiet,
args.legacy, workers=args.workers, args.legacy, workers=args.workers,
invalidation_mode=invalidation_mode): invalidation_mode=invalidation_mode,
stripdir=args.stripdir,
prependdir=args.prependdir,
optimize=args.opt_levels,
limit_sl_dest=args.limit_sl_dest,
hardlink_dupes=args.hardlink_dupes):
success = False success = False
return success return success
else: else:

View File

@ -10,6 +10,7 @@ from concurrent.futures._base import (FIRST_COMPLETED,
ALL_COMPLETED, ALL_COMPLETED,
CancelledError, CancelledError,
TimeoutError, TimeoutError,
InvalidStateError,
BrokenExecutor, BrokenExecutor,
Future, Future,
Executor, Executor,

View File

@ -7,6 +7,7 @@ import collections
import logging import logging
import threading import threading
import time import time
import types
FIRST_COMPLETED = 'FIRST_COMPLETED' FIRST_COMPLETED = 'FIRST_COMPLETED'
FIRST_EXCEPTION = 'FIRST_EXCEPTION' FIRST_EXCEPTION = 'FIRST_EXCEPTION'
@ -53,6 +54,10 @@ class TimeoutError(Error):
"""The operation exceeded the given deadline.""" """The operation exceeded the given deadline."""
pass pass
class InvalidStateError(Error):
"""The operation is not allowed in this state."""
pass
class _Waiter(object): class _Waiter(object):
"""Provides the event that wait() and as_completed() block on.""" """Provides the event that wait() and as_completed() block on."""
def __init__(self): def __init__(self):
@ -212,7 +217,7 @@ def as_completed(fs, timeout=None):
before the given timeout. before the given timeout.
""" """
if timeout is not None: if timeout is not None:
end_time = timeout + time.time() end_time = timeout + time.monotonic()
fs = set(fs) fs = set(fs)
total_futures = len(fs) total_futures = len(fs)
@ -231,7 +236,7 @@ def as_completed(fs, timeout=None):
if timeout is None: if timeout is None:
wait_timeout = None wait_timeout = None
else: else:
wait_timeout = end_time - time.time() wait_timeout = end_time - time.monotonic()
if wait_timeout < 0: if wait_timeout < 0:
raise TimeoutError( raise TimeoutError(
'%d (of %d) futures unfinished' % ( '%d (of %d) futures unfinished' % (
@ -279,13 +284,14 @@ def wait(fs, timeout=None, return_when=ALL_COMPLETED):
A named 2-tuple of sets. The first set, named 'done', contains the A named 2-tuple of sets. The first set, named 'done', contains the
futures that completed (is finished or cancelled) before the wait futures that completed (is finished or cancelled) before the wait
completed. The second set, named 'not_done', contains uncompleted completed. The second set, named 'not_done', contains uncompleted
futures. futures. Duplicate futures given to *fs* are removed and will be
returned only once.
""" """
fs = set(fs)
with _AcquireFutures(fs): with _AcquireFutures(fs):
done = set(f for f in fs done = {f for f in fs
if f._state in [CANCELLED_AND_NOTIFIED, FINISHED]) if f._state in [CANCELLED_AND_NOTIFIED, FINISHED]}
not_done = set(fs) - done not_done = fs - done
if (return_when == FIRST_COMPLETED) and done: if (return_when == FIRST_COMPLETED) and done:
return DoneAndNotDoneFutures(done, not_done) return DoneAndNotDoneFutures(done, not_done)
elif (return_when == FIRST_EXCEPTION) and done: elif (return_when == FIRST_EXCEPTION) and done:
@ -304,7 +310,7 @@ def wait(fs, timeout=None, return_when=ALL_COMPLETED):
f._waiters.remove(waiter) f._waiters.remove(waiter)
done.update(waiter.finished_futures) done.update(waiter.finished_futures)
return DoneAndNotDoneFutures(done, set(fs) - done) return DoneAndNotDoneFutures(done, fs - done)
class Future(object): class Future(object):
"""Represents the result of an asynchronous computation.""" """Represents the result of an asynchronous computation."""
@ -375,13 +381,17 @@ class Future(object):
return self._state == RUNNING return self._state == RUNNING
def done(self): def done(self):
"""Return True of the future was cancelled or finished executing.""" """Return True if the future was cancelled or finished executing."""
with self._condition: with self._condition:
return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED] return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]
def __get_result(self): def __get_result(self):
if self._exception: if self._exception:
try:
raise self._exception raise self._exception
finally:
# Break a reference cycle with the exception in self._exception
self = None
else: else:
return self._result return self._result
@ -400,7 +410,10 @@ class Future(object):
if self._state not in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]: if self._state not in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]:
self._done_callbacks.append(fn) self._done_callbacks.append(fn)
return return
try:
fn(self) fn(self)
except Exception:
LOGGER.exception('exception calling callback for %r', self)
def result(self, timeout=None): def result(self, timeout=None):
"""Return the result of the call that the future represents. """Return the result of the call that the future represents.
@ -418,6 +431,7 @@ class Future(object):
timeout. timeout.
Exception: If the call raised then that exception will be raised. Exception: If the call raised then that exception will be raised.
""" """
try:
with self._condition: with self._condition:
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]: if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
raise CancelledError() raise CancelledError()
@ -432,6 +446,9 @@ class Future(object):
return self.__get_result() return self.__get_result()
else: else:
raise TimeoutError() raise TimeoutError()
finally:
# Break a reference cycle with the exception in self._exception
self = None
def exception(self, timeout=None): def exception(self, timeout=None):
"""Return the exception raised by the call that the future represents. """Return the exception raised by the call that the future represents.
@ -513,6 +530,8 @@ class Future(object):
Should only be used by Executor implementations and unit tests. Should only be used by Executor implementations and unit tests.
""" """
with self._condition: with self._condition:
if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED}:
raise InvalidStateError('{}: {!r}'.format(self._state, self))
self._result = result self._result = result
self._state = FINISHED self._state = FINISHED
for waiter in self._waiters: for waiter in self._waiters:
@ -526,6 +545,8 @@ class Future(object):
Should only be used by Executor implementations and unit tests. Should only be used by Executor implementations and unit tests.
""" """
with self._condition: with self._condition:
if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED}:
raise InvalidStateError('{}: {!r}'.format(self._state, self))
self._exception = exception self._exception = exception
self._state = FINISHED self._state = FINISHED
for waiter in self._waiters: for waiter in self._waiters:
@ -533,10 +554,12 @@ class Future(object):
self._condition.notify_all() self._condition.notify_all()
self._invoke_callbacks() self._invoke_callbacks()
__class_getitem__ = classmethod(types.GenericAlias)
class Executor(object): class Executor(object):
"""This is an abstract base class for concrete asynchronous executors.""" """This is an abstract base class for concrete asynchronous executors."""
def submit(self, fn, *args, **kwargs): def submit(self, fn, /, *args, **kwargs):
"""Submits a callable to be executed with the given arguments. """Submits a callable to be executed with the given arguments.
Schedules the callable to be executed as fn(*args, **kwargs) and returns Schedules the callable to be executed as fn(*args, **kwargs) and returns
@ -570,7 +593,7 @@ class Executor(object):
Exception: If fn(*args) raises for any values. Exception: If fn(*args) raises for any values.
""" """
if timeout is not None: if timeout is not None:
end_time = timeout + time.time() end_time = timeout + time.monotonic()
fs = [self.submit(fn, *args) for args in zip(*iterables)] fs = [self.submit(fn, *args) for args in zip(*iterables)]
@ -585,13 +608,13 @@ class Executor(object):
if timeout is None: if timeout is None:
yield fs.pop().result() yield fs.pop().result()
else: else:
yield fs.pop().result(end_time - time.time()) yield fs.pop().result(end_time - time.monotonic())
finally: finally:
for future in fs: for future in fs:
future.cancel() future.cancel()
return result_iterator() return result_iterator()
def shutdown(self, wait=True): def shutdown(self, wait=True, *, cancel_futures=False):
"""Clean-up the resources associated with the Executor. """Clean-up the resources associated with the Executor.
It is safe to call this method several times. Otherwise, no other It is safe to call this method several times. Otherwise, no other
@ -601,6 +624,9 @@ class Executor(object):
wait: If True then shutdown will not return until all running wait: If True then shutdown will not return until all running
futures have finished executing and the resources used by the futures have finished executing and the resources used by the
executor have been reclaimed. executor have been reclaimed.
cancel_futures: If True then shutdown will cancel all pending
futures. Futures that are completed or running will not be
cancelled.
""" """
pass pass

View File

@ -3,7 +3,7 @@
"""Implements ProcessPoolExecutor. """Implements ProcessPoolExecutor.
The follow diagram and text describe the data-flow through the system: The following diagram and text describe the data-flow through the system:
|======================= In-process =====================|== Out-of-process ==| |======================= In-process =====================|== Out-of-process ==|
@ -45,33 +45,19 @@ Process #1..n:
__author__ = 'Brian Quinlan (brian@sweetapp.com)' __author__ = 'Brian Quinlan (brian@sweetapp.com)'
import atexit
import os import os
from concurrent.futures import _base from concurrent.futures import _base
import queue import queue
from queue import Full
import multiprocessing as mp import multiprocessing as mp
from multiprocessing.connection import wait import multiprocessing.connection
from multiprocessing.queues import Queue from multiprocessing.queues import Queue
import threading import threading
import weakref import weakref
from functools import partial from functools import partial
import itertools import itertools
import sys
import traceback import traceback
# Workers are created as daemon threads and processes. This is done to allow the
# interpreter to exit when there are still idle processes in a
# ProcessPoolExecutor's process pool (i.e. shutdown() was not called). However,
# allowing workers to die with the interpreter has two undesirable properties:
# - The workers would still be running during interpreter shutdown,
# meaning that they would fail in unpredictable ways.
# - The workers could be killed while evaluating a work item, which could
# be bad if the callable being evaluated has external side-effects e.g.
# writing to a file.
#
# To work around this problem, an exit handler is installed which tells the
# workers to exit when their work queues are empty and then waits until the
# threads/processes finish.
_threads_wakeups = weakref.WeakKeyDictionary() _threads_wakeups = weakref.WeakKeyDictionary()
_global_shutdown = False _global_shutdown = False
@ -79,16 +65,21 @@ _global_shutdown = False
class _ThreadWakeup: class _ThreadWakeup:
def __init__(self): def __init__(self):
self._closed = False
self._reader, self._writer = mp.Pipe(duplex=False) self._reader, self._writer = mp.Pipe(duplex=False)
def close(self): def close(self):
if not self._closed:
self._closed = True
self._writer.close() self._writer.close()
self._reader.close() self._reader.close()
def wakeup(self): def wakeup(self):
if not self._closed:
self._writer.send_bytes(b"") self._writer.send_bytes(b"")
def clear(self): def clear(self):
if not self._closed:
while self._reader.poll(): while self._reader.poll():
self._reader.recv_bytes() self._reader.recv_bytes()
@ -98,10 +89,17 @@ def _python_exit():
_global_shutdown = True _global_shutdown = True
items = list(_threads_wakeups.items()) items = list(_threads_wakeups.items())
for _, thread_wakeup in items: for _, thread_wakeup in items:
# call not protected by ProcessPoolExecutor._shutdown_lock
thread_wakeup.wakeup() thread_wakeup.wakeup()
for t, _ in items: for t, _ in items:
t.join() t.join()
# Register for `_python_exit()` to be called just before joining all
# non-daemon threads. This is used instead of `atexit.register()` for
# compatibility with subinterpreters, which no longer support daemon threads.
# See bpo-39812 for context.
threading._register_atexit(_python_exit)
# Controls how many more calls than processes will be queued in the call queue. # Controls how many more calls than processes will be queued in the call queue.
# A smaller number will mean that processes spend more time idle waiting for # A smaller number will mean that processes spend more time idle waiting for
# work while a larger number will make Future.cancel() succeed less frequently # work while a larger number will make Future.cancel() succeed less frequently
@ -109,6 +107,12 @@ def _python_exit():
EXTRA_QUEUED_CALLS = 1 EXTRA_QUEUED_CALLS = 1
# On Windows, WaitForMultipleObjects is used to wait for processes to finish.
# It can wait on, at most, 63 objects. There is an overhead of two objects:
# - the result queue reader
# - the thread wakeup reader
_MAX_WINDOWS_WORKERS = 63 - 2
# Hack to embed stringification of remote traceback in local traceback # Hack to embed stringification of remote traceback in local traceback
class _RemoteTraceback(Exception): class _RemoteTraceback(Exception):
@ -122,6 +126,9 @@ class _ExceptionWithTraceback:
tb = traceback.format_exception(type(exc), exc, tb) tb = traceback.format_exception(type(exc), exc, tb)
tb = ''.join(tb) tb = ''.join(tb)
self.exc = exc self.exc = exc
# Traceback object needs to be garbage-collected as its frames
# contain references to all the objects in the exception scope
self.exc.__traceback__ = None
self.tb = '\n"""\n%s"""' % tb self.tb = '\n"""\n%s"""' % tb
def __reduce__(self): def __reduce__(self):
return _rebuild_exc, (self.exc, self.tb) return _rebuild_exc, (self.exc, self.tb)
@ -153,8 +160,11 @@ class _CallItem(object):
class _SafeQueue(Queue): class _SafeQueue(Queue):
"""Safe Queue set exception to the future object linked to a job""" """Safe Queue set exception to the future object linked to a job"""
def __init__(self, max_size=0, *, ctx, pending_work_items): def __init__(self, max_size=0, *, ctx, pending_work_items, shutdown_lock,
thread_wakeup):
self.pending_work_items = pending_work_items self.pending_work_items = pending_work_items
self.shutdown_lock = shutdown_lock
self.thread_wakeup = thread_wakeup
super().__init__(max_size, ctx=ctx) super().__init__(max_size, ctx=ctx)
def _on_queue_feeder_error(self, e, obj): def _on_queue_feeder_error(self, e, obj):
@ -162,8 +172,11 @@ class _SafeQueue(Queue):
tb = traceback.format_exception(type(e), e, e.__traceback__) tb = traceback.format_exception(type(e), e, e.__traceback__)
e.__cause__ = _RemoteTraceback('\n"""\n{}"""'.format(''.join(tb))) e.__cause__ = _RemoteTraceback('\n"""\n{}"""'.format(''.join(tb)))
work_item = self.pending_work_items.pop(obj.work_id, None) work_item = self.pending_work_items.pop(obj.work_id, None)
# work_item can be None if another process terminated. In this case, with self.shutdown_lock:
# the queue_manager_thread fails all work_items with BrokenProcessPool self.thread_wakeup.wakeup()
# work_item can be None if another process terminated. In this
# case, the executor_manager_thread fails all work_items
# with BrokenProcessPool
if work_item is not None: if work_item is not None:
work_item.future.set_exception(e) work_item.future.set_exception(e)
else: else:
@ -179,6 +192,7 @@ def _get_chunks(*iterables, chunksize):
return return
yield chunk yield chunk
def _process_chunk(fn, chunk): def _process_chunk(fn, chunk):
""" Processes a chunk of an iterable passed to map. """ Processes a chunk of an iterable passed to map.
@ -235,126 +249,139 @@ def _process_worker(call_queue, result_queue, initializer, initargs):
_sendback_result(result_queue, call_item.work_id, exception=exc) _sendback_result(result_queue, call_item.work_id, exception=exc)
else: else:
_sendback_result(result_queue, call_item.work_id, result=r) _sendback_result(result_queue, call_item.work_id, result=r)
del r
# Liberate the resource as soon as possible, to avoid holding onto # Liberate the resource as soon as possible, to avoid holding onto
# open files or shared memory that is not needed anymore # open files or shared memory that is not needed anymore
del call_item del call_item
def _add_call_item_to_queue(pending_work_items, class _ExecutorManagerThread(threading.Thread):
work_ids, """Manages the communication between this process and the worker processes.
call_queue):
"""Fills call_queue with _WorkItems from pending_work_items.
This function never blocks. The manager is run in a local thread.
Args: Args:
pending_work_items: A dict mapping work ids to _WorkItems e.g. executor: A reference to the ProcessPoolExecutor that owns
{5: <_WorkItem...>, 6: <_WorkItem...>, ...} this thread. A weakref will be own by the manager as well as
work_ids: A queue.Queue of work ids e.g. Queue([5, 6, ...]). Work ids references to internal objects used to introspect the state of
are consumed and the corresponding _WorkItems from the executor.
pending_work_items are transformed into _CallItems and put in
call_queue.
call_queue: A multiprocessing.Queue that will be filled with _CallItems
derived from _WorkItems.
""" """
def __init__(self, executor):
# Store references to necessary internals of the executor.
# A _ThreadWakeup to allow waking up the queue_manager_thread from the
# main Thread and avoid deadlocks caused by permanently locked queues.
self.thread_wakeup = executor._executor_manager_thread_wakeup
self.shutdown_lock = executor._shutdown_lock
# A weakref.ref to the ProcessPoolExecutor that owns this thread. Used
# to determine if the ProcessPoolExecutor has been garbage collected
# and that the manager can exit.
# When the executor gets garbage collected, the weakref callback
# will wake up the queue management thread so that it can terminate
# if there is no pending work item.
def weakref_cb(_,
thread_wakeup=self.thread_wakeup,
shutdown_lock=self.shutdown_lock):
mp.util.debug('Executor collected: triggering callback for'
' QueueManager wakeup')
with shutdown_lock:
thread_wakeup.wakeup()
self.executor_reference = weakref.ref(executor, weakref_cb)
# A list of the ctx.Process instances used as workers.
self.processes = executor._processes
# A ctx.Queue that will be filled with _CallItems derived from
# _WorkItems for processing by the process workers.
self.call_queue = executor._call_queue
# A ctx.SimpleQueue of _ResultItems generated by the process workers.
self.result_queue = executor._result_queue
# A queue.Queue of work ids e.g. Queue([5, 6, ...]).
self.work_ids_queue = executor._work_ids
# A dict mapping work ids to _WorkItems e.g.
# {5: <_WorkItem...>, 6: <_WorkItem...>, ...}
self.pending_work_items = executor._pending_work_items
super().__init__()
def run(self):
# Main loop for the executor manager thread.
while True: while True:
if call_queue.full(): self.add_call_item_to_queue()
result_item, is_broken, cause = self.wait_result_broken_or_wakeup()
if is_broken:
self.terminate_broken(cause)
return
if result_item is not None:
self.process_result_item(result_item)
# Delete reference to result_item to avoid keeping references
# while waiting on new results.
del result_item
# attempt to increment idle process count
executor = self.executor_reference()
if executor is not None:
executor._idle_worker_semaphore.release()
del executor
if self.is_shutting_down():
self.flag_executor_shutting_down()
# Since no new work items can be added, it is safe to shutdown
# this thread if there are no pending work items.
if not self.pending_work_items:
self.join_executor_internals()
return
def add_call_item_to_queue(self):
# Fills call_queue with _WorkItems from pending_work_items.
# This function never blocks.
while True:
if self.call_queue.full():
return return
try: try:
work_id = work_ids.get(block=False) work_id = self.work_ids_queue.get(block=False)
except queue.Empty: except queue.Empty:
return return
else: else:
work_item = pending_work_items[work_id] work_item = self.pending_work_items[work_id]
if work_item.future.set_running_or_notify_cancel(): if work_item.future.set_running_or_notify_cancel():
call_queue.put(_CallItem(work_id, self.call_queue.put(_CallItem(work_id,
work_item.fn, work_item.fn,
work_item.args, work_item.args,
work_item.kwargs), work_item.kwargs),
block=True) block=True)
else: else:
del pending_work_items[work_id] del self.pending_work_items[work_id]
continue continue
def wait_result_broken_or_wakeup(self):
def _queue_management_worker(executor_reference,
processes,
pending_work_items,
work_ids_queue,
call_queue,
result_queue,
thread_wakeup):
"""Manages the communication between this process and the worker processes.
This function is run in a local thread.
Args:
executor_reference: A weakref.ref to the ProcessPoolExecutor that owns
this thread. Used to determine if the ProcessPoolExecutor has been
garbage collected and that this function can exit.
process: A list of the ctx.Process instances used as
workers.
pending_work_items: A dict mapping work ids to _WorkItems e.g.
{5: <_WorkItem...>, 6: <_WorkItem...>, ...}
work_ids_queue: A queue.Queue of work ids e.g. Queue([5, 6, ...]).
call_queue: A ctx.Queue that will be filled with _CallItems
derived from _WorkItems for processing by the process workers.
result_queue: A ctx.SimpleQueue of _ResultItems generated by the
process workers.
thread_wakeup: A _ThreadWakeup to allow waking up the
queue_manager_thread from the main Thread and avoid deadlocks
caused by permanently locked queues.
"""
executor = None
def shutting_down():
return (_global_shutdown or executor is None
or executor._shutdown_thread)
def shutdown_worker():
# This is an upper bound on the number of children alive.
n_children_alive = sum(p.is_alive() for p in processes.values())
n_children_to_stop = n_children_alive
n_sentinels_sent = 0
# Send the right number of sentinels, to make sure all children are
# properly terminated.
while n_sentinels_sent < n_children_to_stop and n_children_alive > 0:
for i in range(n_children_to_stop - n_sentinels_sent):
try:
call_queue.put_nowait(None)
n_sentinels_sent += 1
except Full:
break
n_children_alive = sum(p.is_alive() for p in processes.values())
# Release the queue's resources as soon as possible.
call_queue.close()
# If .join() is not called on the created processes then
# some ctx.Queue methods may deadlock on Mac OS X.
for p in processes.values():
p.join()
result_reader = result_queue._reader
wakeup_reader = thread_wakeup._reader
readers = [result_reader, wakeup_reader]
while True:
_add_call_item_to_queue(pending_work_items,
work_ids_queue,
call_queue)
# Wait for a result to be ready in the result_queue while checking # Wait for a result to be ready in the result_queue while checking
# that all worker processes are still running, or for a wake up # that all worker processes are still running, or for a wake up
# signal send. The wake up signals come either from new tasks being # signal send. The wake up signals come either from new tasks being
# submitted, from the executor being shutdown/gc-ed, or from the # submitted, from the executor being shutdown/gc-ed, or from the
# shutdown of the python interpreter. # shutdown of the python interpreter.
worker_sentinels = [p.sentinel for p in processes.values()] result_reader = self.result_queue._reader
ready = wait(readers + worker_sentinels) assert not self.thread_wakeup._closed
wakeup_reader = self.thread_wakeup._reader
readers = [result_reader, wakeup_reader]
worker_sentinels = [p.sentinel for p in list(self.processes.values())]
ready = mp.connection.wait(readers + worker_sentinels)
cause = None cause = None
is_broken = True is_broken = True
result_item = None
if result_reader in ready: if result_reader in ready:
try: try:
result_item = result_reader.recv() result_item = result_reader.recv()
@ -364,79 +391,138 @@ def _queue_management_worker(executor_reference,
elif wakeup_reader in ready: elif wakeup_reader in ready:
is_broken = False is_broken = False
result_item = None
thread_wakeup.clear() with self.shutdown_lock:
if is_broken: self.thread_wakeup.clear()
# Mark the process pool broken so that submits fail right now.
executor = executor_reference() return result_item, is_broken, cause
if executor is not None:
executor._broken = ('A child process terminated ' def process_result_item(self, result_item):
'abruptly, the process pool is not ' # Process the received a result_item. This can be either the PID of a
'usable anymore') # worker that exited gracefully or a _ResultItem
executor._shutdown_thread = True
executor = None
bpe = BrokenProcessPool("A process in the process pool was "
"terminated abruptly while the future was "
"running or pending.")
if cause is not None:
bpe.__cause__ = _RemoteTraceback(
f"\n'''\n{''.join(cause)}'''")
# All futures in flight must be marked failed
for work_id, work_item in pending_work_items.items():
work_item.future.set_exception(bpe)
# Delete references to object. See issue16284
del work_item
pending_work_items.clear()
# Terminate remaining workers forcibly: the queues or their
# locks may be in a dirty state and block forever.
for p in processes.values():
p.terminate()
shutdown_worker()
return
if isinstance(result_item, int): if isinstance(result_item, int):
# Clean shutdown of a worker using its PID # Clean shutdown of a worker using its PID
# (avoids marking the executor broken) # (avoids marking the executor broken)
assert shutting_down() assert self.is_shutting_down()
p = processes.pop(result_item) p = self.processes.pop(result_item)
p.join() p.join()
if not processes: if not self.processes:
shutdown_worker() self.join_executor_internals()
return return
elif result_item is not None: else:
work_item = pending_work_items.pop(result_item.work_id, None) # Received a _ResultItem so mark the future as completed.
work_item = self.pending_work_items.pop(result_item.work_id, None)
# work_item can be None if another process terminated (see above) # work_item can be None if another process terminated (see above)
if work_item is not None: if work_item is not None:
if result_item.exception: if result_item.exception:
work_item.future.set_exception(result_item.exception) work_item.future.set_exception(result_item.exception)
else: else:
work_item.future.set_result(result_item.result) work_item.future.set_result(result_item.result)
# Delete references to object. See issue16284
del work_item
# Delete reference to result_item
del result_item
# Check whether we should start shutting down. def is_shutting_down(self):
executor = executor_reference() # Check whether we should start shutting down the executor.
executor = self.executor_reference()
# No more work items can be added if: # No more work items can be added if:
# - The interpreter is shutting down OR # - The interpreter is shutting down OR
# - The executor that owns this worker has been collected OR # - The executor that owns this worker has been collected OR
# - The executor that owns this worker has been shutdown. # - The executor that owns this worker has been shutdown.
if shutting_down(): return (_global_shutdown or executor is None
try: or executor._shutdown_thread)
# Flag the executor as shutting down as early as possible if it
# is not gc-ed yet. def terminate_broken(self, cause):
# Terminate the executor because it is in a broken state. The cause
# argument can be used to display more information on the error that
# lead the executor into becoming broken.
# Mark the process pool broken so that submits fail right now.
executor = self.executor_reference()
if executor is not None:
executor._broken = ('A child process terminated '
'abruptly, the process pool is not '
'usable anymore')
executor._shutdown_thread = True
executor = None
# All pending tasks are to be marked failed with the following
# BrokenProcessPool error
bpe = BrokenProcessPool("A process in the process pool was "
"terminated abruptly while the future was "
"running or pending.")
if cause is not None:
bpe.__cause__ = _RemoteTraceback(
f"\n'''\n{''.join(cause)}'''")
# Mark pending tasks as failed.
for work_id, work_item in self.pending_work_items.items():
work_item.future.set_exception(bpe)
# Delete references to object. See issue16284
del work_item
self.pending_work_items.clear()
# Terminate remaining workers forcibly: the queues or their
# locks may be in a dirty state and block forever.
for p in self.processes.values():
p.terminate()
# clean up resources
self.join_executor_internals()
def flag_executor_shutting_down(self):
# Flag the executor as shutting down and cancel remaining tasks if
# requested as early as possible if it is not gc-ed yet.
executor = self.executor_reference()
if executor is not None: if executor is not None:
executor._shutdown_thread = True executor._shutdown_thread = True
# Since no new work items can be added, it is safe to shutdown # Cancel pending work items if requested.
# this thread if there are no pending work items. if executor._cancel_pending_futures:
if not pending_work_items: # Cancel all pending futures and update pending_work_items
shutdown_worker() # to only have futures that are currently running.
return new_pending_work_items = {}
except Full: for work_id, work_item in self.pending_work_items.items():
# This is not a problem: we will eventually be woken up (in if not work_item.future.cancel():
# result_queue.get()) and be able to send a sentinel again. new_pending_work_items[work_id] = work_item
pass self.pending_work_items = new_pending_work_items
executor = None # Drain work_ids_queue since we no longer need to
# add items to the call queue.
while True:
try:
self.work_ids_queue.get_nowait()
except queue.Empty:
break
# Make sure we do this only once to not waste time looping
# on running processes over and over.
executor._cancel_pending_futures = False
def shutdown_workers(self):
n_children_to_stop = self.get_n_children_alive()
n_sentinels_sent = 0
# Send the right number of sentinels, to make sure all children are
# properly terminated.
while (n_sentinels_sent < n_children_to_stop
and self.get_n_children_alive() > 0):
for i in range(n_children_to_stop - n_sentinels_sent):
try:
self.call_queue.put_nowait(None)
n_sentinels_sent += 1
except queue.Full:
break
def join_executor_internals(self):
self.shutdown_workers()
# Release the queue's resources as soon as possible.
self.call_queue.close()
self.call_queue.join_thread()
with self.shutdown_lock:
self.thread_wakeup.close()
# If .join() is not called on the created processes then
# some ctx.Queue methods may deadlock on Mac OS X.
for p in self.processes.values():
p.join()
def get_n_children_alive(self):
# This is an upper bound on the number of children alive.
return sum(p.is_alive() for p in self.processes.values())
_system_limits_checked = False _system_limits_checked = False
@ -497,16 +583,23 @@ class ProcessPoolExecutor(_base.Executor):
worker processes will be created as the machine has processors. worker processes will be created as the machine has processors.
mp_context: A multiprocessing context to launch the workers. This mp_context: A multiprocessing context to launch the workers. This
object should provide SimpleQueue, Queue and Process. object should provide SimpleQueue, Queue and Process.
initializer: An callable used to initialize worker processes. initializer: A callable used to initialize worker processes.
initargs: A tuple of arguments to pass to the initializer. initargs: A tuple of arguments to pass to the initializer.
""" """
_check_system_limits() _check_system_limits()
if max_workers is None: if max_workers is None:
self._max_workers = os.cpu_count() or 1 self._max_workers = os.cpu_count() or 1
if sys.platform == 'win32':
self._max_workers = min(_MAX_WINDOWS_WORKERS,
self._max_workers)
else: else:
if max_workers <= 0: if max_workers <= 0:
raise ValueError("max_workers must be greater than 0") raise ValueError("max_workers must be greater than 0")
elif (sys.platform == 'win32' and
max_workers > _MAX_WINDOWS_WORKERS):
raise ValueError(
f"max_workers must be <= {_MAX_WINDOWS_WORKERS}")
self._max_workers = max_workers self._max_workers = max_workers
@ -514,13 +607,17 @@ class ProcessPoolExecutor(_base.Executor):
mp_context = mp.get_context() mp_context = mp.get_context()
self._mp_context = mp_context self._mp_context = mp_context
# https://github.com/python/cpython/issues/90622
self._safe_to_dynamically_spawn_children = (
self._mp_context.get_start_method(allow_none=False) != "fork")
if initializer is not None and not callable(initializer): if initializer is not None and not callable(initializer):
raise TypeError("initializer must be a callable") raise TypeError("initializer must be a callable")
self._initializer = initializer self._initializer = initializer
self._initargs = initargs self._initargs = initargs
# Management thread # Management thread
self._queue_management_thread = None self._executor_manager_thread = None
# Map of pids to processes # Map of pids to processes
self._processes = {} self._processes = {}
@ -528,9 +625,21 @@ class ProcessPoolExecutor(_base.Executor):
# Shutdown is a two-step process. # Shutdown is a two-step process.
self._shutdown_thread = False self._shutdown_thread = False
self._shutdown_lock = threading.Lock() self._shutdown_lock = threading.Lock()
self._idle_worker_semaphore = threading.Semaphore(0)
self._broken = False self._broken = False
self._queue_count = 0 self._queue_count = 0
self._pending_work_items = {} self._pending_work_items = {}
self._cancel_pending_futures = False
# _ThreadWakeup is a communication channel used to interrupt the wait
# of the main loop of executor_manager_thread from another thread (e.g.
# when calling executor.submit or executor.shutdown). We do not use the
# _result_queue to send wakeup signals to the executor_manager_thread
# as it could result in a deadlock if a worker process dies with the
# _result_queue write lock still acquired.
#
# _shutdown_lock must be locked to access _ThreadWakeup.
self._executor_manager_thread_wakeup = _ThreadWakeup()
# Create communication channels for the executor # Create communication channels for the executor
# Make the call queue slightly larger than the number of processes to # Make the call queue slightly larger than the number of processes to
@ -539,7 +648,9 @@ class ProcessPoolExecutor(_base.Executor):
queue_size = self._max_workers + EXTRA_QUEUED_CALLS queue_size = self._max_workers + EXTRA_QUEUED_CALLS
self._call_queue = _SafeQueue( self._call_queue = _SafeQueue(
max_size=queue_size, ctx=self._mp_context, max_size=queue_size, ctx=self._mp_context,
pending_work_items=self._pending_work_items) pending_work_items=self._pending_work_items,
shutdown_lock=self._shutdown_lock,
thread_wakeup=self._executor_manager_thread_wakeup)
# Killed worker processes can produce spurious "broken pipe" # Killed worker processes can produce spurious "broken pipe"
# tracebacks in the queue's own worker thread. But we detect killed # tracebacks in the queue's own worker thread. But we detect killed
# processes anyway, so silence the tracebacks. # processes anyway, so silence the tracebacks.
@ -547,43 +658,40 @@ class ProcessPoolExecutor(_base.Executor):
self._result_queue = mp_context.SimpleQueue() self._result_queue = mp_context.SimpleQueue()
self._work_ids = queue.Queue() self._work_ids = queue.Queue()
# _ThreadWakeup is a communication channel used to interrupt the wait def _start_executor_manager_thread(self):
# of the main loop of queue_manager_thread from another thread (e.g. if self._executor_manager_thread is None:
# when calling executor.submit or executor.shutdown). We do not use the
# _result_queue to send the wakeup signal to the queue_manager_thread
# as it could result in a deadlock if a worker process dies with the
# _result_queue write lock still acquired.
self._queue_management_thread_wakeup = _ThreadWakeup()
def _start_queue_management_thread(self):
if self._queue_management_thread is None:
# When the executor gets garbarge collected, the weakref callback
# will wake up the queue management thread so that it can terminate
# if there is no pending work item.
def weakref_cb(_,
thread_wakeup=self._queue_management_thread_wakeup):
mp.util.debug('Executor collected: triggering callback for'
' QueueManager wakeup')
thread_wakeup.wakeup()
# Start the processes so that their sentinels are known. # Start the processes so that their sentinels are known.
self._adjust_process_count() if not self._safe_to_dynamically_spawn_children: # ie, using fork.
self._queue_management_thread = threading.Thread( self._launch_processes()
target=_queue_management_worker, self._executor_manager_thread = _ExecutorManagerThread(self)
args=(weakref.ref(self, weakref_cb), self._executor_manager_thread.start()
self._processes, _threads_wakeups[self._executor_manager_thread] = \
self._pending_work_items, self._executor_manager_thread_wakeup
self._work_ids,
self._call_queue,
self._result_queue,
self._queue_management_thread_wakeup),
name="QueueManagerThread")
self._queue_management_thread.daemon = True
self._queue_management_thread.start()
_threads_wakeups[self._queue_management_thread] = \
self._queue_management_thread_wakeup
def _adjust_process_count(self): def _adjust_process_count(self):
# if there's an idle process, we don't need to spawn a new one.
if self._idle_worker_semaphore.acquire(blocking=False):
return
process_count = len(self._processes)
if process_count < self._max_workers:
# Assertion disabled as this codepath is also used to replace a
# worker that unexpectedly dies, even when using the 'fork' start
# method. That means there is still a potential deadlock bug. If a
# 'fork' mp_context worker dies, we'll be forking a new one when
# we know a thread is running (self._executor_manager_thread).
#assert self._safe_to_dynamically_spawn_children or not self._executor_manager_thread, 'https://github.com/python/cpython/issues/90622'
self._spawn_process()
def _launch_processes(self):
# https://github.com/python/cpython/issues/90622
assert not self._executor_manager_thread, (
'Processes cannot be fork()ed after the thread has started, '
'deadlock in the child processes could result.')
for _ in range(len(self._processes), self._max_workers): for _ in range(len(self._processes), self._max_workers):
self._spawn_process()
def _spawn_process(self):
p = self._mp_context.Process( p = self._mp_context.Process(
target=_process_worker, target=_process_worker,
args=(self._call_queue, args=(self._call_queue,
@ -593,7 +701,7 @@ class ProcessPoolExecutor(_base.Executor):
p.start() p.start()
self._processes[p.pid] = p self._processes[p.pid] = p
def submit(self, fn, *args, **kwargs): def submit(self, fn, /, *args, **kwargs):
with self._shutdown_lock: with self._shutdown_lock:
if self._broken: if self._broken:
raise BrokenProcessPool(self._broken) raise BrokenProcessPool(self._broken)
@ -610,9 +718,11 @@ class ProcessPoolExecutor(_base.Executor):
self._work_ids.put(self._queue_count) self._work_ids.put(self._queue_count)
self._queue_count += 1 self._queue_count += 1
# Wake up queue management thread # Wake up queue management thread
self._queue_management_thread_wakeup.wakeup() self._executor_manager_thread_wakeup.wakeup()
self._start_queue_management_thread() if self._safe_to_dynamically_spawn_children:
self._adjust_process_count()
self._start_executor_manager_thread()
return f return f
submit.__doc__ = _base.Executor.submit.__doc__ submit.__doc__ = _base.Executor.submit.__doc__
@ -645,29 +755,24 @@ class ProcessPoolExecutor(_base.Executor):
timeout=timeout) timeout=timeout)
return _chain_from_iterable_of_lists(results) return _chain_from_iterable_of_lists(results)
def shutdown(self, wait=True): def shutdown(self, wait=True, *, cancel_futures=False):
with self._shutdown_lock: with self._shutdown_lock:
self._cancel_pending_futures = cancel_futures
self._shutdown_thread = True self._shutdown_thread = True
if self._queue_management_thread: if self._executor_manager_thread_wakeup is not None:
# Wake up queue management thread # Wake up queue management thread
self._queue_management_thread_wakeup.wakeup() self._executor_manager_thread_wakeup.wakeup()
if wait:
self._queue_management_thread.join() if self._executor_manager_thread is not None and wait:
self._executor_manager_thread.join()
# To reduce the risk of opening too many files, remove references to # To reduce the risk of opening too many files, remove references to
# objects that use file descriptors. # objects that use file descriptors.
self._queue_management_thread = None self._executor_manager_thread = None
if self._call_queue is not None:
self._call_queue.close()
if wait:
self._call_queue.join_thread()
self._call_queue = None self._call_queue = None
if self._result_queue is not None and wait:
self._result_queue.close()
self._result_queue = None self._result_queue = None
self._processes = None self._processes = None
self._executor_manager_thread_wakeup = None
if self._queue_management_thread_wakeup:
self._queue_management_thread_wakeup.close()
self._queue_management_thread_wakeup = None
shutdown.__doc__ = _base.Executor.shutdown.__doc__ shutdown.__doc__ = _base.Executor.shutdown.__doc__
atexit.register(_python_exit)

View File

@ -5,33 +5,24 @@
__author__ = 'Brian Quinlan (brian@sweetapp.com)' __author__ = 'Brian Quinlan (brian@sweetapp.com)'
import atexit
from concurrent.futures import _base from concurrent.futures import _base
import itertools import itertools
import queue import queue
import threading import threading
import types
import weakref import weakref
import os import os
# Workers are created as daemon threads. This is done to allow the interpreter
# to exit when there are still idle threads in a ThreadPoolExecutor's thread
# pool (i.e. shutdown() was not called). However, allowing workers to die with
# the interpreter has two undesirable properties:
# - The workers would still be running during interpreter shutdown,
# meaning that they would fail in unpredictable ways.
# - The workers could be killed while evaluating a work item, which could
# be bad if the callable being evaluated has external side-effects e.g.
# writing to a file.
#
# To work around this problem, an exit handler is installed which tells the
# workers to exit when their work queues are empty and then waits until the
# threads finish.
_threads_queues = weakref.WeakKeyDictionary() _threads_queues = weakref.WeakKeyDictionary()
_shutdown = False _shutdown = False
# Lock that ensures that new workers are not created while the interpreter is
# shutting down. Must be held while mutating _threads_queues and _shutdown.
_global_shutdown_lock = threading.Lock()
def _python_exit(): def _python_exit():
global _shutdown global _shutdown
with _global_shutdown_lock:
_shutdown = True _shutdown = True
items = list(_threads_queues.items()) items = list(_threads_queues.items())
for t, q in items: for t, q in items:
@ -39,7 +30,17 @@ def _python_exit():
for t, q in items: for t, q in items:
t.join() t.join()
atexit.register(_python_exit) # Register for `_python_exit()` to be called just before joining all
# non-daemon threads. This is used instead of `atexit.register()` for
# compatibility with subinterpreters, which no longer support daemon threads.
# See bpo-39812 for context.
threading._register_atexit(_python_exit)
# At fork, reinitialize the `_global_shutdown_lock` lock in the child process
if hasattr(os, 'register_at_fork'):
os.register_at_fork(before=_global_shutdown_lock.acquire,
after_in_child=_global_shutdown_lock._at_fork_reinit,
after_in_parent=_global_shutdown_lock.release)
class _WorkItem(object): class _WorkItem(object):
@ -62,6 +63,8 @@ class _WorkItem(object):
else: else:
self.future.set_result(result) self.future.set_result(result)
__class_getitem__ = classmethod(types.GenericAlias)
def _worker(executor_reference, work_queue, initializer, initargs): def _worker(executor_reference, work_queue, initializer, initargs):
if initializer is not None: if initializer is not None:
@ -80,7 +83,14 @@ def _worker(executor_reference, work_queue, initializer, initargs):
work_item.run() work_item.run()
# Delete references to object. See issue16284 # Delete references to object. See issue16284
del work_item del work_item
# attempt to increment idle count
executor = executor_reference()
if executor is not None:
executor._idle_semaphore.release()
del executor
continue continue
executor = executor_reference() executor = executor_reference()
# Exit if: # Exit if:
# - The interpreter is shutting down OR # - The interpreter is shutting down OR
@ -118,13 +128,18 @@ class ThreadPoolExecutor(_base.Executor):
max_workers: The maximum number of threads that can be used to max_workers: The maximum number of threads that can be used to
execute the given calls. execute the given calls.
thread_name_prefix: An optional name prefix to give our threads. thread_name_prefix: An optional name prefix to give our threads.
initializer: An callable used to initialize worker threads. initializer: A callable used to initialize worker threads.
initargs: A tuple of arguments to pass to the initializer. initargs: A tuple of arguments to pass to the initializer.
""" """
if max_workers is None: if max_workers is None:
# Use this number because ThreadPoolExecutor is often # ThreadPoolExecutor is often used to:
# used to overlap I/O instead of CPU work. # * CPU bound task which releases GIL
max_workers = (os.cpu_count() or 1) * 5 # * I/O bound task (which releases GIL, of course)
#
# We use cpu_count + 4 for both types of tasks.
# But we limit it to 32 to avoid consuming surprisingly large resource
# on many core machine.
max_workers = min(32, (os.cpu_count() or 1) + 4)
if max_workers <= 0: if max_workers <= 0:
raise ValueError("max_workers must be greater than 0") raise ValueError("max_workers must be greater than 0")
@ -133,6 +148,7 @@ class ThreadPoolExecutor(_base.Executor):
self._max_workers = max_workers self._max_workers = max_workers
self._work_queue = queue.SimpleQueue() self._work_queue = queue.SimpleQueue()
self._idle_semaphore = threading.Semaphore(0)
self._threads = set() self._threads = set()
self._broken = False self._broken = False
self._shutdown = False self._shutdown = False
@ -142,8 +158,8 @@ class ThreadPoolExecutor(_base.Executor):
self._initializer = initializer self._initializer = initializer
self._initargs = initargs self._initargs = initargs
def submit(self, fn, *args, **kwargs): def submit(self, fn, /, *args, **kwargs):
with self._shutdown_lock: with self._shutdown_lock, _global_shutdown_lock:
if self._broken: if self._broken:
raise BrokenThreadPool(self._broken) raise BrokenThreadPool(self._broken)
@ -162,12 +178,15 @@ class ThreadPoolExecutor(_base.Executor):
submit.__doc__ = _base.Executor.submit.__doc__ submit.__doc__ = _base.Executor.submit.__doc__
def _adjust_thread_count(self): def _adjust_thread_count(self):
# if idle threads are available, don't spin new threads
if self._idle_semaphore.acquire(timeout=0):
return
# When the executor gets lost, the weakref callback will wake up # When the executor gets lost, the weakref callback will wake up
# the worker threads. # the worker threads.
def weakref_cb(_, q=self._work_queue): def weakref_cb(_, q=self._work_queue):
q.put(None) q.put(None)
# TODO(bquinlan): Should avoid creating new threads if there are more
# idle threads than items in the work queue.
num_threads = len(self._threads) num_threads = len(self._threads)
if num_threads < self._max_workers: if num_threads < self._max_workers:
thread_name = '%s_%d' % (self._thread_name_prefix or self, thread_name = '%s_%d' % (self._thread_name_prefix or self,
@ -177,7 +196,6 @@ class ThreadPoolExecutor(_base.Executor):
self._work_queue, self._work_queue,
self._initializer, self._initializer,
self._initargs)) self._initargs))
t.daemon = True
t.start() t.start()
self._threads.add(t) self._threads.add(t)
_threads_queues[t] = self._work_queue _threads_queues[t] = self._work_queue
@ -195,9 +213,22 @@ class ThreadPoolExecutor(_base.Executor):
if work_item is not None: if work_item is not None:
work_item.future.set_exception(BrokenThreadPool(self._broken)) work_item.future.set_exception(BrokenThreadPool(self._broken))
def shutdown(self, wait=True): def shutdown(self, wait=True, *, cancel_futures=False):
with self._shutdown_lock: with self._shutdown_lock:
self._shutdown = True self._shutdown = True
if cancel_futures:
# Drain all work items from the queue, and then cancel their
# associated futures.
while True:
try:
work_item = self._work_queue.get_nowait()
except queue.Empty:
break
if work_item is not None:
work_item.future.cancel()
# Send a wake-up to prevent threads calling
# _work_queue.get(block=True) from permanently blocking.
self._work_queue.put(None) self._work_queue.put(None)
if wait: if wait:
for t in self._threads: for t in self._threads:

View File

@ -80,7 +80,7 @@ ConfigParser -- responsible for parsing a list of
Return list of configuration options for the named section. Return list of configuration options for the named section.
read(filenames, encoding=None) read(filenames, encoding=None)
Read and parse the list of named configuration files, given by Read and parse the iterable of named configuration files, given by
name. A single filename is also allowed. Non-existing files name. A single filename is also allowed. Non-existing files
are ignored. Return list of successfully read files. are ignored. Return list of successfully read files.
@ -139,7 +139,7 @@ ConfigParser -- responsible for parsing a list of
""" """
from collections.abc import MutableMapping from collections.abc import MutableMapping
from collections import OrderedDict as _default_dict, ChainMap as _ChainMap from collections import ChainMap as _ChainMap
import functools import functools
import io import io
import itertools import itertools
@ -157,6 +157,7 @@ __all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
"LegacyInterpolation", "SectionProxy", "ConverterMapping", "LegacyInterpolation", "SectionProxy", "ConverterMapping",
"DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"] "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"]
_default_dict = dict
DEFAULTSECT = "DEFAULT" DEFAULTSECT = "DEFAULT"
MAX_INTERPOLATION_DEPTH = 10 MAX_INTERPOLATION_DEPTH = 10
@ -676,13 +677,13 @@ class RawConfigParser(MutableMapping):
return list(opts.keys()) return list(opts.keys())
def read(self, filenames, encoding=None): def read(self, filenames, encoding=None):
"""Read and parse a filename or a list of filenames. """Read and parse a filename or an iterable of filenames.
Files that cannot be opened are silently ignored; this is Files that cannot be opened are silently ignored; this is
designed so that you can specify a list of potential designed so that you can specify an iterable of potential
configuration file locations (e.g. current directory, user's configuration file locations (e.g. current directory, user's
home directory, systemwide directory), and all existing home directory, systemwide directory), and all existing
configuration files in the list will be read. A single configuration files in the iterable will be read. A single
filename may also be given. filename may also be given.
Return list of successfully read files. Return list of successfully read files.
@ -846,6 +847,7 @@ class RawConfigParser(MutableMapping):
except KeyError: except KeyError:
if section != self.default_section: if section != self.default_section:
raise NoSectionError(section) raise NoSectionError(section)
orig_keys = list(d.keys())
# Update with the entry specific variables # Update with the entry specific variables
if vars: if vars:
for key, value in vars.items(): for key, value in vars.items():
@ -854,7 +856,7 @@ class RawConfigParser(MutableMapping):
section, option, d[option], d) section, option, d[option], d)
if raw: if raw:
value_getter = lambda option: d[option] value_getter = lambda option: d[option]
return [(option, value_getter(option)) for option in d.keys()] return [(option, value_getter(option)) for option in orig_keys]
def popitem(self): def popitem(self):
"""Remove a section from the parser and return it as """Remove a section from the parser and return it as
@ -905,6 +907,9 @@ class RawConfigParser(MutableMapping):
If `space_around_delimiters' is True (the default), delimiters If `space_around_delimiters' is True (the default), delimiters
between keys and values are surrounded by spaces. between keys and values are surrounded by spaces.
Please note that comments in the original configuration file are not
preserved when writing the configuration back.
""" """
if space_around_delimiters: if space_around_delimiters:
d = " {} ".format(self._delimiters[0]) d = " {} ".format(self._delimiters[0])
@ -961,7 +966,8 @@ class RawConfigParser(MutableMapping):
def __setitem__(self, key, value): def __setitem__(self, key, value):
# To conform with the mapping protocol, overwrites existing values in # To conform with the mapping protocol, overwrites existing values in
# the section. # the section.
if key in self and self[key] is value:
return
# XXX this is not atomic if read_dict fails at any point. Then again, # XXX this is not atomic if read_dict fails at any point. Then again,
# no update method in configparser is atomic in this implementation. # no update method in configparser is atomic in this implementation.
if key == self.default_section: if key == self.default_section:
@ -1002,7 +1008,7 @@ class RawConfigParser(MutableMapping):
Configuration files may include comments, prefixed by specific Configuration files may include comments, prefixed by specific
characters (`#' and `;' by default). Comments may appear on their own characters (`#' and `;' by default). Comments may appear on their own
in an otherwise empty line or may be entered in lines holding values or in an otherwise empty line or may be entered in lines holding values or
section names. section names. Please note that comments get stripped off when reading configuration files.
""" """
elements_added = set() elements_added = set()
cursect = None # None, or a dictionary cursect = None # None, or a dictionary

View File

@ -4,6 +4,7 @@ import sys
import _collections_abc import _collections_abc
from collections import deque from collections import deque
from functools import wraps from functools import wraps
from types import MethodType, GenericAlias
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext", __all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
"AbstractContextManager", "AbstractAsyncContextManager", "AbstractContextManager", "AbstractAsyncContextManager",
@ -15,6 +16,8 @@ class AbstractContextManager(abc.ABC):
"""An abstract base class for context managers.""" """An abstract base class for context managers."""
__class_getitem__ = classmethod(GenericAlias)
def __enter__(self): def __enter__(self):
"""Return `self` upon entering the runtime context.""" """Return `self` upon entering the runtime context."""
return self return self
@ -35,6 +38,8 @@ class AbstractAsyncContextManager(abc.ABC):
"""An abstract base class for asynchronous context managers.""" """An abstract base class for asynchronous context managers."""
__class_getitem__ = classmethod(GenericAlias)
async def __aenter__(self): async def __aenter__(self):
"""Return `self` upon entering the runtime context.""" """Return `self` upon entering the runtime context."""
return self return self
@ -92,18 +97,20 @@ class _GeneratorContextManagerBase:
# for the class instead. # for the class instead.
# See http://bugs.python.org/issue19404 for more details. # See http://bugs.python.org/issue19404 for more details.
class _GeneratorContextManager(_GeneratorContextManagerBase,
AbstractContextManager,
ContextDecorator):
"""Helper for @contextmanager decorator."""
def _recreate_cm(self): def _recreate_cm(self):
# _GCM instances are one-shot context managers, so the # _GCMB instances are one-shot context managers, so the
# CM must be recreated each time a decorated function is # CM must be recreated each time a decorated function is
# called # called
return self.__class__(self.func, self.args, self.kwds) return self.__class__(self.func, self.args, self.kwds)
class _GeneratorContextManager(
_GeneratorContextManagerBase,
AbstractContextManager,
ContextDecorator,
):
"""Helper for @contextmanager decorator."""
def __enter__(self): def __enter__(self):
# do not keep args and kwds alive unnecessarily # do not keep args and kwds alive unnecessarily
# they are only needed for recreation, which is not possible anymore # they are only needed for recreation, which is not possible anymore
@ -113,8 +120,8 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
except StopIteration: except StopIteration:
raise RuntimeError("generator didn't yield") from None raise RuntimeError("generator didn't yield") from None
def __exit__(self, type, value, traceback): def __exit__(self, typ, value, traceback):
if type is None: if typ is None:
try: try:
next(self.gen) next(self.gen)
except StopIteration: except StopIteration:
@ -125,9 +132,9 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
if value is None: if value is None:
# Need to force instantiation so we can reliably # Need to force instantiation so we can reliably
# tell if we get the same exception back # tell if we get the same exception back
value = type() value = typ()
try: try:
self.gen.throw(type, value, traceback) self.gen.throw(typ, value, traceback)
except StopIteration as exc: except StopIteration as exc:
# Suppress StopIteration *unless* it's the same exception that # Suppress StopIteration *unless* it's the same exception that
# was passed to throw(). This prevents a StopIteration # was passed to throw(). This prevents a StopIteration
@ -137,35 +144,39 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
# Don't re-raise the passed in exception. (issue27122) # Don't re-raise the passed in exception. (issue27122)
if exc is value: if exc is value:
return False return False
# Likewise, avoid suppressing if a StopIteration exception # Avoid suppressing if a StopIteration exception
# was passed to throw() and later wrapped into a RuntimeError # was passed to throw() and later wrapped into a RuntimeError
# (see PEP 479). # (see PEP 479 for sync generators; async generators also
if type is StopIteration and exc.__cause__ is value: # have this behavior). But do this only if the exception wrapped
# by the RuntimeError is actually Stop(Async)Iteration (see
# issue29692).
if (
isinstance(value, StopIteration)
and exc.__cause__ is value
):
return False return False
raise raise
except: except BaseException as exc:
# only re-raise if it's *not* the exception that was # only re-raise if it's *not* the exception that was
# passed to throw(), because __exit__() must not raise # passed to throw(), because __exit__() must not raise
# an exception unless __exit__() itself failed. But throw() # an exception unless __exit__() itself failed. But throw()
# has to raise the exception to signal propagation, so this # has to raise the exception to signal propagation, so this
# fixes the impedance mismatch between the throw() protocol # fixes the impedance mismatch between the throw() protocol
# and the __exit__() protocol. # and the __exit__() protocol.
# if exc is not value:
# This cannot use 'except BaseException as exc' (as in the
# async implementation) to maintain compatibility with
# Python 2, where old-style class exceptions are not caught
# by 'except BaseException'.
if sys.exc_info()[1] is value:
return False
raise raise
return False
raise RuntimeError("generator didn't stop after throw()") raise RuntimeError("generator didn't stop after throw()")
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase, class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
AbstractAsyncContextManager): AbstractAsyncContextManager):
"""Helper for @asynccontextmanager.""" """Helper for @asynccontextmanager decorator."""
async def __aenter__(self): async def __aenter__(self):
# do not keep args and kwds alive unnecessarily
# they are only needed for recreation, which is not possible anymore
del self.args, self.kwds, self.func
try: try:
return await self.gen.__anext__() return await self.gen.__anext__()
except StopAsyncIteration: except StopAsyncIteration:
@ -176,35 +187,48 @@ class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
try: try:
await self.gen.__anext__() await self.gen.__anext__()
except StopAsyncIteration: except StopAsyncIteration:
return return False
else: else:
raise RuntimeError("generator didn't stop") raise RuntimeError("generator didn't stop")
else: else:
if value is None: if value is None:
# Need to force instantiation so we can reliably
# tell if we get the same exception back
value = typ() value = typ()
# See _GeneratorContextManager.__exit__ for comments on subtleties
# in this implementation
try: try:
await self.gen.athrow(typ, value, traceback) await self.gen.athrow(typ, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopAsyncIteration as exc: except StopAsyncIteration as exc:
# Suppress StopIteration *unless* it's the same exception that
# was passed to throw(). This prevents a StopIteration
# raised inside the "with" statement from being suppressed.
return exc is not value return exc is not value
except RuntimeError as exc: except RuntimeError as exc:
# Don't re-raise the passed in exception. (issue27122)
if exc is value: if exc is value:
return False return False
# Avoid suppressing if a StopIteration exception # Avoid suppressing if a Stop(Async)Iteration exception
# was passed to throw() and later wrapped into a RuntimeError # was passed to athrow() and later wrapped into a RuntimeError
# (see PEP 479 for sync generators; async generators also # (see PEP 479 for sync generators; async generators also
# have this behavior). But do this only if the exception wrapped # have this behavior). But do this only if the exception wrapped
# by the RuntimeError is actully Stop(Async)Iteration (see # by the RuntimeError is actully Stop(Async)Iteration (see
# issue29692). # issue29692).
if isinstance(value, (StopIteration, StopAsyncIteration)): if (
if exc.__cause__ is value: isinstance(value, (StopIteration, StopAsyncIteration))
and exc.__cause__ is value
):
return False return False
raise raise
except BaseException as exc: except BaseException as exc:
# only re-raise if it's *not* the exception that was
# passed to throw(), because __exit__() must not raise
# an exception unless __exit__() itself failed. But throw()
# has to raise the exception to signal propagation, so this
# fixes the impedance mismatch between the throw() protocol
# and the __exit__() protocol.
if exc is not value: if exc is not value:
raise raise
return False
raise RuntimeError("generator didn't stop after athrow()")
def contextmanager(func): def contextmanager(func):
@ -373,12 +397,10 @@ class _BaseExitStack:
@staticmethod @staticmethod
def _create_exit_wrapper(cm, cm_exit): def _create_exit_wrapper(cm, cm_exit):
def _exit_wrapper(exc_type, exc, tb): return MethodType(cm_exit, cm)
return cm_exit(cm, exc_type, exc, tb)
return _exit_wrapper
@staticmethod @staticmethod
def _create_cb_wrapper(callback, *args, **kwds): def _create_cb_wrapper(callback, /, *args, **kwds):
def _exit_wrapper(exc_type, exc, tb): def _exit_wrapper(exc_type, exc, tb):
callback(*args, **kwds) callback(*args, **kwds)
return _exit_wrapper return _exit_wrapper
@ -427,7 +449,7 @@ class _BaseExitStack:
self._push_cm_exit(cm, _exit) self._push_cm_exit(cm, _exit)
return result return result
def callback(self, callback, *args, **kwds): def callback(self, callback, /, *args, **kwds):
"""Registers an arbitrary callback and arguments. """Registers an arbitrary callback and arguments.
Cannot suppress exceptions. Cannot suppress exceptions.
@ -443,7 +465,6 @@ class _BaseExitStack:
def _push_cm_exit(self, cm, cm_exit): def _push_cm_exit(self, cm, cm_exit):
"""Helper to correctly register callbacks to __exit__ methods.""" """Helper to correctly register callbacks to __exit__ methods."""
_exit_wrapper = self._create_exit_wrapper(cm, cm_exit) _exit_wrapper = self._create_exit_wrapper(cm, cm_exit)
_exit_wrapper.__self__ = cm
self._push_exit_callback(_exit_wrapper, True) self._push_exit_callback(_exit_wrapper, True)
def _push_exit_callback(self, callback, is_sync=True): def _push_exit_callback(self, callback, is_sync=True):
@ -475,10 +496,10 @@ class ExitStack(_BaseExitStack, AbstractContextManager):
# Context may not be correct, so find the end of the chain # Context may not be correct, so find the end of the chain
while 1: while 1:
exc_context = new_exc.__context__ exc_context = new_exc.__context__
if exc_context is old_exc: if exc_context is None or exc_context is old_exc:
# Context is already set correctly (see issue 20317) # Context is already set correctly (see issue 20317)
return return
if exc_context is None or exc_context is frame_exc: if exc_context is frame_exc:
break break
new_exc = exc_context new_exc = exc_context
# Change the end of the chain to point to the exception # Change the end of the chain to point to the exception
@ -535,12 +556,10 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
@staticmethod @staticmethod
def _create_async_exit_wrapper(cm, cm_exit): def _create_async_exit_wrapper(cm, cm_exit):
async def _exit_wrapper(exc_type, exc, tb): return MethodType(cm_exit, cm)
return await cm_exit(cm, exc_type, exc, tb)
return _exit_wrapper
@staticmethod @staticmethod
def _create_async_cb_wrapper(callback, *args, **kwds): def _create_async_cb_wrapper(callback, /, *args, **kwds):
async def _exit_wrapper(exc_type, exc, tb): async def _exit_wrapper(exc_type, exc, tb):
await callback(*args, **kwds) await callback(*args, **kwds)
return _exit_wrapper return _exit_wrapper
@ -575,7 +594,7 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
self._push_async_cm_exit(exit, exit_method) self._push_async_cm_exit(exit, exit_method)
return exit # Allow use as a decorator return exit # Allow use as a decorator
def push_async_callback(self, callback, *args, **kwds): def push_async_callback(self, callback, /, *args, **kwds):
"""Registers an arbitrary coroutine function and arguments. """Registers an arbitrary coroutine function and arguments.
Cannot suppress exceptions. Cannot suppress exceptions.
@ -596,7 +615,6 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
"""Helper to correctly register coroutine function to __aexit__ """Helper to correctly register coroutine function to __aexit__
method.""" method."""
_exit_wrapper = self._create_async_exit_wrapper(cm, cm_exit) _exit_wrapper = self._create_async_exit_wrapper(cm, cm_exit)
_exit_wrapper.__self__ = cm
self._push_exit_callback(_exit_wrapper, False) self._push_exit_callback(_exit_wrapper, False)
async def __aenter__(self): async def __aenter__(self):
@ -612,10 +630,10 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
# Context may not be correct, so find the end of the chain # Context may not be correct, so find the end of the chain
while 1: while 1:
exc_context = new_exc.__context__ exc_context = new_exc.__context__
if exc_context is old_exc: if exc_context is None or exc_context is old_exc:
# Context is already set correctly (see issue 20317) # Context is already set correctly (see issue 20317)
return return
if exc_context is None or exc_context is frame_exc: if exc_context is frame_exc:
break break
new_exc = exc_context new_exc = exc_context
# Change the end of the chain to point to the exception # Change the end of the chain to point to the exception

View File

@ -39,8 +39,8 @@ Python's deep copy operation avoids these problems by:
set of components copied set of components copied
This version does not copy types like module, class, function, method, This version does not copy types like module, class, function, method,
nor stack trace, stack frame, nor file, socket, window, nor array, nor nor stack trace, stack frame, nor file, socket, window, nor any
any similar types. similar types.
Classes can use the same interfaces to control copying that they use Classes can use the same interfaces to control copying that they use
to control pickling: they can define methods called __getinitargs__(), to control pickling: they can define methods called __getinitargs__(),
@ -75,24 +75,20 @@ def copy(x):
if copier: if copier:
return copier(x) return copier(x)
try: if issubclass(cls, type):
issc = issubclass(cls, type)
except TypeError: # cls is not a class
issc = False
if issc:
# treat it as a regular class: # treat it as a regular class:
return _copy_immutable(x) return _copy_immutable(x)
copier = getattr(cls, "__copy__", None) copier = getattr(cls, "__copy__", None)
if copier: if copier is not None:
return copier(x) return copier(x)
reductor = dispatch_table.get(cls) reductor = dispatch_table.get(cls)
if reductor: if reductor is not None:
rv = reductor(x) rv = reductor(x)
else: else:
reductor = getattr(x, "__reduce_ex__", None) reductor = getattr(x, "__reduce_ex__", None)
if reductor: if reductor is not None:
rv = reductor(4) rv = reductor(4)
else: else:
reductor = getattr(x, "__reduce__", None) reductor = getattr(x, "__reduce__", None)
@ -111,7 +107,7 @@ _copy_dispatch = d = {}
def _copy_immutable(x): def _copy_immutable(x):
return x return x
for t in (type(None), int, float, bool, complex, str, tuple, for t in (type(None), int, float, bool, complex, str, tuple,
bytes, frozenset, type, range, slice, bytes, frozenset, type, range, slice, property,
types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented), types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented),
types.FunctionType, weakref.ref): types.FunctionType, weakref.ref):
d[t] = _copy_immutable d[t] = _copy_immutable
@ -146,18 +142,14 @@ def deepcopy(x, memo=None, _nil=[]):
cls = type(x) cls = type(x)
copier = _deepcopy_dispatch.get(cls) copier = _deepcopy_dispatch.get(cls)
if copier: if copier is not None:
y = copier(x, memo) y = copier(x, memo)
else: else:
try: if issubclass(cls, type):
issc = issubclass(cls, type)
except TypeError: # cls is not a class (old Boost; see SF #502085)
issc = 0
if issc:
y = _deepcopy_atomic(x, memo) y = _deepcopy_atomic(x, memo)
else: else:
copier = getattr(x, "__deepcopy__", None) copier = getattr(x, "__deepcopy__", None)
if copier: if copier is not None:
y = copier(memo) y = copier(memo)
else: else:
reductor = dispatch_table.get(cls) reductor = dispatch_table.get(cls)
@ -165,7 +157,7 @@ def deepcopy(x, memo=None, _nil=[]):
rv = reductor(x) rv = reductor(x)
else: else:
reductor = getattr(x, "__reduce_ex__", None) reductor = getattr(x, "__reduce_ex__", None)
if reductor: if reductor is not None:
rv = reductor(4) rv = reductor(4)
else: else:
reductor = getattr(x, "__reduce__", None) reductor = getattr(x, "__reduce__", None)
@ -198,14 +190,12 @@ d[bool] = _deepcopy_atomic
d[complex] = _deepcopy_atomic d[complex] = _deepcopy_atomic
d[bytes] = _deepcopy_atomic d[bytes] = _deepcopy_atomic
d[str] = _deepcopy_atomic d[str] = _deepcopy_atomic
try:
d[types.CodeType] = _deepcopy_atomic d[types.CodeType] = _deepcopy_atomic
except AttributeError:
pass
d[type] = _deepcopy_atomic d[type] = _deepcopy_atomic
d[types.BuiltinFunctionType] = _deepcopy_atomic d[types.BuiltinFunctionType] = _deepcopy_atomic
d[types.FunctionType] = _deepcopy_atomic d[types.FunctionType] = _deepcopy_atomic
d[weakref.ref] = _deepcopy_atomic d[weakref.ref] = _deepcopy_atomic
d[property] = _deepcopy_atomic
def _deepcopy_list(x, memo, deepcopy=deepcopy): def _deepcopy_list(x, memo, deepcopy=deepcopy):
y = [] y = []

View File

@ -48,29 +48,36 @@ def _reconstructor(cls, base, state):
return obj return obj
_HEAPTYPE = 1<<9 _HEAPTYPE = 1<<9
_new_type = type(int.__new__)
# Python code for object.__reduce_ex__ for protocols 0 and 1 # Python code for object.__reduce_ex__ for protocols 0 and 1
def _reduce_ex(self, proto): def _reduce_ex(self, proto):
assert proto < 2 assert proto < 2
for base in self.__class__.__mro__: cls = self.__class__
for base in cls.__mro__:
if hasattr(base, '__flags__') and not base.__flags__ & _HEAPTYPE: if hasattr(base, '__flags__') and not base.__flags__ & _HEAPTYPE:
break break
new = base.__new__
if isinstance(new, _new_type) and new.__self__ is base:
break
else: else:
base = object # not really reachable base = object # not really reachable
if base is object: if base is object:
state = None state = None
else: else:
if base is self.__class__: if base is cls:
raise TypeError("can't pickle %s objects" % base.__name__) raise TypeError(f"cannot pickle {cls.__name__!r} object")
state = base(self) state = base(self)
args = (self.__class__, base, state) args = (cls, base, state)
try: try:
getstate = self.__getstate__ getstate = self.__getstate__
except AttributeError: except AttributeError:
if getattr(self, "__slots__", None): if getattr(self, "__slots__", None):
raise TypeError("a class that defines __slots__ without " raise TypeError(f"cannot pickle {cls.__name__!r} object: "
"defining __getstate__ cannot be pickled") from None f"a class that defines __slots__ without "
f"defining __getstate__ cannot be pickled "
f"with protocol {proto}") from None
try: try:
dict = self.__dict__ dict = self.__dict__
except AttributeError: except AttributeError:

View File

@ -1,6 +1,16 @@
"""Wrapper to the POSIX crypt library call and associated functionality.""" """Wrapper to the POSIX crypt library call and associated functionality."""
import sys as _sys
try:
import _crypt import _crypt
except ModuleNotFoundError:
if _sys.platform == 'win32':
raise ImportError("The crypt module is not supported on Windows")
else:
raise ImportError("The required _crypt module was not built as part of CPython")
import errno
import string as _string import string as _string
from random import SystemRandom as _SystemRandom from random import SystemRandom as _SystemRandom
from collections import namedtuple as _namedtuple from collections import namedtuple as _namedtuple
@ -79,7 +89,14 @@ def _add_method(name, *args, rounds=None):
method = _Method(name, *args) method = _Method(name, *args)
globals()['METHOD_' + name] = method globals()['METHOD_' + name] = method
salt = mksalt(method, rounds=rounds) salt = mksalt(method, rounds=rounds)
result = None
try:
result = crypt('', salt) result = crypt('', salt)
except OSError as e:
# Not all libc libraries support all encryption methods.
if e.errno == errno.EINVAL:
return False
raise
if result and len(result) == method.total_size: if result and len(result) == method.total_size:
methods.append(method) methods.append(method)
return True return True

Some files were not shown because too many files have changed in this diff Show More