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,40 @@
1
+ from enum import Enum
2
+ from ul_api_utils.utils.flask_swagger_generator.exceptions import SwaggerGeneratorError
3
+
4
+
5
+ class SwaggerVersion(Enum):
6
+ """
7
+ Class SwaggerVersion: Enum for types of swagger version
8
+ """
9
+
10
+ VERSION_THREE = 'VERSION_THREE'
11
+ VERSION_TWO = 'VERSION_TWO'
12
+
13
+ def __repr__(self) -> str:
14
+ return f'{type(self).__name__}.{self.name}'
15
+
16
+ @staticmethod
17
+ def from_string(value: str) -> 'SwaggerVersion':
18
+ if isinstance(value, str):
19
+ if value.lower() in ['three', 'version_three']:
20
+ return SwaggerVersion.VERSION_THREE
21
+ elif value.lower() in ['two', 'version_two']:
22
+ return SwaggerVersion.VERSION_TWO
23
+ else:
24
+ raise SwaggerGeneratorError('Could not convert value {} to a swagger version'.format(value))
25
+ else:
26
+ raise SwaggerGeneratorError("Could not convert non string value to a swagger version")
27
+
28
+ def equals(self, other): # type: ignore
29
+
30
+ if isinstance(other, Enum):
31
+ return self.value == other.value
32
+ else:
33
+
34
+ try:
35
+ data_base_type = SwaggerVersion.from_string(other)
36
+ return data_base_type == self
37
+ except SwaggerGeneratorError:
38
+ pass
39
+
40
+ return other == self.value
@@ -0,0 +1,77 @@
1
+ from enum import Enum
2
+ from typing import Optional
3
+
4
+ from ul_api_utils.utils.flask_swagger_generator.exceptions import SwaggerGeneratorError
5
+
6
+
7
+ class InputType(Enum):
8
+ """
9
+ Class SwaggerVersion: Enum for types of swagger version
10
+ """
11
+
12
+ INTEGER = 'integer'
13
+ NUMBER = 'number'
14
+ BOOLEAN = 'boolean'
15
+ STRING = 'string'
16
+ ARRAY = 'array'
17
+ OBJECT = 'object'
18
+ NESTED = 'nested'
19
+ DATE_TIME = 'datetime'
20
+ UUID = 'uuid'
21
+
22
+ def __repr__(self) -> str:
23
+ return f'{type(self).__name__}.{self.name}'
24
+
25
+ @staticmethod
26
+ def from_string(value: str) -> 'InputType':
27
+ type_map = {
28
+ "integer": InputType.INTEGER, "int": InputType.INTEGER,
29
+ "number": InputType.NUMBER, "num": InputType.NUMBER,
30
+ "boolean": InputType.BOOLEAN, "bool": InputType.BOOLEAN,
31
+ "string": InputType.STRING, "str": InputType.STRING,
32
+ "array": InputType.ARRAY, "object": InputType.OBJECT,
33
+ "nested": InputType.NESTED, "datetime": InputType.STRING,
34
+ "uuid": InputType.UUID,
35
+ }
36
+
37
+ if isinstance(value, str):
38
+ route_with_arguments_parenthesis = '('
39
+ if route_with_arguments_parenthesis in value:
40
+ value = value.lower().split(route_with_arguments_parenthesis)[0]
41
+ try:
42
+ return type_map[value.lower()]
43
+ except KeyError:
44
+ raise SwaggerGeneratorError(f'Could not convert {value=} to a input type')
45
+ else:
46
+ raise SwaggerGeneratorError("Could not convert non string value to a parameter type")
47
+
48
+ def equals(self, other): # type: ignore
49
+
50
+ if isinstance(other, Enum):
51
+ return self.value == other.value
52
+ else:
53
+
54
+ try:
55
+ data_base_type = InputType.from_string(other)
56
+ return data_base_type == self
57
+ except SwaggerGeneratorError:
58
+ pass
59
+
60
+ return other == self.value
61
+
62
+ def get_flask_input_type_value(self) -> Optional[str]:
63
+ if self.value.lower() == 'integer':
64
+ return 'int'
65
+ elif self.value.lower() in 'number':
66
+ return 'num'
67
+ elif self.value.lower() in 'boolean':
68
+ return 'bool'
69
+ elif self.value.lower() in 'string':
70
+ return 'string'
71
+ elif self.value.lower() == 'array':
72
+ return 'array'
73
+ elif self.value.lower() == 'object':
74
+ return 'object'
75
+ elif self.value.lower() == 'uuid':
76
+ return 'uuid'
77
+ return None
@@ -0,0 +1,51 @@
1
+ from enum import Enum
2
+ from ul_api_utils.utils.flask_swagger_generator.exceptions import SwaggerGeneratorError
3
+
4
+
5
+ class ParameterType(Enum):
6
+ """
7
+ Class SwaggerVersion: Enum for types of swagger version
8
+ """
9
+
10
+ PATH = 'PATH'
11
+ QUERY = 'QUERY'
12
+ HEADER = 'HEADER'
13
+ FORMDATA = 'FORMDATA'
14
+ BODY = 'BODY'
15
+
16
+ def __repr__(self) -> str:
17
+ return f'{type(self).__name__}.{self.name}'
18
+
19
+ @staticmethod
20
+ def from_string(value: str) -> 'ParameterType':
21
+
22
+ if isinstance(value, str):
23
+
24
+ if value.lower() == 'path':
25
+ return ParameterType.PATH
26
+ elif value.lower() == 'query':
27
+ return ParameterType.QUERY
28
+ elif value.lower() == 'header':
29
+ return ParameterType.HEADER
30
+ elif value.lower() == 'formdata':
31
+ return ParameterType.FORMDATA
32
+ elif value.lower() == 'body':
33
+ return ParameterType.BODY
34
+ else:
35
+ raise SwaggerGeneratorError('Could not convert value {} to a parameter type'.format(value))
36
+ else:
37
+ raise SwaggerGeneratorError("Could not convert non string value to a parameter type")
38
+
39
+ def equals(self, other): # type: ignore
40
+
41
+ if isinstance(other, Enum):
42
+ return self.value == other.value
43
+ else:
44
+
45
+ try:
46
+ data_base_type = ParameterType.from_string(other)
47
+ return data_base_type == self
48
+ except SwaggerGeneratorError:
49
+ pass
50
+
51
+ return other == self.value
@@ -0,0 +1,18 @@
1
+ from typing import Union, List, Dict
2
+
3
+
4
+ def replace_value_in_dict(item: Union[List, Dict], original_schema): # type: ignore
5
+ if isinstance(item, list):
6
+ return [replace_value_in_dict(i, original_schema) for i in item]
7
+ elif isinstance(item, dict):
8
+ if '$ref' in list(item.keys()):
9
+ definitions = item['$ref'][2:].split('/')
10
+ res = original_schema.copy()
11
+ for definition in definitions:
12
+ if 'enum' in res[definition]:
13
+ res[definition]['type'] = 'string'
14
+ res = res[definition]
15
+ return res
16
+ else:
17
+ return {key: replace_value_in_dict(i, original_schema) for key, i in item.items()}
18
+ return item # type: ignore
@@ -0,0 +1,52 @@
1
+ from enum import Enum
2
+ from ul_api_utils.utils.flask_swagger_generator.exceptions import SwaggerGeneratorError
3
+
4
+
5
+ class RequestType(Enum):
6
+ """
7
+ Class RequestType: Enum for types of requests
8
+ """
9
+
10
+ POST = 'post'
11
+ GET = 'get'
12
+ DELETE = 'delete'
13
+ PUT = 'put'
14
+ PATCH = 'patch'
15
+
16
+ def __repr__(self) -> str:
17
+ return f'{type(self).__name__}.{self.name}'
18
+
19
+ @staticmethod
20
+ def from_string(value: str) -> 'RequestType':
21
+
22
+ if isinstance(value, str):
23
+
24
+ if value.lower() == 'post':
25
+ return RequestType.POST
26
+ elif value.lower() == 'get':
27
+ return RequestType.GET
28
+ elif value.lower() == 'delete':
29
+ return RequestType.DELETE
30
+ elif value.lower() == 'put':
31
+ return RequestType.PUT
32
+ elif value.lower() == 'patch':
33
+ return RequestType.PATCH
34
+ else:
35
+ raise SwaggerGeneratorError('Could not convert value {} to a request type'.format(value))
36
+
37
+ else:
38
+ raise SwaggerGeneratorError("Could not convert non string value to a request type")
39
+
40
+ def equals(self, other): # type: ignore
41
+
42
+ if isinstance(other, Enum):
43
+ return self.value == other.value
44
+ else:
45
+
46
+ try:
47
+ data_base_type = RequestType.from_string(other)
48
+ return data_base_type == self
49
+ except SwaggerGeneratorError:
50
+ pass
51
+
52
+ return other == self.value
@@ -0,0 +1,15 @@
1
+ from enum import Enum
2
+
3
+
4
+ class SchemaType(Enum):
5
+ """
6
+ Class SchemaType: Enum for types of schema types
7
+ """
8
+
9
+ LIST = 'LIST'
10
+ MARSH_MALLOW = 'MARSH_MALLOW'
11
+ DICT = 'DICT'
12
+ STRING = 'STRING'
13
+
14
+ def __repr__(self) -> str:
15
+ return f'{type(self).__name__}.{self.name}'
@@ -0,0 +1,39 @@
1
+ from enum import Enum
2
+ from ul_api_utils.utils.flask_swagger_generator.exceptions import SwaggerGeneratorError
3
+
4
+
5
+ class SecurityType(Enum):
6
+ """
7
+ Class SecurityType: Enum for types of swagger security types
8
+ """
9
+
10
+ BEARER_AUTH = 'BEARER_AUTH'
11
+
12
+ def __repr__(self) -> str:
13
+ return f'{type(self).__name__}.{self.name}'
14
+
15
+ @staticmethod
16
+ def from_string(value: str) -> 'SecurityType':
17
+
18
+ if isinstance(value, str):
19
+
20
+ if value.lower() == 'bearer_auth':
21
+ return SecurityType.BEARER_AUTH
22
+ else:
23
+ raise SwaggerGeneratorError('Could not convert value {} to a security type'.format(value))
24
+ else:
25
+ raise SwaggerGeneratorError("Could not convert non string value to a security type")
26
+
27
+ def equals(self, other): # type: ignore
28
+
29
+ if isinstance(other, Enum):
30
+ return self.value == other.value
31
+ else:
32
+
33
+ try:
34
+ data_base_type = SecurityType.from_string(other)
35
+ return data_base_type == self
36
+ except SwaggerGeneratorError:
37
+ pass
38
+
39
+ return other == self.value
@@ -0,0 +1,16 @@
1
+ import sys
2
+
3
+
4
+ def has_already_imported(module_name: str) -> bool:
5
+ return module_name in sys.modules
6
+
7
+
8
+ _has_already_imported_db = False
9
+
10
+
11
+ def has_already_imported_db() -> bool:
12
+ global _has_already_imported_db
13
+ if _has_already_imported_db:
14
+ return True
15
+ _has_already_imported_db = has_already_imported('flask_sqlalchemy')
16
+ return _has_already_imported_db
@@ -0,0 +1,16 @@
1
+ from typing import Any
2
+
3
+
4
+ def isinstance_namedtuple(obj: Any) -> bool:
5
+ namedtuple_unique_attributes = ('_asdict', '_fields')
6
+ is_tuple = isinstance(obj, tuple)
7
+ has_namedtuple_attributes = hasattr(obj, namedtuple_unique_attributes[0]) and hasattr(obj, namedtuple_unique_attributes[1])
8
+ return has_namedtuple_attributes and is_tuple
9
+
10
+
11
+ def is_iterable(obj: Any) -> bool:
12
+ try:
13
+ iter(obj)
14
+ except TypeError:
15
+ return False
16
+ return True
File without changes
@@ -0,0 +1,19 @@
1
+ from typing import Any, List, Dict
2
+
3
+ from flask import url_for
4
+
5
+
6
+ # template url_for
7
+ def t_url_for(*args: Any, **kwargs: Any) -> str:
8
+ res_args: List[Any] = []
9
+ res_kwargs: Dict[str, Any] = {}
10
+
11
+ for arg in args:
12
+ if isinstance(arg, dict):
13
+ res_kwargs.update(arg)
14
+ elif isinstance(arg, (list, tuple)):
15
+ res_args = [*res_args, *arg]
16
+ else:
17
+ res_args.append(arg)
18
+ res_kwargs.update(kwargs)
19
+ return url_for(*res_args, **res_kwargs)
@@ -0,0 +1,11 @@
1
+ import json
2
+ from typing import Any
3
+
4
+
5
+ def to_pretty_json(value: Any) -> str:
6
+ if isinstance(value, str):
7
+ try:
8
+ value = json.loads(value)
9
+ except Exception: # noqa: B902
10
+ return value
11
+ return json.dumps(value, sort_keys=True, indent=4, separators=(',', ': '))
@@ -0,0 +1,126 @@
1
+ import dataclasses
2
+ import decimal
3
+ import json
4
+ from base64 import b64encode
5
+ from datetime import date, datetime, time
6
+ from enum import Enum
7
+ from json import JSONEncoder
8
+ from typing import Dict, Any, Union, List, Optional, TYPE_CHECKING
9
+ from uuid import UUID
10
+
11
+ from flask.json.provider import DefaultJSONProvider
12
+ from frozendict import frozendict
13
+ from pydantic import BaseModel
14
+
15
+ from flask_sqlalchemy.query import Query
16
+ from flask_sqlalchemy.model import Model, DefaultMeta
17
+
18
+ from sqlalchemy.orm import Query, registry
19
+
20
+ from ul_db_utils.modules.postgres_modules.db import DbModel
21
+ from ul_db_utils.model.base_model import BaseModel as DbBaseModel
22
+ from ul_api_utils.utils.imports import has_already_imported_db
23
+
24
+ if TYPE_CHECKING:
25
+ from ul_api_utils.api_resource.api_response_db import TDictable
26
+
27
+
28
+ def to_dict(obj: 'TDictable') -> Optional[Dict[str, Any]]:
29
+ if isinstance(obj, dict):
30
+ return obj
31
+ if isinstance(obj, tuple) and hasattr(obj, '_asdict'): # NamedTuple
32
+ return obj._asdict()
33
+ if dataclasses.is_dataclass(obj):
34
+ return dataclasses.asdict(obj)
35
+ if isinstance(obj, BaseModel):
36
+ return obj.model_dump()
37
+ if has_already_imported_db():
38
+ if isinstance(obj, DbBaseModel) or isinstance(obj, DbModel):
39
+ return obj.to_dict()
40
+ if isinstance(obj, Model):
41
+ fields = {}
42
+ for field in (x for x in dir(obj) if not x.startswith('_') and x != 'metadata'):
43
+ val = obj.__getattribute__(field)
44
+ # is this field method defination, or an SQLalchemy object
45
+ if not hasattr(val, "__call__") and not isinstance(val, Query): # noqa: B004
46
+ if isinstance(val, datetime):
47
+ val = str(val.isoformat())
48
+ if isinstance(val, UUID):
49
+ val = str(val)
50
+ if isinstance(val, bytes):
51
+ val = b64encode(val).decode()
52
+ if isinstance(val, registry):
53
+ continue
54
+ fields[field] = val
55
+ return fields
56
+ return None
57
+
58
+
59
+ class CustomJSONEncoder(JSONEncoder):
60
+ def default(self, obj: object) -> Union[str, Dict[str, Any], List[Any], None]:
61
+ if isinstance(obj, (decimal.Decimal)):
62
+ return str(obj)
63
+ if isinstance(obj, BaseModel):
64
+ return obj.model_dump()
65
+ if isinstance(obj, datetime):
66
+ return str(obj.isoformat())
67
+ if isinstance(obj, date):
68
+ return str(obj.isoformat())
69
+ if isinstance(obj, time):
70
+ return str(obj.isoformat())
71
+ if isinstance(obj, UUID):
72
+ return str(obj)
73
+ if isinstance(obj, Enum):
74
+ return str(obj.value)
75
+ if isinstance(obj, set):
76
+ return list(obj)
77
+ if isinstance(obj, frozendict):
78
+ return dict(obj)
79
+ if dataclasses.is_dataclass(obj):
80
+ return dataclasses.asdict(obj) # type: ignore
81
+ if hasattr(obj, "__html__"): # it needs for Flask ?
82
+ return str(obj.__html__())
83
+
84
+ if has_already_imported_db():
85
+ if isinstance(obj, DbBaseModel):
86
+ return obj.to_dict()
87
+ if isinstance(obj, Query) or isinstance(obj, DefaultMeta):
88
+ return None
89
+ if isinstance(obj, Model):
90
+ fields = {}
91
+ for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
92
+ val = obj.__getattribute__(field)
93
+ # is this field method defination, or an SQLalchemy object
94
+ if not hasattr(val, "__call__") and not isinstance(val, Query): # noqa: B004
95
+ if isinstance(val, datetime):
96
+ val = str(val.isoformat())
97
+ if isinstance(val, UUID):
98
+ val = str(val)
99
+ if isinstance(val, bytes):
100
+ val = b64encode(val).decode()
101
+ if isinstance(val, registry):
102
+ continue
103
+ fields[field] = val
104
+ return fields
105
+ return super().default(obj)
106
+
107
+
108
+ class SocketIOJsonWrapper:
109
+ @staticmethod
110
+ def dumps(*args: Any, **kwargs: Any) -> str:
111
+ if 'cls' not in kwargs:
112
+ kwargs['cls'] = CustomJSONEncoder
113
+ return json.dumps(*args, **kwargs)
114
+
115
+ @staticmethod
116
+ def loads(*args: Any, **kwargs: Any) -> Any:
117
+ return json.loads(*args, **kwargs)
118
+
119
+
120
+ class CustomJSONProvider(DefaultJSONProvider):
121
+ def __init__(self, app):
122
+ super().__init__(app)
123
+ self.encoder = CustomJSONEncoder()
124
+
125
+ def default(self, obj) -> Union[str, Dict[str, Any], List[Any], None]:
126
+ return self.encoder.default(obj)
@@ -0,0 +1,15 @@
1
+ import importlib
2
+ import os
3
+ from typing import List, Optional, Tuple
4
+
5
+ from ul_py_tool.utils.arg_files_glob import arg_files_glob, arg_file_glob_compile_files
6
+
7
+
8
+ def load_modules_by_template(include: List[str], exclude: Optional[List[str]] = None) -> Tuple[List[str], List[str]]:
9
+ find = arg_files_glob(ignore_absent=True)
10
+ files, ignored = arg_file_glob_compile_files(include=[find(tpl) for tpl in include], exclude=[find(tpl) for tpl in (exclude or [])])
11
+ for file in files:
12
+ file_rel = os.path.relpath(file, os.getcwd())
13
+ mdl = file_rel[:-len('.py')].replace('\\', '/').strip('/').replace('/', '.')
14
+ importlib.import_module(mdl)
15
+ return files, ignored
File without changes
File without changes
@@ -0,0 +1,8 @@
1
+ class CompositeKeyError(LookupError):
2
+ """Raised in memory db repository when composite key is invalid."""
3
+ ...
4
+
5
+
6
+ class UnsupportedParsingType(TypeError):
7
+ """Raised in memory db repository when repo can't parse memory db object as provided type."""
8
+ ...
@@ -0,0 +1,102 @@
1
+ import decimal
2
+ from datetime import timedelta
3
+
4
+ import redis
5
+ import ormsgpack
6
+ import collections
7
+
8
+ from pydantic import ValidationError, BaseModel, TypeAdapter
9
+ from typing import Any, Type, overload, Iterator, KeysView, cast
10
+
11
+ from ul_api_utils.utils.instance_checks import isinstance_namedtuple
12
+ from ul_api_utils.utils.memory_db.errors import CompositeKeyError, UnsupportedParsingType
13
+
14
+ CompositeKeyT = tuple[str, Type[BaseModel]]
15
+ AnyKeyT = str | CompositeKeyT
16
+ AnyT = Any
17
+ RedisClientT = redis.StrictRedis | redis.Redis # type: ignore
18
+ ExpiryT = int | timedelta | None
19
+
20
+
21
+ class BaseMemoryDbRepository(collections.abc.MutableMapping[str, AnyT]):
22
+ def __init__(self, redis_client: RedisClientT) -> None:
23
+ self._db = redis_client
24
+ self._composite_key_max_length = 2
25
+
26
+ @property
27
+ def db(self) -> RedisClientT:
28
+ return self._db
29
+
30
+ def get(self, __key: str, *, parse_as_type: Type[BaseModel] | None = None, default: Any | None = None) -> AnyT | None: # type: ignore
31
+ try:
32
+ if parse_as_type is None:
33
+ return self[__key]
34
+ return self[__key, parse_as_type]
35
+ except KeyError:
36
+ return default
37
+
38
+ def set(self, __key: str, value: AnyT, *, expires: ExpiryT = None) -> None:
39
+ packed_value = ormsgpack.packb(value, option=ormsgpack.OPT_SERIALIZE_PYDANTIC, default=self._default_serializer)
40
+ self._db.set(__key, packed_value, ex=expires)
41
+
42
+ @overload
43
+ def __getitem__(self, key: str) -> AnyT:
44
+ ...
45
+
46
+ @overload
47
+ def __getitem__(self, key: CompositeKeyT) -> AnyT:
48
+ ...
49
+
50
+ def __getitem__(self, key: AnyKeyT) -> AnyT:
51
+ key_complex = isinstance(key, tuple)
52
+
53
+ if not key_complex:
54
+ single_key = cast(str, key)
55
+ return ormsgpack.unpackb(self._db[single_key])
56
+
57
+ composite_key = cast(CompositeKeyT, key)
58
+ if len(composite_key) > self._composite_key_max_length:
59
+ raise CompositeKeyError(f"Can't retrieve an item with {key=}. Composite key should have only two arguments.")
60
+
61
+ composite_key_name, _parse_as_type = composite_key
62
+ parsing_type_supported = issubclass(_parse_as_type, BaseModel) and _parse_as_type is not BaseModel
63
+ if not parsing_type_supported:
64
+ raise UnsupportedParsingType(f"Unsupported parsing type {_parse_as_type}.")
65
+ value = ormsgpack.unpackb(self._db[composite_key_name])
66
+ try:
67
+ if isinstance(value, list):
68
+ return TypeAdapter(list[_parse_as_type]).validate_python(value) # type: ignore
69
+ return TypeAdapter(_parse_as_type).validate_python(value)
70
+ except ValidationError:
71
+ raise UnsupportedParsingType(f"Could not parse the value of key '{composite_key_name}' with type {_parse_as_type}") from None
72
+
73
+ def __setitem__(self, key: str, value: AnyT) -> None:
74
+ self._db[key] = ormsgpack.packb(value, option=ormsgpack.OPT_SERIALIZE_PYDANTIC, default=self._default_serializer)
75
+
76
+ def __delitem__(self, key: str) -> None:
77
+ del self._db[key]
78
+
79
+ def __iter__(self) -> Iterator[str]:
80
+ return iter(self.keys())
81
+
82
+ def __len__(self) -> int:
83
+ return len(self._db.keys())
84
+
85
+ def keys(self) -> KeysView[str]:
86
+ available_keys = [key.decode() for key in self._db.keys()]
87
+ return cast(KeysView[str], available_keys)
88
+
89
+ def clear(self) -> None:
90
+ self._db.flushdb()
91
+
92
+ @staticmethod
93
+ def _default_serializer(obj: Any) -> Any:
94
+ if isinstance_namedtuple(obj):
95
+ return obj._asdict()
96
+ if isinstance(obj, decimal.Decimal):
97
+ return str(obj)
98
+ if isinstance(obj, set):
99
+ return list(obj)
100
+ if isinstance(obj, frozenset):
101
+ return list(obj)
102
+ raise TypeError
@@ -0,0 +1,14 @@
1
+ from typing import Callable, Type, TYPE_CHECKING
2
+
3
+ from ul_api_utils.modules.api_sdk_jwt import ApiSdkJwt
4
+
5
+
6
+ if TYPE_CHECKING:
7
+ from ul_db_utils.model.base_model import BaseModel
8
+
9
+
10
+ def api_auth_token_check(api_auth_model: Type['BaseModel']) -> Callable[[ApiSdkJwt], bool]:
11
+ def token_exist_check(token: ApiSdkJwt) -> bool:
12
+ token_query = api_auth_model.query.filter_by(id=token.user_id, is_alive=True)
13
+ return token_query.first() is not None
14
+ return token_exist_check
@@ -0,0 +1,16 @@
1
+ from typing import Callable
2
+
3
+ from ul_api_utils.errors import Client4XXInternalApiError, NotFinishedRequestInternalApiError
4
+ from ul_api_utils.internal_api.internal_api import InternalApi
5
+ from ul_api_utils.modules.api_sdk_jwt import ApiSdkJwt
6
+
7
+
8
+ def api_auth_token_check_through_request(auth_internal_api: InternalApi) -> Callable[[ApiSdkJwt], bool]:
9
+ def token_exist_check(token: ApiSdkJwt) -> bool:
10
+ try:
11
+ auth_internal_api.request_get(f"tokens/{token.user_id}").check()
12
+ except (Client4XXInternalApiError, NotFinishedRequestInternalApiError):
13
+ return False
14
+ else:
15
+ return True
16
+ return token_exist_check