Skip to content

Commit

Permalink
Make st.write call st.json to display Streamlit secrets object (#8659)
Browse files Browse the repository at this point in the history
## Describe your changes

When you call `st.write(st.secrets)`, the output is displayed as inline
code with `st.markdown`. This PR makes `st.write` call `st.json` on
objects of type `streamlit.runtime.secrets.Secrets`

## GitHub Issue Link (if applicable)

Closes #2905.

## Testing Plan

- Added Python unit test

---

**Contribution License Agreement**

By submitting this pull request you agree that all contributions to this
project are made under the Apache 2.0 license.
  • Loading branch information
snehankekre committed Jun 4, 2024
1 parent 3525562 commit 7977554
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 1 deletion.
3 changes: 3 additions & 0 deletions lib/streamlit/elements/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,9 @@ def flush_buffer():
):
# We either explicitly allow HTML or infer it's not HTML
self.dg.markdown(repr_html, unsafe_allow_html=unsafe_allow_html)
elif type_util.is_streamlit_secrets_class(arg):
flush_buffer()
self.dg.json(arg.to_dict())
else:
stringified_arg = str(arg)

Expand Down
14 changes: 14 additions & 0 deletions lib/streamlit/runtime/secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
KeysView,
Mapping,
NoReturn,
Union,
ValuesView,
)

Expand All @@ -44,6 +45,14 @@
]


def _convert_to_dict(obj: Union[Mapping[str, Any], AttrDict]) -> dict[str, Any]:
"""Recursively convert Mapping or AttrDict objects to dictionaries."""
if isinstance(obj, AttrDict):
return obj.to_dict()
elif isinstance(obj, Mapping):
return {k: _convert_to_dict(v) for k, v in obj.items()}


def _missing_attr_error_message(attr_name: str) -> str:
return (
f'st.secrets has no attribute "{attr_name}". '
Expand Down Expand Up @@ -227,6 +236,11 @@ def _parse(self, print_exceptions: bool) -> Mapping[str, Any]:

return self._secrets

def to_dict(self) -> dict[str, Any]:
"""Converts the secrets store into a nested dictionary, where nested AttrDict objects are also converted into dictionaries."""
secrets = self._parse(True)
return _convert_to_dict(secrets)

@staticmethod
def _maybe_set_environment_variable(k: Any, v: Any) -> None:
"""Add the given key/value pair to os.environ if the value
Expand Down
7 changes: 7 additions & 0 deletions lib/streamlit/type_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
from plotly.graph_objs import Figure
from pydeck import Deck

from streamlit.runtime.secrets import Secrets


# Maximum number of rows to request from an unevaluated (out-of-core) dataframe
MAX_UNEVALUATED_DF_ROWS = 10000
Expand Down Expand Up @@ -535,6 +537,11 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[Any]]:
return True


def is_streamlit_secrets_class(obj: object) -> TypeGuard[Secrets]:
"""True if obj is a Streamlit Secrets object."""
return is_type(obj, "streamlit.runtime.secrets.Secrets")


def is_sequence(seq: Any) -> bool:
"""True if input looks like a sequence."""
if isinstance(seq, str):
Expand Down
11 changes: 10 additions & 1 deletion lib/tests/streamlit/write_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import unittest
from collections import namedtuple
from typing import Any
from unittest.mock import MagicMock, Mock, PropertyMock, call, patch
from unittest.mock import MagicMock, Mock, PropertyMock, call, mock_open, patch

import numpy as np
import pandas as pd
Expand All @@ -35,6 +35,7 @@
from tests.streamlit.modin_mocks import DataFrame as ModinDataFrame
from tests.streamlit.modin_mocks import Series as ModinSeries
from tests.streamlit.pyspark_mocks import DataFrame as PysparkDataFrame
from tests.streamlit.runtime.secrets_test import MOCK_TOML
from tests.streamlit.snowpandas_mocks import DataFrame as SnowpandasDataFrame
from tests.streamlit.snowpandas_mocks import Series as SnowpandasSeries
from tests.streamlit.snowpark_mocks import DataFrame as SnowparkDataFrame
Expand Down Expand Up @@ -250,6 +251,14 @@ def test_query_params(self):

p.assert_called_once()

@patch("builtins.open", new_callable=mock_open, read_data=MOCK_TOML)
def test_streamlit_secrets(self, *mocks):
"""Test st.write with st.secrets."""
with patch("streamlit.delta_generator.DeltaGenerator.json") as p:
st.write(st.secrets)

p.assert_called_once()

@parameterized.expand(
[
(pd.DataFrame([[20, 30, 50]], columns=["a", "b", "c"]),),
Expand Down

0 comments on commit 7977554

Please sign in to comment.