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/_log.py b/distutils/_log.py new file mode 100644 index 0000000000..4a2ae0acb8 --- /dev/null +++ b/distutils/_log.py @@ -0,0 +1,4 @@ +import logging + + +log = logging.getLogger() 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 88a90ead55..918db85325 100644 --- a/distutils/cmd.py +++ b/distutils/cmd.py @@ -7,9 +7,11 @@ import sys import os import re +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: @@ -156,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 @@ -179,10 +181,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): @@ -334,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..d7458a052f 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) + logging.getLogger().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 be25f6cabd..bb789c300d 100644 --- a/distutils/log.py +++ b/distutils/log.py @@ -1,80 +1,38 @@ -"""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 +from ._log import log as _global_log -class Log: - def __init__(self, threshold=WARN): - self.threshold = threshold +DEBUG = logging.DEBUG +INFO = logging.INFO +WARN = logging.WARN +ERROR = logging.ERROR +FATAL = logging.FATAL - 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() 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/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_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_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_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..ec6a0c8051 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 logging -import pytest - -from distutils import log +from distutils._log 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() 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():