Skip to content

Commit 413d933

Browse files
committed
Update test coverage. move merge_json_schema
1 parent 2b7551b commit 413d933

File tree

3 files changed

+66
-42
lines changed

3 files changed

+66
-42
lines changed

pydantic_forms/utils/json.py

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,11 @@
8181
from datetime import datetime
8282
from functools import partial
8383
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network
84-
from typing import Annotated, Any, Iterable, Sequence, Union, get_args
84+
from typing import Any, Sequence, Union
8585
from uuid import UUID
8686

8787
import structlog
88-
from more_itertools import first
89-
from pydantic import BaseModel, Field
90-
from pydantic.fields import FieldInfo
88+
from pydantic import BaseModel
9189

9290
try:
9391
import orjson
@@ -235,35 +233,3 @@ def isoformat(dt: datetime) -> str:
235233
"""
236234
# IMPORTANT should the format be ever changed, be sure to update TIMESTAMP_REGEX as well!
237235
return dt.isoformat(timespec="seconds")
238-
239-
240-
# Helper utils
241-
def _get_field_info_with_schema(type_: Any) -> Iterable[FieldInfo]:
242-
for annotation in get_args(type_):
243-
if isinstance(annotation, FieldInfo) and annotation.json_schema_extra:
244-
yield annotation
245-
246-
247-
def update_json_schema(type_: Any, json_schema: dict[str, Any]) -> Any:
248-
"""Add json_schema to type_'s annotations.
249-
250-
Existing json schema annotations are updated in a dict.update() fashion.
251-
"""
252-
if not (field_info := first(_get_field_info_with_schema(type_), None)):
253-
return Annotated[type_, Field(json_schema_extra=json_schema)]
254-
255-
if isinstance((existing_schema := field_info.json_schema_extra), dict):
256-
existing_schema.update(json_schema)
257-
else:
258-
raise TypeError(f"Cannot update json_schema_extra of type {type(existing_schema)}")
259-
return type_
260-
261-
262-
def merge_json_schema(target_type: Any, source_type: Any) -> Any:
263-
"""Add json_schema from source_type to target_type."""
264-
if not (source_field_info := first(_get_field_info_with_schema(source_type), None)):
265-
raise TypeError("Source type has no json_schema_extra")
266-
267-
if isinstance((existing_source_schema := source_field_info.json_schema_extra), dict):
268-
return update_json_schema(target_type, existing_source_schema)
269-
raise TypeError(f"Cannot merge source_type json_schema_extra from type {type(existing_source_schema)}")

pydantic_forms/validators/components/read_only.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
import sys
44
from itertools import chain
5-
from typing import Annotated, Any, Literal
5+
from typing import Annotated, Any, Iterable, Literal, get_args
66
from uuid import UUID
77

8+
from more_itertools import first
89
from pydantic import AfterValidator, BeforeValidator, Field, PlainSerializer, TypeAdapter
10+
from pydantic.fields import FieldInfo
911

1012
from pydantic_forms.types import strEnum
11-
from pydantic_forms.utils.json import merge_json_schema
1213

1314
# from pydantic.json_schema
1415
JSON_SCHEMA_TYPES = {
@@ -76,6 +77,30 @@ def to_value(item: Any) -> Any:
7677
]
7778

7879

80+
# Helper utils
81+
def _get_field_info_with_schema(type_: Any) -> Iterable[FieldInfo]:
82+
for annotation in get_args(type_):
83+
if isinstance(annotation, FieldInfo) and annotation.json_schema_extra:
84+
yield annotation
85+
86+
87+
def merge_json_schema(source_type: Any, target_type: Any) -> Any:
88+
"""Add json_schema from source_type to target_type."""
89+
if not (source_field_info := first(_get_field_info_with_schema(source_type), None)):
90+
raise TypeError("Source type has no json_schema_extra")
91+
if not (target_field_info := first(_get_field_info_with_schema(target_type), None)):
92+
raise TypeError("Target type has no json_schema_extra")
93+
source_schema = source_field_info.json_schema_extra
94+
target_schema = target_field_info.json_schema_extra
95+
96+
if not isinstance(source_schema, dict) or not isinstance(target_schema, dict):
97+
raise TypeError(
98+
f"Cannot merge json_schema_extra of source_type {type(source_schema)} with target_type {type(target_type)}"
99+
)
100+
source_schema.update(target_schema)
101+
return source_type
102+
103+
79104
def read_only_field(default: Any, merge_type: Any | None = None) -> Any:
80105
"""Create type with json schema that sets frontend form field to active=false.
81106
@@ -112,7 +137,6 @@ def serialize_json(_v: Any) -> str:
112137
BeforeValidator(validate),
113138
PlainSerializer(serialize_json, when_used="json"),
114139
]
115-
try:
140+
if merge_type is not None:
116141
return merge_json_schema(read_only_type, merge_type)
117-
except TypeError:
118-
return read_only_type
142+
return read_only_type

tests/unit_tests/test_read_only_field.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import json
22
from uuid import UUID
33

4+
from more_itertools import first
5+
from pydantic.config import JsonDict
46
import pytest
57
from pydantic import BaseModel, ValidationError
68

79
from pydantic_forms.core import FormPage
810
from pydantic_forms.types import strEnum
911
from pydantic_forms.validators import read_only_field, read_only_list, LongText, OrganisationId
12+
from pydantic_forms.validators.components.read_only import merge_json_schema, _get_field_info_with_schema
1013

1114

1215
class TestEnum(strEnum):
@@ -150,7 +153,6 @@ class Form(FormPage):
150153
"type": "object",
151154
"properties": {
152155
"read_only_list": {
153-
# "const": schema_value,
154156
"default": schema_value,
155157
"items": expected_item_type,
156158
"title": "Read Only List",
@@ -209,3 +211,35 @@ class OrgIdForm(FormPage):
209211
org_id_read_only = org_id_validated.model_json_schema()["properties"]["read_only"]
210212
assert org_id_read_only["format"] == "organisationId"
211213
assert org_id_read_only["uniforms"]["disabled"]
214+
215+
216+
def test_read_only_unsupported_type():
217+
with pytest.raises(TypeError, match="^Cannot make a read_only_field for type"):
218+
219+
class Form(FormPage):
220+
read_only: read_only_field({"value": 42})
221+
222+
223+
def test_read_only_merge_json_schema_fails_without_json_schema_extra():
224+
with pytest.raises(TypeError, match="Target type has no json_schema_extra"):
225+
226+
class Form(FormPage):
227+
read_only: read_only_field(True, merge_type=JsonDict)
228+
229+
230+
def test_merge_json_schema():
231+
with pytest.raises(TypeError, match="Source type has no json_schema_extra"):
232+
merge_json_schema("text", LongText)
233+
234+
with pytest.raises(TypeError, match="Target type has no json_schema_extra"):
235+
merge_json_schema(OrganisationId, test_uuid1)
236+
237+
# TODO: This may be a test for a contrived error -- not sure if it's possible to hit the block
238+
# this is testing without messing things up pretty bad as shown here
239+
with pytest.raises(TypeError, match="^Cannot merge.*"):
240+
longtext = LongText
241+
org_id = OrganisationId
242+
for typ in (longtext, org_id):
243+
field_info = first(_get_field_info_with_schema(typ))
244+
field_info.json_schema_extra = "wrong"
245+
merge_json_schema(org_id, longtext)

0 commit comments

Comments
 (0)