Repository: ipython/ipython

Issue:
Make writing configurable magics easier
right now if you want to have a Configurable Magics class, you need multiple heritance and have a minimum `__init__` that look like that : 

```
def __init__(self, shell=None):
    Magics.__init__(self, shell)
    Configurable.__init__(self, config=shell.config)
```

`ScriptMagics` seem to be the only configurable magic for now. What about an intermediate `ConfigurableMagic` Class ? Alternative would be to make `Magics` Configurable by default.



Discussion:

Resolving patch:
diff --git a/IPython/core/magic.py b/IPython/core/magic.py
--- a/IPython/core/magic.py
+++ b/IPython/core/magic.py
@@ -477,7 +477,8 @@ def register_alias(self, alias_name, magic_name, magic_kind='line'):
 
 # Key base class that provides the central functionality for magics.
 
-class Magics(object):
+
+class Magics(Configurable):
     """Base class for implementing magic functions.
 
     Shell functions which can be reached as %function_name. All magic
@@ -506,10 +507,17 @@ class Magics(object):
     # Instance of IPython shell
     shell = None
 
-    def __init__(self, shell):
+    def __init__(self, shell=None, **kwargs):
         if not(self.__class__.registered):
             raise ValueError('Magics subclass without registration - '
                              'did you forget to apply @magics_class?')
+        if shell is not None:
+            if hasattr(shell, 'configurables'):
+                shell.configurables.append(self)
+            if hasattr(shell, 'config'):
+                kwargs.setdefault('parent', shell)
+            kwargs['shell'] = shell
+
         self.shell = shell
         self.options_table = {}
         # The method decorators are run when the instance doesn't exist yet, so
@@ -530,6 +538,9 @@ def __init__(self, shell):
                 else:
                     # it's the real thing
                     tab[magic_name] = meth_name
+        # Configurable **need** to be initiated at the end or the config
+        # magics get screwed up.
+        super(Magics, self).__init__(**kwargs)
 
     def arg_err(self,func):
         """Print docstring if incorrect arguments were passed"""
@@ -639,6 +650,7 @@ def default_option(self, fn, optstr):
             error("%s is not a magic function" % fn)
         self.options_table[fn] = optstr
 
+
 class MagicAlias(object):
     """An alias to another magic function.
 
diff --git a/IPython/core/magics/script.py b/IPython/core/magics/script.py
--- a/IPython/core/magics/script.py
+++ b/IPython/core/magics/script.py
@@ -71,7 +71,7 @@ def script_args(f):
     return f
 
 @magics_class
-class ScriptMagics(Magics, Configurable):
+class ScriptMagics(Magics):
     """Magics for talking to scripts
     
     This defines a base `%%script` cell magic for running a cell
@@ -116,9 +116,8 @@ def _script_magics_default(self):
     )
     
     def __init__(self, shell=None):
-        Configurable.__init__(self, config=shell.config)
+        super(ScriptMagics, self).__init__(shell=shell)
         self._generate_script_magics()
-        Magics.__init__(self, shell=shell)
         self.job_manager = BackgroundJobManager()
         self.bg_processes = []
         atexit.register(self.kill_bg_processes)
diff --git a/IPython/extensions/storemagic.py b/IPython/extensions/storemagic.py
--- a/IPython/extensions/storemagic.py
+++ b/IPython/extensions/storemagic.py
@@ -25,7 +25,6 @@
 import inspect, os, sys, textwrap
 
 # Our own
-from IPython.config.configurable import Configurable
 from IPython.core.error import UsageError
 from IPython.core.magic import Magics, magics_class, line_magic
 from IPython.testing.skipdoctest import skip_doctest
@@ -69,7 +68,7 @@ def restore_data(ip):
 
 
 @magics_class
-class StoreMagics(Magics, Configurable):
+class StoreMagics(Magics):
     """Lightweight persistence for python variables.
 
     Provides the %store magic."""
@@ -81,8 +80,7 @@ class StoreMagics(Magics, Configurable):
     )
     
     def __init__(self, shell):
-        Configurable.__init__(self, config=shell.config)
-        Magics.__init__(self, shell=shell)
+        super(StoreMagics, self).__init__(shell=shell)
         self.shell.configurables.append(self)
         if self.autorestore:
             restore_data(self.shell)


Test patch:
diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py
--- a/IPython/core/tests/test_magic.py
+++ b/IPython/core/tests/test_magic.py
@@ -60,7 +60,6 @@ def test_extract_code_ranges():
     actual = list(code.extract_code_ranges(instr))
     nt.assert_equal(actual, expected)
 
-
 def test_extract_symbols():
     source = """import foo\na = 10\ndef b():\n    return 42\n\n\nclass A: pass\n\n\n"""
     symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
@@ -82,6 +81,14 @@ def test_extract_symbols_raises_exception_with_non_python_code():
     with nt.assert_raises(SyntaxError):
         code.extract_symbols(source, "hello")
 
+def test_config():
+    """ test that config magic does not raise
+    can happen if Configurable init is moved too early into
+    Magics.__init__ as then a Config object will be registerd as a
+    magic.
+    """
+    ## should not raise.
+    _ip.magic('config')
 
 def test_rehashx():
     # clear up everything
diff --git a/IPython/extensions/tests/test_autoreload.py b/IPython/extensions/tests/test_autoreload.py
--- a/IPython/extensions/tests/test_autoreload.py
+++ b/IPython/extensions/tests/test_autoreload.py
@@ -33,6 +33,7 @@
 noop = lambda *a, **kw: None
 
 class FakeShell(object):
+
     def __init__(self):
         self.ns = {}
         self.auto_magics = AutoreloadMagics(shell=self)