Validating Theme Management and Sharing Integration in Gradio
This test suite validates theme management functionality in the Gradio framework, focusing on theme versioning, sharing, and customization capabilities. It covers theme asset handling, version matching, and Hub integration features.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
gradio-app/gradio
test/test_theme_sharing.py
import tempfile
from unittest.mock import patch
import huggingface_hub
import pytest
from huggingface_hub.hf_api import SpaceInfo
import gradio as gr
from gradio.themes.utils import ThemeAsset, get_matching_version, get_theme_assets
versions = [
"0.1.0",
"0.1.1",
"0.1.2",
"0.1.3",
"0.4.4",
"0.5.0",
"0.7.0",
"0.9.2",
"0.9.3",
"0.9.4",
"0.9.5",
"0.9.6",
"0.9.7",
"0.9.8",
"2.2.0",
"2.2.1",
"2.2.10",
"2.2.11",
"2.2.12",
"2.2.13",
"2.2.14",
"2.2.15",
"2.2.2",
"2.2.3",
"2.2.4",
"2.2.5",
"2.2.6",
"2.2.7",
"2.2.8",
"3.0.1",
"3.0.10",
"3.0.11",
"3.0.12",
"3.0.13",
"3.0.14",
"3.0.15",
"3.0.16",
"3.0.17",
"3.0.18",
"3.0.19",
"3.0.2",
"3.0.20",
"3.0.20-dev0",
"3.0.21",
"3.0.22",
"3.0.23",
"3.0.23-dev1",
"3.0.24",
"3.0.25",
"3.0.26",
"3.0.3",
"3.0.4",
"3.0.5",
"3.0.6",
"3.0.7",
"3.0.8",
"3.0.9",
"3.1.0",
"3.1.1",
"3.1.2",
"3.1.3",
"3.1.4",
"3.1.5",
"3.1.6",
"3.1.7",
"3.10.0",
"3.10.1",
"3.11.0",
"3.12.0",
"3.13.0",
"3.13.1",
"3.13.2",
"3.14.0",
"3.15.0",
"3.16.0",
"3.16.1",
"3.16.2",
"3.17.0",
"3.17.1",
"3.18.0",
"3.18.1",
"3.18.1-dev0",
"3.18.2-dev0",
"3.18.2",
"3.19.0",
"3.19.1",
"3.20.0",
"3.20.1",
"3.3.1",
"3.4.1",
"3.8.1",
"3.8.2",
"3.9.1",
]
assets = [ThemeAsset(f"theme_schema@{version}") for version in versions]
dracula_gray = gr.themes.colors.Color(
*(
["#6272a4", "#7280ad", "#818eb6", "#919cbf", "#a1aac8"][::-1]
+ ["#586794", "#4e5b83", "#455073", "#3b4462", "#313952", "#272e42"]
)
)
dracula_pink = gr.themes.Color(
*(
[
"#ffd7ee",
"#ff79c6",
"#ff86cc",
"#ff94d1",
"#ffa1d7",
"#ffafdd",
"#ffbce3",
"#ffc9e8",
][::-1]
+ ["#e66db2", "#cc619e", "#b3558b"]
)
)
dracula_gray = gr.themes.colors.Color(
*(
["#6272a4", "#7280ad", "#818eb6", "#919cbf", "#a1aac8"][::-1]
+ ["#586794", "#4e5b83", "#455073", "#3b4462", "#313952", "#272e42"]
)
)
dracula = gr.themes.Base(
primary_hue=gr.themes.colors.pink,
neutral_hue=dracula_gray,
font=gr.themes.GoogleFont("Poppins"),
).set(
body_background_fill=dracula_gray.c500,
color_accent_soft=dracula_gray.c100,
error_border_color="#fecaca",
error_background_fill="#fee2e2",
error_icon_color="#b91c1c",
error_icon_color_dark="#ef4444",
error_text_color="#ef4444",
error_text_color_dark="#ef4444",
background_fill_primary=dracula_gray.c500,
background_fill_secondary=dracula_gray.c500,
block_background_fill=dracula_gray.c300,
body_text_color="#f8f8f2",
body_text_color_dark="#f8f8f2",
body_text_color_subdued="#f8f8f2",
block_label_text_color="#f8f8f2",
block_label_text_color_dark="#f8f8f2",
table_even_background_fill=dracula_gray.c300,
border_color_accent=dracula_gray.c200,
block_info_text_color="#f8f8f2",
block_info_text_color_dark="#f8f8f2",
block_title_text_color="#f8f8f2",
block_title_text_color_dark="#f8f8f2",
checkbox_background_color_selected_dark="#ff79c6",
checkbox_background_color_selected="#ff79c6",
button_primary_background_fill_dark="#ff79c6",
button_primary_background_fill=dracula_pink.c300,
button_secondary_text_color="#f8f8f2",
slider_color_dark="#ff79c6",
slider_color=dracula_pink.c300,
panel_background_fill="#31395294",
block_background_fill_dark="#31395294",
)
dracula.name = "gradio/dracula_test"
class TestSemverMatch:
def test_simple_equality(self):
assert get_matching_version(assets, "3.10.0") == ThemeAsset(
"[email protected]"
)
def test_empty_expression_returns_latest(self):
assert get_matching_version(assets, None) == ThemeAsset("[email protected]")
def test_range(self):
assert get_matching_version(assets, ">=3.10.0,<3.15") == ThemeAsset(
"[email protected]"
)
def test_wildcard(self):
assert get_matching_version(assets, "2.2.*") == ThemeAsset(
"[email protected]"
)
def test_does_not_exist(self):
assert get_matching_version(assets, ">4.0.0") is None
def test_compatible_release_specifier(self):
assert get_matching_version(assets, "~=0.0") == ThemeAsset("[email protected]")
def test_breaks_ties_against_prerelease(self):
assert get_matching_version(assets, ">=3.18,<3.19") == ThemeAsset(
"[email protected]"
)
class TestGetThemeAssets:
def test_get_theme_assets(self):
space_info = huggingface_hub.hf_api.SpaceInfo(
id="freddyaboulton/dracula",
siblings=[
{
"blob_id": None,
"lfs": None,
"rfilename": "themes/[email protected]",
"size": None,
},
{
"blob_id": None,
"lfs": None,
"rfilename": "themes/[email protected]",
"size": None,
},
{
"blob_id": None,
"lfs": None,
"rfilename": "themes/[email protected]",
"size": None,
},
{
"blob_id": None,
"lfs": None,
"rfilename": "themes/[email protected]",
"size": None,
},
],
tags=["gradio-theme", "gradio"],
private=False,
likes=0,
)
assert get_theme_assets(space_info) == [
ThemeAsset("themes/[email protected]"),
ThemeAsset("themes/[email protected]"),
ThemeAsset("themes/[email protected]"),
ThemeAsset("themes/[email protected]"),
]
assert gr.Theme._theme_version_exists(space_info, "0.1.1")
assert not gr.Theme._theme_version_exists(space_info, "2.0.0")
@pytest.mark.flaky
def test_load_space_from_hub_works(self):
theme = gr.Theme.from_hub("gradio/seafoam")
assert isinstance(theme, gr.Theme)
class TestBuiltInThemes:
@pytest.mark.parametrize(
"theme, name",
[
(gr.themes.Base(), "base"),
(gr.themes.Glass(), "glass"),
(gr.themes.Monochrome(), "monochrome"),
(gr.themes.Soft(), "soft"),
(gr.themes.Default(), "default"),
],
)
def test_theme_name(self, theme, name):
assert theme.name == name
class TestThemeUploadDownload:
@patch("gradio.themes.base.get_theme_assets", return_value=assets)
def test_get_next_version(self, mock):
next_version = gr.themes.Base._get_next_version(
SpaceInfo(id="gradio/dracula_test", private=False, likes=0, tags=[])
)
assert next_version == "3.20.2"
## Commenting out until after 4.0 Spaces are up
# @pytest.mark.flaky
# def test_theme_download(self):
# assert (
# gr.themes.Base.from_hub("gradio/[email protected]").to_dict()
# == dracula.to_dict()
# )
# with gr.Blocks(theme="gradio/[email protected]") as demo:
# pass
# assert demo.theme.to_dict() == dracula.to_dict()
# assert demo.theme.name == "gradio/dracula_test"
@pytest.mark.flaky
def test_theme_download_raises_error_if_theme_does_not_exist(self):
with pytest.raises(
ValueError, match="The space freddyaboulton/nonexistent does not exist"
):
gr.themes.Base.from_hub("freddyaboulton/nonexistent").to_dict()
@patch("gradio.themes.base.huggingface_hub")
@patch("gradio.themes.base.Base._theme_version_exists", return_value=True)
def test_theme_upload_fails_if_duplicate_version(self, mock_1, mock_2):
with pytest.raises(ValueError, match="already has a theme with version 0.2.1"):
dracula.push_to_hub("dracula_revamped", version="0.2.1", hf_token="foo")
@patch("gradio.themes.base.huggingface_hub")
@patch("gradio.themes.base.huggingface_hub.HfApi")
def test_upload_fails_if_not_valid_semver(self, mock_1, mock_2):
with pytest.raises(ValueError, match="Invalid version string: '3.0'"):
dracula.push_to_hub("dracula_revamped", version="3.0", hf_token="s")
def test_dump_and_load(self):
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as path:
dracula.dump(path.name)
assert gr.themes.Base.load(path.name).to_dict() == dracula.to_dict()
@patch("gradio.themes.base.Base._get_next_version", return_value="0.1.3")
@patch("gradio.themes.base.Base._theme_version_exists", return_value=False)
@patch("gradio.themes.base.huggingface_hub")
def test_version_and_token_optional(self, mock_1, mock_2, mock_3):
mock_1.whoami.return_value = {"name": "freddyaboulton"}
gr.themes.Monochrome().push_to_hub(repo_name="my_monochrome")
repo_call_args = mock_1.HfApi().create_commit.call_args_list[0][1]
assert repo_call_args["repo_id"] == "freddyaboulton/my_monochrome"
assert any(
o.path_in_repo == "themes/[email protected]"
for o in repo_call_args["operations"]
)
mock_1.whoami.assert_called_with()
@patch("gradio.themes.base.huggingface_hub")
def test_first_upload_no_version(self, mock_1):
mock_1.whoami.return_value = {"name": "freddyaboulton"}
mock_1.HfApi().space_info.side_effect = huggingface_hub.hf_api.HTTPError("Foo")
gr.themes.Monochrome().push_to_hub(repo_name="does_not_exist")
repo_call_args = mock_1.HfApi().create_commit.call_args_list[0][1]
assert repo_call_args["repo_id"] == "freddyaboulton/does_not_exist"
assert any(
o.path_in_repo == "themes/[email protected]"
for o in repo_call_args["operations"]
)
mock_1.whoami.assert_called_with()
@patch("gradio.themes.base.Base._get_next_version", return_value="0.1.3")
@patch("gradio.themes.base.Base._theme_version_exists", return_value=False)
@patch("gradio.themes.base.huggingface_hub")
def test_can_pass_version_and_theme(self, mock_1, mock_2, mock_3):
mock_1.whoami.return_value = {"name": "freddyaboulton"}
gr.themes.Monochrome().push_to_hub(
repo_name="my_monochrome", version="0.1.5", hf_token="foo"
)
repo_call_args = mock_1.HfApi().create_commit.call_args_list[0][1]
assert repo_call_args["repo_id"] == "freddyaboulton/my_monochrome"
assert any(
o.path_in_repo == "themes/[email protected]"
for o in repo_call_args["operations"]
)
mock_1.whoami.assert_called_with(token="foo")
@patch("gradio.themes.base.huggingface_hub")
def test_raise_error_if_no_token_and_not_logged_in(self, mock_1):
mock_1.whoami.side_effect = OSError("not logged in")
with pytest.raises(
ValueError,
match="In order to push to hub, log in via `huggingface-cli login`",
):
gr.themes.Monochrome().push_to_hub(
repo_name="my_monochrome", version="0.1.5"
)
@patch("gradio.themes.base.Base._get_next_version", return_value="0.1.3")
@patch("gradio.themes.base.Base._theme_version_exists", return_value=False)
@patch("gradio.themes.base.huggingface_hub")
def test_can_upload_to_org(self, mock_1, mock_2, mock_3):
mock_1.whoami.return_value = {"name": "freddyaboulton"}
gr.themes.Monochrome().push_to_hub(
repo_name="my_monochrome", version="0.1.9", org_name="gradio"
)
repo_call_args = mock_1.HfApi().create_commit.call_args_list[0][1]
assert repo_call_args["repo_id"] == "gradio/my_monochrome"
assert any(
o.path_in_repo == "themes/[email protected]"
for o in repo_call_args["operations"]
)
mock_1.whoami.assert_called_with()
@patch("gradio.themes.base.Base._get_next_version", return_value="0.1.3")
@patch("gradio.themes.base.Base._theme_version_exists", return_value=False)
@patch("gradio.themes.base.huggingface_hub")
def test_can_make_private(self, mock_1, mock_2, mock_3):
mock_1.whoami.return_value = {"name": "freddyaboulton"}
gr.themes.Monochrome().push_to_hub(
repo_name="my_monochrome", version="0.1.9", org_name="gradio", private=True
)
mock_1.create_repo.assert_called_with(
"gradio/my_monochrome",
repo_type="space",
space_sdk="gradio",
token=None,
exist_ok=True,
private=True,
)
def test_theme_builder_launches():
gr.themes.builder(prevent_thread_lock=True)
gr.close_all()