wiki:StatelessWidgets
Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Version 2 (modified by michele, 13 years ago) (diff)

--

The stateless nature of widgets

Widgets have been designed from the ground up to be stateless objects [1], this means that a widget instance does not hold any knowledge of what happened previously thus the same instance can be reused across all requests.

[FIXME stateless motivations: performance is not the main one, basically reusing the same instance fits well with the way you work with TG controllers and decorators (for example you pass a form instance to the @validate decorator, see [2]]

While writing your application the are two important rules you should keep in mind when working with widgets:

1) Reuse widgets instances inside your application

To effectively share the same widget instance across all requests you should take care of using *only one instance* of a given widget inside your application.

Yes:

banana_widget = BananaWidget()

class MyController(...):
    @turbogears.expose(html="my.template")
    def index(self):
        return dict(widget=banana_widget)
    
class AnotherController(...):
    @turbogears.expose(html="another.template")
    def fruit(self):
        return dict(fruit_widget=banana_widget)

No:

class MyController(...):
    @turbogears.expose(html="my.template")
    def index(self):
        widget = BananaWidget()
        return dict(widget=widget)

class AnotherController(...):
    @turbogears.expose(html="another.template")
    def fruit(self):
        fruit_widget = BananaWidget()
        return dict(fruit_widget=fruit_widget)

No:

<?py from my.widgets import BananaWidget ?>

<div py:content="BananaWidget().display()" />

2) Changing widget instance's attributes inside a request it's not threadsafe

Since the same widget instance is used across all requests its instance attributes should be immutable so that any thread has a consistent view of the instance.

Yes:

class BananaWidget(Widget):
    def __init__(self, foo, **kw):
        super(Widget, self).__init__(**kw)
        self.foo = foo

Rather not:

banana_widget = BananaWidget()
banana_widget.foo = "bar"

class MyController(...):
    @turbogears.expose(html="my.template")
    def index(self):
        return dict(widget=banana_widget)

Definitely not:

banana_widget = BananaWidget()

class MyController(...):
    @turbogears.expose(html="my.template")
    def index(self):
        banana_widget.foo = "bar"
        return dict(widget=banana_widget) 

Definitely not:

class BananaWidget(Widget):
    def update_data(d):
        super(Widget, self).update_data(self, d)
        self.bar = d["bar"]
        

Whenever you see "self." somewhere other than the constructor, you know you're in trouble. If two requests come in simultaneously and both try to render that widget at the same time, the two users might end up getting the same value appearing for their widget. Basically, you can't put *any* values that need to vary from request to request in self.

If the same widget instance is being used for every requests and its attributes are immutable how the widget can behave differently from request to request?

The attributes that define the substantial knowledge and behavior of a widget are sent in via the constructor while the parameters that can change from request to request are sent in at render time.

In substance you can resemble a widget to be like a controller's method, the widget appearance is defined by its template attribute and at render time you send to its template (display() or render() methods) a set of parameters that are request dependant and that the widget manipulates (update_data() method) to behave correctly.

Although the widget/controller's method parallelism can help to understand how a widget works it's important to underline that unlike a controller's method a widget is not responsible of directly responding to a given request, this job is always left to the controller method that takes care of interacting with the widget and sending request dependant parameters to it.

banana_widget = BananaWidget()

class MyController(...):
    @turbogears.expose(html="my.template")
    def index(self):
        value = "Brasilian banana"
        return dict(banana_widget=banana_widget, banana_value=value)

inside my/template.kid:

<div py:content="banana_widget.display(value=banana_value)" />

References

This document also takes inspiration from some discussion that have take place in the google group (for example Bob Ippolito's reply [2] that clearly tells why you shouldn't change an instance attribute inside a request, and some replies by Kevin).

[1]  http://ootips.org/stateless-distributed-objects.html

[2]  http://tinyurl.com/pfyho (see Kevin's reply to jpellerin, use @validate not @expose)

[3]  http://tinyurl.com/p79pg