def _float_schema(*, python_type, annotated, **_): if is_subclass(python_type, float): kwargs = {} for annotation in annotated: if is_instance(annotation, fondat.validation.MinValue): kwargs["minimum"] = annotation.value elif is_instance(annotation, fondat.validation.MaxValue): kwargs["maximum"] = annotations.value return Schema( type="number", format="double", **_kwargs(python_type, annotated), **kwargs )
def _int_schema(*, python_type, annotated, **_): if is_subclass(python_type, int) and not is_subclass(python_type, bool): kwargs = {} for annotation in annotated: if is_instance(annotation, fondat.validation.MinValue): kwargs["minimum"] = annotation.value elif is_instance(annotation, fondat.validation.MaxValue): kwargs["maximum"] = annotation.value return Schema( type="integer", format="int64", **_kwargs(python_type, annotated), **kwargs )
def _str_schema(*, python_type, annotated, **_): if is_subclass(python_type, str): kwargs = {} for annotation in annotated: if is_instance(annotation, fondat.annotation.Format): kwargs["format"] = annotation.value elif is_instance(annotation, fondat.validation.MinLen): kwargs["minLength"] = annotation.value elif is_instance(annotation, fondat.validation.MaxLen): kwargs["maxLength"] = annotation.value elif is_instance(annotation, fondat.validation.Pattern): kwargs["pattern"] = annotation.pattern.pattern return Schema(type="string", **_kwargs(python_type, annotated), **kwargs)
def validate(value: Any, type_hint: Any) -> NoneType: """Validate a value.""" python_type, annotations = split_annotated(type_hint) origin = typing.get_origin(python_type) args = typing.get_args(python_type) # validate using specified validator annotations for annotation in annotations: if isinstance(annotation, Validator): annotation.validate(value) # aggregate type validation if python_type is Any: return elif origin is Union: return _validate_union(value, args) elif origin is Literal: return _validate_literal(value, args) # TypedDict if is_subclass(python_type, dict) and hasattr(python_type, "__annotations__"): origin = dict # basic type validation if origin and not is_instance(value, origin): raise ValidationError( f"expecting {origin.__name__}; received {type(value)}") elif not origin and not is_instance(value, python_type): raise ValidationError( f"expecting {python_type}; received {type(value)}") elif python_type is int and is_instance(value, bool): # bool is subclass of int raise ValidationError("expecting int; received bool") elif is_subclass(origin, Iterable) and is_instance( value, (str, bytes, bytearray)): raise ValidationError(f"expecting Iterable; received {type(value)}") # structured type validation if is_subclass(python_type, dict) and hasattr(python_type, "__annotations__"): return _validate_typeddict(value, python_type) elif is_subclass(origin, Mapping): return _validate_mapping(value, python_type, args) elif is_subclass(origin, tuple): return _validate_tuple(value, python_type, args) elif is_subclass(origin, Iterable): return _validate_iterable(value, python_type, args) elif dataclasses.is_dataclass(python_type): return _validate_dataclass(value, python_type)
def _bytes_schema(*, python_type, annotated, **_): if is_subclass(python_type, (bytes, bytearray)): kwargs = {} for annotation in annotated: if is_instance(annotation, fondat.validation.MinLen): kwargs["minLength"] = annotation.value elif is_instance(annotation, fondat.validation.MaxLen): kwargs["maxLength"] = annotation.value return Schema( type="string", format="binary" if fondat.http.InBody in annotated else "byte", **_kwargs(python_type, annotated), **kwargs, )
def _iterable_schema(*, python_type, annotated, origin, args, processor, **_): if is_subclass(origin, Iterable) and not is_subclass(origin, Mapping) and len(args) == 1: kwargs = {} if is_subclass(origin, set): kwargs["uniqueItems"] = True for annotation in annotated: if is_instance(annotation, fondat.validation.MinLen): kwargs["minItems"] = annotation.value elif is_instance(annotation, fondat.validation.MaxLen): kwargs["maxItems"] = annotation.value return Schema( type="array", items=processor.schema(args[0]), **_kwargs(python_type, annotated), **kwargs, )
def _kwargs(python_type, annotated): kwargs = {} for annotation in annotated: if is_instance(annotation, str): kwargs["description"] = annotation elif is_instance(annotation, fondat.annotation.Description): kwargs["description"] = annotation.value elif is_instance(annotation, fondat.annotation.Example): with fondat.validation.validation_error_path("example"): fondat.validation.validate(annotation.value, python_type) kwargs["example"] = fondat.codec.get_codec(fondat.codec.JSON, python_type).encode( annotation.value ) elif is_instance(annotation, Default): with fondat.validation.validation_error_path("default"): fondat.validation.validate(annotation.value, python_type) kwargs["default"] = fondat.codec.get_codec(fondat.codec.JSON, python_type).encode( annotation.value ) elif is_instance(annotation, fondat.annotation.Deprecated): kwargs["deprecated"] = annotation.value elif annotation is fondat.annotation.Deprecated: kwargs["deprecated"] = True elif is_instance(annotation, fondat.annotation.ReadOnly): kwargs["readOnly"] = annotation.value elif annotation is fondat.annotation.ReadOnly: kwargs["readOnly"] = True return kwargs
def resource(obj): if fondat.resource.is_resource(obj): # resource class or instance return obj if is_instance(obj, property): # unbound property obj = obj.fget if callable(obj): try: returns = typing.get_type_hints(obj)["return"] except: return None if fondat.resource.is_resource(returns): return returns return None
def description(annotated): for annotation in annotated: if is_instance(annotation, str): return annotation elif is_instance(annotation, fondat.annotation.Description): return annotation.value
def operation(self, tag, method): if not callable(method): return None fondat_op = getattr(method, "_fondat_operation", None) if not fondat_op or not fondat_op.publish: return None op = Operation(parameters=[], responses={}) op.tags = [tag] if fondat_op.summary: op.summary = fondat_op.summary if fondat_op.description: op.description = fondat_op.description if fondat_op.deprecated: op.deprecated = True hints = typing.get_type_hints(method, include_extras=True) parameters = inspect.signature(method).parameters for name, hint in hints.items(): python_type, annotated = fondat.types.split_annotated(hint) if name == "return": if python_type is NoneType: op.responses[str(http.HTTPStatus.NO_CONTENT.value)] = Response( description="No content.", ) else: origin = typing.get_origin(python_type) args = typing.get_args(python_type) if origin is typing.Union and NoneType in args: op.responses[str(http.HTTPStatus.NO_CONTENT.value)] = Response( description="No content.", ) python_type = typing.Union[ tuple(arg for arg in args if arg is not NoneType) ] hint = ( Annotated[tuple([python_type, *annotated])] if annotated else python_type ) op.responses[str(http.HTTPStatus.OK.value)] = Response( description=self.description(annotated) or "Response.", content={ fondat.codec.get_codec( fondat.codec.Binary, hint ).content_type: MediaType(schema=self.schema(hint)) }, ) else: param = parameters[name] if param.default is not param.empty: hint = Annotated[hint, Default(param.default)] param_in = fondat.http.get_param_in(method, name, hint) if is_instance(param_in, fondat.http.InQuery): name = param_in.name in_ = "query" style = "form" explode = False else: continue # ignore AsBody, InBody op.parameters.append( Parameter( name=name, in_=in_, description=self.description(annotated), required=param.default is param.empty, schema=self.schema(hint), style=style, explode=explode, ) ) body_type = fondat.http.get_body_type(method) if body_type: python_type, annotated = fondat.types.split_annotated(body_type) op.requestBody = RequestBody( description=self.description(annotated), content={ fondat.codec.get_codec( fondat.codec.Binary, body_type ).content_type: MediaType(schema=self.schema(body_type)) }, required=True, ) if "return" not in hints: op.responses[str(http.HTTPStatus.NO_CONTENT.value)] = Response( description="No content." ) op.responses = {key: op.responses[key] for key in sorted(op.responses.keys())} if not op.parameters: op.parameters = None op.security = self.security_requirements(fondat_op) return op
def _get_component_schema(annotated): for annotation in annotated: if annotation is ComponentSchema or is_instance(annotation, ComponentSchema): return annotation