context.py 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. # -*- coding: utf-8 -*-
  2. import json
  3. import re
  4. import typing
  5. from http import HTTPStatus
  6. from hapic.context import ContextInterface
  7. from hapic.context import RouteRepresentation
  8. from hapic.decorator import DecoratedController
  9. from hapic.decorator import DECORATION_ATTRIBUTE_NAME
  10. from hapic.exception import OutputValidationException
  11. from hapic.processor import RequestParameters, ProcessValidationError
  12. from flask import Flask
  13. if typing.TYPE_CHECKING:
  14. from flask import Response
  15. # flask regular expression to locate url parameters
  16. FLASK_RE_PATH_URL = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')
  17. class FlaskContext(ContextInterface):
  18. def __init__(self, app: Flask):
  19. self.app = app
  20. def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
  21. from flask import request
  22. return RequestParameters(
  23. path_parameters=request.view_args,
  24. query_parameters=request.args, # TODO: Check
  25. body_parameters=request.get_json(), # TODO: Check
  26. form_parameters=request.form,
  27. header_parameters=request.headers,
  28. files_parameters={}, # TODO: BS 20171115: Code it
  29. )
  30. def get_response(
  31. self,
  32. response: dict,
  33. http_code: int,
  34. ) -> 'Response':
  35. from flask import Response
  36. return Response(
  37. response=json.dumps(response),
  38. mimetype='application/json',
  39. status=http_code,
  40. )
  41. def get_validation_error_response(
  42. self,
  43. error: ProcessValidationError,
  44. http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
  45. ) -> typing.Any:
  46. # TODO BS 20171010: Manage error schemas, see #4
  47. from hapic.hapic import _default_global_error_schema
  48. unmarshall = _default_global_error_schema.dump(error)
  49. if unmarshall.errors:
  50. raise OutputValidationException(
  51. 'Validation error during dump of error response: {}'.format(
  52. str(unmarshall.errors)
  53. )
  54. )
  55. from flask import Response
  56. return Response(
  57. response=json.dumps(unmarshall.data),
  58. mimetype='application/json',
  59. status=int(http_code),
  60. )
  61. def find_route(
  62. self,
  63. decorated_controller: 'DecoratedController',
  64. ):
  65. reference = decorated_controller.reference
  66. for route in self.app.url_map.iter_rules():
  67. if route.endpoint not in self.app.view_functions:
  68. continue
  69. route_callback = self.app.view_functions[route.endpoint]
  70. route_token = getattr(
  71. route_callback,
  72. DECORATION_ATTRIBUTE_NAME,
  73. None,
  74. )
  75. match_with_wrapper = route_callback == reference.wrapper
  76. match_with_wrapped = route_callback == reference.wrapped
  77. match_with_token = route_token == reference.token
  78. # FIXME - G.M - 2017-12-04 - return list instead of one method
  79. # This fix, return only 1 allowed method, change this when
  80. # RouteRepresentation is adapted to return multiples methods.
  81. method = [x for x in route.methods
  82. if x not in ['OPTIONS', 'HEAD']][0]
  83. if match_with_wrapper or match_with_wrapped or match_with_token:
  84. return RouteRepresentation(
  85. rule=self.get_swagger_path(route.rule),
  86. method=method,
  87. original_route_object=route,
  88. )
  89. def get_swagger_path(self, contextualised_rule: str) -> str:
  90. # TODO - G.M - 2017-12-05 Check if all route path are handled correctly
  91. return FLASK_RE_PATH_URL.sub(r'{\1}', contextualised_rule)