Upload folder using huggingface_hub
Browse files
README.md
CHANGED
|
@@ -10,11 +10,11 @@ app_file: space.py
|
|
| 10 |
---
|
| 11 |
|
| 12 |
# `gradio_propertysheet`
|
| 13 |
-
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.
|
| 14 |
|
| 15 |
The **PropertySheet** component for Gradio allows you to automatically generate a complete and interactive settings panel from a standard Python `dataclass`. It's designed to bring the power of IDE-like property editors directly into your Gradio applications.
|
| 16 |
|
| 17 |
-
<img src="https://huggingface.co/datasets/DEVAIEXP/assets/resolve/main/
|
| 18 |
|
| 19 |
## Key Features
|
| 20 |
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
# `gradio_propertysheet`
|
| 13 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.5%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_propertysheet"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span></p>
|
| 14 |
|
| 15 |
The **PropertySheet** component for Gradio allows you to automatically generate a complete and interactive settings panel from a standard Python `dataclass`. It's designed to bring the power of IDE-like property editors directly into your Gradio applications.
|
| 16 |
|
| 17 |
+
<img src="https://huggingface.co/datasets/DEVAIEXP/assets/resolve/main/gradio_propertysheet_demo.png" alt="PropertySheet Demo">
|
| 18 |
|
| 19 |
## Key Features
|
| 20 |
|
src/README.md
CHANGED
|
@@ -10,11 +10,11 @@ app_file: space.py
|
|
| 10 |
---
|
| 11 |
|
| 12 |
# `gradio_propertysheet`
|
| 13 |
-
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.
|
| 14 |
|
| 15 |
The **PropertySheet** component for Gradio allows you to automatically generate a complete and interactive settings panel from a standard Python `dataclass`. It's designed to bring the power of IDE-like property editors directly into your Gradio applications.
|
| 16 |
|
| 17 |
-
<img src="https://huggingface.co/datasets/DEVAIEXP/assets/resolve/main/
|
| 18 |
|
| 19 |
## Key Features
|
| 20 |
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
# `gradio_propertysheet`
|
| 13 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.5%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_propertysheet"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span></p>
|
| 14 |
|
| 15 |
The **PropertySheet** component for Gradio allows you to automatically generate a complete and interactive settings panel from a standard Python `dataclass`. It's designed to bring the power of IDE-like property editors directly into your Gradio applications.
|
| 16 |
|
| 17 |
+
<img src="https://huggingface.co/datasets/DEVAIEXP/assets/resolve/main/gradio_propertysheet_demo.png" alt="PropertySheet Demo">
|
| 18 |
|
| 19 |
## Key Features
|
| 20 |
|
src/backend/gradio_propertysheet/helpers.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import fields, is_dataclass
|
| 2 |
+
import dataclasses
|
| 3 |
+
from gradio_client.documentation import document
|
| 4 |
+
from typing import Any, Dict, Literal, Type, get_args, get_origin, get_type_hints
|
| 5 |
+
|
| 6 |
+
@document()
|
| 7 |
+
def extract_prop_metadata(cls: Type, field: dataclasses.Field) -> Dict[str, Any]:
|
| 8 |
+
"""
|
| 9 |
+
Inspects a dataclass field and extracts metadata for UI rendering.
|
| 10 |
+
|
| 11 |
+
This function infers the appropriate frontend component (e.g., slider, checkbox)
|
| 12 |
+
based on the field's type hint if not explicitly specified in the metadata.
|
| 13 |
+
|
| 14 |
+
Args:
|
| 15 |
+
cls: The dataclass instance containing the field.
|
| 16 |
+
field: The dataclasses.Field object to inspect.
|
| 17 |
+
Returns:
|
| 18 |
+
A dictionary of metadata for the frontend to render a property control.
|
| 19 |
+
"""
|
| 20 |
+
metadata = field.metadata.copy()
|
| 21 |
+
metadata["name"] = field.name
|
| 22 |
+
current_value = getattr(cls, field.name)
|
| 23 |
+
metadata["value"] = current_value if current_value is not None else (field.default if field.default is not dataclasses.MISSING else None)
|
| 24 |
+
metadata["label"] = metadata.get("label", field.name.replace("_", " ").capitalize())
|
| 25 |
+
|
| 26 |
+
prop_type = get_type_hints(type(cls)).get(field.name)
|
| 27 |
+
if "component" not in metadata:
|
| 28 |
+
if metadata.get("component") == "colorpicker": pass
|
| 29 |
+
elif get_origin(prop_type) is Literal: metadata["component"] = "dropdown"
|
| 30 |
+
elif prop_type is bool: metadata["component"] = "checkbox"
|
| 31 |
+
elif prop_type is int: metadata["component"] = "number_integer"
|
| 32 |
+
elif prop_type is float: metadata["component"] = "number_float"
|
| 33 |
+
else: metadata["component"] = "string"
|
| 34 |
+
|
| 35 |
+
if metadata.get("component") == "dropdown":
|
| 36 |
+
if get_origin(prop_type) is Literal:
|
| 37 |
+
choices = list(get_args(prop_type))
|
| 38 |
+
metadata["choices"] = choices
|
| 39 |
+
if metadata["value"] not in choices:
|
| 40 |
+
metadata["value"] = choices[0] if choices else None
|
| 41 |
+
return metadata
|
| 42 |
+
|
| 43 |
+
@document()
|
| 44 |
+
def build_dataclass_fields(cls: Type, prefix: str = "") -> Dict[str, str]:
|
| 45 |
+
"""
|
| 46 |
+
Recursively builds a mapping of field labels to field paths for a dataclass.
|
| 47 |
+
|
| 48 |
+
This function traverses a dataclass and its nested dataclasses, creating a dictionary
|
| 49 |
+
that maps metadata labels (from `metadata={"label": ...}`) to dot-separated field paths
|
| 50 |
+
(e.g., "image_settings.model"). It is used to associate metadata labels with their
|
| 51 |
+
corresponding fields in a dataclass hierarchy.
|
| 52 |
+
|
| 53 |
+
Args:
|
| 54 |
+
cls: The dataclass type to process (e.g., PropertyConfig).
|
| 55 |
+
prefix: A string prefix for field paths, used during recursion to track nested fields.
|
| 56 |
+
|
| 57 |
+
Returns:
|
| 58 |
+
A dictionary mapping metadata labels (str) to field paths (str).
|
| 59 |
+
Example: `{"Model": "image_settings.model", "Description": "description"}`
|
| 60 |
+
"""
|
| 61 |
+
dataclass_fields = {}
|
| 62 |
+
type_hints = get_type_hints(cls)
|
| 63 |
+
|
| 64 |
+
for field in fields(cls):
|
| 65 |
+
field_name = field.name
|
| 66 |
+
field_type = type_hints.get(field_name, field.type)
|
| 67 |
+
field_label = field.metadata.get('label')
|
| 68 |
+
current_path = f"{prefix}{field_name}" if prefix else field_name
|
| 69 |
+
|
| 70 |
+
if field_label:
|
| 71 |
+
dataclass_fields[field_label] = current_path
|
| 72 |
+
if is_dataclass(field_type):
|
| 73 |
+
sub_fields = build_dataclass_fields(field_type, f"{current_path}.")
|
| 74 |
+
dataclass_fields.update(sub_fields)
|
| 75 |
+
|
| 76 |
+
return dataclass_fields
|
| 77 |
+
|
| 78 |
+
@document()
|
| 79 |
+
def create_dataclass_instance(cls: Type, data: Dict[str, Any]) -> Any:
|
| 80 |
+
"""
|
| 81 |
+
Recursively creates an instance of a dataclass from a nested dictionary.
|
| 82 |
+
|
| 83 |
+
This function constructs an instance of the specified dataclass, populating its fields
|
| 84 |
+
with values from the provided dictionary. For fields that are themselves dataclasses,
|
| 85 |
+
it recursively creates instances of those dataclasses. If a field is missing from the
|
| 86 |
+
dictionary, it uses the field's default value or default_factory.
|
| 87 |
+
|
| 88 |
+
Args:
|
| 89 |
+
cls: The dataclass type to instantiate (e.g., PropertyConfig).
|
| 90 |
+
data: A dictionary containing field values, which may be nested to match the dataclass hierarchy.
|
| 91 |
+
|
| 92 |
+
Returns:
|
| 93 |
+
An instance of the dataclass with fields populated from the dictionary.
|
| 94 |
+
"""
|
| 95 |
+
kwargs = {}
|
| 96 |
+
type_hints = get_type_hints(cls)
|
| 97 |
+
|
| 98 |
+
for field in fields(cls):
|
| 99 |
+
field_name = field.name
|
| 100 |
+
field_type = type_hints.get(field_name, field.type)
|
| 101 |
+
if field_name in data:
|
| 102 |
+
if is_dataclass(field_type) and isinstance(data[field_name], dict):
|
| 103 |
+
kwargs[field_name] = create_dataclass_instance(field_type, data[field_name])
|
| 104 |
+
else:
|
| 105 |
+
kwargs[field_name] = field.default if data[field_name] is None else data[field_name]
|
| 106 |
+
else:
|
| 107 |
+
if field.default_factory is not None:
|
| 108 |
+
kwargs[field_name] = field.default_factory()
|
| 109 |
+
else:
|
| 110 |
+
kwargs[field_name] = field.default
|
| 111 |
+
|
| 112 |
+
return cls(**kwargs)
|
src/backend/gradio_propertysheet/propertysheet.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
import copy
|
| 3 |
-
from typing import Any, Dict, List, get_type_hints
|
| 4 |
import dataclasses
|
| 5 |
from gradio.components.base import Component
|
|
|
|
|
|
|
| 6 |
|
| 7 |
def prop_meta(**kwargs) -> dataclasses.Field:
|
| 8 |
"""
|
|
@@ -12,7 +14,7 @@ def prop_meta(**kwargs) -> dataclasses.Field:
|
|
| 12 |
A dataclasses.Field instance with the provided metadata.
|
| 13 |
"""
|
| 14 |
return dataclasses.field(metadata=kwargs)
|
| 15 |
-
|
| 16 |
class PropertySheet(Component):
|
| 17 |
"""
|
| 18 |
A Gradio component that renders a dynamic UI from a Python dataclass instance.
|
|
@@ -79,42 +81,8 @@ class PropertySheet(Component):
|
|
| 79 |
value=self._dataclass_value, **kwargs
|
| 80 |
)
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
Inspects a dataclass field and extracts metadata for UI rendering.
|
| 85 |
-
|
| 86 |
-
This function infers the appropriate frontend component (e.g., slider, checkbox)
|
| 87 |
-
based on the field's type hint if not explicitly specified in the metadata.
|
| 88 |
-
|
| 89 |
-
Args:
|
| 90 |
-
obj: The dataclass instance containing the field.
|
| 91 |
-
field: The dataclasses.Field object to inspect.
|
| 92 |
-
Returns:
|
| 93 |
-
A dictionary of metadata for the frontend to render a property control.
|
| 94 |
-
"""
|
| 95 |
-
metadata = field.metadata.copy()
|
| 96 |
-
metadata["name"] = field.name
|
| 97 |
-
current_value = getattr(obj, field.name)
|
| 98 |
-
metadata["value"] = current_value if current_value is not None else (field.default if field.default is not dataclasses.MISSING else None)
|
| 99 |
-
metadata["label"] = metadata.get("label", field.name.replace("_", " ").capitalize())
|
| 100 |
-
|
| 101 |
-
prop_type = get_type_hints(type(obj)).get(field.name)
|
| 102 |
-
if "component" not in metadata:
|
| 103 |
-
if metadata.get("component") == "colorpicker": pass
|
| 104 |
-
elif get_origin(prop_type) is Literal: metadata["component"] = "dropdown"
|
| 105 |
-
elif prop_type is bool: metadata["component"] = "checkbox"
|
| 106 |
-
elif prop_type is int: metadata["component"] = "number_integer"
|
| 107 |
-
elif prop_type is float: metadata["component"] = "number_float"
|
| 108 |
-
else: metadata["component"] = "string"
|
| 109 |
-
|
| 110 |
-
if metadata.get("component") == "dropdown":
|
| 111 |
-
if get_origin(prop_type) is Literal:
|
| 112 |
-
choices = list(get_args(prop_type))
|
| 113 |
-
metadata["choices"] = choices
|
| 114 |
-
if metadata["value"] not in choices:
|
| 115 |
-
metadata["value"] = choices[0] if choices else None
|
| 116 |
-
return metadata
|
| 117 |
-
|
| 118 |
def postprocess(self, value: Any) -> List[Dict[str, Any]]:
|
| 119 |
"""
|
| 120 |
Converts the Python dataclass instance into a JSON schema for the frontend.
|
|
@@ -153,7 +121,7 @@ class PropertySheet(Component):
|
|
| 153 |
group_obj = getattr(current_value, field.name)
|
| 154 |
group_props = []
|
| 155 |
for group_field in dataclasses.fields(group_obj):
|
| 156 |
-
metadata =
|
| 157 |
metadata["name"] = f"{field.name}.{group_field.name}"
|
| 158 |
group_props.append(metadata)
|
| 159 |
|
|
@@ -169,7 +137,7 @@ class PropertySheet(Component):
|
|
| 169 |
json_schema.append({"group_name": unique_group_name, "properties": group_props})
|
| 170 |
else:
|
| 171 |
# Collect root properties to be processed later
|
| 172 |
-
root_properties.append(
|
| 173 |
|
| 174 |
# Process root properties, if any exist
|
| 175 |
if root_properties:
|
|
@@ -185,7 +153,8 @@ class PropertySheet(Component):
|
|
| 185 |
json_schema.insert(0, {"group_name": unique_root_label, "properties": root_properties})
|
| 186 |
|
| 187 |
return json_schema
|
| 188 |
-
|
|
|
|
| 189 |
def preprocess(self, payload: Any) -> Any:
|
| 190 |
"""
|
| 191 |
Processes the payload from the frontend to create an updated dataclass instance.
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
import copy
|
| 3 |
+
from typing import Any, Dict, List, get_type_hints
|
| 4 |
import dataclasses
|
| 5 |
from gradio.components.base import Component
|
| 6 |
+
from gradio_propertysheet.helpers import extract_prop_metadata
|
| 7 |
+
from gradio_client.documentation import document
|
| 8 |
|
| 9 |
def prop_meta(**kwargs) -> dataclasses.Field:
|
| 10 |
"""
|
|
|
|
| 14 |
A dataclasses.Field instance with the provided metadata.
|
| 15 |
"""
|
| 16 |
return dataclasses.field(metadata=kwargs)
|
| 17 |
+
@document()
|
| 18 |
class PropertySheet(Component):
|
| 19 |
"""
|
| 20 |
A Gradio component that renders a dynamic UI from a Python dataclass instance.
|
|
|
|
| 81 |
value=self._dataclass_value, **kwargs
|
| 82 |
)
|
| 83 |
|
| 84 |
+
|
| 85 |
+
@document()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
def postprocess(self, value: Any) -> List[Dict[str, Any]]:
|
| 87 |
"""
|
| 88 |
Converts the Python dataclass instance into a JSON schema for the frontend.
|
|
|
|
| 121 |
group_obj = getattr(current_value, field.name)
|
| 122 |
group_props = []
|
| 123 |
for group_field in dataclasses.fields(group_obj):
|
| 124 |
+
metadata = extract_prop_metadata(group_obj, group_field)
|
| 125 |
metadata["name"] = f"{field.name}.{group_field.name}"
|
| 126 |
group_props.append(metadata)
|
| 127 |
|
|
|
|
| 137 |
json_schema.append({"group_name": unique_group_name, "properties": group_props})
|
| 138 |
else:
|
| 139 |
# Collect root properties to be processed later
|
| 140 |
+
root_properties.append(extract_prop_metadata(current_value, field))
|
| 141 |
|
| 142 |
# Process root properties, if any exist
|
| 143 |
if root_properties:
|
|
|
|
| 153 |
json_schema.insert(0, {"group_name": unique_root_label, "properties": root_properties})
|
| 154 |
|
| 155 |
return json_schema
|
| 156 |
+
|
| 157 |
+
@document()
|
| 158 |
def preprocess(self, payload: Any) -> Any:
|
| 159 |
"""
|
| 160 |
Processes the payload from the frontend to create an updated dataclass instance.
|
src/backend/gradio_propertysheet/templates/component/index.js
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/frontend/Index.svelte
CHANGED
|
@@ -274,6 +274,9 @@
|
|
| 274 |
onMount(() => {
|
| 275 |
storeInitialValues();
|
| 276 |
});
|
|
|
|
|
|
|
|
|
|
| 277 |
</script>
|
| 278 |
|
| 279 |
<!-- The HTML template renders the component's UI based on the `value` prop. -->
|
|
|
|
| 274 |
onMount(() => {
|
| 275 |
storeInitialValues();
|
| 276 |
});
|
| 277 |
+
$: if (open && groupVisibility) {
|
| 278 |
+
tick().then(updateAllSliders);
|
| 279 |
+
}
|
| 280 |
</script>
|
| 281 |
|
| 282 |
<!-- The HTML template renders the component's UI based on the `value` prop. -->
|
src/pyproject.toml
CHANGED
|
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
|
|
| 8 |
|
| 9 |
[project]
|
| 10 |
name = "gradio_propertysheet"
|
| 11 |
-
version = "0.0.
|
| 12 |
description = "Property sheet"
|
| 13 |
readme = "README.md"
|
| 14 |
license = "apache-2.0"
|
|
@@ -38,8 +38,8 @@ classifiers = [
|
|
| 38 |
# encounter your project in the wild.
|
| 39 |
|
| 40 |
# [project.urls]
|
| 41 |
-
# repository = "
|
| 42 |
-
# space = "
|
| 43 |
|
| 44 |
[project.optional-dependencies]
|
| 45 |
dev = ["build", "twine"]
|
|
|
|
| 8 |
|
| 9 |
[project]
|
| 10 |
name = "gradio_propertysheet"
|
| 11 |
+
version = "0.0.5"
|
| 12 |
description = "Property sheet"
|
| 13 |
readme = "README.md"
|
| 14 |
license = "apache-2.0"
|
|
|
|
| 38 |
# encounter your project in the wild.
|
| 39 |
|
| 40 |
# [project.urls]
|
| 41 |
+
# repository = "https://github.com/DEVAIEXP/gradio_component_propertysheet"
|
| 42 |
+
# space = "https://huggingface.co/spaces/elismasilva/gradio_propertysheet"
|
| 43 |
|
| 44 |
[project.optional-dependencies]
|
| 45 |
dev = ["build", "twine"]
|