stackvar 3.3.0
Dispatch function's parameters through the callstack omitting arguments on intermediary functions. (a.k.a.: stack variable)
License: MIT
Keywords: stack, callstack, variable, parameter

stackvar
========

Dispatch function’s parameters through the callstack omitting arguments
on intermediary functions.

Summary example
---------------

Let’s suppose you have 4 functions nested in the call stack like this:

.. code:: python

   def func1():
     value_for_func4 = 10
     func2()

   def func2():
     func3()

   def func3():
     func4()

   def func4(value):
     #need to use value_for_func4 here
     #print(value_for_func4)
     ...

If you want to make ``value_for_func4`` to reach ``func4`` you have
these standard options

1. explicitly passing argument ``value_for_func4`` through all 4
   functions.
2. using a ``global value_for_func4`` variable.
3. move all the functions ``func1 ... func4`` inside an object and
   setting a shared property ``self.value_for_func4``.
4. passing a context object with ``context.value_for_func4`` or using
   ``**kwargs`` dictionary through the functions.

With ``stackvar`` you have a fifth option, which is having a side
channel to dispatch variables through the call stack. Things would be
written as:

.. code:: python

   def func1():
       with stackvar.send(func4, value_for_func4=10):
           func2()

   def func2():
       func3()

   def func3():
       func4()

   @stackvar.receive()
   def send_email(value_for_func4: stackvar.Variable = None):
       print(f'value_for_func4={value_for_func4}')

Advantages: - avoids clogging functions arguments - avoids clogging the
global namespace - avoids clogging the object’s namespace - each thread
has its own value - no need to use objects to solve the problem (still
can do functional programming) - you can define default value - you can
override when calling the receiver directly - you can dispatch to a
function or to a namespace (defined by a UUID)

Disadvantages - not every debugging tools are not prepared to take
``stackvar`` into account. - adds ``stackvar`` and ``pydantic`` as
dependencies - single programmer project in beta state

Warnings - You can’t send stackvars between threads, this is on purpose,
to avoid race conditions for sharing data between threads. You should
pass the data to the thread and then send it inside the thread.

Installing
----------

https://pypi.org/project/stackvar/

::

   pip install -U stackvar

Full example
------------

.. code:: python

   import stackvar

   def test_stackvar():
       # sending within a context
       with stackvar.send(send_email, email='rsanchez@example.com'):
           foo()
       # Use default value
       send_email()
       # pass specific value
       send_email('jerry@example.com')

   def foo():
       # intermediary function
       bar()

   def bar():
       # intermediary function
       send_email()

   @stackvar.receive()
   def send_email(email: stackvar.Variable = 'morty@example.com'):
       print(f'Sending email to={email}')

   if __name__ == '__main__':
       test_stackvar()

Will output

::

   Sending email to=rsanchez@example.com
   Sending email to=morty@example.com
   Sending email to=jerry@example.com

All features showcase
---------------------

.. code:: python

   import stackvar
   import uuid

   def cheat_sheet_doc():
       # Using namespace (recommended method)
       my_namespace = stackvar.Namespace(uuid.uuid4())
       @stackvar.receive(my_namespace)
       def send_email(email: stackvar.Variable = 'morty@example.com'):
           print(f'Sending email to={email}')
       with stackvar.send(my_namespace, email='rsanchez@example.com'):
           send_email()
    
       # Automatic namespace (solved from function)
       @stackvar.receive()
       def send_email2(email: stackvar.Variable = 'morty@example.com'):
           print(f'Sending email to={email}')
       with stackvar.send(send_email2, email='rsanchez@example.com'):
           send_email2()
    
       # Without decorator
       ns_uuid2 = stackvar.Namespace(uuid.uuid4())
       def send_email_nodecorator():
           email1 = ns_uuid2.email1
           # setting default value for a variable
           email2 = getattr(ns_uuid2, 'email2', 'jerry@example.com')
           print(f'Sending email1 to={email1} and {email2}')
           # another fancier way to set a default
           email2 = stackvar.get(ns_uuid2, email2='summer@example.com')
           print(f'Sending email1 to={email1} and {email2}')
       with stackvar.send(ns_uuid2, email1='rsanchez@example.com'):
           send_email_nodecorator()
    
       # No default values
       ns_uuid3 = stackvar.Namespace(uuid.uuid4())
       @stackvar.receive(ns_uuid3)
       def send_no_default(email1: stackvar.Variable, email2: stackvar.Variable):
           print(f'Sending={email1} and {email2}')
       with stackvar.send(ns_uuid3,
                          email1='rsanchez@example.com',
                          email2='summer@example.com'):
           send_no_default()

       # Using a Factory for default values
       ns_uuid4 = stackvar.Namespace(uuid.uuid4())
       @stackvar.receive(ns_uuid4)
       def send_factory(email_list: stackvar.Factory = list):
           email_list.append('squanchy@example.com')
           print(f'Sending to={email_list}')
       with stackvar.send(ns_uuid4):
           send_factory()

   if __name__ == '__main__':
       cheat_sheet_doc()

More docs
---------

Check examples at https://gitlab.com/joaduo/stackvar/-/tree/main/tests