完成微波仿真、点云构网、模型导出、仿真成像功能移植
parent
51341c8e72
commit
796fac0821
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -66,18 +66,20 @@ __all__ = ["all_feature_names"] + all_feature_names
|
||||||
# code.h and used by compile.h, so that an editor search will find them here.
|
# code.h and used by compile.h, so that an editor search will find them here.
|
||||||
# However, they're not exported in __all__, because they don't really belong to
|
# However, they're not exported in __all__, because they don't really belong to
|
||||||
# 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)
|
||||||
|
|
|
||||||
|
|
@ -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,30 +930,21 @@ 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:
|
if isinstance(other, Mapping):
|
||||||
raise TypeError("descriptor 'update' of 'MutableMapping' object "
|
for key in other:
|
||||||
"needs an argument")
|
self[key] = other[key]
|
||||||
self, *args = args
|
elif hasattr(other, "keys"):
|
||||||
if len(args) > 1:
|
for key in other.keys():
|
||||||
raise TypeError('update expected at most 1 arguments, got %d' %
|
self[key] = other[key]
|
||||||
len(args))
|
else:
|
||||||
if args:
|
for key, value in other:
|
||||||
other = args[0]
|
self[key] = value
|
||||||
if isinstance(other, Mapping):
|
|
||||||
for key in other:
|
|
||||||
self[key] = other[key]
|
|
||||||
elif hasattr(other, "keys"):
|
|
||||||
for key in other.keys():
|
|
||||||
self[key] = other[key]
|
|
||||||
else:
|
|
||||||
for key, value in other:
|
|
||||||
self[key] = value
|
|
||||||
for key, value in kwds.items():
|
for key, value in kwds.items():
|
||||||
self[key] = value
|
self[key] = value
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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,7 +67,10 @@ def _read_output(commandstring):
|
||||||
os.getpid(),), "w+b")
|
os.getpid(),), "w+b")
|
||||||
|
|
||||||
with contextlib.closing(fp) as fp:
|
with contextlib.closing(fp) as fp:
|
||||||
cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name)
|
if capture_stderr:
|
||||||
|
cmd = "%s >'%s' 2>&1" % (commandstring, fp.name)
|
||||||
|
else:
|
||||||
|
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'):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -2021,7 +2025,7 @@ class Decimal(object):
|
||||||
if not other and not self:
|
if not other and not self:
|
||||||
return context._raise_error(InvalidOperation,
|
return context._raise_error(InvalidOperation,
|
||||||
'at least one of pow() 1st argument '
|
'at least one of pow() 1st argument '
|
||||||
'and 2nd argument must be nonzero ;'
|
'and 2nd argument must be nonzero; '
|
||||||
'0**0 is not defined')
|
'0**0 is not defined')
|
||||||
|
|
||||||
# compute sign of result
|
# compute sign of result
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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,15 +406,28 @@ class IOBase(metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""Destructor. Calls close()."""
|
"""Destructor. Calls close()."""
|
||||||
# The try/except block is in case this is called at program
|
|
||||||
# exit time, when it's possible that globals have already been
|
|
||||||
# deleted, and then the close() call might fail. Since
|
|
||||||
# there's nothing we can do about such failures and they annoy
|
|
||||||
# the end users, we suppress the traceback.
|
|
||||||
try:
|
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()
|
self.close()
|
||||||
except:
|
else:
|
||||||
pass
|
# The try/except block is in case this is called at program
|
||||||
|
# exit time, when it's possible that globals have already been
|
||||||
|
# deleted, and then the close() call might fail. Since
|
||||||
|
# there's nothing we can do about such failures and they annoy
|
||||||
|
# the end users, we suppress the traceback.
|
||||||
|
try:
|
||||||
|
self.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
### Inquiries ###
|
### Inquiries ###
|
||||||
|
|
||||||
|
|
@ -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,7 +918,8 @@ class BytesIO(BufferedIOBase):
|
||||||
return memoryview(self._buffer)
|
return memoryview(self._buffer)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self._buffer.clear()
|
if self._buffer is not None:
|
||||||
|
self._buffer.clear()
|
||||||
super().close()
|
super().close()
|
||||||
|
|
||||||
def read(self, size=-1):
|
def read(self, size=-1):
|
||||||
|
|
@ -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()).
|
||||||
os.lseek(fd, 0, SEEK_END)
|
try:
|
||||||
|
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:
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:]))
|
||||||
|
|
|
||||||
|
|
@ -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,13 +409,19 @@ class HelpFormatter(object):
|
||||||
inserts[start] += ' ['
|
inserts[start] += ' ['
|
||||||
else:
|
else:
|
||||||
inserts[start] = '['
|
inserts[start] = '['
|
||||||
inserts[end] = ']'
|
if end in inserts:
|
||||||
|
inserts[end] += ']'
|
||||||
|
else:
|
||||||
|
inserts[end] = ']'
|
||||||
else:
|
else:
|
||||||
if start in inserts:
|
if start in inserts:
|
||||||
inserts[start] += ' ('
|
inserts[start] += ' ('
|
||||||
else:
|
else:
|
||||||
inserts[start] = '('
|
inserts[start] = '('
|
||||||
inserts[end] = ')'
|
if end in inserts:
|
||||||
|
inserts[end] += ')'
|
||||||
|
else:
|
||||||
|
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,12 +529,13 @@ 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)
|
||||||
help_lines = self._split_lines(help_text, help_width)
|
if help_text:
|
||||||
parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
|
help_lines = self._split_lines(help_text, help_width)
|
||||||
for line in help_lines[1:]:
|
parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
|
||||||
parts.append('%*s%s\n' % (help_position, '', line))
|
for line in help_lines[1:]:
|
||||||
|
parts.append('%*s%s\n' % (help_position, '', line))
|
||||||
|
|
||||||
# or add a newline if the description doesn't end with one
|
# or add a newline if the description doesn't end with one
|
||||||
elif not action_header.endswith('\n'):
|
elif not action_header.endswith('\n'):
|
||||||
|
|
@ -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:
|
||||||
formats = ['%s' for _ in range(action.nargs)]
|
try:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -710,11 +726,13 @@ def _get_action_name(argument):
|
||||||
if argument is None:
|
if argument is None:
|
||||||
return None
|
return None
|
||||||
elif argument.option_strings:
|
elif argument.option_strings:
|
||||||
return '/'.join(argument.option_strings)
|
return '/'.join(argument.option_strings)
|
||||||
elif argument.metavar not in (None, SUPPRESS):
|
elif argument.metavar not in (None, SUPPRESS):
|
||||||
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,10 +1545,8 @@ 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:
|
long_option_strings.append(option_string)
|
||||||
if option_string[1] in self.prefix_chars:
|
|
||||||
long_option_strings.append(option_string)
|
|
||||||
|
|
||||||
# infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
|
# infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
|
||||||
dest = kwargs.pop('dest', None)
|
dest = kwargs.pop('dest', None)
|
||||||
|
|
@ -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
|
||||||
try:
|
if self.exit_on_error:
|
||||||
|
try:
|
||||||
|
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)
|
namespace, args = self._parse_known_args(args, namespace)
|
||||||
if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
|
|
||||||
args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
|
if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
|
||||||
delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
|
args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
|
||||||
return namespace, args
|
delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
|
||||||
except ArgumentError:
|
return namespace, args
|
||||||
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,24 +2205,23 @@ 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)
|
|
||||||
|
|
||||||
# if multiple actions match, the option string was ambiguous
|
# if multiple actions match, the option string was ambiguous
|
||||||
if len(option_tuples) > 1:
|
if len(option_tuples) > 1:
|
||||||
options = ', '.join([option_string
|
options = ', '.join([option_string
|
||||||
for action, option_string, explicit_arg in option_tuples])
|
for action, option_string, explicit_arg in option_tuples])
|
||||||
args = {'option': arg_string, 'matches': options}
|
args = {'option': arg_string, 'matches': options}
|
||||||
msg = _('ambiguous option: %(option)s could match %(matches)s')
|
msg = _('ambiguous option: %(option)s could match %(matches)s')
|
||||||
self.error(msg % args)
|
self.error(msg % args)
|
||||||
|
|
||||||
# if exactly one action matched, this segmentation is good,
|
# if exactly one action matched, this segmentation is good,
|
||||||
# so return the parsed action
|
# so return the parsed action
|
||||||
elif len(option_tuples) == 1:
|
elif len(option_tuples) == 1:
|
||||||
option_tuple, = option_tuples
|
option_tuple, = option_tuples
|
||||||
return option_tuple
|
return option_tuple
|
||||||
|
|
||||||
# if it was not found as an option, but it looks like a negative
|
# if it was not found as an option, but it looks like a negative
|
||||||
# number, it was meant to be positional
|
# number, it was meant to be positional
|
||||||
|
|
@ -2165,16 +2245,17 @@ 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 '=' in option_string:
|
if self.allow_abbrev:
|
||||||
option_prefix, explicit_arg = option_string.split('=', 1)
|
if '=' in option_string:
|
||||||
else:
|
option_prefix, explicit_arg = option_string.split('=', 1)
|
||||||
option_prefix = option_string
|
else:
|
||||||
explicit_arg = None
|
option_prefix = option_string
|
||||||
for option_string in self._option_string_actions:
|
explicit_arg = None
|
||||||
if option_string.startswith(option_prefix):
|
for option_string in self._option_string_actions:
|
||||||
action = self._option_string_actions[option_string]
|
if option_string.startswith(option_prefix):
|
||||||
tup = action, option_string, explicit_arg
|
action = self._option_string_actions[option_string]
|
||||||
result.append(tup)
|
tup = action, option_string, explicit_arg
|
||||||
|
result.append(tup)
|
||||||
|
|
||||||
# single character options can be concatenated with their arguments
|
# single character options can be concatenated with their arguments
|
||||||
# but multiple character options always have to have their argument
|
# but multiple character options always have to have their argument
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,7 +148,10 @@ 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.
|
||||||
return af, type, proto, '', (host, port)
|
if _HAS_IPv6 and af == socket.AF_INET6:
|
||||||
|
return af, type, proto, '', (host, port, flowinfo, scopeid)
|
||||||
|
else:
|
||||||
|
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:
|
||||||
try:
|
# not using happy eyeballs
|
||||||
sock = socket.socket(family=family, type=type, proto=proto)
|
for addrinfo in infos:
|
||||||
sock.setblocking(False)
|
try:
|
||||||
if local_addr is not None:
|
sock = await self._connect_sock(
|
||||||
for _, _, _, _, laddr in laddr_infos:
|
exceptions, addrinfo, laddr_infos)
|
||||||
try:
|
break
|
||||||
sock.bind(laddr)
|
except OSError:
|
||||||
break
|
continue
|
||||||
except OSError as exc:
|
else: # using happy eyeballs
|
||||||
msg = (
|
sock, _, _ = await staggered.staggered_race(
|
||||||
f'error while attempting to bind on '
|
(functools.partial(self._connect_sock,
|
||||||
f'address {laddr!r}: '
|
exceptions, addrinfo, laddr_infos)
|
||||||
f'{exc.strerror.lower()}'
|
for addrinfo in infos),
|
||||||
)
|
happy_eyeballs_delay, loop=self)
|
||||||
exc = OSError(exc.errno, msg)
|
|
||||||
exceptions.append(exc)
|
if sock is None:
|
||||||
else:
|
exceptions = [exc for sub in exceptions for exc in sub]
|
||||||
sock.close()
|
|
||||||
sock = None
|
|
||||||
continue
|
|
||||||
if self._debug:
|
|
||||||
logger.debug("connect %r to %r", sock, address)
|
|
||||||
await self.sock_connect(sock, address)
|
|
||||||
except OSError as exc:
|
|
||||||
if sock is not None:
|
|
||||||
sock.close()
|
|
||||||
exceptions.append(exc)
|
|
||||||
except:
|
|
||||||
if sock is not None:
|
|
||||||
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,7 +1355,8 @@ class BaseEventLoop(events.AbstractEventLoop):
|
||||||
if local_addr:
|
if local_addr:
|
||||||
sock.bind(local_address)
|
sock.bind(local_address)
|
||||||
if remote_addr:
|
if remote_addr:
|
||||||
await self.sock_connect(sock, remote_address)
|
if not allow_broadcast:
|
||||||
|
await self.sock_connect(sock, remote_address)
|
||||||
r_addr = remote_address
|
r_addr = remote_address
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
if sock is not None:
|
if sock is not None:
|
||||||
|
|
@ -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,30 +1864,9 @@ 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:
|
event_list = self._selector.select(timeout)
|
||||||
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)
|
|
||||||
self._process_events(event_list)
|
self._process_events(event_list)
|
||||||
|
|
||||||
# Handle 'later' callbacks that are ready.
|
# Handle 'later' callbacks that are ready.
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
# use reprlib to limit the length of the output, especially
|
key = id(future), get_ident()
|
||||||
# for very long strings
|
if key in _repr_running:
|
||||||
result = reprlib.repr(future._result)
|
result = '...'
|
||||||
|
else:
|
||||||
|
_repr_running.add(key)
|
||||||
|
try:
|
||||||
|
# use reprlib to limit the length of the output, especially
|
||||||
|
# for very long strings
|
||||||
|
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))
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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".
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
return self._when < other._when
|
if isinstance(other, TimerHandle):
|
||||||
|
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):
|
||||||
return self._when > other._when
|
if isinstance(other, TimerHandle):
|
||||||
|
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:
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,16 +408,17 @@ 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
|
||||||
return True
|
return True
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
loop.set_debug(debug)
|
if debug is not None:
|
||||||
|
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():
|
||||||
|
|
|
||||||
|
|
@ -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,14 +128,16 @@ 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:
|
||||||
try:
|
return
|
||||||
csock.send(b'\0')
|
|
||||||
except OSError:
|
try:
|
||||||
if self._debug:
|
csock.send(b'\0')
|
||||||
logger.debug("Fail to write a null byte into the "
|
except OSError:
|
||||||
"self-pipe socket",
|
if self._debug:
|
||||||
exc_info=True)
|
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,
|
||||||
|
|
@ -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 (SystemExit, KeyboardInterrupt):
|
||||||
except Exception as exc:
|
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):
|
||||||
self.remove_writer(fd)
|
if handle is None or not handle.cancelled():
|
||||||
|
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)
|
||||||
self._extra['sockname'] = sock.getsockname()
|
try:
|
||||||
|
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:
|
||||||
raise ValueError(
|
if addr not in (None, self._address):
|
||||||
f'Invalid address: must be None or {self._address}')
|
raise ValueError(
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,8 +193,8 @@ 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(
|
||||||
|
|
|
||||||
|
|
@ -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,10 +464,16 @@ 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:
|
||||||
fut.remove_done_callback(cb)
|
if fut.done():
|
||||||
fut.cancel()
|
return fut.result()
|
||||||
raise
|
else:
|
||||||
|
fut.remove_done_callback(cb)
|
||||||
|
# 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
|
||||||
|
|
||||||
if fut.done():
|
if fut.done():
|
||||||
return fut.result()
|
return fut.result()
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
self._proc = subprocess.Popen(
|
try:
|
||||||
args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
|
self._proc = subprocess.Popen(
|
||||||
universal_newlines=False, bufsize=bufsize, **kwargs)
|
args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
|
||||||
if stdin_w is not None:
|
universal_newlines=False, bufsize=bufsize, **kwargs)
|
||||||
stdin.close()
|
if stdin_w is not None:
|
||||||
self._proc.stdin = open(stdin_w.detach(), 'wb', buffering=bufsize)
|
stdin.close()
|
||||||
|
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()
|
||||||
|
|
|
||||||
|
|
@ -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,14 +860,25 @@ 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
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
@ -810,4 +910,4 @@ class WindowsProactorEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
|
||||||
_loop_factory = ProactorEventLoop
|
_loop_factory = ProactorEventLoop
|
||||||
|
|
||||||
|
|
||||||
DefaultEventLoopPolicy = WindowsSelectorEventLoopPolicy
|
DefaultEventLoopPolicy = WindowsProactorEventLoopPolicy
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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():
|
||||||
|
|
|
||||||
|
|
@ -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,14 +548,7 @@ 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:
|
s += '()'
|
||||||
args = frame.f_locals['__args__']
|
|
||||||
else:
|
|
||||||
args = None
|
|
||||||
if args:
|
|
||||||
s += reprlib.repr(args)
|
|
||||||
else:
|
|
||||||
s += '()'
|
|
||||||
if '__return__' in frame.f_locals:
|
if '__return__' in frame.f_locals:
|
||||||
rv = frame.f_locals['__return__']
|
rv = frame.f_locals['__return__']
|
||||||
s += '->'
|
s += '->'
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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,23 +109,25 @@ class _Hqxcoderengine:
|
||||||
self.data = self.data[todo:]
|
self.data = self.data[todo:]
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
self.hqxdata = self.hqxdata + binascii.b2a_hqx(data)
|
with _ignore_deprecation_warning():
|
||||||
|
self.hqxdata = self.hqxdata + binascii.b2a_hqx(data)
|
||||||
self._flush(0)
|
self._flush(0)
|
||||||
|
|
||||||
def _flush(self, force):
|
def _flush(self, force):
|
||||||
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:
|
||||||
self.hqxdata = self.hqxdata + binascii.b2a_hqx(self.data)
|
with _ignore_deprecation_warning():
|
||||||
|
self.hqxdata = self.hqxdata + binascii.b2a_hqx(self.data)
|
||||||
self._flush(1)
|
self._flush(1)
|
||||||
self.ofp.close()
|
self.ofp.close()
|
||||||
del self.ofp
|
del self.ofp
|
||||||
|
|
@ -125,13 +143,15 @@ 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
|
||||||
rledata = binascii.rlecode_hqx(self.data)
|
with _ignore_deprecation_warning():
|
||||||
|
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:
|
||||||
rledata = binascii.rlecode_hqx(self.data)
|
with _ignore_deprecation_warning():
|
||||||
|
rledata = binascii.rlecode_hqx(self.data)
|
||||||
self.ofp.write(rledata)
|
self.ofp.write(rledata)
|
||||||
self.ofp.close()
|
self.ofp.close()
|
||||||
del self.ofp
|
del self.ofp
|
||||||
|
|
@ -276,7 +296,8 @@ class _Hqxdecoderengine:
|
||||||
#
|
#
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
decdatacur, self.eof = binascii.a2b_hqx(data)
|
with _ignore_deprecation_warning():
|
||||||
|
decdatacur, self.eof = binascii.a2b_hqx(data)
|
||||||
break
|
break
|
||||||
except binascii.Incomplete:
|
except binascii.Incomplete:
|
||||||
pass
|
pass
|
||||||
|
|
@ -312,8 +333,9 @@ 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:
|
||||||
self.post_buffer = self.post_buffer + \
|
with _ignore_deprecation_warning():
|
||||||
binascii.rledecode_hqx(self.pre_buffer)
|
self.post_buffer = self.post_buffer + \
|
||||||
|
binascii.rledecode_hqx(self.pre_buffer)
|
||||||
self.pre_buffer = b''
|
self.pre_buffer = b''
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -340,8 +362,9 @@ class _Rledecoderengine:
|
||||||
else:
|
else:
|
||||||
mark = mark - 1
|
mark = mark - 1
|
||||||
|
|
||||||
self.post_buffer = self.post_buffer + \
|
with _ignore_deprecation_warning():
|
||||||
binascii.rledecode_hqx(self.pre_buffer[:mark])
|
self.post_buffer = self.post_buffer + \
|
||||||
|
binascii.rledecode_hqx(self.pre_buffer[:mark])
|
||||||
self.pre_buffer = self.pre_buffer[mark:]
|
self.pre_buffer = self.pre_buffer[mark:]
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
runctx(code, globs, None, options.outfile, options.sort)
|
try:
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
headers['Content-Length'] = pdict['CONTENT-LENGTH']
|
try:
|
||||||
|
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("&", "&") # Must be done first!
|
|
||||||
s = s.replace("<", "<")
|
|
||||||
s = s.replace(">", ">")
|
|
||||||
if quote:
|
|
||||||
s = s.replace('"', """)
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def valid_boundary(s):
|
def valid_boundary(s):
|
||||||
import re
|
import re
|
||||||
if isinstance(s, bytes):
|
if isinstance(s, bytes):
|
||||||
|
|
|
||||||
|
|
@ -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 = {}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
info = lookup(encoding)
|
|
||||||
srw = StreamReaderWriter(file, info.streamreader, info.streamwriter, errors)
|
try:
|
||||||
# Add attributes to simplify introspection
|
info = lookup(encoding)
|
||||||
srw.encoding = encoding
|
srw = StreamReaderWriter(file, info.streamreader, info.streamwriter, errors)
|
||||||
return srw
|
# Add attributes to simplify introspection
|
||||||
|
srw.encoding = encoding
|
||||||
|
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'):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,23 +81,31 @@ 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
|
||||||
|
|
||||||
try:
|
# Catch syntax warnings after the first compile
|
||||||
code1 = compiler(source + "\n", filename, symbol)
|
# to emit warnings (SyntaxWarning, DeprecationWarning) at most once.
|
||||||
except SyntaxError as e:
|
with warnings.catch_warnings():
|
||||||
err1 = e
|
warnings.simplefilter("error")
|
||||||
|
|
||||||
|
try:
|
||||||
|
code1 = compiler(source + "\n", filename, symbol)
|
||||||
|
except SyntaxError as e:
|
||||||
|
err1 = e
|
||||||
|
|
||||||
|
try:
|
||||||
|
code2 = compiler(source + "\n\n", filename, symbol)
|
||||||
|
except SyntaxError as e:
|
||||||
|
err2 = e
|
||||||
|
|
||||||
try:
|
try:
|
||||||
code2 = compiler(source + "\n\n", filename, symbol)
|
if code:
|
||||||
except SyntaxError as e:
|
return code
|
||||||
err2 = e
|
if not code1 and repr(err1) == repr(err2):
|
||||||
|
raise err1
|
||||||
if code:
|
finally:
|
||||||
return code
|
err1 = err2 = None
|
||||||
if not code1 and repr(err1) == repr(err2):
|
|
||||||
raise err1
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
else:
|
|
||||||
dfile = None
|
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:
|
||||||
|
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):
|
||||||
if legacy:
|
for opt_level in optimize:
|
||||||
cfile = fullname + 'c'
|
if legacy:
|
||||||
else:
|
opt_cfiles[opt_level] = fullname + 'c'
|
||||||
if optimize >= 0:
|
|
||||||
opt = optimize if optimize >= 1 else ''
|
|
||||||
cfile = importlib.util.cache_from_source(
|
|
||||||
fullname, optimization=opt)
|
|
||||||
else:
|
else:
|
||||||
cfile = importlib.util.cache_from_source(fullname)
|
if opt_level >= 0:
|
||||||
cache_dir = os.path.dirname(cfile)
|
opt = opt_level if opt_level >= 1 else ''
|
||||||
|
cfile = (importlib.util.cache_from_source(
|
||||||
|
fullname, optimization=opt))
|
||||||
|
opt_cfiles[opt_level] = cfile
|
||||||
|
else:
|
||||||
|
cfile = importlib.util.cache_from_source(fullname)
|
||||||
|
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)
|
||||||
with open(cfile, 'rb') as chandle:
|
for cfile in opt_cfiles.values():
|
||||||
actual = chandle.read(12)
|
with open(cfile, 'rb') as chandle:
|
||||||
if expect == actual:
|
actual = chandle.read(12)
|
||||||
|
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:
|
||||||
ok = py_compile.compile(fullname, cfile, dfile, True,
|
for index, opt_level in enumerate(optimize):
|
||||||
optimize=optimize,
|
cfile = opt_cfiles[opt_level]
|
||||||
invalidation_mode=invalidation_mode)
|
ok = py_compile.compile(fullname, cfile, dfile, True,
|
||||||
|
optimize=opt_level,
|
||||||
|
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()
|
||||||
|
invalidation_mode = py_compile.PycInvalidationMode[ivl_mode]
|
||||||
ivl_mode = args.invalidation_mode.replace('-', '_').upper()
|
else:
|
||||||
invalidation_mode = py_compile.PycInvalidationMode[ivl_mode]
|
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:
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
raise self._exception
|
try:
|
||||||
|
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
|
||||||
fn(self)
|
try:
|
||||||
|
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,20 +431,24 @@ 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.
|
||||||
"""
|
"""
|
||||||
with self._condition:
|
try:
|
||||||
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
|
with self._condition:
|
||||||
raise CancelledError()
|
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
|
||||||
elif self._state == FINISHED:
|
raise CancelledError()
|
||||||
return self.__get_result()
|
elif self._state == FINISHED:
|
||||||
|
return self.__get_result()
|
||||||
|
|
||||||
self._condition.wait(timeout)
|
self._condition.wait(timeout)
|
||||||
|
|
||||||
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
|
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
|
||||||
raise CancelledError()
|
raise CancelledError()
|
||||||
elif self._state == FINISHED:
|
elif self._state == FINISHED:
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,18 +65,23 @@ _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):
|
||||||
self._writer.close()
|
if not self._closed:
|
||||||
self._reader.close()
|
self._closed = True
|
||||||
|
self._writer.close()
|
||||||
|
self._reader.close()
|
||||||
|
|
||||||
def wakeup(self):
|
def wakeup(self):
|
||||||
self._writer.send_bytes(b"")
|
if not self._closed:
|
||||||
|
self._writer.send_bytes(b"")
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
while self._reader.poll():
|
if not self._closed:
|
||||||
self._reader.recv_bytes()
|
while self._reader.poll():
|
||||||
|
self._reader.recv_bytes()
|
||||||
|
|
||||||
|
|
||||||
def _python_exit():
|
def _python_exit():
|
||||||
|
|
@ -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,
|
|
||||||
call_queue):
|
|
||||||
"""Fills call_queue with _WorkItems from pending_work_items.
|
|
||||||
|
|
||||||
This function never blocks.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pending_work_items: A dict mapping work ids to _WorkItems e.g.
|
|
||||||
{5: <_WorkItem...>, 6: <_WorkItem...>, ...}
|
|
||||||
work_ids: A queue.Queue of work ids e.g. Queue([5, 6, ...]). Work ids
|
|
||||||
are consumed and the corresponding _WorkItems from
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
if call_queue.full():
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
work_id = work_ids.get(block=False)
|
|
||||||
except queue.Empty:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
work_item = pending_work_items[work_id]
|
|
||||||
|
|
||||||
if work_item.future.set_running_or_notify_cancel():
|
|
||||||
call_queue.put(_CallItem(work_id,
|
|
||||||
work_item.fn,
|
|
||||||
work_item.args,
|
|
||||||
work_item.kwargs),
|
|
||||||
block=True)
|
|
||||||
else:
|
|
||||||
del pending_work_items[work_id]
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
"""Manages the communication between this process and the worker processes.
|
||||||
|
|
||||||
This function is run in a local thread.
|
The manager is run in a local thread.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
executor_reference: A weakref.ref to the ProcessPoolExecutor that owns
|
executor: A reference to the ProcessPoolExecutor that owns
|
||||||
this thread. Used to determine if the ProcessPoolExecutor has been
|
this thread. A weakref will be own by the manager as well as
|
||||||
garbage collected and that this function can exit.
|
references to internal objects used to introspect the state of
|
||||||
process: A list of the ctx.Process instances used as
|
the executor.
|
||||||
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():
|
def __init__(self, executor):
|
||||||
return (_global_shutdown or executor is None
|
# Store references to necessary internals of the executor.
|
||||||
or executor._shutdown_thread)
|
|
||||||
|
|
||||||
def shutdown_worker():
|
# A _ThreadWakeup to allow waking up the queue_manager_thread from the
|
||||||
# This is an upper bound on the number of children alive.
|
# main Thread and avoid deadlocks caused by permanently locked queues.
|
||||||
n_children_alive = sum(p.is_alive() for p in processes.values())
|
self.thread_wakeup = executor._executor_manager_thread_wakeup
|
||||||
n_children_to_stop = n_children_alive
|
self.shutdown_lock = executor._shutdown_lock
|
||||||
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.
|
# A weakref.ref to the ProcessPoolExecutor that owns this thread. Used
|
||||||
call_queue.close()
|
# to determine if the ProcessPoolExecutor has been garbage collected
|
||||||
# If .join() is not called on the created processes then
|
# and that the manager can exit.
|
||||||
# some ctx.Queue methods may deadlock on Mac OS X.
|
# When the executor gets garbage collected, the weakref callback
|
||||||
for p in processes.values():
|
# will wake up the queue management thread so that it can terminate
|
||||||
p.join()
|
# 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()
|
||||||
|
|
||||||
result_reader = result_queue._reader
|
self.executor_reference = weakref.ref(executor, weakref_cb)
|
||||||
wakeup_reader = thread_wakeup._reader
|
|
||||||
readers = [result_reader, wakeup_reader]
|
|
||||||
|
|
||||||
while True:
|
# A list of the ctx.Process instances used as workers.
|
||||||
_add_call_item_to_queue(pending_work_items,
|
self.processes = executor._processes
|
||||||
work_ids_queue,
|
|
||||||
call_queue)
|
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
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
|
||||||
|
try:
|
||||||
|
work_id = self.work_ids_queue.get(block=False)
|
||||||
|
except queue.Empty:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
work_item = self.pending_work_items[work_id]
|
||||||
|
|
||||||
|
if work_item.future.set_running_or_notify_cancel():
|
||||||
|
self.call_queue.put(_CallItem(work_id,
|
||||||
|
work_item.fn,
|
||||||
|
work_item.args,
|
||||||
|
work_item.kwargs),
|
||||||
|
block=True)
|
||||||
|
else:
|
||||||
|
del self.pending_work_items[work_id]
|
||||||
|
continue
|
||||||
|
|
||||||
|
def wait_result_broken_or_wakeup(self):
|
||||||
# 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):
|
||||||
if executor is not None:
|
# Terminate the executor because it is in a broken state. The cause
|
||||||
executor._shutdown_thread = True
|
# argument can be used to display more information on the error that
|
||||||
# Since no new work items can be added, it is safe to shutdown
|
# lead the executor into becoming broken.
|
||||||
# this thread if there are no pending work items.
|
|
||||||
if not pending_work_items:
|
# Mark the process pool broken so that submits fail right now.
|
||||||
shutdown_worker()
|
executor = self.executor_reference()
|
||||||
return
|
if executor is not None:
|
||||||
except Full:
|
executor._broken = ('A child process terminated '
|
||||||
# This is not a problem: we will eventually be woken up (in
|
'abruptly, the process pool is not '
|
||||||
# result_queue.get()) and be able to send a sentinel again.
|
'usable anymore')
|
||||||
pass
|
executor._shutdown_thread = True
|
||||||
executor = None
|
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:
|
||||||
|
executor._shutdown_thread = True
|
||||||
|
# Cancel pending work items if requested.
|
||||||
|
if executor._cancel_pending_futures:
|
||||||
|
# Cancel all pending futures and update pending_work_items
|
||||||
|
# to only have futures that are currently running.
|
||||||
|
new_pending_work_items = {}
|
||||||
|
for work_id, work_item in self.pending_work_items.items():
|
||||||
|
if not work_item.future.cancel():
|
||||||
|
new_pending_work_items[work_id] = work_item
|
||||||
|
self.pending_work_items = new_pending_work_items
|
||||||
|
# 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,53 +658,50 @@ 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):
|
||||||
for _ in range(len(self._processes), self._max_workers):
|
# if there's an idle process, we don't need to spawn a new one.
|
||||||
p = self._mp_context.Process(
|
if self._idle_worker_semaphore.acquire(blocking=False):
|
||||||
target=_process_worker,
|
return
|
||||||
args=(self._call_queue,
|
|
||||||
self._result_queue,
|
|
||||||
self._initializer,
|
|
||||||
self._initargs))
|
|
||||||
p.start()
|
|
||||||
self._processes[p.pid] = p
|
|
||||||
|
|
||||||
def submit(self, fn, *args, **kwargs):
|
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):
|
||||||
|
self._spawn_process()
|
||||||
|
|
||||||
|
def _spawn_process(self):
|
||||||
|
p = self._mp_context.Process(
|
||||||
|
target=_process_worker,
|
||||||
|
args=(self._call_queue,
|
||||||
|
self._result_queue,
|
||||||
|
self._initializer,
|
||||||
|
self._initargs))
|
||||||
|
p.start()
|
||||||
|
self._processes[p.pid] = p
|
||||||
|
|
||||||
|
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 = None
|
||||||
self._call_queue.close()
|
if self._result_queue is not None and wait:
|
||||||
if wait:
|
self._result_queue.close()
|
||||||
self._call_queue.join_thread()
|
|
||||||
self._call_queue = None
|
|
||||||
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)
|
|
||||||
|
|
|
||||||
|
|
@ -5,41 +5,42 @@
|
||||||
|
|
||||||
__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
|
||||||
_shutdown = True
|
with _global_shutdown_lock:
|
||||||
|
_shutdown = True
|
||||||
items = list(_threads_queues.items())
|
items = list(_threads_queues.items())
|
||||||
for t, q in items:
|
for t, q in items:
|
||||||
q.put(None)
|
q.put(None)
|
||||||
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,15 +158,15 @@ 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)
|
||||||
|
|
||||||
if self._shutdown:
|
if self._shutdown:
|
||||||
raise RuntimeError('cannot schedule new futures after shutdown')
|
raise RuntimeError('cannot schedule new futures after shutdown')
|
||||||
if _shutdown:
|
if _shutdown:
|
||||||
raise RuntimeError('cannot schedule new futures after'
|
raise RuntimeError('cannot schedule new futures after '
|
||||||
'interpreter shutdown')
|
'interpreter shutdown')
|
||||||
|
|
||||||
f = _base.Future()
|
f = _base.Future()
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ ConfigParser -- responsible for parsing a list of
|
||||||
|
|
||||||
When `interpolation` is given, it should be an Interpolation subclass
|
When `interpolation` is given, it should be an Interpolation subclass
|
||||||
instance. It will be used as the handler for option value
|
instance. It will be used as the handler for option value
|
||||||
pre-processing when using getters. RawConfigParser object s don't do
|
pre-processing when using getters. RawConfigParser objects don't do
|
||||||
any sort of interpolation, whereas ConfigParser uses an instance of
|
any sort of interpolation, whereas ConfigParser uses an instance of
|
||||||
BasicInterpolation. The library also provides a ``zc.buildbot``
|
BasicInterpolation. The library also provides a ``zc.buildbot``
|
||||||
inspired ExtendedInterpolation implementation.
|
inspired ExtendedInterpolation implementation.
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
raise
|
||||||
# async implementation) to maintain compatibility with
|
return False
|
||||||
# Python 2, where old-style class exceptions are not caught
|
|
||||||
# by 'except BaseException'.
|
|
||||||
if sys.exc_info()[1] is value:
|
|
||||||
return False
|
|
||||||
raise
|
|
||||||
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))
|
||||||
return False
|
and exc.__cause__ is value
|
||||||
|
):
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -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 = []
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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 _crypt
|
import sys as _sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
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 = crypt('', salt)
|
result = None
|
||||||
|
try:
|
||||||
|
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
Loading…
Reference in New Issue