Breaking Changes
- Drop support for Python 3.9 and 3.10.
- [core] Changed pattern matching mechanism from using
path.match()topath.full_match(). Users must update patterns to glob-like syntax: e.g.,*.pyto**/*.py. (#1101) - [core] Added
FileAttribEventandDirAttribEventfor file and directory metadata modifications (such as permissions or timestamps), separate from content modifications. These events no longer triggeron_modified(); implementon_attrib()to handle them. On Windows 10 (1709+) and Windows Server 2016+,ReadDirectoryChangesExWis used to differentiate attribute modifications, with fallback toReadDirectoryChangesWon older versions or unsupported file shares. (#260) - [watchmedo] Changed the default
--patternsvalue from"*"to"**", so the default matches absolute paths at any depth under thefull_match()semantics introduced by #1101, and updated the CLI help text and docs to the**/glob syntax. (#1212)
Other Changes
- Add support for Python 3.15 (#1226)
- [core] Add context manager support to
Observerclass. The observer can now be used with awithstatement for automatic start/stop management. (#1090) - [core] Add support for the
follow_symlinkkeyword argument toObservedWatch. (#1086) - [core] Added the
ignore_policyargument toPatternMatchingEventHandlerandRegexMatchingEventHandlerto configure multi-path event filtering (moves/renames).PatternMatchingEventHandleris now strict by default, aligning withRegexMatchingEventHandler. (#412, #423) - [core] Added the
watchdog.utils.Secondstype alias and used it for the time-duration parameters (timeout,polling_interval,kill_after, ...) instead of documenting the unit in each docstring. Duration parameters previously hinted asintnow acceptfloat. - [core] Adjust
Observer.schedule()pathtype annotation to reflect thepathlib.Pathsupport. (#1096) - [core] Allow scheduling watches on individual files in
Observer.schedule(). (#1034) - [core] Call
task_done()for the stop sentinel indispatch_events()to preventjoin()from hanging. (#1159) - [core]
case_sensitive=Falsematching now really ignores case on Python<3.13. ThePurePath.full_match()fallback compiled its regex withoutre.IGNORECASE, so only the patterns were lower-cased and a path such asFOO.PYnever matched**/*.py. (#1263) - [core]
dispatch_events()no longer re-adds a watch thatunschedule()removed, which leaked one_handlersentry per watch that had an event in flight. (#1261) - [core] Do not retain an event handler when
Observer.schedule()fails to create or start its emitter. - [core]
EventEmitterandEventDispatcherreject a booltimeout.boolsubclassesint, sotimeout=Truewas stored and treated as1.0seconds. (#1287) - [core] Fixed
generate_sub_moved_events()corrupting paths when the directory name appears multiple times in the path. (#1158) - [core]
ObservedWatchequality now includesfollow_symlink, so scheduling a path a second time with a differentfollow_symlinkvalue no longer collapses onto the first watch and silently drops the request. (#1262) - [core]
RegexMatchingEventHandlerno longer matches its regexes against an empty destination path for events that are not moves. Since events became dataclasses,hasattr(event, "dest_path")is always true, so a regex matching the empty string (e.g.^(?!.*\.pyc$).*$) let every event through, and such an ignore regex dropped every event.PatternMatchingEventHandlerhad the same check. - [core]
remove_handler_for_watch()no longer re-adds a watch thatunschedule()removed, which leaked one empty_handlersentry per call. (#1285) - [core] Add
win_arm64to the list of platform-specific wheels published to PyPI. (#1137) - [core] Add
manylinux_2_17to the list of platform-specific wheels published to PyPI. (#1125) - [inotify] Add support for the
follow_symlinkkeyword argument. (#1086) - [inotify] Do not suppress registration errors for the requested root path. A missing root now raises
FileNotFoundErrorfromobserver.start(), or fromobserver.schedule()when already running. Recursive descendant failures remain tolerated. (#1157) - [inotify] Keep watching newly created subdirectories when recursive watches filter out creation events. (#990)
- [inotify] Reduce number of created inotify instance. (#1099)
- [inotify] Snapshot the watch group before the blocking read in
InotifyEmitter.queue_events()so stopping the emitter cannot raiseAttributeErroron a clearedself._inotify. - [fsevents] Added the
latencykeyword argument toFSEventsEmitterandFSEventsObserver(defaulting towatchdog.observers.fsevents.DEFAULT_FSEVENTS_LATENCY), exposing the FSEvents stream latency previously hard-coded at 10 ms. Raising it coalesces duplicate events. (#835) - [fsevents] Add support for the
follow_symlinkkeyword argument. (#1086) - [fsevents] Support free-threading and give each emitter its own stream and run loop to prevent shutdown leaks. (#1140)
- [fsevents] Use multi-phase initialization (PEP 489). (#1108)
- [windows] A rename whose
RENAMED_OLDandRENAMED_NEWrecords arrive in differentget_events()batches no longer emits a move event with an emptysrc_path. (#1286) - [windows] Changing only the case of a name no longer emits a deletion event on top of the move event. (#752)
- [windows] Event file names are decoded as UTF-16LE, so a name starting with U+FEFF or U+FFFE no longer reports the wrong path. (#1228)
- [windows] Fixed an unsynchronized access to
WindowsApiEmitter._reader:queue_events()now
reads it under the same lock used byon_thread_stop(). (#1170) - [windows] Use double-buffering for
read_directory_changesto avoid missed events under high load. (#1152) - [polling] A transient
OSErrorwhile polling, such as a network share that is briefly unreachable, no longer emits aDirDeletedEventfor the watched directory and stops the emitter. The error is logged and the next poll tries again, so changes made in the meantime are still reported. OnlyFileNotFoundErrorandNotADirectoryErrorare still treated as a deletion. (#1065) - [polling] Detect permission, ownership, and file attribute modifications in
DirectorySnapshotDiffand emitFileAttribEventandDirAttribEventinPollingObserver. (#881) - [bsd] Do not let a
PermissionErrorraised while registering a kevent kill the emitter thread. (#1115) - [freebsd] Supports
inotifyon FreeBSD 15+. (#1147) - [utils] Deduplicate consecutive
Noneitems inSkipRepeatsQueue. (#1348) - [utils] Deprecated the
debounce_interval_secondsargument and attribute ofEventDebouncerandAutoRestartTrickin favour ofdebounce_interval, and theDelayedQueue.delay_secattribute in favour ofdelay. The old names still work but emit aDeprecationWarning. - [utils]
DirectorySnapshotDiff(ignore_device=True)now detects moved files and directories when the device ID changes between snapshots. - [utils] Fix
EventDebouncerstarvation on high frequency events and ensure pending events are delivered upon stop. (#998) - [utils] Fixed
DirectorySnapshotDiffreporting a newly created symlink as a move of its target (and a removed symlink as a move onto its target), soPollingObservernow emits a create/delete event to match the native observers. (#1176) - [utils] Fixed
repr(EmptyDirectorySnapshot), before that it was throwing anAttributeError: 'EmptyDirectorySnapshot' object has no attribute '_stat_info'. - [utils] Implemented
len(DirectorySnapshotDiff)to return the total number of changes. - [utils] Make
SkipRepeatsQueue.put()andput_nowait()reject duplicate items aftershutdown()on Python 3.13+. - [utils] Serialize
SkipRepeatsQueueproducers to prevent consecutive duplicates, respecting blocking and timeout limits while acquiring the producer lock. (#1338) - [utils] Use a monotonic clock for
DelayedQueuedelays so system clock adjustments do not stall or prematurely release events. (#1296) - [tricks] Allow configuring
shell_executableandcwdforShellCommandTrickandwatchmedo shell-command. (#921) - [watchmedo] Support running watchmedo via
python -m watchdog. (#1162) - [watchmedo] The
AutoRestartTrickstop signal is now actually delivered on Windows. The subprocess is started in its own process group and signalled withCTRL_BREAK_EVENT, so it can run cleanup handlers and its descendants are no longer orphaned on every restart. (#1221) - [watchmedo]
AutoRestartTrick's--signal SIGBREAKnow routes throughCTRL_BREAK_EVENTon Windows too, instead of falling back to the hard kill that skips the child's cleanup handlers. (#1221) - [watchmedo] Show file system events by default in
watchmedo log. (#913) - [tests] Remove the
eventlettests and test dependency. - [docs] Document that
BaseThreadalways runs as a daemon thread, instead of inheritingdaemonfrom the creating thread as the inheritedthreading.Threaddocumentation states. (#1117) - [docs]
FileSystemEventno longer claims to be immutable; its fields stay writable and the hash is derived from them, so mutating an event held in a dict or set strands it there. (#1240) - [docs] Note that
FileClosedEvent,FileClosedNoWriteEventandFileOpenedEvent, and theiron_closed(),on_closed_no_write()andon_opened()handlers, are emitted only byInotifyObserveron Linux. (#1235)
💟 Thanks to our beloved contributors: @BoboTiG, @Corentin-pro, @Liam-DeVoe, @kirkhansen, @JoachimCoenen, @HaoZeke, @LincolnPuzey, @adicohen1005, @zastrowm, @ngoldbaum, @QWERTIOX, @cclauss, @blitztide, @nascheme, @Prosperibe12, @bysiber, @abra-code, @toroleapinc, @maxtaran2010, @gorakhargosh, @Sanjays2402, @prateek-dagar, @BortnikMaxim, @MohammedAlkindi, @Skeletor-Pirate, @btschwertfeger, @zain-asif-dev, @subikshajegadish, @FanWu-ai, @ryanchou1994, @Rosuav, @TobiasRzepka, @GreatBahram, @arindamsikder, @apoorvdarshan, @kokotatan, @Om-singhaI, @amprew, @Pitchfork-and-Torch, @Dev-iL, @CLCK0622, @Enesuygurs, @galafis, @vaibhav8a, @ivg, @newchannelid432-code, @Aschenbath, @Joelbit-io, @Alex0AI