From 248a034dc93d6ce4d983af6ce83c0b8b112e4f5c Mon Sep 17 00:00:00 2001
From: Jason Saporta
Date: Fri, 17 May 2019 17:19:35 -0700
Subject: [PATCH 001/441] Issue 29847 fix with tests.
---
Lib/pathlib.py | 10 ++++++++++
Lib/test/test_pathlib.py | 7 +++++++
2 files changed, 17 insertions(+)
diff --git a/Lib/pathlib.py b/Lib/pathlib.py
index b5bab1fe8f5acb..2d61bd733a25b3 100644
--- a/Lib/pathlib.py
+++ b/Lib/pathlib.py
@@ -1512,6 +1512,11 @@ class PosixPath(Path, PurePosixPath):
"""
__slots__ = ()
+ def __new__(cls, *args, **kwargs):
+ if kwargs:
+ raise TypeError("keyword arguments provided but ignored")
+ return super().__new__(cls, *args, **kwargs)
+
class WindowsPath(Path, PureWindowsPath):
"""Path subclass for Windows systems.
@@ -1519,6 +1524,11 @@ class WindowsPath(Path, PureWindowsPath):
"""
__slots__ = ()
+ def __new__(cls, *args, **kwargs):
+ if kwargs:
+ raise TypeError("keyword arguments provided but ignored")
+ return super().__new__(cls, *args, **kwargs)
+
def owner(self):
raise NotImplementedError("Path.owner() is unsupported on this system")
diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py
index caad1c23876abd..262d0828ed6f8b 100644
--- a/Lib/test/test_pathlib.py
+++ b/Lib/test/test_pathlib.py
@@ -2254,6 +2254,10 @@ def test_handling_bad_descriptor(self):
self.fail("Bad file descriptor not handled.")
raise
+ def test_kwargs(self):
+ with self.assertRaises(TypeError):
+ self.cls(".", test_kwarg="test")
+
@only_nt
class WindowsPathTest(_BasePathTest, unittest.TestCase):
@@ -2324,6 +2328,9 @@ def check():
env['USERPROFILE'] = 'C:\\Users\\alice'
check()
+ def test_kwargs(self):
+ with self.assertRaises(TypeError):
+ self.cls(".", test_kwarg="test")
if __name__ == "__main__":
unittest.main()
From bab0db6076900cd828588be8595b3cdfade7e7e9 Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Sat, 18 May 2019 03:21:27 +0200
Subject: [PATCH 002/441] bpo-36763: Use _PyCoreConfig_InitPythonConfig()
(GH-13398)
_PyPreConfig_InitPythonConfig() and _PyCoreConfig_InitPythonConfig()
no longer inherit their values from global configuration variables.
Changes:
* _PyPreCmdline_Read() now ignores -X dev and PYTHONDEVMODE
if dev_mode is already set.
* Inline _PyPreConfig_INIT macro into _PyPreConfig_Init() function.
* Inline _PyCoreConfig_INIT macro into _PyCoreConfig_Init() function.
* Replace _PyCoreConfig_Init() with _PyCoreConfig_InitPythonConfig()
in most tests of _testembed.c.
* Replace _PyCoreConfig_Init() with _PyCoreConfig_InitIsolatedConfig()
in _freeze_importlib.c.
* Move some initialization functions from the internal
to the private API.
---
Include/cpython/coreconfig.h | 77 ++++---------------
Include/internal/pycore_coreconfig.h | 20 +----
Lib/test/test_embed.py | 105 +++++++++++++++----------
Programs/_freeze_importlib.c | 6 +-
Programs/_testembed.c | 111 +++++++++++++++++++--------
Python/coreconfig.c | 77 ++++++++++++++-----
Python/frozenmain.c | 2 +-
Python/preconfig.c | 23 ++++--
8 files changed, 240 insertions(+), 181 deletions(-)
diff --git a/Include/cpython/coreconfig.h b/Include/cpython/coreconfig.h
index f05eddad912ffe..ca71c15b67d6f9 100644
--- a/Include/cpython/coreconfig.h
+++ b/Include/cpython/coreconfig.h
@@ -115,27 +115,8 @@ typedef struct {
PyMemAllocatorName allocator;
} _PyPreConfig;
-#ifdef MS_WINDOWS
-# define _PyPreConfig_WINDOWS_INIT \
- .legacy_windows_fs_encoding = -1,
-#else
-# define _PyPreConfig_WINDOWS_INIT
-#endif
-
-#define _PyPreConfig_INIT \
- (_PyPreConfig){ \
- _PyPreConfig_WINDOWS_INIT \
- ._config_version = _Py_CONFIG_VERSION, \
- .isolated = -1, \
- .use_environment = -1, \
- .configure_locale = 1, \
- .utf8_mode = -2, \
- .dev_mode = -1, \
- .allocator = PYMEM_ALLOCATOR_NOT_SET}
-
-PyAPI_FUNC(void) _PyPreConfig_Init(_PyPreConfig *config);
PyAPI_FUNC(void) _PyPreConfig_InitPythonConfig(_PyPreConfig *config);
-PyAPI_FUNC(void) _PyPreConfig_InitIsolateConfig(_PyPreConfig *config);
+PyAPI_FUNC(void) _PyPreConfig_InitIsolatedConfig(_PyPreConfig *config);
/* --- _PyCoreConfig ---------------------------------------------- */
@@ -419,47 +400,23 @@ typedef struct {
} _PyCoreConfig;
-#ifdef MS_WINDOWS
-# define _PyCoreConfig_WINDOWS_INIT \
- .legacy_windows_stdio = -1,
-#else
-# define _PyCoreConfig_WINDOWS_INIT
-#endif
-
-#define _PyCoreConfig_INIT \
- (_PyCoreConfig){ \
- _PyCoreConfig_WINDOWS_INIT \
- ._config_version = _Py_CONFIG_VERSION, \
- .isolated = -1, \
- .use_environment = -1, \
- .dev_mode = -1, \
- .install_signal_handlers = 1, \
- .use_hash_seed = -1, \
- .faulthandler = -1, \
- .tracemalloc = -1, \
- .use_module_search_paths = 0, \
- .parse_argv = 0, \
- .site_import = -1, \
- .bytes_warning = -1, \
- .inspect = -1, \
- .interactive = -1, \
- .optimization_level = -1, \
- .parser_debug= -1, \
- .write_bytecode = -1, \
- .verbose = -1, \
- .quiet = -1, \
- .user_site_directory = -1, \
- .configure_c_stdio = 0, \
- .buffered_stdio = -1, \
- ._install_importlib = 1, \
- .check_hash_pycs_mode = NULL, \
- .pathconfig_warnings = -1, \
- ._init_main = 1}
-/* Note: _PyCoreConfig_INIT sets other fields to 0/NULL */
-
-PyAPI_FUNC(void) _PyCoreConfig_Init(_PyCoreConfig *config);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_InitPythonConfig(_PyCoreConfig *config);
-PyAPI_FUNC(_PyInitError) _PyCoreConfig_InitIsolateConfig(_PyCoreConfig *config);
+PyAPI_FUNC(_PyInitError) _PyCoreConfig_InitIsolatedConfig(_PyCoreConfig *config);
+PyAPI_FUNC(void) _PyCoreConfig_Clear(_PyCoreConfig *);
+PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetString(
+ wchar_t **config_str,
+ const wchar_t *str);
+PyAPI_FUNC(_PyInitError) _PyCoreConfig_DecodeLocale(
+ wchar_t **config_str,
+ const char *str);
+PyAPI_FUNC(_PyInitError) _PyCoreConfig_Read(_PyCoreConfig *config);
+PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetArgv(
+ _PyCoreConfig *config,
+ Py_ssize_t argc,
+ char **argv);
+PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetWideArgv(_PyCoreConfig *config,
+ Py_ssize_t argc,
+ wchar_t **argv);
#ifdef __cplusplus
}
diff --git a/Include/internal/pycore_coreconfig.h b/Include/internal/pycore_coreconfig.h
index ea4418f5061ce2..edde7b1d8d8817 100644
--- a/Include/internal/pycore_coreconfig.h
+++ b/Include/internal/pycore_coreconfig.h
@@ -121,8 +121,6 @@ PyAPI_FUNC(_PyInitError) _PyPreCmdline_Read(_PyPreCmdline *cmdline,
/* --- _PyPreConfig ----------------------------------------------- */
PyAPI_FUNC(void) _PyPreConfig_Init(_PyPreConfig *config);
-PyAPI_FUNC(void) _PyPreConfig_InitPythonConfig(_PyPreConfig *config);
-PyAPI_FUNC(void) _PyPreConfig_InitIsolatedConfig(_PyPreConfig *config);
PyAPI_FUNC(void) _PyPreConfig_Copy(_PyPreConfig *config,
const _PyPreConfig *config2);
PyAPI_FUNC(PyObject*) _PyPreConfig_AsDict(const _PyPreConfig *config);
@@ -135,34 +133,18 @@ PyAPI_FUNC(_PyInitError) _PyPreConfig_Write(const _PyPreConfig *config);
/* --- _PyCoreConfig ---------------------------------------------- */
-PyAPI_FUNC(void) _PyCoreConfig_Clear(_PyCoreConfig *);
-PyAPI_FUNC(_PyInitError) _PyCoreConfig_InitPythonConfig(_PyCoreConfig *config);
-PyAPI_FUNC(_PyInitError) _PyCoreConfig_InitIsolatedConfig(_PyCoreConfig *config);
+PyAPI_FUNC(void) _PyCoreConfig_Init(_PyCoreConfig *config);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_Copy(
_PyCoreConfig *config,
const _PyCoreConfig *config2);
-PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetString(
- wchar_t **config_str,
- const wchar_t *str);
-PyAPI_FUNC(_PyInitError) _PyCoreConfig_DecodeLocale(
- wchar_t **config_str,
- const char *str);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_InitPathConfig(_PyCoreConfig *config);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetPathConfig(
const _PyCoreConfig *config);
-PyAPI_FUNC(_PyInitError) _PyCoreConfig_Read(_PyCoreConfig *config);
PyAPI_FUNC(void) _PyCoreConfig_Write(const _PyCoreConfig *config,
_PyRuntimeState *runtime);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetPyArgv(
_PyCoreConfig *config,
const _PyArgv *args);
-PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetArgv(
- _PyCoreConfig *config,
- Py_ssize_t argc,
- char **argv);
-PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetWideArgv(_PyCoreConfig *config,
- Py_ssize_t argc,
- wchar_t **argv);
/* --- Function used for testing ---------------------------------- */
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index c389df85fb6c09..6b77a2d1fd8dd9 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -285,6 +285,16 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'coerce_c_locale_warn': 0,
'utf8_mode': 0,
}
+ ISOLATED_PRE_CONFIG = dict(DEFAULT_PRE_CONFIG,
+ configure_locale=0,
+ isolated=1,
+ use_environment=0,
+ utf8_mode=0,
+ dev_mode=0,
+ )
+ if MS_WINDOWS:
+ ISOLATED_PRE_CONFIG['legacy_windows_fs_encoding'] = 0
+
COPY_PRE_CONFIG = [
'dev_mode',
'isolated',
@@ -363,6 +373,24 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'legacy_windows_stdio': 0,
})
+ PYTHON_CORE_CONFIG = dict(DEFAULT_CORE_CONFIG,
+ configure_c_stdio=1,
+ parse_argv=1,
+ )
+ ISOLATED_CORE_CONFIG = dict(DEFAULT_CORE_CONFIG,
+ isolated=1,
+ use_environment=0,
+ user_site_directory=0,
+ dev_mode=0,
+ install_signal_handlers=0,
+ use_hash_seed=0,
+ faulthandler=0,
+ tracemalloc=0,
+ pathconfig_warnings=0,
+ )
+ if MS_WINDOWS:
+ ISOLATED_CORE_CONFIG['legacy_windows_stdio'] = 0
+
# global config
DEFAULT_GLOBAL_CONFIG = {
'Py_HasFileSystemDefaultEncoding': 0,
@@ -410,8 +438,15 @@ def main_xoptions(self, xoptions_list):
xoptions[opt] = True
return xoptions
- def get_expected_config(self, expected_preconfig, expected, env, add_path=None):
- expected = dict(self.DEFAULT_CORE_CONFIG, **expected)
+ def get_expected_config(self, expected_preconfig, expected, env, api,
+ add_path=None):
+ if api == "python":
+ default_config = self.PYTHON_CORE_CONFIG
+ elif api == "isolated":
+ default_config = self.ISOLATED_CORE_CONFIG
+ else:
+ default_config = self.DEFAULT_CORE_CONFIG
+ expected = dict(default_config, **expected)
code = textwrap.dedent('''
import json
@@ -521,8 +556,8 @@ def check_global_config(self, config):
self.assertEqual(config['global_config'], expected)
- def check_config(self, testname, expected_config, expected_preconfig,
- add_path=None, stderr=None):
+ def check_config(self, testname, expected_config=None, expected_preconfig=None,
+ add_path=None, stderr=None, api="default"):
env = dict(os.environ)
# Remove PYTHON* environment variables to get deterministic environment
for key in list(env):
@@ -533,8 +568,18 @@ def check_config(self, testname, expected_config, expected_preconfig,
env['PYTHONCOERCECLOCALE'] = '0'
env['PYTHONUTF8'] = '0'
- expected_preconfig = dict(self.DEFAULT_PRE_CONFIG, **expected_preconfig)
- expected_config = self.get_expected_config(expected_preconfig, expected_config, env, add_path)
+ if api == "isolated":
+ default_preconfig = self.ISOLATED_PRE_CONFIG
+ else:
+ default_preconfig = self.DEFAULT_PRE_CONFIG
+ if expected_preconfig is None:
+ expected_preconfig = {}
+ expected_preconfig = dict(default_preconfig, **expected_preconfig)
+ if expected_config is None:
+ expected_config = {}
+ expected_config = self.get_expected_config(expected_preconfig,
+ expected_config, env,
+ api, add_path)
for key in self.COPY_PRE_CONFIG:
if key not in expected_preconfig:
expected_preconfig[key] = expected_config[key]
@@ -677,76 +722,56 @@ def test_init_dev_mode(self):
'dev_mode': 1,
'warnoptions': ['default'],
}
- self.check_config("init_dev_mode", config, preconfig)
+ self.check_config("init_dev_mode", config, preconfig, api="python")
def test_init_isolated_flag(self):
- preconfig = {}
config = {
'isolated': 1,
'use_environment': 0,
'user_site_directory': 0,
}
- self.check_config("init_isolated_flag", config, preconfig)
+ self.check_config("init_isolated_flag", config, api="python")
def test_preinit_isolated1(self):
# _PyPreConfig.isolated=1, _PyCoreConfig.isolated not set
- preconfig = {}
config = {
'isolated': 1,
'use_environment': 0,
'user_site_directory': 0,
}
- self.check_config("preinit_isolated1", config, preconfig)
+ self.check_config("preinit_isolated1", config)
def test_preinit_isolated2(self):
# _PyPreConfig.isolated=0, _PyCoreConfig.isolated=1
- preconfig = {}
config = {
'isolated': 1,
'use_environment': 0,
'user_site_directory': 0,
}
- self.check_config("preinit_isolated2", config, preconfig)
+ self.check_config("preinit_isolated2", config)
def test_init_isolated_config(self):
- preconfig = {
- 'configure_locale': 0,
- }
- config = {
- 'isolated': 1,
- 'use_environment': 0,
- 'user_site_directory': 0,
- 'install_signal_handlers': 0,
- 'pathconfig_warnings': 0,
- }
- self.check_config("init_isolated_config", config, preconfig)
+ self.check_config("init_isolated_config", api="isolated")
def test_init_python_config(self):
- preconfig = {}
- config = {
- 'configure_c_stdio': 1,
- 'parse_argv': 1,
- }
- self.check_config("init_python_config", config, preconfig)
+ self.check_config("init_python_config", api="python")
def test_init_dont_configure_locale(self):
# _PyPreConfig.configure_locale=0
preconfig = {
'configure_locale': 0,
}
- self.check_config("init_dont_configure_locale", {}, preconfig)
+ self.check_config("init_dont_configure_locale", {}, preconfig, api="python")
def test_init_read_set(self):
- preconfig = {}
core_config = {
'program_name': './init_read_set',
'executable': 'my_executable',
}
- self.check_config("init_read_set", core_config, preconfig,
+ self.check_config("init_read_set", core_config, api="python",
add_path="init_read_set_path")
def test_init_run_main(self):
- preconfig = {}
code = ('import _testinternalcapi, json; '
'print(json.dumps(_testinternalcapi.get_configs()))')
core_config = {
@@ -755,10 +780,9 @@ def test_init_run_main(self):
'run_command': code + '\n',
'parse_argv': 1,
}
- self.check_config("init_run_main", core_config, preconfig)
+ self.check_config("init_run_main", core_config, api="python")
def test_init_main(self):
- preconfig = {}
code = ('import _testinternalcapi, json; '
'print(json.dumps(_testinternalcapi.get_configs()))')
core_config = {
@@ -768,25 +792,26 @@ def test_init_main(self):
'parse_argv': 1,
'_init_main': 0,
}
- self.check_config("init_main", core_config, preconfig,
+ self.check_config("init_main", core_config, api="python",
stderr="Run Python code before _Py_InitializeMain")
def test_init_parse_argv(self):
core_config = {
+ 'parse_argv': 1,
'argv': ['-c', 'arg1', '-v', 'arg3'],
'program_name': './argv0',
- 'parse_argv': 1,
'run_command': 'pass\n',
'use_environment': 0,
}
- self.check_config("init_parse_argv", core_config, {})
+ self.check_config("init_parse_argv", core_config, api="python")
def test_init_dont_parse_argv(self):
core_config = {
+ 'parse_argv': 0,
'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
'program_name': './argv0',
}
- self.check_config("init_dont_parse_argv", core_config, {})
+ self.check_config("init_dont_parse_argv", core_config, api="python")
if __name__ == "__main__":
diff --git a/Programs/_freeze_importlib.c b/Programs/_freeze_importlib.c
index d89d66abee7364..1a719e2f96735e 100644
--- a/Programs/_freeze_importlib.c
+++ b/Programs/_freeze_importlib.c
@@ -77,14 +77,12 @@ main(int argc, char *argv[])
text[text_size] = '\0';
_PyCoreConfig config;
- _PyCoreConfig_Init(&config);
- config.use_environment = 0;
- config.user_site_directory = 0;
+ _PyCoreConfig_InitIsolatedConfig(&config);
+
config.site_import = 0;
config.program_name = L"./_freeze_importlib";
/* Don't install importlib, since it could execute outdated bytecode. */
config._install_importlib = 0;
- config.pathconfig_warnings = 0;
config._init_main = 0;
_PyInitError err = _Py_InitializeFromConfig(&config);
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 2352179cc6fc2a..f1bb731dcba2b7 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -5,6 +5,7 @@
#include
#include "pycore_coreconfig.h" /* FIXME: PEP 587 makes these functions public */
+#include
#include "pythread.h"
#include
#include
@@ -404,7 +405,7 @@ static int test_init_from_config(void)
config.use_hash_seed = 1;
config.hash_seed = 123;
- /* dev_mode=1 is tested in init_dev_mode() */
+ /* dev_mode=1 is tested in test_init_dev_mode() */
putenv("PYTHONFAULTHANDLER=");
config.faulthandler = 1;
@@ -521,12 +522,12 @@ static int test_init_from_config(void)
}
-static int test_init_parse_argv(int parse_argv)
+static int check_init_parse_argv(int parse_argv)
{
_PyInitError err;
_PyCoreConfig config;
- _PyCoreConfig_Init(&config);
+ _PyCoreConfig_InitPythonConfig(&config);
static wchar_t* argv[] = {
L"./argv0",
@@ -552,15 +553,15 @@ static int test_init_parse_argv(int parse_argv)
}
-static int init_parse_argv(void)
+static int test_init_parse_argv(void)
{
- return test_init_parse_argv(1);
+ return check_init_parse_argv(1);
}
-static int init_dont_parse_argv(void)
+static int test_init_dont_parse_argv(void)
{
- return test_init_parse_argv(0);
+ return check_init_parse_argv(0);
}
@@ -603,7 +604,7 @@ static int test_init_env(void)
}
-static void test_init_env_dev_mode_putenvs(void)
+static void set_all_env_vars(void)
{
test_init_env_putenvs();
putenv("PYTHONMALLOC=");
@@ -616,7 +617,7 @@ static int test_init_env_dev_mode(void)
{
/* Test initialization from environment variables */
Py_IgnoreEnvironmentFlag = 0;
- test_init_env_dev_mode_putenvs();
+ set_all_env_vars();
_testembed_Py_Initialize();
dump_config();
Py_Finalize();
@@ -628,7 +629,7 @@ static int test_init_env_dev_mode_alloc(void)
{
/* Test initialization from environment variables */
Py_IgnoreEnvironmentFlag = 0;
- test_init_env_dev_mode_putenvs();
+ set_all_env_vars();
putenv("PYTHONMALLOC=malloc");
_testembed_Py_Initialize();
dump_config();
@@ -637,13 +638,13 @@ static int test_init_env_dev_mode_alloc(void)
}
-static int init_isolated_flag(void)
+static int test_init_isolated_flag(void)
{
_PyInitError err;
/* Test _PyCoreConfig.isolated=1 */
_PyCoreConfig config;
- _PyCoreConfig_Init(&config);
+ _PyCoreConfig_InitPythonConfig(&config);
Py_IsolatedFlag = 0;
config.isolated = 1;
@@ -651,7 +652,7 @@ static int init_isolated_flag(void)
/* Use path starting with "./" avoids a search along the PATH */
config.program_name = L"./_testembed";
- test_init_env_dev_mode_putenvs();
+ set_all_env_vars();
err = _Py_InitializeFromConfig(&config);
if (_PyInitError_Failed(err)) {
_Py_ExitInitError(err);
@@ -680,7 +681,7 @@ static int test_preinit_isolated1(void)
_PyCoreConfig_Init(&config);
config.program_name = L"./_testembed";
- test_init_env_dev_mode_putenvs();
+ set_all_env_vars();
err = _Py_InitializeFromConfig(&config);
if (_PyInitError_Failed(err)) {
_Py_ExitInitError(err);
@@ -715,7 +716,7 @@ static int test_preinit_isolated2(void)
/* Use path starting with "./" avoids a search along the PATH */
config.program_name = L"./_testembed";
- test_init_env_dev_mode_putenvs();
+ set_all_env_vars();
err = _Py_InitializeFromConfig(&config);
if (_PyInitError_Failed(err)) {
_Py_ExitInitError(err);
@@ -726,10 +727,38 @@ static int test_preinit_isolated2(void)
}
-static int init_isolated_config(void)
+static void set_all_global_config_variables(void)
+{
+ Py_IsolatedFlag = 0;
+ Py_IgnoreEnvironmentFlag = 0;
+ Py_BytesWarningFlag = 2;
+ Py_InspectFlag = 1;
+ Py_InteractiveFlag = 1;
+ Py_OptimizeFlag = 1;
+ Py_DebugFlag = 1;
+ Py_VerboseFlag = 1;
+ Py_QuietFlag = 1;
+ Py_FrozenFlag = 0;
+ Py_UnbufferedStdioFlag = 1;
+ Py_NoSiteFlag = 1;
+ Py_DontWriteBytecodeFlag = 1;
+ Py_NoUserSiteDirectory = 1;
+#ifdef MS_WINDOWS
+ Py_LegacyWindowsStdioFlag = 1;
+#endif
+}
+
+
+static int test_init_isolated_config(void)
{
_PyInitError err;
+ /* environment variables must be ignored */
+ set_all_env_vars();
+
+ /* global configuration variables must be ignored */
+ set_all_global_config_variables();
+
_PyPreConfig preconfig;
_PyPreConfig_InitIsolatedConfig(&preconfig);
@@ -759,10 +788,23 @@ static int init_isolated_config(void)
}
-static int init_python_config(void)
+static int test_init_python_config(void)
{
_PyInitError err;
+ /* global configuration variables must be ignored */
+ set_all_global_config_variables();
+ Py_IsolatedFlag = 1;
+ Py_IgnoreEnvironmentFlag = 1;
+ Py_FrozenFlag = 1;
+ Py_UnbufferedStdioFlag = 1;
+ Py_NoSiteFlag = 1;
+ Py_DontWriteBytecodeFlag = 1;
+ Py_NoUserSiteDirectory = 1;
+#ifdef MS_WINDOWS
+ Py_LegacyWindowsStdioFlag = 1;
+#endif
+
_PyPreConfig preconfig;
_PyPreConfig_InitPythonConfig(&preconfig);
@@ -788,11 +830,12 @@ static int init_python_config(void)
}
-static int init_dont_configure_locale(void)
+static int test_init_dont_configure_locale(void)
{
_PyInitError err;
- _PyPreConfig preconfig = _PyPreConfig_INIT;
+ _PyPreConfig preconfig;
+ _PyPreConfig_InitPythonConfig(&preconfig);
preconfig.configure_locale = 0;
preconfig.coerce_c_locale = 1;
preconfig.coerce_c_locale_warn = 1;
@@ -802,7 +845,8 @@ static int init_dont_configure_locale(void)
_Py_ExitInitError(err);
}
- _PyCoreConfig config = _PyCoreConfig_INIT;
+ _PyCoreConfig config;
+ _PyCoreConfig_InitPythonConfig(&config);
config.program_name = L"./_testembed";
err = _Py_InitializeFromConfig(&config);
if (_PyInitError_Failed(err)) {
@@ -815,10 +859,10 @@ static int init_dont_configure_locale(void)
}
-static int init_dev_mode(void)
+static int test_init_dev_mode(void)
{
_PyCoreConfig config;
- _PyCoreConfig_Init(&config);
+ _PyCoreConfig_InitPythonConfig(&config);
putenv("PYTHONFAULTHANDLER=");
putenv("PYTHONMALLOC=");
config.dev_mode = 1;
@@ -837,7 +881,7 @@ static int test_init_read_set(void)
{
_PyInitError err;
_PyCoreConfig config;
- _PyCoreConfig_Init(&config);
+ _PyCoreConfig_InitPythonConfig(&config);
err = _PyCoreConfig_DecodeLocale(&config.program_name, "./init_read_set");
if (_PyInitError_Failed(err)) {
@@ -894,7 +938,7 @@ static void configure_init_main(_PyCoreConfig *config)
static int test_init_run_main(void)
{
_PyCoreConfig config;
- _PyCoreConfig_Init(&config);
+ _PyCoreConfig_InitPythonConfig(&config);
configure_init_main(&config);
_PyInitError err = _Py_InitializeFromConfig(&config);
@@ -909,7 +953,7 @@ static int test_init_run_main(void)
static int test_init_main(void)
{
_PyCoreConfig config;
- _PyCoreConfig_Init(&config);
+ _PyCoreConfig_InitPythonConfig(&config);
configure_init_main(&config);
config._init_main = 0;
@@ -939,7 +983,7 @@ static int test_init_main(void)
static int test_run_main(void)
{
_PyCoreConfig config;
- _PyCoreConfig_Init(&config);
+ _PyCoreConfig_InitPythonConfig(&config);
wchar_t *argv[] = {L"python3", L"-c",
(L"import sys; "
@@ -947,7 +991,6 @@ static int test_run_main(void)
L"arg2"};
config.argv.length = Py_ARRAY_LENGTH(argv);
config.argv.items = argv;
- config.parse_argv = 1;
config.program_name = L"./python3";
_PyInitError err = _Py_InitializeFromConfig(&config);
@@ -988,16 +1031,16 @@ static struct TestCase TestCases[] = {
{ "init_default_config", test_init_default_config },
{ "init_global_config", test_init_global_config },
{ "init_from_config", test_init_from_config },
- { "init_parse_argv", init_parse_argv },
- { "init_dont_parse_argv", init_dont_parse_argv },
+ { "init_parse_argv", test_init_parse_argv },
+ { "init_dont_parse_argv", test_init_dont_parse_argv },
{ "init_env", test_init_env },
{ "init_env_dev_mode", test_init_env_dev_mode },
{ "init_env_dev_mode_alloc", test_init_env_dev_mode_alloc },
- { "init_dont_configure_locale", init_dont_configure_locale },
- { "init_dev_mode", init_dev_mode },
- { "init_isolated_flag", init_isolated_flag },
- { "init_isolated_config", init_isolated_config },
- { "init_python_config", init_python_config },
+ { "init_dont_configure_locale", test_init_dont_configure_locale },
+ { "init_dev_mode", test_init_dev_mode },
+ { "init_isolated_flag", test_init_isolated_flag },
+ { "init_isolated_config", test_init_isolated_config },
+ { "init_python_config", test_init_python_config },
{ "preinit_isolated1", test_preinit_isolated1 },
{ "preinit_isolated2", test_preinit_isolated2 },
{ "init_read_set", test_init_read_set },
diff --git a/Python/coreconfig.c b/Python/coreconfig.c
index fd457262a82431..3678d124067169 100644
--- a/Python/coreconfig.c
+++ b/Python/coreconfig.c
@@ -551,14 +551,69 @@ _PyCoreConfig_Clear(_PyCoreConfig *config)
void
_PyCoreConfig_Init(_PyCoreConfig *config)
{
- *config = _PyCoreConfig_INIT;
+ memset(config, 0, sizeof(*config));
+
+ config->_config_version = _Py_CONFIG_VERSION;
+ config->isolated = -1;
+ config->use_environment = -1;
+ config->dev_mode = -1;
+ config->install_signal_handlers = 1;
+ config->use_hash_seed = -1;
+ config->faulthandler = -1;
+ config->tracemalloc = -1;
+ config->use_module_search_paths = 0;
+ config->parse_argv = 0;
+ config->site_import = -1;
+ config->bytes_warning = -1;
+ config->inspect = -1;
+ config->interactive = -1;
+ config->optimization_level = -1;
+ config->parser_debug= -1;
+ config->write_bytecode = -1;
+ config->verbose = -1;
+ config->quiet = -1;
+ config->user_site_directory = -1;
+ config->configure_c_stdio = 0;
+ config->buffered_stdio = -1;
+ config->_install_importlib = 1;
+ config->check_hash_pycs_mode = NULL;
+ config->pathconfig_warnings = -1;
+ config->_init_main = 1;
+#ifdef MS_WINDOWS
+ config->legacy_windows_stdio = -1;
+#endif
+}
+
+
+static void
+_PyCoreConfig_InitDefaults(_PyCoreConfig *config)
+{
+ _PyCoreConfig_Init(config);
+
+ config->isolated = 0;
+ config->use_environment = 1;
+ config->site_import = 1;
+ config->bytes_warning = 0;
+ config->inspect = 0;
+ config->interactive = 0;
+ config->optimization_level = 0;
+ config->parser_debug= 0;
+ config->write_bytecode = 1;
+ config->verbose = 0;
+ config->quiet = 0;
+ config->user_site_directory = 1;
+ config->buffered_stdio = 1;
+ config->pathconfig_warnings = 1;
+#ifdef MS_WINDOWS
+ config->legacy_windows_stdio = 0;
+#endif
}
_PyInitError
_PyCoreConfig_InitPythonConfig(_PyCoreConfig *config)
{
- _PyCoreConfig_Init(config);
+ _PyCoreConfig_InitDefaults(config);
config->configure_c_stdio = 1;
config->parse_argv = 1;
@@ -570,30 +625,16 @@ _PyCoreConfig_InitPythonConfig(_PyCoreConfig *config)
_PyInitError
_PyCoreConfig_InitIsolatedConfig(_PyCoreConfig *config)
{
- _PyCoreConfig_Init(config);
+ _PyCoreConfig_InitDefaults(config);
- /* set to 1 */
config->isolated = 1;
- config->site_import = 1;
- config->write_bytecode = 1;
- config->buffered_stdio = 1;
-
- /* set to 0 */
config->use_environment = 0;
+ config->user_site_directory = 0;
config->dev_mode = 0;
config->install_signal_handlers = 0;
config->use_hash_seed = 0;
config->faulthandler = 0;
config->tracemalloc = 0;
- config->bytes_warning = 0;
- config->inspect = 0;
- config->interactive = 0;
- config->optimization_level = 0;
- config->parser_debug = 0;
- config->verbose = 0;
- config->quiet = 0;
- config->user_site_directory = 0;
- config->configure_c_stdio = 0;
config->pathconfig_warnings = 0;
#ifdef MS_WINDOWS
config->legacy_windows_stdio = 0;
diff --git a/Python/frozenmain.c b/Python/frozenmain.c
index 7b232bb8023fef..a51fb5800127d8 100644
--- a/Python/frozenmain.c
+++ b/Python/frozenmain.c
@@ -40,7 +40,7 @@ Py_FrozenMain(int argc, char **argv)
}
_PyCoreConfig config;
- _PyCoreConfig_Init(&config);
+ _PyCoreConfig_InitPythonConfig(&config);
config.pathconfig_warnings = 0; /* Suppress errors from getpath.c */
if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
diff --git a/Python/preconfig.c b/Python/preconfig.c
index b7bcfeb9b2f65c..0f4bd8ece534c0 100644
--- a/Python/preconfig.c
+++ b/Python/preconfig.c
@@ -241,8 +241,9 @@ _PyPreCmdline_Read(_PyPreCmdline *cmdline,
}
/* dev_mode */
- if ((cmdline && _Py_get_xoption(&cmdline->xoptions, L"dev"))
- || _Py_GetEnv(cmdline->use_environment, "PYTHONDEVMODE"))
+ if ((cmdline->dev_mode < 0)
+ && (_Py_get_xoption(&cmdline->xoptions, L"dev")
+ || _Py_GetEnv(cmdline->use_environment, "PYTHONDEVMODE")))
{
cmdline->dev_mode = 1;
}
@@ -260,10 +261,22 @@ _PyPreCmdline_Read(_PyPreCmdline *cmdline,
/* --- _PyPreConfig ----------------------------------------------- */
+
void
_PyPreConfig_Init(_PyPreConfig *config)
{
- *config = _PyPreConfig_INIT;
+ memset(config, 0, sizeof(*config));
+
+ config->_config_version = _Py_CONFIG_VERSION;
+ config->isolated = -1;
+ config->use_environment = -1;
+ config->configure_locale = 1;
+ config->utf8_mode = -2;
+ config->dev_mode = -1;
+ config->allocator = PYMEM_ALLOCATOR_NOT_SET;
+#ifdef MS_WINDOWS
+ config->legacy_windows_fs_encoding = -1;
+#endif
}
@@ -289,11 +302,11 @@ _PyPreConfig_InitIsolatedConfig(_PyPreConfig *config)
config->configure_locale = 0;
config->isolated = 1;
config->use_environment = 0;
+ config->utf8_mode = 0;
+ config->dev_mode = 0;
#ifdef MS_WINDOWS
config->legacy_windows_fs_encoding = 0;
#endif
- config->utf8_mode = 0;
- config->dev_mode = 0;
}
From 410759fba80aded5247b693c60745aa16906f3bb Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Sat, 18 May 2019 04:17:01 +0200
Subject: [PATCH 003/441] bpo-36763: Remove _PyCoreConfig.dll_path (GH-13402)
---
Include/cpython/coreconfig.h | 3 ---
Include/internal/pycore_pathconfig.h | 4 ++++
Lib/test/test_embed.py | 3 ---
PC/getpathp.c | 18 ++++++++----------
Python/coreconfig.c | 12 ------------
Python/pathconfig.c | 16 +++-------------
6 files changed, 15 insertions(+), 41 deletions(-)
diff --git a/Include/cpython/coreconfig.h b/Include/cpython/coreconfig.h
index ca71c15b67d6f9..a71f16171b7334 100644
--- a/Include/cpython/coreconfig.h
+++ b/Include/cpython/coreconfig.h
@@ -353,9 +353,6 @@ typedef struct {
wchar_t *base_prefix; /* sys.base_prefix */
wchar_t *exec_prefix; /* sys.exec_prefix */
wchar_t *base_exec_prefix; /* sys.base_exec_prefix */
-#ifdef MS_WINDOWS
- wchar_t *dll_path; /* Windows DLL path */
-#endif
/* --- Parameter only used by Py_Main() ---------- */
diff --git a/Include/internal/pycore_pathconfig.h b/Include/internal/pycore_pathconfig.h
index 9eb8e88df76736..bee391187cc97d 100644
--- a/Include/internal/pycore_pathconfig.h
+++ b/Include/internal/pycore_pathconfig.h
@@ -53,6 +53,10 @@ PyAPI_FUNC(int) _Py_FindEnvConfigValue(
wchar_t *value,
size_t value_size);
+#ifdef MS_WINDOWS
+extern wchar_t* _Py_GetDLLPath(void);
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index 6b77a2d1fd8dd9..5a5419dcfcf57e 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -369,7 +369,6 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'legacy_windows_fs_encoding': 0,
})
DEFAULT_CORE_CONFIG.update({
- 'dll_path': GET_DEFAULT_CONFIG,
'legacy_windows_stdio': 0,
})
@@ -466,8 +465,6 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
'filesystem_errors': sys.getfilesystemencodeerrors(),
'module_search_paths': core_config['module_search_paths'],
}
- if sys.platform == 'win32':
- data['dll_path'] = core_config['dll_path']
data = json.dumps(data)
data = data.encode('utf-8')
diff --git a/PC/getpathp.c b/PC/getpathp.c
index 64aa1e0d141f05..62c42ecefe9e18 100644
--- a/PC/getpathp.c
+++ b/PC/getpathp.c
@@ -508,8 +508,8 @@ getpythonregpath(HKEY keyBase, int skipcore)
#endif /* Py_ENABLE_SHARED */
-static _PyInitError
-get_dll_path(PyCalculatePath *calculate, _PyPathConfig *config)
+wchar_t*
+_Py_GetDLLPath(void)
{
wchar_t dll_path[MAXPATHLEN+1];
memset(dll_path, 0, sizeof(dll_path));
@@ -525,11 +525,7 @@ get_dll_path(PyCalculatePath *calculate, _PyPathConfig *config)
dll_path[0] = 0;
#endif
- config->dll_path = _PyMem_RawWcsdup(dll_path);
- if (config->dll_path == NULL) {
- return _Py_INIT_NO_MEMORY();
- }
- return _Py_INIT_OK();
+ return _PyMem_RawWcsdup(dll_path);
}
@@ -956,9 +952,11 @@ calculate_path_impl(const _PyCoreConfig *core_config,
{
_PyInitError err;
- err = get_dll_path(calculate, config);
- if (_Py_INIT_FAILED(err)) {
- return err;
+ assert(config->dll_path == NULL);
+
+ config->dll_path = _Py_GetDLLPath();
+ if (config->dll_path == NULL) {
+ return _Py_INIT_NO_MEMORY();
}
err = get_program_full_path(core_config, calculate, config);
diff --git a/Python/coreconfig.c b/Python/coreconfig.c
index 3678d124067169..470bda870288b5 100644
--- a/Python/coreconfig.c
+++ b/Python/coreconfig.c
@@ -531,9 +531,6 @@ _PyCoreConfig_Clear(_PyCoreConfig *config)
CLEAR(config->prefix);
CLEAR(config->base_prefix);
CLEAR(config->exec_prefix);
-#ifdef MS_WINDOWS
- CLEAR(config->dll_path);
-#endif
CLEAR(config->base_exec_prefix);
CLEAR(config->filesystem_encoding);
@@ -761,9 +758,6 @@ _PyCoreConfig_Copy(_PyCoreConfig *config, const _PyCoreConfig *config2)
COPY_WSTR_ATTR(prefix);
COPY_WSTR_ATTR(base_prefix);
COPY_WSTR_ATTR(exec_prefix);
-#ifdef MS_WINDOWS
- COPY_WSTR_ATTR(dll_path);
-#endif
COPY_WSTR_ATTR(base_exec_prefix);
COPY_ATTR(site_import);
@@ -864,9 +858,6 @@ _PyCoreConfig_AsDict(const _PyCoreConfig *config)
SET_ITEM_WSTR(base_prefix);
SET_ITEM_WSTR(exec_prefix);
SET_ITEM_WSTR(base_exec_prefix);
-#ifdef MS_WINDOWS
- SET_ITEM_WSTR(dll_path);
-#endif
SET_ITEM_INT(site_import);
SET_ITEM_INT(bytes_warning);
SET_ITEM_INT(inspect);
@@ -2355,9 +2346,6 @@ _PyCoreConfig_Read(_PyCoreConfig *config)
assert(config->base_prefix != NULL);
assert(config->exec_prefix != NULL);
assert(config->base_exec_prefix != NULL);
-#ifdef MS_WINDOWS
- assert(config->dll_path != NULL);
-#endif
}
assert(config->filesystem_encoding != NULL);
assert(config->filesystem_errors != NULL);
diff --git a/Python/pathconfig.c b/Python/pathconfig.c
index c8c69ebad6a032..3d9d3b1b205fc9 100644
--- a/Python/pathconfig.c
+++ b/Python/pathconfig.c
@@ -214,7 +214,8 @@ _PyCoreConfig_SetPathConfig(const _PyCoreConfig *core_config)
goto no_memory;
}
#ifdef MS_WINDOWS
- if (copy_wstr(&path_config.dll_path, core_config->dll_path) < 0) {
+ path_config.dll_path = _Py_GetDLLPath();
+ if (path_config.dll_path == NULL) {
goto no_memory;
}
#endif
@@ -322,14 +323,6 @@ _PyCoreConfig_CalculatePathConfig(_PyCoreConfig *config)
}
}
-#ifdef MS_WINDOWS
- if (config->dll_path == NULL) {
- if (copy_wstr(&config->dll_path, path_config.dll_path) < 0) {
- goto no_memory;
- }
- }
-#endif
-
if (path_config.isolated != -1) {
config->isolated = path_config.isolated;
}
@@ -356,9 +349,6 @@ _PyCoreConfig_InitPathConfig(_PyCoreConfig *config)
if (!config->use_module_search_paths
|| (config->executable == NULL)
|| (config->prefix == NULL)
-#ifdef MS_WINDOWS
- || (config->dll_path == NULL)
-#endif
|| (config->exec_prefix == NULL))
{
_PyInitError err = _PyCoreConfig_CalculatePathConfig(config);
@@ -435,7 +425,7 @@ Py_SetPath(const wchar_t *path)
new_config.exec_prefix = _PyMem_RawWcsdup(L"");
alloc_error |= (new_config.exec_prefix == NULL);
#ifdef MS_WINDOWS
- new_config.dll_path = _PyMem_RawWcsdup(L"");
+ new_config.dll_path = _Py_GetDLLPath();
alloc_error |= (new_config.dll_path == NULL);
#endif
new_config.module_search_path = _PyMem_RawWcsdup(path);
From 73934b9da07daefb203e7d26089e7486a1ce4fdf Mon Sep 17 00:00:00 2001
From: Mark Dickinson
Date: Sat, 18 May 2019 12:29:50 +0100
Subject: [PATCH 004/441] bpo-36887: add math.isqrt (GH-13244)
* Add math.isqrt function computing the integer square root.
* Code cleanup: remove redundant comments, rename some variables.
* Tighten up code a bit more; use Py_XDECREF to simplify error handling.
* Update Modules/mathmodule.c
Co-Authored-By: Serhiy Storchaka
* Update Modules/mathmodule.c
Use real argument clinic type instead of an alias
Co-Authored-By: Serhiy Storchaka
* Add proof sketch
* Updates from review.
* Correct and expand documentation.
* Fix bad reference handling on error; make some variables block-local; other tidying.
* Style and consistency fixes.
* Add missing error check; don't try to DECREF a NULL a
* Simplify some error returns.
* Another two test cases:
- clarify that floats are rejected even if they happen to be
squares of small integers
- TypeError beats ValueError for a negative float
* Documentation and markup improvements; thanks Serhiy for the suggestions!
* Cleaner Misc/NEWS entry wording.
* Clean up (with one fix) to the algorithm explanation and proof.
---
Doc/library/math.rst | 17 ++
Doc/whatsnew/3.8.rst | 3 +
Lib/test/test_math.py | 51 ++++
.../2019-05-11-14-50-59.bpo-36887.XD3f22.rst | 1 +
Modules/clinic/mathmodule.c.h | 11 +-
Modules/mathmodule.c | 261 ++++++++++++++++++
6 files changed, 343 insertions(+), 1 deletion(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-11-14-50-59.bpo-36887.XD3f22.rst
diff --git a/Doc/library/math.rst b/Doc/library/math.rst
index 49f932d03845c8..bf660ae9defa07 100644
--- a/Doc/library/math.rst
+++ b/Doc/library/math.rst
@@ -166,6 +166,20 @@ Number-theoretic and representation functions
Return ``True`` if *x* is a NaN (not a number), and ``False`` otherwise.
+.. function:: isqrt(n)
+
+ Return the integer square root of the nonnegative integer *n*. This is the
+ floor of the exact square root of *n*, or equivalently the greatest integer
+ *a* such that *a*\ ² |nbsp| ≤ |nbsp| *n*.
+
+ For some applications, it may be more convenient to have the least integer
+ *a* such that *n* |nbsp| ≤ |nbsp| *a*\ ², or in other words the ceiling of
+ the exact square root of *n*. For positive *n*, this can be computed using
+ ``a = 1 + isqrt(n - 1)``.
+
+ .. versionadded:: 3.8
+
+
.. function:: ldexp(x, i)
Return ``x * (2**i)``. This is essentially the inverse of function
@@ -538,3 +552,6 @@ Constants
Module :mod:`cmath`
Complex number versions of many of these functions.
+
+.. |nbsp| unicode:: 0xA0
+ :trim:
diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index d47993bf1129f8..07da4047a383a7 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -344,6 +344,9 @@ Added new function, :func:`math.prod`, as analogous function to :func:`sum`
that returns the product of a 'start' value (default: 1) times an iterable of
numbers. (Contributed by Pablo Galindo in :issue:`35606`)
+Added new function :func:`math.isqrt` for computing integer square roots.
+(Contributed by Mark Dickinson in :issue:`36887`.)
+
os
--
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py
index cb05dee0e0fd3c..a11a3447856402 100644
--- a/Lib/test/test_math.py
+++ b/Lib/test/test_math.py
@@ -912,6 +912,57 @@ class T(tuple):
self.assertEqual(math.dist(p, q), 5*scale)
self.assertEqual(math.dist(q, p), 5*scale)
+ def testIsqrt(self):
+ # Test a variety of inputs, large and small.
+ test_values = (
+ list(range(1000))
+ + list(range(10**6 - 1000, 10**6 + 1000))
+ + [3**9999, 10**5001]
+ )
+
+ for value in test_values:
+ with self.subTest(value=value):
+ s = math.isqrt(value)
+ self.assertIs(type(s), int)
+ self.assertLessEqual(s*s, value)
+ self.assertLess(value, (s+1)*(s+1))
+
+ # Negative values
+ with self.assertRaises(ValueError):
+ math.isqrt(-1)
+
+ # Integer-like things
+ s = math.isqrt(True)
+ self.assertIs(type(s), int)
+ self.assertEqual(s, 1)
+
+ s = math.isqrt(False)
+ self.assertIs(type(s), int)
+ self.assertEqual(s, 0)
+
+ class IntegerLike(object):
+ def __init__(self, value):
+ self.value = value
+
+ def __index__(self):
+ return self.value
+
+ s = math.isqrt(IntegerLike(1729))
+ self.assertIs(type(s), int)
+ self.assertEqual(s, 41)
+
+ with self.assertRaises(ValueError):
+ math.isqrt(IntegerLike(-3))
+
+ # Non-integer-like things
+ bad_values = [
+ 3.5, "a string", decimal.Decimal("3.5"), 3.5j,
+ 100.0, -4.0,
+ ]
+ for value in bad_values:
+ with self.subTest(value=value):
+ with self.assertRaises(TypeError):
+ math.isqrt(value)
def testLdexp(self):
self.assertRaises(TypeError, math.ldexp)
diff --git a/Misc/NEWS.d/next/Library/2019-05-11-14-50-59.bpo-36887.XD3f22.rst b/Misc/NEWS.d/next/Library/2019-05-11-14-50-59.bpo-36887.XD3f22.rst
new file mode 100644
index 00000000000000..fe2929cea85a9b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-11-14-50-59.bpo-36887.XD3f22.rst
@@ -0,0 +1 @@
+Add new function :func:`math.isqrt` to compute integer square roots.
diff --git a/Modules/clinic/mathmodule.c.h b/Modules/clinic/mathmodule.c.h
index 1806a01588c5ab..e677bd896cd885 100644
--- a/Modules/clinic/mathmodule.c.h
+++ b/Modules/clinic/mathmodule.c.h
@@ -65,6 +65,15 @@ PyDoc_STRVAR(math_fsum__doc__,
#define MATH_FSUM_METHODDEF \
{"fsum", (PyCFunction)math_fsum, METH_O, math_fsum__doc__},
+PyDoc_STRVAR(math_isqrt__doc__,
+"isqrt($module, n, /)\n"
+"--\n"
+"\n"
+"Return the integer part of the square root of the input.");
+
+#define MATH_ISQRT_METHODDEF \
+ {"isqrt", (PyCFunction)math_isqrt, METH_O, math_isqrt__doc__},
+
PyDoc_STRVAR(math_factorial__doc__,
"factorial($module, x, /)\n"
"--\n"
@@ -628,4 +637,4 @@ math_prod(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k
exit:
return return_value;
}
-/*[clinic end generated code: output=96e71135dce41c48 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=aeed62f403b90199 input=a9049054013a1b77]*/
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index 8f6a303cc4deb4..821309221f820a 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -1476,6 +1476,266 @@ count_set_bits(unsigned long n)
return count;
}
+/* Integer square root
+
+Given a nonnegative integer `n`, we want to compute the largest integer
+`a` for which `a * a <= n`, or equivalently the integer part of the exact
+square root of `n`.
+
+We use an adaptive-precision pure-integer version of Newton's iteration. Given
+a positive integer `n`, the algorithm produces at each iteration an integer
+approximation `a` to the square root of `n >> s` for some even integer `s`,
+with `s` decreasing as the iterations progress. On the final iteration, `s` is
+zero and we have an approximation to the square root of `n` itself.
+
+At every step, the approximation `a` is strictly within 1.0 of the true square
+root, so we have
+
+ (a - 1)**2 < (n >> s) < (a + 1)**2
+
+After the final iteration, a check-and-correct step is needed to determine
+whether `a` or `a - 1` gives the desired integer square root of `n`.
+
+The algorithm is remarkable in its simplicity. There's no need for a
+per-iteration check-and-correct step, and termination is straightforward: the
+number of iterations is known in advance (it's exactly `floor(log2(log2(n)))`
+for `n > 1`). The only tricky part of the correctness proof is in establishing
+that the bound `(a - 1)**2 < (n >> s) < (a + 1)**2` is maintained from one
+iteration to the next. A sketch of the proof of this is given below.
+
+In addition to the proof sketch, a formal, computer-verified proof
+of correctness (using Lean) of an equivalent recursive algorithm can be found
+here:
+
+ https://github.com/mdickinson/snippets/blob/master/proofs/isqrt/src/isqrt.lean
+
+
+Here's Python code equivalent to the C implementation below:
+
+ def isqrt(n):
+ """
+ Return the integer part of the square root of the input.
+ """
+ n = operator.index(n)
+
+ if n < 0:
+ raise ValueError("isqrt() argument must be nonnegative")
+ if n == 0:
+ return 0
+
+ c = (n.bit_length() - 1) // 2
+ a = 1
+ d = 0
+ for s in reversed(range(c.bit_length())):
+ e = d
+ d = c >> s
+ a = (a << d - e - 1) + (n >> 2*c - e - d + 1) // a
+ assert (a-1)**2 < n >> 2*(c - d) < (a+1)**2
+
+ return a - (a*a > n)
+
+
+Sketch of proof of correctness
+------------------------------
+
+The delicate part of the correctness proof is showing that the loop invariant
+is preserved from one iteration to the next. That is, just before the line
+
+ a = (a << d - e - 1) + (n >> 2*c - e - d + 1) // a
+
+is executed in the above code, we know that
+
+ (1) (a - 1)**2 < (n >> 2*(c - e)) < (a + 1)**2.
+
+(since `e` is always the value of `d` from the previous iteration). We must
+prove that after that line is executed, we have
+
+ (a - 1)**2 < (n >> 2*(c - d)) < (a + 1)**2
+
+To faciliate the proof, we make some changes of notation. Write `m` for
+`n >> 2*(c-d)`, and write `b` for the new value of `a`, so
+
+ b = (a << d - e - 1) + (n >> 2*c - e - d + 1) // a
+
+or equivalently:
+
+ (2) b = (a << d - e - 1) + (m >> d - e + 1) // a
+
+Then we can rewrite (1) as:
+
+ (3) (a - 1)**2 < (m >> 2*(d - e)) < (a + 1)**2
+
+and we must show that (b - 1)**2 < m < (b + 1)**2.
+
+From this point on, we switch to mathematical notation, so `/` means exact
+division rather than integer division and `^` is used for exponentiation. We
+use the `√` symbol for the exact square root. In (3), we can remove the
+implicit floor operation to give:
+
+ (4) (a - 1)^2 < m / 4^(d - e) < (a + 1)^2
+
+Taking square roots throughout (4), scaling by `2^(d-e)`, and rearranging gives
+
+ (5) 0 <= | 2^(d-e)a - √m | < 2^(d-e)
+
+Squaring and dividing through by `2^(d-e+1) a` gives
+
+ (6) 0 <= 2^(d-e-1) a + m / (2^(d-e+1) a) - √m < 2^(d-e-1) / a
+
+We'll show below that `2^(d-e-1) <= a`. Given that, we can replace the
+right-hand side of (6) with `1`, and now replacing the central
+term `m / (2^(d-e+1) a)` with its floor in (6) gives
+
+ (7) -1 < 2^(d-e-1) a + m // 2^(d-e+1) a - √m < 1
+
+Or equivalently, from (2):
+
+ (7) -1 < b - √m < 1
+
+and rearranging gives that `(b-1)^2 < m < (b+1)^2`, which is what we needed
+to prove.
+
+We're not quite done: we still have to prove the inequality `2^(d - e - 1) <=
+a` that was used to get line (7) above. From the definition of `c`, we have
+`4^c <= n`, which implies
+
+ (8) 4^d <= m
+
+also, since `e == d >> 1`, `d` is at most `2e + 1`, from which it follows
+that `2d - 2e - 1 <= d` and hence that
+
+ (9) 4^(2d - 2e - 1) <= m
+
+Dividing both sides by `4^(d - e)` gives
+
+ (10) 4^(d - e - 1) <= m / 4^(d - e)
+
+But we know from (4) that `m / 4^(d-e) < (a + 1)^2`, hence
+
+ (11) 4^(d - e - 1) < (a + 1)^2
+
+Now taking square roots of both sides and observing that both `2^(d-e-1)` and
+`a` are integers gives `2^(d - e - 1) <= a`, which is what we needed. This
+completes the proof sketch.
+
+*/
+
+/*[clinic input]
+math.isqrt
+
+ n: object
+ /
+
+Return the integer part of the square root of the input.
+[clinic start generated code]*/
+
+static PyObject *
+math_isqrt(PyObject *module, PyObject *n)
+/*[clinic end generated code: output=35a6f7f980beab26 input=5b6e7ae4fa6c43d6]*/
+{
+ int a_too_large, s;
+ size_t c, d;
+ PyObject *a = NULL, *b;
+
+ n = PyNumber_Index(n);
+ if (n == NULL) {
+ return NULL;
+ }
+
+ if (_PyLong_Sign(n) < 0) {
+ PyErr_SetString(
+ PyExc_ValueError,
+ "isqrt() argument must be nonnegative");
+ goto error;
+ }
+ if (_PyLong_Sign(n) == 0) {
+ Py_DECREF(n);
+ return PyLong_FromLong(0);
+ }
+
+ c = _PyLong_NumBits(n);
+ if (c == (size_t)(-1)) {
+ goto error;
+ }
+ c = (c - 1U) / 2U;
+
+ /* s = c.bit_length() */
+ s = 0;
+ while ((c >> s) > 0) {
+ ++s;
+ }
+
+ a = PyLong_FromLong(1);
+ if (a == NULL) {
+ goto error;
+ }
+ d = 0;
+ while (--s >= 0) {
+ PyObject *q, *shift;
+ size_t e = d;
+
+ d = c >> s;
+
+ /* q = (n >> 2*c - e - d + 1) // a */
+ shift = PyLong_FromSize_t(2U*c - d - e + 1U);
+ if (shift == NULL) {
+ goto error;
+ }
+ q = PyNumber_Rshift(n, shift);
+ Py_DECREF(shift);
+ if (q == NULL) {
+ goto error;
+ }
+ Py_SETREF(q, PyNumber_FloorDivide(q, a));
+ if (q == NULL) {
+ goto error;
+ }
+
+ /* a = (a << d - 1 - e) + q */
+ shift = PyLong_FromSize_t(d - 1U - e);
+ if (shift == NULL) {
+ Py_DECREF(q);
+ goto error;
+ }
+ Py_SETREF(a, PyNumber_Lshift(a, shift));
+ Py_DECREF(shift);
+ if (a == NULL) {
+ Py_DECREF(q);
+ goto error;
+ }
+ Py_SETREF(a, PyNumber_Add(a, q));
+ Py_DECREF(q);
+ if (a == NULL) {
+ goto error;
+ }
+ }
+
+ /* The correct result is either a or a - 1. Figure out which, and
+ decrement a if necessary. */
+
+ /* a_too_large = n < a * a */
+ b = PyNumber_Multiply(a, a);
+ if (b == NULL) {
+ goto error;
+ }
+ a_too_large = PyObject_RichCompareBool(n, b, Py_LT);
+ Py_DECREF(b);
+ if (a_too_large == -1) {
+ goto error;
+ }
+
+ if (a_too_large) {
+ Py_SETREF(a, PyNumber_Subtract(a, _PyLong_One));
+ }
+ Py_DECREF(n);
+ return a;
+
+ error:
+ Py_XDECREF(a);
+ Py_DECREF(n);
+ return NULL;
+}
+
/* Divide-and-conquer factorial algorithm
*
* Based on the formula and pseudo-code provided at:
@@ -2737,6 +2997,7 @@ static PyMethodDef math_methods[] = {
MATH_ISFINITE_METHODDEF
MATH_ISINF_METHODDEF
MATH_ISNAN_METHODDEF
+ MATH_ISQRT_METHODDEF
MATH_LDEXP_METHODDEF
{"lgamma", math_lgamma, METH_O, math_lgamma_doc},
MATH_LOG_METHODDEF
From e917f2ed9af044fe808fc9b4ddc6c5eb99003500 Mon Sep 17 00:00:00 2001
From: Raymond Hettinger
Date: Sat, 18 May 2019 10:18:29 -0700
Subject: [PATCH 005/441] bpo-36546: Add more tests and expand docs (#13406)
---
Doc/library/statistics.rst | 33 +++++++++++++++++++++-----------
Lib/test/test_statistics.py | 38 ++++++++++++++++++++++++++-----------
2 files changed, 49 insertions(+), 22 deletions(-)
diff --git a/Doc/library/statistics.rst b/Doc/library/statistics.rst
index fb7df4e7188a07..bc841fda72f887 100644
--- a/Doc/library/statistics.rst
+++ b/Doc/library/statistics.rst
@@ -511,22 +511,33 @@ However, for reading convenience, most of the examples show sorted sequences.
is not least 1.
The *dist* can be any iterable containing sample data or it can be an
- instance of a class that defines an :meth:`~inv_cdf` method.
+ instance of a class that defines an :meth:`~inv_cdf` method. For meaningful
+ results, the number of data points in *dist* should be larger than *n*.
Raises :exc:`StatisticsError` if there are not at least two data points.
For sample data, the cut points are linearly interpolated from the
two nearest data points. For example, if a cut point falls one-third
of the distance between two sample values, ``100`` and ``112``, the
- cut-point will evaluate to ``104``. Other selection methods may be
- offered in the future (for example choose ``100`` as the nearest
- value or compute ``106`` as the midpoint). This might matter if
- there are too few samples for a given number of cut points.
-
- If *method* is set to *inclusive*, *dist* is treated as population data.
- The minimum value is treated as the 0th percentile and the maximum
- value is treated as the 100th percentile. If *dist* is an instance of
- a class that defines an :meth:`~inv_cdf` method, setting *method*
- has no effect.
+ cut-point will evaluate to ``104``.
+
+ The *method* for computing quantiles can be varied depending on
+ whether the data in *dist* includes or excludes the lowest and
+ highest possible values from the population.
+
+ The default *method* is "exclusive" and is used for data sampled from
+ a population that can have more extreme values than found in the
+ samples. The portion of the population falling below the *i-th* of
+ *m* data points is computed as ``i / (m + 1)``.
+
+ Setting the *method* to "inclusive" is used for describing population
+ data or for samples that include the extreme points. The minimum
+ value in *dist* is treated as the 0th percentile and the maximum
+ value is treated as the 100th percentile. The portion of the
+ population falling below the *i-th* of *m* data points is computed as
+ ``(i - 1) / (m - 1)``.
+
+ If *dist* is an instance of a class that defines an
+ :meth:`~inv_cdf` method, setting *method* has no effect.
.. doctest::
diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py
index 1922de5df4b0c5..946c7428c61311 100644
--- a/Lib/test/test_statistics.py
+++ b/Lib/test/test_statistics.py
@@ -2161,17 +2161,18 @@ def test_specific_cases(self):
# Quantiles should be idempotent
if len(expected) >= 2:
self.assertEqual(quantiles(expected, n=n), expected)
- # Cross-check against other methods
- if len(data) >= n:
- # After end caps are added, method='inclusive' should
- # give the same result as method='exclusive' whenever
- # there are more data points than desired cut points.
- padded_data = [min(data) - 1000] + data + [max(data) + 1000]
- self.assertEqual(
- quantiles(data, n=n),
- quantiles(padded_data, n=n, method='inclusive'),
- (n, data),
- )
+ # Cross-check against method='inclusive' which should give
+ # the same result after adding in minimum and maximum values
+ # extrapolated from the two lowest and two highest points.
+ sdata = sorted(data)
+ lo = 2 * sdata[0] - sdata[1]
+ hi = 2 * sdata[-1] - sdata[-2]
+ padded_data = data + [lo, hi]
+ self.assertEqual(
+ quantiles(data, n=n),
+ quantiles(padded_data, n=n, method='inclusive'),
+ (n, data),
+ )
# Invariant under tranlation and scaling
def f(x):
return 3.5 * x - 1234.675
@@ -2188,6 +2189,11 @@ def f(x):
actual = quantiles(statistics.NormalDist(), n=n)
self.assertTrue(all(math.isclose(e, a, abs_tol=0.0001)
for e, a in zip(expected, actual)))
+ # Q2 agrees with median()
+ for k in range(2, 60):
+ data = random.choices(range(100), k=k)
+ q1, q2, q3 = quantiles(data)
+ self.assertEqual(q2, statistics.median(data))
def test_specific_cases_inclusive(self):
# Match results computed by hand and cross-checked
@@ -2233,6 +2239,11 @@ def f(x):
actual = quantiles(statistics.NormalDist(), n=n, method="inclusive")
self.assertTrue(all(math.isclose(e, a, abs_tol=0.0001)
for e, a in zip(expected, actual)))
+ # Natural deciles
+ self.assertEqual(quantiles([0, 100], n=10, method='inclusive'),
+ [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0])
+ self.assertEqual(quantiles(range(0, 101), n=10, method='inclusive'),
+ [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0])
# Whenever n is smaller than the number of data points, running
# method='inclusive' should give the same result as method='exclusive'
# after the two included extreme points are removed.
@@ -2242,6 +2253,11 @@ def f(x):
data.remove(max(data))
expected = quantiles(data, n=32)
self.assertEqual(expected, actual)
+ # Q2 agrees with median()
+ for k in range(2, 60):
+ data = random.choices(range(100), k=k)
+ q1, q2, q3 = quantiles(data, method='inclusive')
+ self.assertEqual(q2, statistics.median(data))
def test_equal_inputs(self):
quantiles = statistics.quantiles
From abea73bf4a320ff658c9a98fef3d948a142e61a9 Mon Sep 17 00:00:00 2001
From: Anthony Sottile
Date: Sat, 18 May 2019 11:27:17 -0700
Subject: [PATCH 006/441] bpo-2180: Treat line continuation at EOF as a
`SyntaxError` (GH-13401)
This makes the parser consistent with the tokenize module (already the case
in `pypy`).
sample
------
```python
x = 5\
```
before
------
```console
$ python3 t.py
$ python3 -mtokenize t.py
t.py:2:0: error: EOF in multi-line statement
```
after
-----
```console
$ ./python t.py
File "t.py", line 3
x = 5\
^
SyntaxError: unexpected EOF while parsing
$ ./python -m tokenize t.py
t.py:2:0: error: EOF in multi-line statement
```
https://bugs.python.org/issue2180
---
Lib/test/test_eof.py | 26 ++++++++++++++++++-
.../2019-05-17-18-34-30.bpo-2180.aBqHeW.rst | 1 +
Parser/tokenizer.c | 11 +++++++-
3 files changed, 36 insertions(+), 2 deletions(-)
create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-05-17-18-34-30.bpo-2180.aBqHeW.rst
diff --git a/Lib/test/test_eof.py b/Lib/test/test_eof.py
index 7baa7ae57c6ff4..a091ceaa25bc4f 100644
--- a/Lib/test/test_eof.py
+++ b/Lib/test/test_eof.py
@@ -1,7 +1,9 @@
"""test script for a few new invalid token catches"""
-import unittest
+import sys
from test import support
+from test.support import script_helper
+import unittest
class EOFTestCase(unittest.TestCase):
def test_EOFC(self):
@@ -24,5 +26,27 @@ def test_EOFS(self):
else:
raise support.TestFailed
+ def test_line_continuation_EOF(self):
+ """A contination at the end of input must be an error; bpo2180."""
+ expect = 'unexpected EOF while parsing (, line 1)'
+ with self.assertRaises(SyntaxError) as excinfo:
+ exec('x = 5\\')
+ self.assertEqual(str(excinfo.exception), expect)
+ with self.assertRaises(SyntaxError) as excinfo:
+ exec('\\')
+ self.assertEqual(str(excinfo.exception), expect)
+
+ @unittest.skipIf(not sys.executable, "sys.executable required")
+ def test_line_continuation_EOF_from_file_bpo2180(self):
+ """Ensure tok_nextc() does not add too many ending newlines."""
+ with support.temp_dir() as temp_dir:
+ file_name = script_helper.make_script(temp_dir, 'foo', '\\')
+ rc, out, err = script_helper.assert_python_failure(file_name)
+ self.assertIn(b'unexpected EOF while parsing', err)
+
+ file_name = script_helper.make_script(temp_dir, 'foo', 'y = 6\\')
+ rc, out, err = script_helper.assert_python_failure(file_name)
+ self.assertIn(b'unexpected EOF while parsing', err)
+
if __name__ == "__main__":
unittest.main()
diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-05-17-18-34-30.bpo-2180.aBqHeW.rst b/Misc/NEWS.d/next/Core and Builtins/2019-05-17-18-34-30.bpo-2180.aBqHeW.rst
new file mode 100644
index 00000000000000..a2207c17aea0a8
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2019-05-17-18-34-30.bpo-2180.aBqHeW.rst
@@ -0,0 +1 @@
+Treat line continuation at EOF as a ``SyntaxError`` by Anthony Sottile.
diff --git a/Parser/tokenizer.c b/Parser/tokenizer.c
index 5dc2ae65c42daf..e52d498d5542d5 100644
--- a/Parser/tokenizer.c
+++ b/Parser/tokenizer.c
@@ -983,7 +983,8 @@ tok_nextc(struct tok_state *tok)
return EOF;
/* Last line does not end in \n,
fake one */
- strcpy(tok->inp, "\n");
+ if (tok->inp[-1] != '\n')
+ strcpy(tok->inp, "\n");
}
tok->inp = strchr(tok->inp, '\0');
done = tok->inp[-1] == '\n';
@@ -1674,6 +1675,14 @@ tok_get(struct tok_state *tok, char **p_start, char **p_end)
tok->cur = tok->inp;
return ERRORTOKEN;
}
+ c = tok_nextc(tok);
+ if (c == EOF) {
+ tok->done = E_EOF;
+ tok->cur = tok->inp;
+ return ERRORTOKEN;
+ } else {
+ tok_backup(tok, c);
+ }
tok->cont_line = 1;
goto again; /* Read next line */
}
From 56027ccd6b9dab4a090e4fef8574933fb9a36ff2 Mon Sep 17 00:00:00 2001
From: Abhishek Kumar Singh
Date: Sun, 19 May 2019 02:06:19 +0530
Subject: [PATCH 007/441] bpo-19376: Added doc mentioning `datetime.strptime()`
without a year fails for Feb 29. (GH-10243)
---
Doc/library/datetime.rst | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst
index abdc977354803e..3c45e56b5f4fef 100644
--- a/Doc/library/datetime.rst
+++ b/Doc/library/datetime.rst
@@ -2048,6 +2048,9 @@ For :class:`date` objects, the format codes for hours, minutes, seconds, and
microseconds should not be used, as :class:`date` objects have no such
values. If they're used anyway, ``0`` is substituted for them.
+For the :meth:`datetime.strptime` class method, the default value is ``1900-01-01T00:00:00.000``:
+any components not specified in the format string will be pulled from the default value. [#]_
+
The full set of format codes supported varies across platforms, because Python
calls the platform C library's :func:`strftime` function, and platform
variations are common. To see the full set of format codes supported on your
@@ -2282,3 +2285,4 @@ Notes:
.. rubric:: Footnotes
.. [#] If, that is, we ignore the effects of Relativity
+.. [#] Passing ``datetime.strptime('Feb 29', '%b %d')`` will fail since ``1900`` is not a leap year.
From eab99650799699f766c2660f4cfa8ff3f9e8457f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Batuhan=20Ta=C5=9Fkaya?=
<47358913+isidentical@users.noreply.github.com>
Date: Sun, 19 May 2019 00:53:53 +0300
Subject: [PATCH 008/441] bpo-36567: Use manpages_url to create links for man
pages (GH-13339)
---
Doc/conf.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/Doc/conf.py b/Doc/conf.py
index afe66270c10e8c..e85ea5b2d2ff49 100644
--- a/Doc/conf.py
+++ b/Doc/conf.py
@@ -23,6 +23,9 @@
except ImportError:
_tkinter = None
'''
+
+manpages_url = 'https://manpages.debian.org/{path}'
+
# General substitutions.
project = 'Python'
copyright = '2001-%s, Python Software Foundation' % time.strftime('%Y')
From fa19a25c238d0769e6a5aa63ce05133d66043556 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Batuhan=20Ta=C5=9Fkaya?=
<47358913+isidentical@users.noreply.github.com>
Date: Sun, 19 May 2019 01:10:20 +0300
Subject: [PATCH 009/441] Add support for PEP572 in ast_unparse.c (GH-13337)
---
Lib/test/test_future.py | 2 ++
.../2019-05-15-14-01-09.bpo-36826.GLrO3W.rst | 1 +
Python/ast_unparse.c | 13 +++++++++++++
3 files changed, 16 insertions(+)
create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-05-15-14-01-09.bpo-36826.GLrO3W.rst
diff --git a/Lib/test/test_future.py b/Lib/test/test_future.py
index 38de3dfdafcdb1..cd320a266a8741 100644
--- a/Lib/test/test_future.py
+++ b/Lib/test/test_future.py
@@ -275,6 +275,8 @@ def test_annotations(self):
eq('f((x for x in a), 2)')
eq('(((a)))', 'a')
eq('(((a, b)))', '(a, b)')
+ eq("(x:=10)")
+ eq("f'{(x:=10):=10}'")
if __name__ == "__main__":
diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-05-15-14-01-09.bpo-36826.GLrO3W.rst b/Misc/NEWS.d/next/Core and Builtins/2019-05-15-14-01-09.bpo-36826.GLrO3W.rst
new file mode 100644
index 00000000000000..5a1b51999f26fc
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2019-05-15-14-01-09.bpo-36826.GLrO3W.rst
@@ -0,0 +1 @@
+Add NamedExpression kind support to ast_unparse.c
diff --git a/Python/ast_unparse.c b/Python/ast_unparse.c
index 25a5c698a1db9e..5f366a188b36e7 100644
--- a/Python/ast_unparse.c
+++ b/Python/ast_unparse.c
@@ -809,6 +809,17 @@ append_ast_await(_PyUnicodeWriter *writer, expr_ty e, int level)
return 0;
}
+static int
+append_named_expr(_PyUnicodeWriter *writer, expr_ty e, int level)
+{
+ APPEND_STR_IF(level > PR_TUPLE, "(");
+ APPEND_EXPR(e->v.NamedExpr.target, PR_ATOM);
+ APPEND_STR(":=");
+ APPEND_EXPR(e->v.NamedExpr.value, PR_ATOM);
+ APPEND_STR_IF(level > PR_TUPLE, ")");
+ return 0;
+}
+
static int
append_ast_expr(_PyUnicodeWriter *writer, expr_ty e, int level)
{
@@ -867,6 +878,8 @@ append_ast_expr(_PyUnicodeWriter *writer, expr_ty e, int level)
return append_ast_list(writer, e);
case Tuple_kind:
return append_ast_tuple(writer, e, level);
+ case NamedExpr_kind:
+ return append_named_expr(writer, e, level);
default:
PyErr_SetString(PyExc_SystemError,
"unknown expression kind");
From da6129e821099c1372d511a11d18af83d6d5d128 Mon Sep 17 00:00:00 2001
From: Pablo Galindo
Date: Sat, 18 May 2019 23:40:22 +0100
Subject: [PATCH 010/441] bpo-36961: Handle positional-only arguments in
uparse.c (GH-13412)
---
Lib/test/test_future.py | 12 ++++++++++++
Python/ast_unparse.c | 22 ++++++++++++++++------
2 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/Lib/test/test_future.py b/Lib/test/test_future.py
index cd320a266a8741..dd148b62956298 100644
--- a/Lib/test/test_future.py
+++ b/Lib/test/test_future.py
@@ -183,6 +183,18 @@ def test_annotations(self):
eq('lambda a, b, c=True: a')
eq("lambda a, b, c=True, *, d=1 << v2, e='str': a")
eq("lambda a, b, c=True, *vararg, d, e='str', **kwargs: a + b")
+ eq("lambda a, /, b, c=True, *vararg, d, e='str', **kwargs: a + b")
+ eq('lambda x, /: x')
+ eq('lambda x=1, /: x')
+ eq('lambda x, /, y: x + y')
+ eq('lambda x=1, /, y=2: x + y')
+ eq('lambda x, /, y=1: x + y')
+ eq('lambda x, /, y=1, *, z=3: x + y + z')
+ eq('lambda x=1, /, y=2, *, z=3: x + y + z')
+ eq('lambda x=1, /, y=2, *, z: x + y + z')
+ eq('lambda x=1, y=2, z=3, /, w=4, *, l, l2: x + y + z + w + l + l2')
+ eq('lambda x=1, y=2, z=3, /, w=4, *, l, l2, **kwargs: x + y + z + w + l + l2')
+ eq('lambda x, /, y=1, *, z: x + y + z')
eq('lambda x: lambda y: x + y')
eq('1 if True else 2')
eq('str or None if int or True else str or bytes or None')
diff --git a/Python/ast_unparse.c b/Python/ast_unparse.c
index 5f366a188b36e7..f1b991a7c38788 100644
--- a/Python/ast_unparse.c
+++ b/Python/ast_unparse.c
@@ -193,22 +193,30 @@ static int
append_ast_args(_PyUnicodeWriter *writer, arguments_ty args)
{
bool first;
- Py_ssize_t i, di, arg_count, default_count;
+ Py_ssize_t i, di, arg_count, posonlyarg_count, default_count;
first = true;
- /* positional arguments with defaults */
+ /* positional-only and positional arguments with defaults */
+ posonlyarg_count = asdl_seq_LEN(args->posonlyargs);
arg_count = asdl_seq_LEN(args->args);
default_count = asdl_seq_LEN(args->defaults);
- for (i = 0; i < arg_count; i++) {
+ for (i = 0; i < posonlyarg_count + arg_count; i++) {
APPEND_STR_IF_NOT_FIRST(", ");
- APPEND(arg, (arg_ty)asdl_seq_GET(args->args, i));
+ if (i < posonlyarg_count){
+ APPEND(arg, (arg_ty)asdl_seq_GET(args->posonlyargs, i));
+ } else {
+ APPEND(arg, (arg_ty)asdl_seq_GET(args->args, i-posonlyarg_count));
+ }
- di = i - arg_count + default_count;
+ di = i - posonlyarg_count - arg_count + default_count;
if (di >= 0) {
APPEND_STR("=");
APPEND_EXPR((expr_ty)asdl_seq_GET(args->defaults, di), PR_TEST);
}
+ if (posonlyarg_count && i + 1 == posonlyarg_count) {
+ APPEND_STR(", /");
+ }
}
/* vararg, or bare '*' if no varargs but keyword-only arguments present */
@@ -251,7 +259,9 @@ static int
append_ast_lambda(_PyUnicodeWriter *writer, expr_ty e, int level)
{
APPEND_STR_IF(level > PR_TEST, "(");
- APPEND_STR(asdl_seq_LEN(e->v.Lambda.args->args) ? "lambda " : "lambda");
+ Py_ssize_t n_positional = (asdl_seq_LEN(e->v.Lambda.args->args) +
+ asdl_seq_LEN(e->v.Lambda.args->posonlyargs));
+ APPEND_STR(n_positional ? "lambda " : "lambda");
APPEND(args, e->v.Lambda.args);
APPEND_STR(": ");
APPEND_EXPR(e->v.Lambda.body, PR_TEST);
From 9892f454d11b7ea9ba394a115b3e6f48ef6f78fe Mon Sep 17 00:00:00 2001
From: Jelle Zijlstra
Date: Sat, 18 May 2019 17:17:56 -0700
Subject: [PATCH 011/441] bpo-33519: clarify that .copy() is not part of the
MutableSequence ABC (GH-6965)
---
Doc/library/stdtypes.rst | 6 ++++--
.../Documentation/2018-05-17-21-02-00.bpo-33519.Q7s2FB.rst | 1 +
2 files changed, 5 insertions(+), 2 deletions(-)
create mode 100644 Misc/NEWS.d/next/Documentation/2018-05-17-21-02-00.bpo-33519.Q7s2FB.rst
diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst
index 53337291dd39ce..293a1ab6a0d966 100644
--- a/Doc/library/stdtypes.rst
+++ b/Doc/library/stdtypes.rst
@@ -1114,7 +1114,7 @@ Notes:
item is removed and returned.
(3)
- ``remove`` raises :exc:`ValueError` when *x* is not found in *s*.
+ :meth:`remove` raises :exc:`ValueError` when *x* is not found in *s*.
(4)
The :meth:`reverse` method modifies the sequence in place for economy of
@@ -1124,7 +1124,9 @@ Notes:
(5)
:meth:`clear` and :meth:`!copy` are included for consistency with the
interfaces of mutable containers that don't support slicing operations
- (such as :class:`dict` and :class:`set`)
+ (such as :class:`dict` and :class:`set`). :meth:`!copy` is not part of the
+ :class:`collections.abc.MutableSequence` ABC, but most concrete
+ mutable sequence classes provide it.
.. versionadded:: 3.3
:meth:`clear` and :meth:`!copy` methods.
diff --git a/Misc/NEWS.d/next/Documentation/2018-05-17-21-02-00.bpo-33519.Q7s2FB.rst b/Misc/NEWS.d/next/Documentation/2018-05-17-21-02-00.bpo-33519.Q7s2FB.rst
new file mode 100644
index 00000000000000..0ee6c0d5f8eae7
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2018-05-17-21-02-00.bpo-33519.Q7s2FB.rst
@@ -0,0 +1 @@
+Clarify that `copy()` is not part of the `MutableSequence` ABC.
From f665b96e92a6a6943e312e2c606f348db95939ab Mon Sep 17 00:00:00 2001
From: Ashwin Ramaswami
Date: Sat, 18 May 2019 18:17:48 -0700
Subject: [PATCH 012/441] Fix typo in test comment (GH-11442)
---
Lib/unittest/test/test_case.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py
index 687fe5b65f109e..c2401c39b917e3 100644
--- a/Lib/unittest/test/test_case.py
+++ b/Lib/unittest/test/test_case.py
@@ -620,7 +620,7 @@ def AllSnakesCreatedEqual(a, b, msg=None):
self.addTypeEqualityFunc(SadSnake, AllSnakesCreatedEqual)
self.assertEqual(s1, s2)
# No this doesn't clean up and remove the SadSnake equality func
- # from this TestCase instance but since its a local nothing else
+ # from this TestCase instance but since it's local nothing else
# will ever notice that.
def testAssertIs(self):
From 1d5bdef550d4395211fbe5f3c1444d7ea5bb54a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bo=C5=A1tjan=20Mejak?=
Date: Sun, 19 May 2019 11:01:36 +0200
Subject: [PATCH 013/441] Orthographical fix (GH-13418)
Add a missing comma.
---
Doc/library/asyncio-task.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst
index e7cf39b2bccd5f..bb064bd93deaf7 100644
--- a/Doc/library/asyncio-task.rst
+++ b/Doc/library/asyncio-task.rst
@@ -40,7 +40,7 @@ be executed::
>>> main()
-To actually run a coroutine asyncio provides three main mechanisms:
+To actually run a coroutine, asyncio provides three main mechanisms:
* The :func:`asyncio.run` function to run the top-level
entry point "main()" function (see the above example.)
From a5119e7d75c9729fc36c059d05f3d7132e7f6bb4 Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka
Date: Sun, 19 May 2019 14:14:38 +0300
Subject: [PATCH 014/441] bpo-36957: Add _PyLong_Rshift() and _PyLong_Lshift().
(GH-13416)
---
Include/longobject.h | 3 ++
Modules/mathmodule.c | 30 +++---------
Objects/floatobject.c | 4 +-
Objects/longobject.c | 103 ++++++++++++++++++++++++++++++------------
4 files changed, 87 insertions(+), 53 deletions(-)
diff --git a/Include/longobject.h b/Include/longobject.h
index b696f544b9c192..a24bbea3a9044f 100644
--- a/Include/longobject.h
+++ b/Include/longobject.h
@@ -230,6 +230,9 @@ PyAPI_FUNC(PyObject *) _PyLong_GCD(PyObject *, PyObject *);
#ifndef Py_LIMITED_API
PyAPI_DATA(PyObject *) _PyLong_Zero;
PyAPI_DATA(PyObject *) _PyLong_One;
+
+PyAPI_FUNC(PyObject *) _PyLong_Rshift(PyObject *, size_t);
+PyAPI_FUNC(PyObject *) _PyLong_Lshift(PyObject *, size_t);
#endif
#ifdef __cplusplus
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index 821309221f820a..7a0044a9fcf0b9 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -1671,18 +1671,13 @@ math_isqrt(PyObject *module, PyObject *n)
}
d = 0;
while (--s >= 0) {
- PyObject *q, *shift;
+ PyObject *q;
size_t e = d;
d = c >> s;
/* q = (n >> 2*c - e - d + 1) // a */
- shift = PyLong_FromSize_t(2U*c - d - e + 1U);
- if (shift == NULL) {
- goto error;
- }
- q = PyNumber_Rshift(n, shift);
- Py_DECREF(shift);
+ q = _PyLong_Rshift(n, 2U*c - d - e + 1U);
if (q == NULL) {
goto error;
}
@@ -1692,13 +1687,7 @@ math_isqrt(PyObject *module, PyObject *n)
}
/* a = (a << d - 1 - e) + q */
- shift = PyLong_FromSize_t(d - 1U - e);
- if (shift == NULL) {
- Py_DECREF(q);
- goto error;
- }
- Py_SETREF(a, PyNumber_Lshift(a, shift));
- Py_DECREF(shift);
+ Py_SETREF(a, _PyLong_Lshift(a, d - 1U - e));
if (a == NULL) {
Py_DECREF(q);
goto error;
@@ -1939,9 +1928,9 @@ static PyObject *
math_factorial(PyObject *module, PyObject *arg)
/*[clinic end generated code: output=6686f26fae00e9ca input=6d1c8105c0d91fb4]*/
{
- long x;
+ long x, two_valuation;
int overflow;
- PyObject *result, *odd_part, *two_valuation, *pyint_form;
+ PyObject *result, *odd_part, *pyint_form;
if (PyFloat_Check(arg)) {
PyObject *lx;
@@ -1990,13 +1979,8 @@ math_factorial(PyObject *module, PyObject *arg)
odd_part = factorial_odd_part(x);
if (odd_part == NULL)
return NULL;
- two_valuation = PyLong_FromLong(x - count_set_bits(x));
- if (two_valuation == NULL) {
- Py_DECREF(odd_part);
- return NULL;
- }
- result = PyNumber_Lshift(odd_part, two_valuation);
- Py_DECREF(two_valuation);
+ two_valuation = x - count_set_bits(x);
+ result = _PyLong_Lshift(odd_part, two_valuation);
Py_DECREF(odd_part);
return result;
}
diff --git a/Objects/floatobject.c b/Objects/floatobject.c
index adb9b80c271349..4ff43bb338f982 100644
--- a/Objects/floatobject.c
+++ b/Objects/floatobject.c
@@ -463,13 +463,13 @@ float_richcompare(PyObject *v, PyObject *w, int op)
*/
PyObject *temp;
- temp = PyNumber_Lshift(ww, _PyLong_One);
+ temp = _PyLong_Lshift(ww, 1);
if (temp == NULL)
goto Error;
Py_DECREF(ww);
ww = temp;
- temp = PyNumber_Lshift(vv, _PyLong_One);
+ temp = _PyLong_Lshift(vv, 1);
if (temp == NULL)
goto Error;
Py_DECREF(vv);
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 9fb1fb02c276bd..1934328820c0c5 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -4416,9 +4416,9 @@ long_bool(PyLongObject *v)
/* wordshift, remshift = divmod(shiftby, PyLong_SHIFT) */
static int
-divmod_shift(PyLongObject *shiftby, Py_ssize_t *wordshift, digit *remshift)
+divmod_shift(PyObject *shiftby, Py_ssize_t *wordshift, digit *remshift)
{
- assert(PyLong_Check((PyObject *)shiftby));
+ assert(PyLong_Check(shiftby));
assert(Py_SIZE(shiftby) >= 0);
Py_ssize_t lshiftby = PyLong_AsSsize_t((PyObject *)shiftby);
if (lshiftby >= 0) {
@@ -4430,7 +4430,7 @@ divmod_shift(PyLongObject *shiftby, Py_ssize_t *wordshift, digit *remshift)
be that PyLong_AsSsize_t raised an OverflowError. */
assert(PyErr_ExceptionMatches(PyExc_OverflowError));
PyErr_Clear();
- PyLongObject *wordshift_obj = divrem1(shiftby, PyLong_SHIFT, remshift);
+ PyLongObject *wordshift_obj = divrem1((PyLongObject *)shiftby, PyLong_SHIFT, remshift);
if (wordshift_obj == NULL) {
return -1;
}
@@ -4448,19 +4448,11 @@ divmod_shift(PyLongObject *shiftby, Py_ssize_t *wordshift, digit *remshift)
}
static PyObject *
-long_rshift(PyLongObject *a, PyLongObject *b)
+long_rshift1(PyLongObject *a, Py_ssize_t wordshift, digit remshift)
{
PyLongObject *z = NULL;
- Py_ssize_t newsize, wordshift, hishift, i, j;
- digit loshift, lomask, himask;
-
- CHECK_BINOP(a, b);
-
- if (Py_SIZE(b) < 0) {
- PyErr_SetString(PyExc_ValueError,
- "negative shift count");
- return NULL;
- }
+ Py_ssize_t newsize, hishift, i, j;
+ digit lomask, himask;
if (Py_SIZE(a) < 0) {
/* Right shifting negative numbers is harder */
@@ -4468,7 +4460,7 @@ long_rshift(PyLongObject *a, PyLongObject *b)
a1 = (PyLongObject *) long_invert(a);
if (a1 == NULL)
return NULL;
- a2 = (PyLongObject *) long_rshift(a1, b);
+ a2 = (PyLongObject *) long_rshift1(a1, wordshift, remshift);
Py_DECREF(a1);
if (a2 == NULL)
return NULL;
@@ -4476,19 +4468,17 @@ long_rshift(PyLongObject *a, PyLongObject *b)
Py_DECREF(a2);
}
else {
- if (divmod_shift(b, &wordshift, &loshift) < 0)
- return NULL;
newsize = Py_SIZE(a) - wordshift;
if (newsize <= 0)
return PyLong_FromLong(0);
- hishift = PyLong_SHIFT - loshift;
+ hishift = PyLong_SHIFT - remshift;
lomask = ((digit)1 << hishift) - 1;
himask = PyLong_MASK ^ lomask;
z = _PyLong_New(newsize);
if (z == NULL)
return NULL;
for (i = 0, j = wordshift; i < newsize; i++, j++) {
- z->ob_digit[i] = (a->ob_digit[j] >> loshift) & lomask;
+ z->ob_digit[i] = (a->ob_digit[j] >> remshift) & lomask;
if (i+1 < newsize)
z->ob_digit[i] |= (a->ob_digit[j+1] << hishift) & himask;
}
@@ -4498,15 +4488,10 @@ long_rshift(PyLongObject *a, PyLongObject *b)
}
static PyObject *
-long_lshift(PyObject *v, PyObject *w)
+long_rshift(PyObject *a, PyObject *b)
{
- /* This version due to Tim Peters */
- PyLongObject *a = (PyLongObject*)v;
- PyLongObject *b = (PyLongObject*)w;
- PyLongObject *z = NULL;
- Py_ssize_t oldsize, newsize, wordshift, i, j;
+ Py_ssize_t wordshift;
digit remshift;
- twodigits accum;
CHECK_BINOP(a, b);
@@ -4517,9 +4502,35 @@ long_lshift(PyObject *v, PyObject *w)
if (Py_SIZE(a) == 0) {
return PyLong_FromLong(0);
}
-
if (divmod_shift(b, &wordshift, &remshift) < 0)
return NULL;
+ return long_rshift1((PyLongObject *)a, wordshift, remshift);
+}
+
+/* Return a >> shiftby. */
+PyObject *
+_PyLong_Rshift(PyObject *a, size_t shiftby)
+{
+ Py_ssize_t wordshift;
+ digit remshift;
+
+ assert(PyLong_Check(a));
+ if (Py_SIZE(a) == 0) {
+ return PyLong_FromLong(0);
+ }
+ wordshift = shiftby / PyLong_SHIFT;
+ remshift = shiftby % PyLong_SHIFT;
+ return long_rshift1((PyLongObject *)a, wordshift, remshift);
+}
+
+static PyObject *
+long_lshift1(PyLongObject *a, Py_ssize_t wordshift, digit remshift)
+{
+ /* This version due to Tim Peters */
+ PyLongObject *z = NULL;
+ Py_ssize_t oldsize, newsize, i, j;
+ twodigits accum;
+
oldsize = Py_ABS(Py_SIZE(a));
newsize = oldsize + wordshift;
if (remshift)
@@ -4547,6 +4558,42 @@ long_lshift(PyObject *v, PyObject *w)
return (PyObject *) maybe_small_long(z);
}
+static PyObject *
+long_lshift(PyObject *a, PyObject *b)
+{
+ Py_ssize_t wordshift;
+ digit remshift;
+
+ CHECK_BINOP(a, b);
+
+ if (Py_SIZE(b) < 0) {
+ PyErr_SetString(PyExc_ValueError, "negative shift count");
+ return NULL;
+ }
+ if (Py_SIZE(a) == 0) {
+ return PyLong_FromLong(0);
+ }
+ if (divmod_shift(b, &wordshift, &remshift) < 0)
+ return NULL;
+ return long_lshift1((PyLongObject *)a, wordshift, remshift);
+}
+
+/* Return a << shiftby. */
+PyObject *
+_PyLong_Lshift(PyObject *a, size_t shiftby)
+{
+ Py_ssize_t wordshift;
+ digit remshift;
+
+ assert(PyLong_Check(a));
+ if (Py_SIZE(a) == 0) {
+ return PyLong_FromLong(0);
+ }
+ wordshift = shiftby / PyLong_SHIFT;
+ remshift = shiftby % PyLong_SHIFT;
+ return long_lshift1((PyLongObject *)a, wordshift, remshift);
+}
+
/* Compute two's complement of digit vector a[0:m], writing result to
z[0:m]. The digit vector a need not be normalized, but should not
be entirely zero. a and z may point to the same digit vector. */
@@ -5552,7 +5599,7 @@ static PyNumberMethods long_as_number = {
(inquiry)long_bool, /*tp_bool*/
(unaryfunc)long_invert, /*nb_invert*/
long_lshift, /*nb_lshift*/
- (binaryfunc)long_rshift, /*nb_rshift*/
+ long_rshift, /*nb_rshift*/
long_and, /*nb_and*/
long_xor, /*nb_xor*/
long_or, /*nb_or*/
From c661b30f89ffe7a7995538d3b1649469b184bee4 Mon Sep 17 00:00:00 2001
From: Xtreak
Date: Sun, 19 May 2019 19:10:06 +0530
Subject: [PATCH 015/441] bpo-36948: Fix NameError in
urllib.request.URLopener.retrieve (GH-13389)
---
Lib/test/test_urllib.py | 20 ++++++++++++++++++-
Lib/urllib/request.py | 10 +++++-----
.../2019-05-17-21-42-58.bpo-36948.vnUDvk.rst | 2 ++
3 files changed, 26 insertions(+), 6 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-17-21-42-58.bpo-36948.vnUDvk.rst
diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py
index 7214492eca9d88..74b19fbdcd8dbe 100644
--- a/Lib/test/test_urllib.py
+++ b/Lib/test/test_urllib.py
@@ -1445,7 +1445,7 @@ def test_thishost(self):
self.assertIsInstance(urllib.request.thishost(), tuple)
-class URLopener_Tests(unittest.TestCase):
+class URLopener_Tests(FakeHTTPMixin, unittest.TestCase):
"""Testcase to test the open method of URLopener class."""
def test_quoted_open(self):
@@ -1463,6 +1463,24 @@ def open_spam(self, url):
"spam://c:|windows%/:=&?~#+!$,;'@()*[]|/path/"),
"//c:|windows%/:=&?~#+!$,;'@()*[]|/path/")
+ @support.ignore_warnings(category=DeprecationWarning)
+ def test_urlopener_retrieve_file(self):
+ with support.temp_dir() as tmpdir:
+ fd, tmpfile = tempfile.mkstemp(dir=tmpdir)
+ os.close(fd)
+ fileurl = "file:" + urllib.request.pathname2url(tmpfile)
+ filename, _ = urllib.request.URLopener().retrieve(fileurl)
+ self.assertEqual(filename, tmpfile)
+
+ @support.ignore_warnings(category=DeprecationWarning)
+ def test_urlopener_retrieve_remote(self):
+ url = "http://www.python.org/file.txt"
+ self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello!")
+ self.addCleanup(self.unfakehttp)
+ filename, _ = urllib.request.URLopener().retrieve(url)
+ self.assertEqual(os.path.splitext(filename)[1], ".txt")
+
+
# Just commented them out.
# Can't really tell why keep failing in windows and sparc.
# Everywhere else they work ok, but on those machines, sometimes
diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py
index df2ff06f0fc9a2..230ac390abb332 100644
--- a/Lib/urllib/request.py
+++ b/Lib/urllib/request.py
@@ -1783,7 +1783,7 @@ def retrieve(self, url, filename=None, reporthook=None, data=None):
fp = self.open_local_file(url1)
hdrs = fp.info()
fp.close()
- return url2pathname(splithost(url1)[1]), hdrs
+ return url2pathname(_splithost(url1)[1]), hdrs
except OSError as msg:
pass
fp = self.open(url, data)
@@ -1792,10 +1792,10 @@ def retrieve(self, url, filename=None, reporthook=None, data=None):
if filename:
tfp = open(filename, 'wb')
else:
- garbage, path = splittype(url)
- garbage, path = splithost(path or "")
- path, garbage = splitquery(path or "")
- path, garbage = splitattr(path or "")
+ garbage, path = _splittype(url)
+ garbage, path = _splithost(path or "")
+ path, garbage = _splitquery(path or "")
+ path, garbage = _splitattr(path or "")
suffix = os.path.splitext(path)[1]
(fd, filename) = tempfile.mkstemp(suffix)
self.__tempfiles.append(filename)
diff --git a/Misc/NEWS.d/next/Library/2019-05-17-21-42-58.bpo-36948.vnUDvk.rst b/Misc/NEWS.d/next/Library/2019-05-17-21-42-58.bpo-36948.vnUDvk.rst
new file mode 100644
index 00000000000000..c8cfa54067fd35
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-17-21-42-58.bpo-36948.vnUDvk.rst
@@ -0,0 +1,2 @@
+Fix :exc:`NameError` in :meth:`urllib.request.URLopener.retrieve`. Patch by
+Karthikeyan Singaravelan.
From f4e1babf44792bdeb0c01da96821ba0800a51fd8 Mon Sep 17 00:00:00 2001
From: Bar Harel
Date: Sun, 19 May 2019 16:57:13 +0300
Subject: [PATCH 016/441] bpo-27141: Fix collections.UserList and UserDict
shallow copy. (GH-4094)
---
Lib/collections/__init__.py | 14 +++++++++++
Lib/test/test_collections.py | 24 +++++++++++++++++++
.../2017-10-24-00-42-14.bpo-27141.zbAgSs.rst | 3 +++
3 files changed, 41 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2017-10-24-00-42-14.bpo-27141.zbAgSs.rst
diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py
index 706907ad4a282d..960d82a5dcfbf5 100644
--- a/Lib/collections/__init__.py
+++ b/Lib/collections/__init__.py
@@ -1038,6 +1038,13 @@ def __contains__(self, key):
# Now, add the methods in dicts but not in MutableMapping
def __repr__(self): return repr(self.data)
+ 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):
if self.__class__ is UserDict:
return UserDict(self.data.copy())
@@ -1050,6 +1057,7 @@ def copy(self):
self.data = data
c.update(self)
return c
+
@classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
@@ -1118,6 +1126,12 @@ def __mul__(self, n):
def __imul__(self, n):
self.data *= n
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"][:]
+ return inst
def append(self, item): self.data.append(item)
def insert(self, i, item): self.data.insert(i, item)
def pop(self, i=-1): return self.data.pop(i)
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index 1f619bcdac9203..e2d04d5b476196 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -37,6 +37,20 @@ def _superset_test(self, a, b):
b=b.__name__,
),
)
+
+ def _copy_test(self, obj):
+ # Test internal copy
+ obj_copy = obj.copy()
+ self.assertIsNot(obj.data, obj_copy.data)
+ self.assertEqual(obj.data, obj_copy.data)
+
+ # Test copy.copy
+ obj.test = [1234] # Make sure instance vars are also copied.
+ obj_copy = copy.copy(obj)
+ self.assertIsNot(obj.data, obj_copy.data)
+ self.assertEqual(obj.data, obj_copy.data)
+ self.assertIs(obj.test, obj_copy.test)
+
def test_str_protocol(self):
self._superset_test(UserString, str)
@@ -46,6 +60,16 @@ def test_list_protocol(self):
def test_dict_protocol(self):
self._superset_test(UserDict, dict)
+ def test_list_copy(self):
+ obj = UserList()
+ obj.append(123)
+ self._copy_test(obj)
+
+ def test_dict_copy(self):
+ obj = UserDict()
+ obj[123] = "abc"
+ self._copy_test(obj)
+
################################################################################
### ChainMap (helper class for configparser and the string module)
diff --git a/Misc/NEWS.d/next/Library/2017-10-24-00-42-14.bpo-27141.zbAgSs.rst b/Misc/NEWS.d/next/Library/2017-10-24-00-42-14.bpo-27141.zbAgSs.rst
new file mode 100644
index 00000000000000..76c2abbf82d689
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-10-24-00-42-14.bpo-27141.zbAgSs.rst
@@ -0,0 +1,3 @@
+Added a ``__copy__()`` to ``collections.UserList`` and
+``collections.UserDict`` in order to correctly implement shallow copying of
+the objects. Patch by Bar Harel.
From 7c59362a15dfce538512ff1fce4e07d33a925cfb Mon Sep 17 00:00:00 2001
From: Berker Peksag
Date: Sun, 19 May 2019 18:56:15 +0300
Subject: [PATCH 017/441] bpo-29183: Fix double exceptions in
wsgiref.handlers.BaseHandler (GH-12914)
---
Lib/test/test_wsgiref.py | 25 +++++++++++++++++++
Lib/wsgiref/handlers.py | 11 +++++++-
.../2019-04-22-22-55-29.bpo-29183.MILvsk.rst | 3 +++
3 files changed, 38 insertions(+), 1 deletion(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-04-22-22-55-29.bpo-29183.MILvsk.rst
diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py
index 46f88a94434bcd..42432bfbd260e7 100644
--- a/Lib/test/test_wsgiref.py
+++ b/Lib/test/test_wsgiref.py
@@ -806,6 +806,31 @@ def write(self, b):
self.assertFalse(stderr.getvalue())
+ def testDontResetInternalStateOnException(self):
+ class CustomException(ValueError):
+ pass
+
+ # We are raising CustomException here to trigger an exception
+ # during the execution of SimpleHandler.finish_response(), so
+ # we can easily test that the internal state of the handler is
+ # preserved in case of an exception.
+ class AbortingWriter:
+ def write(self, b):
+ raise CustomException
+
+ stderr = StringIO()
+ environ = {"SERVER_PROTOCOL": "HTTP/1.0"}
+ h = SimpleHandler(BytesIO(), AbortingWriter(), stderr, environ)
+ h.run(hello_app)
+
+ self.assertIn("CustomException", stderr.getvalue())
+
+ # Test that the internal state of the handler is preserved.
+ self.assertIsNotNone(h.result)
+ self.assertIsNotNone(h.headers)
+ self.assertIsNotNone(h.status)
+ self.assertIsNotNone(h.environ)
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/wsgiref/handlers.py b/Lib/wsgiref/handlers.py
index 834073d50091e0..31360e58785ac6 100644
--- a/Lib/wsgiref/handlers.py
+++ b/Lib/wsgiref/handlers.py
@@ -183,7 +183,16 @@ def finish_response(self):
for data in self.result:
self.write(data)
self.finish_content()
- finally:
+ except:
+ # Call close() on the iterable returned by the WSGI application
+ # in case of an exception.
+ if hasattr(self.result, 'close'):
+ self.result.close()
+ raise
+ else:
+ # We only call close() when no exception is raised, because it
+ # will set status, result, headers, and environ fields to None.
+ # See bpo-29183 for more details.
self.close()
diff --git a/Misc/NEWS.d/next/Library/2019-04-22-22-55-29.bpo-29183.MILvsk.rst b/Misc/NEWS.d/next/Library/2019-04-22-22-55-29.bpo-29183.MILvsk.rst
new file mode 100644
index 00000000000000..1d19f191eeded5
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-04-22-22-55-29.bpo-29183.MILvsk.rst
@@ -0,0 +1,3 @@
+Fix double exceptions in :class:`wsgiref.handlers.BaseHandler` by calling
+its :meth:`~wsgiref.handlers.BaseHandler.close` method only when no
+exception is raised.
From 5c08ce9bf712acbb3f05a3a57baf51fcb534cdf0 Mon Sep 17 00:00:00 2001
From: Mark Dickinson
Date: Sun, 19 May 2019 17:51:56 +0100
Subject: [PATCH 018/441] bpo-36957: Speed up math.isqrt (#13405)
* Add math.isqrt function computing the integer square root.
* Code cleanup: remove redundant comments, rename some variables.
* Tighten up code a bit more; use Py_XDECREF to simplify error handling.
* Update Modules/mathmodule.c
Co-Authored-By: Serhiy Storchaka
* Update Modules/mathmodule.c
Use real argument clinic type instead of an alias
Co-Authored-By: Serhiy Storchaka
* Add proof sketch
* Updates from review.
* Correct and expand documentation.
* Fix bad reference handling on error; make some variables block-local; other tidying.
* Style and consistency fixes.
* Add missing error check; don't try to DECREF a NULL a
* Simplify some error returns.
* Another two test cases:
- clarify that floats are rejected even if they happen to be
squares of small integers
- TypeError beats ValueError for a negative float
* Add fast path for small inputs. Needs tests.
* Speed up isqrt for n >= 2**64 as well; add extra tests.
* Reduce number of test-cases to avoid dominating the run-time of test_math.
* Don't perform unnecessary extra iterations when computing c_bit_length.
* Abstract common uint64_t code out into a separate function.
* Cleanup.
* Add a missing Py_DECREF in an error branch. More cleanup.
* Update Modules/mathmodule.c
Add missing `static` declaration to helper function.
Co-Authored-By: Serhiy Storchaka
* Add missing backtick.
---
Lib/test/test_math.py | 1 +
Modules/mathmodule.c | 64 +++++++++++++++++++++++++++++++++++++------
2 files changed, 57 insertions(+), 8 deletions(-)
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py
index a11a3447856402..853a0e62f82351 100644
--- a/Lib/test/test_math.py
+++ b/Lib/test/test_math.py
@@ -917,6 +917,7 @@ def testIsqrt(self):
test_values = (
list(range(1000))
+ list(range(10**6 - 1000, 10**6 + 1000))
+ + [2**e + i for e in range(60, 200) for i in range(-40, 40)]
+ [3**9999, 10**5001]
)
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index 7a0044a9fcf0b9..a153e984ca59f2 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -1620,6 +1620,22 @@ completes the proof sketch.
*/
+
+/* Approximate square root of a large 64-bit integer.
+
+ Given `n` satisfying `2**62 <= n < 2**64`, return `a`
+ satisfying `(a - 1)**2 < n < (a + 1)**2`. */
+
+static uint64_t
+_approximate_isqrt(uint64_t n)
+{
+ uint32_t u = 1U + (n >> 62);
+ u = (u << 1) + (n >> 59) / u;
+ u = (u << 3) + (n >> 53) / u;
+ u = (u << 7) + (n >> 41) / u;
+ return (u << 15) + (n >> 17) / u;
+}
+
/*[clinic input]
math.isqrt
@@ -1633,8 +1649,9 @@ static PyObject *
math_isqrt(PyObject *module, PyObject *n)
/*[clinic end generated code: output=35a6f7f980beab26 input=5b6e7ae4fa6c43d6]*/
{
- int a_too_large, s;
+ int a_too_large, c_bit_length;
size_t c, d;
+ uint64_t m, u;
PyObject *a = NULL, *b;
n = PyNumber_Index(n);
@@ -1653,24 +1670,55 @@ math_isqrt(PyObject *module, PyObject *n)
return PyLong_FromLong(0);
}
+ /* c = (n.bit_length() - 1) // 2 */
c = _PyLong_NumBits(n);
if (c == (size_t)(-1)) {
goto error;
}
c = (c - 1U) / 2U;
- /* s = c.bit_length() */
- s = 0;
- while ((c >> s) > 0) {
- ++s;
+ /* Fast path: if c <= 31 then n < 2**64 and we can compute directly with a
+ fast, almost branch-free algorithm. In the final correction, we use `u*u
+ - 1 >= m` instead of the simpler `u*u > m` in order to get the correct
+ result in the corner case where `u=2**32`. */
+ if (c <= 31U) {
+ m = (uint64_t)PyLong_AsUnsignedLongLong(n);
+ Py_DECREF(n);
+ if (m == (uint64_t)(-1) && PyErr_Occurred()) {
+ return NULL;
+ }
+ u = _approximate_isqrt(m << (62U - 2U*c)) >> (31U - c);
+ u -= u * u - 1U >= m;
+ return PyLong_FromUnsignedLongLong((unsigned long long)u);
}
- a = PyLong_FromLong(1);
+ /* Slow path: n >= 2**64. We perform the first five iterations in C integer
+ arithmetic, then switch to using Python long integers. */
+
+ /* From n >= 2**64 it follows that c.bit_length() >= 6. */
+ c_bit_length = 6;
+ while ((c >> c_bit_length) > 0U) {
+ ++c_bit_length;
+ }
+
+ /* Initialise d and a. */
+ d = c >> (c_bit_length - 5);
+ b = _PyLong_Rshift(n, 2U*c - 62U);
+ if (b == NULL) {
+ goto error;
+ }
+ m = (uint64_t)PyLong_AsUnsignedLongLong(b);
+ Py_DECREF(b);
+ if (m == (uint64_t)(-1) && PyErr_Occurred()) {
+ goto error;
+ }
+ u = _approximate_isqrt(m) >> (31U - d);
+ a = PyLong_FromUnsignedLongLong((unsigned long long)u);
if (a == NULL) {
goto error;
}
- d = 0;
- while (--s >= 0) {
+
+ for (int s = c_bit_length - 6; s >= 0; --s) {
PyObject *q;
size_t e = d;
From 287b84de939db47aa8c6f30734ceb8aba9d1db29 Mon Sep 17 00:00:00 2001
From: Xtreak
Date: Mon, 20 May 2019 03:22:20 +0530
Subject: [PATCH 019/441] bpo-34580: Update sqlite3 examples to call close()
explicitly (GH-9079)
The sqlit3.Connection object doesn't call its close() method when it's used
as a context manager.
---
Doc/includes/sqlite3/adapter_datetime.py | 2 ++
Doc/includes/sqlite3/adapter_point_1.py | 2 ++
Doc/includes/sqlite3/adapter_point_2.py | 2 ++
Doc/includes/sqlite3/connect_db_1.py | 3 ---
Doc/includes/sqlite3/connect_db_2.py | 3 ---
Doc/includes/sqlite3/countcursors.py | 2 ++
Doc/includes/sqlite3/ctx_manager.py | 4 ++++
Doc/includes/sqlite3/execsql_fetchonerow.py | 2 ++
Doc/includes/sqlite3/execsql_printall_1.py | 2 ++
Doc/includes/sqlite3/execute_1.py | 2 ++
Doc/includes/sqlite3/execute_3.py | 12 ------------
Doc/includes/sqlite3/executemany_1.py | 2 ++
Doc/includes/sqlite3/executemany_2.py | 2 ++
Doc/includes/sqlite3/executescript.py | 1 +
Doc/includes/sqlite3/insert_more_people.py | 2 ++
Doc/includes/sqlite3/load_extension.py | 2 ++
Doc/includes/sqlite3/md5func.py | 2 ++
Doc/includes/sqlite3/mysumaggr.py | 2 ++
Doc/includes/sqlite3/parse_colnames.py | 2 ++
Doc/includes/sqlite3/pysqlite_datetime.py | 2 ++
Doc/includes/sqlite3/row_factory.py | 2 ++
Doc/includes/sqlite3/rowclass.py | 2 ++
Doc/includes/sqlite3/shortcut_methods.py | 4 ++++
Doc/includes/sqlite3/simple_tableprinter.py | 2 ++
Doc/includes/sqlite3/text_factory.py | 2 ++
Doc/library/sqlite3.rst | 6 +++++-
26 files changed, 52 insertions(+), 19 deletions(-)
delete mode 100644 Doc/includes/sqlite3/connect_db_1.py
delete mode 100644 Doc/includes/sqlite3/connect_db_2.py
delete mode 100644 Doc/includes/sqlite3/execute_3.py
diff --git a/Doc/includes/sqlite3/adapter_datetime.py b/Doc/includes/sqlite3/adapter_datetime.py
index be33395100c325..d5221d80c35c8a 100644
--- a/Doc/includes/sqlite3/adapter_datetime.py
+++ b/Doc/includes/sqlite3/adapter_datetime.py
@@ -13,3 +13,5 @@ def adapt_datetime(ts):
now = datetime.datetime.now()
cur.execute("select ?", (now,))
print(cur.fetchone()[0])
+
+con.close()
diff --git a/Doc/includes/sqlite3/adapter_point_1.py b/Doc/includes/sqlite3/adapter_point_1.py
index 6b1af8415648a4..77daf8f16d227b 100644
--- a/Doc/includes/sqlite3/adapter_point_1.py
+++ b/Doc/includes/sqlite3/adapter_point_1.py
@@ -14,3 +14,5 @@ def __conform__(self, protocol):
p = Point(4.0, -3.2)
cur.execute("select ?", (p,))
print(cur.fetchone()[0])
+
+con.close()
diff --git a/Doc/includes/sqlite3/adapter_point_2.py b/Doc/includes/sqlite3/adapter_point_2.py
index d670700f0491b1..cb86331692b61d 100644
--- a/Doc/includes/sqlite3/adapter_point_2.py
+++ b/Doc/includes/sqlite3/adapter_point_2.py
@@ -15,3 +15,5 @@ def adapt_point(point):
p = Point(4.0, -3.2)
cur.execute("select ?", (p,))
print(cur.fetchone()[0])
+
+con.close()
diff --git a/Doc/includes/sqlite3/connect_db_1.py b/Doc/includes/sqlite3/connect_db_1.py
deleted file mode 100644
index 1b975232865aef..00000000000000
--- a/Doc/includes/sqlite3/connect_db_1.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import sqlite3
-
-con = sqlite3.connect("mydb")
diff --git a/Doc/includes/sqlite3/connect_db_2.py b/Doc/includes/sqlite3/connect_db_2.py
deleted file mode 100644
index f9728b36135ed7..00000000000000
--- a/Doc/includes/sqlite3/connect_db_2.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import sqlite3
-
-con = sqlite3.connect(":memory:")
diff --git a/Doc/includes/sqlite3/countcursors.py b/Doc/includes/sqlite3/countcursors.py
index ef3e70a2a9cfc5..112f47703a2ff4 100644
--- a/Doc/includes/sqlite3/countcursors.py
+++ b/Doc/includes/sqlite3/countcursors.py
@@ -13,3 +13,5 @@ def cursor(self, *args, **kwargs):
cur1 = con.cursor()
cur2 = con.cursor()
print(con.numcursors)
+
+con.close()
diff --git a/Doc/includes/sqlite3/ctx_manager.py b/Doc/includes/sqlite3/ctx_manager.py
index 7af4ad1ecfbcca..6db77d45046e1f 100644
--- a/Doc/includes/sqlite3/ctx_manager.py
+++ b/Doc/includes/sqlite3/ctx_manager.py
@@ -14,3 +14,7 @@
con.execute("insert into person(firstname) values (?)", ("Joe",))
except sqlite3.IntegrityError:
print("couldn't add Joe twice")
+
+# Connection object used as context manager only commits or rollbacks transactions,
+# so the connection object should be closed manually
+con.close()
diff --git a/Doc/includes/sqlite3/execsql_fetchonerow.py b/Doc/includes/sqlite3/execsql_fetchonerow.py
index 078873bfc979a9..115bcb50c7c754 100644
--- a/Doc/includes/sqlite3/execsql_fetchonerow.py
+++ b/Doc/includes/sqlite3/execsql_fetchonerow.py
@@ -15,3 +15,5 @@
cur.execute(SELECT)
for row in cur:
print('%s is %d years old.' % (row[0], row[1]))
+
+con.close()
diff --git a/Doc/includes/sqlite3/execsql_printall_1.py b/Doc/includes/sqlite3/execsql_printall_1.py
index a4ce5c528149c5..19306e6e3ca7d1 100644
--- a/Doc/includes/sqlite3/execsql_printall_1.py
+++ b/Doc/includes/sqlite3/execsql_printall_1.py
@@ -11,3 +11,5 @@
# Retrieve all rows as a sequence and print that sequence:
print(cur.fetchall())
+
+con.close()
diff --git a/Doc/includes/sqlite3/execute_1.py b/Doc/includes/sqlite3/execute_1.py
index f864a8984e4e90..3466b1265a5bf2 100644
--- a/Doc/includes/sqlite3/execute_1.py
+++ b/Doc/includes/sqlite3/execute_1.py
@@ -14,3 +14,5 @@
cur.execute("select * from people where name_last=:who and age=:age", {"who": who, "age": age})
print(cur.fetchone())
+
+con.close()
diff --git a/Doc/includes/sqlite3/execute_3.py b/Doc/includes/sqlite3/execute_3.py
deleted file mode 100644
index 0353683fc70476..00000000000000
--- a/Doc/includes/sqlite3/execute_3.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import sqlite3
-
-con = sqlite3.connect("mydb")
-
-cur = con.cursor()
-
-who = "Yeltsin"
-age = 72
-
-cur.execute("select name_last, age from people where name_last=:who and age=:age",
- locals())
-print(cur.fetchone())
diff --git a/Doc/includes/sqlite3/executemany_1.py b/Doc/includes/sqlite3/executemany_1.py
index efae10637c7e8f..edf6f8b7ebe61a 100644
--- a/Doc/includes/sqlite3/executemany_1.py
+++ b/Doc/includes/sqlite3/executemany_1.py
@@ -22,3 +22,5 @@ def __next__(self):
cur.execute("select c from characters")
print(cur.fetchall())
+
+con.close()
diff --git a/Doc/includes/sqlite3/executemany_2.py b/Doc/includes/sqlite3/executemany_2.py
index 527358ebc28e1f..02a594c861e15b 100644
--- a/Doc/includes/sqlite3/executemany_2.py
+++ b/Doc/includes/sqlite3/executemany_2.py
@@ -13,3 +13,5 @@ def char_generator():
cur.execute("select c from characters")
print(cur.fetchall())
+
+con.close()
diff --git a/Doc/includes/sqlite3/executescript.py b/Doc/includes/sqlite3/executescript.py
index 7e5358178d4c0a..aea8943fbee598 100644
--- a/Doc/includes/sqlite3/executescript.py
+++ b/Doc/includes/sqlite3/executescript.py
@@ -22,3 +22,4 @@
1987
);
""")
+con.close()
diff --git a/Doc/includes/sqlite3/insert_more_people.py b/Doc/includes/sqlite3/insert_more_people.py
index edbc79e7e5b6cc..10cf937243f6da 100644
--- a/Doc/includes/sqlite3/insert_more_people.py
+++ b/Doc/includes/sqlite3/insert_more_people.py
@@ -14,3 +14,5 @@
# The changes will not be saved unless the transaction is committed explicitly:
con.commit()
+
+con.close()
diff --git a/Doc/includes/sqlite3/load_extension.py b/Doc/includes/sqlite3/load_extension.py
index b997c70668ace4..624cfe262f38b3 100644
--- a/Doc/includes/sqlite3/load_extension.py
+++ b/Doc/includes/sqlite3/load_extension.py
@@ -24,3 +24,5 @@
""")
for row in con.execute("select rowid, name, ingredients from recipe where name match 'pie'"):
print(row)
+
+con.close()
diff --git a/Doc/includes/sqlite3/md5func.py b/Doc/includes/sqlite3/md5func.py
index 0056b2d6ce84ef..16dc348bf001e2 100644
--- a/Doc/includes/sqlite3/md5func.py
+++ b/Doc/includes/sqlite3/md5func.py
@@ -9,3 +9,5 @@ def md5sum(t):
cur = con.cursor()
cur.execute("select md5(?)", (b"foo",))
print(cur.fetchone()[0])
+
+con.close()
diff --git a/Doc/includes/sqlite3/mysumaggr.py b/Doc/includes/sqlite3/mysumaggr.py
index d2dfd2c0b98d3b..11f96395b6c485 100644
--- a/Doc/includes/sqlite3/mysumaggr.py
+++ b/Doc/includes/sqlite3/mysumaggr.py
@@ -18,3 +18,5 @@ def finalize(self):
cur.execute("insert into test(i) values (2)")
cur.execute("select mysum(i) from test")
print(cur.fetchone()[0])
+
+con.close()
diff --git a/Doc/includes/sqlite3/parse_colnames.py b/Doc/includes/sqlite3/parse_colnames.py
index cc68c76459ecea..5f01dbfe1cb524 100644
--- a/Doc/includes/sqlite3/parse_colnames.py
+++ b/Doc/includes/sqlite3/parse_colnames.py
@@ -6,3 +6,5 @@
cur.execute('select ? as "x [timestamp]"', (datetime.datetime.now(),))
dt = cur.fetchone()[0]
print(dt, type(dt))
+
+con.close()
diff --git a/Doc/includes/sqlite3/pysqlite_datetime.py b/Doc/includes/sqlite3/pysqlite_datetime.py
index 68d49358a578b3..5d843f906b3062 100644
--- a/Doc/includes/sqlite3/pysqlite_datetime.py
+++ b/Doc/includes/sqlite3/pysqlite_datetime.py
@@ -18,3 +18,5 @@
row = cur.fetchone()
print("current_date", row[0], type(row[0]))
print("current_timestamp", row[1], type(row[1]))
+
+con.close()
diff --git a/Doc/includes/sqlite3/row_factory.py b/Doc/includes/sqlite3/row_factory.py
index e436ffc6c80225..9de6e7b1b9052a 100644
--- a/Doc/includes/sqlite3/row_factory.py
+++ b/Doc/includes/sqlite3/row_factory.py
@@ -11,3 +11,5 @@ def dict_factory(cursor, row):
cur = con.cursor()
cur.execute("select 1 as a")
print(cur.fetchone()["a"])
+
+con.close()
diff --git a/Doc/includes/sqlite3/rowclass.py b/Doc/includes/sqlite3/rowclass.py
index 92b5ad60cb5791..fc60287069a854 100644
--- a/Doc/includes/sqlite3/rowclass.py
+++ b/Doc/includes/sqlite3/rowclass.py
@@ -10,3 +10,5 @@
assert row["name"] == row["nAmE"]
assert row[1] == row["age"]
assert row[1] == row["AgE"]
+
+con.close()
diff --git a/Doc/includes/sqlite3/shortcut_methods.py b/Doc/includes/sqlite3/shortcut_methods.py
index 71600d4f60c55e..98a39411495cba 100644
--- a/Doc/includes/sqlite3/shortcut_methods.py
+++ b/Doc/includes/sqlite3/shortcut_methods.py
@@ -18,3 +18,7 @@
print(row)
print("I just deleted", con.execute("delete from person").rowcount, "rows")
+
+# close is not a shortcut method and it's not called automatically,
+# so the connection object should be closed manually
+con.close()
diff --git a/Doc/includes/sqlite3/simple_tableprinter.py b/Doc/includes/sqlite3/simple_tableprinter.py
index 231d8726cd436e..148a1707f948bc 100644
--- a/Doc/includes/sqlite3/simple_tableprinter.py
+++ b/Doc/includes/sqlite3/simple_tableprinter.py
@@ -24,3 +24,5 @@
print(fieldValue.ljust(FIELD_MAX_WIDTH), end=' ')
print() # Finish the row with a newline.
+
+con.close()
diff --git a/Doc/includes/sqlite3/text_factory.py b/Doc/includes/sqlite3/text_factory.py
index 5f96cdb58da1ab..a857a155cdd4ff 100644
--- a/Doc/includes/sqlite3/text_factory.py
+++ b/Doc/includes/sqlite3/text_factory.py
@@ -25,3 +25,5 @@
cur.execute("select ?", ("bar",))
row = cur.fetchone()
assert row[0] == "barfoo"
+
+con.close()
diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst
index 37087ac5af492a..20fca54aab144f 100644
--- a/Doc/library/sqlite3.rst
+++ b/Doc/library/sqlite3.rst
@@ -537,6 +537,7 @@ Connection Objects
with open('dump.sql', 'w') as f:
for line in con.iterdump():
f.write('%s\n' % line)
+ con.close()
.. method:: backup(target, *, pages=0, progress=None, name="main", sleep=0.250)
@@ -573,8 +574,11 @@ Connection Objects
print(f'Copied {total-remaining} of {total} pages...')
con = sqlite3.connect('existing_db.db')
- with sqlite3.connect('backup.db') as bck:
+ bck = sqlite3.connect('backup.db')
+ with bck:
con.backup(bck, pages=1, progress=progress)
+ bck.close()
+ con.close()
Example 2, copy an existing database into a transient copy::
From d673810b9d9df6fbd29f5b7db3973d5adae10fd3 Mon Sep 17 00:00:00 2001
From: Lysandros Nikolaou
Date: Mon, 20 May 2019 00:11:21 +0200
Subject: [PATCH 020/441] bpo-35252: Remove FIXME from test_functools
(GH-10551)
---
Lib/functools.py | 8 +++++---
Lib/test/test_functools.py | 11 +++++------
.../Library/2019-04-02-19-23-12.bpo-35252.VooTVv.rst | 1 +
3 files changed, 11 insertions(+), 9 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-04-02-19-23-12.bpo-35252.VooTVv.rst
diff --git a/Lib/functools.py b/Lib/functools.py
index 28d9f6f75fdb8b..c863341eec5f21 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -861,9 +861,11 @@ def register(cls, func=None):
# only import typing if annotation parsing is necessary
from typing import get_type_hints
argname, cls = next(iter(get_type_hints(func).items()))
- assert isinstance(cls, type), (
- f"Invalid annotation for {argname!r}. {cls!r} is not a class."
- )
+ if not isinstance(cls, type):
+ raise TypeError(
+ f"Invalid annotation for {argname!r}. "
+ f"{cls!r} is not a class."
+ )
registry[cls] = func
if cache_token is None and hasattr(cls, '__abstractmethods__'):
cache_token = get_cache_token()
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index 85c65d18326067..b89d77967a0df2 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -2355,9 +2355,6 @@ def _(arg):
))
self.assertTrue(str(exc.exception).endswith(msg_suffix))
- # FIXME: The following will only work after PEP 560 is implemented.
- return
-
with self.assertRaises(TypeError) as exc:
@i.register
def _(arg: typing.Iterable[str]):
@@ -2366,10 +2363,12 @@ def _(arg: typing.Iterable[str]):
# types from `typing`. Instead, annotate with regular types
# or ABCs.
return "I annotated with a generic collection"
- self.assertTrue(str(exc.exception).startswith(msg_prefix +
- "._"
+ self.assertTrue(str(exc.exception).startswith(
+ "Invalid annotation for 'arg'."
+ ))
+ self.assertTrue(str(exc.exception).endswith(
+ 'typing.Iterable[str] is not a class.'
))
- self.assertTrue(str(exc.exception).endswith(msg_suffix))
def test_invalid_positional_argument(self):
@functools.singledispatch
diff --git a/Misc/NEWS.d/next/Library/2019-04-02-19-23-12.bpo-35252.VooTVv.rst b/Misc/NEWS.d/next/Library/2019-04-02-19-23-12.bpo-35252.VooTVv.rst
new file mode 100644
index 00000000000000..c8f3e7d5f5fb18
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-04-02-19-23-12.bpo-35252.VooTVv.rst
@@ -0,0 +1 @@
+Throw a TypeError instead of an AssertionError when using an invalid type annotation with singledispatch.
From ed48866c55b8e4ee14faa8b5ad97819e8e74c98b Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Mon, 20 May 2019 00:14:57 +0200
Subject: [PATCH 021/441] bpo-35134: Split traceback.h header (GH-13430)
Add new Include/cpython/traceback.h and Include/internal/traceback.h
header files.
---
Include/cpython/traceback.h | 22 +++++++
Include/frameobject.h | 1 -
Include/genobject.h | 2 +
Include/internal/pycore_traceback.h | 92 +++++++++++++++++++++++++++
Include/traceback.h | 99 ++---------------------------
Modules/_tracemalloc.c | 1 +
Modules/faulthandler.c | 1 +
Python/pylifecycle.c | 1 +
8 files changed, 123 insertions(+), 96 deletions(-)
create mode 100644 Include/cpython/traceback.h
create mode 100644 Include/internal/pycore_traceback.h
diff --git a/Include/cpython/traceback.h b/Include/cpython/traceback.h
new file mode 100644
index 00000000000000..746097daaf9400
--- /dev/null
+++ b/Include/cpython/traceback.h
@@ -0,0 +1,22 @@
+#ifndef Py_CPYTHON_TRACEBACK_H
+# error "this header file must not be included directly"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _traceback {
+ PyObject_HEAD
+ struct _traceback *tb_next;
+ struct _frame *tb_frame;
+ int tb_lasti;
+ int tb_lineno;
+} PyTracebackObject;
+
+PyAPI_FUNC(int) _Py_DisplaySourceLine(PyObject *, PyObject *, int, int);
+PyAPI_FUNC(void) _PyTraceback_Add(const char *, const char *, int);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/Include/frameobject.h b/Include/frameobject.h
index a95baf8867a360..3bad86a66f752d 100644
--- a/Include/frameobject.h
+++ b/Include/frameobject.h
@@ -1,4 +1,3 @@
-
/* Frame object interface */
#ifndef Py_LIMITED_API
diff --git a/Include/genobject.h b/Include/genobject.h
index 16b983339cc649..6755963f332f76 100644
--- a/Include/genobject.h
+++ b/Include/genobject.h
@@ -8,6 +8,8 @@
extern "C" {
#endif
+#include "pystate.h" /* _PyErr_StackItem */
+
struct _frame; /* Avoid including frameobject.h */
/* _PyGenObject_HEAD defines the initial segment of generator
diff --git a/Include/internal/pycore_traceback.h b/Include/internal/pycore_traceback.h
new file mode 100644
index 00000000000000..a96199bda5f9f3
--- /dev/null
+++ b/Include/internal/pycore_traceback.h
@@ -0,0 +1,92 @@
+#ifndef Py_INTERNAL_TRACEBACK_H
+#define Py_INTERNAL_TRACEBACK_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef Py_BUILD_CORE
+# error "this header requires Py_BUILD_CORE define"
+#endif
+
+#include "pystate.h" /* PyInterpreterState */
+
+/* Write the Python traceback into the file 'fd'. For example:
+
+ Traceback (most recent call first):
+ File "xxx", line xxx in
+ File "xxx", line xxx in
+ ...
+ File "xxx", line xxx in
+
+ This function is written for debug purpose only, to dump the traceback in
+ the worst case: after a segmentation fault, at fatal error, etc. That's why,
+ it is very limited. Strings are truncated to 100 characters and encoded to
+ ASCII with backslashreplace. It doesn't write the source code, only the
+ function name, filename and line number of each frame. Write only the first
+ 100 frames: if the traceback is truncated, write the line " ...".
+
+ This function is signal safe. */
+
+PyAPI_FUNC(void) _Py_DumpTraceback(
+ int fd,
+ PyThreadState *tstate);
+
+/* Write the traceback of all threads into the file 'fd'. current_thread can be
+ NULL.
+
+ Return NULL on success, or an error message on error.
+
+ This function is written for debug purpose only. It calls
+ _Py_DumpTraceback() for each thread, and so has the same limitations. It
+ only write the traceback of the first 100 threads: write "..." if there are
+ more threads.
+
+ If current_tstate is NULL, the function tries to get the Python thread state
+ of the current thread. It is not an error if the function is unable to get
+ the current Python thread state.
+
+ If interp is NULL, the function tries to get the interpreter state from
+ the current Python thread state, or from
+ _PyGILState_GetInterpreterStateUnsafe() in last resort.
+
+ It is better to pass NULL to interp and current_tstate, the function tries
+ different options to retrieve these informations.
+
+ This function is signal safe. */
+
+PyAPI_FUNC(const char*) _Py_DumpTracebackThreads(
+ int fd,
+ PyInterpreterState *interp,
+ PyThreadState *current_tstate);
+
+/* Write a Unicode object into the file descriptor fd. Encode the string to
+ ASCII using the backslashreplace error handler.
+
+ Do nothing if text is not a Unicode object. The function accepts Unicode
+ string which is not ready (PyUnicode_WCHAR_KIND).
+
+ This function is signal safe. */
+PyAPI_FUNC(void) _Py_DumpASCII(int fd, PyObject *text);
+
+/* Format an integer as decimal into the file descriptor fd.
+
+ This function is signal safe. */
+PyAPI_FUNC(void) _Py_DumpDecimal(
+ int fd,
+ unsigned long value);
+
+/* Format an integer as hexadecimal into the file descriptor fd with at least
+ width digits.
+
+ The maximum width is sizeof(unsigned long)*2 digits.
+
+ This function is signal safe. */
+PyAPI_FUNC(void) _Py_DumpHexadecimal(
+ int fd,
+ unsigned long value,
+ Py_ssize_t width);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !Py_INTERNAL_TRACEBACK_H */
diff --git a/Include/traceback.h b/Include/traceback.h
index b5874100f477a5..b451927fafa3a8 100644
--- a/Include/traceback.h
+++ b/Include/traceback.h
@@ -1,117 +1,26 @@
-
#ifndef Py_TRACEBACK_H
#define Py_TRACEBACK_H
#ifdef __cplusplus
extern "C" {
#endif
-#include "pystate.h"
-
struct _frame;
/* Traceback interface */
-#ifndef Py_LIMITED_API
-typedef struct _traceback {
- PyObject_HEAD
- struct _traceback *tb_next;
- struct _frame *tb_frame;
- int tb_lasti;
- int tb_lineno;
-} PyTracebackObject;
-#endif
PyAPI_FUNC(int) PyTraceBack_Here(struct _frame *);
PyAPI_FUNC(int) PyTraceBack_Print(PyObject *, PyObject *);
-#ifndef Py_LIMITED_API
-PyAPI_FUNC(int) _Py_DisplaySourceLine(PyObject *, PyObject *, int, int);
-PyAPI_FUNC(void) _PyTraceback_Add(const char *, const char *, int);
-#endif
/* Reveal traceback type so we can typecheck traceback objects */
PyAPI_DATA(PyTypeObject) PyTraceBack_Type;
#define PyTraceBack_Check(v) (Py_TYPE(v) == &PyTraceBack_Type)
-#ifndef Py_LIMITED_API
-/* Write the Python traceback into the file 'fd'. For example:
-
- Traceback (most recent call first):
- File "xxx", line xxx in
- File "xxx", line xxx in
- ...
- File "xxx", line xxx in
-
- This function is written for debug purpose only, to dump the traceback in
- the worst case: after a segmentation fault, at fatal error, etc. That's why,
- it is very limited. Strings are truncated to 100 characters and encoded to
- ASCII with backslashreplace. It doesn't write the source code, only the
- function name, filename and line number of each frame. Write only the first
- 100 frames: if the traceback is truncated, write the line " ...".
-
- This function is signal safe. */
-
-PyAPI_FUNC(void) _Py_DumpTraceback(
- int fd,
- PyThreadState *tstate);
-
-/* Write the traceback of all threads into the file 'fd'. current_thread can be
- NULL.
-
- Return NULL on success, or an error message on error.
-
- This function is written for debug purpose only. It calls
- _Py_DumpTraceback() for each thread, and so has the same limitations. It
- only write the traceback of the first 100 threads: write "..." if there are
- more threads.
-
- If current_tstate is NULL, the function tries to get the Python thread state
- of the current thread. It is not an error if the function is unable to get
- the current Python thread state.
-
- If interp is NULL, the function tries to get the interpreter state from
- the current Python thread state, or from
- _PyGILState_GetInterpreterStateUnsafe() in last resort.
-
- It is better to pass NULL to interp and current_tstate, the function tries
- different options to retrieve these informations.
-
- This function is signal safe. */
-
-PyAPI_FUNC(const char*) _Py_DumpTracebackThreads(
- int fd,
- PyInterpreterState *interp,
- PyThreadState *current_tstate);
-#endif /* !Py_LIMITED_API */
#ifndef Py_LIMITED_API
-
-/* Write a Unicode object into the file descriptor fd. Encode the string to
- ASCII using the backslashreplace error handler.
-
- Do nothing if text is not a Unicode object. The function accepts Unicode
- string which is not ready (PyUnicode_WCHAR_KIND).
-
- This function is signal safe. */
-PyAPI_FUNC(void) _Py_DumpASCII(int fd, PyObject *text);
-
-/* Format an integer as decimal into the file descriptor fd.
-
- This function is signal safe. */
-PyAPI_FUNC(void) _Py_DumpDecimal(
- int fd,
- unsigned long value);
-
-/* Format an integer as hexadecimal into the file descriptor fd with at least
- width digits.
-
- The maximum width is sizeof(unsigned long)*2 digits.
-
- This function is signal safe. */
-PyAPI_FUNC(void) _Py_DumpHexadecimal(
- int fd,
- unsigned long value,
- Py_ssize_t width);
-
-#endif /* !Py_LIMITED_API */
+# define Py_CPYTHON_TRACEBACK_H
+# include "cpython/traceback.h"
+# undef Py_CPYTHON_TRACEBACK_H
+#endif
#ifdef __cplusplus
}
diff --git a/Modules/_tracemalloc.c b/Modules/_tracemalloc.c
index c5d5671032ebf1..ee32ac29b7eea2 100644
--- a/Modules/_tracemalloc.c
+++ b/Modules/_tracemalloc.c
@@ -1,4 +1,5 @@
#include "Python.h"
+#include "pycore_traceback.h"
#include "hashtable.h"
#include "frameobject.h"
#include "pythread.h"
diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c
index 2083a03198febd..aa466c46ccff12 100644
--- a/Modules/faulthandler.c
+++ b/Modules/faulthandler.c
@@ -1,5 +1,6 @@
#include "Python.h"
#include "pycore_coreconfig.h"
+#include "pycore_traceback.h"
#include "pythread.h"
#include
#include
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index d29b293b79eb44..0781dc8046b106 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -13,6 +13,7 @@
#include "pycore_pylifecycle.h"
#include "pycore_pymem.h"
#include "pycore_pystate.h"
+#include "pycore_traceback.h"
#include "grammar.h"
#include "node.h"
#include "token.h"
From fd1e0e93b15af018184476ea0b3af0eabef37d89 Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Mon, 20 May 2019 02:22:32 +0200
Subject: [PATCH 022/441] bpo-35134: Register new traceback.h header files
(GH-13431)
Add new cpython/traceback.h and pycore_traceback.h header files to
Makefile.pre.in and PCbuild/ project.
---
Makefile.pre.in | 2 ++
PCbuild/pythoncore.vcxproj | 2 ++
PCbuild/pythoncore.vcxproj.filters | 6 ++++++
3 files changed, 10 insertions(+)
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 4924dedc357c72..9f70cd515ebaba 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -1059,6 +1059,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/cpython/pylifecycle.h \
$(srcdir)/Include/cpython/pymem.h \
$(srcdir)/Include/cpython/pystate.h \
+ $(srcdir)/Include/cpython/traceback.h \
$(srcdir)/Include/cpython/tupleobject.h \
$(srcdir)/Include/cpython/unicodeobject.h \
\
@@ -1078,6 +1079,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/internal/pycore_pylifecycle.h \
$(srcdir)/Include/internal/pycore_pymem.h \
$(srcdir)/Include/internal/pycore_pystate.h \
+ $(srcdir)/Include/internal/pycore_traceback.h \
$(srcdir)/Include/internal/pycore_tupleobject.h \
$(srcdir)/Include/internal/pycore_warnings.h \
$(DTRACE_HEADERS)
diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj
index a71fce6bb60977..df57adcfd6050c 100644
--- a/PCbuild/pythoncore.vcxproj
+++ b/PCbuild/pythoncore.vcxproj
@@ -135,6 +135,7 @@
+
@@ -169,6 +170,7 @@
+
diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters
index 9134646567954e..5515d9bedeb166 100644
--- a/PCbuild/pythoncore.vcxproj.filters
+++ b/PCbuild/pythoncore.vcxproj.filters
@@ -108,6 +108,9 @@
Include
+
+ Include
+
Include
@@ -210,6 +213,9 @@
Include
+
+ Include
+
Include
From 53d378c81286644138415cb56da52a7351e1a477 Mon Sep 17 00:00:00 2001
From: Zackery Spytz
Date: Sun, 19 May 2019 18:26:35 -0600
Subject: [PATCH 023/441] closes bpo-36951: Correct some types in the
type_members struct in typeobject.c. (GH-13403)
---
Objects/typeobject.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index bfbd320b1f54d8..c086f182aa958a 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -369,11 +369,11 @@ assign_version_tag(PyTypeObject *type)
static PyMemberDef type_members[] = {
{"__basicsize__", T_PYSSIZET, offsetof(PyTypeObject,tp_basicsize),READONLY},
{"__itemsize__", T_PYSSIZET, offsetof(PyTypeObject, tp_itemsize), READONLY},
- {"__flags__", T_LONG, offsetof(PyTypeObject, tp_flags), READONLY},
- {"__weakrefoffset__", T_LONG,
+ {"__flags__", T_ULONG, offsetof(PyTypeObject, tp_flags), READONLY},
+ {"__weakrefoffset__", T_PYSSIZET,
offsetof(PyTypeObject, tp_weaklistoffset), READONLY},
{"__base__", T_OBJECT, offsetof(PyTypeObject, tp_base), READONLY},
- {"__dictoffset__", T_LONG,
+ {"__dictoffset__", T_PYSSIZET,
offsetof(PyTypeObject, tp_dictoffset), READONLY},
{"__mro__", T_OBJECT, offsetof(PyTypeObject, tp_mro), READONLY},
{0}
From 6d965b39b7a486dd9e96a60b19ee92382d668299 Mon Sep 17 00:00:00 2001
From: Terry Jan Reedy
Date: Sun, 19 May 2019 22:52:22 -0400
Subject: [PATCH 024/441] bpo-36958: In IDLE, print exit message (GH-13435)
Print any argument other than None or int passed to SystemExit
or sys.exit().
---
Doc/library/idle.rst | 3 +++
Lib/idlelib/NEWS.txt | 6 ++++++
Lib/idlelib/help.html | 4 +++-
Lib/idlelib/run.py | 11 ++++++-----
.../IDLE/2019-05-19-22-02-22.bpo-36958.DZUC6G.rst | 2 ++
5 files changed, 20 insertions(+), 6 deletions(-)
create mode 100644 Misc/NEWS.d/next/IDLE/2019-05-19-22-02-22.bpo-36958.DZUC6G.rst
diff --git a/Doc/library/idle.rst b/Doc/library/idle.rst
index f511d64b550be6..c51cf19e97bd05 100644
--- a/Doc/library/idle.rst
+++ b/Doc/library/idle.rst
@@ -700,6 +700,9 @@ If ``sys`` is reset by user code, such as with ``importlib.reload(sys)``,
IDLE's changes are lost and input from the keyboard and output to the screen
will not work correctly.
+When user code raises SystemExit either directly or by calling sys.exit, IDLE
+returns to a Shell prompt instead of exiting.
+
User output in Shell
^^^^^^^^^^^^^^^^^^^^
diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt
index be855bc4671852..3f19ce73739630 100644
--- a/Lib/idlelib/NEWS.txt
+++ b/Lib/idlelib/NEWS.txt
@@ -3,6 +3,12 @@ Released on 2019-10-20?
======================================
+bpo-36958: Print any argument other than None or int passed to
+SystemExit or sys.exit().
+
+bpo-36807: When saving a file, call file.flush() and os.fsync()
+so bits are flushed to e.g. a USB drive.
+
bpo-36429: Fix starting IDLE with pyshell.
Add idlelib.pyshell alias at top; remove pyshell alias at bottom.
Remove obsolete __name__=='__main__' command.
diff --git a/Lib/idlelib/help.html b/Lib/idlelib/help.html
index 56f9ca503daac2..bc287d637ab738 100644
--- a/Lib/idlelib/help.html
+++ b/Lib/idlelib/help.html
@@ -659,6 +659,8 @@ Running user code
+When user code raises SystemExit either directly or by calling sys.exit, IDLE
+returns to a Shell prompt instead of exiting.
User output in Shell
@@ -941,7 +943,7 @@
Navigation
- Last updated on May 16, 2019.
+ Last updated on May 19, 2019.
Found a bug?
diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py
index 6fa373f2584c27..b4a2b54a33c850 100644
--- a/Lib/idlelib/run.py
+++ b/Lib/idlelib/run.py
@@ -474,15 +474,16 @@ def runcode(self, code):
exec(code, self.locals)
finally:
interruptable = False
- except SystemExit:
- # Scripts that raise SystemExit should just
- # return to the interactive prompt
- pass
+ except SystemExit as e:
+ if e.args: # SystemExit called with an argument.
+ ob = e.args[0]
+ if not isinstance(ob, (type(None), int)):
+ print('SystemExit: ' + str(ob), file=sys.stderr)
+ # Return to the interactive prompt.
except:
self.usr_exc_info = sys.exc_info()
if quitting:
exit()
- # even print a user code SystemExit exception, continue
print_exception()
jit = self.rpchandler.console.getvar("<
>")
if jit:
diff --git a/Misc/NEWS.d/next/IDLE/2019-05-19-22-02-22.bpo-36958.DZUC6G.rst b/Misc/NEWS.d/next/IDLE/2019-05-19-22-02-22.bpo-36958.DZUC6G.rst
new file mode 100644
index 00000000000000..c5a675d6781ae9
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2019-05-19-22-02-22.bpo-36958.DZUC6G.rst
@@ -0,0 +1,2 @@
+Print any argument other than None or int passed to SystemExit or
+sys.exit().
From 6d1c46746e17367caf8a24623cb5c9a9c4e3e036 Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Mon, 20 May 2019 11:02:00 +0200
Subject: [PATCH 025/441] bpo-36763: Fix Python preinitialization (GH-13432)
* Add _PyPreConfig.parse_argv
* Add _PyCoreConfig._config_init field and _PyCoreConfigInitEnum enum
type
* Initialization functions: reject preconfig=NULL and config=NULL
* Add config parameter to _PyCoreConfig_DecodeLocaleErr(): pass
config->argv to _Py_PreInitializeFromPyArgv(), to parse config
command line arguments in preinitialization.
* Add config parameter to _PyCoreConfig_SetString(). It now
preinitializes Python.
* _PyCoreConfig_SetPyArgv() now also preinitializes Python for wide
argv
* Fix _Py_PreInitializeFromCoreConfig(): don't pass args to
_Py_PreInitializeFromPyArgv() if config.parse_argv=0.
* Use "char * const *" and "wchar_t * const *" types for 'argv'
parameters and _PyArgv.argv.
* Add unit test on preinitialization from argv.
* _PyPreConfig.allocator type becomes int
* Add _PyPreConfig_InitFromPreConfig() and
_PyPreConfig_InitFromCoreConfig() helper functions
---
Include/cpython/coreconfig.h | 22 ++-
Include/cpython/pylifecycle.h | 4 +-
Include/internal/pycore_coreconfig.h | 10 +-
Lib/test/test_embed.py | 96 +++++++---
Programs/_testembed.c | 262 +++++++++++++++++++++++----
Python/coreconfig.c | 123 +++++++------
Python/preconfig.c | 80 +++++---
Python/pylifecycle.c | 57 ++++--
8 files changed, 476 insertions(+), 178 deletions(-)
diff --git a/Include/cpython/coreconfig.h b/Include/cpython/coreconfig.h
index a71f16171b7334..decfb70e7345c5 100644
--- a/Include/cpython/coreconfig.h
+++ b/Include/cpython/coreconfig.h
@@ -44,6 +44,10 @@ typedef struct {
int _config_version; /* Internal configuration version,
used for ABI compatibility */
+ /* Parse _Py_PreInitializeFromArgs() arguments?
+ See _PyCoreConfig.parse_argv */
+ int parse_argv;
+
/* If greater than 0, enable isolated mode: sys.path contains
neither the script's directory nor the user's site-packages directory.
@@ -111,8 +115,9 @@ typedef struct {
int dev_mode; /* Development mode. PYTHONDEVMODE, -X dev */
- /* Memory allocator: PYTHONMALLOC env var */
- PyMemAllocatorName allocator;
+ /* Memory allocator: PYTHONMALLOC env var.
+ See PyMemAllocatorName for valid values. */
+ int allocator;
} _PyPreConfig;
PyAPI_FUNC(void) _PyPreConfig_InitPythonConfig(_PyPreConfig *config);
@@ -121,9 +126,16 @@ PyAPI_FUNC(void) _PyPreConfig_InitIsolatedConfig(_PyPreConfig *config);
/* --- _PyCoreConfig ---------------------------------------------- */
+typedef enum {
+ _PyCoreConfig_INIT = 0,
+ _PyCoreConfig_INIT_PYTHON = 1,
+ _PyCoreConfig_INIT_ISOLATED = 2
+} _PyCoreConfigInitEnum;
+
typedef struct {
int _config_version; /* Internal configuration version,
used for ABI compatibility */
+ int _config_init; /* _PyCoreConfigInitEnum value */
int isolated; /* Isolated mode? see _PyPreConfig.isolated */
int use_environment; /* Use environment variables? see _PyPreConfig.use_environment */
@@ -401,19 +413,21 @@ PyAPI_FUNC(_PyInitError) _PyCoreConfig_InitPythonConfig(_PyCoreConfig *config);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_InitIsolatedConfig(_PyCoreConfig *config);
PyAPI_FUNC(void) _PyCoreConfig_Clear(_PyCoreConfig *);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetString(
+ _PyCoreConfig *config,
wchar_t **config_str,
const wchar_t *str);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_DecodeLocale(
+ _PyCoreConfig *config,
wchar_t **config_str,
const char *str);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_Read(_PyCoreConfig *config);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetArgv(
_PyCoreConfig *config,
Py_ssize_t argc,
- char **argv);
+ char * const *argv);
PyAPI_FUNC(_PyInitError) _PyCoreConfig_SetWideArgv(_PyCoreConfig *config,
Py_ssize_t argc,
- wchar_t **argv);
+ wchar_t * const *argv);
#ifdef __cplusplus
}
diff --git a/Include/cpython/pylifecycle.h b/Include/cpython/pylifecycle.h
index 8fc809d30d8278..1e1dabe59ff556 100644
--- a/Include/cpython/pylifecycle.h
+++ b/Include/cpython/pylifecycle.h
@@ -35,11 +35,11 @@ PyAPI_FUNC(_PyInitError) _Py_InitializeFromConfig(
PyAPI_FUNC(_PyInitError) _Py_InitializeFromArgs(
const _PyCoreConfig *config,
Py_ssize_t argc,
- char **argv);
+ char * const *argv);
PyAPI_FUNC(_PyInitError) _Py_InitializeFromWideArgs(
const _PyCoreConfig *config,
Py_ssize_t argc,
- wchar_t **argv);
+ wchar_t * const *argv);
PyAPI_FUNC(_PyInitError) _Py_InitializeMain(void);
PyAPI_FUNC(int) _Py_RunMain(void);
diff --git a/Include/internal/pycore_coreconfig.h b/Include/internal/pycore_coreconfig.h
index edde7b1d8d8817..324e0b82b90c60 100644
--- a/Include/internal/pycore_coreconfig.h
+++ b/Include/internal/pycore_coreconfig.h
@@ -63,8 +63,8 @@ PyAPI_FUNC(int) _PyWstrList_Extend(_PyWstrList *list,
typedef struct {
Py_ssize_t argc;
int use_bytes_argv;
- char **bytes_argv;
- wchar_t **wchar_argv;
+ char * const *bytes_argv;
+ wchar_t * const *wchar_argv;
} _PyArgv;
PyAPI_FUNC(_PyInitError) _PyArgv_AsWstrList(const _PyArgv *args,
@@ -121,6 +121,12 @@ PyAPI_FUNC(_PyInitError) _PyPreCmdline_Read(_PyPreCmdline *cmdline,
/* --- _PyPreConfig ----------------------------------------------- */
PyAPI_FUNC(void) _PyPreConfig_Init(_PyPreConfig *config);
+PyAPI_FUNC(void) _PyPreConfig_InitFromCoreConfig(
+ _PyPreConfig *config,
+ const _PyCoreConfig *coreconfig);
+PyAPI_FUNC(void) _PyPreConfig_InitFromPreConfig(
+ _PyPreConfig *config,
+ const _PyPreConfig *config2);
PyAPI_FUNC(void) _PyPreConfig_Copy(_PyPreConfig *config,
const _PyPreConfig *config2);
PyAPI_FUNC(PyObject*) _PyPreConfig_AsDict(const _PyPreConfig *config);
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index 5a5419dcfcf57e..5be179a266ba5d 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -17,6 +17,10 @@
PYMEM_ALLOCATOR_DEBUG = 2
PYMEM_ALLOCATOR_MALLOC = 3
+CONFIG_INIT = 0
+CONFIG_INIT_PYTHON = 1
+CONFIG_INIT_ISOLATED = 2
+
class EmbeddingTestsMixin:
def setUp(self):
@@ -280,11 +284,19 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
DEFAULT_PRE_CONFIG = {
'allocator': PYMEM_ALLOCATOR_NOT_SET,
+ 'parse_argv': 0,
'configure_locale': 1,
'coerce_c_locale': 0,
'coerce_c_locale_warn': 0,
'utf8_mode': 0,
}
+ if MS_WINDOWS:
+ DEFAULT_PRE_CONFIG.update({
+ 'legacy_windows_fs_encoding': 0,
+ })
+ PYTHON_PRE_CONFIG = dict(DEFAULT_PRE_CONFIG,
+ parse_argv=1,
+ )
ISOLATED_PRE_CONFIG = dict(DEFAULT_PRE_CONFIG,
configure_locale=0,
isolated=1,
@@ -292,8 +304,6 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
utf8_mode=0,
dev_mode=0,
)
- if MS_WINDOWS:
- ISOLATED_PRE_CONFIG['legacy_windows_fs_encoding'] = 0
COPY_PRE_CONFIG = [
'dev_mode',
@@ -302,6 +312,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
]
DEFAULT_CORE_CONFIG = {
+ '_config_init': CONFIG_INIT,
'isolated': 0,
'use_environment': 1,
'dev_mode': 0,
@@ -365,9 +376,6 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'_init_main': 1,
}
if MS_WINDOWS:
- DEFAULT_PRE_CONFIG.update({
- 'legacy_windows_fs_encoding': 0,
- })
DEFAULT_CORE_CONFIG.update({
'legacy_windows_stdio': 0,
})
@@ -439,13 +447,14 @@ def main_xoptions(self, xoptions_list):
def get_expected_config(self, expected_preconfig, expected, env, api,
add_path=None):
- if api == "python":
+ if api == CONFIG_INIT_PYTHON:
default_config = self.PYTHON_CORE_CONFIG
- elif api == "isolated":
+ elif api == CONFIG_INIT_ISOLATED:
default_config = self.ISOLATED_CORE_CONFIG
else:
default_config = self.DEFAULT_CORE_CONFIG
expected = dict(default_config, **expected)
+ expected['_config_init'] = api
code = textwrap.dedent('''
import json
@@ -519,9 +528,7 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
return expected
def check_pre_config(self, config, expected):
- pre_config = dict(config['pre_config'])
- core_config = dict(config['core_config'])
- self.assertEqual(pre_config, expected)
+ self.assertEqual(config['pre_config'], expected)
def check_core_config(self, config, expected):
core_config = dict(config['core_config'])
@@ -554,7 +561,7 @@ def check_global_config(self, config):
self.assertEqual(config['global_config'], expected)
def check_config(self, testname, expected_config=None, expected_preconfig=None,
- add_path=None, stderr=None, api="default"):
+ add_path=None, stderr=None, api=CONFIG_INIT):
env = dict(os.environ)
# Remove PYTHON* environment variables to get deterministic environment
for key in list(env):
@@ -565,8 +572,10 @@ def check_config(self, testname, expected_config=None, expected_preconfig=None,
env['PYTHONCOERCECLOCALE'] = '0'
env['PYTHONUTF8'] = '0'
- if api == "isolated":
+ if api == CONFIG_INIT_ISOLATED:
default_preconfig = self.ISOLATED_PRE_CONFIG
+ elif api == CONFIG_INIT_PYTHON:
+ default_preconfig = self.PYTHON_PRE_CONFIG
else:
default_preconfig = self.DEFAULT_PRE_CONFIG
if expected_preconfig is None:
@@ -719,7 +728,38 @@ def test_init_dev_mode(self):
'dev_mode': 1,
'warnoptions': ['default'],
}
- self.check_config("init_dev_mode", config, preconfig, api="python")
+ self.check_config("init_dev_mode", config, preconfig,
+ api=CONFIG_INIT_PYTHON)
+
+ def test_preinit_parse_argv(self):
+ # Pre-initialize implicitly using argv: make sure that -X dev
+ # is used to configure the allocation in preinitialization
+ preconfig = {
+ 'allocator': PYMEM_ALLOCATOR_DEBUG,
+ }
+ config = {
+ 'argv': ['script.py'],
+ 'run_filename': 'script.py',
+ 'dev_mode': 1,
+ 'faulthandler': 1,
+ 'warnoptions': ['default'],
+ 'xoptions': ['dev'],
+ }
+ self.check_config("preinit_parse_argv", config, preconfig,
+ api=CONFIG_INIT_PYTHON)
+
+ def test_preinit_dont_parse_argv(self):
+ # -X dev must be ignored by isolated preconfiguration
+ preconfig = {
+ 'isolated': 0,
+ }
+ config = {
+ 'argv': ["python3", "-E", "-I",
+ "-X", "dev", "-X", "utf8", "script.py"],
+ 'isolated': 0,
+ }
+ self.check_config("preinit_dont_parse_argv", config, preconfig,
+ api=CONFIG_INIT_ISOLATED)
def test_init_isolated_flag(self):
config = {
@@ -727,7 +767,7 @@ def test_init_isolated_flag(self):
'use_environment': 0,
'user_site_directory': 0,
}
- self.check_config("init_isolated_flag", config, api="python")
+ self.check_config("init_isolated_flag", config, api=CONFIG_INIT_PYTHON)
def test_preinit_isolated1(self):
# _PyPreConfig.isolated=1, _PyCoreConfig.isolated not set
@@ -747,25 +787,30 @@ def test_preinit_isolated2(self):
}
self.check_config("preinit_isolated2", config)
+ def test_preinit_isolated_config(self):
+ self.check_config("preinit_isolated_config", api=CONFIG_INIT_ISOLATED)
+
def test_init_isolated_config(self):
- self.check_config("init_isolated_config", api="isolated")
+ self.check_config("init_isolated_config", api=CONFIG_INIT_ISOLATED)
def test_init_python_config(self):
- self.check_config("init_python_config", api="python")
+ self.check_config("init_python_config", api=CONFIG_INIT_PYTHON)
def test_init_dont_configure_locale(self):
# _PyPreConfig.configure_locale=0
preconfig = {
'configure_locale': 0,
}
- self.check_config("init_dont_configure_locale", {}, preconfig, api="python")
+ self.check_config("init_dont_configure_locale", {}, preconfig,
+ api=CONFIG_INIT_PYTHON)
def test_init_read_set(self):
core_config = {
'program_name': './init_read_set',
'executable': 'my_executable',
}
- self.check_config("init_read_set", core_config, api="python",
+ self.check_config("init_read_set", core_config,
+ api=CONFIG_INIT_PYTHON,
add_path="init_read_set_path")
def test_init_run_main(self):
@@ -777,7 +822,8 @@ def test_init_run_main(self):
'run_command': code + '\n',
'parse_argv': 1,
}
- self.check_config("init_run_main", core_config, api="python")
+ self.check_config("init_run_main", core_config,
+ api=CONFIG_INIT_PYTHON)
def test_init_main(self):
code = ('import _testinternalcapi, json; '
@@ -789,7 +835,8 @@ def test_init_main(self):
'parse_argv': 1,
'_init_main': 0,
}
- self.check_config("init_main", core_config, api="python",
+ self.check_config("init_main", core_config,
+ api=CONFIG_INIT_PYTHON,
stderr="Run Python code before _Py_InitializeMain")
def test_init_parse_argv(self):
@@ -800,15 +847,20 @@ def test_init_parse_argv(self):
'run_command': 'pass\n',
'use_environment': 0,
}
- self.check_config("init_parse_argv", core_config, api="python")
+ self.check_config("init_parse_argv", core_config,
+ api=CONFIG_INIT_PYTHON)
def test_init_dont_parse_argv(self):
+ pre_config = {
+ 'parse_argv': 0,
+ }
core_config = {
'parse_argv': 0,
'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'],
'program_name': './argv0',
}
- self.check_config("init_dont_parse_argv", core_config, api="python")
+ self.check_config("init_dont_parse_argv", core_config, pre_config,
+ api=CONFIG_INIT_PYTHON)
if __name__ == "__main__":
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index f1bb731dcba2b7..3dabf66de15afa 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -527,7 +527,10 @@ static int check_init_parse_argv(int parse_argv)
_PyInitError err;
_PyCoreConfig config;
- _PyCoreConfig_InitPythonConfig(&config);
+ err = _PyCoreConfig_InitPythonConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ _Py_ExitInitError(err);
+ }
static wchar_t* argv[] = {
L"./argv0",
@@ -565,7 +568,7 @@ static int test_init_dont_parse_argv(void)
}
-static void test_init_env_putenvs(void)
+static void set_all_env_vars(void)
{
putenv("PYTHONHASHSEED=42");
putenv("PYTHONMALLOC=malloc");
@@ -596,7 +599,7 @@ static int test_init_env(void)
{
/* Test initialization from environment variables */
Py_IgnoreEnvironmentFlag = 0;
- test_init_env_putenvs();
+ set_all_env_vars();
_testembed_Py_Initialize();
dump_config();
Py_Finalize();
@@ -604,9 +607,9 @@ static int test_init_env(void)
}
-static void set_all_env_vars(void)
+static void set_all_env_vars_dev_mode(void)
{
- test_init_env_putenvs();
+ set_all_env_vars();
putenv("PYTHONMALLOC=");
putenv("PYTHONFAULTHANDLER=");
putenv("PYTHONDEVMODE=1");
@@ -617,7 +620,7 @@ static int test_init_env_dev_mode(void)
{
/* Test initialization from environment variables */
Py_IgnoreEnvironmentFlag = 0;
- set_all_env_vars();
+ set_all_env_vars_dev_mode();
_testembed_Py_Initialize();
dump_config();
Py_Finalize();
@@ -629,7 +632,7 @@ static int test_init_env_dev_mode_alloc(void)
{
/* Test initialization from environment variables */
Py_IgnoreEnvironmentFlag = 0;
- set_all_env_vars();
+ set_all_env_vars_dev_mode();
putenv("PYTHONMALLOC=malloc");
_testembed_Py_Initialize();
dump_config();
@@ -644,7 +647,10 @@ static int test_init_isolated_flag(void)
/* Test _PyCoreConfig.isolated=1 */
_PyCoreConfig config;
- _PyCoreConfig_InitPythonConfig(&config);
+ err = _PyCoreConfig_InitPythonConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ _Py_ExitInitError(err);
+ }
Py_IsolatedFlag = 0;
config.isolated = 1;
@@ -727,6 +733,107 @@ static int test_preinit_isolated2(void)
}
+static int test_preinit_dont_parse_argv(void)
+{
+ _PyInitError err;
+
+ _PyPreConfig preconfig;
+ _PyPreConfig_InitIsolatedConfig(&preconfig);
+
+ preconfig.isolated = 0;
+
+ /* -X dev must be ignored by isolated preconfiguration */
+ wchar_t *argv[] = {L"python3",
+ L"-E",
+ L"-I",
+ L"-X", L"dev",
+ L"-X", L"utf8",
+ L"script.py"};
+ err = _Py_PreInitializeFromWideArgs(&preconfig, Py_ARRAY_LENGTH(argv), argv);
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
+
+ _PyCoreConfig config;
+
+ err = _PyCoreConfig_InitIsolatedConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
+
+ config.isolated = 0;
+
+ /* Pre-initialize implicitly using argv: make sure that -X dev
+ is used to configure the allocation in preinitialization */
+ err = _PyCoreConfig_SetWideArgv(&config, Py_ARRAY_LENGTH(argv), argv);
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
+
+ err = _PyCoreConfig_SetString(&config, &config.program_name,
+ L"./_testembed");
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
+
+ err = _Py_InitializeFromConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
+ _PyCoreConfig_Clear(&config);
+
+ dump_config();
+ Py_Finalize();
+ return 0;
+
+failed:
+ _PyCoreConfig_Clear(&config);
+ _Py_ExitInitError(err);
+}
+
+
+static int test_preinit_parse_argv(void)
+{
+ _PyInitError err;
+ _PyCoreConfig config;
+
+ err = _PyCoreConfig_InitPythonConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
+
+ /* Pre-initialize implicitly using argv: make sure that -X dev
+ is used to configure the allocation in preinitialization */
+ wchar_t *argv[] = {L"python3", L"-X", L"dev", L"script.py"};
+ err = _PyCoreConfig_SetWideArgv(&config, Py_ARRAY_LENGTH(argv), argv);
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
+
+ err = _PyCoreConfig_SetString(&config, &config.program_name,
+ L"./_testembed");
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
+
+ err = _Py_InitializeFromConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
+ _PyCoreConfig_Clear(&config);
+
+ dump_config();
+ Py_Finalize();
+ return 0;
+
+failed:
+ _PyCoreConfig_Clear(&config);
+ _Py_ExitInitError(err);
+}
+
+
+
+
static void set_all_global_config_variables(void)
{
Py_IsolatedFlag = 0;
@@ -749,9 +856,10 @@ static void set_all_global_config_variables(void)
}
-static int test_init_isolated_config(void)
+static int check_preinit_isolated_config(int preinit)
{
_PyInitError err;
+ _PyPreConfig *rt_preconfig;
/* environment variables must be ignored */
set_all_env_vars();
@@ -759,17 +867,19 @@ static int test_init_isolated_config(void)
/* global configuration variables must be ignored */
set_all_global_config_variables();
- _PyPreConfig preconfig;
- _PyPreConfig_InitIsolatedConfig(&preconfig);
+ if (preinit) {
+ _PyPreConfig preconfig;
+ _PyPreConfig_InitIsolatedConfig(&preconfig);
- err = _Py_PreInitialize(&preconfig);
- if (_PyInitError_Failed(err)) {
- _Py_ExitInitError(err);
- }
+ err = _Py_PreInitialize(&preconfig);
+ if (_PyInitError_Failed(err)) {
+ _Py_ExitInitError(err);
+ }
- _PyPreConfig *rt_preconfig = &_PyRuntime.preconfig;
- assert(rt_preconfig->isolated == 1);
- assert(rt_preconfig->use_environment == 0);
+ rt_preconfig = &_PyRuntime.preconfig;
+ assert(rt_preconfig->isolated == 1);
+ assert(rt_preconfig->use_environment == 0);
+ }
_PyCoreConfig config;
err = _PyCoreConfig_InitIsolatedConfig(&config);
@@ -782,13 +892,30 @@ static int test_init_isolated_config(void)
if (_PyInitError_Failed(err)) {
_Py_ExitInitError(err);
}
+
+ rt_preconfig = &_PyRuntime.preconfig;
+ assert(rt_preconfig->isolated == 1);
+ assert(rt_preconfig->use_environment == 0);
+
dump_config();
Py_Finalize();
return 0;
}
-static int test_init_python_config(void)
+static int test_preinit_isolated_config(void)
+{
+ return check_preinit_isolated_config(1);
+}
+
+
+static int test_init_isolated_config(void)
+{
+ return check_preinit_isolated_config(0);
+}
+
+
+static int check_init_python_config(int preinit)
{
_PyInitError err;
@@ -805,12 +932,14 @@ static int test_init_python_config(void)
Py_LegacyWindowsStdioFlag = 1;
#endif
- _PyPreConfig preconfig;
- _PyPreConfig_InitPythonConfig(&preconfig);
+ if (preinit) {
+ _PyPreConfig preconfig;
+ _PyPreConfig_InitPythonConfig(&preconfig);
- err = _Py_PreInitialize(&preconfig);
- if (_PyInitError_Failed(err)) {
- _Py_ExitInitError(err);
+ err = _Py_PreInitialize(&preconfig);
+ if (_PyInitError_Failed(err)) {
+ _Py_ExitInitError(err);
+ }
}
_PyCoreConfig config;
@@ -830,6 +959,18 @@ static int test_init_python_config(void)
}
+static int test_preinit_python_config(void)
+{
+ return check_init_python_config(1);
+}
+
+
+static int test_init_python_config(void)
+{
+ return check_init_python_config(0);
+}
+
+
static int test_init_dont_configure_locale(void)
{
_PyInitError err;
@@ -846,7 +987,10 @@ static int test_init_dont_configure_locale(void)
}
_PyCoreConfig config;
- _PyCoreConfig_InitPythonConfig(&config);
+ err = _PyCoreConfig_InitPythonConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ _Py_ExitInitError(err);
+ }
config.program_name = L"./_testembed";
err = _Py_InitializeFromConfig(&config);
if (_PyInitError_Failed(err)) {
@@ -861,13 +1005,17 @@ static int test_init_dont_configure_locale(void)
static int test_init_dev_mode(void)
{
+ _PyInitError err;
_PyCoreConfig config;
- _PyCoreConfig_InitPythonConfig(&config);
+ err = _PyCoreConfig_InitPythonConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ _Py_ExitInitError(err);
+ }
putenv("PYTHONFAULTHANDLER=");
putenv("PYTHONMALLOC=");
config.dev_mode = 1;
config.program_name = L"./_testembed";
- _PyInitError err = _Py_InitializeFromConfig(&config);
+ err = _Py_InitializeFromConfig(&config);
if (_PyInitError_Failed(err)) {
_Py_ExitInitError(err);
}
@@ -881,9 +1029,13 @@ static int test_init_read_set(void)
{
_PyInitError err;
_PyCoreConfig config;
- _PyCoreConfig_InitPythonConfig(&config);
+ err = _PyCoreConfig_InitPythonConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ _Py_ExitInitError(err);
+ }
- err = _PyCoreConfig_DecodeLocale(&config.program_name, "./init_read_set");
+ err = _PyCoreConfig_DecodeLocale(&config, &config.program_name,
+ "./init_read_set");
if (_PyInitError_Failed(err)) {
goto fail;
}
@@ -900,7 +1052,7 @@ static int test_init_read_set(void)
}
/* override executable computed by _PyCoreConfig_Read() */
- err = _PyCoreConfig_SetString(&config.executable, L"my_executable");
+ err = _PyCoreConfig_SetString(&config, &config.executable, L"my_executable");
if (_PyInitError_Failed(err)) {
goto fail;
}
@@ -937,11 +1089,15 @@ static void configure_init_main(_PyCoreConfig *config)
static int test_init_run_main(void)
{
+ _PyInitError err;
_PyCoreConfig config;
- _PyCoreConfig_InitPythonConfig(&config);
+ err = _PyCoreConfig_InitPythonConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ _Py_ExitInitError(err);
+ }
configure_init_main(&config);
- _PyInitError err = _Py_InitializeFromConfig(&config);
+ err = _Py_InitializeFromConfig(&config);
if (_PyInitError_Failed(err)) {
_Py_ExitInitError(err);
}
@@ -952,12 +1108,17 @@ static int test_init_run_main(void)
static int test_init_main(void)
{
+ _PyInitError err;
_PyCoreConfig config;
- _PyCoreConfig_InitPythonConfig(&config);
+
+ err = _PyCoreConfig_InitPythonConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ _Py_ExitInitError(err);
+ }
configure_init_main(&config);
config._init_main = 0;
- _PyInitError err = _Py_InitializeFromConfig(&config);
+ err = _Py_InitializeFromConfig(&config);
if (_PyInitError_Failed(err)) {
_Py_ExitInitError(err);
}
@@ -982,23 +1143,40 @@ static int test_init_main(void)
static int test_run_main(void)
{
+ _PyInitError err;
_PyCoreConfig config;
- _PyCoreConfig_InitPythonConfig(&config);
+
+ err = _PyCoreConfig_InitPythonConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
wchar_t *argv[] = {L"python3", L"-c",
(L"import sys; "
L"print(f'_Py_RunMain(): sys.argv={sys.argv}')"),
L"arg2"};
- config.argv.length = Py_ARRAY_LENGTH(argv);
- config.argv.items = argv;
- config.program_name = L"./python3";
+ err = _PyCoreConfig_SetWideArgv(&config, Py_ARRAY_LENGTH(argv), argv);
+ if (_PyInitError_Failed(err)) {
+ goto failed;
+ }
- _PyInitError err = _Py_InitializeFromConfig(&config);
+ err = _PyCoreConfig_SetString(&config, &config.program_name,
+ L"./python3");
if (_PyInitError_Failed(err)) {
- _Py_ExitInitError(err);
+ goto failed;
+ }
+
+ err = _Py_InitializeFromConfig(&config);
+ if (_PyInitError_Failed(err)) {
+ goto failed;
}
+ _PyCoreConfig_Clear(&config);
return _Py_RunMain();
+
+failed:
+ _PyCoreConfig_Clear(&config);
+ _Py_ExitInitError(err);
}
@@ -1039,10 +1217,14 @@ static struct TestCase TestCases[] = {
{ "init_dont_configure_locale", test_init_dont_configure_locale },
{ "init_dev_mode", test_init_dev_mode },
{ "init_isolated_flag", test_init_isolated_flag },
+ { "preinit_isolated_config", test_preinit_isolated_config },
{ "init_isolated_config", test_init_isolated_config },
+ { "preinit_python_config", test_preinit_python_config },
{ "init_python_config", test_init_python_config },
{ "preinit_isolated1", test_preinit_isolated1 },
{ "preinit_isolated2", test_preinit_isolated2 },
+ { "preinit_parse_argv", test_preinit_parse_argv },
+ { "preinit_dont_parse_argv", test_preinit_dont_parse_argv },
{ "init_read_set", test_init_read_set },
{ "init_run_main", test_init_run_main },
{ "init_main", test_init_main },
diff --git a/Python/coreconfig.c b/Python/coreconfig.c
index 470bda870288b5..958845e488d7f2 100644
--- a/Python/coreconfig.c
+++ b/Python/coreconfig.c
@@ -551,6 +551,7 @@ _PyCoreConfig_Init(_PyCoreConfig *config)
memset(config, 0, sizeof(*config));
config->_config_version = _Py_CONFIG_VERSION;
+ config->_config_init = (int)_PyCoreConfig_INIT;
config->isolated = -1;
config->use_environment = -1;
config->dev_mode = -1;
@@ -612,6 +613,7 @@ _PyCoreConfig_InitPythonConfig(_PyCoreConfig *config)
{
_PyCoreConfig_InitDefaults(config);
+ config->_config_init = (int)_PyCoreConfig_INIT_PYTHON;
config->configure_c_stdio = 1;
config->parse_argv = 1;
@@ -624,6 +626,7 @@ _PyCoreConfig_InitIsolatedConfig(_PyCoreConfig *config)
{
_PyCoreConfig_InitDefaults(config);
+ config->_config_init = (int)_PyCoreConfig_INIT_ISOLATED;
config->isolated = 1;
config->use_environment = 0;
config->user_site_directory = 0;
@@ -643,8 +646,14 @@ _PyCoreConfig_InitIsolatedConfig(_PyCoreConfig *config)
/* Copy str into *config_str (duplicate the string) */
_PyInitError
-_PyCoreConfig_SetString(wchar_t **config_str, const wchar_t *str)
+_PyCoreConfig_SetString(_PyCoreConfig *config, wchar_t **config_str,
+ const wchar_t *str)
{
+ _PyInitError err = _Py_PreInitializeFromCoreConfig(config, NULL);
+ if (_Py_INIT_FAILED(err)) {
+ return err;
+ }
+
wchar_t *str2;
if (str != NULL) {
str2 = _PyMem_RawWcsdup(str);
@@ -662,10 +671,10 @@ _PyCoreConfig_SetString(wchar_t **config_str, const wchar_t *str)
static _PyInitError
-_PyCoreConfig_DecodeLocaleErr(wchar_t **config_str, const char *str,
- const char *decode_err_msg)
+_PyCoreConfig_DecodeLocaleErr(_PyCoreConfig *config, wchar_t **config_str,
+ const char *str, const char *decode_err_msg)
{
- _PyInitError err = _Py_PreInitialize(NULL);
+ _PyInitError err = _Py_PreInitializeFromCoreConfig(config, NULL);
if (_Py_INIT_FAILED(err)) {
return err;
}
@@ -692,17 +701,18 @@ _PyCoreConfig_DecodeLocaleErr(wchar_t **config_str, const char *str,
}
-#define CONFIG_DECODE_LOCALE(config_str, str, NAME) \
- _PyCoreConfig_DecodeLocaleErr(config_str, str, "cannot decode " NAME)
+#define CONFIG_DECODE_LOCALE(config, config_str, str, NAME) \
+ _PyCoreConfig_DecodeLocaleErr(config, config_str, str, "cannot decode " NAME)
/* Decode str using Py_DecodeLocale() and set the result into *config_str.
Pre-initialize Python if needed to ensure that encodings are properly
configured. */
_PyInitError
-_PyCoreConfig_DecodeLocale(wchar_t **config_str, const char *str)
+_PyCoreConfig_DecodeLocale(_PyCoreConfig *config, wchar_t **config_str,
+ const char *str)
{
- return CONFIG_DECODE_LOCALE(config_str, str, "string");
+ return CONFIG_DECODE_LOCALE(config, config_str, str, "string");
}
@@ -715,7 +725,7 @@ _PyCoreConfig_Copy(_PyCoreConfig *config, const _PyCoreConfig *config2)
#define COPY_ATTR(ATTR) config->ATTR = config2->ATTR
#define COPY_WSTR_ATTR(ATTR) \
do { \
- err = _PyCoreConfig_SetString(&config->ATTR, config2->ATTR); \
+ err = _PyCoreConfig_SetString(config, &config->ATTR, config2->ATTR); \
if (_Py_INIT_FAILED(err)) { \
return err; \
} \
@@ -727,6 +737,7 @@ _PyCoreConfig_Copy(_PyCoreConfig *config, const _PyCoreConfig *config2)
} \
} while (0)
+ COPY_ATTR(_config_init);
COPY_ATTR(isolated);
COPY_ATTR(use_environment);
COPY_ATTR(dev_mode);
@@ -829,6 +840,7 @@ _PyCoreConfig_AsDict(const _PyCoreConfig *config)
#define SET_ITEM_WSTRLIST(LIST) \
SET_ITEM(#LIST, _PyWstrList_AsList(&config->LIST))
+ SET_ITEM_INT(_config_init);
SET_ITEM_INT(isolated);
SET_ITEM_INT(use_environment);
SET_ITEM_INT(dev_mode);
@@ -910,7 +922,7 @@ _PyCoreConfig_GetEnv(const _PyCoreConfig *config, const char *name)
Return 0 on success, but *dest can be NULL.
Return -1 on memory allocation failure. Return -2 on decoding error. */
static _PyInitError
-_PyCoreConfig_GetEnvDup(const _PyCoreConfig *config,
+_PyCoreConfig_GetEnvDup(_PyCoreConfig *config,
wchar_t **dest,
wchar_t *wname, char *name,
const char *decode_err_msg)
@@ -930,7 +942,7 @@ _PyCoreConfig_GetEnvDup(const _PyCoreConfig *config,
return _Py_INIT_OK();
}
- return _PyCoreConfig_SetString(dest, var);
+ return _PyCoreConfig_SetString(config, dest, var);
#else
const char *var = getenv(name);
if (!var || var[0] == '\0') {
@@ -938,7 +950,7 @@ _PyCoreConfig_GetEnvDup(const _PyCoreConfig *config,
return _Py_INIT_OK();
}
- return _PyCoreConfig_DecodeLocaleErr(dest, var, decode_err_msg);
+ return _PyCoreConfig_DecodeLocaleErr(config, dest, var, decode_err_msg);
#endif
}
@@ -1053,7 +1065,7 @@ config_init_program_name(_PyCoreConfig *config)
script. */
const char *p = _PyCoreConfig_GetEnv(config, "PYTHONEXECUTABLE");
if (p != NULL) {
- err = CONFIG_DECODE_LOCALE(&config->program_name, p,
+ err = CONFIG_DECODE_LOCALE(config, &config->program_name, p,
"PYTHONEXECUTABLE environment variable");
if (_Py_INIT_FAILED(err)) {
return err;
@@ -1067,7 +1079,8 @@ config_init_program_name(_PyCoreConfig *config)
/* Used by Mac/Tools/pythonw.c to forward
* the argv0 of the stub executable
*/
- err = CONFIG_DECODE_LOCALE(&config->program_name, pyvenv_launcher,
+ err = CONFIG_DECODE_LOCALE(config,
+ &config->program_name, pyvenv_launcher,
"__PYVENV_LAUNCHER__ environment variable");
if (_Py_INIT_FAILED(err)) {
return err;
@@ -1094,7 +1107,8 @@ config_init_program_name(_PyCoreConfig *config)
#else
const wchar_t *default_program_name = L"python3";
#endif
- err = _PyCoreConfig_SetString(&config->program_name, default_program_name);
+ err = _PyCoreConfig_SetString(config, &config->program_name,
+ default_program_name);
if (_Py_INIT_FAILED(err)) {
return err;
}
@@ -1109,7 +1123,8 @@ config_init_executable(_PyCoreConfig *config)
/* If Py_SetProgramFullPath() was called, use its value */
const wchar_t *program_full_path = _Py_path_config.program_full_path;
if (program_full_path != NULL) {
- _PyInitError err = _PyCoreConfig_SetString(&config->executable,
+ _PyInitError err = _PyCoreConfig_SetString(config,
+ &config->executable,
program_full_path);
if (_Py_INIT_FAILED(err)) {
return err;
@@ -1135,7 +1150,7 @@ config_init_home(_PyCoreConfig *config)
/* If Py_SetPythonHome() was called, use its value */
wchar_t *home = _Py_path_config.home;
if (home) {
- _PyInitError err = _PyCoreConfig_SetString(&config->home, home);
+ _PyInitError err = _PyCoreConfig_SetString(config, &config->home, home);
if (_Py_INIT_FAILED(err)) {
return err;
}
@@ -1392,14 +1407,14 @@ config_get_stdio_errors(const _PyCoreConfig *config)
static _PyInitError
-config_get_locale_encoding(wchar_t **locale_encoding)
+config_get_locale_encoding(_PyCoreConfig *config, wchar_t **locale_encoding)
{
#ifdef MS_WINDOWS
char encoding[20];
PyOS_snprintf(encoding, sizeof(encoding), "cp%u", GetACP());
- return _PyCoreConfig_DecodeLocale(locale_encoding, encoding);
+ return _PyCoreConfig_DecodeLocale(config, locale_encoding, encoding);
#elif defined(_Py_FORCE_UTF8_LOCALE)
- return _PyCoreConfig_SetString(locale_encoding, L"utf-8");
+ return _PyCoreConfig_SetString(config, locale_encoding, L"utf-8");
#else
const char *encoding = nl_langinfo(CODESET);
if (!encoding || encoding[0] == '\0') {
@@ -1407,7 +1422,8 @@ config_get_locale_encoding(wchar_t **locale_encoding)
"nl_langinfo(CODESET) failed");
}
/* nl_langinfo(CODESET) is decoded by Py_DecodeLocale() */
- return CONFIG_DECODE_LOCALE(locale_encoding, encoding,
+ return CONFIG_DECODE_LOCALE(config,
+ locale_encoding, encoding,
"nl_langinfo(CODESET)");
#endif
}
@@ -1422,7 +1438,7 @@ config_init_stdio_encoding(_PyCoreConfig *config,
/* If Py_SetStandardStreamEncoding() have been called, use these
parameters. */
if (config->stdio_encoding == NULL && _Py_StandardStreamEncoding != NULL) {
- err = CONFIG_DECODE_LOCALE(&config->stdio_encoding,
+ err = CONFIG_DECODE_LOCALE(config, &config->stdio_encoding,
_Py_StandardStreamEncoding,
"_Py_StandardStreamEncoding");
if (_Py_INIT_FAILED(err)) {
@@ -1431,7 +1447,7 @@ config_init_stdio_encoding(_PyCoreConfig *config,
}
if (config->stdio_errors == NULL && _Py_StandardStreamErrors != NULL) {
- err = CONFIG_DECODE_LOCALE(&config->stdio_errors,
+ err = CONFIG_DECODE_LOCALE(config, &config->stdio_errors,
_Py_StandardStreamErrors,
"_Py_StandardStreamErrors");
if (_Py_INIT_FAILED(err)) {
@@ -1463,7 +1479,7 @@ config_init_stdio_encoding(_PyCoreConfig *config,
/* Does PYTHONIOENCODING contain an encoding? */
if (pythonioencoding[0]) {
if (config->stdio_encoding == NULL) {
- err = CONFIG_DECODE_LOCALE(&config->stdio_encoding,
+ err = CONFIG_DECODE_LOCALE(config, &config->stdio_encoding,
pythonioencoding,
"PYTHONIOENCODING environment variable");
if (_Py_INIT_FAILED(err)) {
@@ -1482,7 +1498,7 @@ config_init_stdio_encoding(_PyCoreConfig *config,
}
if (config->stdio_errors == NULL && errors != NULL) {
- err = CONFIG_DECODE_LOCALE(&config->stdio_errors,
+ err = CONFIG_DECODE_LOCALE(config, &config->stdio_errors,
errors,
"PYTHONIOENCODING environment variable");
if (_Py_INIT_FAILED(err)) {
@@ -1497,13 +1513,13 @@ config_init_stdio_encoding(_PyCoreConfig *config,
/* UTF-8 Mode uses UTF-8/surrogateescape */
if (preconfig->utf8_mode) {
if (config->stdio_encoding == NULL) {
- err = _PyCoreConfig_SetString(&config->stdio_encoding, L"utf-8");
+ err = _PyCoreConfig_SetString(config, &config->stdio_encoding, L"utf-8");
if (_Py_INIT_FAILED(err)) {
return err;
}
}
if (config->stdio_errors == NULL) {
- err = _PyCoreConfig_SetString(&config->stdio_errors,
+ err = _PyCoreConfig_SetString(config, &config->stdio_errors,
L"surrogateescape");
if (_Py_INIT_FAILED(err)) {
return err;
@@ -1513,7 +1529,7 @@ config_init_stdio_encoding(_PyCoreConfig *config,
/* Choose the default error handler based on the current locale. */
if (config->stdio_encoding == NULL) {
- err = config_get_locale_encoding(&config->stdio_encoding);
+ err = config_get_locale_encoding(config, &config->stdio_encoding);
if (_Py_INIT_FAILED(err)) {
return err;
}
@@ -1522,7 +1538,7 @@ config_init_stdio_encoding(_PyCoreConfig *config,
const wchar_t *errors = config_get_stdio_errors(config);
assert(errors != NULL);
- err = _PyCoreConfig_SetString(&config->stdio_errors, errors);
+ err = _PyCoreConfig_SetString(config, &config->stdio_errors, errors);
if (_Py_INIT_FAILED(err)) {
return err;
}
@@ -1539,34 +1555,35 @@ config_init_fs_encoding(_PyCoreConfig *config, const _PyPreConfig *preconfig)
if (config->filesystem_encoding == NULL) {
#ifdef _Py_FORCE_UTF8_FS_ENCODING
- err = _PyCoreConfig_SetString(&config->filesystem_encoding, L"utf-8");
+ err = _PyCoreConfig_SetString(config, &config->filesystem_encoding, L"utf-8");
#else
#ifdef MS_WINDOWS
if (preconfig->legacy_windows_fs_encoding) {
/* Legacy Windows filesystem encoding: mbcs/replace */
- err = _PyCoreConfig_SetString(&config->filesystem_encoding,
+ err = _PyCoreConfig_SetString(config, &config->filesystem_encoding,
L"mbcs");
}
else
#endif
if (preconfig->utf8_mode) {
- err = _PyCoreConfig_SetString(&config->filesystem_encoding,
+ err = _PyCoreConfig_SetString(config, &config->filesystem_encoding,
L"utf-8");
}
#ifndef MS_WINDOWS
else if (_Py_GetForceASCII()) {
- err = _PyCoreConfig_SetString(&config->filesystem_encoding,
+ err = _PyCoreConfig_SetString(config, &config->filesystem_encoding,
L"ascii");
}
#endif
else {
#ifdef MS_WINDOWS
/* Windows defaults to utf-8/surrogatepass (PEP 529). */
- err = _PyCoreConfig_SetString(&config->filesystem_encoding,
+ err = _PyCoreConfig_SetString(config, &config->filesystem_encoding,
L"utf-8");
#else
- err = config_get_locale_encoding(&config->filesystem_encoding);
+ err = config_get_locale_encoding(config,
+ &config->filesystem_encoding);
#endif
}
#endif /* !_Py_FORCE_UTF8_FS_ENCODING */
@@ -1588,7 +1605,7 @@ config_init_fs_encoding(_PyCoreConfig *config, const _PyPreConfig *preconfig)
#else
errors = L"surrogateescape";
#endif
- err = _PyCoreConfig_SetString(&config->filesystem_errors, errors);
+ err = _PyCoreConfig_SetString(config, &config->filesystem_errors, errors);
if (_Py_INIT_FAILED(err)) {
return err;
}
@@ -1681,7 +1698,7 @@ config_read(_PyCoreConfig *config)
}
if (config->check_hash_pycs_mode == NULL) {
- err = _PyCoreConfig_SetString(&config->check_hash_pycs_mode,
+ err = _PyCoreConfig_SetString(config, &config->check_hash_pycs_mode,
L"default");
if (_Py_INIT_FAILED(err)) {
return err;
@@ -1832,7 +1849,7 @@ config_parse_cmdline(_PyCoreConfig *config, _PyWstrList *warnoptions,
|| wcscmp(_PyOS_optarg, L"never") == 0
|| wcscmp(_PyOS_optarg, L"default") == 0)
{
- err = _PyCoreConfig_SetString(&config->check_hash_pycs_mode,
+ err = _PyCoreConfig_SetString(config, &config->check_hash_pycs_mode,
_PyOS_optarg);
if (_Py_INIT_FAILED(err)) {
return err;
@@ -1966,7 +1983,7 @@ config_parse_cmdline(_PyCoreConfig *config, _PyWstrList *warnoptions,
/* Get warning options from PYTHONWARNINGS environment variable. */
static _PyInitError
-config_init_env_warnoptions(const _PyCoreConfig *config, _PyWstrList *warnoptions)
+config_init_env_warnoptions(_PyCoreConfig *config, _PyWstrList *warnoptions)
{
_PyInitError err;
/* CONFIG_GET_ENV_DUP requires dest to be initialized to NULL */
@@ -2136,8 +2153,7 @@ core_read_precmdline(_PyCoreConfig *config, _PyPreCmdline *precmdline)
}
_PyPreConfig preconfig;
- _PyPreConfig_Init(&preconfig);
- _PyPreConfig_Copy(&preconfig, &_PyRuntime.preconfig);
+ _PyPreConfig_InitFromPreConfig(&preconfig, &_PyRuntime.preconfig);
_PyPreConfig_GetCoreConfig(&preconfig, config);
@@ -2211,24 +2227,11 @@ config_read_cmdline(_PyCoreConfig *config)
_PyInitError
_PyCoreConfig_SetPyArgv(_PyCoreConfig *config, const _PyArgv *args)
{
- if (args->use_bytes_argv) {
- _PyInitError err;
-
- err = _PyRuntime_Initialize();
- if (_Py_INIT_FAILED(err)) {
- return err;
- }
- _PyRuntimeState *runtime = &_PyRuntime;
-
- /* do nothing if Python is already pre-initialized:
- _PyCoreConfig_Write() will update _PyRuntime.preconfig later */
- if (!runtime->pre_initialized) {
- err = _Py_PreInitializeFromCoreConfig(config, args);
- if (_Py_INIT_FAILED(err)) {
- return err;
- }
- }
+ _PyInitError err = _Py_PreInitializeFromCoreConfig(config, args);
+ if (_Py_INIT_FAILED(err)) {
+ return err;
}
+
return _PyArgv_AsWstrList(args, &config->argv);
}
@@ -2236,7 +2239,7 @@ _PyCoreConfig_SetPyArgv(_PyCoreConfig *config, const _PyArgv *args)
/* Set config.argv: decode argv using Py_DecodeLocale(). Pre-initialize Python
if needed to ensure that encodings are properly configured. */
_PyInitError
-_PyCoreConfig_SetArgv(_PyCoreConfig *config, Py_ssize_t argc, char **argv)
+_PyCoreConfig_SetArgv(_PyCoreConfig *config, Py_ssize_t argc, char * const *argv)
{
_PyArgv args = {
.argc = argc,
@@ -2248,7 +2251,7 @@ _PyCoreConfig_SetArgv(_PyCoreConfig *config, Py_ssize_t argc, char **argv)
_PyInitError
-_PyCoreConfig_SetWideArgv(_PyCoreConfig *config, Py_ssize_t argc, wchar_t **argv)
+_PyCoreConfig_SetWideArgv(_PyCoreConfig *config, Py_ssize_t argc, wchar_t * const *argv)
{
_PyArgv args = {
.argc = argc,
diff --git a/Python/preconfig.c b/Python/preconfig.c
index 0f4bd8ece534c0..71a6ee6c072ef0 100644
--- a/Python/preconfig.c
+++ b/Python/preconfig.c
@@ -95,7 +95,7 @@ _PyArgv_AsWstrList(const _PyArgv *args, _PyWstrList *list)
}
else {
wargv.length = args->argc;
- wargv.items = args->wchar_argv;
+ wargv.items = (wchar_t **)args->wchar_argv;
if (_PyWstrList_Copy(list, &wargv) < 0) {
return _Py_INIT_NO_MEMORY();
}
@@ -217,16 +217,15 @@ precmdline_parse_cmdline(_PyPreCmdline *cmdline)
_PyInitError
-_PyPreCmdline_Read(_PyPreCmdline *cmdline,
- const _PyPreConfig *preconfig)
+_PyPreCmdline_Read(_PyPreCmdline *cmdline, const _PyPreConfig *preconfig)
{
- if (preconfig) {
- _PyPreCmdline_GetPreConfig(cmdline, preconfig);
- }
+ _PyPreCmdline_GetPreConfig(cmdline, preconfig);
- _PyInitError err = precmdline_parse_cmdline(cmdline);
- if (_Py_INIT_FAILED(err)) {
- return err;
+ if (preconfig->parse_argv) {
+ _PyInitError err = precmdline_parse_cmdline(cmdline);
+ if (_Py_INIT_FAILED(err)) {
+ return err;
+ }
}
/* isolated, use_environment */
@@ -268,6 +267,7 @@ _PyPreConfig_Init(_PyPreConfig *config)
memset(config, 0, sizeof(*config));
config->_config_version = _Py_CONFIG_VERSION;
+ config->parse_argv = 0;
config->isolated = -1;
config->use_environment = -1;
config->configure_locale = 1;
@@ -285,6 +285,7 @@ _PyPreConfig_InitPythonConfig(_PyPreConfig *config)
{
_PyPreConfig_Init(config);
+ config->parse_argv = 1;
/* Set to -1 to enable C locale coercion (PEP 538) and UTF-8 Mode (PEP 540)
depending on the LC_CTYPE locale, PYTHONUTF8 and PYTHONCOERCECLOCALE
environment variables. */
@@ -310,11 +311,41 @@ _PyPreConfig_InitIsolatedConfig(_PyPreConfig *config)
}
+void
+_PyPreConfig_InitFromPreConfig(_PyPreConfig *config,
+ const _PyPreConfig *config2)
+{
+ _PyPreConfig_Init(config);
+ _PyPreConfig_Copy(config, config2);
+}
+
+
+void
+_PyPreConfig_InitFromCoreConfig(_PyPreConfig *config,
+ const _PyCoreConfig *coreconfig)
+{
+ _PyCoreConfigInitEnum config_init = (_PyCoreConfigInitEnum)coreconfig->_config_init;
+ switch (config_init) {
+ case _PyCoreConfig_INIT_PYTHON:
+ _PyPreConfig_InitPythonConfig(config);
+ break;
+ case _PyCoreConfig_INIT_ISOLATED:
+ _PyPreConfig_InitIsolatedConfig(config);
+ break;
+ case _PyCoreConfig_INIT:
+ default:
+ _PyPreConfig_Init(config);
+ }
+ _PyPreConfig_GetCoreConfig(config, coreconfig);
+}
+
+
void
_PyPreConfig_Copy(_PyPreConfig *config, const _PyPreConfig *config2)
{
#define COPY_ATTR(ATTR) config->ATTR = config2->ATTR
+ COPY_ATTR(parse_argv);
COPY_ATTR(isolated);
COPY_ATTR(use_environment);
COPY_ATTR(configure_locale);
@@ -341,27 +372,20 @@ _PyPreConfig_AsDict(const _PyPreConfig *config)
return NULL;
}
-#define SET_ITEM(KEY, EXPR) \
+#define SET_ITEM_INT(ATTR) \
do { \
- PyObject *obj = (EXPR); \
+ PyObject *obj = PyLong_FromLong(config->ATTR); \
if (obj == NULL) { \
goto fail; \
} \
- int res = PyDict_SetItemString(dict, (KEY), obj); \
+ int res = PyDict_SetItemString(dict, #ATTR, obj); \
Py_DECREF(obj); \
if (res < 0) { \
goto fail; \
} \
} while (0)
-#define SET_ITEM_INT(ATTR) \
- SET_ITEM(#ATTR, PyLong_FromLong(config->ATTR))
-#define FROM_STRING(STR) \
- ((STR != NULL) ? \
- PyUnicode_FromString(STR) \
- : (Py_INCREF(Py_None), Py_None))
-#define SET_ITEM_STR(ATTR) \
- SET_ITEM(#ATTR, FROM_STRING(config->ATTR))
+ SET_ITEM_INT(parse_argv);
SET_ITEM_INT(isolated);
SET_ITEM_INT(use_environment);
SET_ITEM_INT(configure_locale);
@@ -379,10 +403,7 @@ _PyPreConfig_AsDict(const _PyPreConfig *config)
Py_DECREF(dict);
return NULL;
-#undef FROM_STRING
-#undef SET_ITEM
#undef SET_ITEM_INT
-#undef SET_ITEM_STR
}
@@ -395,6 +416,7 @@ _PyPreConfig_GetCoreConfig(_PyPreConfig *config,
config->ATTR = core_config->ATTR; \
}
+ COPY_ATTR(parse_argv);
COPY_ATTR(isolated);
COPY_ATTR(use_environment);
COPY_ATTR(dev_mode);
@@ -662,9 +684,11 @@ preconfig_init_allocator(_PyPreConfig *config)
allocators to "malloc" (and not to "debug"). */
const char *envvar = _Py_GetEnv(config->use_environment, "PYTHONMALLOC");
if (envvar) {
- if (_PyMem_GetAllocatorName(envvar, &config->allocator) < 0) {
+ PyMemAllocatorName name;
+ if (_PyMem_GetAllocatorName(envvar, &name) < 0) {
return _Py_INIT_ERR("PYTHONMALLOC: unknown allocator");
}
+ config->allocator = (int)name;
}
}
@@ -751,8 +775,7 @@ _PyPreConfig_Read(_PyPreConfig *config, const _PyArgv *args)
/* Save the config to be able to restore it if encodings change */
_PyPreConfig save_config;
- _PyPreConfig_Init(&save_config);
- _PyPreConfig_Copy(&save_config, config);
+ _PyPreConfig_InitFromPreConfig(&save_config, config);
/* Set LC_CTYPE to the user preferred locale */
if (config->configure_locale) {
@@ -879,8 +902,9 @@ _PyPreConfig_Write(const _PyPreConfig *config)
return _Py_INIT_OK();
}
- if (config->allocator != PYMEM_ALLOCATOR_NOT_SET) {
- if (_PyMem_SetupAllocators(config->allocator) < 0) {
+ PyMemAllocatorName name = (PyMemAllocatorName)config->allocator;
+ if (name != PYMEM_ALLOCATOR_NOT_SET) {
+ if (_PyMem_SetupAllocators(name) < 0) {
return _Py_INIT_ERR("Unknown PYTHONMALLOC allocator");
}
}
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 0781dc8046b106..01f725f8f45a73 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -691,6 +691,10 @@ _Py_PreInitializeFromPyArgv(const _PyPreConfig *src_config, const _PyArgv *args)
{
_PyInitError err;
+ if (src_config == NULL) {
+ return _Py_INIT_ERR("preinitialization config is NULL");
+ }
+
err = _PyRuntime_Initialize();
if (_Py_INIT_FAILED(err)) {
return err;
@@ -703,11 +707,7 @@ _Py_PreInitializeFromPyArgv(const _PyPreConfig *src_config, const _PyArgv *args)
}
_PyPreConfig config;
- _PyPreConfig_Init(&config);
-
- if (src_config) {
- _PyPreConfig_Copy(&config, src_config);
- }
+ _PyPreConfig_InitFromPreConfig(&config, src_config);
err = _PyPreConfig_Read(&config, args);
if (_Py_INIT_FAILED(err)) {
@@ -751,21 +751,34 @@ _PyInitError
_Py_PreInitializeFromCoreConfig(const _PyCoreConfig *coreconfig,
const _PyArgv *args)
{
- _PyPreConfig config;
- _PyPreConfig_Init(&config);
- if (coreconfig != NULL) {
- _PyPreConfig_GetCoreConfig(&config, coreconfig);
+ assert(coreconfig != NULL);
+
+ _PyInitError err = _PyRuntime_Initialize();
+ if (_Py_INIT_FAILED(err)) {
+ return err;
+ }
+ _PyRuntimeState *runtime = &_PyRuntime;
+
+ if (runtime->pre_initialized) {
+ /* Already initialized: do nothing */
+ return _Py_INIT_OK();
}
- if (args == NULL && coreconfig != NULL && coreconfig->parse_argv) {
+ _PyPreConfig preconfig;
+ _PyPreConfig_InitFromCoreConfig(&preconfig, coreconfig);
+
+ if (!coreconfig->parse_argv) {
+ return _Py_PreInitialize(&preconfig);
+ }
+ else if (args == NULL) {
_PyArgv config_args = {
.use_bytes_argv = 0,
.argc = coreconfig->argv.length,
.wchar_argv = coreconfig->argv.items};
- return _Py_PreInitializeFromPyArgv(&config, &config_args);
+ return _Py_PreInitializeFromPyArgv(&preconfig, &config_args);
}
else {
- return _Py_PreInitializeFromPyArgv(&config, args);
+ return _Py_PreInitializeFromPyArgv(&preconfig, args);
}
}
@@ -777,13 +790,11 @@ pyinit_coreconfig(_PyRuntimeState *runtime,
const _PyArgv *args,
PyInterpreterState **interp_p)
{
- _PyInitError err;
+ assert(src_config != NULL);
- if (src_config) {
- err = _PyCoreConfig_Copy(config, src_config);
- if (_Py_INIT_FAILED(err)) {
- return err;
- }
+ _PyInitError err = _PyCoreConfig_Copy(config, src_config);
+ if (_Py_INIT_FAILED(err)) {
+ return err;
}
if (args) {
@@ -995,6 +1006,10 @@ _Py_InitializeMain(void)
static _PyInitError
init_python(const _PyCoreConfig *config, const _PyArgv *args)
{
+ if (config == NULL) {
+ return _Py_INIT_ERR("initialization config is NULL");
+ }
+
_PyInitError err;
err = _PyRuntime_Initialize();
@@ -1022,7 +1037,8 @@ init_python(const _PyCoreConfig *config, const _PyArgv *args)
_PyInitError
-_Py_InitializeFromArgs(const _PyCoreConfig *config, Py_ssize_t argc, char **argv)
+_Py_InitializeFromArgs(const _PyCoreConfig *config,
+ Py_ssize_t argc, char * const *argv)
{
_PyArgv args = {.use_bytes_argv = 1, .argc = argc, .bytes_argv = argv};
return init_python(config, &args);
@@ -1030,7 +1046,8 @@ _Py_InitializeFromArgs(const _PyCoreConfig *config, Py_ssize_t argc, char **argv
_PyInitError
-_Py_InitializeFromWideArgs(const _PyCoreConfig *config, Py_ssize_t argc, wchar_t **argv)
+_Py_InitializeFromWideArgs(const _PyCoreConfig *config,
+ Py_ssize_t argc, wchar_t * const *argv)
{
_PyArgv args = {.use_bytes_argv = 0, .argc = argc, .wchar_argv = argv};
return init_python(config, &args);
From 9932fd91e878b740704ff599522e945a4bbe2ae1 Mon Sep 17 00:00:00 2001
From: Niklas Fiekas
Date: Mon, 20 May 2019 14:02:17 +0200
Subject: [PATCH 026/441] bpo-35721: Close socket pair if Popen in
_UnixSubprocessTransport fails (GH-11553)
This slightly expands an existing test case `test_popen_error` to trigger a `ResourceWarning` and fixes it.
https://bugs.python.org/issue35721
---
Lib/asyncio/unix_events.py | 18 ++++++++++++------
Lib/test/test_asyncio/test_subprocess.py | 17 +++++++++++++----
Misc/ACKS | 1 +
.../2019-01-18-16-23-00.bpo-35721.d8djAJ.rst | 3 +++
4 files changed, 29 insertions(+), 10 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-01-18-16-23-00.bpo-35721.d8djAJ.rst
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
index 73d4bda7c2341b..1aa3b396086c59 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -757,12 +757,18 @@ def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
# other end). Notably this is needed on AIX, and works
# just fine on other platforms.
stdin, stdin_w = socket.socketpair()
- self._proc = subprocess.Popen(
- args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
- universal_newlines=False, bufsize=bufsize, **kwargs)
- if stdin_w is not None:
- stdin.close()
- self._proc.stdin = open(stdin_w.detach(), 'wb', buffering=bufsize)
+ try:
+ self._proc = subprocess.Popen(
+ args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
+ universal_newlines=False, bufsize=bufsize, **kwargs)
+ if stdin_w is not None:
+ 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:
diff --git a/Lib/test/test_asyncio/test_subprocess.py b/Lib/test/test_asyncio/test_subprocess.py
index 3908aabf5a1321..551974a1806a84 100644
--- a/Lib/test/test_asyncio/test_subprocess.py
+++ b/Lib/test/test_asyncio/test_subprocess.py
@@ -468,9 +468,7 @@ async def kill_running():
isinstance(self, SubprocessFastWatcherTests)):
asyncio.get_child_watcher()._callbacks.clear()
- def test_popen_error(self):
- # Issue #24763: check that the subprocess transport is closed
- # when BaseSubprocessTransport fails
+ def _test_popen_error(self, stdin):
if sys.platform == 'win32':
target = 'asyncio.windows_utils.Popen'
else:
@@ -480,12 +478,23 @@ def test_popen_error(self):
popen.side_effect = exc
create = asyncio.create_subprocess_exec(sys.executable, '-c',
- 'pass', loop=self.loop)
+ 'pass', stdin=stdin,
+ loop=self.loop)
with warnings.catch_warnings(record=True) as warns:
with self.assertRaises(exc):
self.loop.run_until_complete(create)
self.assertEqual(warns, [])
+ def test_popen_error(self):
+ # Issue #24763: check that the subprocess transport is closed
+ # when BaseSubprocessTransport fails
+ self._test_popen_error(stdin=None)
+
+ def test_popen_error_with_stdin_pipe(self):
+ # Issue #35721: check that newly created socket pair is closed when
+ # Popen fails
+ self._test_popen_error(stdin=subprocess.PIPE)
+
def test_read_stdout_after_process_exit(self):
async def execute():
diff --git a/Misc/ACKS b/Misc/ACKS
index 8b3232551e1b09..77f19909b3005b 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -486,6 +486,7 @@ Florian Festi
John Feuerstein
Carl Feynman
Vincent Fiack
+Niklas Fiekas
Anastasia Filatova
Tomer Filiba
Segev Finer
diff --git a/Misc/NEWS.d/next/Library/2019-01-18-16-23-00.bpo-35721.d8djAJ.rst b/Misc/NEWS.d/next/Library/2019-01-18-16-23-00.bpo-35721.d8djAJ.rst
new file mode 100644
index 00000000000000..5af4b1b8999419
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-01-18-16-23-00.bpo-35721.d8djAJ.rst
@@ -0,0 +1,3 @@
+Fix :meth:`asyncio.SelectorEventLoop.subprocess_exec()` leaks file descriptors
+if ``Popen`` fails and called with ``stdin=subprocess.PIPE``.
+Patch by Niklas Fiekas.
From 425717fee1c72df464c9f85b9a8d32b9197d1035 Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Mon, 20 May 2019 16:38:48 +0200
Subject: [PATCH 027/441] bpo-36763: Fix encoding/locale tests in test_embed
(GH-13443)
* Fix encoding and locale tests in test_embed.InitConfigTests.
* InitConfigTests now only computes EXPECTED_CONFIG once.
* Add tests for PYTHONWARNINGS and PYTHONPATH env vars
---
Lib/test/test_embed.py | 190 ++++++++++++++++++++++-------------------
Programs/_testembed.c | 19 +++--
2 files changed, 113 insertions(+), 96 deletions(-)
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index 5be179a266ba5d..4fe005dfa130e3 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -296,6 +296,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
})
PYTHON_PRE_CONFIG = dict(DEFAULT_PRE_CONFIG,
parse_argv=1,
+ coerce_c_locale=GET_DEFAULT_CONFIG,
+ utf8_mode=GET_DEFAULT_CONFIG,
)
ISOLATED_PRE_CONFIG = dict(DEFAULT_PRE_CONFIG,
configure_locale=0,
@@ -303,6 +305,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
use_environment=0,
utf8_mode=0,
dev_mode=0,
+ coerce_c_locale=0,
)
COPY_PRE_CONFIG = [
@@ -435,6 +438,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
('Py_LegacyWindowsStdioFlag', 'legacy_windows_stdio'),
))
+ EXPECTED_CONFIG = None
+
def main_xoptions(self, xoptions_list):
xoptions = {}
for opt in xoptions_list:
@@ -445,37 +450,15 @@ def main_xoptions(self, xoptions_list):
xoptions[opt] = True
return xoptions
- def get_expected_config(self, expected_preconfig, expected, env, api,
- add_path=None):
- if api == CONFIG_INIT_PYTHON:
- default_config = self.PYTHON_CORE_CONFIG
- elif api == CONFIG_INIT_ISOLATED:
- default_config = self.ISOLATED_CORE_CONFIG
- else:
- default_config = self.DEFAULT_CORE_CONFIG
- expected = dict(default_config, **expected)
- expected['_config_init'] = api
-
+ def _get_expected_config(self, env):
code = textwrap.dedent('''
import json
import sys
import _testinternalcapi
configs = _testinternalcapi.get_configs()
- core_config = configs['core_config']
- data = {
- 'stdio_encoding': sys.stdout.encoding,
- 'stdio_errors': sys.stdout.errors,
- 'prefix': sys.prefix,
- 'base_prefix': sys.base_prefix,
- 'exec_prefix': sys.exec_prefix,
- 'base_exec_prefix': sys.base_exec_prefix,
- 'filesystem_encoding': sys.getfilesystemencoding(),
- 'filesystem_errors': sys.getfilesystemencodeerrors(),
- 'module_search_paths': core_config['module_search_paths'],
- }
-
- data = json.dumps(data)
+
+ data = json.dumps(configs)
data = data.encode('utf-8')
sys.stdout.buffer.write(data)
sys.stdout.buffer.flush()
@@ -484,10 +467,6 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
# Use -S to not import the site module: get the proper configuration
# when test_embed is run from a venv (bpo-35313)
args = [sys.executable, '-S', '-c', code]
- env = dict(env)
- if not expected['isolated']:
- env['PYTHONCOERCECLOCALE'] = '0'
- env['PYTHONUTF8'] = '0'
proc = subprocess.run(args, env=env,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
@@ -496,10 +475,46 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
f"stdout={proc.stdout!r} stderr={proc.stderr!r}")
stdout = proc.stdout.decode('utf-8')
try:
- config = json.loads(stdout)
+ return json.loads(stdout)
except json.JSONDecodeError:
self.fail(f"fail to decode stdout: {stdout!r}")
+ def get_expected_config(self, expected_preconfig, expected, env, api,
+ add_path=None):
+ cls = self.__class__
+ if cls.EXPECTED_CONFIG is None:
+ cls.EXPECTED_CONFIG = self._get_expected_config(env)
+ configs = {key: dict(value)
+ for key, value in self.EXPECTED_CONFIG.items()}
+
+ pre_config = configs['pre_config']
+ for key, value in expected_preconfig.items():
+ if value is self.GET_DEFAULT_CONFIG:
+ expected_preconfig[key] = pre_config[key]
+
+ if not expected_preconfig['configure_locale'] or api == CONFIG_INIT:
+ # there is no easy way to get the locale encoding before
+ # setlocale(LC_CTYPE, "") is called: don't test encodings
+ for key in ('filesystem_encoding', 'filesystem_errors',
+ 'stdio_encoding', 'stdio_errors'):
+ expected[key] = self.IGNORE_CONFIG
+
+ if not expected_preconfig['configure_locale']:
+ # UTF-8 Mode depends on the locale. There is no easy way
+ # to guess if UTF-8 Mode will be enabled or not if the locale
+ # is not configured.
+ expected_preconfig['utf8_mode'] = self.IGNORE_CONFIG
+
+ if expected_preconfig['utf8_mode'] == 1:
+ if expected['filesystem_encoding'] is self.GET_DEFAULT_CONFIG:
+ expected['filesystem_encoding'] = 'utf-8'
+ if expected['filesystem_errors'] is self.GET_DEFAULT_CONFIG:
+ expected['filesystem_errors'] = self.UTF8_MODE_ERRORS
+ if expected['stdio_encoding'] is self.GET_DEFAULT_CONFIG:
+ expected['stdio_encoding'] = 'utf-8'
+ if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG:
+ expected['stdio_errors'] = 'surrogateescape'
+
if expected['executable'] is self.GET_DEFAULT_CONFIG:
if sys.platform == 'win32':
expected['executable'] = self.test_exe
@@ -511,24 +526,28 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
if expected['program_name'] is self.GET_DEFAULT_CONFIG:
expected['program_name'] = './_testembed'
+ core_config = configs['core_config']
for key, value in expected.items():
if value is self.GET_DEFAULT_CONFIG:
- expected[key] = config[key]
+ expected[key] = core_config[key]
+ prepend_path = expected['module_search_path_env']
+ if prepend_path is not None:
+ expected['module_search_paths'] = [prepend_path, *expected['module_search_paths']]
if add_path is not None:
- expected['module_search_paths'].append(add_path)
-
- if not expected_preconfig['configure_locale']:
- # there is no easy way to get the locale encoding before
- # setlocale(LC_CTYPE, "") is called: don't test encodings
- for key in ('filesystem_encoding', 'filesystem_errors',
- 'stdio_encoding', 'stdio_errors'):
- expected[key] = self.IGNORE_CONFIG
+ expected['module_search_paths'] = [*expected['module_search_paths'], add_path]
- return expected
+ for key in self.COPY_PRE_CONFIG:
+ if key not in expected_preconfig:
+ expected_preconfig[key] = expected[key]
def check_pre_config(self, config, expected):
- self.assertEqual(config['pre_config'], expected)
+ pre_config = dict(config['pre_config'])
+ for key, value in list(expected.items()):
+ if value is self.IGNORE_CONFIG:
+ del pre_config[key]
+ del expected[key]
+ self.assertEqual(pre_config, expected)
def check_core_config(self, config, expected):
core_config = dict(config['core_config'])
@@ -567,10 +586,6 @@ def check_config(self, testname, expected_config=None, expected_preconfig=None,
for key in list(env):
if key.startswith('PYTHON'):
del env[key]
- # Disable C locale coercion and UTF-8 mode to not depend
- # on the current locale
- env['PYTHONCOERCECLOCALE'] = '0'
- env['PYTHONUTF8'] = '0'
if api == CONFIG_INIT_ISOLATED:
default_preconfig = self.ISOLATED_PRE_CONFIG
@@ -583,12 +598,19 @@ def check_config(self, testname, expected_config=None, expected_preconfig=None,
expected_preconfig = dict(default_preconfig, **expected_preconfig)
if expected_config is None:
expected_config = {}
- expected_config = self.get_expected_config(expected_preconfig,
- expected_config, env,
- api, add_path)
- for key in self.COPY_PRE_CONFIG:
- if key not in expected_preconfig:
- expected_preconfig[key] = expected_config[key]
+
+ if api == CONFIG_INIT_PYTHON:
+ default_config = self.PYTHON_CORE_CONFIG
+ elif api == CONFIG_INIT_ISOLATED:
+ default_config = self.ISOLATED_CORE_CONFIG
+ else:
+ default_config = self.DEFAULT_CORE_CONFIG
+ expected_config = dict(default_config, **expected_config)
+ expected_config['_config_init'] = api
+
+ self.get_expected_config(expected_preconfig,
+ expected_config, env,
+ api, add_path)
out, err = self.run_embedded_interpreter(testname, env=env)
if stderr is None and not expected_config['verbose']:
@@ -624,10 +646,6 @@ def test_init_global_config(self):
'quiet': 1,
'buffered_stdio': 0,
- 'stdio_encoding': 'utf-8',
- 'stdio_errors': 'surrogateescape',
- 'filesystem_encoding': 'utf-8',
- 'filesystem_errors': self.UTF8_MODE_ERRORS,
'user_site_directory': 0,
'pathconfig_warnings': 0,
}
@@ -650,8 +668,6 @@ def test_init_from_config(self):
'stdio_encoding': 'iso8859-1',
'stdio_errors': 'replace',
- 'filesystem_encoding': 'utf-8',
- 'filesystem_errors': self.UTF8_MODE_ERRORS,
'pycache_prefix': 'conf_pycache_prefix',
'program_name': './conf_program_name',
@@ -679,43 +695,42 @@ def test_init_from_config(self):
}
self.check_config("init_from_config", config, preconfig)
- INIT_ENV_PRECONFIG = {
- 'allocator': PYMEM_ALLOCATOR_MALLOC,
- }
- INIT_ENV_CONFIG = {
- 'use_hash_seed': 1,
- 'hash_seed': 42,
- 'tracemalloc': 2,
- 'import_time': 1,
- 'malloc_stats': 1,
- 'inspect': 1,
- 'optimization_level': 2,
- 'pycache_prefix': 'env_pycache_prefix',
- 'write_bytecode': 0,
- 'verbose': 1,
- 'buffered_stdio': 0,
- 'stdio_encoding': 'iso8859-1',
- 'stdio_errors': 'replace',
- 'user_site_directory': 0,
- 'faulthandler': 1,
- }
-
def test_init_env(self):
- self.check_config("init_env", self.INIT_ENV_CONFIG, self.INIT_ENV_PRECONFIG)
+ preconfig = {
+ 'allocator': PYMEM_ALLOCATOR_MALLOC,
+ }
+ config = {
+ 'use_hash_seed': 1,
+ 'hash_seed': 42,
+ 'tracemalloc': 2,
+ 'import_time': 1,
+ 'malloc_stats': 1,
+ 'inspect': 1,
+ 'optimization_level': 2,
+ 'module_search_path_env': '/my/path',
+ 'pycache_prefix': 'env_pycache_prefix',
+ 'write_bytecode': 0,
+ 'verbose': 1,
+ 'buffered_stdio': 0,
+ 'stdio_encoding': 'iso8859-1',
+ 'stdio_errors': 'replace',
+ 'user_site_directory': 0,
+ 'faulthandler': 1,
+ 'warnoptions': ['EnvVar'],
+ }
+ self.check_config("init_env", config, preconfig)
def test_init_env_dev_mode(self):
- preconfig = dict(self.INIT_ENV_PRECONFIG,
- allocator=PYMEM_ALLOCATOR_DEBUG)
- config = dict(self.INIT_ENV_CONFIG,
- dev_mode=1,
+ preconfig = dict(allocator=PYMEM_ALLOCATOR_DEBUG)
+ config = dict(dev_mode=1,
+ faulthandler=1,
warnoptions=['default'])
self.check_config("init_env_dev_mode", config, preconfig)
def test_init_env_dev_mode_alloc(self):
- preconfig = dict(self.INIT_ENV_PRECONFIG,
- allocator=PYMEM_ALLOCATOR_MALLOC)
- config = dict(self.INIT_ENV_CONFIG,
- dev_mode=1,
+ preconfig = dict(allocator=PYMEM_ALLOCATOR_MALLOC)
+ config = dict(dev_mode=1,
+ faulthandler=1,
warnoptions=['default'])
self.check_config("init_env_dev_mode_alloc", config, preconfig)
@@ -800,6 +815,7 @@ def test_init_dont_configure_locale(self):
# _PyPreConfig.configure_locale=0
preconfig = {
'configure_locale': 0,
+ 'coerce_c_locale': 0,
}
self.check_config("init_dont_configure_locale", {}, preconfig,
api=CONFIG_INIT_PYTHON)
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 3dabf66de15afa..bc549369393fc7 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -568,7 +568,7 @@ static int test_init_dont_parse_argv(void)
}
-static void set_all_env_vars(void)
+static void set_most_env_vars(void)
{
putenv("PYTHONHASHSEED=42");
putenv("PYTHONMALLOC=malloc");
@@ -585,13 +585,15 @@ static void set_all_env_vars(void)
putenv("PYTHONNOUSERSITE=1");
putenv("PYTHONFAULTHANDLER=1");
putenv("PYTHONIOENCODING=iso8859-1:replace");
- /* FIXME: test PYTHONWARNINGS */
- /* FIXME: test PYTHONEXECUTABLE */
- /* FIXME: test PYTHONHOME */
- /* FIXME: test PYTHONDEBUG */
- /* FIXME: test PYTHONDUMPREFS */
- /* FIXME: test PYTHONCOERCECLOCALE */
- /* FIXME: test PYTHONPATH */
+}
+
+
+static void set_all_env_vars(void)
+{
+ set_most_env_vars();
+
+ putenv("PYTHONWARNINGS=EnvVar");
+ putenv("PYTHONPATH=/my/path");
}
@@ -609,7 +611,6 @@ static int test_init_env(void)
static void set_all_env_vars_dev_mode(void)
{
- set_all_env_vars();
putenv("PYTHONMALLOC=");
putenv("PYTHONFAULTHANDLER=");
putenv("PYTHONDEVMODE=1");
From 45a24b85f328ad07296123ebc994553983c7a915 Mon Sep 17 00:00:00 2001
From: Andrew Svetlov
Date: Mon, 20 May 2019 17:38:57 +0300
Subject: [PATCH 028/441] Pass _asyncio_internal=True into stream tests on
windows (#13442)
---
Lib/test/test_asyncio/test_windows_events.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/Lib/test/test_asyncio/test_windows_events.py b/Lib/test/test_asyncio/test_windows_events.py
index 05f85159be0cd5..e201a0696796d6 100644
--- a/Lib/test/test_asyncio/test_windows_events.py
+++ b/Lib/test/test_asyncio/test_windows_events.py
@@ -100,9 +100,11 @@ async def _test_pipe(self):
clients = []
for i in range(5):
- stream_reader = asyncio.StreamReader(loop=self.loop)
+ stream_reader = asyncio.StreamReader(loop=self.loop,
+ _asyncio_internal=True)
protocol = asyncio.StreamReaderProtocol(stream_reader,
- loop=self.loop)
+ loop=self.loop,
+ _asyncio_internal=True)
trans, proto = await self.loop.create_pipe_connection(
lambda: protocol, ADDRESS)
self.assertIsInstance(trans, asyncio.Transport)
From 522ccef8690970fc4f78f51a3adb995f2547871a Mon Sep 17 00:00:00 2001
From: Geoff Shannon
Date: Mon, 20 May 2019 08:06:16 -0700
Subject: [PATCH 029/441] bpo-22865: Expand on documentation for the pty.spawn
function (GH-11980)
---
Doc/library/pty.rst | 29 ++++++++++++++++---
Misc/ACKS | 1 +
.../2019-02-21-18-13-50.bpo-22865.6hg6J8.rst | 1 +
3 files changed, 27 insertions(+), 4 deletions(-)
create mode 100644 Misc/NEWS.d/next/Documentation/2019-02-21-18-13-50.bpo-22865.6hg6J8.rst
diff --git a/Doc/library/pty.rst b/Doc/library/pty.rst
index 0ab766065d6e81..12268437d07e98 100644
--- a/Doc/library/pty.rst
+++ b/Doc/library/pty.rst
@@ -43,11 +43,32 @@ The :mod:`pty` module defines the following functions:
Spawn a process, and connect its controlling terminal with the current
process's standard io. This is often used to baffle programs which insist on
- reading from the controlling terminal.
+ reading from the controlling terminal. It is expected that the process
+ spawned behind the pty will eventually terminate, and when it does *spawn*
+ will return.
+
+ The functions *master_read* and *stdin_read* are passed a file descriptor
+ which they should read from, and they should always return a byte string. In
+ order to force spawn to return before the child process exits an
+ :exc:`OSError` should be thrown.
+
+ The default implementation for both functions will read and return up to 1024
+ bytes each time the function is called. The *master_read* callback is passed
+ the pseudoterminal’s master file descriptor to read output from the child
+ process, and *stdin_read* is passed file descriptor 0, to read from the
+ parent process's standard input.
+
+ Returning an empty byte string from either callback is interpreted as an
+ end-of-file (EOF) condition, and that callback will not be called after
+ that. If *stdin_read* signals EOF the controlling terminal can no longer
+ communicate with the parent process OR the child process. Unless the child
+ process will quit without any input, *spawn* will then loop forever. If
+ *master_read* signals EOF the same behavior results (on linux at least).
+
+ If both callbacks signal EOF then *spawn* will probably never return, unless
+ *select* throws an error on your platform when passed three empty lists. This
+ is a bug, documented in `issue 26228 `_.
- The functions *master_read* and *stdin_read* should be functions which read from
- a file descriptor. The defaults try to read 1024 bytes each time they are
- called.
.. versionchanged:: 3.4
:func:`spawn` now returns the status value from :func:`os.waitpid`
diff --git a/Misc/ACKS b/Misc/ACKS
index 77f19909b3005b..f9d01d00867999 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1859,3 +1859,4 @@ Zheao Li
Carsten Klein
Diego Rojas
Edison Abahurire
+Geoff Shannon
diff --git a/Misc/NEWS.d/next/Documentation/2019-02-21-18-13-50.bpo-22865.6hg6J8.rst b/Misc/NEWS.d/next/Documentation/2019-02-21-18-13-50.bpo-22865.6hg6J8.rst
new file mode 100644
index 00000000000000..67a4ed26bede37
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2019-02-21-18-13-50.bpo-22865.6hg6J8.rst
@@ -0,0 +1 @@
+Add detail to the documentation on the `pty.spawn` function.
\ No newline at end of file
From 0f72147ce2b3d65235b41eddc6a57be40237b5c7 Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Mon, 20 May 2019 17:16:38 +0200
Subject: [PATCH 030/441] bpo-36763: Fix _PyRuntime.preconfig.coerce_c_locale
(GH-13444)
_PyRuntime.preconfig.coerce_c_locale can now be used to
check if the C locale has been coerced.
* Fix _Py_LegacyLocaleDetected(): don't attempt to coerce the
C locale if LC_ALL environment variable is set. Add 'warn'
parameter: emit_stderr_warning_for_legacy_locale() must not
the LC_ALL env var.
* _PyPreConfig_Write() sets coerce_c_locale to 0 if
_Py_CoerceLegacyLocale() fails.
---
Include/cpython/pylifecycle.h | 4 ++--
Python/preconfig.c | 26 ++++++++++++++++----------
Python/pylifecycle.c | 26 +++++++++++++++++++-------
3 files changed, 37 insertions(+), 19 deletions(-)
diff --git a/Include/cpython/pylifecycle.h b/Include/cpython/pylifecycle.h
index 1e1dabe59ff556..ba5666465d7e75 100644
--- a/Include/cpython/pylifecycle.h
+++ b/Include/cpython/pylifecycle.h
@@ -69,8 +69,8 @@ PyAPI_FUNC(int) _PyOS_URandom(void *buffer, Py_ssize_t size);
PyAPI_FUNC(int) _PyOS_URandomNonblock(void *buffer, Py_ssize_t size);
/* Legacy locale support */
-PyAPI_FUNC(void) _Py_CoerceLegacyLocale(int warn);
-PyAPI_FUNC(int) _Py_LegacyLocaleDetected(void);
+PyAPI_FUNC(int) _Py_CoerceLegacyLocale(int warn);
+PyAPI_FUNC(int) _Py_LegacyLocaleDetected(int warn);
PyAPI_FUNC(char *) _Py_SetLocaleFromEnv(int category);
#ifdef __cplusplus
diff --git a/Python/preconfig.c b/Python/preconfig.c
index 71a6ee6c072ef0..4df6208cadb841 100644
--- a/Python/preconfig.c
+++ b/Python/preconfig.c
@@ -660,7 +660,7 @@ preconfig_init_coerce_c_locale(_PyPreConfig *config)
It is only coerced if if the LC_CTYPE locale is "C". */
if (config->coerce_c_locale < 0 || config->coerce_c_locale == 1) {
/* The C locale enables the C locale coercion (PEP 538) */
- if (_Py_LegacyLocaleDetected()) {
+ if (_Py_LegacyLocaleDetected(0)) {
config->coerce_c_locale = 2;
}
else {
@@ -888,32 +888,38 @@ _PyPreConfig_Read(_PyPreConfig *config, const _PyArgv *args)
- set the LC_CTYPE locale (coerce C locale, PEP 538) and set the UTF-8 mode
(PEP 540)
- If the memory allocator is changed, config is re-allocated with new
- allocator. So calling _PyPreConfig_Clear(config) is safe after this call.
+ The applied configuration is written into _PyRuntime.preconfig.
+ If the C locale cannot be coerced, set coerce_c_locale to 0.
Do nothing if called after Py_Initialize(): ignore the new
pre-configuration. */
_PyInitError
-_PyPreConfig_Write(const _PyPreConfig *config)
+_PyPreConfig_Write(const _PyPreConfig *src_config)
{
+ _PyPreConfig config;
+ _PyPreConfig_InitFromPreConfig(&config, src_config);
+
if (_PyRuntime.core_initialized) {
/* bpo-34008: Calling this functions after Py_Initialize() ignores
the new configuration. */
return _Py_INIT_OK();
}
- PyMemAllocatorName name = (PyMemAllocatorName)config->allocator;
+ PyMemAllocatorName name = (PyMemAllocatorName)config.allocator;
if (name != PYMEM_ALLOCATOR_NOT_SET) {
if (_PyMem_SetupAllocators(name) < 0) {
return _Py_INIT_ERR("Unknown PYTHONMALLOC allocator");
}
}
- _PyPreConfig_SetGlobalConfig(config);
+ _PyPreConfig_SetGlobalConfig(&config);
- if (config->configure_locale) {
- if (config->coerce_c_locale) {
- _Py_CoerceLegacyLocale(config->coerce_c_locale_warn);
+ if (config.configure_locale) {
+ if (config.coerce_c_locale) {
+ if (!_Py_CoerceLegacyLocale(config.coerce_c_locale_warn)) {
+ /* C locale not coerced */
+ config.coerce_c_locale = 0;
+ }
}
/* Set LC_CTYPE to the user preferred locale */
@@ -921,7 +927,7 @@ _PyPreConfig_Write(const _PyPreConfig *config)
}
/* Write the new pre-configuration into _PyRuntime */
- _PyPreConfig_Copy(&_PyRuntime.preconfig, config);
+ _PyPreConfig_Copy(&_PyRuntime.preconfig, &config);
return _Py_INIT_OK();
}
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 01f725f8f45a73..01344db410a0f1 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -231,9 +231,18 @@ init_importlib_external(PyInterpreterState *interp)
*/
int
-_Py_LegacyLocaleDetected(void)
+_Py_LegacyLocaleDetected(int warn)
{
#ifndef MS_WINDOWS
+ if (!warn) {
+ const char *locale_override = getenv("LC_ALL");
+ if (locale_override != NULL && *locale_override != '\0') {
+ /* Don't coerce C locale if the LC_ALL environment variable
+ is set */
+ return 0;
+ }
+ }
+
/* On non-Windows systems, the C locale is considered a legacy locale */
/* XXX (ncoghlan): some platforms (notably Mac OS X) don't appear to treat
* the POSIX locale as a simple alias for the C locale, so
@@ -257,7 +266,7 @@ static void
emit_stderr_warning_for_legacy_locale(_PyRuntimeState *runtime)
{
const _PyPreConfig *preconfig = &runtime->preconfig;
- if (preconfig->coerce_c_locale_warn && _Py_LegacyLocaleDetected()) {
+ if (preconfig->coerce_c_locale_warn && _Py_LegacyLocaleDetected(1)) {
PySys_FormatStderr("%s", _C_LOCALE_WARNING);
}
}
@@ -292,7 +301,7 @@ static const char C_LOCALE_COERCION_WARNING[] =
"Python detected LC_CTYPE=C: LC_CTYPE coerced to %.20s (set another locale "
"or PYTHONCOERCECLOCALE=0 to disable this locale coercion behavior).\n";
-static void
+static int
_coerce_default_locale_settings(int warn, const _LocaleCoercionTarget *target)
{
const char *newloc = target->locale_name;
@@ -304,7 +313,7 @@ _coerce_default_locale_settings(int warn, const _LocaleCoercionTarget *target)
if (setenv("LC_CTYPE", newloc, 1)) {
fprintf(stderr,
"Error setting LC_CTYPE, skipping C locale coercion\n");
- return;
+ return 0;
}
if (warn) {
fprintf(stderr, C_LOCALE_COERCION_WARNING, newloc);
@@ -312,18 +321,20 @@ _coerce_default_locale_settings(int warn, const _LocaleCoercionTarget *target)
/* Reconfigure with the overridden environment variables */
_Py_SetLocaleFromEnv(LC_ALL);
+ return 1;
}
#endif
-void
+int
_Py_CoerceLegacyLocale(int warn)
{
+ int coerced = 0;
#ifdef PY_COERCE_C_LOCALE
char *oldloc = NULL;
oldloc = _PyMem_RawStrdup(setlocale(LC_CTYPE, NULL));
if (oldloc == NULL) {
- return;
+ return coerced;
}
const char *locale_override = getenv("LC_ALL");
@@ -345,7 +356,7 @@ _Py_CoerceLegacyLocale(int warn)
}
#endif
/* Successfully configured locale, so make it the default */
- _coerce_default_locale_settings(warn, target);
+ coerced = _coerce_default_locale_settings(warn, target);
goto done;
}
}
@@ -357,6 +368,7 @@ _Py_CoerceLegacyLocale(int warn)
done:
PyMem_RawFree(oldloc);
#endif
+ return coerced;
}
/* _Py_SetLocaleFromEnv() is a wrapper around setlocale(category, "") to
From 77b3b7701a34ecf6316469e05b79bb91de2addfa Mon Sep 17 00:00:00 2001
From: Lisa Roach
Date: Mon, 20 May 2019 09:19:53 -0700
Subject: [PATCH 031/441] bpo-26467: Adds AsyncMock for asyncio Mock library
support (GH-9296)
---
Doc/library/unittest.mock.rst | 215 ++++++-
Doc/whatsnew/3.8.rst | 4 +
Lib/unittest/mock.py | 406 ++++++++++++-
Lib/unittest/test/testmock/testasync.py | 549 ++++++++++++++++++
Lib/unittest/test/testmock/testmock.py | 5 +-
.../2018-09-13-20-33-24.bpo-26467.cahAk3.rst | 2 +
6 files changed, 1161 insertions(+), 20 deletions(-)
create mode 100644 Lib/unittest/test/testmock/testasync.py
create mode 100644 Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst
diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst
index ed00ee6d0c2d83..21e4709f81609e 100644
--- a/Doc/library/unittest.mock.rst
+++ b/Doc/library/unittest.mock.rst
@@ -201,9 +201,11 @@ The Mock Class
.. testsetup::
+ import asyncio
+ import inspect
import unittest
from unittest.mock import sentinel, DEFAULT, ANY
- from unittest.mock import patch, call, Mock, MagicMock, PropertyMock
+ from unittest.mock import patch, call, Mock, MagicMock, PropertyMock, AsyncMock
from unittest.mock import mock_open
:class:`Mock` is a flexible mock object intended to replace the use of stubs and
@@ -851,6 +853,217 @@ object::
>>> p.assert_called_once_with()
+.. class:: AsyncMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)
+
+ An asynchronous version of :class:`Mock`. The :class:`AsyncMock` object will
+ behave so the object is recognized as an async function, and the result of a
+ call is an awaitable.
+
+ >>> mock = AsyncMock()
+ >>> asyncio.iscoroutinefunction(mock)
+ True
+ >>> inspect.isawaitable(mock())
+ True
+
+ The result of ``mock()`` is an async function which will have the outcome
+ of ``side_effect`` or ``return_value``:
+
+ - if ``side_effect`` is a function, the async function will return the
+ result of that function,
+ - if ``side_effect`` is an exception, the async function will raise the
+ exception,
+ - if ``side_effect`` is an iterable, the async function will return the
+ next value of the iterable, however, if the sequence of result is
+ exhausted, ``StopIteration`` is raised immediately,
+ - if ``side_effect`` is not defined, the async function will return the
+ value defined by ``return_value``, hence, by default, the async function
+ returns a new :class:`AsyncMock` object.
+
+
+ Setting the *spec* of a :class:`Mock` or :class:`MagicMock` to an async function
+ will result in a coroutine object being returned after calling.
+
+ >>> async def async_func(): pass
+ ...
+ >>> mock = MagicMock(async_func)
+ >>> mock
+
+ >>> mock()
+
+
+ .. method:: assert_awaited()
+
+ Assert that the mock was awaited at least once.
+
+ >>> mock = AsyncMock()
+ >>> async def main():
+ ... await mock()
+ ...
+ >>> asyncio.run(main())
+ >>> mock.assert_awaited()
+ >>> mock_2 = AsyncMock()
+ >>> mock_2.assert_awaited()
+ Traceback (most recent call last):
+ ...
+ AssertionError: Expected mock to have been awaited.
+
+ .. method:: assert_awaited_once()
+
+ Assert that the mock was awaited exactly once.
+
+ >>> mock = AsyncMock()
+ >>> async def main():
+ ... await mock()
+ ...
+ >>> asyncio.run(main())
+ >>> mock.assert_awaited_once()
+ >>> asyncio.run(main())
+ >>> mock.method.assert_awaited_once()
+ Traceback (most recent call last):
+ ...
+ AssertionError: Expected mock to have been awaited once. Awaited 2 times.
+
+ .. method:: assert_awaited_with(*args, **kwargs)
+
+ Assert that the last await was with the specified arguments.
+
+ >>> mock = AsyncMock()
+ >>> async def main(*args, **kwargs):
+ ... await mock(*args, **kwargs)
+ ...
+ >>> asyncio.run(main('foo', bar='bar'))
+ >>> mock.assert_awaited_with('foo', bar='bar')
+ >>> mock.assert_awaited_with('other')
+ Traceback (most recent call last):
+ ...
+ AssertionError: expected call not found.
+ Expected: mock('other')
+ Actual: mock('foo', bar='bar')
+
+ .. method:: assert_awaited_once_with(*args, **kwargs)
+
+ Assert that the mock was awaited exactly once and with the specified
+ arguments.
+
+ >>> mock = AsyncMock()
+ >>> async def main(*args, **kwargs):
+ ... await mock(*args, **kwargs)
+ ...
+ >>> asyncio.run(main('foo', bar='bar'))
+ >>> mock.assert_awaited_once_with('foo', bar='bar')
+ >>> asyncio.run(main('foo', bar='bar'))
+ >>> mock.assert_awaited_once_with('foo', bar='bar')
+ Traceback (most recent call last):
+ ...
+ AssertionError: Expected mock to have been awaited once. Awaited 2 times.
+
+ .. method:: assert_any_await(*args, **kwargs)
+
+ Assert the mock has ever been awaited with the specified arguments.
+
+ >>> mock = AsyncMock()
+ >>> async def main(*args, **kwargs):
+ ... await mock(*args, **kwargs)
+ ...
+ >>> asyncio.run(main('foo', bar='bar'))
+ >>> asyncio.run(main('hello'))
+ >>> mock.assert_any_await('foo', bar='bar')
+ >>> mock.assert_any_await('other')
+ Traceback (most recent call last):
+ ...
+ AssertionError: mock('other') await not found
+
+ .. method:: assert_has_awaits(calls, any_order=False)
+
+ Assert the mock has been awaited with the specified calls.
+ The :attr:`await_args_list` list is checked for the awaits.
+
+ If *any_order* is False (the default) then the awaits must be
+ sequential. There can be extra calls before or after the
+ specified awaits.
+
+ If *any_order* is True then the awaits can be in any order, but
+ they must all appear in :attr:`await_args_list`.
+
+ >>> mock = AsyncMock()
+ >>> async def main(*args, **kwargs):
+ ... await mock(*args, **kwargs)
+ ...
+ >>> calls = [call("foo"), call("bar")]
+ >>> mock.assert_has_calls(calls)
+ Traceback (most recent call last):
+ ...
+ AssertionError: Calls not found.
+ Expected: [call('foo'), call('bar')]
+ >>> asyncio.run(main('foo'))
+ >>> asyncio.run(main('bar'))
+ >>> mock.assert_has_calls(calls)
+
+ .. method:: assert_not_awaited()
+
+ Assert that the mock was never awaited.
+
+ >>> mock = AsyncMock()
+ >>> mock.assert_not_awaited()
+
+ .. method:: reset_mock(*args, **kwargs)
+
+ See :func:`Mock.reset_mock`. Also sets :attr:`await_count` to 0,
+ :attr:`await_args` to None, and clears the :attr:`await_args_list`.
+
+ .. attribute:: await_count
+
+ An integer keeping track of how many times the mock object has been awaited.
+
+ >>> mock = AsyncMock()
+ >>> async def main():
+ ... await mock()
+ ...
+ >>> asyncio.run(main())
+ >>> mock.await_count
+ 1
+ >>> asyncio.run(main())
+ >>> mock.await_count
+ 2
+
+ .. attribute:: await_args
+
+ This is either ``None`` (if the mock hasn’t been awaited), or the arguments that
+ the mock was last awaited with. Functions the same as :attr:`Mock.call_args`.
+
+ >>> mock = AsyncMock()
+ >>> async def main(*args):
+ ... await mock(*args)
+ ...
+ >>> mock.await_args
+ >>> asyncio.run(main('foo'))
+ >>> mock.await_args
+ call('foo')
+ >>> asyncio.run(main('bar'))
+ >>> mock.await_args
+ call('bar')
+
+
+ .. attribute:: await_args_list
+
+ This is a list of all the awaits made to the mock object in sequence (so the
+ length of the list is the number of times it has been awaited). Before any
+ awaits have been made it is an empty list.
+
+ >>> mock = AsyncMock()
+ >>> async def main(*args):
+ ... await mock(*args)
+ ...
+ >>> mock.await_args_list
+ []
+ >>> asyncio.run(main('foo'))
+ >>> mock.await_args_list
+ [call('foo')]
+ >>> asyncio.run(main('bar'))
+ >>> mock.await_args_list
+ [call('foo'), call('bar')]
+
+
Calling
~~~~~~~
diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index 07da4047a383a7..0a79b6ce1a652c 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -538,6 +538,10 @@ unicodedata
unittest
--------
+* XXX Added :class:`AsyncMock` to support an asynchronous version of :class:`Mock`.
+ Appropriate new assert functions for testing have been added as well.
+ (Contributed by Lisa Roach in :issue:`26467`).
+
* Added :func:`~unittest.addModuleCleanup()` and
:meth:`~unittest.TestCase.addClassCleanup()` to unittest to support
cleanups for :func:`~unittest.setUpModule()` and
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 47ed06c6f486af..166c1003769848 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -13,6 +13,7 @@
'ANY',
'call',
'create_autospec',
+ 'AsyncMock',
'FILTER_DIR',
'NonCallableMock',
'NonCallableMagicMock',
@@ -24,13 +25,13 @@
__version__ = '1.0'
-
+import asyncio
import io
import inspect
import pprint
import sys
import builtins
-from types import ModuleType, MethodType
+from types import CodeType, ModuleType, MethodType
from unittest.util import safe_repr
from functools import wraps, partial
@@ -43,6 +44,13 @@
# Without this, the __class__ properties wouldn't be set correctly
_safe_super = super
+def _is_async_obj(obj):
+ if getattr(obj, '__code__', None):
+ return asyncio.iscoroutinefunction(obj) or inspect.isawaitable(obj)
+ else:
+ return False
+
+
def _is_instance_mock(obj):
# can't use isinstance on Mock objects because they override __class__
# The base class for all mocks is NonCallableMock
@@ -355,7 +363,20 @@ def __new__(cls, *args, **kw):
# every instance has its own class
# so we can create magic methods on the
# class without stomping on other mocks
- new = type(cls.__name__, (cls,), {'__doc__': cls.__doc__})
+ bases = (cls,)
+ if not issubclass(cls, AsyncMock):
+ # Check if spec is an async object or function
+ sig = inspect.signature(NonCallableMock.__init__)
+ bound_args = sig.bind_partial(cls, *args, **kw).arguments
+ spec_arg = [
+ arg for arg in bound_args.keys()
+ if arg.startswith('spec')
+ ]
+ if spec_arg:
+ # what if spec_set is different than spec?
+ if _is_async_obj(bound_args[spec_arg[0]]):
+ bases = (AsyncMockMixin, cls,)
+ new = type(cls.__name__, bases, {'__doc__': cls.__doc__})
instance = object.__new__(new)
return instance
@@ -431,6 +452,11 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
_eat_self=False):
_spec_class = None
_spec_signature = None
+ _spec_asyncs = []
+
+ for attr in dir(spec):
+ if asyncio.iscoroutinefunction(getattr(spec, attr, None)):
+ _spec_asyncs.append(attr)
if spec is not None and not _is_list(spec):
if isinstance(spec, type):
@@ -448,7 +474,7 @@ def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,
__dict__['_spec_set'] = spec_set
__dict__['_spec_signature'] = _spec_signature
__dict__['_mock_methods'] = spec
-
+ __dict__['_spec_asyncs'] = _spec_asyncs
def __get_return_value(self):
ret = self._mock_return_value
@@ -886,7 +912,15 @@ def _get_child_mock(self, **kw):
For non-callable mocks the callable variant will be used (rather than
any custom subclass)."""
+ _new_name = kw.get("_new_name")
+ if _new_name in self.__dict__['_spec_asyncs']:
+ return AsyncMock(**kw)
+
_type = type(self)
+ if issubclass(_type, MagicMock) and _new_name in _async_method_magics:
+ klass = AsyncMock
+ if issubclass(_type, AsyncMockMixin):
+ klass = MagicMock
if not issubclass(_type, CallableMixin):
if issubclass(_type, NonCallableMagicMock):
klass = MagicMock
@@ -932,14 +966,12 @@ def _try_iter(obj):
return obj
-
class CallableMixin(Base):
def __init__(self, spec=None, side_effect=None, return_value=DEFAULT,
wraps=None, name=None, spec_set=None, parent=None,
_spec_state=None, _new_name='', _new_parent=None, **kwargs):
self.__dict__['_mock_return_value'] = return_value
-
_safe_super(CallableMixin, self).__init__(
spec, wraps, name, spec_set, parent,
_spec_state, _new_name, _new_parent, **kwargs
@@ -1081,7 +1113,6 @@ class or instance) that acts as the specification for the mock object. If
"""
-
def _dot_lookup(thing, comp, import_path):
try:
return getattr(thing, comp)
@@ -1279,8 +1310,10 @@ def __enter__(self):
if isinstance(original, type):
# If we're patching out a class and there is a spec
inherit = True
-
- Klass = MagicMock
+ if spec is None and _is_async_obj(original):
+ Klass = AsyncMock
+ else:
+ Klass = MagicMock
_kwargs = {}
if new_callable is not None:
Klass = new_callable
@@ -1292,7 +1325,9 @@ def __enter__(self):
not_callable = '__call__' not in this_spec
else:
not_callable = not callable(this_spec)
- if not_callable:
+ if _is_async_obj(this_spec):
+ Klass = AsyncMock
+ elif not_callable:
Klass = NonCallableMagicMock
if spec is not None:
@@ -1733,7 +1768,7 @@ def _patch_stopall():
'__reduce__', '__reduce_ex__', '__getinitargs__', '__getnewargs__',
'__getstate__', '__setstate__', '__getformat__', '__setformat__',
'__repr__', '__dir__', '__subclasses__', '__format__',
- '__getnewargs_ex__',
+ '__getnewargs_ex__', '__aenter__', '__aexit__', '__anext__', '__aiter__',
}
@@ -1750,6 +1785,11 @@ def method(self, *args, **kw):
' '.join([magic_methods, numerics, inplace, right]).split()
}
+# Magic methods used for async `with` statements
+_async_method_magics = {"__aenter__", "__aexit__", "__anext__"}
+# `__aiter__` is a plain function but used with async calls
+_async_magics = _async_method_magics | {"__aiter__"}
+
_all_magics = _magics | _non_defaults
_unsupported_magics = {
@@ -1779,6 +1819,7 @@ def method(self, *args, **kw):
'__float__': 1.0,
'__bool__': True,
'__index__': 1,
+ '__aexit__': False,
}
@@ -1811,10 +1852,19 @@ def __iter__():
return iter(ret_val)
return __iter__
+def _get_async_iter(self):
+ def __aiter__():
+ ret_val = self.__aiter__._mock_return_value
+ if ret_val is DEFAULT:
+ return _AsyncIterator(iter([]))
+ return _AsyncIterator(iter(ret_val))
+ return __aiter__
+
_side_effect_methods = {
'__eq__': _get_eq,
'__ne__': _get_ne,
'__iter__': _get_iter,
+ '__aiter__': _get_async_iter
}
@@ -1879,8 +1929,33 @@ def mock_add_spec(self, spec, spec_set=False):
self._mock_set_magics()
+class AsyncMagicMixin:
+ def __init__(self, *args, **kw):
+ self._mock_set_async_magics() # make magic work for kwargs in init
+ _safe_super(AsyncMagicMixin, self).__init__(*args, **kw)
+ self._mock_set_async_magics() # fix magic broken by upper level init
+
+ def _mock_set_async_magics(self):
+ these_magics = _async_magics
-class MagicMock(MagicMixin, Mock):
+ if getattr(self, "_mock_methods", None) is not None:
+ these_magics = _async_magics.intersection(self._mock_methods)
+ remove_magics = _async_magics - these_magics
+
+ for entry in remove_magics:
+ if entry in type(self).__dict__:
+ # remove unneeded magic methods
+ delattr(self, entry)
+
+ # don't overwrite existing attributes if called a second time
+ these_magics = these_magics - set(type(self).__dict__)
+
+ _type = type(self)
+ for entry in these_magics:
+ setattr(_type, entry, MagicProxy(entry, self))
+
+
+class MagicMock(MagicMixin, AsyncMagicMixin, Mock):
"""
MagicMock is a subclass of Mock with default implementations
of most of the magic methods. You can use MagicMock without having to
@@ -1920,6 +1995,218 @@ def __get__(self, obj, _type=None):
return self.create_mock()
+class AsyncMockMixin(Base):
+ awaited = _delegating_property('awaited')
+ await_count = _delegating_property('await_count')
+ await_args = _delegating_property('await_args')
+ await_args_list = _delegating_property('await_args_list')
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ # asyncio.iscoroutinefunction() checks _is_coroutine property to say if an
+ # object is a coroutine. Without this check it looks to see if it is a
+ # function/method, which in this case it is not (since it is an
+ # AsyncMock).
+ # It is set through __dict__ because when spec_set is True, this
+ # attribute is likely undefined.
+ self.__dict__['_is_coroutine'] = asyncio.coroutines._is_coroutine
+ self.__dict__['_mock_awaited'] = _AwaitEvent(self)
+ self.__dict__['_mock_await_count'] = 0
+ self.__dict__['_mock_await_args'] = None
+ self.__dict__['_mock_await_args_list'] = _CallList()
+ code_mock = NonCallableMock(spec_set=CodeType)
+ code_mock.co_flags = inspect.CO_COROUTINE
+ self.__dict__['__code__'] = code_mock
+
+ async def _mock_call(_mock_self, *args, **kwargs):
+ self = _mock_self
+ try:
+ result = super()._mock_call(*args, **kwargs)
+ except (BaseException, StopIteration) as e:
+ side_effect = self.side_effect
+ if side_effect is not None and not callable(side_effect):
+ raise
+ return await _raise(e)
+
+ _call = self.call_args
+
+ async def proxy():
+ try:
+ if inspect.isawaitable(result):
+ return await result
+ else:
+ return result
+ finally:
+ self.await_count += 1
+ self.await_args = _call
+ self.await_args_list.append(_call)
+ await self.awaited._notify()
+
+ return await proxy()
+
+ def assert_awaited(_mock_self):
+ """
+ Assert that the mock was awaited at least once.
+ """
+ self = _mock_self
+ if self.await_count == 0:
+ msg = f"Expected {self._mock_name or 'mock'} to have been awaited."
+ raise AssertionError(msg)
+
+ def assert_awaited_once(_mock_self):
+ """
+ Assert that the mock was awaited exactly once.
+ """
+ self = _mock_self
+ if not self.await_count == 1:
+ msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once."
+ f" Awaited {self.await_count} times.")
+ raise AssertionError(msg)
+
+ def assert_awaited_with(_mock_self, *args, **kwargs):
+ """
+ Assert that the last await was with the specified arguments.
+ """
+ self = _mock_self
+ if self.await_args is None:
+ expected = self._format_mock_call_signature(args, kwargs)
+ raise AssertionError(f'Expected await: {expected}\nNot awaited')
+
+ def _error_message():
+ msg = self._format_mock_failure_message(args, kwargs)
+ return msg
+
+ expected = self._call_matcher((args, kwargs))
+ actual = self._call_matcher(self.await_args)
+ if expected != actual:
+ cause = expected if isinstance(expected, Exception) else None
+ raise AssertionError(_error_message()) from cause
+
+ def assert_awaited_once_with(_mock_self, *args, **kwargs):
+ """
+ Assert that the mock was awaited exactly once and with the specified
+ arguments.
+ """
+ self = _mock_self
+ if not self.await_count == 1:
+ msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once."
+ f" Awaited {self.await_count} times.")
+ raise AssertionError(msg)
+ return self.assert_awaited_with(*args, **kwargs)
+
+ def assert_any_await(_mock_self, *args, **kwargs):
+ """
+ Assert the mock has ever been awaited with the specified arguments.
+ """
+ self = _mock_self
+ expected = self._call_matcher((args, kwargs))
+ actual = [self._call_matcher(c) for c in self.await_args_list]
+ if expected not in actual:
+ cause = expected if isinstance(expected, Exception) else None
+ expected_string = self._format_mock_call_signature(args, kwargs)
+ raise AssertionError(
+ '%s await not found' % expected_string
+ ) from cause
+
+ def assert_has_awaits(_mock_self, calls, any_order=False):
+ """
+ Assert the mock has been awaited with the specified calls.
+ The :attr:`await_args_list` list is checked for the awaits.
+
+ If `any_order` is False (the default) then the awaits must be
+ sequential. There can be extra calls before or after the
+ specified awaits.
+
+ If `any_order` is True then the awaits can be in any order, but
+ they must all appear in :attr:`await_args_list`.
+ """
+ self = _mock_self
+ expected = [self._call_matcher(c) for c in calls]
+ cause = expected if isinstance(expected, Exception) else None
+ all_awaits = _CallList(self._call_matcher(c) for c in self.await_args_list)
+ if not any_order:
+ if expected not in all_awaits:
+ raise AssertionError(
+ f'Awaits not found.\nExpected: {_CallList(calls)}\n',
+ f'Actual: {self.await_args_list}'
+ ) from cause
+ return
+
+ all_awaits = list(all_awaits)
+
+ not_found = []
+ for kall in expected:
+ try:
+ all_awaits.remove(kall)
+ except ValueError:
+ not_found.append(kall)
+ if not_found:
+ raise AssertionError(
+ '%r not all found in await list' % (tuple(not_found),)
+ ) from cause
+
+ def assert_not_awaited(_mock_self):
+ """
+ Assert that the mock was never awaited.
+ """
+ self = _mock_self
+ if self.await_count != 0:
+ msg = (f"Expected {self._mock_name or 'mock'} to have been awaited once."
+ f" Awaited {self.await_count} times.")
+ raise AssertionError(msg)
+
+ def reset_mock(self, *args, **kwargs):
+ """
+ See :func:`.Mock.reset_mock()`
+ """
+ super().reset_mock(*args, **kwargs)
+ self.await_count = 0
+ self.await_args = None
+ self.await_args_list = _CallList()
+
+
+class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock):
+ """
+ Enhance :class:`Mock` with features allowing to mock
+ an async function.
+
+ The :class:`AsyncMock` object will behave so the object is
+ recognized as an async function, and the result of a call is an awaitable:
+
+ >>> mock = AsyncMock()
+ >>> asyncio.iscoroutinefunction(mock)
+ True
+ >>> inspect.isawaitable(mock())
+ True
+
+
+ The result of ``mock()`` is an async function which will have the outcome
+ of ``side_effect`` or ``return_value``:
+
+ - if ``side_effect`` is a function, the async function will return the
+ result of that function,
+ - if ``side_effect`` is an exception, the async function will raise the
+ exception,
+ - if ``side_effect`` is an iterable, the async function will return the
+ next value of the iterable, however, if the sequence of result is
+ exhausted, ``StopIteration`` is raised immediately,
+ - if ``side_effect`` is not defined, the async function will return the
+ value defined by ``return_value``, hence, by default, the async function
+ returns a new :class:`AsyncMock` object.
+
+ If the outcome of ``side_effect`` or ``return_value`` is an async function,
+ the mock async function obtained when the mock object is called will be this
+ async function itself (and not an async function returning an async
+ function).
+
+ The test author can also specify a wrapped object with ``wraps``. In this
+ case, the :class:`Mock` object behavior is the same as with an
+ :class:`.Mock` object: the wrapped object may have methods
+ defined as async function functions.
+
+ Based on Martin Richard's asyntest project.
+ """
+
class _ANY(object):
"A helper object that compares equal to everything."
@@ -2145,7 +2432,6 @@ def call_list(self):
call = _Call(from_kall=False)
-
def create_autospec(spec, spec_set=False, instance=False, _parent=None,
_name=None, **kwargs):
"""Create a mock object using another object as a spec. Attributes on the
@@ -2171,7 +2457,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
spec = type(spec)
is_type = isinstance(spec, type)
-
+ if getattr(spec, '__code__', None):
+ is_async_func = asyncio.iscoroutinefunction(spec)
+ else:
+ is_async_func = False
_kwargs = {'spec': spec}
if spec_set:
_kwargs = {'spec_set': spec}
@@ -2188,6 +2477,11 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
# descriptors don't have a spec
# because we don't know what type they return
_kwargs = {}
+ elif is_async_func:
+ if instance:
+ raise RuntimeError("Instance can not be True when create_autospec "
+ "is mocking an async function")
+ Klass = AsyncMock
elif not _callable(spec):
Klass = NonCallableMagicMock
elif is_type and instance and not _instance_callable(spec):
@@ -2204,9 +2498,26 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
name=_name, **_kwargs)
if isinstance(spec, FunctionTypes):
+ wrapped_mock = mock
# should only happen at the top level because we don't
# recurse for functions
mock = _set_signature(mock, spec)
+ if is_async_func:
+ mock._is_coroutine = asyncio.coroutines._is_coroutine
+ mock.await_count = 0
+ mock.await_args = None
+ mock.await_args_list = _CallList()
+
+ for a in ('assert_awaited',
+ 'assert_awaited_once',
+ 'assert_awaited_with',
+ 'assert_awaited_once_with',
+ 'assert_any_await',
+ 'assert_has_awaits',
+ 'assert_not_awaited'):
+ def f(*args, **kwargs):
+ return getattr(wrapped_mock, a)(*args, **kwargs)
+ setattr(mock, a, f)
else:
_check_signature(spec, mock, is_type, instance)
@@ -2250,9 +2561,13 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
skipfirst = _must_skip(spec, entry, is_type)
kwargs['_eat_self'] = skipfirst
- new = MagicMock(parent=parent, name=entry, _new_name=entry,
- _new_parent=parent,
- **kwargs)
+ if asyncio.iscoroutinefunction(original):
+ child_klass = AsyncMock
+ else:
+ child_klass = MagicMock
+ new = child_klass(parent=parent, name=entry, _new_name=entry,
+ _new_parent=parent,
+ **kwargs)
mock._mock_children[entry] = new
_check_signature(original, new, skipfirst=skipfirst)
@@ -2438,3 +2753,60 @@ def seal(mock):
continue
if m._mock_new_parent is mock:
seal(m)
+
+
+async def _raise(exception):
+ raise exception
+
+
+class _AsyncIterator:
+ """
+ Wraps an iterator in an asynchronous iterator.
+ """
+ def __init__(self, iterator):
+ self.iterator = iterator
+ code_mock = NonCallableMock(spec_set=CodeType)
+ code_mock.co_flags = inspect.CO_ITERABLE_COROUTINE
+ self.__dict__['__code__'] = code_mock
+
+ def __aiter__(self):
+ return self
+
+ async def __anext__(self):
+ try:
+ return next(self.iterator)
+ except StopIteration:
+ pass
+ raise StopAsyncIteration
+
+
+class _AwaitEvent:
+ def __init__(self, mock):
+ self._mock = mock
+ self._condition = None
+
+ async def _notify(self):
+ condition = self._get_condition()
+ try:
+ await condition.acquire()
+ condition.notify_all()
+ finally:
+ condition.release()
+
+ def _get_condition(self):
+ """
+ Creation of condition is delayed, to minimize the chance of using the
+ wrong loop.
+ A user may create a mock with _AwaitEvent before selecting the
+ execution loop. Requiring a user to delay creation is error-prone and
+ inflexible. Instead, condition is created when user actually starts to
+ use the mock.
+ """
+ # No synchronization is needed:
+ # - asyncio is thread unsafe
+ # - there are no awaits here, method will be executed without
+ # switching asyncio context.
+ if self._condition is None:
+ self._condition = asyncio.Condition()
+
+ return self._condition
diff --git a/Lib/unittest/test/testmock/testasync.py b/Lib/unittest/test/testmock/testasync.py
new file mode 100644
index 00000000000000..a9aa1434b963f1
--- /dev/null
+++ b/Lib/unittest/test/testmock/testasync.py
@@ -0,0 +1,549 @@
+import asyncio
+import inspect
+import unittest
+
+from unittest.mock import call, AsyncMock, patch, MagicMock, create_autospec
+
+
+def tearDownModule():
+ asyncio.set_event_loop_policy(None)
+
+
+class AsyncClass:
+ def __init__(self):
+ pass
+ async def async_method(self):
+ pass
+ def normal_method(self):
+ pass
+
+async def async_func():
+ pass
+
+def normal_func():
+ pass
+
+class NormalClass(object):
+ def a(self):
+ pass
+
+
+async_foo_name = f'{__name__}.AsyncClass'
+normal_foo_name = f'{__name__}.NormalClass'
+
+
+class AsyncPatchDecoratorTest(unittest.TestCase):
+ def test_is_coroutine_function_patch(self):
+ @patch.object(AsyncClass, 'async_method')
+ def test_async(mock_method):
+ self.assertTrue(asyncio.iscoroutinefunction(mock_method))
+ test_async()
+
+ def test_is_async_patch(self):
+ @patch.object(AsyncClass, 'async_method')
+ def test_async(mock_method):
+ m = mock_method()
+ self.assertTrue(inspect.isawaitable(m))
+ asyncio.run(m)
+
+ @patch(f'{async_foo_name}.async_method')
+ def test_no_parent_attribute(mock_method):
+ m = mock_method()
+ self.assertTrue(inspect.isawaitable(m))
+ asyncio.run(m)
+
+ test_async()
+ test_no_parent_attribute()
+
+ def test_is_AsyncMock_patch(self):
+ @patch.object(AsyncClass, 'async_method')
+ def test_async(mock_method):
+ self.assertIsInstance(mock_method, AsyncMock)
+
+ test_async()
+
+
+class AsyncPatchCMTest(unittest.TestCase):
+ def test_is_async_function_cm(self):
+ def test_async():
+ with patch.object(AsyncClass, 'async_method') as mock_method:
+ self.assertTrue(asyncio.iscoroutinefunction(mock_method))
+
+ test_async()
+
+ def test_is_async_cm(self):
+ def test_async():
+ with patch.object(AsyncClass, 'async_method') as mock_method:
+ m = mock_method()
+ self.assertTrue(inspect.isawaitable(m))
+ asyncio.run(m)
+
+ test_async()
+
+ def test_is_AsyncMock_cm(self):
+ def test_async():
+ with patch.object(AsyncClass, 'async_method') as mock_method:
+ self.assertIsInstance(mock_method, AsyncMock)
+
+ test_async()
+
+
+class AsyncMockTest(unittest.TestCase):
+ def test_iscoroutinefunction_default(self):
+ mock = AsyncMock()
+ self.assertTrue(asyncio.iscoroutinefunction(mock))
+
+ def test_iscoroutinefunction_function(self):
+ async def foo(): pass
+ mock = AsyncMock(foo)
+ self.assertTrue(asyncio.iscoroutinefunction(mock))
+ self.assertTrue(inspect.iscoroutinefunction(mock))
+
+ def test_isawaitable(self):
+ mock = AsyncMock()
+ m = mock()
+ self.assertTrue(inspect.isawaitable(m))
+ asyncio.run(m)
+ self.assertIn('assert_awaited', dir(mock))
+
+ def test_iscoroutinefunction_normal_function(self):
+ def foo(): pass
+ mock = AsyncMock(foo)
+ self.assertTrue(asyncio.iscoroutinefunction(mock))
+ self.assertTrue(inspect.iscoroutinefunction(mock))
+
+ def test_future_isfuture(self):
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ fut = asyncio.Future()
+ loop.stop()
+ loop.close()
+ mock = AsyncMock(fut)
+ self.assertIsInstance(mock, asyncio.Future)
+
+
+class AsyncAutospecTest(unittest.TestCase):
+ def test_is_AsyncMock_patch(self):
+ @patch(async_foo_name, autospec=True)
+ def test_async(mock_method):
+ self.assertIsInstance(mock_method.async_method, AsyncMock)
+ self.assertIsInstance(mock_method, MagicMock)
+
+ @patch(async_foo_name, autospec=True)
+ def test_normal_method(mock_method):
+ self.assertIsInstance(mock_method.normal_method, MagicMock)
+
+ test_async()
+ test_normal_method()
+
+ def test_create_autospec_instance(self):
+ with self.assertRaises(RuntimeError):
+ create_autospec(async_func, instance=True)
+
+ def test_create_autospec(self):
+ spec = create_autospec(async_func)
+ self.assertTrue(asyncio.iscoroutinefunction(spec))
+
+
+class AsyncSpecTest(unittest.TestCase):
+ def test_spec_as_async_positional_magicmock(self):
+ mock = MagicMock(async_func)
+ self.assertIsInstance(mock, MagicMock)
+ m = mock()
+ self.assertTrue(inspect.isawaitable(m))
+ asyncio.run(m)
+
+ def test_spec_as_async_kw_magicmock(self):
+ mock = MagicMock(spec=async_func)
+ self.assertIsInstance(mock, MagicMock)
+ m = mock()
+ self.assertTrue(inspect.isawaitable(m))
+ asyncio.run(m)
+
+ def test_spec_as_async_kw_AsyncMock(self):
+ mock = AsyncMock(spec=async_func)
+ self.assertIsInstance(mock, AsyncMock)
+ m = mock()
+ self.assertTrue(inspect.isawaitable(m))
+ asyncio.run(m)
+
+ def test_spec_as_async_positional_AsyncMock(self):
+ mock = AsyncMock(async_func)
+ self.assertIsInstance(mock, AsyncMock)
+ m = mock()
+ self.assertTrue(inspect.isawaitable(m))
+ asyncio.run(m)
+
+ def test_spec_as_normal_kw_AsyncMock(self):
+ mock = AsyncMock(spec=normal_func)
+ self.assertIsInstance(mock, AsyncMock)
+ m = mock()
+ self.assertTrue(inspect.isawaitable(m))
+ asyncio.run(m)
+
+ def test_spec_as_normal_positional_AsyncMock(self):
+ mock = AsyncMock(normal_func)
+ self.assertIsInstance(mock, AsyncMock)
+ m = mock()
+ self.assertTrue(inspect.isawaitable(m))
+ asyncio.run(m)
+
+ def test_spec_async_mock(self):
+ @patch.object(AsyncClass, 'async_method', spec=True)
+ def test_async(mock_method):
+ self.assertIsInstance(mock_method, AsyncMock)
+
+ test_async()
+
+ def test_spec_parent_not_async_attribute_is(self):
+ @patch(async_foo_name, spec=True)
+ def test_async(mock_method):
+ self.assertIsInstance(mock_method, MagicMock)
+ self.assertIsInstance(mock_method.async_method, AsyncMock)
+
+ test_async()
+
+ def test_target_async_spec_not(self):
+ @patch.object(AsyncClass, 'async_method', spec=NormalClass.a)
+ def test_async_attribute(mock_method):
+ self.assertIsInstance(mock_method, MagicMock)
+ self.assertFalse(inspect.iscoroutine(mock_method))
+ self.assertFalse(inspect.isawaitable(mock_method))
+
+ test_async_attribute()
+
+ def test_target_not_async_spec_is(self):
+ @patch.object(NormalClass, 'a', spec=async_func)
+ def test_attribute_not_async_spec_is(mock_async_func):
+ self.assertIsInstance(mock_async_func, AsyncMock)
+ test_attribute_not_async_spec_is()
+
+ def test_spec_async_attributes(self):
+ @patch(normal_foo_name, spec=AsyncClass)
+ def test_async_attributes_coroutines(MockNormalClass):
+ self.assertIsInstance(MockNormalClass.async_method, AsyncMock)
+ self.assertIsInstance(MockNormalClass, MagicMock)
+
+ test_async_attributes_coroutines()
+
+
+class AsyncSpecSetTest(unittest.TestCase):
+ def test_is_AsyncMock_patch(self):
+ @patch.object(AsyncClass, 'async_method', spec_set=True)
+ def test_async(async_method):
+ self.assertIsInstance(async_method, AsyncMock)
+
+ def test_is_async_AsyncMock(self):
+ mock = AsyncMock(spec_set=AsyncClass.async_method)
+ self.assertTrue(asyncio.iscoroutinefunction(mock))
+ self.assertIsInstance(mock, AsyncMock)
+
+ def test_is_child_AsyncMock(self):
+ mock = MagicMock(spec_set=AsyncClass)
+ self.assertTrue(asyncio.iscoroutinefunction(mock.async_method))
+ self.assertFalse(asyncio.iscoroutinefunction(mock.normal_method))
+ self.assertIsInstance(mock.async_method, AsyncMock)
+ self.assertIsInstance(mock.normal_method, MagicMock)
+ self.assertIsInstance(mock, MagicMock)
+
+
+class AsyncArguments(unittest.TestCase):
+ def test_add_return_value(self):
+ async def addition(self, var):
+ return var + 1
+
+ mock = AsyncMock(addition, return_value=10)
+ output = asyncio.run(mock(5))
+
+ self.assertEqual(output, 10)
+
+ def test_add_side_effect_exception(self):
+ async def addition(var):
+ return var + 1
+ mock = AsyncMock(addition, side_effect=Exception('err'))
+ with self.assertRaises(Exception):
+ asyncio.run(mock(5))
+
+ def test_add_side_effect_function(self):
+ async def addition(var):
+ return var + 1
+ mock = AsyncMock(side_effect=addition)
+ result = asyncio.run(mock(5))
+ self.assertEqual(result, 6)
+
+ def test_add_side_effect_iterable(self):
+ vals = [1, 2, 3]
+ mock = AsyncMock(side_effect=vals)
+ for item in vals:
+ self.assertEqual(item, asyncio.run(mock()))
+
+ with self.assertRaises(RuntimeError) as e:
+ asyncio.run(mock())
+ self.assertEqual(
+ e.exception,
+ RuntimeError('coroutine raised StopIteration')
+ )
+
+
+class AsyncContextManagerTest(unittest.TestCase):
+ class WithAsyncContextManager:
+ def __init__(self):
+ self.entered = False
+ self.exited = False
+
+ async def __aenter__(self, *args, **kwargs):
+ self.entered = True
+ return self
+
+ async def __aexit__(self, *args, **kwargs):
+ self.exited = True
+
+ def test_magic_methods_are_async_mocks(self):
+ mock = MagicMock(self.WithAsyncContextManager())
+ self.assertIsInstance(mock.__aenter__, AsyncMock)
+ self.assertIsInstance(mock.__aexit__, AsyncMock)
+
+ def test_mock_supports_async_context_manager(self):
+ called = False
+ instance = self.WithAsyncContextManager()
+ mock_instance = MagicMock(instance)
+
+ async def use_context_manager():
+ nonlocal called
+ async with mock_instance as result:
+ called = True
+ return result
+
+ result = asyncio.run(use_context_manager())
+ self.assertFalse(instance.entered)
+ self.assertFalse(instance.exited)
+ self.assertTrue(called)
+ self.assertTrue(mock_instance.entered)
+ self.assertTrue(mock_instance.exited)
+ self.assertTrue(mock_instance.__aenter__.called)
+ self.assertTrue(mock_instance.__aexit__.called)
+ self.assertIsNot(mock_instance, result)
+ self.assertIsInstance(result, AsyncMock)
+
+ def test_mock_customize_async_context_manager(self):
+ instance = self.WithAsyncContextManager()
+ mock_instance = MagicMock(instance)
+
+ expected_result = object()
+ mock_instance.__aenter__.return_value = expected_result
+
+ async def use_context_manager():
+ async with mock_instance as result:
+ return result
+
+ self.assertIs(asyncio.run(use_context_manager()), expected_result)
+
+ def test_mock_customize_async_context_manager_with_coroutine(self):
+ enter_called = False
+ exit_called = False
+
+ async def enter_coroutine(*args):
+ nonlocal enter_called
+ enter_called = True
+
+ async def exit_coroutine(*args):
+ nonlocal exit_called
+ exit_called = True
+
+ instance = self.WithAsyncContextManager()
+ mock_instance = MagicMock(instance)
+
+ mock_instance.__aenter__ = enter_coroutine
+ mock_instance.__aexit__ = exit_coroutine
+
+ async def use_context_manager():
+ async with mock_instance:
+ pass
+
+ asyncio.run(use_context_manager())
+ self.assertTrue(enter_called)
+ self.assertTrue(exit_called)
+
+ def test_context_manager_raise_exception_by_default(self):
+ async def raise_in(context_manager):
+ async with context_manager:
+ raise TypeError()
+
+ instance = self.WithAsyncContextManager()
+ mock_instance = MagicMock(instance)
+ with self.assertRaises(TypeError):
+ asyncio.run(raise_in(mock_instance))
+
+
+class AsyncIteratorTest(unittest.TestCase):
+ class WithAsyncIterator(object):
+ def __init__(self):
+ self.items = ["foo", "NormalFoo", "baz"]
+
+ def __aiter__(self):
+ return self
+
+ async def __anext__(self):
+ try:
+ return self.items.pop()
+ except IndexError:
+ pass
+
+ raise StopAsyncIteration
+
+ def test_mock_aiter_and_anext(self):
+ instance = self.WithAsyncIterator()
+ mock_instance = MagicMock(instance)
+
+ self.assertEqual(asyncio.iscoroutine(instance.__aiter__),
+ asyncio.iscoroutine(mock_instance.__aiter__))
+ self.assertEqual(asyncio.iscoroutine(instance.__anext__),
+ asyncio.iscoroutine(mock_instance.__anext__))
+
+ iterator = instance.__aiter__()
+ if asyncio.iscoroutine(iterator):
+ iterator = asyncio.run(iterator)
+
+ mock_iterator = mock_instance.__aiter__()
+ if asyncio.iscoroutine(mock_iterator):
+ mock_iterator = asyncio.run(mock_iterator)
+
+ self.assertEqual(asyncio.iscoroutine(iterator.__aiter__),
+ asyncio.iscoroutine(mock_iterator.__aiter__))
+ self.assertEqual(asyncio.iscoroutine(iterator.__anext__),
+ asyncio.iscoroutine(mock_iterator.__anext__))
+
+ def test_mock_async_for(self):
+ async def iterate(iterator):
+ accumulator = []
+ async for item in iterator:
+ accumulator.append(item)
+
+ return accumulator
+
+ expected = ["FOO", "BAR", "BAZ"]
+ with self.subTest("iterate through default value"):
+ mock_instance = MagicMock(self.WithAsyncIterator())
+ self.assertEqual([], asyncio.run(iterate(mock_instance)))
+
+ with self.subTest("iterate through set return_value"):
+ mock_instance = MagicMock(self.WithAsyncIterator())
+ mock_instance.__aiter__.return_value = expected[:]
+ self.assertEqual(expected, asyncio.run(iterate(mock_instance)))
+
+ with self.subTest("iterate through set return_value iterator"):
+ mock_instance = MagicMock(self.WithAsyncIterator())
+ mock_instance.__aiter__.return_value = iter(expected[:])
+ self.assertEqual(expected, asyncio.run(iterate(mock_instance)))
+
+
+class AsyncMockAssert(unittest.TestCase):
+ def setUp(self):
+ self.mock = AsyncMock()
+
+ async def _runnable_test(self, *args):
+ if not args:
+ await self.mock()
+ else:
+ await self.mock(*args)
+
+ def test_assert_awaited(self):
+ with self.assertRaises(AssertionError):
+ self.mock.assert_awaited()
+
+ asyncio.run(self._runnable_test())
+ self.mock.assert_awaited()
+
+ def test_assert_awaited_once(self):
+ with self.assertRaises(AssertionError):
+ self.mock.assert_awaited_once()
+
+ asyncio.run(self._runnable_test())
+ self.mock.assert_awaited_once()
+
+ asyncio.run(self._runnable_test())
+ with self.assertRaises(AssertionError):
+ self.mock.assert_awaited_once()
+
+ def test_assert_awaited_with(self):
+ asyncio.run(self._runnable_test())
+ with self.assertRaises(AssertionError):
+ self.mock.assert_awaited_with('foo')
+
+ asyncio.run(self._runnable_test('foo'))
+ self.mock.assert_awaited_with('foo')
+
+ asyncio.run(self._runnable_test('SomethingElse'))
+ with self.assertRaises(AssertionError):
+ self.mock.assert_awaited_with('foo')
+
+ def test_assert_awaited_once_with(self):
+ with self.assertRaises(AssertionError):
+ self.mock.assert_awaited_once_with('foo')
+
+ asyncio.run(self._runnable_test('foo'))
+ self.mock.assert_awaited_once_with('foo')
+
+ asyncio.run(self._runnable_test('foo'))
+ with self.assertRaises(AssertionError):
+ self.mock.assert_awaited_once_with('foo')
+
+ def test_assert_any_wait(self):
+ with self.assertRaises(AssertionError):
+ self.mock.assert_any_await('NormalFoo')
+
+ asyncio.run(self._runnable_test('foo'))
+ with self.assertRaises(AssertionError):
+ self.mock.assert_any_await('NormalFoo')
+
+ asyncio.run(self._runnable_test('NormalFoo'))
+ self.mock.assert_any_await('NormalFoo')
+
+ asyncio.run(self._runnable_test('SomethingElse'))
+ self.mock.assert_any_await('NormalFoo')
+
+ def test_assert_has_awaits_no_order(self):
+ calls = [call('NormalFoo'), call('baz')]
+
+ with self.assertRaises(AssertionError):
+ self.mock.assert_has_awaits(calls)
+
+ asyncio.run(self._runnable_test('foo'))
+ with self.assertRaises(AssertionError):
+ self.mock.assert_has_awaits(calls)
+
+ asyncio.run(self._runnable_test('NormalFoo'))
+ with self.assertRaises(AssertionError):
+ self.mock.assert_has_awaits(calls)
+
+ asyncio.run(self._runnable_test('baz'))
+ self.mock.assert_has_awaits(calls)
+
+ asyncio.run(self._runnable_test('SomethingElse'))
+ self.mock.assert_has_awaits(calls)
+
+ def test_assert_has_awaits_ordered(self):
+ calls = [call('NormalFoo'), call('baz')]
+ with self.assertRaises(AssertionError):
+ self.mock.assert_has_awaits(calls, any_order=True)
+
+ asyncio.run(self._runnable_test('baz'))
+ with self.assertRaises(AssertionError):
+ self.mock.assert_has_awaits(calls, any_order=True)
+
+ asyncio.run(self._runnable_test('foo'))
+ with self.assertRaises(AssertionError):
+ self.mock.assert_has_awaits(calls, any_order=True)
+
+ asyncio.run(self._runnable_test('NormalFoo'))
+ self.mock.assert_has_awaits(calls, any_order=True)
+
+ asyncio.run(self._runnable_test('qux'))
+ self.mock.assert_has_awaits(calls, any_order=True)
+
+ def test_assert_not_awaited(self):
+ self.mock.assert_not_awaited()
+
+ asyncio.run(self._runnable_test())
+ with self.assertRaises(AssertionError):
+ self.mock.assert_not_awaited()
diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py
index b20b8e20e7e6fd..307b8b7657afa1 100644
--- a/Lib/unittest/test/testmock/testmock.py
+++ b/Lib/unittest/test/testmock/testmock.py
@@ -9,7 +9,7 @@
from unittest.mock import (
call, DEFAULT, patch, sentinel,
MagicMock, Mock, NonCallableMock,
- NonCallableMagicMock, _Call, _CallList,
+ NonCallableMagicMock, AsyncMock, _Call, _CallList,
create_autospec
)
@@ -1618,7 +1618,8 @@ def test_mock_add_spec_magic_methods(self):
def test_adding_child_mock(self):
- for Klass in NonCallableMock, Mock, MagicMock, NonCallableMagicMock:
+ for Klass in (NonCallableMock, Mock, MagicMock, NonCallableMagicMock,
+ AsyncMock):
mock = Klass()
mock.foo = Mock()
diff --git a/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst b/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst
new file mode 100644
index 00000000000000..4cf3f2ae7ef7e6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-09-13-20-33-24.bpo-26467.cahAk3.rst
@@ -0,0 +1,2 @@
+Added AsyncMock to support using unittest to mock asyncio coroutines.
+Patch by Lisa Roach.
From 5ae1c84bcd13b766989fc3f1e1c851e7bd4c1faa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Batuhan=20Ta=C5=9Fkaya?=
<47358913+isidentical@users.noreply.github.com>
Date: Mon, 20 May 2019 20:01:07 +0300
Subject: [PATCH 032/441] bpo-36949: Implement __repr__ on WeakSet (GH-13415)
---
Lib/_weakrefset.py | 3 +++
Lib/test/test_weakset.py | 3 +++
.../next/Library/2019-05-19-06-54-26.bpo-36949.jBlG9F.rst | 1 +
3 files changed, 7 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-19-06-54-26.bpo-36949.jBlG9F.rst
diff --git a/Lib/_weakrefset.py b/Lib/_weakrefset.py
index 304c66f59bd1be..7a84823622ee7c 100644
--- a/Lib/_weakrefset.py
+++ b/Lib/_weakrefset.py
@@ -194,3 +194,6 @@ def union(self, other):
def isdisjoint(self, other):
return len(self.intersection(other)) == 0
+
+ def __repr__(self):
+ return repr(self.data)
diff --git a/Lib/test/test_weakset.py b/Lib/test/test_weakset.py
index 691b95e77c6af9..569facdd30c11c 100644
--- a/Lib/test/test_weakset.py
+++ b/Lib/test/test_weakset.py
@@ -434,6 +434,9 @@ def test_len_race(self):
self.assertGreaterEqual(n2, 0)
self.assertLessEqual(n2, n1)
+ def test_repr(self):
+ assert repr(self.s) == repr(self.s.data)
+
if __name__ == "__main__":
unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2019-05-19-06-54-26.bpo-36949.jBlG9F.rst b/Misc/NEWS.d/next/Library/2019-05-19-06-54-26.bpo-36949.jBlG9F.rst
new file mode 100644
index 00000000000000..e4eeb4010a231d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-19-06-54-26.bpo-36949.jBlG9F.rst
@@ -0,0 +1 @@
+Implement __repr__ for WeakSet objects.
From c09a9f56c08d80567454cae6f78f738a89e1ae94 Mon Sep 17 00:00:00 2001
From: Thomas Moreau
Date: Mon, 20 May 2019 21:37:05 +0200
Subject: [PATCH 033/441] bpo-36888: Add multiprocessing.parent_process()
(GH-13247)
---
Doc/library/multiprocessing.rst | 8 +++
Lib/multiprocessing/context.py | 1 +
Lib/multiprocessing/forkserver.py | 3 +-
Lib/multiprocessing/popen_fork.py | 8 ++-
Lib/multiprocessing/popen_forkserver.py | 6 +-
Lib/multiprocessing/popen_spawn_posix.py | 10 +++-
Lib/multiprocessing/process.py | 52 +++++++++++++++-
Lib/multiprocessing/spawn.py | 19 +++---
Lib/multiprocessing/util.py | 6 ++
Lib/test/_test_multiprocessing.py | 59 +++++++++++++++++++
.../2019-05-16-18-02-08.bpo-36888.-H2Dkm.rst | 2 +
Modules/_winapi.c | 1 +
12 files changed, 155 insertions(+), 20 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-16-18-02-08.bpo-36888.-H2Dkm.rst
diff --git a/Doc/library/multiprocessing.rst b/Doc/library/multiprocessing.rst
index c6ffb00819c32c..cc6dd4e9d70226 100644
--- a/Doc/library/multiprocessing.rst
+++ b/Doc/library/multiprocessing.rst
@@ -944,6 +944,14 @@ Miscellaneous
An analogue of :func:`threading.current_thread`.
+.. function:: parent_process()
+
+ Return the :class:`Process` object corresponding to the parent process of
+ the :func:`current_process`. For the main process, ``parent_process`` will
+ be ``None``.
+
+ .. versionadded:: 3.8
+
.. function:: freeze_support()
Add support for when a program which uses :mod:`multiprocessing` has been
diff --git a/Lib/multiprocessing/context.py b/Lib/multiprocessing/context.py
index 871746b1a047b3..5a4865751c2272 100644
--- a/Lib/multiprocessing/context.py
+++ b/Lib/multiprocessing/context.py
@@ -35,6 +35,7 @@ class BaseContext(object):
AuthenticationError = AuthenticationError
current_process = staticmethod(process.current_process)
+ parent_process = staticmethod(process.parent_process)
active_children = staticmethod(process.active_children)
def cpu_count(self):
diff --git a/Lib/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py
index dabf7bcbe6d785..9b6398671dbc5e 100644
--- a/Lib/multiprocessing/forkserver.py
+++ b/Lib/multiprocessing/forkserver.py
@@ -294,7 +294,8 @@ def _serve_one(child_r, fds, unused_fds, handlers):
*_forkserver._inherited_fds) = fds
# Run process object received over pipe
- code = spawn._main(child_r)
+ parent_sentinel = os.dup(child_r)
+ code = spawn._main(child_r, parent_sentinel)
return code
diff --git a/Lib/multiprocessing/popen_fork.py b/Lib/multiprocessing/popen_fork.py
index 685e8daf77ca1f..11e216072d0919 100644
--- a/Lib/multiprocessing/popen_fork.py
+++ b/Lib/multiprocessing/popen_fork.py
@@ -66,16 +66,20 @@ def kill(self):
def _launch(self, process_obj):
code = 1
parent_r, child_w = os.pipe()
+ child_r, parent_w = os.pipe()
self.pid = os.fork()
if self.pid == 0:
try:
os.close(parent_r)
- code = process_obj._bootstrap()
+ os.close(parent_w)
+ code = process_obj._bootstrap(parent_sentinel=child_r)
finally:
os._exit(code)
else:
os.close(child_w)
- self.finalizer = util.Finalize(self, os.close, (parent_r,))
+ os.close(child_r)
+ self.finalizer = util.Finalize(self, util.close_fds,
+ (parent_r, parent_w,))
self.sentinel = parent_r
def close(self):
diff --git a/Lib/multiprocessing/popen_forkserver.py b/Lib/multiprocessing/popen_forkserver.py
index a51a2771aed8cc..a56eb9bf11080b 100644
--- a/Lib/multiprocessing/popen_forkserver.py
+++ b/Lib/multiprocessing/popen_forkserver.py
@@ -49,7 +49,11 @@ def _launch(self, process_obj):
set_spawning_popen(None)
self.sentinel, w = forkserver.connect_to_new_process(self._fds)
- self.finalizer = util.Finalize(self, os.close, (self.sentinel,))
+ # Keep a duplicate of the data pipe's write end as a sentinel of the
+ # parent process used by the child process.
+ _parent_w = os.dup(w)
+ self.finalizer = util.Finalize(self, util.close_fds,
+ (_parent_w, self.sentinel))
with open(w, 'wb', closefd=True) as f:
f.write(buf.getbuffer())
self.pid = forkserver.read_signed(self.sentinel)
diff --git a/Lib/multiprocessing/popen_spawn_posix.py b/Lib/multiprocessing/popen_spawn_posix.py
index 59f8e452cae1d5..24b8634523e5f2 100644
--- a/Lib/multiprocessing/popen_spawn_posix.py
+++ b/Lib/multiprocessing/popen_spawn_posix.py
@@ -61,8 +61,12 @@ def _launch(self, process_obj):
with open(parent_w, 'wb', closefd=False) as f:
f.write(fp.getbuffer())
finally:
- if parent_r is not None:
- self.finalizer = util.Finalize(self, os.close, (parent_r,))
- for fd in (child_r, child_w, parent_w):
+ fds_to_close = []
+ for fd in (parent_r, parent_w):
+ if fd is not None:
+ fds_to_close.append(fd)
+ self.finalizer = util.Finalize(self, util.close_fds, fds_to_close)
+
+ for fd in (child_r, child_w):
if fd is not None:
os.close(fd)
diff --git a/Lib/multiprocessing/process.py b/Lib/multiprocessing/process.py
index 780f2d0c273472..c62c826cff9580 100644
--- a/Lib/multiprocessing/process.py
+++ b/Lib/multiprocessing/process.py
@@ -7,7 +7,8 @@
# Licensed to PSF under a Contributor Agreement.
#
-__all__ = ['BaseProcess', 'current_process', 'active_children']
+__all__ = ['BaseProcess', 'current_process', 'active_children',
+ 'parent_process']
#
# Imports
@@ -46,6 +47,13 @@ def active_children():
_cleanup()
return list(_children)
+
+def parent_process():
+ '''
+ Return process object representing the parent process
+ '''
+ return _parent_process
+
#
#
#
@@ -76,6 +84,7 @@ def __init__(self, group=None, target=None, name=None, args=(), kwargs={},
self._identity = _current_process._identity + (count,)
self._config = _current_process._config.copy()
self._parent_pid = os.getpid()
+ self._parent_name = _current_process.name
self._popen = None
self._closed = False
self._target = target
@@ -278,9 +287,9 @@ def __repr__(self):
##
- def _bootstrap(self):
+ def _bootstrap(self, parent_sentinel=None):
from . import util, context
- global _current_process, _process_counter, _children
+ global _current_process, _parent_process, _process_counter, _children
try:
if self._start_method is not None:
@@ -290,6 +299,8 @@ def _bootstrap(self):
util._close_stdin()
old_process = _current_process
_current_process = self
+ _parent_process = _ParentProcess(
+ self._parent_name, self._parent_pid, parent_sentinel)
try:
util._finalizer_registry.clear()
util._run_after_forkers()
@@ -337,6 +348,40 @@ def __reduce__(self):
)
return AuthenticationString, (bytes(self),)
+
+#
+# Create object representing the parent process
+#
+
+class _ParentProcess(BaseProcess):
+
+ def __init__(self, name, pid, sentinel):
+ self._identity = ()
+ self._name = name
+ self._pid = pid
+ self._parent_pid = None
+ self._popen = None
+ self._closed = False
+ self._sentinel = sentinel
+ self._config = {}
+
+ def is_alive(self):
+ from multiprocessing.connection import wait
+ return not wait([self._sentinel], timeout=0)
+
+ @property
+ def ident(self):
+ return self._pid
+
+ def join(self, timeout=None):
+ '''
+ Wait until parent process terminates
+ '''
+ from multiprocessing.connection import wait
+ wait([self._sentinel], timeout=timeout)
+
+ pid = ident
+
#
# Create object representing the main process
#
@@ -365,6 +410,7 @@ def close(self):
pass
+_parent_process = None
_current_process = _MainProcess()
_process_counter = itertools.count(1)
_children = set()
diff --git a/Lib/multiprocessing/spawn.py b/Lib/multiprocessing/spawn.py
index f66b5aa9267b6d..7cc129e2610761 100644
--- a/Lib/multiprocessing/spawn.py
+++ b/Lib/multiprocessing/spawn.py
@@ -100,25 +100,24 @@ def spawn_main(pipe_handle, parent_pid=None, tracker_fd=None):
if parent_pid is not None:
source_process = _winapi.OpenProcess(
- _winapi.PROCESS_DUP_HANDLE, False, parent_pid)
+ _winapi.SYNCHRONIZE | _winapi.PROCESS_DUP_HANDLE,
+ False, parent_pid)
else:
source_process = None
- try:
- new_handle = reduction.duplicate(pipe_handle,
- source_process=source_process)
- finally:
- if source_process is not None:
- _winapi.CloseHandle(source_process)
+ new_handle = reduction.duplicate(pipe_handle,
+ source_process=source_process)
fd = msvcrt.open_osfhandle(new_handle, os.O_RDONLY)
+ parent_sentinel = source_process
else:
from . import resource_tracker
resource_tracker._resource_tracker._fd = tracker_fd
fd = pipe_handle
- exitcode = _main(fd)
+ parent_sentinel = os.dup(pipe_handle)
+ exitcode = _main(fd, parent_sentinel)
sys.exit(exitcode)
-def _main(fd):
+def _main(fd, parent_sentinel):
with os.fdopen(fd, 'rb', closefd=True) as from_parent:
process.current_process()._inheriting = True
try:
@@ -127,7 +126,7 @@ def _main(fd):
self = reduction.pickle.load(from_parent)
finally:
del process.current_process()._inheriting
- return self._bootstrap()
+ return self._bootstrap(parent_sentinel)
def _check_not_importing_main():
diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py
index 0c4eb2473273b4..5674ad773f9764 100644
--- a/Lib/multiprocessing/util.py
+++ b/Lib/multiprocessing/util.py
@@ -421,3 +421,9 @@ def spawnv_passfds(path, args, passfds):
finally:
os.close(errpipe_read)
os.close(errpipe_write)
+
+
+def close_fds(*fds):
+ """Close each file descriptor given as an argument"""
+ for fd in fds:
+ os.close(fd)
diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py
index 78ec53beb0f01d..071b54a713e2d8 100644
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -269,6 +269,64 @@ def _test(cls, q, *args, **kwds):
q.put(bytes(current.authkey))
q.put(current.pid)
+ def test_parent_process_attributes(self):
+ if self.TYPE == "threads":
+ self.skipTest('test not appropriate for {}'.format(self.TYPE))
+
+ self.assertIsNone(self.parent_process())
+
+ rconn, wconn = self.Pipe(duplex=False)
+ p = self.Process(target=self._test_send_parent_process, args=(wconn,))
+ p.start()
+ p.join()
+ parent_pid, parent_name = rconn.recv()
+ self.assertEqual(parent_pid, self.current_process().pid)
+ self.assertEqual(parent_pid, os.getpid())
+ self.assertEqual(parent_name, self.current_process().name)
+
+ @classmethod
+ def _test_send_parent_process(cls, wconn):
+ from multiprocessing.process import parent_process
+ wconn.send([parent_process().pid, parent_process().name])
+
+ def test_parent_process(self):
+ if self.TYPE == "threads":
+ self.skipTest('test not appropriate for {}'.format(self.TYPE))
+
+ # Launch a child process. Make it launch a grandchild process. Kill the
+ # child process and make sure that the grandchild notices the death of
+ # its parent (a.k.a the child process).
+ rconn, wconn = self.Pipe(duplex=False)
+ p = self.Process(
+ target=self._test_create_grandchild_process, args=(wconn, ))
+ p.start()
+
+ if not rconn.poll(timeout=5):
+ raise AssertionError("Could not communicate with child process")
+ parent_process_status = rconn.recv()
+ self.assertEqual(parent_process_status, "alive")
+
+ p.terminate()
+ p.join()
+
+ if not rconn.poll(timeout=5):
+ raise AssertionError("Could not communicate with child process")
+ parent_process_status = rconn.recv()
+ self.assertEqual(parent_process_status, "not alive")
+
+ @classmethod
+ def _test_create_grandchild_process(cls, wconn):
+ p = cls.Process(target=cls._test_report_parent_status, args=(wconn, ))
+ p.start()
+ time.sleep(100)
+
+ @classmethod
+ def _test_report_parent_status(cls, wconn):
+ from multiprocessing.process import parent_process
+ wconn.send("alive" if parent_process().is_alive() else "not alive")
+ parent_process().join(timeout=5)
+ wconn.send("alive" if parent_process().is_alive() else "not alive")
+
def test_process(self):
q = self.Queue(1)
e = self.Event()
@@ -5398,6 +5456,7 @@ class ProcessesMixin(BaseMixin):
Process = multiprocessing.Process
connection = multiprocessing.connection
current_process = staticmethod(multiprocessing.current_process)
+ parent_process = staticmethod(multiprocessing.parent_process)
active_children = staticmethod(multiprocessing.active_children)
Pool = staticmethod(multiprocessing.Pool)
Pipe = staticmethod(multiprocessing.Pipe)
diff --git a/Misc/NEWS.d/next/Library/2019-05-16-18-02-08.bpo-36888.-H2Dkm.rst b/Misc/NEWS.d/next/Library/2019-05-16-18-02-08.bpo-36888.-H2Dkm.rst
new file mode 100644
index 00000000000000..e7b54677280c9f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-16-18-02-08.bpo-36888.-H2Dkm.rst
@@ -0,0 +1,2 @@
+Python child processes can now access the status of their parent process
+using multiprocessing.process.parent_process
diff --git a/Modules/_winapi.c b/Modules/_winapi.c
index 2eb708e9073e91..8873519e6ce5df 100644
--- a/Modules/_winapi.c
+++ b/Modules/_winapi.c
@@ -1955,6 +1955,7 @@ PyInit__winapi(void)
WINAPI_CONSTANT(F_DWORD, PIPE_UNLIMITED_INSTANCES);
WINAPI_CONSTANT(F_DWORD, PIPE_WAIT);
WINAPI_CONSTANT(F_DWORD, PROCESS_ALL_ACCESS);
+ WINAPI_CONSTANT(F_DWORD, SYNCHRONIZE);
WINAPI_CONSTANT(F_DWORD, PROCESS_DUP_HANDLE);
WINAPI_CONSTANT(F_DWORD, SEC_COMMIT);
WINAPI_CONSTANT(F_DWORD, SEC_IMAGE);
From 4011d865d0572a3dd9988f2935cd835cc8fb792a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Batuhan=20Ta=C5=9Fkaya?=
<47358913+isidentical@users.noreply.github.com>
Date: Mon, 20 May 2019 23:27:10 +0300
Subject: [PATCH 034/441] bpo-23896: Add a grammar where exec isn't a stmt
(#13272)
https://bugs.python.org/issue23896
---
Lib/lib2to3/pygram.py | 3 +++
Misc/ACKS | 1 +
.../next/Library/2019-05-13-05-49-15.bpo-23896.8TtUKo.rst | 2 ++
3 files changed, 6 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-13-05-49-15.bpo-23896.8TtUKo.rst
diff --git a/Lib/lib2to3/pygram.py b/Lib/lib2to3/pygram.py
index 919624eb399706..24d9db9217f131 100644
--- a/Lib/lib2to3/pygram.py
+++ b/Lib/lib2to3/pygram.py
@@ -36,5 +36,8 @@ def __init__(self, grammar):
python_grammar_no_print_statement = python_grammar.copy()
del python_grammar_no_print_statement.keywords["print"]
+python_grammar_no_print_and_exec_statement = python_grammar_no_print_statement.copy()
+del python_grammar_no_print_and_exec_statement.keywords["exec"]
+
pattern_grammar = driver.load_packaged_grammar("lib2to3", _PATTERN_GRAMMAR_FILE)
pattern_symbols = Symbols(pattern_grammar)
diff --git a/Misc/ACKS b/Misc/ACKS
index f9d01d00867999..87107b9cfc7d2b 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1860,3 +1860,4 @@ Carsten Klein
Diego Rojas
Edison Abahurire
Geoff Shannon
+Batuhan Taskaya
diff --git a/Misc/NEWS.d/next/Library/2019-05-13-05-49-15.bpo-23896.8TtUKo.rst b/Misc/NEWS.d/next/Library/2019-05-13-05-49-15.bpo-23896.8TtUKo.rst
new file mode 100644
index 00000000000000..3c154822c9acdb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-13-05-49-15.bpo-23896.8TtUKo.rst
@@ -0,0 +1,2 @@
+Adds a grammar to lib2to3.pygram that contains exec as a function not as
+statement.
From 1a3faf9d9740a8c7505c61839ef09929a7ff9e35 Mon Sep 17 00:00:00 2001
From: Matthias Bussonnier
Date: Mon, 20 May 2019 13:44:11 -0700
Subject: [PATCH 035/441] bpo-36952: Remove the bufsize parameter in
fileinput.input(). (GH-13400)
This parameter is marked as deprecated since 3.6 and for removal in 3.8.
It already had no effects.
---
Doc/library/fileinput.rst | 17 ++++---
Doc/whatsnew/3.8.rst | 4 ++
Lib/fileinput.py | 13 ++---
Lib/test/test_fileinput.py | 50 +++++++------------
.../2019-05-20-11-01-28.bpo-36952.MgZi7-.rst | 4 ++
5 files changed, 41 insertions(+), 47 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-20-11-01-28.bpo-36952.MgZi7-.rst
diff --git a/Doc/library/fileinput.rst b/Doc/library/fileinput.rst
index af9dff34a80827..14be492f55a67f 100644
--- a/Doc/library/fileinput.rst
+++ b/Doc/library/fileinput.rst
@@ -54,7 +54,7 @@ provided by this module.
The following function is the primary interface of this module:
-.. function:: input(files=None, inplace=False, backup='', bufsize=0, mode='r', openhook=None)
+.. function:: input(files=None, inplace=False, backup='', *, mode='r', openhook=None)
Create an instance of the :class:`FileInput` class. The instance will be used
as global state for the functions of this module, and is also returned to use
@@ -72,8 +72,9 @@ The following function is the primary interface of this module:
.. versionchanged:: 3.2
Can be used as a context manager.
- .. deprecated-removed:: 3.6 3.8
- The *bufsize* parameter.
+ .. versionchanged:: 3.8
+ The keyword parameters *mode* and *openhook* are now keyword-only.
+
The following functions use the global state created by :func:`fileinput.input`;
if there is no active state, :exc:`RuntimeError` is raised.
@@ -135,7 +136,7 @@ The class which implements the sequence behavior provided by the module is
available for subclassing as well:
-.. class:: FileInput(files=None, inplace=False, backup='', bufsize=0, mode='r', openhook=None)
+.. class:: FileInput(files=None, inplace=False, backup='', *, mode='r', openhook=None)
Class :class:`FileInput` is the implementation; its methods :meth:`filename`,
:meth:`fileno`, :meth:`lineno`, :meth:`filelineno`, :meth:`isfirstline`,
@@ -160,18 +161,20 @@ available for subclassing as well:
with FileInput(files=('spam.txt', 'eggs.txt')) as input:
process(input)
+
.. versionchanged:: 3.2
Can be used as a context manager.
.. deprecated:: 3.4
The ``'rU'`` and ``'U'`` modes.
- .. deprecated-removed:: 3.6 3.8
- The *bufsize* parameter.
-
.. deprecated:: 3.8
Support for :meth:`__getitem__` method is deprecated.
+ .. versionchanged:: 3.8
+ The keyword parameter *mode* and *openhook* are now keyword-only.
+
+
**Optional in-place filtering:** if the keyword argument ``inplace=True`` is
passed to :func:`fileinput.input` or to the :class:`FileInput` constructor, the
diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index 0a79b6ce1a652c..5f8208d5bf4a5e 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -828,6 +828,10 @@ The following features and APIs have been removed from Python 3.8:
exposed to the user.
(Contributed by Aviv Palivoda in :issue:`30262`.)
+* The ``bufsize`` keyword argument of :func:`fileinput.input` and
+ :func:`fileinput.FileInput` which was ignored and deprecated since Python 3.6
+ has been removed. :issue:`36952` (Contributed by Matthias Bussonnier)
+
Porting to Python 3.8
=====================
diff --git a/Lib/fileinput.py b/Lib/fileinput.py
index 0764aa5e4d2480..d868e74cd5e969 100644
--- a/Lib/fileinput.py
+++ b/Lib/fileinput.py
@@ -80,8 +80,7 @@
_state = None
-def input(files=None, inplace=False, backup="", bufsize=0,
- mode="r", openhook=None):
+def input(files=None, inplace=False, backup="", *, mode="r", openhook=None):
"""Return an instance of the FileInput class, which can be iterated.
The parameters are passed to the constructor of the FileInput class.
@@ -91,7 +90,7 @@ def input(files=None, inplace=False, backup="", bufsize=0,
global _state
if _state and _state._file:
raise RuntimeError("input() already active")
- _state = FileInput(files, inplace, backup, bufsize, mode, openhook)
+ _state = FileInput(files, inplace, backup, mode=mode, openhook=openhook)
return _state
def close():
@@ -173,7 +172,7 @@ def isstdin():
return _state.isstdin()
class FileInput:
- """FileInput([files[, inplace[, backup[, bufsize, [, mode[, openhook]]]]]])
+ """FileInput([files[, inplace[, backup]]], *, mode=None, openhook=None)
Class FileInput is the implementation of the module; its methods
filename(), lineno(), fileline(), isfirstline(), isstdin(), fileno(),
@@ -185,7 +184,7 @@ class FileInput:
sequential order; random access and readline() cannot be mixed.
"""
- def __init__(self, files=None, inplace=False, backup="", bufsize=0,
+ def __init__(self, files=None, inplace=False, backup="", *,
mode="r", openhook=None):
if isinstance(files, str):
files = (files,)
@@ -201,10 +200,6 @@ def __init__(self, files=None, inplace=False, backup="", bufsize=0,
self._files = files
self._inplace = inplace
self._backup = backup
- if bufsize:
- import warnings
- warnings.warn('bufsize is deprecated and ignored',
- DeprecationWarning, stacklevel=2)
self._savestdout = None
self._output = None
self._filename = None
diff --git a/Lib/test/test_fileinput.py b/Lib/test/test_fileinput.py
index 8b7577b0205cde..014f19e6cbdb1a 100644
--- a/Lib/test/test_fileinput.py
+++ b/Lib/test/test_fileinput.py
@@ -82,25 +82,17 @@ def close(self):
class BufferSizesTests(BaseTests, unittest.TestCase):
def test_buffer_sizes(self):
- # First, run the tests with default and teeny buffer size.
- for round, bs in (0, 0), (1, 30):
- t1 = self.writeTmp(''.join("Line %s of file 1\n" % (i+1) for i in range(15)))
- t2 = self.writeTmp(''.join("Line %s of file 2\n" % (i+1) for i in range(10)))
- t3 = self.writeTmp(''.join("Line %s of file 3\n" % (i+1) for i in range(5)))
- t4 = self.writeTmp(''.join("Line %s of file 4\n" % (i+1) for i in range(1)))
- if bs:
- with self.assertWarns(DeprecationWarning):
- self.buffer_size_test(t1, t2, t3, t4, bs, round)
- else:
- self.buffer_size_test(t1, t2, t3, t4, bs, round)
-
- def buffer_size_test(self, t1, t2, t3, t4, bs=0, round=0):
+
+ t1 = self.writeTmp(''.join("Line %s of file 1\n" % (i+1) for i in range(15)))
+ t2 = self.writeTmp(''.join("Line %s of file 2\n" % (i+1) for i in range(10)))
+ t3 = self.writeTmp(''.join("Line %s of file 3\n" % (i+1) for i in range(5)))
+ t4 = self.writeTmp(''.join("Line %s of file 4\n" % (i+1) for i in range(1)))
+
pat = re.compile(r'LINE (\d+) OF FILE (\d+)')
- start = 1 + round*6
if verbose:
- print('%s. Simple iteration (bs=%s)' % (start+0, bs))
- fi = FileInput(files=(t1, t2, t3, t4), bufsize=bs)
+ print('1. Simple iteration')
+ fi = FileInput(files=(t1, t2, t3, t4))
lines = list(fi)
fi.close()
self.assertEqual(len(lines), 31)
@@ -110,8 +102,8 @@ def buffer_size_test(self, t1, t2, t3, t4, bs=0, round=0):
self.assertEqual(fi.filename(), t4)
if verbose:
- print('%s. Status variables (bs=%s)' % (start+1, bs))
- fi = FileInput(files=(t1, t2, t3, t4), bufsize=bs)
+ print('2. Status variables')
+ fi = FileInput(files=(t1, t2, t3, t4))
s = "x"
while s and s != 'Line 6 of file 2\n':
s = fi.readline()
@@ -122,15 +114,15 @@ def buffer_size_test(self, t1, t2, t3, t4, bs=0, round=0):
self.assertFalse(fi.isstdin())
if verbose:
- print('%s. Nextfile (bs=%s)' % (start+2, bs))
+ print('3. Nextfile')
fi.nextfile()
self.assertEqual(fi.readline(), 'Line 1 of file 3\n')
self.assertEqual(fi.lineno(), 22)
fi.close()
if verbose:
- print('%s. Stdin (bs=%s)' % (start+3, bs))
- fi = FileInput(files=(t1, t2, t3, t4, '-'), bufsize=bs)
+ print('4. Stdin')
+ fi = FileInput(files=(t1, t2, t3, t4, '-'))
savestdin = sys.stdin
try:
sys.stdin = StringIO("Line 1 of stdin\nLine 2 of stdin\n")
@@ -143,8 +135,8 @@ def buffer_size_test(self, t1, t2, t3, t4, bs=0, round=0):
sys.stdin = savestdin
if verbose:
- print('%s. Boundary conditions (bs=%s)' % (start+4, bs))
- fi = FileInput(files=(t1, t2, t3, t4), bufsize=bs)
+ print('5. Boundary conditions')
+ fi = FileInput(files=(t1, t2, t3, t4))
self.assertEqual(fi.lineno(), 0)
self.assertEqual(fi.filename(), None)
fi.nextfile()
@@ -152,10 +144,10 @@ def buffer_size_test(self, t1, t2, t3, t4, bs=0, round=0):
self.assertEqual(fi.filename(), None)
if verbose:
- print('%s. Inplace (bs=%s)' % (start+5, bs))
+ print('6. Inplace')
savestdout = sys.stdout
try:
- fi = FileInput(files=(t1, t2, t3, t4), inplace=1, bufsize=bs)
+ fi = FileInput(files=(t1, t2, t3, t4), inplace=1)
for line in fi:
line = line[:-1].upper()
print(line)
@@ -163,7 +155,7 @@ def buffer_size_test(self, t1, t2, t3, t4, bs=0, round=0):
finally:
sys.stdout = savestdout
- fi = FileInput(files=(t1, t2, t3, t4), bufsize=bs)
+ fi = FileInput(files=(t1, t2, t3, t4))
for line in fi:
self.assertEqual(line[-1], '\n')
m = pat.match(line[:-1])
@@ -533,12 +525,11 @@ def test_pathlib_file_inplace(self):
class MockFileInput:
"""A class that mocks out fileinput.FileInput for use during unit tests"""
- def __init__(self, files=None, inplace=False, backup="", bufsize=0,
+ def __init__(self, files=None, inplace=False, backup="", *,
mode="r", openhook=None):
self.files = files
self.inplace = inplace
self.backup = backup
- self.bufsize = bufsize
self.mode = mode
self.openhook = openhook
self._file = None
@@ -641,13 +632,11 @@ def do_test_call_input(self):
files = object()
inplace = object()
backup = object()
- bufsize = object()
mode = object()
openhook = object()
# call fileinput.input() with different values for each argument
result = fileinput.input(files=files, inplace=inplace, backup=backup,
- bufsize=bufsize,
mode=mode, openhook=openhook)
# ensure fileinput._state was set to the returned object
@@ -658,7 +647,6 @@ def do_test_call_input(self):
self.assertIs(files, result.files, "files")
self.assertIs(inplace, result.inplace, "inplace")
self.assertIs(backup, result.backup, "backup")
- self.assertIs(bufsize, result.bufsize, "bufsize")
self.assertIs(mode, result.mode, "mode")
self.assertIs(openhook, result.openhook, "openhook")
diff --git a/Misc/NEWS.d/next/Library/2019-05-20-11-01-28.bpo-36952.MgZi7-.rst b/Misc/NEWS.d/next/Library/2019-05-20-11-01-28.bpo-36952.MgZi7-.rst
new file mode 100644
index 00000000000000..f5ce2aadf4a18c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-20-11-01-28.bpo-36952.MgZi7-.rst
@@ -0,0 +1,4 @@
+:func:`fileinput.input` and :class:`fileinput.FileInput` **bufsize**
+argument has been removed (was deprecated and ignored since Python 3.6),
+and as a result the **mode** and **openhook** arguments have been made
+keyword-only.
From bf457c7d8224179a023957876e757f2a7ffc3d9d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?=
Date: Tue, 21 May 2019 00:17:30 +0200
Subject: [PATCH 036/441] bpo-36969: Make PDB args command display keyword only
arguments (GH-13452)
---
Lib/pdb.py | 6 ++---
Lib/test/test_pdb.py | 25 ++++++++++++++++---
.../2019-05-20-23-31-20.bpo-36969.JkZORP.rst | 2 ++
3 files changed, 26 insertions(+), 7 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-20-23-31-20.bpo-36969.JkZORP.rst
diff --git a/Lib/pdb.py b/Lib/pdb.py
index f5d33c27fc1d91..0e7609e43d4eea 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -1132,9 +1132,9 @@ def do_args(self, arg):
"""
co = self.curframe.f_code
dict = self.curframe_locals
- n = co.co_argcount
- if co.co_flags & 4: n = n+1
- if co.co_flags & 8: n = n+1
+ n = co.co_argcount + co.co_kwonlyargcount
+ if co.co_flags & inspect.CO_VARARGS: n = n+1
+ if co.co_flags & inspect.CO_VARKEYWORDS: n = n+1
for i in range(n):
name = co.co_varnames[i]
if name in dict:
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index 56d823249544ae..a33494d6d878dd 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -77,9 +77,13 @@ def test_pdb_basic_commands():
... print('...')
... return foo.upper()
+ >>> def test_function3(arg=None, *, kwonly=None):
+ ... pass
+
>>> def test_function():
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
... ret = test_function_2('baz')
+ ... test_function3(kwonly=True)
... print(ret)
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
@@ -97,10 +101,13 @@ def test_pdb_basic_commands():
... 'jump 8', # jump over second for loop
... 'return', # return out of function
... 'retval', # display return value
+ ... 'next', # step to test_function3()
+ ... 'step', # stepping into test_function3()
+ ... 'args', # display function args
... 'continue',
... ]):
... test_function()
- > (3)test_function()
+ > (3)test_function()
-> ret = test_function_2('baz')
(Pdb) step
--Call--
@@ -123,14 +130,14 @@ def test_pdb_basic_commands():
[EOF]
(Pdb) bt
...
- (18)()
+ (21)()
-> test_function()
- (3)test_function()
+ (3)test_function()
-> ret = test_function_2('baz')
> (1)test_function_2()
-> def test_function_2(foo, bar='default'):
(Pdb) up
- > (3)test_function()
+ > (3)test_function()
-> ret = test_function_2('baz')
(Pdb) down
> (1)test_function_2()
@@ -168,6 +175,16 @@ def test_pdb_basic_commands():
-> return foo.upper()
(Pdb) retval
'BAZ'
+ (Pdb) next
+ > (4)test_function()
+ -> test_function3(kwonly=True)
+ (Pdb) step
+ --Call--
+ > (1)test_function3()
+ -> def test_function3(arg=None, *, kwonly=None):
+ (Pdb) args
+ arg = None
+ kwonly = True
(Pdb) continue
BAZ
"""
diff --git a/Misc/NEWS.d/next/Library/2019-05-20-23-31-20.bpo-36969.JkZORP.rst b/Misc/NEWS.d/next/Library/2019-05-20-23-31-20.bpo-36969.JkZORP.rst
new file mode 100644
index 00000000000000..9253ab92f1fb6e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-20-23-31-20.bpo-36969.JkZORP.rst
@@ -0,0 +1,2 @@
+PDB command `args` now display keyword only arguments. Patch contributed by
+Rémi Lapeyre.
From 6220c02e09e9f3a7458d32ad774ada0ba1571cb8 Mon Sep 17 00:00:00 2001
From: Cheryl Sabella
Date: Mon, 20 May 2019 18:45:05 -0400
Subject: [PATCH 037/441] bpo-35563: Add reference links to warnings.rst
(GH-11289)
---
Doc/library/warnings.rst | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst
index d121f320d6a3eb..a481a3509d4ec8 100644
--- a/Doc/library/warnings.rst
+++ b/Doc/library/warnings.rst
@@ -19,10 +19,10 @@ Python programmers issue warnings by calling the :func:`warn` function defined
in this module. (C programmers use :c:func:`PyErr_WarnEx`; see
:ref:`exceptionhandling` for details).
-Warning messages are normally written to ``sys.stderr``, but their disposition
+Warning messages are normally written to :data:`sys.stderr`, but their disposition
can be changed flexibly, from ignoring all warnings to turning them into
-exceptions. The disposition of warnings can vary based on the warning category
-(see below), the text of the warning message, and the source location where it
+exceptions. The disposition of warnings can vary based on the :ref:`warning category
+`, the text of the warning message, and the source location where it
is issued. Repetitions of a particular warning for the same source location are
typically suppressed.
@@ -31,7 +31,7 @@ determination is made whether a message should be issued or not; next, if a
message is to be issued, it is formatted and printed using a user-settable hook.
The determination whether to issue a warning message is controlled by the
-warning filter, which is a sequence of matching rules and actions. Rules can be
+:ref:`warning filter `, which is a sequence of matching rules and actions. Rules can be
added to the filter by calling :func:`filterwarnings` and reset to its default
state by calling :func:`resetwarnings`.
@@ -181,9 +181,9 @@ Describing Warning Filters
The warnings filter is initialized by :option:`-W` options passed to the Python
interpreter command line and the :envvar:`PYTHONWARNINGS` environment variable.
The interpreter saves the arguments for all supplied entries without
-interpretation in ``sys.warnoptions``; the :mod:`warnings` module parses these
+interpretation in :data:`sys.warnoptions`; the :mod:`warnings` module parses these
when it is first imported (invalid options are ignored, after printing a
-message to ``sys.stderr``).
+message to :data:`sys.stderr`).
Individual warnings filters are specified as a sequence of fields separated by
colons::
@@ -192,7 +192,7 @@ colons::
The meaning of each of these fields is as described in :ref:`warning-filter`.
When listing multiple filters on a single line (as for
-:envvar:`PYTHONWARNINGS`), the individual filters are separated by commas,and
+:envvar:`PYTHONWARNINGS`), the individual filters are separated by commas and
the filters listed later take precedence over those listed before them (as
they're applied left-to-right, and the most recently applied filters take
precedence over earlier ones).
@@ -395,12 +395,12 @@ Available Functions
.. function:: warn(message, category=None, stacklevel=1, source=None)
Issue a warning, or maybe ignore it or raise an exception. The *category*
- argument, if given, must be a warning category class (see above); it defaults to
- :exc:`UserWarning`. Alternatively *message* can be a :exc:`Warning` instance,
+ argument, if given, must be a :ref:`warning category class `; it
+ defaults to :exc:`UserWarning`. Alternatively, *message* can be a :exc:`Warning` instance,
in which case *category* will be ignored and ``message.__class__`` will be used.
- In this case the message text will be ``str(message)``. This function raises an
+ In this case, the message text will be ``str(message)``. This function raises an
exception if the particular warning issued is changed into an error by the
- warnings filter see above. The *stacklevel* argument can be used by wrapper
+ :ref:`warnings filter `. The *stacklevel* argument can be used by wrapper
functions written in Python, like this::
def deprecation(message):
@@ -444,7 +444,7 @@ Available Functions
Write a warning to a file. The default implementation calls
``formatwarning(message, category, filename, lineno, line)`` and writes the
- resulting string to *file*, which defaults to ``sys.stderr``. You may replace
+ resulting string to *file*, which defaults to :data:`sys.stderr`. You may replace
this function with any callable by assigning to ``warnings.showwarning``.
*line* is a line of source code to be included in the warning
message; if *line* is not supplied, :func:`showwarning` will
From 3099ae407541b63249657b971fb35ff217508d86 Mon Sep 17 00:00:00 2001
From: Andre Delfino
Date: Mon, 20 May 2019 21:52:17 -0300
Subject: [PATCH 038/441] Remove workaround for defaults in namedtuple now that
we have the defaults parameter (GH-13263)
---
Doc/library/collections.rst | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst
index e0469c20810006..ae21db216fdebe 100644
--- a/Doc/library/collections.rst
+++ b/Doc/library/collections.rst
@@ -1017,15 +1017,6 @@ fields:
.. versionchanged:: 3.5
Property docstrings became writeable.
-Default values can be implemented by using :meth:`~somenamedtuple._replace` to
-customize a prototype instance:
-
- >>> Account = namedtuple('Account', 'owner balance transaction_count')
- >>> default_account = Account('', 0.0, 0)
- >>> johns_account = default_account._replace(owner='John')
- >>> janes_account = default_account._replace(owner='Jane')
-
-
.. seealso::
* See :class:`typing.NamedTuple` for a way to add type hints for named
From d0ebf13e50dd736cdb355fa42c23837abbb88127 Mon Sep 17 00:00:00 2001
From: Matthias Bussonnier
Date: Mon, 20 May 2019 23:20:10 -0700
Subject: [PATCH 039/441] bpo-36932: use proper deprecation-removed directive
(GH-13349)
.. And update some deprecation warnings with version numbers.
https://bugs.python.org/issue36932
---
Doc/library/asyncio-task.rst | 37 +++++++++++++++++++++---------------
Doc/library/sys.rst | 2 +-
Lib/asyncio/tasks.py | 16 ++++++++--------
3 files changed, 31 insertions(+), 24 deletions(-)
diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst
index bb064bd93deaf7..d94fa587cd3a47 100644
--- a/Doc/library/asyncio-task.rst
+++ b/Doc/library/asyncio-task.rst
@@ -279,8 +279,8 @@ Sleeping
``sleep()`` always suspends the current task, allowing other tasks
to run.
- The *loop* argument is deprecated and scheduled for removal
- in Python 3.10.
+ .. deprecated-removed:: 3.8 3.10
+ The *loop* parameter.
.. _asyncio_example_sleep:
@@ -437,8 +437,8 @@ Timeouts
If the wait is cancelled, the future *aw* is also cancelled.
- The *loop* argument is deprecated and scheduled for removal
- in Python 3.10.
+ .. deprecated-removed:: 3.8 3.10
+ The *loop* parameter.
.. _asyncio_example_waitfor:
@@ -478,10 +478,12 @@ Waiting Primitives
set concurrently and block until the condition specified
by *return_when*.
- If any awaitable in *aws* is a coroutine, it is automatically
- scheduled as a Task. Passing coroutines objects to
- ``wait()`` directly is deprecated as it leads to
- :ref:`confusing behavior `.
+ .. deprecated:: 3.8
+
+ If any awaitable in *aws* is a coroutine, it is automatically
+ scheduled as a Task. Passing coroutines objects to
+ ``wait()`` directly is deprecated as it leads to
+ :ref:`confusing behavior `.
Returns two sets of Tasks/Futures: ``(done, pending)``.
@@ -489,8 +491,8 @@ Waiting Primitives
done, pending = await asyncio.wait(aws)
- The *loop* argument is deprecated and scheduled for removal
- in Python 3.10.
+ .. deprecated-removed:: 3.8 3.10
+ The *loop* parameter.
*timeout* (a float or int), if specified, can be used to control
the maximum number of seconds to wait before returning.
@@ -550,6 +552,8 @@ Waiting Primitives
if task in done:
# Everything will work as expected now.
+ .. deprecated:: 3.8
+
Passing coroutine objects to ``wait()`` directly is
deprecated.
@@ -868,8 +872,10 @@ Task Object
If *loop* is ``None``, the :func:`get_event_loop` function
is used to get the current loop.
- This method is **deprecated** and will be removed in
- Python 3.9. Use the :func:`asyncio.all_tasks` function instead.
+ .. deprecated-removed:: 3.7 3.9
+
+ Do not call this as a task method. Use the :func:`asyncio.all_tasks`
+ function instead.
.. classmethod:: current_task(loop=None)
@@ -878,9 +884,10 @@ Task Object
If *loop* is ``None``, the :func:`get_event_loop` function
is used to get the current loop.
- This method is **deprecated** and will be removed in
- Python 3.9. Use the :func:`asyncio.current_task` function
- instead.
+ .. deprecated-removed:: 3.7 3.9
+
+ Do not call this as a task method. Use the
+ :func:`asyncio.current_task` function instead.
.. _asyncio_generator_based_coro:
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index 5039ffa933ac54..7d27c89fac9572 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -1353,7 +1353,7 @@ always available.
This function has been added on a provisional basis (see :pep:`411`
for details.) Use it only for debugging purposes.
- .. deprecated:: 3.7
+ .. deprecated-removed:: 3.7 3.8
The coroutine wrapper functionality has been deprecated, and
will be removed in 3.8. See :issue:`32591` for details.
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
index b274b9bd332994..1dc595298c556d 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -95,7 +95,7 @@ def current_task(cls, loop=None):
None is returned when called not in the context of a Task.
"""
- warnings.warn("Task.current_task() is deprecated, "
+ warnings.warn("Task.current_task() is deprecated since Python 3.7, "
"use asyncio.current_task() instead",
DeprecationWarning,
stacklevel=2)
@@ -109,7 +109,7 @@ def all_tasks(cls, loop=None):
By default all tasks for the current event loop are returned.
"""
- warnings.warn("Task.all_tasks() is deprecated, "
+ warnings.warn("Task.all_tasks() is deprecated since Python 3.7, "
"use asyncio.all_tasks() instead",
DeprecationWarning,
stacklevel=2)
@@ -388,8 +388,8 @@ async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
if loop is None:
loop = events.get_running_loop()
else:
- warnings.warn("The loop argument is deprecated and scheduled for "
- "removal in Python 3.10.",
+ 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)}
@@ -418,8 +418,8 @@ async def wait_for(fut, timeout, *, loop=None):
if loop is None:
loop = events.get_running_loop()
else:
- warnings.warn("The loop argument is deprecated and scheduled for "
- "removal in Python 3.10.",
+ 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:
@@ -600,8 +600,8 @@ async def sleep(delay, result=None, *, loop=None):
if loop is None:
loop = events.get_running_loop()
else:
- warnings.warn("The loop argument is deprecated and scheduled for "
- "removal in Python 3.10.",
+ warnings.warn("The loop argument is deprecated since Python 3.8, "
+ "and scheduled for removal in Python 3.10.",
DeprecationWarning, stacklevel=2)
future = loop.create_future()
From e7cb23bf2079087068a08502f96fdf20b317d69c Mon Sep 17 00:00:00 2001
From: Xtreak
Date: Tue, 21 May 2019 14:17:17 +0530
Subject: [PATCH 040/441] Fix RuntimeWarning in unittest.mock asyncio example
(GH-13449)
* This PR fixes the `RuntimeWarning` in `inspect.isawaitable(mock())` where `mock()` was not awaited.
* Fix typo in asynctest project.
---
Doc/library/unittest.mock.rst | 4 ++--
Lib/unittest/mock.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst
index 21e4709f81609e..163da9aecdbbc5 100644
--- a/Doc/library/unittest.mock.rst
+++ b/Doc/library/unittest.mock.rst
@@ -862,7 +862,7 @@ object::
>>> mock = AsyncMock()
>>> asyncio.iscoroutinefunction(mock)
True
- >>> inspect.isawaitable(mock())
+ >>> inspect.isawaitable(mock()) # doctest: +SKIP
True
The result of ``mock()`` is an async function which will have the outcome
@@ -888,7 +888,7 @@ object::
>>> mock = MagicMock(async_func)
>>> mock
- >>> mock()
+ >>> mock() # doctest: +SKIP
.. method:: assert_awaited()
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index 166c1003769848..654462cbf7e792 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -2204,7 +2204,7 @@ class AsyncMock(AsyncMockMixin, AsyncMagicMixin, Mock):
:class:`.Mock` object: the wrapped object may have methods
defined as async function functions.
- Based on Martin Richard's asyntest project.
+ Based on Martin Richard's asynctest project.
"""
From 925af1d99b69bf3e229411022ad840c5a0cfdcf8 Mon Sep 17 00:00:00 2001
From: Erik Janssens
Date: Tue, 21 May 2019 12:11:11 +0200
Subject: [PATCH 041/441] bpo-36965: Fix includes in main.c on Windows with
non-MSC compilers (GH-13421)
Include windows.h rather than crtdbg.h to get STATUS_CONTROL_C_EXIT constant.
Moreover, include windows.h on Windows, not only when MSC is used.
---
.../next/Windows/2019-05-20-20-26-36.bpo-36965.KsfI-N.rst | 1 +
Modules/main.c | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
create mode 100644 Misc/NEWS.d/next/Windows/2019-05-20-20-26-36.bpo-36965.KsfI-N.rst
diff --git a/Misc/NEWS.d/next/Windows/2019-05-20-20-26-36.bpo-36965.KsfI-N.rst b/Misc/NEWS.d/next/Windows/2019-05-20-20-26-36.bpo-36965.KsfI-N.rst
new file mode 100644
index 00000000000000..2a531d2c14d9a2
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2019-05-20-20-26-36.bpo-36965.KsfI-N.rst
@@ -0,0 +1 @@
+include of STATUS_CONTROL_C_EXIT without depending on MSC compiler
diff --git a/Modules/main.c b/Modules/main.c
index 6d4b351e5e177d..08fb0e0417d038 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -18,8 +18,8 @@
#if defined(HAVE_GETPID) && defined(HAVE_UNISTD_H)
# include /* getpid() */
#endif
-#ifdef _MSC_VER
-# include /* STATUS_CONTROL_C_EXIT */
+#ifdef MS_WINDOWS
+# include /* STATUS_CONTROL_C_EXIT */
#endif
/* End of includes for exit_sigint() */
From d12e75734d46ecde588c5de65e6d64146911d20c Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Tue, 21 May 2019 12:44:57 +0200
Subject: [PATCH 042/441] Revert "bpo-36084: Add native thread ID to
threading.Thread objects (GH-11993)" (GH-13458)
This reverts commit 4959c33d2555b89b494c678d99be81a65ee864b0.
---
Doc/library/_thread.rst | 12 -------
Doc/library/threading.rst | 31 -------------------
Include/pythread.h | 1 -
Lib/_dummy_thread.py | 4 ---
Lib/test/test_threading.py | 4 ---
Lib/threading.py | 23 ++------------
.../2019-02-22-23-03-20.bpo-36084.86Eh4X.rst | 1 -
Modules/_threadmodule.c | 16 ----------
Python/thread_nt.h | 16 ----------
Python/thread_pthread.h | 27 ----------------
10 files changed, 2 insertions(+), 133 deletions(-)
delete mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-02-22-23-03-20.bpo-36084.86Eh4X.rst
diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst
index d7814f218b502f..acffabf24bad5f 100644
--- a/Doc/library/_thread.rst
+++ b/Doc/library/_thread.rst
@@ -85,18 +85,6 @@ This module defines the following constants and functions:
may be recycled when a thread exits and another thread is created.
-.. function:: get_native_id()
-
- Return the native integral Thread ID of the current thread assigned by the kernel.
- This is a non-negative integer.
- Its value may be used to uniquely identify this particular thread system-wide
- (until the thread terminates, after which the value may be recycled by the OS).
-
- .. availability:: Windows, FreeBSD, Linux, macOS.
-
- .. versionadded:: 3.8
-
-
.. function:: stack_size([size])
Return the thread stack size used when creating new threads. The optional
diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst
index 715940c1c52bfc..22342803e5e6d6 100644
--- a/Doc/library/threading.rst
+++ b/Doc/library/threading.rst
@@ -49,18 +49,6 @@ This module defines the following functions:
.. versionadded:: 3.3
-.. function:: get_native_id()
-
- Return the native integral Thread ID of the current thread assigned by the kernel.
- This is a non-negative integer.
- Its value may be used to uniquely identify this particular thread system-wide
- (until the thread terminates, after which the value may be recycled by the OS).
-
- .. availability:: Windows, FreeBSD, Linux, macOS.
-
- .. versionadded:: 3.8
-
-
.. function:: enumerate()
Return a list of all :class:`Thread` objects currently alive. The list
@@ -309,25 +297,6 @@ since it is impossible to detect the termination of alien threads.
another thread is created. The identifier is available even after the
thread has exited.
- .. attribute:: native_id
-
- The native integral thread ID of this thread or ``0`` if the thread has not
- been started. This is a non-negative integer. See the
- :func:`get_native_id` function.
- This represents the Thread ID (``TID``) as assigned to the
- thread by the OS (kernel). Its value may be used to uniquely identify
- this particular thread system-wide.
-
- .. note::
-
- Similar to Process IDs, Thread IDs are only valid (guaranteed unique
- system-wide) from the time the thread is created until the thread
- has been terminated.
-
- .. availability:: Windows, FreeBSD, Linux, macOS.
-
- .. versionadded:: 3.8
-
.. method:: is_alive()
Return whether the thread is alive.
diff --git a/Include/pythread.h b/Include/pythread.h
index e083383af80b47..bc1d92cd1ff199 100644
--- a/Include/pythread.h
+++ b/Include/pythread.h
@@ -25,7 +25,6 @@ PyAPI_FUNC(void) PyThread_init_thread(void);
PyAPI_FUNC(unsigned long) PyThread_start_new_thread(void (*)(void *), void *);
PyAPI_FUNC(void) _Py_NO_RETURN PyThread_exit_thread(void);
PyAPI_FUNC(unsigned long) PyThread_get_thread_ident(void);
-PyAPI_FUNC(unsigned long) PyThread_get_thread_native_id(void);
PyAPI_FUNC(PyThread_type_lock) PyThread_allocate_lock(void);
PyAPI_FUNC(void) PyThread_free_lock(PyThread_type_lock);
diff --git a/Lib/_dummy_thread.py b/Lib/_dummy_thread.py
index 0a877e1fa16905..a2cae54b0580db 100644
--- a/Lib/_dummy_thread.py
+++ b/Lib/_dummy_thread.py
@@ -71,10 +71,6 @@ def get_ident():
"""
return 1
-def get_native_id():
- """Dummy implementation of _thread.get_native_id()."""
- return 0
-
def allocate_lock():
"""Dummy implementation of _thread.allocate_lock()."""
return LockType()
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 6ac6e9de7a5d10..2ddc77b266b542 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -104,10 +104,6 @@ def test_various_ops(self):
self.assertRegex(repr(t), r'^$')
t.start()
- native_ids = set(t.native_id for t in threads) | {threading.get_native_id()}
- self.assertNotIn(None, native_ids)
- self.assertEqual(len(native_ids), NUMTASKS + 1)
-
if verbose:
print('waiting for all tasks to complete')
for t in threads:
diff --git a/Lib/threading.py b/Lib/threading.py
index 3137e495b2504c..0ebbd6776ef40f 100644
--- a/Lib/threading.py
+++ b/Lib/threading.py
@@ -23,8 +23,8 @@
# with the multiprocessing module, which doesn't provide the old
# Java inspired names.
-__all__ = ['get_ident', 'get_native_id', 'active_count', 'Condition',
- 'current_thread', 'enumerate', 'main_thread', 'TIMEOUT_MAX',
+__all__ = ['get_ident', 'active_count', 'Condition', 'current_thread',
+ 'enumerate', 'main_thread', 'TIMEOUT_MAX',
'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread',
'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError',
'setprofile', 'settrace', 'local', 'stack_size']
@@ -34,7 +34,6 @@
_allocate_lock = _thread.allocate_lock
_set_sentinel = _thread._set_sentinel
get_ident = _thread.get_ident
-get_native_id = _thread.get_native_id
ThreadError = _thread.error
try:
_CRLock = _thread.RLock
@@ -791,7 +790,6 @@ class is implemented.
else:
self._daemonic = current_thread().daemon
self._ident = None
- self._native_id = 0
self._tstate_lock = None
self._started = Event()
self._is_stopped = False
@@ -893,9 +891,6 @@ def _bootstrap(self):
def _set_ident(self):
self._ident = get_ident()
- def _set_native_id(self):
- self._native_id = get_native_id()
-
def _set_tstate_lock(self):
"""
Set a lock object which will be released by the interpreter when
@@ -908,7 +903,6 @@ def _bootstrap_inner(self):
try:
self._set_ident()
self._set_tstate_lock()
- self._set_native_id()
self._started.set()
with _active_limbo_lock:
_active[self._ident] = self
@@ -1083,17 +1077,6 @@ def ident(self):
assert self._initialized, "Thread.__init__() not called"
return self._ident
- @property
- def native_id(self):
- """Native integral thread ID of this thread or 0 if it has not been started.
-
- This is a non-negative integer. See the get_native_id() function.
- This represents the Thread ID as reported by the kernel.
-
- """
- assert self._initialized, "Thread.__init__() not called"
- return self._native_id
-
def is_alive(self):
"""Return whether the thread is alive.
@@ -1193,7 +1176,6 @@ def __init__(self):
self._set_tstate_lock()
self._started.set()
self._set_ident()
- self._set_native_id()
with _active_limbo_lock:
_active[self._ident] = self
@@ -1213,7 +1195,6 @@ def __init__(self):
self._started.set()
self._set_ident()
- self._set_native_id()
with _active_limbo_lock:
_active[self._ident] = self
diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-02-22-23-03-20.bpo-36084.86Eh4X.rst b/Misc/NEWS.d/next/Core and Builtins/2019-02-22-23-03-20.bpo-36084.86Eh4X.rst
deleted file mode 100644
index 4a612964de0f5f..00000000000000
--- a/Misc/NEWS.d/next/Core and Builtins/2019-02-22-23-03-20.bpo-36084.86Eh4X.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add native thread ID (TID) to threading.Thread objects
\ No newline at end of file
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index a123cd0efd6279..3c02d8dd514571 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -1159,20 +1159,6 @@ allocated consecutive numbers starting at 1, this behavior should not\n\
be relied upon, and the number should be seen purely as a magic cookie.\n\
A thread's identity may be reused for another thread after it exits.");
-static PyObject *
-thread_get_native_id(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
- unsigned long native_id = PyThread_get_thread_native_id();
- return PyLong_FromUnsignedLong(native_id);
-}
-
-PyDoc_STRVAR(get_native_id_doc,
-"get_native_id() -> integer\n\
-\n\
-Return a non-negative integer identifying the thread as reported\n\
-by the OS (kernel). This may be used to uniquely identify a\n\
-particular thread within a system.");
-
static PyObject *
thread__count(PyObject *self, PyObject *Py_UNUSED(ignored))
{
@@ -1324,8 +1310,6 @@ static PyMethodDef thread_methods[] = {
METH_NOARGS, interrupt_doc},
{"get_ident", thread_get_ident,
METH_NOARGS, get_ident_doc},
- {"get_native_id", thread_get_native_id,
- METH_NOARGS, get_native_id_doc},
{"_count", thread__count,
METH_NOARGS, _count_doc},
{"stack_size", (PyCFunction)thread_stack_size,
diff --git a/Python/thread_nt.h b/Python/thread_nt.h
index d3dc2bec6ffebb..5e00c351146055 100644
--- a/Python/thread_nt.h
+++ b/Python/thread_nt.h
@@ -143,8 +143,6 @@ LeaveNonRecursiveMutex(PNRMUTEX mutex)
unsigned long PyThread_get_thread_ident(void);
-unsigned long PyThread_get_thread_native_id(void);
-
/*
* Initialization of the C package, should not be needed.
*/
@@ -229,20 +227,6 @@ PyThread_get_thread_ident(void)
return GetCurrentThreadId();
}
-/*
- * Return the native Thread ID (TID) of the calling thread.
- * The native ID of a thread is valid and guaranteed to be unique system-wide
- * from the time the thread is created until the thread has been terminated.
- */
-unsigned long
-PyThread_get_thread_native_id(void)
-{
- if (!initialized)
- PyThread_init_thread();
-
- return GetCurrentThreadId();
-}
-
void _Py_NO_RETURN
PyThread_exit_thread(void)
{
diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h
index 87c98d3e93cb77..4c106d9959c10b 100644
--- a/Python/thread_pthread.h
+++ b/Python/thread_pthread.h
@@ -12,12 +12,6 @@
#endif
#include
-#if defined(__linux__)
-#include
-#elif defined(__FreeBSD__)
-#include
-#endif
-
/* The POSIX spec requires that use of pthread_attr_setstacksize
be conditional on _POSIX_THREAD_ATTR_STACKSIZE being defined. */
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
@@ -308,27 +302,6 @@ PyThread_get_thread_ident(void)
return (unsigned long) threadid;
}
-unsigned long
-PyThread_get_thread_native_id(void)
-{
- if (!initialized)
- PyThread_init_thread();
-#ifdef __APPLE__
- uint64_t native_id;
- pthread_threadid_np(NULL, &native_id);
-#elif defined(__linux__)
- pid_t native_id;
- native_id = syscall(__NR_gettid);
-#elif defined(__FreeBSD__)
- pid_t native_id;
- native_id = pthread_getthreadid_np();
-#else
- unsigned long native_id;
- native_id = 0;
-#endif
- return (unsigned long) native_id;
-}
-
void _Py_NO_RETURN
PyThread_exit_thread(void)
{
From f2d7ac7e5bd821e29e0fcb78a760a282059ae000 Mon Sep 17 00:00:00 2001
From: pxinwr
Date: Tue, 21 May 2019 18:46:37 +0800
Subject: [PATCH 043/441] bpo-31904: Add posix module support for VxWorks
(GH-12118)
---
Doc/library/os.rst | 4 ++
Lib/test/test_os.py | 5 +-
.../2019-03-01-17-59-39.bpo-31904.38djdk.rst | 1 +
Modules/clinic/posixmodule.c.h | 10 +--
Modules/posixmodule.c | 72 +++++++++++++++++--
configure | 2 +-
configure.ac | 2 +-
pyconfig.h.in | 3 +
8 files changed, 84 insertions(+), 15 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-03-01-17-59-39.bpo-31904.38djdk.rst
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index e77a8fed377ad6..0bbfce97c54ba6 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -32,6 +32,7 @@ Notes on the availability of these functions:
objects, and result in an object of the same type, if a path or file name is
returned.
+* On VxWorks, os.fork, os.execv and os.spawn*p* are not supported.
.. note::
@@ -3578,6 +3579,9 @@ written in Python, such as a mail server's external command delivery program.
process. On Windows, the process id will actually be the process handle, so can
be used with the :func:`waitpid` function.
+ Note on VxWorks, this function doesn't return ``-signal`` when the new process is
+ killed. Instead it raises OSError exception.
+
The "l" and "v" variants of the :func:`spawn\* ` functions differ in how
command-line arguments are passed. The "l" variants are perhaps the easiest
to work with if the number of parameters is fixed when the code is written; the
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index a2021b1eba068f..353b9a50a2b7fb 100644
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -1407,6 +1407,8 @@ def test_getrandom_value(self):
@unittest.skipIf(OS_URANDOM_DONT_USE_FD ,
"os.random() does not use a file descriptor")
+@unittest.skipIf(sys.platform == "vxworks",
+ "VxWorks can't set RLIMIT_NOFILE to 1")
class URandomFDTests(unittest.TestCase):
@unittest.skipUnless(resource, "test requires the resource module")
def test_urandom_failure(self):
@@ -1517,7 +1519,8 @@ def mock_execve(name, *args):
os.execve = orig_execve
os.defpath = orig_defpath
-
+@unittest.skipUnless(hasattr(os, 'execv'),
+ "need os.execv()")
class ExecTests(unittest.TestCase):
@unittest.skipIf(USING_LINUXTHREADS,
"avoid triggering a linuxthreads bug: see issue #4970")
diff --git a/Misc/NEWS.d/next/Library/2019-03-01-17-59-39.bpo-31904.38djdk.rst b/Misc/NEWS.d/next/Library/2019-03-01-17-59-39.bpo-31904.38djdk.rst
new file mode 100644
index 00000000000000..319c0a319f8c34
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-03-01-17-59-39.bpo-31904.38djdk.rst
@@ -0,0 +1 @@
+Add posix module support for VxWorks.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 43f8ba6b4e6161..f2745591b2353d 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -2491,7 +2491,7 @@ os_posix_spawnp(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj
#endif /* defined(HAVE_POSIX_SPAWNP) */
-#if (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV))
+#if (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV) || defined(HAVE_RTPSPAWN))
PyDoc_STRVAR(os_spawnv__doc__,
"spawnv($module, mode, path, argv, /)\n"
@@ -2545,9 +2545,9 @@ os_spawnv(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
return return_value;
}
-#endif /* (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV)) */
+#endif /* (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV) || defined(HAVE_RTPSPAWN)) */
-#if (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV))
+#if (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV) || defined(HAVE_RTPSPAWN))
PyDoc_STRVAR(os_spawnve__doc__,
"spawnve($module, mode, path, argv, env, /)\n"
@@ -2606,7 +2606,7 @@ os_spawnve(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
return return_value;
}
-#endif /* (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV)) */
+#endif /* (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV) || defined(HAVE_RTPSPAWN)) */
#if defined(HAVE_FORK)
@@ -8576,4 +8576,4 @@ os__remove_dll_directory(PyObject *module, PyObject *const *args, Py_ssize_t nar
#ifndef OS__REMOVE_DLL_DIRECTORY_METHODDEF
#define OS__REMOVE_DLL_DIRECTORY_METHODDEF
#endif /* !defined(OS__REMOVE_DLL_DIRECTORY_METHODDEF) */
-/*[clinic end generated code: output=ab36ec0376a422ae input=a9049054013a1b77]*/
+/*[clinic end generated code: output=5ee9420fb2e7aa2c input=a9049054013a1b77]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index aca64efeb1e37f..9f15866d9d3db4 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -191,11 +191,13 @@ corresponding Unix manual entries for more information on calls.");
#define fsync _commit
#else
/* Unix functions that the configure script doesn't check for */
+#ifndef __VXWORKS__
#define HAVE_EXECV 1
#define HAVE_FORK 1
#if defined(__USLC__) && defined(__SCO_VERSION__) /* SCO UDK Compiler */
#define HAVE_FORK1 1
#endif
+#endif
#define HAVE_GETEGID 1
#define HAVE_GETEUID 1
#define HAVE_GETGID 1
@@ -227,6 +229,18 @@ extern char *ctermid_r(char *);
#endif /* !_MSC_VER */
+#if defined(__VXWORKS__)
+#include
+#include
+#include
+#include
+#ifndef _P_WAIT
+#define _P_WAIT 0
+#define _P_NOWAIT 1
+#define _P_NOWAITO 1
+#endif
+#endif /* __VXWORKS__ */
+
#ifdef HAVE_POSIX_SPAWN
#include
#endif
@@ -1353,7 +1367,7 @@ win32_get_reparse_tag(HANDLE reparse_point_handle, ULONG *reparse_tag)
*/
#include
static char **environ;
-#elif !defined(_MSC_VER) && ( !defined(__WATCOMC__) || defined(__QNX__) )
+#elif !defined(_MSC_VER) && (!defined(__WATCOMC__) || defined(__QNX__) || defined(__VXWORKS__))
extern char **environ;
#endif /* !_MSC_VER */
@@ -4870,7 +4884,7 @@ os__exit_impl(PyObject *module, int status)
#define EXECV_CHAR char
#endif
-#if defined(HAVE_EXECV) || defined(HAVE_SPAWNV)
+#if defined(HAVE_EXECV) || defined(HAVE_SPAWNV) || defined(HAVE_RTPSPAWN)
static void
free_string_array(EXECV_CHAR **array, Py_ssize_t count)
{
@@ -4908,7 +4922,7 @@ fsconvert_strdup(PyObject *o, EXECV_CHAR **out)
}
#endif
-#if defined(HAVE_EXECV) || defined (HAVE_FEXECVE)
+#if defined(HAVE_EXECV) || defined (HAVE_FEXECVE) || defined(HAVE_RTPSPAWN)
static EXECV_CHAR**
parse_envlist(PyObject* env, Py_ssize_t *envc_ptr)
{
@@ -5632,8 +5646,41 @@ os_posix_spawnp_impl(PyObject *module, path_t *path, PyObject *argv,
}
#endif /* HAVE_POSIX_SPAWNP */
-
-#if defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV)
+#ifdef HAVE_RTPSPAWN
+static intptr_t
+_rtp_spawn(int mode, const char *rtpFileName, const char *argv[],
+ const char *envp[])
+{
+ RTP_ID rtpid;
+ int status;
+ pid_t res;
+ int async_err = 0;
+
+ /* Set priority=100 and uStackSize=16 MiB (0x1000000) for new processes.
+ uStackSize=0 cannot be used, the default stack size is too small for
+ Python. */
+ if (envp) {
+ rtpid = rtpSpawn(rtpFileName, argv, envp,
+ 100, 0x1000000, 0, VX_FP_TASK);
+ }
+ else {
+ rtpid = rtpSpawn(rtpFileName, argv, (const char **)environ,
+ 100, 0x1000000, 0, VX_FP_TASK);
+ }
+ if ((rtpid != RTP_ID_ERROR) && (mode == _P_WAIT)) {
+ do {
+ res = waitpid((pid_t)rtpid, &status, 0);
+ } while (res < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
+
+ if (res < 0)
+ return RTP_ID_ERROR;
+ return ((intptr_t)status);
+ }
+ return ((intptr_t)rtpid);
+}
+#endif
+
+#if defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV) || defined(HAVE_RTPSPAWN)
/*[clinic input]
os.spawnv
@@ -5703,13 +5750,17 @@ os_spawnv_impl(PyObject *module, int mode, path_t *path, PyObject *argv)
}
argvlist[argc] = NULL;
+#if !defined(HAVE_RTPSPAWN)
if (mode == _OLD_P_OVERLAY)
mode = _P_OVERLAY;
+#endif
Py_BEGIN_ALLOW_THREADS
_Py_BEGIN_SUPPRESS_IPH
#ifdef HAVE_WSPAWNV
spawnval = _wspawnv(mode, path->wide, argvlist);
+#elif defined(HAVE_RTPSPAWN)
+ spawnval = _rtp_spawn(mode, path->narrow, (const char **)argvlist, NULL);
#else
spawnval = _spawnv(mode, path->narrow, argvlist);
#endif
@@ -5808,13 +5859,18 @@ os_spawnve_impl(PyObject *module, int mode, path_t *path, PyObject *argv,
if (envlist == NULL)
goto fail_1;
+#if !defined(HAVE_RTPSPAWN)
if (mode == _OLD_P_OVERLAY)
mode = _P_OVERLAY;
+#endif
Py_BEGIN_ALLOW_THREADS
_Py_BEGIN_SUPPRESS_IPH
#ifdef HAVE_WSPAWNV
spawnval = _wspawnve(mode, path->wide, argvlist, envlist);
+#elif defined(HAVE_RTPSPAWN)
+ spawnval = _rtp_spawn(mode, path->narrow, (const char **)argvlist,
+ (const char **)envlist);
#else
spawnval = _spawnve(mode, path->narrow, argvlist, envlist);
#endif
@@ -13844,11 +13900,13 @@ all_ins(PyObject *m)
if (PyModule_AddIntConstant(m, "POSIX_SPAWN_DUP2", POSIX_SPAWN_DUP2)) return -1;
#endif
-#ifdef HAVE_SPAWNV
+#if defined(HAVE_SPAWNV) || defined (HAVE_RTPSPAWN)
if (PyModule_AddIntConstant(m, "P_WAIT", _P_WAIT)) return -1;
if (PyModule_AddIntConstant(m, "P_NOWAIT", _P_NOWAIT)) return -1;
- if (PyModule_AddIntConstant(m, "P_OVERLAY", _OLD_P_OVERLAY)) return -1;
if (PyModule_AddIntConstant(m, "P_NOWAITO", _P_NOWAITO)) return -1;
+#endif
+#ifdef HAVE_SPAWNV
+ if (PyModule_AddIntConstant(m, "P_OVERLAY", _OLD_P_OVERLAY)) return -1;
if (PyModule_AddIntConstant(m, "P_DETACH", _P_DETACH)) return -1;
#endif
diff --git a/configure b/configure
index 6da65ddbba50cc..a0767b97136308 100755
--- a/configure
+++ b/configure
@@ -11484,7 +11484,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy strsignal symlinkat sync \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
- wcscoll wcsftime wcsxfrm wmemcmp writev _getpty
+ wcscoll wcsftime wcsxfrm wmemcmp writev _getpty rtpSpawn
do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
diff --git a/configure.ac b/configure.ac
index e673e136be8a53..3a915eb5577c28 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3541,7 +3541,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy strsignal symlinkat sync \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
- wcscoll wcsftime wcsxfrm wmemcmp writev _getpty)
+ wcscoll wcsftime wcsxfrm wmemcmp writev _getpty rtpSpawn)
# Force lchmod off for Linux. Linux disallows changing the mode of symbolic
# links. Some libc implementations have a stub lchmod implementation that always
diff --git a/pyconfig.h.in b/pyconfig.h.in
index 4b779614727406..1cafb9ae42ddb8 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -835,6 +835,9 @@
/* Define to 1 if you have the `round' function. */
#undef HAVE_ROUND
+/* Define to 1 if you have the `rtpSpawn' function. */
+#undef HAVE_RTPSPAWN
+
/* Define to 1 if you have the `sched_get_priority_max' function. */
#undef HAVE_SCHED_GET_PRIORITY_MAX
From 4fb15021890d327023aefd95f5a84ac33b037d19 Mon Sep 17 00:00:00 2001
From: Lihua Zhao <44661095+LihuaZhao@users.noreply.github.com>
Date: Tue, 21 May 2019 18:50:14 +0800
Subject: [PATCH 044/441] bpo-36648: fix mmap issue for VxWorks (GH-12394)
The mmap module set MAP_SHARED flag when map anonymous memory, however VxWorks
only support MAP_PRIVATE when map anonymous memory, this commit clear MAP_SHARED
and set MAP_PRIVATE.
---
.../next/Library/2019-03-18-14-25-36.bpo-31904.ds3d67.rst | 1 +
Modules/mmapmodule.c | 7 +++++++
2 files changed, 8 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2019-03-18-14-25-36.bpo-31904.ds3d67.rst
diff --git a/Misc/NEWS.d/next/Library/2019-03-18-14-25-36.bpo-31904.ds3d67.rst b/Misc/NEWS.d/next/Library/2019-03-18-14-25-36.bpo-31904.ds3d67.rst
new file mode 100644
index 00000000000000..fd82fe086f068f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-03-18-14-25-36.bpo-31904.ds3d67.rst
@@ -0,0 +1 @@
+Fix mmap fail for VxWorks
diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c
index 33366b2d933194..917c6362c11d9a 100644
--- a/Modules/mmapmodule.c
+++ b/Modules/mmapmodule.c
@@ -1164,6 +1164,13 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
#ifdef MAP_ANONYMOUS
/* BSD way to map anonymous memory */
flags |= MAP_ANONYMOUS;
+
+ /* VxWorks only supports MAP_ANONYMOUS with MAP_PRIVATE flag */
+#ifdef __VXWORKS__
+ flags &= ~MAP_SHARED;
+ flags |= MAP_PRIVATE;
+#endif
+
#else
/* SVR4 method to map anonymous memory is to open /dev/zero */
fd = devzero = _Py_open("/dev/zero", O_RDWR);
From ad098b6750f4d74387ac21c8e23ae1ee7ff13571 Mon Sep 17 00:00:00 2001
From: Chris Angelico
Date: Tue, 21 May 2019 23:34:19 +1000
Subject: [PATCH 045/441] Annotate the unexplained assignment in exception
unbinding (GH-11448)
---
Python/compile.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Python/compile.c b/Python/compile.c
index b20548c777246c..63b2456bb3e850 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -2931,7 +2931,7 @@ compiler_try_except(struct compiler *c, stmt_ty s)
try:
# body
finally:
- name = None
+ name = None # in case body contains "del name"
del name
*/
From ccb7ca728e09b307f9e9fd36ec40353137e68a3b Mon Sep 17 00:00:00 2001
From: Max Bernstein
Date: Tue, 21 May 2019 10:09:21 -0700
Subject: [PATCH 046/441] bpo-36929: Modify io/re tests to allow for missing
mod name (#13392)
* bpo-36929: Modify io/re tests to allow for missing mod name
For a vanishingly small number of internal types, CPython sets the
tp_name slot to mod_name.type_name, either in the PyTypeObject or the
PyType_Spec. There are a few minor places where this surfaces:
* Custom repr functions for those types (some of which ignore the
tp_name in favor of using a string literal, such as _io.TextIOWrapper)
* Pickling error messages
The test suite only tests the former. This commit modifies the test
suite to allow Python implementations to omit the module prefix.
https://bugs.python.org/issue36929
---
Lib/test/test_io.py | 34 +++++++++++++++++-----------------
Lib/test/test_re.py | 28 ++++++++++++++++------------
Misc/ACKS | 1 +
3 files changed, 34 insertions(+), 29 deletions(-)
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index 5406a2891bb251..dc44e506b1d0f4 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -1119,12 +1119,12 @@ def f():
def test_repr(self):
raw = self.MockRawIO()
b = self.tp(raw)
- clsname = "%s.%s" % (self.tp.__module__, self.tp.__qualname__)
- self.assertEqual(repr(b), "<%s>" % clsname)
+ clsname = r"(%s\.)?%s" % (self.tp.__module__, self.tp.__qualname__)
+ self.assertRegex(repr(b), "<%s>" % clsname)
raw.name = "dummy"
- self.assertEqual(repr(b), "<%s name='dummy'>" % clsname)
+ self.assertRegex(repr(b), "<%s name='dummy'>" % clsname)
raw.name = b"dummy"
- self.assertEqual(repr(b), "<%s name=b'dummy'>" % clsname)
+ self.assertRegex(repr(b), "<%s name=b'dummy'>" % clsname)
def test_recursive_repr(self):
# Issue #25455
@@ -2598,17 +2598,17 @@ def test_repr(self):
b = self.BufferedReader(raw)
t = self.TextIOWrapper(b, encoding="utf-8")
modname = self.TextIOWrapper.__module__
- self.assertEqual(repr(t),
- "<%s.TextIOWrapper encoding='utf-8'>" % modname)
+ self.assertRegex(repr(t),
+ r"<(%s\.)?TextIOWrapper encoding='utf-8'>" % modname)
raw.name = "dummy"
- self.assertEqual(repr(t),
- "<%s.TextIOWrapper name='dummy' encoding='utf-8'>" % modname)
+ self.assertRegex(repr(t),
+ r"<(%s\.)?TextIOWrapper name='dummy' encoding='utf-8'>" % modname)
t.mode = "r"
- self.assertEqual(repr(t),
- "<%s.TextIOWrapper name='dummy' mode='r' encoding='utf-8'>" % modname)
+ self.assertRegex(repr(t),
+ r"<(%s\.)?TextIOWrapper name='dummy' mode='r' encoding='utf-8'>" % modname)
raw.name = b"dummy"
- self.assertEqual(repr(t),
- "<%s.TextIOWrapper name=b'dummy' mode='r' encoding='utf-8'>" % modname)
+ self.assertRegex(repr(t),
+ r"<(%s\.)?TextIOWrapper name=b'dummy' mode='r' encoding='utf-8'>" % modname)
t.buffer.detach()
repr(t) # Should not raise an exception
@@ -4174,11 +4174,11 @@ def run():
err = res.err.decode()
if res.rc != 0:
# Failure: should be a fatal error
- self.assertIn("Fatal Python error: could not acquire lock "
- "for <_io.BufferedWriter name='<{stream_name}>'> "
- "at interpreter shutdown, possibly due to "
- "daemon threads".format_map(locals()),
- err)
+ pattern = (r"Fatal Python error: could not acquire lock "
+ r"for <(_io\.)?BufferedWriter name='<{stream_name}>'> "
+ r"at interpreter shutdown, possibly due to "
+ r"daemon threads".format_map(locals()))
+ self.assertRegex(err, pattern)
else:
self.assertFalse(err.strip('.!'))
diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py
index 0a77e6fe9edc03..137c31de59ae4a 100644
--- a/Lib/test/test_re.py
+++ b/Lib/test/test_re.py
@@ -1761,24 +1761,28 @@ def test_issue17998(self):
def test_match_repr(self):
for string in '[abracadabra]', S('[abracadabra]'):
m = re.search(r'(.+)(.*?)\1', string)
- self.assertEqual(repr(m), "<%s.%s object; "
- "span=(1, 12), match='abracadabra'>" %
- (type(m).__module__, type(m).__qualname__))
+ pattern = r"<(%s\.)?%s object; span=\(1, 12\), match='abracadabra'>" % (
+ type(m).__module__, type(m).__qualname__
+ )
+ self.assertRegex(repr(m), pattern)
for string in (b'[abracadabra]', B(b'[abracadabra]'),
bytearray(b'[abracadabra]'),
memoryview(b'[abracadabra]')):
m = re.search(br'(.+)(.*?)\1', string)
- self.assertEqual(repr(m), "<%s.%s object; "
- "span=(1, 12), match=b'abracadabra'>" %
- (type(m).__module__, type(m).__qualname__))
+ pattern = r"<(%s\.)?%s object; span=\(1, 12\), match=b'abracadabra'>" % (
+ type(m).__module__, type(m).__qualname__
+ )
+ self.assertRegex(repr(m), pattern)
first, second = list(re.finditer("(aa)|(bb)", "aa bb"))
- self.assertEqual(repr(first), "<%s.%s object; "
- "span=(0, 2), match='aa'>" %
- (type(second).__module__, type(first).__qualname__))
- self.assertEqual(repr(second), "<%s.%s object; "
- "span=(3, 5), match='bb'>" %
- (type(second).__module__, type(second).__qualname__))
+ pattern = r"<(%s\.)?%s object; span=\(0, 2\), match='aa'>" % (
+ type(second).__module__, type(second).__qualname__
+ )
+ self.assertRegex(repr(first), pattern)
+ pattern = r"<(%s\.)?%s object; span=\(3, 5\), match='bb'>" % (
+ type(second).__module__, type(second).__qualname__
+ )
+ self.assertRegex(repr(second), pattern)
def test_zerowidth(self):
# Issues 852532, 1647489, 3262, 25054.
diff --git a/Misc/ACKS b/Misc/ACKS
index 87107b9cfc7d2b..6b1fdbc37fa934 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -143,6 +143,7 @@ Michel Van den Bergh
Julian Berman
Brice Berna
Olivier Bernard
+Maxwell Bernstein
Eric Beser
Steven Bethard
Stephen Bevan
From d5c120f7eb6f2a9cdab282a5d588afed307a23df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Stucke?=
Date: Tue, 21 May 2019 19:44:40 +0200
Subject: [PATCH 047/441] bpo-36035: fix Path.rglob for broken links (GH-11988)
Links creating an infinite symlink loop would raise an exception.
---
Lib/pathlib.py | 13 ++++++++++---
Lib/test/test_pathlib.py | 8 ++++++--
.../2019-02-22-14-30-19.bpo-36035.-6dy1y.rst | 1 +
3 files changed, 17 insertions(+), 5 deletions(-)
create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-02-22-14-30-19.bpo-36035.-6dy1y.rst
diff --git a/Lib/pathlib.py b/Lib/pathlib.py
index b5bab1fe8f5acb..6369c4b2218d33 100644
--- a/Lib/pathlib.py
+++ b/Lib/pathlib.py
@@ -7,7 +7,7 @@
import re
import sys
from _collections_abc import Sequence
-from errno import EINVAL, ENOENT, ENOTDIR, EBADF
+from errno import EINVAL, ENOENT, ENOTDIR, EBADF, ELOOP
from operator import attrgetter
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
from urllib.parse import quote_from_bytes as urlquote_from_bytes
@@ -35,10 +35,11 @@
#
# EBADF - guard against macOS `stat` throwing EBADF
-_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF)
+_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF, ELOOP)
_IGNORED_WINERRORS = (
21, # ERROR_NOT_READY - drive exists but is not accessible
+ 1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself
)
def _ignore_error(exception):
@@ -520,7 +521,13 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
cf = parent_path._flavour.casefold
entries = list(scandir(parent_path))
for entry in entries:
- if not self.dironly or entry.is_dir():
+ entry_is_dir = False
+ try:
+ entry_is_dir = entry.is_dir()
+ except OSError as e:
+ if not _ignore_error(e):
+ raise
+ if not self.dironly or entry_is_dir:
name = entry.name
casefolded = cf(name)
if self.pat.match(casefolded):
diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py
index caad1c23876abd..069467a8459f00 100644
--- a/Lib/test/test_pathlib.py
+++ b/Lib/test/test_pathlib.py
@@ -1221,7 +1221,8 @@ class _BasePathTest(object):
# |-- dirE # No permissions
# |-- fileA
# |-- linkA -> fileA
- # `-- linkB -> dirB
+ # |-- linkB -> dirB
+ # `-- brokenLinkLoop -> brokenLinkLoop
#
def setUp(self):
@@ -1252,6 +1253,8 @@ def cleanup():
self.dirlink(os.path.join('..', 'dirB'), join('dirA', 'linkC'))
# This one goes upwards, creating a loop.
self.dirlink(os.path.join('..', 'dirB'), join('dirB', 'linkD'))
+ # Broken symlink (pointing to itself).
+ os.symlink('brokenLinkLoop', join('brokenLinkLoop'))
if os.name == 'nt':
# Workaround for http://bugs.python.org/issue13772.
@@ -1384,7 +1387,7 @@ def test_iterdir(self):
paths = set(it)
expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA']
if support.can_symlink():
- expected += ['linkA', 'linkB', 'brokenLink']
+ expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop']
self.assertEqual(paths, { P(BASE, q) for q in expected })
@support.skip_unless_symlink
@@ -1465,6 +1468,7 @@ def test_rglob_symlink_loop(self):
'fileA',
'linkA',
'linkB',
+ 'brokenLinkLoop',
}
self.assertEqual(given, {p / x for x in expect})
diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-02-22-14-30-19.bpo-36035.-6dy1y.rst b/Misc/NEWS.d/next/Core and Builtins/2019-02-22-14-30-19.bpo-36035.-6dy1y.rst
new file mode 100644
index 00000000000000..1e4f7ac71f3395
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2019-02-22-14-30-19.bpo-36035.-6dy1y.rst
@@ -0,0 +1 @@
+Added fix for broken symlinks in combination with pathlib
\ No newline at end of file
From aa32a7e1116f7aaaef9fec453db910e90ab7b101 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Batuhan=20Ta=C5=9Fkaya?=
<47358913+isidentical@users.noreply.github.com>
Date: Tue, 21 May 2019 20:47:42 +0300
Subject: [PATCH 048/441] bpo-23378: Add an extend action to argparse
(GH-13305)
Add an extend action to argparse
https://bugs.python.org/issue23378
---
Doc/library/argparse.rst | 9 +++++++++
Lib/argparse.py | 7 +++++++
Lib/test/test_argparse.py | 9 +++++++++
.../Library/2019-05-14-05-38-22.bpo-23378.R25teI.rst | 1 +
4 files changed, 26 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-14-05-38-22.bpo-23378.R25teI.rst
diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst
index cef197f3055581..b77a38ccd48577 100644
--- a/Doc/library/argparse.rst
+++ b/Doc/library/argparse.rst
@@ -797,6 +797,15 @@ how the command-line arguments should be handled. The supplied actions are:
>>> parser.parse_args(['--version'])
PROG 2.0
+* ``'extend'`` - This stores a list, and extends each argument value to the
+ list.
+ Example usage::
+
+ >>> parser = argparse.ArgumentParser()
+ >>> parser.add_argument("--foo", action="extend", nargs="+", type=str)
+ >>> parser.parse_args(["--foo", "f1", "--foo", "f2", "f3", "f4"])
+ Namespace(foo=['f1', 'f2', 'f3', 'f4'])
+
You may also specify an arbitrary action by passing an Action subclass or
other object that implements the same interface. The recommended way to do
this is to extend :class:`Action`, overriding the ``__call__`` method
diff --git a/Lib/argparse.py b/Lib/argparse.py
index 798766f6c4086a..ef888f063b3286 100644
--- a/Lib/argparse.py
+++ b/Lib/argparse.py
@@ -1154,6 +1154,12 @@ def __call__(self, parser, namespace, values, option_string=None):
vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
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
@@ -1262,6 +1268,7 @@ def __init__(self,
self.register('action', 'help', _HelpAction)
self.register('action', 'version', _VersionAction)
self.register('action', 'parsers', _SubParsersAction)
+ self.register('action', 'extend', _ExtendAction)
# raise an exception if the conflict handler is invalid
self._get_handler()
diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py
index e849c7ba49bcd2..9d68f40571fe6f 100644
--- a/Lib/test/test_argparse.py
+++ b/Lib/test/test_argparse.py
@@ -1786,6 +1786,15 @@ def test(self):
self.assertEqual(parser.parse_args(['42']), NS(badger='foo[42]'))
+class TestActionExtend(ParserTestCase):
+ argument_signatures = [
+ Sig('--foo', action="extend", nargs="+", type=str),
+ ]
+ failures = ()
+ successes = [
+ ('--foo f1 --foo f2 f3 f4', NS(foo=['f1', 'f2', 'f3', 'f4'])),
+ ]
+
# ================
# Subparsers tests
# ================
diff --git a/Misc/NEWS.d/next/Library/2019-05-14-05-38-22.bpo-23378.R25teI.rst b/Misc/NEWS.d/next/Library/2019-05-14-05-38-22.bpo-23378.R25teI.rst
new file mode 100644
index 00000000000000..c7c3f174d637e4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-14-05-38-22.bpo-23378.R25teI.rst
@@ -0,0 +1 @@
+Add an extend action to argparser.
From 565b4f1ac7304d1e690c404ca8316f383ba60862 Mon Sep 17 00:00:00 2001
From: Matthias Bussonnier
Date: Tue, 21 May 2019 13:12:03 -0700
Subject: [PATCH 049/441] bpo-34616: Add PyCF_ALLOW_TOP_LEVEL_AWAIT to allow
top-level await (GH-13148)
Co-Authored-By: Yury Selivanov
---
Doc/library/functions.rst | 10 +++
Include/compile.h | 1 +
Lib/test/test_builtin.py | 73 ++++++++++++++++++-
.../2019-05-07-17-12-37.bpo-34616.0Y0_9r.rst | 1 +
Parser/asdl_c.py | 2 +
Python/Python-ast.c | 2 +
Python/compile.c | 28 +++++--
7 files changed, 109 insertions(+), 8 deletions(-)
create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-05-07-17-12-37.bpo-34616.0Y0_9r.rst
diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst
index 613e4f74ac4176..1a9a8b5beeeebe 100644
--- a/Doc/library/functions.rst
+++ b/Doc/library/functions.rst
@@ -257,6 +257,12 @@ are always available. They are listed here in alphabetical order.
can be found as the :attr:`~__future__._Feature.compiler_flag` attribute on
the :class:`~__future__._Feature` instance in the :mod:`__future__` module.
+ The optional argument *flags* also controls whether the compiled source is
+ allowed to contain top-level ``await``, ``async for`` and ``async with``.
+ When the bit ``ast.PyCF_ALLOW_TOP_LEVEL_AWAIT`` is set, the return code
+ object has ``CO_COROUTINE`` set in ``co_code``, and can be interactively
+ executed via ``await eval(code_object)``.
+
The argument *optimize* specifies the optimization level of the compiler; the
default value of ``-1`` selects the optimization level of the interpreter as
given by :option:`-O` options. Explicit levels are ``0`` (no optimization;
@@ -290,6 +296,10 @@ are always available. They are listed here in alphabetical order.
Previously, :exc:`TypeError` was raised when null bytes were encountered
in *source*.
+ .. versionadded:: 3.8
+ ``ast.PyCF_ALLOW_TOP_LEVEL_AWAIT`` can now be passed in flags to enable
+ support for top-level ``await``, ``async for``, and ``async with``.
+
.. class:: complex([real[, imag]])
diff --git a/Include/compile.h b/Include/compile.h
index 13708678675f7b..a833caa06b9dad 100644
--- a/Include/compile.h
+++ b/Include/compile.h
@@ -23,6 +23,7 @@ PyAPI_FUNC(PyCodeObject *) PyNode_Compile(struct _node *, const char *);
#define PyCF_ONLY_AST 0x0400
#define PyCF_IGNORE_COOKIE 0x0800
#define PyCF_TYPE_COMMENTS 0x1000
+#define PyCF_ALLOW_TOP_LEVEL_AWAIT 0x2000
#ifndef Py_LIMITED_API
typedef struct {
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index 5674ea89b1c408..4a358e89d1c2e5 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -1,6 +1,7 @@
# Python test set -- built-in functions
import ast
+import asyncio
import builtins
import collections
import decimal
@@ -18,9 +19,14 @@
import unittest
import warnings
from contextlib import ExitStack
+from inspect import CO_COROUTINE
+from itertools import product
+from textwrap import dedent
+from types import AsyncGeneratorType, FunctionType
from operator import neg
from test.support import (
- EnvironmentVarGuard, TESTFN, check_warnings, swap_attr, unlink)
+ EnvironmentVarGuard, TESTFN, check_warnings, swap_attr, unlink,
+ maybe_get_event_loop_policy)
from test.support.script_helper import assert_python_ok
from unittest.mock import MagicMock, patch
try:
@@ -358,6 +364,71 @@ def f(): """doc"""
rv = ns['f']()
self.assertEqual(rv, tuple(expected))
+ def test_compile_top_level_await(self):
+ """Test whether code some top level await can be compiled.
+
+ Make sure it compiles only with the PyCF_ALLOW_TOP_LEVEL_AWAIT flag set,
+ and make sure the generated code object has the CO_COROUTINE flag set in
+ order to execute it with `await eval(.....)` instead of exec, or via a
+ FunctionType.
+ """
+
+ # helper function just to check we can run top=level async-for
+ async def arange(n):
+ for i in range(n):
+ yield i
+
+ modes = ('single', 'exec')
+ code_samples = ['''a = await asyncio.sleep(0, result=1)''',
+ '''async for i in arange(1):
+ a = 1''',
+ '''async with asyncio.Lock() as l:
+ a = 1''']
+ policy = maybe_get_event_loop_policy()
+ try:
+ for mode, code_sample in product(modes,code_samples):
+ source = dedent(code_sample)
+ with self.assertRaises(SyntaxError, msg=f"{source=} {mode=}"):
+ compile(source, '?' , mode)
+
+ co = compile(source,
+ '?',
+ mode,
+ flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
+
+ self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE,
+ msg=f"{source=} {mode=}")
+
+
+ # test we can create and advance a function type
+ globals_ = {'asyncio': asyncio, 'a':0, 'arange': arange}
+ async_f = FunctionType(co, globals_)
+ asyncio.run(async_f())
+ self.assertEqual(globals_['a'], 1)
+
+ # test we can await-eval,
+ globals_ = {'asyncio': asyncio, 'a':0, 'arange': arange}
+ asyncio.run(eval(co, globals_))
+ self.assertEqual(globals_['a'], 1)
+ finally:
+ asyncio.set_event_loop_policy(policy)
+
+ def test_compile_async_generator(self):
+ """
+ With the PyCF_ALLOW_TOP_LEVEL_AWAIT flag added in 3.8, we want to
+ make sure AsyncGenerators are still properly not marked with CO_COROUTINE
+ """
+ code = dedent("""async def ticker():
+ for i in range(10):
+ yield i
+ await asyncio.sleep(0)""")
+
+ co = compile(code, '?', 'exec', flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
+ glob = {}
+ exec(co, glob)
+ self.assertEqual(type(glob['ticker']()), AsyncGeneratorType)
+
+
def test_delattr(self):
sys.spam = 1
delattr(sys, 'spam')
diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-05-07-17-12-37.bpo-34616.0Y0_9r.rst b/Misc/NEWS.d/next/Core and Builtins/2019-05-07-17-12-37.bpo-34616.0Y0_9r.rst
new file mode 100644
index 00000000000000..c264d21bd01610
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2019-05-07-17-12-37.bpo-34616.0Y0_9r.rst
@@ -0,0 +1 @@
+The ``compile()`` builtin functions now support the ``ast.PyCF_ALLOW_TOP_LEVEL_AWAIT`` flag, which allow to compile sources that contains top-level ``await``, ``async with`` or ``async for``. This is useful to evaluate async-code from with an already async functions; for example in a custom REPL.
\ No newline at end of file
diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py
index 4091b6db638cd6..cb0e6d7f9df26a 100644
--- a/Parser/asdl_c.py
+++ b/Parser/asdl_c.py
@@ -1000,6 +1000,8 @@ def visitModule(self, mod):
self.emit("if (!m) return NULL;", 1)
self.emit("d = PyModule_GetDict(m);", 1)
self.emit('if (PyDict_SetItemString(d, "AST", (PyObject*)&AST_type) < 0) return NULL;', 1)
+ self.emit('if (PyModule_AddIntMacro(m, PyCF_ALLOW_TOP_LEVEL_AWAIT) < 0)', 1)
+ self.emit("return NULL;", 2)
self.emit('if (PyModule_AddIntMacro(m, PyCF_ONLY_AST) < 0)', 1)
self.emit("return NULL;", 2)
self.emit('if (PyModule_AddIntMacro(m, PyCF_TYPE_COMMENTS) < 0)', 1)
diff --git a/Python/Python-ast.c b/Python/Python-ast.c
index cb53a41cdf35bd..552750584480b7 100644
--- a/Python/Python-ast.c
+++ b/Python/Python-ast.c
@@ -8776,6 +8776,8 @@ PyInit__ast(void)
if (!m) return NULL;
d = PyModule_GetDict(m);
if (PyDict_SetItemString(d, "AST", (PyObject*)&AST_type) < 0) return NULL;
+ if (PyModule_AddIntMacro(m, PyCF_ALLOW_TOP_LEVEL_AWAIT) < 0)
+ return NULL;
if (PyModule_AddIntMacro(m, PyCF_ONLY_AST) < 0)
return NULL;
if (PyModule_AddIntMacro(m, PyCF_TYPE_COMMENTS) < 0)
diff --git a/Python/compile.c b/Python/compile.c
index 63b2456bb3e850..734e8401ff0247 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -2609,7 +2609,9 @@ static int
compiler_async_for(struct compiler *c, stmt_ty s)
{
basicblock *start, *except, *end;
- if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION) {
+ if (c->c_flags->cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT){
+ c->u->u_ste->ste_coroutine = 1;
+ } else if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION) {
return compiler_error(c, "'async for' outside async function");
}
@@ -4564,7 +4566,9 @@ compiler_async_with(struct compiler *c, stmt_ty s, int pos)
withitem_ty item = asdl_seq_GET(s->v.AsyncWith.items, pos);
assert(s->kind == AsyncWith_kind);
- if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION) {
+ if (c->c_flags->cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT){
+ c->u->u_ste->ste_coroutine = 1;
+ } else if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION){
return compiler_error(c, "'async with' outside async function");
}
@@ -4773,12 +4777,16 @@ compiler_visit_expr1(struct compiler *c, expr_ty e)
ADDOP(c, YIELD_FROM);
break;
case Await_kind:
- if (c->u->u_ste->ste_type != FunctionBlock)
- return compiler_error(c, "'await' outside function");
+ if (!(c->c_flags->cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT)){
+ if (c->u->u_ste->ste_type != FunctionBlock){
+ return compiler_error(c, "'await' outside function");
+ }
- if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION &&
- c->u->u_scope_type != COMPILER_SCOPE_COMPREHENSION)
- return compiler_error(c, "'await' outside async function");
+ if (c->u->u_scope_type != COMPILER_SCOPE_ASYNC_FUNCTION &&
+ c->u->u_scope_type != COMPILER_SCOPE_COMPREHENSION){
+ return compiler_error(c, "'await' outside async function");
+ }
+ }
VISIT(c, expr, e->v.Await.value);
ADDOP(c, GET_AWAITABLE);
@@ -5712,6 +5720,12 @@ compute_code_flags(struct compiler *c)
/* (Only) inherit compilerflags in PyCF_MASK */
flags |= (c->c_flags->cf_flags & PyCF_MASK);
+ if ((c->c_flags->cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT) &&
+ ste->ste_coroutine &&
+ !ste->ste_generator) {
+ flags |= CO_COROUTINE;
+ }
+
return flags;
}
From 7abf8c60819d5749e6225b371df51a9c5f1ea8e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Batuhan=20Ta=C5=9Fkaya?=
<47358913+isidentical@users.noreply.github.com>
Date: Tue, 21 May 2019 23:27:36 +0300
Subject: [PATCH 050/441] bpo-25652: Fix __rmod__ of UserString (GH-13326)
The ``__rmod__`` method of ``collections.UserString`` class had a bug that made it unusable.
https://bugs.python.org/issue25652
---
Lib/collections/__init__.py | 5 ++---
Lib/test/test_userstring.py | 12 ++++++++++++
.../Library/2019-05-14-21-39-52.bpo-25652.xLw42k.rst | 1 +
3 files changed, 15 insertions(+), 3 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-14-21-39-52.bpo-25652.xLw42k.rst
diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py
index 960d82a5dcfbf5..cb7f1bb1fcfe41 100644
--- a/Lib/collections/__init__.py
+++ b/Lib/collections/__init__.py
@@ -1214,9 +1214,8 @@ def __mul__(self, n):
__rmul__ = __mul__
def __mod__(self, 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:
def capitalize(self): return self.__class__(self.data.capitalize())
def casefold(self):
diff --git a/Lib/test/test_userstring.py b/Lib/test/test_userstring.py
index 71528223d35bb5..19b0acfc760fa4 100644
--- a/Lib/test/test_userstring.py
+++ b/Lib/test/test_userstring.py
@@ -39,6 +39,18 @@ def checkcall(self, object, methodname, *args):
# we don't fix the arguments, because UserString can't cope with it
getattr(object, methodname)(*args)
+ def test_rmod(self):
+ class ustr2(UserString):
+ pass
+
+ class ustr3(ustr2):
+ def __rmod__(self, other):
+ return super().__rmod__(other)
+
+ fmt2 = ustr2('value is %s')
+ str3 = ustr3('TEST')
+ self.assertEqual(fmt2 % str3, 'value is TEST')
+
if __name__ == "__main__":
unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2019-05-14-21-39-52.bpo-25652.xLw42k.rst b/Misc/NEWS.d/next/Library/2019-05-14-21-39-52.bpo-25652.xLw42k.rst
new file mode 100644
index 00000000000000..421fccfe8c73a6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-14-21-39-52.bpo-25652.xLw42k.rst
@@ -0,0 +1 @@
+Fix bug in ``__rmod__`` of ``UserString`` - by Batuhan Taskaya.
From eca18aac7b5952d23854d27dc8e502dfb0bb0505 Mon Sep 17 00:00:00 2001
From: Yury Selivanov
Date: Tue, 21 May 2019 17:20:21 -0400
Subject: [PATCH 051/441] bpo-34616: Fix code style and unbreak buildbots
(GH-13473)
See also PR GH-13148.
---
Lib/test/test_builtin.py | 38 ++++++++++++++++++++------------------
1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index 4a358e89d1c2e5..e32fb75d81912d 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -367,10 +367,10 @@ def f(): """doc"""
def test_compile_top_level_await(self):
"""Test whether code some top level await can be compiled.
- Make sure it compiles only with the PyCF_ALLOW_TOP_LEVEL_AWAIT flag set,
- and make sure the generated code object has the CO_COROUTINE flag set in
- order to execute it with `await eval(.....)` instead of exec, or via a
- FunctionType.
+ Make sure it compiles only with the PyCF_ALLOW_TOP_LEVEL_AWAIT flag
+ set, and make sure the generated code object has the CO_COROUTINE flag
+ set in order to execute it with `await eval(.....)` instead of exec,
+ or via a FunctionType.
"""
# helper function just to check we can run top=level async-for
@@ -379,17 +379,20 @@ async def arange(n):
yield i
modes = ('single', 'exec')
- code_samples = ['''a = await asyncio.sleep(0, result=1)''',
- '''async for i in arange(1):
- a = 1''',
- '''async with asyncio.Lock() as l:
- a = 1''']
+ code_samples = [
+ '''a = await asyncio.sleep(0, result=1)''',
+ '''async for i in arange(1):
+ a = 1''',
+ '''async with asyncio.Lock() as l:
+ a = 1'''
+ ]
policy = maybe_get_event_loop_policy()
try:
- for mode, code_sample in product(modes,code_samples):
+ for mode, code_sample in product(modes, code_samples):
source = dedent(code_sample)
- with self.assertRaises(SyntaxError, msg=f"{source=} {mode=}"):
- compile(source, '?' , mode)
+ with self.assertRaises(
+ SyntaxError, msg=f"source={source} mode={mode}"):
+ compile(source, '?', mode)
co = compile(source,
'?',
@@ -397,17 +400,16 @@ async def arange(n):
flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
self.assertEqual(co.co_flags & CO_COROUTINE, CO_COROUTINE,
- msg=f"{source=} {mode=}")
-
+ msg=f"source={source} mode={mode}")
# test we can create and advance a function type
- globals_ = {'asyncio': asyncio, 'a':0, 'arange': arange}
+ globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
async_f = FunctionType(co, globals_)
asyncio.run(async_f())
self.assertEqual(globals_['a'], 1)
# test we can await-eval,
- globals_ = {'asyncio': asyncio, 'a':0, 'arange': arange}
+ globals_ = {'asyncio': asyncio, 'a': 0, 'arange': arange}
asyncio.run(eval(co, globals_))
self.assertEqual(globals_['a'], 1)
finally:
@@ -416,7 +418,8 @@ async def arange(n):
def test_compile_async_generator(self):
"""
With the PyCF_ALLOW_TOP_LEVEL_AWAIT flag added in 3.8, we want to
- make sure AsyncGenerators are still properly not marked with CO_COROUTINE
+ make sure AsyncGenerators are still properly not marked with the
+ CO_COROUTINE flag.
"""
code = dedent("""async def ticker():
for i in range(10):
@@ -428,7 +431,6 @@ def test_compile_async_generator(self):
exec(co, glob)
self.assertEqual(type(glob['ticker']()), AsyncGeneratorType)
-
def test_delattr(self):
sys.spam = 1
delattr(sys, 'spam')
From 2725cb01d7cbf5caecb51cc20d97ba324b09ce96 Mon Sep 17 00:00:00 2001
From: Berker Peksag
Date: Wed, 22 May 2019 02:00:35 +0300
Subject: [PATCH 052/441] bpo-36948: Fix test_urlopener_retrieve_file on
Windows (GH-13476)
---
Lib/test/test_urllib.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py
index 74b19fbdcd8dbe..6b995fef8cb561 100644
--- a/Lib/test/test_urllib.py
+++ b/Lib/test/test_urllib.py
@@ -1470,7 +1470,8 @@ def test_urlopener_retrieve_file(self):
os.close(fd)
fileurl = "file:" + urllib.request.pathname2url(tmpfile)
filename, _ = urllib.request.URLopener().retrieve(fileurl)
- self.assertEqual(filename, tmpfile)
+ # Some buildbots have TEMP folder that uses a lowercase drive letter.
+ self.assertEqual(os.path.normcase(filename), os.path.normcase(tmpfile))
@support.ignore_warnings(category=DeprecationWarning)
def test_urlopener_retrieve_remote(self):
From ef9d9b63129a2f243591db70e9a2dd53fab95d86 Mon Sep 17 00:00:00 2001
From: Victor Stinner
Date: Wed, 22 May 2019 11:28:22 +0200
Subject: [PATCH 053/441] bpo-36829: Add sys.unraisablehook() (GH-13187)
Add new sys.unraisablehook() function which can be overridden to
control how "unraisable exceptions" are handled. It is called when an
exception has occurred but there is no way for Python to handle it.
For example, when a destructor raises an exception or during garbage
collection (gc.collect()).
Changes:
* Add an internal UnraisableHookArgs type used to pass arguments to
sys.unraisablehook.
* Add _PyErr_WriteUnraisableDefaultHook().
* The default hook now ignores exception on writing the traceback.
* test_sys now uses unittest.main() to automatically discover tests:
remove test_main().
* Add _PyErr_Init().
* Fix PyErr_WriteUnraisable(): hold a strong reference to sys.stderr
while using it
---
Doc/c-api/exceptions.rst | 5 +
Doc/library/sys.rst | 33 ++-
Doc/whatsnew/3.8.rst | 10 +
Include/internal/pycore_pylifecycle.h | 4 +
Lib/test/test_sys.py | 80 ++++-
.../2019-05-08-12-51-37.bpo-36829.8enFMA.rst | 5 +
Modules/_testcapimodule.c | 15 +
Python/clinic/sysmodule.c.h | 18 +-
Python/errors.c | 278 +++++++++++++++---
Python/importlib.h | 120 ++++----
Python/pylifecycle.c | 12 +
Python/sysmodule.c | 28 ++
12 files changed, 491 insertions(+), 117 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-08-12-51-37.bpo-36829.8enFMA.rst
diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst
index 00ef00526bc5f8..18ff697a2325eb 100644
--- a/Doc/c-api/exceptions.rst
+++ b/Doc/c-api/exceptions.rst
@@ -72,6 +72,9 @@ Printing and clearing
.. c:function:: void PyErr_WriteUnraisable(PyObject *obj)
+ Call :func:`sys.unraisablehook` using the current exception and *obj*
+ argument.
+
This utility function prints a warning message to ``sys.stderr`` when an
exception has been set but it is impossible for the interpreter to actually
raise the exception. It is used, for example, when an exception occurs in an
@@ -81,6 +84,8 @@ Printing and clearing
in which the unraisable exception occurred. If possible,
the repr of *obj* will be printed in the warning message.
+ An exception must be set when calling this function.
+
Raising exceptions
==================
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index 7d27c89fac9572..3b754bd4e27677 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -248,16 +248,19 @@ always available.
before the program exits. The handling of such top-level exceptions can be
customized by assigning another three-argument function to ``sys.excepthook``.
+ See also :func:`unraisablehook` which handles unraisable exceptions.
+
.. data:: __breakpointhook__
__displayhook__
__excepthook__
+ __unraisablehook__
These objects contain the original values of ``breakpointhook``,
- ``displayhook``, and ``excepthook`` at the start of the program. They are
- saved so that ``breakpointhook``, ``displayhook`` and ``excepthook`` can be
- restored in case they happen to get replaced with broken or alternative
- objects.
+ ``displayhook``, ``excepthook``, and ``unraisablehook`` at the start of the
+ program. They are saved so that ``breakpointhook``, ``displayhook`` and
+ ``excepthook``, ``unraisablehook`` can be restored in case they happen to
+ get replaced with broken or alternative objects.
.. versionadded:: 3.7
__breakpointhook__
@@ -1487,6 +1490,28 @@ always available.
is suppressed and only the exception type and value are printed.
+.. function:: unraisablehook(unraisable, /)
+
+ Handle an unraisable exception.
+
+ Called when an exception has occurred but there is no way for Python to
+ handle it. For example, when a destructor raises an exception or during
+ garbage collection (:func:`gc.collect`).
+
+ The *unraisable* argument has the following attributes:
+
+ * *exc_type*: Exception type.
+ * *exc_value*: Exception value, can be ``None``.
+ * *exc_traceback*: Exception traceback, can be ``None``.
+ * *object*: Object causing the exception, can be ``None``.
+
+ :func:`sys.unraisablehook` can be overridden to control how unraisable
+ exceptions are handled.
+
+ See also :func:`excepthook` which handles uncaught exceptions.
+
+ .. versionadded:: 3.8
+
.. data:: version
A string containing the version number of the Python interpreter plus additional
diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst
index 5f8208d5bf4a5e..63b8200a1528f0 100644
--- a/Doc/whatsnew/3.8.rst
+++ b/Doc/whatsnew/3.8.rst
@@ -481,6 +481,16 @@ and manipulating normal distributions of a random variable.
[7.672102882379219, 12.000027119750287, 4.647488369766392]
+sys
+---
+
+Add new :func:`sys.unraisablehook` function which can be overridden to control
+how "unraisable exceptions" are handled. It is called when an exception has
+occurred but there is no way for Python to handle it. For example, when a
+destructor raises an exception or during garbage collection
+(:func:`gc.collect`).
+
+
tarfile
-------
diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h
index 4684ebea44a683..07cf815363b779 100644
--- a/Include/internal/pycore_pylifecycle.h
+++ b/Include/internal/pycore_pylifecycle.h
@@ -48,6 +48,7 @@ extern int _PySys_InitMain(
PyInterpreterState *interp);
extern _PyInitError _PyImport_Init(PyInterpreterState *interp);
extern _PyInitError _PyExc_Init(void);
+extern _PyInitError _PyErr_Init(void);
extern _PyInitError _PyBuiltins_AddExceptions(PyObject * bltinmod);
extern _PyInitError _PyImportHooks_Init(void);
extern int _PyFloat_Init(void);
@@ -100,8 +101,11 @@ PyAPI_FUNC(_PyInitError) _Py_PreInitializeFromCoreConfig(
const _PyCoreConfig *coreconfig,
const _PyArgv *args);
+
PyAPI_FUNC(int) _Py_HandleSystemExit(int *exitcode_p);
+PyAPI_FUNC(PyObject*) _PyErr_WriteUnraisableDefaultHook(PyObject *unraisable);
+
#ifdef __cplusplus
}
#endif
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index d1c7daad7bba48..2b358ca0466e6e 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -876,6 +876,81 @@ def test__enablelegacywindowsfsencoding(self):
self.assertEqual(out, 'mbcs replace')
+@test.support.cpython_only
+class UnraisableHookTest(unittest.TestCase):
+ def write_unraisable_exc(self, exc, obj):
+ import _testcapi
+ import types
+ try:
+ # raise the exception to get a traceback in the except block
+ try:
+ raise exc
+ except Exception as exc2:
+ _testcapi.write_unraisable_exc(exc2, obj)
+ return types.SimpleNamespace(exc_type=type(exc2),
+ exc_value=exc2,
+ exc_traceback=exc2.__traceback__,
+ object=obj)
+ finally:
+ # Explicitly break any reference cycle
+ exc = None
+ exc2 = None
+
+ def test_original_unraisablehook(self):
+ obj = "an object"
+
+ with test.support.captured_output("stderr") as stderr:
+ with test.support.swap_attr(sys, 'unraisablehook',
+ sys.__unraisablehook__):
+ self.write_unraisable_exc(ValueError(42), obj)
+
+ err = stderr.getvalue()
+ self.assertIn(f'Exception ignored in: {obj!r}\n', err)
+ self.assertIn('Traceback (most recent call last):\n', err)
+ self.assertIn('ValueError: 42\n', err)
+
+ def test_original_unraisablehook_wrong_type(self):
+ exc = ValueError(42)
+ with test.support.swap_attr(sys, 'unraisablehook',
+ sys.__unraisablehook__):
+ with self.assertRaises(TypeError):
+ sys.unraisablehook(exc)
+
+ def test_custom_unraisablehook(self):
+ hook_args = None
+
+ def hook_func(args):
+ nonlocal hook_args
+ hook_args = args
+
+ obj = object()
+ try:
+ with test.support.swap_attr(sys, 'unraisablehook', hook_func):
+ expected = self.write_unraisable_exc(ValueError(42), obj)
+ for attr in "exc_type exc_value exc_traceback object".split():
+ self.assertEqual(getattr(hook_args, attr),
+ getattr(expected, attr),
+ (hook_args, expected))
+ finally:
+ # expected and hook_args contain an exception: break reference cycle
+ expected = None
+ hook_args = None
+
+ def test_custom_unraisablehook_fail(self):
+ def hook_func(*args):
+ raise Exception("hook_func failed")
+
+ with test.support.captured_output("stderr") as stderr:
+ with test.support.swap_attr(sys, 'unraisablehook', hook_func):
+ self.write_unraisable_exc(ValueError(42), None)
+
+ err = stderr.getvalue()
+ self.assertIn(f'Exception ignored in: {hook_func!r}\n',
+ err)
+ self.assertIn('Traceback (most recent call last):\n', err)
+ self.assertIn('Exception: hook_func failed\n', err)
+
+
@test.support.cpython_only
class SizeofTest(unittest.TestCase):
@@ -1277,8 +1352,5 @@ def test_asyncgen_hooks(self):
self.assertIsNone(cur.finalizer)
-def test_main():
- test.support.run_unittest(SysModuleTest, SizeofTest)
-
if __name__ == "__main__":
- test_main()
+ unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2019-05-08-12-51-37.bpo-36829.8enFMA.rst b/Misc/NEWS.d/next/Library/2019-05-08-12-51-37.bpo-36829.8enFMA.rst
new file mode 100644
index 00000000000000..0b04efcb7e2f0e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-08-12-51-37.bpo-36829.8enFMA.rst
@@ -0,0 +1,5 @@
+Add new :func:`sys.unraisablehook` function which can be overridden to
+control how "unraisable exceptions" are handled. It is called when an
+exception has occurred but there is no way for Python to handle it. For
+example, when a destructor raises an exception or during garbage collection
+(:func:`gc.collect`).
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 2af553960cc7da..7945f498f9e2e7 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -4982,6 +4982,20 @@ negative_refcount(PyObject *self, PyObject *Py_UNUSED(args))
#endif
+static PyObject*
+test_write_unraisable_exc(PyObject *self, PyObject *args)
+{
+ PyObject *exc, *obj;
+ if (!PyArg_ParseTuple(args, "OO", &exc, &obj)) {
+ return NULL;
+ }
+
+ PyErr_SetObject((PyObject *)Py_TYPE(exc), exc);
+ PyErr_WriteUnraisable(obj);
+ Py_RETURN_NONE;
+}
+
+
static PyMethodDef TestMethods[] = {
{"raise_exception", raise_exception, METH_VARARGS},
{"raise_memoryerror", raise_memoryerror, METH_NOARGS},
@@ -5221,6 +5235,7 @@ static PyMethodDef TestMethods[] = {
#ifdef Py_REF_DEBUG
{"negative_refcount", negative_refcount, METH_NOARGS},
#endif
+ {"write_unraisable_exc", test_write_unraisable_exc, METH_VARARGS},
{NULL, NULL} /* sentinel */
};
diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h
index c70b721983c1c4..aede60ac85b519 100644
--- a/Python/clinic/sysmodule.c.h
+++ b/Python/clinic/sysmodule.c.h
@@ -65,6 +65,22 @@ sys_exc_info(PyObject *module, PyObject *Py_UNUSED(ignored))
return sys_exc_info_impl(module);
}
+PyDoc_STRVAR(sys_unraisablehook__doc__,
+"unraisablehook($module, unraisable, /)\n"
+"--\n"
+"\n"
+"Handle an unraisable exception.\n"
+"\n"
+"The unraisable argument has the following attributes:\n"
+"\n"
+"* exc_type: Exception type.\n"
+"* exc_value: Exception value.\n"
+"* exc_tb: Exception traceback, can be None.\n"
+"* obj: Object causing the exception, can be None.");
+
+#define SYS_UNRAISABLEHOOK_METHODDEF \
+ {"unraisablehook", (PyCFunction)sys_unraisablehook, METH_O, sys_unraisablehook__doc__},
+
PyDoc_STRVAR(sys_exit__doc__,
"exit($module, status=None, /)\n"
"--\n"
@@ -1060,4 +1076,4 @@ sys_getandroidapilevel(PyObject *module, PyObject *Py_UNUSED(ignored))
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
#define SYS_GETANDROIDAPILEVEL_METHODDEF
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
-/*[clinic end generated code: output=3ba4c194d00f1866 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=603e4d5a453dc769 input=a9049054013a1b77]*/
diff --git a/Python/errors.c b/Python/errors.c
index b8af1df4161d57..9622b5a1067df9 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -2,6 +2,7 @@
/* Error handling */
#include "Python.h"
+#include "pycore_coreconfig.h"
#include "pycore_pystate.h"
#ifndef __STDC__
@@ -944,90 +945,271 @@ PyErr_NewExceptionWithDoc(const char *name, const char *doc,
}
-/* Call when an exception has occurred but there is no way for Python
- to handle it. Examples: exception in __del__ or during GC. */
-void
-PyErr_WriteUnraisable(PyObject *obj)
+PyDoc_STRVAR(UnraisableHookArgs__doc__,
+"UnraisableHookArgs\n\
+\n\
+Type used to pass arguments to sys.unraisablehook.");
+
+static PyTypeObject UnraisableHookArgsType;
+
+static PyStructSequence_Field UnraisableHookArgs_fields[] = {
+ {"exc_type", "Exception type"},
+ {"exc_value", "Exception value"},
+ {"exc_traceback", "Exception traceback"},
+ {"object", "Object causing the exception"},
+ {0}
+};
+
+static PyStructSequence_Desc UnraisableHookArgs_desc = {
+ .name = "UnraisableHookArgs",
+ .doc = UnraisableHookArgs__doc__,
+ .fields = UnraisableHookArgs_fields,
+ .n_in_sequence = 4
+};
+
+
+_PyInitError
+_PyErr_Init(void)
{
- _Py_IDENTIFIER(__module__);
- PyObject *f, *t, *v, *tb;
- PyObject *moduleName = NULL;
- const char *className;
+ if (UnraisableHookArgsType.tp_name == NULL) {
+ if (PyStructSequence_InitType2(&UnraisableHookArgsType,
+ &UnraisableHookArgs_desc) < 0) {
+ return _Py_INIT_ERR("failed to initialize UnraisableHookArgs type");
+ }
+ }
+ return _Py_INIT_OK();
+}
- PyErr_Fetch(&t, &v, &tb);
- f = _PySys_GetObjectId(&PyId_stderr);
- if (f == NULL || f == Py_None)
- goto done;
+static PyObject *
+make_unraisable_hook_args(PyObject *exc_type, PyObject *exc_value,
+ PyObject *exc_tb, PyObject *obj)
+{
+ PyObject *args = PyStructSequence_New(&UnraisableHookArgsType);
+ if (args == NULL) {
+ return NULL;
+ }
+
+ Py_ssize_t pos = 0;
+#define ADD_ITEM(exc_type) \
+ do { \
+ if (exc_type == NULL) { \
+ exc_type = Py_None; \
+ } \
+ Py_INCREF(exc_type); \
+ PyStructSequence_SET_ITEM(args, pos++, exc_type); \
+ } while (0)
+
+
+ ADD_ITEM(exc_type);
+ ADD_ITEM(exc_value);
+ ADD_ITEM(exc_tb);
+ ADD_ITEM(obj);
+#undef ADD_ITEM
+
+ if (PyErr_Occurred()) {
+ Py_DECREF(args);
+ return NULL;
+ }
+ return args;
+}
+
+
+
+/* Default implementation of sys.unraisablehook.
+
+ It can be called to log the exception of a custom sys.unraisablehook.
- if (obj) {
- if (PyFile_WriteString("Exception ignored in: ", f) < 0)
- goto done;
- if (PyFile_WriteObject(obj, f, 0) < 0) {
+ Do nothing if sys.stderr attribute doesn't exist or is set to None. */
+static int
+write_unraisable_exc_file(PyObject *exc_type, PyObject *exc_value,
+ PyObject *exc_tb, PyObject *obj, PyObject *file)
+{
+ if (obj != NULL && obj != Py_None) {
+ if (PyFile_WriteString("Exception ignored in: ", file) < 0) {
+ return -1;
+ }
+
+ if (PyFile_WriteObject(obj, file, 0) < 0) {
PyErr_Clear();
- if (PyFile_WriteString("