def get_param_field( *, param: inspect.Parameter, param_name: str, default_field_info: Type[params.Param] = params.Param, force_type: Optional[params.ParamTypes] = None, ignore_default: bool = False, ) -> ModelField: default_value: Any = Undefined had_schema = False if not param.default == param.empty and ignore_default is False: default_value = param.default if isinstance(default_value, FieldInfo): had_schema = True field_info = default_value default_value = field_info.default if (isinstance(field_info, params.Param) and getattr(field_info, "in_", None) is None): field_info.in_ = default_field_info.in_ if force_type: field_info.in_ = force_type # type: ignore else: field_info = default_field_info(default=default_value) required = True if default_value is Required or ignore_default: required = True default_value = None elif default_value is not Undefined: required = False annotation: Any = Any if not param.annotation == param.empty: annotation = param.annotation annotation = get_annotation_from_field_info(annotation, field_info, param_name) if not field_info.alias and getattr(field_info, "convert_underscores", None): alias = param.name.replace("_", "-") else: alias = field_info.alias or param.name field = create_response_field( name=param.name, type_=annotation, default=default_value, alias=alias, required=required, field_info=field_info, ) if not had_schema and not is_scalar_field(field=field): field.field_info = params.Body(field_info.default) if not had_schema and lenient_issubclass(field.type_, UploadFile): field.field_info = params.File(field_info.default) return field
def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]: 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 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: final_field = get_schema_compatible_field(field=first_param) check_file_field(final_field) return final_field # If one field requires to embed, all have to be embedded # in case a sub-dependency is evaluated with a single unique body field # That is combined (embedded) with other body fields for param in flat_dependant.body_params: setattr(param.field_info, "embed", True) model_name = "Body_" + name BodyModel: Type[BaseModel] = create_model(model_name) for f in flat_dependant.body_params: BodyModel.__fields__[f.name] = get_schema_compatible_field(field=f) required = any(True for f in flat_dependant.body_params if f.required) BodyFieldInfo_kwargs: Dict[str, Any] = dict(default=None) if any( isinstance(f.field_info, params.File) for f in flat_dependant.body_params): BodyFieldInfo: Type[params.Body] = params.File elif any( isinstance(f.field_info, params.Form) for f in flat_dependant.body_params): BodyFieldInfo = params.Form else: BodyFieldInfo = params.Body body_param_media_types = [ getattr(f.field_info, "media_type") for f in flat_dependant.body_params if isinstance(f.field_info, params.Body) ] if len(set(body_param_media_types)) == 1: BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0] final_field = create_response_field( name="body", type_=BodyModel, required=required, alias="body", field_info=BodyFieldInfo(**BodyFieldInfo_kwargs), ) check_file_field(final_field) return final_field
def get_param_field( *, param: inspect.Parameter, param_name: str, default_field_info: Type[params.Param] = params.Param, force_type: params.ParamTypes = None, ignore_default: bool = False, ) -> ModelField: default_value = Required had_schema = False if not param.default == param.empty and ignore_default is False: default_value = param.default if isinstance(default_value, FieldInfo): had_schema = True field_info = default_value default_value = field_info.default if (isinstance(field_info, params.Param) and getattr(field_info, "in_", None) is None): field_info.in_ = default_field_info.in_ if force_type: field_info.in_ = force_type # type: ignore else: field_info = default_field_info(default_value) required = default_value == Required annotation: Any = Any if not param.annotation == param.empty: annotation = param.annotation annotation = get_annotation_from_field_info(annotation, field_info, param_name) if not field_info.alias and getattr(field_info, "convert_underscores", None): alias = param.name.replace("_", "-") else: alias = field_info.alias or param.name field = create_response_field( name=param.name, type_=annotation, default=None if required else default_value, alias=alias, required=required, field_info=field_info, ) field.required = required if not had_schema and not is_scalar_field(field=field): if PYDANTIC_1: field.field_info = params.Body(field_info.default) else: field.schema = params.Body( field_info.default) # type: ignore # pragma: nocover return field
def get_schema_compatible_field(*, field: ModelField) -> ModelField: out_field = field if lenient_issubclass(field.type_, UploadFile): use_type: type = bytes if field.shape in sequence_shapes: use_type = List[bytes] out_field = create_response_field( name=field.name, type_=use_type, class_validators=field.class_validators, model_config=field.model_config, default=field.default, required=field.required, alias=field.alias, field_info=field.field_info, ) return out_field
def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]: flat_dependant = get_flat_dependant(dependant) if not flat_dependant.body_params: return None first_param = flat_dependant.body_params[0] field_info = get_field_info(first_param) embed = getattr(field_info, "embed", None) if len(flat_dependant.body_params) == 1 and not embed: return get_schema_compatible_field(field=first_param) model_name = "Body_" + name BodyModel = create_model(model_name) for f in flat_dependant.body_params: BodyModel.__fields__[f.name] = get_schema_compatible_field(field=f) required = any(True for f in flat_dependant.body_params if f.required) BodyFieldInfo_kwargs: Dict[str, Any] = dict(default=None) if any( isinstance(get_field_info(f), params.File) for f in flat_dependant.body_params): BodyFieldInfo: Type[params.Body] = params.File elif any( isinstance(get_field_info(f), params.Form) for f in flat_dependant.body_params): BodyFieldInfo = params.Form else: BodyFieldInfo = params.Body body_param_media_types = [ getattr(get_field_info(f), "media_type") for f in flat_dependant.body_params if isinstance(get_field_info(f), params.Body) ] if len(set(body_param_media_types)) == 1: BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0] return create_response_field( name="body", type_=BodyModel, required=required, alias="body", field_info=BodyFieldInfo(**BodyFieldInfo_kwargs), )
def __init__( self, path: str, endpoint: Callable, *, response_model: Type[Any] = None, status_code: int = 200, tags: List[str] = None, dependencies: Sequence[params.Depends] = None, summary: str = None, description: str = None, response_description: str = "Successful Response", responses: Dict[Union[int, str], Dict[str, Any]] = None, deprecated: bool = None, name: str = None, methods: Optional[Union[Set[str], List[str]]] = None, operation_id: str = None, response_model_include: Union[SetIntStr, DictIntStrAny] = None, response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(), response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, include_in_schema: bool = True, response_class: Optional[Type[Response]] = None, dependency_overrides_provider: Any = None, callbacks: Optional[List["APIRoute"]] = None, ) -> None: self.path = path self.endpoint = endpoint self.name = get_name(endpoint) if name is None else name self.path_regex, self.path_format, self.param_convertors = compile_path(path) if methods is None: methods = ["GET"] self.methods = set([method.upper() for method in methods]) self.unique_id = generate_operation_id_for_path( name=self.name, path=self.path_format, method=list(methods)[0] ) self.response_model = response_model if self.response_model: assert ( status_code not in STATUS_CODES_WITH_NO_BODY ), f"Status code {status_code} must not have a response body" response_name = "Response_" + self.unique_id self.response_field = create_response_field( name=response_name, type_=self.response_model ) # Create a clone of the field, so that a Pydantic submodel is not returned # as is just because it's an instance of a subclass of a more limited class # e.g. UserInDB (containing hashed_password) could be a subclass of User # that doesn't have the hashed_password. But because it's a subclass, it # would pass the validation and be returned as is. # By being a new field, no inheritance will be passed as is. A new model # will be always created. self.secure_cloned_response_field: Optional[ ModelField ] = create_cloned_field(self.response_field) else: self.response_field = None # type: ignore self.secure_cloned_response_field = None self.status_code = status_code self.tags = tags or [] if dependencies: self.dependencies = list(dependencies) else: self.dependencies = [] self.summary = summary self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "") # if a "form feed" character (page break) is found in the description text, # truncate description text to the content preceding the first "form feed" self.description = self.description.split("\f")[0] self.response_description = response_description self.responses = responses or {} response_fields = {} for additional_status_code, response in self.responses.items(): assert isinstance(response, dict), "An additional response must be a dict" model = response.get("model") if model: assert ( additional_status_code not in STATUS_CODES_WITH_NO_BODY ), f"Status code {additional_status_code} must not have a response body" response_name = f"Response_{additional_status_code}_{self.unique_id}" response_field = create_response_field(name=response_name, type_=model) response_fields[additional_status_code] = response_field if response_fields: self.response_fields: Dict[Union[int, str], ModelField] = response_fields else: self.response_fields = {} self.deprecated = deprecated self.operation_id = operation_id self.response_model_include = response_model_include self.response_model_exclude = response_model_exclude self.response_model_by_alias = response_model_by_alias self.response_model_exclude_unset = response_model_exclude_unset self.include_in_schema = include_in_schema self.response_class = response_class assert callable(endpoint), f"An endpoint must be a callable" self.dependant = get_dependant(path=self.path_format, call=self.endpoint) for depends in self.dependencies[::-1]: self.dependant.dependencies.insert( 0, get_parameterless_sub_dependant(depends=depends, path=self.path_format), ) self.body_field = get_body_field(dependant=self.dependant, name=self.unique_id) self.dependency_overrides_provider = dependency_overrides_provider self.callbacks = callbacks self.app = request_response(self.get_route_handler())