diff options
| author | David Lord <davidism@gmail.com> | 2020-04-19 21:44:28 -0700 |
|---|---|---|
| committer | David Lord <davidism@gmail.com> | 2020-04-19 23:58:27 -0700 |
| commit | 20fddf68e85ca98eff368aa171965754f9f446b5 (patch) | |
| tree | f2dad9ecc3f62bc6ff38e3ceaac5615aaaa37543 | |
| parent | 9df61f821079bd623acf911532b4e529ee7293c1 (diff) | |
| download | click-20fddf68e85ca98eff368aa171965754f9f446b5.tar.gz | |
remove py2 parts of _compat module
| -rw-r--r-- | docs/conf.py | 5 | ||||
| -rw-r--r-- | src/click/__init__.py | 6 | ||||
| -rw-r--r-- | src/click/_compat.py | 545 | ||||
| -rw-r--r-- | src/click/_termui_impl.py | 22 | ||||
| -rw-r--r-- | src/click/_unicodefun.py | 49 | ||||
| -rw-r--r-- | src/click/_winconsole.py | 75 | ||||
| -rw-r--r-- | src/click/core.py | 26 | ||||
| -rw-r--r-- | src/click/decorators.py | 5 | ||||
| -rw-r--r-- | src/click/exceptions.py | 21 | ||||
| -rw-r--r-- | src/click/termui.py | 9 | ||||
| -rw-r--r-- | src/click/testing.py | 53 | ||||
| -rw-r--r-- | src/click/types.py | 26 | ||||
| -rw-r--r-- | src/click/utils.py | 54 | ||||
| -rw-r--r-- | tests/test_arguments.py | 23 | ||||
| -rw-r--r-- | tests/test_options.py | 7 | ||||
| -rw-r--r-- | tests/test_testing.py | 12 |
16 files changed, 283 insertions, 655 deletions
diff --git a/docs/conf.py b/docs/conf.py index f04804c..54c7a4d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,11 @@ from pallets_sphinx_themes import get_version from pallets_sphinx_themes import ProjectLink +import click._compat + +# compat until pallets-sphinx-themes is updated +click._compat.text_type = str + # Project -------------------------------------------------------------- project = "Click" diff --git a/src/click/__init__.py b/src/click/__init__.py index a033e8b..9cd0129 100644 --- a/src/click/__init__.py +++ b/src/click/__init__.py @@ -72,8 +72,4 @@ from .utils import get_os_args from .utils import get_text_stream from .utils import open_file -# Controls if click should emit the warning about the use of unicode -# literals. -disable_unicode_literals_warning = False - -__version__ = "8.0.dev" +__version__ = "8.0.0.dev" diff --git a/src/click/_compat.py b/src/click/_compat.py index cbc64a1..63bebef 100644 --- a/src/click/_compat.py +++ b/src/click/_compat.py @@ -1,4 +1,3 @@ -# flake8: noqa import codecs import io import os @@ -6,7 +5,6 @@ import re import sys from weakref import WeakKeyDictionary -PY2 = sys.version_info[0] == 2 CYGWIN = sys.platform.startswith("cygwin") MSYS2 = sys.platform.startswith("win") and ("GCC" in sys.version) # Determine local App Engine environment, per Google's own suggestion @@ -15,8 +13,9 @@ APP_ENGINE = "APPENGINE_RUNTIME" in os.environ and "Development/" in os.environ. ) WIN = sys.platform.startswith("win") and not APP_ENGINE and not MSYS2 DEFAULT_COLUMNS = 80 - - +auto_wrap_for_ansi = None +colorama = None +get_winterm_size = None _ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") @@ -68,25 +67,7 @@ class _NonClosingTextIOWrapper(io.TextIOWrapper): **extra ): self._stream = stream = _FixupStream(stream, force_readable, force_writable) - io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra) - - # The io module is a place where the Python 3 text behavior - # was forced upon Python 2, so we need to unbreak - # it to look like Python 2. - if PY2: - - def write(self, x): - if isinstance(x, str) or is_bytes(x): - try: - self.flush() - except Exception: - pass - return self.buffer.write(str(x)) - return io.TextIOWrapper.write(self, x) - - def writelines(self, lines): - for line in lines: - self.write(line) + super().__init__(stream, encoding, errors, **extra) def __del__(self): try: @@ -121,11 +102,7 @@ class _FixupStream(object): f = getattr(self._stream, "read1", None) if f is not None: return f(size) - # We only dispatch to readline instead of read in Python 2 as we - # do not want cause problems with the different implementation - # of line buffering. - if PY2: - return self._stream.readline(size) + return self._stream.read(size) def readable(self): @@ -166,320 +143,207 @@ class _FixupStream(object): return True -if PY2: - text_type = unicode - raw_input = raw_input - string_types = (str, unicode) - int_types = (int, long) - iteritems = lambda x: x.iteritems() - range_type = xrange - - from pipes import quote as shlex_quote - - def is_bytes(x): - return isinstance(x, (buffer, bytearray)) - - _identifier_re = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$") - - # For Windows, we need to force stdout/stdin/stderr to binary if it's - # fetched for that. This obviously is not the most correct way to do - # it as it changes global state. Unfortunately, there does not seem to - # be a clear better way to do it as just reopening the file in binary - # mode does not change anything. - # - # An option would be to do what Python 3 does and to open the file as - # binary only, patch it back to the system, and then use a wrapper - # stream that converts newlines. It's not quite clear what's the - # correct option here. - # - # This code also lives in _winconsole for the fallback to the console - # emulation stream. - # - # There are also Windows environments where the `msvcrt` module is not - # available (which is why we use try-catch instead of the WIN variable - # here), such as the Google App Engine development server on Windows. In - # those cases there is just nothing we can do. - def set_binary_mode(f): - return f +def is_bytes(x): + return isinstance(x, (bytes, memoryview, bytearray)) + +def _is_binary_reader(stream, default=False): try: - import msvcrt - except ImportError: - pass - else: + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. - def set_binary_mode(f): - try: - fileno = f.fileno() - except Exception: - pass - else: - msvcrt.setmode(fileno, os.O_BINARY) - return f +def _is_binary_writer(stream, default=False): try: - import fcntl - except ImportError: - pass - else: + stream.write(b"") + except Exception: + try: + stream.write("") + return False + except Exception: + pass + return default + return True - def set_binary_mode(f): - try: - fileno = f.fileno() - except Exception: - pass - else: - flags = fcntl.fcntl(fileno, fcntl.F_GETFL) - fcntl.fcntl(fileno, fcntl.F_SETFL, flags & ~os.O_NONBLOCK) - return f - def isidentifier(x): - return _identifier_re.search(x) is not None +def _find_binary_reader(stream): + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return stream - def get_binary_stdin(): - return set_binary_mode(sys.stdin) + buf = getattr(stream, "buffer", None) - def get_binary_stdout(): - _wrap_std_stream("stdout") - return set_binary_mode(sys.stdout) + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return buf - def get_binary_stderr(): - _wrap_std_stream("stderr") - return set_binary_mode(sys.stderr) - def get_text_stdin(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stdin, encoding, errors) - if rv is not None: - return rv - return _make_text_stream(sys.stdin, encoding, errors, force_readable=True) +def _find_binary_writer(stream): + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return stream - def get_text_stdout(encoding=None, errors=None): - _wrap_std_stream("stdout") - rv = _get_windows_console_stream(sys.stdout, encoding, errors) - if rv is not None: - return rv - return _make_text_stream(sys.stdout, encoding, errors, force_writable=True) + buf = getattr(stream, "buffer", None) - def get_text_stderr(encoding=None, errors=None): - _wrap_std_stream("stderr") - rv = _get_windows_console_stream(sys.stderr, encoding, errors) - if rv is not None: - return rv - return _make_text_stream(sys.stderr, encoding, errors, force_writable=True) + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return buf - def filename_to_ui(value): - if isinstance(value, bytes): - value = value.decode(get_filesystem_encoding(), "replace") - return value +def _stream_is_misconfigured(stream): + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") -else: - import io - text_type = str - raw_input = input - string_types = (str,) - int_types = (int,) - range_type = range - isidentifier = lambda x: x.isidentifier() - iteritems = lambda x: iter(x.items()) +def _is_compat_stream_attr(stream, attr, value): + """A stream attribute is compatible if it is equal to the + desired value or the desired value is unset and the attribute + has a value. + """ + stream_value = getattr(stream, attr, None) + return stream_value == value or (value is None and stream_value is not None) - from shlex import quote as shlex_quote - def is_bytes(x): - return isinstance(x, (bytes, memoryview, bytearray)) +def _is_compatible_text_stream(stream, encoding, errors): + """Check if a stream's encoding and errors attributes are + compatible with the desired values. + """ + return _is_compat_stream_attr( + stream, "encoding", encoding + ) and _is_compat_stream_attr(stream, "errors", errors) + + +def _force_correct_text_stream( + text_stream, + encoding, + errors, + is_binary, + find_binary, + force_readable=False, + force_writable=False, +): + if is_binary(text_stream, False): + binary_reader = text_stream + else: + # If the stream looks compatible, and won't default to a + # misconfigured ascii encoding, return it as-is. + if _is_compatible_text_stream(text_stream, encoding, errors) and not ( + encoding is None and _stream_is_misconfigured(text_stream) + ): + return text_stream + + # Otherwise, get the underlying binary reader. + binary_reader = find_binary(text_stream) + + # If that's not possible, silently use the original reader + # and get mojibake instead of exceptions. + if binary_reader is None: + return text_stream + + # Default errors to replace instead of strict in order to get + # something that works. + if errors is None: + errors = "replace" - def _is_binary_reader(stream, default=False): - try: - return isinstance(stream.read(0), bytes) - except Exception: - return default - # This happens in some cases where the stream was already - # closed. In this case, we assume the default. + # Wrap the binary stream in a text stream with the correct + # encoding parameters. + return _make_text_stream( + binary_reader, + encoding, + errors, + force_readable=force_readable, + force_writable=force_writable, + ) - def _is_binary_writer(stream, default=False): - try: - stream.write(b"") - except Exception: - try: - stream.write("") - return False - except Exception: - pass - return default - return True - def _find_binary_reader(stream): - # We need to figure out if the given stream is already binary. - # This can happen because the official docs recommend detaching - # the streams to get binary streams. Some code might do this, so - # we need to deal with this case explicitly. - if _is_binary_reader(stream, False): - return stream - - buf = getattr(stream, "buffer", None) - - # Same situation here; this time we assume that the buffer is - # actually binary in case it's closed. - if buf is not None and _is_binary_reader(buf, True): - return buf - - def _find_binary_writer(stream): - # We need to figure out if the given stream is already binary. - # This can happen because the official docs recommend detaching - # the streams to get binary streams. Some code might do this, so - # we need to deal with this case explicitly. - if _is_binary_writer(stream, False): - return stream - - buf = getattr(stream, "buffer", None) - - # Same situation here; this time we assume that the buffer is - # actually binary in case it's closed. - if buf is not None and _is_binary_writer(buf, True): - return buf - - def _stream_is_misconfigured(stream): - """A stream is misconfigured if its encoding is ASCII.""" - # If the stream does not have an encoding set, we assume it's set - # to ASCII. This appears to happen in certain unittest - # environments. It's not quite clear what the correct behavior is - # but this at least will force Click to recover somehow. - return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") - - def _is_compat_stream_attr(stream, attr, value): - """A stream attribute is compatible if it is equal to the - desired value or the desired value is unset and the attribute - has a value. - """ - stream_value = getattr(stream, attr, None) - return stream_value == value or (value is None and stream_value is not None) - - def _is_compatible_text_stream(stream, encoding, errors): - """Check if a stream's encoding and errors attributes are - compatible with the desired values. - """ - return _is_compat_stream_attr( - stream, "encoding", encoding - ) and _is_compat_stream_attr(stream, "errors", errors) - - def _force_correct_text_stream( - text_stream, +def _force_correct_text_reader(text_reader, encoding, errors, force_readable=False): + return _force_correct_text_stream( + text_reader, encoding, errors, - is_binary, - find_binary, - force_readable=False, - force_writable=False, - ): - if is_binary(text_stream, False): - binary_reader = text_stream - else: - # If the stream looks compatible, and won't default to a - # misconfigured ascii encoding, return it as-is. - if _is_compatible_text_stream(text_stream, encoding, errors) and not ( - encoding is None and _stream_is_misconfigured(text_stream) - ): - return text_stream - - # Otherwise, get the underlying binary reader. - binary_reader = find_binary(text_stream) - - # If that's not possible, silently use the original reader - # and get mojibake instead of exceptions. - if binary_reader is None: - return text_stream - - # Default errors to replace instead of strict in order to get - # something that works. - if errors is None: - errors = "replace" - - # Wrap the binary stream in a text stream with the correct - # encoding parameters. - return _make_text_stream( - binary_reader, - encoding, - errors, - force_readable=force_readable, - force_writable=force_writable, - ) + _is_binary_reader, + _find_binary_reader, + force_readable=force_readable, + ) - def _force_correct_text_reader(text_reader, encoding, errors, force_readable=False): - return _force_correct_text_stream( - text_reader, - encoding, - errors, - _is_binary_reader, - _find_binary_reader, - force_readable=force_readable, - ) - def _force_correct_text_writer(text_writer, encoding, errors, force_writable=False): - return _force_correct_text_stream( - text_writer, - encoding, - errors, - _is_binary_writer, - _find_binary_writer, - force_writable=force_writable, - ) +def _force_correct_text_writer(text_writer, encoding, errors, force_writable=False): + return _force_correct_text_stream( + text_writer, + encoding, + errors, + _is_binary_writer, + _find_binary_writer, + force_writable=force_writable, + ) - def get_binary_stdin(): - reader = _find_binary_reader(sys.stdin) - if reader is None: - raise RuntimeError("Was not able to determine binary stream for sys.stdin.") - return reader - - def get_binary_stdout(): - writer = _find_binary_writer(sys.stdout) - if writer is None: - raise RuntimeError( - "Was not able to determine binary stream for sys.stdout." - ) - return writer - - def get_binary_stderr(): - writer = _find_binary_writer(sys.stderr) - if writer is None: - raise RuntimeError( - "Was not able to determine binary stream for sys.stderr." - ) - return writer - - def get_text_stdin(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stdin, encoding, errors) - if rv is not None: - return rv - return _force_correct_text_reader( - sys.stdin, encoding, errors, force_readable=True - ) - def get_text_stdout(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stdout, encoding, errors) - if rv is not None: - return rv - return _force_correct_text_writer( - sys.stdout, encoding, errors, force_writable=True - ) +def get_binary_stdin(): + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdin.") + return reader - def get_text_stderr(encoding=None, errors=None): - rv = _get_windows_console_stream(sys.stderr, encoding, errors) - if rv is not None: - return rv - return _force_correct_text_writer( - sys.stderr, encoding, errors, force_writable=True - ) - def filename_to_ui(value): - if isinstance(value, bytes): - value = value.decode(get_filesystem_encoding(), "replace") - else: - value = value.encode("utf-8", "surrogateescape").decode("utf-8", "replace") - return value +def get_binary_stdout(): + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdout.") + return writer + + +def get_binary_stderr(): + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stderr.") + return writer + + +def get_text_stdin(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True) -def get_streerror(e, default=None): +def get_text_stdout(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True) + + +def get_text_stderr(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True) + + +def filename_to_ui(value): + if isinstance(value, bytes): + value = value.decode(get_filesystem_encoding(), "replace") + else: + value = value.encode("utf-8", "surrogateescape").decode("utf-8", "replace") + return value + + +def get_strerror(e, default=None): if hasattr(e, "strerror"): msg = e.strerror else: @@ -493,25 +357,11 @@ def get_streerror(e, default=None): def _wrap_io_open(file, mode, encoding, errors): - """On Python 2, :func:`io.open` returns a text file wrapper that - requires passing ``unicode`` to ``write``. Need to open the file in - binary mode then wrap it in a subclass that can write ``str`` and - ``unicode``. - - Also handles not passing ``encoding`` and ``errors`` in binary mode. - """ - binary = "b" in mode - - if binary: - kwargs = {} - else: - kwargs = {"encoding": encoding, "errors": errors} - - if not PY2 or binary: - return io.open(file, mode, **kwargs) + """Handles not passing ``encoding`` and ``errors`` in binary mode.""" + if "b" in mode: + return io.open(file, mode) - f = io.open(file, "{}b".format(mode.replace("t", ""))) - return _make_text_stream(f, **kwargs) + return io.open(file, mode, encoding=encoding, errors=errors) def open_stream(filename, mode="r", encoding=None, errors="strict", atomic=False): @@ -587,15 +437,6 @@ def open_stream(filename, mode="r", encoding=None, errors="strict", atomic=False return _AtomicFile(f, tmp_filename, os.path.realpath(filename)), True -# Used in a destructor call, needs extra protection from interpreter cleanup. -if hasattr(os, "replace"): - _replace = os.replace - _can_replace = True -else: - _replace = os.rename - _can_replace = not WIN - - class _AtomicFile(object): def __init__(self, f, tmp_filename, real_filename): self._f = f @@ -611,12 +452,7 @@ class _AtomicFile(object): if self.closed: return self._f.close() - if not _can_replace: - try: - os.remove(self._real_filename) - except OSError: - pass - _replace(self._tmp_filename, self._real_filename) + os.replace(self._tmp_filename, self._real_filename) self.closed = True def __getattr__(self, name): @@ -632,11 +468,6 @@ class _AtomicFile(object): return repr(self._f) -auto_wrap_for_ansi = None -colorama = None -get_winterm_size = None - - def strip_ansi(value): return _ansi_re.sub("", value) @@ -668,23 +499,13 @@ if WIN: # Windows has a smaller terminal DEFAULT_COLUMNS = 79 - from ._winconsole import _get_windows_console_stream, _wrap_std_stream + from ._winconsole import _get_windows_console_stream def _get_argv_encoding(): import locale return locale.getpreferredencoding() - if PY2: - - def raw_input(prompt=""): - sys.stderr.flush() - if prompt: - stdout = _default_text_stdout() - stdout.write(prompt) - stdin = _default_text_stdin() - return stdin.readline().rstrip("\r\n") - try: import colorama except ImportError: @@ -712,7 +533,7 @@ if WIN: def _safe_write(s): try: return _write(s) - except: + except BaseException: ansi_wrapper.reset_all() raise @@ -735,8 +556,8 @@ else: def _get_argv_encoding(): return getattr(sys.stdin, "encoding", None) or get_filesystem_encoding() - _get_windows_console_stream = lambda *x: None - _wrap_std_stream = lambda *x: None + def _get_windows_console_stream(f, encoding, errors): + return None def term_len(x): diff --git a/src/click/_termui_impl.py b/src/click/_termui_impl.py index cdc6fab..1a6adf1 100644 --- a/src/click/_termui_impl.py +++ b/src/click/_termui_impl.py @@ -7,17 +7,15 @@ placed in this module and only imported as needed. import contextlib import math import os +import shlex import sys import time from ._compat import _default_text_stdout from ._compat import CYGWIN from ._compat import get_best_encoding -from ._compat import int_types from ._compat import isatty from ._compat import open_stream -from ._compat import range_type -from ._compat import shlex_quote from ._compat import strip_ansi from ._compat import term_len from ._compat import WIN @@ -45,7 +43,7 @@ def _length_hint(obj): hint = get_hint(obj) except TypeError: return None - if hint is NotImplemented or not isinstance(hint, int_types) or hint < 0: + if hint is NotImplemented or not isinstance(hint, int) or hint < 0: return None return hint @@ -89,7 +87,7 @@ class ProgressBar(object): if iterable is None: if length is None: raise TypeError("iterable or length is required") - iterable = range_type(length) + iterable = range(length) self.iter = iter(iterable) self.length = length self.length_known = length is not None @@ -361,7 +359,7 @@ def pager(generator, color=None): try: if ( hasattr(os, "system") - and os.system("more {}".format(shlex_quote(filename))) == 0 + and os.system("more {}".format(shlex.quote(filename))) == 0 ): return _pipepager(generator, "more", color) return _nullpager(stdout, generator, color) @@ -431,7 +429,7 @@ def _tempfilepager(generator, cmd, color): with open_stream(filename, "wb")[0] as f: f.write(text.encode(encoding)) try: - os.system("{} {}".format(shlex_quote(cmd), shlex_quote(filename))) + os.system("{} {}".format(shlex.quote(cmd), shlex.quote(filename))) finally: os.unlink(filename) @@ -476,7 +474,7 @@ class Editor(object): environ = None try: c = subprocess.Popen( - "{} {}".format(shlex_quote(editor), shlex_quote(filename)), + "{} {}".format(shlex.quote(editor), shlex.quote(filename)), env=environ, shell=True, ) @@ -553,16 +551,16 @@ def open_url(url, wait=False, locate=False): elif WIN: if locate: url = _unquote_file(url) - args = "explorer /select,{}".format(shlex_quote(url)) + args = "explorer /select,{}".format(shlex.quote(url)) else: - args = 'start {} "" {}'.format("/WAIT" if wait else "", shlex_quote(url)) + args = 'start {} "" {}'.format("/WAIT" if wait else "", shlex.quote(url)) return os.system(args) elif CYGWIN: if locate: url = _unquote_file(url) - args = "cygstart {}".format(shlex_quote(os.path.dirname(url))) + args = "cygstart {}".format(shlex.quote(os.path.dirname(url))) else: - args = "cygstart {} {}".format("-w" if wait else "", shlex_quote(url)) + args = "cygstart {} {}".format("-w" if wait else "", shlex.quote(url)) return os.system(args) try: diff --git a/src/click/_unicodefun.py b/src/click/_unicodefun.py index 781c365..57545e0 100644 --- a/src/click/_unicodefun.py +++ b/src/click/_unicodefun.py @@ -1,58 +1,9 @@ import codecs import os -import sys - -from ._compat import PY2 - - -def _find_unicode_literals_frame(): - import __future__ - - if not hasattr(sys, "_getframe"): # not all Python implementations have it - return 0 - frm = sys._getframe(1) - idx = 1 - while frm is not None: - if frm.f_globals.get("__name__", "").startswith("click."): - frm = frm.f_back - idx += 1 - elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag: - return idx - else: - break - return 0 - - -def _check_for_unicode_literals(): - if not __debug__: - return - - from . import disable_unicode_literals_warning - - if not PY2 or disable_unicode_literals_warning: - return - bad_frame = _find_unicode_literals_frame() - if bad_frame <= 0: - return - from warnings import warn - - warn( - Warning( - "Click detected the use of the unicode_literals __future__" - " import. This is heavily discouraged because it can" - " introduce subtle bugs in your code. You should instead" - ' use explicit u"" literals for your unicode strings. For' - " more information see" - " https://click.palletsprojects.com/python3/" - ), - stacklevel=bad_frame, - ) def _verify_python3_env(): """Ensures that the environment is good for unicode on Python 3.""" - if PY2: - return try: import locale diff --git a/src/click/_winconsole.py b/src/click/_winconsole.py index cae8ded..18310ad 100644 --- a/src/click/_winconsole.py +++ b/src/click/_winconsole.py @@ -9,10 +9,7 @@ # echo and prompt. import ctypes import io -import os -import sys import time -import zlib from ctypes import byref from ctypes import c_char from ctypes import c_char_p @@ -23,7 +20,6 @@ from ctypes import c_void_p from ctypes import POINTER from ctypes import py_object from ctypes import windll -from ctypes import WinError from ctypes import WINFUNCTYPE from ctypes.wintypes import DWORD from ctypes.wintypes import HANDLE @@ -33,8 +29,6 @@ from ctypes.wintypes import LPWSTR import msvcrt from ._compat import _NonClosingTextIOWrapper -from ._compat import PY2 -from ._compat import text_type try: from ctypes import pythonapi @@ -97,9 +91,6 @@ class Py_buffer(ctypes.Structure): ("internal", c_void_p), ] - if PY2: - _fields_.insert(-1, ("smalltable", c_ssize_t * 2)) - # On PyPy we cannot get buffers so our ability to operate here is # severely limited. @@ -204,7 +195,7 @@ class ConsoleStream(object): return self.buffer.name def write(self, x): - if isinstance(x, text_type): + if isinstance(x, str): return self._text_stream.write(x) try: self.flush() @@ -253,20 +244,6 @@ class WindowsChunkedWriter(object): written += to_write -_wrapped_std_streams = set() - - -def _wrap_std_stream(name): - # Python 2 & Windows 7 and below - if ( - PY2 - and sys.getwindowsversion()[:2] <= (6, 1) - and name not in _wrapped_std_streams - ): - setattr(sys, name, WindowsChunkedWriter(getattr(sys, name))) - _wrapped_std_streams.add(name) - - def _get_text_stdin(buffer_stream): text_stream = _NonClosingTextIOWrapper( io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), @@ -297,37 +274,6 @@ def _get_text_stderr(buffer_stream): return ConsoleStream(text_stream, buffer_stream) -if PY2: - - def _hash_py_argv(): - return zlib.crc32("\x00".join(sys.argv[1:])) - - _initial_argv_hash = _hash_py_argv() - - def _get_windows_argv(): - argc = c_int(0) - argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc)) - if not argv_unicode: - raise WinError() - try: - argv = [argv_unicode[i] for i in range(0, argc.value)] - finally: - LocalFree(argv_unicode) - del argv_unicode - - if not hasattr(sys, "frozen"): - argv = argv[1:] - while len(argv) > 0: - arg = argv[0] - if not arg.startswith("-") or arg == "-": - break - argv = argv[1:] - if arg.startswith(("-c", "-m")): - break - - return argv[1:] - - _stream_factories = { 0: _get_text_stdin, 1: _get_text_stdout, @@ -351,20 +297,15 @@ def _is_console(f): def _get_windows_console_stream(f, encoding, errors): if ( get_buffer is not None - and encoding in ("utf-16-le", None) - and errors in ("strict", None) + and encoding in {"utf-16-le", None} + and errors in {"strict", None} and _is_console(f) ): func = _stream_factories.get(f.fileno()) if func is not None: - if not PY2: - f = getattr(f, "buffer", None) - if f is None: - return None - else: - # If we are on Python 2 we need to set the stream that we - # deal with to binary mode as otherwise the exercise if a - # bit moot. The same problems apply as for - # get_binary_stdin and friends from _compat. - msvcrt.setmode(f.fileno(), os.O_BINARY) + f = getattr(f, "buffer", None) + + if f is None: + return None + return func(f) diff --git a/src/click/core.py b/src/click/core.py index 5e8bf6c..1f88902 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -6,11 +6,6 @@ from contextlib import contextmanager from functools import update_wrapper from itertools import repeat -from ._compat import isidentifier -from ._compat import iteritems -from ._compat import PY2 -from ._compat import string_types -from ._unicodefun import _check_for_unicode_literals from ._unicodefun import _verify_python3_env from .exceptions import Abort from .exceptions import BadParameter @@ -31,7 +26,6 @@ from .types import BOOL from .types import convert_type from .types import IntRange from .utils import echo -from .utils import get_os_args from .utils import make_default_short_help from .utils import make_str from .utils import PacifyFlushWrapper @@ -736,7 +730,7 @@ class BaseCommand(object): :param extra: extra keyword arguments forwarded to the context constructor. """ - for key, value in iteritems(self.context_settings): + for key, value in self.context_settings.items(): if key not in extra: extra[key] = value ctx = Context(self, info_name=info_name, parent=parent, **extra) @@ -797,16 +791,12 @@ class BaseCommand(object): :param extra: extra keyword arguments are forwarded to the context constructor. See :class:`Context` for more information. """ - # If we are in Python 3, we will verify that the environment is - # sane at this point or reject further execution to avoid a - # broken script. - if not PY2: - _verify_python3_env() - else: - _check_for_unicode_literals() + # Verify that the environment is configured correctly, or reject + # further execution to avoid a broken script. + _verify_python3_env() if args is None: - args = get_os_args() + args = sys.argv[1:] else: args = list(args) @@ -1841,7 +1831,7 @@ class Option(Parameter): possible_names = [] for decl in decls: - if isidentifier(decl): + if decl.isidentifier(): if name is not None: raise TypeError("Name defined twice") name = decl @@ -1863,7 +1853,7 @@ class Option(Parameter): if name is None and possible_names: possible_names.sort(key=lambda x: -len(x[0])) # group long options first name = possible_names[0][1].replace("-", "_").lower() - if not isidentifier(name): + if not name.isidentifier(): name = None if name is None: @@ -1942,7 +1932,7 @@ class Option(Parameter): ) ) if self.default is not None and (self.show_default or ctx.show_default): - if isinstance(self.show_default, string_types): + if isinstance(self.show_default, str): default_string = "({})".format(self.show_default) elif isinstance(self.default, (list, tuple)): default_string = ", ".join(str(d) for d in self.default) diff --git a/src/click/decorators.py b/src/click/decorators.py index c7b5af6..e0596c8 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -2,8 +2,6 @@ import inspect import sys from functools import update_wrapper -from ._compat import iteritems -from ._unicodefun import _check_for_unicode_literals from .core import Argument from .core import Command from .core import Group @@ -94,7 +92,6 @@ def _make_command(f, name, attrs, cls): else: help = inspect.cleandoc(help) attrs["help"] = help - _check_for_unicode_literals() return cls( name=name or f.__name__.lower().replace("_", "-"), callback=f, @@ -287,7 +284,7 @@ def version_option(version=None, *param_decls, **attrs): else: for dist in pkg_resources.working_set: scripts = dist.get_entry_map().get("console_scripts") or {} - for _, entry_point in iteritems(scripts): + for entry_point in scripts.values(): if entry_point.module_name == module: ver = dist.version break diff --git a/src/click/exceptions.py b/src/click/exceptions.py index 592ee38..f75f4db 100644 --- a/src/click/exceptions.py +++ b/src/click/exceptions.py @@ -1,6 +1,5 @@ from ._compat import filename_to_ui from ._compat import get_text_stderr -from ._compat import PY2 from .utils import echo @@ -13,15 +12,11 @@ def _join_param_hints(param_hint): class ClickException(Exception): """An exception that Click can handle and show to the user.""" - #: The exit code for this exception + #: The exit code for this exception. exit_code = 1 def __init__(self, message): - ctor_msg = message - if PY2: - if ctor_msg is not None: - ctor_msg = ctor_msg.encode("utf-8") - Exception.__init__(self, ctor_msg) + super().__init__(message) self.message = message def format_message(self): @@ -30,12 +25,6 @@ class ClickException(Exception): def __str__(self): return self.message - if PY2: - __unicode__ = __str__ - - def __str__(self): - return self.message.encode("utf-8") - def show(self, file=None): if file is None: file = get_text_stderr() @@ -162,12 +151,6 @@ class MissingParameter(BadParameter): else: return self.message - if PY2: - __unicode__ = __str__ - - def __str__(self): - return self.__unicode__().encode("utf-8") - class NoSuchOption(UsageError): """Raised if click attempted to handle an option that does not diff --git a/src/click/termui.py b/src/click/termui.py index 7f29eb6..73f35f5 100644 --- a/src/click/termui.py +++ b/src/click/termui.py @@ -8,10 +8,7 @@ import sys from ._compat import DEFAULT_COLUMNS from ._compat import get_winterm_size from ._compat import isatty -from ._compat import raw_input -from ._compat import string_types from ._compat import strip_ansi -from ._compat import text_type from ._compat import WIN from .exceptions import Abort from .exceptions import UsageError @@ -24,7 +21,7 @@ from .utils import LazyFile # The prompt functions to use. The doc tools currently override these # functions to customize how they work. -visible_prompt_func = raw_input +visible_prompt_func = input _ansi_colors = { "black": 30, @@ -278,13 +275,13 @@ def echo_via_pager(text_or_generator, color=None): if inspect.isgeneratorfunction(text_or_generator): i = text_or_generator() - elif isinstance(text_or_generator, string_types): + elif isinstance(text_or_generator, str): i = [text_or_generator] else: i = iter(text_or_generator) # convert every element of i to a text type if necessary - text_generator = (el if isinstance(el, string_types) else text_type(el) for el in i) + text_generator = (el if isinstance(el, str) else str(el) for el in i) from ._termui_impl import pager diff --git a/src/click/testing.py b/src/click/testing.py index a3dba3b..9c3c012 100644 --- a/src/click/testing.py +++ b/src/click/testing.py @@ -1,4 +1,5 @@ import contextlib +import io import os import shlex import shutil @@ -8,16 +9,7 @@ import tempfile from . import formatting from . import termui from . import utils -from ._compat import iteritems -from ._compat import PY2 -from ._compat import string_types - - -if PY2: - from cStringIO import StringIO -else: - import io - from ._compat import _find_binary_reader +from ._compat import _find_binary_reader class EchoingStdin(object): @@ -51,19 +43,18 @@ class EchoingStdin(object): def make_input_stream(input, charset): # Is already an input stream. if hasattr(input, "read"): - if PY2: - return input rv = _find_binary_reader(input) + if rv is not None: return rv + raise TypeError("Could not find binary reader for input stream.") if input is None: input = b"" elif not isinstance(input, bytes): input = input.encode(charset) - if PY2: - return StringIO(input) + return io.BytesIO(input) @@ -184,23 +175,17 @@ class CliRunner(object): env = self.make_env(env) - if PY2: - bytes_output = StringIO() - if self.echo_stdin: - input = EchoingStdin(input, bytes_output) - sys.stdout = bytes_output - if not self.mix_stderr: - bytes_error = StringIO() - sys.stderr = bytes_error - else: - bytes_output = io.BytesIO() - if self.echo_stdin: - input = EchoingStdin(input, bytes_output) - input = io.TextIOWrapper(input, encoding=self.charset) - sys.stdout = io.TextIOWrapper(bytes_output, encoding=self.charset) - if not self.mix_stderr: - bytes_error = io.BytesIO() - sys.stderr = io.TextIOWrapper(bytes_error, encoding=self.charset) + bytes_output = io.BytesIO() + + if self.echo_stdin: + input = EchoingStdin(input, bytes_output) + + input = io.TextIOWrapper(input, encoding=self.charset) + sys.stdout = io.TextIOWrapper(bytes_output, encoding=self.charset) + + if not self.mix_stderr: + bytes_error = io.BytesIO() + sys.stderr = io.TextIOWrapper(bytes_error, encoding=self.charset) if self.mix_stderr: sys.stderr = sys.stdout @@ -244,7 +229,7 @@ class CliRunner(object): old_env = {} try: - for key, value in iteritems(env): + for key, value in env.items(): old_env[key] = os.environ.get(key) if value is None: try: @@ -255,7 +240,7 @@ class CliRunner(object): os.environ[key] = value yield (bytes_output, not self.mix_stderr and bytes_error) finally: - for key, value in iteritems(old_env): + for key, value in old_env.items(): if value is None: try: del os.environ[key] @@ -317,7 +302,7 @@ class CliRunner(object): exception = None exit_code = 0 - if isinstance(args, string_types): + if isinstance(args, str): args = shlex.split(args) try: diff --git a/src/click/types.py b/src/click/types.py index 505c39f..5647df5 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -5,10 +5,8 @@ from datetime import datetime from ._compat import _get_argv_encoding from ._compat import filename_to_ui from ._compat import get_filesystem_encoding -from ._compat import get_streerror +from ._compat import get_strerror from ._compat import open_stream -from ._compat import PY2 -from ._compat import text_type from .exceptions import BadParameter from .utils import LazyFile from .utils import safecall @@ -94,9 +92,10 @@ class FuncParamType(ParamType): return self.func(value) except ValueError: try: - value = text_type(value) + value = str(value) except UnicodeError: - value = str(value).decode("utf-8", "replace") + value = value.decode("utf-8", "replace") + self.fail(value, param, ctx) @@ -179,14 +178,9 @@ class Choice(ParamType): } if not self.case_sensitive: - if PY2: - lower = str.lower - else: - lower = str.casefold - - normed_value = lower(normed_value) + normed_value = normed_value.casefold() normed_choices = { - lower(normed_choice): original + normed_choice.casefold(): original for normed_choice, original in normed_choices.items() } @@ -427,8 +421,6 @@ class UUIDParameterType(ParamType): import uuid try: - if PY2 and isinstance(value, text_type): - value = value.encode("ascii") return uuid.UUID(value) except ValueError: self.fail("{} is not a valid UUID value".format(value), param, ctx) @@ -517,7 +509,7 @@ class File(ParamType): except (IOError, OSError) as e: # noqa: B014 self.fail( "Could not open file: {}: {}".format( - filename_to_ui(value), get_streerror(e) + filename_to_ui(value), get_strerror(e) ), param, ctx, @@ -589,7 +581,7 @@ class Path(ParamType): def coerce_path_result(self, rv): if self.type is not None and not isinstance(rv, self.type): - if self.type is text_type: + if self.type is str: rv = rv.decode(get_filesystem_encoding()) else: rv = rv.encode(get_filesystem_encoding()) @@ -701,7 +693,7 @@ def convert_type(ty, default=None): return Tuple(ty) if isinstance(ty, ParamType): return ty - if ty is text_type or ty is str or ty is None: + if ty is str or ty is None: return STRING if ty is int: return INT diff --git a/src/click/utils.py b/src/click/utils.py index 79265e7..f5aac49 100644 --- a/src/click/utils.py +++ b/src/click/utils.py @@ -3,30 +3,22 @@ import sys from ._compat import _default_text_stderr from ._compat import _default_text_stdout +from ._compat import _find_binary_writer from ._compat import auto_wrap_for_ansi from ._compat import binary_streams from ._compat import filename_to_ui from ._compat import get_filesystem_encoding -from ._compat import get_streerror +from ._compat import get_strerror from ._compat import is_bytes from ._compat import open_stream -from ._compat import PY2 from ._compat import should_strip_ansi -from ._compat import string_types from ._compat import strip_ansi from ._compat import text_streams -from ._compat import text_type from ._compat import WIN from .globals import resolve_color_default -if not PY2: - from ._compat import _find_binary_writer -elif WIN: - from ._winconsole import _get_windows_argv - from ._winconsole import _hash_py_argv - from ._winconsole import _initial_argv_hash -echo_native_types = string_types + (bytes, bytearray) +echo_native_types = (str, bytes, bytearray) def _posixify(name): @@ -52,7 +44,7 @@ def make_str(value): return value.decode(get_filesystem_encoding()) except UnicodeError: return value.decode("utf-8", "replace") - return text_type(value) + return str(value) def make_default_short_help(help, max_length=45): @@ -129,7 +121,7 @@ class LazyFile(object): except (IOError, OSError) as e: # noqa: E402 from .exceptions import FileError - raise FileError(self.name, hint=get_streerror(e)) + raise FileError(self.name, hint=get_strerror(e)) self._f = rv return rv @@ -231,11 +223,11 @@ def echo(message=None, file=None, nl=True, err=False, color=None): # Convert non bytes/text into the native string type. if message is not None and not isinstance(message, echo_native_types): - message = text_type(message) + message = str(message) if nl: message = message or u"" - if isinstance(message, text_type): + if isinstance(message, str): message += u"\n" else: message += b"\n" @@ -245,7 +237,7 @@ def echo(message=None, file=None, nl=True, err=False, color=None): # message in there. This is done separately so that most stream # types will work as you would expect. Eg: you can write to StringIO # for other cases. - if message and not PY2 and is_bytes(message): + if message and is_bytes(message): binary_file = _find_binary_writer(file) if binary_file is not None: file.flush() @@ -340,23 +332,21 @@ def open_file( def get_os_args(): - """This returns the argument part of sys.argv in the most appropriate - form for processing. What this means is that this return value is in - a format that works for Click to process but does not necessarily - correspond well to what's actually standard for the interpreter. - - On most environments the return value is ``sys.argv[:1]`` unchanged. - However if you are on Windows and running Python 2 the return value - will actually be a list of unicode strings instead because the - default behavior on that platform otherwise will not be able to - carry all possible values that sys.argv can have. - - .. versionadded:: 6.0 + """Returns the argument part of ``sys.argv``, removing the first + value which is the name of the script. + + .. deprecated:: 8.0 + Will be removed in 8.1. Access ``sys.argv[1:]`` directly + instead. """ - # We can only extract the unicode argv if sys.argv has not been - # changed since the startup of the application. - if PY2 and WIN and _initial_argv_hash == _hash_py_argv(): - return _get_windows_argv() + import warnings + + warnings.warn( + "'get_os_args' is deprecated and will be removed in 8.1. Access" + " 'sys.argv[1:]' directly instead.", + DeprecationWarning, + stacklevel=2, + ) return sys.argv[1:] diff --git a/tests/test_arguments.py b/tests/test_arguments.py index 0b510c0..bb580fc 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -4,8 +4,6 @@ import sys import pytest import click -from click._compat import PY2 -from click._compat import text_type def test_nargs_star(runner): @@ -83,18 +81,13 @@ def test_bytes_args(runner, monkeypatch): @click.argument("arg") def from_bytes(arg): assert isinstance( - arg, text_type + arg, str ), "UTF-8 encoded argument should be implicitly converted to Unicode" # Simulate empty locale environment variables - if PY2: - monkeypatch.setattr(sys.stdin, "encoding", "ANSI_X3.4-1968") - monkeypatch.setattr(sys, "getfilesystemencoding", lambda: "ANSI_X3.4-1968") - monkeypatch.setattr(sys, "getdefaultencoding", lambda: "ascii") - else: - monkeypatch.setattr(sys.stdin, "encoding", "utf-8") - monkeypatch.setattr(sys, "getfilesystemencoding", lambda: "utf-8") - monkeypatch.setattr(sys, "getdefaultencoding", lambda: "utf-8") + monkeypatch.setattr(sys.stdin, "encoding", "utf-8") + monkeypatch.setattr(sys, "getfilesystemencoding", lambda: "utf-8") + monkeypatch.setattr(sys, "getdefaultencoding", lambda: "utf-8") runner.invoke( from_bytes, @@ -268,7 +261,7 @@ def test_nargs_star_ordering(runner): click.echo(arg) result = runner.invoke(cmd, ["a", "b", "c"]) - assert result.output.splitlines() == ["(u'a',)" if PY2 else "('a',)", "b", "c"] + assert result.output.splitlines() == ["('a',)", "b", "c"] def test_nargs_specified_plus_star_ordering(runner): @@ -281,11 +274,7 @@ def test_nargs_specified_plus_star_ordering(runner): click.echo(arg) result = runner.invoke(cmd, ["a", "b", "c", "d", "e", "f"]) - assert result.output.splitlines() == [ - "(u'a', u'b', u'c')" if PY2 else "('a', 'b', 'c')", - "d", - "(u'e', u'f')" if PY2 else "('e', 'f')", - ] + assert result.output.splitlines() == ["('a', 'b', 'c')", "d", "('e', 'f')"] def test_defaults_for_nargs(runner): diff --git a/tests/test_options.py b/tests/test_options.py index 4baa374..3eea5e6 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -5,7 +5,6 @@ import re import pytest import click -from click._compat import text_type def test_prefixes(runner): @@ -170,11 +169,11 @@ def test_multiple_default_type(runner): @click.option("--arg1", multiple=True, default=("foo", "bar")) @click.option("--arg2", multiple=True, default=(1, "a")) def cmd(arg1, arg2): - assert all(isinstance(e[0], text_type) for e in arg1) - assert all(isinstance(e[1], text_type) for e in arg1) + assert all(isinstance(e[0], str) for e in arg1) + assert all(isinstance(e[1], str) for e in arg1) assert all(isinstance(e[0], int) for e in arg2) - assert all(isinstance(e[1], text_type) for e in arg2) + assert all(isinstance(e[1], str) for e in arg2) result = runner.invoke( cmd, "--arg1 a b --arg1 test 1 --arg2 2 two --arg2 4 four".split() diff --git a/tests/test_testing.py b/tests/test_testing.py index 22a285d..99701b5 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,19 +1,13 @@ import os import sys +from io import BytesIO import pytest import click -from click._compat import PY2 from click._compat import WIN from click.testing import CliRunner -# Use the most reasonable io that users would use for the python version. -if PY2: - from cStringIO import StringIO as ReasonableBytesIO -else: - from io import BytesIO as ReasonableBytesIO - def test_runner(): @click.command() @@ -51,12 +45,12 @@ def test_runner_with_stream(): o.flush() runner = CliRunner() - result = runner.invoke(test, input=ReasonableBytesIO(b"Hello World!\n")) + result = runner.invoke(test, input=BytesIO(b"Hello World!\n")) assert not result.exception assert result.output == "Hello World!\n" runner = CliRunner(echo_stdin=True) - result = runner.invoke(test, input=ReasonableBytesIO(b"Hello World!\n")) + result = runner.invoke(test, input=BytesIO(b"Hello World!\n")) assert not result.exception assert result.output == "Hello World!\nHello World!\n" |
