/usr/lib/python3/dist-packages/pystache/context.py is in python3-pystache 0.5.3-2ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 | # coding: utf-8
"""
Exposes a ContextStack class.
The Mustache spec makes a special distinction between two types of context
stack elements: hashes and objects. For the purposes of interpreting the
spec, we define these categories mutually exclusively as follows:
(1) Hash: an item whose type is a subclass of dict.
(2) Object: an item that is neither a hash nor an instance of a
built-in type.
"""
from pystache.common import PystacheError
import collections
# This equals '__builtin__' in Python 2 and 'builtins' in Python 3.
_BUILTIN_MODULE = type(0).__module__
# We use this private global variable as a return value to represent a key
# not being found on lookup. This lets us distinguish between the case
# of a key's value being None with the case of a key not being found --
# without having to rely on exceptions (e.g. KeyError) for flow control.
#
# TODO: eliminate the need for a private global variable, e.g. by using the
# preferred Python approach of "easier to ask for forgiveness than permission":
# http://docs.python.org/glossary.html#term-eafp
class NotFound(object):
pass
_NOT_FOUND = NotFound()
def _get_value(context, key):
"""
Retrieve a key's value from a context item.
Returns _NOT_FOUND if the key does not exist.
The ContextStack.get() docstring documents this function's intended behavior.
"""
if isinstance(context, dict):
# Then we consider the argument a "hash" for the purposes of the spec.
#
# We do a membership test to avoid using exceptions for flow control
# (e.g. catching KeyError).
if key in context:
return context[key]
elif type(context).__module__ != _BUILTIN_MODULE:
# Then we consider the argument an "object" for the purposes of
# the spec.
#
# The elif test above lets us avoid treating instances of built-in
# types like integers and strings as objects (cf. issue #81).
# Instances of user-defined classes on the other hand, for example,
# are considered objects by the test above.
try:
attr = getattr(context, key)
except AttributeError:
# TODO: distinguish the case of the attribute not existing from
# an AttributeError being raised by the call to the attribute.
# See the following issue for implementation ideas:
# http://bugs.python.org/issue7559
pass
else:
# TODO: consider using EAFP here instead.
# http://docs.python.org/glossary.html#term-eafp
if isinstance(attr, collections.Callable):
return attr()
return attr
return _NOT_FOUND
class KeyNotFoundError(PystacheError):
"""
An exception raised when a key is not found in a context stack.
"""
def __init__(self, key, details):
self.key = key
self.details = details
def __str__(self):
return "Key %s not found: %s" % (repr(self.key), self.details)
class ContextStack(object):
"""
Provides dictionary-like access to a stack of zero or more items.
Instances of this class are meant to act as the rendering context
when rendering Mustache templates in accordance with mustache(5)
and the Mustache spec.
Instances encapsulate a private stack of hashes, objects, and built-in
type instances. Querying the stack for the value of a key queries
the items in the stack in order from last-added objects to first
(last in, first out).
Caution: this class does not currently support recursive nesting in
that items in the stack cannot themselves be ContextStack instances.
See the docstrings of the methods of this class for more details.
"""
# We reserve keyword arguments for future options (e.g. a "strict=True"
# option for enabling a strict mode).
def __init__(self, *items):
"""
Construct an instance, and initialize the private stack.
The *items arguments are the items with which to populate the
initial stack. Items in the argument list are added to the
stack in order so that, in particular, items at the end of
the argument list are queried first when querying the stack.
Caution: items should not themselves be ContextStack instances, as
recursive nesting does not behave as one might expect.
"""
self._stack = list(items)
def __repr__(self):
"""
Return a string representation of the instance.
For example--
>>> context = ContextStack({'alpha': 'abc'}, {'numeric': 123})
>>> repr(context)
"ContextStack({'alpha': 'abc'}, {'numeric': 123})"
"""
return "%s%s" % (self.__class__.__name__, tuple(self._stack))
@staticmethod
def create(*context, **kwargs):
"""
Build a ContextStack instance from a sequence of context-like items.
This factory-style method is more general than the ContextStack class's
constructor in that, unlike the constructor, the argument list
can itself contain ContextStack instances.
Here is an example illustrating various aspects of this method:
>>> obj1 = {'animal': 'cat', 'vegetable': 'carrot', 'mineral': 'copper'}
>>> obj2 = ContextStack({'vegetable': 'spinach', 'mineral': 'silver'})
>>>
>>> context = ContextStack.create(obj1, None, obj2, mineral='gold')
>>>
>>> context.get('animal')
'cat'
>>> context.get('vegetable')
'spinach'
>>> context.get('mineral')
'gold'
Arguments:
*context: zero or more dictionaries, ContextStack instances, or objects
with which to populate the initial context stack. None
arguments will be skipped. Items in the *context list are
added to the stack in order so that later items in the argument
list take precedence over earlier items. This behavior is the
same as the constructor's.
**kwargs: additional key-value data to add to the context stack.
As these arguments appear after all items in the *context list,
in the case of key conflicts these values take precedence over
all items in the *context list. This behavior is the same as
the constructor's.
"""
items = context
context = ContextStack()
for item in items:
if item is None:
continue
if isinstance(item, ContextStack):
context._stack.extend(item._stack)
else:
context.push(item)
if kwargs:
context.push(kwargs)
return context
# TODO: add more unit tests for this.
# TODO: update the docstring for dotted names.
def get(self, name):
"""
Resolve a dotted name against the current context stack.
This function follows the rules outlined in the section of the
spec regarding tag interpolation. This function returns the value
as is and does not coerce the return value to a string.
Arguments:
name: a dotted or non-dotted name.
default: the value to return if name resolution fails at any point.
Defaults to the empty string per the Mustache spec.
This method queries items in the stack in order from last-added
objects to first (last in, first out). The value returned is
the value of the key in the first item that contains the key.
If the key is not found in any item in the stack, then the default
value is returned. The default value defaults to None.
In accordance with the spec, this method queries items in the
stack for a key differently depending on whether the item is a
hash, object, or neither (as defined in the module docstring):
(1) Hash: if the item is a hash, then the key's value is the
dictionary value of the key. If the dictionary doesn't contain
the key, then the key is considered not found.
(2) Object: if the item is an an object, then the method looks for
an attribute with the same name as the key. If an attribute
with that name exists, the value of the attribute is returned.
If the attribute is callable, however (i.e. if the attribute
is a method), then the attribute is called with no arguments
and that value is returned. If there is no attribute with
the same name as the key, then the key is considered not found.
(3) Neither: if the item is neither a hash nor an object, then
the key is considered not found.
*Caution*:
Callables are handled differently depending on whether they are
dictionary values, as in (1) above, or attributes, as in (2).
The former are returned as-is, while the latter are first
called and that value returned.
Here is an example to illustrate:
>>> def greet():
... return "Hi Bob!"
>>>
>>> class Greeter(object):
... greet = None
>>>
>>> dct = {'greet': greet}
>>> obj = Greeter()
>>> obj.greet = greet
>>>
>>> dct['greet'] is obj.greet
True
>>> ContextStack(dct).get('greet') #doctest: +ELLIPSIS
<function greet at 0x...>
>>> ContextStack(obj).get('greet')
'Hi Bob!'
TODO: explain the rationale for this difference in treatment.
"""
if name == '.':
try:
return self.top()
except IndexError:
raise KeyNotFoundError(".", "empty context stack")
parts = name.split('.')
try:
result = self._get_simple(parts[0])
except KeyNotFoundError:
raise KeyNotFoundError(name, "first part")
for part in parts[1:]:
# The full context stack is not used to resolve the remaining parts.
# From the spec--
#
# 5) If any name parts were retained in step 1, each should be
# resolved against a context stack containing only the result
# from the former resolution. If any part fails resolution, the
# result should be considered falsey, and should interpolate as
# the empty string.
#
# TODO: make sure we have a test case for the above point.
result = _get_value(result, part)
# TODO: consider using EAFP here instead.
# http://docs.python.org/glossary.html#term-eafp
if result is _NOT_FOUND:
raise KeyNotFoundError(name, "missing %s" % repr(part))
return result
def _get_simple(self, name):
"""
Query the stack for a non-dotted name.
"""
for item in reversed(self._stack):
result = _get_value(item, name)
if result is not _NOT_FOUND:
return result
raise KeyNotFoundError(name, "part missing")
def push(self, item):
"""
Push an item onto the stack.
"""
self._stack.append(item)
def pop(self):
"""
Pop an item off of the stack, and return it.
"""
return self._stack.pop()
def top(self):
"""
Return the item last added to the stack.
"""
return self._stack[-1]
def copy(self):
"""
Return a copy of this instance.
"""
return ContextStack(*self._stack)
|