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,61 @@
1
+ import json
2
+ from enum import Enum
3
+ from typing import Tuple, Any, Optional
4
+
5
+ import msgpack
6
+
7
+ from ul_api_utils.const import MIME__JSON, MIME__MSGPCK
8
+ from ul_api_utils.utils.json_encoder import CustomJSONEncoder
9
+
10
+
11
+ class ApiFormat(Enum):
12
+ JSON = 'JSON'
13
+ MESSAGE_PACK = 'MESSAGE_PACK'
14
+
15
+ def __repr__(self) -> str:
16
+ return f'{type(self).__name__}.{self.name}'
17
+
18
+ @staticmethod
19
+ def accept_mimes(force: Optional['ApiFormat'] = None) -> Tuple[str, ...]:
20
+ if force is ApiFormat.MESSAGE_PACK:
21
+ return MIME__MSGPCK, # noqa: C818
22
+ if force is ApiFormat.JSON:
23
+ return MIME__JSON, # noqa: C818
24
+ return MIME__MSGPCK, MIME__JSON
25
+
26
+ @property
27
+ def mime(self) -> str:
28
+ if self is ApiFormat.MESSAGE_PACK:
29
+ return MIME__MSGPCK
30
+ if self is ApiFormat.JSON:
31
+ return MIME__JSON
32
+ raise NotImplementedError
33
+
34
+ @staticmethod
35
+ def from_mime(mime: str) -> Optional['ApiFormat']:
36
+ if mime == MIME__MSGPCK:
37
+ return ApiFormat.MESSAGE_PACK
38
+ if mime == MIME__JSON or (mime.startswith("application/") and mime.endswith("+json")):
39
+ return ApiFormat.JSON
40
+ return None
41
+
42
+ def serialize_bytes(self, data: Any) -> bytes:
43
+ if self is ApiFormat.MESSAGE_PACK:
44
+ return msgpack.dumps(data, default=CustomJSONEncoder().default)
45
+ if self is ApiFormat.JSON:
46
+ return json.dumps(data, cls=CustomJSONEncoder).encode('utf-8')
47
+ raise NotImplementedError
48
+
49
+ def parse_bytes(self, data: bytes) -> Any:
50
+ if self is ApiFormat.MESSAGE_PACK:
51
+ return msgpack.unpackb(data, use_list=True)
52
+ if self is ApiFormat.JSON:
53
+ return json.loads(data)
54
+ raise NotImplementedError
55
+
56
+ def parse_text(self, data: str) -> Any:
57
+ if self is ApiFormat.MESSAGE_PACK:
58
+ return msgpack.unpackb(data.encode('utf-8'), use_list=True)
59
+ if self is ApiFormat.JSON:
60
+ return json.loads(data)
61
+ raise NotImplementedError
@@ -0,0 +1,55 @@
1
+ from enum import Enum, unique
2
+ from typing import Union, List, Tuple, Any, Literal
3
+
4
+ from ul_api_utils.const import REQUEST_METHOD__PUT, REQUEST_METHOD__GET, REQUEST_METHOD__POST, \
5
+ REQUEST_METHOD__PATCH, REQUEST_METHOD__DELETE, REQUEST_METHOD__OPTIONS, REQUEST_METHOD__QUERY
6
+
7
+ TMethodStr = Union[Literal['GET'], Literal['POST'], Literal['PUT'], Literal['PATCH'], Literal['DELETE'], Literal['OPTIONS']]
8
+ TMethod = Union[TMethodStr, 'ApiMethod', List[TMethodStr], Tuple[TMethodStr, ...], List['ApiMethod'], Tuple['ApiMethod', ...]]
9
+ TMethodShort = Union[TMethodStr, 'ApiMethod']
10
+
11
+
12
+ @unique
13
+ class ApiMethod(Enum):
14
+ PUT = REQUEST_METHOD__PUT
15
+ GET = REQUEST_METHOD__GET
16
+ POST = REQUEST_METHOD__POST
17
+ QUERY = REQUEST_METHOD__QUERY
18
+ PATCH = REQUEST_METHOD__PATCH
19
+ DELETE = REQUEST_METHOD__DELETE
20
+ OPTIONS = REQUEST_METHOD__OPTIONS
21
+
22
+ def __repr__(self) -> str:
23
+ return f'{type(self).__name__}.{self.name}'
24
+
25
+ def __hash__(self) -> int:
26
+ return hash(self.value)
27
+
28
+ def __str__(self) -> str:
29
+ return self.value
30
+
31
+ def __eq__(self, other: Any) -> bool:
32
+ if isinstance(other, str):
33
+ try:
34
+ other = type(self)(other.upper())
35
+ except Exception: # noqa: B902
36
+ return False
37
+ return super(ApiMethod, self).__eq__(other)
38
+
39
+ @staticmethod
40
+ def compile_methods(methods: TMethod) -> Tuple[List[str], List['ApiMethod']]:
41
+ str_res_methods = []
42
+ enum_res_methods = []
43
+ for m in (list(methods) if isinstance(methods, (list, tuple)) else [methods]):
44
+ if isinstance(m, ApiMethod):
45
+ str_res_methods.append(m.value)
46
+ enum_res_methods.append(m)
47
+ else:
48
+ assert isinstance(m, str)
49
+ m = m.strip().upper() # type: ignore
50
+ enum_res_methods.append(ApiMethod(m))
51
+ str_res_methods.append(m)
52
+ return str_res_methods, enum_res_methods
53
+
54
+
55
+ NO_REQUEST_BODY_METHODS = {ApiMethod.GET, ApiMethod.OPTIONS}
@@ -0,0 +1,58 @@
1
+ from typing import Iterable, Any, Optional, Iterator
2
+
3
+ import math
4
+
5
+
6
+ class ApiPagination:
7
+ def __init__(self, page: int, per_page: int, total: int, items: Iterable[Any]) -> None:
8
+ self.query = None
9
+ self.page = page
10
+ self.per_page = per_page
11
+ self.total = total
12
+ self.items = items
13
+
14
+ @property
15
+ def pages(self) -> int:
16
+ """The total number of pages"""
17
+ if self.per_page == 0:
18
+ pages = 0
19
+ else:
20
+ pages = int(math.ceil(self.total / float(self.per_page)))
21
+ return pages
22
+
23
+ @property
24
+ def prev_num(self) -> Optional[int]:
25
+ """Number of the previous page."""
26
+ if not self.has_prev:
27
+ return None
28
+ return self.page - 1
29
+
30
+ @property
31
+ def has_prev(self) -> bool:
32
+ """True if a previous page exists"""
33
+ return self.page > 1
34
+
35
+ def prev(self, error_out: bool = False) -> None:
36
+ raise AssertionError('a query object is required for this method to work')
37
+
38
+ def next(self, error_out: bool = False) -> None:
39
+ raise AssertionError('a query object is required for this method to work')
40
+
41
+ @property
42
+ def has_next(self) -> bool:
43
+ return self.page < self.pages
44
+
45
+ @property
46
+ def next_num(self) -> Optional[int]:
47
+ if not self.has_next:
48
+ return None
49
+ return self.page + 1
50
+
51
+ def iter_pages(self, left_edge: int = 2, left_current: int = 2, right_current: int = 5, right_edge: int = 2) -> Iterator[Optional[int]]:
52
+ last = 0
53
+ for num in range(1, self.pages + 1):
54
+ if num <= left_edge or (num > self.page - left_current - 1 and num < self.page + right_current) or num > self.pages - right_edge:
55
+ if last + 1 != num:
56
+ yield None
57
+ yield num
58
+ last = num
@@ -0,0 +1,60 @@
1
+ from enum import Enum
2
+ from typing import Optional, Mapping, Union, Dict, Any
3
+
4
+
5
+ class ApiPathVersion(Enum):
6
+ V01 = 'v1'
7
+ V02 = 'v2'
8
+ V03 = 'v3'
9
+ V04 = 'v4'
10
+ V05 = 'v5'
11
+ V06 = 'v6'
12
+ V07 = 'v7'
13
+ V08 = 'v8'
14
+ V09 = 'v9'
15
+ V10 = 'v10'
16
+
17
+ NO_VERSION = "no-version"
18
+ NO_PREFIX = 'no-prefix'
19
+
20
+ def __repr__(self) -> str:
21
+ return f'{type(self).__name__}.{self.name}'
22
+
23
+ @staticmethod
24
+ def cleanup_q(q: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
25
+ if q is None:
26
+ return None
27
+ res = {}
28
+ for k, v in q.items():
29
+ if v is not None:
30
+ res[k] = v
31
+ return res
32
+
33
+ def compile_path(self, path: str, prefix: str = "", *, q: Optional[Mapping[str, Union[int, str]]] = None) -> str:
34
+ assert '?' not in path, f'restricted symbol "?" was found in url.path="{path}"'
35
+ assert '&' not in path, f'restricted symbol "&" was found in url.path="{path}"'
36
+ assert '?' not in prefix, f'restricted symbol "?" was found in url.prefix="{prefix}"'
37
+ assert '&' not in prefix, f'restricted symbol "&" was found in url.prefix="{prefix}"'
38
+
39
+ qr = ''
40
+ if q:
41
+ for k, kv in q.items():
42
+ if kv is None:
43
+ continue # type: ignore
44
+ qr += ('?' if len(qr) == 0 else '&') + f'{k}={kv}'
45
+
46
+ if self is ApiPathVersion.NO_PREFIX:
47
+ if not path:
48
+ return qr
49
+ return f'/{path.lstrip("/")}{qr}'
50
+
51
+ if self is ApiPathVersion.NO_VERSION:
52
+ if not path:
53
+ return f'{"/" if prefix else ""}{prefix.lstrip("/")}{qr}'
54
+ return f'{"/" if prefix else ""}{prefix.strip("/")}/{path.lstrip("/")}{qr}'
55
+
56
+ assert prefix, f'prefix must not be empty. {prefix} was given'
57
+
58
+ if not path:
59
+ return f'/{prefix.strip("/")}/{self.value}{qr}'
60
+ return f'/{prefix.strip("/")}/{self.value}/{path.lstrip("/")}{qr}'
@@ -0,0 +1,6 @@
1
+ from typing import NamedTuple
2
+
3
+
4
+ class ApiRequestInfo(NamedTuple):
5
+ user_agent: str
6
+ ipv4: str
@@ -0,0 +1,131 @@
1
+ from typing import Type, Dict, Any, Optional, List, Set
2
+
3
+ from pydantic import BaseModel
4
+
5
+ # CODE FROM https://github.com/godatadriven/pydantic-avro/blob/main/src/pydantic_avro/base.py
6
+
7
+
8
+ def get_avro_schema(model: Type[BaseModel], by_alias: bool = True, namespace: Optional[str] = None) -> Dict[str, Any]:
9
+ schema = model.schema(by_alias=by_alias)
10
+ return {
11
+ "type": "record",
12
+ "namespace": schema["title"] if namespace is None else namespace,
13
+ "name": schema["title"],
14
+ "fields": _get_fields(schema, set()),
15
+ }
16
+
17
+
18
+ def _get_type(schema: Dict[str, Any], value: Dict[str, Any], classes_seen: Set[str]) -> Dict[str, Any]:
19
+ """Returns a type of single field"""
20
+ t = value.get("type")
21
+ f = value.get("format")
22
+ r = value.get("$ref")
23
+ a = value.get("additionalProperties")
24
+ avro_type_dict: Dict[str, Any] = {}
25
+ if "default" in value:
26
+ avro_type_dict["default"] = value.get("default")
27
+ if "description" in value:
28
+ avro_type_dict["doc"] = value.get("description")
29
+ if "allOf" in value and len(value["allOf"]) == 1:
30
+ r = value["allOf"][0]["$ref"]
31
+ if r is not None:
32
+ class_name = r.replace("#/definitions/", "")
33
+ if class_name in classes_seen:
34
+ avro_type_dict["type"] = class_name
35
+ else:
36
+ d = _get_definition(r, schema)
37
+ if "enum" in d:
38
+ avro_type_dict["type"] = {
39
+ "type": "enum",
40
+ "symbols": [str(v) for v in d["enum"]],
41
+ "name": d["title"],
42
+ }
43
+ else:
44
+ avro_type_dict["type"] = {
45
+ "type": "record",
46
+ "fields": _get_fields(d, classes_seen),
47
+ # Name of the struct should be unique true the complete schema
48
+ # Because of this the path in the schema is tracked and used as name for a nested struct/array
49
+ "name": class_name,
50
+ }
51
+ classes_seen.add(class_name)
52
+ elif t == "array":
53
+ items: Dict[str, Any] = value.get("items") # type: ignore
54
+ tn: Dict[str, Any] = _get_type(schema, items, classes_seen)
55
+ # If items in array are a object:
56
+ if "$ref" in items:
57
+ tn = tn["type"]
58
+ # If items in array are a logicalType
59
+ if (
60
+ isinstance(tn, dict)
61
+ and isinstance(tn.get("type", {}), dict)
62
+ and tn.get("type", {}).get("logicalType") is not None
63
+ ):
64
+ tn = tn["type"]
65
+ avro_type_dict["type"] = {"type": "array", "items": tn}
66
+ elif t == "string" and f == "date-time":
67
+ avro_type_dict["type"] = {
68
+ "type": "long",
69
+ "logicalType": "timestamp-micros",
70
+ }
71
+ elif t == "string" and f == "date":
72
+ avro_type_dict["type"] = {
73
+ "type": "int",
74
+ "logicalType": "date",
75
+ }
76
+ elif t == "string" and f == "time":
77
+ avro_type_dict["type"] = {
78
+ "type": "long",
79
+ "logicalType": "time-micros",
80
+ }
81
+ elif t == "string" and f == "uuid":
82
+ avro_type_dict["type"] = {
83
+ "type": "string",
84
+ "logicalType": "uuid",
85
+ }
86
+ elif t == "string":
87
+ avro_type_dict["type"] = "string"
88
+ elif t == "number":
89
+ avro_type_dict["type"] = "double"
90
+ elif t == "integer":
91
+ # integer in python can be a long
92
+ avro_type_dict["type"] = "long"
93
+ elif t == "boolean":
94
+ avro_type_dict["type"] = "boolean"
95
+ elif t == "object":
96
+ if a is None:
97
+ value_type = "string"
98
+ else:
99
+ value_type = _get_type(schema, a, classes_seen) # type: ignore
100
+ if isinstance(value_type, dict) and len(value_type) == 1: # type: ignore
101
+ value_type = value_type.get("type") # type: ignore
102
+ avro_type_dict["type"] = {"type": "map", "values": value_type}
103
+ else:
104
+ raise NotImplementedError(f"Type '{t}' not support yet, please report this at https://github.com/godatadriven/pydantic-avro/issues")
105
+ return avro_type_dict
106
+
107
+
108
+ def _get_definition(ref: str, schema: Dict[str, Any]) -> Dict[str, Any]:
109
+ """Reading definition of base schema for nested structs"""
110
+ id = ref.replace("#/definitions/", "")
111
+ d = schema.get("definitions", {}).get(id)
112
+ if d is None:
113
+ raise RuntimeError(f"Definition {id} does not exist")
114
+ return d
115
+
116
+
117
+ def _get_fields(schema: Dict[str, Any], classes_seen: Set[str]) -> List[Dict[str, Any]]:
118
+ """Return a list of fields of a struct"""
119
+ fields = []
120
+ required = schema.get("required", [])
121
+ for key, value in schema.get("properties", {}).items():
122
+ avro_type_dict = _get_type(schema, value, classes_seen)
123
+ avro_type_dict["name"] = key
124
+
125
+ if key not in required:
126
+ if avro_type_dict.get("default") is None:
127
+ avro_type_dict["type"] = ["null", avro_type_dict["type"]]
128
+ avro_type_dict["default"] = None
129
+
130
+ fields.append(avro_type_dict)
131
+ return fields
@@ -0,0 +1,47 @@
1
+ from typing import NamedTuple, List, Optional
2
+
3
+ from ul_unipipeline.errors import UniError
4
+ from ul_unipipeline.modules.uni import Uni
5
+
6
+
7
+ class DataStreamStats(NamedTuple):
8
+ messages_count: int
9
+ queue_name: str
10
+ error_queue: bool
11
+ error: Optional[str]
12
+
13
+
14
+ def get_data_streams_stats(uni: Uni) -> List[DataStreamStats]:
15
+ stats = []
16
+ error__error_messages_count = error__expect_messages_count = None
17
+ for wd in uni.config.workers.values():
18
+ try:
19
+ broker = uni._mediator.get_broker(wd.broker.name)
20
+ except UniError:
21
+ pass
22
+ else:
23
+ try:
24
+ expect_messages_count = broker.get_topic_approximate_messages_count(wd.topic)
25
+ except UniError:
26
+ expect_messages_count = -1
27
+ error__expect_messages_count = "inactive"
28
+ try:
29
+ error_messages_count = broker.get_topic_approximate_messages_count(wd.error_topic)
30
+ except UniError:
31
+ error_messages_count = -1
32
+ error__error_messages_count = "inactive"
33
+
34
+ stats.append(DataStreamStats(
35
+ messages_count=expect_messages_count,
36
+ queue_name=wd.topic,
37
+ error_queue=False,
38
+ error=error__expect_messages_count,
39
+ ))
40
+ stats.append(DataStreamStats(
41
+ messages_count=error_messages_count,
42
+ queue_name=wd.error_topic,
43
+ error_queue=True,
44
+ error=error__error_messages_count,
45
+ ))
46
+
47
+ return stats
@@ -0,0 +1,23 @@
1
+ import functools
2
+ from typing import Callable, TypeVar, Any
3
+
4
+ from flask import g
5
+
6
+ TFn = TypeVar("TFn", bound=Callable[..., Any])
7
+
8
+
9
+ DEFAULT_OBJ = object()
10
+
11
+
12
+ def cached_per_request(key: str) -> Callable[[TFn], TFn]:
13
+ def wrapper(fn: Callable[[...], Any]) -> Any: # type: ignore
14
+ @functools.wraps(fn)
15
+ def wr(*args: Any, **kwargs: Any) -> Any:
16
+ cached_res = getattr(g, key, DEFAULT_OBJ)
17
+ if cached_res is not DEFAULT_OBJ:
18
+ return cached_res
19
+ res = fn(*args, **kwargs)
20
+ setattr(g, key, res)
21
+ return res
22
+ return wr
23
+ return wrapper
@@ -0,0 +1,31 @@
1
+ from typing import Dict
2
+
3
+ C_NC = ""
4
+ C_FG_RED = ""
5
+ C_FG_GREEN = ""
6
+ C_FG_YELLOW = ""
7
+ C_FG_GRAY = ""
8
+ C_FG_BLUE = ""
9
+
10
+ COLORS_MAP__TERMINAL: Dict[str, str] = {
11
+ C_NC: C_NC,
12
+ C_FG_RED: C_FG_RED,
13
+ C_FG_GREEN: C_FG_GREEN,
14
+ C_FG_YELLOW: C_FG_YELLOW,
15
+ C_FG_GRAY: C_FG_GRAY,
16
+ C_FG_BLUE: C_FG_BLUE,
17
+ "": "",
18
+ }
19
+
20
+
21
+ COLORS_MAP__HTML = {
22
+ C_NC: '</span>',
23
+ C_FG_GRAY: '<span style="color: gray;">',
24
+ C_FG_RED: '<span style="color: red;">',
25
+ C_FG_YELLOW: '<span style="color: yellow;">',
26
+ C_FG_GREEN: '<span style="color: green;">',
27
+ C_FG_BLUE: '<span style="color: blue;">',
28
+ "": "",
29
+ }
30
+
31
+ assert set(COLORS_MAP__TERMINAL.keys()) == set(COLORS_MAP__HTML.keys())
@@ -0,0 +1,7 @@
1
+ from typing import Any, Dict, TypeVar, Tuple, Union
2
+
3
+ from flask import Response
4
+
5
+ TKwargs = TypeVar('TKwargs', bound=Dict[str, Any])
6
+ TJsonResponse = Union[Tuple[str, int], Tuple[Response, int]]
7
+ TJsonObj = Dict[str, Any]
@@ -0,0 +1,9 @@
1
+ import base64
2
+
3
+
4
+ def decode_base64_to_string(encoded_str: str) -> str:
5
+ assert isinstance(encoded_str, str) and len(encoded_str) > 0, f'String with item required. Object {encoded_str} with type {type(encoded_str)} was given'
6
+ encoded_bytes = encoded_str.encode('utf-8')
7
+ decoded_bytes = base64.b64decode(encoded_bytes)
8
+ decoded_str = decoded_bytes.decode('utf-8')
9
+ return decoded_str
@@ -0,0 +1,19 @@
1
+ import functools
2
+ import warnings
3
+ from typing import TypeVar, Callable, cast
4
+
5
+ TFunc = TypeVar('TFunc', bound=Callable) # type: ignore
6
+
7
+
8
+ # https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically
9
+ def deprecated(func: TFunc) -> TFunc:
10
+ """This is a decorator which can be used to mark functions
11
+ as deprecated. It will result in a warning being emitted
12
+ when the function is used."""
13
+ @functools.wraps(func)
14
+ def new_func(*args, **kwargs): # type: ignore
15
+ warnings.simplefilter('always', DeprecationWarning) # turn off filter
16
+ warnings.warn("Call to deprecated function {}.".format(func.__name__), category=DeprecationWarning, stacklevel=2)
17
+ warnings.simplefilter('default', DeprecationWarning) # reset filter
18
+ return func(*args, **kwargs)
19
+ return cast(TFunc, new_func)
@@ -0,0 +1,29 @@
1
+ import functools
2
+ import hashlib
3
+
4
+
5
+ class Flagged:
6
+ def __init__(self, flags: str) -> None:
7
+ self._flsgs = set(_feature.strip() for _feature in flags.strip().upper().split(',') if len(_feature.strip()))
8
+
9
+ @functools.cache # noqa: B019
10
+ def has_or_unset(self, flag: str) -> bool:
11
+ flags = set(_feature.strip() for _feature in flag.strip().upper().split(',') if len(_feature.strip()))
12
+ if not len(flags):
13
+ return True
14
+ for f in flags:
15
+ if f in self._flsgs:
16
+ return True
17
+ fh = hashlib.sha1()
18
+ fh.update(f.encode())
19
+ if fh.digest().hex().upper() in self._flsgs:
20
+ return True
21
+ return False
22
+
23
+ def gen_hashed(self) -> str:
24
+ res_flags = []
25
+ for f in self._flsgs:
26
+ fh = hashlib.sha1()
27
+ fh.update(f.encode())
28
+ res_flags.append(fh.digest().hex().upper())
29
+ return ",".join(res_flags)
File without changes
@@ -0,0 +1,4 @@
1
+ from typing import Tuple
2
+
3
+ TVersion = Tuple[int, int, int, str, int]
4
+ VERSION: TVersion = (0, 3, 1, 'alpha', 0)
@@ -0,0 +1,7 @@
1
+ from typing import Optional
2
+
3
+
4
+ class SwaggerGeneratorError(Exception):
5
+
6
+ def __init__(self, message: Optional[str] = None) -> None:
7
+ super().__init__(message)
@@ -0,0 +1,57 @@
1
+ from abc import ABC, abstractmethod
2
+ from random import randint
3
+
4
+
5
+ class SwaggerModel(ABC):
6
+ TAB = " "
7
+
8
+ def __init__(self) -> None:
9
+ self.swagger_models = [] # type: ignore
10
+
11
+ def add_swagger_model(self, child_swagger_model): # type: ignore
12
+ self.swagger_models.append(child_swagger_model)
13
+
14
+ def add_swagger_models(self, child_swagger_models): # type: ignore
15
+ self.swagger_models += child_swagger_models
16
+
17
+ def has_swagger_model_child_type(self, child_swagger_model_type) -> bool: # type: ignore
18
+ for swagger_model in self.swagger_models:
19
+
20
+ if isinstance(swagger_model, child_swagger_model_type):
21
+ return True
22
+
23
+ return False
24
+
25
+ def get_swagger_child_models_of_type(self, swagger_model_type): # type: ignore
26
+ selection = []
27
+
28
+ for swagger_model in self.swagger_models:
29
+
30
+ if isinstance(swagger_model, swagger_model_type):
31
+ selection.append(swagger_model)
32
+
33
+ return selection
34
+
35
+ def write(self, file) -> None: # type: ignore
36
+ self.perform_write(file)
37
+
38
+ for swagger_model in self.swagger_models:
39
+ swagger_model.write(file)
40
+
41
+ @staticmethod
42
+ def indent(string: str, prefix: str) -> str:
43
+ return ''.join(prefix + line for line in string.splitlines(True))
44
+
45
+ @abstractmethod
46
+ def perform_write(self, file) -> None: # type: ignore
47
+ raise NotImplementedError()
48
+
49
+ def random_id(self) -> int:
50
+ """
51
+ Function to create a random ID.
52
+ Returns: random integer that can be used as an ID
53
+ """
54
+ minimal = 100
55
+ maximal = 1000000000
56
+ rand = randint(minimal, maximal)
57
+ return rand
@@ -0,0 +1,48 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List, Optional
3
+
4
+ from ul_api_utils.utils.flask_swagger_generator.utils.request_type import RequestType
5
+ from ul_api_utils.utils.flask_swagger_generator.utils.security_type import SecurityType
6
+
7
+
8
+ class SwaggerSpecifier(ABC):
9
+
10
+ def __init__(self) -> None:
11
+ self.application_name: Optional[str] = None
12
+ self.application_version: Optional[str] = None
13
+
14
+ @abstractmethod
15
+ def add_response(self, function_name: str, status_code: int, schema, description: str = ""): # type: ignore
16
+ raise NotImplementedError()
17
+
18
+ @abstractmethod
19
+ def add_endpoint(
20
+ self,
21
+ function_name: str,
22
+ path: str,
23
+ request_types: List[RequestType],
24
+ group: Optional[str] = None,
25
+ ) -> None:
26
+ raise NotImplementedError()
27
+
28
+ @abstractmethod
29
+ def add_query_parameters(self) -> None:
30
+ raise NotImplementedError()
31
+
32
+ @abstractmethod
33
+ def add_request_body(self, function_name: str, schema) -> None: # type: ignore
34
+ raise NotImplementedError()
35
+
36
+ @abstractmethod
37
+ def add_security(self, function_name: str, security_type: SecurityType) -> None:
38
+ raise NotImplementedError()
39
+
40
+ def set_application_name(self, application_name: str) -> None:
41
+ self.application_name = application_name
42
+
43
+ def set_application_version(self, application_version: str) -> None:
44
+ self.application_version = application_version
45
+
46
+ @abstractmethod
47
+ def clean(self) -> None:
48
+ pass