from typing import (
Any,
Dict,
Optional,
Protocol,
Tuple,
Type,
TypeVar,
cast,
runtime_checkable,
)
from stackholm.context import Context
from stackholm.state import State
__all__ = (
'Storage',
)
STATE_T = TypeVar('STATE_T', bound=State)
[docs]@runtime_checkable
class Storage(
Protocol[STATE_T],
):
"""
Storage is a container for the state of a stack.
**Built-in Implementations:**
- :class:`~stackholm.storages.asgiref_local.asgiref_local_storage.ASGIRefLocalStorage`
- :class:`~stackholm.storages.contextvar.contextvar_storage.ContextVarStorage`
- :class:`~stackholm.storages.optimized_list.optimized_list_storage.OptimizedListStorage`
- :class:`~stackholm.storages.thread_local.thread_local_storage.ThreadLocalStorage`
**Creating a new implementation:**
An implementation of this protocol can be developed using the generic feature
of the class.
.. code-block:: python
class MyStorage(Storage[MyState]):
...
"""
[docs] def create_context_class(
self,
name: Optional[str] = None,
bases: Optional[Tuple[Type]] = None,
namespace: Optional[Dict[str, Any]] = None,
) -> Type[Context]:
"""
Creates and returns a new context class.
The created class will be bound to the storage.
.. caution::
If the parameter `bases` is provided, it must include
:class:`.Context` or at least one of its subclass as
the first element.
.. hint::
- https://www.python.org/download/releases/2.3/mro/
:param name:
The name of the class. (Default: `'Context'`)
:param bases:
The base classes of the class. (Default: `(Context,)`)
:param namespace:
The namespace of the class. (Default: `{}`)
"""
name = name or 'Context'
bases = bases or (Context,)
namespace = namespace or {}
namespace['_storage'] = self
context_class = cast(Type[Context], type(name, bases, namespace))
assert issubclass(context_class, Context), 'Context class must be a subclass of stackholm.Context' # noqa
return context_class
[docs] def get_state(self) -> STATE_T:
"""
Returns the state of the storage.
"""
raise NotImplementedError()
[docs] def set_state(
self,
state: STATE_T,
) -> None:
"""
Sets the state of the storage.
:param state:
The :class:`.State` instance to set.
"""
raise NotImplementedError()
@property
def state(self) -> STATE_T:
"""
Returns the state of the storage.
"""
return self.get_state()
[docs] def push_context(
self,
context: Context,
) -> int:
"""
Pushes a new context to the storage.
Returns the index of the pushed context.
.. seealso::
- :meth:`.State.push_context`
:param context:
The :class:`.Context` instance to push.
"""
return self.state.push_context(context)
[docs] def pop_context(
self,
index: int = -1,
) -> Optional[Context]:
"""
Pops and returns a context from the storage.
If the stack is empty, returns `None`.
.. seealso::
- :meth:`.State.pop_context`
:param index:
The index of the context to pop. (Default: `-1`)
"""
return self.state.pop_context(index)
[docs] def get_last_context(self) -> Optional[Context]:
"""
Returns the latest context in the stack.
.. seealso::
- :meth:`.State.get_last_context`
"""
return self.state.get_last_context()
[docs] def add_checkpoint(
self,
key: str,
context_index: int,
) -> None:
"""
Adds a checkpoint to the storage.
.. seealso::
- :meth:`.State.add_checkpoint`
:param key:
The partition key of the checkpoint.
:param context_index:
The index of the context.
"""
self.state.add_checkpoint(key, context_index)
[docs] def remove_checkpoint(
self,
key: str,
context_index: int,
) -> None:
"""
Removes a checkpoint from the storage.
.. seealso::
- :meth:`.State.remove_checkpoint`
:param key:
The partition key of the checkpoint.
:param context_index:
The index of the context.
"""
self.state.remove_checkpoint(key, context_index)
[docs] def get_nearest_checkpoint(self, key: str) -> Optional[Context]:
"""
Returns the latest context in the partition with the given partition key.
.. seealso::
- :meth:`.State.get_nearest_checkpoint`
:param key:
The partition key of the checkpoint.
"""
return self.state.get_nearest_checkpoint(key)