context.py 3.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. # -*- coding: utf-8 -*-
  2. import json
  3. import re
  4. import typing
  5. from http import HTTPStatus
  6. import bottle
  7. from multidict import MultiDict
  8. from hapic.context import ContextInterface
  9. from hapic.context import RouteRepresentation
  10. from hapic.decorator import DecoratedController
  11. from hapic.decorator import DECORATION_ATTRIBUTE_NAME
  12. from hapic.exception import OutputValidationException
  13. from hapic.exception import NoRoutesException
  14. from hapic.exception import RouteNotFound
  15. from hapic.processor import RequestParameters
  16. from hapic.processor import ProcessValidationError
  17. # Bottle regular expression to locate url parameters
  18. BOTTLE_RE_PATH_URL = re.compile(r'<([^:<>]+)(?::[^<>]+)?>')
  19. class BottleContext(ContextInterface):
  20. def __init__(self, app: bottle.Bottle):
  21. self.app = app
  22. def get_request_parameters(self, *args, **kwargs) -> RequestParameters:
  23. path_parameters = dict(bottle.request.url_args)
  24. query_parameters = MultiDict(bottle.request.query.allitems())
  25. body_parameters = dict(bottle.request.json or {})
  26. form_parameters = MultiDict(bottle.request.forms.allitems())
  27. header_parameters = dict(bottle.request.headers)
  28. files_parameters = dict(bottle.request.files)
  29. return RequestParameters(
  30. path_parameters=path_parameters,
  31. query_parameters=query_parameters,
  32. body_parameters=body_parameters,
  33. form_parameters=form_parameters,
  34. header_parameters=header_parameters,
  35. files_parameters=files_parameters,
  36. )
  37. def get_response(
  38. self,
  39. response: dict,
  40. http_code: int,
  41. ) -> bottle.HTTPResponse:
  42. return bottle.HTTPResponse(
  43. body=json.dumps(response),
  44. headers=[
  45. ('Content-Type', 'application/json'),
  46. ],
  47. status=http_code,
  48. )
  49. def get_validation_error_response(
  50. self,
  51. error: ProcessValidationError,
  52. http_code: HTTPStatus=HTTPStatus.BAD_REQUEST,
  53. ) -> typing.Any:
  54. # TODO BS 20171010: Manage error schemas, see #4
  55. from hapic.hapic import _default_global_error_schema
  56. unmarshall = _default_global_error_schema.dump(error)
  57. if unmarshall.errors:
  58. raise OutputValidationException(
  59. 'Validation error during dump of error response: {}'.format(
  60. str(unmarshall.errors)
  61. )
  62. )
  63. return bottle.HTTPResponse(
  64. body=json.dumps(unmarshall.data),
  65. headers=[
  66. ('Content-Type', 'application/json'),
  67. ],
  68. status=int(http_code),
  69. )
  70. def find_route(
  71. self,
  72. decorated_controller: DecoratedController,
  73. ) -> RouteRepresentation:
  74. if not self.app.routes:
  75. raise NoRoutesException('There is no routes in your bottle app')
  76. reference = decorated_controller.reference
  77. for route in self.app.routes:
  78. route_token = getattr(
  79. route.callback,
  80. DECORATION_ATTRIBUTE_NAME,
  81. None,
  82. )
  83. match_with_wrapper = route.callback == reference.wrapper
  84. match_with_wrapped = route.callback == reference.wrapped
  85. match_with_token = route_token == reference.token
  86. if match_with_wrapper or match_with_wrapped or match_with_token:
  87. return RouteRepresentation(
  88. rule=self.get_swagger_path(route.rule),
  89. method=route.method.lower(),
  90. original_route_object=route,
  91. )
  92. # TODO BS 20171010: Raise exception or print error ? see #10
  93. raise RouteNotFound(
  94. 'Decorated route "{}" was not found in bottle routes'.format(
  95. decorated_controller.name,
  96. )
  97. )
  98. def get_swagger_path(self, contextualised_rule: str) -> str:
  99. return BOTTLE_RE_PATH_URL.sub(r'{\1}', contextualised_rule)