Open Mark90 opened 10 months ago
Hello! Could this issue possibly be expanded to include "wrapped" types for ReadOnly
field?
For instance, our input forms for NSO "dry-runs" in v1.0 of orch-core (before pydantic-forms was the norm) were annotated like this:
class SomeProductInputForm(FormPage):
dry_run_field: LongText = ReadOnly(dry_run_text)
which would appear correctly on the frontend UI as a disabled
"long text" field...
If I change it to use the new style:
class SomeProductInputForm(FormPage):
dry_run_field: ReadOnly(dry_run_text)
it is just a regular, horizontally scrollable text block, and if I attempt other configurations like:
dry_run_field: Annotated[ReadOnly, LongText(dry_run_text)]
# or
dry_run_field: ReadOnly(dry_run_text) = LongText(dry_run_text)
# etc...
These configurations just error out, or result in the ReadOnly
attribute being ignored. I believe maybe there should be a way to annotate something like
dry_run_field: ReadOnly(LongText(dry_run_text))
or similar, in which the _read_only_field
fn merges the json_schema
object to contain both "disabled": true
and "format": "longText"
(or "organisationId"
, etc)
This may be the wrong place to ask this question, but is there already a recommended method to achieve this, and, if not, does this seem like a good idea?
Hey! Sorry for the late reply. Yeah, that is a good suggestion, I'll add it.
The problem is indeed that the LongText json schema should be merged with that of the ReadOnlyField.
This is something that pydantic v2 intentionally doesn't do, because it is not trivial to handle conflicting keys in the schema.
I've written a workaround function somewhere that takes 2 annotated types with json schema's and returns them combined, but I have no idea where 😅 I'll keep looking for it
Found it!
@Sparrow1029 This should do it as a workaround. I recommend to add the code to a separate file
from typing import Annotated, Any, Iterable, get_args
from more_itertools import first
from pydantic import Field
from pydantic.fields import FieldInfo
def _get_field_info_with_schema(type_: Any) -> Iterable[FieldInfo]:
for annotation in get_args(type_):
if isinstance(annotation, FieldInfo) and annotation.json_schema_extra:
yield annotation
def update_json_schema(type_: Any, json_schema: dict[str, Any]) -> Any:
"""Add json_schema to type_'s annotations.
Existing json schema annotations are updated in a dict.update() fashion.
"""
if not (field_info := first(_get_field_info_with_schema(type_), None)):
return Annotated[type_, Field(json_schema_extra=json_schema)]
match field_info.json_schema_extra:
case dict() as existing_schema:
existing_schema.update(json_schema)
case _ as unknown:
raise TypeError(f"Cannot update json_schema_extra of type {type(unknown)}")
return type_
def merge_json_schema(target_type: Any, source_type: Any) -> Any:
"""Add json_schema from source_type to target_type."""
if not (source_field_info := first(_get_field_info_with_schema(source_type), None)):
raise TypeError("Source type has no json_schema_extra")
match source_field_info.json_schema_extra:
case dict() as existing_source_schema:
return update_json_schema(target_type, existing_source_schema)
case _ as unknown:
raise TypeError(f"Cannot merge source_type json_schema_extra from type {type(unknown)}")
This is an example usage;
class SomeProductInputForm(FormPage):
dry_run_field: merge_json_schema(ReadOnlyField(dry_run_text), LongText) # type: ignore[valid-type]
The # type: ignore
is only needed if you're using a type checker like mypy.
@Mark90 Thanks for the code snippet! That will definitely work for my use case 👍
Tasks
_read_only_list
a separate type with generic type.https://github.com/workfloworchestrator/pydantic-forms/pull/14#discussion_r1415813050_
https://github.com/workfloworchestrator/pydantic-forms/pull/14#discussion_r1415813304_
json_schema_extra
of the read only field itself and the wrapped annotated type