diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62c3aaa77..fff4d9808 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,27 +28,25 @@ jobs: python-version: "3.x" - uses: pre-commit/action@v3.0.1 - name: pylint - run: | - echo "::add-matcher::$GITHUB_WORKSPACE/.github/matchers/pylint.json" - pipx run --python python nox -s pylint + run: pipx run --python python nox -s pylint -- --output-format=github tests: name: Tests on 🐍 ${{ matrix.python-version }} ${{ matrix.os }} strategy: fail-fast: false matrix: - python-version: ["3.6", "3.8", "3.11", "3.12"] + python-version: ["3.8", "3.11", "3.13"] os: [ubuntu-latest, windows-latest, macos-13] include: - python-version: 'pypy-3.8' os: ubuntu-latest - python-version: 'pypy-3.10' os: ubuntu-latest - - python-version: '3.6' - os: ubuntu-20.04 - exclude: - - python-version: '3.6' + - python-version: '3.9' os: ubuntu-latest + exclude: + - python-version: '3.13' + os: windows-latest runs-on: ${{ matrix.os }} steps: @@ -60,19 +58,15 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - - uses: actions/cache@v4 - if: runner.os == 'Linux' && startsWith(matrix.python-version, 'pypy') - with: - path: ~/.cache/pip - key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('setup.cfg') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.python-version }}-pip- + - name: Setup uv + uses: yezz123/setup-uv@v4 - name: Install run: | - pip install wheel coveralls pytest-github-actions-annotate-failures - pip install -e .[dev] + uv pip install --system pip install pytest-github-actions-annotate-failures + uv pip install --system -e .[test] - name: Setup SSH tests if: runner.os != 'Windows' @@ -92,7 +86,7 @@ jobs: run: pytest --cov --run-optional-tests=ssh,sudo - name: Upload coverage - run: coveralls --service=github + run: pipx run coveralls --service=github env: COVERALLS_PARALLEL: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -100,7 +94,7 @@ jobs: coverage: needs: [tests] - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/setup-python@v5 with: diff --git a/examples/SimpleColorCLI.py b/examples/SimpleColorCLI.py index 25febb5d6..1458bfca1 100644 --- a/examples/SimpleColorCLI.py +++ b/examples/SimpleColorCLI.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum import cli, colors # from plumbum.colorlib import HTMLStyle, StyleFactory diff --git a/examples/alignment.py b/examples/alignment.py index 2803a0290..9445d4416 100755 --- a/examples/alignment.py +++ b/examples/alignment.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +from __future__ import annotations + from plumbum import cli diff --git a/examples/color.py b/examples/color.py index 649d27028..585a80ae4 100755 --- a/examples/color.py +++ b/examples/color.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from __future__ import annotations from plumbum import colors diff --git a/examples/filecopy.py b/examples/filecopy.py index 6efc4f213..2ba9def78 100755 --- a/examples/filecopy.py +++ b/examples/filecopy.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +from __future__ import annotations + import logging from plumbum import cli, local diff --git a/examples/fullcolor.py b/examples/fullcolor.py index aac32597d..e35be50f5 100755 --- a/examples/fullcolor.py +++ b/examples/fullcolor.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from __future__ import annotations from plumbum import colors diff --git a/examples/geet.py b/examples/geet.py index f09fa1402..d3165b9af 100755 --- a/examples/geet.py +++ b/examples/geet.py @@ -40,6 +40,8 @@ committing... """ +from __future__ import annotations + try: import colorama diff --git a/examples/make_figures.py b/examples/make_figures.py index f13d80c3d..042e40757 100755 --- a/examples/make_figures.py +++ b/examples/make_figures.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from __future__ import annotations from plumbum import FG, cli, local from plumbum.cmd import convert, pdflatex diff --git a/examples/simple_cli.py b/examples/simple_cli.py index 24095b226..0e979502f 100755 --- a/examples/simple_cli.py +++ b/examples/simple_cli.py @@ -35,6 +35,8 @@ Compiling: ('x.cpp', 'y.cpp', 'z.cpp') """ +from __future__ import annotations + import logging from plumbum import cli diff --git a/experiments/parallel.py b/experiments/parallel.py index f4fef22c0..1fd526a05 100644 --- a/experiments/parallel.py +++ b/experiments/parallel.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum.commands.base import BaseCommand from plumbum.commands.processes import CommandNotFound, ProcessExecutionError, run_proc diff --git a/experiments/test_parallel.py b/experiments/test_parallel.py index 370642fcd..7b2b3bd7c 100644 --- a/experiments/test_parallel.py +++ b/experiments/test_parallel.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import unittest from parallel import Cluster diff --git a/noxfile.py b/noxfile.py index 3a6af51a9..a447f4600 100644 --- a/noxfile.py +++ b/noxfile.py @@ -2,9 +2,11 @@ import nox -ALL_PYTHONS = ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] +ALL_PYTHONS = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] -nox.options.sessions = ["lint", "tests"] +nox.needs_version = ">=2024.3.2" +nox.options.sessions = ["lint", "pylint", "tests"] +nox.options.default_venv_backend = "uv|virtualenv" @nox.session(reuse_venv=True) @@ -22,7 +24,7 @@ def pylint(session): Run pylint. """ - session.install(".", "paramiko", "ipython", "pylint~=3.1.0") + session.install(".", "paramiko", "ipython", "pylint") session.run("pylint", "plumbum", *session.posargs) @@ -31,7 +33,7 @@ def tests(session): """ Run the unit and regular tests. """ - session.install("-e", ".[dev]") + session.install("-e", ".[test]") session.run("pytest", *session.posargs, env={"PYTHONTRACEMALLOC": "5"}) diff --git a/plumbum/__init__.py b/plumbum/__init__.py index d8df78d87..668653277 100644 --- a/plumbum/__init__.py +++ b/plumbum/__init__.py @@ -35,7 +35,7 @@ See https://plumbum.readthedocs.io for full details """ -import sys +from __future__ import annotations # Avoids a circular import error later import plumbum.path # noqa: F401 @@ -83,35 +83,7 @@ "cmd", ) -# =================================================================================================== -# Module hack: ``from plumbum.cmd import ls`` -# Can be replaced by a real module with __getattr__ after Python 3.6 is dropped -# =================================================================================================== - - -if sys.version_info < (3, 7): # noqa: UP036 - from types import ModuleType - from typing import List - - class LocalModule(ModuleType): - """The module-hack that allows us to use ``from plumbum.cmd import some_program``""" - - __all__ = () # to make help() happy - __package__ = __name__ - - def __getattr__(self, name): - try: - return local[name] - except CommandNotFound: - raise AttributeError(name) from None - - __path__: List[str] = [] - __file__ = __file__ - - cmd = LocalModule(__name__ + ".cmd", LocalModule.__doc__) - sys.modules[cmd.__name__] = cmd -else: - from . import cmd +from . import cmd def __dir__(): diff --git a/plumbum/_testtools.py b/plumbum/_testtools.py index a7c4bef1d..de4a449e6 100644 --- a/plumbum/_testtools.py +++ b/plumbum/_testtools.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import platform import sys diff --git a/plumbum/cli/__init__.py b/plumbum/cli/__init__.py index 446bf9611..f7c5732aa 100644 --- a/plumbum/cli/__init__.py +++ b/plumbum/cli/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .application import Application from .config import Config, ConfigINI from .switches import ( diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index 341817fc9..2218e7b2e 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import functools import inspect import os diff --git a/plumbum/cli/config.py b/plumbum/cli/config.py index b75e5515b..67e143833 100644 --- a/plumbum/cli/config.py +++ b/plumbum/cli/config.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib from abc import ABC, abstractmethod from configparser import ConfigParser, NoOptionError, NoSectionError diff --git a/plumbum/cli/i18n.py b/plumbum/cli/i18n.py index 8e22f6887..1921fe5ba 100644 --- a/plumbum/cli/i18n.py +++ b/plumbum/cli/i18n.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import locale # High performance method for English (no translation needed) diff --git a/plumbum/cli/image.py b/plumbum/cli/image.py index 89cc12e89..05607ccb2 100644 --- a/plumbum/cli/image.py +++ b/plumbum/cli/image.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from plumbum import colors diff --git a/plumbum/cli/progress.py b/plumbum/cli/progress.py index ddfa2cc33..e6f47176e 100644 --- a/plumbum/cli/progress.py +++ b/plumbum/cli/progress.py @@ -3,6 +3,8 @@ ------------ """ +from __future__ import annotations + import datetime import sys import warnings diff --git a/plumbum/cli/switches.py b/plumbum/cli/switches.py index d8c91d83d..cd932dabe 100644 --- a/plumbum/cli/switches.py +++ b/plumbum/cli/switches.py @@ -1,8 +1,10 @@ +from __future__ import annotations + import collections.abc import contextlib import inspect from abc import ABC, abstractmethod -from typing import Callable, Generator, List, Union +from typing import Callable, Generator from plumbum import local from plumbum.cli.i18n import get_translation_for @@ -464,10 +466,10 @@ class MyApp(Application): def __init__( self, - *values: Union[str, Callable[[str], str]], + *values: str | Callable[[str], str], case_sensitive: bool = False, - csv: Union[bool, str] = False, - all_markers: "collections.abc.Set[str]" = frozenset(), + csv: bool | str = False, + all_markers: collections.abc.Set[str] = frozenset(), ) -> None: self.case_sensitive = case_sensitive if isinstance(csv, bool): @@ -501,7 +503,7 @@ def _call_iter( with contextlib.suppress(ValueError): yield opt(value) - def __call__(self, value: str, check_csv: bool = True) -> Union[str, List[str]]: + def __call__(self, value: str, check_csv: bool = True) -> str | list[str]: items = list(self._call_iter(value, check_csv)) if not items: msg = f"Invalid value: {value} (Expected one of {self.values})" diff --git a/plumbum/cli/terminal.py b/plumbum/cli/terminal.py index 713b70979..88a861ce6 100644 --- a/plumbum/cli/terminal.py +++ b/plumbum/cli/terminal.py @@ -3,10 +3,11 @@ -------------------------- """ +from __future__ import annotations + import contextlib import os import sys -from typing import List, Optional from plumbum import local @@ -24,7 +25,7 @@ ] -def __dir__() -> List[str]: +def __dir__() -> list[str]: return __all__ @@ -35,7 +36,7 @@ def readline(message: str = "") -> str: return sys.stdin.readline() -def ask(question: str, default: Optional[bool] = None) -> bool: +def ask(question: str, default: bool | None = None) -> bool: """ Presents the user with a yes/no question. diff --git a/plumbum/cli/termsize.py b/plumbum/cli/termsize.py index 869ae137f..cfb1b987c 100644 --- a/plumbum/cli/termsize.py +++ b/plumbum/cli/termsize.py @@ -3,17 +3,18 @@ --------------------- """ +from __future__ import annotations + import contextlib import os import platform import warnings from struct import Struct -from typing import Optional, Tuple from plumbum import local -def get_terminal_size(default: Tuple[int, int] = (80, 25)) -> Tuple[int, int]: +def get_terminal_size(default: tuple[int, int] = (80, 25)) -> tuple[int, int]: """ Get width and height of console; works on linux, os x, windows and cygwin @@ -73,7 +74,7 @@ def _get_terminal_size_tput(): # pragma: no cover return None -def _ioctl_GWINSZ(fd: int) -> Optional[Tuple[int, int]]: +def _ioctl_GWINSZ(fd: int) -> tuple[int, int] | None: yx = Struct("hh") try: import fcntl @@ -85,7 +86,7 @@ def _ioctl_GWINSZ(fd: int) -> Optional[Tuple[int, int]]: return None -def _get_terminal_size_linux() -> Optional[Tuple[int, int]]: +def _get_terminal_size_linux() -> tuple[int, int] | None: cr = _ioctl_GWINSZ(0) or _ioctl_GWINSZ(1) or _ioctl_GWINSZ(2) if not cr: with contextlib.suppress(Exception): diff --git a/plumbum/cmd.py b/plumbum/cmd.py index aa170266a..0e240759b 100644 --- a/plumbum/cmd.py +++ b/plumbum/cmd.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import plumbum diff --git a/plumbum/colorlib/__init__.py b/plumbum/colorlib/__init__.py index 12b532428..b163087b9 100644 --- a/plumbum/colorlib/__init__.py +++ b/plumbum/colorlib/__init__.py @@ -4,6 +4,8 @@ underlined text. It also provides ``reset`` to recover the normal font. """ +from __future__ import annotations + import sys from .factories import StyleFactory diff --git a/plumbum/colorlib/__main__.py b/plumbum/colorlib/__main__.py index ab37c1589..1fd468d4b 100644 --- a/plumbum/colorlib/__main__.py +++ b/plumbum/colorlib/__main__.py @@ -4,6 +4,8 @@ to recover terminal color. """ +from __future__ import annotations + from . import main main() diff --git a/plumbum/colorlib/_ipython_ext.py b/plumbum/colorlib/_ipython_ext.py index 37f41581f..01f40c5b0 100644 --- a/plumbum/colorlib/_ipython_ext.py +++ b/plumbum/colorlib/_ipython_ext.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from io import StringIO diff --git a/plumbum/colorlib/factories.py b/plumbum/colorlib/factories.py index 0f124aa4c..0df9213fc 100644 --- a/plumbum/colorlib/factories.py +++ b/plumbum/colorlib/factories.py @@ -2,6 +2,8 @@ Color-related factories. They produce Styles. """ +from __future__ import annotations + import functools import operator import sys diff --git a/plumbum/colorlib/names.py b/plumbum/colorlib/names.py index 80f8ab96a..f0bd99f6f 100644 --- a/plumbum/colorlib/names.py +++ b/plumbum/colorlib/names.py @@ -6,7 +6,7 @@ rgb values with ``r=int(html[n][1:3],16)``, etc. """ -from typing import Tuple +from __future__ import annotations color_names = """\ black @@ -417,7 +417,7 @@ def all_fast(self) -> int: return colors[min(range(len(distances)), key=distances.__getitem__)] -def from_html(color: str) -> Tuple[int, int, int]: +def from_html(color: str) -> tuple[int, int, int]: """Convert html hex code to rgb.""" if len(color) != 7 or color[0] != "#": raise ValueError("Invalid length of html code") diff --git a/plumbum/colorlib/styles.py b/plumbum/colorlib/styles.py index 60ca7a336..d2b9a5729 100644 --- a/plumbum/colorlib/styles.py +++ b/plumbum/colorlib/styles.py @@ -7,6 +7,8 @@ With the ``Style`` class, any color can be directly called or given to a with statement. """ +from __future__ import annotations + import contextlib import os import platform @@ -14,7 +16,7 @@ import sys from abc import ABCMeta, abstractmethod from copy import copy -from typing import IO, Dict, Optional, Union +from typing import IO from .names import ( FindNearest, @@ -339,9 +341,9 @@ class Style(metaclass=ABCMeta): """The class of color to use. Never hardcode ``Color`` call when writing a Style method.""" - attribute_names: Union[Dict[str, str], Dict[str, int]] + attribute_names: dict[str, str] | dict[str, int] - _stdout: Optional[IO] = None + _stdout: IO | None = None end = "\n" """The endline character. Override if needed in subclasses.""" diff --git a/plumbum/colors.py b/plumbum/colors.py index 1815273b6..fa0cb4882 100644 --- a/plumbum/colors.py +++ b/plumbum/colors.py @@ -4,6 +4,8 @@ all the standard syntax for colors. """ +from __future__ import annotations + import atexit import sys diff --git a/plumbum/commands/__init__.py b/plumbum/commands/__init__.py index 286c84bfd..195ce01c0 100644 --- a/plumbum/commands/__init__.py +++ b/plumbum/commands/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum.commands.base import ( ERROUT, BaseCommand, diff --git a/plumbum/commands/base.py b/plumbum/commands/base.py index 8f8ac0d08..29638ce1c 100644 --- a/plumbum/commands/base.py +++ b/plumbum/commands/base.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import functools import shlex diff --git a/plumbum/commands/daemons.py b/plumbum/commands/daemons.py index 419d9b412..cb02bf7b8 100644 --- a/plumbum/commands/daemons.py +++ b/plumbum/commands/daemons.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import errno import os diff --git a/plumbum/commands/modifiers.py b/plumbum/commands/modifiers.py index af59071a8..736dd6423 100644 --- a/plumbum/commands/modifiers.py +++ b/plumbum/commands/modifiers.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from logging import DEBUG, INFO from select import select diff --git a/plumbum/commands/processes.py b/plumbum/commands/processes.py index 7470d8c22..f1ddc2666 100644 --- a/plumbum/commands/processes.py +++ b/plumbum/commands/processes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import atexit import contextlib import heapq diff --git a/plumbum/fs/atomic.py b/plumbum/fs/atomic.py index 813e3a31e..af5014859 100644 --- a/plumbum/fs/atomic.py +++ b/plumbum/fs/atomic.py @@ -2,6 +2,8 @@ Atomic file operations """ +from __future__ import annotations + import atexit import contextlib import os diff --git a/plumbum/fs/mounts.py b/plumbum/fs/mounts.py index c8c761324..d614594a4 100644 --- a/plumbum/fs/mounts.py +++ b/plumbum/fs/mounts.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re diff --git a/plumbum/lib.py b/plumbum/lib.py index 9a489de57..85833d3ed 100644 --- a/plumbum/lib.py +++ b/plumbum/lib.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import inspect import os import sys diff --git a/plumbum/machines/__init__.py b/plumbum/machines/__init__.py index bc89dcb3d..89351c6b8 100644 --- a/plumbum/machines/__init__.py +++ b/plumbum/machines/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum.machines.local import LocalCommand, LocalMachine, local from plumbum.machines.remote import BaseRemoteMachine, RemoteCommand from plumbum.machines.ssh_machine import PuttyMachine, SshMachine diff --git a/plumbum/machines/_windows.py b/plumbum/machines/_windows.py index b289bcfe5..c459f38b0 100644 --- a/plumbum/machines/_windows.py +++ b/plumbum/machines/_windows.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import struct LFANEW_OFFSET = 30 * 2 diff --git a/plumbum/machines/base.py b/plumbum/machines/base.py index aa94f17ff..d3f2cb569 100644 --- a/plumbum/machines/base.py +++ b/plumbum/machines/base.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum.commands.processes import ( CommandNotFound, ProcessExecutionError, diff --git a/plumbum/machines/env.py b/plumbum/machines/env.py index 60df5a1f4..8ea38b07a 100644 --- a/plumbum/machines/env.py +++ b/plumbum/machines/env.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from contextlib import contextmanager diff --git a/plumbum/machines/local.py b/plumbum/machines/local.py index 436778579..9848d2293 100644 --- a/plumbum/machines/local.py +++ b/plumbum/machines/local.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import logging import os @@ -9,7 +11,6 @@ from contextlib import contextmanager from subprocess import PIPE, Popen from tempfile import mkdtemp -from typing import Dict, Tuple from plumbum.commands import CommandNotFound, ConcreteCommand from plumbum.commands.daemons import posix_daemonize, win32_daemonize @@ -143,7 +144,7 @@ class LocalMachine(BaseMachine): custom_encoding = sys.getfilesystemencoding() uname = platform.uname()[0] - _program_cache: Dict[Tuple[str, str], LocalPath] = {} + _program_cache: dict[tuple[str, str], LocalPath] = {} def __init__(self): self._as_user_stack = [] diff --git a/plumbum/machines/paramiko_machine.py b/plumbum/machines/paramiko_machine.py index 04ea8f1e0..ab56c2ed8 100644 --- a/plumbum/machines/paramiko_machine.py +++ b/plumbum/machines/paramiko_machine.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import errno import logging diff --git a/plumbum/machines/remote.py b/plumbum/machines/remote.py index 377da463f..b17cb53b0 100644 --- a/plumbum/machines/remote.py +++ b/plumbum/machines/remote.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import re from tempfile import NamedTemporaryFile diff --git a/plumbum/machines/session.py b/plumbum/machines/session.py index fef838e77..2ba10746a 100644 --- a/plumbum/machines/session.py +++ b/plumbum/machines/session.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import logging import random diff --git a/plumbum/machines/ssh_machine.py b/plumbum/machines/ssh_machine.py index 82eb29cef..a79bd1416 100644 --- a/plumbum/machines/ssh_machine.py +++ b/plumbum/machines/ssh_machine.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re import socket import warnings diff --git a/plumbum/path/__init__.py b/plumbum/path/__init__.py index 311d6a036..795205bc1 100644 --- a/plumbum/path/__init__.py +++ b/plumbum/path/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum.path.base import FSUser, Path, RelativePath from plumbum.path.local import LocalPath, LocalWorkdir from plumbum.path.remote import RemotePath, RemoteWorkdir diff --git a/plumbum/path/base.py b/plumbum/path/base.py index 29f13ac08..afb878039 100644 --- a/plumbum/path/base.py +++ b/plumbum/path/base.py @@ -1,3 +1,6 @@ +from __future__ import annotations + +import builtins import io import itertools import operator @@ -162,7 +165,7 @@ def suffix(self) -> str: @property @abstractmethod - def suffixes(self) -> typing.List[str]: + def suffixes(self) -> list[str]: """This is a list of all suffixes""" @property @@ -180,7 +183,7 @@ def gid(self) -> FSUser: attribute that holds the string-name of the group""" @abstractmethod - def as_uri(self, scheme: typing.Optional[str] = None) -> str: + def as_uri(self, scheme: str | None = None) -> str: """Returns a universal resource identifier. Use ``scheme`` to force a scheme.""" @abstractmethod @@ -192,7 +195,7 @@ def join(self: _PathImpl, *parts: typing.Any) -> _PathImpl: """Joins this path with any number of paths""" @abstractmethod - def list(self: _PathImpl) -> typing.List[_PathImpl]: + def list(self: _PathImpl) -> builtins.list[_PathImpl]: """Returns the files in this directory""" @abstractmethod @@ -239,9 +242,7 @@ def with_name(self: _PathImpl, name: typing.Any) -> _PathImpl: """Returns a path with the name replaced""" @abstractmethod - def with_suffix( - self: _PathImpl, suffix: str, depth: typing.Optional[int] = 1 - ) -> _PathImpl: + def with_suffix(self: _PathImpl, suffix: str, depth: int | None = 1) -> _PathImpl: """Returns a path with the suffix replaced. Up to last ``depth`` suffixes will be replaced. None will replace all suffixes. If there are less than ``depth`` suffixes, this will replace all suffixes. ``.tar.gz`` is an example where ``depth=2`` or @@ -254,8 +255,8 @@ def preferred_suffix(self, suffix): @abstractmethod def glob( - self: _PathImpl, pattern: typing.Union[str, typing.Iterable[str]] - ) -> typing.List[_PathImpl]: + self: _PathImpl, pattern: str | typing.Iterable[str] + ) -> builtins.list[_PathImpl]: """Returns a (possibly empty) list of paths that matched the glob-pattern under this path""" @abstractmethod @@ -298,18 +299,16 @@ def mkdir(self, mode=0o777, parents=True, exist_ok=True): """ @abstractmethod - def open( - self, mode: str = "r", *, encoding: typing.Optional[str] = None - ) -> io.IOBase: + def open(self, mode: str = "r", *, encoding: str | None = None) -> io.IOBase: """opens this path as a file""" @abstractmethod - def read(self, encoding: typing.Optional[str] = None) -> str: + def read(self, encoding: str | None = None) -> str: """returns the contents of this file as a ``str``. By default the data is read as text, but you can specify the encoding, e.g., ``'latin1'`` or ``'utf8'``""" @abstractmethod - def write(self, data: typing.AnyStr, encoding: typing.Optional[str] = None) -> None: + def write(self, data: typing.AnyStr, encoding: str | None = None) -> None: """writes the given data to this file. By default the data is written as-is (either text or binary), but you can specify the encoding, e.g., ``'latin1'`` or ``'utf8'``""" @@ -347,7 +346,7 @@ def _access_mode_to_flags(mode, flags=None): return mode @abstractmethod - def access(self, mode: typing.Union[int, str] = 0) -> bool: + def access(self, mode: int | str = 0) -> bool: """Test file existence or permission bits :param mode: a bitwise-or of access bits, or a string-representation thereof: diff --git a/plumbum/path/local.py b/plumbum/path/local.py index cea125a47..c46983f32 100644 --- a/plumbum/path/local.py +++ b/plumbum/path/local.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import errno import glob import logging diff --git a/plumbum/path/remote.py b/plumbum/path/remote.py index 19c5c4da8..70cf0cab8 100644 --- a/plumbum/path/remote.py +++ b/plumbum/path/remote.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import errno import os import urllib.request as urllib diff --git a/plumbum/path/utils.py b/plumbum/path/utils.py index 5f644fdd3..840a6d8ff 100644 --- a/plumbum/path/utils.py +++ b/plumbum/path/utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from plumbum.machines.local import local diff --git a/plumbum/typed_env.py b/plumbum/typed_env.py index 9bc85d862..0d6fcacf2 100644 --- a/plumbum/typed_env.py +++ b/plumbum/typed_env.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import inspect import os from collections.abc import MutableMapping diff --git a/pyproject.toml b/pyproject.toml index 176d61c49..a2d98be8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ description = "Plumbum: shell combinators library" readme = "README.rst" authors = [{ name="Tomer Filiba", email="tomerfiliba@gmail.com" }] license = { file="LICENSE" } -requires-python = ">=3.6" +requires-python = ">=3.8" dynamic = ["version"] dependencies = [ "pywin32; platform_system=='Windows' and platform_python_implementation!='PyPy'", @@ -24,8 +24,6 @@ classifiers = [ "Operating System :: POSIX", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -57,13 +55,17 @@ Cheatsheet = "https://plumbum.readthedocs.io/en/latest/quickref.html" [project.optional-dependencies] -dev = [ +test = [ + "coverage[toml]", "paramiko", "psutil", - "pytest>=6.0", "pytest-cov", "pytest-mock", "pytest-timeout", + "pytest>=6.0", +] +dev = [ + "plumbum[test]", ] docs = [ "sphinx>=4.0.0", @@ -106,10 +108,10 @@ ignore_missing_imports = true [tool.pytest.ini_options] testpaths = ["tests"] minversion = "6.0" -addopts = ["-ra", "--showlocals", "--cov-config=setup.cfg", "--strict-markers", "--strict-config"] +addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config", "--cov-config=pyproject.toml" ] norecursedirs = ["examples", "experiments"] filterwarnings = [ - "always" + "always", ] log_cli_level = "info" xfail_strict = true @@ -122,7 +124,7 @@ optional_tests = """ [tool.pylint] -master.py-version = "3.6" +master.py-version = "3.8" master.jobs = "0" master.load-plugins = ["pylint.extensions.no_self_use"] reports.output-format = "colorized" @@ -169,7 +171,6 @@ messages_control.disable = [ ] [tool.ruff] -target-version = "py37" exclude = ["docs/conf.py"] [tool.ruff.lint] @@ -195,11 +196,12 @@ ignore = [ "E501", "PLR", "PT004", - "PT011", # TODO: add match parameter + "PT011", # TODO: add match parameter "RUF012", # ClassVar required if mutable "ISC001", # conflicts with formatter ] flake8-unused-arguments.ignore-variadic-names = true +isort.required-imports = ["from __future__ import annotations"] [tool.ruff.lint.per-file-ignores] "examples/*" = ["T20"] @@ -212,3 +214,22 @@ flake8-unused-arguments.ignore-variadic-names = true [tool.codespell] ignore-words-list = "ans,switchs,hart,ot,twoo,fo" skip = "*.po" + + +[tool.coverage.run] +branch = true +relative_files = true +source_pkgs = ["plumbum"] +omit = [ + "*ipython*.py", + "*__main__.py", + "*_windows.py", +] + +[tool.coverage.report] +exclude_also = [ + "def __repr__", + "raise AssertionError", + "raise NotImplementedError", + "if __name__ == .__main__.:", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 19fb47031..000000000 --- a/setup.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[coverage:run] -branch = True -relative_files = True -source_pkgs = - plumbum -omit = - *ipython*.py - *__main__.py - *_windows.py - -[coverage:report] -exclude_lines = - pragma: no cover - def __repr__ - raise AssertionError - raise NotImplementedError - if __name__ == .__main__.: diff --git a/tests/_test_paramiko.py b/tests/_test_paramiko.py index 62a6d4344..24f760deb 100644 --- a/tests/_test_paramiko.py +++ b/tests/_test_paramiko.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum import local from plumbum.paramiko_machine import ParamikoMachine as PM diff --git a/tests/conftest.py b/tests/conftest.py index 355dfc472..ce46b7f1e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import itertools import logging import os diff --git a/tests/env.py b/tests/env.py index b08daf2f8..273753d34 100644 --- a/tests/env.py +++ b/tests/env.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import platform import sys diff --git a/tests/test_3_cli.py b/tests/test_3_cli.py index 279a56ba0..6ecca3663 100644 --- a/tests/test_3_cli.py +++ b/tests/test_3_cli.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum import cli @@ -14,7 +16,7 @@ def test_prog(self, capsys): class Main4Validator(cli.Application): - def main(self, myint: int, myint2: int, *mylist: "int") -> None: + def main(self, myint: int, myint2: int, *mylist: int) -> None: print(myint, myint2, mylist) diff --git a/tests/test_cli.py b/tests/test_cli.py index a36284e63..b2f51b173 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum import cli, local from plumbum.cli.terminal import get_terminal_size diff --git a/tests/test_clicolor.py b/tests/test_clicolor.py index 04547bd73..92d2ec1a7 100644 --- a/tests/test_clicolor.py +++ b/tests/test_clicolor.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum import cli, colors colors.use_color = 3 diff --git a/tests/test_color.py b/tests/test_color.py index 224b28ad0..6eb3c22de 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,4 +1,6 @@ # Just check to see if this file is importable +from __future__ import annotations + from plumbum.cli.image import Image # noqa: F401 from plumbum.colorlib.names import FindNearest, color_html from plumbum.colorlib.styles import ( # noqa: F401 diff --git a/tests/test_config.py b/tests/test_config.py index 7cd67c23c..c6d5fc599 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from plumbum import local diff --git a/tests/test_env.py b/tests/test_env.py index e76f3f1de..a822dc19a 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib from plumbum import local diff --git a/tests/test_factories.py b/tests/test_factories.py index 4a1a52489..d0cddf63a 100644 --- a/tests/test_factories.py +++ b/tests/test_factories.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from __future__ import annotations import pytest diff --git a/tests/test_local.py b/tests/test_local.py index 63583b415..a5559facc 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import pickle import signal diff --git a/tests/test_nohup.py b/tests/test_nohup.py index 6c8163059..b0fac51e2 100644 --- a/tests/test_nohup.py +++ b/tests/test_nohup.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import time diff --git a/tests/test_pipelines.py b/tests/test_pipelines.py index 8ea47e2d4..3efbbc41c 100644 --- a/tests/test_pipelines.py +++ b/tests/test_pipelines.py @@ -1,4 +1,4 @@ -from typing import List, Tuple +from __future__ import annotations import pytest @@ -75,7 +75,7 @@ def process_cmd(tmp_path): return plumbum.local["python"][process] -def get_output_with_iter_lines(cmd: BaseCommand) -> Tuple[List[str], List[str]]: +def get_output_with_iter_lines(cmd: BaseCommand) -> tuple[list[str], list[str]]: stderr, stdout = [], [] proc = cmd.popen() for stdout_line, stderr_line in proc.iter_lines(retcode=[0, None]): diff --git a/tests/test_putty.py b/tests/test_putty.py index 0b75124fa..b446aa7a8 100644 --- a/tests/test_putty.py +++ b/tests/test_putty.py @@ -1,5 +1,7 @@ """Test that PuttyMachine initializes its SshMachine correctly""" +from __future__ import annotations + import pytest from plumbum import PuttyMachine, SshMachine diff --git a/tests/test_remote.py b/tests/test_remote.py index 1f296d7b6..0bd44fb19 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging import os import socket diff --git a/tests/test_sudo.py b/tests/test_sudo.py index 0dfee675d..4450235cf 100644 --- a/tests/test_sudo.py +++ b/tests/test_sudo.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from plumbum import local diff --git a/tests/test_terminal.py b/tests/test_terminal.py index 2ad6a3afe..5f72b1c57 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from collections import OrderedDict from contextlib import contextmanager diff --git a/tests/test_typed_env.py b/tests/test_typed_env.py index 076842da3..548ae3bd6 100644 --- a/tests/test_typed_env.py +++ b/tests/test_typed_env.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from plumbum.typed_env import TypedEnv diff --git a/tests/test_utils.py b/tests/test_utils.py index 53ea22f8a..f5a38f2d9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from plumbum import SshMachine, local diff --git a/tests/test_validate.py b/tests/test_validate.py index 4dbbe7bb9..1590d9a15 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from plumbum import cli diff --git a/tests/test_visual_color.py b/tests/test_visual_color.py index 6ca244289..0be587a48 100644 --- a/tests/test_visual_color.py +++ b/tests/test_visual_color.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from __future__ import annotations import os import unittest diff --git a/translations.py b/translations.py index 4b984de7c..89b782fa5 100755 --- a/translations.py +++ b/translations.py @@ -2,6 +2,7 @@ # If you are on macOS and using brew, you might need the following first: # export PATH="/usr/local/opt/gettext/bin:$PATH" +from __future__ import annotations from plumbum import FG, local from plumbum.cmd import msgfmt, msgmerge, xgettext