From 059f96814d80fc1a3c14e7e9f40428865ceb233a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 22:16:11 +0200 Subject: [PATCH 1/6] Add basic tests for get_class util --- tests/utils_test.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/utils_test.py b/tests/utils_test.py index d5c98d86..9a8f1129 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -11,6 +11,15 @@ from mopidy.models import Track, Artist, Album from tests import SkipTest, data_folder +class GetClassTest(unittest.TestCase): + def test_loading_class_that_does_not_exist(self): + test = lambda: get_class('foo.bar.Baz') + self.assertRaises(ImportError, test) + + def test_loading_existing_class(self): + cls = get_class('unittest.TestCase') + self.assertEqual(cls.__name__, 'TestCase') + class GetOrCreateFolderTest(unittest.TestCase): def setUp(self): self.parent = tempfile.mkdtemp() From e4bdacbb61a6894a8515bd5bae100cb978514028 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 22:28:02 +0200 Subject: [PATCH 2/6] Add test_import_error_message_contains_complete_class_path test for get_class --- mopidy/utils.py | 5 ++++- tests/utils_test.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/mopidy/utils.py b/mopidy/utils.py index ff032b4e..b8aa574c 100644 --- a/mopidy/utils.py +++ b/mopidy/utils.py @@ -24,7 +24,10 @@ def get_class(name): module_name = name[:name.rindex('.')] class_name = name[name.rindex('.') + 1:] logger.debug('Loading: %s', name) - module = import_module(module_name) + try: + module = import_module(module_name) + except ImportError: + raise ImportError("Couldn't load: %s" % name) class_object = getattr(module, class_name) return class_object diff --git a/tests/utils_test.py b/tests/utils_test.py index 9a8f1129..d5beade2 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -16,6 +16,12 @@ class GetClassTest(unittest.TestCase): test = lambda: get_class('foo.bar.Baz') self.assertRaises(ImportError, test) + def test_import_error_message_contains_complete_class_path(self): + try: + get_class('foo.bar.Baz') + except ImportError as e: + self.assert_('foo.bar.Baz' in str(e)) + def test_loading_existing_class(self): cls = get_class('unittest.TestCase') self.assertEqual(cls.__name__, 'TestCase') From ec67d43fc960d5537fef3fdf5e6fc46c6a354e0f Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 22:29:41 +0200 Subject: [PATCH 3/6] Test both case where class and/or module does not exist for get_class --- mopidy/utils.py | 4 ++-- tests/utils_test.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mopidy/utils.py b/mopidy/utils.py index b8aa574c..bdc0b632 100644 --- a/mopidy/utils.py +++ b/mopidy/utils.py @@ -26,9 +26,9 @@ def get_class(name): logger.debug('Loading: %s', name) try: module = import_module(module_name) - except ImportError: + class_object = getattr(module, class_name) + except (ImportError, AttributeError): raise ImportError("Couldn't load: %s" % name) - class_object = getattr(module, class_name) return class_object def get_or_create_folder(folder): diff --git a/tests/utils_test.py b/tests/utils_test.py index d5beade2..ca44de45 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -12,10 +12,14 @@ from mopidy.models import Track, Artist, Album from tests import SkipTest, data_folder class GetClassTest(unittest.TestCase): - def test_loading_class_that_does_not_exist(self): + def test_loading_module_that_does_not_exist(self): test = lambda: get_class('foo.bar.Baz') self.assertRaises(ImportError, test) + def test_loading_class_that_does_not_exist(self): + test = lambda: get_class('unittest.FooBarBaz') + self.assertRaises(ImportError, test) + def test_import_error_message_contains_complete_class_path(self): try: get_class('foo.bar.Baz') From 9c11c5ecb9ad5f57307a30d133ccdeb8f1a11f93 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 22:40:38 +0200 Subject: [PATCH 4/6] Log when a process has a problem importing classes and try to exit --- mopidy/process.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mopidy/process.py b/mopidy/process.py index 9759c4e6..79638515 100644 --- a/mopidy/process.py +++ b/mopidy/process.py @@ -28,6 +28,9 @@ class BaseProcess(multiprocessing.Process): except SettingsError as e: logger.error(e.message) sys.exit(1) + except ImportError as e: + logger.error(e) + sys.exit(1) def run_inside_try(self): raise NotImplementedError From a05212251bec02740b651eaf9849e190ebd1546e Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 22:48:51 +0200 Subject: [PATCH 5/6] Pass output, backend and frontend classes into coreprocess to so that import errors are handeled better --- mopidy/__main__.py | 5 ++++- mopidy/process.py | 14 ++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 7c62033b..c92ce1ed 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -22,7 +22,10 @@ def main(): get_or_create_folder('~/.mopidy/') core_queue = multiprocessing.Queue() get_class(settings.SERVER)(core_queue).start() - core = CoreProcess(core_queue) + output_class = get_class(settings.OUTPUT) + backend_class = get_class(settings.BACKENDS[0]) + frontend_class = get_class(settings.FRONTEND) + core = CoreProcess(core_queue, output_class, backend_class, frontend_class) core.start() asyncore.loop() diff --git a/mopidy/process.py b/mopidy/process.py index 79638515..b1cdc8af 100644 --- a/mopidy/process.py +++ b/mopidy/process.py @@ -37,10 +37,14 @@ class BaseProcess(multiprocessing.Process): class CoreProcess(BaseProcess): - def __init__(self, core_queue): + def __init__(self, core_queue, output_class, backend_class, + frontend_class): super(CoreProcess, self).__init__() self.core_queue = core_queue self.output_queue = None + self.output_class = output_class + self.backend_class = backend_class + self.frontend_class = frontend_class self.output = None self.backend = None self.frontend = None @@ -53,11 +57,9 @@ class CoreProcess(BaseProcess): def setup(self): self.output_queue = multiprocessing.Queue() - self.output = get_class(settings.OUTPUT)(self.core_queue, - self.output_queue) - self.backend = get_class(settings.BACKENDS[0])(self.core_queue, - self.output_queue) - self.frontend = get_class(settings.FRONTEND)(self.backend) + self.output = self.output_class(self.core_queue, self.output_queue) + self.backend = self.backend_class(self.core_queue, self.output_queue) + self.frontend = self.frontend_class(self.backend) def process_message(self, message): if message.get('to') == 'output': From b777397cceb63bbccce302904e78b51c5991ab17 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 13 Aug 2010 23:52:46 +0200 Subject: [PATCH 6/6] Cleanup pipe creation for GStreamer output --- mopidy/outputs/gstreamer.py | 44 +++++-------------------------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/mopidy/outputs/gstreamer.py b/mopidy/outputs/gstreamer.py index 65b65504..b81fbd0f 100644 --- a/mopidy/outputs/gstreamer.py +++ b/mopidy/outputs/gstreamer.py @@ -39,6 +39,8 @@ class GStreamerProcess(BaseProcess): http://jameswestby.net/weblog/tech/14-caution-python-multiprocessing-and-glib-dont-mix.html. """ + pipeline_description = 'appsrc name=data ! volume name=volume ! autoaudiosink name=sink' + def __init__(self, core_queue, output_queue): super(GStreamerProcess, self).__init__() self.core_queue = core_queue @@ -65,8 +67,10 @@ class GStreamerProcess(BaseProcess): messages_thread.daemon = True messages_thread.start() - # A pipeline consisting of many elements - self.gst_pipeline = gst.Pipeline("pipeline") + self.gst_pipeline = gst.parse_launch(self.pipeline_description) + self.gst_data_src = self.gst_pipeline.get_by_name('data') + self.gst_volume = self.gst_pipeline.get_by_name('volume') + self.gst_sink = self.gst_pipeline.get_by_name('sink') # Setup bus and message processor self.gst_bus = self.gst_pipeline.get_bus() @@ -74,42 +78,6 @@ class GStreamerProcess(BaseProcess): self.gst_bus_id = self.gst_bus.connect('message', self.process_gst_message) - # Bin for playing audio URIs - #self.gst_uri_src = gst.element_factory_make('uridecodebin', 'uri_src') - #self.gst_pipeline.add(self.gst_uri_src) - - # Bin for playing audio data - self.gst_data_src = gst.element_factory_make('appsrc', 'data_src') - self.gst_pipeline.add(self.gst_data_src) - - # Volume filter - self.gst_volume = gst.element_factory_make('volume', 'volume') - self.gst_pipeline.add(self.gst_volume) - - # Audio output sink - self.gst_sink = gst.element_factory_make('autoaudiosink', 'sink') - self.gst_pipeline.add(self.gst_sink) - - # Add callback that will link uri_src output with volume filter input - # when the output pad is ready. - # See http://stackoverflow.com/questions/2993777 for details. - def on_new_decoded_pad(dbin, pad, is_last): - uri_src = pad.get_parent() - pipeline = uri_src.get_parent() - volume = pipeline.get_by_name('volume') - uri_src.link(volume) - logger.debug("Linked uri_src's new decoded pad to volume filter") - # FIXME uridecodebin got no new-decoded-pad signal, but it's - # subcomponent decodebin2 got that signal. Fixing this is postponed - # till after data_src is up and running perfectly - #self.gst_uri_src.connect('new-decoded-pad', on_new_decoded_pad) - - # Link data source output with volume filter input - self.gst_data_src.link(self.gst_volume) - - # Link volume filter output to audio sink input - self.gst_volume.link(self.gst_sink) - def process_mopidy_message(self, message): """Process messages from the rest of Mopidy.""" if message['command'] == 'play_uri':