def get_openapi_path( *, route: routing.APIRoute, model_name_map: Dict[Type, str]) -> Tuple[Dict, Dict, Dict]: path = {} security_schemes: Dict[str, Any] = {} definitions: Dict[str, Any] = {} assert route.methods is not None, "Methods must be a list" for method in route.methods: operation = get_openapi_operation_metadata(route=route, method=method) parameters: List[Dict] = [] flat_dependant = get_flat_dependant(route.dependant) security_definitions, operation_security = get_openapi_security_definitions( flat_dependant=flat_dependant) if operation_security: operation.setdefault("security", []).extend(operation_security) if security_definitions: security_schemes.update(security_definitions) all_route_params = get_openapi_params(route.dependant) validation_definitions, operation_parameters = get_openapi_operation_parameters( all_route_params=all_route_params) definitions.update(validation_definitions) parameters.extend(operation_parameters) if parameters: operation["parameters"] = parameters if method in METHODS_WITH_BODY: request_body_oai = get_openapi_operation_request_body( body_field=route.body_field, model_name_map=model_name_map) if request_body_oai: operation["requestBody"] = request_body_oai status_code = str(route.status_code) response_schema = {"type": "string"} if lenient_issubclass(route.content_type, JSONResponse): if route.response_field: response_schema, _ = field_schema( route.response_field, model_name_map=model_name_map, ref_prefix=REF_PREFIX, ) else: response_schema = {} content = {route.content_type.media_type: {"schema": response_schema}} operation["responses"] = { status_code: { "description": route.response_description, "content": content } } if all_route_params or route.body_field: operation["responses"][str(HTTP_422_UNPROCESSABLE_ENTITY)] = { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": REF_PREFIX + "HTTPValidationError" } } }, } path[method.lower()] = operation return path, security_schemes, definitions
def get_openapi_params(dependant: Dependant) -> List[ModelField]: flat_dependant = get_flat_dependant(dependant, skip_repeats=True) return ( flat_dependant.path_params + flat_dependant.query_params + flat_dependant.header_params + flat_dependant.cookie_params )
def get_openapi_params(dependant: Dependant) -> List[Field]: flat_dependant = get_flat_dependant(dependant) return ( flat_dependant.path_params + flat_dependant.query_params + flat_dependant.header_params + flat_dependant.cookie_params )
def get_openapi_path( *, route: routing.APIRoute, model_name_map: Dict[Type, str] ) -> Tuple[Dict, Dict, Dict]: path = {} security_schemes: Dict[str, Any] = {} definitions: Dict[str, Any] = {} assert route.methods is not None, "Methods must be a list" assert route.response_class, "A response class is needed to generate OpenAPI" route_response_media_type: Optional[str] = route.response_class.media_type if route.include_in_schema: for method in route.methods: operation = get_openapi_operation_metadata(route=route, method=method) parameters: List[Dict] = [] flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True) security_definitions, operation_security = get_openapi_security_definitions( flat_dependant=flat_dependant ) if operation_security: operation.setdefault("security", []).extend(operation_security) if security_definitions: security_schemes.update(security_definitions) all_route_params = get_openapi_params(route.dependant) operation_parameters = get_openapi_operation_parameters(all_route_params) parameters.extend(operation_parameters) if parameters: operation["parameters"] = parameters if method in METHODS_WITH_BODY: request_body_oai = get_openapi_operation_request_body( body_field=route.body_field, model_name_map=model_name_map ) if request_body_oai: operation["requestBody"] = request_body_oai if route.callbacks: callbacks = {} for callback in route.callbacks: cb_path, cb_security_schemes, cb_definitions, = get_openapi_path( route=callback, model_name_map=model_name_map ) callbacks[callback.name] = {callback.path: cb_path} operation["callbacks"] = callbacks if route.responses: for (additional_status_code, response) in route.responses.items(): assert isinstance( response, dict ), "An additional response must be a dict" field = route.response_fields.get(additional_status_code) if field: response_schema, _, _ = field_schema( field, model_name_map=model_name_map, ref_prefix=REF_PREFIX ) response.setdefault("content", {}).setdefault( route_response_media_type or "application/json", {} )["schema"] = response_schema status_text: Optional[str] = status_code_ranges.get( str(additional_status_code).upper() ) or http.client.responses.get(int(additional_status_code)) response.setdefault( "description", status_text or "Additional Response" ) status_code_key = str(additional_status_code).upper() if status_code_key == "DEFAULT": status_code_key = "default" operation.setdefault("responses", {})[status_code_key] = response status_code = str(route.status_code) operation.setdefault("responses", {}).setdefault(status_code, {})[ "description" ] = route.response_description if ( route_response_media_type and route.status_code not in STATUS_CODES_WITH_NO_BODY ): response_schema = {"type": "string"} if lenient_issubclass(route.response_class, JSONResponse): if route.response_field: response_schema, _, _ = field_schema( route.response_field, model_name_map=model_name_map, ref_prefix=REF_PREFIX, ) else: response_schema = {} operation.setdefault("responses", {}).setdefault( status_code, {} ).setdefault("content", {}).setdefault(route_response_media_type, {})[ "schema" ] = response_schema http422 = str(HTTP_422_UNPROCESSABLE_ENTITY) if (all_route_params or route.body_field) and not any( [ status in operation["responses"] for status in [http422, "4XX", "default"] ] ): operation["responses"][http422] = { "description": "Validation Error", "content": { "application/json": { "schema": {"$ref": REF_PREFIX + "HTTPValidationError"} } }, } if "ValidationError" not in definitions: definitions.update( { "ValidationError": validation_error_definition, "HTTPValidationError": validation_error_response_definition, } ) path[method.lower()] = operation return path, security_schemes, definitions
def __init__( self, entrypoint: 'Entrypoint', path: str, *, name: str = None, errors: Sequence[Type[BaseError]] = None, common_dependencies: Sequence[Depends] = None, response_class: Type[Response] = JSONResponse, **kwargs, ): name = name or 'entrypoint' _, path_format, _ = compile_path(path) _Request = JsonRpcRequest common_dependant = Dependant(path=path_format) if common_dependencies: insert_dependencies(common_dependant, common_dependencies) fix_query_dependencies(common_dependant) common_dependant = get_flat_dependant(common_dependant, skip_repeats=True) if common_dependant.body_params: _Request = make_request_model(name, entrypoint.callee_module, common_dependant.body_params) # This is only necessary for generating OpenAPI def endpoint(__request__: _Request): del __request__ responses = errors_responses(errors) super().__init__( path, endpoint, methods=['POST'], name=name, response_class=response_class, response_model=JsonRpcResponse, responses=responses, **kwargs, ) flat_dependant = get_flat_dependant(self.dependant, skip_repeats=True) if len(flat_dependant.body_params) > 1: body_params = [p for p in flat_dependant.body_params if p.type_ is not _Request] raise RuntimeError( f"Entrypoint shared dependencies can't use 'Body' parameters: " f"params={body_params}" ) if flat_dependant.query_params: raise RuntimeError( f"Entrypoint shared dependencies can't use 'Query' parameters: " f"params={flat_dependant.query_params}" ) self.shared_dependant = clone_dependant(self.dependant) # No shared 'Body' params, because each JSON-RPC request in batch has own body self.shared_dependant.body_params = [] # Add dependencies and other parameters from common_dependant for correct OpenAPI generation self.dependant.path_params.extend(common_dependant.path_params) self.dependant.header_params.extend(common_dependant.header_params) self.dependant.cookie_params.extend(common_dependant.cookie_params) self.dependant.dependencies.extend(common_dependant.dependencies) self.dependant.security_requirements.extend(common_dependant.security_requirements) self.app = request_response(self.handle_http_request) self.entrypoint = entrypoint self.common_dependencies = common_dependencies
def __init__( self, entrypoint: 'Entrypoint', path: str, func: Union[FunctionType, CoroutineType], *, result_model: Type[Any] = None, name: str = None, errors: Sequence[Type[BaseError]] = None, dependencies: Sequence[Depends] = None, response_class: Type[Response] = JSONResponse, **kwargs, ): name = name or func.__name__ result_model = result_model or func.__annotations__.get('return') _, path_format, _ = compile_path(path) func_dependant = get_dependant(path=path_format, call=func) insert_dependencies(func_dependant, dependencies) insert_dependencies(func_dependant, entrypoint.common_dependencies) fix_query_dependencies(func_dependant) flat_dependant = get_flat_dependant(func_dependant, skip_repeats=True) _Request = make_request_model(name, func.__module__, flat_dependant.body_params) @component_name(f'_Response[{name}]', func.__module__) class _Response(BaseModel): jsonrpc: StrictStr = Field('2.0', const=True, example='2.0') id: Union[StrictStr, int] = Field(None, example=0) result: result_model class Config: extra = 'forbid' # Only needed to generate OpenAPI async def endpoint(__request__: _Request): del __request__ endpoint.__name__ = func.__name__ endpoint.__doc__ = func.__doc__ responses = errors_responses(errors) super().__init__( path, endpoint, methods=['POST'], name=name, response_class=response_class, response_model=_Response, response_model_exclude_unset=True, responses=responses, **kwargs, ) # Add dependencies and other parameters from func_dependant for correct OpenAPI generation self.dependant.path_params = func_dependant.path_params self.dependant.header_params = func_dependant.header_params self.dependant.cookie_params = func_dependant.cookie_params self.dependant.dependencies = func_dependant.dependencies self.dependant.security_requirements = func_dependant.security_requirements self.func = func self.func_dependant = func_dependant self.entrypoint = entrypoint self.app = request_response(self.handle_http_request)
def get_openapi_path( *, route: routing.APIRoute, model_name_map: Dict[Type, str] ) -> Tuple[Dict, Dict, Dict]: path = {} security_schemes: Dict[str, Any] = {} definitions: Dict[str, Any] = {} assert route.methods is not None, "Methods must be a list" if route.include_in_schema: for method in route.methods: operation = get_openapi_operation_metadata(route=route, method=method) parameters: List[Dict] = [] flat_dependant = get_flat_dependant(route.dependant) security_definitions, operation_security = get_openapi_security_definitions( flat_dependant=flat_dependant ) if operation_security: operation.setdefault("security", []).extend(operation_security) if security_definitions: security_schemes.update(security_definitions) all_route_params = get_openapi_params(route.dependant) validation_definitions, operation_parameters = get_openapi_operation_parameters( all_route_params=all_route_params ) definitions.update(validation_definitions) parameters.extend(operation_parameters) if parameters: operation["parameters"] = parameters if method in METHODS_WITH_BODY: request_body_oai = get_openapi_operation_request_body( body_field=route.body_field, model_name_map=model_name_map ) if request_body_oai: operation["requestBody"] = request_body_oai if "ValidationError" not in definitions: definitions["ValidationError"] = validation_error_definition definitions[ "HTTPValidationError" ] = validation_error_response_definition if route.responses: for (additional_status_code, response) in route.responses.items(): assert isinstance( response, dict ), "An additional response must be a dict" field = route.response_fields.get(additional_status_code) if field: response_schema, _ = field_schema( field, model_name_map=model_name_map, ref_prefix=REF_PREFIX ) response.setdefault("content", {}).setdefault( "application/json", {} )["schema"] = response_schema status_text = http.client.responses.get(int(additional_status_code)) response.setdefault( "description", status_text or "Additional Response" ) operation.setdefault("responses", {})[ str(additional_status_code) ] = response status_code = str(route.status_code) response_schema = {"type": "string"} if lenient_issubclass(route.response_class, JSONResponse): if route.response_field: response_schema, _ = field_schema( route.response_field, model_name_map=model_name_map, ref_prefix=REF_PREFIX, ) else: response_schema = {} operation.setdefault("responses", {}).setdefault(status_code, {})[ "description" ] = route.response_description operation.setdefault("responses", {}).setdefault( status_code, {} ).setdefault("content", {}).setdefault(route.response_class.media_type, {})[ "schema" ] = response_schema if all_route_params or route.body_field: operation["responses"][str(HTTP_422_UNPROCESSABLE_ENTITY)] = { "description": "Validation Error", "content": { "application/json": { "schema": {"$ref": REF_PREFIX + "HTTPValidationError"} } }, } path[method.lower()] = operation return path, security_schemes, definitions
def get_openapi_path( *, route: routing.APIRoute, model_name_map: Dict[type, str], operation_ids: Set[str] ) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]: path = {} security_schemes: Dict[str, Any] = {} definitions: Dict[str, Any] = {} assert route.methods is not None, "Methods must be a list" if isinstance(route.response_class, DefaultPlaceholder): current_response_class: Type[Response] = route.response_class.value else: current_response_class = route.response_class assert current_response_class, "A response class is needed to generate OpenAPI" route_response_media_type: Optional[ str] = current_response_class.media_type if route.include_in_schema: for method in route.methods: operation = get_openapi_operation_metadata( route=route, method=method, operation_ids=operation_ids) parameters: List[Dict[str, Any]] = [] flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True) security_definitions, operation_security = get_openapi_security_definitions( flat_dependant=flat_dependant) if operation_security: operation.setdefault("security", []).extend(operation_security) if security_definitions: security_schemes.update(security_definitions) all_route_params = get_flat_params(route.dependant) operation_parameters = get_openapi_operation_parameters( all_route_params=all_route_params, model_name_map=model_name_map) parameters.extend(operation_parameters) if parameters: operation["parameters"] = list( {param["name"]: param for param in parameters}.values()) if method in METHODS_WITH_BODY: request_body_oai = get_openapi_operation_request_body( body_field=route.body_field, model_name_map=model_name_map) if request_body_oai: operation["requestBody"] = request_body_oai if route.callbacks: callbacks = {} for callback in route.callbacks: if isinstance(callback, routing.APIRoute): ( cb_path, cb_security_schemes, cb_definitions, ) = get_openapi_path( route=callback, model_name_map=model_name_map, operation_ids=operation_ids, ) callbacks[callback.name] = {callback.path: cb_path} operation["callbacks"] = callbacks if route.status_code is not None: status_code = str(route.status_code) else: # It would probably make more sense for all response classes to have an # explicit default status_code, and to extract it from them, instead of # doing this inspection tricks, that would probably be in the future # TODO: probably make status_code a default class attribute for all # responses in Starlette response_signature = inspect.signature( current_response_class.__init__) status_code_param = response_signature.parameters.get( "status_code") if status_code_param is not None: if isinstance(status_code_param.default, int): status_code = str(status_code_param.default) operation.setdefault("responses", {}).setdefault( status_code, {})["description"] = route.response_description if (route_response_media_type and route.status_code not in STATUS_CODES_WITH_NO_BODY): response_schema = {"type": "string"} if lenient_issubclass(current_response_class, JSONResponse): if route.response_field: response_schema, _, _ = field_schema( route.response_field, model_name_map=model_name_map, ref_prefix=REF_PREFIX, ) else: response_schema = {} operation.setdefault("responses", {}).setdefault( status_code, {}).setdefault("content", {}).setdefault( route_response_media_type, {})["schema"] = response_schema if route.responses: operation_responses = operation.setdefault("responses", {}) for ( additional_status_code, additional_response, ) in route.responses.items(): process_response = additional_response.copy() process_response.pop("model", None) status_code_key = str(additional_status_code).upper() if status_code_key == "DEFAULT": status_code_key = "default" openapi_response = operation_responses.setdefault( status_code_key, {}) assert isinstance( process_response, dict), "An additional response must be a dict" field = route.response_fields.get(additional_status_code) additional_field_schema: Optional[Dict[str, Any]] = None if field: additional_field_schema, _, _ = field_schema( field, model_name_map=model_name_map, ref_prefix=REF_PREFIX) media_type = route_response_media_type or "application/json" additional_schema = (process_response.setdefault( "content", {}).setdefault(media_type, {}).setdefault("schema", {})) deep_dict_update(additional_schema, additional_field_schema) status_text: Optional[str] = status_code_ranges.get( str(additional_status_code).upper() ) or http.client.responses.get(int(additional_status_code)) description = (process_response.get("description") or openapi_response.get("description") or status_text or "Additional Response") deep_dict_update(openapi_response, process_response) openapi_response["description"] = description http422 = str(HTTP_422_UNPROCESSABLE_ENTITY) if (all_route_params or route.body_field) and not any([ status in operation["responses"] for status in [http422, "4XX", "default"] ]): operation["responses"][http422] = { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": REF_PREFIX + "HTTPValidationError" } } }, } if "ValidationError" not in definitions: definitions.update({ "ValidationError": validation_error_definition, "HTTPValidationError": validation_error_response_definition, }) if route.openapi_extra: deep_dict_update(operation, route.openapi_extra) path[method.lower()] = operation return path, security_schemes, definitions