diff --git a/.github/workflows/python_agent_sdk_build_and_test.yml b/.github/workflows/python_agent_sdk_build_and_test.yml index fad8fee4e..815d86454 100644 --- a/.github/workflows/python_agent_sdk_build_and_test.yml +++ b/.github/workflows/python_agent_sdk_build_and_test.yml @@ -69,6 +69,10 @@ jobs: working-directory: agent_sdks/python/a2ui_core run: uv run --with pytest pytest tests/ + - name: Typecheck Core + working-directory: agent_sdks/python/a2ui_core + run: uv run mypy src/ tests/ + - name: Run conformance YAML validation working-directory: agent_sdks/conformance run: uv run pytest diff --git a/agent_sdks/python/a2ui_core/pyproject.toml b/agent_sdks/python/a2ui_core/pyproject.toml index c9d92f9bb..54a298638 100644 --- a/agent_sdks/python/a2ui_core/pyproject.toml +++ b/agent_sdks/python/a2ui_core/pyproject.toml @@ -28,6 +28,8 @@ dependencies = [ dev = [ "pytest>=8.0.0", "pyink>=24.10.0", + "mypy>=1.14.0", + "types-jsonschema>=4.26.0", ] [build-system] @@ -40,3 +42,17 @@ packages = ["src/a2ui"] [[tool.uv.index]] url = "https://pypi.org/simple" default = true + +[tool.mypy] +mypy_path = ["src", "scripts"] +explicit_package_bases = true +strict = true +plugins = [ + "pydantic.mypy" +] + +[tool.pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true +warn_untyped_fields = true diff --git a/agent_sdks/python/a2ui_core/scripts/generate_schemas.py b/agent_sdks/python/a2ui_core/scripts/generate_schemas.py index 9e7fa27fc..0e6f03734 100644 --- a/agent_sdks/python/a2ui_core/scripts/generate_schemas.py +++ b/agent_sdks/python/a2ui_core/scripts/generate_schemas.py @@ -15,7 +15,7 @@ import json import os import re -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional # Base directories SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -86,16 +86,18 @@ def map_json_type_to_python(prop_name: str, prop: Dict[str, Any]) -> str: return f"Literal[{cval}]" if "$ref" in prop: - ref = prop["$ref"] + ref = str(prop["$ref"]) if "common_types.json" in ref: ref_name = ref.split("/")[-1] - return ref_name + return str(ref_name) elif ref.startswith("#/"): - return ref.split("/")[-1] + return str(ref.split("/")[-1]) return "Any" if "oneOf" in prop or "anyOf" in prop: union_items = prop.get("oneOf") or prop.get("anyOf") + if not union_items: + return "Any" mapped_items = [] for item in union_items: mapped = map_json_type_to_python(prop_name, item) @@ -227,7 +229,7 @@ def compile_component_to_pydantic( name: str, schema: Dict[str, Any], base_class: str = "ComponentCommon", - common_data: Dict[str, Any] = None, + common_data: Optional[Dict[str, Any]] = None, ) -> str: """Generates Python Pydantic class string representing one Component.""" lines = [ @@ -264,7 +266,7 @@ def compile_component_to_pydantic( def compile_object_def( - class_name: str, spec: Dict[str, Any], base_class: str = None + class_name: str, spec: Dict[str, Any], base_class: Optional[str] = None ) -> str: """Generates Python Pydantic class representing a standard JSON Schema object definition.""" add_props = spec.get("additionalProperties", False) @@ -422,7 +424,7 @@ def generate_common_types(common_data: Dict[str, Any]) -> str: def generate_basic_catalog_components( - catalog_data: Dict[str, Any], common_data: Dict[str, Any] = None + catalog_data: Dict[str, Any], common_data: Optional[Dict[str, Any]] = None ) -> tuple[str, List[str]]: """Generates components.py containing all component schemas extending CatalogComponentCommon.""" global ALLOW_INLINE_COMPILATION @@ -616,7 +618,9 @@ def generate_server_to_client(s2c_data: Dict[str, Any]) -> tuple[str, List[str]] f', alias="{envelope_key}"' if snake_envelope != envelope_key else "" ) output.append(f"class {mname}(StrictBaseModel):") - output.append(f" version: Literal[SPEC_VERSION] = SPEC_VERSION") + output.append( + f' version: Literal["{SPEC_VERSION_DOT}"] = "{SPEC_VERSION_DOT}"' + ) output.append(f" {snake_envelope}: {payload_name} = Field(...{alias_opt})") output.append("\n") @@ -663,7 +667,7 @@ def generate_client_capabilities(capabilities_data: Dict[str, Any]) -> str: output.append("class A2uiClientCapabilities(StrictBaseModel):") output.append( - " v0_9: Optional[V09Capabilities] = Field(None, alias=SPEC_VERSION)" + f' v0_9: Optional[V09Capabilities] = Field(None, alias="{SPEC_VERSION_DOT}")' ) code = "\n".join(output) @@ -702,12 +706,12 @@ def generate_client_to_server(c2s_data: Dict[str, Any]) -> str: output.append(f"A2uiClientError = Union[{', '.join(error_class_names)}]\n") output.append("class A2uiClientActionMessage(StrictBaseModel):") - output.append(f" version: Literal[SPEC_VERSION] = SPEC_VERSION") + output.append(f' version: Literal["{SPEC_VERSION_DOT}"] = "{SPEC_VERSION_DOT}"') output.append(" action: A2uiClientAction = Field(...)") output.append("\n") output.append("class A2uiClientErrorMessage(StrictBaseModel):") - output.append(f" version: Literal[SPEC_VERSION] = SPEC_VERSION") + output.append(f' version: Literal["{SPEC_VERSION_DOT}"] = "{SPEC_VERSION_DOT}"') output.append(" error: A2uiClientError = Field(...)") output.append("\n") @@ -717,7 +721,7 @@ def generate_client_to_server(c2s_data: Dict[str, Any]) -> str: # Client Data Model output.append("class A2uiClientDataModel(StrictBaseModel):") - output.append(f" version: Literal[SPEC_VERSION] = SPEC_VERSION") + output.append(f' version: Literal["{SPEC_VERSION_DOT}"] = "{SPEC_VERSION_DOT}"') output.append( ' surfaces: Dict[str, Dict[str, Any]] = Field(..., description="A map of surface IDs to their current data models.")\n' ) @@ -769,7 +773,7 @@ def generate_schema_init(msg_names: List[str]) -> str: return "\n".join(output) -def main(): +def main() -> None: print("Compiling modular and symmetrical A2UI schemas mirroring web_core...") os.makedirs(SCHEMA_DIR, exist_ok=True) diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/__init__.py b/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/__init__.py index 938ae5554..bd0d73a26 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/__init__.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/__init__.py @@ -13,59 +13,59 @@ # limitations under the License. from .components import ( - AudioPlayerComponent, - ButtonComponent, - CardComponent, - CheckBoxComponent, - ChoicePickerComponent, - ColumnComponent, - DateTimeInputComponent, - DividerComponent, - IconComponent, - ImageComponent, - ListComponent, - ModalComponent, - RowComponent, - SliderComponent, - TabsComponent, - TextComponent, - TextFieldComponent, - VideoComponent, - AnyComponent, + AudioPlayerComponent as AudioPlayerComponent, + ButtonComponent as ButtonComponent, + CardComponent as CardComponent, + CheckBoxComponent as CheckBoxComponent, + ChoicePickerComponent as ChoicePickerComponent, + ColumnComponent as ColumnComponent, + DateTimeInputComponent as DateTimeInputComponent, + DividerComponent as DividerComponent, + IconComponent as IconComponent, + ImageComponent as ImageComponent, + ListComponent as ListComponent, + ModalComponent as ModalComponent, + RowComponent as RowComponent, + SliderComponent as SliderComponent, + TabsComponent as TabsComponent, + TextComponent as TextComponent, + TextFieldComponent as TextFieldComponent, + VideoComponent as VideoComponent, + AnyComponent as AnyComponent, ) from .function_apis import ( - RequiredApi, - RegexApi, - LengthApi, - NumericApi, - EmailApi, - FormatStringApi, - FormatNumberApi, - FormatCurrencyApi, - FormatDateApi, - PluralizeApi, - OpenUrlApi, - AndApi, - OrApi, - NotApi, + RequiredApi as RequiredApi, + RegexApi as RegexApi, + LengthApi as LengthApi, + NumericApi as NumericApi, + EmailApi as EmailApi, + FormatStringApi as FormatStringApi, + FormatNumberApi as FormatNumberApi, + FormatCurrencyApi as FormatCurrencyApi, + FormatDateApi as FormatDateApi, + PluralizeApi as PluralizeApi, + OpenUrlApi as OpenUrlApi, + AndApi as AndApi, + OrApi as OrApi, + NotApi as NotApi, ) from .operator_apis import ( - AddApi, - SubtractApi, - MultiplyApi, - DivideApi, - EqualsApi, - NotEqualsApi, - GreaterThanApi, - LessThanApi, - ContainsApi, - StartsWithApi, - EndsWithApi, + AddApi as AddApi, + SubtractApi as SubtractApi, + MultiplyApi as MultiplyApi, + DivideApi as DivideApi, + EqualsApi as EqualsApi, + NotEqualsApi as NotEqualsApi, + GreaterThanApi as GreaterThanApi, + LessThanApi as LessThanApi, + ContainsApi as ContainsApi, + StartsWithApi as StartsWithApi, + EndsWithApi as EndsWithApi, ) -from .styles import Theme -from .function_impls import BASIC_FUNCTION_IMPLEMENTATIONS -from ..schema.constants import SPEC_VERSION, SPEC_BASE_URL -from ..catalog import ModelCatalog +from .styles import Theme as Theme +from .function_impls import BASIC_FUNCTION_IMPLEMENTATIONS as BASIC_FUNCTION_IMPLEMENTATIONS +from ..schema.constants import SPEC_VERSION as SPEC_VERSION, SPEC_BASE_URL as SPEC_BASE_URL +from ..catalog import ModelCatalog as ModelCatalog def _basic_catalog_id(spec_version: str) -> str: @@ -74,10 +74,14 @@ def _basic_catalog_id(spec_version: str) -> str: ) +from typing import Dict, Type +from pydantic import BaseModel + + class BasicCatalog(ModelCatalog): - def __init__(self): - components_map = { + def __init__(self) -> None: + components_map: Dict[str, Type[BaseModel]] = { "Text": TextComponent, "Image": ImageComponent, "Icon": IconComponent, diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/expression_parser.py b/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/expression_parser.py index 79e75c30b..a65b024e6 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/expression_parser.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/expression_parser.py @@ -55,7 +55,7 @@ def matches_keyword(self, keyword: str) -> bool: return True return False - def skip_whitespace(self): + def skip_whitespace(self) -> None: while not self.is_at_end() and self.peek().isspace(): self.advance() diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/function_impls.py b/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/function_impls.py index d62e3566f..b2b9175c0 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/function_impls.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/basic_catalog/function_impls.py @@ -74,7 +74,7 @@ def _to_str(val: Any) -> str: RequiredImplementation = create_function_implementation( RequiredApi, - lambda args, context=None, abort_signal=None: _to_bool( + lambda args, context, abort_signal: _to_bool( args.get("value") is not None and args.get("value") != "" and args.get("value") != [] @@ -83,14 +83,14 @@ def _to_str(val: Any) -> str: RegexImplementation = create_function_implementation( RegexApi, - lambda args, context=None, abort_signal=None: bool( + lambda args, context, abort_signal: bool( re.search(_to_str(args.get("pattern", "")), _to_str(args.get("value", ""))) ), ) LengthImplementation = create_function_implementation( LengthApi, - lambda args, context=None, abort_signal=None: ( + lambda args, context, abort_signal: ( ( args.get("min") is None or len(_to_str(args.get("value", ""))) >= int(args["min"]) @@ -104,7 +104,7 @@ def _to_str(val: Any) -> str: NumericImplementation = create_function_implementation( NumericApi, - lambda args, context=None, abort_signal=None: ( + lambda args, context, abort_signal: ( (args.get("min") is None or _to_float(args["value"]) >= _to_float(args["min"])) and ( args.get("max") is None @@ -115,7 +115,7 @@ def _to_str(val: Any) -> str: EmailImplementation = create_function_implementation( EmailApi, - lambda args, context=None, abort_signal=None: bool( + lambda args, context, abort_signal: bool( re.match( r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", _to_str(args.get("value", "")), @@ -124,7 +124,11 @@ def _to_str(val: Any) -> str: ) -def _format_string(args, context=None, abort_signal=None): +def _format_string( + args: Dict[str, Any], + context: Optional[Any] = None, + abort_signal: Optional[Any] = None, +) -> Any: template = args.get("value", "") if not template: return "" @@ -154,7 +158,11 @@ def _format_string(args, context=None, abort_signal=None): ) -def _format_number(args, context=None, abort_signal=None): +def _format_number( + args: Dict[str, Any], + context: Optional[Any] = None, + abort_signal: Optional[Any] = None, +) -> Any: val = _to_float(args.get("value", 0)) decimals = args.get("decimals") grouping = args.get("grouping") @@ -173,7 +181,11 @@ def _format_number(args, context=None, abort_signal=None): ) -def _format_currency(args, context=None, abort_signal=None): +def _format_currency( + args: Dict[str, Any], + context: Optional[Any] = None, + abort_signal: Optional[Any] = None, +) -> Any: val = _to_float(args.get("value", 0)) currency = args.get("currency", "USD") decimals = args.get("decimals") @@ -217,7 +229,11 @@ def _format_currency(args, context=None, abort_signal=None): } -def _format_date(args, context=None, abort_signal=None): +def _format_date( + args: Dict[str, Any], + context: Optional[Any] = None, + abort_signal: Optional[Any] = None, +) -> Any: val = args.get("value") fmt = args.get("format", "yyyy-MM-dd") if not val: @@ -235,7 +251,11 @@ def _format_date(args, context=None, abort_signal=None): FormatDateImplementation = create_function_implementation(FormatDateApi, _format_date) -def _pluralize(args, context=None, abort_signal=None): +def _pluralize( + args: Dict[str, Any], + context: Optional[Any] = None, + abort_signal: Optional[Any] = None, +) -> Any: val = _to_float(args.get("value", 0)) category = "other" if val == 0: @@ -251,30 +271,34 @@ def _pluralize(args, context=None, abort_signal=None): PluralizeImplementation = create_function_implementation(PluralizeApi, _pluralize) OpenUrlImplementation = create_function_implementation( - OpenUrlApi, lambda args, context=None, abort_signal=None: None + OpenUrlApi, lambda args, context, abort_signal: None ) AndImplementation = create_function_implementation( AndApi, - lambda args, context=None, abort_signal=None: all( + lambda args, context, abort_signal: all( _to_bool(v) for v in args.get("values", []) ), ) OrImplementation = create_function_implementation( OrApi, - lambda args, context=None, abort_signal=None: any( + lambda args, context, abort_signal: any( _to_bool(v) for v in args.get("values", []) ), ) NotImplementation = create_function_implementation( NotApi, - lambda args, context=None, abort_signal=None: not _to_bool(args.get("value")), + lambda args, context, abort_signal: not _to_bool(args.get("value")), ) -def _add(args, context=None, abort_signal=None): +def _add( + args: Dict[str, Any], + context: Optional[Any] = None, + abort_signal: Optional[Any] = None, +) -> Any: res = _to_float(args["a"]) + _to_float(args["b"]) return int(res) if res.is_integer() else res @@ -282,7 +306,11 @@ def _add(args, context=None, abort_signal=None): AddImplementation = create_function_implementation(AddApi, _add) -def _subtract(args, context=None, abort_signal=None): +def _subtract( + args: Dict[str, Any], + context: Optional[Any] = None, + abort_signal: Optional[Any] = None, +) -> Any: res = _to_float(args["a"]) - _to_float(args["b"]) return int(res) if res.is_integer() else res @@ -290,7 +318,11 @@ def _subtract(args, context=None, abort_signal=None): SubtractImplementation = create_function_implementation(SubtractApi, _subtract) -def _multiply(args, context=None, abort_signal=None): +def _multiply( + args: Dict[str, Any], + context: Optional[Any] = None, + abort_signal: Optional[Any] = None, +) -> Any: res = _to_float(args["a"]) * _to_float(args["b"]) return int(res) if res.is_integer() else res @@ -298,7 +330,11 @@ def _multiply(args, context=None, abort_signal=None): MultiplyImplementation = create_function_implementation(MultiplyApi, _multiply) -def _divide(args, context=None, abort_signal=None): +def _divide( + args: Dict[str, Any], + context: Optional[Any] = None, + abort_signal: Optional[Any] = None, +) -> Any: a = _to_float(args["a"]) b = _to_float(args["b"]) if b == 0: @@ -317,44 +353,44 @@ def _divide(args, context=None, abort_signal=None): EqualsImplementation = create_function_implementation( EqualsApi, - lambda args, context=None, abort_signal=None: args.get("a") == args.get("b"), + lambda args, context, abort_signal: args.get("a") == args.get("b"), ) NotEqualsImplementation = create_function_implementation( NotEqualsApi, - lambda args, context=None, abort_signal=None: args.get("a") != args.get("b"), + lambda args, context, abort_signal: args.get("a") != args.get("b"), ) GreaterThanImplementation = create_function_implementation( GreaterThanApi, - lambda args, context=None, abort_signal=None: _to_float(args.get("a")) + lambda args, context, abort_signal: _to_float(args.get("a")) > _to_float(args.get("b")), ) LessThanImplementation = create_function_implementation( LessThanApi, - lambda args, context=None, abort_signal=None: _to_float(args.get("a")) + lambda args, context, abort_signal: _to_float(args.get("a")) < _to_float(args.get("b")), ) ContainsImplementation = create_function_implementation( ContainsApi, - lambda args, context=None, abort_signal=None: _to_str(args.get("substring", "")) + lambda args, context, abort_signal: _to_str(args.get("substring", "")) in _to_str(args.get("string", "")), ) StartsWithImplementation = create_function_implementation( StartsWithApi, - lambda args, context=None, abort_signal=None: _to_str( - args.get("string", "") - ).startswith(_to_str(args.get("prefix", ""))), + lambda args, context, abort_signal: _to_str(args.get("string", "")).startswith( + _to_str(args.get("prefix", "")) + ), ) EndsWithImplementation = create_function_implementation( EndsWithApi, - lambda args, context=None, abort_signal=None: _to_str( - args.get("string", "") - ).endswith(_to_str(args.get("suffix", ""))), + lambda args, context, abort_signal: _to_str(args.get("string", "")).endswith( + _to_str(args.get("suffix", "")) + ), ) BASIC_FUNCTION_IMPLEMENTATIONS = [ diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/catalog.py b/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/catalog.py index 84a786f6f..a9c9f8a16 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/catalog.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/catalog.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict, List, Optional, Set, Tuple +from typing import Any, Dict, List, Optional, Set, Tuple, Type class CatalogApi: @@ -56,11 +56,11 @@ class CatalogImplementation(CatalogApi): local function evaluation. """ - def get_component_class(self, comp_type: str) -> Optional[Any]: + def get_component_class(self, comp_type: str) -> Optional[Type[Any]]: """Retrieves the concrete model class representing a component.""" raise NotImplementedError("Subclasses must implement get_component_class()") - def get_function_class(self, func_name: str) -> Optional[Any]: + def get_function_class(self, func_name: str) -> Optional[Type[Any]]: """Retrieves the concrete model class representing a function's schema.""" raise NotImplementedError("Subclasses must implement get_function_class()") @@ -74,7 +74,7 @@ def invoke_function( self, name: str, args: Dict[str, Any], - context: Any = None, + context: Optional[Any] = None, abort_signal: Optional[Any] = None, ) -> Any: """Executes a catalog function dynamically.""" diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/functions.py b/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/functions.py index fc9253b4b..a27b72861 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/functions.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/functions.py @@ -29,15 +29,20 @@ def __init__( return_type: Optional[str] = None, schema: Optional[Type[Any]] = None, ): - self.name = name or getattr(self.__class__, "name", "") - self.return_type = return_type or getattr( - self.__class__, "return_type", getattr(self.__class__, "returnType", "any") + self.name = str(name or getattr(self.__class__, "name", "")) + self.return_type = str( + return_type + or getattr( + self.__class__, + "return_type", + getattr(self.__class__, "returnType", "any"), + ) ) self.schema = schema or getattr( self.__class__, "schema", getattr(self.__class__, "args", None) ) - def __init_subclass__(cls, **kwargs): + def __init_subclass__(cls, **kwargs: Any) -> None: super().__init_subclass__(**kwargs) if not getattr(cls, "name", None): cls.name = getattr(cls, "call", "") @@ -58,7 +63,7 @@ def __init__(self, name: str, return_type: str, schema: Any): def execute( self, args: Dict[str, Any], - context: Any = None, + context: Optional[Any] = None, abort_signal: Optional[Any] = None, ) -> Any: """Executes the functional implementation with validated, coerced arguments.""" @@ -66,13 +71,13 @@ def execute( def create_function_implementation( - api: Any, execute: Callable[[Dict[str, Any], Any, Optional[Any]], Any] + api: Any, execute: Callable[[Dict[str, Any], Optional[Any], Optional[Any]], Any] ) -> FunctionImplementation: """Utility helper to dynamically compose an API specification with an executable closure.""" class DynamicFunctionImplementation(FunctionImplementation): - def __init__(self): + def __init__(self) -> None: # Extract attributes from Api class or Api instance name = getattr(api, "name", getattr(api.__class__, "name", "")) return_type = getattr( @@ -104,7 +109,7 @@ def __init__(self): def execute( self, args: Dict[str, Any], - context: Any = None, + context: Optional[Any] = None, abort_signal: Optional[Any] = None, ) -> Any: return execute(args, context, abort_signal) @@ -124,4 +129,6 @@ def execute( Returns: The result of the function call (e.g. literal, list, dict, or None). """ -FunctionInvoker = Callable[[str, Dict[str, Any], Any, Optional[Any]], Any] +FunctionInvoker = Callable[ + [str, Dict[str, Any], Optional[Dict[str, Any]], Optional[Any]], Any +] diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/json_catalog.py b/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/json_catalog.py index 2c1c5a11a..e86cb8c3c 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/json_catalog.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/json_catalog.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict, List, Optional, Set, Tuple +from typing import Any, Dict, List, Optional, Set, Tuple, cast from ..schema.constants import CATALOG_COMPONENTS_KEY from .catalog import CatalogApi @@ -46,12 +46,12 @@ def __init__( def get_component_schema(self, comp_type: str) -> Optional[Dict[str, Any]]: """Retrieves the raw JSON schema representing a component's properties.""" components = self.catalog_schema.get("components", {}) - return components.get(comp_type) + return cast(Optional[Dict[str, Any]], components.get(comp_type)) def get_function_schema(self, func_name: str) -> Optional[Dict[str, Any]]: """Retrieves the raw JSON schema representing a function's arguments and returnType.""" functions = self.catalog_schema.get("functions", {}) - return functions.get(func_name) + return cast(Optional[Dict[str, Any]], functions.get(func_name)) def get_theme_schema(self) -> Optional[Dict[str, Any]]: return self.catalog_schema.get("theme") @@ -126,7 +126,7 @@ def resolve_ref(schema: Any, visited: Optional[Set[str]] = None) -> Any: single_refs = set() list_refs = set() - def extract_from_props(comp_schema: Dict[str, Any]): + def extract_from_props(comp_schema: Dict[str, Any]) -> Any: if not isinstance(comp_schema, dict): return props = comp_schema.get("properties", {}) diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/model_catalog.py b/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/model_catalog.py index f0f5ad122..0e2e18286 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/model_catalog.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/catalog/model_catalog.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union, get_args, get_origin +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union, get_args, get_origin, cast from pydantic import BaseModel from .catalog import CatalogImplementation -from .functions import FunctionImplementation +from .functions import FunctionImplementation, FunctionApi from ..schema.common_types import ComponentReference, SingleReference, ListReference @@ -27,7 +27,7 @@ def __init__( spec_version: str, catalog_id: str, components: Dict[str, Type[BaseModel]], - functions: Optional[Dict[str, Any]] = None, + functions: Optional[Union[Dict[str, Any], List[Any]]] = None, theme: Optional[Type[BaseModel]] = None, ): super().__init__( @@ -37,9 +37,7 @@ def __init__( self.components = components self.theme = theme - from .functions import FunctionImplementation, FunctionApi - - self.functions: Dict[str, FunctionImplementation] = {} + self.functions: Dict[str, Union[FunctionImplementation, FunctionApi]] = {} if functions: source_dict = ( functions @@ -55,12 +53,17 @@ def __init__( # Coerce Pydantic Model into FunctionImplementation class CoercedFunctionImplementation(FunctionImplementation): - def __init__(self, name_str, schema_class): + def __init__(self, name_str: str, schema_class: Any) -> None: super().__init__( name=name_str, return_type="any", schema=schema_class ) - def execute(self, args, context=None, abort_signal=None): + def execute( + self, + args: Dict[str, Any], + context: Optional[Any] = None, + abort_signal: Optional[Any] = None, + ) -> Any: return None self.functions[name] = CoercedFunctionImplementation(name, fn) @@ -136,11 +139,11 @@ def get_function_class(self, func_name: str) -> Optional[Type[BaseModel]]: ) if fn is not None: if hasattr(fn, "schema"): - return fn.schema + return cast(Type[BaseModel], fn.schema) if isinstance(fn, type) and issubclass(fn, BaseModel): return fn if isinstance(fn, type) and issubclass(fn, FunctionApi): - return fn().schema + return cast(Type[BaseModel], fn().schema) return None def get_component_schema(self, comp_type: str) -> Optional[Dict[str, Any]]: @@ -163,7 +166,10 @@ def get_theme_schema(self) -> Optional[Dict[str, Any]]: def get_function_implementation( self, func_name: str ) -> Optional[FunctionImplementation]: - return self.functions.get(func_name) + fn = self.functions.get(func_name) + if isinstance(fn, FunctionImplementation): + return fn + return None def invoke_function( self, diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/py.typed b/agent_sdks/python/a2ui_core/src/a2ui/core/py.typed new file mode 100644 index 000000000..3b320da1f --- /dev/null +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/py.typed @@ -0,0 +1 @@ +# Type marker diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/schema/__init__.py b/agent_sdks/python/a2ui_core/src/a2ui/core/schema/__init__.py index f6231f1f0..256543c88 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/schema/__init__.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/schema/__init__.py @@ -14,43 +14,43 @@ # Auto-generated. Do not edit manually. from .common_types import ( - StrictBaseModel, - DataBinding, - FunctionCall, - AccessibilityAttributes, - CheckRule, - ActionEvent, - Action, - ComponentCommon, + StrictBaseModel as StrictBaseModel, + DataBinding as DataBinding, + FunctionCall as FunctionCall, + AccessibilityAttributes as AccessibilityAttributes, + CheckRule as CheckRule, + ActionEvent as ActionEvent, + Action as Action, + ComponentCommon as ComponentCommon, ) from .constants import * from .server_to_client import ( - CreateSurfaceMessage, - CreateSurface, - UpdateComponentsMessage, - UpdateComponents, - UpdateDataModelMessage, - UpdateDataModel, - DeleteSurfaceMessage, - DeleteSurface, - A2uiMessage, - A2uiMessageListWrapper, + CreateSurfaceMessage as CreateSurfaceMessage, + CreateSurface as CreateSurface, + UpdateComponentsMessage as UpdateComponentsMessage, + UpdateComponents as UpdateComponents, + UpdateDataModelMessage as UpdateDataModelMessage, + UpdateDataModel as UpdateDataModel, + DeleteSurfaceMessage as DeleteSurfaceMessage, + DeleteSurface as DeleteSurface, + A2uiMessage as A2uiMessage, + A2uiMessageListWrapper as A2uiMessageListWrapper, ) from .client_capabilities import ( - A2uiClientCapabilities, - V09Capabilities, - InlineCatalog, - FunctionDefinition, + A2uiClientCapabilities as A2uiClientCapabilities, + V09Capabilities as V09Capabilities, + InlineCatalog as InlineCatalog, + FunctionDefinition as FunctionDefinition, ) from .client_to_server import ( - A2uiClientMessage, - A2uiClientActionMessage, - A2uiClientErrorMessage, - A2uiClientAction, - A2uiValidationError, - A2uiGenericError, - A2uiClientError, - A2uiClientDataModel, - A2uiClientMessageList, - A2uiClientMessageListWrapper, + A2uiClientMessage as A2uiClientMessage, + A2uiClientActionMessage as A2uiClientActionMessage, + A2uiClientErrorMessage as A2uiClientErrorMessage, + A2uiClientAction as A2uiClientAction, + A2uiValidationError as A2uiValidationError, + A2uiGenericError as A2uiGenericError, + A2uiClientError as A2uiClientError, + A2uiClientDataModel as A2uiClientDataModel, + A2uiClientMessageList as A2uiClientMessageList, + A2uiClientMessageListWrapper as A2uiClientMessageListWrapper, ) diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/schema/client_capabilities.py b/agent_sdks/python/a2ui_core/src/a2ui/core/schema/client_capabilities.py index ddb727984..cae54a6c6 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/schema/client_capabilities.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/schema/client_capabilities.py @@ -66,4 +66,4 @@ class V09Capabilities(StrictBaseModel): class A2uiClientCapabilities(StrictBaseModel): - v0_9: Optional[V09Capabilities] = Field(None, alias=SPEC_VERSION) + v0_9: Optional[V09Capabilities] = Field(None, alias="v0.9") diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/schema/client_to_server.py b/agent_sdks/python/a2ui_core/src/a2ui/core/schema/client_to_server.py index a10d7e757..6a4c3d48e 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/schema/client_to_server.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/schema/client_to_server.py @@ -78,12 +78,12 @@ class A2uiGenericError(BaseModel): class A2uiClientActionMessage(StrictBaseModel): - version: Literal[SPEC_VERSION] = SPEC_VERSION + version: Literal["v0.9"] = "v0.9" action: A2uiClientAction = Field(...) class A2uiClientErrorMessage(StrictBaseModel): - version: Literal[SPEC_VERSION] = SPEC_VERSION + version: Literal["v0.9"] = "v0.9" error: A2uiClientError = Field(...) @@ -91,7 +91,7 @@ class A2uiClientErrorMessage(StrictBaseModel): class A2uiClientDataModel(StrictBaseModel): - version: Literal[SPEC_VERSION] = SPEC_VERSION + version: Literal["v0.9"] = "v0.9" surfaces: Dict[str, Dict[str, Any]] = Field( ..., description="A map of surface IDs to their current data models." ) diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/schema/common_types.py b/agent_sdks/python/a2ui_core/src/a2ui/core/schema/common_types.py index 66fb6a59a..fc2bd19d3 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/schema/common_types.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/schema/common_types.py @@ -24,7 +24,7 @@ class ComponentReference: class SingleReference(str, ComponentReference): @classmethod - def __get_pydantic_core_schema__(cls, source_type, handler): + def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> Any: from pydantic_core import core_schema return core_schema.no_info_after_validator_function( diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/schema/server_to_client.py b/agent_sdks/python/a2ui_core/src/a2ui/core/schema/server_to_client.py index 9d92519de..3f91c7a3d 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/schema/server_to_client.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/schema/server_to_client.py @@ -43,7 +43,7 @@ class CreateSurface(StrictBaseModel): class CreateSurfaceMessage(StrictBaseModel): - version: Literal[SPEC_VERSION] = SPEC_VERSION + version: Literal["v0.9"] = "v0.9" create_surface: CreateSurface = Field(..., alias="createSurface") @@ -59,7 +59,7 @@ class UpdateComponents(StrictBaseModel): class UpdateComponentsMessage(StrictBaseModel): - version: Literal[SPEC_VERSION] = SPEC_VERSION + version: Literal["v0.9"] = "v0.9" update_components: UpdateComponents = Field(..., alias="updateComponents") @@ -80,7 +80,7 @@ class UpdateDataModel(StrictBaseModel): class UpdateDataModelMessage(StrictBaseModel): - version: Literal[SPEC_VERSION] = SPEC_VERSION + version: Literal["v0.9"] = "v0.9" update_data_model: UpdateDataModel = Field(..., alias="updateDataModel") @@ -93,7 +93,7 @@ class DeleteSurface(StrictBaseModel): class DeleteSurfaceMessage(StrictBaseModel): - version: Literal[SPEC_VERSION] = SPEC_VERSION + version: Literal["v0.9"] = "v0.9" delete_surface: DeleteSurface = Field(..., alias="deleteSurface") diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/validating/integrity_checker.py b/agent_sdks/python/a2ui_core/src/a2ui/core/validating/integrity_checker.py index f6be15f67..2b219b470 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/validating/integrity_checker.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/validating/integrity_checker.py @@ -104,7 +104,7 @@ def validate_component_integrity( def validate_recursion_and_paths(data: Any) -> None: - def traverse(item: Any, global_depth: int, func_depth: int): + def traverse(item: Any, global_depth: int, func_depth: int) -> None: if global_depth > MAX_GLOBAL_DEPTH: raise ValueError( f"Global recursion limit exceeded: Depth > {MAX_GLOBAL_DEPTH}" diff --git a/agent_sdks/python/a2ui_core/src/a2ui/core/validating/topology_analyzer.py b/agent_sdks/python/a2ui_core/src/a2ui/core/validating/topology_analyzer.py index cf1521872..f7a088762 100644 --- a/agent_sdks/python/a2ui_core/src/a2ui/core/validating/topology_analyzer.py +++ b/agent_sdks/python/a2ui_core/src/a2ui/core/validating/topology_analyzer.py @@ -48,7 +48,7 @@ def analyze_topology( visited: Set[str] = set() recursion_stack: Set[str] = set() - def dfs(node_id: str, depth: int): + def dfs(node_id: str, depth: int) -> None: if depth > MAX_GLOBAL_DEPTH: raise ValueError( f"Global recursion limit exceeded: logical depth > {MAX_GLOBAL_DEPTH}" diff --git a/agent_sdks/python/a2ui_core/tests/test_catalog.py b/agent_sdks/python/a2ui_core/tests/test_catalog.py index 76c32773f..4b7841633 100644 --- a/agent_sdks/python/a2ui_core/tests/test_catalog.py +++ b/agent_sdks/python/a2ui_core/tests/test_catalog.py @@ -22,7 +22,7 @@ from a2ui.core.schema.constants import SPEC_VERSION -def _val(catalog: CatalogApi) -> CatalogValidator: +def _val(catalog: CatalogApi) -> Any: return CatalogValidator.from_catalog(catalog) @@ -31,7 +31,7 @@ def _val(catalog: CatalogApi) -> CatalogValidator: # ============================================================================== -def test_model_catalog_initialization(): +def test_model_catalog_initialization() -> None: class EmptyModel(BaseModel): pass @@ -44,7 +44,7 @@ class EmptyModel(BaseModel): assert cat.catalog_id == "https://a2ui.org/model-init" -def test_model_catalog_additional_properties_handling(): +def test_model_catalog_additional_properties_handling() -> None: class DefaultBox(BaseModel): component: Literal["DefaultBox"] = "DefaultBox" @@ -81,7 +81,7 @@ class ForbidBox(BaseModel): ) -def test_model_catalog_unevaluated_properties_handling(): +def test_model_catalog_unevaluated_properties_handling() -> None: class DefaultBox(BaseModel): component: Literal["DefaultBox"] = "DefaultBox" @@ -118,7 +118,7 @@ class ForbidBox(BaseModel): ) -def test_model_catalog_validate_theme(): +def test_model_catalog_validate_theme() -> None: class TestTheme(BaseModel): primary: str = Field(..., pattern="^#[0-9A-F]{6}$") @@ -140,7 +140,7 @@ class TestTheme(BaseModel): assert "pattern" in error_msg.lower() or "string" in error_msg.lower() -def test_model_catalog_nested_function_validation(): +def test_model_catalog_nested_function_validation() -> None: class InnerComp(BaseModel): component: Literal["InnerComp"] = "InnerComp" call: str @@ -207,7 +207,7 @@ class CustomFunc(BaseModel): ) -def test_model_catalog_validate_components(): +def test_model_catalog_validate_components() -> None: class ButtonComp(BaseModel): id: str component: Literal["Button"] = "Button" @@ -232,7 +232,7 @@ class ButtonComp(BaseModel): assert "Field required" in error_msg or "missing" in error_msg.lower() -def test_model_catalog_validate_functions(): +def test_model_catalog_validate_functions() -> None: class RegexFunc(BaseModel): call: Literal["regex"] = "regex" args: Dict[str, Any] @@ -252,7 +252,7 @@ class RegexFunc(BaseModel): _val(cat).validate_function("unknownFunc", {}) -def test_model_catalog_custom_reference_fields_coverage(): +def test_model_catalog_custom_reference_fields_coverage() -> None: class CustomLayoutComp(BaseModel): id: str component: Literal["CustomLayout"] = "CustomLayout" @@ -338,7 +338,7 @@ class CustomLayoutComp(BaseModel): _val(catalog).validate_components(orphan_payload) -def test_model_catalog_unrecognized_type_and_mismatched_properties(): +def test_model_catalog_unrecognized_type_and_mismatched_properties() -> None: class CardComp(BaseModel): id: str component: Literal["Card"] = "Card" @@ -388,7 +388,7 @@ class CardComp(BaseModel): # ============================================================================== -def test_json_catalog_initialization(): +def test_json_catalog_initialization() -> None: schema = { "catalogId": "https://a2ui.org/spec/v0.9/catalog.json", "components": { @@ -403,7 +403,7 @@ def test_json_catalog_initialization(): assert catalog.catalog_id == "https://a2ui.org/spec/v0.9/catalog.json" -def test_json_catalog_extract_ref_fields_dynamic_coverage(): +def test_json_catalog_extract_ref_fields_dynamic_coverage() -> None: schema = { "catalogId": "https://a2ui.org/json", "components": { @@ -441,7 +441,7 @@ def test_json_catalog_extract_ref_fields_dynamic_coverage(): assert "regularProp" not in list_refs -def test_json_catalog_extract_ref_fields_tabs(): +def test_json_catalog_extract_ref_fields_tabs() -> None: schema = { "catalogId": "https://a2ui.org/json", "components": { @@ -484,7 +484,7 @@ def test_json_catalog_extract_ref_fields_tabs(): assert "child" not in single -def test_json_catalog_extract_ref_fields_empty_fallback(): +def test_json_catalog_extract_ref_fields_empty_fallback() -> None: schema = { "catalogId": "https://a2ui.org/json", "components": {"EmptyNode": {"type": "object", "properties": {}}}, @@ -494,7 +494,7 @@ def test_json_catalog_extract_ref_fields_empty_fallback(): assert refs == {} -def test_json_catalog_common_types_defs_and_refs_resolution(): +def test_json_catalog_common_types_defs_and_refs_resolution() -> None: common_types = { "$id": "https://a2ui.org/specification/v0_9/common_types.json", "$defs": {"ColorHex": {"type": "string", "pattern": "^#[0-9a-fA-F]{6}$"}}, @@ -552,7 +552,7 @@ def test_json_catalog_common_types_defs_and_refs_resolution(): ) -def test_json_catalog_custom_reference_fields_coverage(): +def test_json_catalog_custom_reference_fields_coverage() -> None: catalog_json = { "catalogId": "https://a2ui.org/custom-json", "components": { @@ -585,7 +585,7 @@ def test_json_catalog_custom_reference_fields_coverage(): assert "slaveNodes" in refs["CustomContainer"][1] -def test_json_catalog_validate_theme(): +def test_json_catalog_validate_theme() -> None: catalog_json = { "catalogId": "https://rizzcharts.com/catalog.json", "theme": { @@ -613,7 +613,7 @@ def test_json_catalog_validate_theme(): _val(catalog).validate_theme({"primaryColor": "red"}) -def test_json_catalog_validate_functions(): +def test_json_catalog_validate_functions() -> None: catalog_json = { "catalogId": "https://rizzcharts.com/catalog.json", "functions": { @@ -653,7 +653,7 @@ def test_json_catalog_validate_functions(): _val(catalog).validate_function("unknownFunc", {}) -def test_json_catalog_nested_function_validation(): +def test_json_catalog_nested_function_validation() -> None: catalog_json = { "catalogId": "https://rizzcharts.com/catalog.json", "components": { @@ -730,7 +730,7 @@ def test_json_catalog_nested_function_validation(): ) -def test_json_catalog_additional_properties_handling(): +def test_json_catalog_additional_properties_handling() -> None: # 1. additionalProperties is not set explicitly (defaults to True) cat_default_json = { "catalogId": "https://a2ui.org/default", @@ -769,7 +769,7 @@ def test_json_catalog_additional_properties_handling(): ) -def test_json_catalog_unevaluated_properties_handling(): +def test_json_catalog_unevaluated_properties_handling() -> None: # 1. unevaluatedProperties with the default settings (omitted/true) cat_default_json = { "catalogId": "https://a2ui.org/unevaluated-default", @@ -834,13 +834,13 @@ def test_json_catalog_unevaluated_properties_handling(): # ============================================================================== -def test_basic_catalog_initialization(): +def test_basic_catalog_initialization() -> None: catalog = BasicCatalog() assert catalog.spec_version == SPEC_VERSION assert "https://a2ui.org/specification" in catalog.catalog_id -def test_basic_catalog_validate_components(): +def test_basic_catalog_validate_components() -> None: catalog = BasicCatalog() # Valid component payload @@ -862,7 +862,7 @@ def test_basic_catalog_validate_components(): _val(catalog).validate_components([invalid_text_comp]) -def test_basic_catalog_validate_theme(): +def test_basic_catalog_validate_theme() -> None: catalog = BasicCatalog() # 1. Test Valid Theme @@ -873,7 +873,7 @@ def test_basic_catalog_validate_theme(): _val(catalog).validate_theme({"primaryColor": "invalid-color-name"}) -def test_basic_catalog_validate_functions(): +def test_basic_catalog_validate_functions() -> None: catalog = BasicCatalog() # 1. Test validate_function Valid @@ -889,7 +889,7 @@ def test_basic_catalog_validate_functions(): _val(catalog).validate_function("unknownFunc", {}) -def test_basic_catalog_nested_function_validation(): +def test_basic_catalog_nested_function_validation() -> None: catalog = BasicCatalog() # 1. Rejects unrecognized nested catalog function call @@ -926,7 +926,7 @@ def test_basic_catalog_nested_function_validation(): ) -def test_basic_catalog_extract_ref_fields(): +def test_basic_catalog_extract_ref_fields() -> None: catalog = BasicCatalog() ref_map = catalog.extract_ref_fields() @@ -941,7 +941,7 @@ def test_basic_catalog_extract_ref_fields(): assert "children" in col_list -def test_basic_catalog_tabs_ref(): +def test_basic_catalog_tabs_ref() -> None: catalog = BasicCatalog() ref_map = catalog.extract_ref_fields() assert "Tabs" in ref_map @@ -949,7 +949,7 @@ def test_basic_catalog_tabs_ref(): assert "tabs" in list_refs -def test_model_catalog_tabs_ref(): +def test_model_catalog_tabs_ref() -> None: class CustomTab(BaseModel): title: str child: ComponentId @@ -969,7 +969,7 @@ class CustomTabsComponent(BaseModel): assert "tabs" in list_refs -def test_json_catalog_custom_tabs_ref(): +def test_json_catalog_custom_tabs_ref() -> None: catalog_schema = { "$defs": { "ComponentId": {"type": "string"}, @@ -1005,7 +1005,7 @@ def test_json_catalog_custom_tabs_ref(): assert "tabs" in list_refs -def test_json_catalog_basic_spec_tabs_ref(): +def test_json_catalog_basic_spec_tabs_ref() -> None: import json from pathlib import Path diff --git a/agent_sdks/python/a2ui_core/tests/test_components.py b/agent_sdks/python/a2ui_core/tests/test_components.py index eb435c778..71bc8028c 100644 --- a/agent_sdks/python/a2ui_core/tests/test_components.py +++ b/agent_sdks/python/a2ui_core/tests/test_components.py @@ -1,3 +1,4 @@ +from typing import Any, Dict # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,9 +24,10 @@ AnyComponent, ) from a2ui.core.schema import UpdateComponentsMessage, A2uiMessageListWrapper +from a2ui.core.schema.common_types import ActionEventWrapper, SingleReference -def test_image_component_valid_with_description(): +def test_image_component_valid_with_description() -> None: valid_image = { "id": "img-1", "component": "Image", @@ -38,7 +40,7 @@ def test_image_component_valid_with_description(): assert parsed.description == "An example image" -def test_image_component_valid_without_description(): +def test_image_component_valid_without_description() -> None: valid_image = { "id": "img-2", "component": "Image", @@ -50,7 +52,7 @@ def test_image_component_valid_without_description(): assert parsed.description is None -def test_image_component_invalid_field_type(): +def test_image_component_invalid_field_type() -> None: invalid_image = { "id": "img-3", "component": "Image", @@ -60,8 +62,8 @@ def test_image_component_invalid_field_type(): ImageComponent.model_validate(invalid_image) -def test_any_component_discriminated_union(): - adapter = TypeAdapter(AnyComponent) +def test_any_component_discriminated_union() -> None: + adapter: TypeAdapter[AnyComponent] = TypeAdapter(AnyComponent) # 1. Test TextComponent routing text_data = { @@ -103,11 +105,12 @@ def test_any_component_discriminated_union(): assert isinstance(comp, ButtonComponent) assert comp.component == "Button" assert comp.child == "text-1" + assert isinstance(comp.action, ActionEventWrapper) assert comp.action.event.name == "click" -def test_any_component_invalid_discriminator(): - adapter = TypeAdapter(AnyComponent) +def test_any_component_invalid_discriminator() -> None: + adapter: TypeAdapter[AnyComponent] = TypeAdapter(AnyComponent) invalid_data = { "id": "unknown-1", "component": "UnknownComponentType", @@ -116,10 +119,10 @@ def test_any_component_invalid_discriminator(): adapter.validate_python(invalid_data) -def test_text_component_validation(): +def test_text_component_validation() -> None: # 1. Valid Text component instantiation comp = TextComponent( - id="welcome_text", + id=SingleReference("welcome_text"), component="Text", text="Hello World!", variant="h1", @@ -130,40 +133,42 @@ def test_text_component_validation(): # 2. Validation Fails on missing required properties with pytest.raises(ValidationError): - TextComponent(id="bad") # type: ignore + TextComponent(id="bad") # type: ignore[call-arg, arg-type] -def test_text_component_variant_enum(): +def test_text_component_variant_enum() -> None: # 1. Omitted variant uses default value "body" - comp_default = TextComponent(id="text_1", component="Text", text="Hello Default") + comp_default = TextComponent( + id=SingleReference("text_1"), component="Text", text="Hello Default" + ) assert comp_default.variant == "body" # 2. Validation fails on invalid variant value with pytest.raises(ValidationError) as exc_info: TextComponent( - id="text_2", + id=SingleReference("text_2"), component="Text", text="Hello Mismatch", - variant="bold", # type: ignore + variant="bold", # type: ignore[arg-type] ) assert "Input should be 'h1', 'h2', 'h3', 'h4', 'h5', 'caption' or 'body'" in str( exc_info.value ) -def test_button_component_strict_extra_forbid(): +def test_button_component_strict_extra_forbid() -> None: # StrictBaseModel forbids extra fields with pytest.raises(ValidationError): ButtonComponent( - id="btn", + id="btn", # type: ignore[arg-type] component="Button", - child="welcome_text", - action={"event": {"name": "click"}}, # type: ignore - extra_invalid_field="not allowed", # type: ignore - ) + child="welcome_text", # type: ignore[arg-type] + action={"event": {"name": "click"}}, # type: ignore[arg-type] + extra_invalid_field="not allowed", + ) # type: ignore[call-arg] -def test_message_payload_parsing(): +def test_message_payload_parsing() -> None: payload = { "version": "v0.9", "updateComponents": { @@ -188,14 +193,14 @@ def test_message_payload_parsing(): assert msg.update_components.components[1]["component"] == "Button" -def test_model_json_schema_generation(): +def test_model_json_schema_generation() -> None: schema = A2uiMessageListWrapper.model_json_schema() assert schema is not None assert "properties" in schema assert "messages" in schema["properties"] -def test_theme_allows_additional_properties(): +def test_theme_allows_additional_properties() -> None: # 1. Valid theme instantiation with documented properties theme = Theme(primary_color="#00BFFF") assert theme.primary_color == "#00BFFF" @@ -207,24 +212,26 @@ def test_theme_allows_additional_properties(): theme_extra = Theme( primary_color="#00BFFF", extra_custom_color="#FF0000", - ) + ) # type: ignore[call-arg] assert theme_extra.primary_color == "#00BFFF" # 3. For comparison, a strict model (like ButtonComponent) must throw ValidationError with pytest.raises(ValidationError): ButtonComponent( - id="btn", + id="btn", # type: ignore[arg-type] component="Button", - child="text", - action={"event": {"name": "click"}}, # type: ignore - extra_garbage_property="forbidden", # type: ignore - ) + child="text", # type: ignore[arg-type] + action={"event": {"name": "click"}}, # type: ignore[arg-type] + extra_garbage_property="forbidden", + ) # type: ignore[call-arg] -def test_text_component_discriminator_behavior(): +def test_text_component_discriminator_behavior() -> None: # 1. Direct Python Instantiation succeeds WITHOUT passing component name explicitly # because Pydantic applies the Literal default value. - comp = TextComponent(id="text_1", text="Direct Instantiation works!") + comp = TextComponent( + id=SingleReference("text_1"), text="Direct Instantiation works!" + ) assert comp.component == "Text" assert comp.text == "Direct Instantiation works!" @@ -235,7 +242,9 @@ def test_text_component_discriminator_behavior(): "component": "Text", "text": "Payload works!", } - comp_validated = TypeAdapter(AnyComponent).validate_python(valid_payload) + comp_validated: AnyComponent = TypeAdapter(AnyComponent).validate_python( + valid_payload + ) assert isinstance(comp_validated, TextComponent) assert comp_validated.component == "Text" diff --git a/agent_sdks/python/a2ui_core/tests/test_expression_parser.py b/agent_sdks/python/a2ui_core/tests/test_expression_parser.py index e265d92ed..6052c97ee 100644 --- a/agent_sdks/python/a2ui_core/tests/test_expression_parser.py +++ b/agent_sdks/python/a2ui_core/tests/test_expression_parser.py @@ -17,73 +17,75 @@ @pytest.fixture -def parser(): +def parser() -> ExpressionParser: return ExpressionParser() -def test_parses_literal_strings_unchanged(parser): +def test_parses_literal_strings_unchanged(parser: ExpressionParser) -> None: assert parser.parse("hello world") == ["hello world"] -def test_parses_simple_interpolation(parser): +def test_parses_simple_interpolation(parser: ExpressionParser) -> None: assert parser.parse("hello ${foo}") == ["hello ", {"path": "foo"}] -def test_parses_number_interpolation(parser): +def test_parses_number_interpolation(parser: ExpressionParser) -> None: assert parser.parse("number is ${num}") == ["number is ", {"path": "num"}] -def test_parses_nested_interpolation(parser): +def test_parses_nested_interpolation(parser: ExpressionParser) -> None: assert parser.parse("val is ${${nested}}") == ["val is ", {"path": "nested"}] -def test_handles_escaped_interpolation(parser): +def test_handles_escaped_interpolation(parser: ExpressionParser) -> None: assert parser.parse("escaped \\${foo}") == ["escaped ", "${", "foo}"] -def test_parses_function_calls(parser): +def test_parses_function_calls(parser: ExpressionParser) -> None: assert parser.parse("sum is ${add(a: 10, b: 20)}") == [ "sum is ", {"call": "add", "args": {"a": 10, "b": 20}, "returnType": "any"}, ] -def test_parses_function_calls_with_string_literals(parser): +def test_parses_function_calls_with_string_literals(parser: ExpressionParser) -> None: assert parser.parse('case is ${upper(text: "hello")}') == [ "case is ", {"call": "upper", "args": {"text": "hello"}, "returnType": "any"}, ] -def test_parses_keywords(parser): +def test_parses_keywords(parser: ExpressionParser) -> None: assert parser.parse("${true} ${false} ${null}") == [True, " ", False, " "] -def test_returns_error_on_max_depth_exceeded(parser): +def test_returns_error_on_max_depth_exceeded(parser: ExpressionParser) -> None: with pytest.raises(ValueError, match="Max recursion depth reached"): parser.parse("depth", 11) -def test_handles_deep_recursion_gracefully(parser): +def test_handles_deep_recursion_gracefully(parser: ExpressionParser) -> None: assert parser.parse('${${"hello"}}') == ["hello"] -def test_returns_error_on_unclosed_interpolation(parser): +def test_returns_error_on_unclosed_interpolation(parser: ExpressionParser) -> None: with pytest.raises(ValueError, match="Unclosed interpolation"): parser.parse("hello ${world") -def test_returns_error_on_invalid_function_syntax(parser): +def test_returns_error_on_invalid_function_syntax(parser: ExpressionParser) -> None: with pytest.raises(ValueError, match="Expected '\\)'"): parser.parse("${add(a: 1, b: 2}") -def test_returns_error_on_unexpected_characters_at_end(parser): +def test_returns_error_on_unexpected_characters_at_end( + parser: ExpressionParser, +) -> None: with pytest.raises(ValueError, match="Unexpected characters"): parser.parse("${true false}") -def test_handles_empty_identifiers(parser): +def test_handles_empty_identifiers(parser: ExpressionParser) -> None: assert parser.parse("${()}") == [{"call": "", "args": {}, "returnType": "any"}] assert parser.parse_expression("") == "" assert parser.parse_expression("()") == { @@ -93,16 +95,22 @@ def test_handles_empty_identifiers(parser): } -def test_handles_string_literals_with_escaped_characters(parser): +def test_handles_string_literals_with_escaped_characters( + parser: ExpressionParser, +) -> None: assert parser.parse_expression(r"'line1\nline2\t\r\'\\x'") == "line1\nline2\t\r'\\x" -def test_handles_parsing_paths_with_special_characters(parser): +def test_handles_parsing_paths_with_special_characters( + parser: ExpressionParser, +) -> None: assert parser.parse_expression("my-path.with_underscores") == { "path": "my-path.with_underscores" } -def test_returns_error_on_missing_colon_in_function_args(parser): +def test_returns_error_on_missing_colon_in_function_args( + parser: ExpressionParser, +) -> None: with pytest.raises(ValueError, match="Expected ':'"): parser.parse_expression("add(a 10, b: 20)") diff --git a/agent_sdks/python/a2ui_core/tests/test_functions.py b/agent_sdks/python/a2ui_core/tests/test_functions.py index d81cd71f3..d4c542bdf 100644 --- a/agent_sdks/python/a2ui_core/tests/test_functions.py +++ b/agent_sdks/python/a2ui_core/tests/test_functions.py @@ -14,7 +14,7 @@ import pytest import math -from typing import Any +from typing import Any, Dict, Callable, Optional, Union from pydantic import ValidationError from a2ui.core.basic_catalog.function_impls import BASIC_FUNCTION_IMPLEMENTATIONS @@ -22,7 +22,7 @@ IMPLS_MAP = {impl.name: impl for impl in BASIC_FUNCTION_IMPLEMENTATIONS} -def invoke(name: str, args: dict, context: Any = None) -> Any: +def invoke(name: str, args: Dict[str, Any], context: Any = None) -> Any: impl = IMPLS_MAP.get(name) if not impl: raise ValueError(f"Function {name} not found") @@ -33,7 +33,7 @@ def invoke(name: str, args: dict, context: Any = None) -> Any: return impl.execute(validated_args, context) -def test_arithmetic_add(): +def test_arithmetic_add() -> None: assert invoke("add", {"a": 1, "b": 2}) == 3 assert invoke("add", {"a": "1", "b": "2"}) == 3 with pytest.raises(ValidationError): @@ -42,7 +42,7 @@ def test_arithmetic_add(): invoke("add", {"a": 10}) -def test_arithmetic_subtract(): +def test_arithmetic_subtract() -> None: assert invoke("subtract", {"a": 5, "b": 3}) == 2 with pytest.raises(ValidationError): invoke("subtract", {"a": 10, "b": None}) @@ -50,7 +50,7 @@ def test_arithmetic_subtract(): invoke("subtract", {"a": 10}) -def test_arithmetic_multiply(): +def test_arithmetic_multiply() -> None: assert invoke("multiply", {"a": 4, "b": 2}) == 8 with pytest.raises(ValidationError): invoke("multiply", {"a": 10, "b": None}) @@ -58,7 +58,7 @@ def test_arithmetic_multiply(): invoke("multiply", {"a": 10}) -def test_arithmetic_divide(): +def test_arithmetic_divide() -> None: assert invoke("divide", {"a": 10, "b": 2}) == 5 assert invoke("divide", {"a": 10, "b": 0}) == math.inf with pytest.raises(ValidationError): @@ -69,7 +69,7 @@ def test_arithmetic_divide(): assert invoke("divide", {"a": "10", "b": "2"}) == 5 -def test_comparison_equals(): +def test_comparison_equals() -> None: assert invoke("equals", {"a": 1, "b": 1}) is True assert invoke("equals", {"a": 1, "b": 2}) is False with pytest.raises(ValidationError): @@ -78,14 +78,14 @@ def test_comparison_equals(): invoke("equals", {"b": 1}) -def test_comparison_not_equals(): +def test_comparison_not_equals() -> None: assert invoke("not_equals", {"a": 1, "b": 2}) is True assert invoke("not_equals", {"a": 1, "b": 1}) is False with pytest.raises(ValidationError): invoke("not_equals", {"a": 1}) -def test_comparison_greater_than(): +def test_comparison_greater_than() -> None: assert invoke("greater_than", {"a": 5, "b": 3}) is True assert invoke("greater_than", {"a": 3, "b": 5}) is False with pytest.raises(ValidationError): @@ -94,7 +94,7 @@ def test_comparison_greater_than(): invoke("greater_than", {"a": 10}) -def test_comparison_less_than(): +def test_comparison_less_than() -> None: assert invoke("less_than", {"a": 3, "b": 5}) is True assert invoke("less_than", {"a": 5, "b": 3}) is False with pytest.raises(ValidationError): @@ -103,25 +103,25 @@ def test_comparison_less_than(): invoke("less_than", {"a": 3}) -def test_logical_and(): +def test_logical_and() -> None: assert invoke("and", {"values": [True, True]}) is True assert invoke("and", {"values": [True, False]}) is False assert invoke("and", {"values": [True]}) is True -def test_logical_or(): +def test_logical_or() -> None: assert invoke("or", {"values": [False, True]}) is True assert invoke("or", {"values": [False, False]}) is False -def test_logical_not(): +def test_logical_not() -> None: assert invoke("not", {"value": False}) is True assert invoke("not", {"value": True}) is False with pytest.raises(ValidationError): invoke("not", {}) -def test_string_contains(): +def test_string_contains() -> None: assert invoke("contains", {"string": "hello world", "substring": "world"}) is True assert invoke("contains", {"string": "hello world", "substring": "foo"}) is False with pytest.raises(ValidationError): @@ -130,21 +130,21 @@ def test_string_contains(): invoke("contains", {"substring": "hello"}) -def test_string_starts_with(): +def test_string_starts_with() -> None: assert invoke("starts_with", {"string": "hello", "prefix": "he"}) is True assert invoke("starts_with", {"string": "hello", "prefix": "lo"}) is False with pytest.raises(ValidationError): invoke("starts_with", {"string": "hello"}) -def test_string_ends_with(): +def test_string_ends_with() -> None: assert invoke("ends_with", {"string": "hello", "suffix": "lo"}) is True assert invoke("ends_with", {"string": "hello", "suffix": "he"}) is False with pytest.raises(ValidationError): invoke("ends_with", {"string": "hello"}) -def test_validation_required(): +def test_validation_required() -> None: assert invoke("required", {"value": "a"}) is True assert invoke("required", {"value": ""}) is False assert invoke("required", {"value": None}) is False @@ -152,21 +152,21 @@ def test_validation_required(): invoke("required", {}) -def test_validation_length(): +def test_validation_length() -> None: assert invoke("length", {"value": "abc", "min": 2}) is True assert invoke("length", {"value": "abc", "max": 2}) is False with pytest.raises(ValidationError): invoke("length", {}) -def test_validation_numeric(): +def test_validation_numeric() -> None: assert invoke("numeric", {"value": 10, "min": 5, "max": 15}) is True assert invoke("numeric", {"value": 3, "min": 5}) is False with pytest.raises(ValidationError): invoke("numeric", {}) -def test_validation_email(): +def test_validation_email() -> None: assert invoke("email", {"value": "test@example.com"}) is True assert invoke("email", {"value": "test.name@example.com"}) is True assert invoke("email", {"value": "test+label@example.com"}) is True @@ -181,7 +181,7 @@ def test_validation_email(): invoke("email", {}) -def test_validation_regex(): +def test_validation_regex() -> None: assert invoke("regex", {"value": "abc", "pattern": "^[a-z]+$"}) is True assert invoke("regex", {"value": "123", "pattern": "^[a-z]+$"}) is False # In python, re.match/re.search throws re.error if pattern is invalid. @@ -194,11 +194,15 @@ def test_validation_regex(): class MockDataContext: - def __init__(self, data_model: dict, invoker=None): + def __init__( + self, + data_model: Dict[str, Any], + invoker: Optional[Callable[[str, Dict[str, Any]], Any]] = None, + ) -> None: self.data_model = data_model self.invoker = invoker - def resolve_dynamic_value(self, part: Any) -> Any: + def resolve_dynamic_value(self, part: Union[str, Dict[str, Any]]) -> Any: if isinstance(part, dict) and "path" in part: path = part["path"].lstrip("/") return self.data_model.get(path) @@ -209,19 +213,19 @@ def resolve_dynamic_value(self, part: Any) -> Any: return part -def test_formatting_format_string_static(): +def test_formatting_format_string_static() -> None: assert invoke("formatString", {"value": "hello world"}) == "hello world" -def test_formatting_format_string_data_binding(): +def test_formatting_format_string_data_binding() -> None: context = MockDataContext({"a": 10}) assert ( invoke("formatString", {"value": "Value: ${a}"}, context=context) == "Value: 10" ) -def test_formatting_format_string_function_call(): - def mock_invoker(name, args): +def test_formatting_format_string_function_call() -> None: + def mock_invoker(name: str, args: Dict[str, Any]) -> Any: if name == "add": return int(args["a"]) + int(args["b"]) return None @@ -233,7 +237,7 @@ def mock_invoker(name, args): ) -def test_formatting_format_string_serialization(): +def test_formatting_format_string_serialization() -> None: # Test dictionary serialization context = MockDataContext({"user": {"name": "Alice", "age": 30}}) assert ( @@ -262,7 +266,7 @@ def test_formatting_format_string_serialization(): ) -def test_formatting_format_number(): +def test_formatting_format_number() -> None: assert invoke("formatNumber", {"value": 1234.56, "decimals": 1}) == "1,234.6" assert ( invoke("formatNumber", {"value": 1234.56, "decimals": 1, "grouping": False}) @@ -270,7 +274,7 @@ def test_formatting_format_number(): ) -def test_formatting_format_currency(): +def test_formatting_format_currency() -> None: assert ( invoke("formatCurrency", {"value": 1234.56, "currency": "USD", "decimals": 2}) == "$1,234.56" @@ -285,7 +289,7 @@ def test_formatting_format_currency(): ) -def test_formatting_format_date(): +def test_formatting_format_date() -> None: assert ( invoke("formatDate", {"value": "2025-01-01T12:00:00Z", "format": "yyyy-MM-dd"}) == "2025-01-01" @@ -299,7 +303,7 @@ def test_formatting_format_date(): assert invoke("formatDate", {"value": "invalid-date", "format": "yyyy"}) == "" -def test_formatting_pluralize(): +def test_formatting_pluralize() -> None: assert ( invoke("pluralize", {"value": 1, "one": "apple", "other": "apples"}) == "apple" ) @@ -312,6 +316,6 @@ def test_formatting_pluralize(): assert invoke("pluralize", {"value": 1, "other": "apples"}) == "apples" -def test_actions_open_url(): +def test_actions_open_url() -> None: # Since openUrl has side effects in browser only and returns None in python, we verify it executes without error. assert invoke("openUrl", {"url": "https://google.com"}) is None diff --git a/agent_sdks/python/a2ui_core/tests/test_generate_schemas.py b/agent_sdks/python/a2ui_core/tests/test_generate_schemas.py index f55924509..9820fbcb3 100644 --- a/agent_sdks/python/a2ui_core/tests/test_generate_schemas.py +++ b/agent_sdks/python/a2ui_core/tests/test_generate_schemas.py @@ -1,3 +1,4 @@ +from typing import Any # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +26,7 @@ import generate_schemas -def test_map_json_type_to_python(): +def test_map_json_type_to_python() -> None: # Ref mappings assert ( generate_schemas.map_json_type_to_python( @@ -105,9 +106,9 @@ def test_map_json_type_to_python(): assert generate_schemas.map_json_type_to_python("prop", {}) == "Any" -def test_compile_properties_to_pydantic(): +def test_compile_properties_to_pydantic() -> None: # Required property - props = {"title": {"type": "string", "description": "Simple title"}} + props: dict[str, Any] = {"title": {"type": "string", "description": "Simple title"}} lines = generate_schemas.compile_properties_to_pydantic(props, ["title"]) assert len(lines) == 1 assert lines[0] == ' title: str = Field(..., description="Simple title")' @@ -134,7 +135,7 @@ def test_compile_properties_to_pydantic(): assert len(lines) == 0 -def test_compile_component_to_pydantic(): +def test_compile_component_to_pydantic() -> None: schema = { "properties": { "component": {"const": "MyComp"}, @@ -152,9 +153,9 @@ def test_compile_component_to_pydantic(): assert "accessibility: " not in code -def test_compile_object_def(): +def test_compile_object_def() -> None: # Extends StrictBaseModel by default - spec = {"properties": {"x": {"type": "number"}}, "required": ["x"]} + spec: dict[str, Any] = {"properties": {"x": {"type": "number"}}, "required": ["x"]} code = generate_schemas.compile_object_def("Point", spec) assert "class Point(StrictBaseModel):" in code assert " x: float = Field(...)" in code @@ -170,15 +171,15 @@ def test_compile_object_def(): assert " pass" in code -def test_compile_union_def(): - spec = { +def test_compile_union_def() -> None: + spec: dict[str, Any] = { "oneOf": [{"type": "string"}, {"$ref": "common_types.json#/$defs/DataBinding"}] } code = generate_schemas.compile_union_def("StringOrBinding", spec) assert code == "StringOrBinding = Union[str, DataBinding]\n" -def test_compile_function_to_pydantic(): +def test_compile_function_to_pydantic() -> None: # Function with args schema = { "properties": { @@ -205,7 +206,7 @@ def test_compile_function_to_pydantic(): assert ' return_type = "number"' in code -def test_generate_common_types(): +def test_generate_common_types() -> None: mock_common_data = { "$defs": { "DataBinding": {"properties": {"path": {"type": "string"}}}, @@ -247,7 +248,7 @@ def test_generate_common_types(): assert "ChildList = Union[List[ComponentId], TemplateChildList]" in code -def test_generate_basic_catalog_components(): +def test_generate_basic_catalog_components() -> None: # Scenario A: No $defs/anyComponent/oneOf provided (fallback to all components) mock_catalog_data = { "components": { @@ -332,7 +333,7 @@ def test_generate_basic_catalog_components(): assert 'Union[Literal["add", "close"], SvgPath]' in code_svg -def test_generate_basic_catalog_functions(): +def test_generate_basic_catalog_functions() -> None: # Scenario A: Fallback to all functions mock_catalog_data = { "functions": { @@ -372,7 +373,7 @@ def test_generate_basic_catalog_functions(): assert "class PrivateFuncApi(FunctionApi):" in code_defs -def test_generate_basic_catalog_styles(): +def test_generate_basic_catalog_styles() -> None: mock_catalog_data = { "$defs": { "theme": { @@ -392,7 +393,7 @@ def test_generate_basic_catalog_styles(): ) -def test_generate_server_to_client(): +def test_generate_server_to_client() -> None: mock_s2c_data = { "$defs": { "CreateSurfaceMessage": { @@ -412,7 +413,7 @@ def test_generate_server_to_client(): assert "class CreateSurfaceMessage(StrictBaseModel):" in code -def test_generate_schema_init(): +def test_generate_schema_init() -> None: code = generate_schemas.generate_schema_init(["CreateSurfaceMessage"]) assert "from .common_types import (" in code assert "from .constants import *" in code @@ -420,7 +421,7 @@ def test_generate_schema_init(): assert " CreateSurface," in code -def test_generate_client_capabilities(): +def test_generate_client_capabilities() -> None: mock_capabilities_data = { "properties": { "v0.9": { @@ -447,10 +448,10 @@ def test_generate_client_capabilities(): assert "class FunctionDefinition(StrictBaseModel):" in code assert "class V09Capabilities(StrictBaseModel):" in code assert "class A2uiClientCapabilities(StrictBaseModel):" in code - assert "v0_9: Optional[V09Capabilities] = Field(None, alias=SPEC_VERSION)" in code + assert 'v0_9: Optional[V09Capabilities] = Field(None, alias="v0.9")' in code -def test_generate_client_to_server(): +def test_generate_client_to_server() -> None: mock_c2s_data = { "properties": { "action": { @@ -481,7 +482,7 @@ def test_generate_client_to_server(): ) -def test_const_keyword_mapping(): +def test_const_keyword_mapping() -> None: assert ( generate_schemas.map_json_type_to_python("code", {"const": "SUCCESS"}) == "Literal['SUCCESS']" @@ -491,19 +492,19 @@ def test_const_keyword_mapping(): == "Literal[404]" ) - props = {"code": {"const": "FAIL"}} + props: dict[str, Any] = {"code": {"const": "FAIL"}} lines = generate_schemas.compile_properties_to_pydantic(props, ["code"]) assert len(lines) == 1 assert " code: Literal['FAIL'] = Field(\"FAIL\")" in lines[0] -def test_file_header_preamble(): +def test_file_header_preamble() -> None: header = generate_schemas.FILE_HEADER assert "Copyright 2026 Google LLC" in header assert "Auto-generated. Do not edit manually." in header -def test_generate_catalog_functions(): +def test_generate_catalog_functions() -> None: code = generate_schemas.generate_catalog_functions() assert "class FunctionApi:" in code assert 'name: str = ""' in code diff --git a/agent_sdks/python/a2ui_core/tests/test_schema.py b/agent_sdks/python/a2ui_core/tests/test_schema.py index db4d7ab81..e5e611f01 100644 --- a/agent_sdks/python/a2ui_core/tests/test_schema.py +++ b/agent_sdks/python/a2ui_core/tests/test_schema.py @@ -1,3 +1,4 @@ +from typing import Any # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +29,7 @@ ) -def test_valid_action_message(): +def test_valid_action_message() -> None: valid_action = { "version": "v0.9", "action": { @@ -45,7 +46,7 @@ def test_valid_action_message(): assert msg.action.context == {"foo": "bar"} -def test_valid_validation_error_message(): +def test_valid_validation_error_message() -> None: valid_error = { "version": "v0.9", "error": { @@ -62,7 +63,7 @@ def test_valid_validation_error_message(): assert msg.error.path == "/components/0/text" -def test_valid_generic_error_message(): +def test_valid_generic_error_message() -> None: valid_error = { "version": "v0.9", "error": { @@ -78,7 +79,7 @@ def test_valid_generic_error_message(): assert msg.error.message == "Something went wrong" -def test_valid_data_model_message(): +def test_valid_data_model_message() -> None: valid_data_model = { "version": "v0.9", "surfaces": { @@ -92,7 +93,7 @@ def test_valid_data_model_message(): assert msg.surfaces["s2"] == {"cart": []} -def test_fails_on_invalid_version(): +def test_fails_on_invalid_version() -> None: invalid_action = { "version": "v0.8", "action": { @@ -107,7 +108,7 @@ def test_fails_on_invalid_version(): A2uiClientActionMessage.model_validate(invalid_action) -def test_valid_delete_surface_server_message(): +def test_valid_delete_surface_server_message() -> None: msg = { "version": "v0.9", "deleteSurface": {"surfaceId": "surface-1"}, @@ -117,7 +118,7 @@ def test_valid_delete_surface_server_message(): assert parsed.delete_surface.surface_id == "surface-1" -def test_seamless_programmatic_construction_snake_or_alias(): +def test_seamless_programmatic_construction_snake_or_alias() -> None: from a2ui.core.schema import CreateSurface # 1. Construct using snake_case keyword arguments @@ -127,7 +128,7 @@ def test_seamless_programmatic_construction_snake_or_alias(): assert obj_snake.model_dump(by_alias=True)["surfaceId"] == "surf-snake" # 2. Construct using external camelCase alias keyword arguments - obj_alias = CreateSurface(surfaceId="surf-alias", catalogId="cat-alias") + obj_alias = CreateSurface(surfaceId="surf-alias", catalogId="cat-alias") # type: ignore[call-arg] assert obj_alias.surface_id == "surf-alias" assert obj_alias.catalog_id == "cat-alias" assert obj_alias.model_dump(by_alias=True)["surfaceId"] == "surf-alias" diff --git a/agent_sdks/python/a2ui_core/tests/test_validating.py b/agent_sdks/python/a2ui_core/tests/test_validating.py index 0636562c7..558a9ff3d 100644 --- a/agent_sdks/python/a2ui_core/tests/test_validating.py +++ b/agent_sdks/python/a2ui_core/tests/test_validating.py @@ -34,8 +34,8 @@ # ============================================================================== -def test_get_component_references(): - ref_map = { +def test_get_component_references() -> None: + ref_map: dict[str, Any] = { "Container": ({"singleChild", "nestedObj"}, {"childrenList", "tabs"}), } comp = { @@ -61,8 +61,8 @@ def test_get_component_references(): assert "tab2" in ref_ids -def test_validate_component_integrity_valid(): - ref_map = {"Box": ({"child"}, set())} +def test_validate_component_integrity_valid() -> None: + ref_map: dict[str, Any] = {"Box": ({"child"}, set())} components = [ {"id": "root", "component": {"Box": {"child": "c1"}}}, {"id": "c1", "component": {"Box": {}}}, @@ -74,7 +74,7 @@ def test_validate_component_integrity_valid(): ) -def test_validate_component_integrity_duplicate_id(): +def test_validate_component_integrity_duplicate_id() -> None: components = [ {"id": "c1", "component": "Box"}, {"id": "c1", "component": "Text"}, @@ -86,7 +86,7 @@ def test_validate_component_integrity_duplicate_id(): ) -def test_validate_component_integrity_missing_root(): +def test_validate_component_integrity_missing_root() -> None: components = [ {"id": "c1", "component": "Box"}, ] @@ -99,8 +99,8 @@ def test_validate_component_integrity_missing_root(): ) -def test_validate_component_integrity_dangling_ref(): - ref_map = {"Box": ({"child"}, set())} +def test_validate_component_integrity_dangling_ref() -> None: + ref_map: dict[str, Any] = {"Box": ({"child"}, set())} components = [ {"id": "root", "component": {"Box": {"child": "nonexistent"}}}, ] @@ -113,18 +113,18 @@ def test_validate_component_integrity_dangling_ref(): ) -def test_validate_recursion_and_paths_valid(): +def test_validate_recursion_and_paths_valid() -> None: data = {"path": "/valid/path", "nested": [{"path": "/another"}]} validate_recursion_and_paths(data) -def test_validate_recursion_and_paths_invalid_path(): +def test_validate_recursion_and_paths_invalid_path() -> None: data = {"path": "invalid~path//double"} with pytest.raises(ValueError, match="Invalid path syntax"): validate_recursion_and_paths(data) -def test_validate_recursion_and_paths_global_depth(): +def test_validate_recursion_and_paths_global_depth() -> None: # Construct a nested structure deeper than 50 deep_list: Any = [] for _ in range(52): @@ -134,7 +134,7 @@ def test_validate_recursion_and_paths_global_depth(): validate_recursion_and_paths(deep_list) -def test_validate_recursion_and_paths_func_depth(): +def test_validate_recursion_and_paths_func_depth() -> None: # Construct functionCall nesting deeper than 5 deep_call: Dict[str, Any] = {} curr = deep_call @@ -154,8 +154,8 @@ def test_validate_recursion_and_paths_func_depth(): # ============================================================================== -def test_analyze_topology_valid(): - ref_map = {"Node": ({"next"}, set())} +def test_analyze_topology_valid() -> None: + ref_map: dict[str, Any] = {"Node": ({"next"}, set())} components = [ {"id": "root", "component": {"Node": {"next": "n1"}}}, {"id": "n1", "component": {"Node": {}}}, @@ -168,8 +168,8 @@ def test_analyze_topology_valid(): assert visited == {"root", "n1"} -def test_analyze_topology_self_ref(): - ref_map = {"Node": ({"next"}, set())} +def test_analyze_topology_self_ref() -> None: + ref_map: dict[str, Any] = {"Node": ({"next"}, set())} components = [ {"id": "root", "component": {"Node": {"next": "root"}}}, ] @@ -183,8 +183,8 @@ def test_analyze_topology_self_ref(): ) -def test_analyze_topology_circular_ref(): - ref_map = {"Node": ({"next"}, set())} +def test_analyze_topology_circular_ref() -> None: + ref_map: dict[str, Any] = {"Node": ({"next"}, set())} components = [ {"id": "root", "component": {"Node": {"next": "n1"}}}, {"id": "n1", "component": {"Node": {"next": "root"}}}, @@ -198,8 +198,8 @@ def test_analyze_topology_circular_ref(): ) -def test_analyze_topology_orphans(): - ref_map = {"Node": ({"next"}, set())} +def test_analyze_topology_orphans() -> None: + ref_map: dict[str, Any] = {"Node": ({"next"}, set())} components = [ {"id": "root", "component": {"Node": {}}}, {"id": "orphan", "component": {"Node": {}}}, @@ -219,7 +219,7 @@ def test_analyze_topology_orphans(): # ============================================================================== -def test_a2ui_validator_protocol_envelope_invalid_version(): +def test_a2ui_validator_protocol_envelope_invalid_version() -> None: validator = A2uiValidator() with pytest.raises( A2uiValidatorError, @@ -228,13 +228,13 @@ def test_a2ui_validator_protocol_envelope_invalid_version(): validator.validate_protocol_envelope([{"not_version": "v0.8"}]) -def test_a2ui_validator_protocol_envelope_not_dict(): +def test_a2ui_validator_protocol_envelope_not_dict() -> None: validator = A2uiValidator() with pytest.raises(A2uiValidatorError, match="Message must be an object"): - validator.validate_protocol_envelope(["not_a_dict"]) # type: ignore + validator.validate_protocol_envelope(["not_a_dict"]) # type: ignore[list-item] -def test_a2ui_validator_validate_valid_payload(): +def test_a2ui_validator_validate_valid_payload() -> None: catalog = BasicCatalog() validator = A2uiValidator() @@ -270,7 +270,7 @@ def test_a2ui_validator_validate_valid_payload(): validator.validate(CatalogValidator.from_catalog(catalog), messages) -def test_a2ui_validator_validate_components_error(): +def test_a2ui_validator_validate_components_error() -> None: catalog = BasicCatalog() validator = A2uiValidator() @@ -293,8 +293,8 @@ def test_a2ui_validator_validate_components_error(): validator.validate(CatalogValidator.from_catalog(catalog), messages) -def test_topology_cyclomatic_orphans_coverage(): - ref_map = {"Node": ({"child"}, set())} +def test_topology_cyclomatic_orphans_coverage() -> None: + ref_map: dict[str, Any] = {"Node": ({"child"}, set())} components_orphan = [ {"id": "root", "component": "Node", "child": "A"}, @@ -327,8 +327,8 @@ def test_topology_cyclomatic_orphans_coverage(): ) -def test_integrity_dangling_and_duplicate_pointers(): - ref_map = {"Node": ({"child"}, set())} +def test_integrity_dangling_and_duplicate_pointers() -> None: + ref_map: dict[str, Any] = {"Node": ({"child"}, set())} components_dup = [ {"id": "root", "component": "Node"}, @@ -348,7 +348,7 @@ def test_integrity_dangling_and_duplicate_pointers(): ) -def test_validate_recursion_and_paths_syntax_coverage(): +def test_validate_recursion_and_paths_syntax_coverage() -> None: # 1. Realistic A2UI v0.9 Payload with Invalid Pointer Syntax invalid_path_payload = { "version": "v0.9", @@ -407,7 +407,7 @@ def test_validate_recursion_and_paths_syntax_coverage(): validate_recursion_and_paths(payload_deep_func) -def test_validator_aggregated_pydantic_error_formatting(): +def test_validator_aggregated_pydantic_error_formatting() -> None: validator = A2uiValidator() invalid_s2c_payload = [{"version": "v0.9"}] @@ -418,7 +418,7 @@ def test_validator_aggregated_pydantic_error_formatting(): assert "messages.0" in str(exc_info.value) -def test_validator_config_parameter(): +def test_validator_config_parameter() -> None: # Verify that ValidationConfig is respected during validation catalog = CatalogValidator.from_catalog(BasicCatalog()) @@ -431,7 +431,7 @@ def test_validator_config_parameter(): ) # 1. Orphan component: with strict_config, this fails. With relaxed_config, it succeeds! - orphan_components = [ + orphan_components: list[dict[str, Any]] = [ {"id": "root", "component": "Column", "children": []}, {"id": "orphan", "component": "Text", "text": "I am an orphan"}, ] @@ -441,7 +441,7 @@ def test_validator_config_parameter(): validator.validate_components(catalog, orphan_components, config=relaxed_config) # 2. Dangling reference: with strict_config, this fails. With relaxed_config, it succeeds! - dangling_components = [ + dangling_components: list[dict[str, Any]] = [ {"id": "root", "component": "Column", "children": ["non_existent_id"]} ] with pytest.raises(A2uiValidatorError, match="references non-existent component"): diff --git a/agent_sdks/python/a2ui_core/uv.lock b/agent_sdks/python/a2ui_core/uv.lock index f3afb0ab4..dca6b7571 100644 --- a/agent_sdks/python/a2ui_core/uv.lock +++ b/agent_sdks/python/a2ui_core/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 requires-python = ">=3.14" +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version < '3.15'", +] [[package]] name = "a2ui-core" @@ -14,8 +18,10 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "mypy" }, { name = "pyink" }, { name = "pytest" }, + { name = "types-jsonschema" }, ] [package.metadata] @@ -27,8 +33,10 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "mypy", specifier = ">=1.14.0" }, { name = "pyink", specifier = ">=24.10.0" }, { name = "pytest", specifier = ">=8.0.0" }, + { name = "types-jsonschema", specifier = ">=4.26.0" }, ] [[package]] @@ -40,6 +48,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "ast-serialize" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, + { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, + { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, +] + [[package]] name = "attrs" version = "26.1.0" @@ -128,6 +176,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] +[[package]] +name = "librt" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, + { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, + { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, + { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, +] + +[[package]] +name = "mypy" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, +] + [[package]] name = "mypy-extensions" version = "1.1.0" @@ -370,6 +482,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, ] +[[package]] +name = "types-jsonschema" +version = "4.26.0.20260518" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/46/73b6a5d61a61015c4248030a8cb07e5bdddb4041430fae9e585a68692578/types_jsonschema-4.26.0.20260518.tar.gz", hash = "sha256:e1dd53dc97a64f5eccdd6fa9839666e09bb500a8ebba2db6fdaf1789faea81a6", size = 16638, upload-time = "2026-05-18T06:06:44.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/d5/134f8a147dcecda10db7f60cfc6af0578a25a5c53c87b3907a64385e0184/types_jsonschema-4.26.0.20260518-py3-none-any.whl", hash = "sha256:30b30a518c7fe335df85c919fcbcc631b69c03d4a4b5b632fa916bea03065307", size = 16072, upload-time = "2026-05-18T06:06:43.264Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"