From 46a9a12476f59c0eaf093da44687a9aaf296a648 2009-08-27 21:34:41 From: Brian Granger Date: 2009-08-27 21:34:41 Subject: [PATCH] A number of changes to how traitlets and components work. * Classes can be given as strings, like 'foo.bar.Bar' in the Type and Instance traitlets as well as the get_instances method of Component. This is done to allow for forward declarations. * New IPython.utils.importstring that imports a class from a string, like 'foo.bar.Bar'. --- diff --git a/IPython/core/builtin_trap.py b/IPython/core/builtin_trap.py index d3b2eba..227d659 100644 --- a/IPython/core/builtin_trap.py +++ b/IPython/core/builtin_trap.py @@ -24,6 +24,8 @@ import __builtin__ from IPython.core.component import Component from IPython.core.quitter import Quitter +from IPython.utils.traitlets import Instance + #----------------------------------------------------------------------------- # Classes and functions #----------------------------------------------------------------------------- @@ -34,12 +36,13 @@ BuiltinUndefined = BuiltinUndefined() class BuiltinTrap(Component): + shell = Instance('IPython.core.iplib.InteractiveShell') def __init__(self, parent, name=None, config=None): super(BuiltinTrap, self).__init__(parent, name, config) # Don't just grab parent!!! - from IPython.core.iplib import InteractiveShell - self.shell = InteractiveShell.get_instances(root=self.root)[0] + self.shell = Component.get_instances(root=self.root, + klass='IPython.core.iplib.InteractiveShell')[0] self._orig_builtins = {} def __enter__(self): diff --git a/IPython/core/component.py b/IPython/core/component.py index bc914cd..5565098 100644 --- a/IPython/core/component.py +++ b/IPython/core/component.py @@ -24,6 +24,7 @@ from copy import deepcopy import datetime from weakref import WeakValueDictionary +from IPython.utils.importstring import import_item from IPython.utils.ipstruct import Struct from IPython.utils.traitlets import ( HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This @@ -64,7 +65,7 @@ class MetaComponentTracker(type): return instance - def get_instances(cls, name=None, root=None, classname=None): + def get_instances(cls, name=None, root=None, klass=None): """Get all instances of cls and its subclasses. Parameters @@ -73,26 +74,31 @@ class MetaComponentTracker(type): Limit to components with this name. root : Component or subclass Limit to components having this root. - classname : str - The string name of a class to match exactly. + klass : class or str + Limits to instances of the class or its subclasses. If a str + is given ut must be in the form 'foo.bar.MyClass'. The str + form of this argument is useful for forward declarations. """ + if klass is not None: + if isinstance(klass, basestring): + klass = import_item(klass) instances = cls.__instance_refs.values() if name is not None: instances = [i for i in instances if i.name == name] - if classname is not None: - instances = [i for i in instances if i.__class__.__name__ == classname] + if klass is not None: + instances = [i for i in instances if isinstance(i, klass)] if root is not None: instances = [i for i in instances if i.root == root] return instances def get_instances_by_condition(cls, call, name=None, root=None, - classname=None): + klass=None): """Get all instances of cls, i such that call(i)==True. This also takes the ``name`` and ``root`` and ``classname`` arguments of :meth:`get_instance` """ - return [i for i in cls.get_instances(name, root, classname) if call(i)] + return [i for i in cls.get_instances(name, root, klass) if call(i)] class ComponentNameGenerator(object): diff --git a/IPython/core/iplib.py b/IPython/core/iplib.py index 68efdce..346da0c 100644 --- a/IPython/core/iplib.py +++ b/IPython/core/iplib.py @@ -1252,8 +1252,8 @@ class InteractiveShell(Component, Magic): else: magic_args = self.var_expand(magic_args,1) with self.builtin_trap: - result = fn(magic_args) - return result + return fn(magic_args) + # return result def define_magic(self, magicname, func): """Expose own function as magic function for ipython @@ -1357,8 +1357,7 @@ class InteractiveShell(Component, Magic): Returns the result of evaluation """ with self.builtin_trap: - result = eval(expr, self.user_global_ns, self.user_ns) - return result + return eval(expr, self.user_global_ns, self.user_ns) def getoutput(self, cmd): return getoutput(self.var_expand(cmd,depth=2), @@ -1414,7 +1413,7 @@ class InteractiveShell(Component, Magic): outcomps.sort() #print "T:",text,"OC:",outcomps # dbg #print "vars:",self.user_ns.keys() - return outcomps + return outcomps def set_completer_frame(self, frame=None): if frame: diff --git a/IPython/kernel/clientconnector.py b/IPython/kernel/clientconnector.py index 595a353..5d90467 100644 --- a/IPython/kernel/clientconnector.py +++ b/IPython/kernel/clientconnector.py @@ -20,7 +20,7 @@ from twisted.internet import defer from IPython.kernel.fcutil import Tub, UnauthenticatedTub from IPython.kernel.config import config_manager as kernel_config_manager -from IPython.config.cutils import import_item +from IPython.utils.importstring import import_item from IPython.kernel.fcutil import find_furl co = kernel_config_manager.get_config_obj() diff --git a/IPython/kernel/scripts/ipcontroller.py b/IPython/kernel/scripts/ipcontroller.py index 3ab6175..0958be1 100755 --- a/IPython/kernel/scripts/ipcontroller.py +++ b/IPython/kernel/scripts/ipcontroller.py @@ -52,7 +52,7 @@ get_log_dir() get_security_dir() from IPython.kernel.config import config_manager as kernel_config_manager -from IPython.config.cutils import import_item +from IPython.utils.importstring import import_item #------------------------------------------------------------------------------- diff --git a/IPython/kernel/scripts/ipengine.py b/IPython/kernel/scripts/ipengine.py index 9a41047..425c3c1 100755 --- a/IPython/kernel/scripts/ipengine.py +++ b/IPython/kernel/scripts/ipengine.py @@ -31,7 +31,7 @@ from twisted.python import log from IPython.kernel.fcutil import Tub, UnauthenticatedTub from IPython.kernel.core.config import config_manager as core_config_manager -from IPython.config.cutils import import_item +from IPython.utils.importstring import import_item from IPython.kernel.engineservice import EngineService # Create various ipython directories if they don't exist. diff --git a/IPython/utils/importstring.py b/IPython/utils/importstring.py new file mode 100644 index 0000000..12752f4 --- /dev/null +++ b/IPython/utils/importstring.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +A simple utility to import something by its string name. + +Authors: + +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Functions and classes +#----------------------------------------------------------------------------- + +def import_item(name): + """Import and return bar given the string foo.bar.""" + package = '.'.join(name.split('.')[0:-1]) + obj = name.split('.')[-1] + execString = 'from %s import %s' % (package, obj) + try: + exec execString + except SyntaxError: + raise ImportError("Invalid class specification: %s" % name) + exec 'temp = %s' % obj + return temp diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py index e5ad5d9..09661eb 100644 --- a/IPython/utils/tests/test_traitlets.py +++ b/IPython/utils/tests/test_traitlets.py @@ -377,6 +377,7 @@ class TestType(TestCase): a = A() self.assertEquals(a.klass, None) + a.klass = B self.assertEquals(a.klass, B) self.assertRaises(TraitletError, setattr, a, 'klass', 10) @@ -409,11 +410,15 @@ class TestType(TestCase): def test_validate_klass(self): - def inner(): - class A(HasTraitlets): - klass = Type('no strings allowed') + class A(HasTraitlets): + klass = Type('no strings allowed') - self.assertRaises(TraitletError, inner) + self.assertRaises(ImportError, A) + + class A(HasTraitlets): + klass = Type('rub.adub.Duck') + + self.assertRaises(ImportError, A) def test_validate_default(self): @@ -421,13 +426,25 @@ class TestType(TestCase): class A(HasTraitlets): klass = Type('bad default', B) - self.assertRaises(TraitletError, A) + self.assertRaises(ImportError, A) class C(HasTraitlets): klass = Type(None, B, allow_none=False) self.assertRaises(TraitletError, C) + def test_str_klass(self): + + class A(HasTraitlets): + klass = Type('IPython.utils.ipstruct.Struct') + + from IPython.utils.ipstruct import Struct + a = A() + a.klass = Struct + self.assertEquals(a.klass, Struct) + + self.assertRaises(TraitletError, setattr, a, 'klass', 10) + class TestInstance(TestCase): def test_basic(self): @@ -449,7 +466,7 @@ class TestInstance(TestCase): self.assertRaises(TraitletError, setattr, a, 'inst', Bah()) def test_unique_default_value(self): - class Foo(object): pass + class Foo(object): pass class A(HasTraitlets): inst = Instance(Foo,(),{}) diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py index 216df41..9438d9a 100644 --- a/IPython/utils/traitlets.py +++ b/IPython/utils/traitlets.py @@ -57,6 +57,8 @@ from types import ( ListType, TupleType ) +from IPython.utils.importstring import import_item + ClassTypes = (ClassType, type) SequenceTypes = (ListType, TupleType) @@ -142,7 +144,6 @@ def parse_notifier_name(name): class _SimpleTest: def __init__ ( self, value ): self.value = value def __call__ ( self, test ): - print test, self.value return test == self.value def __repr__(self): return "