Skip to content
Snippets Groups Projects
Commit 2c7053be authored by Piotr Maślanka's avatar Piotr Maślanka
Browse files

add Contexts

parent 74bdc994
No related branches found
No related tags found
No related merge requests found
# v2.21.4
# v2.22.0rc1
* moar tests for CPManager
* added tainting
\ No newline at end of file
* added tainting
* added Environments
\ No newline at end of file
Sometimes, you have a bunch of arguments to functions
such as the database connection object, and would
prefer them not to be passed by an argument, but configured
through Satella's Environments themselves:
.. autoclass:: satella.coding.Context
:members:
You can think of them as a stack of cards each carrying a set of variable names. The variable, if not present
on the current card, will be checked upwards for parent of this card. Each thread, by default, gets a
separate hierarchy. Removing a variable on one card will not affect it's parent, however the variable will remain
inaccessible for the duration of this context. Use them like this:
.. code-block:: python
with Context() as ctxt:
ctxt.value = 55
with Context() as new_ctxt:
new_ctxt.value = 66
assert ctxt.value == 66
assert ctxt.value == 55
......@@ -13,6 +13,7 @@ Visit the project's page at GitHub_!
configuration/schema
configuration/sources
coding/ctxt_managers
coding/environment
coding/functions
coding/futures
coding/structures
......
__version__ = '2.21.4a2'
__version__ = '2.22.0rc2'
......@@ -17,6 +17,7 @@ from .misc import update_if_not_none, update_key_if_none, update_attr_if_none, q
update_key_if_not_none, source_to_function, update_key_if_true, \
get_arguments, call_with_arguments, chain_callables, Closeable, contains, \
enum_value
from .environment import Context
from .overloading import overload, class_or_instancemethod
from .recast_exceptions import rethrow_as, silence_excs, catch_exception, log_exceptions, \
raises_exception, reraise_as
......@@ -24,7 +25,7 @@ from .expect_exception import expect_exception
from .deep_compare import assert_equal, InequalityReason, Inequal
__all__ = [
'EmptyContextManager',
'EmptyContextManager', 'Context',
'assert_equal', 'InequalityReason', 'Inequal',
'Closeable', 'contains', 'enum_value', 'reraise_as',
'expect_exception',
......
from __future__ import annotations
import typing as tp
import threading
from satella.coding.typing import V
from .misc import Closeable
local = threading.local()
THEY_HATIN = object()
class Context:
"""
New layer of environment. Can have it's own variables, or can hoist them onto the parent.
"""
def __init__(self, parent: tp.Optional[Context] = None, **variables):
self.parent = parent
self.variables = {}
self.bool = None
def __str__(self):
return str(id(self))
def push_up(self, item: str) -> None:
"""
Advance current variable to the top of the card stack.
:param item: variable name
"""
var = self.variables.pop(item)
self.parent.variables[item] = var
def __getattr__(self, item: str):
if item in self.variables:
v = self.variables[item]
if v is not THEY_HATIN:
return v
raise AttributeError()
if self.parent is not None:
return getattr(self.parent, item)
raise AttributeError()
def __enter__(self):
global local
try:
parent = local.thread_context
except AttributeError:
parent = None
ctxt = Context(parent=parent)
if ctxt is not parent:
ctxt.parent = parent
local.thread_context = ctxt
return ctxt
def __setattr__(self, key: str, value: V):
"""
Set a value
"""
if key in ('parent', 'variables', 'bool'):
return super().__setattr__(key, value)
else:
self.variables[key] = value
def __delattr__(self, item: str) -> None:
self.variables[item] = THEY_HATIN
def does_exist(self, val: str) -> bool:
"""
Does a given value exist on stack for this call of function?
"""
if val in self.variables:
if self.variables[val] is THEY_HATIN:
return False
return True
else:
if self.parent is None:
return False
return self.parent.does_exist(val)
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
if self.parent is None:
try:
del local.thread_context
except AttributeError:
pass
else:
local.thread_context = self.parent
return False
@staticmethod
def get() -> Context:
"""
Return a local context for this thread
"""
global local
try:
return local.thread_context
except AttributeError:
ctxt = Context()
local.thread_context = ctxt
return ctxt
import unittest
from satella.coding.environment import Context
class TestEnvs(unittest.TestCase):
def test_envs(self):
ctxt = Context.get()
ctxt.value = 5
self.assertEqual(ctxt.value, 5)
with Context() as new_ctxt:
self.assertEqual(new_ctxt.value, 5)
def test_delete_envs(self):
ctxt = Context.get()
ctxt.value = 5
self.assertEqual(ctxt.value, 5)
with Context() as new_ctxt:
del new_ctxt.value
self.assertRaises(AttributeError, lambda: new_ctxt.value)
self.assertEqual(ctxt.value, 5)
def test_nesting(self):
with Context() as ctxt:
ctxt.value = 55
with Context() as new_ctxt:
new_ctxt.value = 66
assert new_ctxt.value == 66
assert ctxt.value == 55
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment