From 21d17db7e2ff72b299a7e728aef39538c0d23e66 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Apr 2013 22:37:35 +0200 Subject: [PATCH 1/7] formatting: Make indent() more useful --- mopidy/utils/formatting.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mopidy/utils/formatting.py b/mopidy/utils/formatting.py index ba311fb5..3c313eae 100644 --- a/mopidy/utils/formatting.py +++ b/mopidy/utils/formatting.py @@ -4,13 +4,15 @@ import re import unicodedata -def indent(string, places=4, linebreak='\n'): +def indent(string, places=4, linebreak='\n', singles=False): lines = string.split(linebreak) - if len(lines) == 1: + if not singles and len(lines) == 1: return string - result = '' - for line in lines: - result += linebreak + ' ' * places + line + for i, line in enumerate(lines): + lines[i] = ' ' * places + line + result = linebreak.join(lines) + if not singles: + result = linebreak + result return result From 5de80228ea22d8f8c22ca039dd212394183916ed Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Apr 2013 22:37:56 +0200 Subject: [PATCH 2/7] deps: Output one dep per line --- mopidy/utils/deps.py | 36 ++++++++++++++++++++++++------------ tests/utils/deps_test.py | 23 +++++++++++++++++++---- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/mopidy/utils/deps.py b/mopidy/utils/deps.py index c83780fb..167e9be2 100644 --- a/mopidy/utils/deps.py +++ b/mopidy/utils/deps.py @@ -39,19 +39,31 @@ def format_dependency_list(adapters=None): ws4py_info, ] + return '\n'.join([_format_dependency(a()) for a in adapters]) + + +def _format_dependency(dep_info): lines = [] - for adapter in adapters: - dep_info = adapter() - lines.append('%(name)s: %(version)s' % { - 'name': dep_info['name'], - 'version': dep_info.get('version', 'not found'), - }) - if 'path' in dep_info: - lines.append(' Imported from: %s' % ( - os.path.dirname(dep_info['path']))) - if 'other' in dep_info: - lines.append(' Other: %s' % ( - formatting.indent(dep_info['other'])),) + + if 'version' not in dep_info: + lines.append('%s: not found' % dep_info['name']) + else: + lines.append('%s: %s from %s' % ( + dep_info['name'], + dep_info['version'], + os.path.dirname(dep_info.get('path', 'none')), + )) + + if 'other' in dep_info: + lines.append(' Detailed information: %s' % ( + formatting.indent(dep_info['other'], places=4)),) + + if dep_info.get('dependencies', []): + for sub_dep_info in dep_info['dependencies']: + sub_dep_lines = _format_dependency(sub_dep_info) + lines.append( + formatting.indent(sub_dep_lines, places=2, singles=True)) + return '\n'.join(lines) diff --git a/tests/utils/deps_test.py b/tests/utils/deps_test.py index 65a1eda1..90afd45c 100644 --- a/tests/utils/deps_test.py +++ b/tests/utils/deps_test.py @@ -47,17 +47,32 @@ class DepsTest(unittest.TestCase): adapters = [ lambda: dict(name='Python', version='FooPython 2.7.3'), lambda: dict(name='Platform', version='Loonix 4.0.1'), - lambda: dict(name='Pykka', path='/foo/bar/baz.py', other='Quux') + lambda: dict( + name='Pykka', version='1.1', + path='/foo/bar/baz.py', other='Quux'), + lambda: dict(name='Foo'), + lambda: dict(name='Mopidy', version='0.13', dependencies=[ + dict(name='pylast', version='0.5', dependencies=[ + dict(name='setuptools', version='0.6') + ]) + ]) ] result = deps.format_dependency_list(adapters) self.assertIn('Python: FooPython 2.7.3', result) + self.assertIn('Platform: Loonix 4.0.1', result) - self.assertIn('Pykka: not found', result) - self.assertIn('Imported from: /foo/bar', result) + + self.assertIn('Pykka: 1.1 from /foo/bar', result) self.assertNotIn('/baz.py', result) - self.assertIn('Quux', result) + self.assertIn('Detailed information: Quux', result) + + self.assertIn('Foo: not found', result) + + self.assertIn('Mopidy: 0.13', result) + self.assertIn(' pylast: 0.5', result) + self.assertIn(' setuptools: 0.6', result) def test_platform_info(self): result = deps.platform_info() From 609b6ace85c8092199818fa90b7b300dcbcd7a47 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Apr 2013 22:33:06 +0200 Subject: [PATCH 3/7] deps: Look up deps in pkg_resources --- mopidy/utils/deps.py | 26 ++++++++++++++++++++++++++ tests/utils/deps_test.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/mopidy/utils/deps.py b/mopidy/utils/deps.py index 167e9be2..2c55efb3 100644 --- a/mopidy/utils/deps.py +++ b/mopidy/utils/deps.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import functools import os import platform import sys @@ -8,6 +9,7 @@ import pygst pygst.require('0.10') import gst +import pkg_resources import pykka from . import formatting @@ -37,8 +39,17 @@ def format_dependency_list(adapters=None): serial_info, cherrypy_info, ws4py_info, + functools.partial(pkg_info, 'Mopidy', True), ] + dist_names = set([ + ep.dist.project_name for ep in + pkg_resources.iter_entry_points('mopidy.ext') + if ep.dist.project_name != 'Mopidy']) + for dist_name in dist_names: + adapters.append( + functools.partial(pkg_info, dist_name)) + return '\n'.join([_format_dependency(a()) for a in adapters]) @@ -83,6 +94,21 @@ def python_info(): } +def pkg_info(project_name=None, include_extras=False): + if project_name is None: + project_name = 'Mopidy' + distribution = pkg_resources.get_distribution(project_name) + extras = include_extras and distribution.extras or [] + dependencies = [ + pkg_info(d) for d in distribution.requires(extras)] + return { + 'name': distribution.project_name, + 'version': distribution.version, + 'path': distribution.location, + 'dependencies': dependencies, + } + + def gstreamer_info(): other = [] other.append('Python wrapper: gst-python %s' % ( diff --git a/tests/utils/deps_test.py b/tests/utils/deps_test.py index 90afd45c..fff0c1c3 100644 --- a/tests/utils/deps_test.py +++ b/tests/utils/deps_test.py @@ -5,6 +5,8 @@ import platform import pygst pygst.require('0.10') import gst + +import mock import pykka try: @@ -156,3 +158,40 @@ class DepsTest(unittest.TestCase): self.assertEquals('ws4py', result['name']) self.assertEquals(ws4py.__version__, result['version']) self.assertIn('ws4py', result['path']) + + @mock.patch('pkg_resources.get_distribution') + def test_pkg_info(self, get_distribution_mock): + dist_mopidy = mock.Mock() + dist_mopidy.project_name = 'Mopidy' + dist_mopidy.version = '0.13' + dist_mopidy.location = '/tmp/example/mopidy' + dist_mopidy.requires.return_value = ['Pykka'] + + dist_pykka = mock.Mock() + dist_pykka.project_name = 'Pykka' + dist_pykka.version = '1.1' + dist_pykka.location = '/tmp/example/pykka' + dist_pykka.requires.return_value = ['setuptools'] + + dist_setuptools = mock.Mock() + dist_setuptools.project_name = 'setuptools' + dist_setuptools.version = '0.6' + dist_setuptools.location = '/tmp/example/setuptools' + dist_setuptools.requires.return_value = [] + + get_distribution_mock.side_effect = [ + dist_mopidy, dist_pykka, dist_setuptools] + + result = deps.pkg_info() + + self.assertEquals('Mopidy', result['name']) + self.assertEquals('0.13', result['version']) + self.assertIn('mopidy', result['path']) + + dep_info_pykka = result['dependencies'][0] + self.assertEquals('Pykka', dep_info_pykka['name']) + self.assertEquals('1.1', dep_info_pykka['version']) + + dep_info_setuptools = dep_info_pykka['dependencies'][0] + self.assertEquals('setuptools', dep_info_setuptools['name']) + self.assertEquals('0.6', dep_info_setuptools['version']) From 07e6d118508d25243528fad6947896f92956457d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Apr 2013 22:33:32 +0200 Subject: [PATCH 4/7] deps: Remove old manual deps checkers --- mopidy/utils/deps.py | 87 --------------------------------------- tests/utils/deps_test.py | 88 ---------------------------------------- 2 files changed, 175 deletions(-) diff --git a/mopidy/utils/deps.py b/mopidy/utils/deps.py index 2c55efb3..e743e19e 100644 --- a/mopidy/utils/deps.py +++ b/mopidy/utils/deps.py @@ -10,7 +10,6 @@ pygst.require('0.10') import gst import pkg_resources -import pykka from . import formatting @@ -32,13 +31,6 @@ def format_dependency_list(adapters=None): platform_info, python_info, gstreamer_info, - pykka_info, - pyspotify_info, - pylast_info, - dbus_info, - serial_info, - cherrypy_info, - ws4py_info, functools.partial(pkg_info, 'Mopidy', True), ] @@ -172,82 +164,3 @@ def _gstreamer_check_elements(): gst.registry_get_default().get_feature_list(gst.TYPE_ELEMENT_FACTORY)] return [ (element, element in known_elements) for element in elements_to_check] - - -def pykka_info(): - return { - 'name': 'Pykka', - 'version': pykka.__version__, - 'path': pykka.__file__, - } - - -def pyspotify_info(): - dep_info = {'name': 'pyspotify'} - try: - import spotify - if hasattr(spotify, '__version__'): - dep_info['version'] = spotify.__version__ - else: - dep_info['version'] = '< 1.3' - dep_info['path'] = spotify.__file__ - dep_info['other'] = 'Built for libspotify API version %d' % ( - spotify.api_version,) - except ImportError: - pass - return dep_info - - -def pylast_info(): - dep_info = {'name': 'pylast'} - try: - import pylast - dep_info['version'] = pylast.__version__ - dep_info['path'] = pylast.__file__ - except ImportError: - pass - return dep_info - - -def dbus_info(): - dep_info = {'name': 'dbus-python'} - try: - import dbus - dep_info['version'] = dbus.__version__ - dep_info['path'] = dbus.__file__ - except ImportError: - pass - return dep_info - - -def serial_info(): - dep_info = {'name': 'pyserial'} - try: - import serial - dep_info['version'] = serial.VERSION - dep_info['path'] = serial.__file__ - except ImportError: - pass - return dep_info - - -def cherrypy_info(): - dep_info = {'name': 'cherrypy'} - try: - import cherrypy - dep_info['version'] = cherrypy.__version__ - dep_info['path'] = cherrypy.__file__ - except ImportError: - pass - return dep_info - - -def ws4py_info(): - dep_info = {'name': 'ws4py'} - try: - import ws4py - dep_info['version'] = ws4py.__version__ - dep_info['path'] = ws4py.__file__ - except ImportError: - pass - return dep_info diff --git a/tests/utils/deps_test.py b/tests/utils/deps_test.py index fff0c1c3..482a1cf5 100644 --- a/tests/utils/deps_test.py +++ b/tests/utils/deps_test.py @@ -7,37 +7,6 @@ pygst.require('0.10') import gst import mock -import pykka - -try: - import dbus -except ImportError: - dbus = False - -try: - import pylast -except ImportError: - pylast = False - -try: - import serial -except ImportError: - serial = False - -try: - import spotify -except ImportError: - spotify = False - -try: - import cherrypy -except ImportError: - cherrypy = False - -try: - import ws4py -except ImportError: - ws4py = False from mopidy.utils import deps @@ -102,63 +71,6 @@ class DepsTest(unittest.TestCase): '.'.join(map(str, gst.get_pygst_version())), result['other']) self.assertIn('Relevant elements:', result['other']) - def test_pykka_info(self): - result = deps.pykka_info() - - self.assertEquals('Pykka', result['name']) - self.assertEquals(pykka.__version__, result['version']) - self.assertIn('pykka', result['path']) - - @unittest.skipUnless(spotify, 'pyspotify not found') - def test_pyspotify_info(self): - result = deps.pyspotify_info() - - self.assertEquals('pyspotify', result['name']) - self.assertEquals(spotify.__version__, result['version']) - self.assertIn('spotify', result['path']) - self.assertIn('Built for libspotify API version', result['other']) - self.assertIn(str(spotify.api_version), result['other']) - - @unittest.skipUnless(pylast, 'pylast not found') - def test_pylast_info(self): - result = deps.pylast_info() - - self.assertEquals('pylast', result['name']) - self.assertEquals(pylast.__version__, result['version']) - self.assertIn('pylast', result['path']) - - @unittest.skipUnless(dbus, 'dbus not found') - def test_dbus_info(self): - result = deps.dbus_info() - - self.assertEquals('dbus-python', result['name']) - self.assertEquals(dbus.__version__, result['version']) - self.assertIn('dbus', result['path']) - - @unittest.skipUnless(serial, 'serial not found') - def test_serial_info(self): - result = deps.serial_info() - - self.assertEquals('pyserial', result['name']) - self.assertEquals(serial.VERSION, result['version']) - self.assertIn('serial', result['path']) - - @unittest.skipUnless(cherrypy, 'cherrypy not found') - def test_cherrypy_info(self): - result = deps.cherrypy_info() - - self.assertEquals('cherrypy', result['name']) - self.assertEquals(cherrypy.__version__, result['version']) - self.assertIn('cherrypy', result['path']) - - @unittest.skipUnless(ws4py, 'ws4py not found') - def test_ws4py_info(self): - result = deps.ws4py_info() - - self.assertEquals('ws4py', result['name']) - self.assertEquals(ws4py.__version__, result['version']) - self.assertIn('ws4py', result['path']) - @mock.patch('pkg_resources.get_distribution') def test_pkg_info(self, get_distribution_mock): dist_mopidy = mock.Mock() From 87983687634236a583bca38b8df5935150bbed9f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Apr 2013 22:47:38 +0200 Subject: [PATCH 5/7] deps: Move GStreamer to the bottom --- mopidy/utils/deps.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mopidy/utils/deps.py b/mopidy/utils/deps.py index e743e19e..9e73583c 100644 --- a/mopidy/utils/deps.py +++ b/mopidy/utils/deps.py @@ -27,20 +27,21 @@ def list_deps_optparse_callback(*args): def format_dependency_list(adapters=None): if adapters is None: - adapters = [ - platform_info, - python_info, - gstreamer_info, - functools.partial(pkg_info, 'Mopidy', True), - ] - dist_names = set([ ep.dist.project_name for ep in pkg_resources.iter_entry_points('mopidy.ext') if ep.dist.project_name != 'Mopidy']) - for dist_name in dist_names: - adapters.append( - functools.partial(pkg_info, dist_name)) + dist_infos = [ + functools.partial(pkg_info, dist_name) + for dist_name in dist_names] + + adapters = [ + platform_info, + python_info, + functools.partial(pkg_info, 'Mopidy', True) + ] + dist_infos + [ + gstreamer_info, + ] return '\n'.join([_format_dependency(a()) for a in adapters]) From 164eaffea7a0d8bff65f554de70c8e5937830166 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Apr 2013 23:05:15 +0200 Subject: [PATCH 6/7] deps: Group GStreamer elements by found/not found --- mopidy/utils/deps.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/mopidy/utils/deps.py b/mopidy/utils/deps.py index 9e73583c..b1e9c508 100644 --- a/mopidy/utils/deps.py +++ b/mopidy/utils/deps.py @@ -106,9 +106,27 @@ def gstreamer_info(): other = [] other.append('Python wrapper: gst-python %s' % ( '.'.join(map(str, gst.get_pygst_version())))) - other.append('Relevant elements:') + + found_elements = [] + missing_elements = [] for name, status in _gstreamer_check_elements(): - other.append(' %s: %s' % (name, 'OK' if status else 'not found')) + if status: + found_elements.append(name) + else: + missing_elements.append(name) + + other.append('Relevant elements:') + other.append(' Found:') + for element in found_elements: + other.append(' %s' % element) + else: + other.append(' none') + other.append(' Not found:') + for element in missing_elements: + other.append(' %s' % element) + else: + other.append(' none') + return { 'name': 'GStreamer', 'version': '.'.join(map(str, gst.get_gst_version())), From 466cf3ba33fcd2393d83db28bd949e1fd97e794f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 16 Apr 2013 23:07:38 +0200 Subject: [PATCH 7/7] deps: Rename --list-deps to --show-deps --- docs/changelog.rst | 3 +++ docs/running.rst | 4 ++-- docs/troubleshooting.rst | 2 +- mopidy/__main__.py | 6 +++--- mopidy/utils/deps.py | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index a2a73dd3..6a4ab026 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -55,6 +55,9 @@ and improved. - The command option :option:`mopidy --list-settings` is now named :option:`mopidy --show-config`. +- The command option :option:`mopidy --list-deps` is now named + :option:`mopidy --show-deps`. + - What configuration files to use can now be specified through the command option :option:`mopidy --config`. diff --git a/docs/running.rst b/docs/running.rst index 58f7d591..7e0bacbb 100644 --- a/docs/running.rst +++ b/docs/running.rst @@ -52,9 +52,9 @@ mopidy command together to show the effective document. Secret values like passwords are masked out. Config for disabled extensions are not included. -.. cmdoption:: --list-deps +.. cmdoption:: --show-deps - List dependencies and their versions. + Show dependencies, their versions and installation location. .. cmdoption:: --config diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 0c0f10da..66b942c9 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -27,7 +27,7 @@ with others for debugging. Installed dependencies ====================== -The command :option:`mopidy --list-deps` will list the paths to and versions of +The command :option:`mopidy --show-deps` will list the paths to and versions of any dependency Mopidy or the extensions might need to work. This is very useful data for checking that you're using the right versions, and that you're using the right installation if you have multiple installations of a dependency on diff --git a/mopidy/__main__.py b/mopidy/__main__.py index cd082b9f..d2df7f81 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -120,9 +120,9 @@ def parse_options(): action='callback', callback=show_config_callback, help='show current config') parser.add_option( - b'--list-deps', - action='callback', callback=deps.list_deps_optparse_callback, - help='list dependencies and their versions') + b'--show-deps', + action='callback', callback=deps.show_deps_optparse_callback, + help='show dependencies and their versions') parser.add_option( b'--config', action='store', dest='config', diff --git a/mopidy/utils/deps.py b/mopidy/utils/deps.py index b1e9c508..742536a5 100644 --- a/mopidy/utils/deps.py +++ b/mopidy/utils/deps.py @@ -14,11 +14,11 @@ import pkg_resources from . import formatting -def list_deps_optparse_callback(*args): +def show_deps_optparse_callback(*args): """ Prints a list of all dependencies. - Called by optparse when Mopidy is run with the :option:`--list-deps` + Called by optparse when Mopidy is run with the :option:`--show-deps` option. """ print format_dependency_list()