The Context class is an interesting data-structure Moya uses to implement much of the features of its high level language and template system. It is flexible enough that it could have applications in other Python projects.

Template systems, such as Django's, also have a context object, which is a container for template data. A Moya context is an encapsulation of a container plus the functionality for extracting values from it. But unlike Django where a context is created for the purpose of rendering a template, Moya creates a single context for each request were all data is stored.

In essence, the context is a namespace for nested data where every item may be referenced by a data index. A data index is much like a path in a filesystem; where a path contains directory names, a data index contains the keys or attributes of the intermediate Python objects.

Let's consider the following nested data:

middle_earth = { "species": { "hobbits": { "names": ["Sam", "Bilbo", "Frodo"], }, "dwarves" : { "names": ["Durin", "Thorin", "Fili", "Kili"] } } }

If we want the second name in the "hobbits" dictionary, we could write the expression middle_earth["species"]["hobbits"]["names"][1] . With the data stored in a Context object, we could refer to the same value using the equivalent data index, with middle_earth["species.hobbits.names.1"] . We can see that the data index is more or less a series of chained getitem calls, where each key is delimited with a period in the index. The final '1' in the data index is notable in that it is treated as an integer even though it is part of a string.

In addition to saving a few characters, the extra layer of abstraction provided by the data index allows the Context to implement some more advance features such as scopes, links, and lazy evaluation. I'll cover those here, but first an introduction to working with Context objects.

Creating a Context

Creating a Context object is very simple. Here's how we could turn the above data in to a context:

from moya.context import Context middle_earth = Context(middle_earth)

Constructing a Context instance like this doesn't copy or modify the original data in anyway. In fact, the Context keeps a reference to it; middle_earth.root is a reference to the original dictionary

Frames

In keeping with the filesystem analogy, a Context object has a 'current working directory' of sorts. By default, a data index starts from the root of the context, but the starting point for the index may be set by pushing a frame. Here's an example:

with middle_earth.frame('species.hobbits'): print(middle_earth['names.1'])

The first line pushes the frame 'species.hobbits'. All further indexes are considered to be relative to the this index. In filesystem terms, this would be the equivalent of doing cd species/hobbit.

Absolute Indexes

Like filesystem paths, a data index can be relative or absolute. A relative index will start indexing from the current frame, whereas an absolute index will start indexing from the root. Absolute data indexes begin with a single period. Here's an example of using an absolute index:

with middle_earth.frame('species.hobbit'): print(middle_earth['.species.hobbit.names.1']) print(middle_earth['names.1'])

The above code print "Bilbo" twice. The first index, .species.hobbit.names.1 , is absolute due to the initial period and will start indexing from the root regardless of the current frame. The second index, names.1 , starts indexing from the current frame, species.hobbits , and ultimately references the same value as the first index.

Moya makes use of absolute indexes by placing frequently needed data in the root of the context. For instance, the request object is always available as .request .

Scopes

A scope is a similar concept to a frame. Like a frame, a scope is a data index which sets a new initial starting point for index operations. But unlike a frame, multiple scopes my apply. The Context object will try each scope in turn until it finds a value. Probably the best way to illustrate this is with an example of how it is used in Moya's template language. The following is a simple for loop that generates a paragraph for every item in the dwarves list:

{% for name in .species.dwarves %} <p>${name} is a dwarf</p> {% endfor %}

In each pass through the above code, Moya creates a new scope containing the value 'name'. Inside the loop, references to 'name' will use the value from the recently created scope. When the loop completes, 'name' will have its original value restored (assuming 'name' was assigned to anything prior to the loop).

Here's some abbreviated code that explains what the template system is doing behind the scenes:

for name in middle_earth[".species.dwarves"]: middle_earth[".for"] = {"name": name} with middle_earth.scope(".for"): yield iter(children) # defer to the code inside the loop

Magic

Within the template system or Moya Code, a data index is essentially a variable, much like a reference in Python. But because it's the Context instance that looks up the value of an index, there is greater opportunity for magic where the context runs some code when a value is requested, rather than pulling it out of a dictionary.

The Python language can and does do magic like this (weakref , asyncio), but with a Context interface, the abstraction is less leaky.

Dynamic Values

One of the simplest ways of implementing such magic is with dynamic variables. Rather than set a value on the context, you can set a callable which returns a value. The following is an example of setting a dynamic variable:

>>> from moya.context import Context >>> from datetime import datetime >>> c = Context() >>> c.set_dynamic('now', lambda context: datetime.now()) >>> c['now'] datetime.datetime(2015, 4, 5, 15, 41, 9, 713115)

The data index 'now' returns the current time every time it is looked up. The value returned is a simple Python object, and not a proxy of some kind.

Lazy Evaluation

Another use for magic is lazy evaluation. This involves setting a value on the context to the result of a function call, but only invoking the function if the value is referenced; a concept known as a future or promise in some languages. Let's look at an example.

The following Python code sets a lazy value 'mol' to the return value of the function 'meaning_of_life'.

>>> import time >>> def meaning_of_life(): ... time.sleep(5) ... return 42 >>> c.set_lazy('mol', meaning_of_life)

The call to set_lazy sets the value of 'mol' to the result of calling 'meaning_of_life', but returns instantly. It's only when we attempt to use that return value that the 'meaning_of_life' function is invoked. So the first time the value of 'mol' is looked up it will take 5 seconds:

>>> c['mol'] 42

Subsequent calls to c['mol'] will return the value instantly. As before, the value returned is a simple Python object. Any code referencing the value of 'mol' in the context, would be unaware that it wasn't just an integer stored in a container.

Moya uses lazy evaluation for things like current user object. The value .user is always available, but it is only retrieved from the database when it is referenced. This avoids the requirement to call a function everywhere the user is required, without the risk of making a query that is never used.

Expressions

The Context is also the interface to Moya's expressions system. Here's an example of evaluating an expression from a Context:

>>> from moya.context import Context >>> c = Context() >>> c['bitstamp_api'] = 'https://www.bitstamp.net/api/ticker/' >>> c.eval("float:(fromjson:get:bitstamp_api)['ask']") 257.91

The above code gets the current asking price of Bitcoin from an online API. Moya's expressions are designed to pack a lot of punch and can be used in templates and in Moya Code (the high level language). Data indexes in expressions are treated as variables, for instance c.eval('a+b') is the same as c['a'] + c['b'] .

Summary

The Context is a powerful Python collection, with a remarkably simple interface. This post touches on some of the uses of the Context from the Python side. In a Moya project the context is entirely transparent, in that you use the context every time you assign or read data. See the Moya Documentation if you would be interested in learning more.