From 3e168651171d170daedc41a03e7c763c8654e322 Mon Sep 17 00:00:00 2001 From: Kotesh Kumar Yelamati Date: Thu, 2 Jul 2026 13:29:24 -0400 Subject: [PATCH 1/2] fix: escape square brackets in patterns to match literal bracket filenames Square brackets in patterns (e.g. "[test].txt") were being interpreted as character classes by fnmatch/pathlib, causing the pattern to match files like "t.txt" instead of a file literally named "[test].txt". Fix by replacing "[" with "[[[]" in the pattern before matching, which makes the bracket literal in fnmatch/glob syntax. Fixes #991 --- src/watchdog/utils/patterns.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/watchdog/utils/patterns.py b/src/watchdog/utils/patterns.py index 927aa87a..d8754dbd 100644 --- a/src/watchdog/utils/patterns.py +++ b/src/watchdog/utils/patterns.py @@ -37,6 +37,14 @@ def _get_sep(path: PurePath) -> str: def _full_match(path: PurePath, pattern: str) -> bool: + # Escape square brackets in the pattern so they match file/directory names + # containing literal brackets rather than being interpreted as character + # classes. For example, the pattern "[test].txt" should match a file + # literally named "[test].txt", not a file like "t.txt" (where [test] is a + # character class matching t, e, s). Replacing "[" with "[[[]" makes the + # opening bracket literal in fnmatch/glob syntax. + # See: https://github.com/gorakhargosh/watchdog/issues/991 + pattern = pattern.replace("[", "[[[]") try: return path.full_match(pattern) except AttributeError: From dd2ad3f5b511befe7d2fa1c63f03e6ae015d52b9 Mon Sep 17 00:00:00 2001 From: Kotesh Kumar Yelamati Date: Tue, 14 Jul 2026 23:47:11 -0400 Subject: [PATCH 2/2] Add regression tests for literal bracket pattern matching --- tests/test_patterns.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_patterns.py b/tests/test_patterns.py index 1ee5d039..baaefd9b 100644 --- a/tests/test_patterns.py +++ b/tests/test_patterns.py @@ -79,3 +79,19 @@ def test_match_any_paths(included_patterns, excluded_patterns, case_sensitive, e ) == expected ) + + +@pytest.mark.parametrize( + ("raw_path", "included_patterns", "expected"), + [ + # Regression tests for https://github.com/gorakhargosh/watchdog/issues/991: + # square brackets in patterns must match literal bracket characters + # instead of being interpreted as fnmatch character classes. + ("/users/gorakhargosh/[test].txt", {"**/[test].txt"}, True), + ("/users/gorakhargosh/t.txt", {"**/[test].txt"}, False), + ("/users/gorakhargosh/file[1].py", {"**/file[1].py"}, True), + ("/users/gorakhargosh/file1.py", {"**/file[1].py"}, False), + ], +) +def test_match_path_literal_brackets(raw_path, included_patterns, expected): + assert _match_path(raw_path, included_patterns, set(), case_sensitive=True) is expected