From a22a1544198ab522bd78e42e5ef80690c27fb918 Mon Sep 17 00:00:00 2001 From: Thor Whalen Date: Fri, 22 Mar 2024 13:53:25 +0100 Subject: [PATCH] refactor: Shuffling things around --- ju/json_schema.py | 25 ++++++++++++-- ju/rjsf.py | 86 +++-------------------------------------------- ju/util.py | 66 ++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 84 deletions(-) diff --git a/ju/json_schema.py b/ju/json_schema.py index c54dd14..d79fe21 100644 --- a/ju/json_schema.py +++ b/ju/json_schema.py @@ -23,9 +23,13 @@ """ +from typing import Mapping, Sequence, Callable +from inspect import Parameter +from functools import partial + from pydantic import BaseModel -from typing import Mapping, Sequence, Callable +from ju.util import is_type class _BasicPythonTypes(BaseModel): @@ -73,6 +77,23 @@ def gen(): DFLT_JSON_TYPE = 'string' # TODO: 'string' or 'object'? + +def parametrized_param_to_type( + param: Parameter, + *, + type_mapping=DFLT_PY_JSON_TYPE_PAIRS, + default=DFLT_JSON_TYPE, +): + for python_type, json_type in type_mapping: + if is_type(param, python_type): + return json_type + return default + + +DFLT_PARAM_TO_TYPE = partial( + parametrized_param_to_type, type_mapping=DFLT_PY_JSON_TYPE_PAIRS +) + # ------------------------------------------------------------------------------------- # util @@ -104,7 +125,6 @@ def wrap_schema_in_opus_spec(schema: dict): from typing import Mapping, Sequence import inspect -from inspect import Parameter from operator import attrgetter from i2 import Sig @@ -129,6 +149,7 @@ def wrap_schema_in_opus_spec(schema: dict): ) +# TODO: See def function_to_json_schema( func: Callable, *, diff --git a/ju/rjsf.py b/ju/rjsf.py index 91431db..9c6d54c 100644 --- a/ju/rjsf.py +++ b/ju/rjsf.py @@ -77,86 +77,7 @@ def func_to_form_spec(func: Callable): } -# def is_type(param: Parameter, type_: type): -# return param.annotation is type_ or isinstance(param.default, type_) - -from typing import get_args, get_origin, Any, Union, GenericAlias, Type -from types import GenericAlias - -SomeType = Union[Type, GenericAlias, Any] -SomeType.__doc__ = "A type or a GenericAlias, but also Any, just in case" - - -def is_type(param: Parameter, type_: SomeType): - """ - Checks if the type of a parameter's default value or its annotation matches a - given type. - - This function handles both regular types and subscripted generics. - - Args: - param (Parameter): The parameter to check. - type_ (type): The type to check against. - - Returns: - bool: True if the parameter's type matches the given type, False otherwise. - - Doctests: - >>> from inspect import Parameter - >>> param = Parameter('p', Parameter.KEYWORD_ONLY, default=3.14) - >>> is_type(param, float) - True - >>> is_type(param, int) - False - >>> param = Parameter('p', Parameter.KEYWORD_ONLY, default=[1, 2, 3]) - >>> is_type(param, list) - True - >>> from typing import List, Union - >>> is_type(param, List[int]) - True - >>> is_type(param, List[str]) - False - >>> is_type(param, Union[int, List[int]]) - True - """ - if param.annotation is type_: - return True - if isinstance(type_, type): - return isinstance(param.default, type_) - if hasattr(type_, '__origin__'): - origin = get_origin(type_) - if origin is Union: - args = get_args(type_) - return any(is_type(param, arg) for arg in args) - else: - args = get_args(type_) - if isinstance(param.default, origin): - if all( - any(isinstance(element, arg) for element in param.default) - for arg in args - ): - return True - return False - - -from ju.json_schema import DFLT_PY_JSON_TYPE_PAIRS, DFLT_JSON_TYPE - - -def parametrized_param_to_type( - param: Parameter, - *, - type_mapping=DFLT_PY_JSON_TYPE_PAIRS, - default=DFLT_JSON_TYPE, -): - for python_type, json_type in type_mapping: - if is_type(param, python_type): - return json_type - return default - - -_dflt_param_to_type = partial( - parametrized_param_to_type, type_mapping=DFLT_PY_JSON_TYPE_PAIRS -) +from ju.json_schema import DFLT_PARAM_TO_TYPE # TODO: The loop body could be factored out @@ -173,9 +94,10 @@ def get_properties(parameters, *, param_to_prop_type): ... ): ... '''A Foo function''' >>> + >>> from ju.json_schema import DFLT_PARAM_TO_TYPE >>> parameters = inspect.signature(foo).parameters >>> assert ( - ... get_properties(parameters, param_to_prop_type=_dflt_param_to_type) + ... get_properties(parameters, param_to_prop_type=DFLT_PARAM_TO_TYPE) ... == { ... 'a_bool': {'type': 'boolean'}, ... 'a_float': {'type': 'number', 'default': 3.14}, @@ -208,7 +130,7 @@ def get_required(properties: dict): # TODO: This all should really use meshed instead, to be easily composable. -def _func_to_rjsf_schemas(func, *, param_to_prop_type: Callable = _dflt_param_to_type): +def _func_to_rjsf_schemas(func, *, param_to_prop_type: Callable = DFLT_PARAM_TO_TYPE): """ Returns the JSON schema and the UI schema for a function. diff --git a/ju/util.py b/ju/util.py index d90022b..8ccc54e 100644 --- a/ju/util.py +++ b/ju/util.py @@ -12,9 +12,16 @@ Tuple, runtime_checkable, Protocol, + get_args, + get_origin, + Any, + Type, ) from collections import defaultdict from dataclasses import dataclass +from types import GenericAlias +from inspect import Parameter + from i2 import mk_sentinel try: @@ -37,6 +44,65 @@ def is_jsonable(x): return False +SomeType = Union[Type, GenericAlias, Any] +SomeType.__doc__ = "A type or a GenericAlias, but also Any, just in case" + + +def is_type(param: Parameter, type_: SomeType): + """ + Checks if the type of a parameter's default value or its annotation matches a + given type. + + This function handles both regular types and subscripted generics. + + Args: + param (Parameter): The parameter to check. + type_ (type): The type to check against. + + Returns: + bool: True if the parameter's type matches the given type, False otherwise. + + Doctests: + >>> from inspect import Parameter + >>> param = Parameter('p', Parameter.KEYWORD_ONLY, default=3.14) + >>> is_type(param, float) + True + >>> is_type(param, int) + False + >>> param = Parameter('p', Parameter.KEYWORD_ONLY, default=[1, 2, 3]) + >>> is_type(param, list) + True + >>> from typing import List, Union + >>> is_type(param, List[int]) + True + >>> is_type(param, List[str]) + False + >>> is_type(param, Union[int, List[int]]) + True + """ + if param.annotation is type_: + return True + if isinstance(type_, type): + return isinstance(param.default, type_) + if hasattr(type_, '__origin__'): + origin = get_origin(type_) + if origin is Union: + args = get_args(type_) + return any(is_type(param, arg) for arg in args) + else: + args = get_args(type_) + if isinstance(param.default, origin): + if all( + any(isinstance(element, arg) for element in param.default) + for arg in args + ): + return True + return False + + +# ------------------------------------------------------------------------------------- +# Mappers + CallableMapper = Callable[[KT], VT] MappingMapper = Mapping[KT, VT] PairsMapper = Sequence[Tuple[KT, VT]]