##// END OF EJS Templates
Fixing subtle bug in the traitlets with This....
Fixing subtle bug in the traitlets with This. Previously subclasses This traitlets wouldn't accept superclass instances as values. I have added a this_class attribute to TraitletType that is set by the metaclass and is used later by This.validate to properly handle this case. I have also added new tests for this.

File last commit:

r2078:c48a2472
r2183:0c46f18e
Show More
ipstruct.py
400 lines | 11.9 KiB | text/x-python | PythonLexer
Brian Granger
Full refactor of ipstruct.Struct....
r2078 #!/usr/bin/env python
# encoding: utf-8
"""A dict subclass that supports attribute style access.
Authors:
* Fernando Perez (original)
* Brian Granger (refactoring to a dict subclass)
Fernando Perez
Remove svn-style $Id marks from docstrings and Release imports....
r1853 """
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
Full refactor of ipstruct.Struct....
r2078 #-----------------------------------------------------------------------------
# Copyright (C) 2008-2009 The IPython Development Team
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 #
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
Brian Granger
Full refactor of ipstruct.Struct....
r2078 #-----------------------------------------------------------------------------
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
Full refactor of ipstruct.Struct....
r2078 #-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
vivainio
Struct no pprints the dict inside it
r464 import pprint
fperez
Cosmetic cleanups: put all imports in a single line, and sort them...
r52
Brian Granger
genutils.py => utils/genutils.py and updated imports and tests.
r2023 from IPython.utils.genutils import list2dict2
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
Full refactor of ipstruct.Struct....
r2078 __all__ = ['Struct']
#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------
class Struct(dict):
"""A dict subclass with attribute style access.
This dict subclass has a a few extra features:
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
Full refactor of ipstruct.Struct....
r2078 * Attribute style access.
* Protection of class members (like keys, items) when using attribute
style access.
* The ability to restrict assignment to only existing keys.
* Intelligent merging.
* Overloaded operators.
"""
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
Full refactor of ipstruct.Struct....
r2078 def __init__(self, *args, **kw):
Brian Granger
First draft of refactored ipstruct.py.
r2077 """Initialize with a dictionary, another Struct, or data.
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
First draft of refactored ipstruct.py.
r2077 Parameters
----------
Brian Granger
Full refactor of ipstruct.Struct....
r2078 args : dict, Struct
Initialize with one dict or Struct
Brian Granger
First draft of refactored ipstruct.py.
r2077 kw : dict
Initialize with key, value pairs.
Examples
--------
Brian Granger
Full refactor of ipstruct.Struct....
r2078
>>> s = Struct(a=10,b=30)
>>> s.a
10
>>> s.b
30
>>> s2 = Struct(s,c=30)
>>> s2.keys()
['a', 'c', 'b']
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 """
Brian Granger
First draft of refactored ipstruct.py.
r2077 object.__setattr__(self, '_allownew', True)
Brian Granger
Full refactor of ipstruct.Struct....
r2078 dict.__init__(self, *args, **kw)
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
First draft of refactored ipstruct.py.
r2077 def __setitem__(self, key, value):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """Set an item with check for allownew.
Examples
--------
>>> s = Struct()
>>> s['a'] = 10
>>> s.allow_new_attr(False)
>>> s['a'] = 10
>>> s['a']
10
>>> try:
... s['b'] = 20
... except KeyError:
... print 'this is not allowed'
...
this is not allowed
"""
if not self._allownew and not self.has_key(key):
raise KeyError(
"can't create new attribute %s when allow_new_attr(False)" % key)
dict.__setitem__(self, key, value)
def __setattr__(self, key, value):
"""Set an attr with protection of class members.
This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
:exc:`AttributeError`.
Examples
--------
>>> s = Struct()
>>> s.a = 10
>>> s.a
10
>>> try:
... s.get = 10
... except AttributeError:
... print "you can't set a class member"
...
you can't set a class member
"""
# If key is an str it might be a class member or instance var
Brian Granger
First draft of refactored ipstruct.py.
r2077 if isinstance(key, str):
# I can't simply call hasattr here because it calls getattr, which
# calls self.__getattr__, which returns True for keys in
# self._data. But I only want keys in the class and in
# self.__dict__
if key in self.__dict__ or hasattr(Struct, key):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 raise AttributeError(
'attr %s is a protected member of class Struct.' % key
Brian Granger
First draft of refactored ipstruct.py.
r2077 )
Brian Granger
Full refactor of ipstruct.Struct....
r2078 try:
self.__setitem__(key, value)
except KeyError, e:
raise AttributeError(e)
Brian Granger
First draft of refactored ipstruct.py.
r2077
def __getattr__(self, key):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """Get an attr by calling :meth:`dict.__getitem__`.
Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
:exc:`AttributeError`.
Examples
--------
>>> s = Struct(a=10)
>>> s.a
10
>>> type(s.get)
<type 'builtin_function_or_method'>
>>> try:
... s.b
... except AttributeError:
... print "I don't have that key"
...
I don't have that key
"""
Brian Granger
First draft of refactored ipstruct.py.
r2077 try:
Brian Granger
Full refactor of ipstruct.Struct....
r2078 result = self[key]
Brian Granger
First draft of refactored ipstruct.py.
r2077 except KeyError:
raise AttributeError(key)
else:
return result
def __iadd__(self, other):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """s += s2 is a shorthand for s.merge(s2).
Examples
--------
>>> s = Struct(a=10,b=30)
>>> s2 = Struct(a=20,c=40)
>>> s += s2
>>> s
{'a': 10, 'c': 40, 'b': 30}
"""
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 self.merge(other)
return self
def __add__(self,other):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """s + s2 -> New Struct made from s.merge(s2).
Examples
--------
>>> s1 = Struct(a=10,b=30)
>>> s2 = Struct(a=20,c=40)
>>> s = s1 + s2
>>> s
{'a': 10, 'c': 40, 'b': 30}
"""
sout = self.copy()
sout.merge(other)
return sout
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
def __sub__(self,other):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """s1 - s2 -> remove keys in s2 from s1.
Examples
--------
>>> s1 = Struct(a=10,b=30)
>>> s2 = Struct(a=40)
>>> s = s1 - s2
>>> s
{'b': 30}
"""
sout = self.copy()
sout -= other
return sout
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
def __isub__(self,other):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """Inplace remove keys from self that are in other.
Examples
--------
>>> s1 = Struct(a=10,b=30)
>>> s2 = Struct(a=40)
>>> s1 -= s2
>>> s1
{'b': 30}
"""
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 for k in other.keys():
if self.has_key(k):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 del self[k]
return self
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0
Brian Granger
First draft of refactored ipstruct.py.
r2077 def __dict_invert(self, data):
"""Helper function for merge.
Brian Granger
Full refactor of ipstruct.Struct....
r2078
Brian Granger
First draft of refactored ipstruct.py.
r2077 Takes a dictionary whose values are lists and returns a dict with
the elements of each list as keys and the original keys as values.
"""
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 outdict = {}
Brian Granger
First draft of refactored ipstruct.py.
r2077 for k,lst in data.items():
if isinstance(lst, str):
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 lst = lst.split()
for entry in lst:
outdict[entry] = k
return outdict
Brian Granger
Full refactor of ipstruct.Struct....
r2078
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 def dict(self):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 return self
def copy(self):
"""Return a copy as a Struct.
Brian Granger
First draft of refactored ipstruct.py.
r2077
Brian Granger
Full refactor of ipstruct.Struct....
r2078 Examples
--------
Brian Granger
First draft of refactored ipstruct.py.
r2077
Brian Granger
Full refactor of ipstruct.Struct....
r2078 >>> s = Struct(a=10,b=30)
>>> s2 = s.copy()
>>> s2
{'a': 10, 'b': 30}
>>> type(s2).__name__
'Struct'
"""
return Struct(dict.copy(self))
def hasattr(self, key):
"""hasattr function available as a method.
Implemented like has_key.
Examples
--------
>>> s = Struct(a=10)
>>> s.hasattr('a')
True
>>> s.hasattr('b')
False
>>> s.hasattr('get')
False
Brian Granger
First draft of refactored ipstruct.py.
r2077 """
Brian Granger
Full refactor of ipstruct.Struct....
r2078 return self.has_key(key)
def allow_new_attr(self, allow = True):
"""Set whether new attributes can be created in this Struct.
This can be used to catch typos by verifying that the attribute user
tries to change already exists in this Struct.
"""
object.__setattr__(self, '_allownew', allow)
Brian Granger
First draft of refactored ipstruct.py.
r2077 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
Brian Granger
Full refactor of ipstruct.Struct....
r2078 """Merge two Structs with customizable conflict resolution.
This is similar to :meth:`update`, but much more flexible. First, a
dict is made from data+key=value pairs. When merging this dict with
the Struct S, the optional dictionary 'conflict' is used to decide
what to do.
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 If conflict is not given, the default behavior is to preserve any keys
Brian Granger
Full refactor of ipstruct.Struct....
r2078 with their current value (the opposite of the :meth:`update` method's
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 behavior).
Brian Granger
Full refactor of ipstruct.Struct....
r2078
Parameters
----------
__loc_data : dict, Struct
The data to merge into self
__conflict_solve : dict
The conflict policy dict. The keys are binary functions used to
resolve the conflict and the values are lists of strings naming
the keys the conflict resolution function applies to. Instead of
a list of strings a space separated string can be used, like
'a b c'.
kw : dict
Additional key, value pairs to merge in
Notes
-----
The `__conflict_solve` dict is a dictionary of binary functions which will be used to
solve key conflicts. Here is an example::
__conflict_solve = dict(
func1=['a','b','c'],
func2=['d','e']
)
In this case, the function :func:`func1` will be used to resolve
keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
keys 'd' and 'e'. This could also be written as::
__conflict_solve = dict(func1='a b c',func2='d e')
These functions will be called for each key they apply to with the
form::
func1(self['a'], other['a'])
The return value is used as the final merged value.
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 As a convenience, merge() provides five (the most commonly needed)
pre-defined policies: preserve, update, add, add_flip and add_s. The
Brian Granger
Full refactor of ipstruct.Struct....
r2078 easiest explanation is their implementation::
preserve = lambda old,new: old
update = lambda old,new: new
add = lambda old,new: old + new
add_flip = lambda old,new: new + old # note change of order!
add_s = lambda old,new: old + ' ' + new # only for str!
You can use those four words (as strings) as keys instead
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 of defining them as functions, and the merge method will substitute
Brian Granger
Full refactor of ipstruct.Struct....
r2078 the appropriate functions for you.
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 For more complicated conflict resolution policies, you still need to
Brian Granger
Full refactor of ipstruct.Struct....
r2078 construct your own functions.
Examples
--------
This show the default policy:
>>> s = Struct(a=10,b=30)
>>> s2 = Struct(a=20,c=40)
>>> s.merge(s2)
>>> s
{'a': 10, 'c': 40, 'b': 30}
Now, show how to specify a conflict dict:
>>> s = Struct(a=10,b=30)
>>> s2 = Struct(a=20,b=40)
>>> conflict = {'update':'a','add':'b'}
>>> s.merge(s2,conflict)
>>> s
{'a': 20, 'b': 70}
"""
data_dict = dict(__loc_data__,**kw)
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 # policies for conflict resolution: two argument functions which return
# the value that will go in the new struct
preserve = lambda old,new: old
update = lambda old,new: new
add = lambda old,new: old + new
add_flip = lambda old,new: new + old # note change of order!
add_s = lambda old,new: old + ' ' + new
Brian Granger
Full refactor of ipstruct.Struct....
r2078
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 # default policy is to keep current keys when there's a conflict
Brian Granger
First draft of refactored ipstruct.py.
r2077 conflict_solve = list2dict2(self.keys(), default = preserve)
Brian Granger
Full refactor of ipstruct.Struct....
r2078
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 # the conflict_solve dictionary is given by the user 'inverted': we
# need a name-function mapping, it comes as a function -> names
# dict. Make a local copy (b/c we'll make changes), replace user
# strings for the three builtin policies and invert it.
if __conflict_solve:
inv_conflict_solve_user = __conflict_solve.copy()
for name, func in [('preserve',preserve), ('update',update),
Fernando Perez
Updated ipstruct to fix doctest failures.
r1280 ('add',add), ('add_flip',add_flip),
('add_s',add_s)]:
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 if name in inv_conflict_solve_user.keys():
inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
del inv_conflict_solve_user[name]
Brian Granger
First draft of refactored ipstruct.py.
r2077 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg
#print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve)
fperez
Unicode fixes (utf-8 used by default if ascii is not enough). This should fix some reported crashes....
r5 for key in data_dict:
fperez
Reorganized the directory for ipython/ to have its own dir, which is a bit...
r0 if key not in self:
self[key] = data_dict[key]
else:
self[key] = conflict_solve[key](self[key],data_dict[key])