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,110 @@
1
+ import argparse
2
+ import gc
3
+ import os
4
+ import re
5
+ import subprocess
6
+ import sys
7
+ from datetime import datetime
8
+ from typing import Optional
9
+
10
+ from ul_py_tool.commands.cmd import Cmd
11
+ from ul_py_tool.utils.arg_str2bool import arg_str2bool
12
+ from ul_py_tool.utils.write_stdout import write_stdout
13
+
14
+ from ul_api_utils.conf import APPLICATION_GUNICORN_WORKERS
15
+ from ul_api_utils.const import THIS_LIB_CWD
16
+
17
+ ENV_LOCAL = 'local'
18
+ PY_FILE_SUF = '.py'
19
+
20
+
21
+ class CmdStart(Cmd):
22
+ app_dir: str
23
+ app_file_name: str = 'main.py'
24
+ app_name: str = 'flask_app'
25
+ env: str
26
+ port: int
27
+ debug: bool
28
+ max_requests: int
29
+ max_requests_jitter: int
30
+ worker_class: str
31
+ freeze_gc: bool
32
+ statsd_endpoint: Optional[str] = None
33
+ statsd_prefix: Optional[str] = None
34
+
35
+ @property
36
+ def app_rel_dir(self) -> str:
37
+ return os.path.relpath(self.app_dir, os.getcwd())
38
+
39
+ @property
40
+ def app_file_path(self) -> str:
41
+ return os.path.join(self.app_dir, self.app_file_name)
42
+
43
+ @property
44
+ def app_module(self) -> str:
45
+ file_rel_path = os.path.relpath(self.app_file_path, os.getcwd())
46
+ if file_rel_path.endswith(PY_FILE_SUF):
47
+ file_rel_path = file_rel_path[:-len(PY_FILE_SUF)]
48
+ return re.sub(r'/+', '.', file_rel_path.replace('\\', '/')).strip('.')
49
+
50
+ @staticmethod
51
+ def add_parser_args(parser: argparse.ArgumentParser) -> None:
52
+ parser.add_argument('--app-dir', dest='app_dir', type=str, required=True, help="dir to import ")
53
+ parser.add_argument('--env', dest='env', type=str, required=True)
54
+ parser.add_argument('--port', dest='port', type=int, required=False, default=30000)
55
+ parser.add_argument('--debug', dest='debug', type=arg_str2bool, default=False, required=False)
56
+ parser.add_argument('--max-requests', dest='max_requests', type=int, default=1000, required=False)
57
+ parser.add_argument('--max-requests-jitter', dest='max_requests_jitter', type=int, default=50, required=False)
58
+ parser.add_argument('--worker-class', dest='worker_class', type=str, default='sync', required=False)
59
+ parser.add_argument('--statsd_endpoint', dest='statsd_endpoint', type=str, default=None, required=False)
60
+ parser.add_argument('--statsd_prefix', dest='statsd_prefix', type=str, default=None, required=False)
61
+ parser.add_argument('--freeze-gc', dest='freeze_gc', type=bool, default=False, required=False)
62
+
63
+ def run(self) -> None:
64
+ if self.freeze_gc:
65
+ gc.disable()
66
+
67
+ env = os.environ.copy()
68
+ name = re.sub(r'[^0-9a-z]+', '-', f'gncrn-{os.path.relpath(self.app_dir, os.getcwd()).lower().strip("/").strip()}')
69
+ env['PYTHONUNBUFFERED'] = os.environ.get('PYTHONUNBUFFERED', '0')
70
+ env['PYTHONPATH'] = os.getcwd()
71
+ env['APPLICATION_START_DT'] = datetime.now().isoformat()
72
+ env['APPLICATION_ENV'] = self.env
73
+ env['APPLICATION_DIR'] = self.app_dir
74
+ env['APPLICATION_DEBUG'] = '1' if self.debug else '0'
75
+ env['FLASK_APP'] = self.app_file_path
76
+
77
+ assert len(APPLICATION_GUNICORN_WORKERS) > 0
78
+
79
+ debug = (self.debug and self.env == ENV_LOCAL)
80
+ local_conf = os.path.abspath(os.path.normpath(os.path.join(THIS_LIB_CWD, "commands", "start", "gunicorn.conf.local.py")))
81
+ prod_conf = os.path.abspath(os.path.normpath(os.path.join(THIS_LIB_CWD, "commands", "start", "gunicorn.conf.py")))
82
+ gunicorn_config = prod_conf if self.freeze_gc and not debug else local_conf
83
+
84
+ args = [
85
+ f'-n={name}',
86
+ f'-w={APPLICATION_GUNICORN_WORKERS}',
87
+ f'--worker-class={self.worker_class}',
88
+ f'-b=0.0.0.0:{self.port}',
89
+ f'--config={gunicorn_config}',
90
+ '--log-level=INFO',
91
+ f'--max-requests={self.max_requests}',
92
+ f'--max-requests-jitter={self.max_requests_jitter}',
93
+ '--timeout=60',
94
+ '--access-logfile=-',
95
+ '--error-logfile=-',
96
+ '--disable-redirect-access-to-syslog',
97
+ *(['--reload'] if debug else ['--preload']),
98
+ f'{self.app_module}:{self.app_name}',
99
+ ]
100
+
101
+ if self.statsd_endpoint and self.env != ENV_LOCAL:
102
+ if self.statsd_prefix:
103
+ args.extend([f'--statsd-host={self.statsd_endpoint}', f"--statsd-prefix={self.statsd_prefix}"])
104
+ else:
105
+ args.extend([f'--statsd-host={self.statsd_endpoint}', f"--statsd-prefix={self.app_module.split('.')[1]}_{self.env}"])
106
+
107
+ write_stdout(f'name={name}')
108
+ write_stdout(f'args={args}')
109
+ subprocess.run(['gunicorn', '--check-config', '--print-config', *args], cwd=os.getcwd(), stdout=sys.stdout, stderr=sys.stderr, text=True, env=env)
110
+ os.execvpe('gunicorn', args, env)
@@ -0,0 +1,76 @@
1
+ import argparse
2
+ import os
3
+ import re
4
+ import subprocess
5
+ import sys
6
+ from datetime import datetime
7
+
8
+ from ul_py_tool.commands.cmd import Cmd
9
+ from ul_py_tool.utils.arg_str2bool import arg_str2bool
10
+
11
+ from ul_api_utils.const import APPLICATION_ENV__LOCAL, PY_FILE_SUF
12
+
13
+
14
+ class CmdWorkerStart(Cmd):
15
+ app_dir: str
16
+ env: str
17
+ debug: bool
18
+ worker_name: str
19
+ app_file_name: str = 'main.py'
20
+
21
+ @property
22
+ def app_file_path(self) -> str:
23
+ return os.path.join(self.app_dir, self.app_file_name)
24
+
25
+ @property
26
+ def app_module(self) -> str:
27
+ file_rel_path = os.path.relpath(self.app_file_path, os.getcwd())
28
+ if file_rel_path.endswith(PY_FILE_SUF):
29
+ file_rel_path = file_rel_path[:-len(PY_FILE_SUF)]
30
+ return re.sub(r'/+', '.', file_rel_path.replace('\\', '/')).strip('.')
31
+
32
+ @staticmethod
33
+ def add_parser_args(parser: argparse.ArgumentParser) -> None:
34
+ parser.add_argument('--app-dir', dest='app_dir', type=str, required=True, help="dir to import main.py")
35
+ parser.add_argument('--env', dest='env', type=str, required=True)
36
+ parser.add_argument('--worker', dest='worker_name', type=str, required=True)
37
+ parser.add_argument('--debug', dest='debug', type=arg_str2bool, default=False, required=False)
38
+
39
+ def run(self) -> None:
40
+ env = os.environ.copy()
41
+ env['PYTHONUNBUFFERED'] = '1'
42
+ env['PYTHONPATH'] = os.getcwd()
43
+ env['APPLICATION_START_DT'] = datetime.now().isoformat()
44
+ env['APPLICATION_ENV'] = self.env
45
+ env['APPLICATION_DIR'] = self.app_dir
46
+ env['APPLICATION_DEBUG'] = '1' if self.debug else '0'
47
+
48
+ if self.debug and self.env == APPLICATION_ENV__LOCAL:
49
+ subprocess.run(
50
+ [
51
+ 'watchmedo',
52
+ 'auto-restart',
53
+ '--pattern=*.py',
54
+ '--recursive',
55
+ f'--directory={os.getcwd()}',
56
+ f'--command=\'python3 -m {self.app_module} --worker={self.worker_name}\'',
57
+ '.',
58
+ ],
59
+ cwd=os.getcwd(),
60
+ stdout=sys.stdout,
61
+ stderr=sys.stderr,
62
+ text=True,
63
+ env=env,
64
+ )
65
+ else:
66
+ subprocess.run(
67
+ [
68
+ 'python3', '-m', f'{self.app_module}',
69
+ f'--worker={self.worker_name}',
70
+ ],
71
+ cwd=os.getcwd(),
72
+ stdout=sys.stdout,
73
+ stderr=sys.stderr,
74
+ text=True,
75
+ env=env,
76
+ )
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ import gc
2
+ from typing import Any
3
+
4
+ from ul_py_tool.utils.write_stdout import write_stdout
5
+
6
+
7
+ def when_ready(server: Any) -> None:
8
+ """
9
+ Use only with --preload option.
10
+ Called just after the server is started.
11
+ Freeze garbage collector objects after preloading the application.
12
+ https://docs.gunicorn.org/en/20.1.0/settings.html?highlight=preload#when-ready
13
+ """
14
+ gc.freeze()
15
+ write_stdout("Objects frozen in permanent generation: ", gc.get_freeze_count())
16
+
17
+
18
+ def post_fork(server: Any, worker: Any) -> None:
19
+ """
20
+ Works only with --preload.
21
+ Called just after a worker has been forked.
22
+ Enable garbage collection on each worker if it's not enabled for some reason.
23
+ https://docs.gunicorn.org/en/20.1.0/settings.html?highlight=preload#post-fork
24
+ """
25
+ write_stdout("Enabling GC for worker", worker.pid)
26
+ gc.enable()
@@ -0,0 +1,22 @@
1
+ from typing import Dict, Any, Optional
2
+
3
+ from gunicorn.app.wsgiapp import WSGIApplication # type: ignore
4
+
5
+
6
+ class UnicLabWSGIApplication(WSGIApplication):
7
+ def __init__(self, app_uri: str, options: Optional[Dict[str, Any]] = None) -> None:
8
+ self.options = options or {}
9
+ self.app_uri = app_uri
10
+ super().__init__()
11
+
12
+ def load_config(self) -> None:
13
+ config = {
14
+ key: value
15
+ for key, value in self.options.items()
16
+ if key in self.cfg.settings and value is not None
17
+ }
18
+ for key, value in config.items():
19
+ self.cfg.set(key.lower(), value)
20
+
21
+ def load(self) -> str:
22
+ return self.app_uri