events.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # Copyright 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
  5. # Copyright 2012 Google, Inc.
  6. #
  7. # Licensed under the Apache License, Version 2.0 (the "License");
  8. # you may not use this file except in compliance with the License.
  9. # You may obtain a copy of the License at
  10. #
  11. # http://www.apache.org/licenses/LICENSE-2.0
  12. #
  13. # Unless required by applicable law or agreed to in writing, software
  14. # distributed under the License is distributed on an "AS IS" BASIS,
  15. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. # See the License for the specific language governing permissions and
  17. # limitations under the License.
  18. """
  19. :module: watchdog.events
  20. :synopsis: File system events and event handlers.
  21. :author: yesudeep@google.com (Yesudeep Mangalapilly)
  22. Event Classes
  23. -------------
  24. .. autoclass:: FileSystemEvent
  25. :members:
  26. :show-inheritance:
  27. :inherited-members:
  28. .. autoclass:: FileSystemMovedEvent
  29. :members:
  30. :show-inheritance:
  31. .. autoclass:: FileMovedEvent
  32. :members:
  33. :show-inheritance:
  34. .. autoclass:: DirMovedEvent
  35. :members:
  36. :show-inheritance:
  37. .. autoclass:: FileModifiedEvent
  38. :members:
  39. :show-inheritance:
  40. .. autoclass:: DirModifiedEvent
  41. :members:
  42. :show-inheritance:
  43. .. autoclass:: FileCreatedEvent
  44. :members:
  45. :show-inheritance:
  46. .. autoclass:: DirCreatedEvent
  47. :members:
  48. :show-inheritance:
  49. .. autoclass:: FileDeletedEvent
  50. :members:
  51. :show-inheritance:
  52. .. autoclass:: DirDeletedEvent
  53. :members:
  54. :show-inheritance:
  55. Event Handler Classes
  56. ---------------------
  57. .. autoclass:: FileSystemEventHandler
  58. :members:
  59. :show-inheritance:
  60. .. autoclass:: PatternMatchingEventHandler
  61. :members:
  62. :show-inheritance:
  63. .. autoclass:: RegexMatchingEventHandler
  64. :members:
  65. :show-inheritance:
  66. .. autoclass:: LoggingEventHandler
  67. :members:
  68. :show-inheritance:
  69. """
  70. import os.path
  71. import logging
  72. import re
  73. from pathtools.patterns import match_any_paths
  74. from watchdog.utils import has_attribute
  75. from watchdog.utils import unicode_paths
  76. EVENT_TYPE_MOVED = 'moved'
  77. EVENT_TYPE_DELETED = 'deleted'
  78. EVENT_TYPE_CREATED = 'created'
  79. EVENT_TYPE_MODIFIED = 'modified'
  80. class FileSystemEvent(object):
  81. """
  82. Immutable type that represents a file system event that is triggered
  83. when a change occurs on the monitored file system.
  84. All FileSystemEvent objects are required to be immutable and hence
  85. can be used as keys in dictionaries or be added to sets.
  86. """
  87. event_type = None
  88. """The type of the event as a string."""
  89. is_directory = False
  90. """True if event was emitted for a directory; False otherwise."""
  91. is_synthetic = False
  92. """
  93. True if event was synthesized; False otherwise.
  94. These are events that weren't actually broadcast by the OS, but
  95. are presumed to have happened based on other, actual events.
  96. """
  97. def __init__(self, src_path):
  98. self._src_path = src_path
  99. @property
  100. def src_path(self):
  101. """Source path of the file system object that triggered this event."""
  102. return self._src_path
  103. def __str__(self):
  104. return self.__repr__()
  105. def __repr__(self):
  106. return ("<%(class_name)s: event_type=%(event_type)s, "
  107. "src_path=%(src_path)r, "
  108. "is_directory=%(is_directory)s>"
  109. ) % (dict(
  110. class_name=self.__class__.__name__,
  111. event_type=self.event_type,
  112. src_path=self.src_path,
  113. is_directory=self.is_directory))
  114. # Used for comparison of events.
  115. @property
  116. def key(self):
  117. return (self.event_type, self.src_path, self.is_directory)
  118. def __eq__(self, event):
  119. return self.key == event.key
  120. def __ne__(self, event):
  121. return self.key != event.key
  122. def __hash__(self):
  123. return hash(self.key)
  124. class FileSystemMovedEvent(FileSystemEvent):
  125. """
  126. File system event representing any kind of file system movement.
  127. """
  128. event_type = EVENT_TYPE_MOVED
  129. def __init__(self, src_path, dest_path):
  130. super(FileSystemMovedEvent, self).__init__(src_path)
  131. self._dest_path = dest_path
  132. @property
  133. def dest_path(self):
  134. """The destination path of the move event."""
  135. return self._dest_path
  136. # Used for hashing this as an immutable object.
  137. @property
  138. def key(self):
  139. return (self.event_type, self.src_path, self.dest_path, self.is_directory)
  140. def __repr__(self):
  141. return ("<%(class_name)s: src_path=%(src_path)r, "
  142. "dest_path=%(dest_path)r, "
  143. "is_directory=%(is_directory)s>"
  144. ) % (dict(class_name=self.__class__.__name__,
  145. src_path=self.src_path,
  146. dest_path=self.dest_path,
  147. is_directory=self.is_directory))
  148. # File events.
  149. class FileDeletedEvent(FileSystemEvent):
  150. """File system event representing file deletion on the file system."""
  151. event_type = EVENT_TYPE_DELETED
  152. def __init__(self, src_path):
  153. super(FileDeletedEvent, self).__init__(src_path)
  154. def __repr__(self):
  155. return "<%(class_name)s: src_path=%(src_path)r>" %\
  156. dict(class_name=self.__class__.__name__,
  157. src_path=self.src_path)
  158. class FileModifiedEvent(FileSystemEvent):
  159. """File system event representing file modification on the file system."""
  160. event_type = EVENT_TYPE_MODIFIED
  161. def __init__(self, src_path):
  162. super(FileModifiedEvent, self).__init__(src_path)
  163. def __repr__(self):
  164. return ("<%(class_name)s: src_path=%(src_path)r>"
  165. ) % (dict(class_name=self.__class__.__name__,
  166. src_path=self.src_path))
  167. class FileCreatedEvent(FileSystemEvent):
  168. """File system event representing file creation on the file system."""
  169. event_type = EVENT_TYPE_CREATED
  170. def __init__(self, src_path):
  171. super(FileCreatedEvent, self).__init__(src_path)
  172. def __repr__(self):
  173. return ("<%(class_name)s: src_path=%(src_path)r>"
  174. ) % (dict(class_name=self.__class__.__name__,
  175. src_path=self.src_path))
  176. class FileMovedEvent(FileSystemMovedEvent):
  177. """File system event representing file movement on the file system."""
  178. def __init__(self, src_path, dest_path):
  179. super(FileMovedEvent, self).__init__(src_path, dest_path)
  180. def __repr__(self):
  181. return ("<%(class_name)s: src_path=%(src_path)r, "
  182. "dest_path=%(dest_path)r>"
  183. ) % (dict(class_name=self.__class__.__name__,
  184. src_path=self.src_path,
  185. dest_path=self.dest_path))
  186. # Directory events.
  187. class DirDeletedEvent(FileSystemEvent):
  188. """File system event representing directory deletion on the file system."""
  189. event_type = EVENT_TYPE_DELETED
  190. is_directory = True
  191. def __init__(self, src_path):
  192. super(DirDeletedEvent, self).__init__(src_path)
  193. def __repr__(self):
  194. return ("<%(class_name)s: src_path=%(src_path)r>"
  195. ) % (dict(class_name=self.__class__.__name__,
  196. src_path=self.src_path))
  197. class DirModifiedEvent(FileSystemEvent):
  198. """
  199. File system event representing directory modification on the file system.
  200. """
  201. event_type = EVENT_TYPE_MODIFIED
  202. is_directory = True
  203. def __init__(self, src_path):
  204. super(DirModifiedEvent, self).__init__(src_path)
  205. def __repr__(self):
  206. return ("<%(class_name)s: src_path=%(src_path)r>"
  207. ) % (dict(class_name=self.__class__.__name__,
  208. src_path=self.src_path))
  209. class DirCreatedEvent(FileSystemEvent):
  210. """File system event representing directory creation on the file system."""
  211. event_type = EVENT_TYPE_CREATED
  212. is_directory = True
  213. def __init__(self, src_path):
  214. super(DirCreatedEvent, self).__init__(src_path)
  215. def __repr__(self):
  216. return ("<%(class_name)s: src_path=%(src_path)r>"
  217. ) % (dict(class_name=self.__class__.__name__,
  218. src_path=self.src_path))
  219. class DirMovedEvent(FileSystemMovedEvent):
  220. """File system event representing directory movement on the file system."""
  221. is_directory = True
  222. def __init__(self, src_path, dest_path):
  223. super(DirMovedEvent, self).__init__(src_path, dest_path)
  224. def __repr__(self):
  225. return ("<%(class_name)s: src_path=%(src_path)r, "
  226. "dest_path=%(dest_path)r>"
  227. ) % (dict(class_name=self.__class__.__name__,
  228. src_path=self.src_path,
  229. dest_path=self.dest_path))
  230. class FileSystemEventHandler(object):
  231. """
  232. Base file system event handler that you can override methods from.
  233. """
  234. def dispatch(self, event):
  235. """Dispatches events to the appropriate methods.
  236. :param event:
  237. The event object representing the file system event.
  238. :type event:
  239. :class:`FileSystemEvent`
  240. """
  241. self.on_any_event(event)
  242. _method_map = {
  243. EVENT_TYPE_MODIFIED: self.on_modified,
  244. EVENT_TYPE_MOVED: self.on_moved,
  245. EVENT_TYPE_CREATED: self.on_created,
  246. EVENT_TYPE_DELETED: self.on_deleted,
  247. }
  248. event_type = event.event_type
  249. _method_map[event_type](event)
  250. def on_any_event(self, event):
  251. """Catch-all event handler.
  252. :param event:
  253. The event object representing the file system event.
  254. :type event:
  255. :class:`FileSystemEvent`
  256. """
  257. def on_moved(self, event):
  258. """Called when a file or a directory is moved or renamed.
  259. :param event:
  260. Event representing file/directory movement.
  261. :type event:
  262. :class:`DirMovedEvent` or :class:`FileMovedEvent`
  263. """
  264. def on_created(self, event):
  265. """Called when a file or directory is created.
  266. :param event:
  267. Event representing file/directory creation.
  268. :type event:
  269. :class:`DirCreatedEvent` or :class:`FileCreatedEvent`
  270. """
  271. def on_deleted(self, event):
  272. """Called when a file or directory is deleted.
  273. :param event:
  274. Event representing file/directory deletion.
  275. :type event:
  276. :class:`DirDeletedEvent` or :class:`FileDeletedEvent`
  277. """
  278. def on_modified(self, event):
  279. """Called when a file or directory is modified.
  280. :param event:
  281. Event representing file/directory modification.
  282. :type event:
  283. :class:`DirModifiedEvent` or :class:`FileModifiedEvent`
  284. """
  285. class PatternMatchingEventHandler(FileSystemEventHandler):
  286. """
  287. Matches given patterns with file paths associated with occurring events.
  288. """
  289. def __init__(self, patterns=None, ignore_patterns=None,
  290. ignore_directories=False, case_sensitive=False):
  291. super(PatternMatchingEventHandler, self).__init__()
  292. self._patterns = patterns
  293. self._ignore_patterns = ignore_patterns
  294. self._ignore_directories = ignore_directories
  295. self._case_sensitive = case_sensitive
  296. @property
  297. def patterns(self):
  298. """
  299. (Read-only)
  300. Patterns to allow matching event paths.
  301. """
  302. return self._patterns
  303. @property
  304. def ignore_patterns(self):
  305. """
  306. (Read-only)
  307. Patterns to ignore matching event paths.
  308. """
  309. return self._ignore_patterns
  310. @property
  311. def ignore_directories(self):
  312. """
  313. (Read-only)
  314. ``True`` if directories should be ignored; ``False`` otherwise.
  315. """
  316. return self._ignore_directories
  317. @property
  318. def case_sensitive(self):
  319. """
  320. (Read-only)
  321. ``True`` if path names should be matched sensitive to case; ``False``
  322. otherwise.
  323. """
  324. return self._case_sensitive
  325. def dispatch(self, event):
  326. """Dispatches events to the appropriate methods.
  327. :param event:
  328. The event object representing the file system event.
  329. :type event:
  330. :class:`FileSystemEvent`
  331. """
  332. if self.ignore_directories and event.is_directory:
  333. return
  334. paths = []
  335. if has_attribute(event, 'dest_path'):
  336. paths.append(unicode_paths.decode(event.dest_path))
  337. if event.src_path:
  338. paths.append(unicode_paths.decode(event.src_path))
  339. if match_any_paths(paths,
  340. included_patterns=self.patterns,
  341. excluded_patterns=self.ignore_patterns,
  342. case_sensitive=self.case_sensitive):
  343. self.on_any_event(event)
  344. _method_map = {
  345. EVENT_TYPE_MODIFIED: self.on_modified,
  346. EVENT_TYPE_MOVED: self.on_moved,
  347. EVENT_TYPE_CREATED: self.on_created,
  348. EVENT_TYPE_DELETED: self.on_deleted,
  349. }
  350. event_type = event.event_type
  351. _method_map[event_type](event)
  352. class RegexMatchingEventHandler(FileSystemEventHandler):
  353. """
  354. Matches given regexes with file paths associated with occurring events.
  355. """
  356. def __init__(self, regexes=[r".*"], ignore_regexes=[],
  357. ignore_directories=False, case_sensitive=False):
  358. super(RegexMatchingEventHandler, self).__init__()
  359. if case_sensitive:
  360. self._regexes = [re.compile(r) for r in regexes]
  361. self._ignore_regexes = [re.compile(r) for r in ignore_regexes]
  362. else:
  363. self._regexes = [re.compile(r, re.I) for r in regexes]
  364. self._ignore_regexes = [re.compile(r, re.I) for r in ignore_regexes]
  365. self._ignore_directories = ignore_directories
  366. self._case_sensitive = case_sensitive
  367. @property
  368. def regexes(self):
  369. """
  370. (Read-only)
  371. Regexes to allow matching event paths.
  372. """
  373. return self._regexes
  374. @property
  375. def ignore_regexes(self):
  376. """
  377. (Read-only)
  378. Regexes to ignore matching event paths.
  379. """
  380. return self._ignore_regexes
  381. @property
  382. def ignore_directories(self):
  383. """
  384. (Read-only)
  385. ``True`` if directories should be ignored; ``False`` otherwise.
  386. """
  387. return self._ignore_directories
  388. @property
  389. def case_sensitive(self):
  390. """
  391. (Read-only)
  392. ``True`` if path names should be matched sensitive to case; ``False``
  393. otherwise.
  394. """
  395. return self._case_sensitive
  396. def dispatch(self, event):
  397. """Dispatches events to the appropriate methods.
  398. :param event:
  399. The event object representing the file system event.
  400. :type event:
  401. :class:`FileSystemEvent`
  402. """
  403. if self.ignore_directories and event.is_directory:
  404. return
  405. paths = []
  406. if has_attribute(event, 'dest_path'):
  407. paths.append(unicode_paths.decode(event.dest_path))
  408. if event.src_path:
  409. paths.append(unicode_paths.decode(event.src_path))
  410. if any(r.match(p) for r in self.ignore_regexes for p in paths):
  411. return
  412. if any(r.match(p) for r in self.regexes for p in paths):
  413. self.on_any_event(event)
  414. _method_map = {
  415. EVENT_TYPE_MODIFIED: self.on_modified,
  416. EVENT_TYPE_MOVED: self.on_moved,
  417. EVENT_TYPE_CREATED: self.on_created,
  418. EVENT_TYPE_DELETED: self.on_deleted,
  419. }
  420. event_type = event.event_type
  421. _method_map[event_type](event)
  422. class LoggingEventHandler(FileSystemEventHandler):
  423. """Logs all the events captured."""
  424. def on_moved(self, event):
  425. super(LoggingEventHandler, self).on_moved(event)
  426. what = 'directory' if event.is_directory else 'file'
  427. logging.info("Moved %s: from %s to %s", what, event.src_path,
  428. event.dest_path)
  429. def on_created(self, event):
  430. super(LoggingEventHandler, self).on_created(event)
  431. what = 'directory' if event.is_directory else 'file'
  432. logging.info("Created %s: %s", what, event.src_path)
  433. def on_deleted(self, event):
  434. super(LoggingEventHandler, self).on_deleted(event)
  435. what = 'directory' if event.is_directory else 'file'
  436. logging.info("Deleted %s: %s", what, event.src_path)
  437. def on_modified(self, event):
  438. super(LoggingEventHandler, self).on_modified(event)
  439. what = 'directory' if event.is_directory else 'file'
  440. logging.info("Modified %s: %s", what, event.src_path)
  441. class LoggingFileSystemEventHandler(LoggingEventHandler):
  442. """
  443. For backwards-compatibility. Please use :class:`LoggingEventHandler`
  444. instead.
  445. """
  446. def generate_sub_moved_events(src_dir_path, dest_dir_path):
  447. """Generates an event list of :class:`DirMovedEvent` and
  448. :class:`FileMovedEvent` objects for all the files and directories within
  449. the given moved directory that were moved along with the directory.
  450. :param src_dir_path:
  451. The source path of the moved directory.
  452. :param dest_dir_path:
  453. The destination path of the moved directory.
  454. :returns:
  455. An iterable of file system events of type :class:`DirMovedEvent` and
  456. :class:`FileMovedEvent`.
  457. """
  458. for root, directories, filenames in os.walk(dest_dir_path):
  459. for directory in directories:
  460. full_path = os.path.join(root, directory)
  461. renamed_path = full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else None
  462. event = DirMovedEvent(renamed_path, full_path)
  463. event.is_synthetic = True
  464. yield event
  465. for filename in filenames:
  466. full_path = os.path.join(root, filename)
  467. renamed_path = full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else None
  468. event = FileMovedEvent(renamed_path, full_path)
  469. event.is_synthetic = True
  470. yield event
  471. def generate_sub_created_events(src_dir_path):
  472. """Generates an event list of :class:`DirCreatedEvent` and
  473. :class:`FileCreatedEvent` objects for all the files and directories within
  474. the given moved directory that were moved along with the directory.
  475. :param src_dir_path:
  476. The source path of the created directory.
  477. :returns:
  478. An iterable of file system events of type :class:`DirCreatedEvent` and
  479. :class:`FileCreatedEvent`.
  480. """
  481. for root, directories, filenames in os.walk(src_dir_path):
  482. for directory in directories:
  483. event = DirCreatedEvent(os.path.join(root, directory))
  484. event.is_synthetic = True
  485. yield event
  486. for filename in filenames:
  487. event = FileCreatedEvent(os.path.join(root, filename))
  488. event.is_synthetic = True
  489. yield event