Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/connexion/operations/openapi.py: 25%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

110 statements  

1""" 

2This module defines an OpenAPIOperation class, a Connexion operation specific for OpenAPI 3 specs. 

3""" 

4 

5import logging 

6import typing as t 

7from http import HTTPStatus 

8 

9from connexion.datastructures import MediaTypeDict, NoContent 

10from connexion.operations.abstract import AbstractOperation 

11from connexion.uri_parsing import OpenAPIURIParser 

12from connexion.utils import build_example_from_schema, deep_get 

13 

14logger = logging.getLogger("connexion.operations.openapi3") 

15 

16 

17class OpenAPIOperation(AbstractOperation): 

18 """ 

19 A single API operation on a path. 

20 """ 

21 

22 def __init__( 

23 self, 

24 method, 

25 path, 

26 operation, 

27 resolver, 

28 path_parameters=None, 

29 app_security=None, 

30 security_schemes=None, 

31 components=None, 

32 randomize_endpoint=None, 

33 uri_parser_class=None, 

34 ): 

35 """ 

36 This class uses the OperationID identify the module and function that will handle the operation 

37 

38 From Swagger Specification: 

39 

40 **OperationID** 

41 

42 A friendly name for the operation. The id MUST be unique among all operations described in the API. 

43 Tools and libraries MAY use the operation id to uniquely identify an operation. 

44 

45 :param method: HTTP method 

46 :type method: str 

47 :param path: 

48 :type path: str 

49 :param operation: swagger operation object 

50 :type operation: dict 

51 :param resolver: Callable that maps operationID to a function 

52 :param path_parameters: Parameters defined in the path level 

53 :type path_parameters: list 

54 :param app_security: list of security rules the application uses by default 

55 :type app_security: list 

56 :param security_schemes: `Security Definitions Object 

57 <https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object>`_ 

58 :type security_schemes: dict 

59 :param components: `Components Object 

60 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#componentsObject>`_ 

61 :type components: dict 

62 :param randomize_endpoint: number of random characters to append to operation name 

63 :type randomize_endpoint: integer 

64 :param uri_parser_class: class to use for uri parsing 

65 :type uri_parser_class: AbstractURIParser 

66 """ 

67 self.components = components or {} 

68 

69 uri_parser_class = uri_parser_class or OpenAPIURIParser 

70 

71 self._router_controller = operation.get("x-openapi-router-controller") 

72 

73 super().__init__( 

74 method=method, 

75 path=path, 

76 operation=operation, 

77 resolver=resolver, 

78 app_security=app_security, 

79 security_schemes=security_schemes, 

80 randomize_endpoint=randomize_endpoint, 

81 uri_parser_class=uri_parser_class, 

82 ) 

83 

84 self._parameters = operation.get("parameters", []) 

85 if path_parameters: 

86 self._parameters += path_parameters 

87 

88 self._responses = operation.get("responses", {}) 

89 

90 # TODO figure out how to support multiple mimetypes 

91 # NOTE we currently just combine all of the possible mimetypes, 

92 # but we need to refactor to support mimetypes by response code 

93 response_content_types = [] 

94 for _, defn in self._responses.items(): 

95 response_content_types += defn.get("content", {}).keys() 

96 self._produces = response_content_types 

97 self._consumes = None 

98 

99 logger.debug("consumes: %s" % self.consumes) 

100 logger.debug("produces: %s" % self.produces) 

101 

102 @classmethod 

103 def from_spec(cls, spec, *args, path, method, resolver, **kwargs): 

104 return cls( 

105 method, 

106 path, 

107 spec.get_operation(path, method), 

108 resolver=resolver, 

109 path_parameters=spec.get_path_params(path), 

110 app_security=spec.security, 

111 security_schemes=spec.security_schemes, 

112 components=spec.components, 

113 *args, 

114 **kwargs, 

115 ) 

116 

117 @property 

118 def request_body(self): 

119 return self._operation.get("requestBody", {}) 

120 

121 @property 

122 def parameters(self): 

123 return self._parameters 

124 

125 @property 

126 def consumes(self): 

127 if self._consumes is None: 

128 request_content = self.request_body.get("content", {}) 

129 self._consumes = list(request_content.keys()) 

130 return self._consumes 

131 

132 @property 

133 def produces(self): 

134 return self._produces 

135 

136 def with_definitions(self, schema: dict): 

137 if self.components: 

138 schema.setdefault("schema", {}) 

139 schema["schema"]["components"] = self.components 

140 return schema 

141 

142 def response_schema(self, status_code=None, content_type=None): 

143 response_definition = self.response_definition(status_code, content_type) 

144 content_definition = response_definition.get("content", response_definition) 

145 content_definition = content_definition.get(content_type, content_definition) 

146 if "schema" in content_definition: 

147 return self.with_definitions(content_definition).get("schema", {}) 

148 return {} 

149 

150 def example_response(self, status_code=None, content_type=None): 

151 """ 

152 Returns example response from spec 

153 """ 

154 # simply use the first/lowest status code, this is probably 200 or 201 

155 status_code = status_code or sorted(self._responses.keys())[0] 

156 

157 content_type = content_type or self.get_mimetype() 

158 examples_path = [str(status_code), "content", content_type, "examples"] 

159 example_path = [str(status_code), "content", content_type, "example"] 

160 schema_example_path = [ 

161 str(status_code), 

162 "content", 

163 content_type, 

164 "schema", 

165 "example", 

166 ] 

167 schema_path = [str(status_code), "content", content_type, "schema"] 

168 

169 try: 

170 status_code = int(status_code) 

171 except ValueError: 

172 status_code = 200 

173 

174 if status_code == HTTPStatus.NO_CONTENT: 

175 return NoContent, status_code 

176 

177 try: 

178 # TODO also use example header? 

179 return ( 

180 list(deep_get(self._responses, examples_path).values())[0]["value"], 

181 status_code, 

182 ) 

183 except (KeyError, IndexError): 

184 pass 

185 try: 

186 return (deep_get(self._responses, example_path), status_code) 

187 except KeyError: 

188 pass 

189 try: 

190 return (deep_get(self._responses, schema_example_path), status_code) 

191 except KeyError: 

192 pass 

193 

194 try: 

195 schema = deep_get(self._responses, schema_path) 

196 except KeyError: 

197 return ("No example response or response schema defined.", status_code) 

198 

199 return (build_example_from_schema(schema), status_code) 

200 

201 def get_path_parameter_types(self): 

202 types = {} 

203 path_parameters = (p for p in self.parameters if p["in"] == "path") 

204 for path_defn in path_parameters: 

205 path_schema = path_defn["schema"] 

206 if ( 

207 path_schema.get("type") == "string" 

208 and path_schema.get("format") == "path" 

209 ): 

210 # path is special case for type 'string' 

211 path_type = "path" 

212 else: 

213 path_type = path_schema.get("type") 

214 types[path_defn["name"]] = path_type 

215 return types 

216 

217 def body_name(self, _content_type: str) -> str: 

218 return self.request_body.get("x-body-name", "body") 

219 

220 def body_schema(self, content_type: t.Optional[str] = None) -> dict: 

221 """ 

222 The body schema definition for this operation. 

223 """ 

224 return self.body_definition(content_type).get("schema", {}) 

225 

226 def body_definition(self, content_type: t.Optional[str] = None) -> dict: 

227 """ 

228 The body complete definition for this operation. 

229 

230 **There can be one "body" parameter at most.** 

231 """ 

232 if self.request_body: 

233 if content_type is None: 

234 # TODO: make content type required 

235 content_type = self.consumes[0] 

236 if len(self.consumes) > 1: 

237 logger.warning( 

238 "this operation accepts multiple content types, using %s", 

239 content_type, 

240 ) 

241 content_type_dict = MediaTypeDict(self.request_body.get("content", {})) 

242 res = content_type_dict.get(content_type, {}) 

243 return self.with_definitions(res) 

244 return {}