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 @@
1
+ (()=>{"use strict";var e={2:(e,t,r)=>{r.r(t),r.d(t,{API_UTILS_DEBUGGER_ENABLED:()=>n,DURATION_DANGER:()=>a,DURATION_WARNING:()=>o,X_API_UTILS_DEBUGGER_PIN:()=>i});const n="api-utils-debugger-enabled",i="x-api-utils-debugger-pin",o=.2,a=.4},219:(e,t,r)=>{r.r(t),r.d(t,{default:()=>i});var n=r(2);function i(e,t){function r(e,r,n){const i=new Date;i.setTime(i.getTime()+24*n*60*60*1e3);let o="expires="+i.toUTCString();t.cookie=e+"="+r+";"+o+";path=/"}function i(e){let r=e+"=",n=decodeURIComponent(t.cookie).split(";");for(let e=0;e<n.length;e++){let t=n[e];for(;" "===t.charAt(0);)t=t.substring(1);if(0===t.indexOf(r))return t.substring(r.length,t.length)}return""}e.setDebuggerPin=function(t){r(n.X_API_UTILS_DEBUGGER_PIN,t,1),e.location.reload()},e.$apiUtilsDebugger={},e.$apiUtilsDebugger.setCookie=r,e.$apiUtilsDebugger.getCookie=i;let o=[];const a=e.addDebuggerRequestStats=function(...e){o.push(e)};if(""===i(n.X_API_UTILS_DEBUGGER_PIN))return;const u=t.querySelector("script[data-ul-debugger]");if(!u)return;e.$apiUtilsDebugger.mainScript=u.dataset.ulDebugger;let l=u.dataset.ulDebugger.split("/");l[l.length-1]="",e.$apiUtilsDebugger.apiExplain=l.join("/")+"debugger-explain";let s=document.createElement("script");s.src=e.$apiUtilsDebugger.mainScript,s.async=!1,t.body.append(s);let g=e.setInterval((function(){e.addDebuggerRequestStats!==a&&(o.forEach((function(t){e.addDebuggerRequestStats(...t)})),o=[],e.clearInterval(g))}),50)}}},t={};function r(n){var i=t[n];if(void 0!==i)return i.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};(()=>{r.r(n);var e=r(219);window instanceof Window?(0,e.default)(window,window.document):console.error("window is not of Window instance")})()})();
ul_api_utils/conf.py ADDED
@@ -0,0 +1,70 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+ from ul_py_tool.utils.colors import NC, FG_BLUE
8
+
9
+ from ul_api_utils.const import APPLICATION_ENV__LOCAL, API_PATH__SWAGGER, API_PATH__SWAGGER_SPEC
10
+ from ul_api_utils.utils.decode_base64 import decode_base64_to_string
11
+ from ul_api_utils.utils.flags import Flagged
12
+
13
+ APPLICATION_START_DT = datetime.fromisoformat(os.environ.get('APPLICATION_START_DT', datetime.now().isoformat()))
14
+ APPLICATION_DEBUGGER_PIN = os.environ.get('APPLICATION_DEBUGGER_PIN', '1232344321')
15
+ assert len(APPLICATION_DEBUGGER_PIN) > 0
16
+ APPLICATION_GUNICORN_WORKERS = os.environ.get('APPLICATION_GUNICORN_WORKERS', '')
17
+ DOCKER_BUILD__CONTAINER_CODE_COMMIT_HASH = os.environ.get('DOCKER_BUILD__CONTAINER_CODE_COMMIT_HASH', '')
18
+ DOCKER_BUILD__CONTAINER_SERVER_TIME = os.environ.get('DOCKER_BUILD__CONTAINER_SERVER_TIME', '')
19
+ DOCKER_BUILD__CONTAINER_CODE_TAG = os.environ.get('DOCKER_BUILD__CONTAINER_CODE_TAG', '')
20
+
21
+ APPLICATION_ENV: str = os.environ.get('APPLICATION_ENV', APPLICATION_ENV__LOCAL) # TODO: make it required!
22
+ APPLICATION_ENV_IS_LOCAL = APPLICATION_ENV == APPLICATION_ENV__LOCAL
23
+
24
+ APPLICATION_DIR: str = os.path.abspath(os.environ.get('APPLICATION_DIR', os.getcwd()))
25
+ APPLICATION_TMP: str = os.path.join(APPLICATION_DIR, '.tmp')
26
+
27
+ APPLICATION_UNDER_DOCKER = '/docker_app/' in os.getcwd()
28
+
29
+ APPLICATION_DEBUG = os.environ.get('APPLICATION_DEBUG', '0') == '1' # this env var set in app-utils
30
+ APPLICATION_DEBUG_LOGGING = os.environ.get('APPLICATION_DEBUG_LOGGING', '0') == '1' # this env var set in app-utils
31
+
32
+ _APPLICATION_JWT_PUBLIC_KEY = os.environ.get('APPLICATION_JWT_PUBLIC_KEY', None) or None # "or None" in case empty string. TODO: make it required!
33
+ APPLICATION_JWT_PUBLIC_KEY: str = decode_base64_to_string(_APPLICATION_JWT_PUBLIC_KEY) if _APPLICATION_JWT_PUBLIC_KEY is not None else ''
34
+
35
+ _APPLICATION_JWT_PRIVATE_KEY = os.environ.get('APPLICATION_JWT_PRIVATE_KEY', None) or None # "or None" in case empty string
36
+ APPLICATION_JWT_PRIVATE_KEY: Optional[str] = decode_base64_to_string(_APPLICATION_JWT_PRIVATE_KEY) if _APPLICATION_JWT_PRIVATE_KEY is not None else None
37
+
38
+ APPLICATION_LOG_FORMAT = os.environ.get(
39
+ 'APPLICATION_LOG_FORMAT',
40
+ f'%(asctime)s | %(levelname)-7s | %(name)s:%(funcName)s |'
41
+ f'{FG_BLUE if APPLICATION_ENV_IS_LOCAL and APPLICATION_DEBUG else ""} %(message)s{NC if APPLICATION_ENV_IS_LOCAL and APPLICATION_DEBUG else ""}',
42
+ )
43
+ assert len(APPLICATION_LOG_FORMAT) and '(message)' in APPLICATION_LOG_FORMAT
44
+
45
+ os.makedirs(APPLICATION_TMP, exist_ok=True)
46
+
47
+ APPLICATION_LOG_LEVEL = os.environ.get('APPLICATION_LOG_LEVEL', os.environ.get('LOGLEVEL', 'INFO')).strip().upper()
48
+ _LOG_MAP = {
49
+ 'INFO': logging.INFO,
50
+ 'DEBUG': logging.DEBUG,
51
+ 'ERROR': logging.ERROR,
52
+ 'WARNING': logging.WARNING,
53
+ }
54
+ assert APPLICATION_LOG_LEVEL in _LOG_MAP
55
+
56
+ APPLICATION_SENTRY_DSN = os.environ.get('APPLICATION_SENTRY_DSN', '')
57
+ APPLICATION_SENTRY_ENABLED_FLASK = os.environ.get('APPLICATION_SENTRY_FLASK', '1') == '1'
58
+
59
+ APPLICATION_F = Flagged(os.environ.get('APPLICATION_F', ''))
60
+
61
+ APPLICATION_SWAGGER_SPECIFICATION_PATH = os.environ.get('APPLICATION_SWAGGER_SPECIFICATION_PATH', API_PATH__SWAGGER_SPEC)
62
+ APPLICATION_SWAGGER_PATH = os.environ.get('APPLICATION_SWAGGER_PATH', API_PATH__SWAGGER)
63
+
64
+ logging.basicConfig(
65
+ handlers=[logging.StreamHandler(sys.stdout)],
66
+ level=_LOG_MAP[APPLICATION_LOG_LEVEL],
67
+ format=APPLICATION_LOG_FORMAT,
68
+ )
69
+
70
+ logging.getLogger('').setLevel(_LOG_MAP[APPLICATION_LOG_LEVEL])
ul_api_utils/const.py ADDED
@@ -0,0 +1,78 @@
1
+ import os.path
2
+ from typing import Union, Dict, Any, List
3
+
4
+ THIS_LIB_CWD = os.path.dirname(__file__)
5
+
6
+ PY_FILE_SUF = '.py'
7
+
8
+ RESPONSE_PROP_OK = 'ok'
9
+ RESPONSE_PROP_STATUS = 'status'
10
+ RESPONSE_PROP_PAYLOAD = 'payload'
11
+ RESPONSE_PROP_COUNT = 'count'
12
+ RESPONSE_PROP_TOTAL = 'total_count'
13
+ RESPONSE_PROP_ERRORS = 'errors'
14
+ RESPONSE_PROP_DEBUG_STATS = '_debug_stats_'
15
+
16
+
17
+ APPLICATION_ENV__LOCAL = 'local'
18
+ APPLICATION_ENV__DEV = 'dev'
19
+ APPLICATION_ENV__QA = 'qa'
20
+ APPLICATION_ENV__STAGING = 'staging'
21
+ APPLICATION_ENV__PROD = 'prod'
22
+
23
+
24
+ RESPONSE_HEADER__CONTENT_TYPE = 'Content-Type'
25
+ RESPONSE_HEADER__AUTHORIZATION = 'Authorization'
26
+ RESPONSE_HEADER__WWW_AUTH = 'WWW-Authenticate'
27
+ REQUEST_HEADER__CONTENT_TYPE = 'Content-Type'
28
+ REQUEST_HEADER__CONTENT_ENCODING = 'Content-Encoding'
29
+ REQUEST_HEADER__ACCEPT_CONTENT_ENCODING = 'Accept-Encoding'
30
+ REQUEST_HEADER__DEBUGGER = 'x-api-utils-debugger-pin'
31
+ REQUEST_HEADER__INTERNAL = 'x-api-utils-internal-api'
32
+ REQUEST_HEADER__ACCEPT = 'Accept'
33
+ REQUEST_HEADER__X_FORWARDED_FOR = 'X-Forwarded-For'
34
+ REQUEST_HEADER__USER_AGENT = 'User-Agent'
35
+
36
+
37
+ MIME__AVRO = 'application/avro'
38
+ MIME__JSON = 'application/json'
39
+ MIME__MSGPCK = 'application/x-msgpack'
40
+
41
+
42
+ ENCODING_MIME__GZIP = 'gzip'
43
+
44
+ REQUEST_METHOD__PUT = 'PUT'
45
+ REQUEST_METHOD__GET = 'GET'
46
+ REQUEST_METHOD__POST = 'POST'
47
+ REQUEST_METHOD__QUERY = 'QUERY'
48
+ REQUEST_METHOD__PATCH = 'PATCH'
49
+ REQUEST_METHOD__DELETE = 'DELETE'
50
+ REQUEST_METHOD__OPTIONS = 'OPTIONS'
51
+
52
+
53
+ UNDEFINED = 'UNDEFINED'
54
+ OOPS = '...Ooops something went wrong. internal error'
55
+
56
+
57
+ TRestJson = Union[List[Dict[str, Any]], Dict[str, Any]]
58
+
59
+
60
+ API_PATH__SWAGGER = '/swagger'
61
+ API_PATH__SWAGGER_SPEC = '/swagger-specification'
62
+ API_PATH__PERMISSIONS = '/permissions'
63
+ API_PATH__DEBUGGER_JS_UI = '/ul-debugger-ui.js'
64
+ API_PATH__DEBUGGER_JS_MAIN = '/ul-debugger-main.js'
65
+
66
+
67
+ INTERNAL_API__DEFAULT_PATH_PREFIX = '/api'
68
+
69
+ AUTO_GZIP_THRESHOLD_LENGTH = 1000
70
+
71
+ CRON_EXPRESSION_VALIDATION_REGEX = "(^((\*\/)?([0-5]?[0-9])((\,|\-|\/)([0-5]?[0-9]))*|\*)\s+((\*\/)?((2[0-3]|1[0-9]|[0-9]|00))" \
72
+ "((\,|\-|\/)(2[0-3]|1[0-9]|[0-9]|00))*|\*)\s+((\*\/)?([1-9]|[12][0-9]|3[01])((\,|\-|\/)" \
73
+ "([1-9]|[12][0-9]|3[01]))*|\*)\s+((\*\/)?([1-9]|1[0-2])((\,|\-|\/)" \
74
+ "([1-9]|1[0-2]))*|\*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|des))\s+((\*\/)?[0-6]" \
75
+ "((\,|\-|\/)[0-6])*|\*|00|(sun|mon|tue|wed|thu|fri|sat))\s*$)|@(annually|yearly|monthly|weekly|daily|hourly|reboot)" # noqa
76
+
77
+ MAX_UTC_OFFSET_SECONDS = 50400
78
+ MIN_UTC_OFFSET_SECONDS = -43200
File without changes
@@ -0,0 +1,119 @@
1
+ import json
2
+ import time
3
+ from enum import Enum
4
+ from json import JSONEncoder
5
+ from typing import Dict, Any, Union, List, NamedTuple
6
+
7
+ from ul_api_utils.const import RESPONSE_PROP_DEBUG_STATS, API_PATH__DEBUGGER_JS_UI, API_PATH__DEBUGGER_JS_MAIN
8
+ from ul_api_utils.debug import stat
9
+ # https://www.w3schools.com/js/js_cookies.asp
10
+ from ul_api_utils.debug.stat import collecting_enabled
11
+ from ul_api_utils.utils.api_method import ApiMethod
12
+ from ul_api_utils.utils.api_path_version import ApiPathVersion
13
+ from ul_api_utils.utils.colors import COLORS_MAP__TERMINAL
14
+
15
+
16
+ class DebuggerJSONEncoder(JSONEncoder):
17
+ def default(self, obj: object) -> Union[str, Dict[str, Any], List[Any], None]:
18
+ if isinstance(obj, tuple):
19
+ return list(obj)
20
+ if isinstance(obj, Enum):
21
+ return str(obj.value)
22
+ if isinstance(obj, set):
23
+ return list(obj)
24
+ if hasattr(obj, "__html__"): # it needs for Flask ?
25
+ return str(obj.__html__())
26
+ return super().default(obj)
27
+
28
+
29
+ AJAX_INTERSEPTOR = """
30
+ <script>
31
+ (function(window) {
32
+ "use strict";
33
+
34
+ const {fetch: origFetch} = window;
35
+ window.fetch = (resource, options = {}, ...args) => {
36
+ const startedAt = Date.now() / 1000;
37
+ const p = origFetch(resource, options, ...args);
38
+
39
+ p
40
+ .then((resp) => {
41
+ return resp.clone().json().then((result) => {
42
+ if (window.addDebuggerRequestStats != null) {
43
+ window.addDebuggerRequestStats(options.method, resource, startedAt, Date.now() / 1000, result._debug_stats_, resp.statusCode)
44
+ }
45
+ })
46
+ })
47
+ .catch((err) => console.error(err))
48
+
49
+ return p;
50
+ };
51
+ })(window);
52
+ </script>
53
+ """
54
+
55
+
56
+ class DebuggerError(NamedTuple):
57
+ err: Exception
58
+ tb: str
59
+ at: float
60
+
61
+
62
+ class Debugger:
63
+
64
+ __slots__ = (
65
+ 'name',
66
+ 'enabled',
67
+ 'method',
68
+ 'url',
69
+ )
70
+
71
+ def __init__(self, name: str, enabled: bool, method: ApiMethod, url: str) -> None:
72
+ self.name = name
73
+ self.enabled = enabled
74
+ self.method = method
75
+ self.url = url
76
+
77
+ def render_console(self) -> None:
78
+ if not self.enabled:
79
+ return
80
+
81
+ started_at = stat.get_request_started_at()
82
+ ended_at = time.perf_counter()
83
+ name = self.name
84
+
85
+ stats = stat.get_stat(code_spans=True, started_at=started_at, ended_at=ended_at)
86
+ stat_str = stat.mk_stat_string(name, stats, started_at=started_at, ended_at=ended_at, truncate_request_len=None, trim_txt_new_line=True, cm=COLORS_MAP__TERMINAL)
87
+
88
+ print(f'\n{stat_str}\n') # noqa
89
+
90
+ def render_dict(self, status_code: int) -> Dict[str, Any]:
91
+ if not collecting_enabled():
92
+ return {}
93
+ stats = [st.unwrap() for st in stat.get_stat(started_at=stat.get_request_started_at(), ended_at=time.perf_counter())]
94
+
95
+ return {
96
+ RESPONSE_PROP_DEBUG_STATS: stats,
97
+ }
98
+
99
+ def render_html(self, status_code: int) -> str:
100
+ script = (
101
+ f'<script '
102
+ f'data-ul-debugger="{ApiPathVersion.NO_VERSION.compile_path(API_PATH__DEBUGGER_JS_MAIN, prefix="/api")}" '
103
+ f'src="{ApiPathVersion.NO_VERSION.compile_path(API_PATH__DEBUGGER_JS_UI, prefix="/api")}" '
104
+ f'></script>'
105
+ )
106
+ if not self.enabled:
107
+ return script
108
+
109
+ started_at = stat.get_request_started_at()
110
+ ended_at = time.perf_counter()
111
+
112
+ stats = stat.get_stat(code_spans=True, started_at=started_at, ended_at=ended_at)
113
+
114
+ return (
115
+ f'{script}'
116
+ '<script>setTimeout(function(){{\n'
117
+ f'window.addDebuggerRequestStats("{self.method.value}", "{self.url}", {started_at}, {ended_at}, {json.dumps(stats, cls=DebuggerJSONEncoder)}, {status_code});'
118
+ '\n}}, 300);</script>'
119
+ )
@@ -0,0 +1,93 @@
1
+ import contextlib
2
+ import os
3
+ import sys
4
+ import tracemalloc
5
+ from typing import Dict, Optional, Iterator
6
+
7
+ import math
8
+ from ul_py_tool.utils.colors import FG_BLUE, NC, FG_GRAY, FG_YELLOW, FG_GREEN
9
+ from ul_py_tool.utils.write_stdout import write_stdout
10
+
11
+
12
+ def display_stat(name: str, stat: Dict[str, float], sort_by_value: bool = False, color: Optional[str] = None) -> None:
13
+ stat_max_len = max(len(key) for key in stat.keys())
14
+ max_val = max(v for v in stat.values()) if len(stat) > 1 else 1.
15
+
16
+ if sort_by_value:
17
+ sorted_keys = sorted(stat.keys(), key=stat.get) # type: ignore
18
+ else:
19
+ sorted_keys = sorted(stat.keys())
20
+
21
+ chart_width = 30
22
+ write_stdout(f'\n{name}:')
23
+ for key in sorted_keys:
24
+ clr = color
25
+ if clr is None:
26
+ clr = FG_BLUE if key.endswith(".py") else FG_GRAY
27
+ value = stat[key]
28
+ chart_size = (math.ceil(value * chart_width / max_val) if max_val else 0)
29
+ write_stdout(f'{key.strip().strip(os.sep).strip(): <{stat_max_len}} = {clr}{value: >10.3f} KiB [{"▉" * chart_size}{" " * (chart_width - chart_size)}]{NC}')
30
+
31
+
32
+ @contextlib.contextmanager
33
+ def trace_malloc(show_all: bool = True, show_libs: bool = True, show_code: bool = True) -> Iterator[None]:
34
+ tracemalloc.start()
35
+
36
+ yield
37
+
38
+ snapshot = tracemalloc.take_snapshot()
39
+
40
+ snapshot = snapshot.filter_traces((
41
+ tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
42
+ tracemalloc.Filter(False, "<frozen importlib._bootstrap_external>"),
43
+ tracemalloc.Filter(False, "<unknown>"),
44
+ ))
45
+
46
+ top_stats = snapshot.statistics('filename')
47
+
48
+ sites_packages = '/site-packages/'
49
+ cwd = os.path.normpath(os.path.abspath(os.getcwd()))
50
+
51
+ libs_stat: Dict[str, float] = {}
52
+ code_stat: Dict[str, float] = {}
53
+ all_files_stat: Dict[str, float] = {}
54
+ other_stat: Dict[str, float] = {}
55
+
56
+ for st in top_stats:
57
+ frame = st.traceback[0]
58
+ fname = os.path.normpath(os.path.abspath(frame.filename))
59
+ fname_segm = fname.strip().split(os.sep)
60
+ size = st.size / 1024
61
+
62
+ if not fname.endswith('.py'):
63
+ other_stat[fname] = other_stat.get(fname, 0.) + size
64
+ continue
65
+
66
+ for i in range(len(fname_segm)):
67
+ key = os.sep.join(fname_segm[:i + 1])
68
+ all_files_stat[key] = all_files_stat.get(key, 0.) + size
69
+
70
+ if key.startswith(sys.prefix) and sites_packages in key:
71
+ i = key.index(sites_packages)
72
+ lib_key = "/".join(key[i:].split(os.sep)[2:3])
73
+ if lib_key:
74
+ libs_stat[lib_key] = libs_stat.get(lib_key, 0.) + size
75
+ elif key.startswith(cwd):
76
+ code_key = key.removeprefix(cwd)
77
+ code_stat[code_key] = code_stat.get(code_key, 0.) + size
78
+
79
+ if show_all:
80
+ display_stat('All Files', all_files_stat)
81
+
82
+ if show_libs:
83
+ display_stat('Libs', libs_stat, sort_by_value=True, color=FG_YELLOW)
84
+
85
+ if show_code:
86
+ display_stat('Code', code_stat, sort_by_value=True, color=FG_BLUE)
87
+
88
+ display_stat('Other', other_stat, sort_by_value=True, color=FG_BLUE)
89
+
90
+ total = sum(st.size for st in top_stats)
91
+ write_stdout(f"{FG_GREEN}Total allocated size: {(total / 1024):.3f} KiB{NC}")
92
+
93
+ tracemalloc.stop()