Skip to content

Commit

Permalink
Use the standard pathlib instead of pathtools (fixes #556)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boris Staletic committed Nov 28, 2020
1 parent 432c31f commit efdc69f
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 13 deletions.
2 changes: 0 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ appropriate observer like in the example above, do::
Dependencies
------------
1. Python 3.4 or above.
2. pathtools_
3. XCode_ (only on Mac OS X)
4. PyYAML_ (only for ``watchmedo`` script)
5. argh_ (only for ``watchmedo`` script)
Expand Down Expand Up @@ -277,7 +276,6 @@ to do:
.. _PyYAML: http://www.pyyaml.org/
.. _XCode: http://developer.apple.com/technologies/tools/xcode.html
.. _LibYAML: http://pyyaml.org/wiki/LibYAML
.. _pathtools: http://github.com/gorakhargosh/pathtools

.. _pnotify: http://mark.heily.com/pnotify
.. _unison fsmonitor: https://webdav.seas.upenn.edu/viewvc/unison/trunk/src/fsmonitor.py?view=markup&pathrev=471
Expand Down
1 change: 1 addition & 0 deletions changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Changelog
- Allow file paths on Unix that don't follow the file system encoding (`# <https://github.com/gorakhargosh/watchdog/pull/703>`_)
- Drop support for Python 2.7 (`# <https://github.com/gorakhargosh/watchdog/pull/703>`_)
- Thanks to our beloved contributors: @SamSchott
- Use `pathlib` from the standard library, instead of pathtools: @bstaletic


0.10.4
Expand Down
1 change: 0 additions & 1 deletion docs/requirements.txt

This file was deleted.

1 change: 0 additions & 1 deletion docs/source/global.rst.inc
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,3 @@
.. _unison fsmonitor: https://webdav.seas.upenn.edu/viewvc/unison/trunk/src/fsmonitor.py?view=markup&pathrev=471
.. _XCode: http://developer.apple.com/technologies/tools/xcode.html
.. _zc.buildout: http://www.buildout.org/
.. _pathtools: http://github.com/gorakhargosh/pathtools
2 changes: 0 additions & 2 deletions docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ using.
+=====================+=============+=============+=============+=============+
| XCode_ | | | Yes | |
+---------------------+-------------+-------------+-------------+-------------+
| pathtools_ | Yes | Yes | Yes | Yes |
+---------------------+-------------+-------------+-------------+-------------+

The following is a list of dependencies you need based on the operating system you are
using the ``watchmedo`` utility.
Expand Down
4 changes: 0 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@
),
]

install_requires = [
"pathtools>=0.1.1",
]
extras_require = {
'watchmedo': ['PyYAML>=3.10', 'argh>=0.24.1'],
}
Expand Down Expand Up @@ -145,7 +142,6 @@
package_dir={'': SRC_DIR},
packages=find_packages(SRC_DIR),
include_package_data=True,
install_requires=install_requires,
extras_require=extras_require,
cmdclass={
'build_ext': build_ext,
Expand Down
2 changes: 1 addition & 1 deletion src/watchdog/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@
import os.path
import logging
import re
from pathtools.patterns import match_any_paths
from watchdog.utils import has_attribute
from watchdog.utils.patterns import match_any_paths


EVENT_TYPE_MOVED = 'moved'
Expand Down
6 changes: 5 additions & 1 deletion src/watchdog/observers/kqueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
import os.path
import select

from pathtools.path import absolute_path
from pathlib import Path

from watchdog.observers.api import (
BaseObserver,
Expand Down Expand Up @@ -126,6 +126,10 @@
| select.KQ_NOTE_REVOKE
)


def absolute_path(path):
return Path(path).resolve()

# Flag tests.


Expand Down
88 changes: 88 additions & 0 deletions src/watchdog/utils/patterns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# patterns.py: Common wildcard searching/filtering functionality for files.
#
# Copyright (C) 2010 Yesudeep Mangalapilly <yesudeep@gmail.com>
#
# Written by Boris Staletic <boris.staletic@gmail.com>

# Non-pure path objects are only allowed on their respective OS's.
# Thus, these utilities require "pure" path objects that don't access the filesystem.
# Since pathlib doesn't have a `case_sensitive` parameter, we have to approximate it
# by converting input paths to `PureWindowsPath` and `PurePosixPath` where:
# - `PureWindowsPath` is always case-insensitive.
# - `PurePosixPath` is always case-sensitive.
# Reference: https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.match
from pathlib import PureWindowsPath, PurePosixPath


def _match_path(path, included_patterns, excluded_patterns, case_sensitive):
"""Internal function same as :func:`match_path` but does not check arguments."""
if case_sensitive:
path = PurePosixPath(path)
else:
included_patterns = {pattern.lower() for pattern in included_patterns}
excluded_patterns = {pattern.lower() for pattern in excluded_patterns}
path = PureWindowsPath(path)

common_patterns = included_patterns & excluded_patterns
if common_patterns:
raise ValueError('conflicting patterns `{}` included and excluded'.format(common_patterns))
return (any(path.match(p) for p in included_patterns)
and not any(path.match(p) for p in excluded_patterns))


def filter_paths(paths, included_patterns=None, excluded_patterns=None, case_sensitive=True):
"""
Filters from a set of paths based on acceptable patterns and
ignorable patterns.
:param pathnames:
A list of path names that will be filtered based on matching and
ignored patterns.
:param included_patterns:
Allow filenames matching wildcard patterns specified in this list.
If no pattern list is specified, ["*"] is used as the default pattern,
which matches all files.
:param excluded_patterns:
Ignores filenames matching wildcard patterns specified in this list.
If no pattern list is specified, no files are ignored.
:param case_sensitive:
``True`` if matching should be case-sensitive; ``False`` otherwise.
:returns:
A list of pathnames that matched the allowable patterns and passed
through the ignored patterns.
"""
included = ["*"] if included_patterns is None else included_patterns
excluded = [] if excluded_patterns is None else excluded_patterns

for path in paths:
if _match_path(path, set(included), set(excluded), case_sensitive):
yield path


def match_any_paths(paths, included_patterns=None, excluded_patterns=None, case_sensitive=True):
"""
Matches from a set of paths based on acceptable patterns and
ignorable patterns.
:param pathnames:
A list of path names that will be filtered based on matching and
ignored patterns.
:param included_patterns:
Allow filenames matching wildcard patterns specified in this list.
If no pattern list is specified, ["*"] is used as the default pattern,
which matches all files.
:param excluded_patterns:
Ignores filenames matching wildcard patterns specified in this list.
If no pattern list is specified, no files are ignored.
:param case_sensitive:
``True`` if matching should be case-sensitive; ``False`` otherwise.
:returns:
``True`` if any of the paths matches; ``False`` otherwise.
"""
included = ["*"] if included_patterns is None else included_patterns
excluded = [] if excluded_patterns is None else excluded_patterns

for path in paths:
if _match_path(path, set(included), set(excluded), case_sensitive):
return True
return False
2 changes: 1 addition & 1 deletion tests/test_pattern_matching_event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from pathtools.patterns import filter_paths
from watchdog.events import (
FileDeletedEvent,
FileModifiedEvent,
Expand All @@ -33,6 +32,7 @@
EVENT_TYPE_MOVED,
)
from watchdog.utils import has_attribute
from watchdog.utils.patterns import filter_paths

path_1 = '/path/xyz'
path_2 = '/path/abc'
Expand Down
44 changes: 44 additions & 0 deletions tests/test_patterns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 Yesudeep Mangalapilly <yesudeep@gmail.com>
# Copyright 2020 Boris Staletic <boris.staletic@gmail.com>

import pytest
from watchdog.utils.patterns import _match_path, filter_paths, match_any_paths


@pytest.mark.parametrize("input, included_patterns, excluded_patterns, case_sensitive, expected", [
("/users/gorakhargosh/foobar.py", {"*.py"}, {"*.PY"}, True, True),
("/users/gorakhargosh/foobar.py", {"*.py"}, {"*.PY"}, True, True),
("/users/gorakhargosh/", {"*.py"}, {"*.txt"}, False, False),
("/users/gorakhargosh/foobar.py", {"*.py"}, {"*.PY"}, False, ValueError),
])
def test_match_path(input, included_patterns, excluded_patterns, case_sensitive, expected):
if expected == ValueError:
with pytest.raises(expected):
_match_path(input, included_patterns, excluded_patterns, case_sensitive)
else:
assert _match_path(input, included_patterns, excluded_patterns, case_sensitive) is expected


@pytest.mark.parametrize("included_patterns, excluded_patterns, case_sensitive, expected", [
(None, None, True, None),
(None, None, False, None),
(["*.py", "*.conf"], ["*.status"], True, {"/users/gorakhargosh/foobar.py", "/etc/pdnsd.conf"}),
])
def test_filter_paths(included_patterns, excluded_patterns, case_sensitive, expected):
pathnames = {"/users/gorakhargosh/foobar.py", "/var/cache/pdnsd.status", "/etc/pdnsd.conf", "/usr/local/bin/python"}
actual = set(filter_paths(pathnames, included_patterns, excluded_patterns, case_sensitive))
assert actual == expected if expected else pathnames


@pytest.mark.parametrize("included_patterns, excluded_patterns, case_sensitive, expected", [
(None, None, True, True),
(None, None, False, True),
(["*py", "*.conf"], ["*.status"], True, True),
(["*.txt"], None, False, False),
(["*.txt"], None, True, False),
])
def test_match_any_paths(included_patterns, excluded_patterns, case_sensitive, expected):
pathnames = {"/users/gorakhargosh/foobar.py", "/var/cache/pdnsd.status", "/etc/pdnsd.conf", "/usr/local/bin/python"}
assert match_any_paths(pathnames, included_patterns, excluded_patterns, case_sensitive) == expected

0 comments on commit efdc69f

Please sign in to comment.