def _schema( builder: Type[SchemaBuilder], cls: AnyType, schema: Optional[Schema], conversions: Optional[Conversions], version: Optional[JsonSchemaVersion], aliaser: Optional[Aliaser], ref_factory: Optional[RefFactory], all_refs: Optional[bool], with_schema: bool, ) -> Mapping[str, Any]: add_defs = ref_factory is None if ref_factory is not None and all_refs is None: all_refs = True if aliaser is None: aliaser = settings.aliaser() version, ref_factory, all_refs = _default_version(version, ref_factory, all_refs) refs = _extract_refs([(cls, conversions)], builder, all_refs) visitor = builder(aliaser, ref_factory, refs, False) with visitor._replace_conversions(conversions): json_schema = visitor.visit_with_schema(cls, schema) if add_defs: defs = _refs_schema(builder, aliaser, refs, ref_factory) if defs: json_schema["$defs"] = defs result = serialize(json_schema, conversions=version.conversions) if with_schema and version.schema is not None: result["$schema"] = version.schema return result
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 partial_serialize( obj: Any, *, conversions: Conversions = None, aliaser: Aliaser = None, ) -> Any: assert aliaser is not None cls = obj.__class__ if cls in PRIMITIVE_TYPES_SET: return obj if cls in COLLECTION_TYPE_SET: return [ partial_serialize(elt, conversions=conversions, aliaser=aliaser) for elt in obj ] if cls in MAPPING_TYPE_SET: return serialize(obj, conversions=conversions, aliaser=aliaser, exclude_unset=False) target = None if conversions is not None: try: target = conversions[cls] except KeyError: pass conversion = SerializationVisitor._is_conversion(cls, target) if conversion is not None: _, (converter, sub_conversions) = conversion if isinstance(target, DataclassModelWrapper): return obj return partial_serialize(converter(obj), conversions=sub_conversions, aliaser=aliaser) if is_dataclass(cls): return obj if issubclass(cls, Enum): return serialize(obj.value, aliaser=aliaser, exclude_unset=False) if isinstance(obj, PRIMITIVE_TYPES): return obj if isinstance(obj, Mapping): return serialize(obj, aliaser=aliaser, exclude_unset=False) if isinstance(obj, Collection): return [partial_serialize(elt, aliaser=aliaser) for elt in obj] if issubclass(cls, tuple) and hasattr(cls, "_fields"): return obj raise Unsupported(cls)
def _schema( builder: Type[SchemaBuilder], tp: AnyType, schema: Optional[Schema], conversion: Optional[AnyConversion], default_conversion: DefaultConversion, version: Optional[JsonSchemaVersion], aliaser: Optional[Aliaser], ref_factory: Optional[RefFactory], all_refs: Optional[bool], with_schema: bool, additional_properties: Optional[bool], ) -> Mapping[str, Any]: from apischema import settings add_defs = ref_factory is None if aliaser is None: aliaser = settings.aliaser if additional_properties is None: additional_properties = settings.deserialization.additional_properties version, ref_factory, all_refs = _default_version(version, ref_factory, all_refs) refs = _extract_refs([(tp, conversion)], default_conversion, builder, all_refs) json_schema = builder(additional_properties, aliaser, default_conversion, False, ref_factory, refs).visit_with_conv(tp, conversion) json_schema = full_schema(json_schema, schema) if add_defs: defs = _refs_schema( builder, aliaser, default_conversion, refs, ref_factory, additional_properties, ) if defs: json_schema["$defs"] = defs result = serialize( JsonSchema, json_schema, aliaser=aliaser, fall_back_on_any=True, check_type=True, conversion=version.conversion, default_conversion=converters.default_serialization, ) if with_schema and version.schema is not None: result["$schema"] = version.schema return result
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 resolve(self, info, **kwargs): errors: Dict[str, ValidationError] = {} for arg_name, arg_type in arg_types: if arg_name in kwargs: try: kwargs[arg_name] = deserialize(arg_type, kwargs[arg_name]) except ValidationError as err: errors[aliaser(arg_name)] = err if errors: raise TypeError(serialize(ValidationError(children=errors))) if info_param: kwargs[info_param] = info try: return serialize_result(func(self, **kwargs)) except Exception: if error_as_null: return None else: raise
def visit_field(self, field: Field, field_type: AnyType): field_type, conversions, _ = get_field_conversion( field, field_type, self.operation) schema: Optional[Schema] = None if SCHEMA_METADATA in field.metadata: schema = cast(Schema, field.metadata[SCHEMA_METADATA]) if schema.annotations is not None and schema.annotations is ...: if not has_default(field): raise TypeError("Invalid ... without field default") try: default = serialize(get_default(field), conversions=conversions) except Exception: pass else: annotations = replace(schema.annotations, default=default) schema = replace(schema, annotations=annotations) with self._replace_conversions(conversions): return self.visit_with_schema(field_type, schema)
def definitions_schema( *, deserialization: TypesWithConversions = (), serialization: TypesWithConversions = (), aliaser: Aliaser = None, version: JsonSchemaVersion = None, ref_factory: Optional[RefFactory] = None, all_refs: bool = None, ) -> Mapping[str, Mapping[str, Any]]: if aliaser is None: aliaser = settings.aliaser() version, ref_factory, all_refs = _default_version(version, ref_factory, all_refs) deserialization_schemas = _defs_schema(deserialization, DeserializationSchemaBuilder, aliaser, ref_factory, all_refs) serialization_schemas = _defs_schema(serialization, SerializationSchemaBuilder, aliaser, ref_factory, all_refs) for duplicate in deserialization_schemas.keys( ) & serialization_schemas.keys(): d_schema = deserialization_schemas[duplicate] s_schema = serialization_schemas[duplicate] _set_missing_properties(s_schema, d_schema.get("properties"), "writeOnly") _set_missing_properties(d_schema, s_schema.get("properties"), "readOnly") if "required" in d_schema and "required" in s_schema: s_schema["required"] = d_schema["required"] if d_schema != s_schema: raise TypeError(f"Reference {duplicate} has different schemas" f" for deserialization and serialization") return { ref: serialize(schema, conversions=version.conversions) for ref, schema in chain(deserialization_schemas.items(), serialization_schemas.items()) }
def definitions_schema( *, deserialization: TypesWithConversion = (), serialization: TypesWithConversion = (), default_deserialization: DefaultConversion = None, default_serialization: DefaultConversion = None, aliaser: Aliaser = None, version: JsonSchemaVersion = None, ref_factory: Optional[RefFactory] = None, all_refs: bool = None, additional_properties: bool = None, ) -> Mapping[str, Mapping[str, Any]]: from apischema import settings if additional_properties is None: additional_properties = settings.deserialization.additional_properties if aliaser is None: aliaser = settings.aliaser if default_deserialization is None: default_deserialization = settings.deserialization.default_conversion if default_serialization is None: default_serialization = settings.serialization.default_conversion version, ref_factory, all_refs = _default_version(version, ref_factory, all_refs) deserialization_schemas = _defs_schema( deserialization, default_deserialization, DeserializationSchemaBuilder, aliaser, ref_factory, all_refs, additional_properties, ) serialization_schemas = _defs_schema( serialization, default_serialization, SerializationSchemaBuilder, aliaser, ref_factory, all_refs, additional_properties, ) schemas = {} for ref in deserialization_schemas.keys() | serialization_schemas.keys(): if ref in deserialization_schemas and ref in serialization_schemas: try: schemas[ref] = compare_schemas(deserialization_schemas[ref], serialization_schemas[ref]) except ValueError: raise TypeError(f"Reference {ref} has different schemas" f" for deserialization and serialization") else: schemas[ref] = deserialization_schemas.get( ref, serialization_schemas.get(ref)) return { ref: serialize( JsonSchema, schema, aliaser=aliaser, fall_back_on_any=True, check_type=True, conversion=version.conversion, default_conversion=converters.default_serialization, ) for ref, schema in schemas.items() }
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, )
def bijection(cls, data, expected): obj = deserialize(cls, data) assert obj == expected assert serialize(cls, obj) == data