| 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.