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 @@
|
|
|
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()
|