ul-api-utils 7.7.9__tar.gz → 7.8.0__tar.gz

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.

Potentially problematic release.


This version of ul-api-utils might be problematic. Click here for more details.

Files changed (147) hide show
  1. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/PKG-INFO +1 -1
  2. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/setup.py +1 -1
  3. ul-api-utils-7.8.0/ul_api_utils/commands/cmd_generate_api_docs.py +165 -0
  4. ul-api-utils-7.8.0/ul_api_utils/resources/web_forms/custom_fields/custom_checkbox_select.py +5 -0
  5. ul-api-utils-7.8.0/ul_api_utils/resources/web_forms/custom_widgets/custom_select_widget.py +86 -0
  6. ul-api-utils-7.8.0/ul_api_utils/resources/web_forms/custom_widgets/custom_text_input_widget.py +42 -0
  7. ul-api-utils-7.8.0/ul_api_utils/resources/web_forms/uni_form.py +75 -0
  8. ul-api-utils-7.8.0/ul_api_utils/utils/jinja/__init__.py +0 -0
  9. ul-api-utils-7.8.0/ul_api_utils/validators/__init__.py +0 -0
  10. ul-api-utils-7.8.0/ul_api_utils/validators/__tests__/__init__.py +0 -0
  11. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils.egg-info/PKG-INFO +1 -1
  12. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils.egg-info/SOURCES.txt +7 -0
  13. ul-api-utils-7.7.9/ul_api_utils/commands/cmd_generate_api_docs.py +0 -134
  14. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/LICENSE +0 -0
  15. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/README.md +0 -0
  16. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/__init__.py +0 -0
  17. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/conf.py +0 -0
  18. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/main.py +0 -0
  19. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/models/__init__.py +0 -0
  20. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/permissions.py +0 -0
  21. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/pure_flask_example.py +0 -0
  22. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/rate_limit_load.py +0 -0
  23. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/routes/__init__.py +0 -0
  24. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/routes/api_some.py +0 -0
  25. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/workers/__init__.py +0 -0
  26. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/example/workers/worker.py +0 -0
  27. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/setup.cfg +0 -0
  28. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/__init__.py +0 -0
  29. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/access/__init__.py +0 -0
  30. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/__init__.py +0 -0
  31. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/api_request.py +0 -0
  32. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/api_resource.py +0 -0
  33. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/api_resource_config.py +0 -0
  34. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/api_resource_fn_typing.py +0 -0
  35. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/api_resource_type.py +0 -0
  36. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/api_response.py +0 -0
  37. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/api_response_db.py +0 -0
  38. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/api_response_payload_alias.py +0 -0
  39. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/db_types.py +0 -0
  40. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/api_resource/signature_check.py +0 -0
  41. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/commands/__init__.py +0 -0
  42. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/commands/cmd_enc_keys.py +0 -0
  43. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/commands/cmd_gen_api_user_token.py +0 -0
  44. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/commands/cmd_gen_new_api_user.py +0 -0
  45. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/commands/cmd_start.py +0 -0
  46. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/commands/cmd_worker_start.py +0 -0
  47. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/commands/start/__init__.py +0 -0
  48. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/commands/start/gunicorn.conf.py +0 -0
  49. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/commands/start/wsgi.py +0 -0
  50. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/conf/ul-debugger-main.js +0 -0
  51. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/conf/ul-debugger-ui.js +0 -0
  52. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/conf.py +0 -0
  53. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/const.py +0 -0
  54. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/debug/__init__.py +0 -0
  55. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/debug/debugger.py +0 -0
  56. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/debug/malloc.py +0 -0
  57. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/debug/stat.py +0 -0
  58. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/encrypt/__init__.py +0 -0
  59. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/encrypt/encrypt_decrypt_abstract.py +0 -0
  60. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/encrypt/encrypt_decrypt_aes_xtea.py +0 -0
  61. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/errors.py +0 -0
  62. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/internal_api/__init__.py +0 -0
  63. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/internal_api/__tests__/__init__.py +0 -0
  64. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/internal_api/__tests__/internal_api.py +0 -0
  65. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/internal_api/__tests__/internal_api_content_type.py +0 -0
  66. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/internal_api/internal_api.py +0 -0
  67. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/internal_api/internal_api_check_context.py +0 -0
  68. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/internal_api/internal_api_error.py +0 -0
  69. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/internal_api/internal_api_response.py +0 -0
  70. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/main.py +0 -0
  71. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/modules/__init__.py +0 -0
  72. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/modules/__tests__/__init__.py +0 -0
  73. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/modules/__tests__/test_api_sdk_jwt.py +0 -0
  74. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/modules/api_sdk.py +0 -0
  75. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/modules/api_sdk_config.py +0 -0
  76. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/modules/api_sdk_jwt.py +0 -0
  77. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/modules/intermediate_state.py +0 -0
  78. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/modules/worker_context.py +0 -0
  79. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/modules/worker_sdk.py +0 -0
  80. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/modules/worker_sdk_config.py +0 -0
  81. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/py.typed +0 -0
  82. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/__init__.py +0 -0
  83. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/caching.py +0 -0
  84. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/debugger_scripts.py +0 -0
  85. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/health_check/__init__.py +0 -0
  86. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/health_check/const.py +0 -0
  87. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/health_check/health_check.py +0 -0
  88. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/health_check/health_check_template.py +0 -0
  89. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/health_check/resource.py +0 -0
  90. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/not_implemented.py +0 -0
  91. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/permissions.py +0 -0
  92. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/rate_limitter.py +0 -0
  93. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/resources/swagger.py +0 -0
  94. {ul-api-utils-7.7.9/ul_api_utils/utils → ul-api-utils-7.8.0/ul_api_utils/resources/web_forms}/__init__.py +0 -0
  95. {ul-api-utils-7.7.9/ul_api_utils/utils/__tests__ → ul-api-utils-7.8.0/ul_api_utils/resources/web_forms/custom_fields}/__init__.py +0 -0
  96. {ul-api-utils-7.7.9/ul_api_utils/utils/flask_swagger_generator → ul-api-utils-7.8.0/ul_api_utils/resources/web_forms/custom_widgets}/__init__.py +0 -0
  97. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/sentry.py +0 -0
  98. {ul-api-utils-7.7.9/ul_api_utils/utils/flask_swagger_generator/specifiers → ul-api-utils-7.8.0/ul_api_utils/utils}/__init__.py +0 -0
  99. {ul-api-utils-7.7.9/ul_api_utils/utils/flask_swagger_generator/utils → ul-api-utils-7.8.0/ul_api_utils/utils/__tests__}/__init__.py +0 -0
  100. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/__tests__/api_path_version.py +0 -0
  101. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/__tests__/unwrap_typing.py +0 -0
  102. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/api_encoding.py +0 -0
  103. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/api_format.py +0 -0
  104. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/api_method.py +0 -0
  105. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/api_pagination.py +0 -0
  106. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/api_path_version.py +0 -0
  107. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/api_request_info.py +0 -0
  108. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/avro.py +0 -0
  109. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/broker_topics_message_count.py +0 -0
  110. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/cached_per_request.py +0 -0
  111. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/colors.py +0 -0
  112. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/constants.py +0 -0
  113. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/decode_base64.py +0 -0
  114. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/deprecated.py +0 -0
  115. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flags.py +0 -0
  116. {ul-api-utils-7.7.9/ul_api_utils/utils/jinja → ul-api-utils-7.8.0/ul_api_utils/utils/flask_swagger_generator}/__init__.py +0 -0
  117. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/conf.py +0 -0
  118. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/exceptions.py +0 -0
  119. {ul-api-utils-7.7.9/ul_api_utils/validators → ul-api-utils-7.8.0/ul_api_utils/utils/flask_swagger_generator/specifiers}/__init__.py +0 -0
  120. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_models.py +0 -0
  121. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_specifier.py +0 -0
  122. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_three_specifier.py +0 -0
  123. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_version.py +0 -0
  124. {ul-api-utils-7.7.9/ul_api_utils/validators/__tests__ → ul-api-utils-7.8.0/ul_api_utils/utils/flask_swagger_generator/utils}/__init__.py +0 -0
  125. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/utils/input_type.py +0 -0
  126. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/utils/parameter_type.py +0 -0
  127. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/utils/replace_in_dict.py +0 -0
  128. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/utils/request_type.py +0 -0
  129. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/utils/schema_type.py +0 -0
  130. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/flask_swagger_generator/utils/security_type.py +0 -0
  131. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/imports.py +0 -0
  132. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/jinja/t_url_for.py +0 -0
  133. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/jinja/to_pretty_json.py +0 -0
  134. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/json_encoder.py +0 -0
  135. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/load_modules.py +0 -0
  136. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/token_check.py +0 -0
  137. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/token_check_through_request.py +0 -0
  138. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/unwrap_typing.py +0 -0
  139. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/utils/uuid_converter.py +0 -0
  140. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/validators/__tests__/test_custom_fields.py +0 -0
  141. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/validators/custom_fields.py +0 -0
  142. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/validators/validate_empty_object.py +0 -0
  143. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils/validators/validate_uuid.py +0 -0
  144. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils.egg-info/dependency_links.txt +0 -0
  145. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils.egg-info/entry_points.txt +0 -0
  146. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils.egg-info/requires.txt +0 -0
  147. {ul-api-utils-7.7.9 → ul-api-utils-7.8.0}/ul_api_utils.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ul-api-utils
3
- Version: 7.7.9
3
+ Version: 7.8.0
4
4
  Summary: Python api utils
5
5
  Author: Unic-lab
6
6
  Author-email:
@@ -9,7 +9,7 @@ with open(path.join(HERE, 'README.md'), encoding='utf-8') as f:
9
9
 
10
10
  setup(
11
11
  name='ul-api-utils',
12
- version='7.7.9',
12
+ version='7.8.0',
13
13
  description='Python api utils',
14
14
  author='Unic-lab',
15
15
  long_description=long_description,
@@ -0,0 +1,165 @@
1
+ import argparse
2
+ import ast
3
+ import importlib
4
+ import inspect
5
+ import os
6
+ import logging
7
+ from datetime import datetime
8
+ from typing import Dict, Callable, Any, List, TextIO
9
+
10
+ from flask import url_for, Flask
11
+ from ul_py_tool.commands.cmd import Cmd
12
+
13
+ from ul_api_utils import conf
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class CmdGenApiFunctionDocumentation(Cmd):
19
+ api_dir: str
20
+ db_dir: str
21
+ include_api_utils_doc: bool
22
+ include_db_utils_doc: bool
23
+
24
+ @staticmethod
25
+ def add_parser_args(parser: argparse.ArgumentParser) -> None:
26
+ parser.add_argument('--api-dir', dest='api_dir', type=str, required=True)
27
+ parser.add_argument('--db-dir', dest='db_dir', type=str, required=True)
28
+ parser.add_argument('--include-utils-api', dest='include_api_utils_doc', type=bool, required=False, default=False)
29
+ parser.add_argument('--include-utils-db', dest='include_db_utils_doc', type=bool, required=False, default=False)
30
+
31
+ @property
32
+ def api_module(self) -> str:
33
+ return self.api_dir.replace('/', '.')
34
+
35
+ @property
36
+ def api_main_module(self) -> str:
37
+ return self.api_dir.replace('/', '.') + ".main"
38
+
39
+ def run(self) -> None:
40
+ root_folder = os.getcwd()
41
+ conf.APPLICATION_DIR = os.path.join(root_folder, self.api_dir) # because sdk uses this variable to load routes
42
+ current_app = self.load_flask_app(self.api_main_module)
43
+ api_utils_functions = self.load_functions(f'{self.api_dir}/utils')
44
+ conf.APPLICATION_DIR = os.path.join(root_folder, self.db_dir)
45
+ db_helper_functions = self.load_functions(f'{self.db_dir}/models_manager')
46
+ db_utils_functions = self.load_functions(f"{self.db_dir}/utils")
47
+ with current_app.app_context():
48
+ current_app.config['SERVER_NAME'] = '{base_url}'
49
+ with current_app.app_context(), open(f'.tmp/api_doc_{datetime.now().isoformat()}.md', 'w') as file:
50
+ for api_route_id, flask_api_rule in enumerate(current_app.url_map.iter_rules()):
51
+ options = {}
52
+ for arg in flask_api_rule.arguments:
53
+ options[arg] = "[{0}]".format(arg)
54
+ api_route_methods = ','.join([method for method in flask_api_rule.methods if method not in ('HEAD', 'OPTIONS')]) # type: ignore
55
+ api_route_path = url_for(flask_api_rule.endpoint, **options).replace('%5B', '[').replace('%5D', ']')
56
+ func_object = current_app.view_functions[flask_api_rule.endpoint]
57
+ if not func_object.__module__.startswith(self.api_module):
58
+ continue
59
+ self.generate_documentation(
60
+ func_object,
61
+ file,
62
+ api_route_id=api_route_id,
63
+ api_route_path=api_route_path,
64
+ api_route_methods=api_route_methods,
65
+ loaded_db_helper_functions=db_helper_functions,
66
+ loaded_api_utils_functions=api_utils_functions,
67
+ loaded_db_utils_functions=db_utils_functions,
68
+ )
69
+
70
+ @staticmethod
71
+ def load_functions(directory: str) -> Dict[str, Callable[..., Any]]:
72
+ function_name_object__map = {}
73
+ for root, _dirs, files in os.walk(directory):
74
+ for file in files:
75
+ py_postfix = '.py'
76
+ if file.endswith(py_postfix):
77
+ module_name = file[:-len(py_postfix)]
78
+ module_path = os.path.join(root, file)
79
+ spec = importlib.util.spec_from_file_location(module_name, module_path)
80
+ assert spec is not None # only for mypy
81
+ assert spec.loader is not None # only for mypy
82
+ module = importlib.util.module_from_spec(spec)
83
+ spec.loader.exec_module(module)
84
+ functions = inspect.getmembers(module, inspect.isfunction)
85
+ for name, func in functions:
86
+ function_name_object__map[name] = func
87
+ return function_name_object__map
88
+
89
+ @staticmethod
90
+ def load_flask_app(api_sdk_module: str) -> Flask:
91
+ module = importlib.import_module(api_sdk_module)
92
+ return module.flask_app
93
+
94
+ @staticmethod
95
+ def find_called_functions_in_api(api_function_object: Callable[..., Any]) -> List[Any]:
96
+ calls = []
97
+ source = inspect.getsource(api_function_object)
98
+ tree = ast.parse(source)
99
+ for node in ast.walk(tree):
100
+ if isinstance(node, ast.Call):
101
+ if isinstance(node.func, ast.Name):
102
+ calls.append(node.func.id)
103
+ elif isinstance(node.func, ast.Attribute):
104
+ calls.append(node.func.attr)
105
+ else:
106
+ continue
107
+ return calls
108
+
109
+ def generate_documentation(
110
+ self,
111
+ func_object: Callable[..., Any],
112
+ file_object: TextIO,
113
+ *,
114
+ api_route_id: int,
115
+ api_route_path: str,
116
+ api_route_methods: str,
117
+ loaded_db_helper_functions: Dict[str, Callable[..., Any]],
118
+ loaded_db_utils_functions: Dict[str, Callable[..., Any]],
119
+ loaded_api_utils_functions: Dict[str, Callable[..., Any]],
120
+ ) -> None:
121
+ func_name = func_object.__name__
122
+ functions_called_in_api_route = self.find_called_functions_in_api(func_object)
123
+ docstring = inspect.getdoc(func_object)
124
+ api_docstring = 'None' if docstring is None else docstring
125
+
126
+ file_object.write(f"## {api_route_id} Путь апи {api_route_path}\n\n")
127
+ file_object.write(f"#### Имя функции апи: {func_name}\n")
128
+ file_object.write(f"### Апи методы: {api_route_methods}\n\n")
129
+ file_object.write("**Описание апи метода:** \n\n")
130
+ file_object.write(f"```python\n{api_docstring}\n```\n")
131
+ helper_call = 1
132
+ for function_called_in_api_route in functions_called_in_api_route:
133
+ if function_called_in_api_route not in ('transaction_commit', 'and_', 'or_', 'foreign', 'query_soft_delete', 'ensure_db_object_exists', 'db_search'):
134
+ if function_called_in_api_route in loaded_db_helper_functions:
135
+ helper_func_obj = loaded_db_helper_functions[function_called_in_api_route]
136
+ helper_docstring = inspect.getdoc(helper_func_obj)
137
+ helper_docstring = 'None' if helper_docstring is None else helper_docstring
138
+
139
+ file_object.write(f"### {api_route_id}.{helper_call} Вызвана функция работы с БД : {function_called_in_api_route}\n")
140
+ file_object.write(f"**Описание функции {function_called_in_api_route}:**\n\n")
141
+ file_object.write(f"```python\n{helper_docstring}\n```\n")
142
+ helper_call += 1
143
+ elif function_called_in_api_route in loaded_api_utils_functions:
144
+ if self.include_api_utils_doc:
145
+ util_func_obj = loaded_api_utils_functions[function_called_in_api_route]
146
+ util_docstring = inspect.getdoc(util_func_obj)
147
+ util_docstring = 'None' if util_docstring is None else util_docstring
148
+ if 'db_tables_used' in util_docstring or 'db_table_used' in util_docstring:
149
+ file_object.write(f"### {api_route_id}.{helper_call} Вызвана функция работы с БД : {function_called_in_api_route}\n")
150
+ file_object.write(f"**Описание функции {function_called_in_api_route}:**\n\n")
151
+ file_object.write(f"```python\n{util_docstring}\n```\n")
152
+ helper_call += 1
153
+ elif function_called_in_api_route in loaded_db_utils_functions:
154
+ if self.include_db_utils_doc:
155
+ util_func_obj = loaded_db_utils_functions[function_called_in_api_route]
156
+ db_util_docstring = inspect.getdoc(util_func_obj)
157
+ db_util_docstring = 'None' if db_util_docstring is None else db_util_docstring
158
+ if 'db_tables_used' in db_util_docstring or 'db_table_used' in db_util_docstring:
159
+ file_object.write(f"### {api_route_id}.{helper_call} Вызвана функция работы с БД : {function_called_in_api_route}\n")
160
+ file_object.write(f"**Описание функции {function_called_in_api_route}:**\n\n")
161
+ file_object.write(f"```python\n{db_util_docstring}\n```\n")
162
+ helper_call += 1
163
+ file_object.write('-' * 20)
164
+ file_object.write('\n\n')
165
+ file_object.write('\n\n')
@@ -0,0 +1,5 @@
1
+ from wtforms import SelectMultipleField, widgets # type: ignore
2
+
3
+
4
+ class MultiCheckboxField(SelectMultipleField):
5
+ option_widget = widgets.CheckboxInput()
@@ -0,0 +1,86 @@
1
+ from typing import Any
2
+
3
+ from markupsafe import Markup
4
+ from wtforms import SelectField # type: ignore
5
+ from wtforms.widgets.core import html_params, Select # type: ignore
6
+
7
+
8
+ class CustomLiveSearchPlaceholderSelect(Select):
9
+ """
10
+ Renders a CUSTOM select field.
11
+
12
+ If `multiple` is True, then the `size` property should be specified on
13
+ rendering to make the field useful.
14
+
15
+ The field must provide an `iter_choices()` method which the widget will
16
+ call on rendering; this method must yield tuples of
17
+ `(value, label, selected)`.
18
+ It also must provide a `has_groups()` method which tells whether choices
19
+ are divided into groups, and if they do, the field must have an
20
+ `iter_groups()` method that yields tuples of `(label, choices)`, where
21
+ `choices` is an iterable of `(value, label, selected)` tuples.
22
+ Otherwise, `selected` is False for any option field in select item group.
23
+ """
24
+ def __call__(self, field: SelectField, **kwargs: Any) -> Markup:
25
+ kwargs.setdefault("id", field.id)
26
+ if self.multiple:
27
+ kwargs["multiple"] = True
28
+ flags = getattr(field, "flags", {})
29
+ for k in dir(flags):
30
+ if k in self.validation_attrs and k not in kwargs:
31
+ kwargs[k] = getattr(flags, k)
32
+
33
+ html = ["<select data-live-search='true' data-show-subtext='true' %s>" % html_params(name=field.name, **kwargs),
34
+ "<option value='' disabled selected>Select something...</option>"]
35
+
36
+ if field.has_groups():
37
+ for group, choices in field.iter_groups():
38
+ html.append("<optgroup %s>" % html_params(label=group))
39
+ for val, label, selected in choices:
40
+ html.append(self.render_option(val, label, selected))
41
+ html.append("</optgroup>")
42
+ else:
43
+ for val, label, _ in field.iter_choices():
44
+ html.append(self.render_option(val, label, False))
45
+ html.append("</select>")
46
+ return Markup("".join(html))
47
+
48
+
49
+ class CustomLiveSearchSelect(Select):
50
+ """
51
+ Renders a CUSTOM select field.
52
+
53
+ If `multiple` is True, then the `size` property should be specified on
54
+ rendering to make the field useful.
55
+
56
+ The field must provide an `iter_choices()` method which the widget will
57
+ call on rendering; this method must yield tuples of
58
+ `(value, label, selected)`.
59
+ It also must provide a `has_groups()` method which tells whether choices
60
+ are divided into groups, and if they do, the field must have an
61
+ `iter_groups()` method that yields tuples of `(label, choices)`, where
62
+ `choices` is an iterable of `(value, label, selected)` tuples.
63
+ Otherwise, `selected` is False for any option field in select item group.
64
+ """
65
+ def __call__(self, field: SelectField, **kwargs: Any) -> Markup:
66
+ kwargs.setdefault("id", field.id)
67
+ if self.multiple:
68
+ kwargs["multiple"] = True
69
+ flags = getattr(field, "flags", {})
70
+ for k in dir(flags):
71
+ if k in self.validation_attrs and k not in kwargs:
72
+ kwargs[k] = getattr(flags, k)
73
+
74
+ html = ["<select data-live-search='true' data-show-subtext='true' %s>" % html_params(name=field.name, **kwargs)]
75
+
76
+ if field.has_groups():
77
+ for group, choices in field.iter_groups():
78
+ html.append("<optgroup %s>" % html_params(label=group))
79
+ for val, label, selected in choices:
80
+ html.append(self.render_option(val, label, selected))
81
+ html.append("</optgroup>")
82
+ else:
83
+ for val, label, selected in field.iter_choices():
84
+ html.append(self.render_option(val, label, selected))
85
+ html.append("</select>")
86
+ return Markup("".join(html))
@@ -0,0 +1,42 @@
1
+ from typing import Optional, Any
2
+
3
+ from wtforms import StringField # type: ignore
4
+ from wtforms.widgets import TextInput # type: ignore
5
+ from markupsafe import Markup
6
+
7
+
8
+ class CustomTextInput(TextInput):
9
+ """
10
+ Render a single-line text input with optional input attributes ("required", "maxlength", "minlength", "pattern").
11
+
12
+ examples: TextInputCustom(required=True, min_length=5, max_length=255, pattern=r"^(d+(,d+)*)?$")
13
+ TextInputCustom(max_length=255, pattern=r"^(d+(,d+)*)?$")
14
+ TextInputCustom(required=True, pattern=r"^(d+(,d+)*)?$")
15
+ TextInputCustom(max_length=255)
16
+ In model usage: ... info={"label": "LABEL:", "widget": TextInputCustom(pattern=r"^(d+(,d+)*)?$")}
17
+ """
18
+ validation_attrs = ["required", "maxlength", "minlength", "pattern"]
19
+
20
+ def __init__(self, required: Optional[bool] = None, max_length: Optional[int] = None, min_length: Optional[int] = None, pattern: Optional[str] = None):
21
+ super().__init__()
22
+ self.required = required
23
+ self.max_length = max_length
24
+ self.min_length = min_length
25
+ self.pattern = pattern
26
+
27
+ def __call__(self, field: StringField, **kwargs: Any) -> Markup:
28
+ kwargs.setdefault("id", field.id)
29
+ kwargs.setdefault("type", self.input_type)
30
+ if self.min_length:
31
+ kwargs.setdefault("minlength", self.min_length)
32
+ if self.max_length:
33
+ kwargs.setdefault("maxlength", self.max_length)
34
+ if self.pattern:
35
+ kwargs.setdefault("pattern", self.pattern)
36
+ if "value" not in kwargs:
37
+ kwargs["value"] = field._value()
38
+ flags = getattr(field, "flags", {})
39
+ for k in dir(flags):
40
+ if k in self.validation_attrs and k not in kwargs:
41
+ kwargs[k] = getattr(flags, k)
42
+ return Markup("<input %s>" % self.html_params(name=field.name, **kwargs))
@@ -0,0 +1,75 @@
1
+ from typing import Type, Any, Dict, Optional, Tuple
2
+
3
+ from ul_db_utils.modules.db import db
4
+ from sqlalchemy.dialects import postgresql
5
+ from sqlalchemy.dialects.postgresql import ARRAY
6
+ from wtforms_alchemy import ModelForm, ClassMap, FormGenerator # type: ignore
7
+ from wtforms.fields import SelectField # type: ignore
8
+
9
+ from ul_db_utils.model.base_model import BaseModel
10
+
11
+ from ul_api_utils.resources.web_forms.custom_fields.custom_checkbox_select import MultiCheckboxField
12
+
13
+
14
+ def form_factory(model_obj: Type[BaseModel], edition: bool = False, *, extra_fields: Optional[Dict[str, Any]] = None) -> Type[ModelForm]:
15
+ """
16
+ Returns generated model form.
17
+
18
+ Parameters:
19
+ model_obj (type_of(BaseModel)): model object which will use for form generation
20
+ edition (bool): flag to indicate creation or edition form will be generated
21
+ extra_fields (optional(dict(str, any))): additional fields for generated form
22
+
23
+ Returns:
24
+ Form (type_of(ModelForm)): web model form
25
+ """
26
+
27
+ class ExtraFieldsFormGenerator(FormGenerator):
28
+ def create_fields(self, form: Any, properties: Dict[str, Any]) -> None:
29
+ """
30
+ Creates fields for given form based on given model attributes.
31
+
32
+ :param form: form to attach the generated fields into
33
+ :param properties: model attributes to generate the form fields from
34
+ """
35
+ super(ExtraFieldsFormGenerator, self).create_fields(form, properties)
36
+
37
+ if extra_fields:
38
+ for field_name, field in extra_fields.items():
39
+ setattr(form, field_name, field)
40
+
41
+ class Form(ModelForm):
42
+ """
43
+ A class for representing a web form.
44
+
45
+ ...
46
+
47
+ Attributes
48
+ ----------
49
+ property_columns : dict(str, any)
50
+ Model property columns such as ID, USER_CREATED etc.
51
+
52
+ Methods
53
+ -------
54
+
55
+ """
56
+
57
+ property_columns: Dict[str, Any] = {}
58
+
59
+ def __init__(self, *args: Tuple[Any], **kwargs: Dict[str, Any]) -> None:
60
+ super(Form, self).__init__(*args, **kwargs)
61
+ self.property_columns = model_obj.get_property_columns(self._obj) if self._obj else {}
62
+
63
+ class Meta:
64
+ model = model_obj
65
+ type_map = ClassMap({postgresql.UUID: SelectField, ARRAY: MultiCheckboxField})
66
+ only = model_obj.get_edit_columns() if edition else model_obj.get_create_columns()
67
+ form_generator = ExtraFieldsFormGenerator
68
+
69
+ @classmethod
70
+ def get_session(cls) -> Any:
71
+ return db.session
72
+
73
+ Form.__name__ = f"{model_obj.__name__}Form"
74
+
75
+ return Form
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ul-api-utils
3
- Version: 7.7.9
3
+ Version: 7.8.0
4
4
  Summary: Python api utils
5
5
  Author: Unic-lab
6
6
  Author-email:
@@ -86,6 +86,13 @@ ul_api_utils/resources/health_check/const.py
86
86
  ul_api_utils/resources/health_check/health_check.py
87
87
  ul_api_utils/resources/health_check/health_check_template.py
88
88
  ul_api_utils/resources/health_check/resource.py
89
+ ul_api_utils/resources/web_forms/__init__.py
90
+ ul_api_utils/resources/web_forms/uni_form.py
91
+ ul_api_utils/resources/web_forms/custom_fields/__init__.py
92
+ ul_api_utils/resources/web_forms/custom_fields/custom_checkbox_select.py
93
+ ul_api_utils/resources/web_forms/custom_widgets/__init__.py
94
+ ul_api_utils/resources/web_forms/custom_widgets/custom_select_widget.py
95
+ ul_api_utils/resources/web_forms/custom_widgets/custom_text_input_widget.py
89
96
  ul_api_utils/utils/__init__.py
90
97
  ul_api_utils/utils/api_encoding.py
91
98
  ul_api_utils/utils/api_format.py
@@ -1,134 +0,0 @@
1
- import argparse
2
- import ast
3
- import importlib
4
- import inspect
5
- import os
6
- import logging
7
- from datetime import datetime
8
- from typing import Dict, Callable, Any
9
-
10
- from flask import url_for, Flask
11
- from ul_py_tool.commands.cmd import Cmd
12
-
13
- from ul_api_utils import conf
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- class CmdGenApiFunctionDocumentation(Cmd):
19
- api_dir: str
20
- db_dir: str
21
- include_api_utils_doc: bool
22
- include_db_utils_doc: bool
23
-
24
- @staticmethod
25
- def add_parser_args(parser: argparse.ArgumentParser) -> None:
26
- parser.add_argument('--api-dir', dest='api_dir', type=str, required=True)
27
- parser.add_argument('--db-dir', dest='db_dir', type=str, required=True)
28
- parser.add_argument('--include-utils-api', dest='include_api_utils_doc', type=bool, required=False, default=False)
29
- parser.add_argument('--include-utils-db', dest='include_db_utils_doc', type=bool, required=False, default=False)
30
-
31
- @property
32
- def api_module(self) -> str:
33
- return self.api_dir.replace('/', '.')
34
-
35
- @property
36
- def main_module(self) -> str:
37
- return self.api_dir.replace('/', '.') + ".main"
38
-
39
- @staticmethod
40
- def get_db_helpers_names(directory: str) -> Dict[str, Callable[..., Any]]:
41
- db_helpers = {}
42
- for root, _dirs, files in os.walk(directory):
43
- for file in files:
44
- if file.endswith('.py'):
45
- module_name = file[:-3] # Strip the .py to get the module name.
46
- module_path = os.path.join(root, file)
47
- spec = importlib.util.spec_from_file_location(module_name, module_path)
48
- module = importlib.util.module_from_spec(spec) # type: ignore
49
- spec.loader.exec_module(module) # type: ignore
50
-
51
- functions = inspect.getmembers(module, inspect.isfunction)
52
- for name, func in functions:
53
- db_helpers[name] = func
54
- return db_helpers
55
-
56
- @staticmethod
57
- def get_flask_app(api_sdk_module: str) -> Flask:
58
- module = importlib.import_module(api_sdk_module)
59
- return module.flask_app
60
-
61
- def run(self) -> None:
62
- conf.APPLICATION_DIR = os.path.join(os.getcwd(), self.api_dir)
63
- current_app = self.get_flask_app(self.main_module)
64
- utils = self.get_db_helpers_names(f'{self.api_dir}/utils')
65
- db_utils = self.get_db_helpers_names(f'{self.db_dir}/models_manager')
66
- db_helpers = self.get_db_helpers_names(f"{self.db_dir}/utils")
67
- with current_app.app_context():
68
- current_app.config['SERVER_NAME'] = '{base_url}'
69
- with current_app.app_context(), open(f'.tmp/api_doc_{datetime.now().isoformat()}.md', 'w') as file:
70
- for api_num, rule in enumerate(current_app.url_map.iter_rules()):
71
- options = {}
72
- for arg in rule.arguments:
73
- options[arg] = "[{0}]".format(arg)
74
- methods = ','.join([method for method in rule.methods if method not in ('HEAD', 'OPTIONS')]) # type: ignore
75
- url = url_for(rule.endpoint, **options).replace('%5B', '[').replace('%5D', ']')
76
- func_obj = current_app.view_functions[rule.endpoint]
77
- if not func_obj.__module__.startswith(self.api_module):
78
- continue
79
- func_name = func_obj.__name__
80
- source = inspect.getsource(func_obj)
81
- tree = ast.parse(source)
82
- calls = []
83
- for node in ast.walk(tree):
84
- if isinstance(node, ast.Call):
85
- if isinstance(node.func, ast.Name):
86
- calls.append(node.func.id)
87
- elif isinstance(node.func, ast.Attribute):
88
- calls.append(node.func.attr)
89
- else:
90
- pass
91
-
92
- docstring = inspect.getdoc(func_obj)
93
- api_docstring = 'None' if docstring is None else docstring
94
-
95
- file.write(f"## {api_num} Путь апи {url}\n\n")
96
- file.write(f"#### Имя функции апи: {func_name}\n")
97
- file.write(f"### Апи методы: {methods}\n\n")
98
- file.write("**Описание апи метода:** \n\n")
99
- file.write(f"```python\n{api_docstring}\n```\n")
100
- helper_call = 1
101
- for call in calls:
102
- if call not in ('transaction_commit', 'and_', 'or_', 'foreign', 'query_soft_delete', 'ensure_db_object_exists', 'db_search'):
103
- if call in db_helpers:
104
- helper_func_obj = db_helpers[call]
105
- helper_docstring = inspect.getdoc(helper_func_obj)
106
- helper_docstring = 'None' if helper_docstring is None else helper_docstring
107
-
108
- file.write(f"### {api_num}.{helper_call} Вызвана функция работы с БД : {call}\n")
109
- file.write(f"**Описание функции {call}:**\n\n")
110
- file.write(f"```python\n{helper_docstring}\n```\n")
111
- helper_call += 1
112
- elif call in utils:
113
- if self.include_api_utils_doc:
114
- util_func_obj = utils[call]
115
- util_docstring = inspect.getdoc(util_func_obj)
116
- util_docstring = 'None' if util_docstring is None else util_docstring
117
- if 'db_tables_used' in util_docstring or 'db_table_used' in util_docstring:
118
- file.write(f"### {api_num}.{helper_call} Вызвана функция работы с БД : {call}\n")
119
- file.write(f"**Описание функции {call}:**\n\n")
120
- file.write(f"```python\n{util_docstring}\n```\n")
121
- helper_call += 1
122
- elif call in db_utils:
123
- if self.include_db_utils_doc:
124
- util_func_obj = db_utils[call]
125
- db_util_docstring = inspect.getdoc(util_func_obj)
126
- db_util_docstring = 'None' if db_util_docstring is None else db_util_docstring
127
- if 'db_tables_used' in db_util_docstring or 'db_table_used' in db_util_docstring:
128
- file.write(f"### {api_num}.{helper_call} Вызвана функция работы с БД : {call}\n")
129
- file.write(f"**Описание функции {call}:**\n\n")
130
- file.write(f"```python\n{db_util_docstring}\n```\n")
131
- helper_call += 1
132
- file.write('-' * 20)
133
- file.write('\n\n')
134
- file.write('\n\n')
File without changes
File without changes
File without changes