Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 54 additions & 56 deletions src/openai/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,35 +504,51 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]
origin = get_origin(type_) or type_
args = get_args(type_)

if is_union(origin):
# --- OPTIMIZED: local function & constants cache for hotpaths ---
isunion = is_union
isnumeric = isinstance
isdict = is_mapping
islst = is_list
iscls = inspect.isclass
issubcls = issubclass
build_discriminated = _build_discriminated_union_meta

# Fast check for totally common paths before entering costly blocks
if origin == float and isinstance(value, int):
coerced = float(value)
if coerced != value:
return value
return coerced

if type_ == datetime:
try:
return parse_datetime(value) # type: ignore
except Exception:
return value

if type_ == date:
try:
return parse_date(value) # type: ignore
except Exception:
return value

# --- End OPTIMIZED fast numeric & datetime path ---

if isunion(origin):
try:
return validate_type(type_=cast("type[object]", original_type or type_), value=value)
except Exception:
pass

# if the type is a discriminated union then we want to construct the right variant
# in the union, even if the data doesn't match exactly, otherwise we'd break code
# that relies on the constructed class types, e.g.
#
# class FooType:
# kind: Literal['foo']
# value: str
#
# class BarType:
# kind: Literal['bar']
# value: int
#
# without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then
# we'd end up constructing `FooType` when it should be `BarType`.
discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta)
if discriminator and is_mapping(value):
discriminator = build_discriminated(union=type_, meta_annotations=meta)
if discriminator and isdict(value):
variant_value = value.get(discriminator.field_alias_from or discriminator.field_name)
if variant_value and isinstance(variant_value, str):
variant_type = discriminator.mapping.get(variant_value)
if variant_type:
return construct_type(type_=variant_type, value=value)

# if the data is not valid, use the first variant that doesn't fail while deserializing
# Try variants in order, return on first non-Exception
for variant in args:
try:
return construct_type(value=value, type_=variant)
Expand All @@ -542,53 +558,35 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]
raise RuntimeError(f"Could not convert data into a valid instance of {type_}")

if origin == dict:
if not is_mapping(value):
if not isdict(value):
return value

_, items_type = get_args(type_) # Dict[_, items_type]
return {key: construct_type(value=item, type_=items_type) for key, item in value.items()}
key_type, items_type = args # Dict[key_type, items_type]
# Optimize with generator to list comprehension, reduce function call overhead
construct = construct_type
return {key: construct(value=item, type_=items_type) for key, item in value.items()}

if (
not is_literal_type(type_)
and inspect.isclass(origin)
and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel))
):
if is_list(value):
return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value]
if not is_literal_type(type_) and iscls(origin) and (issubcls(origin, BaseModel) or issubcls(origin, GenericModel)):
if islst(value):
typ = type_
# Optimize check for mapping once in a tuple unpack
typ_cast = cast(Any, typ)
return [typ_cast.construct(**entry) if isdict(entry) else entry for entry in value]

if is_mapping(value):
if issubclass(type_, BaseModel):
return type_.construct(**value) # type: ignore[arg-type]

return cast(Any, type_).construct(**value)
if isdict(value):
typ = type_
if issubcls(typ, BaseModel):
return typ.construct(**value) # type: ignore[arg-type]
return cast(Any, typ).construct(**value)

if origin == list:
if not is_list(value):
if not islst(value):
return value

inner_type = args[0] # List[inner_type]
return [construct_type(value=entry, type_=inner_type) for entry in value]

if origin == float:
if isinstance(value, int):
coerced = float(value)
if coerced != value:
return value
return coerced

return value

if type_ == datetime:
try:
return parse_datetime(value) # type: ignore
except Exception:
return value

if type_ == date:
try:
return parse_date(value) # type: ignore
except Exception:
return value
construct = construct_type
# Avoiding late binding by alias
return [construct(value=entry, type_=inner_type) for entry in value]

return value

Expand Down