Module aws_lambda_powertools.event_handler.openapi.dependant

Functions

def add_param_to_fields(*, field: ModelField, dependant: Dependant) ‑> None
Expand source code
def add_param_to_fields(
    *,
    field: ModelField,
    dependant: Dependant,
) -> None:
    """
    Adds a parameter to the list of parameters in the dependant model.

    Parameters
    ----------
    field: ModelField
        The field to add
    dependant: Dependant
        The dependant model to add the field to

    """
    field_info = cast(Param, field.field_info)

    # Dictionary to map ParamTypes to their corresponding lists in dependant
    param_type_map = {
        ParamTypes.path: dependant.path_params,
        ParamTypes.query: dependant.query_params,
        ParamTypes.header: dependant.header_params,
        ParamTypes.cookie: dependant.cookie_params,
    }

    # Check if field_info.in_ is a valid key in param_type_map and append the field to the corresponding list
    # or raise an exception if it's not a valid key.
    if field_info.in_ in param_type_map:
        param_type_map[field_info.in_].append(field)
    else:
        raise AssertionError(f"Unsupported param type: {field_info.in_}")

Adds a parameter to the list of parameters in the dependant model.

Parameters

field : ModelField
The field to add
dependant : Dependant
The dependant model to add the field to
def get_body_field(*, dependant: Dependant, name: str) ‑> ModelField | None
Expand source code
def get_body_field(*, dependant: Dependant, name: str) -> ModelField | None:
    """
    Get the Body field for a given Dependant object.
    """

    flat_dependant = get_flat_dependant(dependant)
    if not flat_dependant.body_params:
        return None

    first_param = flat_dependant.body_params[0]
    field_info = first_param.field_info

    # Handle the case where there is only one body parameter and it is embedded
    embed = getattr(field_info, "embed", None)
    body_param_names_set = {param.name for param in flat_dependant.body_params}
    if len(body_param_names_set) == 1 and not embed:
        return first_param

    # If one field requires to embed, all have to be embedded
    for param in flat_dependant.body_params:
        setattr(param.field_info, "embed", True)  # noqa: B010

    # Generate a custom body model for this endpoint
    model_name = "Body_" + name
    body_model = create_body_model(fields=flat_dependant.body_params, model_name=model_name)

    required = any(True for f in flat_dependant.body_params if f.required)

    body_field_info, body_field_info_kwargs = get_body_field_info(
        body_model=body_model,
        flat_dependant=flat_dependant,
        required=required,
    )

    final_field = create_response_field(
        name="body",
        type_=body_model,
        required=required,
        alias="body",
        field_info=body_field_info(**body_field_info_kwargs),
    )
    return final_field

Get the Body field for a given Dependant object.

def get_body_field_info(*, body_model: type[BaseModel], flat_dependant: Dependant, required: bool) ‑> tuple[type[Body], dict[str, Any]]
Expand source code
def get_body_field_info(
    *,
    body_model: type[BaseModel],
    flat_dependant: Dependant,
    required: bool,
) -> tuple[type[Body], dict[str, Any]]:
    """
    Get the Body field info and kwargs for a given body model.
    """

    body_field_info_kwargs: dict[str, Any] = {"annotation": body_model, "alias": "body"}

    if not required:
        body_field_info_kwargs["default"] = None

    if any(isinstance(f.field_info, _File) for f in flat_dependant.body_params):
        # MAINTENANCE: body_field_info: type[Body] = _File
        raise NotImplementedError("_File fields are not supported in request bodies")
    elif any(isinstance(f.field_info, _Form) for f in flat_dependant.body_params):
        # MAINTENANCE: body_field_info: type[Body] = _Form
        raise NotImplementedError("_Form fields are not supported in request bodies")
    else:
        body_field_info = Body

        body_param_media_types = [
            f.field_info.media_type for f in flat_dependant.body_params if isinstance(f.field_info, Body)
        ]
        if len(set(body_param_media_types)) == 1:
            body_field_info_kwargs["media_type"] = body_param_media_types[0]

    return body_field_info, body_field_info_kwargs

Get the Body field info and kwargs for a given body model.

def get_dependant(*,
path: str,
call: Callable[..., Any],
name: str | None = None,
responses: dict[int, OpenAPIResponse] | None = None) ‑> Dependant
Expand source code
def get_dependant(
    *,
    path: str,
    call: Callable[..., Any],
    name: str | None = None,
    responses: dict[int, OpenAPIResponse] | None = None,
) -> Dependant:
    """
    Returns a dependant model for a handler function. A dependant model is a model that contains
    the parameters and return value of a handler function.

    Parameters
    ----------
    path: str
        The path template
    call: Callable[..., Any]
        The handler function
    name: str, optional
        The name of the handler function
    responses: list[dict[int, OpenAPIResponse]], optional
        The list of extra responses for the handler function

    Returns
    -------
    Dependant
        The dependant model for the handler function
    """
    path_param_names = get_path_param_names(path)
    endpoint_signature = get_typed_signature(call)
    signature_params = endpoint_signature.parameters

    dependant = Dependant(
        call=call,
        name=name,
        path=path,
    )

    # Add each parameter to the dependant model
    for param_name, param in signature_params.items():
        # If the parameter is a path parameter, we need to set the in_ field to "path".
        is_path_param = param_name in path_param_names

        # Analyze the parameter to get the Pydantic field.
        param_field = analyze_param(
            param_name=param_name,
            annotation=param.annotation,
            value=param.default,
            is_path_param=is_path_param,
            is_response_param=False,
        )
        if param_field is None:
            raise AssertionError(f"Parameter field is None for param: {param_name}")

        if is_body_param(param_field=param_field, is_path_param=is_path_param):
            dependant.body_params.append(param_field)
        else:
            add_param_to_fields(field=param_field, dependant=dependant)

    _add_return_annotation(dependant, endpoint_signature)
    _add_extra_responses(dependant, responses)

    return dependant

Returns a dependant model for a handler function. A dependant model is a model that contains the parameters and return value of a handler function.

Parameters

path : str
The path template
call : Callable[…, Any]
The handler function
name : str, optional
The name of the handler function
responses : list[dict[int, OpenAPIResponse]], optional
The list of extra responses for the handler function

Returns

Dependant
The dependant model for the handler function
def get_flat_params(dependant: Dependant) ‑> list[ModelField]
Expand source code
def get_flat_params(dependant: Dependant) -> list[ModelField]:
    """
    Get a list of all the parameters from a Dependant object.

    Parameters
    ----------
    dependant : Dependant
        The Dependant object containing the parameters.

    Returns
    -------
    list[ModelField]
        A list of ModelField objects containing the flat parameters from the Dependant object.

    """
    flat_dependant = get_flat_dependant(dependant)
    return (
        flat_dependant.path_params
        + flat_dependant.query_params
        + flat_dependant.header_params
        + flat_dependant.cookie_params
    )

Get a list of all the parameters from a Dependant object.

Parameters

dependant : Dependant
The Dependant object containing the parameters.

Returns

list[ModelField]
A list of ModelField objects containing the flat parameters from the Dependant object.
def get_path_param_names(path: str) ‑> set[str]
Expand source code
def get_path_param_names(path: str) -> set[str]:
    """
    Returns the path parameter names from a path template. Those are the strings between { and }.

    Parameters
    ----------
    path: str
        The path template

    Returns
    -------
    set[str]
        The path parameter names

    """
    return set(re.findall("{(.*?)}", path))

Returns the path parameter names from a path template. Those are the strings between { and }.

Parameters

path : str
The path template

Returns

set[str]
The path parameter names
def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) ‑> Any
Expand source code
def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
    """
    Evaluates a type annotation, which can be a string or a ForwardRef.
    """
    if isinstance(annotation, str):
        annotation = ForwardRef(annotation)
        annotation = evaluate_forwardref(annotation, globalns, globalns)
    return annotation

Evaluates a type annotation, which can be a string or a ForwardRef.

def get_typed_signature(call: Callable[..., Any]) ‑> inspect.Signature
Expand source code
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
    """
    Returns a typed signature for a callable, resolving forward references.

    Parameters
    ----------
    call: Callable[..., Any]
        The callable to get the signature for

    Returns
    -------
    inspect.Signature
        The typed signature
    """
    signature = inspect.signature(call)

    # Gets the global namespace for the call. This is used to resolve forward references.
    globalns = getattr(call, "__globals__", {})

    typed_params = [
        inspect.Parameter(
            name=param.name,
            kind=param.kind,
            default=param.default,
            annotation=get_typed_annotation(param.annotation, globalns),
        )
        for param in signature.parameters.values()
    ]

    # If the return annotation is not empty, add it to the signature.
    if signature.return_annotation is not inspect.Signature.empty:
        return_param = inspect.Parameter(
            name="Return",
            kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
            default=None,
            annotation=get_typed_annotation(signature.return_annotation, globalns),
        )
        return inspect.Signature(typed_params, return_annotation=return_param.annotation)
    else:
        return inspect.Signature(typed_params)

Returns a typed signature for a callable, resolving forward references.

Parameters

call : Callable[…, Any]
The callable to get the signature for

Returns

inspect.Signature
The typed signature
def is_body_param(*, param_field: ModelField, is_path_param: bool) ‑> bool
Expand source code
def is_body_param(*, param_field: ModelField, is_path_param: bool) -> bool:
    """
    Returns whether a parameter is a request body parameter, by checking if it is a scalar field or a body field.

    Parameters
    ----------
    param_field: ModelField
        The parameter field
    is_path_param: bool
        Whether the parameter is a path parameter

    Returns
    -------
    bool
        Whether the parameter is a request body parameter
    """
    if is_path_param:
        if not is_scalar_field(field=param_field):
            raise AssertionError("Path params must be of one of the supported types")
        return False
    elif is_scalar_field(field=param_field):
        return False
    elif isinstance(param_field.field_info, (Query, Header)) and is_scalar_sequence_field(param_field):
        return False
    else:
        if not isinstance(param_field.field_info, Body):
            raise AssertionError(f"Param: {param_field.name} can only be a request body, use Body()")
        return True

Returns whether a parameter is a request body parameter, by checking if it is a scalar field or a body field.

Parameters

param_field : ModelField
The parameter field
is_path_param : bool
Whether the parameter is a path parameter

Returns

bool
Whether the parameter is a request body parameter