dearpygui-forms 0.1.3__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,124 +1,4 @@
1
1
  """
2
2
  Dearpygui extention for autogeneration forms powered by pydantic.
3
3
  """
4
- import decimal
5
- from typing import Any, Type
6
- from pprint import pformat
7
-
8
-
9
- import dearpygui.dearpygui as dpg
10
- from loguru import logger
11
- import pydantic
12
-
13
-
14
- def parse_property_type(property_schema: dict[str, Any]) -> str:
15
- property_type = property_schema.get("type", None)
16
- if property_type is None:
17
- any_of = property_schema.get("anyOf", None)
18
- if any_of is None:
19
- raise ValueError(f"Property type not detected. {property_schema}")
20
-
21
- # Text input field preferred for Decimal type than float input field
22
- if 'string' in map(lambda x: x.get("type"), any_of):
23
- property_type = 'string'
24
- else:
25
- property_type = any_of[0].get("type", None)
26
-
27
- if property_type is None:
28
- raise ValueError(f"Property type not detected. {property_schema}")
29
- return property_type
30
-
31
- class DPGForm:
32
- """
33
- Base class for dearpygui forms.
34
- Sublasses must define `__pydantic_model__` with some Pydantic model type.
35
-
36
- # Example:
37
- ```python
38
- import dearpygui.dearpygui as dpg
39
- from pydantic import BaseModel
40
- from dearpygui_forms import DPGForm
41
-
42
- class User(BaseModel):
43
- name: str
44
- age: int
45
-
46
- class UserForm(DPGForm):
47
- __pydantic_model__ = User
48
-
49
- dpg.create_context()
50
- dpg.create_viewport()
51
- with dpg.window(label="User Form"):
52
- user_form = UserForm(callback=lambda x: print(x))
53
- user_form.add()
54
- dpg.setup_dearpygui()
55
- dpg.show_viewport()
56
- dpg.start_dearpygui()
57
- dpg.destroy_context()
58
- ```
59
- """
60
- __pydantic_model__: Type[pydantic.BaseModel]
61
-
62
- def __init__(self, callback):
63
- """
64
- Initializes the UserForm instance.
65
-
66
- Args:
67
- callback (Callable): The callback function to be called with a validated pydantic model.
68
- """
69
- self.callback = callback
70
- logger.debug(self.__pydantic_model__)
71
- self.model_schema = self.__pydantic_model__.model_json_schema()
72
- logger.debug(pformat(self.model_schema))
73
-
74
- def add(self):
75
- """Adds form as child_window dearpygui element."""
76
- schema = self.model_schema
77
- with dpg.child_window(label=schema["title"]):
78
- for property_name, property_schema in schema["properties"].items():
79
- property_type = parse_property_type(property_schema)
80
- default_value = property_schema.get("default")
81
- if property_type == "string":
82
- schema["properties"][property_name]["dpg_form_id"] = \
83
- dpg.add_input_text(label=property_schema["title"], default_value=default_value or '')
84
- elif property_type == "integer":
85
- schema["properties"][property_name]["dpg_form_id"] = \
86
- dpg.add_input_int(label=property_schema["title"], default_value=default_value or 0)
87
- elif property_type == "number":
88
- schema["properties"][property_name]["dpg_form_id"] = \
89
- dpg.add_input_float(label=property_schema["title"], default_value=float(default_value or 0))
90
- elif property_type == "boolean":
91
- schema["properties"][property_name]["dpg_form_id"] = \
92
- dpg.add_checkbox(label=property_schema["title"], default_value=default_value or False)
93
- elif property_type == "array":
94
- schema["properties"][property_name]["dpg_form_id"] = \
95
- dpg.add_input_text(label=property_schema["title"])
96
- elif property_type == "object":
97
- schema["properties"][property_name]["dpg_form_id"] = \
98
- dpg.add_input_text(label=property_schema["title"])
99
- else:
100
- raise ValueError(f"Unsupported type: {property_type}")
101
-
102
- dpg.add_button(label="Submit", callback=self.submit)
103
-
104
- logger.debug(pformat(self.model_schema))
105
-
106
- def submit(self):
107
- try:
108
- data = self._handle_data()
109
- except pydantic.ValidationError as e:
110
- with dpg.window(label="Validation error", modal=True):
111
- dpg.add_text(str(e))
112
- else:
113
- self.callback(data)
114
-
115
- def _handle_data(self):
116
- json_data = {}
117
- for property_name, property_schema in self.model_schema["properties"].items():
118
- form_id = property_schema["dpg_form_id"]
119
- if form_id is not None:
120
- json_data[property_name] = dpg.get_value(form_id)
121
- else:
122
- raise ValueError(f"Missing form ID for property: {property_name}")
123
-
124
- return self.__pydantic_model__(**json_data)
4
+ from .dpg_form import DPGForm
@@ -0,0 +1,57 @@
1
+ from pprint import pformat
2
+ from typing import Any, Type
3
+ import copy
4
+
5
+ import pydantic
6
+ import dearpygui.dearpygui as dpg
7
+ from loguru import logger
8
+
9
+
10
+ from .models import PropertySchema
11
+ from .widgets import *
12
+ from . import exceptions
13
+
14
+
15
+ def extract_defs(schema: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
16
+ """split schema on root models schema and $defs"""
17
+ schema = copy.deepcopy(schema)
18
+ if "$defs" in schema:
19
+ defs = schema["$defs"]
20
+ del schema["$defs"]
21
+ root_model_schema = schema
22
+ else:
23
+ defs = {}
24
+ root_model_schema = schema
25
+ return root_model_schema, defs
26
+
27
+
28
+ class DPGForm:
29
+ def __init_subclass__(cls, *, model: Type[pydantic.BaseModel]) -> None:
30
+ cls._Model = model
31
+
32
+ def __init__(self, callback):
33
+ model_schema, models_defs = extract_defs(self._Model.model_json_schema())
34
+ self._model_widget = generate_widget(model_schema, models_defs)
35
+ self._callback = callback
36
+
37
+ def add(self):
38
+ self._model_widget.add()
39
+ dpg.add_button(label="Submit", callback=self._on_submit)
40
+
41
+ def fill_form(self, data_model: pydantic.BaseModel):
42
+ self._model_widget.set_value(data_model.model_dump())
43
+
44
+ def _on_submit(self):
45
+ try:
46
+ data = self._Model(**self._model_widget.get_value())
47
+ except pydantic.ValidationError as e:
48
+ with dpg.window(modal=True, label="Validation Error"):
49
+ dpg.add_text(f"Validation error: {e}")
50
+ except exceptions.DearpyguiFormsError as e:
51
+ with dpg.window(modal=True, label="Form Error"):
52
+ dpg.add_text(f"Form error: {e}")
53
+ else:
54
+ self._callback(data)
55
+
56
+ def get_form_data(self):
57
+ return self._Model(**self._model_widget.get_value())
@@ -0,0 +1,2 @@
1
+ class DearpyguiFormsError(Exception):
2
+ pass
@@ -0,0 +1,14 @@
1
+ from typing import Any, Literal
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class PropertySchema:
6
+ def __init__(self, schema: dict[str, Any]) -> None:
7
+ self.title: str | None = schema.get("title", None)
8
+ self.type: str | None = schema.get("type", None)
9
+ self.anyOf: list[dict[str, Any]] = schema.get("anyOf", [])
10
+ self.properties: dict[str, dict[str, Any]] = schema.get("properties", {})
11
+ self.default = schema.get("default", None)
12
+
13
+ def __repr__(self) -> str:
14
+ return f"PropertySchema(title={self.title}, type={self.type}, anyOf={self.anyOf}, properties={self.properties}, default={self.default})"
@@ -0,0 +1,256 @@
1
+ import copy
2
+ from typing import Any
3
+
4
+ import dearpygui.dearpygui as dpg
5
+ from loguru import logger
6
+
7
+
8
+ from .models import PropertySchema
9
+ from .exceptions import DearpyguiFormsError
10
+
11
+
12
+ @logger.catch
13
+ def dereference_property(schema: dict[str, Any], defs: dict[str, Any]):
14
+ if "$ref" in schema:
15
+ path = schema["$ref"].split("/")[-1]
16
+ new_schema = copy.deepcopy(defs[path])
17
+ new_schema.update(copy.deepcopy(schema))
18
+ del new_schema["$ref"]
19
+ else:
20
+ new_schema = copy.deepcopy(schema)
21
+ return new_schema
22
+
23
+
24
+ class Widget:
25
+ def __init__(self, schema: PropertySchema, defs: dict, **kwargs):
26
+ self.schema = schema
27
+ self._defs = defs
28
+ self._kwargs = kwargs
29
+ with dpg.stage() as self._staging_container_id:
30
+ with dpg.group() as self._root_item:
31
+ self._ui()
32
+
33
+ if schema.default:
34
+ self.set_value(schema.default)
35
+
36
+ def _ui(self):
37
+ dpg.add_text(f"Property {self.schema.title}, type: {self.schema.type}")
38
+
39
+ def add(self, parent=None, hidden=False):
40
+ """
41
+ Add widget as dpg ui item.
42
+ """
43
+ if parent is not None:
44
+ dpg.push_container_stack(parent)
45
+ dpg.unstage(self._staging_container_id)
46
+ dpg.pop_container_stack()
47
+ else:
48
+ dpg.unstage(self._staging_container_id)
49
+
50
+ if hidden:
51
+ self.hide()
52
+
53
+ def hide(self):
54
+ dpg.hide_item(self._root_item)
55
+
56
+ def show(self):
57
+ dpg.show_item(self._root_item)
58
+
59
+ def set_value(self, value: Any):
60
+ pass
61
+
62
+ def get_value(self) -> Any:
63
+ pass
64
+
65
+
66
+ class ObjectWidget(Widget):
67
+ def __init__(self, schema, defs, **kwargs):
68
+ self._properties: dict[str, Widget] = {}
69
+ super().__init__(schema, defs, **kwargs)
70
+
71
+ def _ui(self):
72
+ for property_name, property in self.schema.properties.items():
73
+ property_widget = generate_widget(property, self._defs, generate_object=False)
74
+ property_widget.add()
75
+ self._properties[property_name] = property_widget
76
+
77
+ def get_value(self) -> dict[str, Any]:
78
+ return {property_name: property_widget.get_value() for property_name, property_widget in self._properties.items()}
79
+
80
+ def set_value(self, value: dict[str, Any]):
81
+ for property_name, property_widget in self._properties.items():
82
+ property_widget.set_value(value.get(property_name))
83
+
84
+
85
+ class ArrayWidget(Widget):
86
+ def __init__(self, schema, defs, **kwargs):
87
+ super().__init__(schema, defs, **kwargs)
88
+
89
+
90
+ class MultiTypeWidget(Widget):
91
+ def __init__(self, schema, defs, **kwargs):
92
+ self._type_switcher_id = dpg.generate_uuid()
93
+ self._widget: Widget | None = None
94
+ self._widgets: dict[str, Widget] = {}
95
+ for type_schema in schema.anyOf:
96
+ widget = generate_widget(type_schema, defs, generate_object=False)
97
+ self._widgets[widget.schema.type] = widget
98
+
99
+ super().__init__(schema, defs, **kwargs)
100
+
101
+ def _ui(self):
102
+ with dpg.group(horizontal=True):
103
+ dpg.add_text(self.schema.title)
104
+ dpg.add_combo(label="Type",
105
+ tag=self._type_switcher_id,
106
+ items=list(self._widgets.keys()),
107
+ callback=self.switch_value_type,
108
+ no_preview=True
109
+ )
110
+ with dpg.group(indent=25) as self._form:
111
+ for widget in self._widgets.values():
112
+ widget.add(hidden=True)
113
+
114
+ def switch_value_type(self):
115
+ new_type = dpg.get_value(self._type_switcher_id)
116
+ if self._widget is not None:
117
+ self._widget.hide()
118
+ self._widget = self._widgets[new_type]
119
+ self._widget.show()
120
+
121
+ def get_value(self):
122
+ if self._widget:
123
+ return self._widget.get_value()
124
+ else:
125
+ raise DearpyguiFormsError(f"{self.schema.title}: choose value type")
126
+
127
+ def set_value(self, value):
128
+ pass
129
+ # dpg.set_value(self._type_switcher_id, value)
130
+
131
+
132
+ class StringWidget(Widget):
133
+ def __init__(self, schema, defs, **kwargs):
134
+ self._input_id = dpg.generate_uuid()
135
+ super().__init__(schema, defs, **kwargs)
136
+
137
+ def _ui(self):
138
+ dpg.add_input_text(label=self.schema.title, tag=self._input_id)
139
+
140
+ def get_value(self):
141
+ return dpg.get_value(self._input_id)
142
+
143
+ def set_value(self, value):
144
+ dpg.set_value(self._input_id, value)
145
+
146
+
147
+ class IntegerWidget(Widget):
148
+ def __init__(self, schema, defs, **kwargs):
149
+ self._input_id = dpg.generate_uuid()
150
+ super().__init__(schema, defs, **kwargs)
151
+
152
+ def _ui(self):
153
+ dpg.add_input_int(label=self.schema.title, tag=self._input_id)
154
+
155
+ def get_value(self):
156
+ return dpg.get_value(self._input_id)
157
+
158
+ def set_value(self, value):
159
+ dpg.set_value(self._input_id, value)
160
+
161
+
162
+ class NumberWidget(Widget):
163
+ def __init__(self, schema, defs, **kwargs):
164
+ self._input_id = dpg.generate_uuid()
165
+ super().__init__(schema, defs, **kwargs)
166
+
167
+ def _ui(self):
168
+ dpg.add_input_float(label=self.schema.title, tag=self._input_id)
169
+
170
+ def get_value(self):
171
+ return dpg.get_value(self._input_id)
172
+
173
+ def set_value(self, value):
174
+ dpg.set_value(self._input_id, value)
175
+
176
+
177
+ class BooleanWidget(Widget):
178
+ def __init__(self, schema, defs, **kwargs):
179
+ self._checkbox_id = dpg.generate_uuid()
180
+ super().__init__(schema, defs, **kwargs)
181
+
182
+ def _ui(self):
183
+ dpg.add_checkbox(label=self.schema.title, tag=self._checkbox_id)
184
+
185
+ def get_value(self):
186
+ return dpg.get_value(self._checkbox_id)
187
+
188
+ def set_value(self, value: bool):
189
+ dpg.set_value(self._checkbox_id, value)
190
+
191
+ class NoneWidget(Widget):
192
+ def __init__(self, schema, defs, **kwargs):
193
+ self._none_id = dpg.generate_uuid()
194
+ super().__init__(schema, defs, **kwargs)
195
+
196
+ def _ui(self):
197
+ dpg.add_input_text(default_value='<None>', label=self.schema.title, tag=self._none_id, enabled=False)
198
+
199
+ def get_value(self):
200
+ return None
201
+
202
+ def set_value(self, value):
203
+ pass
204
+
205
+
206
+ class ExternalWidget(Widget):
207
+ def __init__(self, schema, defs, **kwargs):
208
+ self._external_id = dpg.generate_uuid()
209
+ self._widget: Widget | None = None
210
+ super().__init__(schema, defs, **kwargs)
211
+
212
+ def _ui(self):
213
+ with dpg.tree_node(label=self.schema.title) as self._form:
214
+ self._edit_button = dpg.add_button(label=f"Edit", callback=self.show_object_form)
215
+
216
+ def show_object_form(self):
217
+ if self._widget is None:
218
+ dpg.delete_item(self._edit_button)
219
+ self._widget = ObjectWidget(self.schema, self._defs)
220
+ self._widget.add(parent=self._form)
221
+
222
+ def get_value(self):
223
+ if self._widget is None:
224
+ raise DearpyguiFormsError(f"{self.schema.title}: set up object.")
225
+ return self._widget.get_value()
226
+
227
+ def set_value(self, value):
228
+ self.show_object_form()
229
+ self._widget.set_value(value)
230
+
231
+
232
+ def generate_widget(json_schema: dict[str, Any], defs: dict[str, Any], generate_object: bool = True, **kwargs) -> Widget:
233
+ schema = PropertySchema(dereference_property(json_schema, defs))
234
+ match schema:
235
+ case PropertySchema(type='object'):
236
+ if generate_object:
237
+ return ObjectWidget(schema, defs, **kwargs)
238
+ else:
239
+ return ExternalWidget(schema, defs, **kwargs)
240
+ case PropertySchema(type='array'):
241
+ raise NotImplementedError("ArrayWidget is not implemented yet")
242
+ return ArrayWidget(schema, defs, **kwargs)
243
+ case PropertySchema(type='string'):
244
+ return StringWidget(schema, defs, **kwargs)
245
+ case PropertySchema(type='integer'):
246
+ return IntegerWidget(schema, defs, **kwargs)
247
+ case PropertySchema(type='number'):
248
+ return NumberWidget(schema, defs, **kwargs)
249
+ case PropertySchema(type='boolean'):
250
+ return BooleanWidget(schema, defs, **kwargs)
251
+ case PropertySchema(type='null'):
252
+ return NoneWidget(schema, defs, **kwargs)
253
+ case PropertySchema(anyOf=types) if len(types) > 0:
254
+ return MultiTypeWidget(schema, defs, **kwargs)
255
+ case _:
256
+ raise ValueError(f"Unsupported schema: {schema}")
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: dearpygui-forms
3
+ Version: 0.2.1
4
+ Summary: Dearpygui extention for autogeneration forms powered by pydantic.
5
+ Project-URL: homepage, https://github.com/vlewicki/dearpygui-forms
6
+ Author-email: Valentin Lewicki <vlewicki@vlewicki.ru>
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: dearpygui>=2.0.0
9
+ Requires-Dist: loguru>=0.7.3
10
+ Requires-Dist: pydantic>=2.11.7
11
+ Description-Content-Type: text/markdown
12
+
13
+ # DearPyGui Forms
14
+ Generate GUI forms for your Pydantic models.
15
+
16
+ ## Features
17
+ - Fill form fields with Pydantic model data
18
+ - Fill from fields with default values
19
+ - Validation on form submission
20
+ - Callbacks for form submission
21
+
22
+
23
+ Currently supported Pydantic fields:
24
+ - str
25
+ - int
26
+ - float
27
+ - bool
28
+ - datetime
29
+ - date
30
+ - time
31
+
32
+ ## Example:
33
+ ```python
34
+ from pprint import pprint
35
+ import dearpygui.dearpygui as dpg
36
+ from pydantic import BaseModel, Field
37
+ from dearpygui_forms import DPGForm
38
+
39
+ class User(BaseModel):
40
+ name: str = Field(default="John Doe", min_length=3)
41
+ age: int = Field(ge=18)
42
+
43
+
44
+ class Storage(BaseModel):
45
+ users: list[User] = []
46
+
47
+ class UserForm(DPGForm, model=User):
48
+ pass
49
+
50
+
51
+ dpg.create_context()
52
+ dpg.create_viewport()
53
+
54
+ store = Storage()
55
+
56
+ with dpg.window(label="User Form"):
57
+ user_form = UserForm(callback=lambda x: store.users.append(x))
58
+ user_form.add()
59
+ dpg.add_button(label="Print Users", callback=lambda: pprint(store.model_dump()))
60
+ dpg.setup_dearpygui()
61
+ dpg.show_viewport()
62
+ dpg.start_dearpygui()
63
+ dpg.destroy_context()
64
+ ```
@@ -0,0 +1,8 @@
1
+ dearpygui_forms/__init__.py,sha256=G_SP5hMol1eeOBLl0Lsphe1yyQ800aaCzI0gp03UagE,104
2
+ dearpygui_forms/dpg_form.py,sha256=ShdPOPKSjOFS2jC2aG1f_8-c3V3Vhc2JJLrPxqBjvJc,1778
3
+ dearpygui_forms/exceptions.py,sha256=3ZEGpeCdkIIpH92yoNu6pVcF6HxyFSuWdjAOwfIdVuU,47
4
+ dearpygui_forms/models.py,sha256=74CL8WvfY5Qrh-Fx1uSmRU-TY33r_LtFtS1unYqWI5M,636
5
+ dearpygui_forms/widgets.py,sha256=shzIP0P4rSM5OJC_LnjtSO-Y50rOH2wMgQNUZ8Te4W0,8254
6
+ dearpygui_forms-0.2.1.dist-info/METADATA,sha256=p9k_2L1e2UAuL5mtQvNj5Hu3laHZA2Iz1VB7x22qgnw,1457
7
+ dearpygui_forms-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ dearpygui_forms-0.2.1.dist-info/RECORD,,
@@ -1,34 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: dearpygui-forms
3
- Version: 0.1.3
4
- Summary: Dearpygui extention for autogeneration forms powered by pydantic.
5
- Author-email: Valentin Lewicki <vlewicki@vlewicki.ru>
6
- Requires-Python: >=3.13
7
- Requires-Dist: dearpygui>=2.0.0
8
- Requires-Dist: loguru>=0.7.3
9
- Requires-Dist: pydantic>=2.11.7
10
- Description-Content-Type: text/markdown
11
-
12
- # Example:
13
- ```python
14
- import dearpygui.dearpygui as dpg
15
- from pydantic import BaseModel
16
- from dearpygui_forms import DPGForm
17
-
18
- class User(BaseModel):
19
- name: str
20
- age: int
21
-
22
- class UserForm(DPGForm):
23
- __pydantic_model__ = User
24
-
25
- dpg.create_context()
26
- dpg.create_viewport()
27
- with dpg.window(label="User Form"):
28
- user_form = UserForm(callback=lambda x: print(x))
29
- user_form.add()
30
- dpg.setup_dearpygui()
31
- dpg.show_viewport()
32
- dpg.start_dearpygui()
33
- dpg.destroy_context()
34
- ```
@@ -1,5 +0,0 @@
1
- dearpygui_forms/__init__.py,sha256=-V6Mirf6zPtqJuPTSVl53fnvIVR-wES0MNG7Ljq_mvo,4706
2
- dearpygui_forms-0.1.3.dist-info/METADATA,sha256=knJqvRYCLJgqa0mjv75VDvze2qWyqiOGEwO1eIsw_rk,819
3
- dearpygui_forms-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
- dearpygui_forms-0.1.3.dist-info/entry_points.txt,sha256=7Iv9K4ckShx5C5O1M7Ta6zoIN9HUBBa7vHllqm2kvDQ,57
5
- dearpygui_forms-0.1.3.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- dearpygui-forms = dearpygui_forms:main