Example #1
0
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
        )
Example #2
0
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
        )
Example #3
0
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)
Example #4
0
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)
Example #5
0
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,
        )
Example #6
0
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,
        )
Example #7
0
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
Example #8
0
 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
Example #9
0
 def description(annotated):
     for annotation in annotated:
         if is_instance(annotation, str):
             return annotation
         elif is_instance(annotation, fondat.annotation.Description):
             return annotation.value
Example #10
0
 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
Example #11
0
def _get_component_schema(annotated):
    for annotation in annotated:
        if annotation is ComponentSchema or is_instance(annotation, ComponentSchema):
            return annotation