Skip to content

Commit 05a922e

Browse files
committed
Caught in between the difficulties of both cleanly and simply supporting a common interface between a simple decentant of object and a more useful custom class (simple bunch vs. PysonBunch). Abandoning this effort for now, until a situation arises where it is actually definitley useful to have a "SimpleBunch".
1 parent 82ef328 commit 05a922e

File tree

3 files changed

+112
-70
lines changed

3 files changed

+112
-70
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ The optional `delname` argument can be used to remove unwanted name polution (su
7979

8080
`PysonBunch` objects behave very much like dicts and support most of their built-in operations (such as indexing and `in` tests), but the names stored in them are, of course, also accessible via `getattr()` and the `.` operator.
8181

82-
`PysonBunch` objects support conversion back to valid PySON strings, via `__repr__`. Calling `repr` on a `PysonBunch` will only produce a representation of the values contained **within** the bunch, since the bunch does not know its own name. To get a full representation of the named bunch, you can use the helper method `namedPysonBunchRepr(name, bunch)`.
82+
`PysonBunch` objects support conversion back to valid PySON strings, via `__repr__`. Calling `repr` on a `PysonBunch` will only produce a representation of the values contained **within** the bunch, since the bunch does not know its own name. To get a full representation of the named bunch, you can use the helper method `namedBunchRepr(name, bunch)`.
8383

8484
#### Limitations
8585

__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@
1919

2020
# The present purpose of this file is simply to allow using PySON as a file or a package interchangeably. TODO: Only export things that should be part of the interface.
2121

22-
from .pyson import namedPysonBunchRepr, PysonBunch, parseDir, parseFile, parse
22+
from .pyson import namedBunchRepr, PysonBunch, parseDir, parseFile, parse
2323
from .importer import registerPysonImportRoot, convertToPysonRootPackage

pyson.py

+110-68
Original file line numberDiff line numberDiff line change
@@ -17,91 +17,128 @@
1717
# along with PySON. If not, see <http://www.gnu.org/licenses/>.
1818
#
1919

20-
import ast, copy, itertools
20+
import ast, copy, itertools, inspect
2121

2222
# python 2/3 compatibility helper
2323
def iteritems(dictionary): return getattr(dictionary, "iteritems", dictionary.items)()
2424

2525
defaultBunchIndent = " " * 4
2626

27-
commentChar = "#"
28-
assignmentChar = "="
29-
blockChar = ":"
30-
pysonExtension = ".pyson"
31-
32-
def namedPysonBunchRepr(name, bunch):
33-
return name + blockChar + ("" if len(bunch) == 0 else "\n" + defaultBunchIndent + repr(bunch).replace("\n", "\n" + defaultBunchIndent))
34-
35-
class PysonBunch(object):
36-
def __repr__(self, *args, **kwargs):
37-
"""Returns a valid PySON representation of this PysonBunch object.
38-
39-
Since an object usually doesn't know the name it is stored under, a bunch settles for just returning a valid PySON representation of the set of its constituents. For creating a repr of a named PySON bunch use pyson.namedPysonBunchRepr(name, bunch)."""
40-
if len(self) == 0: return ""
41-
lines = []
42-
maxKeyLen = max([0] + [len(key) for key in self if not isinstance(self[key], self.__class__)])
43-
for key, val in iteritems(self.__dict__):
44-
if not isinstance(val, self.__class__):
45-
padding = " " * (maxKeyLen + 1 - len(key))
46-
lines.append(key + padding + assignmentChar + " " + repr(val))
47-
else:
48-
lines.append(namedPysonBunchRepr(key, val))
49-
return "\n".join(sorted(lines))
50-
51-
def __mergedDeepCopy__(self, *args):
52-
"""return a deep copy of this bunch, which has been updated with deep copies of any further given bunches"""
53-
res = PysonBunch()
54-
allBunches = (self,) + args
55-
allKeys = set(itertools.chain(*(bunch.__dict__.keys() for bunch in allBunches)))
56-
for key in allKeys:
57-
allDefinitions = tuple(bunch[key] for bunch in allBunches if key in bunch)
58-
if isinstance(allDefinitions[-1], self.__class__): # if the final (dominating) definition is a bunch, we want to merge it with any relevant preceding bunch definitions (i.e. ones which would not have been overriden by a non-bunch object)
59-
allMergeableDefinitions = []
60-
for definition in reversed(allDefinitions):
61-
if isinstance(definition, self.__class__): allMergeableDefinitions.insert(0, definition)
62-
else : break
63-
res.__dict__[key] = allMergeableDefinitions[0].__mergedDeepCopy__(*allMergeableDefinitions[1:])
64-
else:
65-
res.__dict__[key] = copy.deepcopy(allDefinitions[-1])
66-
return res
67-
68-
#a bunch may as well behave like the underlying dict, in most situations
69-
def __getitem__(self, key ): return self.__dict__[key]
70-
def __setitem__(self, key, val): self.__dict__[key] = val
71-
def __delitem__(self, key ): del self.__dict__[key]
72-
def __len__ (self): return len (self.__dict__)
73-
def __iter__ (self): return iter (self.__dict__)
74-
def __reversed__(self): return reversed(self.__dict__)
75-
def __contains__(self, item): return item in self.__dict__
76-
def __cmp__ (self, other): return cmp(self.__dict__, other)
77-
def __eq__ (self, other): return self.__dict__ == other
78-
def __ge__ (self, other): return self.__dict__ >= other
79-
def __gt__ (self, other): return self.__dict__ > other
80-
def __le__ (self, other): return self.__dict__ <= other
81-
def __lt__ (self, other): return self.__dict__ < other
82-
def __ne__ (self, other): return self.__dict__ != other
83-
84-
85-
def parseDir(dirp):
27+
commentChar = "#"
28+
assignmentChar = "="
29+
blockChar = ":"
30+
pysonExtension = ".pyson"
31+
32+
def bunchRepr(bunch, *args, **kwargs):
33+
"""Returns a valid PySON representation of the given bunch object.
34+
35+
Since an object usually doesn't know the name it is stored under, a bunch settles for just returning a valid PySON representation of the set of its constituents. For creating a repr of any kind of named PySON bunch use namedBunchRepr(name, bunch)."""
36+
print "boing"
37+
if len(bunch...) == 0: return ""
38+
print "boing"
39+
lines = []
40+
maxKeyLen = max([0] + [len(key) for key in bunch if not isinstance(bunch[key], BaseBunch)])
41+
for key, val in iteritems(bunch...):
42+
if not isinstance(val, BaseBunch):
43+
padding = " " * (maxKeyLen + 1 - len(key))
44+
lines.append(key + padding + assignmentChar + " " + repr(val, *args, **kwargs))
45+
else:
46+
lines.append(namedBunchRepr(key, val, *args, **kwargs))
47+
return "\n".join(sorted(lines))
48+
49+
def namedBunchRepr(name, bunch, *args, **kwargs):
50+
return name + blockChar + ("" if len(bunch) == 0 else "\n" + defaultBunchIndent + repr(bunch, *args, **kwargs).replace("\n", "\n" + defaultBunchIndent))
51+
52+
def bunchRepr
53+
54+
def bunchMergedDeepCopy(*allBunches):
55+
"""return a deep copy of the first given bunch, which has been updated (overwriting any existing contents) with deep copies of any further given bunches. Returned bunch type is determined by the first argument."""
56+
res = type(allBunches[0])()
57+
allKeys = set(itertools.chain(*(getattr(bunch, "keys", bunch.__dict__.keys)() for bunch in allBunches)))
58+
for key in allKeys:
59+
allDefinitions = tuple(getattr(bunch, key) for bunch in allBunches if hasattr(bunch, key))
60+
if isinstance(allDefinitions[-1], BaseBunch): # if the final (dominating) definition is a bunch, we want to merge it with any relevant preceding bunch definitions (i.e. ones which would not have been overriden by a non-bunch object)
61+
allMergeableDefinitions = []
62+
for definition in reversed(allDefinitions):
63+
if isinstance(definition, BaseBunch): allMergeableDefinitions.insert(0, definition)
64+
else : break
65+
setattr(res, key, allMergeableDefinitions[0].__mergedDeepCopy__(*allMergeableDefinitions[1:]))
66+
else:
67+
setattr(res, key, copy.deepcopy(allDefinitions[-1]))
68+
return res
69+
70+
def bunchIterItems(self): return iteritems(self)
71+
72+
class BaseBunch(object): pass #FIXME: Base type is misleading. They won't support the same interface (at least for now: no way to "list" the keys in a SimplePysonBunch).
73+
class SimplePysonBunch(BaseBunch):
74+
"""Use this if you *really* want to avoid name collisions between entries in the raw PySON code and names belonging to the bunch object.
75+
76+
WARNINGS:
77+
* still vulnerable to collisions with anything that is part of a basic python object
78+
* does not support any convenience, such as using repr(SimplePysonBunch) to get a PySON string representation of the object
79+
80+
Where possible, it is recommended to use PysonBunch and simply avoid the use of the following names in PySON definitions:
81+
* anything that is a part of basic python objects
82+
* anything that is a part of python dict objects
83+
* anything that is listed in PysonBunchConvenienceMethods above"""
84+
85+
#def dictMerged(future_class_name, future_class_parents, future_class_attr):
86+
# mergedAttrs = future_class_attr.copy()
87+
# class dum(object): pass
88+
# sample = dum().__dict__
89+
# for name in dir(sample):
90+
# if name not in mergedAttrs:
91+
# if inspect.isbuiltin(getattr(sample, name)):
92+
# def dictRedirect(*args, **kwargs): getattr(args[0].__dict__, name)(*args[1:], **kwargs)
93+
# mergedAttrs[name] = dictRedirect
94+
# return type(future_class_name, future_class_parents, mergedAttrs)
95+
def attributeAugmentedClass(additionalAttributes):
96+
def attributeAugmentedClass_inner(futureClsName, futureClsParents, futureClsAttrs):
97+
mergedAttrs = futureClsAttrs.copy()
98+
mergedAttrs.update(additionalAttributes)
99+
return type(futureClsName, futureClsParents, mergedAttrs)
100+
return attributeAugmentedClass_inner
101+
102+
class PysonBunch(BaseBunch, dict):#SimplePysonBunch, dict):
103+
__metaclass__ = attributeAugmentedClass(PysonBunchConvenienceMethods)
104+
# def __repr__(self): pass
105+
# def __getattribute__(self, name):
106+
# print name
107+
# supergetattr = super(PysonBunch, self).__getattribute__ #probably going to effectively map to object.__getattribute__, but may not
108+
# selfdict = supergetattr("__dict__")
109+
# if name == "__getattribute__": return supergetattr(name) #faithfully return ourselves, if asked
110+
# elif name in PysonBunchConvenienceMethods: #bunch convenience methods take priority over any other features
111+
# unboundMethod = PysonBunchConvenienceMethods[name]
112+
# def fakeBound(*args, **kwargs): return unboundMethod(self, *args, **kwargs)
113+
# return fakeBound
114+
# else:
115+
# try: return supergetattr(name) #in any other case, fall back to default getattr behaviour
116+
# except AttributeError as e:
117+
## if hasattr(selfdict, name): return getattr(selfdict, name) #if we are trying to use a feature supported by dictionaries, we support this by calling the method on the underlying __dict__, which PySON exclusively uses to store any bunch's members
118+
# raise e
119+
def __setattr__(self, name, value): self[name] = value
120+
def __getattr__(self, name ): return self[name]
121+
122+
def parseDir(dirp, useBunchType = None):
86123
import os
87124
pysonExt = pysonExtension.lower()
88-
bunch = PysonBunch()
125+
bunch = useBunchType() if useBunchType is not None else DefaultBunchType()
89126
for name in os.listdir(dirp):
90127
path = os.path.join(dirp, name)
91128
if os.path.isdir (path ): bunch.__dict__[name ] = parseDir (path)
92129
elif name.lower().endswith(pysonExt): bunch.__dict__[name[:-len(pysonExt)]] = parseFile(path)
93130
return bunch
94131

95-
def parseFile(fname):
96-
with open(fname, "r") as f: return parse(f.read())
132+
def parseFile(fname, useBunchType = None):
133+
with open(fname, "r") as f: return parse(f.read(), useBunchType)
97134

98-
def parse(string):
135+
def parse(string, useBunchType = None):
99136
"""Parses the given PySON string and returns its contents as a single PysonBunch representing the whole string."""
100-
result = PysonBunch()
137+
result = useBunchType() if useBunchType is not None else DefaultBunchType()
101138
lines = string.split("\n")
102139
curIndex = 0
103140
while curIndex < len(lines):
104-
item, value, curIndex = _parseItem(lines, "", curIndex)
141+
item, value, curIndex = _parseItem(lines, "", curIndex, type(result))
105142
if item is not None: result[item] = value
106143
return result
107144

@@ -114,11 +151,11 @@ def _multiTokenPartition(s, tokChars):
114151
return (s[:minInd], s[minInd:minInd + 1], s[minInd + 1:])
115152

116153

117-
def _parseItem(lines, curIndent, curIndex):
154+
def _parseItem(lines, curIndent, curIndex, bunchType):
118155
# white-space-only lines and comment only lines are ignored
119156
def isIgnoredLine(curLine): stripped = curLine.strip(); return stripped == "" or stripped.startswith("#")
120157
def _parseBlock(lines, parentIndent, curIndex):
121-
obj = PysonBunch()
158+
obj = bunchType()
122159
while curIndex < len(lines) and isIgnoredLine(lines[curIndex]): curIndex += 1
123160
if curIndex != len(lines):
124161
curIndent = _leadingWhitespace(lines[curIndex]) #found what must be the first line of the new block; thus, determine the new indent
@@ -142,3 +179,8 @@ def _parseBlock(lines, parentIndent, curIndex):
142179
else : raise SyntaxError("Line no. %d does not contain a valid assignment or block:\n%s" % (curIndex + 1, lines[curIndex]))
143180

144181
return item.strip(), value, curIndex
182+
183+
DefaultBunchType = PysonBunch
184+
PysonBunchConvenienceMethods = {"__repr__" : bunchRepr,
185+
"mergedDeepCopy" : bunchMergedDeepCopy,
186+
}

0 commit comments

Comments
 (0)