From 26fb9558d5e3e39ec2980e6937e1db69ea999764 2011-02-23 22:07:04 From: Thomas Kluyver Date: 2011-02-23 22:07:04 Subject: [PATCH] Merge branch 'issue-129' closes gh-129 closes gh-251 --- diff --git a/IPython/utils/tests/test_wildcard.py b/IPython/utils/tests/test_wildcard.py index d2bc4df..7c5cff5 100644 --- a/IPython/utils/tests/test_wildcard.py +++ b/IPython/utils/tests/test_wildcard.py @@ -115,3 +115,34 @@ class Tests (unittest.TestCase): show_all=True).keys() a.sort() self.assertEqual(a,res) + + def test_dict_attributes(self): + """Dictionaries should be indexed by attributes, not by keys. This was + causing Github issue 129.""" + ns = {"az":{"king":55}, "pq":{1:0}} + tests = [ + ("a*", ["az"]), + ("az.k*", ["az.keys"]), + ("pq.k*", ["pq.keys"]) + ] + for pat, res in tests: + res.sort() + a = wildcard.list_namespace(ns, "all", pat, ignore_case=False, + show_all=True).keys() + a.sort() + self.assertEqual(a, res) + + def test_dict_dir(self): + class A(object): + def __init__(self): + self.a = 1 + self.b = 2 + def __getattribute__(self, name): + if name=="a": + raise AttributeError + return object.__getattribute__(self, name) + + a = A() + adict = wildcard.dict_dir(a) + assert "a" not in adict # change to assertNotIn method in >= 2.7 + self.assertEqual(adict["b"], 2) diff --git a/IPython/utils/wildcard.py b/IPython/utils/wildcard.py index e9bd81d..e0fd9b4 100644 --- a/IPython/utils/wildcard.py +++ b/IPython/utils/wildcard.py @@ -4,6 +4,7 @@ Authors ------- - Jörgen Stenarson +- Thomas Kluyver """ #***************************************************************************** @@ -13,134 +14,98 @@ Authors # the file COPYING, distributed as part of this software. #***************************************************************************** -import __builtin__ import re import types from IPython.utils.dir2 import dir2 -def create_typestr2type_dicts(dont_include_in_type2type2str=["lambda"]): - """Return dictionaries mapping lower case typename to type objects, from - the types package, and vice versa.""" - typenamelist=[] - for tname in dir(types): - if tname[-4:]=="Type": - typenamelist.append(tname) - typestr2type={} - type2typestr={} +def create_typestr2type_dicts(dont_include_in_type2typestr=["lambda"]): + """Return dictionaries mapping lower case typename (e.g. 'tuple') to type + objects from the types package, and vice versa.""" + typenamelist = [tname for tname in dir(types) if tname.endswith("Type")] + typestr2type, type2typestr = {}, {} + for tname in typenamelist: - name=tname[:-4].lower() - obj=getattr(types,tname) - typestr2type[name]=getattr(types,tname) - if name in dont_include_in_type2type2str: - type2typestr[obj]=name - return typestr2type,type2typestr + name = tname[:-4].lower() # Cut 'Type' off the end of the name + obj = getattr(types, tname) + typestr2type[name] = obj + if name not in dont_include_in_type2typestr: + type2typestr[obj] = name + return typestr2type, type2typestr -typestr2type,type2typestr=create_typestr2type_dicts() +typestr2type, type2typestr = create_typestr2type_dicts() -def is_type(obj,typestr_or_type): - """is_type(obj,typestr_or_type) verifies if obj is of a certain type or - group of types takes strings as parameters of the for 'tuple'<->TupleType - 'all' matches all types. TODO: Should be extended for choosing more than - one type - """ - if typestr_or_type=="all": +def is_type(obj, typestr_or_type): + """is_type(obj, typestr_or_type) verifies if obj is of a certain type. It + can take strings or actual python types for the second argument, i.e. + 'tuple'<->TupleType. 'all' matches all types. + + TODO: Should be extended for choosing more than one type.""" + if typestr_or_type == "all": return True - if type(typestr_or_type)==types.TypeType: - test_type=typestr_or_type + if type(typestr_or_type) == types.TypeType: + test_type = typestr_or_type else: - test_type=typestr2type.get(typestr_or_type,False) + test_type = typestr2type.get(typestr_or_type, False) if test_type: - return isinstance(obj,test_type) - else: - return False + return isinstance(obj, test_type) + return False -def show_hidden(str,show_all=False): +def show_hidden(str, show_all=False): """Return true for strings starting with single _ if show_all is true.""" return show_all or str.startswith("__") or not str.startswith("_") -class NameSpace(object): - """NameSpace holds the dictionary for a namespace and implements filtering - on name and types""" - def __init__(self,obj,name_pattern="*",type_pattern="all",ignore_case=True, - show_all=True): - self.show_all = show_all #Hide names beginning with single _ - self.object = obj - self.name_pattern = name_pattern - self.type_pattern = type_pattern - self.ignore_case = ignore_case - - # We should only match EXACT dicts here, so DON'T use isinstance() - if type(obj) == types.DictType: - self._ns = obj - else: - kv = [] - for key in dir2(obj): - if isinstance(key, basestring): - # This seemingly unnecessary try/except is actually needed - # because there is code out there with metaclasses that - # create 'write only' attributes, where a getattr() call - # will fail even if the attribute appears listed in the - # object's dictionary. Properties can actually do the same - # thing. In particular, Traits use this pattern - try: - kv.append((key,getattr(obj,key))) - except AttributeError: - pass - self._ns = dict(kv) - - def get_ns(self): - """Return name space dictionary with objects matching type and name patterns.""" - return self.filter(self.name_pattern,self.type_pattern) - ns=property(get_ns) - - def get_ns_names(self): - """Return list of object names in namespace that match the patterns.""" - return self.ns.keys() - ns_names=property(get_ns_names,doc="List of objects in name space that " - "match the type and name patterns.") +def dict_dir(obj): + """Produce a dictionary of an object's attributes. Builds on dir2 by + checking that a getattr() call actually succeeds.""" + ns = {} + for key in dir2(obj): + # This seemingly unnecessary try/except is actually needed + # because there is code out there with metaclasses that + # create 'write only' attributes, where a getattr() call + # will fail even if the attribute appears listed in the + # object's dictionary. Properties can actually do the same + # thing. In particular, Traits use this pattern + try: + ns[key] = getattr(obj, key) + except AttributeError: + pass + return ns - def filter(self,name_pattern,type_pattern): - """Return dictionary of filtered namespace.""" - def glob_filter(lista,name_pattern,hidehidden,ignore_case): - """Return list of elements in lista that match pattern.""" - pattern=name_pattern.replace("*",".*").replace("?",".") - if ignore_case: - reg=re.compile(pattern+"$",re.I) - else: - reg=re.compile(pattern+"$") - result=[x for x in lista if reg.match(x) and show_hidden(x,hidehidden)] - return result - ns=self._ns - #Filter namespace by the name_pattern - all=[(x,ns[x]) for x in glob_filter(ns.keys(),name_pattern, - self.show_all,self.ignore_case)] - #Filter namespace by type_pattern - all=[(key,obj) for key,obj in all if is_type(obj,type_pattern)] - all=dict(all) - return all - - #TODO: Implement dictionary like access to filtered name space? +def filter_ns(ns, name_pattern="*", type_pattern="all", ignore_case=True, + show_all=True): + """Filter a namespace dictionary by name pattern and item type.""" + pattern = name_pattern.replace("*",".*").replace("?",".") + if ignore_case: + reg = re.compile(pattern+"$", re.I) + else: + reg = re.compile(pattern+"$") + + # Check each one matches regex; shouldn't be hidden; of correct type. + return dict((key,obj) for key, obj in ns.iteritems() if reg.match(key) \ + and show_hidden(key, show_all) \ + and is_type(obj, type_pattern) ) -def list_namespace(namespace,type_pattern,filter,ignore_case=False,show_all=False): - """Return dictionary of all objects in namespace that matches type_pattern - and filter.""" +def list_namespace(namespace, type_pattern, filter, ignore_case=False, show_all=False): + """Return dictionary of all objects in a namespace dictionary that match + type_pattern and filter.""" pattern_list=filter.split(".") - if len(pattern_list)==1: - ns=NameSpace(namespace,name_pattern=pattern_list[0],type_pattern=type_pattern, - ignore_case=ignore_case,show_all=show_all) - return ns.ns + if len(pattern_list) == 1: + return filter_ns(namespace, name_pattern=pattern_list[0], + type_pattern=type_pattern, + ignore_case=ignore_case, show_all=show_all) else: # This is where we can change if all objects should be searched or # only modules. Just change the type_pattern to module to search only # modules - ns=NameSpace(namespace,name_pattern=pattern_list[0],type_pattern="all", - ignore_case=ignore_case,show_all=show_all) - res={} - nsdict=ns.ns - for name,obj in nsdict.iteritems(): - ns=list_namespace(obj,type_pattern,".".join(pattern_list[1:]), - ignore_case=ignore_case,show_all=show_all) - for inner_name,inner_obj in ns.iteritems(): - res["%s.%s"%(name,inner_name)]=inner_obj - return res + filtered = filter_ns(namespace, name_pattern=pattern_list[0], + type_pattern="all", + ignore_case=ignore_case, show_all=show_all) + results = {} + for name, obj in filtered.iteritems(): + ns = list_namespace(dict_dir(obj), type_pattern, + ".".join(pattern_list[1:]), + ignore_case=ignore_case, show_all=show_all) + for inner_name, inner_obj in ns.iteritems(): + results["%s.%s"%(name,inner_name)] = inner_obj + return results