Hi,

Many web frameworks and ORM tools have the need to propagate data
depending on some or other context within which a request is dealt with.
Passing it all via parameters to every nook of your code is cumbersome.

A lot of the frameworks use a thread local context to solve this
problem. I'm assuming these are based on threading.local.  

(See, for example:
http://www.sqlalchemy.org/docs/05/session.html#unitofwork_contextual )

Such usage assumes that one request is served per thread.

This is not necessarily the case.  (Twisted would perhaps be an example,
but I have not checked how the twisted people deal with the issue.)

The bottom line for me is that if you build a WSGI app, you'd not want
to restrict it to being able to run in a one request-per-thread setup.

So I've been playing with the idea to use something that creates a
context local to the current call stack instead. I.e. a context (dict)
which is inserted into the call stack at some point, and can be accessed
by any method/function deeper in the stack.

The normal use case for this is to propagate a database connection. But
it can also be used to propagate other things, such as information about
the user who is currently logged in, etc.

Since this is one way of creating objects that are global to a context
(the call stack), I'm sure it is in some ways evil and can be abused.
But that criticism can be levelled against the thread-local solution
too...

I attach some code to illustrate - and would appreciate some feedback on
the idea and its implementation.

-i
# --------------------------------------------- CC
# 
import inspect

class CC(object):
    """A class used to create and maintain a call-local context.

    A call-local context is a way to have data that is accessable to
    any code from a certain point in the call stack onwards, without the
    need to explicitly pass parameters each time.

    A call-local context does for code beyond it in the call stack, what
    thread-local does for code in a certain thread.

    Such a context is a dict, and it is a singleton per call stack.


    To create the context, do:
    
    >>> cc = CC(somename='somevalue')

    Code can use the context by means of a class method which will return
    the correct singleton per call stack:

    >>> def foo():
    >>>     assert CC.get_context()['somename'] == 'somevalue'
    
    Code that will use the context then needs to be invoked via the context:

    >>> cc(foo)

    Another example:

    >>> def bar(someparam):
    >>>     print someparam
    >>>     foo()
    >>>
    >>> cc(bar, 'value for some param')

    """

    def __init__(self, **kwargs):
        self.context = None
        self.context = self.__class__.get_context() or {}
        self.context.update(kwargs)

    def __call__(self, callable, *args, **kwargs):
        """This is used to call a callable within the constructed call context"""
        return callable(*args, **kwargs)

    @classmethod
    def get_context_hash(cls):
        """Returns a unique, hashable identifier identifying the current call context"""
        return id(cls.get_context())

    @classmethod
    def get_context(cls):
        """Returns the current call context, or None if there is none"""
        context = None
        f = inspect.currentframe()
        while context is None and f:
            candidate = f.f_locals.get('self', None)
            if isinstance(candidate, cls):
                context = candidate.context
            to_delete = f
            f = f.f_back
            del to_delete
        
        return context


# ------------------------------------ Use case in SQLAlchemy
#
# Note the dict itself is not really used to store a session, 
# since SQLAlchemy's scoped session already takes care of 
# storing sessions.
#
from sqlalchemy.orm import scoped_session, sessionmaker
Session = scoped_session(sessionmaker(), scopefunc=CC.get_context_hash) 

def checkit():
    sess = Session()
    sess2 = Session()
    assert sess is sess2

CC()(checkit)

def checkit2(session):
    sess = Session()
    assert sess is not session
    
session = Session()
CC()(checkit2, session)


# ---------------------------------------- To show/ test what CC does
#
class A(object):
    def foo(self):
        print CC.get_context()  # Here's how you'd get the dict

class B(object):
    a = A()
    def bar(self, arg):
        print arg
        print CC.get_context()
        CC.get_context()[5] = '5' # It can be modified
        self.a.foo()

b = B()
cc = CC(one=2)    # Create a call context
cc(b.bar, 'some argument for bar()')  # Call methods that will use it


class C(object):
    b = B()
    def goo(self):
        CC(two=2)(b.bar, 'some argument for bar() sent in from goo()')
        

# Here, a call context is created, then we call a method (goo)
# which will create another. The point of the excercise is to 
# show that the context is a singleton
c = C()
CC(a='A')(c.goo)

_______________________________________________
Web-SIG mailing list
Web-SIG@python.org
Web SIG: http://www.python.org/sigs/web-sig
Unsubscribe: 
http://mail.python.org/mailman/options/web-sig/archive%40mail-archive.com

Reply via email to