def _field(self, field: ObjectField) -> Lazy[graphql.GraphQLInputField]: field_type = field.type field_default = graphql.Undefined if field.required else field.get_default( ) default: Any = graphql.Undefined # Don't put `null` default + handle Undefined as None if field_default in {None, Undefined}: field_type = Optional[field_type] elif field_default is not graphql.Undefined: try: default = serialize( field_type, field_default, aliaser=self.aliaser, conversion=field.deserialization, ) except Exception: field_type = Optional[field_type] factory = self.visit_with_conv(field_type, field.deserialization) return lambda: graphql.GraphQLInputField( factory.type, # type: ignore default_value=default, description=get_description(field.schema, field.type), extensions={field.name: ""}, )
def attrs_fields(cls: type) -> Optional[Sequence[ObjectField]]: if hasattr(cls, "__attrs_attrs__"): return [ ObjectField(a.name, a.type, required=a.default == attr.NOTHING, default=a.default) for a in getattr(cls, "__attrs_attrs__") ] else: return prev_default_object_fields(cls)
def column_field(name: str, column: Column) -> ObjectField: required = False default: Any = ... if column.default is not None: default = column.default elif column.server_default is not None: default = Undefined elif column.nullable: default = None else: required = True col_type = column.type.python_type col_type = Optional[col_type] if column.nullable else col_type return ObjectField(column.name or name, col_type, required, default=default)
def visit_field(self, field: ObjectField) -> JsonSchema: result = full_schema( self.visit_with_conv(field.type, self._field_conversion(field)), field.schema, ) if (not field.flattened and not field.pattern_properties and not field.additional_properties and not field.required and "default" not in result): result = JsonSchema(result) with suppress(Exception): result["default"] = serialize( field.type, field.get_default(), fall_back_on_any=False, check_type=True, conversion=field.serialization, ) return result
def _resolver(self, field: ResolverField) -> Lazy[graphql.GraphQLField]: resolve = self._wrap_resolve( resolver_resolve( field.resolver, field.types, self.aliaser, self.input_builder.default_conversion, self.default_conversion, )) args = None if field.parameters is not None: args = {} for param in field.parameters: default: Any = graphql.Undefined param_type = field.types[param.name] if is_union_of(param_type, graphql.GraphQLResolveInfo): break param_field = ObjectField( param.name, param_type, param.default is Parameter.empty, field.metadata.get(param.name, empty_dict), default=param.default, ) if param_field.required: pass # Don't put `null` default + handle Undefined as None # also https://github.com/python/typing/issues/775 elif param.default in {None, Undefined}: param_type = Optional[param_type] # param.default == graphql.Undefined means the parameter is required # even if it has a default elif param.default not in {Parameter.empty, graphql.Undefined}: try: default = serialize( param_type, param.default, fall_back_on_any=False, check_type=True, ) except Exception: param_type = Optional[param_type] arg_factory = self.input_builder.visit_with_conv( param_type, param_field.deserialization) description = get_description(param_field.schema, param_field.type) def arg_thunk( arg_factory=arg_factory, default=default, description=description) -> graphql.GraphQLArgument: return graphql.GraphQLArgument(arg_factory.type, default, description) args[self.aliaser(param_field.alias)] = arg_thunk factory = self.visit_with_conv(field.type, field.resolver.conversion) return lambda: graphql.GraphQLField( factory.type, # type: ignore {name: arg() for name, arg in args.items()} if args else None, resolve, field.subscribe, field.description, field.deprecated, )
from graphql import graphql_sync, print_schema from pytest import raises from apischema import deserialize, serialize from apischema.graphql import graphql_schema from apischema.json_schema import deserialization_schema, serialization_schema from apischema.metadata import conversion, flattened from apischema.objects import ObjectField, set_object_fields class Field: def __init__(self, attr: int): self.attr = attr set_object_fields(Field, [ObjectField("attr", int)]) @dataclass class Data: data_field: Field = field(metadata=flattened) json_schema = { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "object", "allOf": [ { "type": "object",
from apischema import deserialize, serialize from apischema.objects import ObjectField, set_object_fields class Foo: def __init__(self, bar: int): self.bar = bar set_object_fields(Foo, lambda: [ObjectField("bar", int, required=True)]) foo = deserialize(Foo, {"bar": 0}) assert type(foo) == Foo and foo.bar == 0 assert serialize(Foo, Foo(0)) == {"bar": 0}
from apischema import deserialize, serialize from apischema.json_schema import deserialization_schema from apischema.objects import ObjectField, set_object_fields class Foo: def __init__(self, bar): self.bar = bar set_object_fields(Foo, [ObjectField("bar", int)]) # Fields can also be passed in a factory set_object_fields(Foo, lambda: [ObjectField("bar", int)]) foo = deserialize(Foo, {"bar": 0}) assert isinstance(foo, Foo) and foo.bar == 0 assert serialize(Foo, Foo(0)) == {"bar": 0} assert deserialization_schema(Foo) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "object", "properties": { "bar": { "type": "integer" } }, "required": ["bar"], "additionalProperties": False, }
def resolver_resolve( resolver: Resolver, types: Mapping[str, AnyType], aliaser: Aliaser, default_deserialization: DefaultConversion, default_serialization: DefaultConversion, serialized: bool = True, ) -> Callable: # graphql deserialization will give Enum objects instead of strings def handle_enum(tp: AnyType) -> Optional[AnyConversion]: if is_type(tp) and issubclass(tp, Enum): return Conversion(identity, source=Any, target=tp) return default_deserialization(tp) parameters, info_parameter = [], None for param in resolver.parameters: param_type = types[param.name] if is_union_of(param_type, graphql.GraphQLResolveInfo): info_parameter = param.name else: param_field = ObjectField( param.name, param_type, param.default is Parameter.empty, resolver.parameters_metadata.get(param.name, empty_dict), param.default, ) deserializer = deserialization_method( param_type, additional_properties=False, aliaser=aliaser, coerce=False, conversion=param_field.deserialization, default_conversion=handle_enum, fall_back_on_default=False, schema=param_field.schema, ) opt_param = is_union_of(param_type, NoneType) or param.default is None parameters.append( ( aliaser(param_field.alias), param.name, deserializer, opt_param, param_field.required, ) ) func, error_handler = resolver.func, resolver.error_handler method_factory = partial_serialization_method_factory( aliaser, resolver.conversion, default_serialization ) serialize_result: Callable[[Any], Any] if not serialized: serialize_result = lambda res: res elif is_async(resolver.func): serialize_result = as_async(method_factory(types["return"])) else: serialize_result = method_factory(types["return"]) serialize_error: Optional[Callable[[Any], Any]] if error_handler is None: serialize_error = None elif is_async(error_handler): serialize_error = as_async(method_factory(resolver.error_type())) else: serialize_error = method_factory(resolver.error_type()) def resolve(__self, __info, **kwargs): values = {} errors: Dict[str, ValidationError] = {} for alias, param_name, deserializer, opt_param, required in parameters: if alias in kwargs: # It is possible for the parameter to be non-optional in Python # type hints but optional in the generated schema. In this case # we should ignore it. # See: https://github.com/wyfo/apischema/pull/130#issuecomment-845497392 if not opt_param and kwargs[alias] is None: assert not required continue try: values[param_name] = deserializer(kwargs[alias]) except ValidationError as err: errors[aliaser(param_name)] = err elif opt_param and required: values[param_name] = None if errors: raise ValueError(ValidationError(children=errors).errors) if info_parameter: values[info_parameter] = __info try: return serialize_result(func(__self, **values)) except Exception as error: if error_handler is None: raise assert serialize_error is not None return serialize_error(error_handler(error, __self, __info, **kwargs)) return resolve