diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index 03d5de4ab..252472fbc 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -12,8 +12,7 @@ requirements: run: - python - - paramiko<2.4 # [py26] - - paramiko # [not py26] + - paramiko build: number: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }} @@ -35,8 +34,7 @@ test: requires: # Put any additional test requirements here. For example - pytest - - paramiko<2.4 # [py26] - - paramiko # [not py26] + - paramiko about: home: https://plumbum.readthedocs.io diff --git a/noxfile.py b/noxfile.py index 37ff78db0..0ed930890 100644 --- a/noxfile.py +++ b/noxfile.py @@ -2,7 +2,7 @@ import nox -ALL_PYTHONS = ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9"] +ALL_PYTHONS = ["3.6", "3.7", "3.8", "3.9", "3.10"] nox.options.sessions = ["lint", "tests"] diff --git a/plumbum/__init__.py b/plumbum/__init__.py index 87c228253..8e83988e0 100644 --- a/plumbum/__init__.py +++ b/plumbum/__init__.py @@ -86,11 +86,7 @@ # =================================================================================================== import sys from types import ModuleType - -try: - from typing import List -except ImportError: - pass +from typing import List class LocalModule(ModuleType): diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index 675487967..cb6499212 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -1,4 +1,5 @@ import functools +import inspect import os import sys from collections import defaultdict @@ -6,7 +7,7 @@ from plumbum import colors, local from plumbum.cli.i18n import get_translation_for -from plumbum.lib import getdoc, six +from plumbum.lib import getdoc from .switches import ( CountOf, @@ -503,8 +504,8 @@ def _validate_args(self, swfuncs, tailargs): ) ) - m = six.getfullargspec(self.main) - max_args = six.MAXSIZE if m.varargs else len(m.args) - 1 + m = inspect.getfullargspec(self.main) + max_args = sys.maxsize if m.varargs else len(m.args) - 1 min_args = len(m.args) - 1 - (len(m.defaults) if m.defaults else 0) if len(tailargs) < min_args: raise PositionalArgumentsError( @@ -523,7 +524,7 @@ def _validate_args(self, swfuncs, tailargs): ).format(max_args, tailargs) ) - # Positional arguement validataion + # Positional argument validation if hasattr(self.main, "positional"): tailargs = self._positional_validate( tailargs, @@ -850,7 +851,7 @@ def wrapped_paragraphs(text, width): for line in wrapped_paragraphs(self.DESCRIPTION_MORE, cols): print(line) - m = six.getfullargspec(self.main) + m = inspect.getfullargspec(self.main) tailargs = m.args[1:] # skip self if m.defaults: for i, d in enumerate(reversed(m.defaults)): diff --git a/plumbum/cli/config.py b/plumbum/cli/config.py index 636f23fcd..c01e7efdb 100644 --- a/plumbum/cli/config.py +++ b/plumbum/cli/config.py @@ -1,13 +1,11 @@ -import sys -from abc import abstractmethod +from abc import ABC, abstractmethod +from configparser import ConfigParser, NoOptionError, NoSectionError from plumbum import local -from plumbum.lib import _setdoc, six - -from configparser import ConfigParser, NoOptionError, NoSectionError +from plumbum.lib import _setdoc -class ConfigBase(six.ABC): +class ConfigBase(ABC): """Base class for Config parsers. :param filename: The file to use diff --git a/plumbum/cli/i18n.py b/plumbum/cli/i18n.py index 5c40c791c..f9847c3fe 100644 --- a/plumbum/cli/i18n.py +++ b/plumbum/cli/i18n.py @@ -27,10 +27,7 @@ def get_translation_for(package_name): except ImportError: pkg_resources = None - try: - from typing import Callable, List, Tuple - except ImportError: - pass + from typing import Callable, List, Tuple local_dir = os.path.basename(__file__) diff --git a/plumbum/cli/progress.py b/plumbum/cli/progress.py index 0bc8b1755..dbb22c7b8 100644 --- a/plumbum/cli/progress.py +++ b/plumbum/cli/progress.py @@ -6,13 +6,12 @@ import datetime import sys import warnings -from abc import abstractmethod +from abc import ABC, abstractmethod from plumbum.cli.termsize import get_terminal_size -from plumbum.lib import six -class ProgressBase(six.ABC): +class ProgressBase(ABC): """Base class for progress bars. Customize for types of progress bars. :param iterator: The iterator to wrap with a progress bar diff --git a/plumbum/cli/switches.py b/plumbum/cli/switches.py index 89c192842..d6699b837 100644 --- a/plumbum/cli/switches.py +++ b/plumbum/cli/switches.py @@ -1,8 +1,9 @@ -from abc import abstractmethod +import inspect +from abc import ABC, abstractmethod from plumbum import local from plumbum.cli.i18n import get_translation_for -from plumbum.lib import getdoc, six +from plumbum.lib import getdoc _translation = get_translation_for(__name__) _, ngettext = _translation.gettext, _translation.ngettext @@ -172,7 +173,7 @@ def set_terse(self): def deco(func): if argname is None: - argspec = six.getfullargspec(func).args + argspec = inspect.getfullargspec(func).args if len(argspec) == 2: argname2 = argspec[1] else: @@ -364,7 +365,7 @@ def __init__(self, *args, **kargs): self.kargs = kargs def __call__(self, function): - m = six.getfullargspec(function) + m = inspect.getfullargspec(function) args_names = list(m.args[1:]) positional = [None] * len(args_names) @@ -388,7 +389,7 @@ def __call__(self, function): return function -class Validator(six.ABC): +class Validator(ABC): __slots__ = () @abstractmethod @@ -497,14 +498,10 @@ def __call__(self, value, check_csv=True): return opt(value) except ValueError: pass - raise ValueError( - f"Invalid value: {value} (Expected one of {self.values})" - ) + raise ValueError(f"Invalid value: {value} (Expected one of {self.values})") def choices(self, partial=""): - choices = { - opt if isinstance(opt, str) else f"({opt})" for opt in self.values - } + choices = {opt if isinstance(opt, str) else f"({opt})" for opt in self.values} if partial: choices = {opt for opt in choices if opt.lower().startswith(partial)} return choices @@ -542,9 +539,7 @@ def ExistingDirectory(val): def MakeDirectory(val): p = local.path(val) if p.is_file(): - raise ValueError( - f"{val} is a file, should be nonexistent, or a directory" - ) + raise ValueError(f"{val} is a file, should be nonexistent, or a directory") elif not p.exists(): p.mkdir() return p diff --git a/plumbum/colorlib/_ipython_ext.py b/plumbum/colorlib/_ipython_ext.py index ebcffda65..707be59e8 100644 --- a/plumbum/colorlib/_ipython_ext.py +++ b/plumbum/colorlib/_ipython_ext.py @@ -1,11 +1,9 @@ import sys +from io import StringIO import IPython.display from IPython.core.magic import Magics, cell_magic, magics_class, needs_local_scope -from io import StringIO - - valid_choices = [x[8:] for x in dir(IPython.display) if "display_" == x[:8]] diff --git a/plumbum/colorlib/names.py b/plumbum/colorlib/names.py index 7f3e1bb54..318beb159 100644 --- a/plumbum/colorlib/names.py +++ b/plumbum/colorlib/names.py @@ -306,15 +306,9 @@ _base_pattern = [(n // 4, n // 2 % 2, n % 2) for n in range(8)] _base_html = ( - [ - f"#{x[2] * 192:02x}{x[1] * 192:02x}{x[0] * 192:02x}" - for x in _base_pattern - ] + [f"#{x[2] * 192:02x}{x[1] * 192:02x}{x[0] * 192:02x}" for x in _base_pattern] + ["#808080"] - + [ - f"#{x[2] * 255:02x}{x[1] * 255:02x}{x[0] * 255:02x}" - for x in _base_pattern - ][1:] + + [f"#{x[2] * 255:02x}{x[1] * 255:02x}{x[0] * 255:02x}" for x in _base_pattern][1:] ) color_html = _base_html + _normal_html + _grey_html diff --git a/plumbum/colorlib/styles.py b/plumbum/colorlib/styles.py index cdf6cdf4d..bc06f79dd 100644 --- a/plumbum/colorlib/styles.py +++ b/plumbum/colorlib/styles.py @@ -12,8 +12,9 @@ import platform import re import sys -from abc import ABCMeta, abstractmethod +from abc import ABC, ABCMeta, abstractmethod from copy import copy +from typing import IO, Dict, Union from .names import ( FindNearest, @@ -24,13 +25,6 @@ from_html, ) -from abc import ABC - -try: - from typing import IO, Dict, Union -except ImportError: - pass - __all__ = [ "Color", "Style", diff --git a/plumbum/commands/base.py b/plumbum/commands/base.py index e5fb16d21..095cfee10 100644 --- a/plumbum/commands/base.py +++ b/plumbum/commands/base.py @@ -9,7 +9,6 @@ import plumbum.commands.modifiers from plumbum.commands.processes import iter_lines, run_proc -from plumbum.lib import six class RedirectionError(Exception): @@ -27,7 +26,7 @@ class RedirectionError(Exception): def shquote(text): """Quotes the given text with shell escaping (assumes as syntax similar to ``sh``)""" - text = six.str(text) + text = str(text) return shlex.quote(text) @@ -341,7 +340,7 @@ def popen(self, args=(), cwd=None, env=None, **kwargs): args, cwd=self.cwd if cwd is None else cwd, env=dict(self.env, **env), - **kwargs + **kwargs, ) @@ -530,7 +529,7 @@ def popen(self, args=(), **kwargs): if "stdin" in kwargs and kwargs["stdin"] != PIPE: raise RedirectionError("stdin is already redirected") data = self.data - if isinstance(data, six.unicode_type) and self._get_encoding() is not None: + if isinstance(data, str) and self._get_encoding() is not None: data = data.encode(self._get_encoding()) f = TemporaryFile() while data: @@ -564,7 +563,7 @@ def _get_encoding(self): return self.custom_encoding def formulate(self, level=0, args=()): - argv = [six.str(self.executable)] + argv = [str(self.executable)] for a in args: if a is None: continue @@ -575,10 +574,10 @@ def formulate(self, level=0, args=()): argv.extend(a.formulate(level + 1)) elif isinstance(a, (list, tuple)): argv.extend( - shquote(b) if level >= self.QUOTE_LEVEL else six.str(b) for b in a + shquote(b) if level >= self.QUOTE_LEVEL else str(b) for b in a ) else: - argv.append(shquote(a) if level >= self.QUOTE_LEVEL else six.str(a)) + argv.append(shquote(a) if level >= self.QUOTE_LEVEL else str(a)) # if self.custom_encoding: - # argv = [a.encode(self.custom_encoding) for a in argv if isinstance(a, six.string_types)] + # argv = [a.encode(self.custom_encoding) for a in argv if isinstance(a, str)] return argv diff --git a/plumbum/commands/processes.py b/plumbum/commands/processes.py index 9e6310a2d..abca937ce 100644 --- a/plumbum/commands/processes.py +++ b/plumbum/commands/processes.py @@ -2,13 +2,12 @@ import heapq import sys import time -from threading import Thread - -from plumbum.lib import IS_WIN32, six - from io import StringIO from queue import Empty as QueueEmpty from queue import Queue +from threading import Thread + +from plumbum.lib import IS_WIN32 # =================================================================================================== @@ -20,40 +19,23 @@ def _check_process(proc, retcode, timeout, stdout, stderr): def _iter_lines_posix(proc, decode, linesize, line_timeout=None): - try: - from selectors import EVENT_READ, DefaultSelector - except ImportError: - # Pre Python 3.4 implementation - from select import select - - def selector(): - while True: - rlist, _, _ = select([proc.stdout, proc.stderr], [], [], line_timeout) - if not rlist and line_timeout: - raise ProcessLineTimedOut( - "popen line timeout expired", - getattr(proc, "argv", None), - getattr(proc, "machine", None), - ) - for stream in rlist: - yield (stream is proc.stderr), decode(stream.readline(linesize)) - - else: - # Python 3.4 implementation - def selector(): - sel = DefaultSelector() - sel.register(proc.stdout, EVENT_READ, 0) - sel.register(proc.stderr, EVENT_READ, 1) - while True: - ready = sel.select(line_timeout) - if not ready and line_timeout: - raise ProcessLineTimedOut( - "popen line timeout expired", - getattr(proc, "argv", None), - getattr(proc, "machine", None), - ) - for key, mask in ready: - yield key.data, decode(key.fileobj.readline(linesize)) + from selectors import EVENT_READ, DefaultSelector + + # Python 3.4+ implementation + def selector(): + sel = DefaultSelector() + sel.register(proc.stdout, EVENT_READ, 0) + sel.register(proc.stderr, EVENT_READ, 1) + while True: + ready = sel.select(line_timeout) + if not ready and line_timeout: + raise ProcessLineTimedOut( + "popen line timeout expired", + getattr(proc, "argv", None), + getattr(proc, "machine", None), + ) + for key, mask in ready: + yield key.data, decode(key.fileobj.readline(linesize)) for ret in selector(): yield ret @@ -138,10 +120,10 @@ def __init__(self, argv, retcode, stdout, stderr, message=None): self.message = message self.argv = argv self.retcode = retcode - if six.PY3 and isinstance(stdout, six.bytes): - stdout = six.ascii(stdout) - if six.PY3 and isinstance(stderr, six.bytes): - stderr = six.ascii(stderr) + if isinstance(stdout, bytes): + stdout = ascii(stdout) + if isinstance(stderr, bytes): + stderr = ascii(stderr) self.stdout = stdout self.stderr = stderr diff --git a/plumbum/fs/atomic.py b/plumbum/fs/atomic.py index 53d48b241..5d6622046 100644 --- a/plumbum/fs/atomic.py +++ b/plumbum/fs/atomic.py @@ -8,7 +8,6 @@ import threading from contextlib import contextmanager -from plumbum.lib import six from plumbum.machines.local import local if not hasattr(threading, "get_ident"): @@ -98,11 +97,7 @@ def __init__(self, filename, ignore_deletion=False): self.reopen() def __repr__(self): - return ( - f"" - if self._fileobj - else "" - ) + return f"" if self._fileobj else "" def __del__(self): self.close() diff --git a/plumbum/lib.py b/plumbum/lib.py index 25248a26b..92c8c6462 100644 --- a/plumbum/lib.py +++ b/plumbum/lib.py @@ -31,70 +31,8 @@ def __repr__(self): ) -class six: - """ - A light-weight version of six (which works on IronPython) - """ - - PY3 = sys.version_info[0] >= 3 - from abc import ABC - - # Be sure to use named-tuple access, so that usage is not affected - try: - getfullargspec = staticmethod(inspect.getfullargspec) - except AttributeError: - getfullargspec = staticmethod( - inspect.getargspec - ) # extra fields will not be available - - if PY3: - integer_types = (int,) - string_types = (str,) - MAXSIZE = sys.maxsize - ascii = ascii # @UndefinedVariable - bytes = bytes # @ReservedAssignment - unicode_type = str - - @staticmethod - def b(s): - return s.encode("latin-1", "replace") - - @staticmethod - def u(s): - return s - - @staticmethod - def get_method_function(m): - return m.__func__ - - else: - integer_types = (int, long) - string_types = (str, unicode) - MAXSIZE = getattr(sys, "maxsize", sys.maxint) - ascii = repr # @ReservedAssignment - bytes = str # @ReservedAssignment - unicode_type = unicode - - @staticmethod - def b(st): - return st - - @staticmethod - def u(s): - return s.decode("unicode-escape") - - @staticmethod - def get_method_function(m): - return m.im_func - - str = unicode_type - - -# Try/except fails because io has the wrong StringIO in Python2 -# You'll get str/unicode errors -from io import StringIO - from glob import escape as glob_escape +from io import StringIO @contextmanager diff --git a/plumbum/machines/_windows.py b/plumbum/machines/_windows.py index 3a656e62a..0118c733f 100644 --- a/plumbum/machines/_windows.py +++ b/plumbum/machines/_windows.py @@ -1,7 +1,5 @@ import struct -from plumbum.lib import six - LFANEW_OFFSET = 30 * 2 FILE_HEADER_SIZE = 5 * 4 SUBSYSTEM_OFFSET = 17 * 4 diff --git a/plumbum/machines/local.py b/plumbum/machines/local.py index 58614f75f..7b12b9bc8 100644 --- a/plumbum/machines/local.py +++ b/plumbum/machines/local.py @@ -6,34 +6,19 @@ import sys import time from contextlib import contextmanager +from subprocess import PIPE, Popen from tempfile import mkdtemp from plumbum.commands import CommandNotFound, ConcreteCommand from plumbum.commands.daemons import posix_daemonize, win32_daemonize from plumbum.commands.processes import iter_lines -from plumbum.lib import IS_WIN32, ProcInfo, StaticProperty, six +from plumbum.lib import IS_WIN32, ProcInfo, StaticProperty from plumbum.machines.base import BaseMachine, PopenAddons from plumbum.machines.env import BaseEnv from plumbum.machines.session import ShellSession from plumbum.path.local import LocalPath, LocalWorkdir from plumbum.path.remote import RemotePath -if sys.version_info[0] >= 3: - # python 3 has the new-and-improved subprocess module - from subprocess import PIPE, Popen - - has_new_subprocess = True -else: - # otherwise, see if we have subprocess32 - try: - from subprocess32 import PIPE, Popen - - has_new_subprocess = True - except ImportError: - from subprocess import PIPE, Popen - - has_new_subprocess = False - class PlumbumLocalPopen(PopenAddons): iter_lines = iter_lines @@ -131,7 +116,7 @@ def popen(self, args=(), cwd=None, env=None, **kwargs): self.formulate(0, args), cwd=self.cwd if cwd is None else cwd, env=self.env if env is None else env, - **kwargs + **kwargs, ) @@ -258,22 +243,10 @@ def _popen( cwd=None, env=None, new_session=False, - **kwargs + **kwargs, ): if new_session: - if has_new_subprocess: - kwargs["start_new_session"] = True - elif IS_WIN32: - kwargs["creationflags"] = ( - kwargs.get("creationflags", 0) | subprocess.CREATE_NEW_PROCESS_GROUP - ) - else: - - def preexec_fn(prev_fn=kwargs.get("preexec_fn", lambda: None)): - os.setsid() - prev_fn() - - kwargs["preexec_fn"] = preexec_fn + kwargs["start_new_session"] = True if IS_WIN32 and "startupinfo" not in kwargs and stdin not in (sys.stdin, None): subsystem = get_pe_subsystem(str(executable)) @@ -293,15 +266,6 @@ def preexec_fn(prev_fn=kwargs.get("preexec_fn", lambda: None)): sui.dwFlags |= subprocess.STARTF_USESHOWWINDOW # @UndefinedVariable sui.wShowWindow = subprocess.SW_HIDE # @UndefinedVariable - if not has_new_subprocess and "close_fds" not in kwargs: - if IS_WIN32 and ( - stdin is not None or stdout is not None or stderr is not None - ): - # we can't close fds if we're on windows and we want to redirect any std handle - kwargs["close_fds"] = False - else: - kwargs["close_fds"] = True - if cwd is None: cwd = self.cwd @@ -326,7 +290,7 @@ def preexec_fn(prev_fn=kwargs.get("preexec_fn", lambda: None)): stderr=stderr, cwd=str(cwd), env=env, - **kwargs + **kwargs, ) # bufsize = 4096 proc._start_time = time.time() proc.custom_encoding = self.custom_encoding @@ -368,9 +332,6 @@ def list_processes(self): tasklist = local["tasklist"] output = tasklist("/V", "/FO", "CSV") - if not six.PY3: - # The Py2 csv reader does not support non-ascii values - output = output.encode("ascii", "ignore") lines = output.splitlines() rows = csv.reader(lines) header = next(rows) diff --git a/plumbum/machines/paramiko_machine.py b/plumbum/machines/paramiko_machine.py index 154cc3c7b..c53822e2d 100644 --- a/plumbum/machines/paramiko_machine.py +++ b/plumbum/machines/paramiko_machine.py @@ -6,7 +6,7 @@ from plumbum.commands.base import shquote from plumbum.commands.processes import ProcessLineTimedOut, iter_lines -from plumbum.lib import _setdoc, six +from plumbum.lib import _setdoc from plumbum.machines.base import PopenAddons from plumbum.machines.remote import BaseRemoteMachine from plumbum.machines.session import ShellSession @@ -422,7 +422,7 @@ def _path_read(self, fn): return data def _path_write(self, fn, data): - if self.custom_encoding and isinstance(data, six.unicode_type): + if self.custom_encoding and isinstance(data, str): data = data.encode(self.custom_encoding) f = self.sftp.open(str(fn), "wb") f.write(data) @@ -484,39 +484,22 @@ def recv(self, count): ################################################################################################### def _iter_lines(proc, decode, linesize, line_timeout=None): - try: - from selectors import EVENT_READ, DefaultSelector - except ImportError: - # Pre Python 3.4 implementation - from select import select - - def selector(): - while True: - rlist, _, _ = select([proc.stdout.channel], [], [], line_timeout) - if not rlist and line_timeout: - raise ProcessLineTimedOut( - "popen line timeout expired", - getattr(proc, "argv", None), - getattr(proc, "machine", None), - ) - for _ in rlist: - yield - - else: - # Python 3.4 implementation - def selector(): - sel = DefaultSelector() - sel.register(proc.stdout.channel, EVENT_READ) - while True: - ready = sel.select(line_timeout) - if not ready and line_timeout: - raise ProcessLineTimedOut( - "popen line timeout expired", - getattr(proc, "argv", None), - getattr(proc, "machine", None), - ) - for key, mask in ready: - yield + from selectors import EVENT_READ, DefaultSelector + + # Python 3.4+ implementation + def selector(): + sel = DefaultSelector() + sel.register(proc.stdout.channel, EVENT_READ) + while True: + ready = sel.select(line_timeout) + if not ready and line_timeout: + raise ProcessLineTimedOut( + "popen line timeout expired", + getattr(proc, "argv", None), + getattr(proc, "machine", None), + ) + for key, mask in ready: + yield for _ in selector(): if proc.stdout.channel.recv_ready(): diff --git a/plumbum/machines/remote.py b/plumbum/machines/remote.py index 3c5fd9fcc..d2e4b4fb6 100644 --- a/plumbum/machines/remote.py +++ b/plumbum/machines/remote.py @@ -3,7 +3,7 @@ from tempfile import NamedTemporaryFile from plumbum.commands import CommandNotFound, ConcreteCommand, shquote -from plumbum.lib import ProcInfo, _setdoc, six +from plumbum.lib import ProcInfo, _setdoc from plumbum.machines.base import BaseMachine from plumbum.machines.env import BaseEnv from plumbum.machines.local import LocalPath @@ -58,8 +58,7 @@ def pop(self, name, *default): def update(self, *args, **kwargs): BaseEnv.update(self, *args, **kwargs) self.remote._session.run( - "export " - + " ".join(f"{k}={shquote(v)}" for k, v in self.getdict().items()) + "export " + " ".join(f"{k}={shquote(v)}" for k, v in self.getdict().items()) ) def expand(self, expr): @@ -346,9 +345,9 @@ def _path_glob(self, fn, pattern): # shquote does not work here due to the way bash loops use space as a seperator pattern = pattern.replace(" ", r"\ ") fn = fn.replace(" ", r"\ ") - matches = self._session.run( - fr"for fn in {fn}/{pattern}; do echo $fn; done" - )[1].splitlines() + matches = self._session.run(fr"for fn in {fn}/{pattern}; do echo $fn; done")[ + 1 + ].splitlines() if len(matches) == 1 and not self._path_stat(matches[0]): return [] # pattern expansion failed return matches @@ -418,12 +417,12 @@ def _path_chown(self, fn, owner, group, recursive): def _path_read(self, fn): data = self["cat"](fn) - if self.custom_encoding and isinstance(data, six.unicode_type): + if self.custom_encoding and isinstance(data, str): data = data.encode(self.custom_encoding) return data def _path_write(self, fn, data): - if self.custom_encoding and isinstance(data, six.unicode_type): + if self.custom_encoding and isinstance(data, str): data = data.encode(self.custom_encoding) with NamedTemporaryFile() as f: f.write(data) diff --git a/plumbum/machines/session.py b/plumbum/machines/session.py index cbe818e78..d9ba6a274 100644 --- a/plumbum/machines/session.py +++ b/plumbum/machines/session.py @@ -5,7 +5,6 @@ from plumbum.commands import BaseCommand, run_proc from plumbum.commands.processes import ProcessExecutionError -from plumbum.lib import six from plumbum.machines.base import PopenAddons @@ -48,7 +47,7 @@ class MarkedPipe: def __init__(self, pipe, marker): self.pipe = pipe self.marker = marker - self.marker = six.bytes(self.marker, "ascii") + self.marker = bytes(self.marker, "ascii") def close(self): """'Closes' the marked pipe; following calls to ``readline`` will return """ "" diff --git a/plumbum/machines/ssh_machine.py b/plumbum/machines/ssh_machine.py index a49f80e3b..dbae08151 100644 --- a/plumbum/machines/ssh_machine.py +++ b/plumbum/machines/ssh_machine.py @@ -148,9 +148,7 @@ def popen(self, args, ssh_opts=(), env=None, cwd=None, **kwargs): cmdline.extend(["cd", str(cwd), "&&"]) if envdelta: cmdline.append("env") - cmdline.extend( - f"{k}={shquote(v)}" for k, v in envdelta.items() - ) + cmdline.extend(f"{k}={shquote(v)}" for k, v in envdelta.items()) if isinstance(args, (tuple, list)): cmdline.extend(args) else: diff --git a/plumbum/path/base.py b/plumbum/path/base.py index ca2afb2a7..9027d5ded 100644 --- a/plumbum/path/base.py +++ b/plumbum/path/base.py @@ -2,11 +2,9 @@ import operator import os import warnings -from abc import abstractmethod, abstractproperty +from abc import ABC, abstractmethod, abstractproperty from functools import reduce -from plumbum.lib import six - class FSUser(int): """A special object that represents a file-system user. It derives from ``int``, so it behaves @@ -20,7 +18,7 @@ def __new__(cls, val, name=None): return self -class Path(str, six.ABC): +class Path(str, ABC): """An abstraction over file system paths. This class is abstract, and the two implementations are :class:`LocalPath ` and :class:`RemotePath `. diff --git a/plumbum/path/local.py b/plumbum/path/local.py index 61bcb2c42..2071f1d09 100644 --- a/plumbum/path/local.py +++ b/plumbum/path/local.py @@ -6,7 +6,7 @@ import sys from contextlib import contextmanager -from plumbum.lib import IS_WIN32, _setdoc, glob_escape, six +from plumbum.lib import IS_WIN32, _setdoc, glob_escape from plumbum.path.base import FSUser, Path from plumbum.path.remote import RemotePath @@ -28,13 +28,8 @@ def getgrnam(x): # type: ignore raise OSError("`getgrnam` not supported") -try: # Py3 - import urllib.parse as urlparse - import urllib.request as urllib -except ImportError: - import urllib # type: ignore - - import urlparse # type: ignore +import urllib.parse as urlparse +import urllib.request as urllib logger = logging.getLogger("plumbum.local") @@ -247,7 +242,7 @@ def write(self, data, encoding=None, mode=None): if encoding: data = data.encode(encoding) if mode is None: - if isinstance(data, six.unicode_type): + if isinstance(data, str): mode = "w" else: mode = "wb" diff --git a/plumbum/path/remote.py b/plumbum/path/remote.py index 66b0956f2..833fa61ba 100644 --- a/plumbum/path/remote.py +++ b/plumbum/path/remote.py @@ -1,17 +1,13 @@ import errno import os import sys +import urllib.request as urllib from contextlib import contextmanager from plumbum.commands import ProcessExecutionError, shquote -from plumbum.lib import _setdoc, six +from plumbum.lib import _setdoc from plumbum.path.base import FSUser, Path -try: # Py3 - import urllib.request as urllib -except ImportError: - import urllib # type: ignore - class StatRes: """POSIX-like stat result""" @@ -362,9 +358,7 @@ class RemoteWorkdir(RemotePath): """Remote working directory manipulator""" def __new__(cls, remote): - self = super().__new__( - cls, remote, remote._session.run("pwd")[1].strip() - ) + self = super().__new__(cls, remote, remote._session.run("pwd")[1].strip()) return self def __hash__(self): diff --git a/plumbum/path/utils.py b/plumbum/path/utils.py index 55f4451b4..9b64afc7d 100644 --- a/plumbum/path/utils.py +++ b/plumbum/path/utils.py @@ -1,6 +1,5 @@ import os -from plumbum.lib import six from plumbum.machines.local import LocalPath, local from plumbum.path.base import Path diff --git a/plumbum/typed_env.py b/plumbum/typed_env.py index f71015df4..588652e74 100644 --- a/plumbum/typed_env.py +++ b/plumbum/typed_env.py @@ -1,11 +1,6 @@ import inspect import os - -try: - from collections.abc import MutableMapping -except ImportError: - from collections import MutableMapping - +from collections.abc import MutableMapping NO_DEFAULT = object() @@ -152,9 +147,7 @@ def __getattr__(self, name): try: return self._raw_get(name) except EnvironmentVariableError: - raise AttributeError( - f"{self.__class__} has no attribute {name!r}" - ) + raise AttributeError(f"{self.__class__} has no attribute {name!r}") def __getitem__(self, key): return getattr(self, key) # delegate through the descriptors diff --git a/tests/conftest.py b/tests/conftest.py index 148cd2bbf..b4e8bca70 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,8 @@ import os -import sys import tempfile import pytest -if sys.version_info[0] < 3: - collect_ignore = ["test_3_cli.py"] - SDIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/tests/env.py b/tests/env.py index 1b518b35f..b08daf2f8 100644 --- a/tests/env.py +++ b/tests/env.py @@ -9,9 +9,5 @@ CPYTHON = platform.python_implementation() == "CPython" PYPY = platform.python_implementation() == "PyPy" -PY2 = sys.version_info.major == 2 - -PY = sys.version_info - IS_A_TTY = sys.stdin.isatty() HAS_CHOWN = hasattr(os, "chown") diff --git a/tests/test_color.py b/tests/test_color.py index 2c04cc370..ef0c57311 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -86,6 +86,4 @@ def test_allcolors(self): for g in myrange: for b in myrange: near = FindNearest(r, g, b) - assert ( - near.all_slow() == near.all_fast() - ), f"Tested: {r}, {g}, {b}" + assert near.all_slow() == near.all_fast(), f"Tested: {r}, {g}, {b}" diff --git a/tests/test_local.py b/tests/test_local.py index 40503788e..474b2429a 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -28,7 +28,7 @@ xfail_on_pypy, ) from plumbum.fs.atomic import AtomicCounterFile, AtomicFile, PidFile -from plumbum.lib import IS_WIN32, six +from plumbum.lib import IS_WIN32 from plumbum.machines.local import LocalCommand, PlumbumLocalPopen from plumbum.path import RelativePath @@ -37,9 +37,6 @@ class TestLocalPopen: - @pytest.mark.skipif( - sys.version_info < (3, 2), reason="Context Manager was introduced in Python 3.2" - ) def test_contextmanager(self): if IS_WIN32: command = ["dir"] @@ -98,10 +95,6 @@ def test_split(self): p = local.path("/var/log/messages") p.split() == ["var", "log", "messages"] - @pytest.mark.xfail( - sys.platform == "win32" and (sys.version_info[0] == 2), - reason="Caseless comparison (at least in pytest) fails on Windows 2.7", - ) def test_suffix(self): # This picks up the drive letter differently if not constructed here p1 = local.path("/some/long/path/to/file.txt") @@ -119,10 +112,6 @@ def test_suffix(self): with pytest.raises(ValueError): p1.with_suffix("nodot") - @pytest.mark.xfail( - sys.platform == "win32" and (sys.version_info[0] == 2), - reason="Caseless comparison (at least in pytest) fails on Windows 2.7", - ) def test_newname(self): # This picks up the drive letter differently if not constructed here p1 = local.path("/some/long/path/to/file.txt") @@ -594,9 +583,9 @@ def test_iter_lines_error(self): pass assert i == 1 assert ( - "/bin/ls: unrecognized option '--bla'" in err.value.stderr - or "bin/ls: illegal option -- -" in err.value.stderr - ) + "ls: unrecognized option" in err.value.stderr + and "--bla" in err.value.stderr + ) or "ls: illegal option -- -" in err.value.stderr @skip_on_windows def test_iter_lines_line_timeout(self): @@ -1042,10 +1031,6 @@ def test_inout_rich(self): out = echo(self.richstr) assert self.richstr in out - @pytest.mark.xfail( - IS_WIN32 and sys.version_info < (3, 6), - reason="Unicode output on Windows requires Python 3.6+", - ) @pytest.mark.usefixtures("cleandir") def test_out_rich(self): import io @@ -1058,13 +1043,12 @@ def test_out_rich(self): assert self.richstr in out @pytest.mark.xfail(IS_WIN32, reason="Unicode path not supported on Windows for now") - @pytest.mark.skipif(not six.PY3, reason="Unicode paths only supported on Python 3") @pytest.mark.usefixtures("cleandir") def test_runfile_rich(self): import os import stat - name = self.richstr + six.str("_program") + name = self.richstr + "_program" with open(name, "w") as f: f.write(f"#!{sys.executable}\nprint('yes')") diff --git a/tests/test_putty.py b/tests/test_putty.py index 705a1976f..0b75124fa 100644 --- a/tests/test_putty.py +++ b/tests/test_putty.py @@ -1,6 +1,5 @@ """Test that PuttyMachine initializes its SshMachine correctly""" -import env import pytest from plumbum import PuttyMachine, SshMachine @@ -12,7 +11,6 @@ def ssh_port(request): class TestPuttyMachine: - @pytest.mark.skipif(env.PYPY & env.PY2, reason="PyPy2 doesn't support mocker.spy") def test_putty_command(self, mocker, ssh_port): local = mocker.patch("plumbum.machines.ssh_machine.local") init = mocker.spy(SshMachine, "__init__") diff --git a/tests/test_remote.py b/tests/test_remote.py index da54486b3..03e950dfb 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -21,7 +21,6 @@ local, ) from plumbum._testtools import skip_on_windows, skip_without_chown -from plumbum.lib import six from plumbum.machines.session import HostPublicKeyUnknown, IncorrectLogin try: @@ -58,9 +57,6 @@ def test_connection(): SshMachine(TEST_HOST) -@pytest.mark.skip( - env.LINUX and env.PY[:2] == (3, 5), reason="Doesn't work on 3.5 on Linux on GHA" -) def test_incorrect_login(sshpass): with pytest.raises(IncorrectLogin): SshMachine( diff --git a/tests/test_terminal.py b/tests/test_terminal.py index 383634607..001d2fb81 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -1,22 +1,12 @@ import sys import time +from collections import OrderedDict from contextlib import contextmanager +from io import StringIO import pytest from plumbum.cli.terminal import Progress, ask, choose, hexdump, prompt -from plumbum.lib import StringIO - -try: - from collections import OrderedDict -except ImportError: - try: - from ordereddict import OrderedDict - except ImportError: - OrderedDict = None -needs_od = pytest.mark.skipif( - OrderedDict is None, reason="Ordered dict not available (Py 2.6)" -) @contextmanager @@ -118,7 +108,6 @@ def test_choose_dict(self): value = choose("Pick", dict(one="a", two="b")) assert value in ("a", "b") - @needs_od def test_ordered_dict(self): dic = OrderedDict() dic["one"] = "a" @@ -130,7 +119,6 @@ def test_ordered_dict(self): value = choose("Pick", dic) assert value == "b" - @needs_od def test_choose_dict_default(self, capsys): dic = OrderedDict() dic["one"] = "a"