pydantic-forms 2.4.0__tar.gz → 2.6.0__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 (85) hide show
  1. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/PKG-INFO +39 -95
  2. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/README.md +17 -42
  3. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/__init__.py +3 -1
  4. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/core/asynchronous.py +5 -2
  5. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/core/shared.py +32 -1
  6. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/core/sync.py +5 -2
  7. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/exceptions.py +20 -4
  8. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/__init__.py +3 -0
  9. pydantic_forms-2.6.0/pydantic_forms/validators/components/markdown.py +70 -0
  10. pydantic_forms-2.6.0/pyproject.toml +238 -0
  11. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_core.py +106 -1
  12. pydantic_forms-2.6.0/tests/unit_tests/test_default_factory.py +57 -0
  13. pydantic_forms-2.6.0/tests/unit_tests/test_markdown.py +73 -0
  14. pydantic_forms-2.4.0/.bumpversion.cfg +0 -12
  15. pydantic_forms-2.4.0/.coveragerc +0 -3
  16. pydantic_forms-2.4.0/.github/dependabot.yml +0 -11
  17. pydantic_forms-2.4.0/.github/workflows/publish-release.yaml +0 -25
  18. pydantic_forms-2.4.0/.github/workflows/run-linting-tests.yml +0 -37
  19. pydantic_forms-2.4.0/.github/workflows/run-unit-tests.yml +0 -73
  20. pydantic_forms-2.4.0/.gitignore +0 -130
  21. pydantic_forms-2.4.0/.pre-commit-config.yaml +0 -53
  22. pydantic_forms-2.4.0/.stignore +0 -117
  23. pydantic_forms-2.4.0/codecov.yml +0 -2
  24. pydantic_forms-2.4.0/pyproject.toml +0 -171
  25. pydantic_forms-2.4.0/setup.cfg +0 -61
  26. pydantic_forms-2.4.0/setup.py +0 -20
  27. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/LICENSE +0 -0
  28. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/core/__init__.py +0 -0
  29. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/core/translations.py +0 -0
  30. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/exception_handlers/__init__.py +0 -0
  31. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/exception_handlers/fastapi.py +0 -0
  32. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/py.typed +0 -0
  33. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/types.py +0 -0
  34. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/utils/__init__.py +0 -0
  35. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/utils/json.py +0 -0
  36. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/utils/schema.py +0 -0
  37. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/__init__.py +0 -0
  38. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/accept.py +0 -0
  39. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/callout.py +0 -0
  40. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/choice.py +0 -0
  41. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/choice_list.py +0 -0
  42. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/contact_person.py +0 -0
  43. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/contact_person_list.py +0 -0
  44. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/display_subscription.py +0 -0
  45. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/divider.py +0 -0
  46. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/hidden.py +0 -0
  47. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/label.py +0 -0
  48. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/list_of_one.py +0 -0
  49. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/list_of_two.py +0 -0
  50. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/long_text.py +0 -0
  51. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/migration_summary.py +0 -0
  52. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/organisation_id.py +0 -0
  53. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/read_only.py +0 -0
  54. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/timestamp.py +0 -0
  55. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/components/unique_constrained_list.py +0 -0
  56. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/constants.py +0 -0
  57. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/pydantic_forms/validators/helpers.py +0 -0
  58. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/__init__.py +0 -0
  59. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/__init__.py +0 -0
  60. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/exception_handlers/__init__.py +0 -0
  61. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/exception_handlers/test_fastapi.py +0 -0
  62. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/helpers.py +0 -0
  63. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_accept.py +0 -0
  64. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_callout.py +0 -0
  65. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_choice.py +0 -0
  66. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_choice_list.py +0 -0
  67. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_constrained_list.py +0 -0
  68. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_contact_person.py +0 -0
  69. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_contact_person_list.py +0 -0
  70. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_core_async.py +0 -0
  71. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_display_subscription.py +0 -0
  72. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_divider.py +0 -0
  73. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_hidden.py +0 -0
  74. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_label.py +0 -0
  75. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_list_of_one.py +0 -0
  76. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_list_of_two.py +0 -0
  77. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_long_text.py +0 -0
  78. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_migration_summary.py +0 -0
  79. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_organisation_id.py +0 -0
  80. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_read_only_field.py +0 -0
  81. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_shared.py +0 -0
  82. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_timestamp.py +0 -0
  83. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_utils_dict_without_none.py +0 -0
  84. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_utils_from_serializable.py +0 -0
  85. {pydantic_forms-2.4.0 → pydantic_forms-2.6.0}/tests/unit_tests/test_utils_to_serializable.py +0 -0
@@ -1,23 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-forms
3
- Version: 2.4.0
3
+ Version: 2.6.0
4
4
  Summary: Pydantic-forms engine.
5
- Home-page: https://github.com/workfloworchestrator/pydantic-forms
6
5
  Author: SURF
7
- Author-email: automation-beheer@surf.nl
8
- Requires-Python: >=3.10,<3.15
9
- Description-Content-Type: text/markdown
10
- Classifier: Intended Audience :: Information Technology
11
- Classifier: Intended Audience :: System Administrators
12
- Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python
15
- Classifier: Topic :: Internet
16
- Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
17
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
- Classifier: Topic :: Software Development :: Libraries
19
- Classifier: Topic :: Software Development
20
- Classifier: Typing :: Typed
6
+ Author-email: SURF <automation-beheer@surf.nl>
7
+ License-Expression: Apache-2.0
8
+ License-File: LICENSE
21
9
  Classifier: Development Status :: 4 - Beta
22
10
  Classifier: Environment :: Web Environment
23
11
  Classifier: Framework :: AsyncIO
@@ -25,56 +13,38 @@ Classifier: Framework :: FastAPI
25
13
  Classifier: Framework :: Flask
26
14
  Classifier: Framework :: Pydantic :: 2
27
15
  Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Information Technology
17
+ Classifier: Intended Audience :: System Administrators
28
18
  Classifier: Intended Audience :: Telecommunications Industry
29
- Classifier: License :: OSI Approved :: Apache Software License
19
+ Classifier: Operating System :: OS Independent
30
20
  Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3
31
22
  Classifier: Programming Language :: Python :: 3.10
32
23
  Classifier: Programming Language :: Python :: 3.11
33
24
  Classifier: Programming Language :: Python :: 3.12
34
25
  Classifier: Programming Language :: Python :: 3.13
35
26
  Classifier: Programming Language :: Python :: 3.14
27
+ Classifier: Programming Language :: Python
36
28
  Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
37
29
  Classifier: Topic :: Internet :: WWW/HTTP
38
- License-File: LICENSE
39
- Requires-Dist: more-itertools
30
+ Classifier: Topic :: Internet
31
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
32
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
33
+ Classifier: Topic :: Software Development :: Libraries
34
+ Classifier: Topic :: Software Development
35
+ Classifier: Typing :: Typed
36
+ Requires-Dist: more-itertools>=10.0.0
40
37
  Requires-Dist: pydantic[email]>=2.9.0
41
38
  Requires-Dist: pydantic-i18n==0.4.5
42
- Requires-Dist: toml ; extra == "dev"
43
- Requires-Dist: bumpversion ; extra == "dev"
44
- Requires-Dist: mypy_extensions ; extra == "dev"
45
- Requires-Dist: pre-commit ; extra == "dev"
46
- Requires-Dist: pydocstyle ; extra == "dev"
47
- Requires-Dist: python-dotenv ; extra == "dev"
48
- Requires-Dist: watchdog ; extra == "dev"
49
- Requires-Dist: mkdocs ; extra == "doc"
50
- Requires-Dist: mkdocs-material ; extra == "doc"
51
- Requires-Dist: mkdocs-render-swagger-plugin ; extra == "doc"
52
- Requires-Dist: mkdocs-include-markdown-plugin ; extra == "doc"
53
- Requires-Dist: fastapi >=0.80.0 ; extra == "fastapi"
54
- Requires-Dist: orjson >=3.6.0,<4.0.0 ; extra == "orjson"
55
- Requires-Dist: apache-license-check ; extra == "test"
56
- Requires-Dist: black ; extra == "test"
57
- Requires-Dist: structlog ; extra == "test"
58
- Requires-Dist: jsonref ; extra == "test"
59
- Requires-Dist: mypy==1.18.2 ; extra == "test"
60
- Requires-Dist: pytest ; extra == "test"
61
- Requires-Dist: pytest-cov ; extra == "test"
62
- Requires-Dist: pytest-asyncio ; extra == "test"
63
- Requires-Dist: pytest-xdist ; extra == "test"
64
- Requires-Dist: ruff ; extra == "test"
65
- Requires-Dist: types-Deprecated ; extra == "test"
66
- Requires-Dist: types-certifi ; extra == "test"
67
- Requires-Dist: types-python-dateutil ; extra == "test"
68
- Requires-Dist: types-pytz ; extra == "test"
69
- Requires-Dist: types-toml ; extra == "test"
70
- Requires-Dist: types-ujson ; extra == "test"
71
- Requires-Dist: types-orjson ; extra == "test"
39
+ Requires-Dist: fastapi>=0.103.2 ; extra == 'fastapi'
40
+ Requires-Dist: orjson>=3.10.7,<4.0.0 ; extra == 'orjson'
41
+ Requires-Python: >=3.10, <3.15
72
42
  Project-URL: Documentation, https://github.com/workfloworchestrator/pydantic-forms/blob/main/README.md
73
- Provides-Extra: dev
74
- Provides-Extra: doc
43
+ Project-URL: Homepage, https://github.com/workfloworchestrator/pydantic-forms
44
+ Project-URL: Source, https://github.com/workfloworchestrator/pydantic-forms
75
45
  Provides-Extra: fastapi
76
46
  Provides-Extra: orjson
77
- Provides-Extra: test
47
+ Description-Content-Type: text/markdown
78
48
 
79
49
  # Pydantic forms
80
50
 
@@ -95,71 +65,46 @@ Documentation regarding the usage of Forms can be found
95
65
  [here](https://github.com/workfloworchestrator/orchestrator-core/blob/main/docs/architecture/application/forms-frontend.md)
96
66
 
97
67
  ### Installation (Development standalone)
98
- Install the project and its dependencies to develop on the code.
99
-
100
- #### Step 1 - install flit:
101
68
 
102
- ```shell
103
- python3 -m venv venv
104
- source venv/bin/activate
105
- pip install flit
106
- ```
69
+ This project uses [uv](https://docs.astral.sh/uv/). Install the project and its dependencies to develop on the code:
107
70
 
108
- #### Step 2 - install the development code:
109
71
  ```shell
110
- flit install --deps develop --symlink --python venv/bin/python
111
- ```
112
-
113
- !!! danger
114
- Make sure to use the flit binary that is installed in your environment. You can check the correct
115
- path by running
116
- ```shell
117
- which flit
118
- ```
119
-
120
- To be sure that the packages will be installed against the correct venv you can also prepend the python interpreter
121
- that you want to use:
122
-
123
- ```shell
124
- flit install --deps develop --symlink --python venv/bin/python
72
+ uv sync --all-extras
125
73
  ```
126
74
 
75
+ This creates a `.venv`, installs `pydantic-forms` in editable mode, and installs the `dev` dependency group
76
+ (which chains in `test` and `doc`) plus the `fastapi` and `orjson` extras. There is no need to create or
77
+ activate a virtualenv yourself -- prefix commands with `uv run` and uv will use the right interpreter.
127
78
 
128
79
  ### Running tests
129
80
  Run the unit-test suite to verify a correct setup.
130
81
 
131
- #### Step 2 - Run tests
132
82
  ```shell
133
- pytest tests/unit_tests
83
+ uv run pytest tests/unit_tests
134
84
  ```
135
85
 
136
86
  or with xdist:
137
87
 
138
88
  ```shell
139
- pytest -n auto tests/unit_tests
89
+ uv run pytest -n auto tests/unit_tests
140
90
  ```
141
91
 
142
92
  If you do not encounter any failures in the test, you should be able to develop features in the pydantic-forms.
143
93
 
144
- ### Installation (Development symlinked into project that use pydantic-forms)
94
+ ### Installation (Development version used by a project that depends on pydantic-forms)
145
95
 
146
- If you are working on a project that already uses the `pydantic-forms` and you want to test your new form features
147
- against it, you can use some `flit` magic to symlink the dev version of the forms to your project. It will
148
- automatically replace the pypi dep with a symlink to the development version
149
- of the core and update/downgrade all required packages in your own project.
150
-
151
- #### Step 1 - install flit:
96
+ If you are working on a project that already uses `pydantic-forms` and you want to test your new form features
97
+ against it, point that project at your local checkout. Run this **from the other project**:
152
98
 
153
99
  ```shell
154
- python - m venv venv
155
- source venv/bin/activate
156
- pip install flit
100
+ uv add --editable /path/to/pydantic-forms
157
101
  ```
158
102
 
159
- ### Step 2 - symlink pydantic-forms to your own project
103
+ This replaces the PyPI dependency with an editable install of your working copy and records it under
104
+ `[tool.uv.sources]` in that project's `pyproject.toml`. Undo it with:
160
105
 
161
106
  ```shell
162
- flit install --deps develop --symlink --python /path/to/a/project/venv/bin/python
107
+ uv remove pydantic-forms && uv add pydantic-forms
163
108
  ```
164
109
 
165
110
  # Increasing the version number for a (pre) release.
@@ -169,11 +114,11 @@ When your PR is accepted you will get a version number.
169
114
  You can do the necessary change with a clean, e.g. every change committed, branch:
170
115
 
171
116
  ```shell
172
- bumpversion patch --new-version 0.0.1
117
+ uv version 0.0.1
173
118
  ```
174
119
 
175
- Note: specifying it like this, instead of relying on bumpversion itself to increase the version, allows you to
176
- set a "RC1" version if needed.
120
+ Note: specifying the version explicitly, instead of relying on `uv version --bump patch` to increase it, allows
121
+ you to set a "RC1" version if needed -- e.g. `uv version 0.0.1rc1`.
177
122
 
178
123
  # Debugging Form behaviour
179
124
 
@@ -185,4 +130,3 @@ LOG_LEVEL_PYDANTIC_FORMS=DEBUG
185
130
 
186
131
  This will add the traceback to the `JSONResponse`. If the loglevel is set to DEBUG the library will also add the
187
132
  traceback to the logger.
188
-
@@ -17,71 +17,46 @@ Documentation regarding the usage of Forms can be found
17
17
  [here](https://github.com/workfloworchestrator/orchestrator-core/blob/main/docs/architecture/application/forms-frontend.md)
18
18
 
19
19
  ### Installation (Development standalone)
20
- Install the project and its dependencies to develop on the code.
21
20
 
22
- #### Step 1 - install flit:
21
+ This project uses [uv](https://docs.astral.sh/uv/). Install the project and its dependencies to develop on the code:
23
22
 
24
23
  ```shell
25
- python3 -m venv venv
26
- source venv/bin/activate
27
- pip install flit
28
- ```
29
-
30
- #### Step 2 - install the development code:
31
- ```shell
32
- flit install --deps develop --symlink --python venv/bin/python
33
- ```
34
-
35
- !!! danger
36
- Make sure to use the flit binary that is installed in your environment. You can check the correct
37
- path by running
38
- ```shell
39
- which flit
40
- ```
41
-
42
- To be sure that the packages will be installed against the correct venv you can also prepend the python interpreter
43
- that you want to use:
44
-
45
- ```shell
46
- flit install --deps develop --symlink --python venv/bin/python
24
+ uv sync --all-extras
47
25
  ```
48
26
 
27
+ This creates a `.venv`, installs `pydantic-forms` in editable mode, and installs the `dev` dependency group
28
+ (which chains in `test` and `doc`) plus the `fastapi` and `orjson` extras. There is no need to create or
29
+ activate a virtualenv yourself -- prefix commands with `uv run` and uv will use the right interpreter.
49
30
 
50
31
  ### Running tests
51
32
  Run the unit-test suite to verify a correct setup.
52
33
 
53
- #### Step 2 - Run tests
54
34
  ```shell
55
- pytest tests/unit_tests
35
+ uv run pytest tests/unit_tests
56
36
  ```
57
37
 
58
38
  or with xdist:
59
39
 
60
40
  ```shell
61
- pytest -n auto tests/unit_tests
41
+ uv run pytest -n auto tests/unit_tests
62
42
  ```
63
43
 
64
44
  If you do not encounter any failures in the test, you should be able to develop features in the pydantic-forms.
65
45
 
66
- ### Installation (Development symlinked into project that use pydantic-forms)
67
-
68
- If you are working on a project that already uses the `pydantic-forms` and you want to test your new form features
69
- against it, you can use some `flit` magic to symlink the dev version of the forms to your project. It will
70
- automatically replace the pypi dep with a symlink to the development version
71
- of the core and update/downgrade all required packages in your own project.
46
+ ### Installation (Development version used by a project that depends on pydantic-forms)
72
47
 
73
- #### Step 1 - install flit:
48
+ If you are working on a project that already uses `pydantic-forms` and you want to test your new form features
49
+ against it, point that project at your local checkout. Run this **from the other project**:
74
50
 
75
51
  ```shell
76
- python - m venv venv
77
- source venv/bin/activate
78
- pip install flit
52
+ uv add --editable /path/to/pydantic-forms
79
53
  ```
80
54
 
81
- ### Step 2 - symlink pydantic-forms to your own project
55
+ This replaces the PyPI dependency with an editable install of your working copy and records it under
56
+ `[tool.uv.sources]` in that project's `pyproject.toml`. Undo it with:
82
57
 
83
58
  ```shell
84
- flit install --deps develop --symlink --python /path/to/a/project/venv/bin/python
59
+ uv remove pydantic-forms && uv add pydantic-forms
85
60
  ```
86
61
 
87
62
  # Increasing the version number for a (pre) release.
@@ -91,11 +66,11 @@ When your PR is accepted you will get a version number.
91
66
  You can do the necessary change with a clean, e.g. every change committed, branch:
92
67
 
93
68
  ```shell
94
- bumpversion patch --new-version 0.0.1
69
+ uv version 0.0.1
95
70
  ```
96
71
 
97
- Note: specifying it like this, instead of relying on bumpversion itself to increase the version, allows you to
98
- set a "RC1" version if needed.
72
+ Note: specifying the version explicitly, instead of relying on `uv version --bump patch` to increase it, allows
73
+ you to set a "RC1" version if needed -- e.g. `uv version 0.0.1rc1`.
99
74
 
100
75
  # Debugging Form behaviour
101
76
 
@@ -13,4 +13,6 @@
13
13
 
14
14
  """Pydantic-forms engine."""
15
15
 
16
- __version__ = "2.4.0"
16
+ from importlib.metadata import version
17
+
18
+ __version__ = version("pydantic-forms")
@@ -18,7 +18,7 @@ import structlog
18
18
  from pydantic import ValidationError
19
19
  from pydantic_i18n import PydanticI18n
20
20
 
21
- from pydantic_forms.core.shared import FORMS
21
+ from pydantic_forms.core.shared import FORMS, GenerateFormJsonSchema
22
22
  from pydantic_forms.core.translations import translations
23
23
  from pydantic_forms.exceptions import (
24
24
  FormException,
@@ -102,7 +102,10 @@ async def post_form(
102
102
  return generated_form
103
103
 
104
104
  # Form is not completely filled raise next form
105
- raise FormNotCompleteError(generated_form.model_json_schema(), meta=getattr(generated_form, "meta__", None))
105
+ raise FormNotCompleteError(
106
+ generated_form.model_json_schema(schema_generator=GenerateFormJsonSchema),
107
+ meta=getattr(generated_form, "meta__", None),
108
+ )
106
109
 
107
110
 
108
111
  def _get_form(key: str) -> StateInputFormGeneratorAsync:
@@ -11,10 +11,12 @@
11
11
  # See the License for the specific language governing permissions and
12
12
  # limitations under the License.
13
13
  from inspect import isasyncgenfunction, isgeneratorfunction
14
- from typing import Any, Callable
14
+ from typing import Any, Callable, cast
15
15
 
16
16
  import structlog
17
17
  from pydantic import BaseModel, ConfigDict, PydanticUndefinedAnnotation, version
18
+ from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
19
+ from pydantic_core import core_schema
18
20
 
19
21
  logger = structlog.get_logger(__name__)
20
22
 
@@ -22,6 +24,35 @@ logger = structlog.get_logger(__name__)
22
24
  PYDANTIC_VERSION = version.version_short()
23
25
 
24
26
 
27
+ class GenerateFormJsonSchema(GenerateJsonSchema):
28
+ """JSON schema generator that also emits ``default`` values produced by ``default_factory``.
29
+
30
+ Pydantic intentionally leaves ``default_factory`` results out of the generated JSON schema. For
31
+ forms this is a problem: ``default_factory`` is the recommended way to declare mutable defaults
32
+ (``Field(default_factory=list)`` and friends), and frontends pre-fill fields from the schema
33
+ ``default``. Without it such fields render uninitialised, and dynamic array/object widgets reject
34
+ submission until the user performs a dummy add/remove interaction to materialise an empty value.
35
+
36
+ This generator evaluates argument-less factories and adds their result as the schema ``default``.
37
+ Factories that need the already-validated data (``default_factory_takes_data``) cannot be
38
+ evaluated at schema-generation time and are skipped, as is any factory that raises.
39
+ """
40
+
41
+ def default_schema(self, schema: core_schema.WithDefaultSchema) -> JsonSchemaValue:
42
+ json_schema = super().default_schema(schema)
43
+ if (
44
+ "default" not in json_schema
45
+ and "default_factory" in schema
46
+ and not schema.get("default_factory_takes_data", False)
47
+ ):
48
+ factory = cast(Callable[[], Any], schema["default_factory"])
49
+ try:
50
+ json_schema["default"] = self.encode_default(factory())
51
+ except Exception: # never let a factory break schema generation
52
+ logger.debug("Could not evaluate default_factory for form schema", exc_info=True)
53
+ return json_schema
54
+
55
+
25
56
  class DisplayOnlyFieldType:
26
57
  pass
27
58
 
@@ -18,7 +18,7 @@ import structlog
18
18
  from pydantic import ValidationError
19
19
  from pydantic_i18n import PydanticI18n
20
20
 
21
- from pydantic_forms.core.shared import FORMS
21
+ from pydantic_forms.core.shared import FORMS, GenerateFormJsonSchema
22
22
  from pydantic_forms.core.translations import translations
23
23
  from pydantic_forms.exceptions import FormException, FormNotCompleteError, FormOverflowError, FormValidationError
24
24
  from pydantic_forms.types import InputForm, State, StateInputFormGenerator
@@ -88,7 +88,10 @@ def post_form(
88
88
  generated_form = generator.send(form_validated_data)
89
89
 
90
90
  # Form is not completely filled; raise next form
91
- raise FormNotCompleteError(generated_form.model_json_schema(), meta=getattr(generated_form, "meta__", None))
91
+ raise FormNotCompleteError(
92
+ generated_form.model_json_schema(schema_generator=GenerateFormJsonSchema),
93
+ meta=getattr(generated_form, "meta__", None),
94
+ )
92
95
  except StopIteration as e:
93
96
  if user_inputs:
94
97
  raise FormOverflowError(f"Did not process all user_inputs ({len(user_inputs)} remaining)")
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import traceback
3
+ from collections.abc import Mapping
3
4
  from typing import Any, Iterable, Optional, TypedDict, Union, cast
4
5
 
5
6
  import structlog
@@ -36,9 +37,22 @@ class FormOverflowError(FormException):
36
37
  """Raised when more inputs are provided than the form can process."""
37
38
 
38
39
 
40
+ Loc = tuple[Union[int, str], ...]
41
+
42
+
43
+ def _error_is_nested_object_validator(error: ErrorDetails) -> bool:
44
+ """Return True for custom errors raised for an object payload instead of a leaf field."""
45
+ exc = error.get("ctx", {}).get("error")
46
+ return (
47
+ bool(error["loc"]) and isinstance(exc, (ValueError, AssertionError)) and isinstance(error.get("input"), Mapping)
48
+ )
49
+
50
+
39
51
  # def convert_errors(validation_error: ValidationError) -> Iterable[ErrorDetails]:
40
52
  def convert_errors(
41
- validation_error: ValidationError, tr: PydanticI18n, locale: str = "en_US"
53
+ validation_error: ValidationError,
54
+ tr: PydanticI18n,
55
+ locale: str = "en_US",
42
56
  ) -> Iterable[ErrorDetails]:
43
57
  """Convert Pydantic's error messages to our needs.
44
58
 
@@ -55,6 +69,11 @@ def convert_errors(
55
69
  # Reinstate the "__root__" location for validation errors raised by Pydantic v1's root_validator.
56
70
  # With Pydantic v2's model_validator the location is an empty string, which breaks frontend clients.
57
71
  error["loc"] = ("__root__",)
72
+ elif _error_is_nested_object_validator(error):
73
+ # Errors raised by a model_validator on a nested model point at the nested model itself, which
74
+ # frontend clients cannot map to an input field. Mark them as model-level errors so that clients
75
+ # display the custom message, like they do for the form's own "__root__" errors.
76
+ error["loc"] = (*error["loc"], "__root__")
58
77
  return
59
78
 
60
79
  if LOG_LEVEL_PYDANTIC_FORMS == "DEBUG":
@@ -90,9 +109,6 @@ class FormValidationError(FormException):
90
109
  )
91
110
 
92
111
 
93
- Loc = tuple[Union[int, str], ...]
94
-
95
-
96
112
  class _ErrorDictRequired(TypedDict):
97
113
  loc: Loc
98
114
  msg: str
@@ -11,6 +11,7 @@ from pydantic_forms.validators.components.label import Label
11
11
  from pydantic_forms.validators.components.list_of_one import ListOfOne
12
12
  from pydantic_forms.validators.components.list_of_two import ListOfTwo
13
13
  from pydantic_forms.validators.components.long_text import LongText
14
+ from pydantic_forms.validators.components.markdown import Markdown, markdown
14
15
  from pydantic_forms.validators.components.migration_summary import MigrationSummary, migration_summary
15
16
  from pydantic_forms.validators.components.organisation_id import OrganisationId
16
17
  from pydantic_forms.validators.components.read_only import read_only_field, read_only_list
@@ -43,4 +44,6 @@ __all__ = (
43
44
  "validate_unique_list",
44
45
  "callout",
45
46
  "Callout",
47
+ "markdown",
48
+ "Markdown",
46
49
  )
@@ -0,0 +1,70 @@
1
+ # Copyright 2019-2026 SURF.
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ from enum import Enum
14
+ from functools import partial
15
+ from types import new_class
16
+ from typing import Annotated, Any, ClassVar, TypedDict, Union
17
+
18
+ from pydantic import BaseModel, Field
19
+
20
+
21
+ class MarkdownColor(str, Enum):
22
+ PRIMARY = "primary"
23
+ SUCCESS = "success"
24
+ WARNING = "warning"
25
+ DANGER = "danger"
26
+ ACCENT = "accent"
27
+
28
+
29
+ class MarkdownData(TypedDict, total=False):
30
+ content: Union[str, None]
31
+ color: Union[MarkdownColor, str]
32
+
33
+
34
+ class _Markdown(BaseModel):
35
+ data: ClassVar[Union[MarkdownData, None]] = None
36
+
37
+
38
+ Markdown = Annotated[_Markdown, Field(frozen=True, default=None, validate_default=False)]
39
+
40
+
41
+ def create_markdown_schema(data: MarkdownData, schema: dict[str, Any]) -> None:
42
+ schema.update(
43
+ {
44
+ "format": "markdown",
45
+ "type": "string",
46
+ "default": data,
47
+ }
48
+ )
49
+
50
+
51
+ def markdown(
52
+ *,
53
+ content: Union[str, None] = None,
54
+ color: Union[MarkdownColor, str] = MarkdownColor.PRIMARY,
55
+ ) -> type[Markdown]:
56
+
57
+ data: MarkdownData = {
58
+ "content": content,
59
+ "color": color,
60
+ }
61
+
62
+ namespace = {"data": data}
63
+ klass: type[Markdown] = new_class("MarkdownValue", (_Markdown,), {}, lambda ns: ns.update(namespace))
64
+
65
+ json_schema_extra = partial(create_markdown_schema, data)
66
+
67
+ return Annotated[
68
+ klass,
69
+ Field(frozen=True, default=None, validate_default=False, json_schema_extra=json_schema_extra),
70
+ ] # type: ignore