HTTP_METHODS_WITH_JSON_BODY = ('PATCH', 'POST', 'PUT') class ForbiddenError(DoctorError):
    """Raised when a request is forbidden for the authorized user.

    Corresponds to a HTTP 403 Forbidden error.
    """
    pass class ImmutableError(DoctorError):
    """Raised for immutable errors for a schema.

    Corresponds to a HTTP 409 Conflict error.
    """
    pass class InvalidValueError(DoctorError):
    """
    Raised for errors when doing more complex validation that
    can't be done in a schema.

    Corresponds to a HTTP 400 Bad Request error.
    """
    pass class NotFoundError(DoctorError):
    """Raised when a resource is not found.

    Corresponds to a HTTP 404 Not Found error.
    """
    pass class TypeSystemError(DoctorError):
    """An error that represents an invalid value for a type.

    This is borrowed from apistar:
    https://github.com/encode/apistar/blob/
    50dd15f0878f0a7c50ce829a72adb276782bcb78/apistar/exceptions.py#L4-L15

    :param detail: Detail about the error.
    :param cls: The class type that was being instantiated.
    :param code: The error code.
    :param errors: A dict containing all validation errors during the request.
        The key is the param name and the value is the error message.
    """
    def __init__(self,
                 detail: Union[str, dict] = None,
                 cls: type = None,
                 code: str = None,
                 errors: dict = None) -> None:

        if cls is not None and code is not None:
            cls_errors = getattr(cls, 'errors')
            detail = cls_errors[code].format(**cls.__dict__)

        self.detail = detail
        if errors and len(errors) == 1:
            param = list(errors.keys())[0]
            msg = list(errors.values())[0]
            detail = '{} - {}'.format(param, msg)
        super().__init__(detail, errors=errors) class UnauthorizedError(DoctorError):
    """Raised when a request is unauthorized.

    Corresponds to a HTTP 401 Unauthorized error.
    """
    pass def map_param_names(
        req_params: dict, sig_params: List[inspect.Parameter]) -> dict:
    """Maps request param names to match logic function param names.

    If a doctor type defined a `param_name` attribute for the name of the
    parameter in the request, we should use that as the key when looking up
    the value for the request parameter.

    When we declare a type we can specify what the parameter name
    should be in the request that the annotated type should get mapped to.

    >>> from doctor.types import number
    >>> Latitude = number('The latitude', param_name='location.lat')
    >>> def my_logic(lat: Latitude): pass
    >>> request_params = {'location.lat': 45.2342343}

    In the above example doctor knows to pass the value at key `location.lat`
    to the logic function variable named `lat` since it's annotated by the
    `Latitude` type which specifies what the param_name is on the request.

    :param dict req_params: The parameters specified in the request.
    :param dict sig_params: The logic function's signature parameters.
    :returns: A dict of re-mapped params.
    """
    new_request_params = {}
    for k, param in sig_params.items():
        param_name = getattr(param.annotation, 'param_name', None)
        key = k if param_name is None else param_name
        if key in req_params:
            new_request_params[k] = req_params[key]
    return new_request_params def parse_form_and_query_params(req_params: dict, sig_params: dict) -> dict:
    """Uses the parameter annotations to coerce string params.

    This is used for HTTP requests, in which the form parameters are all
    strings, but need to be converted to the appropriate types before
    validating them.

    :param dict req_params: The parameters specified in the request.
    :param dict sig_params: The logic function's signature parameters.
    :returns: a dict of params parsed from the input dict.
    :raises TypeSystemError: If there are errors parsing values.
    """
    # Importing here to prevent circular dependencies.
    from doctor.types import SuperType, UnionType
    errors = {}
    parsed_params = {}
    for param, value in req_params.items():
        # Skip request variables not in the function signature.
        if param not in sig_params:
            continue
        # Skip coercing parameters not annotated by a doctor type.
        if not issubclass(sig_params[param].annotation, SuperType):
            continue

        # Check if the type has a custom parser for the parameter.
        custom_parser = sig_params[param].annotation.parser
        if custom_parser is not None:
            if not callable(custom_parser):
                warnings.warn(
                    'Parser `{}` is not callable, using default parser.'.format(
                        custom_parser))
                custom_parser = None

        try:
            if custom_parser is not None:
                parsed_params[param] = custom_parser(value)
            else:
                if issubclass(sig_params[param].annotation, UnionType):
                    json_type = [
                        _native_type_to_json[_type.native_type]
                        for _type in sig_params[param].annotation.types
                    ]
                else:
                    native_type = sig_params[param].annotation.native_type
                    json_type = [_native_type_to_json[native_type]]
                # If the type is nullable, also add null as an allowed type.
                if sig_params[param].annotation.nullable:
                    json_type.append('null')
                _, parsed_params[param] = parse_value(value, json_type)
        except ParseError as e:
            errors[param] = str(e)

    if errors:
        raise TypeSystemError(errors, errors=errors)

    return parsed_params class Response(Generic[CT]):
    """Represents a response.

    This object contains the response itself along with any additional headers
    that should be added and returned with the response data.  An instance of
    this class can be returned from a logic function in order to modify
    response headers.

    :param content: The data to be returned with the response.
    :param dict headers: A dict of response headers to include with the response
    :param int status_code: The status code for the response.
    """

    def __init__(self, content: CT, headers: dict = None,
                 status_code: int = None):
        self.content = content
        self.headers = headers
        self.status_code = status_code def create_routes(routes: Sequence[HTTPMethod], handle_http: Callable,
                  default_base_handler_class: Any) -> List[Tuple[str, Any]]:
    """Creates handler routes from the provided routes.

    :param routes: A tuple containing the route and another tuple with
        all http methods allowed for the route.
    :param handle_http: The HTTP handler function that should be
        used to wrap the logic functions.
    :param default_base_handler_class: The default base handler class that
        should be used.
    :returns: A list of tuples containing the route and generated handler.
    """
    created_routes = []
    all_handler_names = []
    for r in routes:
        handler = None
        if r.base_handler_class is not None:
            base_handler_class = r.base_handler_class
        else:
            base_handler_class = default_base_handler_class

        # Define the handler name.  To prevent issues where auto-generated
        # handler names conflict with existing, appending a number to the
        # end of the hanlder name if it already exists.
        handler_name = get_handler_name(r, r.methods[0].logic)
        if handler_name in all_handler_names:
            handler_name = '{}{}'.format(
                handler_name, len(all_handler_names))
        all_handler_names.append(handler_name)

        for method in r.methods:
            logic = method.logic
            http_method = method.method
            http_func = create_http_method(logic, http_method, handle_http,
                                           before=r.before, after=r.after)

            handler_methods_and_properties = {
                '__name__': handler_name,
                '_doctor_heading': r.heading,
                'methods': set([http_method.upper()]),
                http_method: http_func,
            }
            if handler is None:
                handler = type(
                    handler_name, (base_handler_class,),
                    handler_methods_and_properties)
            else:
                setattr(handler, http_method, http_func)
                # This is specific to Flask.  Its MethodView class
                # initializes the methods attribute in __new__ so we
                # need to add all the other http methods we are defining
                # on the handler after it gets created by type.
                if hasattr(handler, 'methods'):
                    handler.methods.add(http_method.upper())
        created_routes.append((r.route, handler))
    return created_routes class Route(object):

    """Represents a route.

    :param route: The route path, e.g. `r'^/foo/<int:foo_id>/?$'`
    :param methods: A tuple of defined HTTPMethods for the route.
    :param heading: An optional heading that this route should be grouped
        under in the api documentation.
    :param base_handler_class: The base handler class to use.
    :param handler_name: The name that should be given to the handler class.
    :param before: A function to be called before the logic function associated
        with the route.
    :param after: A function to be called after the logic function associated
        with the route.
    """
    def __init__(self, route: str, methods: Sequence[HTTPMethod],
                 heading: str = 'API', base_handler_class = None,
                 handler_name: str = None, before: Callable = None,
                 after: Callable = None):
        self.after = after
        self.base_handler_class = base_handler_class
        self.before = before
        self.handler_name = handler_name
        self.heading = heading
        self.methods = methods
        self.route = route
import logging
import os
from typing import Callable, Dict, List, Tuple, Union
from typing_inspect import get_origin
    from flask import current_app, request
    from flask_restful import Resource
    from werkzeug.exceptions import (BadRequest, Conflict, Forbidden,
                                     HTTPException, NotFound, Unauthorized,
                                     InternalServerError)
from .constants import HTTP_METHODS_WITH_JSON_BODY
from .errors import (ForbiddenError, ImmutableError, InvalidValueError,
                     NotFoundError, TypeSystemError, UnauthorizedError)
from .parsers import map_param_names, parse_form_and_query_params
from .response import Response
from .routing import create_routes as doctor_create_routes
from .routing import Route

    If the environment variable `RAISE_RESPONSE_VALIDATION_ERRORS` is set,
    it will return True.

    :returns: True if it should, False otherwise.
    """
    return bool(os.environ.get('RAISE_RESPONSE_VALIDATION_ERRORS', False))


def handle_http(handler: Resource, args: Tuple, kwargs: Dict, logic: Callable):
    """Handle a Flask HTTP request

    :param handler: flask_restful.Resource: An instance of a Flask Restful
        resource class.
    :param tuple args: Any positional arguments passed to the wrapper method.
    :param dict kwargs: Any keyword arguments passed to the wrapper method.
    :param callable logic: The callable to invoke to actually perform the
        business logic for this request.
    """
    try:
        # We are checking mimetype here instead of content_type because
        # mimetype is just the content-type, where as content_type can
        # contain encoding, charset, and language information.  e.g.
        # `Content-Type: application/json; charset=UTF8`
        if (request.mimetype == 'application/json' and
                request.method in HTTP_METHODS_WITH_JSON_BODY):
            # This is a proper typed JSON request. The parameters will be
            # encoded into the request body as a JSON blob.
            if not logic._doctor_req_obj_type:
                request_params = map_param_names(
                    request.json, logic._doctor_signature.parameters)
            else:
                request_params = request.json
        else:
            # Try to parse things from normal HTTP parameters
            request_params = parse_form_and_query_params(
                request.values, logic._doctor_signature.parameters)

        params = request_params
        # Only filter out additional params if a req_obj_type was not specified.
        if not logic._doctor_req_obj_type:
            # Filter out any params not part of the logic signature.
            all_params = logic._doctor_params.all
            params = {k: v for k, v in params.items() if k in all_params}
        params.update(**kwargs)

        # Check for required params
        missing = []
        for required in logic._doctor_params.required:
            if required not in params:
                missing.append(required)
        if missing:
            verb = 'are'
            if len(missing) == 1:
                verb = 'is'
                missing = missing[0]
            error = '{} {} required.'.format(missing, verb)
            raise InvalidValueError(error)

        # Validate and coerce parameters to the appropriate types.
        errors = {}
        sig = logic._doctor_signature
        # If a `req_obj_type` was defined for the route, pass all request
        # params to that type for validation/coercion
        if logic._doctor_req_obj_type:
            annotation = logic._doctor_req_obj_type
            try:
                # NOTE: We calculate the value before applying native type in
                # order to support UnionType types which dynamically modifies
                # the native_type property based on the initialized value.
                value = annotation(params)
                params = annotation.native_type(value)
            except TypeError:
                logging.exception(
                    'Error casting and validating params with value `%s`.',
                    params)
                raise
            except TypeSystemError as e:
                errors['__all__'] = e.detail
        else:
            for name, value in params.items():
                annotation = sig.parameters[name].annotation
                if annotation.nullable and value is None:
                    continue
                try:
                    # NOTE: We calculate the value before applying native type
                    # in order to support UnionType types which dynamically
                    # modifies the native_type property based on the initialized
                    # value.
                    value = annotation(value)
                    params[name] = annotation.native_type(value)
                except TypeSystemError as e:
                    errors[name] = e.detail
        if errors:
            raise TypeSystemError(errors, errors=errors)

        if logic._doctor_req_obj_type:
            # Pass any positional arguments followed by the coerced request
            # parameters to the logic function.
            response = logic(*args, params)
        else:
            # Only pass request parameters defined by the logic signature.
            logic_params = {k: v for k, v in params.items()
                            if k in logic._doctor_params.logic}
            response = logic(*args, **logic_params)

        # response validation
        if sig.return_annotation != sig.empty:
            return_annotation = sig.return_annotation
            _response = response
            if isinstance(response, Response):
                _response = response.content
                # Check if our return annotation is a Response that supplied a
                # type to validate against.  If so, use that type for validation
                # e.g. def logic() -> Response[MyType]
                if ((get_origin(return_annotation) == Response) and
                        return_annotation.__args__ is not None):
                    return_annotation = return_annotation.__args__[0]
            try:
                return_annotation(_response)

            except TypeSystemError as e: