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.
- example/__init__.py +0 -0
- example/conf.py +35 -0
- example/main.py +24 -0
- example/models/__init__.py +0 -0
- example/permissions.py +6 -0
- example/pure_flask_example.py +65 -0
- example/rate_limit_load.py +10 -0
- example/redis_repository.py +22 -0
- example/routes/__init__.py +0 -0
- example/routes/api_some.py +335 -0
- example/sockets/__init__.py +0 -0
- example/sockets/on_connect.py +16 -0
- example/sockets/on_disconnect.py +14 -0
- example/sockets/on_json.py +10 -0
- example/sockets/on_message.py +13 -0
- example/sockets/on_open.py +16 -0
- example/workers/__init__.py +0 -0
- example/workers/worker.py +28 -0
- ul_api_utils/__init__.py +0 -0
- ul_api_utils/access/__init__.py +122 -0
- ul_api_utils/api_resource/__init__.py +0 -0
- ul_api_utils/api_resource/api_request.py +105 -0
- ul_api_utils/api_resource/api_resource.py +414 -0
- ul_api_utils/api_resource/api_resource_config.py +20 -0
- ul_api_utils/api_resource/api_resource_error_handling.py +21 -0
- ul_api_utils/api_resource/api_resource_fn_typing.py +356 -0
- ul_api_utils/api_resource/api_resource_type.py +16 -0
- ul_api_utils/api_resource/api_response.py +300 -0
- ul_api_utils/api_resource/api_response_db.py +26 -0
- ul_api_utils/api_resource/api_response_payload_alias.py +25 -0
- ul_api_utils/api_resource/db_types.py +9 -0
- ul_api_utils/api_resource/signature_check.py +41 -0
- ul_api_utils/commands/__init__.py +0 -0
- ul_api_utils/commands/cmd_enc_keys.py +172 -0
- ul_api_utils/commands/cmd_gen_api_user_token.py +77 -0
- ul_api_utils/commands/cmd_gen_new_api_user.py +106 -0
- ul_api_utils/commands/cmd_generate_api_docs.py +181 -0
- ul_api_utils/commands/cmd_start.py +110 -0
- ul_api_utils/commands/cmd_worker_start.py +76 -0
- ul_api_utils/commands/start/__init__.py +0 -0
- ul_api_utils/commands/start/gunicorn.conf.local.py +0 -0
- ul_api_utils/commands/start/gunicorn.conf.py +26 -0
- ul_api_utils/commands/start/wsgi.py +22 -0
- ul_api_utils/conf/ul-debugger-main.js +1 -0
- ul_api_utils/conf/ul-debugger-ui.js +1 -0
- ul_api_utils/conf.py +70 -0
- ul_api_utils/const.py +78 -0
- ul_api_utils/debug/__init__.py +0 -0
- ul_api_utils/debug/debugger.py +119 -0
- ul_api_utils/debug/malloc.py +93 -0
- ul_api_utils/debug/stat.py +444 -0
- ul_api_utils/encrypt/__init__.py +0 -0
- ul_api_utils/encrypt/encrypt_decrypt_abstract.py +15 -0
- ul_api_utils/encrypt/encrypt_decrypt_aes_xtea.py +59 -0
- ul_api_utils/errors.py +200 -0
- ul_api_utils/internal_api/__init__.py +0 -0
- ul_api_utils/internal_api/__tests__/__init__.py +0 -0
- ul_api_utils/internal_api/__tests__/internal_api.py +29 -0
- ul_api_utils/internal_api/__tests__/internal_api_content_type.py +22 -0
- ul_api_utils/internal_api/internal_api.py +369 -0
- ul_api_utils/internal_api/internal_api_check_context.py +42 -0
- ul_api_utils/internal_api/internal_api_error.py +17 -0
- ul_api_utils/internal_api/internal_api_response.py +296 -0
- ul_api_utils/main.py +29 -0
- ul_api_utils/modules/__init__.py +0 -0
- ul_api_utils/modules/__tests__/__init__.py +0 -0
- ul_api_utils/modules/__tests__/test_api_sdk_jwt.py +195 -0
- ul_api_utils/modules/api_sdk.py +555 -0
- ul_api_utils/modules/api_sdk_config.py +63 -0
- ul_api_utils/modules/api_sdk_jwt.py +377 -0
- ul_api_utils/modules/intermediate_state.py +34 -0
- ul_api_utils/modules/worker_context.py +35 -0
- ul_api_utils/modules/worker_sdk.py +109 -0
- ul_api_utils/modules/worker_sdk_config.py +13 -0
- ul_api_utils/py.typed +0 -0
- ul_api_utils/resources/__init__.py +0 -0
- ul_api_utils/resources/caching.py +196 -0
- ul_api_utils/resources/debugger_scripts.py +97 -0
- ul_api_utils/resources/health_check/__init__.py +0 -0
- ul_api_utils/resources/health_check/const.py +2 -0
- ul_api_utils/resources/health_check/health_check.py +439 -0
- ul_api_utils/resources/health_check/health_check_template.py +64 -0
- ul_api_utils/resources/health_check/resource.py +97 -0
- ul_api_utils/resources/not_implemented.py +25 -0
- ul_api_utils/resources/permissions.py +29 -0
- ul_api_utils/resources/rate_limitter.py +84 -0
- ul_api_utils/resources/socketio.py +55 -0
- ul_api_utils/resources/swagger.py +119 -0
- ul_api_utils/resources/web_forms/__init__.py +0 -0
- ul_api_utils/resources/web_forms/custom_fields/__init__.py +0 -0
- ul_api_utils/resources/web_forms/custom_fields/custom_checkbox_select.py +5 -0
- ul_api_utils/resources/web_forms/custom_widgets/__init__.py +0 -0
- ul_api_utils/resources/web_forms/custom_widgets/custom_select_widget.py +86 -0
- ul_api_utils/resources/web_forms/custom_widgets/custom_text_input_widget.py +42 -0
- ul_api_utils/resources/web_forms/uni_form.py +75 -0
- ul_api_utils/sentry.py +52 -0
- ul_api_utils/utils/__init__.py +0 -0
- ul_api_utils/utils/__tests__/__init__.py +0 -0
- ul_api_utils/utils/__tests__/api_path_version.py +16 -0
- ul_api_utils/utils/__tests__/unwrap_typing.py +67 -0
- ul_api_utils/utils/api_encoding.py +51 -0
- ul_api_utils/utils/api_format.py +61 -0
- ul_api_utils/utils/api_method.py +55 -0
- ul_api_utils/utils/api_pagination.py +58 -0
- ul_api_utils/utils/api_path_version.py +60 -0
- ul_api_utils/utils/api_request_info.py +6 -0
- ul_api_utils/utils/avro.py +131 -0
- ul_api_utils/utils/broker_topics_message_count.py +47 -0
- ul_api_utils/utils/cached_per_request.py +23 -0
- ul_api_utils/utils/colors.py +31 -0
- ul_api_utils/utils/constants.py +7 -0
- ul_api_utils/utils/decode_base64.py +9 -0
- ul_api_utils/utils/deprecated.py +19 -0
- ul_api_utils/utils/flags.py +29 -0
- ul_api_utils/utils/flask_swagger_generator/__init__.py +0 -0
- ul_api_utils/utils/flask_swagger_generator/conf.py +4 -0
- ul_api_utils/utils/flask_swagger_generator/exceptions.py +7 -0
- ul_api_utils/utils/flask_swagger_generator/specifiers/__init__.py +0 -0
- ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_models.py +57 -0
- ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_specifier.py +48 -0
- ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_three_specifier.py +777 -0
- ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_version.py +40 -0
- ul_api_utils/utils/flask_swagger_generator/utils/__init__.py +0 -0
- ul_api_utils/utils/flask_swagger_generator/utils/input_type.py +77 -0
- ul_api_utils/utils/flask_swagger_generator/utils/parameter_type.py +51 -0
- ul_api_utils/utils/flask_swagger_generator/utils/replace_in_dict.py +18 -0
- ul_api_utils/utils/flask_swagger_generator/utils/request_type.py +52 -0
- ul_api_utils/utils/flask_swagger_generator/utils/schema_type.py +15 -0
- ul_api_utils/utils/flask_swagger_generator/utils/security_type.py +39 -0
- ul_api_utils/utils/imports.py +16 -0
- ul_api_utils/utils/instance_checks.py +16 -0
- ul_api_utils/utils/jinja/__init__.py +0 -0
- ul_api_utils/utils/jinja/t_url_for.py +19 -0
- ul_api_utils/utils/jinja/to_pretty_json.py +11 -0
- ul_api_utils/utils/json_encoder.py +126 -0
- ul_api_utils/utils/load_modules.py +15 -0
- ul_api_utils/utils/memory_db/__init__.py +0 -0
- ul_api_utils/utils/memory_db/__tests__/__init__.py +0 -0
- ul_api_utils/utils/memory_db/errors.py +8 -0
- ul_api_utils/utils/memory_db/repository.py +102 -0
- ul_api_utils/utils/token_check.py +14 -0
- ul_api_utils/utils/token_check_through_request.py +16 -0
- ul_api_utils/utils/unwrap_typing.py +117 -0
- ul_api_utils/utils/uuid_converter.py +22 -0
- ul_api_utils/validators/__init__.py +0 -0
- ul_api_utils/validators/__tests__/__init__.py +0 -0
- ul_api_utils/validators/__tests__/test_custom_fields.py +32 -0
- ul_api_utils/validators/custom_fields.py +66 -0
- ul_api_utils/validators/validate_empty_object.py +10 -0
- ul_api_utils/validators/validate_uuid.py +11 -0
- ul_api_utils-9.3.0.dist-info/LICENSE +21 -0
- ul_api_utils-9.3.0.dist-info/METADATA +279 -0
- ul_api_utils-9.3.0.dist-info/RECORD +156 -0
- ul_api_utils-9.3.0.dist-info/WHEEL +5 -0
- ul_api_utils-9.3.0.dist-info/entry_points.txt +2 -0
- 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
|