From b953665cf54aa3a482e09fdd9c7400c070674f11 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 15:33:17 -0400 Subject: [PATCH 01/31] Remove docstring in distutils --- distutils/__init__.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/distutils/__init__.py b/distutils/__init__.py index b3ac0146cb..1a405387ff 100644 --- a/distutils/__init__.py +++ b/distutils/__init__.py @@ -1,13 +1,3 @@ -"""distutils - -The main package for the Python Module Distribution Utilities. Normally -used from a setup script as - - from distutils.core import setup - - setup (...) -""" - import sys import importlib From a3fcf976c6b0438cc58e3b125f120cb18357e1ad Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 15:35:10 -0400 Subject: [PATCH 02/31] Prefer partition for splitting a string. --- distutils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distutils/__init__.py b/distutils/__init__.py index 1a405387ff..1a188c35cb 100644 --- a/distutils/__init__.py +++ b/distutils/__init__.py @@ -1,7 +1,7 @@ import sys import importlib -__version__ = sys.version[: sys.version.index(' ')] +__version__, _, _ = sys.version.partition(' ') try: From bf3e726384d6c485e876fcb04c678e160d506694 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 15:41:16 -0400 Subject: [PATCH 03/31] Remove readme --- distutils/README | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 distutils/README diff --git a/distutils/README b/distutils/README deleted file mode 100644 index 23f488506f..0000000000 --- a/distutils/README +++ /dev/null @@ -1,11 +0,0 @@ -This directory contains the Distutils package. - -There's a full documentation available at: - - http://docs.python.org/distutils/ - -The Distutils-SIG web page is also a good starting point: - - http://www.python.org/sigs/distutils-sig/ - -$Id$ From 74090e3a28c28232c22fbb5165381f2a8c36bf37 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 15:44:13 -0400 Subject: [PATCH 04/31] Prefer relative imports for better portability. --- distutils/_msvccompiler.py | 8 ++++---- distutils/archive_util.py | 8 ++++---- distutils/bcppcompiler.py | 10 +++++----- distutils/ccompiler.py | 14 +++++++------- distutils/cmd.py | 6 +++--- distutils/command/bdist.py | 8 ++++---- distutils/command/bdist_dumb.py | 10 +++++----- distutils/command/bdist_rpm.py | 10 +++++----- distutils/command/build.py | 8 ++++---- distutils/command/build_clib.py | 10 +++++----- distutils/command/build_ext.py | 24 ++++++++++++------------ distutils/command/build_py.py | 8 ++++---- distutils/command/build_scripts.py | 6 +++--- distutils/command/check.py | 4 ++-- distutils/command/clean.py | 4 ++-- distutils/command/config.py | 16 ++++++++-------- distutils/command/install.py | 16 ++++++++-------- distutils/command/install_data.py | 4 ++-- distutils/command/install_egg_info.py | 2 +- distutils/command/install_headers.py | 2 +- distutils/command/install_lib.py | 6 +++--- distutils/command/install_scripts.py | 2 +- distutils/command/register.py | 2 +- distutils/command/sdist.py | 14 +++++++------- distutils/command/upload.py | 6 +++--- distutils/config.py | 2 +- distutils/core.py | 12 ++++++------ distutils/cygwinccompiler.py | 8 ++++---- distutils/dep_util.py | 2 +- distutils/dir_util.py | 4 ++-- distutils/dist.py | 10 +++++----- distutils/fancy_getopt.py | 2 +- distutils/file_util.py | 4 ++-- distutils/filelist.py | 6 +++--- distutils/msvc9compiler.py | 8 ++++---- distutils/msvccompiler.py | 6 +++--- distutils/spawn.py | 6 +++--- distutils/unixccompiler.py | 10 +++++----- distutils/util.py | 8 ++++---- distutils/versionpredicate.py | 10 +++++----- 40 files changed, 153 insertions(+), 153 deletions(-) diff --git a/distutils/_msvccompiler.py b/distutils/_msvccompiler.py index 729c2dd521..d25dec1c2e 100644 --- a/distutils/_msvccompiler.py +++ b/distutils/_msvccompiler.py @@ -22,16 +22,16 @@ with contextlib.suppress(ImportError): import winreg -from distutils.errors import ( +from .errors import ( DistutilsExecError, DistutilsPlatformError, CompileError, LibError, LinkError, ) -from distutils.ccompiler import CCompiler, gen_lib_options -from distutils import log -from distutils.util import get_platform +from .ccompiler import CCompiler, gen_lib_options +from . import log +from .util import get_platform from itertools import count diff --git a/distutils/archive_util.py b/distutils/archive_util.py index 5dfe2a16ff..f9c4ed628b 100644 --- a/distutils/archive_util.py +++ b/distutils/archive_util.py @@ -13,10 +13,10 @@ zipfile = None -from distutils.errors import DistutilsExecError -from distutils.spawn import spawn -from distutils.dir_util import mkpath -from distutils import log +from .errors import DistutilsExecError +from .spawn import spawn +from .dir_util import mkpath +from . import log try: from pwd import getpwnam diff --git a/distutils/bcppcompiler.py b/distutils/bcppcompiler.py index 80b6bd8522..4aa1edf21e 100644 --- a/distutils/bcppcompiler.py +++ b/distutils/bcppcompiler.py @@ -15,17 +15,17 @@ import os import warnings -from distutils.errors import ( +from .errors import ( DistutilsExecError, CompileError, LibError, LinkError, UnknownFileError, ) -from distutils.ccompiler import CCompiler, gen_preprocess_options -from distutils.file_util import write_file -from distutils.dep_util import newer -from distutils import log +from .ccompiler import CCompiler, gen_preprocess_options +from .file_util import write_file +from .dep_util import newer +from . import log warnings.warn( diff --git a/distutils/ccompiler.py b/distutils/ccompiler.py index 97551c99fe..1e79e8e45e 100644 --- a/distutils/ccompiler.py +++ b/distutils/ccompiler.py @@ -7,19 +7,19 @@ import os import re -from distutils.errors import ( +from .errors import ( CompileError, LinkError, UnknownFileError, DistutilsPlatformError, DistutilsModuleError, ) -from distutils.spawn import spawn -from distutils.file_util import move_file -from distutils.dir_util import mkpath -from distutils.dep_util import newer_group -from distutils.util import split_quoted, execute -from distutils import log +from .spawn import spawn +from .file_util import move_file +from .dir_util import mkpath +from .dep_util import newer_group +from .util import split_quoted, execute +from . import log class CCompiler: diff --git a/distutils/cmd.py b/distutils/cmd.py index 68a9267c65..88a90ead55 100644 --- a/distutils/cmd.py +++ b/distutils/cmd.py @@ -7,9 +7,9 @@ import sys import os import re -from distutils.errors import DistutilsOptionError -from distutils import util, dir_util, file_util, archive_util, dep_util -from distutils import log + +from .errors import DistutilsOptionError +from . import util, dir_util, file_util, archive_util, dep_util, log class Command: diff --git a/distutils/command/bdist.py b/distutils/command/bdist.py index de37dae0ff..bf0baab0d2 100644 --- a/distutils/command/bdist.py +++ b/distutils/command/bdist.py @@ -6,14 +6,14 @@ import os import warnings -from distutils.core import Command -from distutils.errors import DistutilsPlatformError, DistutilsOptionError -from distutils.util import get_platform +from ..core import Command +from ..errors import DistutilsPlatformError, DistutilsOptionError +from ..util import get_platform def show_formats(): """Print list of available formats (arguments to "--format" option).""" - from distutils.fancy_getopt import FancyGetopt + from ..fancy_getopt import FancyGetopt formats = [] for format in bdist.format_commands: diff --git a/distutils/command/bdist_dumb.py b/distutils/command/bdist_dumb.py index 0f52330f67..4afea28ceb 100644 --- a/distutils/command/bdist_dumb.py +++ b/distutils/command/bdist_dumb.py @@ -5,11 +5,11 @@ $exec_prefix).""" import os -from distutils.core import Command -from distutils.util import get_platform -from distutils.dir_util import remove_tree, ensure_relative -from distutils.errors import DistutilsPlatformError -from distutils.sysconfig import get_python_version +from ..core import Command +from ..util import get_platform +from ..dir_util import remove_tree, ensure_relative +from ..errors import DistutilsPlatformError +from ..sysconfig import get_python_version from distutils import log diff --git a/distutils/command/bdist_rpm.py b/distutils/command/bdist_rpm.py index 6a50ef34ea..524314386d 100644 --- a/distutils/command/bdist_rpm.py +++ b/distutils/command/bdist_rpm.py @@ -7,16 +7,16 @@ import sys import os -from distutils.core import Command -from distutils.debug import DEBUG -from distutils.file_util import write_file -from distutils.errors import ( +from ..core import Command +from ..debug import DEBUG +from ..file_util import write_file +from ..errors import ( DistutilsOptionError, DistutilsPlatformError, DistutilsFileError, DistutilsExecError, ) -from distutils.sysconfig import get_python_version +from ..sysconfig import get_python_version from distutils import log diff --git a/distutils/command/build.py b/distutils/command/build.py index 6d453419d0..c3ab410f29 100644 --- a/distutils/command/build.py +++ b/distutils/command/build.py @@ -4,13 +4,13 @@ import sys import os -from distutils.core import Command -from distutils.errors import DistutilsOptionError -from distutils.util import get_platform +from ..core import Command +from ..errors import DistutilsOptionError +from ..util import get_platform def show_compilers(): - from distutils.ccompiler import show_compilers + from ..ccompiler import show_compilers show_compilers() diff --git a/distutils/command/build_clib.py b/distutils/command/build_clib.py index 50bb9bbabb..442cd54ae6 100644 --- a/distutils/command/build_clib.py +++ b/distutils/command/build_clib.py @@ -15,14 +15,14 @@ # cut 'n paste. Sigh. import os -from distutils.core import Command -from distutils.errors import DistutilsSetupError -from distutils.sysconfig import customize_compiler +from ..core import Command +from ..errors import DistutilsSetupError +from ..sysconfig import customize_compiler from distutils import log def show_compilers(): - from distutils.ccompiler import show_compilers + from ..ccompiler import show_compilers show_compilers() @@ -92,7 +92,7 @@ def run(self): return # Yech -- this is cut 'n pasted from build_ext.py! - from distutils.ccompiler import new_compiler + from ..ccompiler import new_compiler self.compiler = new_compiler( compiler=self.compiler, dry_run=self.dry_run, force=self.force diff --git a/distutils/command/build_ext.py b/distutils/command/build_ext.py index 3c6cee7e36..3019c7570b 100644 --- a/distutils/command/build_ext.py +++ b/distutils/command/build_ext.py @@ -8,8 +8,8 @@ import os import re import sys -from distutils.core import Command -from distutils.errors import ( +from ..core import Command +from ..errors import ( DistutilsOptionError, DistutilsSetupError, CCompilerError, @@ -17,11 +17,11 @@ CompileError, DistutilsPlatformError, ) -from distutils.sysconfig import customize_compiler, get_python_version -from distutils.sysconfig import get_config_h_filename -from distutils.dep_util import newer_group -from distutils.extension import Extension -from distutils.util import get_platform +from ..sysconfig import customize_compiler, get_python_version +from ..sysconfig import get_config_h_filename +from ..dep_util import newer_group +from ..extension import Extension +from ..util import get_platform from distutils import log from . import py37compat @@ -33,7 +33,7 @@ def show_compilers(): - from distutils.ccompiler import show_compilers + from ..ccompiler import show_compilers show_compilers() @@ -280,7 +280,7 @@ def finalize_options(self): # noqa: C901 raise DistutilsOptionError("parallel should be an integer") def run(self): # noqa: C901 - from distutils.ccompiler import new_compiler + from ..ccompiler import new_compiler # 'self.extensions', as supplied by setup.py, is a list of # Extension instances. See the documentation for Extension (in @@ -704,7 +704,7 @@ def get_ext_filename(self, ext_name): of the file from which it will be loaded (eg. "foo/bar.so", or "foo\bar.pyd"). """ - from distutils.sysconfig import get_config_var + from ..sysconfig import get_config_var ext_path = ext_name.split('.') ext_suffix = get_config_var('EXT_SUFFIX') @@ -742,7 +742,7 @@ def get_libraries(self, ext): # noqa: C901 # to need it mentioned explicitly, though, so that's what we do. # Append '_d' to the python import library on debug builds. if sys.platform == "win32": - from distutils._msvccompiler import MSVCCompiler + from .._msvccompiler import MSVCCompiler if not isinstance(self.compiler, MSVCCompiler): template = "python%d%d" @@ -764,7 +764,7 @@ def get_libraries(self, ext): # noqa: C901 # On Cygwin (and if required, other POSIX-like platforms based on # Windows like MinGW) it is simply necessary that all symbols in # shared libraries are resolved at link time. - from distutils.sysconfig import get_config_var + from ..sysconfig import get_config_var link_libpython = False if get_config_var('Py_ENABLE_SHARED'): diff --git a/distutils/command/build_py.py b/distutils/command/build_py.py index 47c6158e0f..d3dfbf8b24 100644 --- a/distutils/command/build_py.py +++ b/distutils/command/build_py.py @@ -7,9 +7,9 @@ import sys import glob -from distutils.core import Command -from distutils.errors import DistutilsOptionError, DistutilsFileError -from distutils.util import convert_path +from ..core import Command +from ..errors import DistutilsOptionError, DistutilsFileError +from ..util import convert_path from distutils import log @@ -384,7 +384,7 @@ def byte_compile(self, files): self.warn('byte-compiling is disabled, skipping.') return - from distutils.util import byte_compile + from ..util import byte_compile prefix = self.build_lib if prefix[-1] != os.sep: diff --git a/distutils/command/build_scripts.py b/distutils/command/build_scripts.py index 2cc5d1e09c..728535dad7 100644 --- a/distutils/command/build_scripts.py +++ b/distutils/command/build_scripts.py @@ -6,9 +6,9 @@ import re from stat import ST_MODE from distutils import sysconfig -from distutils.core import Command -from distutils.dep_util import newer -from distutils.util import convert_path +from ..core import Command +from ..dep_util import newer +from ..util import convert_path from distutils import log import tokenize diff --git a/distutils/command/check.py b/distutils/command/check.py index 539481c946..575e49fb4b 100644 --- a/distutils/command/check.py +++ b/distutils/command/check.py @@ -4,8 +4,8 @@ """ import contextlib -from distutils.core import Command -from distutils.errors import DistutilsSetupError +from ..core import Command +from ..errors import DistutilsSetupError with contextlib.suppress(ImportError): import docutils.utils diff --git a/distutils/command/clean.py b/distutils/command/clean.py index b731b60609..d1b8a20696 100644 --- a/distutils/command/clean.py +++ b/distutils/command/clean.py @@ -5,8 +5,8 @@ # contributed by Bastian Kleineidam , added 2000-03-18 import os -from distutils.core import Command -from distutils.dir_util import remove_tree +from ..core import Command +from ..dir_util import remove_tree from distutils import log diff --git a/distutils/command/config.py b/distutils/command/config.py index 4492c89660..e7ae83f1bd 100644 --- a/distutils/command/config.py +++ b/distutils/command/config.py @@ -12,9 +12,9 @@ import os import re -from distutils.core import Command -from distutils.errors import DistutilsExecError -from distutils.sysconfig import customize_compiler +from ..core import Command +from ..errors import DistutilsExecError +from ..sysconfig import customize_compiler from distutils import log LANG_EXT = {"c": ".c", "c++": ".cxx"} @@ -87,7 +87,7 @@ def _check_compiler(self): """ # We do this late, and only on-demand, because this is an expensive # import. - from distutils.ccompiler import CCompiler, new_compiler + from ..ccompiler import CCompiler, new_compiler if not isinstance(self.compiler, CCompiler): self.compiler = new_compiler( @@ -174,7 +174,7 @@ def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"): preprocessor succeeded, false if there were any errors. ('body' probably isn't of much use, but what the heck.) """ - from distutils.ccompiler import CompileError + from ..ccompiler import CompileError self._check_compiler() ok = True @@ -217,7 +217,7 @@ def try_compile(self, body, headers=None, include_dirs=None, lang="c"): """Try to compile a source file built from 'body' and 'headers'. Return true on success, false otherwise. """ - from distutils.ccompiler import CompileError + from ..ccompiler import CompileError self._check_compiler() try: @@ -243,7 +243,7 @@ def try_link( 'headers', to executable form. Return true on success, false otherwise. """ - from distutils.ccompiler import CompileError, LinkError + from ..ccompiler import CompileError, LinkError self._check_compiler() try: @@ -269,7 +269,7 @@ def try_run( built from 'body' and 'headers'. Return true on success, false otherwise. """ - from distutils.ccompiler import CompileError, LinkError + from ..ccompiler import CompileError, LinkError self._check_compiler() try: diff --git a/distutils/command/install.py b/distutils/command/install.py index a38cddcda5..9db4ad99af 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -9,13 +9,13 @@ import itertools from distutils import log -from distutils.core import Command -from distutils.debug import DEBUG -from distutils.sysconfig import get_config_vars -from distutils.file_util import write_file -from distutils.util import convert_path, subst_vars, change_root -from distutils.util import get_platform -from distutils.errors import DistutilsOptionError, DistutilsPlatformError +from ..core import Command +from ..debug import DEBUG +from ..sysconfig import get_config_vars +from ..file_util import write_file +from ..util import convert_path, subst_vars, change_root +from ..util import get_platform +from ..errors import DistutilsOptionError, DistutilsPlatformError from . import _framework_compat as fw from .. import _collections @@ -515,7 +515,7 @@ def dump_dirs(self, msg): """Dumps the list of user options.""" if not DEBUG: return - from distutils.fancy_getopt import longopt_xlate + from ..fancy_getopt import longopt_xlate log.debug(msg + ":") for opt in self.user_options: diff --git a/distutils/command/install_data.py b/distutils/command/install_data.py index 23d91aded2..d92ed87a64 100644 --- a/distutils/command/install_data.py +++ b/distutils/command/install_data.py @@ -6,8 +6,8 @@ # contributed by Bastian Kleineidam import os -from distutils.core import Command -from distutils.util import change_root, convert_path +from ..core import Command +from ..util import change_root, convert_path class install_data(Command): diff --git a/distutils/command/install_egg_info.py b/distutils/command/install_egg_info.py index d5e68a6e47..ff9f0284bb 100644 --- a/distutils/command/install_egg_info.py +++ b/distutils/command/install_egg_info.py @@ -9,7 +9,7 @@ import sys import re -from distutils.cmd import Command +from ..cmd import Command from distutils import log, dir_util diff --git a/distutils/command/install_headers.py b/distutils/command/install_headers.py index 87046ab391..1cdee823dc 100644 --- a/distutils/command/install_headers.py +++ b/distutils/command/install_headers.py @@ -3,7 +3,7 @@ Implements the Distutils 'install_headers' command, to install C/C++ header files to the Python include directory.""" -from distutils.core import Command +from ..core import Command # XXX force is never used diff --git a/distutils/command/install_lib.py b/distutils/command/install_lib.py index ad3089c8b1..840d3403c4 100644 --- a/distutils/command/install_lib.py +++ b/distutils/command/install_lib.py @@ -7,8 +7,8 @@ import importlib.util import sys -from distutils.core import Command -from distutils.errors import DistutilsOptionError +from ..core import Command +from ..errors import DistutilsOptionError # Extension for Python source files. @@ -126,7 +126,7 @@ def byte_compile(self, files): self.warn('byte-compiling is disabled, skipping.') return - from distutils.util import byte_compile + from ..util import byte_compile # Get the "--root" directory supplied to the "install" command, # and use it as a prefix to strip off the purported filename diff --git a/distutils/command/install_scripts.py b/distutils/command/install_scripts.py index f09bd64420..d4d3e3f333 100644 --- a/distutils/command/install_scripts.py +++ b/distutils/command/install_scripts.py @@ -6,7 +6,7 @@ # contributed by Bastian Kleineidam import os -from distutils.core import Command +from ..core import Command from distutils import log from stat import ST_MODE diff --git a/distutils/command/register.py b/distutils/command/register.py index c1402650d7..1a62ee3f16 100644 --- a/distutils/command/register.py +++ b/distutils/command/register.py @@ -11,7 +11,7 @@ import urllib.request from warnings import warn -from distutils.core import PyPIRCCommand +from ..core import PyPIRCCommand from distutils import log diff --git a/distutils/command/sdist.py b/distutils/command/sdist.py index d6e9489d1b..86e41e5fe7 100644 --- a/distutils/command/sdist.py +++ b/distutils/command/sdist.py @@ -7,23 +7,23 @@ from glob import glob from warnings import warn -from distutils.core import Command +from ..core import Command from distutils import dir_util from distutils import file_util from distutils import archive_util -from distutils.text_file import TextFile -from distutils.filelist import FileList +from ..text_file import TextFile +from ..filelist import FileList from distutils import log -from distutils.util import convert_path -from distutils.errors import DistutilsOptionError, DistutilsTemplateError +from ..util import convert_path +from ..errors import DistutilsOptionError, DistutilsTemplateError def show_formats(): """Print all possible values for the 'formats' option (used by the "--help-formats" command-line option). """ - from distutils.fancy_getopt import FancyGetopt - from distutils.archive_util import ARCHIVE_FORMATS + from ..fancy_getopt import FancyGetopt + from ..archive_util import ARCHIVE_FORMATS formats = [] for format in ARCHIVE_FORMATS.keys(): diff --git a/distutils/command/upload.py b/distutils/command/upload.py index 6af5394339..633273ee7d 100644 --- a/distutils/command/upload.py +++ b/distutils/command/upload.py @@ -11,9 +11,9 @@ from base64 import standard_b64encode from urllib.request import urlopen, Request, HTTPError from urllib.parse import urlparse -from distutils.errors import DistutilsError, DistutilsOptionError -from distutils.core import PyPIRCCommand -from distutils.spawn import spawn +from ..errors import DistutilsError, DistutilsOptionError +from ..core import PyPIRCCommand +from ..spawn import spawn from distutils import log diff --git a/distutils/config.py b/distutils/config.py index 6e0c3a71f1..9a4044adaf 100644 --- a/distutils/config.py +++ b/distutils/config.py @@ -6,7 +6,7 @@ import os from configparser import RawConfigParser -from distutils.cmd import Command +from .cmd import Command DEFAULT_PYPIRC = """\ [distutils] diff --git a/distutils/core.py b/distutils/core.py index de13978f02..34cafbceec 100644 --- a/distutils/core.py +++ b/distutils/core.py @@ -10,8 +10,8 @@ import sys import tokenize -from distutils.debug import DEBUG -from distutils.errors import ( +from .debug import DEBUG +from .errors import ( DistutilsSetupError, DistutilsError, CCompilerError, @@ -19,10 +19,10 @@ ) # Mainly import these so setup scripts can "from distutils.core import" them. -from distutils.dist import Distribution -from distutils.cmd import Command -from distutils.config import PyPIRCCommand -from distutils.extension import Extension +from .dist import Distribution +from .cmd import Command +from .config import PyPIRCCommand +from .extension import Extension __all__ = ['Distribution', 'Command', 'PyPIRCCommand', 'Extension', 'setup'] diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 2c4da5b57e..8019f6a8a3 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -13,15 +13,15 @@ import warnings from subprocess import check_output -from distutils.unixccompiler import UnixCCompiler -from distutils.file_util import write_file -from distutils.errors import ( +from .unixccompiler import UnixCCompiler +from .file_util import write_file +from .errors import ( DistutilsExecError, DistutilsPlatformError, CCompilerError, CompileError, ) -from distutils.version import LooseVersion, suppress_known_deprecation +from .version import LooseVersion, suppress_known_deprecation def get_msvcr(): diff --git a/distutils/dep_util.py b/distutils/dep_util.py index db1fa01996..48da8641c6 100644 --- a/distutils/dep_util.py +++ b/distutils/dep_util.py @@ -5,7 +5,7 @@ timestamp dependency analysis.""" import os -from distutils.errors import DistutilsFileError +from .errors import DistutilsFileError def newer(source, target): diff --git a/distutils/dir_util.py b/distutils/dir_util.py index 6f0bb8ad76..54f5410340 100644 --- a/distutils/dir_util.py +++ b/distutils/dir_util.py @@ -4,8 +4,8 @@ import os import errno -from distutils.errors import DistutilsInternalError, DistutilsFileError -from distutils import log +from .errors import DistutilsInternalError, DistutilsFileError +from . import log # cache for by mkpath() -- in addition to cheapening redundant calls, # eliminates redundant "creating /foo/bar/baz" messages in dry-run mode diff --git a/distutils/dist.py b/distutils/dist.py index 917cd94a0c..d6677b6260 100644 --- a/distutils/dist.py +++ b/distutils/dist.py @@ -16,16 +16,16 @@ except ImportError: warnings = None -from distutils.errors import ( +from .errors import ( DistutilsOptionError, DistutilsModuleError, DistutilsArgError, DistutilsClassError, ) -from distutils.fancy_getopt import FancyGetopt, translate_longopt -from distutils.util import check_environ, strtobool, rfc822_escape -from distutils import log -from distutils.debug import DEBUG +from .fancy_getopt import FancyGetopt, translate_longopt +from .util import check_environ, strtobool, rfc822_escape +from . import log +from .debug import DEBUG # Regex to define acceptable Distutils command names. This is not *quite* # the same as a Python NAME -- I don't allow leading underscores. The fact diff --git a/distutils/fancy_getopt.py b/distutils/fancy_getopt.py index 830f047e28..6abb884d36 100644 --- a/distutils/fancy_getopt.py +++ b/distutils/fancy_getopt.py @@ -12,7 +12,7 @@ import string import re import getopt -from distutils.errors import DistutilsGetoptError, DistutilsArgError +from .errors import DistutilsGetoptError, DistutilsArgError # Much like command_re in distutils.core, this is close to but not quite # the same as a Python NAME -- except, in the spirit of most GNU diff --git a/distutils/file_util.py b/distutils/file_util.py index 1f1e444b1c..bead68eb2d 100644 --- a/distutils/file_util.py +++ b/distutils/file_util.py @@ -4,8 +4,8 @@ """ import os -from distutils.errors import DistutilsFileError -from distutils import log +from .errors import DistutilsFileError +from . import log # for generating verbose output in 'copy_file()' _copy_action = {None: 'copying', 'hard': 'hard linking', 'sym': 'symbolically linking'} diff --git a/distutils/filelist.py b/distutils/filelist.py index 987931a988..619d6338b0 100644 --- a/distutils/filelist.py +++ b/distutils/filelist.py @@ -9,9 +9,9 @@ import fnmatch import functools -from distutils.util import convert_path -from distutils.errors import DistutilsTemplateError, DistutilsInternalError -from distutils import log +from .util import convert_path +from .errors import DistutilsTemplateError, DistutilsInternalError +from . import log class FileList: diff --git a/distutils/msvc9compiler.py b/distutils/msvc9compiler.py index 2202183108..e9f02c1a9e 100644 --- a/distutils/msvc9compiler.py +++ b/distutils/msvc9compiler.py @@ -18,16 +18,16 @@ import re import warnings -from distutils.errors import ( +from .errors import ( DistutilsExecError, DistutilsPlatformError, CompileError, LibError, LinkError, ) -from distutils.ccompiler import CCompiler, gen_lib_options -from distutils import log -from distutils.util import get_platform +from .ccompiler import CCompiler, gen_lib_options +from . import log +from .util import get_platform import winreg diff --git a/distutils/msvccompiler.py b/distutils/msvccompiler.py index 1069e9951a..d15499d7d7 100644 --- a/distutils/msvccompiler.py +++ b/distutils/msvccompiler.py @@ -11,15 +11,15 @@ import sys import os import warnings -from distutils.errors import ( +from .errors import ( DistutilsExecError, DistutilsPlatformError, CompileError, LibError, LinkError, ) -from distutils.ccompiler import CCompiler, gen_lib_options -from distutils import log +from .ccompiler import CCompiler, gen_lib_options +from . import log _can_read_reg = False try: diff --git a/distutils/spawn.py b/distutils/spawn.py index b18ba9db7d..7ae364486b 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -10,9 +10,9 @@ import os import subprocess -from distutils.errors import DistutilsExecError -from distutils.debug import DEBUG -from distutils import log +from .errors import DistutilsExecError +from .debug import DEBUG +from . import log def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): # noqa: C901 diff --git a/distutils/unixccompiler.py b/distutils/unixccompiler.py index 4ab771a475..62e34ef521 100644 --- a/distutils/unixccompiler.py +++ b/distutils/unixccompiler.py @@ -19,11 +19,11 @@ import shlex import itertools -from distutils import sysconfig -from distutils.dep_util import newer -from distutils.ccompiler import CCompiler, gen_preprocess_options, gen_lib_options -from distutils.errors import DistutilsExecError, CompileError, LibError, LinkError -from distutils import log +from . import sysconfig +from .dep_util import newer +from .ccompiler import CCompiler, gen_preprocess_options, gen_lib_options +from .errors import DistutilsExecError, CompileError, LibError, LinkError +from . import log from ._macos_compat import compiler_fixup # XXX Things not currently handled: diff --git a/distutils/util.py b/distutils/util.py index 4763202b67..f18641762c 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -13,10 +13,10 @@ import sysconfig import functools -from distutils.errors import DistutilsPlatformError, DistutilsByteCompileError -from distutils.dep_util import newer -from distutils.spawn import spawn -from distutils import log +from .errors import DistutilsPlatformError, DistutilsByteCompileError +from .dep_util import newer +from .spawn import spawn +from . import log def get_host_platform(): diff --git a/distutils/versionpredicate.py b/distutils/versionpredicate.py index 6ea1192d4c..d6c0c007aa 100644 --- a/distutils/versionpredicate.py +++ b/distutils/versionpredicate.py @@ -1,7 +1,7 @@ """Module for parsing and testing package version predicate strings. """ import re -import distutils.version +from . import version import operator @@ -22,8 +22,8 @@ def splitUp(pred): if not res: raise ValueError("bad package restriction syntax: %r" % pred) comp, verStr = res.groups() - with distutils.version.suppress_known_deprecation(): - other = distutils.version.StrictVersion(verStr) + with version.suppress_known_deprecation(): + other = version.StrictVersion(verStr) return (comp, other) @@ -170,6 +170,6 @@ def split_provision(value): raise ValueError("illegal provides specification: %r" % value) ver = m.group(2) or None if ver: - with distutils.version.suppress_known_deprecation(): - ver = distutils.version.StrictVersion(ver) + with version.suppress_known_deprecation(): + ver = version.StrictVersion(ver) return m.group(1), ver From b9cd85a6f12fe661fc53343335f8857339b1e816 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 16:04:07 -0400 Subject: [PATCH 05/31] No need to save 'threshold' on the instance. --- conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conftest.py b/conftest.py index 76903f5697..65d9727ef1 100644 --- a/conftest.py +++ b/conftest.py @@ -41,7 +41,7 @@ def distutils_logging_silencer(request): from distutils import log self = request.instance - self.threshold = log.set_threshold(log.FATAL) + threshold = log.set_threshold(log.FATAL) # catching warnings # when log will be replaced by logging # we won't need such monkey-patch anymore @@ -52,7 +52,7 @@ def distutils_logging_silencer(request): try: yield finally: - log.set_threshold(self.threshold) + log.set_threshold(threshold) log.Log._log = self._old_log From 886dcdab32cddb839789ce0850504c8877df11b3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 16:10:25 -0400 Subject: [PATCH 06/31] Use monkeypatch to set log and threshold. --- conftest.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/conftest.py b/conftest.py index 65d9727ef1..48ff8000f1 100644 --- a/conftest.py +++ b/conftest.py @@ -37,23 +37,16 @@ def needs_zlib(): @pytest.fixture -def distutils_logging_silencer(request): +def distutils_logging_silencer(request, monkeypatch): from distutils import log self = request.instance - threshold = log.set_threshold(log.FATAL) # catching warnings # when log will be replaced by logging # we won't need such monkey-patch anymore - self._old_log = log.Log._log - log.Log._log = self._log + monkeypatch.setattr(log.Log, '_log', self._log) self.logs = [] - - try: - yield - finally: - log.set_threshold(threshold) - log.Log._log = self._old_log + monkeypatch.setattr(log._global_log, 'threshold', log.FATAL) def _save_cwd(): From 21f2f14c9780afcd47c412999e6f3a8bb44c6ed3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 16:26:35 -0400 Subject: [PATCH 07/31] Extract fixture for capturing logs in _util modules. --- conftest.py | 13 +++++++++++++ distutils/tests/test_dir_util.py | 31 +++++++++++-------------------- distutils/tests/test_file_util.py | 19 +++++-------------- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/conftest.py b/conftest.py index 48ff8000f1..2f568dee99 100644 --- a/conftest.py +++ b/conftest.py @@ -49,6 +49,19 @@ def distutils_logging_silencer(request, monkeypatch): monkeypatch.setattr(log._global_log, 'threshold', log.FATAL) +@pytest.fixture +def logs(monkeypatch): + from distutils import log + + logs = [] + + def save_log(msg, *args): + logs.append(msg % args) + + monkeypatch.setattr(log, 'info', save_log) + return logs + + def _save_cwd(): return path.Path('.') diff --git a/distutils/tests/test_dir_util.py b/distutils/tests/test_dir_util.py index cd7e018f5e..e7c1ebce68 100644 --- a/distutils/tests/test_dir_util.py +++ b/distutils/tests/test_dir_util.py @@ -12,7 +12,6 @@ ensure_relative, ) -from distutils import log from distutils.tests import support import pytest @@ -20,36 +19,28 @@ @pytest.fixture(autouse=True) def stuff(request, monkeypatch, distutils_managed_tempdir): self = request.instance - self._logs = [] tmp_dir = self.mkdtemp() self.root_target = os.path.join(tmp_dir, 'deep') self.target = os.path.join(self.root_target, 'here') self.target2 = os.path.join(tmp_dir, 'deep2') - monkeypatch.setattr(log, 'info', self._log) class TestDirUtil(support.TempdirManager): - def _log(self, msg, *args): - if len(args) > 0: - self._logs.append(msg % args) - else: - self._logs.append(msg) - - def test_mkpath_remove_tree_verbosity(self): + def test_mkpath_remove_tree_verbosity(self, logs): mkpath(self.target, verbose=0) wanted = [] - assert self._logs == wanted + assert logs == wanted remove_tree(self.root_target, verbose=0) mkpath(self.target, verbose=1) wanted = ['creating %s' % self.root_target, 'creating %s' % self.target] - assert self._logs == wanted - self._logs = [] + assert logs == wanted + logs.clear() remove_tree(self.root_target, verbose=1) wanted = ["removing '%s' (and everything under it)" % self.root_target] - assert self._logs == wanted + assert logs == wanted @pytest.mark.skipif("platform.system() == 'Windows'") def test_mkpath_with_custom_mode(self): @@ -61,24 +52,24 @@ def test_mkpath_with_custom_mode(self): mkpath(self.target2, 0o555) assert stat.S_IMODE(os.stat(self.target2).st_mode) == 0o555 & ~umask - def test_create_tree_verbosity(self): + def test_create_tree_verbosity(self, logs): create_tree(self.root_target, ['one', 'two', 'three'], verbose=0) - assert self._logs == [] + assert logs == [] remove_tree(self.root_target, verbose=0) wanted = ['creating %s' % self.root_target] create_tree(self.root_target, ['one', 'two', 'three'], verbose=1) - assert self._logs == wanted + assert logs == wanted remove_tree(self.root_target, verbose=0) - def test_copy_tree_verbosity(self): + def test_copy_tree_verbosity(self, logs): mkpath(self.target, verbose=0) copy_tree(self.target, self.target2, verbose=0) - assert self._logs == [] + assert logs == [] remove_tree(self.root_target, verbose=0) @@ -89,7 +80,7 @@ def test_copy_tree_verbosity(self): wanted = ['copying {} -> {}'.format(a_file, self.target2)] copy_tree(self.target, self.target2, verbose=1) - assert self._logs == wanted + assert logs == wanted remove_tree(self.root_target, verbose=0) remove_tree(self.target2, verbose=0) diff --git a/distutils/tests/test_file_util.py b/distutils/tests/test_file_util.py index b2e83c52f2..0dc68c1832 100644 --- a/distutils/tests/test_file_util.py +++ b/distutils/tests/test_file_util.py @@ -4,7 +4,6 @@ import unittest.mock as mock from distutils.file_util import move_file, copy_file -from distutils import log from distutils.tests import support from distutils.errors import DistutilsFileError from .py38compat import unlink @@ -14,22 +13,14 @@ @pytest.fixture(autouse=True) def stuff(request, monkeypatch, distutils_managed_tempdir): self = request.instance - self._logs = [] tmp_dir = self.mkdtemp() self.source = os.path.join(tmp_dir, 'f1') self.target = os.path.join(tmp_dir, 'f2') self.target_dir = os.path.join(tmp_dir, 'd1') - monkeypatch.setattr(log, 'info', self._log) class TestFileUtil(support.TempdirManager): - def _log(self, msg, *args): - if len(args) > 0: - self._logs.append(msg % args) - else: - self._logs.append(msg) - - def test_move_file_verbosity(self): + def test_move_file_verbosity(self, logs): f = open(self.source, 'w') try: f.write('some content') @@ -38,24 +29,24 @@ def test_move_file_verbosity(self): move_file(self.source, self.target, verbose=0) wanted = [] - assert self._logs == wanted + assert logs == wanted # back to original state move_file(self.target, self.source, verbose=0) move_file(self.source, self.target, verbose=1) wanted = ['moving {} -> {}'.format(self.source, self.target)] - assert self._logs == wanted + assert logs == wanted # back to original state move_file(self.target, self.source, verbose=0) - self._logs = [] + logs.clear() # now the target is a dir os.mkdir(self.target_dir) move_file(self.source, self.target_dir, verbose=1) wanted = ['moving {} -> {}'.format(self.source, self.target_dir)] - assert self._logs == wanted + assert logs == wanted def test_move_file_exception_unpacking_rename(self): # see issue 22182 From a1e48e7feb64e54eb22d490e31ec16348d9b7912 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 17:19:29 -0400 Subject: [PATCH 08/31] Expand 'logs' fixture to support features needed by LoggingSilencer. --- conftest.py | 30 ++++++++++++++++++++++++++---- distutils/tests/test_dir_util.py | 14 +++++++------- distutils/tests/test_file_util.py | 6 +++--- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/conftest.py b/conftest.py index 2f568dee99..731bf0af64 100644 --- a/conftest.py +++ b/conftest.py @@ -49,16 +49,38 @@ def distutils_logging_silencer(request, monkeypatch): monkeypatch.setattr(log._global_log, 'threshold', log.FATAL) +# from jaraco.collections +class Everything: + def __contains__(self, other): + return True + + +class SavedLogs(list): + def render(self, *levels): + return [ + msg % args for level, msg, args in self if level in levels or Everything() + ] + + @pytest.fixture def logs(monkeypatch): from distutils import log - logs = [] + logs = SavedLogs() + log_levels = log.DEBUG, log.INFO, log.WARN, log.ERROR, log.FATAL + + def _log(self, level, msg, args): + self.logs.append((level, msg, args)) - def save_log(msg, *args): - logs.append(msg % args) + def save_log(self, level, msg, args): + if level not in log_levels: + raise ValueError(f'invalid log level {level}') + if not isinstance(msg, str): + raise TypeError(f'msg should be str, not {type(msg).__name__!r}') + logs.append((level, msg, args)) - monkeypatch.setattr(log, 'info', save_log) + monkeypatch.setattr(log.Log, '_log', save_log) + monkeypatch.setattr(log._global_log, 'threshold', log.FATAL) return logs diff --git a/distutils/tests/test_dir_util.py b/distutils/tests/test_dir_util.py index e7c1ebce68..a48be7364f 100644 --- a/distutils/tests/test_dir_util.py +++ b/distutils/tests/test_dir_util.py @@ -30,17 +30,17 @@ def test_mkpath_remove_tree_verbosity(self, logs): mkpath(self.target, verbose=0) wanted = [] - assert logs == wanted + assert logs.render() == wanted remove_tree(self.root_target, verbose=0) mkpath(self.target, verbose=1) wanted = ['creating %s' % self.root_target, 'creating %s' % self.target] - assert logs == wanted + assert logs.render() == wanted logs.clear() remove_tree(self.root_target, verbose=1) wanted = ["removing '%s' (and everything under it)" % self.root_target] - assert logs == wanted + assert logs.render() == wanted @pytest.mark.skipif("platform.system() == 'Windows'") def test_mkpath_with_custom_mode(self): @@ -55,12 +55,12 @@ def test_mkpath_with_custom_mode(self): def test_create_tree_verbosity(self, logs): create_tree(self.root_target, ['one', 'two', 'three'], verbose=0) - assert logs == [] + assert logs.render() == [] remove_tree(self.root_target, verbose=0) wanted = ['creating %s' % self.root_target] create_tree(self.root_target, ['one', 'two', 'three'], verbose=1) - assert logs == wanted + assert logs.render() == wanted remove_tree(self.root_target, verbose=0) @@ -69,7 +69,7 @@ def test_copy_tree_verbosity(self, logs): mkpath(self.target, verbose=0) copy_tree(self.target, self.target2, verbose=0) - assert logs == [] + assert logs.render() == [] remove_tree(self.root_target, verbose=0) @@ -80,7 +80,7 @@ def test_copy_tree_verbosity(self, logs): wanted = ['copying {} -> {}'.format(a_file, self.target2)] copy_tree(self.target, self.target2, verbose=1) - assert logs == wanted + assert logs.render() == wanted remove_tree(self.root_target, verbose=0) remove_tree(self.target2, verbose=0) diff --git a/distutils/tests/test_file_util.py b/distutils/tests/test_file_util.py index 0dc68c1832..8ec56c3ba3 100644 --- a/distutils/tests/test_file_util.py +++ b/distutils/tests/test_file_util.py @@ -29,14 +29,14 @@ def test_move_file_verbosity(self, logs): move_file(self.source, self.target, verbose=0) wanted = [] - assert logs == wanted + assert logs.render() == wanted # back to original state move_file(self.target, self.source, verbose=0) move_file(self.source, self.target, verbose=1) wanted = ['moving {} -> {}'.format(self.source, self.target)] - assert logs == wanted + assert logs.render() == wanted # back to original state move_file(self.target, self.source, verbose=0) @@ -46,7 +46,7 @@ def test_move_file_verbosity(self, logs): os.mkdir(self.target_dir) move_file(self.source, self.target_dir, verbose=1) wanted = ['moving {} -> {}'.format(self.source, self.target_dir)] - assert logs == wanted + assert logs.render() == wanted def test_move_file_exception_unpacking_rename(self): # see issue 22182 From e615a4d0a0002d23127db19c4febc54d6080378e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 17:57:08 -0400 Subject: [PATCH 09/31] Consolidate fixture for capturing logs. Removes LoggingSilencer. --- conftest.py | 15 +------- distutils/tests/support.py | 17 --------- distutils/tests/test_archive_util.py | 2 +- distutils/tests/test_bdist_dumb.py | 1 - distutils/tests/test_bdist_rpm.py | 1 - distutils/tests/test_build.py | 2 +- distutils/tests/test_build_clib.py | 2 +- distutils/tests/test_build_ext.py | 3 +- distutils/tests/test_build_py.py | 6 +-- distutils/tests/test_build_scripts.py | 2 +- distutils/tests/test_check.py | 2 +- distutils/tests/test_clean.py | 2 +- distutils/tests/test_config.py | 5 +-- distutils/tests/test_config_cmd.py | 2 +- distutils/tests/test_dist.py | 5 +-- distutils/tests/test_filelist.py | 49 ++++++++++++------------- distutils/tests/test_install.py | 16 +++----- distutils/tests/test_install_data.py | 1 - distutils/tests/test_install_headers.py | 1 - distutils/tests/test_install_lib.py | 5 +-- distutils/tests/test_install_scripts.py | 2 +- distutils/tests/test_register.py | 9 ++--- distutils/tests/test_sdist.py | 24 ++++++------ distutils/tests/test_spawn.py | 2 +- distutils/tests/test_upload.py | 14 +++---- 25 files changed, 71 insertions(+), 119 deletions(-) diff --git a/conftest.py b/conftest.py index 731bf0af64..7427da7a09 100644 --- a/conftest.py +++ b/conftest.py @@ -36,19 +36,6 @@ def needs_zlib(): pytest.importorskip('zlib') -@pytest.fixture -def distutils_logging_silencer(request, monkeypatch): - from distutils import log - - self = request.instance - # catching warnings - # when log will be replaced by logging - # we won't need such monkey-patch anymore - monkeypatch.setattr(log.Log, '_log', self._log) - self.logs = [] - monkeypatch.setattr(log._global_log, 'threshold', log.FATAL) - - # from jaraco.collections class Everything: def __contains__(self, other): @@ -58,7 +45,7 @@ def __contains__(self, other): class SavedLogs(list): def render(self, *levels): return [ - msg % args for level, msg, args in self if level in levels or Everything() + msg % args for level, msg, args in self if level in (levels or Everything()) ] diff --git a/distutils/tests/support.py b/distutils/tests/support.py index 5203ed19d4..86431539a1 100644 --- a/distutils/tests/support.py +++ b/distutils/tests/support.py @@ -8,26 +8,9 @@ import pytest -from distutils.log import DEBUG, INFO, WARN, ERROR, FATAL from distutils.core import Distribution -@pytest.mark.usefixtures('distutils_logging_silencer') -class LoggingSilencer: - def _log(self, level, msg, args): - if level not in (DEBUG, INFO, WARN, ERROR, FATAL): - raise ValueError('%s wrong log level' % str(level)) - if not isinstance(msg, str): - raise TypeError("msg should be str, not '%.200s'" % (type(msg).__name__)) - self.logs.append((level, msg, args)) - - def get_logs(self, *levels): - return [msg % args for level, msg, args in self.logs if level in levels] - - def clear_logs(self): - self.logs = [] - - @pytest.mark.usefixtures('distutils_managed_tempdir') class TempdirManager: """ diff --git a/distutils/tests/test_archive_util.py b/distutils/tests/test_archive_util.py index d0f5b73481..7778c3ad36 100644 --- a/distutils/tests/test_archive_util.py +++ b/distutils/tests/test_archive_util.py @@ -48,7 +48,7 @@ def same_drive(*paths): return all_equal(pathlib.Path(path).drive for path in paths) -class ArchiveUtilTestCase(support.TempdirManager, support.LoggingSilencer): +class ArchiveUtilTestCase(support.TempdirManager): @pytest.mark.usefixtures('needs_zlib') def test_make_tarball(self, name='archive'): # creating something to tar diff --git a/distutils/tests/test_bdist_dumb.py b/distutils/tests/test_bdist_dumb.py index 8624a4290d..b9bec05137 100644 --- a/distutils/tests/test_bdist_dumb.py +++ b/distutils/tests/test_bdist_dumb.py @@ -26,7 +26,6 @@ @pytest.mark.usefixtures('save_cwd') class TestBuildDumb( support.TempdirManager, - support.LoggingSilencer, ): @pytest.mark.usefixtures('needs_zlib') def test_simple_built(self): diff --git a/distutils/tests/test_bdist_rpm.py b/distutils/tests/test_bdist_rpm.py index 2d14bafc98..4a702fb913 100644 --- a/distutils/tests/test_bdist_rpm.py +++ b/distutils/tests/test_bdist_rpm.py @@ -42,7 +42,6 @@ def sys_executable_encodable(): @pytest.mark.usefixtures('save_cwd') class TestBuildRpm( support.TempdirManager, - support.LoggingSilencer, ): @mac_woes @requires_zlib() diff --git a/distutils/tests/test_build.py b/distutils/tests/test_build.py index 80367607f5..66d8af50ac 100644 --- a/distutils/tests/test_build.py +++ b/distutils/tests/test_build.py @@ -7,7 +7,7 @@ from sysconfig import get_platform -class TestBuild(support.TempdirManager, support.LoggingSilencer): +class TestBuild(support.TempdirManager): def test_finalize_options(self): pkg_dir, dist = self.create_dist() cmd = build(dist) diff --git a/distutils/tests/test_build_clib.py b/distutils/tests/test_build_clib.py index c931c06ec5..709d0b7d66 100644 --- a/distutils/tests/test_build_clib.py +++ b/distutils/tests/test_build_clib.py @@ -10,7 +10,7 @@ from distutils.tests import support -class TestBuildCLib(support.TempdirManager, support.LoggingSilencer): +class TestBuildCLib(support.TempdirManager): def test_check_library_dist(self): pkg_dir, dist = self.create_dist() cmd = build_clib(dist) diff --git a/distutils/tests/test_build_ext.py b/distutils/tests/test_build_ext.py index cf6e98985c..f5058487a5 100644 --- a/distutils/tests/test_build_ext.py +++ b/distutils/tests/test_build_ext.py @@ -18,7 +18,6 @@ from distutils import sysconfig from distutils.tests.support import ( TempdirManager, - LoggingSilencer, copy_xxmodule_c, fixup_build_ext, ) @@ -85,7 +84,7 @@ def extension_redirect(mod, path): @pytest.mark.usefixtures('user_site_dir') -class TestBuildExt(TempdirManager, LoggingSilencer): +class TestBuildExt(TempdirManager): def build_ext(self, *args, **kwargs): return build_ext(*args, **kwargs) diff --git a/distutils/tests/test_build_py.py b/distutils/tests/test_build_py.py index 63543dcaa1..5753818371 100644 --- a/distutils/tests/test_build_py.py +++ b/distutils/tests/test_build_py.py @@ -14,7 +14,7 @@ @support.combine_markers -class TestBuildPy(support.TempdirManager, support.LoggingSilencer): +class TestBuildPy(support.TempdirManager): def test_package_data(self): sources = self.mkdtemp() f = open(os.path.join(sources, "__init__.py"), "w") @@ -151,7 +151,7 @@ def test_dir_in_package_data(self): except DistutilsFileError: self.fail("failed package_data when data dir includes a dir") - def test_dont_write_bytecode(self): + def test_dont_write_bytecode(self, logs): # makes sure byte_compile is not used dist = self.create_dist()[1] cmd = build_py(dist) @@ -165,7 +165,7 @@ def test_dont_write_bytecode(self): finally: sys.dont_write_bytecode = old_dont_write_bytecode - assert 'byte-compiling is disabled' in self.logs[0][1] % self.logs[0][2] + assert 'byte-compiling is disabled' in logs.render()[0] @mock.patch("distutils.command.build_py.log.warn") def test_namespace_package_does_not_warn(self, log_warn): diff --git a/distutils/tests/test_build_scripts.py b/distutils/tests/test_build_scripts.py index 00d7fc5929..1a5753c772 100644 --- a/distutils/tests/test_build_scripts.py +++ b/distutils/tests/test_build_scripts.py @@ -9,7 +9,7 @@ from distutils.tests import support -class TestBuildScripts(support.TempdirManager, support.LoggingSilencer): +class TestBuildScripts(support.TempdirManager): def test_default_settings(self): cmd = self.get_build_scripts_cmd("/foo/bar", []) assert not cmd.force diff --git a/distutils/tests/test_check.py b/distutils/tests/test_check.py index 3e5f6034bf..546540679a 100644 --- a/distutils/tests/test_check.py +++ b/distutils/tests/test_check.py @@ -18,7 +18,7 @@ @support.combine_markers -class TestCheck(support.LoggingSilencer, support.TempdirManager): +class TestCheck(support.TempdirManager): def _run(self, metadata=None, cwd=None, **options): if metadata is None: metadata = {} diff --git a/distutils/tests/test_clean.py b/distutils/tests/test_clean.py index 4166bb7e9b..157b60a1e9 100644 --- a/distutils/tests/test_clean.py +++ b/distutils/tests/test_clean.py @@ -5,7 +5,7 @@ from distutils.tests import support -class TestClean(support.TempdirManager, support.LoggingSilencer): +class TestClean(support.TempdirManager): def test_simple_run(self): pkg_dir, dist = self.create_dist() cmd = clean(dist) diff --git a/distutils/tests/test_config.py b/distutils/tests/test_config.py index 43ba6766ae..cdf73bb95b 100644 --- a/distutils/tests/test_config.py +++ b/distutils/tests/test_config.py @@ -48,10 +48,7 @@ @support.combine_markers @pytest.mark.usefixtures('threshold_warn') @pytest.mark.usefixtures('pypirc') -class BasePyPIRCCommandTestCase( - support.TempdirManager, - support.LoggingSilencer, -): +class BasePyPIRCCommandTestCase(support.TempdirManager): pass diff --git a/distutils/tests/test_config_cmd.py b/distutils/tests/test_config_cmd.py index 65c60f64dd..6d13c24f74 100644 --- a/distutils/tests/test_config_cmd.py +++ b/distutils/tests/test_config_cmd.py @@ -18,7 +18,7 @@ def info_log(request, monkeypatch): @support.combine_markers -class TestConfig(support.LoggingSilencer, support.TempdirManager): +class TestConfig(support.TempdirManager): def _info(self, msg, *args): for line in msg.splitlines(): self._logs.append(line) diff --git a/distutils/tests/test_dist.py b/distutils/tests/test_dist.py index 52e0b3ce26..385007f201 100644 --- a/distutils/tests/test_dist.py +++ b/distutils/tests/test_dist.py @@ -52,10 +52,7 @@ def clear_argv(): @support.combine_markers @pytest.mark.usefixtures('save_env') @pytest.mark.usefixtures('save_argv') -class TestDistributionBehavior( - support.LoggingSilencer, - support.TempdirManager, -): +class TestDistributionBehavior(support.TempdirManager): def create_distribution(self, configfiles=()): d = TestDistribution() d._config_files = configfiles diff --git a/distutils/tests/test_filelist.py b/distutils/tests/test_filelist.py index 7ff9d3e866..74a2a41d4e 100644 --- a/distutils/tests/test_filelist.py +++ b/distutils/tests/test_filelist.py @@ -12,7 +12,6 @@ import pytest import jaraco.path -from distutils.tests import support from . import py38compat as os_helper @@ -37,14 +36,14 @@ def make_local_path(s): return s.replace('/', os.sep) -class TestFileList(support.LoggingSilencer): - def assertNoWarnings(self): - assert self.get_logs(WARN) == [] - self.clear_logs() +class TestFileList: + def assertNoWarnings(self, logs): + assert logs.render(WARN) == [] + logs.clear() - def assertWarnings(self): - assert len(self.get_logs(WARN)) > 0 - self.clear_logs() + def assertWarnings(self, logs): + assert logs.render(WARN) + logs.clear() def test_glob_to_re(self): sep = os.sep @@ -188,7 +187,7 @@ def test_include_pattern(self): file_list.include_pattern('*') assert file_list.allfiles == ['a.py', 'b.txt'] - def test_process_template(self): + def test_process_template(self, logs): mlp = make_local_path # invalid lines file_list = FileList() @@ -212,11 +211,11 @@ def test_process_template(self): file_list.process_template_line('include *.py') assert file_list.files == ['a.py'] - self.assertNoWarnings() + self.assertNoWarnings(logs) file_list.process_template_line('include *.rb') assert file_list.files == ['a.py'] - self.assertWarnings() + self.assertWarnings(logs) # exclude file_list = FileList() @@ -224,11 +223,11 @@ def test_process_template(self): file_list.process_template_line('exclude *.py') assert file_list.files == ['b.txt', mlp('d/c.py')] - self.assertNoWarnings() + self.assertNoWarnings(logs) file_list.process_template_line('exclude *.rb') assert file_list.files == ['b.txt', mlp('d/c.py')] - self.assertWarnings() + self.assertWarnings(logs) # global-include file_list = FileList() @@ -236,11 +235,11 @@ def test_process_template(self): file_list.process_template_line('global-include *.py') assert file_list.files == ['a.py', mlp('d/c.py')] - self.assertNoWarnings() + self.assertNoWarnings(logs) file_list.process_template_line('global-include *.rb') assert file_list.files == ['a.py', mlp('d/c.py')] - self.assertWarnings() + self.assertWarnings(logs) # global-exclude file_list = FileList() @@ -248,11 +247,11 @@ def test_process_template(self): file_list.process_template_line('global-exclude *.py') assert file_list.files == ['b.txt'] - self.assertNoWarnings() + self.assertNoWarnings(logs) file_list.process_template_line('global-exclude *.rb') assert file_list.files == ['b.txt'] - self.assertWarnings() + self.assertWarnings(logs) # recursive-include file_list = FileList() @@ -260,11 +259,11 @@ def test_process_template(self): file_list.process_template_line('recursive-include d *.py') assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] - self.assertNoWarnings() + self.assertNoWarnings(logs) file_list.process_template_line('recursive-include e *.py') assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] - self.assertWarnings() + self.assertWarnings(logs) # recursive-exclude file_list = FileList() @@ -272,11 +271,11 @@ def test_process_template(self): file_list.process_template_line('recursive-exclude d *.py') assert file_list.files == ['a.py', mlp('d/c.txt')] - self.assertNoWarnings() + self.assertNoWarnings(logs) file_list.process_template_line('recursive-exclude e *.py') assert file_list.files == ['a.py', mlp('d/c.txt')] - self.assertWarnings() + self.assertWarnings(logs) # graft file_list = FileList() @@ -284,11 +283,11 @@ def test_process_template(self): file_list.process_template_line('graft d') assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] - self.assertNoWarnings() + self.assertNoWarnings(logs) file_list.process_template_line('graft e') assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] - self.assertWarnings() + self.assertWarnings(logs) # prune file_list = FileList() @@ -296,11 +295,11 @@ def test_process_template(self): file_list.process_template_line('prune d') assert file_list.files == ['a.py', mlp('f/f.py')] - self.assertNoWarnings() + self.assertNoWarnings(logs) file_list.process_template_line('prune e') assert file_list.files == ['a.py', mlp('f/f.py')] - self.assertWarnings() + self.assertWarnings(logs) class TestFindAll: diff --git a/distutils/tests/test_install.py b/distutils/tests/test_install.py index 32a18b2f2f..f597ad3e26 100644 --- a/distutils/tests/test_install.py +++ b/distutils/tests/test_install.py @@ -17,6 +17,7 @@ from distutils.core import Distribution from distutils.errors import DistutilsOptionError from distutils.extension import Extension +from distutils.log import DEBUG from distutils.tests import support from test import support as test_support @@ -30,7 +31,6 @@ def _make_ext_name(modname): @pytest.mark.usefixtures('save_env') class TestInstall( support.TempdirManager, - support.LoggingSilencer, ): @pytest.mark.xfail( 'platform.system() == "Windows" and sys.version_info > (3, 11)', @@ -246,13 +246,9 @@ def test_record_extensions(self): ] assert found == expected - def test_debug_mode(self): + def test_debug_mode(self, logs, monkeypatch): # this covers the code called when DEBUG is set - old_logs_len = len(self.logs) - install_module.DEBUG = True - try: - with captured_stdout(): - self.test_record() - finally: - install_module.DEBUG = False - assert len(self.logs) > old_logs_len + monkeypatch.setattr(install_module, 'DEBUG', True) + with captured_stdout(): + self.test_record() + assert logs.render(DEBUG) diff --git a/distutils/tests/test_install_data.py b/distutils/tests/test_install_data.py index f77c790fca..9badbc264f 100644 --- a/distutils/tests/test_install_data.py +++ b/distutils/tests/test_install_data.py @@ -10,7 +10,6 @@ @pytest.mark.usefixtures('save_env') class TestInstallData( support.TempdirManager, - support.LoggingSilencer, ): def test_simple_run(self): pkg_dir, dist = self.create_dist() diff --git a/distutils/tests/test_install_headers.py b/distutils/tests/test_install_headers.py index 7594f5af3c..1e8ccf7991 100644 --- a/distutils/tests/test_install_headers.py +++ b/distutils/tests/test_install_headers.py @@ -10,7 +10,6 @@ @pytest.mark.usefixtures('save_env') class TestInstallHeaders( support.TempdirManager, - support.LoggingSilencer, ): def test_simple_run(self): # we have two headers diff --git a/distutils/tests/test_install_lib.py b/distutils/tests/test_install_lib.py index a654a66a79..cdf3fc977e 100644 --- a/distutils/tests/test_install_lib.py +++ b/distutils/tests/test_install_lib.py @@ -15,7 +15,6 @@ @pytest.mark.usefixtures('save_env') class TestInstallLib( support.TempdirManager, - support.LoggingSilencer, ): def test_finalize_options(self): dist = self.create_dist()[1] @@ -94,7 +93,7 @@ def test_get_inputs(self): inputs = cmd.get_inputs() assert len(inputs) == 2, inputs - def test_dont_write_bytecode(self): + def test_dont_write_bytecode(self, logs): # makes sure byte_compile is not used dist = self.create_dist()[1] cmd = install_lib(dist) @@ -108,4 +107,4 @@ def test_dont_write_bytecode(self): finally: sys.dont_write_bytecode = old_dont_write_bytecode - assert 'byte-compiling is disabled' in self.logs[0][1] % self.logs[0][2] + assert 'byte-compiling is disabled' in logs.render()[0] diff --git a/distutils/tests/test_install_scripts.py b/distutils/tests/test_install_scripts.py index 0d17f11b5b..58313f2864 100644 --- a/distutils/tests/test_install_scripts.py +++ b/distutils/tests/test_install_scripts.py @@ -8,7 +8,7 @@ from distutils.tests import support -class TestInstallScripts(support.TempdirManager, support.LoggingSilencer): +class TestInstallScripts(support.TempdirManager): def test_default_settings(self): dist = Distribution() dist.command_obj["build"] = support.DummyCommand(build_scripts="/foo/bar") diff --git a/distutils/tests/test_register.py b/distutils/tests/test_register.py index 0a5765f1fd..d0b4cc7cc1 100644 --- a/distutils/tests/test_register.py +++ b/distutils/tests/test_register.py @@ -303,14 +303,13 @@ def test_register_invalid_long_description(self, monkeypatch): with pytest.raises(DistutilsSetupError): cmd.run() - def test_list_classifiers(self): + def test_list_classifiers(self, logs): cmd = self._get_cmd() cmd.list_classifiers = 1 cmd.run() - results = self.get_logs(INFO) - assert results == ['running check', 'xxx'] + assert logs.render(INFO) == ['running check', 'xxx'] - def test_show_response(self): + def test_show_response(self, logs): # test that the --show-response option return a well formatted response cmd = self._get_cmd() inputs = Inputs('1', 'tarek', 'y') @@ -321,5 +320,5 @@ def test_show_response(self): finally: del register_module.input - results = self.get_logs(INFO) + results = logs.render(INFO) assert results[3] == 75 * '-' + '\nxxx\n' + 75 * '-' diff --git a/distutils/tests/test_sdist.py b/distutils/tests/test_sdist.py index b11fe7c41e..da36bfcf01 100644 --- a/distutils/tests/test_sdist.py +++ b/distutils/tests/test_sdist.py @@ -253,7 +253,7 @@ def test_add_defaults(self): assert manifest == MANIFEST % {'sep': os.sep} @pytest.mark.usefixtures('needs_zlib') - def test_metadata_check_option(self): + def test_metadata_check_option(self, logs): # testing the `medata-check` option dist, cmd = self.get_cmd(metadata={}) @@ -262,18 +262,18 @@ def test_metadata_check_option(self): cmd.ensure_finalized() cmd.run() warnings = [ - msg for msg in self.get_logs(WARN) if msg.startswith('warning: check:') + msg for msg in logs.render(WARN) if msg.startswith('warning: check:') ] assert len(warnings) == 1 # trying with a complete set of metadata - self.clear_logs() + logs.clear() dist, cmd = self.get_cmd() cmd.ensure_finalized() cmd.metadata_check = 0 cmd.run() warnings = [ - msg for msg in self.get_logs(WARN) if msg.startswith('warning: check:') + msg for msg in logs.render(WARN) if msg.startswith('warning: check:') ] assert len(warnings) == 0 @@ -323,28 +323,28 @@ def test_finalize_options(self): # the following tests make sure there is a nice error message instead # of a traceback when parsing an invalid manifest template - def _check_template(self, content): + def _check_template(self, content, logs): dist, cmd = self.get_cmd() os.chdir(self.tmp_dir) self.write_file('MANIFEST.in', content) cmd.ensure_finalized() cmd.filelist = FileList() cmd.read_template() - warnings = self.get_logs(WARN) + warnings = logs.render(WARN) assert len(warnings) == 1 - def test_invalid_template_unknown_command(self): - self._check_template('taunt knights *') + def test_invalid_template_unknown_command(self, logs): + self._check_template('taunt knights *', logs) - def test_invalid_template_wrong_arguments(self): + def test_invalid_template_wrong_arguments(self, logs): # this manifest command takes one argument - self._check_template('prune') + self._check_template('prune', logs) @pytest.mark.skipif("platform.system() != 'Windows'") - def test_invalid_template_wrong_path(self): + def test_invalid_template_wrong_path(self, logs): # on Windows, trailing slashes are not allowed # this used to crash instead of raising a warning: #8286 - self._check_template('include examples/') + self._check_template('include examples/', logs) @pytest.mark.usefixtures('needs_zlib') def test_get_file_list(self): diff --git a/distutils/tests/test_spawn.py b/distutils/tests/test_spawn.py index 5da499777a..08a34ee2b8 100644 --- a/distutils/tests/test_spawn.py +++ b/distutils/tests/test_spawn.py @@ -17,7 +17,7 @@ import pytest -class TestSpawn(support.TempdirManager, support.LoggingSilencer): +class TestSpawn(support.TempdirManager): @pytest.mark.skipif("os.name not in ('nt', 'posix')") def test_spawn(self): tmpdir = self.mkdtemp() diff --git a/distutils/tests/test_upload.py b/distutils/tests/test_upload.py index fb905b641a..efd9e90617 100644 --- a/distutils/tests/test_upload.py +++ b/distutils/tests/test_upload.py @@ -109,7 +109,7 @@ def test_saved_password(self): cmd.finalize_options() assert cmd.password == 'xxx' - def test_upload(self): + def test_upload(self, logs): tmp = self.mkdtemp() path = os.path.join(tmp, 'xxx') self.write_file(path) @@ -150,7 +150,7 @@ def test_upload(self): ) # The PyPI response body was echoed - results = self.get_logs(INFO) + results = logs.render(INFO) assert results[-1] == 75 * '-' + '\nxyzzy\n' + 75 * '-' # bpo-32304: archives whose last byte was b'\r' were corrupted due to @@ -178,11 +178,11 @@ def test_upload_correct_cr(self): assert int(headers['Content-length']) >= 2172 assert b'long description\r' in self.last_open.req.data - def test_upload_fails(self): + def test_upload_fails(self, logs): self.next_msg = "Not Found" self.next_code = 404 with pytest.raises(DistutilsError): - self.test_upload() + self.test_upload(logs) @pytest.mark.parametrize( 'exception,expected,raised_exception', @@ -196,7 +196,7 @@ def test_upload_fails(self): ), ], ) - def test_wrong_exception_order(self, exception, expected, raised_exception): + def test_wrong_exception_order(self, exception, expected, raised_exception, logs): tmp = self.mkdtemp() path = os.path.join(tmp, 'xxx') self.write_file(path) @@ -213,6 +213,6 @@ def test_wrong_exception_order(self, exception, expected, raised_exception): cmd = upload(dist) cmd.ensure_finalized() cmd.run() - results = self.get_logs(ERROR) + results = logs.render(ERROR) assert expected in results[-1] - self.clear_logs() + logs.clear() From ce8692d32aaab57f2c801dfa9ccedd59fb0854c3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 21:23:42 -0400 Subject: [PATCH 10/31] Prefer capsys to test.support.captured* --- distutils/tests/test_cmd.py | 21 ++++++--------------- distutils/tests/test_core.py | 21 ++++++--------------- distutils/tests/test_dist.py | 31 ++++++++++++++----------------- distutils/tests/test_filelist.py | 21 +++++++-------------- distutils/tests/test_install.py | 5 +---- distutils/tests/test_sdist.py | 8 +++----- 6 files changed, 37 insertions(+), 70 deletions(-) diff --git a/distutils/tests/test_cmd.py b/distutils/tests/test_cmd.py index e4d5bf3c01..3aac448d5e 100644 --- a/distutils/tests/test_cmd.py +++ b/distutils/tests/test_cmd.py @@ -1,6 +1,5 @@ """Tests for distutils.cmd.""" import os -from test.support import captured_stdout from distutils.cmd import Command from distutils.dist import Distribution @@ -100,17 +99,9 @@ def test_ensure_dirname(self, cmd): with pytest.raises(DistutilsOptionError): cmd.ensure_dirname('option2') - def test_debug_print(self, cmd): - with captured_stdout() as stdout: - cmd.debug_print('xxx') - stdout.seek(0) - assert stdout.read() == '' - - debug.DEBUG = True - try: - with captured_stdout() as stdout: - cmd.debug_print('xxx') - stdout.seek(0) - assert stdout.read() == 'xxx\n' - finally: - debug.DEBUG = False + def test_debug_print(self, cmd, capsys, monkeypatch): + cmd.debug_print('xxx') + assert capsys.readouterr().out == '' + monkeypatch.setattr(debug, 'DEBUG', True) + cmd.debug_print('xxx') + assert capsys.readouterr().out == 'xxx\n' diff --git a/distutils/tests/test_core.py b/distutils/tests/test_core.py index 5fe7e958f5..2c11ff769e 100644 --- a/distutils/tests/test_core.py +++ b/distutils/tests/test_core.py @@ -4,7 +4,6 @@ import distutils.core import os import sys -from test.support import captured_stdout import pytest @@ -121,20 +120,12 @@ def test_run_commands(self, temp_file): distutils.core.run_commands(dist) assert 'build' in dist.have_run - def test_debug_mode(self): + def test_debug_mode(self, capsys, monkeypatch): # this covers the code called when DEBUG is set sys.argv = ['setup.py', '--name'] - with captured_stdout() as stdout: - distutils.core.setup(name='bar') - stdout.seek(0) - assert stdout.read() == 'bar\n' - - distutils.core.DEBUG = True - try: - with captured_stdout() as stdout: - distutils.core.setup(name='bar') - finally: - distutils.core.DEBUG = False - stdout.seek(0) + distutils.core.setup(name='bar') + capsys.readouterr().out == 'bar\n' + monkeypatch.setattr(distutils.core, 'DEBUG', True) + distutils.core.setup(name='bar') wanted = "options (after parsing config files):\n" - assert stdout.readlines()[0] == wanted + assert capsys.readouterr().out.startswith(wanted) diff --git a/distutils/tests/test_dist.py b/distutils/tests/test_dist.py index 385007f201..34d75d73b3 100644 --- a/distutils/tests/test_dist.py +++ b/distutils/tests/test_dist.py @@ -13,7 +13,6 @@ from distutils.dist import Distribution, fix_help_options from distutils.cmd import Command -from test.support import captured_stdout, captured_stderr from distutils.tests import support from distutils import log @@ -370,16 +369,15 @@ def test_classifier(self): meta = self.format_metadata(dist) assert 'Metadata-Version: 1.1' in meta - def test_classifier_invalid_type(self): + def test_classifier_invalid_type(self, capsys): attrs = { 'name': 'Boa', 'version': '3.0', 'classifiers': ('Programming Language :: Python :: 3',), } - with captured_stderr() as error: - d = Distribution(attrs) + d = Distribution(attrs) # should have warning about passing a non-list - assert 'should be a list' in error.getvalue() + assert 'should be a list' in capsys.readouterr().err # should be converted to a list assert isinstance(d.metadata.classifiers, list) assert d.metadata.classifiers == list(attrs['classifiers']) @@ -393,16 +391,15 @@ def test_keywords(self): dist = Distribution(attrs) assert dist.get_keywords() == ['spam', 'eggs', 'life of brian'] - def test_keywords_invalid_type(self): + def test_keywords_invalid_type(self, capsys): attrs = { 'name': 'Monty', 'version': '1.0', 'keywords': ('spam', 'eggs', 'life of brian'), } - with captured_stderr() as error: - d = Distribution(attrs) + d = Distribution(attrs) # should have warning about passing a non-list - assert 'should be a list' in error.getvalue() + assert 'should be a list' in capsys.readouterr().err # should be converted to a list assert isinstance(d.metadata.keywords, list) assert d.metadata.keywords == list(attrs['keywords']) @@ -416,16 +413,15 @@ def test_platforms(self): dist = Distribution(attrs) assert dist.get_platforms() == ['GNU/Linux', 'Some Evil Platform'] - def test_platforms_invalid_types(self): + def test_platforms_invalid_types(self, capsys): attrs = { 'name': 'Monty', 'version': '1.0', 'platforms': ('GNU/Linux', 'Some Evil Platform'), } - with captured_stderr() as error: - d = Distribution(attrs) + d = Distribution(attrs) # should have warning about passing a non-list - assert 'should be a list' in error.getvalue() + assert 'should be a list' in capsys.readouterr().err # should be converted to a list assert isinstance(d.metadata.platforms, list) assert d.metadata.platforms == list(attrs['platforms']) @@ -476,7 +472,7 @@ def test_fix_help_options(self): assert fancy_options[0] == ('a', 'b', 'c') assert fancy_options[1] == (1, 2, 3) - def test_show_help(self, request): + def test_show_help(self, request, capsys): # smoke test, just makes sure some help is displayed reset_log = functools.partial(log.set_threshold, log._global_log.threshold) request.addfinalizer(reset_log) @@ -484,10 +480,11 @@ def test_show_help(self, request): sys.argv = [] dist.help = 1 dist.script_name = 'setup.py' - with captured_stdout() as s: - dist.parse_command_line() + dist.parse_command_line() - output = [line for line in s.getvalue().split('\n') if line.strip() != ''] + output = [ + line for line in capsys.readouterr().out.split('\n') if line.strip() != '' + ] assert output def test_read_metadata(self): diff --git a/distutils/tests/test_filelist.py b/distutils/tests/test_filelist.py index 74a2a41d4e..ed68df32b0 100644 --- a/distutils/tests/test_filelist.py +++ b/distutils/tests/test_filelist.py @@ -7,8 +7,6 @@ from distutils.filelist import glob_to_re, translate_pattern, FileList from distutils import filelist -from test.support import captured_stdout - import pytest import jaraco.path @@ -109,19 +107,14 @@ def test_process_template_line(self): assert file_list.files == wanted - def test_debug_print(self): + def test_debug_print(self, capsys, monkeypatch): file_list = FileList() - with captured_stdout() as stdout: - file_list.debug_print('xxx') - assert stdout.getvalue() == '' - - debug.DEBUG = True - try: - with captured_stdout() as stdout: - file_list.debug_print('xxx') - assert stdout.getvalue() == 'xxx\n' - finally: - debug.DEBUG = False + file_list.debug_print('xxx') + assert capsys.readouterr().out == '' + + monkeypatch.setattr(debug, 'DEBUG', True) + file_list.debug_print('xxx') + assert capsys.readouterr().out == 'xxx\n' def test_set_allfiles(self): file_list = FileList() diff --git a/distutils/tests/test_install.py b/distutils/tests/test_install.py index f597ad3e26..e240b156f6 100644 --- a/distutils/tests/test_install.py +++ b/distutils/tests/test_install.py @@ -5,8 +5,6 @@ import site import pathlib -from test.support import captured_stdout - import pytest from distutils import sysconfig @@ -249,6 +247,5 @@ def test_record_extensions(self): def test_debug_mode(self, logs, monkeypatch): # this covers the code called when DEBUG is set monkeypatch.setattr(install_module, 'DEBUG', True) - with captured_stdout(): - self.test_record() + self.test_record() assert logs.render(DEBUG) diff --git a/distutils/tests/test_sdist.py b/distutils/tests/test_sdist.py index da36bfcf01..bc535f38a8 100644 --- a/distutils/tests/test_sdist.py +++ b/distutils/tests/test_sdist.py @@ -5,7 +5,6 @@ import zipfile from os.path import join from textwrap import dedent -from test.support import captured_stdout from .unix_compat import require_unix_id, require_uid_0, pwd, grp import pytest @@ -285,15 +284,14 @@ def test_check_metadata_deprecated(self): cmd.check_metadata() assert len(w.warnings) == 1 - def test_show_formats(self): - with captured_stdout() as stdout: - show_formats() + def test_show_formats(self, capsys): + show_formats() # the output should be a header line + one line per format num_formats = len(ARCHIVE_FORMATS.keys()) output = [ line - for line in stdout.getvalue().split('\n') + for line in capsys.readouterr().out.split('\n') if line.strip().startswith('--formats=') ] assert len(output) == num_formats From d0bfcdb416345dbeed1a72ba182e33acf9511f6c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Oct 2022 21:26:11 -0400 Subject: [PATCH 11/31] Fix broken test --- distutils/tests/test_dist.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/distutils/tests/test_dist.py b/distutils/tests/test_dist.py index 34d75d73b3..6726d50648 100644 --- a/distutils/tests/test_dist.py +++ b/distutils/tests/test_dist.py @@ -236,10 +236,8 @@ def test_get_command_packages(self): def test_announce(self): # make sure the level is known dist = Distribution() - args = ('ok',) - kwargs = {'level': 'ok2'} with pytest.raises(ValueError): - dist.announce(args, kwargs) + dist.announce('ok', level='ok2') def test_find_config_files_disable(self, temp_home): # Ticket #1180: Allow user to disable their home config file. From 45295fc5dc2b85a4a0d19b92429d4779db87ba79 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 5 Oct 2022 03:49:34 -0400 Subject: [PATCH 12/31] Prefer caplog to mocking the logging interface. --- distutils/tests/test_build_py.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/distutils/tests/test_build_py.py b/distutils/tests/test_build_py.py index 5753818371..e5f4320cae 100644 --- a/distutils/tests/test_build_py.py +++ b/distutils/tests/test_build_py.py @@ -2,7 +2,6 @@ import os import sys -import unittest.mock as mock import pytest @@ -167,8 +166,7 @@ def test_dont_write_bytecode(self, logs): assert 'byte-compiling is disabled' in logs.render()[0] - @mock.patch("distutils.command.build_py.log.warn") - def test_namespace_package_does_not_warn(self, log_warn): + def test_namespace_package_does_not_warn(self, caplog): """ Originally distutils implementation did not account for PEP 420 and included warns for package directories that did not contain @@ -182,13 +180,6 @@ def test_namespace_package_does_not_warn(self, log_warn): os.makedirs("ns/pkg") open("ns/pkg/module.py", "w").close() - # Set up a trap if the undesirable effect is observed: - def _trap(msg, *args): - if "package init file" in msg and "not found" in msg: - raise AssertionError(f"Undesired warning: {msg!r} {args!r}") - - log_warn.side_effect = _trap - # Configure the package: attrs = { "name": "ns.pkg", @@ -206,4 +197,7 @@ def _trap(msg, *args): assert module_path.replace(os.sep, "/") == "ns/pkg/module.py" cmd.run() - # Test should complete successfully with no exception + + assert not any( + "package init file" in msg and "not found" in msg for msg in caplog.messages + ) From 27217adb579fd1a859a569ef03318872a750bfc1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 03:24:28 -0400 Subject: [PATCH 13/31] or maybe not --- distutils/cygwinccompiler.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 8019f6a8a3..c88e2422bc 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -280,10 +280,6 @@ def __init__(self, verbose=0, dry_run=0, force=0): linker_so='{} {}'.format(self.linker_dll, shared_option), ) - # Maybe we should also append -mthreads, but then the finished - # dlls need another dll (mingwm10.dll see Mingw32 docs) - # (-mthreads: Support thread-safe exception handling on `Mingw32') - # no additional libraries needed self.dll_libraries = [] From 060fec6444011bc74ced48a4fefb109182e32d46 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 03:26:26 -0400 Subject: [PATCH 14/31] Remove setting of dll_libraries in Mingw32CCompiler. One call superseded the other, and the result was redundant with what happens in the parent call. --- distutils/cygwinccompiler.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index c88e2422bc..0568dec9cd 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -280,13 +280,6 @@ def __init__(self, verbose=0, dry_run=0, force=0): linker_so='{} {}'.format(self.linker_dll, shared_option), ) - # no additional libraries needed - self.dll_libraries = [] - - # Include the appropriate MSVC runtime library if Python was built - # with MSVC 7.0 or later. - self.dll_libraries = get_msvcr() - def runtime_library_dir_option(self, dir): raise DistutilsPlatformError(_runtime_library_dirs_msg) From ff69195b9b05de58f3fc4ff956bbe5dad31be444 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 03:27:30 -0400 Subject: [PATCH 15/31] Short circuit when MSC version is not found. --- distutils/cygwinccompiler.py | 58 +++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 0568dec9cd..a23db4e187 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -29,34 +29,36 @@ def get_msvcr(): with MSVC 7.0 or later. """ msc_pos = sys.version.find('MSC v.') - if msc_pos != -1: - msc_ver = sys.version[msc_pos + 6 : msc_pos + 10] - if msc_ver == '1300': - # MSVC 7.0 - return ['msvcr70'] - elif msc_ver == '1310': - # MSVC 7.1 - return ['msvcr71'] - elif msc_ver == '1400': - # VS2005 / MSVC 8.0 - return ['msvcr80'] - elif msc_ver == '1500': - # VS2008 / MSVC 9.0 - return ['msvcr90'] - elif msc_ver == '1600': - # VS2010 / MSVC 10.0 - return ['msvcr100'] - elif msc_ver == '1700': - # VS2012 / MSVC 11.0 - return ['msvcr110'] - elif msc_ver == '1800': - # VS2013 / MSVC 12.0 - return ['msvcr120'] - elif 1900 <= int(msc_ver) < 2000: - # VS2015 / MSVC 14.0 - return ['ucrt', 'vcruntime140'] - else: - raise ValueError("Unknown MS Compiler version %s " % msc_ver) + if msc_pos == -1: + return + + msc_ver = sys.version[msc_pos + 6 : msc_pos + 10] + if msc_ver == '1300': + # MSVC 7.0 + return ['msvcr70'] + elif msc_ver == '1310': + # MSVC 7.1 + return ['msvcr71'] + elif msc_ver == '1400': + # VS2005 / MSVC 8.0 + return ['msvcr80'] + elif msc_ver == '1500': + # VS2008 / MSVC 9.0 + return ['msvcr90'] + elif msc_ver == '1600': + # VS2010 / MSVC 10.0 + return ['msvcr100'] + elif msc_ver == '1700': + # VS2012 / MSVC 11.0 + return ['msvcr110'] + elif msc_ver == '1800': + # VS2013 / MSVC 12.0 + return ['msvcr120'] + elif 1900 <= int(msc_ver) < 2000: + # VS2015 / MSVC 14.0 + return ['ucrt', 'vcruntime140'] + else: + raise ValueError("Unknown MS Compiler version %s " % msc_ver) _runtime_library_dirs_msg = ( From 855475e32cd41310774d8ebe90fd7a1a08ddddbc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 03:31:22 -0400 Subject: [PATCH 16/31] Prefer partition to find. --- distutils/cygwinccompiler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index a23db4e187..2d3fd638ea 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -28,11 +28,11 @@ def get_msvcr(): """Include the appropriate MSVC runtime library if Python was built with MSVC 7.0 or later. """ - msc_pos = sys.version.find('MSC v.') - if msc_pos == -1: + _, _, rest = sys.version.partition('MSC v.') + if not rest: return - msc_ver = sys.version[msc_pos + 6 : msc_pos + 10] + msc_ver = rest[:4] if msc_ver == '1300': # MSVC 7.0 return ['msvcr70'] From 3868aadc7a651fecaed062d778082d583caf7cf7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 03:35:09 -0400 Subject: [PATCH 17/31] Work with ints uniformally. --- distutils/cygwinccompiler.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 2d3fd638ea..5908d104b5 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -32,29 +32,29 @@ def get_msvcr(): if not rest: return - msc_ver = rest[:4] - if msc_ver == '1300': + msc_ver = int(rest[:4]) + if msc_ver == 1300: # MSVC 7.0 return ['msvcr70'] - elif msc_ver == '1310': + elif msc_ver == 1310: # MSVC 7.1 return ['msvcr71'] - elif msc_ver == '1400': + elif msc_ver == 1400: # VS2005 / MSVC 8.0 return ['msvcr80'] - elif msc_ver == '1500': + elif msc_ver == 1500: # VS2008 / MSVC 9.0 return ['msvcr90'] - elif msc_ver == '1600': + elif msc_ver == 1600: # VS2010 / MSVC 10.0 return ['msvcr100'] - elif msc_ver == '1700': + elif msc_ver == 1700: # VS2012 / MSVC 11.0 return ['msvcr110'] - elif msc_ver == '1800': + elif msc_ver == 1800: # VS2013 / MSVC 12.0 return ['msvcr120'] - elif 1900 <= int(msc_ver) < 2000: + elif 1900 <= msc_ver < 2000: # VS2015 / MSVC 14.0 return ['ucrt', 'vcruntime140'] else: From 347eba074610c04c46e8e5fee6598bf992031ca3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 03:38:42 -0400 Subject: [PATCH 18/31] Replace if/else with a lookup. --- distutils/cygwinccompiler.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 5908d104b5..fbcf28f479 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -32,33 +32,30 @@ def get_msvcr(): if not rest: return - msc_ver = int(rest[:4]) - if msc_ver == 1300: + lookup = { # MSVC 7.0 - return ['msvcr70'] - elif msc_ver == 1310: + 1300: ['msvcr70'], # MSVC 7.1 - return ['msvcr71'] - elif msc_ver == 1400: + 1310: ['msvcr71'], # VS2005 / MSVC 8.0 - return ['msvcr80'] - elif msc_ver == 1500: + 1400: ['msvcr80'], # VS2008 / MSVC 9.0 - return ['msvcr90'] - elif msc_ver == 1600: + 1500: ['msvcr90'], # VS2010 / MSVC 10.0 - return ['msvcr100'] - elif msc_ver == 1700: + 1600: ['msvcr100'], # VS2012 / MSVC 11.0 - return ['msvcr110'] - elif msc_ver == 1800: + 1700: ['msvcr110'], # VS2013 / MSVC 12.0 - return ['msvcr120'] - elif 1900 <= msc_ver < 2000: + 1800: ['msvcr120'], + } + msc_ver = int(rest[:4]) + if 1900 <= msc_ver < 2000: # VS2015 / MSVC 14.0 return ['ucrt', 'vcruntime140'] - else: - raise ValueError("Unknown MS Compiler version %s " % msc_ver) + if msc_ver in lookup: + return lookup[msc_ver] + + raise ValueError("Unknown MS Compiler version %s " % msc_ver) _runtime_library_dirs_msg = ( From a8ebe3ffef3e7dacbe7cb0c9440c53c19e742051 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 03:39:46 -0400 Subject: [PATCH 19/31] Move lookup out of the function --- distutils/cygwinccompiler.py | 38 +++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index fbcf28f479..8321d90537 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -24,6 +24,24 @@ from .version import LooseVersion, suppress_known_deprecation +_msvcr_lookup = { + # MSVC 7.0 + 1300: ['msvcr70'], + # MSVC 7.1 + 1310: ['msvcr71'], + # VS2005 / MSVC 8.0 + 1400: ['msvcr80'], + # VS2008 / MSVC 9.0 + 1500: ['msvcr90'], + # VS2010 / MSVC 10.0 + 1600: ['msvcr100'], + # VS2012 / MSVC 11.0 + 1700: ['msvcr110'], + # VS2013 / MSVC 12.0 + 1800: ['msvcr120'], +} + + def get_msvcr(): """Include the appropriate MSVC runtime library if Python was built with MSVC 7.0 or later. @@ -32,28 +50,12 @@ def get_msvcr(): if not rest: return - lookup = { - # MSVC 7.0 - 1300: ['msvcr70'], - # MSVC 7.1 - 1310: ['msvcr71'], - # VS2005 / MSVC 8.0 - 1400: ['msvcr80'], - # VS2008 / MSVC 9.0 - 1500: ['msvcr90'], - # VS2010 / MSVC 10.0 - 1600: ['msvcr100'], - # VS2012 / MSVC 11.0 - 1700: ['msvcr110'], - # VS2013 / MSVC 12.0 - 1800: ['msvcr120'], - } msc_ver = int(rest[:4]) if 1900 <= msc_ver < 2000: # VS2015 / MSVC 14.0 return ['ucrt', 'vcruntime140'] - if msc_ver in lookup: - return lookup[msc_ver] + if msc_ver in _msvcr_lookup: + return _msvcr_lookup[msc_ver] raise ValueError("Unknown MS Compiler version %s " % msc_ver) From 15266db44d85a9a40201f2b35c8d7079e5a96df7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 03:41:59 -0400 Subject: [PATCH 20/31] Prefer regex search to string manipulation. --- distutils/cygwinccompiler.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 8321d90537..9332db1d81 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -7,6 +7,7 @@ """ import os +import re import sys import copy import shlex @@ -46,11 +47,11 @@ def get_msvcr(): """Include the appropriate MSVC runtime library if Python was built with MSVC 7.0 or later. """ - _, _, rest = sys.version.partition('MSC v.') - if not rest: + match = re.search(r'MSC v\.(\d{4})', sys.version) + if not match: return - msc_ver = int(rest[:4]) + msc_ver = int(match.group(1)) if 1900 <= msc_ver < 2000: # VS2015 / MSVC 14.0 return ['ucrt', 'vcruntime140'] From c5f0b2714691a83f868b7d8d69ea3de6a82bc797 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 10:25:24 -0400 Subject: [PATCH 21/31] Use RangeMap to define the ranges in one place. --- distutils/_collections.py | 115 +++++++++++++++++++++++++++++++++++ distutils/cygwinccompiler.py | 52 ++++++++-------- 2 files changed, 143 insertions(+), 24 deletions(-) diff --git a/distutils/_collections.py b/distutils/_collections.py index 98fce8008d..870556358d 100644 --- a/distutils/_collections.py +++ b/distutils/_collections.py @@ -1,5 +1,7 @@ import collections +import functools import itertools +import operator # from jaraco.collections 3.5.1 @@ -54,3 +56,116 @@ def __contains__(self, other): def __len__(self): return len(list(iter(self))) + + +# from jaraco.collections 3.7 +class RangeMap(dict): + """ + A dictionary-like object that uses the keys as bounds for a range. + Inclusion of the value for that range is determined by the + key_match_comparator, which defaults to less-than-or-equal. + A value is returned for a key if it is the first key that matches in + the sorted list of keys. + One may supply keyword parameters to be passed to the sort function used + to sort keys (i.e. key, reverse) as sort_params. + Let's create a map that maps 1-3 -> 'a', 4-6 -> 'b' + >>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy + >>> r[1], r[2], r[3], r[4], r[5], r[6] + ('a', 'a', 'a', 'b', 'b', 'b') + Even float values should work so long as the comparison operator + supports it. + >>> r[4.5] + 'b' + But you'll notice that the way rangemap is defined, it must be open-ended + on one side. + >>> r[0] + 'a' + >>> r[-1] + 'a' + One can close the open-end of the RangeMap by using undefined_value + >>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'}) + >>> r[0] + Traceback (most recent call last): + ... + KeyError: 0 + One can get the first or last elements in the range by using RangeMap.Item + >>> last_item = RangeMap.Item(-1) + >>> r[last_item] + 'b' + .last_item is a shortcut for Item(-1) + >>> r[RangeMap.last_item] + 'b' + Sometimes it's useful to find the bounds for a RangeMap + >>> r.bounds() + (0, 6) + RangeMap supports .get(key, default) + >>> r.get(0, 'not found') + 'not found' + >>> r.get(7, 'not found') + 'not found' + One often wishes to define the ranges by their left-most values, + which requires use of sort params and a key_match_comparator. + >>> r = RangeMap({1: 'a', 4: 'b'}, + ... sort_params=dict(reverse=True), + ... key_match_comparator=operator.ge) + >>> r[1], r[2], r[3], r[4], r[5], r[6] + ('a', 'a', 'a', 'b', 'b', 'b') + That wasn't nearly as easy as before, so an alternate constructor + is provided: + >>> r = RangeMap.left({1: 'a', 4: 'b', 7: RangeMap.undefined_value}) + >>> r[1], r[2], r[3], r[4], r[5], r[6] + ('a', 'a', 'a', 'b', 'b', 'b') + """ + + def __init__(self, source, sort_params={}, key_match_comparator=operator.le): + dict.__init__(self, source) + self.sort_params = sort_params + self.match = key_match_comparator + + @classmethod + def left(cls, source): + return cls( + source, sort_params=dict(reverse=True), key_match_comparator=operator.ge + ) + + def __getitem__(self, item): + sorted_keys = sorted(self.keys(), **self.sort_params) + if isinstance(item, RangeMap.Item): + result = self.__getitem__(sorted_keys[item]) + else: + key = self._find_first_match_(sorted_keys, item) + result = dict.__getitem__(self, key) + if result is RangeMap.undefined_value: + raise KeyError(key) + return result + + def get(self, key, default=None): + """ + Return the value for key if key is in the dictionary, else default. + If default is not given, it defaults to None, so that this method + never raises a KeyError. + """ + try: + return self[key] + except KeyError: + return default + + def _find_first_match_(self, keys, item): + is_match = functools.partial(self.match, item) + matches = list(filter(is_match, keys)) + if matches: + return matches[0] + raise KeyError(item) + + def bounds(self): + sorted_keys = sorted(self.keys(), **self.sort_params) + return (sorted_keys[RangeMap.first_item], sorted_keys[RangeMap.last_item]) + + # some special values for the RangeMap + undefined_value = type(str('RangeValueUndefined'), (), {})() + + class Item(int): + "RangeMap Item" + + first_item = Item(0) + last_item = Item(-1) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 9332db1d81..db10bf4658 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -12,6 +12,7 @@ import copy import shlex import warnings +import operator from subprocess import check_output from .unixccompiler import UnixCCompiler @@ -23,24 +24,30 @@ CompileError, ) from .version import LooseVersion, suppress_known_deprecation - - -_msvcr_lookup = { - # MSVC 7.0 - 1300: ['msvcr70'], - # MSVC 7.1 - 1310: ['msvcr71'], - # VS2005 / MSVC 8.0 - 1400: ['msvcr80'], - # VS2008 / MSVC 9.0 - 1500: ['msvcr90'], - # VS2010 / MSVC 10.0 - 1600: ['msvcr100'], - # VS2012 / MSVC 11.0 - 1700: ['msvcr110'], - # VS2013 / MSVC 12.0 - 1800: ['msvcr120'], -} +from ._collections import RangeMap + + +_msvcr_lookup = RangeMap.left( + { + # MSVC 7.0 + 1300: ['msvcr70'], + # MSVC 7.1 + 1310: ['msvcr71'], + # VS2005 / MSVC 8.0 + 1400: ['msvcr80'], + # VS2008 / MSVC 9.0 + 1500: ['msvcr90'], + # VS2010 / MSVC 10.0 + 1600: ['msvcr100'], + # VS2012 / MSVC 11.0 + 1700: ['msvcr110'], + # VS2013 / MSVC 12.0 + 1800: ['msvcr120'], + # VS2015 / MSVC 14.0 + 1900: ['ucrt', 'vcruntime140'], + 2000: RangeMap.undefined_value, + }, +) def get_msvcr(): @@ -52,13 +59,10 @@ def get_msvcr(): return msc_ver = int(match.group(1)) - if 1900 <= msc_ver < 2000: - # VS2015 / MSVC 14.0 - return ['ucrt', 'vcruntime140'] - if msc_ver in _msvcr_lookup: + try: return _msvcr_lookup[msc_ver] - - raise ValueError("Unknown MS Compiler version %s " % msc_ver) + except KeyError: + raise ValueError("Unknown MS Compiler version %s " % msc_ver) _runtime_library_dirs_msg = ( From 6c39b5053c17ec41690dab9d11399e2f39389892 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 10:31:39 -0400 Subject: [PATCH 22/31] Use try/except when assigning msc_ver. --- distutils/cygwinccompiler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index db10bf4658..fade05821a 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -55,10 +55,10 @@ def get_msvcr(): with MSVC 7.0 or later. """ match = re.search(r'MSC v\.(\d{4})', sys.version) - if not match: + try: + msc_ver = int(match.group(1)) + except AttributeError: return - - msc_ver = int(match.group(1)) try: return _msvcr_lookup[msc_ver] except KeyError: From b37f867005f177b4cf8a3dc2b72aee6adf6008e4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 16:36:04 -0400 Subject: [PATCH 23/31] =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins=20?= =?UTF-8?q?(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- distutils/cygwinccompiler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index fade05821a..f15b8eee26 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -12,7 +12,6 @@ import copy import shlex import warnings -import operator from subprocess import check_output from .unixccompiler import UnixCCompiler From e414cdf6542427db75e2b32b00a02825ad897a35 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 16:37:29 -0400 Subject: [PATCH 24/31] Re-paste RangeMap with newlines restored (unsure what made them disappear). --- distutils/_collections.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/distutils/_collections.py b/distutils/_collections.py index 870556358d..02556614a5 100644 --- a/distutils/_collections.py +++ b/distutils/_collections.py @@ -66,55 +66,78 @@ class RangeMap(dict): key_match_comparator, which defaults to less-than-or-equal. A value is returned for a key if it is the first key that matches in the sorted list of keys. + One may supply keyword parameters to be passed to the sort function used to sort keys (i.e. key, reverse) as sort_params. + Let's create a map that maps 1-3 -> 'a', 4-6 -> 'b' + >>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy >>> r[1], r[2], r[3], r[4], r[5], r[6] ('a', 'a', 'a', 'b', 'b', 'b') + Even float values should work so long as the comparison operator supports it. + >>> r[4.5] 'b' + But you'll notice that the way rangemap is defined, it must be open-ended on one side. + >>> r[0] 'a' >>> r[-1] 'a' + One can close the open-end of the RangeMap by using undefined_value + >>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'}) >>> r[0] Traceback (most recent call last): ... KeyError: 0 + One can get the first or last elements in the range by using RangeMap.Item + >>> last_item = RangeMap.Item(-1) >>> r[last_item] 'b' + .last_item is a shortcut for Item(-1) + >>> r[RangeMap.last_item] 'b' + Sometimes it's useful to find the bounds for a RangeMap + >>> r.bounds() (0, 6) + RangeMap supports .get(key, default) + >>> r.get(0, 'not found') 'not found' + >>> r.get(7, 'not found') 'not found' + One often wishes to define the ranges by their left-most values, which requires use of sort params and a key_match_comparator. + >>> r = RangeMap({1: 'a', 4: 'b'}, ... sort_params=dict(reverse=True), ... key_match_comparator=operator.ge) >>> r[1], r[2], r[3], r[4], r[5], r[6] ('a', 'a', 'a', 'b', 'b', 'b') + That wasn't nearly as easy as before, so an alternate constructor is provided: + >>> r = RangeMap.left({1: 'a', 4: 'b', 7: RangeMap.undefined_value}) >>> r[1], r[2], r[3], r[4], r[5], r[6] ('a', 'a', 'a', 'b', 'b', 'b') + """ def __init__(self, source, sort_params={}, key_match_comparator=operator.le): From 01ce88bbefdaa97ee36e5d2e71b703203a584be0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 16:57:55 -0400 Subject: [PATCH 25/31] Pin pytest to <7.2. Workaround for pypa/distutils#186. --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c23177c11f..d8d46d3fb3 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,8 @@ toxworkdir={env:TOX_WORK_DIR:.tox} [testenv] deps = - pytest + # < 7.2 due to pypa/distutils#186 + pytest < 7.2 pytest-flake8 # workaround for tholo/pytest-flake8#87 From 85df0b24d56d6629289db72539ccd070a9560a3a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 17:06:33 -0400 Subject: [PATCH 26/31] In TempdirManager, use pathlib and more_itertools to more simply write the text. --- distutils/tests/support.py | 11 +++-------- tox.ini | 1 + 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/distutils/tests/support.py b/distutils/tests/support.py index 86431539a1..fd4b11bf75 100644 --- a/distutils/tests/support.py +++ b/distutils/tests/support.py @@ -5,8 +5,10 @@ import tempfile import sysconfig import itertools +import pathlib import pytest +from more_itertools import always_iterable from distutils.core import Distribution @@ -29,16 +31,9 @@ def mkdtemp(self): def write_file(self, path, content='xxx'): """Writes a file in the given path. - path can be a string or a sequence. """ - if isinstance(path, (list, tuple)): - path = os.path.join(*path) - f = open(path, 'w') - try: - f.write(content) - finally: - f.close() + pathlib.Path(*always_iterable(path)).write_text(content) def create_dist(self, pkg_name='foo', **kw): """Will generate a test environment. diff --git a/tox.ini b/tox.ini index d8d46d3fb3..c42004831a 100644 --- a/tox.ini +++ b/tox.ini @@ -22,6 +22,7 @@ deps = path docutils pyfakefs + more_itertools commands = pytest {posargs} setenv = From f95d384957ba4c358c6e1345c932b4445a4a38d3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 3 Nov 2022 11:23:01 -0400 Subject: [PATCH 27/31] Rewrite init/for/append loop as comprehension and if/else as tertiary statement. --- distutils/sysconfig.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/distutils/sysconfig.py b/distutils/sysconfig.py index 6a979f8c91..0ec69366fd 100644 --- a/distutils/sysconfig.py +++ b/distutils/sysconfig.py @@ -537,13 +537,7 @@ def get_config_vars(*args): _config_vars = sysconfig.get_config_vars().copy() py39compat.add_ext_suffix(_config_vars) - if args: - vals = [] - for name in args: - vals.append(_config_vars.get(name)) - return vals - else: - return _config_vars + return [_config_vars.get(name) for name in args] if args else _config_vars def get_config_var(name): From 74652cabdeaacadc76ccf126563bed8ee2ccf3ef Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 5 Oct 2022 03:27:32 -0400 Subject: [PATCH 28/31] Replace bespoke logging facility with logging module, available since Python 2.3. --- conftest.py | 53 +++++-------------- distutils/cmd.py | 6 +-- distutils/log.py | 79 +++++++---------------------- distutils/tests/test_build_py.py | 4 +- distutils/tests/test_config.py | 1 - distutils/tests/test_dir_util.py | 24 ++++----- distutils/tests/test_dist.py | 17 +++---- distutils/tests/test_file_util.py | 11 ++-- distutils/tests/test_filelist.py | 51 ++++++++++--------- distutils/tests/test_install.py | 7 +-- distutils/tests/test_install_lib.py | 4 +- distutils/tests/test_log.py | 51 +++---------------- distutils/tests/test_register.py | 10 ++-- distutils/tests/test_sdist.py | 36 ++++++------- distutils/tests/test_upload.py | 15 +++--- 15 files changed, 124 insertions(+), 245 deletions(-) diff --git a/conftest.py b/conftest.py index 7427da7a09..b01b313085 100644 --- a/conftest.py +++ b/conftest.py @@ -2,6 +2,7 @@ import sys import platform import pathlib +import logging import pytest import path @@ -36,39 +37,20 @@ def needs_zlib(): pytest.importorskip('zlib') -# from jaraco.collections -class Everything: - def __contains__(self, other): - return True - - -class SavedLogs(list): - def render(self, *levels): - return [ - msg % args for level, msg, args in self if level in (levels or Everything()) - ] - - -@pytest.fixture -def logs(monkeypatch): - from distutils import log - - logs = SavedLogs() - log_levels = log.DEBUG, log.INFO, log.WARN, log.ERROR, log.FATAL - - def _log(self, level, msg, args): - self.logs.append((level, msg, args)) +@pytest.fixture(autouse=True) +def log_everything(): + """ + For tests, set the level on the logger to log everything. + """ + logging.getLogger('distutils').setLevel(0) - def save_log(self, level, msg, args): - if level not in log_levels: - raise ValueError(f'invalid log level {level}') - if not isinstance(msg, str): - raise TypeError(f'msg should be str, not {type(msg).__name__!r}') - logs.append((level, msg, args)) - monkeypatch.setattr(log.Log, '_log', save_log) - monkeypatch.setattr(log._global_log, 'threshold', log.FATAL) - return logs +@pytest.fixture(autouse=True) +def capture_log_at_info(caplog): + """ + By default, capture logs at INFO and greater. + """ + caplog.set_level(logging.INFO) def _save_cwd(): @@ -111,15 +93,6 @@ def temp_cwd(tmp_path): yield -@pytest.fixture -def threshold_warn(): - from distutils.log import set_threshold, WARN - - orig = set_threshold(WARN) - yield - set_threshold(orig) - - @pytest.fixture def pypirc(request, save_env, distutils_managed_tempdir): from distutils.core import PyPIRCCommand diff --git a/distutils/cmd.py b/distutils/cmd.py index 88a90ead55..0cf7dd0232 100644 --- a/distutils/cmd.py +++ b/distutils/cmd.py @@ -7,6 +7,7 @@ import sys import os import re +import logging from .errors import DistutilsOptionError from . import util, dir_util, file_util, archive_util, dep_util, log @@ -179,10 +180,7 @@ def run(self): "abstract method -- subclass %s must override" % self.__class__ ) - def announce(self, msg, level=1): - """If the current verbosity level is of greater than or equal to - 'level' print 'msg' to stdout. - """ + def announce(self, msg, level=logging.DEBUG): log.log(level, msg) def debug_print(self, msg): diff --git a/distutils/log.py b/distutils/log.py index be25f6cabd..d0365e684f 100644 --- a/distutils/log.py +++ b/distutils/log.py @@ -1,80 +1,37 @@ -"""A simple log mechanism styled after PEP 282.""" +""" +A simple log mechanism styled after PEP 282. -# The class here is styled after PEP 282 so that it could later be -# replaced with a standard Python logging implementation. +Retained for compatibility and should not be used. +""" -import sys +import logging -DEBUG = 1 -INFO = 2 -WARN = 3 -ERROR = 4 -FATAL = 5 +DEBUG = logging.DEBUG +INFO = logging.INFO +WARN = logging.WARN +ERROR = logging.ERROR +FATAL = logging.FATAL -class Log: - def __init__(self, threshold=WARN): - self.threshold = threshold - - def _log(self, level, msg, args): - if level not in (DEBUG, INFO, WARN, ERROR, FATAL): - raise ValueError('%s wrong log level' % str(level)) - - if level >= self.threshold: - if args: - msg = msg % args - if level in (WARN, ERROR, FATAL): - stream = sys.stderr - else: - stream = sys.stdout - try: - stream.write('%s\n' % msg) - except UnicodeEncodeError: - # emulate backslashreplace error handler - encoding = stream.encoding - msg = msg.encode(encoding, "backslashreplace").decode(encoding) - stream.write('%s\n' % msg) - stream.flush() - - def log(self, level, msg, *args): - self._log(level, msg, args) - - def debug(self, msg, *args): - self._log(DEBUG, msg, args) - - def info(self, msg, *args): - self._log(INFO, msg, args) - - def warn(self, msg, *args): - self._log(WARN, msg, args) - - def error(self, msg, *args): - self._log(ERROR, msg, args) - - def fatal(self, msg, *args): - self._log(FATAL, msg, args) - - -_global_log = Log() +_global_log = logging.getLogger('distutils') log = _global_log.log debug = _global_log.debug info = _global_log.info -warn = _global_log.warn +warn = _global_log.warning error = _global_log.error fatal = _global_log.fatal def set_threshold(level): - # return the old threshold for use from tests - old = _global_log.threshold - _global_log.threshold = level - return old + orig = _global_log.level + _global_log.setLevel(level) + return orig def set_verbosity(v): if v <= 0: - set_threshold(WARN) + set_threshold(logging.WARN) elif v == 1: - set_threshold(INFO) + set_threshold(logging.INFO) elif v >= 2: - set_threshold(DEBUG) + set_threshold(logging.DEBUG) diff --git a/distutils/tests/test_build_py.py b/distutils/tests/test_build_py.py index e5f4320cae..3bef9d79ec 100644 --- a/distutils/tests/test_build_py.py +++ b/distutils/tests/test_build_py.py @@ -150,7 +150,7 @@ def test_dir_in_package_data(self): except DistutilsFileError: self.fail("failed package_data when data dir includes a dir") - def test_dont_write_bytecode(self, logs): + def test_dont_write_bytecode(self, caplog): # makes sure byte_compile is not used dist = self.create_dist()[1] cmd = build_py(dist) @@ -164,7 +164,7 @@ def test_dont_write_bytecode(self, logs): finally: sys.dont_write_bytecode = old_dont_write_bytecode - assert 'byte-compiling is disabled' in logs.render()[0] + assert 'byte-compiling is disabled' in caplog.records[0].message def test_namespace_package_does_not_warn(self, caplog): """ diff --git a/distutils/tests/test_config.py b/distutils/tests/test_config.py index cdf73bb95b..1ae615db95 100644 --- a/distutils/tests/test_config.py +++ b/distutils/tests/test_config.py @@ -46,7 +46,6 @@ @support.combine_markers -@pytest.mark.usefixtures('threshold_warn') @pytest.mark.usefixtures('pypirc') class BasePyPIRCCommandTestCase(support.TempdirManager): pass diff --git a/distutils/tests/test_dir_util.py b/distutils/tests/test_dir_util.py index a48be7364f..0c6db4afae 100644 --- a/distutils/tests/test_dir_util.py +++ b/distutils/tests/test_dir_util.py @@ -26,21 +26,19 @@ def stuff(request, monkeypatch, distutils_managed_tempdir): class TestDirUtil(support.TempdirManager): - def test_mkpath_remove_tree_verbosity(self, logs): - + def test_mkpath_remove_tree_verbosity(self, caplog): mkpath(self.target, verbose=0) - wanted = [] - assert logs.render() == wanted + assert not caplog.records remove_tree(self.root_target, verbose=0) mkpath(self.target, verbose=1) wanted = ['creating %s' % self.root_target, 'creating %s' % self.target] - assert logs.render() == wanted - logs.clear() + assert caplog.messages == wanted + caplog.clear() remove_tree(self.root_target, verbose=1) wanted = ["removing '%s' (and everything under it)" % self.root_target] - assert logs.render() == wanted + assert caplog.messages == wanted @pytest.mark.skipif("platform.system() == 'Windows'") def test_mkpath_with_custom_mode(self): @@ -52,24 +50,24 @@ def test_mkpath_with_custom_mode(self): mkpath(self.target2, 0o555) assert stat.S_IMODE(os.stat(self.target2).st_mode) == 0o555 & ~umask - def test_create_tree_verbosity(self, logs): + def test_create_tree_verbosity(self, caplog): create_tree(self.root_target, ['one', 'two', 'three'], verbose=0) - assert logs.render() == [] + assert caplog.messages == [] remove_tree(self.root_target, verbose=0) wanted = ['creating %s' % self.root_target] create_tree(self.root_target, ['one', 'two', 'three'], verbose=1) - assert logs.render() == wanted + assert caplog.messages == wanted remove_tree(self.root_target, verbose=0) - def test_copy_tree_verbosity(self, logs): + def test_copy_tree_verbosity(self, caplog): mkpath(self.target, verbose=0) copy_tree(self.target, self.target2, verbose=0) - assert logs.render() == [] + assert caplog.messages == [] remove_tree(self.root_target, verbose=0) @@ -80,7 +78,7 @@ def test_copy_tree_verbosity(self, logs): wanted = ['copying {} -> {}'.format(a_file, self.target2)] copy_tree(self.target, self.target2, verbose=1) - assert logs.render() == wanted + assert caplog.messages == wanted remove_tree(self.root_target, verbose=0) remove_tree(self.target2, verbose=0) diff --git a/distutils/tests/test_dist.py b/distutils/tests/test_dist.py index 6726d50648..b5e81d0356 100644 --- a/distutils/tests/test_dist.py +++ b/distutils/tests/test_dist.py @@ -14,7 +14,6 @@ from distutils.cmd import Command from distutils.tests import support -from distutils import log pydistutils_cfg = '.' * (os.name == 'posix') + 'pydistutils.cfg' @@ -236,7 +235,7 @@ def test_get_command_packages(self): def test_announce(self): # make sure the level is known dist = Distribution() - with pytest.raises(ValueError): + with pytest.raises(TypeError): dist.announce('ok', level='ok2') def test_find_config_files_disable(self, temp_home): @@ -367,7 +366,7 @@ def test_classifier(self): meta = self.format_metadata(dist) assert 'Metadata-Version: 1.1' in meta - def test_classifier_invalid_type(self, capsys): + def test_classifier_invalid_type(self, caplog): attrs = { 'name': 'Boa', 'version': '3.0', @@ -375,7 +374,7 @@ def test_classifier_invalid_type(self, capsys): } d = Distribution(attrs) # should have warning about passing a non-list - assert 'should be a list' in capsys.readouterr().err + assert 'should be a list' in caplog.messages[0] # should be converted to a list assert isinstance(d.metadata.classifiers, list) assert d.metadata.classifiers == list(attrs['classifiers']) @@ -389,7 +388,7 @@ def test_keywords(self): dist = Distribution(attrs) assert dist.get_keywords() == ['spam', 'eggs', 'life of brian'] - def test_keywords_invalid_type(self, capsys): + def test_keywords_invalid_type(self, caplog): attrs = { 'name': 'Monty', 'version': '1.0', @@ -397,7 +396,7 @@ def test_keywords_invalid_type(self, capsys): } d = Distribution(attrs) # should have warning about passing a non-list - assert 'should be a list' in capsys.readouterr().err + assert 'should be a list' in caplog.messages[0] # should be converted to a list assert isinstance(d.metadata.keywords, list) assert d.metadata.keywords == list(attrs['keywords']) @@ -411,7 +410,7 @@ def test_platforms(self): dist = Distribution(attrs) assert dist.get_platforms() == ['GNU/Linux', 'Some Evil Platform'] - def test_platforms_invalid_types(self, capsys): + def test_platforms_invalid_types(self, caplog): attrs = { 'name': 'Monty', 'version': '1.0', @@ -419,7 +418,7 @@ def test_platforms_invalid_types(self, capsys): } d = Distribution(attrs) # should have warning about passing a non-list - assert 'should be a list' in capsys.readouterr().err + assert 'should be a list' in caplog.messages[0] # should be converted to a list assert isinstance(d.metadata.platforms, list) assert d.metadata.platforms == list(attrs['platforms']) @@ -472,8 +471,6 @@ def test_fix_help_options(self): def test_show_help(self, request, capsys): # smoke test, just makes sure some help is displayed - reset_log = functools.partial(log.set_threshold, log._global_log.threshold) - request.addfinalizer(reset_log) dist = Distribution() sys.argv = [] dist.help = 1 diff --git a/distutils/tests/test_file_util.py b/distutils/tests/test_file_util.py index 8ec56c3ba3..9f44f91dfa 100644 --- a/distutils/tests/test_file_util.py +++ b/distutils/tests/test_file_util.py @@ -20,7 +20,7 @@ def stuff(request, monkeypatch, distutils_managed_tempdir): class TestFileUtil(support.TempdirManager): - def test_move_file_verbosity(self, logs): + def test_move_file_verbosity(self, caplog): f = open(self.source, 'w') try: f.write('some content') @@ -28,25 +28,24 @@ def test_move_file_verbosity(self, logs): f.close() move_file(self.source, self.target, verbose=0) - wanted = [] - assert logs.render() == wanted + assert not caplog.messages # back to original state move_file(self.target, self.source, verbose=0) move_file(self.source, self.target, verbose=1) wanted = ['moving {} -> {}'.format(self.source, self.target)] - assert logs.render() == wanted + assert caplog.messages == wanted # back to original state move_file(self.target, self.source, verbose=0) - logs.clear() + caplog.clear() # now the target is a dir os.mkdir(self.target_dir) move_file(self.source, self.target_dir, verbose=1) wanted = ['moving {} -> {}'.format(self.source, self.target_dir)] - assert logs.render() == wanted + assert caplog.messages == wanted def test_move_file_exception_unpacking_rename(self): # see issue 22182 diff --git a/distutils/tests/test_filelist.py b/distutils/tests/test_filelist.py index ed68df32b0..2cee42cddd 100644 --- a/distutils/tests/test_filelist.py +++ b/distutils/tests/test_filelist.py @@ -1,8 +1,9 @@ """Tests for distutils.filelist.""" import os import re +import logging + from distutils import debug -from distutils.log import WARN from distutils.errors import DistutilsTemplateError from distutils.filelist import glob_to_re, translate_pattern, FileList from distutils import filelist @@ -35,13 +36,15 @@ def make_local_path(s): class TestFileList: - def assertNoWarnings(self, logs): - assert logs.render(WARN) == [] - logs.clear() + def assertNoWarnings(self, caplog): + warnings = [rec for rec in caplog.records if rec.levelno == logging.WARNING] + assert not warnings + caplog.clear() - def assertWarnings(self, logs): - assert logs.render(WARN) - logs.clear() + def assertWarnings(self, caplog): + warnings = [rec for rec in caplog.records if rec.levelno == logging.WARNING] + assert warnings + caplog.clear() def test_glob_to_re(self): sep = os.sep @@ -180,7 +183,7 @@ def test_include_pattern(self): file_list.include_pattern('*') assert file_list.allfiles == ['a.py', 'b.txt'] - def test_process_template(self, logs): + def test_process_template(self, caplog): mlp = make_local_path # invalid lines file_list = FileList() @@ -204,11 +207,11 @@ def test_process_template(self, logs): file_list.process_template_line('include *.py') assert file_list.files == ['a.py'] - self.assertNoWarnings(logs) + self.assertNoWarnings(caplog) file_list.process_template_line('include *.rb') assert file_list.files == ['a.py'] - self.assertWarnings(logs) + self.assertWarnings(caplog) # exclude file_list = FileList() @@ -216,11 +219,11 @@ def test_process_template(self, logs): file_list.process_template_line('exclude *.py') assert file_list.files == ['b.txt', mlp('d/c.py')] - self.assertNoWarnings(logs) + self.assertNoWarnings(caplog) file_list.process_template_line('exclude *.rb') assert file_list.files == ['b.txt', mlp('d/c.py')] - self.assertWarnings(logs) + self.assertWarnings(caplog) # global-include file_list = FileList() @@ -228,11 +231,11 @@ def test_process_template(self, logs): file_list.process_template_line('global-include *.py') assert file_list.files == ['a.py', mlp('d/c.py')] - self.assertNoWarnings(logs) + self.assertNoWarnings(caplog) file_list.process_template_line('global-include *.rb') assert file_list.files == ['a.py', mlp('d/c.py')] - self.assertWarnings(logs) + self.assertWarnings(caplog) # global-exclude file_list = FileList() @@ -240,11 +243,11 @@ def test_process_template(self, logs): file_list.process_template_line('global-exclude *.py') assert file_list.files == ['b.txt'] - self.assertNoWarnings(logs) + self.assertNoWarnings(caplog) file_list.process_template_line('global-exclude *.rb') assert file_list.files == ['b.txt'] - self.assertWarnings(logs) + self.assertWarnings(caplog) # recursive-include file_list = FileList() @@ -252,11 +255,11 @@ def test_process_template(self, logs): file_list.process_template_line('recursive-include d *.py') assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] - self.assertNoWarnings(logs) + self.assertNoWarnings(caplog) file_list.process_template_line('recursive-include e *.py') assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] - self.assertWarnings(logs) + self.assertWarnings(caplog) # recursive-exclude file_list = FileList() @@ -264,11 +267,11 @@ def test_process_template(self, logs): file_list.process_template_line('recursive-exclude d *.py') assert file_list.files == ['a.py', mlp('d/c.txt')] - self.assertNoWarnings(logs) + self.assertNoWarnings(caplog) file_list.process_template_line('recursive-exclude e *.py') assert file_list.files == ['a.py', mlp('d/c.txt')] - self.assertWarnings(logs) + self.assertWarnings(caplog) # graft file_list = FileList() @@ -276,11 +279,11 @@ def test_process_template(self, logs): file_list.process_template_line('graft d') assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] - self.assertNoWarnings(logs) + self.assertNoWarnings(caplog) file_list.process_template_line('graft e') assert file_list.files == [mlp('d/b.py'), mlp('d/d/e.py')] - self.assertWarnings(logs) + self.assertWarnings(caplog) # prune file_list = FileList() @@ -288,11 +291,11 @@ def test_process_template(self, logs): file_list.process_template_line('prune d') assert file_list.files == ['a.py', mlp('f/f.py')] - self.assertNoWarnings(logs) + self.assertNoWarnings(caplog) file_list.process_template_line('prune e') assert file_list.files == ['a.py', mlp('f/f.py')] - self.assertWarnings(logs) + self.assertWarnings(caplog) class TestFindAll: diff --git a/distutils/tests/test_install.py b/distutils/tests/test_install.py index e240b156f6..102218bc00 100644 --- a/distutils/tests/test_install.py +++ b/distutils/tests/test_install.py @@ -4,6 +4,7 @@ import sys import site import pathlib +import logging import pytest @@ -15,7 +16,6 @@ from distutils.core import Distribution from distutils.errors import DistutilsOptionError from distutils.extension import Extension -from distutils.log import DEBUG from distutils.tests import support from test import support as test_support @@ -244,8 +244,9 @@ def test_record_extensions(self): ] assert found == expected - def test_debug_mode(self, logs, monkeypatch): + def test_debug_mode(self, caplog, monkeypatch): # this covers the code called when DEBUG is set monkeypatch.setattr(install_module, 'DEBUG', True) + caplog.set_level(logging.DEBUG) self.test_record() - assert logs.render(DEBUG) + assert any(rec for rec in caplog.records if rec.levelno == logging.DEBUG) diff --git a/distutils/tests/test_install_lib.py b/distutils/tests/test_install_lib.py index cdf3fc977e..0bd67cd04d 100644 --- a/distutils/tests/test_install_lib.py +++ b/distutils/tests/test_install_lib.py @@ -93,7 +93,7 @@ def test_get_inputs(self): inputs = cmd.get_inputs() assert len(inputs) == 2, inputs - def test_dont_write_bytecode(self, logs): + def test_dont_write_bytecode(self, caplog): # makes sure byte_compile is not used dist = self.create_dist()[1] cmd = install_lib(dist) @@ -107,4 +107,4 @@ def test_dont_write_bytecode(self, logs): finally: sys.dont_write_bytecode = old_dont_write_bytecode - assert 'byte-compiling is disabled' in logs.render()[0] + assert 'byte-compiling is disabled' in caplog.messages[0] diff --git a/distutils/tests/test_log.py b/distutils/tests/test_log.py index 7aeee4057f..d346d07b07 100644 --- a/distutils/tests/test_log.py +++ b/distutils/tests/test_log.py @@ -1,52 +1,13 @@ """Tests for distutils.log""" -import io -import sys -from test.support import swap_attr - -import pytest +import logging from distutils import log class TestLog: - @pytest.mark.parametrize( - 'errors', - ( - 'strict', - 'backslashreplace', - 'surrogateescape', - 'replace', - 'ignore', - ), - ) - def test_non_ascii(self, errors): - # Issues #8663, #34421: test that non-encodable text is escaped with - # backslashreplace error handler and encodable non-ASCII text is - # output as is. - stdout = io.TextIOWrapper(io.BytesIO(), encoding='cp437', errors=errors) - stderr = io.TextIOWrapper(io.BytesIO(), encoding='cp437', errors=errors) - old_threshold = log.set_threshold(log.DEBUG) - try: - with swap_attr(sys, 'stdout', stdout), swap_attr(sys, 'stderr', stderr): - log.debug('Dεbug\tMėssãge') - log.fatal('Fαtal\tÈrrōr') - finally: - log.set_threshold(old_threshold) - - stdout.seek(0) - assert stdout.read().rstrip() == ( - 'Dεbug\tM?ss?ge' - if errors == 'replace' - else 'Dεbug\tMssge' - if errors == 'ignore' - else 'Dεbug\tM\\u0117ss\\xe3ge' - ) - stderr.seek(0) - assert stderr.read().rstrip() == ( - 'Fαtal\t?rr?r' - if errors == 'replace' - else 'Fαtal\trrr' - if errors == 'ignore' - else 'Fαtal\t\\xc8rr\\u014dr' - ) + def test_non_ascii(self, caplog): + caplog.set_level(logging.DEBUG) + log.debug('Dεbug\tMėssãge') + log.fatal('Fαtal\tÈrrōr') + assert caplog.messages == ['Dεbug\tMėssãge', 'Fαtal\tÈrrōr'] diff --git a/distutils/tests/test_register.py b/distutils/tests/test_register.py index d0b4cc7cc1..a10393b5e6 100644 --- a/distutils/tests/test_register.py +++ b/distutils/tests/test_register.py @@ -6,7 +6,6 @@ from distutils.command import register as register_module from distutils.command.register import register from distutils.errors import DistutilsSetupError -from distutils.log import INFO from distutils.tests.test_config import BasePyPIRCCommandTestCase import pytest @@ -303,13 +302,13 @@ def test_register_invalid_long_description(self, monkeypatch): with pytest.raises(DistutilsSetupError): cmd.run() - def test_list_classifiers(self, logs): + def test_list_classifiers(self, caplog): cmd = self._get_cmd() cmd.list_classifiers = 1 cmd.run() - assert logs.render(INFO) == ['running check', 'xxx'] + assert caplog.messages == ['running check', 'xxx'] - def test_show_response(self, logs): + def test_show_response(self, caplog): # test that the --show-response option return a well formatted response cmd = self._get_cmd() inputs = Inputs('1', 'tarek', 'y') @@ -320,5 +319,4 @@ def test_show_response(self, logs): finally: del register_module.input - results = logs.render(INFO) - assert results[3] == 75 * '-' + '\nxxx\n' + 75 * '-' + assert caplog.messages[3] == 75 * '-' + '\nxxx\n' + 75 * '-' diff --git a/distutils/tests/test_sdist.py b/distutils/tests/test_sdist.py index bc535f38a8..97504722ac 100644 --- a/distutils/tests/test_sdist.py +++ b/distutils/tests/test_sdist.py @@ -18,7 +18,6 @@ from distutils.tests.test_config import BasePyPIRCCommandTestCase from distutils.errors import DistutilsOptionError from distutils.spawn import find_executable # noqa: F401 -from distutils.log import WARN from distutils.filelist import FileList from distutils.archive_util import ARCHIVE_FORMATS @@ -251,8 +250,12 @@ def test_add_defaults(self): f.close() assert manifest == MANIFEST % {'sep': os.sep} + @staticmethod + def warnings(messages, prefix='warning: '): + return [msg for msg in messages if msg.startswith(prefix)] + @pytest.mark.usefixtures('needs_zlib') - def test_metadata_check_option(self, logs): + def test_metadata_check_option(self, caplog): # testing the `medata-check` option dist, cmd = self.get_cmd(metadata={}) @@ -260,21 +263,15 @@ def test_metadata_check_option(self, logs): # with the `check` subcommand cmd.ensure_finalized() cmd.run() - warnings = [ - msg for msg in logs.render(WARN) if msg.startswith('warning: check:') - ] - assert len(warnings) == 1 + assert len(self.warnings(caplog.messages, 'warning: check: ')) == 1 # trying with a complete set of metadata - logs.clear() + caplog.clear() dist, cmd = self.get_cmd() cmd.ensure_finalized() cmd.metadata_check = 0 cmd.run() - warnings = [ - msg for msg in logs.render(WARN) if msg.startswith('warning: check:') - ] - assert len(warnings) == 0 + assert len(self.warnings(caplog.messages, 'warning: check: ')) == 0 def test_check_metadata_deprecated(self): # makes sure make_metadata is deprecated @@ -321,28 +318,27 @@ def test_finalize_options(self): # the following tests make sure there is a nice error message instead # of a traceback when parsing an invalid manifest template - def _check_template(self, content, logs): + def _check_template(self, content, caplog): dist, cmd = self.get_cmd() os.chdir(self.tmp_dir) self.write_file('MANIFEST.in', content) cmd.ensure_finalized() cmd.filelist = FileList() cmd.read_template() - warnings = logs.render(WARN) - assert len(warnings) == 1 + assert len(self.warnings(caplog.messages)) == 1 - def test_invalid_template_unknown_command(self, logs): - self._check_template('taunt knights *', logs) + def test_invalid_template_unknown_command(self, caplog): + self._check_template('taunt knights *', caplog) - def test_invalid_template_wrong_arguments(self, logs): + def test_invalid_template_wrong_arguments(self, caplog): # this manifest command takes one argument - self._check_template('prune', logs) + self._check_template('prune', caplog) @pytest.mark.skipif("platform.system() != 'Windows'") - def test_invalid_template_wrong_path(self, logs): + def test_invalid_template_wrong_path(self, caplog): # on Windows, trailing slashes are not allowed # this used to crash instead of raising a warning: #8286 - self._check_template('include examples/', logs) + self._check_template('include examples/', caplog) @pytest.mark.usefixtures('needs_zlib') def test_get_file_list(self): diff --git a/distutils/tests/test_upload.py b/distutils/tests/test_upload.py index efd9e90617..9685c065f5 100644 --- a/distutils/tests/test_upload.py +++ b/distutils/tests/test_upload.py @@ -8,7 +8,6 @@ from distutils.command.upload import upload from distutils.core import Distribution from distutils.errors import DistutilsError -from distutils.log import ERROR, INFO from distutils.tests.test_config import PYPIRC, BasePyPIRCCommandTestCase import pytest @@ -109,7 +108,7 @@ def test_saved_password(self): cmd.finalize_options() assert cmd.password == 'xxx' - def test_upload(self, logs): + def test_upload(self, caplog): tmp = self.mkdtemp() path = os.path.join(tmp, 'xxx') self.write_file(path) @@ -150,7 +149,7 @@ def test_upload(self, logs): ) # The PyPI response body was echoed - results = logs.render(INFO) + results = caplog.messages assert results[-1] == 75 * '-' + '\nxyzzy\n' + 75 * '-' # bpo-32304: archives whose last byte was b'\r' were corrupted due to @@ -178,11 +177,11 @@ def test_upload_correct_cr(self): assert int(headers['Content-length']) >= 2172 assert b'long description\r' in self.last_open.req.data - def test_upload_fails(self, logs): + def test_upload_fails(self, caplog): self.next_msg = "Not Found" self.next_code = 404 with pytest.raises(DistutilsError): - self.test_upload(logs) + self.test_upload(caplog) @pytest.mark.parametrize( 'exception,expected,raised_exception', @@ -196,7 +195,7 @@ def test_upload_fails(self, logs): ), ], ) - def test_wrong_exception_order(self, exception, expected, raised_exception, logs): + def test_wrong_exception_order(self, exception, expected, raised_exception, caplog): tmp = self.mkdtemp() path = os.path.join(tmp, 'xxx') self.write_file(path) @@ -213,6 +212,6 @@ def test_wrong_exception_order(self, exception, expected, raised_exception, logs cmd = upload(dist) cmd.ensure_finalized() cmd.run() - results = logs.render(ERROR) + results = caplog.messages assert expected in results[-1] - logs.clear() + caplog.clear() From 8e5842aedb57a9ad44c99f891b1baf33f7e32625 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 5 Oct 2022 04:04:48 -0400 Subject: [PATCH 29/31] Avoid use of the distutils.log module. --- distutils/_log.py | 4 ++++ distutils/_msvccompiler.py | 2 +- distutils/archive_util.py | 2 +- distutils/bcppcompiler.py | 4 ++-- distutils/ccompiler.py | 2 +- distutils/cmd.py | 9 +++++---- distutils/command/bdist_dumb.py | 2 +- distutils/command/bdist_rpm.py | 2 +- distutils/command/build_clib.py | 2 +- distutils/command/build_ext.py | 10 ++++++---- distutils/command/build_py.py | 4 ++-- distutils/command/build_scripts.py | 2 +- distutils/command/clean.py | 4 ++-- distutils/command/config.py | 2 +- distutils/command/install.py | 4 ++-- distutils/command/install_egg_info.py | 3 ++- distutils/command/install_scripts.py | 2 +- distutils/command/register.py | 16 +++++++++------- distutils/command/sdist.py | 6 +++--- distutils/command/upload.py | 14 ++++++++------ distutils/dir_util.py | 4 ++-- distutils/dist.py | 9 +++++---- distutils/file_util.py | 2 +- distutils/filelist.py | 18 +++++++++--------- distutils/log.py | 3 ++- distutils/msvc9compiler.py | 2 +- distutils/msvccompiler.py | 2 +- distutils/spawn.py | 2 +- distutils/tests/test_config_cmd.py | 2 +- distutils/tests/test_log.py | 2 +- distutils/unixccompiler.py | 2 +- distutils/util.py | 2 +- 32 files changed, 80 insertions(+), 66 deletions(-) create mode 100644 distutils/_log.py diff --git a/distutils/_log.py b/distutils/_log.py new file mode 100644 index 0000000000..129cb18a62 --- /dev/null +++ b/distutils/_log.py @@ -0,0 +1,4 @@ +import logging + + +log = logging.getLogger('distutils') diff --git a/distutils/_msvccompiler.py b/distutils/_msvccompiler.py index d25dec1c2e..8b4023c420 100644 --- a/distutils/_msvccompiler.py +++ b/distutils/_msvccompiler.py @@ -30,7 +30,7 @@ LinkError, ) from .ccompiler import CCompiler, gen_lib_options -from . import log +from ._log import log from .util import get_platform from itertools import count diff --git a/distutils/archive_util.py b/distutils/archive_util.py index f9c4ed628b..7f9e1e00cc 100644 --- a/distutils/archive_util.py +++ b/distutils/archive_util.py @@ -16,7 +16,7 @@ from .errors import DistutilsExecError from .spawn import spawn from .dir_util import mkpath -from . import log +from ._log import log try: from pwd import getpwnam diff --git a/distutils/bcppcompiler.py b/distutils/bcppcompiler.py index 4aa1edf21e..5d6b86536e 100644 --- a/distutils/bcppcompiler.py +++ b/distutils/bcppcompiler.py @@ -25,7 +25,7 @@ from .ccompiler import CCompiler, gen_preprocess_options from .file_util import write_file from .dep_util import newer -from . import log +from ._log import log warnings.warn( @@ -210,7 +210,7 @@ def link( # noqa: C901 ) if runtime_library_dirs: - log.warn( + log.warning( "I don't know what to do with 'runtime_library_dirs': %s", str(runtime_library_dirs), ) diff --git a/distutils/ccompiler.py b/distutils/ccompiler.py index 1e79e8e45e..646353111f 100644 --- a/distutils/ccompiler.py +++ b/distutils/ccompiler.py @@ -19,7 +19,7 @@ from .dir_util import mkpath from .dep_util import newer_group from .util import split_quoted, execute -from . import log +from ._log import log class CCompiler: diff --git a/distutils/cmd.py b/distutils/cmd.py index 0cf7dd0232..918db85325 100644 --- a/distutils/cmd.py +++ b/distutils/cmd.py @@ -10,7 +10,8 @@ import logging from .errors import DistutilsOptionError -from . import util, dir_util, file_util, archive_util, dep_util, log +from . import util, dir_util, file_util, archive_util, dep_util +from ._log import log class Command: @@ -157,14 +158,14 @@ def dump_options(self, header=None, indent=""): if header is None: header = "command options for '%s':" % self.get_command_name() - self.announce(indent + header, level=log.INFO) + self.announce(indent + header, level=logging.INFO) indent = indent + " " for (option, _, _) in self.user_options: option = option.translate(longopt_xlate) if option[-1] == "=": option = option[:-1] value = getattr(self, option) - self.announce(indent + "{} = {}".format(option, value), level=log.INFO) + self.announce(indent + "{} = {}".format(option, value), level=logging.INFO) def run(self): """A command's raison d'etre: carry out the action it exists to @@ -332,7 +333,7 @@ def get_sub_commands(self): # -- External world manipulation ----------------------------------- def warn(self, msg): - log.warn("warning: %s: %s\n", self.get_command_name(), msg) + log.warning("warning: %s: %s\n", self.get_command_name(), msg) def execute(self, func, args, msg=None, level=1): util.execute(func, args, msg, dry_run=self.dry_run) diff --git a/distutils/command/bdist_dumb.py b/distutils/command/bdist_dumb.py index 4afea28ceb..071da77e18 100644 --- a/distutils/command/bdist_dumb.py +++ b/distutils/command/bdist_dumb.py @@ -10,7 +10,7 @@ from ..dir_util import remove_tree, ensure_relative from ..errors import DistutilsPlatformError from ..sysconfig import get_python_version -from distutils import log +from distutils._log import log class bdist_dumb(Command): diff --git a/distutils/command/bdist_rpm.py b/distutils/command/bdist_rpm.py index 524314386d..340527b08a 100644 --- a/distutils/command/bdist_rpm.py +++ b/distutils/command/bdist_rpm.py @@ -17,7 +17,7 @@ DistutilsExecError, ) from ..sysconfig import get_python_version -from distutils import log +from distutils._log import log class bdist_rpm(Command): diff --git a/distutils/command/build_clib.py b/distutils/command/build_clib.py index 442cd54ae6..f90c566432 100644 --- a/distutils/command/build_clib.py +++ b/distutils/command/build_clib.py @@ -18,7 +18,7 @@ from ..core import Command from ..errors import DistutilsSetupError from ..sysconfig import customize_compiler -from distutils import log +from distutils._log import log def show_compilers(): diff --git a/distutils/command/build_ext.py b/distutils/command/build_ext.py index 3019c7570b..f4c0eccd4f 100644 --- a/distutils/command/build_ext.py +++ b/distutils/command/build_ext.py @@ -22,7 +22,7 @@ from ..dep_util import newer_group from ..extension import Extension from ..util import get_platform -from distutils import log +from distutils._log import log from . import py37compat from site import USER_BASE @@ -373,7 +373,7 @@ def check_extensions_list(self, extensions): # noqa: C901 ext_name, build_info = ext - log.warn( + log.warning( "old-style (ext_name, build_info) tuple found in " "ext_modules for extension '%s' " "-- please convert to Extension instance", @@ -413,7 +413,9 @@ def check_extensions_list(self, extensions): # noqa: C901 # Medium-easy stuff: same syntax/semantics, different names. ext.runtime_library_dirs = build_info.get('rpath') if 'def_file' in build_info: - log.warn("'def_file' element of build info dict " "no longer supported") + log.warning( + "'def_file' element of build info dict " "no longer supported" + ) # Non-trivial stuff: 'macros' split into 'define_macros' # and 'undef_macros'. @@ -597,7 +599,7 @@ def swig_sources(self, sources, extension): # the temp dir. if self.swig_cpp: - log.warn("--swig-cpp is deprecated - use --swig-opts=-c++") + log.warning("--swig-cpp is deprecated - use --swig-opts=-c++") if ( self.swig_cpp diff --git a/distutils/command/build_py.py b/distutils/command/build_py.py index d3dfbf8b24..9f78324452 100644 --- a/distutils/command/build_py.py +++ b/distutils/command/build_py.py @@ -10,7 +10,7 @@ from ..core import Command from ..errors import DistutilsOptionError, DistutilsFileError from ..util import convert_path -from distutils import log +from distutils._log import log class build_py(Command): @@ -212,7 +212,7 @@ def check_package(self, package, package_dir): def check_module(self, module, module_file): if not os.path.isfile(module_file): - log.warn("file %s (for module %s) not found", module_file, module) + log.warning("file %s (for module %s) not found", module_file, module) return False else: return True diff --git a/distutils/command/build_scripts.py b/distutils/command/build_scripts.py index 728535dad7..87174f6bb1 100644 --- a/distutils/command/build_scripts.py +++ b/distutils/command/build_scripts.py @@ -9,7 +9,7 @@ from ..core import Command from ..dep_util import newer from ..util import convert_path -from distutils import log +from distutils._log import log import tokenize shebang_pattern = re.compile('^#!.*python[0-9.]*([ \t].*)?$') diff --git a/distutils/command/clean.py b/distutils/command/clean.py index d1b8a20696..d6eb3ebad6 100644 --- a/distutils/command/clean.py +++ b/distutils/command/clean.py @@ -7,7 +7,7 @@ import os from ..core import Command from ..dir_util import remove_tree -from distutils import log +from distutils._log import log class clean(Command): @@ -64,7 +64,7 @@ def run(self): if os.path.exists(directory): remove_tree(directory, dry_run=self.dry_run) else: - log.warn("'%s' does not exist -- can't clean it", directory) + log.warning("'%s' does not exist -- can't clean it", directory) # just for the heck of it, try to remove the base build directory: # we might have emptied it right now, but if not we don't care diff --git a/distutils/command/config.py b/distutils/command/config.py index e7ae83f1bd..8bf0e4893b 100644 --- a/distutils/command/config.py +++ b/distutils/command/config.py @@ -15,7 +15,7 @@ from ..core import Command from ..errors import DistutilsExecError from ..sysconfig import customize_compiler -from distutils import log +from distutils._log import log LANG_EXT = {"c": ".c", "c++": ".cxx"} diff --git a/distutils/command/install.py b/distutils/command/install.py index 9db4ad99af..08d2f8812f 100644 --- a/distutils/command/install.py +++ b/distutils/command/install.py @@ -8,7 +8,7 @@ import sysconfig import itertools -from distutils import log +from distutils._log import log from ..core import Command from ..debug import DEBUG from ..sysconfig import get_config_vars @@ -644,7 +644,7 @@ def handle_extra_path(self): self.extra_path = self.distribution.extra_path if self.extra_path is not None: - log.warn( + log.warning( "Distribution option extra_path is deprecated. " "See issue27919 for details." ) diff --git a/distutils/command/install_egg_info.py b/distutils/command/install_egg_info.py index ff9f0284bb..f3e8f3447d 100644 --- a/distutils/command/install_egg_info.py +++ b/distutils/command/install_egg_info.py @@ -10,7 +10,8 @@ import re from ..cmd import Command -from distutils import log, dir_util +from .. import dir_util +from .._log import log class install_egg_info(Command): diff --git a/distutils/command/install_scripts.py b/distutils/command/install_scripts.py index d4d3e3f333..ec6ec5acaa 100644 --- a/distutils/command/install_scripts.py +++ b/distutils/command/install_scripts.py @@ -7,7 +7,7 @@ import os from ..core import Command -from distutils import log +from distutils._log import log from stat import ST_MODE diff --git a/distutils/command/register.py b/distutils/command/register.py index 1a62ee3f16..55c1045ec6 100644 --- a/distutils/command/register.py +++ b/distutils/command/register.py @@ -7,12 +7,13 @@ import getpass import io +import logging import urllib.parse import urllib.request from warnings import warn from ..core import PyPIRCCommand -from distutils import log +from distutils._log import log class register(PyPIRCCommand): @@ -153,7 +154,7 @@ def send_metadata(self): # noqa: C901 3. have the server generate a new password for you (and email it to you), or 4. quit Your selection [default 1]: ''', - log.INFO, + logging.INFO, ) choice = input() if not choice: @@ -174,7 +175,7 @@ def send_metadata(self): # noqa: C901 auth.add_password(self.realm, host, username, password) # send the info to the server and report the result code, result = self.post_to_server(self.build_post_data('submit'), auth) - self.announce('Server response ({}): {}'.format(code, result), log.INFO) + self.announce('Server response ({}): {}'.format(code, result), logging.INFO) # possibly save the login if code == 200: @@ -188,11 +189,11 @@ def send_metadata(self): # noqa: C901 'I can store your PyPI login so future ' 'submissions will be faster.' ), - log.INFO, + logging.INFO, ) self.announce( '(the login will be stored in %s)' % self._get_rc_file(), - log.INFO, + logging.INFO, ) choice = 'X' while choice.lower() not in 'yn': @@ -265,7 +266,8 @@ def post_to_server(self, data, auth=None): # noqa: C901 '''Post a query to the server, and return a string response.''' if 'name' in data: self.announce( - 'Registering {} to {}'.format(data['name'], self.repository), log.INFO + 'Registering {} to {}'.format(data['name'], self.repository), + logging.INFO, ) # Build up the MIME payload for the urllib2 POST data boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' @@ -315,5 +317,5 @@ def post_to_server(self, data, auth=None): # noqa: C901 result = 200, 'OK' if self.show_response: msg = '\n'.join(('-' * 75, data, '-' * 75)) - self.announce(msg, log.INFO) + self.announce(msg, logging.INFO) return result diff --git a/distutils/command/sdist.py b/distutils/command/sdist.py index 86e41e5fe7..5cfd4c1456 100644 --- a/distutils/command/sdist.py +++ b/distutils/command/sdist.py @@ -13,7 +13,7 @@ from distutils import archive_util from ..text_file import TextFile from ..filelist import FileList -from distutils import log +from distutils._log import log from ..util import convert_path from ..errors import DistutilsOptionError, DistutilsTemplateError @@ -481,12 +481,12 @@ def make_release_tree(self, base_dir, files): msg = "copying files to %s..." % base_dir if not files: - log.warn("no files to distribute -- empty manifest?") + log.warning("no files to distribute -- empty manifest?") else: log.info(msg) for file in files: if not os.path.isfile(file): - log.warn("'%s' not a regular file -- skipping", file) + log.warning("'%s' not a regular file -- skipping", file) else: dest = os.path.join(base_dir, file) self.copy_file(file, dest, link=link) diff --git a/distutils/command/upload.py b/distutils/command/upload.py index 633273ee7d..16e15d8b60 100644 --- a/distutils/command/upload.py +++ b/distutils/command/upload.py @@ -8,13 +8,13 @@ import os import io import hashlib +import logging from base64 import standard_b64encode from urllib.request import urlopen, Request, HTTPError from urllib.parse import urlparse from ..errors import DistutilsError, DistutilsOptionError from ..core import PyPIRCCommand from ..spawn import spawn -from distutils import log # PyPI Warehouse supports MD5, SHA256, and Blake2 (blake2-256) @@ -171,7 +171,7 @@ def upload_file(self, command, pyversion, filename): # noqa: C901 body = body.getvalue() msg = "Submitting {} to {}".format(filename, self.repository) - self.announce(msg, log.INFO) + self.announce(msg, logging.INFO) # build the Request headers = { @@ -190,16 +190,18 @@ def upload_file(self, command, pyversion, filename): # noqa: C901 status = e.code reason = e.msg except OSError as e: - self.announce(str(e), log.ERROR) + self.announce(str(e), logging.ERROR) raise if status == 200: - self.announce('Server response ({}): {}'.format(status, reason), log.INFO) + self.announce( + 'Server response ({}): {}'.format(status, reason), logging.INFO + ) if self.show_response: text = self._read_pypi_response(result) msg = '\n'.join(('-' * 75, text, '-' * 75)) - self.announce(msg, log.INFO) + self.announce(msg, logging.INFO) else: msg = 'Upload failed ({}): {}'.format(status, reason) - self.announce(msg, log.ERROR) + self.announce(msg, logging.ERROR) raise DistutilsError(msg) diff --git a/distutils/dir_util.py b/distutils/dir_util.py index 54f5410340..80f7764902 100644 --- a/distutils/dir_util.py +++ b/distutils/dir_util.py @@ -5,7 +5,7 @@ import os import errno from .errors import DistutilsInternalError, DistutilsFileError -from . import log +from ._log import log # cache for by mkpath() -- in addition to cheapening redundant calls, # eliminates redundant "creating /foo/bar/baz" messages in dry-run mode @@ -229,7 +229,7 @@ def remove_tree(directory, verbose=1, dry_run=0): if abspath in _path_created: del _path_created[abspath] except OSError as exc: - log.warn("error removing %s: %s", directory, exc) + log.warning("error removing %s: %s", directory, exc) def ensure_relative(path): diff --git a/distutils/dist.py b/distutils/dist.py index d6677b6260..f6dd50a0b6 100644 --- a/distutils/dist.py +++ b/distutils/dist.py @@ -9,6 +9,7 @@ import re import pathlib import contextlib +import logging from email import message_from_file try: @@ -24,7 +25,7 @@ ) from .fancy_getopt import FancyGetopt, translate_longopt from .util import check_environ, strtobool, rfc822_escape -from . import log +from ._log import log from .debug import DEBUG # Regex to define acceptable Distutils command names. This is not *quite* @@ -44,7 +45,7 @@ def _ensure_list(value, fieldname): typename = type(value).__name__ msg = "Warning: '{fieldname}' should be a list, got type '{typename}'" msg = msg.format(**locals()) - log.log(log.WARN, msg) + log.warning(msg) value = list(value) return value @@ -465,7 +466,7 @@ def parse_command_line(self): parser.set_aliases({'licence': 'license'}) args = parser.getopt(args=self.script_args, object=self) option_order = parser.get_option_order() - log.set_verbosity(self.verbose) + log.setLevel(logging.WARN - 10 * self.verbose) # for display options we return immediately if self.handle_display_options(option_order): @@ -956,7 +957,7 @@ def reinitialize_command(self, command, reinit_subcommands=0): # -- Methods that operate on the Distribution ---------------------- - def announce(self, msg, level=log.INFO): + def announce(self, msg, level=logging.INFO): log.log(level, msg) def run_commands(self): diff --git a/distutils/file_util.py b/distutils/file_util.py index bead68eb2d..1b7cd53bd9 100644 --- a/distutils/file_util.py +++ b/distutils/file_util.py @@ -5,7 +5,7 @@ import os from .errors import DistutilsFileError -from . import log +from ._log import log # for generating verbose output in 'copy_file()' _copy_action = {None: 'copying', 'hard': 'hard linking', 'sym': 'symbolically linking'} diff --git a/distutils/filelist.py b/distutils/filelist.py index 619d6338b0..6dadf923d7 100644 --- a/distutils/filelist.py +++ b/distutils/filelist.py @@ -11,7 +11,7 @@ from .util import convert_path from .errors import DistutilsTemplateError, DistutilsInternalError -from . import log +from ._log import log class FileList: @@ -120,13 +120,13 @@ def process_template_line(self, line): # noqa: C901 self.debug_print("include " + ' '.join(patterns)) for pattern in patterns: if not self.include_pattern(pattern, anchor=1): - log.warn("warning: no files found matching '%s'", pattern) + log.warning("warning: no files found matching '%s'", pattern) elif action == 'exclude': self.debug_print("exclude " + ' '.join(patterns)) for pattern in patterns: if not self.exclude_pattern(pattern, anchor=1): - log.warn( + log.warning( ( "warning: no previously-included files " "found matching '%s'" @@ -138,7 +138,7 @@ def process_template_line(self, line): # noqa: C901 self.debug_print("global-include " + ' '.join(patterns)) for pattern in patterns: if not self.include_pattern(pattern, anchor=0): - log.warn( + log.warning( ( "warning: no files found matching '%s' " "anywhere in distribution" @@ -150,7 +150,7 @@ def process_template_line(self, line): # noqa: C901 self.debug_print("global-exclude " + ' '.join(patterns)) for pattern in patterns: if not self.exclude_pattern(pattern, anchor=0): - log.warn( + log.warning( ( "warning: no previously-included files matching " "'%s' found anywhere in distribution" @@ -165,13 +165,13 @@ def process_template_line(self, line): # noqa: C901 msg = ( "warning: no files found matching '%s' " "under directory '%s'" ) - log.warn(msg, pattern, dir) + log.warning(msg, pattern, dir) elif action == 'recursive-exclude': self.debug_print("recursive-exclude {} {}".format(dir, ' '.join(patterns))) for pattern in patterns: if not self.exclude_pattern(pattern, prefix=dir): - log.warn( + log.warning( ( "warning: no previously-included files matching " "'%s' found under directory '%s'" @@ -183,12 +183,12 @@ def process_template_line(self, line): # noqa: C901 elif action == 'graft': self.debug_print("graft " + dir_pattern) if not self.include_pattern(None, prefix=dir_pattern): - log.warn("warning: no directories found matching '%s'", dir_pattern) + log.warning("warning: no directories found matching '%s'", dir_pattern) elif action == 'prune': self.debug_print("prune " + dir_pattern) if not self.exclude_pattern(None, prefix=dir_pattern): - log.warn( + log.warning( ("no previously-included directories found " "matching '%s'"), dir_pattern, ) diff --git a/distutils/log.py b/distutils/log.py index d0365e684f..bb789c300d 100644 --- a/distutils/log.py +++ b/distutils/log.py @@ -6,6 +6,8 @@ import logging +from ._log import log as _global_log + DEBUG = logging.DEBUG INFO = logging.INFO @@ -13,7 +15,6 @@ ERROR = logging.ERROR FATAL = logging.FATAL -_global_log = logging.getLogger('distutils') log = _global_log.log debug = _global_log.debug info = _global_log.info diff --git a/distutils/msvc9compiler.py b/distutils/msvc9compiler.py index e9f02c1a9e..a4714a559d 100644 --- a/distutils/msvc9compiler.py +++ b/distutils/msvc9compiler.py @@ -26,7 +26,7 @@ LinkError, ) from .ccompiler import CCompiler, gen_lib_options -from . import log +from ._log import log from .util import get_platform import winreg diff --git a/distutils/msvccompiler.py b/distutils/msvccompiler.py index d15499d7d7..59ebe99caa 100644 --- a/distutils/msvccompiler.py +++ b/distutils/msvccompiler.py @@ -19,7 +19,7 @@ LinkError, ) from .ccompiler import CCompiler, gen_lib_options -from . import log +from ._log import log _can_read_reg = False try: diff --git a/distutils/spawn.py b/distutils/spawn.py index 7ae364486b..afefe525ef 100644 --- a/distutils/spawn.py +++ b/distutils/spawn.py @@ -12,7 +12,7 @@ from .errors import DistutilsExecError from .debug import DEBUG -from . import log +from ._log import log def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): # noqa: C901 diff --git a/distutils/tests/test_config_cmd.py b/distutils/tests/test_config_cmd.py index 6d13c24f74..e72a7c5ff8 100644 --- a/distutils/tests/test_config_cmd.py +++ b/distutils/tests/test_config_cmd.py @@ -7,7 +7,7 @@ from distutils.command.config import dump_file, config from distutils.tests import support -from distutils import log +from distutils._log import log @pytest.fixture(autouse=True) diff --git a/distutils/tests/test_log.py b/distutils/tests/test_log.py index d346d07b07..ec6a0c8051 100644 --- a/distutils/tests/test_log.py +++ b/distutils/tests/test_log.py @@ -2,7 +2,7 @@ import logging -from distutils import log +from distutils._log import log class TestLog: diff --git a/distutils/unixccompiler.py b/distutils/unixccompiler.py index 62e34ef521..4bf2e6a681 100644 --- a/distutils/unixccompiler.py +++ b/distutils/unixccompiler.py @@ -23,7 +23,7 @@ from .dep_util import newer from .ccompiler import CCompiler, gen_preprocess_options, gen_lib_options from .errors import DistutilsExecError, CompileError, LibError, LinkError -from . import log +from ._log import log from ._macos_compat import compiler_fixup # XXX Things not currently handled: diff --git a/distutils/util.py b/distutils/util.py index f18641762c..8668b43699 100644 --- a/distutils/util.py +++ b/distutils/util.py @@ -16,7 +16,7 @@ from .errors import DistutilsPlatformError, DistutilsByteCompileError from .dep_util import newer from .spawn import spawn -from . import log +from ._log import log def get_host_platform(): From d763948a0508fddcfb9cc723bc95e133e99f8279 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 5 Oct 2022 20:28:01 -0400 Subject: [PATCH 30/31] Log to the root logger, as that's the one Setuptools patches/validates. --- distutils/_log.py | 2 +- distutils/dist.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distutils/_log.py b/distutils/_log.py index 129cb18a62..4a2ae0acb8 100644 --- a/distutils/_log.py +++ b/distutils/_log.py @@ -1,4 +1,4 @@ import logging -log = logging.getLogger('distutils') +log = logging.getLogger() diff --git a/distutils/dist.py b/distutils/dist.py index f6dd50a0b6..d7458a052f 100644 --- a/distutils/dist.py +++ b/distutils/dist.py @@ -466,7 +466,7 @@ def parse_command_line(self): parser.set_aliases({'licence': 'license'}) args = parser.getopt(args=self.script_args, object=self) option_order = parser.get_option_order() - log.setLevel(logging.WARN - 10 * self.verbose) + logging.getLogger().setLevel(logging.WARN - 10 * self.verbose) # for display options we return immediately if self.handle_display_options(option_order): From e034926af657f4fa17971d871242fbe5ebdabdb3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 13 Nov 2022 14:40:05 -0500 Subject: [PATCH 31/31] Update changelog. --- changelog.d/3674.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3674.change.rst diff --git a/changelog.d/3674.change.rst b/changelog.d/3674.change.rst new file mode 100644 index 0000000000..e90e16eb5d --- /dev/null +++ b/changelog.d/3674.change.rst @@ -0,0 +1 @@ +Sync with pypa/distutils@e0787fa, including pypa/distutils#183 updating distutils to use the Python logging framework.