Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions pavelib/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

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
Expand Down Expand Up @@ -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
Expand All @@ -255,7 +280,8 @@ def register(self, observer):
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)
Expand Down Expand Up @@ -288,7 +314,8 @@ def register(self, observer, directories):
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
Expand All @@ -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()
Expand All @@ -324,7 +352,8 @@ def register(self, observer):
"""
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()
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions pavelib/paver_tests/test_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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__))))
Expand Down Expand Up @@ -161,7 +161,7 @@ def test_watch_assets(self):
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},
Expand All @@ -170,7 +170,7 @@ def test_watch_assets(self):

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)

Expand All @@ -186,7 +186,7 @@ def test_watch_theme_assets(self):
])

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(),
Expand All @@ -195,7 +195,7 @@ def test_watch_theme_assets(self):
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)

Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down