ul-api-utils 9.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- example/__init__.py +0 -0
- example/conf.py +35 -0
- example/main.py +24 -0
- example/models/__init__.py +0 -0
- example/permissions.py +6 -0
- example/pure_flask_example.py +65 -0
- example/rate_limit_load.py +10 -0
- example/redis_repository.py +22 -0
- example/routes/__init__.py +0 -0
- example/routes/api_some.py +335 -0
- example/sockets/__init__.py +0 -0
- example/sockets/on_connect.py +16 -0
- example/sockets/on_disconnect.py +14 -0
- example/sockets/on_json.py +10 -0
- example/sockets/on_message.py +13 -0
- example/sockets/on_open.py +16 -0
- example/workers/__init__.py +0 -0
- example/workers/worker.py +28 -0
- ul_api_utils/__init__.py +0 -0
- ul_api_utils/access/__init__.py +122 -0
- ul_api_utils/api_resource/__init__.py +0 -0
- ul_api_utils/api_resource/api_request.py +105 -0
- ul_api_utils/api_resource/api_resource.py +414 -0
- ul_api_utils/api_resource/api_resource_config.py +20 -0
- ul_api_utils/api_resource/api_resource_error_handling.py +21 -0
- ul_api_utils/api_resource/api_resource_fn_typing.py +356 -0
- ul_api_utils/api_resource/api_resource_type.py +16 -0
- ul_api_utils/api_resource/api_response.py +300 -0
- ul_api_utils/api_resource/api_response_db.py +26 -0
- ul_api_utils/api_resource/api_response_payload_alias.py +25 -0
- ul_api_utils/api_resource/db_types.py +9 -0
- ul_api_utils/api_resource/signature_check.py +41 -0
- ul_api_utils/commands/__init__.py +0 -0
- ul_api_utils/commands/cmd_enc_keys.py +172 -0
- ul_api_utils/commands/cmd_gen_api_user_token.py +77 -0
- ul_api_utils/commands/cmd_gen_new_api_user.py +106 -0
- ul_api_utils/commands/cmd_generate_api_docs.py +181 -0
- ul_api_utils/commands/cmd_start.py +110 -0
- ul_api_utils/commands/cmd_worker_start.py +76 -0
- ul_api_utils/commands/start/__init__.py +0 -0
- ul_api_utils/commands/start/gunicorn.conf.local.py +0 -0
- ul_api_utils/commands/start/gunicorn.conf.py +26 -0
- ul_api_utils/commands/start/wsgi.py +22 -0
- ul_api_utils/conf/ul-debugger-main.js +1 -0
- ul_api_utils/conf/ul-debugger-ui.js +1 -0
- ul_api_utils/conf.py +70 -0
- ul_api_utils/const.py +78 -0
- ul_api_utils/debug/__init__.py +0 -0
- ul_api_utils/debug/debugger.py +119 -0
- ul_api_utils/debug/malloc.py +93 -0
- ul_api_utils/debug/stat.py +444 -0
- ul_api_utils/encrypt/__init__.py +0 -0
- ul_api_utils/encrypt/encrypt_decrypt_abstract.py +15 -0
- ul_api_utils/encrypt/encrypt_decrypt_aes_xtea.py +59 -0
- ul_api_utils/errors.py +200 -0
- ul_api_utils/internal_api/__init__.py +0 -0
- ul_api_utils/internal_api/__tests__/__init__.py +0 -0
- ul_api_utils/internal_api/__tests__/internal_api.py +29 -0
- ul_api_utils/internal_api/__tests__/internal_api_content_type.py +22 -0
- ul_api_utils/internal_api/internal_api.py +369 -0
- ul_api_utils/internal_api/internal_api_check_context.py +42 -0
- ul_api_utils/internal_api/internal_api_error.py +17 -0
- ul_api_utils/internal_api/internal_api_response.py +296 -0
- ul_api_utils/main.py +29 -0
- ul_api_utils/modules/__init__.py +0 -0
- ul_api_utils/modules/__tests__/__init__.py +0 -0
- ul_api_utils/modules/__tests__/test_api_sdk_jwt.py +195 -0
- ul_api_utils/modules/api_sdk.py +555 -0
- ul_api_utils/modules/api_sdk_config.py +63 -0
- ul_api_utils/modules/api_sdk_jwt.py +377 -0
- ul_api_utils/modules/intermediate_state.py +34 -0
- ul_api_utils/modules/worker_context.py +35 -0
- ul_api_utils/modules/worker_sdk.py +109 -0
- ul_api_utils/modules/worker_sdk_config.py +13 -0
- ul_api_utils/py.typed +0 -0
- ul_api_utils/resources/__init__.py +0 -0
- ul_api_utils/resources/caching.py +196 -0
- ul_api_utils/resources/debugger_scripts.py +97 -0
- ul_api_utils/resources/health_check/__init__.py +0 -0
- ul_api_utils/resources/health_check/const.py +2 -0
- ul_api_utils/resources/health_check/health_check.py +439 -0
- ul_api_utils/resources/health_check/health_check_template.py +64 -0
- ul_api_utils/resources/health_check/resource.py +97 -0
- ul_api_utils/resources/not_implemented.py +25 -0
- ul_api_utils/resources/permissions.py +29 -0
- ul_api_utils/resources/rate_limitter.py +84 -0
- ul_api_utils/resources/socketio.py +55 -0
- ul_api_utils/resources/swagger.py +119 -0
- ul_api_utils/resources/web_forms/__init__.py +0 -0
- ul_api_utils/resources/web_forms/custom_fields/__init__.py +0 -0
- ul_api_utils/resources/web_forms/custom_fields/custom_checkbox_select.py +5 -0
- ul_api_utils/resources/web_forms/custom_widgets/__init__.py +0 -0
- ul_api_utils/resources/web_forms/custom_widgets/custom_select_widget.py +86 -0
- ul_api_utils/resources/web_forms/custom_widgets/custom_text_input_widget.py +42 -0
- ul_api_utils/resources/web_forms/uni_form.py +75 -0
- ul_api_utils/sentry.py +52 -0
- ul_api_utils/utils/__init__.py +0 -0
- ul_api_utils/utils/__tests__/__init__.py +0 -0
- ul_api_utils/utils/__tests__/api_path_version.py +16 -0
- ul_api_utils/utils/__tests__/unwrap_typing.py +67 -0
- ul_api_utils/utils/api_encoding.py +51 -0
- ul_api_utils/utils/api_format.py +61 -0
- ul_api_utils/utils/api_method.py +55 -0
- ul_api_utils/utils/api_pagination.py +58 -0
- ul_api_utils/utils/api_path_version.py +60 -0
- ul_api_utils/utils/api_request_info.py +6 -0
- ul_api_utils/utils/avro.py +131 -0
- ul_api_utils/utils/broker_topics_message_count.py +47 -0
- ul_api_utils/utils/cached_per_request.py +23 -0
- ul_api_utils/utils/colors.py +31 -0
- ul_api_utils/utils/constants.py +7 -0
- ul_api_utils/utils/decode_base64.py +9 -0
- ul_api_utils/utils/deprecated.py +19 -0
- ul_api_utils/utils/flags.py +29 -0
- ul_api_utils/utils/flask_swagger_generator/__init__.py +0 -0
- ul_api_utils/utils/flask_swagger_generator/conf.py +4 -0
- ul_api_utils/utils/flask_swagger_generator/exceptions.py +7 -0
- ul_api_utils/utils/flask_swagger_generator/specifiers/__init__.py +0 -0
- ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_models.py +57 -0
- ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_specifier.py +48 -0
- ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_three_specifier.py +777 -0
- ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_version.py +40 -0
- ul_api_utils/utils/flask_swagger_generator/utils/__init__.py +0 -0
- ul_api_utils/utils/flask_swagger_generator/utils/input_type.py +77 -0
- ul_api_utils/utils/flask_swagger_generator/utils/parameter_type.py +51 -0
- ul_api_utils/utils/flask_swagger_generator/utils/replace_in_dict.py +18 -0
- ul_api_utils/utils/flask_swagger_generator/utils/request_type.py +52 -0
- ul_api_utils/utils/flask_swagger_generator/utils/schema_type.py +15 -0
- ul_api_utils/utils/flask_swagger_generator/utils/security_type.py +39 -0
- ul_api_utils/utils/imports.py +16 -0
- ul_api_utils/utils/instance_checks.py +16 -0
- ul_api_utils/utils/jinja/__init__.py +0 -0
- ul_api_utils/utils/jinja/t_url_for.py +19 -0
- ul_api_utils/utils/jinja/to_pretty_json.py +11 -0
- ul_api_utils/utils/json_encoder.py +126 -0
- ul_api_utils/utils/load_modules.py +15 -0
- ul_api_utils/utils/memory_db/__init__.py +0 -0
- ul_api_utils/utils/memory_db/__tests__/__init__.py +0 -0
- ul_api_utils/utils/memory_db/errors.py +8 -0
- ul_api_utils/utils/memory_db/repository.py +102 -0
- ul_api_utils/utils/token_check.py +14 -0
- ul_api_utils/utils/token_check_through_request.py +16 -0
- ul_api_utils/utils/unwrap_typing.py +117 -0
- ul_api_utils/utils/uuid_converter.py +22 -0
- ul_api_utils/validators/__init__.py +0 -0
- ul_api_utils/validators/__tests__/__init__.py +0 -0
- ul_api_utils/validators/__tests__/test_custom_fields.py +32 -0
- ul_api_utils/validators/custom_fields.py +66 -0
- ul_api_utils/validators/validate_empty_object.py +10 -0
- ul_api_utils/validators/validate_uuid.py +11 -0
- ul_api_utils-9.3.0.dist-info/LICENSE +21 -0
- ul_api_utils-9.3.0.dist-info/METADATA +279 -0
- ul_api_utils-9.3.0.dist-info/RECORD +156 -0
- ul_api_utils-9.3.0.dist-info/WHEEL +5 -0
- ul_api_utils-9.3.0.dist-info/entry_points.txt +2 -0
- ul_api_utils-9.3.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Dict, Union, Optional, Any, Iterable, Generic, TypeVar, Type, List
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from ul_api_utils.api_resource.api_response import AnyJsonApiResponse
|
|
6
|
+
from ul_api_utils.api_resource.db_types import TDictable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AnyJsonDbApiResponse(AnyJsonApiResponse):
|
|
10
|
+
payload: Union[Optional[TDictable], Iterable[TDictable]]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
TJsonObjApiResponsePayload = TypeVar('TJsonObjApiResponsePayload')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class JsonDbApiResponse(Generic[TJsonObjApiResponsePayload], AnyJsonApiResponse):
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def _internal_use__mk_schema(cls, inner_type: Optional[Type[BaseModel]]) -> Type[BaseModel]:
|
|
20
|
+
class _ResponseStd(BaseModel):
|
|
21
|
+
ok: bool
|
|
22
|
+
payload: inner_type # type: ignore
|
|
23
|
+
errors: List[Dict[str, Any]]
|
|
24
|
+
total_count: Optional[int] = None
|
|
25
|
+
count: Optional[int] = None
|
|
26
|
+
return _ResponseStd
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from pydantic import UUID4
|
|
4
|
+
|
|
5
|
+
from ul_api_utils.api_resource.api_response import JsonApiResponsePayload, RootJsonApiResponsePayload
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ApiBaseModelPayloadResponse(JsonApiResponsePayload):
|
|
9
|
+
id: UUID4
|
|
10
|
+
date_created: datetime
|
|
11
|
+
date_modified: datetime
|
|
12
|
+
is_alive: bool
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ApiBaseUserModelPayloadResponse(JsonApiResponsePayload):
|
|
16
|
+
id: UUID4
|
|
17
|
+
date_created: datetime
|
|
18
|
+
date_modified: datetime
|
|
19
|
+
user_created_id: UUID4
|
|
20
|
+
user_modified_id: UUID4
|
|
21
|
+
is_alive: bool
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ApiEmptyResponse(RootJsonApiResponsePayload[None]):
|
|
25
|
+
pass
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from typing import Union, Dict, Any, Tuple, Optional, Iterable
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from ul_db_utils.model.base_model import BaseModel as DbBaseModel
|
|
5
|
+
from flask_sqlalchemy.model import Model
|
|
6
|
+
|
|
7
|
+
# TODO: remove DbBaseModel/Model from it BECAUSE IT loads sqlalchemy (>20mb of code)
|
|
8
|
+
TDictable = Union[Dict[str, Any], BaseModel, Tuple[Any, ...], DbBaseModel, Model]
|
|
9
|
+
TPayloadInputUnion = Union[Optional[TDictable], Iterable[TDictable]]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional, Union, Type, TypeVar, Tuple, TYPE_CHECKING, get_origin, get_args
|
|
2
|
+
from typing import _GenericAlias # type: ignore
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, RootModel
|
|
5
|
+
|
|
6
|
+
from ul_api_utils.utils.json_encoder import to_dict
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ul_api_utils.api_resource.db_types import TDictable
|
|
10
|
+
|
|
11
|
+
TPydanticModel = TypeVar('TPydanticModel', bound=BaseModel)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def set_model(model: Type[TPydanticModel], data: Union[Dict[str, Any], TPydanticModel]) -> TPydanticModel:
|
|
15
|
+
if isinstance(data, model):
|
|
16
|
+
return data
|
|
17
|
+
if issubclass(model, RootModel):
|
|
18
|
+
return model(data).root
|
|
19
|
+
assert isinstance(data, dict), f'data must be dict. "{type(data).__name__}" was given'
|
|
20
|
+
return model(**data)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def set_model_dictable(model: Type[TPydanticModel], data: 'TDictable') -> Optional[TPydanticModel]:
|
|
24
|
+
if isinstance(data, model):
|
|
25
|
+
return data
|
|
26
|
+
res: Optional[Dict[str, Any]] = to_dict(data)
|
|
27
|
+
if res is None:
|
|
28
|
+
return None
|
|
29
|
+
if issubclass(model, RootModel):
|
|
30
|
+
return model(data).root
|
|
31
|
+
return model(**res)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_typing(t: Type[Any]) -> Tuple[Type[Any], ...]:
|
|
35
|
+
if type(t) == _GenericAlias: # noqa: E721
|
|
36
|
+
return get_origin(t), *(it for it in get_args(t))
|
|
37
|
+
if t.__class__.__name__ == 'ModelMetaclass':
|
|
38
|
+
if hasattr(t, '_generic_params') and t._generic_params is not None:
|
|
39
|
+
unspecialized_class = t.__bases__[0]
|
|
40
|
+
return unspecialized_class, t._generic_params[0]
|
|
41
|
+
return t, # noqa: C818
|
|
File without changes
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import base64
|
|
3
|
+
import importlib
|
|
4
|
+
import os.path
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import List, Optional, Any, Dict
|
|
10
|
+
from uuid import UUID
|
|
11
|
+
import getpass
|
|
12
|
+
import socket
|
|
13
|
+
# import json
|
|
14
|
+
|
|
15
|
+
from ul_py_tool.commands.cmd import Cmd
|
|
16
|
+
from ul_py_tool.utils.colors import FG_GREEN, NC
|
|
17
|
+
from ul_py_tool.utils.write_stdout import write_stdout
|
|
18
|
+
from yaml import dump
|
|
19
|
+
|
|
20
|
+
from ul_api_utils.access import PermissionRegistry
|
|
21
|
+
from ul_api_utils.modules.api_sdk_jwt import ApiSdkJwt, ALGORITHMS
|
|
22
|
+
# from ul_api_utils.utils.json_encoder import CustomJSONEncoder
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CmdEncKeys(Cmd):
|
|
26
|
+
algo: str
|
|
27
|
+
name: str
|
|
28
|
+
services: List[str]
|
|
29
|
+
dir: str
|
|
30
|
+
permissions_module: Optional[str] = None
|
|
31
|
+
permissions_uri: Optional[str] = None
|
|
32
|
+
env: Optional[str] = None
|
|
33
|
+
org_id: Optional[UUID] = None
|
|
34
|
+
user_id: Optional[UUID] = None
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def add_parser_args(parser: argparse.ArgumentParser) -> None:
|
|
38
|
+
parser.add_argument('--algorithm', dest='algo', type=str, choices=ALGORITHMS, required=True)
|
|
39
|
+
parser.add_argument('--service-name', dest='name', type=str, required=True)
|
|
40
|
+
parser.add_argument('--follower-services', dest='services', nargs='*', required=False, default=[], type=str)
|
|
41
|
+
parser.add_argument('--result-dir', dest='dir', type=str, required=False, default=os.path.join(os.getcwd(), '.tmp', f'enc-keys-{datetime.now().date().isoformat()}'))
|
|
42
|
+
parser.add_argument('--jwt-permissions-module', dest='permissions_module', type=str, required=False, default=None)
|
|
43
|
+
parser.add_argument('--jwt-permissions-uri', dest='permissions_uri', type=str, required=False, default=None)
|
|
44
|
+
parser.add_argument('--jwt-environment', dest='env', type=str, required=False, default=None)
|
|
45
|
+
parser.add_argument('--jwt-user-id', dest='user_id', type=UUID, required=False, default=None)
|
|
46
|
+
parser.add_argument('--jwt-organization-id', dest='org_id', type=UUID, required=False, default=None)
|
|
47
|
+
|
|
48
|
+
def run(self) -> None:
|
|
49
|
+
write_stdout('')
|
|
50
|
+
os.makedirs(self.dir, exist_ok=True)
|
|
51
|
+
|
|
52
|
+
services = set(self.services)
|
|
53
|
+
|
|
54
|
+
private_key, pub_key_factory = ApiSdkJwt.generate_cert(self.algo) # type: ignore
|
|
55
|
+
service_pub_key = pub_key_factory()
|
|
56
|
+
|
|
57
|
+
structure: Dict[str, Any] = dict()
|
|
58
|
+
|
|
59
|
+
structure['info'] = dict()
|
|
60
|
+
structure['info']['generated_at'] = datetime.now().isoformat()
|
|
61
|
+
structure['info']['generated_user'] = getpass.getuser()
|
|
62
|
+
structure['info']['generated_hostname'] = socket.gethostname()
|
|
63
|
+
structure['info']['generated_algorithm'] = self.algo
|
|
64
|
+
if self.env:
|
|
65
|
+
structure['info']['environment'] = self.env
|
|
66
|
+
|
|
67
|
+
structure['service'] = dict()
|
|
68
|
+
structure['service'][self.name] = dict()
|
|
69
|
+
structure['service'][self.name]['public_key'] = base64.b64encode(service_pub_key.encode('utf-8')).decode('utf-8')
|
|
70
|
+
structure['service'][self.name]['private_key'] = base64.b64encode(private_key.encode('utf-8')).decode('utf-8')
|
|
71
|
+
|
|
72
|
+
structure['follower_services'] = dict()
|
|
73
|
+
for service in services:
|
|
74
|
+
pub_key = pub_key_factory().encode('utf-8')
|
|
75
|
+
structure['follower_services'][service] = dict()
|
|
76
|
+
structure['follower_services'][service]['public_key'] = base64.b64encode(pub_key).decode('utf-8')
|
|
77
|
+
# structure['follower_services'][service]['private_key'] = ""
|
|
78
|
+
|
|
79
|
+
if self.permissions_uri:
|
|
80
|
+
assert self.permissions_uri
|
|
81
|
+
assert self.algo
|
|
82
|
+
assert self.env
|
|
83
|
+
assert self.user_id
|
|
84
|
+
permissions_response = requests.get(self.permissions_uri)
|
|
85
|
+
assert permissions_response.status_code == 200, f'permissions requests faild. {permissions_response.status_code} :: {permissions_response.json()}'
|
|
86
|
+
permissions_json = permissions_response.json()
|
|
87
|
+
assert 'payload' in permissions_json
|
|
88
|
+
permissions_payload: List[Dict[str, str | int]] = permissions_json['payload']
|
|
89
|
+
|
|
90
|
+
permissions_list: List[int] = []
|
|
91
|
+
for permissions_dict in permissions_payload:
|
|
92
|
+
permissions_list.extend([p['id'] for p in permissions_dict.get('permissions')]) # type: ignore
|
|
93
|
+
|
|
94
|
+
jwt_data = dict(
|
|
95
|
+
environment=self.env,
|
|
96
|
+
user_id=self.user_id,
|
|
97
|
+
organization_id=self.org_id,
|
|
98
|
+
permissions=permissions_list,
|
|
99
|
+
access_expiration_date=datetime(2030, 1, 1),
|
|
100
|
+
refresh_expiration_date=datetime(2030, 1, 1),
|
|
101
|
+
)
|
|
102
|
+
att, _ = ApiSdkJwt.create_jwt_pair(**jwt_data) # type: ignore
|
|
103
|
+
structure['service'][self.name]['full_access_jwt_token'] = att.encode(private_key, self.algo) # type: ignore
|
|
104
|
+
# structure['service'][self.name]['full_access_jwt_data'] = json.loads(json.dumps(jwt_data, cls=CustomJSONEncoder))
|
|
105
|
+
|
|
106
|
+
for service in self.services:
|
|
107
|
+
if len(service.split(':')) > 1:
|
|
108
|
+
service_name, service_user_id = service.split(':')
|
|
109
|
+
try:
|
|
110
|
+
UUID(service_user_id)
|
|
111
|
+
except ValueError:
|
|
112
|
+
raise ValueError(f"invlid user_id type must be UUID hex for follower service - {service_name}")
|
|
113
|
+
jwt_data['user_id'] = service_user_id
|
|
114
|
+
att, _rtt = ApiSdkJwt.create_jwt_pair(**jwt_data) # type: ignore
|
|
115
|
+
structure['follower_services'][service]['full_access_jwt_token'] = att.encode(private_key, self.algo) # type: ignore
|
|
116
|
+
# structure['service'][service]['full_access_jwt_data'] = json.loads(json.dumps(jwt_data, cls=CustomJSONEncoder))
|
|
117
|
+
|
|
118
|
+
elif self.permissions_module:
|
|
119
|
+
assert self.permissions_module
|
|
120
|
+
assert self.algo
|
|
121
|
+
assert self.env
|
|
122
|
+
assert self.user_id
|
|
123
|
+
sys.path.append(os.getcwd())
|
|
124
|
+
mdl = importlib.import_module(self.permissions_module)
|
|
125
|
+
permissions = None
|
|
126
|
+
for k in dir(mdl):
|
|
127
|
+
v = getattr(mdl, k)
|
|
128
|
+
if isinstance(v, PermissionRegistry):
|
|
129
|
+
permissions = v
|
|
130
|
+
break
|
|
131
|
+
assert permissions is not None
|
|
132
|
+
|
|
133
|
+
jwt_data = dict(
|
|
134
|
+
environment=self.env,
|
|
135
|
+
user_id=self.user_id,
|
|
136
|
+
organization_id=self.org_id,
|
|
137
|
+
permissions=permissions.get_permissions_ids(),
|
|
138
|
+
access_expiration_date=datetime(2030, 1, 1),
|
|
139
|
+
refresh_expiration_date=datetime(2030, 1, 1),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
att, _ = ApiSdkJwt.create_jwt_pair(**jwt_data) # type: ignore
|
|
143
|
+
structure['service'][self.name]['full_access_jwt_token'] = att.encode(private_key, self.algo) # type: ignore
|
|
144
|
+
# structure['service'][self.name]['full_access_jwt_data'] = json.loads(json.dumps(jwt_data, cls=CustomJSONEncoder))
|
|
145
|
+
|
|
146
|
+
for service in self.services:
|
|
147
|
+
if len(service.split(':')) > 1:
|
|
148
|
+
service_name, service_user_id = service.split(':')
|
|
149
|
+
try:
|
|
150
|
+
UUID(service_user_id)
|
|
151
|
+
except ValueError:
|
|
152
|
+
raise ValueError(f"invlid user_id type must be UUID hex for follower service - {service_name}")
|
|
153
|
+
jwt_data['user_id'] = service_user_id
|
|
154
|
+
att, _rtt = ApiSdkJwt.create_jwt_pair(**jwt_data) # type: ignore
|
|
155
|
+
structure['follower_services'][service]['full_access_jwt_token'] = att.encode(private_key, self.algo) # type: ignore
|
|
156
|
+
# structure['follower_services'][service]['full_access_jwt_data'] = json.loads(json.dumps(jwt_data, cls=CustomJSONEncoder))
|
|
157
|
+
|
|
158
|
+
# with open(os.path.join(self.dir, f'{self.env.upper()}__{self.name}__{self.algo}.private_key.pem'), 'w') as f:
|
|
159
|
+
# f.writelines(private_key.splitlines(keepends=True))
|
|
160
|
+
# write_stdout(f' {FG_GREEN}saved:{NC} {os.path.relpath(f.name, os.getcwd())}')
|
|
161
|
+
|
|
162
|
+
if self.env:
|
|
163
|
+
result_file_name = f'{self.name}__{self.env.upper()}__{self.algo}__{datetime.now().date()}__encryption_keys.yml'
|
|
164
|
+
else:
|
|
165
|
+
result_file_name = f'{self.name}__{self.algo}__{datetime.now().date()}__encryption_keys.yml'
|
|
166
|
+
|
|
167
|
+
with open(os.path.join(self.dir, result_file_name), 'w') as f:
|
|
168
|
+
dump(structure, f, sort_keys=False)
|
|
169
|
+
write_stdout(f' {FG_GREEN}saved:{NC} {os.path.relpath(f.name, os.getcwd())}')
|
|
170
|
+
|
|
171
|
+
write_stdout('')
|
|
172
|
+
write_stdout(f'{FG_GREEN}DONE{NC}')
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import os.path
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from requests.exceptions import HTTPError
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
from ul_py_tool.commands.cmd import Cmd
|
|
14
|
+
from ul_py_tool.utils.colors import FG_GREEN, NC
|
|
15
|
+
from ul_py_tool.utils.write_stdout import write_stdout
|
|
16
|
+
from yaml import dump
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def short_date_validator(value: str) -> datetime:
|
|
23
|
+
try:
|
|
24
|
+
return datetime.combine(datetime.strptime(value, "%Y-%m-%d"), datetime.max.time())
|
|
25
|
+
except ValueError:
|
|
26
|
+
msg = "not a valid date: {0!r}".format(value)
|
|
27
|
+
raise argparse.ArgumentTypeError(msg)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CmdGenerateApiUserToken(Cmd):
|
|
31
|
+
uri_auth_api: str
|
|
32
|
+
internal_access_key: str
|
|
33
|
+
api_user_id: UUID
|
|
34
|
+
dir: str
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def add_parser_args(parser: argparse.ArgumentParser) -> None:
|
|
38
|
+
parser.add_argument('--uri', dest='uri_auth_api', type=str, required=True)
|
|
39
|
+
parser.add_argument('--key', dest='internal_access_key', type=str, required=True)
|
|
40
|
+
parser.add_argument('--id', dest='api_user_id', type=UUID, required=True)
|
|
41
|
+
parser.add_argument('--result-dir', dest='dir', type=str, required=False, default=os.path.join(os.getcwd(), '.tmp', f'api-user-tokens-{datetime.now().date().isoformat()}'))
|
|
42
|
+
|
|
43
|
+
def run(self) -> None:
|
|
44
|
+
write_stdout('')
|
|
45
|
+
os.makedirs(self.dir, exist_ok=True)
|
|
46
|
+
|
|
47
|
+
structure: Dict[str, Any] = dict()
|
|
48
|
+
|
|
49
|
+
api_user_headers = {
|
|
50
|
+
'Authorization': f'Bearer {self.internal_access_key}',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
new_api_user_token_request = requests.post(
|
|
55
|
+
url=f"{self.uri_auth_api}/api/v1/tokens/{self.api_user_id}",
|
|
56
|
+
headers=api_user_headers,
|
|
57
|
+
)
|
|
58
|
+
new_api_user_token_request.raise_for_status()
|
|
59
|
+
new_api_user_token = new_api_user_token_request.json()['payload']
|
|
60
|
+
except HTTPError as e:
|
|
61
|
+
logger.info(new_api_user_token_request.text)
|
|
62
|
+
logger.error(f'request for create api user failed :: {new_api_user_token_request.status_code} status code :: {e}')
|
|
63
|
+
raise
|
|
64
|
+
|
|
65
|
+
structure['meta'] = {}
|
|
66
|
+
structure['meta']['uri'] = self.uri_auth_api
|
|
67
|
+
structure[str(self.api_user_id)] = {}
|
|
68
|
+
structure[str(self.api_user_id)]['token'] = new_api_user_token['access_token']
|
|
69
|
+
|
|
70
|
+
result_file_name = f'{self.api_user_id}__{datetime.now().date()}__api_user_token.yml'
|
|
71
|
+
|
|
72
|
+
with open(os.path.join(self.dir, result_file_name), 'w') as f:
|
|
73
|
+
dump(structure, f, sort_keys=False)
|
|
74
|
+
write_stdout(f' {FG_GREEN}saved:{NC} {os.path.relpath(f.name, os.getcwd())}')
|
|
75
|
+
|
|
76
|
+
write_stdout('')
|
|
77
|
+
write_stdout(f'{FG_GREEN}DONE{NC}')
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import os.path
|
|
4
|
+
from requests.exceptions import HTTPError
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import List, Optional, Any, Dict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from ul_py_tool.commands.cmd import Cmd
|
|
12
|
+
from ul_py_tool.utils.colors import FG_GREEN, NC
|
|
13
|
+
from ul_py_tool.utils.write_stdout import write_stdout
|
|
14
|
+
from yaml import dump
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def short_date_validator(value: str) -> datetime:
|
|
21
|
+
try:
|
|
22
|
+
return datetime.combine(datetime.strptime(value, "%Y-%m-%d"), datetime.max.time())
|
|
23
|
+
except ValueError:
|
|
24
|
+
msg = "not a valid date: {0!r}".format(value)
|
|
25
|
+
raise argparse.ArgumentTypeError(msg)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CmdGenerateNewApiUser(Cmd):
|
|
29
|
+
uri_auth_api: str
|
|
30
|
+
internal_access_key: str
|
|
31
|
+
permissions_list: List[int]
|
|
32
|
+
permissions_uri: Optional[str] = None
|
|
33
|
+
api_user_name: str
|
|
34
|
+
api_user_note: str
|
|
35
|
+
api_user_date_exp: datetime
|
|
36
|
+
dir: str
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def add_parser_args(parser: argparse.ArgumentParser) -> None:
|
|
40
|
+
parser.add_argument('--uri', dest='uri_auth_api', type=str, required=True)
|
|
41
|
+
parser.add_argument('--key', dest='internal_access_key', type=str, required=True)
|
|
42
|
+
parser.add_argument('--name', dest='api_user_name', type=str, required=True)
|
|
43
|
+
parser.add_argument('--note', dest='api_user_note', type=str, required=True)
|
|
44
|
+
parser.add_argument('--exp-dt', dest='api_user_date_exp', type=short_date_validator, required=True)
|
|
45
|
+
parser.add_argument('--permissions', dest='permissions_list', nargs='*', required=False, default=[], type=int)
|
|
46
|
+
parser.add_argument('--permissions-uri', dest='permissions_uri', type=str, required=False, default=None)
|
|
47
|
+
parser.add_argument('--result-dir', dest='dir', type=str, required=False, default=os.path.join(os.getcwd(), '.tmp', f'api-user-tokens-{datetime.now().date().isoformat()}'))
|
|
48
|
+
|
|
49
|
+
def run(self) -> None:
|
|
50
|
+
write_stdout('')
|
|
51
|
+
os.makedirs(self.dir, exist_ok=True)
|
|
52
|
+
|
|
53
|
+
structure: Dict[str, Any] = dict()
|
|
54
|
+
|
|
55
|
+
# generate permissions list
|
|
56
|
+
permissions_list: List[int] = self.permissions_list
|
|
57
|
+
if self.permissions_uri and not permissions_list:
|
|
58
|
+
permissions_response = requests.get(self.permissions_uri)
|
|
59
|
+
assert permissions_response.status_code == 200, f'permissions requests faild. {permissions_response.status_code} :: {permissions_response.json()}'
|
|
60
|
+
permissions_json = permissions_response.json()
|
|
61
|
+
assert 'payload' in permissions_json
|
|
62
|
+
permissions_payload: List[Dict[str, str | int]] = permissions_json['payload']
|
|
63
|
+
|
|
64
|
+
for permissions_dict in permissions_payload:
|
|
65
|
+
permissions_list.extend([p['id'] for p in permissions_dict.get('permissions')]) # type: ignore
|
|
66
|
+
|
|
67
|
+
api_user_payload = {
|
|
68
|
+
'name': self.api_user_name,
|
|
69
|
+
'note': self.api_user_note,
|
|
70
|
+
'permissions': permissions_list,
|
|
71
|
+
'date_expiration': self.api_user_date_exp.isoformat(),
|
|
72
|
+
}
|
|
73
|
+
api_user_headers = {
|
|
74
|
+
'Authorization': f'Bearer {self.internal_access_key}',
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
new_api_user_request = requests.post(
|
|
79
|
+
url=f"{self.uri_auth_api}/api/v1/tokens",
|
|
80
|
+
json=api_user_payload,
|
|
81
|
+
headers=api_user_headers,
|
|
82
|
+
)
|
|
83
|
+
new_api_user_request.raise_for_status()
|
|
84
|
+
new_api_user_data = new_api_user_request.json()['payload']
|
|
85
|
+
except HTTPError as e:
|
|
86
|
+
logger.info(new_api_user_request.json())
|
|
87
|
+
logger.error(f'request for create api user failed :: {new_api_user_request.status_code} status code :: {e}')
|
|
88
|
+
raise
|
|
89
|
+
structure['meta'] = {}
|
|
90
|
+
structure['meta']['uri'] = self.uri_auth_api
|
|
91
|
+
structure['meta']['name'] = self.api_user_name
|
|
92
|
+
structure['meta']['note'] = self.api_user_note
|
|
93
|
+
structure['meta']['permissions'] = permissions_list
|
|
94
|
+
structure['meta']['date_expiration'] = self.api_user_date_exp
|
|
95
|
+
structure[self.api_user_name] = {}
|
|
96
|
+
structure[self.api_user_name]['id'] = new_api_user_data['id']
|
|
97
|
+
structure[self.api_user_name]['token'] = new_api_user_data['access_token']
|
|
98
|
+
|
|
99
|
+
result_file_name = f'{self.api_user_name}__{datetime.now().date()}__api_user_data.yml'
|
|
100
|
+
|
|
101
|
+
with open(os.path.join(self.dir, result_file_name), 'w') as f:
|
|
102
|
+
dump(structure, f, sort_keys=False)
|
|
103
|
+
write_stdout(f' {FG_GREEN}saved:{NC} {os.path.relpath(f.name, os.getcwd())}')
|
|
104
|
+
|
|
105
|
+
write_stdout('')
|
|
106
|
+
write_stdout(f'{FG_GREEN}DONE{NC}')
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import ast
|
|
3
|
+
import csv
|
|
4
|
+
import importlib
|
|
5
|
+
import inspect
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Dict, Callable, Any, List, TextIO
|
|
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
|
+
app_host: str
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def add_parser_args(parser: argparse.ArgumentParser) -> None:
|
|
27
|
+
parser.add_argument('--api-dir', dest='api_dir', type=str, required=True)
|
|
28
|
+
parser.add_argument('--db-dir', dest='db_dir', type=str, required=True)
|
|
29
|
+
parser.add_argument('--app-host', dest='app_host', type=str, required=False, default="{baseUrl}")
|
|
30
|
+
parser.add_argument('--include-utils-api', dest='include_api_utils_doc', type=bool, required=False, default=False)
|
|
31
|
+
parser.add_argument('--include-utils-db', dest='include_db_utils_doc', type=bool, required=False, default=False)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def api_module(self) -> str:
|
|
35
|
+
return self.api_dir.replace('/', '.')
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def api_main_module(self) -> str:
|
|
39
|
+
return self.api_dir.replace('/', '.') + ".main"
|
|
40
|
+
|
|
41
|
+
def run(self) -> None:
|
|
42
|
+
root_folder = os.getcwd()
|
|
43
|
+
conf.APPLICATION_DIR = os.path.join(root_folder, self.api_dir) # because sdk uses this variable to load routes
|
|
44
|
+
current_app = self.load_flask_app(self.api_main_module)
|
|
45
|
+
api_utils_functions = self.load_functions(f'{self.api_dir}/utils')
|
|
46
|
+
conf.APPLICATION_DIR = os.path.join(root_folder, self.db_dir)
|
|
47
|
+
db_helper_functions = self.load_functions(f'{self.db_dir}/models_manager')
|
|
48
|
+
db_utils_functions = self.load_functions(f"{self.db_dir}/utils")
|
|
49
|
+
with current_app.app_context():
|
|
50
|
+
current_app.config['SERVER_NAME'] = self.app_host
|
|
51
|
+
csv_data: List[Dict[str, str]] = []
|
|
52
|
+
with current_app.app_context():
|
|
53
|
+
now = datetime.now().isoformat()
|
|
54
|
+
filename = f'.tmp/{now}-doc.md'
|
|
55
|
+
with open(filename, 'w') as file:
|
|
56
|
+
for api_route_id, flask_api_rule in enumerate(current_app.url_map.iter_rules()):
|
|
57
|
+
options = {}
|
|
58
|
+
for arg in flask_api_rule.arguments:
|
|
59
|
+
options[arg] = "[{0}]".format(arg)
|
|
60
|
+
api_route_methods = ','.join([method for method in flask_api_rule.methods if method not in ('HEAD', 'OPTIONS')]) # type: ignore
|
|
61
|
+
api_route_path = url_for(flask_api_rule.endpoint, **options).replace('%5B', '[').replace('%5D', ']')
|
|
62
|
+
func_object = current_app.view_functions[flask_api_rule.endpoint]
|
|
63
|
+
if not func_object.__module__.startswith(self.api_module):
|
|
64
|
+
continue
|
|
65
|
+
csv_data.append({
|
|
66
|
+
"api_path": api_route_path,
|
|
67
|
+
"api_methods": api_route_methods,
|
|
68
|
+
"api_function_name": func_object.__name__,
|
|
69
|
+
})
|
|
70
|
+
self.generate_documentation(
|
|
71
|
+
func_object,
|
|
72
|
+
file,
|
|
73
|
+
api_route_id=api_route_id,
|
|
74
|
+
api_route_path=api_route_path,
|
|
75
|
+
api_route_methods=api_route_methods,
|
|
76
|
+
loaded_db_helper_functions=db_helper_functions,
|
|
77
|
+
loaded_api_utils_functions=api_utils_functions,
|
|
78
|
+
loaded_db_utils_functions=db_utils_functions,
|
|
79
|
+
)
|
|
80
|
+
with open(f'.tmp/{now}-doc.csv', 'w') as csvfile:
|
|
81
|
+
fields = ['api_path', 'api_methods', 'api_function_name']
|
|
82
|
+
writer = csv.DictWriter(csvfile, fieldnames=fields)
|
|
83
|
+
writer.writeheader()
|
|
84
|
+
writer.writerows(csv_data)
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def load_functions(directory: str) -> Dict[str, Callable[..., Any]]:
|
|
88
|
+
function_name_object__map: dict[str, Callable[..., Any]] = {}
|
|
89
|
+
for root, _dirs, files in os.walk(directory):
|
|
90
|
+
for file in files:
|
|
91
|
+
py_postfix = '.py'
|
|
92
|
+
if file.endswith(py_postfix):
|
|
93
|
+
module_name = file[:-len(py_postfix)]
|
|
94
|
+
module_path = os.path.join(root, file)
|
|
95
|
+
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
|
96
|
+
assert spec is not None # only for mypy
|
|
97
|
+
assert spec.loader is not None # only for mypy
|
|
98
|
+
module = importlib.util.module_from_spec(spec)
|
|
99
|
+
spec.loader.exec_module(module)
|
|
100
|
+
functions = inspect.getmembers(module, inspect.isfunction)
|
|
101
|
+
for name, func in functions:
|
|
102
|
+
function_name_object__map[name] = func
|
|
103
|
+
return function_name_object__map
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def load_flask_app(api_sdk_module: str) -> Flask:
|
|
107
|
+
module = importlib.import_module(api_sdk_module)
|
|
108
|
+
return module.flask_app
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def find_called_functions_in_api(api_function_object: Callable[..., Any]) -> List[Any]:
|
|
112
|
+
calls = []
|
|
113
|
+
source = inspect.getsource(api_function_object)
|
|
114
|
+
tree = ast.parse(source)
|
|
115
|
+
for node in ast.walk(tree):
|
|
116
|
+
if isinstance(node, ast.Call):
|
|
117
|
+
if isinstance(node.func, ast.Name):
|
|
118
|
+
calls.append(node.func.id)
|
|
119
|
+
elif isinstance(node.func, ast.Attribute):
|
|
120
|
+
calls.append(node.func.attr)
|
|
121
|
+
else:
|
|
122
|
+
continue
|
|
123
|
+
return calls
|
|
124
|
+
|
|
125
|
+
def generate_documentation(
|
|
126
|
+
self,
|
|
127
|
+
func_object: Callable[..., Any],
|
|
128
|
+
file_object: TextIO,
|
|
129
|
+
*,
|
|
130
|
+
api_route_id: int,
|
|
131
|
+
api_route_path: str,
|
|
132
|
+
api_route_methods: str,
|
|
133
|
+
loaded_db_helper_functions: Dict[str, Callable[..., Any]],
|
|
134
|
+
loaded_db_utils_functions: Dict[str, Callable[..., Any]],
|
|
135
|
+
loaded_api_utils_functions: Dict[str, Callable[..., Any]],
|
|
136
|
+
) -> None:
|
|
137
|
+
func_name = func_object.__name__
|
|
138
|
+
functions_called_in_api_route = self.find_called_functions_in_api(func_object)
|
|
139
|
+
docstring = inspect.getdoc(func_object)
|
|
140
|
+
api_docstring = 'None' if docstring is None else docstring
|
|
141
|
+
|
|
142
|
+
file_object.write(f"## {api_route_id} Путь апи {api_route_path}\n\n")
|
|
143
|
+
file_object.write(f"#### Имя функции апи: {func_name}\n")
|
|
144
|
+
file_object.write(f"### Апи методы: {api_route_methods}\n\n")
|
|
145
|
+
file_object.write("**Описание апи метода:** \n\n")
|
|
146
|
+
file_object.write(f"```python\n{api_docstring}\n```\n")
|
|
147
|
+
helper_call = 1
|
|
148
|
+
for function_called_in_api_route in functions_called_in_api_route:
|
|
149
|
+
if function_called_in_api_route not in ('transaction_commit', 'and_', 'or_', 'foreign', 'query_soft_delete', 'ensure_db_object_exists', 'db_search'):
|
|
150
|
+
if function_called_in_api_route in loaded_db_helper_functions:
|
|
151
|
+
helper_func_obj = loaded_db_helper_functions[function_called_in_api_route]
|
|
152
|
+
helper_docstring = inspect.getdoc(helper_func_obj)
|
|
153
|
+
helper_docstring = 'None' if helper_docstring is None else helper_docstring
|
|
154
|
+
|
|
155
|
+
file_object.write(f"### {api_route_id}.{helper_call} Вызвана функция работы с БД : {function_called_in_api_route}\n")
|
|
156
|
+
file_object.write(f"**Описание функции {function_called_in_api_route}:**\n\n")
|
|
157
|
+
file_object.write(f"```python\n{helper_docstring}\n```\n")
|
|
158
|
+
helper_call += 1
|
|
159
|
+
elif function_called_in_api_route in loaded_api_utils_functions:
|
|
160
|
+
if self.include_api_utils_doc:
|
|
161
|
+
util_func_obj = loaded_api_utils_functions[function_called_in_api_route]
|
|
162
|
+
util_docstring = inspect.getdoc(util_func_obj)
|
|
163
|
+
util_docstring = 'None' if util_docstring is None else util_docstring
|
|
164
|
+
if 'db_tables_used' in util_docstring or 'db_table_used' in util_docstring:
|
|
165
|
+
file_object.write(f"### {api_route_id}.{helper_call} Вызвана функция работы с БД : {function_called_in_api_route}\n")
|
|
166
|
+
file_object.write(f"**Описание функции {function_called_in_api_route}:**\n\n")
|
|
167
|
+
file_object.write(f"```python\n{util_docstring}\n```\n")
|
|
168
|
+
helper_call += 1
|
|
169
|
+
elif function_called_in_api_route in loaded_db_utils_functions:
|
|
170
|
+
if self.include_db_utils_doc:
|
|
171
|
+
util_func_obj = loaded_db_utils_functions[function_called_in_api_route]
|
|
172
|
+
db_util_docstring = inspect.getdoc(util_func_obj)
|
|
173
|
+
db_util_docstring = 'None' if db_util_docstring is None else db_util_docstring
|
|
174
|
+
if 'db_tables_used' in db_util_docstring or 'db_table_used' in db_util_docstring:
|
|
175
|
+
file_object.write(f"### {api_route_id}.{helper_call} Вызвана функция работы с БД : {function_called_in_api_route}\n")
|
|
176
|
+
file_object.write(f"**Описание функции {function_called_in_api_route}:**\n\n")
|
|
177
|
+
file_object.write(f"```python\n{db_util_docstring}\n```\n")
|
|
178
|
+
helper_call += 1
|
|
179
|
+
file_object.write('-' * 20)
|
|
180
|
+
file_object.write('\n\n')
|
|
181
|
+
file_object.write('\n\n')
|