Commit 53642579 by Omar Khan

Fix paver watch_assets

- Update to latest version of watchdog.
- inotify doesn't work on nfs share in devstack. Use polling instead.
- Some editors (vim) do not trigger modify events in their default
  configuration. Rebuild assets on all filesystem changes, and debounce
  when changes happen too quickly.
parent ed66a1d2
......@@ -4,13 +4,15 @@ Asset compilation and collection.
from __future__ import print_function
from datetime import datetime
from functools import wraps
from threading import Timer
import argparse
import glob
import traceback
from paver import tasks
from paver.easy import sh, path, task, cmdopts, needs, consume_args, call_task, no_help
from watchdog.observers import Observer
from watchdog.observers.polling import PollingObserver
from watchdog.events import PatternMatchingEventHandler
from .utils.envs import Env
......@@ -238,6 +240,29 @@ def get_watcher_dirs(themes_base_dir=None, themes=None):
return dirs
def debounce(seconds=1):
"""
Prevents the decorated function from being called more than every `seconds`
seconds. Waits until calls stop coming in before calling the decorated
function.
"""
def decorator(func): # pylint: disable=missing-docstring
func.timer = None
@wraps(func)
def wrapper(*args, **kwargs): # pylint: disable=missing-docstring
def call(): # pylint: disable=missing-docstring
func(*args, **kwargs)
func.timer = None
if func.timer:
func.timer.cancel()
func.timer = Timer(seconds, call)
func.timer.start()
return wrapper
return decorator
class CoffeeScriptWatcher(PatternMatchingEventHandler):
"""
Watches for coffeescript changes
......@@ -255,7 +280,8 @@ class CoffeeScriptWatcher(PatternMatchingEventHandler):
for dirname in dirnames:
observer.schedule(self, dirname)
def on_modified(self, event):
@debounce()
def on_any_event(self, event):
print('\tCHANGED:', event.src_path)
try:
compile_coffeescript(event.src_path)
......@@ -288,7 +314,8 @@ class SassWatcher(PatternMatchingEventHandler):
for dirname in paths:
observer.schedule(self, dirname, recursive=True)
def on_modified(self, event):
@debounce()
def on_any_event(self, event):
print('\tCHANGED:', event.src_path)
try:
compile_sass() # pylint: disable=no-value-for-parameter
......@@ -303,7 +330,8 @@ class XModuleSassWatcher(SassWatcher):
ignore_directories = True
ignore_patterns = []
def on_modified(self, event):
@debounce()
def on_any_event(self, event):
print('\tCHANGED:', event.src_path)
try:
process_xmodule_assets()
......@@ -324,7 +352,8 @@ class XModuleAssetsWatcher(PatternMatchingEventHandler):
"""
observer.schedule(self, 'common/lib/xmodule/', recursive=True)
def on_modified(self, event):
@debounce()
def on_any_event(self, event):
print('\tCHANGED:', event.src_path)
try:
process_xmodule_assets()
......@@ -634,7 +663,7 @@ def watch_assets(options):
themes = themes if isinstance(themes, list) else [themes]
sass_directories = get_watcher_dirs(theme_base_dir, themes)
observer = Observer()
observer = PollingObserver()
CoffeeScriptWatcher().register(observer)
SassWatcher().register(observer, sass_directories)
......
......@@ -6,7 +6,7 @@ from unittest import TestCase
from paver.easy import call_task
from paver.easy import path
from mock import patch
from watchdog.observers import Observer
from watchdog.observers.polling import PollingObserver
from .utils import PaverTestCase
ROOT_PATH = path(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
......@@ -161,7 +161,7 @@ class TestPaverWatchAssetTasks(TestCase):
Test the "compile_sass" task.
"""
with patch('pavelib.assets.SassWatcher.register') as mock_register:
with patch('pavelib.assets.Observer.start'):
with patch('pavelib.assets.PollingObserver.start'):
call_task(
'pavelib.assets.watch_assets',
options={"background": True},
......@@ -170,7 +170,7 @@ class TestPaverWatchAssetTasks(TestCase):
sass_watcher_args = mock_register.call_args_list[0][0]
self.assertIsInstance(sass_watcher_args[0], Observer)
self.assertIsInstance(sass_watcher_args[0], PollingObserver)
self.assertIsInstance(sass_watcher_args[1], list)
self.assertItemsEqual(sass_watcher_args[1], self.expected_sass_directories)
......@@ -186,7 +186,7 @@ class TestPaverWatchAssetTasks(TestCase):
])
with patch('pavelib.assets.SassWatcher.register') as mock_register:
with patch('pavelib.assets.Observer.start'):
with patch('pavelib.assets.PollingObserver.start'):
call_task(
'pavelib.assets.watch_assets',
options={"background": True, "themes_dir": TEST_THEME.dirname(),
......@@ -195,7 +195,7 @@ class TestPaverWatchAssetTasks(TestCase):
self.assertEqual(mock_register.call_count, 2)
sass_watcher_args = mock_register.call_args_list[0][0]
self.assertIsInstance(sass_watcher_args[0], Observer)
self.assertIsInstance(sass_watcher_args[0], PollingObserver)
self.assertIsInstance(sass_watcher_args[1], list)
self.assertItemsEqual(sass_watcher_args[1], self.expected_sass_directories)
......
......@@ -114,7 +114,7 @@ reportlab==3.1.44
pdfminer==20140328
# Used for development operation
watchdog==0.7.1
watchdog==0.8.3
# Metrics gathering and monitoring
dogapi==1.2.1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment