From 65bda6c7ca6f5a798edd100a2cbb292a9f0d8032 Mon Sep 17 00:00:00 2001 From: Andrew Senetar Date: Wed, 6 Aug 2025 05:16:43 +0000 Subject: [PATCH] feat: Drop support for Python 2 and remove compatibility code This removes support for Python 2, and drops most of the compatibility code that was used to support both Python 2 and Python 3. --- send2trash/__init__.py | 3 +++ send2trash/__main__.py | 3 +++ send2trash/compat.py | 25 ------------------------- send2trash/exceptions.py | 10 ++-------- send2trash/mac/legacy.py | 3 +-- send2trash/mac/modern.py | 3 +-- send2trash/plat_other.py | 15 +++++++-------- send2trash/util.py | 4 ++-- send2trash/win/legacy.py | 3 +-- send2trash/win/modern.py | 3 +-- tests/test_plat_other.py | 8 ++------ 11 files changed, 23 insertions(+), 57 deletions(-) delete mode 100644 send2trash/compat.py diff --git a/send2trash/__init__.py b/send2trash/__init__.py index d09faa3..7683a5e 100644 --- a/send2trash/__init__.py +++ b/send2trash/__init__.py @@ -8,6 +8,9 @@ import sys from send2trash.exceptions import TrashPermissionError # noqa: F401 +if sys.version_info[0] < 3: + raise RuntimeError("send2trash is only compatible with Python 3 and above (use versions <= 1.8.3 for python 2).") + if sys.platform == "darwin": from send2trash.mac import send2trash elif sys.platform == "win32": diff --git a/send2trash/__main__.py b/send2trash/__main__.py index a733e82..1df566a 100644 --- a/send2trash/__main__.py +++ b/send2trash/__main__.py @@ -12,6 +12,9 @@ import sys from argparse import ArgumentParser from send2trash import send2trash +if sys.version_info[0] < 3: + raise RuntimeError("send2trash is only compatible with Python 3 and above (use versions <= 1.8.3 for python 2).") + def main(args=None): parser = ArgumentParser(description="Tool to send files to trash") diff --git a/send2trash/compat.py b/send2trash/compat.py deleted file mode 100644 index a3043a4..0000000 --- a/send2trash/compat.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2017 Virgil Dupras - -# This software is licensed under the "BSD" License as described in the "LICENSE" file, -# which should be included with this package. The terms are also available at -# http://www.hardcoded.net/licenses/bsd_license - -import sys -import os - -PY3 = sys.version_info[0] >= 3 -if PY3: - text_type = str - binary_type = bytes - if os.supports_bytes_environ: - # environb will be unset under Windows, but then again we're not supposed to use it. - environb = os.environb -else: - text_type = unicode # noqa: F821 - binary_type = str - environb = os.environ - -try: - from collections.abc import Iterable as iterable_type -except ImportError: - from collections import Iterable as iterable_type # noqa: F401 diff --git a/send2trash/exceptions.py b/send2trash/exceptions.py index 9e82766..5f134b9 100644 --- a/send2trash/exceptions.py +++ b/send2trash/exceptions.py @@ -1,13 +1,7 @@ import errno -from send2trash.compat import PY3 - -if PY3: - _permission_error = PermissionError # noqa: F821 -else: - _permission_error = OSError -class TrashPermissionError(_permission_error): +class TrashPermissionError(PermissionError): """A permission error specific to a trash directory. Raising this error indicates that permissions prevent us efficiently @@ -23,4 +17,4 @@ class TrashPermissionError(_permission_error): """ def __init__(self, filename): - _permission_error.__init__(self, errno.EACCES, "Permission denied", filename) + PermissionError.__init__(self, errno.EACCES, "Permission denied", filename) diff --git a/send2trash/mac/legacy.py b/send2trash/mac/legacy.py index 82f43d9..3a9bb29 100644 --- a/send2trash/mac/legacy.py +++ b/send2trash/mac/legacy.py @@ -9,7 +9,6 @@ from __future__ import unicode_literals from ctypes import cdll, byref, Structure, c_char, c_char_p from ctypes.util import find_library -from send2trash.compat import binary_type from send2trash.util import preprocess_paths Foundation = cdll.LoadLibrary(find_library("Foundation")) @@ -42,7 +41,7 @@ def check_op_result(op_result): def send2trash(paths): paths = preprocess_paths(paths) - paths = [path.encode("utf-8") if not isinstance(path, binary_type) else path for path in paths] + paths = [path.encode("utf-8") if not isinstance(path, bytes) else path for path in paths] for path in paths: fp = FSRef() opts = kFSPathMakeRefDoNotFollowLeafSymlink diff --git a/send2trash/mac/modern.py b/send2trash/mac/modern.py index 6098d5a..62dc97a 100644 --- a/send2trash/mac/modern.py +++ b/send2trash/mac/modern.py @@ -5,7 +5,6 @@ # http://www.hardcoded.net/licenses/bsd_license from Foundation import NSFileManager, NSURL -from send2trash.compat import text_type from send2trash.util import preprocess_paths @@ -18,7 +17,7 @@ def check_op_result(op_result): def send2trash(paths): paths = preprocess_paths(paths) - paths = [path.decode("utf-8") if not isinstance(path, text_type) else path for path in paths] + paths = [path.decode("utf-8") if not isinstance(path, str) else path for path in paths] for path in paths: file_url = NSURL.fileURLWithPath_(path) fm = NSFileManager.defaultManager() diff --git a/send2trash/plat_other.py b/send2trash/plat_other.py index ace7b13..617ff6c 100644 --- a/send2trash/plat_other.py +++ b/send2trash/plat_other.py @@ -30,7 +30,6 @@ except ImportError: # Python 2 from urllib import quote -from send2trash.compat import text_type, environb from send2trash.util import preprocess_paths from send2trash.exceptions import TrashPermissionError @@ -53,21 +52,21 @@ INFO_DIR = b"info" INFO_SUFFIX = b".trashinfo" # Default of ~/.local/share [3] -XDG_DATA_HOME = op.expanduser(environb.get(b"XDG_DATA_HOME", b"~/.local/share")) +XDG_DATA_HOME = op.expanduser(os.environb.get(b"XDG_DATA_HOME", b"~/.local/share")) HOMETRASH_B = op.join(XDG_DATA_HOME, b"Trash") HOMETRASH = fsdecode(HOMETRASH_B) uid = os.getuid() TOPDIR_TRASH = b".Trash" -TOPDIR_FALLBACK = b".Trash-" + text_type(uid).encode("ascii") +TOPDIR_FALLBACK = b".Trash-" + str(uid).encode("ascii") def is_parent(parent, path): path = op.realpath(path) # In case it's a symlink - if isinstance(path, text_type): + if isinstance(path, str): path = fsencode(path) parent = op.realpath(parent) - if isinstance(parent, text_type): + if isinstance(parent, str): parent = fsencode(parent) return path.startswith(parent) @@ -106,7 +105,7 @@ def trash_move(src, dst, topdir=None, cross_dev=False): destname = filename while op.exists(op.join(filespath, destname)) or op.exists(op.join(infopath, destname + INFO_SUFFIX)): counter += 1 - destname = base_name + b" " + text_type(counter).encode("ascii") + ext + destname = base_name + b" " + str(counter).encode("ascii") + ext check_create(filespath) check_create(infopath) @@ -142,7 +141,7 @@ def find_ext_volume_global_trash(volume_root): if not op.isdir(trash_dir) or op.islink(trash_dir) or not (mode & stat.S_ISVTX): return None - trash_dir = op.join(trash_dir, text_type(uid).encode("ascii")) + trash_dir = op.join(trash_dir, str(uid).encode("ascii")) try: check_create(trash_dir) except OSError: @@ -178,7 +177,7 @@ def get_dev(path): def send2trash(paths): paths = preprocess_paths(paths) for path in paths: - if isinstance(path, text_type): + if isinstance(path, str): path_b = fsencode(path) elif isinstance(path, bytes): path_b = path diff --git a/send2trash/util.py b/send2trash/util.py index 108537e..ea51b12 100644 --- a/send2trash/util.py +++ b/send2trash/util.py @@ -5,11 +5,11 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license -from send2trash.compat import text_type, binary_type, iterable_type +import collections.abc def preprocess_paths(paths): - if isinstance(paths, iterable_type) and not isinstance(paths, (text_type, binary_type)): + if isinstance(paths, collections.abc.Iterable) and not isinstance(paths, (str, bytes)): paths = list(paths) elif not isinstance(paths, list): paths = [paths] diff --git a/send2trash/win/legacy.py b/send2trash/win/legacy.py index 9abf5bc..f2d0767 100644 --- a/send2trash/win/legacy.py +++ b/send2trash/win/legacy.py @@ -7,7 +7,6 @@ from __future__ import unicode_literals import os.path as op -from send2trash.compat import text_type from send2trash.util import preprocess_paths from ctypes import ( @@ -143,7 +142,7 @@ def send2trash(paths): if not paths: return # convert data type - paths = [text_type(path, "mbcs") if not isinstance(path, text_type) else path for path in paths] + paths = [str(path, "mbcs") if not isinstance(path, str) else path for path in paths] # convert to full paths paths = [op.abspath(path) if not op.isabs(path) else path for path in paths] # get short path to handle path length issues diff --git a/send2trash/win/modern.py b/send2trash/win/modern.py index 7927a89..7cb60fb 100644 --- a/send2trash/win/modern.py +++ b/send2trash/win/modern.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals import os.path as op -from send2trash.compat import text_type from send2trash.util import preprocess_paths from platform import version import pythoncom @@ -20,7 +19,7 @@ def send2trash(paths): if not paths: return # convert data type - paths = [text_type(path, "mbcs") if not isinstance(path, text_type) else path for path in paths] + paths = [str(path, "mbcs") if not isinstance(path, str) else path for path in paths] # convert to full paths paths = [op.abspath(path) if not op.isabs(path) else path for path in paths] # remove the leading \\?\ if present diff --git a/tests/test_plat_other.py b/tests/test_plat_other.py index 331e3ef..925efe7 100644 --- a/tests/test_plat_other.py +++ b/tests/test_plat_other.py @@ -4,7 +4,6 @@ import codecs import os import sys from os import path as op -from send2trash.compat import PY3 from send2trash import TrashPermissionError try: @@ -89,7 +88,7 @@ def _filesys_enc(): @pytest.fixture def gen_unicode_file(): - name = u"send2trash_tést1" + name = "send2trash_tést1" file = op.join(op.expanduser(b"~"), name.encode("utf-8")) touch(file) assert op.exists(file) is True @@ -117,10 +116,7 @@ def test_trash_unicode(gen_unicode_file): class ExtVol: def __init__(self, path): self.trash_topdir = path - if PY3: - self.trash_topdir_b = os.fsencode(self.trash_topdir) - else: - self.trash_topdir_b = self.trash_topdir + self.trash_topdir_b = os.fsencode(self.trash_topdir) def s_getdev(path): from send2trash.plat_other import is_parent