mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-12-11 19:56:03 +00:00
fix(schema): add Sequence type support to schema generator
Added support for collections.abc.Sequence types in the schema generator to fix OpenAPI spec generation after changing API types from list to Sequence. Changes: - Added is_generic_sequence() and unwrap_generic_sequence() helper functions in inspection.py - Updated python_type_to_name() in name.py to handle Sequence types (treats them as List for schema purposes) - Fixed force parameter propagation in recursive python_type_to_name() calls to ensure unions within generic types are handled correctly - Updated docstring_to_schema() to call python_type_to_name() with force=True - Regenerated OpenAPI specs with updated type handling This enables using Sequence instead of list in API definitions while maintaining schema generation compatibility.
This commit is contained in:
parent
aba98f49db
commit
35e251090b
3 changed files with 43 additions and 7 deletions
|
|
@ -430,6 +430,32 @@ def _unwrap_generic_list(typ: type[list[T]]) -> type[T]:
|
||||||
return list_type # type: ignore[no-any-return]
|
return list_type # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
|
def is_generic_sequence(typ: object) -> bool:
|
||||||
|
"True if the specified type is a generic Sequence, i.e. `Sequence[T]`."
|
||||||
|
import collections.abc
|
||||||
|
|
||||||
|
typ = unwrap_annotated_type(typ)
|
||||||
|
return typing.get_origin(typ) is collections.abc.Sequence
|
||||||
|
|
||||||
|
|
||||||
|
def unwrap_generic_sequence(typ: object) -> type:
|
||||||
|
"""
|
||||||
|
Extracts the item type of a Sequence type.
|
||||||
|
|
||||||
|
:param typ: The Sequence type `Sequence[T]`.
|
||||||
|
:returns: The item type `T`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return rewrap_annotated_type(_unwrap_generic_sequence, typ) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
|
def _unwrap_generic_sequence(typ: object) -> type:
|
||||||
|
"Extracts the item type of a Sequence type (e.g. returns `T` for `Sequence[T]`)."
|
||||||
|
|
||||||
|
(sequence_type,) = typing.get_args(typ) # unpack single tuple element
|
||||||
|
return sequence_type # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
def is_generic_set(typ: object) -> TypeGuard[type[set]]:
|
def is_generic_set(typ: object) -> TypeGuard[type[set]]:
|
||||||
"True if the specified type is a generic set, i.e. `Set[T]`."
|
"True if the specified type is a generic set, i.e. `Set[T]`."
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,12 @@ from .inspection import (
|
||||||
TypeLike,
|
TypeLike,
|
||||||
is_generic_dict,
|
is_generic_dict,
|
||||||
is_generic_list,
|
is_generic_list,
|
||||||
|
is_generic_sequence,
|
||||||
is_type_optional,
|
is_type_optional,
|
||||||
is_type_union,
|
is_type_union,
|
||||||
unwrap_generic_dict,
|
unwrap_generic_dict,
|
||||||
unwrap_generic_list,
|
unwrap_generic_list,
|
||||||
|
unwrap_generic_sequence,
|
||||||
unwrap_optional_type,
|
unwrap_optional_type,
|
||||||
unwrap_union_types,
|
unwrap_union_types,
|
||||||
)
|
)
|
||||||
|
|
@ -155,24 +157,28 @@ def python_type_to_name(data_type: TypeLike, force: bool = False) -> str:
|
||||||
if metadata is not None:
|
if metadata is not None:
|
||||||
# type is Annotated[T, ...]
|
# type is Annotated[T, ...]
|
||||||
arg = typing.get_args(data_type)[0]
|
arg = typing.get_args(data_type)[0]
|
||||||
return python_type_to_name(arg)
|
return python_type_to_name(arg, force=force)
|
||||||
|
|
||||||
if force:
|
if force:
|
||||||
# generic types
|
# generic types
|
||||||
if is_type_optional(data_type, strict=True):
|
if is_type_optional(data_type, strict=True):
|
||||||
inner_name = python_type_to_name(unwrap_optional_type(data_type))
|
inner_name = python_type_to_name(unwrap_optional_type(data_type), force=True)
|
||||||
return f"Optional__{inner_name}"
|
return f"Optional__{inner_name}"
|
||||||
elif is_generic_list(data_type):
|
elif is_generic_list(data_type):
|
||||||
item_name = python_type_to_name(unwrap_generic_list(data_type))
|
item_name = python_type_to_name(unwrap_generic_list(data_type), force=True)
|
||||||
|
return f"List__{item_name}"
|
||||||
|
elif is_generic_sequence(data_type):
|
||||||
|
# Treat Sequence the same as List for schema generation purposes
|
||||||
|
item_name = python_type_to_name(unwrap_generic_sequence(data_type), force=True)
|
||||||
return f"List__{item_name}"
|
return f"List__{item_name}"
|
||||||
elif is_generic_dict(data_type):
|
elif is_generic_dict(data_type):
|
||||||
key_type, value_type = unwrap_generic_dict(data_type)
|
key_type, value_type = unwrap_generic_dict(data_type)
|
||||||
key_name = python_type_to_name(key_type)
|
key_name = python_type_to_name(key_type, force=True)
|
||||||
value_name = python_type_to_name(value_type)
|
value_name = python_type_to_name(value_type, force=True)
|
||||||
return f"Dict__{key_name}__{value_name}"
|
return f"Dict__{key_name}__{value_name}"
|
||||||
elif is_type_union(data_type):
|
elif is_type_union(data_type):
|
||||||
member_types = unwrap_union_types(data_type)
|
member_types = unwrap_union_types(data_type)
|
||||||
member_names = "__".join(python_type_to_name(member_type) for member_type in member_types)
|
member_names = "__".join(python_type_to_name(member_type, force=True) for member_type in member_types)
|
||||||
return f"Union__{member_names}"
|
return f"Union__{member_names}"
|
||||||
|
|
||||||
# named system or user-defined type
|
# named system or user-defined type
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ def get_class_property_docstrings(
|
||||||
def docstring_to_schema(data_type: type) -> Schema:
|
def docstring_to_schema(data_type: type) -> Schema:
|
||||||
short_description, long_description = get_class_docstrings(data_type)
|
short_description, long_description = get_class_docstrings(data_type)
|
||||||
schema: Schema = {
|
schema: Schema = {
|
||||||
"title": python_type_to_name(data_type),
|
"title": python_type_to_name(data_type, force=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
description = "\n".join(filter(None, [short_description, long_description]))
|
description = "\n".join(filter(None, [short_description, long_description]))
|
||||||
|
|
@ -417,6 +417,10 @@ class JsonSchemaGenerator:
|
||||||
if origin_type is list:
|
if origin_type is list:
|
||||||
(list_type,) = typing.get_args(typ) # unpack single tuple element
|
(list_type,) = typing.get_args(typ) # unpack single tuple element
|
||||||
return {"type": "array", "items": self.type_to_schema(list_type)}
|
return {"type": "array", "items": self.type_to_schema(list_type)}
|
||||||
|
elif origin_type is collections.abc.Sequence:
|
||||||
|
# Treat Sequence the same as list for JSON schema (both are arrays)
|
||||||
|
(sequence_type,) = typing.get_args(typ) # unpack single tuple element
|
||||||
|
return {"type": "array", "items": self.type_to_schema(sequence_type)}
|
||||||
elif origin_type is dict:
|
elif origin_type is dict:
|
||||||
key_type, value_type = typing.get_args(typ)
|
key_type, value_type = typing.get_args(typ)
|
||||||
if not (key_type is str or key_type is int or is_type_enum(key_type)):
|
if not (key_type is str or key_type is int or is_type_enum(key_type)):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue