|
|
from typing import Any, TypedDict |
|
|
|
|
|
from pydantic import BaseModel as PydanticBaseModel |
|
|
from pydantic import ConfigDict, Field, create_model |
|
|
|
|
|
TRUE_VALUES = ["true", "1", "t", "y", "yes"] |
|
|
|
|
|
|
|
|
class SchemaField(TypedDict): |
|
|
name: str |
|
|
type: str |
|
|
description: str |
|
|
multiple: bool |
|
|
|
|
|
|
|
|
class BaseModel(PydanticBaseModel): |
|
|
model_config = ConfigDict(populate_by_name=True) |
|
|
|
|
|
|
|
|
def _get_type_annotation(type_str: str, *, multiple: bool) -> type: |
|
|
type_mapping = { |
|
|
"str": str, |
|
|
"int": int, |
|
|
"float": float, |
|
|
"bool": bool, |
|
|
"boolean": bool, |
|
|
"list": list[Any], |
|
|
"dict": dict[str, Any], |
|
|
"number": float, |
|
|
"text": str, |
|
|
} |
|
|
try: |
|
|
base_type = type_mapping[type_str] |
|
|
except KeyError as e: |
|
|
msg = f"Invalid type: {type_str}" |
|
|
raise ValueError(msg) from e |
|
|
if multiple: |
|
|
return list[base_type] |
|
|
return base_type |
|
|
|
|
|
|
|
|
def build_model_from_schema(schema: list[SchemaField]) -> type[PydanticBaseModel]: |
|
|
fields = {} |
|
|
for field in schema: |
|
|
field_name = field["name"] |
|
|
field_type_str = field["type"] |
|
|
description = field.get("description", "") |
|
|
multiple = field.get("multiple", False) |
|
|
multiple = coalesce_bool(multiple) |
|
|
field_type_annotation = _get_type_annotation(field_type_str, multiple=multiple) |
|
|
fields[field_name] = (field_type_annotation, Field(description=description)) |
|
|
return create_model("OutputModel", **fields) |
|
|
|
|
|
|
|
|
def coalesce_bool(value: Any) -> bool: |
|
|
"""Coalesces the given value into a boolean. |
|
|
|
|
|
Args: |
|
|
value (Any): The value to be coalesced. |
|
|
|
|
|
Returns: |
|
|
bool: The coalesced boolean value. |
|
|
|
|
|
""" |
|
|
if isinstance(value, bool): |
|
|
return value |
|
|
if isinstance(value, str): |
|
|
return value.lower() in TRUE_VALUES |
|
|
if isinstance(value, int): |
|
|
return bool(value) |
|
|
return False |
|
|
|