team-10/env/Lib/site-packages/streamlit/delta_generator_singletons.py

205 lines
7.4 KiB
Python
Raw Permalink Normal View History

2025-08-02 07:34:44 +02:00
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The main purpose of this module (right now at least) is to avoid a dependency
cycle between streamlit.delta_generator and some elements.
"""
from __future__ import annotations
from contextvars import ContextVar, Token
from typing import TYPE_CHECKING, Callable, Generic, TypeVar
from streamlit.proto.RootContainer_pb2 import RootContainer as _RootContainer
if TYPE_CHECKING:
from streamlit.delta_generator import DeltaGenerator
from streamlit.elements.lib.dialog import Dialog
from streamlit.elements.lib.mutable_status_container import StatusContainer
class DeltaGeneratorSingleton:
"""Used to initialize the DeltaGenerator classes and store them as singletons.
This module allows us to avoid circular imports between DeltaGenerator and elements,
because elements can import this singleton module instead of DeltaGenerator directly.
"""
_instance: DeltaGeneratorSingleton | None = None
@classmethod
def instance(cls) -> DeltaGeneratorSingleton:
"""Return the singleton DeltaGeneratorSingleton instance. Raise an Error if the
DeltaGeneratorSingleton hasn't been created yet.
"""
if cls._instance is None:
raise RuntimeError("DeltaGeneratorSingleton hasn't been created!")
return cls._instance
def __init__(
self,
delta_generator_cls: type[DeltaGenerator],
status_container_cls: type[StatusContainer],
dialog_container_cls: type[Dialog],
) -> None:
"""Registers and initializes all delta-generator classes.
Parameters
----------
delta_generator_cls : type[DeltaGenerator]
The main DeltaGenerator class.
status_container_cls : type[StatusContainer]
The delta-generator class that is used as return value for `st.status`.
dialog_container_cls : type[Dialog]
The delta-generator class used is used as return value for `st.dialog`.
Raises
------
RuntimeError
If the DeltaGeneratorSingleton instance already exists.
"""
if DeltaGeneratorSingleton._instance is not None:
raise RuntimeError("DeltaGeneratorSingleton instance already exists!")
DeltaGeneratorSingleton._instance = self
self._main_dg = delta_generator_cls(root_container=_RootContainer.MAIN)
self._sidebar_dg = delta_generator_cls(
root_container=_RootContainer.SIDEBAR, parent=self._main_dg
)
self._event_dg = delta_generator_cls(
root_container=_RootContainer.EVENT, parent=self._main_dg
)
self._bottom_dg = delta_generator_cls(
root_container=_RootContainer.BOTTOM, parent=self._main_dg
)
self._status_container_cls = status_container_cls
self._dialog_container_cls = dialog_container_cls
@property
def main_dg(self) -> DeltaGenerator:
return self._main_dg
@property
def sidebar_dg(self) -> DeltaGenerator:
return self._sidebar_dg
@property
def event_dg(self) -> DeltaGenerator:
return self._event_dg
@property
def bottom_dg(self) -> DeltaGenerator:
return self._bottom_dg
@property
def status_container_cls(
self,
) -> type[StatusContainer]:
"""Stub for StatusContainer. Since StatusContainer inherits from DeltaGenerator,
this is used to avoid circular imports.
"""
return self._status_container_cls
@property
def dialog_container_cls(self) -> type[Dialog]:
"""Stub for Dialog. Since Dialog inherits from DeltaGenerator,
this is used to avoid circular imports.
"""
return self._dialog_container_cls
def get_dg_singleton_instance() -> DeltaGeneratorSingleton:
"""Return the DeltaGeneratorSingleton instance. Raise an Error if the
DeltaGeneratorSingleton hasn't been created yet.
"""
return DeltaGeneratorSingleton.instance()
_T = TypeVar("_T")
class ContextVarWithLazyDefault(Generic[_T]):
"""The dg_stack tracks the currently active DeltaGenerator, and is pushed to when
a DeltaGenerator is entered via a `with` block. This is implemented as a ContextVar
so that different threads or async tasks can have their own stacks.
We have a wrapper around it because ContextVar default cannot be a function, but
the default dg (main_dg) might not exist yet when this module is imported.
"""
def __init__(self, name: str, *, default: Callable[[], _T]) -> None:
self._name = name
self._default = default
self._context_var: ContextVar[_T] | None = None
def _init_context_var(self) -> None:
self._context_var = ContextVar(self._name, default=self._default()) # noqa: B039
def get(self) -> _T:
if self._context_var is None:
self._init_context_var()
return self._context_var.get() # type: ignore[union-attr]
def set(self, value: _T) -> Token[_T]:
if self._context_var is None:
self._init_context_var()
return self._context_var.set(value) # type: ignore[union-attr]
def reset(self, token: Token[_T]) -> None:
if self._context_var is None:
self._init_context_var()
self._context_var.reset(token) # type: ignore[union-attr]
def __hash__(self) -> int:
if self._context_var is None:
self._init_context_var()
return self._context_var.__hash__()
# we don't use the default factory here because `main_dg` is not initialized when this
# module is imported. This is why we have our own ContextVar wrapper.
context_dg_stack: ContextVarWithLazyDefault[tuple[DeltaGenerator, ...]] = (
ContextVarWithLazyDefault(
"context_dg_stack", default=lambda: (get_dg_singleton_instance().main_dg,)
)
)
def get_default_dg_stack_value() -> tuple[DeltaGenerator, ...]:
"""Get the default dg_stack value with which the dg_stack should
be initialized and reset if needed.
"""
instance = get_dg_singleton_instance()
if instance._main_dg is None:
raise RuntimeError("main_dg is not set")
return (instance._main_dg,)
def get_last_dg_added_to_context_stack() -> DeltaGenerator | None:
"""Get the last added DeltaGenerator of the stack in the current context.
Returns None if the stack has only one element or is empty for whatever reason.
"""
current_stack = context_dg_stack.get()
# If set to "> 0" and thus return the only delta generator in the stack -
# which logically makes more sense -, some unit tests fail.
# It looks like the reason is that they create their own main delta generator
# but do not populate the dg_stack correctly. However, to be on the safe-side,
# we keep the logic but leave the comment as shared knowledge for whoever will look
# into this in the future.
if len(current_stack) > 1:
return current_stack[-1]
return None