diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 524dcf845..b0a1777c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,13 +29,15 @@ jobs: strategy: fail-fast: false matrix: - python-version: [2.7, 3.5, 3.7, 3.9] + python-version: ["2.7", "3.5", "3.7", "3.9"] os: [ubuntu-latest, windows-latest, macos-latest] include: - python-version: 'pypy-2.7' os: ubuntu-latest - python-version: 'pypy-3.7' os: ubuntu-latest + - python-version: '3.10' + os: ubuntu-latest runs-on: ${{ matrix.os }} steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ad371c5e9..fe9bab01c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,11 @@ +ci: + autoupdate_commit_msg: "chore: update pre-commit hooks" + autofix_commit_msg: "style: pre-commit fixes" + repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.0.1 hooks: - id: check-added-large-files - id: check-case-conflict @@ -17,39 +21,45 @@ repos: - id: fix-encoding-pragma - repo: https://github.com/psf/black - rev: 20.8b1 + rev: "21.9b0" hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.7.0 + rev: "5.9.3" hooks: - id: isort - repo: https://github.com/asottile/pyupgrade - rev: v2.10.0 + rev: "v2.29.0" hooks: - id: pyupgrade +- repo: https://github.com/asottile/setup-cfg-fmt + rev: "v1.18.0" + hooks: + - id: setup-cfg-fmt + args: [--min-py3-version=3.5] + - repo: https://github.com/pycqa/flake8 - rev: 3.8.4 + rev: "3.9.2" hooks: - id: flake8 exclude: docs/conf.py - additional_dependencies: [flake8-bugbear, flake8-print] + additional_dependencies: [flake8-bugbear, flake8-print, flake8-2020] stages: [manual] - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.800 + rev: "v0.910-1" hooks: - id: mypy files: plumbum stages: [manual] + additional_dependencies: [typed-ast, types-paramiko] # This wants the .mo files removed - repo: https://github.com/mgedmin/check-manifest - rev: "0.46" + rev: "0.47" hooks: - id: check-manifest stages: [manual] diff --git a/plumbum/__init__.py b/plumbum/__init__.py index 988e4b91d..e0615b69a 100644 --- a/plumbum/__init__.py +++ b/plumbum/__init__.py @@ -37,7 +37,7 @@ """ # Avoids a circular import error later -import plumbum.path +import plumbum.path # noqa: F401 from plumbum.commands import ( BG, ERROUT, @@ -58,6 +58,30 @@ __author__ = "Tomer Filiba (tomerfiliba@gmail.com)" __version__ = version +__all__ = ( + "BG", + "ERROUT", + "FG", + "NOHUP", + "RETCODE", + "TEE", + "TF", + "CommandNotFound", + "ProcessExecutionError", + "ProcessLineTimedOut", + "ProcessTimedOut", + "BaseRemoteMachine", + "PuttyMachine", + "SshMachine", + "local", + "LocalPath", + "Path", + "RemotePath", + "__author__", + "__version__", + "cmd", +) + # =================================================================================================== # Module hack: ``from plumbum.cmd import ls`` # =================================================================================================== @@ -92,3 +116,8 @@ def __getattr__(self, name): del sys del ModuleType del LocalModule + + +def __dir__(): + "Support nice tab completion" + return __all__ diff --git a/plumbum/cli/__init__.py b/plumbum/cli/__init__.py index 8013ae0ae..9da92dbad 100644 --- a/plumbum/cli/__init__.py +++ b/plumbum/cli/__init__.py @@ -19,3 +19,23 @@ positional, switch, ) + +__all__ = ( + "Application", + "Config", + "ConfigINI", + "CSV", + "CountOf", + "ExistingDirectory", + "ExistingFile", + "Flag", + "NonexistentPath", + "Predicate", + "Range", + "Set", + "SwitchAttr", + "SwitchError", + "autoswitch", + "positional", + "switch", +) diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index a56223ea7..cbce75aa0 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -554,7 +554,7 @@ def _validate_args(self, swfuncs, tailargs): ordered = [ (f, a) - for _, f, a in sorted([(sf.index, f, sf.val) for f, sf in swfuncs.items()]) + for _, f, a in sorted((sf.index, f, sf.val) for f, sf in swfuncs.items()) ] return ordered, tailargs diff --git a/plumbum/cli/config.py b/plumbum/cli/config.py index 2a8fe2078..486fdb5db 100644 --- a/plumbum/cli/config.py +++ b/plumbum/cli/config.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- from __future__ import division, print_function -import os +import sys from abc import abstractmethod from plumbum import local from plumbum.lib import _setdoc, six -try: - from configparser import ConfigParser, NoOptionError, NoSectionError # Py3 -except ImportError: - from ConfigParser import ConfigParser, NoOptionError, NoSectionError # type: ignore +if sys.version_info >= (3,): + from configparser import ConfigParser, NoOptionError, NoSectionError +else: + from ConfigParser import ConfigParser, NoOptionError, NoSectionError class ConfigBase(six.ABC): diff --git a/plumbum/cli/progress.py b/plumbum/cli/progress.py index 6f6b06390..f7a0de6e9 100644 --- a/plumbum/cli/progress.py +++ b/plumbum/cli/progress.py @@ -196,9 +196,9 @@ def __init__(self, *args, **kargs): with warnings.catch_warnings(): warnings.simplefilter("ignore") try: - from ipywidgets import HTML, HBox, IntProgress # type: ignore + from ipywidgets import HTML, HBox, IntProgress except ImportError: # Support IPython < 4.0 - from IPython.html.widgets import HTML, HBox, IntProgress # type: ignore + from IPython.html.widgets import HTML, HBox, IntProgress super(ProgressIPy, self).__init__(*args, **kargs) self.prog = IntProgress(max=self.length) @@ -206,7 +206,7 @@ def __init__(self, *args, **kargs): self._box = HBox((self.prog, self._label)) def start(self): - from IPython.display import display # type: ignore + from IPython.display import display display(self._box) super(ProgressIPy, self).start() @@ -248,9 +248,9 @@ def __new__(cls, *args, **kargs): try: # pragma: no cover __IPYTHON__ try: - from traitlets import TraitError # type: ignore + from traitlets import TraitError except ImportError: # Support for IPython < 4.0 - from IPython.utils.traitlets import TraitError # type: ignore + from IPython.utils.traitlets import TraitError try: return ProgressIPy(*args, **kargs) diff --git a/plumbum/cli/terminal.py b/plumbum/cli/terminal.py index c2646b4b9..4c31c30b5 100644 --- a/plumbum/cli/terminal.py +++ b/plumbum/cli/terminal.py @@ -14,6 +14,20 @@ from .progress import Progress from .termsize import get_terminal_size +__all__ = ( + "readline", + "ask", + "choose", + "prompt", + "get_terminal_size", + "Progress", + "get_terminal_size", +) + + +def __dir__(): + return __all__ + def readline(message=""): """Gets a line of input from the user (stdin)""" diff --git a/plumbum/cli/termsize.py b/plumbum/cli/termsize.py index dbf410682..9ba1539d0 100644 --- a/plumbum/cli/termsize.py +++ b/plumbum/cli/termsize.py @@ -44,7 +44,7 @@ def get_terminal_size(default=(80, 25)): def _get_terminal_size_windows(): # pragma: no cover try: - from ctypes import create_string_buffer, windll # type: ignore + from ctypes import create_string_buffer, windll STDERR_HANDLE = -12 h = windll.kernel32.GetStdHandle(STDERR_HANDLE) diff --git a/plumbum/colorlib/_ipython_ext.py b/plumbum/colorlib/_ipython_ext.py index fe70a8a5a..926f23c85 100644 --- a/plumbum/colorlib/_ipython_ext.py +++ b/plumbum/colorlib/_ipython_ext.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- -import IPython.display # type: ignore -from IPython.core.magic import magics_class # type: ignore; type: ignore -from IPython.core.magic import Magics, cell_magic, needs_local_scope +import sys + +import IPython.display +from IPython.core.magic import Magics, cell_magic, magics_class, needs_local_scope -try: +if sys.version_info >= (3,): from io import StringIO -except ImportError: +else: try: - from cStringIO import StringIO # type: ignore + from cStringIO import StringIO except ImportError: from StringIO import StringIO # type: ignore -import sys valid_choices = [x[8:] for x in dir(IPython.display) if "display_" == x[:8]] diff --git a/plumbum/colorlib/styles.py b/plumbum/colorlib/styles.py index 020429d1a..d6c1db604 100644 --- a/plumbum/colorlib/styles.py +++ b/plumbum/colorlib/styles.py @@ -26,14 +26,14 @@ from_html, ) -try: +if sys.version_info >= (3,): from abc import ABC -except ImportError: - from abc import ABCMeta # type: ignore +else: + from abc import ABCMeta ABC = ABCMeta( "ABC", (object,), {"__module__": __name__, "__slots__": ("__weakref__")} - ) # type: ignore + ) try: from typing import IO, Dict, Union diff --git a/plumbum/commands/__init__.py b/plumbum/commands/__init__.py index d4ea0a067..9066f1f5f 100644 --- a/plumbum/commands/__init__.py +++ b/plumbum/commands/__init__.py @@ -24,3 +24,28 @@ ProcessTimedOut, run_proc, ) + +__all__ = ( + "BaseCommand", + "ConcreteCommand", + "shquote", + "shquote_list", + "ERROUT", + "BG", + "FG", + "NOHUP", + "RETCODE", + "TEE", + "TF", + "ExecutionModifier", + "Future", + "CommandNotFound", + "ProcessExecutionError", + "ProcessLineTimedOut", + "ProcessTimedOut", + "run_proc", +) + + +def __dir__(): + return __all__ diff --git a/plumbum/commands/base.py b/plumbum/commands/base.py index ca486af1e..1854b8845 100644 --- a/plumbum/commands/base.py +++ b/plumbum/commands/base.py @@ -323,9 +323,9 @@ def popen(self, args=(), **kwargs): class BoundEnvCommand(BaseCommand): __slots__ = ("cmd", "env", "cwd") - def __init__(self, cmd, env={}, cwd=None): + def __init__(self, cmd, env=None, cwd=None): self.cmd = cmd - self.env = env + self.env = env or {} self.cwd = cwd def __repr__(self): @@ -341,7 +341,8 @@ def formulate(self, level=0, args=()): def machine(self): return self.cmd.machine - def popen(self, args=(), cwd=None, env={}, **kwargs): + def popen(self, args=(), cwd=None, env=None, **kwargs): + env = env or {} return self.cmd.popen( args, cwd=self.cwd if cwd is None else cwd, diff --git a/plumbum/commands/daemons.py b/plumbum/commands/daemons.py index cf0552abe..d96c0a2f2 100644 --- a/plumbum/commands/daemons.py +++ b/plumbum/commands/daemons.py @@ -49,7 +49,7 @@ def posix_daemonize(command, cwd, stdout=None, stderr=None, append=True): stderr=stderr.fileno(), ) os.write(wfd, str(proc.pid).encode("utf8")) - except: + except Exception: rc = 1 tbtext = "".join(traceback.format_exception(*sys.exc_info()))[-MAX_SIZE:] os.write(wfd, tbtext.encode("utf8")) diff --git a/plumbum/commands/processes.py b/plumbum/commands/processes.py index a4d8ff28d..d321f3faf 100644 --- a/plumbum/commands/processes.py +++ b/plumbum/commands/processes.py @@ -1,23 +1,21 @@ # -*- coding: utf-8 -*- import atexit import heapq +import sys import time from threading import Thread from plumbum.lib import IS_WIN32, six -try: +if sys.version_info >= (3,): + from io import StringIO from queue import Empty as QueueEmpty from queue import Queue -except ImportError: - from Queue import Empty as QueueEmpty # type: ignore +else: + from cStringIO import StringIO + from Queue import Empty as QueueEmpty from Queue import Queue -try: - from io import StringIO -except ImportError: - from cStringIO import StringIO # type: ignore - # =================================================================================================== # utility functions diff --git a/plumbum/lib.py b/plumbum/lib.py index 7ae905e7c..959a2ecb0 100644 --- a/plumbum/lib.py +++ b/plumbum/lib.py @@ -2,7 +2,6 @@ import inspect import os import sys -from abc import ABCMeta from contextlib import contextmanager IS_WIN32 = sys.platform == "win32" @@ -38,14 +37,12 @@ class six(object): """ PY3 = sys.version_info[0] >= 3 - try: + if sys.version_info >= (3, 4): from abc import ABC - except ImportError: - from abc import ABCMeta # type: ignore + else: + from abc import ABCMeta - ABC = ABCMeta( - "ABC", (object,), {"__module__": __name__, "__slots__": ()} - ) # type: ignore + ABC = ABCMeta("ABC", (object,), {"__module__": __name__, "__slots__": ()}) # Be sure to use named-tuple access, so that usage is not affected try: @@ -100,10 +97,10 @@ def get_method_function(m): # Try/except fails because io has the wrong StringIO in Python2 # You'll get str/unicode errors -if six.PY3: +if sys.version_info >= (3, 0): from io import StringIO else: - from StringIO import StringIO # type: ignore + from StringIO import StringIO @contextmanager diff --git a/plumbum/machines/__init__.py b/plumbum/machines/__init__.py index 4e3599b67..289d5f3fc 100644 --- a/plumbum/machines/__init__.py +++ b/plumbum/machines/__init__.py @@ -2,3 +2,13 @@ from plumbum.machines.local import LocalCommand, LocalMachine, local from plumbum.machines.remote import BaseRemoteMachine, RemoteCommand from plumbum.machines.ssh_machine import PuttyMachine, SshMachine + +__all__ = ( + "LocalCommand", + "LocalMachine", + "local", + "BaseRemoteMachine", + "RemoteCommand", + "PuttyMachine", + "SshMachine", +) diff --git a/plumbum/machines/local.py b/plumbum/machines/local.py index c3f0d6ae4..d82a597b7 100644 --- a/plumbum/machines/local.py +++ b/plumbum/machines/local.py @@ -7,7 +7,6 @@ import sys import time from contextlib import contextmanager -from functools import partial from tempfile import mkdtemp from plumbum.commands import CommandNotFound, ConcreteCommand diff --git a/plumbum/machines/paramiko_machine.py b/plumbum/machines/paramiko_machine.py index 5e54e072e..447b75ebb 100644 --- a/plumbum/machines/paramiko_machine.py +++ b/plumbum/machines/paramiko_machine.py @@ -19,7 +19,7 @@ import paramiko except ImportError: - class paramiko(object): + class paramiko(object): # type: ignore def __nonzero__(self): return False @@ -28,7 +28,7 @@ def __nonzero__(self): def __getattr__(self, name): raise ImportError("No module named paramiko") - paramiko = paramiko() + paramiko = paramiko() # type: ignore logger = logging.getLogger("plumbum.paramiko") diff --git a/plumbum/path/local.py b/plumbum/path/local.py index 6dc356107..a4af06917 100644 --- a/plumbum/path/local.py +++ b/plumbum/path/local.py @@ -164,8 +164,8 @@ def with_suffix(self, suffix, depth=1): raise ValueError("Invalid suffix %r" % (suffix)) name = self.name depth = len(self.suffixes) if depth is None else min(depth, len(self.suffixes)) - for i in range(depth): - name, ext = os.path.splitext(name) + for _ in range(depth): + name, _ = os.path.splitext(name) return LocalPath(self.dirname) / (name + suffix) @_setdoc(Path) diff --git a/pyproject.toml b/pyproject.toml index bba4f5970..b3db3f6a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,3 +8,15 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] write_to = "plumbum/version.py" + + +[tool.mypy] +files = ["plumbum"] +python_version = "2.7" +warn_unused_configs = true +warn_unused_ignores = true + + +[[tool.mypy.overrides]] +module = ["IPython.*", "pywintypes.*", "win32con.*", "win32file.*", "PIL.*"] +ignore_missing_imports = true diff --git a/setup.cfg b/setup.cfg index bda142174..bf9c195a1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,30 +1,20 @@ [metadata] name = plumbum -author = Tomer Filiba -author_email = tomerfiliba@gmail.com -license = MIT description = Plumbum: shell combinators library -keywords = - path, - local, - remote, - ssh, - shell, - pipe, - popen, - process, - execution, - color, - cli -url = https://plumbum.readthedocs.io long_description = file: README.rst long_description_content_type = text/x-rst +url = https://plumbum.readthedocs.io +author = Tomer Filiba +author_email = tomerfiliba@gmail.com +license = MIT license_file = LICENSE +platforms = POSIX, Windows classifiers = Development Status :: 5 - Production/Stable License :: OSI Approved :: MIT License Operating System :: Microsoft :: Windows Operating System :: POSIX + Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 @@ -32,37 +22,49 @@ classifiers = Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 Topic :: Software Development :: Build Tools Topic :: System :: Systems Administration -platforms = POSIX, Windows +keywords = + path, + local, + remote, + ssh, + shell, + pipe, + popen, + process, + execution, + color, + cli provides = plumbum [options] -python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4 packages = find: install_requires = - pypiwin32; platform_system=='Windows' and platform_python_implementation!="PyPy" + pypiwin32;platform_system=='Windows' and platform_python_implementation!="PyPy" +python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* [options.packages.find] exclude = tests -[options.package_data] -plumbum.cli = i18n/*/LC_MESSAGES/*.mo - [options.extras_require] -ssh = - paramiko dev = + paramiko + psutil pytest pytest-cov pytest-mock pytest-timeout - paramiko - psutil docs = - Sphinx >=3.0.0 - sphinx_rtd_theme >=0.5.0 + Sphinx>=3.0.0 + sphinx_rtd_theme>=0.5.0 +ssh = + paramiko + +[options.package_data] +plumbum.cli = i18n/*/LC_MESSAGES/*.mo [bdist_wheel] universal = 1 @@ -94,31 +96,9 @@ exclude_lines = raise NotImplementedError if __name__ == .__main__.: -[mypy] -files = plumbum -python_version = 2.7 -warn_unused_configs = True -warn_unused_ignores = True - -[mypy-IPython.*] -ignore_missing_imports = True - -[mypy-pywintypes.*] -ignore_missing_imports = True - -[mypy-win32con.*] -ignore_missing_imports = True - -[mypy-win32file.*] -ignore_missing_imports = True - -[mypy-PIL.*] -ignore_missing_imports = True - - [flake8] -max-complexity = 12 -ignore = E203, E231, E501, E722, W503 +max-complexity = 50 +ignore = E203, E231, E501, E722, W503, B950, B904, B003, B008, E731, B902 select = C,E,F,W,B,B9 [tool:isort] @@ -133,5 +113,3 @@ ignore = experiments/* conda.recipe/* CONTRIBUTING.rst - -# TODO: these .mo files should be generated diff --git a/tests/_test_paramiko.py b/tests/_test_paramiko.py index 0b6ef1a1c..a32c0528f 100644 --- a/tests/_test_paramiko.py +++ b/tests/_test_paramiko.py @@ -3,7 +3,7 @@ from plumbum.paramiko_machine import ParamikoMachine as PM local.env.path.append("c:\\progra~1\\git\\bin") -from plumbum.cmd import grep, ls +from plumbum.cmd import grep, ls # noqa: E402 m = PM("192.168.1.143") mls = m["ls"] diff --git a/tests/test_clicolor.py b/tests/test_clicolor.py index d6d635c07..8af5ee27e 100644 --- a/tests/test_clicolor.py +++ b/tests/test_clicolor.py @@ -86,7 +86,3 @@ def main(self): assert str(colors.red | "crunchy") in output assert str(colors.cyan | "this is a bacon switch") in output assert expected in output - - -if __name__ == "__main__": - NotSoSimpleApp.run() diff --git a/tests/test_nohup.py b/tests/test_nohup.py index 63549ec39..43a2a5581 100644 --- a/tests/test_nohup.py +++ b/tests/test_nohup.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import os -import sys import time import psutil diff --git a/tests/test_sudo.py b/tests/test_sudo.py index 3cb703276..25ed4e8d9 100644 --- a/tests/test_sudo.py +++ b/tests/test_sudo.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- - import pytest -import plumbum from plumbum import local from plumbum._testtools import skip_on_windows diff --git a/tests/test_validate.py b/tests/test_validate.py index 953727d2d..95186f575 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -2,7 +2,6 @@ from __future__ import division, print_function from plumbum import cli -from plumbum.lib import six class TestValidator: @@ -13,7 +12,7 @@ def main(selfy, x, y): pass assert Try.main.positional == [abs, str] - assert Try.main.positional_varargs == None + assert Try.main.positional_varargs is None def test_position(self): class Try(object): @@ -22,7 +21,7 @@ def main(selfy, x, y): pass assert Try.main.positional == [abs, str] - assert Try.main.positional_varargs == None + assert Try.main.positional_varargs is None def test_mix(self): class Try(object): @@ -31,7 +30,7 @@ def main(selfy, x, y, z, d): pass assert Try.main.positional == [abs, str, None, bool] - assert Try.main.positional_varargs == None + assert Try.main.positional_varargs is None def test_var(self): class Try(object): @@ -40,7 +39,7 @@ def main(selfy, x, y, *g): pass assert Try.main.positional == [abs, str] - assert Try.main.positional_varargs == int + assert Try.main.positional_varargs is int def test_defaults(self): class Try(object): diff --git a/translations.py b/translations.py index 578e78717..02459ceb8 100755 --- a/translations.py +++ b/translations.py @@ -10,17 +10,20 @@ translation_dir = local.cwd / "plumbum/cli/i18n" template = translation_dir / "messages.pot" -xgettext[ - "--from-code", - "utf-8", - "-L", - "python", - "--keyword=T_", - "--package-name=Plumbum.cli", - "-o", - template, - sorted([x - local.cwd for x in local.cwd / "plumbum/cli" // "*.py"]), -] & FG +( + xgettext[ + "--from-code", + "utf-8", + "-L", + "python", + "--keyword=T_", + "--package-name=Plumbum.cli", + "-o", + template, + sorted(x - local.cwd for x in local.cwd / "plumbum/cli" // "*.py"), + ] + & FG +) for translation in translation_dir // "*.po": lang = translation.stem