api_wrapper.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. from __future__ import annotations
  2. from typing import Any
  3. from flask import current_app, request
  4. from flask_security import current_user
  5. import requests
  6. class InternalApi(object):
  7. """
  8. Simple wrapper around the requests lib, which we use to talk to
  9. our actual internal JSON Api via requests. It can only be used to perform
  10. requests on the same URL root as the current request.
  11. - We use this because it is cleaner than calling the API code directly.
  12. That would re-use the same request we are working on here, which
  13. works differently in some ways like content-type and authentication.
  14. The Flask/Werkzeug request is also immutable, so we could not adapt the
  15. request anyways.
  16. - Also, we implement auth token handling
  17. - Finally we have some logic to control which error codes we want to raise.
  18. """
  19. _log_prefix = "Internal API call ― "
  20. def _auth_headers(self):
  21. return {
  22. "content-type": "application/json",
  23. "Authorization": current_user.get_auth_token(),
  24. }
  25. def _maybe_raise(
  26. self, response: requests.Response, do_not_raise_for: list | None = None
  27. ):
  28. """
  29. Raise an error in the API (4xx, 5xx) if the error code is not in the list of codes
  30. we want to ignore / handle explicitly.
  31. """
  32. if do_not_raise_for is None:
  33. do_not_raise_for = []
  34. if response.status_code not in do_not_raise_for:
  35. response.raise_for_status()
  36. def _url_root(self) -> str:
  37. """
  38. Get the root for the URLs this API should use to call FlexMeasures.
  39. """
  40. url_root = request.url_root
  41. if current_app.config.get("FLEXMEASURES_FORCE_HTTPS", False):
  42. # this replacement is for the case we are behind a load balancer who talks http internally
  43. url_root = url_root.replace("http://", "https://")
  44. return url_root
  45. def get(
  46. self,
  47. url: str,
  48. query: dict[str, Any] | None = None,
  49. do_not_raise_for: list | None = None,
  50. ) -> requests.Response:
  51. full_url = f"{self._url_root()}{url}"
  52. current_app.logger.debug(
  53. f"{self._log_prefix} Calling GET to {full_url} with query {query} ..."
  54. )
  55. response = requests.get(
  56. full_url,
  57. params=query,
  58. headers=self._auth_headers(),
  59. )
  60. self._maybe_raise(response, do_not_raise_for)
  61. return response
  62. def post(
  63. self,
  64. url: str,
  65. args: dict | None = None,
  66. do_not_raise_for: list | None = None,
  67. ) -> requests.Response:
  68. full_url = f"{self._url_root()}{url}"
  69. current_app.logger.debug(
  70. f"{self._log_prefix} Call POST to {full_url} with json data {args} ..."
  71. )
  72. response = requests.post(
  73. full_url,
  74. headers=self._auth_headers(),
  75. json=args if args else {},
  76. )
  77. self._maybe_raise(response, do_not_raise_for)
  78. return response
  79. def patch(
  80. self,
  81. url: str,
  82. args: dict | None = None,
  83. do_not_raise_for: list | None = None,
  84. ) -> requests.Response:
  85. full_url = f"{self._url_root()}{url}"
  86. current_app.logger.debug(
  87. f"{self._log_prefix} Calling PATCH to {full_url} with json data {args} ..."
  88. )
  89. response = requests.patch(
  90. full_url,
  91. headers=self._auth_headers(),
  92. json=args if args else {},
  93. )
  94. self._maybe_raise(response, do_not_raise_for)
  95. return response
  96. def delete(
  97. self,
  98. url: str,
  99. do_not_raise_for: list | None = None,
  100. ) -> requests.Response:
  101. full_url = f"{self._url_root()}{url}"
  102. current_app.logger.debug(f"{self._log_prefix} Calling DELETE to {full_url} ...")
  103. response = requests.delete(
  104. full_url,
  105. headers=self._auth_headers(),
  106. )
  107. self._maybe_raise(response, do_not_raise_for)
  108. return response