1
0
mirror of https://github.com/arsenetar/send2trash.git synced 2026-03-12 02:41:39 +00:00

11 Commits

Author SHA1 Message Date
9a2c5bc690 chore: Update project configuration and ci for python version changes
- Drop support for Python 2
- Drop support for Python 3.7, 3.8 is new minimum
- Update tox to include newer python version and drop old ones
- Update GitHub action for python version changes, use standard python
  setup action
- Update GitHub action to use pinned action versions
- Update version to 2.0.0-dev
2025-08-06 05:27:33 +00:00
65bda6c7ca 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.
2025-08-06 05:27:32 +00:00
91d0698967 Merge pull request #91 from arsenetar/as/misc-fixes
chore: Upgrade version to 1.8.3, bump CI
2024-04-06 18:53:45 -05:00
5c47eb063c fix(ci): Use alternative setup-python to allow 2.7
The setup python action has removed 2.7, we want to keep it for the
moment.  Using an alternative action that supports 2.7.
2024-04-06 16:51:34 -07:00
c7a23884a9 chore: Upgrade version to 1.8.3, bump CI
- Upgrade version to 1.8.3, add changelog entry
- Fix minor flake8 error that can be ignored
- Update CI workflow to use newer actions and python versions
2024-04-06 16:36:23 -07:00
78fa300cac Merge pull request #90 from yogeshiitm/master
Fix bug when source and destination directories are on different file systems
2024-03-28 01:17:37 -05:00
Yogesh Agarwala
baeb9e59f9 Fix bug in send2trash: Use os.fsdecode() in shutil.move()
`shutil.move()` function expects string paths, not byte paths. This bug is leading to failure when src and dst are on on the different file system.
2024-03-23 05:37:21 +05:30
0a48c26f68 Merge pull request #88 from PalmtopTiger/iterable-types
Support for any iterable type as input data
2024-02-19 09:08:53 -06:00
Andrey Efremov
ed039dc892 Support for any iterable type as input data 2024-01-11 14:25:22 +07:00
e59ddcae98 Merge pull request #79 from mgorny/wheel
Remove redundant wheel dep from pyproject.toml
2023-05-03 02:41:22 -05:00
Michał Górny
19cf5d941a Remove redundant wheel dep from pyproject.toml
Remove the redundant `wheel` dependency, as it is added by the backend
automatically.  Listing it explicitly in the documentation was
a historical mistake and has been fixed since, see:
f7d30a9529
2023-04-27 18:12:32 +02:00
16 changed files with 49 additions and 83 deletions

View File

@@ -2,19 +2,15 @@
name: Default CI/CD name: Default CI/CD
on: on: push
push:
branches: [master]
pull_request:
branches: [master]
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python 3.x - name: Set up Python 3.x
uses: actions/setup-python@v3 uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with: with:
python-version: 3.x python-version: 3.x
- name: Install dependencies - name: Install dependencies
@@ -30,6 +26,10 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- os: ubuntu-latest
python-version: 3.13
- os: ubuntu-latest
python-version: 3.12
- os: ubuntu-latest - os: ubuntu-latest
python-version: 3.11 python-version: 3.11
- os: ubuntu-latest - os: ubuntu-latest
@@ -38,31 +38,19 @@ jobs:
python-version: 3.9 python-version: 3.9
- os: ubuntu-latest - os: ubuntu-latest
python-version: 3.8 python-version: 3.8
- os: ubuntu-latest
python-version: 3.7
- os: ubuntu-20.04
python-version: 3.6
- os: ubuntu-20.04
python-version: 3.5
- os: ubuntu-latest
python-version: 2.7
# - os: macos-latest # - os: macos-latest
# python-version: 3.11 # python-version: 3.13
# - os: macos-latest # - os: macos-latest
# python-version: 3.8 # python-version: 3.8
# - os: macos-latest
# python-version: 2.7
- os: windows-latest - os: windows-latest
python-version: 3.11 python-version: 3.13
- os: windows-latest - os: windows-latest
python-version: 3.8 python-version: 3.8
- os: windows-latest
python-version: 2.7
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3 uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies

View File

@@ -1,6 +1,11 @@
Changes Changes
======= =======
Version 1.8.3 -- 2024/04/06
---------------------------
* Add support for any iterable type as input by @PalmtopTiger in https://github.com/arsenetar/send2trash/pull/88
* fix: Use os.fsdecode() for arguments to shutil.move() by @yogeshiitm in https://github.com/arsenetar/send2trash/pull/90
Version 1.8.2 -- 2023/04/27 Version 1.8.2 -- 2023/04/27
--------------------------- ---------------------------
* win/legacy: tiny logic simplification by @BoboTiG in https://github.com/arsenetar/send2trash/pull/77 * win/legacy: tiny logic simplification by @BoboTiG in https://github.com/arsenetar/send2trash/pull/77

View File

@@ -1,5 +1,5 @@
[build-system] [build-system]
requires = ["setuptools >= 40.6.0", "wheel"] requires = ["setuptools >= 75.3.1"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.black] [tool.black]

View File

@@ -8,6 +8,9 @@ import sys
from send2trash.exceptions import TrashPermissionError # noqa: F401 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": if sys.platform == "darwin":
from send2trash.mac import send2trash from send2trash.mac import send2trash
elif sys.platform == "win32": elif sys.platform == "win32":

View File

@@ -12,6 +12,9 @@ import sys
from argparse import ArgumentParser from argparse import ArgumentParser
from send2trash import send2trash 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): def main(args=None):
parser = ArgumentParser(description="Tool to send files to trash") parser = ArgumentParser(description="Tool to send files to trash")

View File

@@ -1,20 +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

View File

@@ -1,13 +1,7 @@
import errno 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. """A permission error specific to a trash directory.
Raising this error indicates that permissions prevent us efficiently Raising this error indicates that permissions prevent us efficiently
@@ -23,4 +17,4 @@ class TrashPermissionError(_permission_error):
""" """
def __init__(self, filename): def __init__(self, filename):
_permission_error.__init__(self, errno.EACCES, "Permission denied", filename) PermissionError.__init__(self, errno.EACCES, "Permission denied", filename)

View File

@@ -9,7 +9,6 @@ from __future__ import unicode_literals
from ctypes import cdll, byref, Structure, c_char, c_char_p from ctypes import cdll, byref, Structure, c_char, c_char_p
from ctypes.util import find_library from ctypes.util import find_library
from send2trash.compat import binary_type
from send2trash.util import preprocess_paths from send2trash.util import preprocess_paths
Foundation = cdll.LoadLibrary(find_library("Foundation")) Foundation = cdll.LoadLibrary(find_library("Foundation"))
@@ -42,7 +41,7 @@ def check_op_result(op_result):
def send2trash(paths): def send2trash(paths):
paths = preprocess_paths(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: for path in paths:
fp = FSRef() fp = FSRef()
opts = kFSPathMakeRefDoNotFollowLeafSymlink opts = kFSPathMakeRefDoNotFollowLeafSymlink

View File

@@ -5,7 +5,6 @@
# http://www.hardcoded.net/licenses/bsd_license # http://www.hardcoded.net/licenses/bsd_license
from Foundation import NSFileManager, NSURL from Foundation import NSFileManager, NSURL
from send2trash.compat import text_type
from send2trash.util import preprocess_paths from send2trash.util import preprocess_paths
@@ -18,7 +17,7 @@ def check_op_result(op_result):
def send2trash(paths): def send2trash(paths):
paths = preprocess_paths(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: for path in paths:
file_url = NSURL.fileURLWithPath_(path) file_url = NSURL.fileURLWithPath_(path)
fm = NSFileManager.defaultManager() fm = NSFileManager.defaultManager()

View File

@@ -30,7 +30,6 @@ except ImportError:
# Python 2 # Python 2
from urllib import quote from urllib import quote
from send2trash.compat import text_type, environb
from send2trash.util import preprocess_paths from send2trash.util import preprocess_paths
from send2trash.exceptions import TrashPermissionError from send2trash.exceptions import TrashPermissionError
@@ -53,21 +52,21 @@ INFO_DIR = b"info"
INFO_SUFFIX = b".trashinfo" INFO_SUFFIX = b".trashinfo"
# Default of ~/.local/share [3] # 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_B = op.join(XDG_DATA_HOME, b"Trash")
HOMETRASH = fsdecode(HOMETRASH_B) HOMETRASH = fsdecode(HOMETRASH_B)
uid = os.getuid() uid = os.getuid()
TOPDIR_TRASH = b".Trash" 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): def is_parent(parent, path):
path = op.realpath(path) # In case it's a symlink path = op.realpath(path) # In case it's a symlink
if isinstance(path, text_type): if isinstance(path, str):
path = fsencode(path) path = fsencode(path)
parent = op.realpath(parent) parent = op.realpath(parent)
if isinstance(parent, text_type): if isinstance(parent, str):
parent = fsencode(parent) parent = fsencode(parent)
return path.startswith(parent) return path.startswith(parent)
@@ -106,7 +105,7 @@ def trash_move(src, dst, topdir=None, cross_dev=False):
destname = filename destname = filename
while op.exists(op.join(filespath, destname)) or op.exists(op.join(infopath, destname + INFO_SUFFIX)): while op.exists(op.join(filespath, destname)) or op.exists(op.join(infopath, destname + INFO_SUFFIX)):
counter += 1 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(filespath)
check_create(infopath) check_create(infopath)
@@ -115,7 +114,7 @@ def trash_move(src, dst, topdir=None, cross_dev=False):
f.write(info_for(src, topdir)) f.write(info_for(src, topdir))
destpath = op.join(filespath, destname) destpath = op.join(filespath, destname)
if cross_dev: if cross_dev:
shutil.move(src, destpath) shutil.move(fsdecode(src), fsdecode(destpath))
else: else:
os.rename(src, destpath) os.rename(src, destpath)
@@ -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): if not op.isdir(trash_dir) or op.islink(trash_dir) or not (mode & stat.S_ISVTX):
return None return None
trash_dir = op.join(trash_dir, text_type(uid).encode("ascii")) trash_dir = op.join(trash_dir, str(uid).encode("ascii"))
try: try:
check_create(trash_dir) check_create(trash_dir)
except OSError: except OSError:
@@ -178,7 +177,7 @@ def get_dev(path):
def send2trash(paths): def send2trash(paths):
paths = preprocess_paths(paths) paths = preprocess_paths(paths)
for path in paths: for path in paths:
if isinstance(path, text_type): if isinstance(path, str):
path_b = fsencode(path) path_b = fsencode(path)
elif isinstance(path, bytes): elif isinstance(path, bytes):
path_b = path path_b = path

View File

@@ -5,9 +5,13 @@
# which should be included with this package. The terms are also available at # which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license # http://www.hardcoded.net/licenses/bsd_license
import collections.abc
def preprocess_paths(paths): def preprocess_paths(paths):
if not isinstance(paths, list): if isinstance(paths, collections.abc.Iterable) and not isinstance(paths, (str, bytes)):
paths = list(paths)
elif not isinstance(paths, list):
paths = [paths] paths = [paths]
# Convert items such as pathlib paths to strings # Convert items such as pathlib paths to strings
paths = [path.__fspath__() if hasattr(path, "__fspath__") else path for path in paths] paths = [path.__fspath__() if hasattr(path, "__fspath__") else path for path in paths]

View File

@@ -7,7 +7,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os.path as op import os.path as op
from send2trash.compat import text_type
from send2trash.util import preprocess_paths from send2trash.util import preprocess_paths
from ctypes import ( from ctypes import (
@@ -143,7 +142,7 @@ def send2trash(paths):
if not paths: if not paths:
return return
# convert data type # 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 # convert to full paths
paths = [op.abspath(path) if not op.isabs(path) else path for path in paths] paths = [op.abspath(path) if not op.isabs(path) else path for path in paths]
# get short path to handle path length issues # get short path to handle path length issues

View File

@@ -6,7 +6,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os.path as op import os.path as op
from send2trash.compat import text_type
from send2trash.util import preprocess_paths from send2trash.util import preprocess_paths
from platform import version from platform import version
import pythoncom import pythoncom
@@ -20,7 +19,7 @@ def send2trash(paths):
if not paths: if not paths:
return return
# convert data type # 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 # convert to full paths
paths = [op.abspath(path) if not op.isabs(path) else path for path in paths] paths = [op.abspath(path) if not op.isabs(path) else path for path in paths]
# remove the leading \\?\ if present # remove the leading \\?\ if present

View File

@@ -1,6 +1,6 @@
[metadata] [metadata]
name = Send2Trash name = Send2Trash
version = 1.8.2 version = 2.0.0-dev
url = https://github.com/arsenetar/send2trash url = https://github.com/arsenetar/send2trash
project_urls = project_urls =
Bug Reports = https://github.com/arsenetar/send2trash/issues Bug Reports = https://github.com/arsenetar/send2trash/issues
@@ -18,21 +18,19 @@ classifiers =
Operating System :: MacOS :: MacOS X Operating System :: MacOS :: MacOS X
Operating System :: Microsoft :: Windows Operating System :: Microsoft :: Windows
Operating System :: POSIX Operating System :: POSIX
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: 3.13
Topic :: Desktop Environment :: File Managers Topic :: Desktop Environment :: File Managers
[options] [options]
packages = find: packages = find:
tests_require = pytest tests_require = pytest
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* python_requires = !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*
[options.packages.find] [options.packages.find]
include= include=

View File

@@ -4,7 +4,6 @@ import codecs
import os import os
import sys import sys
from os import path as op from os import path as op
from send2trash.compat import PY3
from send2trash import TrashPermissionError from send2trash import TrashPermissionError
try: try:
@@ -89,7 +88,7 @@ def _filesys_enc():
@pytest.fixture @pytest.fixture
def gen_unicode_file(): def gen_unicode_file():
name = u"send2trash_tést1" name = "send2trash_tést1"
file = op.join(op.expanduser(b"~"), name.encode("utf-8")) file = op.join(op.expanduser(b"~"), name.encode("utf-8"))
touch(file) touch(file)
assert op.exists(file) is True assert op.exists(file) is True
@@ -117,10 +116,7 @@ def test_trash_unicode(gen_unicode_file):
class ExtVol: class ExtVol:
def __init__(self, path): def __init__(self, path):
self.trash_topdir = path self.trash_topdir = path
if PY3: self.trash_topdir_b = os.fsencode(self.trash_topdir)
self.trash_topdir_b = os.fsencode(self.trash_topdir)
else:
self.trash_topdir_b = self.trash_topdir
def s_getdev(path): def s_getdev(path):
from send2trash.plat_other import is_parent from send2trash.plat_other import is_parent

View File

@@ -1,5 +1,5 @@
[tox] [tox]
envlist = py{27,34,35,36,37,38,39,310} envlist = py{38,39,310,311,312,313}
skip_missing_interpreters = True skip_missing_interpreters = True
isolated_build = True isolated_build = True