diff --git a/src/openai/_models.py b/src/openai/_models.py index af71a91850..9f77d5aa74 100644 --- a/src/openai/_models.py +++ b/src/openai/_models.py @@ -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) @@ -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