Conditions | 54 |
Total Lines | 211 |
Lines | 0 |
Ratio | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like Router.__call__() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | # Licensed to the StackStorm, Inc ('StackStorm') under one or more |
||
207 | def __call__(self, req): |
||
208 | """ |
||
209 | The method is invoked on every request and shows the lifecycle of the request received from |
||
210 | the middleware. |
||
211 | |||
212 | Although some middleware may use parts of the API spec, it is safe to assume that if you're |
||
213 | looking for the particular spec property handler, it's most likely a part of this method. |
||
214 | |||
215 | At the time of writing, the only property being utilized by middleware was `x-log-result`. |
||
216 | """ |
||
217 | endpoint, path_vars = self.match(req) |
||
218 | |||
219 | context = copy.copy(getattr(self, 'mock_context', {})) |
||
220 | |||
221 | # Handle security |
||
222 | if 'security' in endpoint: |
||
223 | security = endpoint.get('security') |
||
224 | else: |
||
225 | security = self.spec.get('security', []) |
||
226 | |||
227 | if self.auth and security: |
||
228 | try: |
||
229 | auth_resp = None |
||
230 | security_definitions = self.spec.get('securityDefinitions', {}) |
||
231 | for statement in security: |
||
232 | declaration, options = statement.copy().popitem() |
||
233 | definition = security_definitions[declaration] |
||
234 | |||
235 | if definition['type'] == 'apiKey': |
||
236 | if definition['in'] == 'header': |
||
237 | token = req.headers.get(definition['name']) |
||
238 | elif definition['in'] == 'query': |
||
239 | token = req.GET.get(definition['name']) |
||
240 | else: |
||
241 | token = None |
||
242 | |||
243 | if token: |
||
244 | if auth_resp: |
||
245 | raise auth_exc.MultipleAuthSourcesError( |
||
246 | 'Only one of Token or API key expected.') |
||
247 | |||
248 | auth_func = op_resolver(definition['x-operationId']) |
||
249 | auth_resp = auth_func(token) |
||
250 | |||
251 | context['user'] = User.get_by_name(auth_resp.user) |
||
252 | |||
253 | if 'user' not in context: |
||
254 | raise auth_exc.NoAuthSourceProvidedError('One of Token or API key required.') |
||
255 | except (auth_exc.NoAuthSourceProvidedError, |
||
256 | auth_exc.MultipleAuthSourcesError) as e: |
||
257 | LOG.error(str(e)) |
||
258 | return abort_unauthorized(str(e)) |
||
259 | except auth_exc.TokenNotProvidedError as e: |
||
260 | LOG.exception('Token is not provided.') |
||
261 | return abort_unauthorized(str(e)) |
||
262 | except auth_exc.TokenNotFoundError as e: |
||
263 | LOG.exception('Token is not found.') |
||
264 | return abort_unauthorized(str(e)) |
||
265 | except auth_exc.TokenExpiredError as e: |
||
266 | LOG.exception('Token has expired.') |
||
267 | return abort_unauthorized(str(e)) |
||
268 | except auth_exc.ApiKeyNotProvidedError as e: |
||
269 | LOG.exception('API key is not provided.') |
||
270 | return abort_unauthorized(str(e)) |
||
271 | except auth_exc.ApiKeyNotFoundError as e: |
||
272 | LOG.exception('API key is not found.') |
||
273 | return abort_unauthorized(str(e)) |
||
274 | except auth_exc.ApiKeyDisabledError as e: |
||
275 | LOG.exception('API key is disabled.') |
||
276 | return abort_unauthorized(str(e)) |
||
277 | |||
278 | if cfg.CONF.rbac.enable: |
||
279 | user_db = context['user'] |
||
280 | |||
281 | permission_type = endpoint.get('x-permissions', None) |
||
282 | if permission_type: |
||
283 | resolver = resolvers.get_resolver_for_permission_type(permission_type) |
||
284 | has_permission = resolver.user_has_permission(user_db, permission_type) |
||
285 | |||
286 | if not has_permission: |
||
287 | raise rbac_exc.ResourceTypeAccessDeniedError(user_db, |
||
288 | permission_type) |
||
289 | |||
290 | # Collect parameters |
||
291 | kw = {} |
||
292 | for param in endpoint.get('parameters', []) + endpoint.get('x-parameters', []): |
||
293 | name = param['name'] |
||
294 | argument_name = param.get('x-as', None) or name |
||
295 | source = param['in'] |
||
296 | default = param.get('default', None) |
||
297 | |||
298 | # Collecting params from different sources |
||
299 | if source == 'query': |
||
300 | kw[argument_name] = req.GET.get(name, default) |
||
301 | elif source == 'path': |
||
302 | kw[argument_name] = path_vars[name] |
||
303 | elif source == 'header': |
||
304 | kw[argument_name] = req.headers.get(name, default) |
||
305 | elif source == 'formData': |
||
306 | kw[argument_name] = req.POST.get(name, default) |
||
307 | elif source == 'environ': |
||
308 | kw[argument_name] = req.environ.get(name.upper(), default) |
||
309 | elif source == 'context': |
||
310 | kw[argument_name] = context.get(name, default) |
||
311 | elif source == 'request': |
||
312 | kw[argument_name] = getattr(req, name) |
||
313 | elif source == 'body': |
||
314 | if req.body: |
||
315 | content_type = req.headers.get('Content-Type', 'application/json') |
||
316 | content_type = parse_content_type_header(content_type=content_type)[0] |
||
317 | schema = param['schema'] |
||
318 | |||
319 | try: |
||
320 | if content_type == 'application/json': |
||
321 | data = req.json |
||
322 | elif content_type == 'text/plain': |
||
323 | data = req.body |
||
324 | elif content_type in ['application/x-www-form-urlencoded', |
||
325 | 'multipart/form-data']: |
||
326 | data = urlparse.parse_qs(req.body) |
||
327 | else: |
||
328 | raise ValueError('Unsupported Content-Type: "%s"' % (content_type)) |
||
329 | except Exception as e: |
||
330 | detail = 'Failed to parse request body: %s' % str(e) |
||
331 | raise exc.HTTPBadRequest(detail=detail) |
||
332 | |||
333 | try: |
||
334 | CustomValidator(schema, resolver=self.spec_resolver).validate(data) |
||
335 | except (jsonschema.ValidationError, ValueError) as e: |
||
336 | raise exc.HTTPBadRequest(detail=e.message, |
||
337 | comment=traceback.format_exc()) |
||
338 | |||
339 | if content_type == 'text/plain': |
||
340 | kw[argument_name] = data |
||
341 | else: |
||
342 | class Body(object): |
||
343 | def __init__(self, **entries): |
||
344 | self.__dict__.update(entries) |
||
345 | |||
346 | ref = schema.get('$ref', None) |
||
347 | if ref: |
||
348 | with self.spec_resolver.resolving(ref) as resolved: |
||
349 | schema = resolved |
||
350 | |||
351 | if 'x-api-model' in schema: |
||
352 | Model = op_resolver(schema['x-api-model']) |
||
353 | else: |
||
354 | Model = Body |
||
355 | |||
356 | kw[argument_name] = Model(**data) |
||
357 | else: |
||
358 | kw[argument_name] = None |
||
359 | |||
360 | # Making sure all required params are present |
||
361 | required = param.get('required', False) |
||
362 | if required and kw[argument_name] is None: |
||
363 | detail = 'Required parameter "%s" is missing' % name |
||
364 | raise exc.HTTPBadRequest(detail=detail) |
||
365 | |||
366 | # Validating and casting param types |
||
367 | type = param.get('type', None) |
||
368 | if kw[argument_name] is not None: |
||
369 | if type == 'boolean': |
||
370 | positive = ('true', '1', 'yes', 'y') |
||
371 | negative = ('false', '0', 'no', 'n') |
||
372 | |||
373 | if str(kw[argument_name]).lower() not in positive + negative: |
||
374 | detail = 'Parameter "%s" is not of type boolean' % argument_name |
||
375 | raise exc.HTTPBadRequest(detail=detail) |
||
376 | |||
377 | kw[argument_name] = str(kw[argument_name]).lower() in positive |
||
378 | elif type == 'integer': |
||
379 | regex = r'^-?[0-9]+$' |
||
380 | |||
381 | if not re.search(regex, str(kw[argument_name])): |
||
382 | detail = 'Parameter "%s" is not of type integer' % argument_name |
||
383 | raise exc.HTTPBadRequest(detail=detail) |
||
384 | |||
385 | kw[argument_name] = int(kw[argument_name]) |
||
386 | elif type == 'number': |
||
387 | regex = r'^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$' |
||
388 | |||
389 | if not re.search(regex, str(kw[argument_name])): |
||
390 | detail = 'Parameter "%s" is not of type float' % argument_name |
||
391 | raise exc.HTTPBadRequest(detail=detail) |
||
392 | |||
393 | kw[argument_name] = float(kw[argument_name]) |
||
394 | |||
395 | # Call the controller |
||
396 | func = op_resolver(endpoint['operationId']) |
||
397 | resp = func(**kw) |
||
398 | |||
399 | # Handle response |
||
400 | if resp is None: |
||
401 | resp = Response() |
||
402 | |||
403 | if not hasattr(resp, '__call__'): |
||
404 | resp = Response(json=resp) |
||
405 | |||
406 | responses = endpoint.get('responses', {}) |
||
407 | response_spec = responses.get(str(resp.status_code), responses.get('default', None)) |
||
408 | |||
409 | if response_spec and 'schema' in response_spec: |
||
410 | try: |
||
411 | validator = CustomValidator(response_spec['schema'], resolver=self.spec_resolver) |
||
412 | validator.validate(resp.json) |
||
413 | except (jsonschema.ValidationError, ValueError): |
||
414 | LOG.exception('Response validation failed.') |
||
415 | resp.headers.add('Warning', '199 OpenAPI "Response validation failed"') |
||
416 | |||
417 | return resp |
||
418 | |||
426 |
It is generally discouraged to redefine built-ins as this makes code very hard to read.