Commit df27a475 authored by Ryan Herbert's avatar Ryan Herbert

modules/* add jstree oython dependencies to modules

parent c76904c8
import pickle
class DictionaryObject(object):
"""
A class that has all the functionality of a normal Python dictionary, except
for the fact it is itself immutable. It also has the added feature of
being able to lookup values by using keys as attributes.
The reason for the class being immutable by default is to help make it a
little easier to use in multiprocessing situations. Granted, the underlying
values themselves are not deeply copied, but the aim is to enforce some
ensurances of immutability on the container class.
When using positional arguments, the first argument must always be something
that would be a valid argument for a dict(). However, a second, optional
argument may be passed to create a default value when keys are not found.
Examples:
>>> d = DictionaryObject({'a':1, 'b':True, 3:'x'})
>>> d.a == 1
True
>>> d.b
True
>>> d[3] == 'x'
True
>>> d = DictionaryObject((('a',1),('b',2)))
>>> d.a == 1
True
>>> d.b == 2
True
>>> d = DictionaryObject({'a':1, 'b':True}, None)
>>> d.a == 1
True
>>> d.b
True
>>> d.c
>>> d = DictionaryObject({'a':1}, None)
>>> m = MutableDictionaryObject(d)
>>> d == m
True
>>> m.a = 0
>>> d == m
False
>>> d != m
True
>>> import pickle
>>> m1 = MutableDictionaryObject({'a':1}, None)
>>> m2 = pickle.loads(pickle.dumps(m1))
>>> m1 == m2
True
>>> m1.a = 3
>>> m1 == m2
False
>>> m1.a == 3
True
>>> m1['c'] = 5
>>> m1['c']
5
"""
def __init__(self, contents=(), *args, **kwargs):
"""
Take as input a dictionary-like object and return a DictionaryObject.
It also makes sure any keys containing dictionaries are also converted
to DictionaryObjects.
"""
super(DictionaryObject, self).__init__()
if isinstance(contents, DictionaryObject):
self.__dict__.update(pickle.loads(pickle.dumps(contents.__dict__)))
return
self.__dict__['_items'] = dict(contents, **kwargs)
if len(args) > 1:
raise TypeError("too many arguments")
# If we have more than one argument passed in, use the second argument
# as a default value.
if args:
try:
default = type(self)(args[0])
except:
default = args[0]
self.__dict__['_defaultValue'] = default
else:
self.__dict__['_defaultValue'] = None
self.__dict__['_defaultIsSet'] = len(args) > 0
for k in self._items:
if isinstance(self._items[k], dict):
self._items[k] = type(self)(self._items[k])
def __setstate__(self, dict):
self.__dict__.update(dict)
def __getstate__(self):
return self.__dict__.copy()
def __getattr__(self, name):
"""
This is the method that makes all the magic happen. Search for
'name' in self._items and return the value if found. If a default
value has been set and 'name' is not found in self._items, return it.
Otherwise raise an AttributeError.
Example:
>>> d = DictionaryObject({'keys':[1,2], 'values':3, 'x':1})
>>> sorted(list(d.keys())) == ['keys', 'values', 'x']
True
>>> [1, 2] in list(d.values())
True
>>> 1 in list(d.values())
True
>>> d.x
1
>>> d['keys']
[1, 2]
>>> d['values']
3
"""
if name in self._items:
return self._items[name]
if self._defaultIsSet:
return self._defaultValue
raise AttributeError("'%s' object has no attribute '%s'" % (type(self).__name__, name))
def __setattr__(self, name, value):
"""
This class is immutable-by-default. See MutableDictionaryObject.
"""
raise AttributeError("'%s' object does not support assignment" % type(self).__name__)
def __getitem__(self, name):
return self._items[name]
def __contains__(self, name):
return name in self._items
def __len__(self):
return len(self._items)
def __iter__(self):
return iter(self._items)
def __repr__(self):
if self._defaultIsSet:
params = "%s, %s" % (repr(self._items), self._defaultValue)
else:
params = repr(self._items)
return "%s(%s)" % (type(self).__name__, params)
def __cmp__(self, rhs):
if self < rhs:
return -1
if self > rhs:
return 1
return 0
def __eq__(self, rhs):
if self._items == rhs._items:
return self._defaultValue == rhs._defaultValue
return False
def __ne__(self, rhs):
return not (self == rhs)
def keys(self):
return self._items.keys()
def values(self):
return self._items.values()
def asdict(self):
"""
Copy the data back out of here and into a dict. Then return it.
Some libraries may check specifically for dict objects, such
as the json library; so, this makes it convenient to get the data
back out.
>>> import dictobj
>>> d = {'a':1, 'b':2}
>>> dictobj.DictionaryObject(d).asdict() == d
True
>>> d['c'] = {1:2, 3:4}
>>> dictobj.DictionaryObject(d).asdict() == d
True
"""
items = {}
for name in self._items:
value = self._items[name]
if isinstance(value, DictionaryObject):
items[name] = value.asdict()
else:
items[name] = value
return items
class MutableDictionaryObject(DictionaryObject):
"""
Slight enhancement of the DictionaryObject allowing one to add
attributes easily, in cases where that functionality is wanted.
Examples:
>>> d = MutableDictionaryObject({'a':1, 'b':True}, None)
>>> d.a == 1
True
>>> d.b == True
True
>>> d.c is None
True
>>> d.d is None
True
>>> d.c = 3
>>> del d.a
>>> d.a is None
True
>>> d.c == 3
True
"""
def __setattr__(self, name, value):
self._items[name] = value
def __delattr__(self, name):
del self._items[name]
__setitem__ = __setattr__
__delitem__ = __delattr__
import dictobj
import os
from collections import namedtuple
Path = namedtuple('Path', ('path', 'id'))
class Node(dictobj.DictionaryObject):
"""
This class exists as a helper to the jsTree. Its "jsonData" method can
generate sub-tree JSON without putting the logic directly into the jsTree.
This data structure is only semi-immutable. The jsTree uses a directly
iterative (i.e. no stack is managed) builder pattern to construct a
tree out of paths. Therefore, the children are not known in advance, and
we have to keep the children attribute mutable.
"""
def __init__(self, path, oid, **kwargs):
"""
kwargs allows users to pass arbitrary information into a Node that
will later be output in jsonData(). It allows for more advanced
configuration than the default path handling that jsTree currently allows.
For example, users may want to pass "attr" or some other valid jsTree options.
Example:
>>> from dictobj import *
>>> node = Node('a', None)
>>> node._items == {'text': 'a', 'children': MutableDictionaryObject({})}
True
>>> node.jsonData() == {'text': 'a'}
True
>>> import jstree
>>> from dictobj import *
>>> node = jstree.Node('a', 1)
>>> node._items == {'text': 'a', 'children': MutableDictionaryObject({}), 'li_attr': DictionaryObject({'id': 1})}
True
>>> d = node.jsonData()
>>> d == {'text': 'a', 'li_attr': {'id': 1}}
True
>>> import jstree
>>> node = jstree.Node('a', 5, icon="folder", state = {'opened': True})
>>> node._items == {'text': 'a', 'state': DictionaryObject({'opened': True}), 'children': MutableDictionaryObject({}), 'li_attr': DictionaryObject({'id': 5}), 'icon': 'folder'}
True
>>> node.jsonData() == {'text': 'a', 'state': {'opened': True}, 'li_attr': {'id': 5}, 'icon': 'folder'}
True
"""
super(Node, self).__init__()
children = kwargs.get('children', {})
if any(filter(lambda key: not isinstance(children[key], Node), children)):
raise TypeError(
"One or more children were not instances of '%s'" % Node.__name__)
if 'children' in kwargs:
del kwargs['children']
self._items['children'] = dictobj.MutableDictionaryObject(children)
if oid is not None:
li_attr = kwargs.get('li_attr', {})
li_attr['id'] = oid
kwargs['li_attr'] = li_attr
self._items.update(dictobj.DictionaryObject(**kwargs))
self._items['text'] = path
def jsonData(self):
children = [self.children[k].jsonData() for k in sorted(self.children)]
output = {}
for k in self._items:
if 'children' == k:
continue
if isinstance(self._items[k], dictobj.DictionaryObject):
output[k] = self._items[k].asdict()
else:
output[k] = self._items[k]
if len(children):
output['children'] = children
return output
class JSTree(dictobj.DictionaryObject):
"""
An immutable dictionary-like object that converts a list of "paths"
into a tree structure suitable for jQuery's jsTree.
"""
def __init__(self, paths, **kwargs):
"""
Take a list of paths and put them into a tree. Paths with the same prefix should
be at the same level in the tree.
kwargs may be standard jsTree options used at all levels in the tree. These will be outputted
in the JSON.
Example (basic usage):
>>> import jstree
>>> paths = [jstree.Path("editor/2012-07/31/.classpath", 1), jstree.Path("editor/2012-07/31/.project", 2)]
>>> t1 = jstree.JSTree(paths)
>>> t1.jsonData() == [{'text': 'editor', 'children': [{'text': '2012-07', 'children': [{'text': '31', 'children': [{'text': '.classpath', 'li_attr': {'id': 1}}, {'text': '.project', 'li_attr': {'id': 2}}]}]}]}]
True
"""
if any(filter(lambda p: not isinstance(p, Path), paths)):
raise TypeError(
"All paths must be instances of '%s'" % Path.__name__)
super(JSTree, self).__init__()
root = Node('', None, **kwargs)
for path in sorted(set(paths)):
curr = root
subpaths = path.path.split(os.path.sep)
for i, subpath in enumerate(subpaths):
if subpath not in curr.children:
oid = path.id if len(subpaths) - 1 == i else None
curr.children[subpath] = Node(subpath, oid, **kwargs)
curr = curr.children[subpath]
self._items['_root'] = root
def pretty(self, root=None, depth=0, spacing=2):
"""
Create a "pretty print" represenation of the tree with customized indentation at each
level of the tree.
Example:
>>> import jstree
>>> paths = [jstree.Path("editor/2012-07/31/.classpath", 1), jstree.Path("editor/2012-07/31/.project", 2)]
>>> print(jstree.JSTree(paths).pretty())
/
editor/
2012-07/
31/
.classpath
.project
"""
if root is None:
root = self._root
fmt = "%s%s/" if root.children else "%s%s"
s = fmt % (" " * depth * spacing, root.text)
for child in sorted(root.children):
child = root.children[child]
s += "\n%s" % self.pretty(child, depth + 1, spacing)
return s
def jsonData(self):
"""
Returns a copy of the internal tree in a JSON-friendly format,
ready for consumption by jsTree. The data is represented as a
list of dictionaries, each of which are our internal nodes.
Examples:
>>> import jstree
>>> paths = [jstree.Path("editor/2012-07/31/.classpath", 1), jstree.Path("editor/2012-07/31/.project", 2)]
>>> t = jstree.JSTree(paths)
>>> d = t.jsonData()
>>> d == [{'text': 'editor', 'children': [{'text': '2012-07', 'children': [{'text': '31', 'children': [{'text': '.classpath', 'li_attr': {'id': 1}}, {'text': '.project', 'li_attr': {'id': 2}}]}]}]}]
True
>>> d[0]['children'][0]['children'][0]['children'][1] == {'text': '.project', 'li_attr': {'id': 2}}
True
>>> d[0]['children'][0]['children'][0]['children'][1]['text'] == '.project'
True
>>> d[0]['children'][0]['children'][0]['children'][1]['li_attr']['id']
2
"""
return [self._root.children[k].jsonData() for k in sorted(self._root.children)]
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment