Commit 66ac9b01 by Omar Khan

Merge pull request #11849 from open-craft/omar/watch-assets

Fix paver watch_assets
parents 9baae2ff 53642579
...@@ -4,13 +4,15 @@ Asset compilation and collection. ...@@ -4,13 +4,15 @@ Asset compilation and collection.
from __future__ import print_function from __future__ import print_function
from datetime import datetime from datetime import datetime
from functools import wraps
from threading import Timer
import argparse import argparse
import glob import glob
import traceback import traceback
from paver import tasks from paver import tasks
from paver.easy import sh, path, task, cmdopts, needs, consume_args, call_task, no_help 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 watchdog.events import PatternMatchingEventHandler
from .utils.envs import Env from .utils.envs import Env
...@@ -239,6 +241,29 @@ def get_watcher_dirs(themes_base_dir=None, themes=None): ...@@ -239,6 +241,29 @@ def get_watcher_dirs(themes_base_dir=None, themes=None):
return dirs 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): class CoffeeScriptWatcher(PatternMatchingEventHandler):
""" """
Watches for coffeescript changes Watches for coffeescript changes
...@@ -256,7 +281,8 @@ class CoffeeScriptWatcher(PatternMatchingEventHandler): ...@@ -256,7 +281,8 @@ class CoffeeScriptWatcher(PatternMatchingEventHandler):
for dirname in dirnames: for dirname in dirnames:
observer.schedule(self, dirname) observer.schedule(self, dirname)
def on_modified(self, event): @debounce()
def on_any_event(self, event):
print('\tCHANGED:', event.src_path) print('\tCHANGED:', event.src_path)
try: try:
compile_coffeescript(event.src_path) compile_coffeescript(event.src_path)
...@@ -289,7 +315,8 @@ class SassWatcher(PatternMatchingEventHandler): ...@@ -289,7 +315,8 @@ class SassWatcher(PatternMatchingEventHandler):
for dirname in paths: for dirname in paths:
observer.schedule(self, dirname, recursive=True) observer.schedule(self, dirname, recursive=True)
def on_modified(self, event): @debounce()
def on_any_event(self, event):
print('\tCHANGED:', event.src_path) print('\tCHANGED:', event.src_path)
try: try:
compile_sass() # pylint: disable=no-value-for-parameter compile_sass() # pylint: disable=no-value-for-parameter
...@@ -304,7 +331,8 @@ class XModuleSassWatcher(SassWatcher): ...@@ -304,7 +331,8 @@ class XModuleSassWatcher(SassWatcher):
ignore_directories = True ignore_directories = True
ignore_patterns = [] ignore_patterns = []
def on_modified(self, event): @debounce()
def on_any_event(self, event):
print('\tCHANGED:', event.src_path) print('\tCHANGED:', event.src_path)
try: try:
process_xmodule_assets() process_xmodule_assets()
...@@ -325,7 +353,8 @@ class XModuleAssetsWatcher(PatternMatchingEventHandler): ...@@ -325,7 +353,8 @@ class XModuleAssetsWatcher(PatternMatchingEventHandler):
""" """
observer.schedule(self, 'common/lib/xmodule/', recursive=True) 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) print('\tCHANGED:', event.src_path)
try: try:
process_xmodule_assets() process_xmodule_assets()
...@@ -635,7 +664,7 @@ def watch_assets(options): ...@@ -635,7 +664,7 @@ def watch_assets(options):
themes = themes if isinstance(themes, list) else [themes] themes = themes if isinstance(themes, list) else [themes]
sass_directories = get_watcher_dirs(theme_base_dir, themes) sass_directories = get_watcher_dirs(theme_base_dir, themes)
observer = Observer() observer = PollingObserver()
CoffeeScriptWatcher().register(observer) CoffeeScriptWatcher().register(observer)
SassWatcher().register(observer, sass_directories) SassWatcher().register(observer, sass_directories)
......
...@@ -6,7 +6,7 @@ from unittest import TestCase ...@@ -6,7 +6,7 @@ from unittest import TestCase
from paver.easy import call_task from paver.easy import call_task
from paver.easy import path from paver.easy import path
from mock import patch from mock import patch
from watchdog.observers import Observer from watchdog.observers.polling import PollingObserver
from .utils import PaverTestCase from .utils import PaverTestCase
ROOT_PATH = path(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) ROOT_PATH = path(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
...@@ -161,7 +161,7 @@ class TestPaverWatchAssetTasks(TestCase): ...@@ -161,7 +161,7 @@ class TestPaverWatchAssetTasks(TestCase):
Test the "compile_sass" task. Test the "compile_sass" task.
""" """
with patch('pavelib.assets.SassWatcher.register') as mock_register: with patch('pavelib.assets.SassWatcher.register') as mock_register:
with patch('pavelib.assets.Observer.start'): with patch('pavelib.assets.PollingObserver.start'):
call_task( call_task(
'pavelib.assets.watch_assets', 'pavelib.assets.watch_assets',
options={"background": True}, options={"background": True},
...@@ -170,7 +170,7 @@ class TestPaverWatchAssetTasks(TestCase): ...@@ -170,7 +170,7 @@ class TestPaverWatchAssetTasks(TestCase):
sass_watcher_args = mock_register.call_args_list[0][0] 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.assertIsInstance(sass_watcher_args[1], list)
self.assertItemsEqual(sass_watcher_args[1], self.expected_sass_directories) self.assertItemsEqual(sass_watcher_args[1], self.expected_sass_directories)
...@@ -186,7 +186,7 @@ class TestPaverWatchAssetTasks(TestCase): ...@@ -186,7 +186,7 @@ class TestPaverWatchAssetTasks(TestCase):
]) ])
with patch('pavelib.assets.SassWatcher.register') as mock_register: with patch('pavelib.assets.SassWatcher.register') as mock_register:
with patch('pavelib.assets.Observer.start'): with patch('pavelib.assets.PollingObserver.start'):
call_task( call_task(
'pavelib.assets.watch_assets', 'pavelib.assets.watch_assets',
options={"background": True, "themes_dir": TEST_THEME.dirname(), options={"background": True, "themes_dir": TEST_THEME.dirname(),
...@@ -195,7 +195,7 @@ class TestPaverWatchAssetTasks(TestCase): ...@@ -195,7 +195,7 @@ class TestPaverWatchAssetTasks(TestCase):
self.assertEqual(mock_register.call_count, 2) self.assertEqual(mock_register.call_count, 2)
sass_watcher_args = mock_register.call_args_list[0][0] 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.assertIsInstance(sass_watcher_args[1], list)
self.assertItemsEqual(sass_watcher_args[1], self.expected_sass_directories) self.assertItemsEqual(sass_watcher_args[1], self.expected_sass_directories)
......
...@@ -114,7 +114,7 @@ reportlab==3.1.44 ...@@ -114,7 +114,7 @@ reportlab==3.1.44
pdfminer==20140328 pdfminer==20140328
# Used for development operation # Used for development operation
watchdog==0.7.1 watchdog==0.8.3
# Metrics gathering and monitoring # Metrics gathering and monitoring
dogapi==1.2.1 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