File size: 8,481 Bytes
910e21b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import marimo as mo
import json


def create_widgets(widget_type, values, widget_dict=None, stack=None):
    """
    Create marimo widgets for a list of values.

    Args:
        widget_type: Default marimo widget type (e.g., mo.ui.text_area)
        values: List of values to create widgets for
        widget_dict: Optional dict mapping specific values to widget types
        stack: 'horizontal' for hstack, 'vertical' for vstack, None for dict return
    """
    widgets = {}

    for value in values:
        # Use specific widget type if provided, otherwise use default
        current_widget_type = (
            widget_dict.get(value, widget_type) if widget_dict else widget_type
        )
        widgets[value] = current_widget_type(label=f"**{str(value)}**")

    if stack == "horizontal":
        return mo.hstack(list(widgets.values()), gap=1)
    elif stack == "vertical":
        return mo.vstack(list(widgets.values()), gap=1)
    else:
        return widgets


def json_to_marimo_ui(
    data,
    replicate_values=False,
    label="",
    label_containers=False,
    label_nested_leaves=False,
):
    """
    Convert a JSON structure to marimo UI elements.

    Args:
        data: JSON-like dict/list, or mo.ui.file element containing JSON
        replicate_values: If True, populate widgets with existing values; if False, leave empty
        label: Optional label for the root element
        label_containers: If True, apply labels to nested dicts/arrays (causes double nesting display);
                            if False (default), only label leaf input widgets
        label_nested_leaves: If True, label leaf widgets with their key names;
                            if False (default), only root label is applied, nested leaves have no labels

    Returns:
        mo.ui.dictionary for dicts, mo.ui.array for lists
    """
    # import marimo as mo
    # import json

    # Handle mo.ui.file input
    if hasattr(data, "contents") and callable(data.contents):
        file_contents = data.contents()
        if file_contents is None:
            return mo.ui.dictionary({}, label=label)
        if isinstance(file_contents, bytes):
            file_contents = file_contents.decode("utf-8")
        data = json.loads(file_contents)

    def _convert(obj, replicate, lbl="", is_root=False):
        if isinstance(obj, dict):
            elements = {
                str(key): _convert(
                    value,
                    replicate,
                    lbl=str(key) if label_nested_leaves else "",
                )
                for key, value in obj.items()
            }
            container_label = lbl if (is_root or label_containers) else ""
            return mo.ui.dictionary(elements, label=container_label)

        elif isinstance(obj, list):
            if not obj:
                container_label = lbl if (is_root or label_containers) else ""
                return mo.ui.array([], label=container_label)
            elements = [
                _convert(item, replicate, lbl="" if not label_nested_leaves else "")
                for item in obj
            ]
            container_label = lbl if (is_root or label_containers) else ""
            return mo.ui.array(elements, label=container_label)

        elif isinstance(obj, bool):
            leaf_label = lbl if (is_root or label_nested_leaves) else ""
            return mo.ui.checkbox(
                value=obj if replicate else False,
                label=leaf_label,
            )

        elif isinstance(obj, int):
            leaf_label = lbl if (is_root or label_nested_leaves) else ""
            return mo.ui.number(
                value=obj if replicate else None,
                label=leaf_label,
                full_width=True,
            )

        elif isinstance(obj, float):
            leaf_label = lbl if (is_root or label_nested_leaves) else ""
            return mo.ui.number(
                value=obj if replicate else None,
                step=0.01,
                label=leaf_label,
                full_width=True,
            )

        elif isinstance(obj, str):
            val = obj if replicate else ""
            leaf_label = lbl if (is_root or label_nested_leaves) else ""
            if len(obj) > 40:
                return mo.ui.text_area(value=val, label=leaf_label, full_width=True)
            return mo.ui.text(value=val, label=leaf_label, full_width=True)

        elif obj is None:
            leaf_label = lbl if (is_root or label_nested_leaves) else ""
            return mo.ui.text(value="", label=leaf_label, full_width=True)

        else:
            leaf_label = lbl if (is_root or label_nested_leaves) else ""
            return mo.ui.text(
                value=str(obj) if replicate else "",
                label=leaf_label,
                full_width=True,
            )

    return _convert(data, replicate_values, label, is_root=True)


def create_download_button(
    widget_value, filename_prefix="download", add_uuid_suffix=True
):
    """Create a marimo download button appropriate for the widget value type.

    Automatically detects data type and creates download button with correct format and MIME type.

    Args:
        widget_value: Data to download (dict/list as JSON, DataFrames as CSV,
            str as text, bytes as binary, others converted to string)
        filename_prefix (str): Filename prefix. Defaults to "download".
        add_uuid_suffix (bool): Add 4-char UUID suffix to filename. Defaults to True.

    Returns:
        mo.download: Configured marimo download button widget.

    Examples:
        >>> data = {"key": "value", "numbers": [1, 2, 3]}
        >>> button = create_download_button(data, "my_data")

        >>> df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
        >>> button = create_download_button(df, "dataframe")

    Note:
        Requires marimo (mo) and uuid modules. JSON formatted with 2-space indent.
        CSV exports exclude index for pandas DataFrames.
    """
    import marimo as mo
    import json
    import uuid

    if isinstance(widget_value, dict) or isinstance(widget_value, list):
        # JSON data
        data = json.dumps(widget_value, indent=2)
        filename = f"{filename_prefix}.json"
        if add_uuid_suffix:
            uuid_suffix = str(uuid.uuid4())[:4]
            filename = f"{filename_prefix}_{uuid_suffix}.json"
        return mo.download(
            data=data.encode(), filename=filename, mimetype="application/json"
        )

    elif hasattr(widget_value, "to_csv"):
        # Pandas DataFrame
        data = widget_value.to_csv(index=False)
        filename = f"{filename_prefix}.csv"
        if add_uuid_suffix:
            uuid_suffix = str(uuid.uuid4())[:4]
            filename = f"{filename_prefix}_{uuid_suffix}.csv"
        return mo.download(data=data.encode(), filename=filename, mimetype="text/csv")

    elif hasattr(widget_value, "write_csv"):
        # Polars DataFrame
        data = widget_value.write_csv()
        filename = f"{filename_prefix}.csv"
        if add_uuid_suffix:
            uuid_suffix = str(uuid.uuid4())[:4]
            filename = f"{filename_prefix}_{uuid_suffix}.csv"
        return mo.download(data=data.encode(), filename=filename, mimetype="text/csv")

    elif isinstance(widget_value, str):
        # Text data
        filename = f"{filename_prefix}.txt"
        if add_uuid_suffix:
            uuid_suffix = str(uuid.uuid4())[:4]
            filename = f"{filename_prefix}_{uuid_suffix}.txt"
        return mo.download(
            data=widget_value.encode(),
            filename=filename,
            mimetype="text/plain",
        )

    elif isinstance(widget_value, bytes):
        # Binary data
        filename = f"{filename_prefix}.bin"
        if add_uuid_suffix:
            uuid_suffix = str(uuid.uuid4())[:4]
            filename = f"{filename_prefix}_{uuid_suffix}.bin"
        return mo.download(
            data=widget_value,
            filename=filename,
            mimetype="application/octet-stream",
        )

    else:
        # Fallback: convert to string
        data = str(widget_value)
        filename = f"{filename_prefix}.txt"
        if add_uuid_suffix:
            uuid_suffix = str(uuid.uuid4())[:4]
            filename = f"{filename_prefix}_{uuid_suffix}.txt"
        return mo.download(data=data.encode(), filename=filename, mimetype="text/plain")


if __name__ == "__main__":
    print("Only Importable in Marimo Notebooks")