dash-pydantic-form 0.1.1__tar.gz

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.
Files changed (34) hide show
  1. dash_pydantic_form-0.1.1/.gitignore +166 -0
  2. dash_pydantic_form-0.1.1/.pre-commit-config.yaml +15 -0
  3. dash_pydantic_form-0.1.1/.vscode/launch.json +28 -0
  4. dash_pydantic_form-0.1.1/CHANGELOG.md +10 -0
  5. dash_pydantic_form-0.1.1/PKG-INFO +268 -0
  6. dash_pydantic_form-0.1.1/README.md +248 -0
  7. dash_pydantic_form-0.1.1/dash_pydantic_form/__init__.py +24 -0
  8. dash_pydantic_form-0.1.1/dash_pydantic_form/fields/__init__.py +52 -0
  9. dash_pydantic_form-0.1.1/dash_pydantic_form/fields/all_fields.py +47 -0
  10. dash_pydantic_form-0.1.1/dash_pydantic_form/fields/base_fields.py +563 -0
  11. dash_pydantic_form-0.1.1/dash_pydantic_form/fields/editabletable_field.py +430 -0
  12. dash_pydantic_form-0.1.1/dash_pydantic_form/fields/model_fields.py +743 -0
  13. dash_pydantic_form-0.1.1/dash_pydantic_form/form_section.py +36 -0
  14. dash_pydantic_form-0.1.1/dash_pydantic_form/ids.py +31 -0
  15. dash_pydantic_form-0.1.1/dash_pydantic_form/model_form.py +329 -0
  16. dash_pydantic_form-0.1.1/dash_pydantic_form/pydantic_form_scripts.js +254 -0
  17. dash_pydantic_form-0.1.1/dash_pydantic_form/pydantic_form_styles.css +60 -0
  18. dash_pydantic_form-0.1.1/dash_pydantic_form/utils.py +138 -0
  19. dash_pydantic_form-0.1.1/dash_pydantic_form.egg-info/PKG-INFO +268 -0
  20. dash_pydantic_form-0.1.1/dash_pydantic_form.egg-info/SOURCES.txt +32 -0
  21. dash_pydantic_form-0.1.1/dash_pydantic_form.egg-info/dependency_links.txt +1 -0
  22. dash_pydantic_form-0.1.1/dash_pydantic_form.egg-info/requires.txt +11 -0
  23. dash_pydantic_form-0.1.1/dash_pydantic_form.egg-info/top_level.txt +4 -0
  24. dash_pydantic_form-0.1.1/docs/_config.yml +4 -0
  25. dash_pydantic_form-0.1.1/docs/index.md +1 -0
  26. dash_pydantic_form-0.1.1/images/conditionnally-visible-field.gif +0 -0
  27. dash_pydantic_form-0.1.1/images/editable-table.png +0 -0
  28. dash_pydantic_form-0.1.1/images/form-sections.png +0 -0
  29. dash_pydantic_form-0.1.1/images/model-list.png +0 -0
  30. dash_pydantic_form-0.1.1/images/nested-model.png +0 -0
  31. dash_pydantic_form-0.1.1/images/simple-form.png +0 -0
  32. dash_pydantic_form-0.1.1/pyproject.toml +53 -0
  33. dash_pydantic_form-0.1.1/setup.cfg +4 -0
  34. dash_pydantic_form-0.1.1/usage.py +178 -0
@@ -0,0 +1,166 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Terraform - .terraform* matches the local .lock.hcl files and modules that we do not want to export to git
10
+ .terraform*
11
+
12
+ # Distribution / packaging
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ share/python-wheels/
27
+ *.egg-info/
28
+ .installed.cfg
29
+ *.egg
30
+ MANIFEST
31
+
32
+ # PyInstaller
33
+ # Usually these files are written by a python script from a template
34
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
35
+ *.manifest
36
+ *.spec
37
+
38
+ # Installer logs
39
+ pip-log.txt
40
+ pip-delete-this-directory.txt
41
+
42
+ # Unit test / coverage reports
43
+ htmlcov/
44
+ .tox/
45
+ .nox/
46
+ .coverage
47
+ .coverage.*
48
+ .cache
49
+ nosetests.xml
50
+ coverage.xml
51
+ *.cover
52
+ *.py,cover
53
+ .hypothesis/
54
+ .pytest_cache/
55
+ cover/
56
+
57
+ # Translations
58
+ *.mo
59
+ *.pot
60
+
61
+ # Django stuff:
62
+ *.log
63
+ local_settings.py
64
+ db.sqlite3
65
+ db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ instance/
69
+ .webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ .scrapy
73
+
74
+ # Sphinx documentation
75
+ docs/_build/
76
+
77
+ # PyBuilder
78
+ .pybuilder/
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ *.ipynb
83
+ .ipynb_checkpoints
84
+
85
+ # IPython
86
+ profile_default/
87
+ ipython_config.py
88
+
89
+ # pyenv
90
+ # For a library or package, you might want to ignore these files since the code is
91
+ # intended to run in multiple environments; otherwise, check them in:
92
+ # .python-version
93
+
94
+ # pipenv
95
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
96
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
97
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
98
+ # install all needed dependencies.
99
+ #Pipfile.lock
100
+
101
+ # poetry
102
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
103
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
104
+ # commonly ignored for libraries.
105
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
106
+ #poetry.lock
107
+
108
+ # pdm
109
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
110
+ #pdm.lock
111
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
112
+ # in version control.
113
+ # https://pdm.fming.dev/#use-with-ide
114
+ .pdm.toml
115
+
116
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
117
+ __pypackages__/
118
+
119
+ # Celery stuff
120
+ celerybeat-schedule
121
+ celerybeat.pid
122
+
123
+ # SageMath parsed files
124
+ *.sage.py
125
+
126
+ # Environments
127
+ .env
128
+ .venv
129
+ env/
130
+ venv/
131
+ ENV/
132
+ env.bak/
133
+ venv.bak/
134
+
135
+ # Spyder project settings
136
+ .spyderproject
137
+ .spyproject
138
+
139
+ # Rope project settings
140
+ .ropeproject
141
+
142
+ # mkdocs documentation
143
+ /site
144
+
145
+ # mypy
146
+ .mypy_cache/
147
+ .dmypy.json
148
+ dmypy.json
149
+
150
+ # Pyre type checker
151
+ .pyre/
152
+
153
+ # pytype static type analyzer
154
+ .pytype/
155
+
156
+ # Cython debug symbols
157
+ cython_debug/
158
+
159
+ # PyCharm
160
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
161
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
162
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
163
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
164
+ #.idea/
165
+
166
+ .vscode/settings.json
@@ -0,0 +1,15 @@
1
+ # See https://pre-commit.com for more information
2
+ # See https://pre-commit.com/hooks.html for more hooks
3
+ repos:
4
+ - repo: https://github.com/astral-sh/ruff-pre-commit
5
+ rev: v0.4.1
6
+ hooks:
7
+ - id: ruff
8
+ args: [ --fix ]
9
+ - id: ruff-format
10
+
11
+ - repo: https://github.com/pre-commit/pre-commit-hooks
12
+ rev: v4.5.0
13
+ hooks:
14
+ - id: trailing-whitespace
15
+ - id: end-of-file-fixer
@@ -0,0 +1,28 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Python Debugger: Flask",
9
+ "type": "debugpy",
10
+ "request": "launch",
11
+ "module": "flask",
12
+ "env": {
13
+ "FLASK_APP": "usage.py:server",
14
+ "FLASK_DEBUG": "1",
15
+ "REACT_VERSION": "18.2.0",
16
+ "DEBUG": "1",
17
+ },
18
+ "args": [
19
+ "run",
20
+ "--no-debugger",
21
+ "--no-reload"
22
+ ],
23
+ "jinja": true,
24
+ "autoStartBrowser": false,
25
+ "justMyCode": false
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+
8
+ ## [0.1.0] - 2022-10-27
9
+ ### Added
10
+ - Working initial version
@@ -0,0 +1,268 @@
1
+ Metadata-Version: 2.1
2
+ Name: dash-pydantic-form
3
+ Version: 0.1.1
4
+ Summary: Create Dash forms from pydantic objects
5
+ Author-email: Renaud Lainé <renaud.laine@enea-consulting.com>
6
+ License: Copyright ENEA Australia Pty Ltd - All Rights Reserved. Proprietary and confidential
7
+ Project-URL: Source, https://github.com/RenaudLN/dash-pydantic-form
8
+ Requires-Python: <3.11,>=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: dash
11
+ Requires-Dist: dash_mantine_components>=0.14
12
+ Requires-Dist: dash_iconify
13
+ Requires-Dist: dash_ag_grid
14
+ Requires-Dist: pandas
15
+ Requires-Dist: pydantic==2.*
16
+ Requires-Dist: flatdict
17
+ Provides-Extra: dev
18
+ Requires-Dist: pre-commit; extra == "dev"
19
+ Requires-Dist: pytest; extra == "dev"
20
+
21
+ # Dash pydantic form
22
+
23
+ This package allows users to quickly create forms with Plotly Dash based on pydantic models.
24
+
25
+ ## Getting started
26
+
27
+ Create a pydantic model you would like to display a form for.
28
+
29
+ *Note: This package uses pydantic 2.*
30
+
31
+ ```py
32
+ from datetime import date
33
+ from typing import Literal
34
+ from pydantic import BaseModel, Field
35
+
36
+ class Employee(BaseModel):
37
+ first_name: str = Field(title="First name")
38
+ last_name: str = Field(title="Last name")
39
+ office: Literal["au", "uk", "us", "fr"] = Field(title="Office")
40
+ joined: date = Field(title="Employment date")
41
+ ```
42
+
43
+ Then you can get an auto-generated form with `ModelForm`, leveraging [dash-mantine-components](https://dash-mantine-components.com) for form inputs.
44
+
45
+ ```py
46
+ from dash_pydantic_form import ModelForm
47
+
48
+ # somewhere in your layout:
49
+ form = ModelForm(
50
+ Employee,
51
+ aio_id="employees",
52
+ form_id="new_employee",
53
+ )
54
+ ```
55
+
56
+ ![Simple form](images/simple-form.png)
57
+
58
+ You can then retrieve the contents of the whole form at once in a callback as follows
59
+
60
+ ```py
61
+ from dash import Input, Output, callback
62
+
63
+ @callback(
64
+ Output("some-output-id", "some-output-attribute"),
65
+ Input(ModelForm.ids.main("employees", "new_employee"), "data"),
66
+ )
67
+ def use_form_data(form_data: dict):
68
+ try:
69
+ print(Employee(**form_data))
70
+ except ValidationError as exc:
71
+ print("Could not validate form data:")
72
+ print(exc.errors())
73
+ return # ...
74
+ ```
75
+
76
+ ## Customising inputs
77
+
78
+ The `ModelForm` will automaticlly pick which input type to use based on the type annotation for the model field. However, you can customise how each field input is rendered, and or pass additional props to the DMC component.
79
+
80
+ ```py
81
+ from dash_pydantic_form import ModelfForm, fields
82
+
83
+ form = ModelForm(
84
+ Employee,
85
+ aio_id="employees",
86
+ form_id="new_employee",
87
+ fields_repr={
88
+ # Change the default from a Select to Radio items
89
+ # NOTE: `description` can be set on pydantic fields as well
90
+ "office": fields.RadioItems(description="Wich country office?"),
91
+ # Pass additional props to the default input field
92
+ "joined": {"input_kwargs": {"maxDate": "2024-01-01"}},
93
+ },
94
+ )
95
+ ```
96
+
97
+ ### List of current field inputs:
98
+
99
+ Based on DMC:
100
+ * Checkbox
101
+ * Checklist
102
+ * Color
103
+ * Date
104
+ * Json
105
+ * MultiSelect
106
+ * Number
107
+ * Password
108
+ * RadioItems
109
+ * Range
110
+ * SegmentedControl
111
+ * Select
112
+ * Slider
113
+ * Switch
114
+ * Textarea
115
+ * Text
116
+ * Time
117
+
118
+ Custom:
119
+ * EditableTable
120
+ * Model
121
+ * ModelList
122
+
123
+ ## Creating sections
124
+
125
+ There are 2 main avenues to create form sections:
126
+
127
+ ### 1. Create a submodel in one of the model fields
128
+
129
+ ```py
130
+ class HRData(BaseModel):
131
+ office: Literal["au", "uk", "us", "fr"] = Field(title="Office")
132
+ joined: date = Field(title="Employment date")
133
+
134
+ class EmployeeNested(BaseModel):
135
+ first_name: str = Field(title="First name")
136
+ last_name: str = Field(title="Last name")
137
+ hr_data: HRData = Field(title="HR data")
138
+ ```
139
+ ModelForm will then recognise HRData as a pydantic model and use the `fields.Model` to render it, de facto creating a section.
140
+
141
+ ![Nested model](images/nested-model.png)
142
+
143
+ ### 2. Pass sections information to ModelForm
144
+
145
+ ```py
146
+ from dash_pydantic_form import FormSection, ModelForm, Sections
147
+
148
+ form = ModelForm(
149
+ Employee,
150
+ aio_id="employees",
151
+ form_id="new_employee",
152
+ sections=Sections(
153
+ sections=[
154
+ FormSection(name="General", fields=["first_name", "last_name"], default_open=True),
155
+ FormSection(name="HR data", fields=["office", "joined"], default_open=False),
156
+ ],
157
+ # 3 render values are available: accordion, tabs and steps
158
+ render="tabs",
159
+ ),
160
+ )
161
+ ```
162
+
163
+ ![Form sections](images/form-sections.png)
164
+
165
+ ## List of nested models
166
+
167
+ Dash pydantic form also handles lists of nested models with the possibility to add/remove items from the list and edit each one.
168
+
169
+ Let's say we now want to record the employee's pets
170
+
171
+ ### 1. ModelList
172
+
173
+ This creates a list of sub-forms each of which can take similar arguments as a ModelForm (fields_repr, sections).
174
+
175
+ ```py
176
+ class Pet(BaseModel):
177
+ name: str = Field(title="Name")
178
+ species: Literal["cat", "dog"] = Field(title="Species")
179
+ age: int = Field(title="Age")
180
+
181
+ class Employee(BaseModel):
182
+ first_name: str = Field(title="First name")
183
+ last_name: str = Field(title="Last name")
184
+ pets: list[Pet] = Field(title="Pets", default_factory=list)
185
+
186
+ form = ModelForm(
187
+ Employee,
188
+ aio_id="employees",
189
+ form_id="new_employee",
190
+ fields_repr={
191
+ "pets": fields.ModelList(
192
+ fields_repr={
193
+ "species": {"options_labels": {"cat": "Cat", "dog": "Dog"}}
194
+ },
195
+ # 3 render_type options: accordion, list or modal
196
+ render_type="accordion",
197
+ )
198
+ },
199
+ )
200
+ ```
201
+
202
+ ![ModelList](images/model-list.png)
203
+
204
+ ### 2. EditableTable
205
+
206
+ You can also represent the list of sub-models as an ag-grid table with `fields.EditableTable`.
207
+
208
+ ```py
209
+ form = ModelForm(
210
+ Employee,
211
+ aio_id="employees",
212
+ form_id="new_employee",
213
+ fields_repr={
214
+ "pets": fields.EditableTable(
215
+ fields_repr={
216
+ "species": {"options_labels": {"cat": "Cat", "dog": "Dog"}}
217
+ },
218
+ )
219
+ },
220
+ )
221
+ ```
222
+
223
+ ![EditableTable](images/editable-table.png)
224
+
225
+
226
+ ## Make fields conditionnally visible
227
+
228
+ You can make field visibility depend on the value of other fields in the form. To do so, simply pass a `visible` argument to the field.
229
+
230
+ ```py
231
+ class Employee(BaseModel):
232
+ first_name: str
233
+ last_name: str
234
+ only_bob: str | None = Field(
235
+ title="Only for Bobs",
236
+ description="What's your favourite thing about being a Bob?",
237
+ default=None,
238
+ )
239
+
240
+ form = ModelForm(
241
+ Employee,
242
+ aio_id="employees",
243
+ form_id="new_employee",
244
+ fields_repr={
245
+ "only_bob": fields.Textarea(
246
+ visible=("first_name", "==", "Bob"),
247
+ )
248
+ },
249
+ )
250
+ ```
251
+
252
+ ![Conditionnally visible field](images/conditionnally-visible-field.gif)
253
+
254
+ `visible` accepts a boolean, a 3-tuple or list of 3-tuples with format: (field, operator, value). The available operators are:
255
+ * "=="
256
+ * "!="
257
+ * "in"
258
+ * "not in"
259
+ * "array_contains"
260
+ * "array_contains_any"
261
+
262
+ NOTE: The field in the 3-tuples is a ":" separated path relative to the current field's level of nesting. If you need to reference a field from a parent or the root use the special values `_parent_` or `_root_`.
263
+
264
+ E.g., `visible=("_root_:first_name", "==", "Bob")`
265
+
266
+ ## Creating custom fields
267
+
268
+ *To be written*