ul-api-utils 9.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. example/__init__.py +0 -0
  2. example/conf.py +35 -0
  3. example/main.py +24 -0
  4. example/models/__init__.py +0 -0
  5. example/permissions.py +6 -0
  6. example/pure_flask_example.py +65 -0
  7. example/rate_limit_load.py +10 -0
  8. example/redis_repository.py +22 -0
  9. example/routes/__init__.py +0 -0
  10. example/routes/api_some.py +335 -0
  11. example/sockets/__init__.py +0 -0
  12. example/sockets/on_connect.py +16 -0
  13. example/sockets/on_disconnect.py +14 -0
  14. example/sockets/on_json.py +10 -0
  15. example/sockets/on_message.py +13 -0
  16. example/sockets/on_open.py +16 -0
  17. example/workers/__init__.py +0 -0
  18. example/workers/worker.py +28 -0
  19. ul_api_utils/__init__.py +0 -0
  20. ul_api_utils/access/__init__.py +122 -0
  21. ul_api_utils/api_resource/__init__.py +0 -0
  22. ul_api_utils/api_resource/api_request.py +105 -0
  23. ul_api_utils/api_resource/api_resource.py +414 -0
  24. ul_api_utils/api_resource/api_resource_config.py +20 -0
  25. ul_api_utils/api_resource/api_resource_error_handling.py +21 -0
  26. ul_api_utils/api_resource/api_resource_fn_typing.py +356 -0
  27. ul_api_utils/api_resource/api_resource_type.py +16 -0
  28. ul_api_utils/api_resource/api_response.py +300 -0
  29. ul_api_utils/api_resource/api_response_db.py +26 -0
  30. ul_api_utils/api_resource/api_response_payload_alias.py +25 -0
  31. ul_api_utils/api_resource/db_types.py +9 -0
  32. ul_api_utils/api_resource/signature_check.py +41 -0
  33. ul_api_utils/commands/__init__.py +0 -0
  34. ul_api_utils/commands/cmd_enc_keys.py +172 -0
  35. ul_api_utils/commands/cmd_gen_api_user_token.py +77 -0
  36. ul_api_utils/commands/cmd_gen_new_api_user.py +106 -0
  37. ul_api_utils/commands/cmd_generate_api_docs.py +181 -0
  38. ul_api_utils/commands/cmd_start.py +110 -0
  39. ul_api_utils/commands/cmd_worker_start.py +76 -0
  40. ul_api_utils/commands/start/__init__.py +0 -0
  41. ul_api_utils/commands/start/gunicorn.conf.local.py +0 -0
  42. ul_api_utils/commands/start/gunicorn.conf.py +26 -0
  43. ul_api_utils/commands/start/wsgi.py +22 -0
  44. ul_api_utils/conf/ul-debugger-main.js +1 -0
  45. ul_api_utils/conf/ul-debugger-ui.js +1 -0
  46. ul_api_utils/conf.py +70 -0
  47. ul_api_utils/const.py +78 -0
  48. ul_api_utils/debug/__init__.py +0 -0
  49. ul_api_utils/debug/debugger.py +119 -0
  50. ul_api_utils/debug/malloc.py +93 -0
  51. ul_api_utils/debug/stat.py +444 -0
  52. ul_api_utils/encrypt/__init__.py +0 -0
  53. ul_api_utils/encrypt/encrypt_decrypt_abstract.py +15 -0
  54. ul_api_utils/encrypt/encrypt_decrypt_aes_xtea.py +59 -0
  55. ul_api_utils/errors.py +200 -0
  56. ul_api_utils/internal_api/__init__.py +0 -0
  57. ul_api_utils/internal_api/__tests__/__init__.py +0 -0
  58. ul_api_utils/internal_api/__tests__/internal_api.py +29 -0
  59. ul_api_utils/internal_api/__tests__/internal_api_content_type.py +22 -0
  60. ul_api_utils/internal_api/internal_api.py +369 -0
  61. ul_api_utils/internal_api/internal_api_check_context.py +42 -0
  62. ul_api_utils/internal_api/internal_api_error.py +17 -0
  63. ul_api_utils/internal_api/internal_api_response.py +296 -0
  64. ul_api_utils/main.py +29 -0
  65. ul_api_utils/modules/__init__.py +0 -0
  66. ul_api_utils/modules/__tests__/__init__.py +0 -0
  67. ul_api_utils/modules/__tests__/test_api_sdk_jwt.py +195 -0
  68. ul_api_utils/modules/api_sdk.py +555 -0
  69. ul_api_utils/modules/api_sdk_config.py +63 -0
  70. ul_api_utils/modules/api_sdk_jwt.py +377 -0
  71. ul_api_utils/modules/intermediate_state.py +34 -0
  72. ul_api_utils/modules/worker_context.py +35 -0
  73. ul_api_utils/modules/worker_sdk.py +109 -0
  74. ul_api_utils/modules/worker_sdk_config.py +13 -0
  75. ul_api_utils/py.typed +0 -0
  76. ul_api_utils/resources/__init__.py +0 -0
  77. ul_api_utils/resources/caching.py +196 -0
  78. ul_api_utils/resources/debugger_scripts.py +97 -0
  79. ul_api_utils/resources/health_check/__init__.py +0 -0
  80. ul_api_utils/resources/health_check/const.py +2 -0
  81. ul_api_utils/resources/health_check/health_check.py +439 -0
  82. ul_api_utils/resources/health_check/health_check_template.py +64 -0
  83. ul_api_utils/resources/health_check/resource.py +97 -0
  84. ul_api_utils/resources/not_implemented.py +25 -0
  85. ul_api_utils/resources/permissions.py +29 -0
  86. ul_api_utils/resources/rate_limitter.py +84 -0
  87. ul_api_utils/resources/socketio.py +55 -0
  88. ul_api_utils/resources/swagger.py +119 -0
  89. ul_api_utils/resources/web_forms/__init__.py +0 -0
  90. ul_api_utils/resources/web_forms/custom_fields/__init__.py +0 -0
  91. ul_api_utils/resources/web_forms/custom_fields/custom_checkbox_select.py +5 -0
  92. ul_api_utils/resources/web_forms/custom_widgets/__init__.py +0 -0
  93. ul_api_utils/resources/web_forms/custom_widgets/custom_select_widget.py +86 -0
  94. ul_api_utils/resources/web_forms/custom_widgets/custom_text_input_widget.py +42 -0
  95. ul_api_utils/resources/web_forms/uni_form.py +75 -0
  96. ul_api_utils/sentry.py +52 -0
  97. ul_api_utils/utils/__init__.py +0 -0
  98. ul_api_utils/utils/__tests__/__init__.py +0 -0
  99. ul_api_utils/utils/__tests__/api_path_version.py +16 -0
  100. ul_api_utils/utils/__tests__/unwrap_typing.py +67 -0
  101. ul_api_utils/utils/api_encoding.py +51 -0
  102. ul_api_utils/utils/api_format.py +61 -0
  103. ul_api_utils/utils/api_method.py +55 -0
  104. ul_api_utils/utils/api_pagination.py +58 -0
  105. ul_api_utils/utils/api_path_version.py +60 -0
  106. ul_api_utils/utils/api_request_info.py +6 -0
  107. ul_api_utils/utils/avro.py +131 -0
  108. ul_api_utils/utils/broker_topics_message_count.py +47 -0
  109. ul_api_utils/utils/cached_per_request.py +23 -0
  110. ul_api_utils/utils/colors.py +31 -0
  111. ul_api_utils/utils/constants.py +7 -0
  112. ul_api_utils/utils/decode_base64.py +9 -0
  113. ul_api_utils/utils/deprecated.py +19 -0
  114. ul_api_utils/utils/flags.py +29 -0
  115. ul_api_utils/utils/flask_swagger_generator/__init__.py +0 -0
  116. ul_api_utils/utils/flask_swagger_generator/conf.py +4 -0
  117. ul_api_utils/utils/flask_swagger_generator/exceptions.py +7 -0
  118. ul_api_utils/utils/flask_swagger_generator/specifiers/__init__.py +0 -0
  119. ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_models.py +57 -0
  120. ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_specifier.py +48 -0
  121. ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_three_specifier.py +777 -0
  122. ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_version.py +40 -0
  123. ul_api_utils/utils/flask_swagger_generator/utils/__init__.py +0 -0
  124. ul_api_utils/utils/flask_swagger_generator/utils/input_type.py +77 -0
  125. ul_api_utils/utils/flask_swagger_generator/utils/parameter_type.py +51 -0
  126. ul_api_utils/utils/flask_swagger_generator/utils/replace_in_dict.py +18 -0
  127. ul_api_utils/utils/flask_swagger_generator/utils/request_type.py +52 -0
  128. ul_api_utils/utils/flask_swagger_generator/utils/schema_type.py +15 -0
  129. ul_api_utils/utils/flask_swagger_generator/utils/security_type.py +39 -0
  130. ul_api_utils/utils/imports.py +16 -0
  131. ul_api_utils/utils/instance_checks.py +16 -0
  132. ul_api_utils/utils/jinja/__init__.py +0 -0
  133. ul_api_utils/utils/jinja/t_url_for.py +19 -0
  134. ul_api_utils/utils/jinja/to_pretty_json.py +11 -0
  135. ul_api_utils/utils/json_encoder.py +126 -0
  136. ul_api_utils/utils/load_modules.py +15 -0
  137. ul_api_utils/utils/memory_db/__init__.py +0 -0
  138. ul_api_utils/utils/memory_db/__tests__/__init__.py +0 -0
  139. ul_api_utils/utils/memory_db/errors.py +8 -0
  140. ul_api_utils/utils/memory_db/repository.py +102 -0
  141. ul_api_utils/utils/token_check.py +14 -0
  142. ul_api_utils/utils/token_check_through_request.py +16 -0
  143. ul_api_utils/utils/unwrap_typing.py +117 -0
  144. ul_api_utils/utils/uuid_converter.py +22 -0
  145. ul_api_utils/validators/__init__.py +0 -0
  146. ul_api_utils/validators/__tests__/__init__.py +0 -0
  147. ul_api_utils/validators/__tests__/test_custom_fields.py +32 -0
  148. ul_api_utils/validators/custom_fields.py +66 -0
  149. ul_api_utils/validators/validate_empty_object.py +10 -0
  150. ul_api_utils/validators/validate_uuid.py +11 -0
  151. ul_api_utils-9.3.0.dist-info/LICENSE +21 -0
  152. ul_api_utils-9.3.0.dist-info/METADATA +279 -0
  153. ul_api_utils-9.3.0.dist-info/RECORD +156 -0
  154. ul_api_utils-9.3.0.dist-info/WHEEL +5 -0
  155. ul_api_utils-9.3.0.dist-info/entry_points.txt +2 -0
  156. ul_api_utils-9.3.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,196 @@
1
+ import functools
2
+ import hashlib
3
+ import inspect
4
+ import itertools
5
+ import logging
6
+ from _hashlib import HASH
7
+ from collections import defaultdict
8
+ from enum import Enum
9
+ from typing import Optional, Callable, Dict, Tuple, List, Union, Literal, cast, TypeVar, Any, Set, TypedDict
10
+
11
+ import ormsgpack
12
+ from pydantic import ValidationError
13
+ from werkzeug import Response as BaseResponse
14
+
15
+ from flask import request
16
+ from flask_caching import Cache, CachedResponse
17
+
18
+ from ul_api_utils.api_resource.api_response import ApiResponse, JsonApiResponse
19
+ from ul_api_utils.conf import APPLICATION_DEBUG
20
+ from ul_api_utils.utils.constants import TKwargs
21
+ from ul_api_utils.utils.json_encoder import CustomJSONEncoder
22
+
23
+ TCacheModeStr = Union[Literal['READ'], Literal['REFRESH']]
24
+ TFn = TypeVar("TFn", bound=Callable[..., ApiResponse])
25
+
26
+
27
+ class ULCacheMode(Enum):
28
+ READ = 'READ'
29
+ REFRESH = 'REFRESH'
30
+
31
+ @staticmethod
32
+ def compile_mode(mode: Union[TCacheModeStr, 'ULCacheMode']) -> str:
33
+ if isinstance(mode, ULCacheMode):
34
+ return mode.value
35
+ assert isinstance(mode, str)
36
+ m = mode.strip().upper()
37
+ return ULCacheMode(m).value
38
+
39
+
40
+ TCacheMode = Union[TCacheModeStr, 'ULCacheMode']
41
+
42
+
43
+ class ULCacheConfig(TypedDict): # https://flask-caching.readthedocs.io/en/latest/#configuring-flask-caching
44
+ CACHE_TYPE: str
45
+ CACHE_REDIS_HOST: str
46
+ CACHE_REDIS_PORT: str
47
+ CACHE_REDIS_PASSWORD: str
48
+ CACHE_KEY_PREFIX: str
49
+ CACHE_DEFAULT_TIMEOUT: int
50
+ CACHE_SOURCE_CHECK: bool
51
+
52
+
53
+ class ULCache(Cache):
54
+
55
+ @property
56
+ def tags_map(self) -> Dict[Tuple[str, ...], Set[str]]:
57
+ if self.cache.has(':tags_map'):
58
+ return self.cache.get(':tags_map')
59
+ else:
60
+ return defaultdict(set)
61
+
62
+ @tags_map.setter
63
+ def tags_map(self, value: Dict[Tuple[str], Set[str]]) -> None:
64
+ self.cache.set(':tags_map', value, -1)
65
+
66
+ @staticmethod
67
+ def _has_common_elements(seq: Set[Tuple[str, ...]]) -> bool:
68
+ return bool(functools.reduce(set.intersection, map(set, seq)))
69
+
70
+ @staticmethod
71
+ def _format_cache_key(parts: List[Any]) -> str:
72
+ return ':' + ':'.join([p for p in parts if p])
73
+
74
+ def make_cache_key(
75
+ self,
76
+ fn: TFn,
77
+ source_check: bool,
78
+ hash_method: Callable[[bytes], HASH],
79
+ ) -> str:
80
+ cache_key_parts: List[str] = [request.path]
81
+ cache_hash: HASH | None = None
82
+ if request.args:
83
+ args_as_sorted_tuple = tuple(sorted(pair for pair in request.args.items(multi=True)))
84
+
85
+ args_as_bytes = str(args_as_sorted_tuple).encode()
86
+ cache_hash = hash_method(args_as_bytes)
87
+
88
+ # Use the source code if source_check is True and update the
89
+ # cache_hash before generating the hashing and using it in cache_key
90
+ if source_check and callable(fn):
91
+ func_source_code = inspect.getsource(fn)
92
+ if cache_hash is not None:
93
+ cache_hash.update(func_source_code.encode("utf-8"))
94
+ else:
95
+ cache_hash = hash_method(func_source_code.encode("utf-8"))
96
+ cache_hash_str = str(cache_hash.hexdigest()) if cache_hash is not None else ''
97
+ cache_key_parts.append(cache_hash_str)
98
+
99
+ return self._format_cache_key(cache_key_parts)
100
+
101
+ def cache_refresh_wrap(
102
+ self,
103
+ tags: Tuple[str, ...] | str,
104
+ ) -> Callable[[TFn], TFn]:
105
+ def wrap(fn: TFn) -> TFn:
106
+ assert fn.__module__, 'empty __module__ of function'
107
+ func_tags = tags if isinstance(tags, tuple) else (tags,)
108
+
109
+ @functools.wraps(fn)
110
+ def wrapper(*args: Any, **kwargs: TKwargs) -> Tuple[BaseResponse, int]:
111
+ dependent_keys = []
112
+ tags_map = self.tags_map.copy()
113
+
114
+ for t in tags_map.keys():
115
+ stored_tags = t if isinstance(t, tuple) else (t,)
116
+ if self._has_common_elements({stored_tags, func_tags}):
117
+ dependent_keys.append(tags_map[stored_tags].copy())
118
+ tags_map[stored_tags] = set()
119
+ cache_list_to_reset: List[str] = list(itertools.chain.from_iterable(dependent_keys))
120
+ self.delete_many(*cache_list_to_reset)
121
+ tags_map[func_tags] = set()
122
+ self.tags_map = tags_map
123
+ return self._call_fn(fn, *args, **kwargs)
124
+
125
+ return cast(TFn, wrapper)
126
+ return wrap
127
+
128
+ def cache_noop(
129
+ self,
130
+ ) -> Callable[[TFn], TFn]:
131
+ def wrap(fn: TFn) -> TFn:
132
+ assert fn.__module__, 'empty __module__ of function'
133
+ return fn
134
+
135
+ return wrap
136
+
137
+ def cache_read_wrap(
138
+ self,
139
+ tags: Tuple[str, ...] | str,
140
+ timeout: Optional[int] = None,
141
+ source_check: Optional[bool] = None,
142
+ hash_method: Callable[[bytes], HASH] = hashlib.md5,
143
+ ) -> Callable[[TFn], TFn]:
144
+ def cache_wrap(fn: TFn) -> TFn:
145
+ assert fn.__module__, 'empty __module__ of function'
146
+
147
+ nonlocal source_check
148
+ if source_check is None:
149
+ source_check = self.source_check or False
150
+
151
+ func_tags = tags if isinstance(tags, tuple) else (tags,)
152
+ if self.tags_map.get(func_tags) is None:
153
+ self.tags_map[func_tags] = set()
154
+
155
+ logger = logging.getLogger(fn.__module__)
156
+
157
+ @functools.wraps(fn)
158
+ def cache_wrapper(*args: Any, **kwargs: TKwargs) -> Tuple[BaseResponse | CachedResponse, int]:
159
+ try:
160
+ assert source_check is not None # just for mypy
161
+ cache_key = self.make_cache_key(fn, source_check, hash_method)
162
+ if resp := self.cache.get(cache_key):
163
+ try:
164
+ resp = JsonApiResponse(**ormsgpack.unpackb(resp))
165
+ except ValidationError:
166
+ logger.error(f'cache read error of {fn.__name__} :: response not type of {JsonApiResponse.__name__}')
167
+ raise
168
+
169
+ found = True
170
+ # If the value returned by cache.get() is None
171
+ # it might be because the key is not found in the cache
172
+ # or because the cached value is actually None
173
+ if resp is None:
174
+ found = self.cache.has(cache_key)
175
+ except Exception as e: # noqa: B902 # cuz lot of variations of cache factory impementaions
176
+ logger.error(f'error due cache check :: {e}')
177
+ return self._call_fn(fn, *args, **kwargs)
178
+ if not found:
179
+ tags_map = self.tags_map.copy()
180
+ tags_map[func_tags].add(cache_key)
181
+ resp = self._call_fn(fn, *args, **kwargs)
182
+ try:
183
+ self.cache.set(
184
+ key=cache_key,
185
+ value=ormsgpack.packb(resp, default=CustomJSONEncoder().default),
186
+ timeout=timeout,
187
+ )
188
+ except Exception as e: # noqa: B902 # cuz lot of variations of cache factory impementaions
189
+ if APPLICATION_DEBUG:
190
+ raise
191
+ logger.error(f'exception possibly due to cache response :: {e}')
192
+ self.tags_map = self.tags_map | tags_map
193
+ return resp
194
+
195
+ return cast(TFn, cache_wrapper)
196
+ return cache_wrap
@@ -0,0 +1,97 @@
1
+ import os.path
2
+ from typing import TYPE_CHECKING, Optional, List
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from ul_api_utils.access import GLOBAL_PERMISSION__PUBLIC
7
+ from ul_api_utils.api_resource.api_resource import ApiResource
8
+ from ul_api_utils.api_resource.api_resource_config import ApiResourceConfig
9
+ from ul_api_utils.api_resource.api_response import FileApiResponse, JsonApiResponse, JsonApiResponsePayload
10
+ from ul_api_utils.const import API_PATH__DEBUGGER_JS_UI, API_PATH__DEBUGGER_JS_MAIN, THIS_LIB_CWD
11
+ from ul_api_utils.errors import NoResultFoundApiError
12
+ from ul_api_utils.internal_api.internal_api import internal_api_registry
13
+ from ul_api_utils.utils.api_path_version import ApiPathVersion
14
+
15
+ if TYPE_CHECKING:
16
+ from ul_api_utils.modules.api_sdk import ApiSdk
17
+
18
+
19
+ class DebuggerExplainBody(BaseModel):
20
+ sql: str
21
+ requests_hierarchy: List[str] = []
22
+
23
+ costs: bool = True
24
+ summary: bool = True
25
+ buffers: bool = True
26
+ verbose: bool = False
27
+ # timing: bool = False
28
+ # analyze: bool = True
29
+
30
+
31
+ class DebuggerExplainResponse(JsonApiResponsePayload):
32
+ sql: str
33
+ explanation: Optional[List[str]] = None
34
+
35
+
36
+ def load_debugger_static_scripts(sdk: 'ApiSdk') -> None:
37
+ conf = ApiResourceConfig(swagger_disabled=True)
38
+
39
+ # INIT JS File
40
+
41
+ @sdk.file_download('GET', API_PATH__DEBUGGER_JS_UI, v=ApiPathVersion.NO_PREFIX, access=GLOBAL_PERMISSION__PUBLIC, config=conf)
42
+ def debugger_js_ui(api_resource: ApiResource) -> FileApiResponse:
43
+ return api_resource.response_file_ok(os.path.join(THIS_LIB_CWD, 'conf', 'ul-debugger-ui.js'), mimetype='application/javascript')
44
+
45
+ @sdk.file_download('GET', API_PATH__DEBUGGER_JS_UI, v=ApiPathVersion.NO_VERSION, access=GLOBAL_PERMISSION__PUBLIC, config=conf)
46
+ def debugger_js_ui_nov(api_resource: ApiResource) -> FileApiResponse:
47
+ return api_resource.response_file_ok(os.path.join(THIS_LIB_CWD, 'conf', 'ul-debugger-ui.js'), mimetype='application/javascript')
48
+
49
+ @sdk.file_download('GET', API_PATH__DEBUGGER_JS_UI, v=ApiPathVersion.V01, access=GLOBAL_PERMISSION__PUBLIC, config=conf)
50
+ def debugger_js_ui_v1(api_resource: ApiResource) -> FileApiResponse:
51
+ return api_resource.response_file_ok(os.path.join(THIS_LIB_CWD, 'conf', 'ul-debugger-ui.js'), mimetype='application/javascript')
52
+
53
+ # MAIN JS File
54
+
55
+ @sdk.file_download('GET', API_PATH__DEBUGGER_JS_MAIN, v=ApiPathVersion.NO_PREFIX, access=GLOBAL_PERMISSION__PUBLIC, config=conf)
56
+ def debugger_js_main(api_resource: ApiResource) -> FileApiResponse:
57
+ if not api_resource.debugger_enabled:
58
+ raise NoResultFoundApiError('invalid file path')
59
+ return api_resource.response_file_ok(os.path.join(THIS_LIB_CWD, 'conf', 'ul-debugger-main.js'), mimetype='application/javascript')
60
+
61
+ @sdk.file_download('GET', API_PATH__DEBUGGER_JS_MAIN, v=ApiPathVersion.NO_VERSION, access=GLOBAL_PERMISSION__PUBLIC, config=conf)
62
+ def debugger_js_main_nov(api_resource: ApiResource) -> FileApiResponse:
63
+ if not api_resource.debugger_enabled:
64
+ raise NoResultFoundApiError('invalid file path')
65
+ return api_resource.response_file_ok(os.path.join(THIS_LIB_CWD, 'conf', 'ul-debugger-main.js'), mimetype='application/javascript')
66
+
67
+ @sdk.file_download('GET', API_PATH__DEBUGGER_JS_MAIN, v=ApiPathVersion.V01, access=GLOBAL_PERMISSION__PUBLIC, config=conf)
68
+ def debugger_js_main_v1(api_resource: ApiResource) -> FileApiResponse:
69
+ if not api_resource.debugger_enabled:
70
+ raise NoResultFoundApiError('invalid file path')
71
+ return api_resource.response_file_ok(os.path.join(THIS_LIB_CWD, 'conf', 'ul-debugger-main.js'), mimetype='application/javascript')
72
+
73
+ @sdk.rest_api('POST', '/debugger-explain', v=ApiPathVersion.NO_VERSION, access=GLOBAL_PERMISSION__PUBLIC, config=conf)
74
+ def debugger_service_explain(api_resource: ApiResource, body: DebuggerExplainBody) -> JsonApiResponse[DebuggerExplainResponse]:
75
+ if not api_resource.debugger_enabled:
76
+ raise NoResultFoundApiError('invalid file path')
77
+
78
+ for req_url in reversed(body.requests_hierarchy):
79
+ for k, api in internal_api_registry.items():
80
+ if req_url.startswith(k):
81
+ api_body = body.model_dump()
82
+ api_body['requests_hierarchy'] = []
83
+ api_result = api.request_post('/debugger-explain', v=ApiPathVersion.NO_VERSION, json=api_body).check().typed(DebuggerExplainResponse)
84
+ return api_resource.response_ok(api_result.payload)
85
+
86
+ from ul_db_utils.modules.postgres_modules.db import db
87
+ options = 'COSTS ' + ('TRUE' if body.costs else 'FALSE')
88
+ options += ', SUMMARY ' + ('TRUE' if body.summary else 'FALSE')
89
+ options += ', VERBOSE ' + ('TRUE' if body.verbose else 'FALSE')
90
+ options += ', BUFFERS ' + ('TRUE' if body.buffers else 'FALSE')
91
+
92
+ sql = f'EXPLAIN ({options})\n{body.sql.strip().strip(";").strip()};'
93
+
94
+ return api_resource.response_ok(DebuggerExplainResponse(
95
+ sql=sql,
96
+ explanation=[i for i, *_i in db.session.execute(sql).fetchall()]),
97
+ )
File without changes
@@ -0,0 +1,2 @@
1
+ INTERNAL_API_HEALTH_CHECK_PATH = '/health-check'
2
+ SERVICE_NAME_DELIMITER = '@'