From 432c31f392563ae52f430d5a88416d99d3f387d4 Mon Sep 17 00:00:00 2001 From: SamSchott Date: Mon, 23 Nov 2020 20:32:19 +0000 Subject: [PATCH] Drop Python 2.7 support and update encoding handling (#703) * drop python 2.7 support * switch to os.fsencode / os.fsdecode on Unixes * updated tests * remove Python 2.7 from CI * removed obsolete __future__ imports * removed Py 2.7 references from docs * removed Py 2.7 references from readme * add test for path with invalid bytes * add entries to changelog --- .travis.yml | 16 -------- README.rst | 2 +- changelog.rst | 5 ++- docs/source/hacking.rst | 8 +--- docs/source/index.rst | 2 +- setup.py | 1 - src/watchdog/events.py | 9 ++-- src/watchdog/observers/api.py | 1 - src/watchdog/observers/fsevents.py | 2 - src/watchdog/observers/fsevents2.py | 2 +- src/watchdog/observers/inotify.py | 9 ++-- src/watchdog/observers/inotify_c.py | 4 +- src/watchdog/observers/kqueue.py | 1 - src/watchdog/observers/polling.py | 1 - src/watchdog/utils/unicode_paths.py | 64 ----------------------------- tests/shell.py | 2 - tests/test_emitter.py | 25 +++++++++-- tests/test_inotify_buffer.py | 2 - tests/test_inotify_c.py | 1 - tests/test_observer.py | 2 - tests/test_watchmedo.py | 1 - 21 files changed, 36 insertions(+), 124 deletions(-) delete mode 100644 src/watchdog/utils/unicode_paths.py diff --git a/.travis.yml b/.travis.yml index 0256b0af7..22670d350 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,10 +13,6 @@ env: jobs: fast_finish: true include: - - name: Python 2.7 on GNU/Linux - os: linux - python: "2.7" - env: TOXENV=py27 - name: Python 3.5 on GNU/Linux os: linux python: "3.5" @@ -44,10 +40,6 @@ jobs: os: linux python: nightly env: TOXENV=py310 - - name: PyPy 2.7 on GNU/Linux - os: linux - python: "pypy" - env: TOXENV=pypy - name: PyPy 3.6 on GNU/Linux os: linux python: "pypy3" @@ -118,14 +110,6 @@ jobs: - pyenv install --skip-existing 3.10-dev - pyenv global system 3.10-dev env: TOXENV=py310 - - name: Python 2.7 on Windows - os: windows - language: shell - before_install: - - choco install python2 - env: - - TOXENV=py27 - - export PATH="/c/Python27:/c/Python27/Scripts:$PATH" - name: Python 3.5 on Windows os: windows language: shell diff --git a/README.rst b/README.rst index 4085e55ba..064f76a77 100755 --- a/README.rst +++ b/README.rst @@ -226,7 +226,7 @@ appropriate observer like in the example above, do:: Dependencies ------------ -1. Python 2.7, 3.4 or above. +1. Python 3.4 or above. 2. pathtools_ 3. XCode_ (only on Mac OS X) 4. PyYAML_ (only for ``watchmedo`` script) diff --git a/changelog.rst b/changelog.rst index 749afc7f2..c2505547f 100644 --- a/changelog.rst +++ b/changelog.rst @@ -8,8 +8,9 @@ Changelog 2021-xx-xx • `full history `__ -- (`# `_) -- Thanks to our beloved contributors: @ +- Allow file paths on Unix that don't follow the file system encoding (`# `_) +- Drop support for Python 2.7 (`# `_) +- Thanks to our beloved contributors: @SamSchott 0.10.4 diff --git a/docs/source/hacking.rst b/docs/source/hacking.rst index 9b4259525..253fca464 100644 --- a/docs/source/hacking.rst +++ b/docs/source/hacking.rst @@ -40,13 +40,7 @@ Steps to setting up a clean environment: 3. Linux -For example Debian and Python 2.7: - -.. code:: bash - - $ sudo apt-get install python-pip python-virtualenv - -For Python 3: +For example Debian: .. code:: bash diff --git a/docs/source/index.rst b/docs/source/index.rst index 765d0f04a..d387588c9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -11,7 +11,7 @@ Watchdog Python API library and shell utilities to monitor file system events. -Works on Python 2.7 and 3.4+. If you want to use an old version of Python, you should stick with watchdog < 0.10.0. +Works on Python 3.4+. If you want to use an old version of Python, you should stick with watchdog < 0.10.0. Directory monitoring made easy with ----------------------------------- diff --git a/setup.py b/setup.py index ed2ed4858..a379f553c 100644 --- a/setup.py +++ b/setup.py @@ -128,7 +128,6 @@ 'Operating System :: Microsoft :: Windows :: Windows NT/2000', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', diff --git a/src/watchdog/events.py b/src/watchdog/events.py index 766cca050..38e0d8c77 100755 --- a/src/watchdog/events.py +++ b/src/watchdog/events.py @@ -90,7 +90,6 @@ import re from pathtools.patterns import match_any_paths from watchdog.utils import has_attribute -from watchdog.utils import unicode_paths EVENT_TYPE_MOVED = 'moved' @@ -441,9 +440,9 @@ def dispatch(self, event): paths = [] if has_attribute(event, 'dest_path'): - paths.append(unicode_paths.decode(event.dest_path)) + paths.append(os.fsdecode(event.dest_path)) if event.src_path: - paths.append(unicode_paths.decode(event.src_path)) + paths.append(os.fsdecode(event.src_path)) if match_any_paths(paths, included_patterns=self.patterns, @@ -520,9 +519,9 @@ def dispatch(self, event): paths = [] if has_attribute(event, 'dest_path'): - paths.append(unicode_paths.decode(event.dest_path)) + paths.append(os.fsdecode(event.dest_path)) if event.src_path: - paths.append(unicode_paths.decode(event.src_path)) + paths.append(os.fsdecode(event.src_path)) if any(r.match(p) for r in self.ignore_regexes for p in paths): return diff --git a/src/watchdog/observers/api.py b/src/watchdog/observers/api.py index d81a33b73..8b296abd0 100644 --- a/src/watchdog/observers/api.py +++ b/src/watchdog/observers/api.py @@ -16,7 +16,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement import threading from watchdog.utils import BaseThread from watchdog.utils.compat import queue diff --git a/src/watchdog/observers/fsevents.py b/src/watchdog/observers/fsevents.py index 26ae8becd..b7c5e89a6 100644 --- a/src/watchdog/observers/fsevents.py +++ b/src/watchdog/observers/fsevents.py @@ -23,8 +23,6 @@ :platforms: Mac OS X """ -from __future__ import with_statement - import os import sys import threading diff --git a/src/watchdog/observers/fsevents2.py b/src/watchdog/observers/fsevents2.py index 9f48d7e75..0b01a3aea 100644 --- a/src/watchdog/observers/fsevents2.py +++ b/src/watchdog/observers/fsevents2.py @@ -87,7 +87,7 @@ def __init__(self, path): self._run_loop = None if isinstance(path, bytes): - path = path.decode('utf-8') + path = os.fsdecode(path) self._path = unicodedata.normalize('NFC', path) context = None diff --git a/src/watchdog/observers/inotify.py b/src/watchdog/observers/inotify.py index 68ffa66ea..373a6343e 100644 --- a/src/watchdog/observers/inotify.py +++ b/src/watchdog/observers/inotify.py @@ -67,8 +67,6 @@ """ -from __future__ import with_statement - import os import threading from .inotify_buffer import InotifyBuffer @@ -92,7 +90,6 @@ generate_sub_moved_events, generate_sub_created_events, ) -from watchdog.utils import unicode_paths class InotifyEmitter(EventEmitter): @@ -117,7 +114,7 @@ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): self._inotify = None def on_thread_start(self): - path = unicode_paths.encode(self.watch.path) + path = os.fsencode(self.watch.path) self._inotify = InotifyBuffer(path, self.watch.is_recursive) def on_thread_stop(self): @@ -176,10 +173,10 @@ def queue_events(self, timeout, full_events=False): self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) def _decode_path(self, path): - """ Decode path only if unicode string was passed to this emitter. """ + """Decode path only if unicode string was passed to this emitter. """ if isinstance(self.watch.path, bytes): return path - return unicode_paths.decode(path) + return os.fsdecode(path) class InotifyFullEmitter(InotifyEmitter): diff --git a/src/watchdog/observers/inotify_c.py b/src/watchdog/observers/inotify_c.py index 040224356..eb02f3c38 100644 --- a/src/watchdog/observers/inotify_c.py +++ b/src/watchdog/observers/inotify_c.py @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import with_statement import os import errno import struct @@ -26,7 +25,6 @@ from ctypes import c_int, c_char_p, c_uint32 from watchdog.utils import has_attribute from watchdog.utils import UnsupportedLibc -from watchdog.utils.unicode_paths import decode def _load_libc(): @@ -588,4 +586,4 @@ def __repr__(self): mask_string = self._get_mask_string(self.mask) s = '<%s: src_path=%r, wd=%d, mask=%s, cookie=%d, name=%s>' return s % (type(self).__name__, self.src_path, self.wd, mask_string, - self.cookie, decode(self.name)) + self.cookie, os.fsdecode(self.name)) diff --git a/src/watchdog/observers/kqueue.py b/src/watchdog/observers/kqueue.py index 553e4bee6..20468e052 100644 --- a/src/watchdog/observers/kqueue.py +++ b/src/watchdog/observers/kqueue.py @@ -67,7 +67,6 @@ """ -from __future__ import with_statement from watchdog.utils import platform import threading diff --git a/src/watchdog/observers/polling.py b/src/watchdog/observers/polling.py index 46ad99f5f..fc500d95d 100644 --- a/src/watchdog/observers/polling.py +++ b/src/watchdog/observers/polling.py @@ -34,7 +34,6 @@ :special-members: """ -from __future__ import with_statement import threading from functools import partial from watchdog.utils import stat as default_stat diff --git a/src/watchdog/utils/unicode_paths.py b/src/watchdog/utils/unicode_paths.py deleted file mode 100644 index 501a2f151..000000000 --- a/src/watchdog/utils/unicode_paths.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2013 Will Bond -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -import sys - -from watchdog.utils import platform - -try: - # Python 2 - str_cls = unicode - bytes_cls = str -except NameError: - # Python 3 - str_cls = str - bytes_cls = bytes - - -# This is used by Linux when the locale seems to be improperly set. UTF-8 tends -# to be the encoding used by all distros, so this is a good fallback. -fs_fallback_encoding = 'utf-8' -fs_encoding = sys.getfilesystemencoding() or fs_fallback_encoding - - -def encode(path): - if isinstance(path, str_cls): - try: - path = path.encode(fs_encoding, 'strict') - except UnicodeEncodeError: - if not platform.is_linux(): - raise - path = path.encode(fs_fallback_encoding, 'strict') - return path - - -def decode(path): - if isinstance(path, bytes_cls): - try: - path = path.decode(fs_encoding, 'strict') - except UnicodeDecodeError: - if not platform.is_linux(): - raise - path = path.decode(fs_fallback_encoding, 'strict') - return path diff --git a/tests/shell.py b/tests/shell.py index 376fdf14e..75649901f 100644 --- a/tests/shell.py +++ b/tests/shell.py @@ -22,8 +22,6 @@ :author: yesudeep@google.com (Yesudeep Mangalapilly) """ -from __future__ import with_statement - import os import os.path import tempfile diff --git a/tests/test_emitter.py b/tests/test_emitter.py index d5e2a63d8..cfe3d0d13 100644 --- a/tests/test_emitter.py +++ b/tests/test_emitter.py @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import unicode_literals import os import time import pytest @@ -23,7 +22,6 @@ from . import Queue, Empty from .shell import mkdir, touch, mv, rm from watchdog.utils import platform -from watchdog.utils.unicode_paths import str_cls from watchdog.events import ( FileDeletedEvent, FileModifiedEvent, @@ -105,6 +103,25 @@ def test_create(): assert isinstance(event, DirModifiedEvent) +@pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) +@pytest.mark.skipif( + platform.is_darwin() or platform.is_windows(), + reason="Windows and macOS enforce proper encoding" +) +def test_create_wrong_encoding(): + start_watching() + open(p('a_\udce4'), 'a').close() + + event = event_queue.get(timeout=5)[0] + assert event.src_path == p('a_\udce4') + assert isinstance(event, FileCreatedEvent) + + if not platform.is_windows(): + event = event_queue.get(timeout=5)[0] + assert os.path.normpath(event.src_path) == os.path.normpath(p('')) + assert isinstance(event, DirModifiedEvent) + + @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) def test_delete(): touch(p('a')) @@ -303,10 +320,10 @@ def test_fast_subdirectory_creation_deletion(): @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) def test_passing_unicode_should_give_unicode(): - start_watching(str_cls(p(""))) + start_watching(str(p(""))) touch(p('a')) event = event_queue.get(timeout=5)[0] - assert isinstance(event.src_path, str_cls) + assert isinstance(event.src_path, str) @pytest.mark.skipif(platform.is_windows(), diff --git a/tests/test_inotify_buffer.py b/tests/test_inotify_buffer.py index 24d467eb6..f1d6d2cb8 100644 --- a/tests/test_inotify_buffer.py +++ b/tests/test_inotify_buffer.py @@ -14,8 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import unicode_literals - import pytest from watchdog.utils import platform diff --git a/tests/test_inotify_c.py b/tests/test_inotify_c.py index b4d9f9fc6..ee1072ec8 100644 --- a/tests/test_inotify_c.py +++ b/tests/test_inotify_c.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import pytest from watchdog.utils import platform diff --git a/tests/test_observer.py b/tests/test_observer.py index 4d3e96707..1ad14156f 100644 --- a/tests/test_observer.py +++ b/tests/test_observer.py @@ -14,8 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import unicode_literals - import pytest from watchdog.events import FileSystemEventHandler, FileModifiedEvent from watchdog.utils.compat import Event diff --git a/tests/test_watchmedo.py b/tests/test_watchmedo.py index ec8f27265..fdc4d050d 100644 --- a/tests/test_watchmedo.py +++ b/tests/test_watchmedo.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import pytest