# --------------------------------------------- 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)

