The life of an app in Nagare
This document aims to help beginners looking at the python continuation-passing style web framework Nagare, who struggle to understand what makes Nagare tick on the inside.
This text was written in July 2010, the current Nagare version at the time of writing was Nagare 0.3.0. This is the first version of this text.
The heart of the application
The core of your Application in Nagare is a simple python object, called simply the App.
This App is usually a Class, that holds the most global data in your application. When a user first accesses your web-app, Nagare instanciates this App and it is then rendered. The magic of Nagare happens, when the request is done, rendered and sent back to the user.
Before we can continue, we first have to learn about what Nagare calls the object graph.
The object graph
The object graph in itself is nothing special at all. You may have never thought about it, but you have always been using this graph while programming python. Until now it probably wasn't interesting enough for you to care, or just didn't make a difference. In Nagare, however, it is important to know what it is and how it behaves.
If you have never had anything to do with graph theory, allow me to quickly introduce you to the basics. A graph is a collection of nodes, which are in any pattern or way connected by edges. In our case, we are interested only in directed graphs, in which the direction an edge points is important. In undirected graphs, every edge points in both directions.
Now the object graph is best explained by an example. Look at this code:
class Foo(object): def __init__(self): self.name = 'example object' class Bar(object): def __init__(self): self.other = Foo() self.name = 'other example object' b = Bar()
In this very simple example we create an instance of the Bar class, which in turn instanciates the Foo class. In the object graph, all instances are nodes and all references are edges. It looks somewhat like this:
As you can see, the python module we are in references an instance of Bar, which I called Bar1, instead of the rather cryptic <main.Bar object at 0xdf8310> which python would output if we were to print b. Here you can see, that Foo1 is attached to Bar1 via the .other reference and both of them also reference a string each.
The relevance of the object graph
Now is the point we investigate, what happens, when a request is finished and sent back. When that happens, Nagare pickles the App object and along with it the complete object graph beginning at the App instance and following along all the edges.
Note especially, that you have to be careful with what you reference from your App object and all things inside the object graph. The pickling and unpickling causes amajor effects on objects you may not want:
References to global objects will diverge directly after the request is done.
When the next request comes in, Nagare unpickles the instance again and does what needs to be done. In the next section, we will discover, what that is and how Nagare knows why.
Consider this example: You want to share an object between all requests, so you put it somewhere the App object or any other object in the object graph can reach it. Everything works just as expected, but as soon as you accidentally keep a reference to that global object, it will become part of the object graph. This means, that an unwanted copy operation will be of the result of pickling and later unpickling the object graph. This copy and the reference to the global object you once had will now be distinct and change independently.
That's a pretty easy question to answer. When you rendered the presentation for your app object, you might have written something like this:
@presentation.render_for(MyApp) def render_app(self, h, binding, *args): h << h.p('Please click my pretty', h.a('link').action(self.act), '!') return h.root
In order to make the link do something, you used the action method on the a tag. You passed self.act to it, which is a method (not a method call).
Whenever you use .action or similar methods, Nagare registers the callback for this action and gives the a tag a specific href. This href identifies the callback that is supposed to run when this link is clicked. Let's have a quick look at the URLs Nagare builds.
You might have already seen the URLs Nagare builds for links and such. There are three elements to note: the s parameter, the c parameter and the _action part:
- _s parameter
- This parameter identifies the session. When the user first accesses your web-app, we saw, that it instanciates the *app* object. This parameter identifies what instance of the *app* object is relevant to the user.
- _c parameter
- This parameter identifies one State of the *app* object (and by extension all objects in the *object graph*, too). We will see later, what this means.
- _action parameter
- This parameter tells Nagare, which callback to call when unpickling the object.
States of the app object
In the last section, we briefly discussed the _c parameter in the URLs Nagare generates for us. This parameter needs to be explained a bit further for us to fully understand what is happening.
Since the user has a "back" button in his browser and we don't want our web-app to horribly break whenever it is used, Nagare stores more than just the newest state of our app object. In fact, many of these states are kept around until Nagare decides to drop them, as they are no longer needed.
What this means is, that while being used, our app leaves behind a trail of frozen previous states that can be reactivated by the user at any time.
This might not always be what we want. One example is the "number guessing game" in the Nagare examples. At the beginning of the game, the app decides on a random number and the user has to guess it iteratively. When the user found the number, he can push the back button to get back to the state directly after the app has decided on a random number and enter it, thus winning instantly.
This happens, because at every step, the _c parameter is different, so that going back with the back button gets us another state. We can get around this problem, though, by using state.stateless. Looking at the code, this little method is really small. All it does is set a persistent id on the object passed to it. This causes the pickling and unpickling to treat it differently. It will only ever store one instance of this object which is shared across all states.
Using stateless on the Number component in the example makes all states of the app to share the same Number instance, regardless of the back button.
In the same way you can completely defeat usage of the back button by creating a small factory, that instanciates your app object and makes it stateless and then return it. You then set this factory instead of your app class as the app and you're done. However, in many situations, you might want the user to be able to use the back button, so think twice before doing this.
One very interesting thing in Nagare are components.
Components give you an infrastructure to modularize the display of your web-app. Another very neat feature of components is, that a component can replace itself with another. This is accomplished by a call to the becomes method. This will replace the component permanently (unless of course you turn it back with another call to becomes).
Another feature of components is, that they can call another component. Calling a component temporarily puts the called component in the place of the calling component and puts it back to normal when the called component answers the call. Even though it may not seem like it, this creates a stack, much like any call stack in python.
A task is a small wrapper around a method that does something, which involves calling other components for rendering and interaction. A simpler example than the tic tac toe one from the Nagare wesbite would be a multi-step order process. When the user clicks on the "check out" button. First the task would be called and itself call a "verify" component, which would allow the user to adjust quantities in his shopping cart. The verify component would answer "OK" when the user clicks "continue" or "back" when the user clicks "cancel". In the latter case, the task itself would answer "back" to the calling component and thus terminate. In the former case, it would go on to call a "login" component, if the user wasn't logged in already, then a component to enter billing and shipping information would be called and so on. Note, that these components themselves may in turn call other components. For instance there might be a component that handles coupons or gift wrapping.
Putting it all together
Now we have learnt about the basic parts which a Nagare web app is made up of, but we don't really know, how those really relate to each other. Thus, we will take a look at the control flow inside a Nagare web app.
When the user enters an URL and accesses the web app for the first time, Nagare instanciates the app object for us, which may in turn instanciate other components. Then the registered render function is called, which probably calls render functions for its components. During the render phase, Nagare will collect all callbacks that we presented to the user. The app object and its object graph is then pickled and stored away for later re-use and the request is sent to the browser.
The first callback
When the user clicks a link and thus causes a callback to be called, Nagare selects the object graph to depickle based on the s (session) and c (state) parameter in the URL and selects the callback from the list of registered callbacks based on the _action* URL parameter.
The callback will then modify things in the object graph and return. In case of a component call, execution of the callback will be temorarily frozen (and actually pickled away with the rest of the graph) until the component answers.
After the callback has returned or called another component, the rendering will commence, a new state is spawned (there is no actual copying involved - this is basically a side-effect of pickling and unpickling, but the state ID will be changed) and new callbacks are gathered. The result will be sent to the browser and the object graph will be pickled and stored away.
How component calls work
Another detail is the way component calls work. Stackless python allows us to pickle away running methods. The code of component.Component.call looks somewhat like this:
# save away previous things previous_stuff = self.stuff # replace own stuff with stuff of called component self.stuff = other.stuff self.becomes(other) # create a channel to listen for an answer from the called component self._channel = stackless.channel() # this call will block until data is sent via self._channel.send() r = self._channel.receive() # restore old things self.stuff = previous_stuff self.becomes(previous_component) # return what the called component answered return r
And the relevant part of component.Component.answer looks like this:
# send the answer via the channel, causing execution of .call to resume self._channel.send(answer)
The trick is, that while the previous components waits for the channel.receive call to complete, it (with the running method!) will be pickled away, too.
This document is rather unorganized, but I hope it's helpful nonetheless.
Thanks to apoirer and crumbel on the #nagare IRC channel for enduring my endless questions about everything :)