dmart 1.4.17__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.
- alembic.ini +117 -0
- api/__init__.py +0 -0
- api/info/__init__.py +0 -0
- api/info/router.py +109 -0
- api/managed/__init__.py +0 -0
- api/managed/router.py +1541 -0
- api/managed/utils.py +1879 -0
- api/public/__init__.py +0 -0
- api/public/router.py +758 -0
- api/qr/__init__.py +0 -0
- api/qr/router.py +108 -0
- api/user/__init__.py +0 -0
- api/user/model/__init__.py +0 -0
- api/user/model/errors.py +14 -0
- api/user/model/requests.py +165 -0
- api/user/model/responses.py +11 -0
- api/user/router.py +1413 -0
- api/user/service.py +270 -0
- bundler.py +55 -0
- config/__init__.py +0 -0
- config/channels.json +11 -0
- config/notification.json +17 -0
- cxb/__init__.py +0 -0
- cxb/client/__init__.py +0 -0
- cxb/client/assets/@codemirror-Rn7_6DkE.js +10 -0
- cxb/client/assets/@edraj-CS4NwVbD.js +1 -0
- cxb/client/assets/@floating-ui-BwwcF-xh.js +1 -0
- cxb/client/assets/@formatjs-yKEsAtjs.js +1 -0
- cxb/client/assets/@fortawesome-DRW1UCdr.js +9 -0
- cxb/client/assets/@jsonquerylang-laKNoFFq.js +12 -0
- cxb/client/assets/@lezer-za4Q-8Ew.js +1 -0
- cxb/client/assets/@marijn-DXwl3gUT.js +1 -0
- cxb/client/assets/@popperjs-l0sNRNKZ.js +1 -0
- cxb/client/assets/@replit--ERk53eB.js +1 -0
- cxb/client/assets/@roxi-CGMFK4i8.js +6 -0
- cxb/client/assets/@typewriter-cCzskkIv.js +17 -0
- cxb/client/assets/@zerodevx-BlBZjKxu.js +1 -0
- cxb/client/assets/@zerodevx-CVEpe6WZ.css +1 -0
- cxb/client/assets/BreadCrumbLite-DAhOx38v.js +1 -0
- cxb/client/assets/EntryRenderer-25YDhRen.js +32 -0
- cxb/client/assets/EntryRenderer-DXytdFp9.css +1 -0
- cxb/client/assets/ListView-BpAycA2h.js +16 -0
- cxb/client/assets/ListView-U8of-_c-.css +1 -0
- cxb/client/assets/Prism--hMplq-p.js +3 -0
- cxb/client/assets/Prism-Uh6uStUw.css +1 -0
- cxb/client/assets/Table2Cols-BsbwicQm.js +1 -0
- cxb/client/assets/_..-BvT6vdHa.css +1 -0
- cxb/client/assets/_...404_-fuLH_rX9.js +2 -0
- cxb/client/assets/_...fallback_-Ba_NLmAE.js +1 -0
- cxb/client/assets/_module-Bfk8MiCs.js +3 -0
- cxb/client/assets/_module-CEW0D5oI.js +4 -0
- cxb/client/assets/_module-Dgq0ZVtz.js +1 -0
- cxb/client/assets/ajv-Cpj98o6Y.js +1 -0
- cxb/client/assets/axios-CG2WSiiR.js +6 -0
- cxb/client/assets/clsx-B-dksMZM.js +1 -0
- cxb/client/assets/codemirror-wrapped-line-indent-DPhKvljI.js +1 -0
- cxb/client/assets/compare-C3AjiGFR.js +1 -0
- cxb/client/assets/compute-scroll-into-view-Bl8rNFhg.js +1 -0
- cxb/client/assets/consolite-DlCuI0F9.js +1 -0
- cxb/client/assets/crelt-C8TCjufn.js +1 -0
- cxb/client/assets/date-fns-l0sNRNKZ.js +1 -0
- cxb/client/assets/deepmerge-rn4rBaHU.js +1 -0
- cxb/client/assets/dmart_services-AL6-IdDE.js +1 -0
- cxb/client/assets/downloadFile-D08i0YDh.js +1 -0
- cxb/client/assets/easy-signal-BiPFIK3O.js +1 -0
- cxb/client/assets/esm-env-rsSWfq8L.js +1 -0
- cxb/client/assets/export-OF_rTiXu.js +1 -0
- cxb/client/assets/fast-deep-equal-l0sNRNKZ.js +1 -0
- cxb/client/assets/fast-diff-C-IidNf4.js +1 -0
- cxb/client/assets/fast-uri-l0sNRNKZ.js +1 -0
- cxb/client/assets/flowbite-svelte-BLvjb-sa.js +1 -0
- cxb/client/assets/flowbite-svelte-CD54FDqW.css +1 -0
- cxb/client/assets/flowbite-svelte-icons-BI8GVhw_.js +1 -0
- cxb/client/assets/github-slugger-CQ4oX9Ud.js +1 -0
- cxb/client/assets/global-igKv-1g9.js +1 -0
- cxb/client/assets/hookar-BMRD9G9H.js +1 -0
- cxb/client/assets/immutable-json-patch-DtRO2E_S.js +1 -0
- cxb/client/assets/import-1vE3gBat.js +1 -0
- cxb/client/assets/index-B-eTh-ZX.js +1 -0
- cxb/client/assets/index-BVyxzKtH.js +1 -0
- cxb/client/assets/index-BdeNM69f.js +1 -0
- cxb/client/assets/index-C6cPO4op.js +1 -0
- cxb/client/assets/index-CC-A1ipE.js +1 -0
- cxb/client/assets/index-CTxJ-lDp.js +1 -0
- cxb/client/assets/index-Cd-F5j_k.js +1 -0
- cxb/client/assets/index-D742rwaM.js +1 -0
- cxb/client/assets/index-DTfhnhwd.js +1 -0
- cxb/client/assets/index-DdXRK7n9.js +2 -0
- cxb/client/assets/index-DtiCmB4o.js +1 -0
- cxb/client/assets/index-NBrXBlLA.css +2 -0
- cxb/client/assets/index-ac-Buu_H.js +4 -0
- cxb/client/assets/index-iYkH7C67.js +1 -0
- cxb/client/assets/info-B986lRiM.js +1 -0
- cxb/client/assets/intl-messageformat-Dc5UU-HB.js +3 -0
- cxb/client/assets/jmespath-l0sNRNKZ.js +1 -0
- cxb/client/assets/json-schema-traverse-l0sNRNKZ.js +1 -0
- cxb/client/assets/json-source-map-DRgZidqy.js +5 -0
- cxb/client/assets/jsonpath-plus-l0sNRNKZ.js +1 -0
- cxb/client/assets/jsonrepair-B30Dx381.js +8 -0
- cxb/client/assets/lodash-es-DZVAA2ox.js +1 -0
- cxb/client/assets/marked-DKjyhwJX.js +56 -0
- cxb/client/assets/marked-gfm-heading-id-U5zO829x.js +2 -0
- cxb/client/assets/marked-mangle-CDMeiHC6.js +1 -0
- cxb/client/assets/memoize-one-BdPwpGay.js +1 -0
- cxb/client/assets/natural-compare-lite-Bg2Xcf-o.js +7 -0
- cxb/client/assets/pagination-svelte-D5CyoiE_.js +13 -0
- cxb/client/assets/pagination-svelte-v10nAbbM.css +1 -0
- cxb/client/assets/plantuml-encoder-C47mzt9T.js +1 -0
- cxb/client/assets/prismjs-DTUiLGJu.js +9 -0
- cxb/client/assets/profile-BUf-tKMe.js +1 -0
- cxb/client/assets/query-CNmXTsgf.js +1 -0
- cxb/client/assets/queryHelpers-C9iBWwqe.js +1 -0
- cxb/client/assets/scroll-into-view-if-needed-KR58zyjF.js +1 -0
- cxb/client/assets/spaces-0oyGvpii.js +1 -0
- cxb/client/assets/style-mod-Bs6eFhZE.js +3 -0
- cxb/client/assets/svelte-B2XmcTi_.js +4 -0
- cxb/client/assets/svelte-awesome-COLlx0DN.css +1 -0
- cxb/client/assets/svelte-awesome-DhnMA6Q_.js +1 -0
- cxb/client/assets/svelte-datatables-net-CY7LBj6I.js +1 -0
- cxb/client/assets/svelte-floating-ui-BlS3sOAQ.js +1 -0
- cxb/client/assets/svelte-i18n-CT2KkQaN.js +3 -0
- cxb/client/assets/svelte-jsoneditor-BzfX6Usi.css +1 -0
- cxb/client/assets/svelte-jsoneditor-CUGSvWId.js +25 -0
- cxb/client/assets/svelte-select-CegQKzqH.css +1 -0
- cxb/client/assets/svelte-select-CjHAt_85.js +6 -0
- cxb/client/assets/tailwind-merge-CJvxXMcu.js +1 -0
- cxb/client/assets/tailwind-variants-Cj20BoQ3.js +1 -0
- cxb/client/assets/toast-B9WDyfyI.js +1 -0
- cxb/client/assets/tslib-pJfR_DrR.js +1 -0
- cxb/client/assets/typewriter-editor-DkTVIJdm.js +25 -0
- cxb/client/assets/user-DeK_NB5v.js +1 -0
- cxb/client/assets/vanilla-picker-l5rcX3cq.js +8 -0
- cxb/client/assets/w3c-keyname-Vcq4gwWv.js +1 -0
- cxb/client/config.json +11 -0
- cxb/client/config.sample.json +11 -0
- cxb/client/favicon.ico +0 -0
- cxb/client/favicon.png +0 -0
- cxb/client/index.html +28 -0
- data_adapters/__init__.py +0 -0
- data_adapters/adapter.py +16 -0
- data_adapters/base_data_adapter.py +467 -0
- data_adapters/file/__init__.py +0 -0
- data_adapters/file/adapter.py +2043 -0
- data_adapters/file/adapter_helpers.py +1013 -0
- data_adapters/file/archive.py +150 -0
- data_adapters/file/create_index.py +331 -0
- data_adapters/file/create_users_folders.py +52 -0
- data_adapters/file/custom_validations.py +68 -0
- data_adapters/file/drop_index.py +40 -0
- data_adapters/file/health_check.py +560 -0
- data_adapters/file/redis_services.py +1110 -0
- data_adapters/helpers.py +27 -0
- data_adapters/sql/__init__.py +0 -0
- data_adapters/sql/adapter.py +3218 -0
- data_adapters/sql/adapter_helpers.py +491 -0
- data_adapters/sql/create_tables.py +451 -0
- data_adapters/sql/create_users_folders.py +53 -0
- data_adapters/sql/db_to_json_migration.py +485 -0
- data_adapters/sql/health_check_sql.py +232 -0
- data_adapters/sql/json_to_db_migration.py +454 -0
- data_adapters/sql/update_query_policies.py +101 -0
- data_generator.py +81 -0
- dmart-1.4.17.dist-info/METADATA +65 -0
- dmart-1.4.17.dist-info/RECORD +289 -0
- dmart-1.4.17.dist-info/WHEEL +5 -0
- dmart-1.4.17.dist-info/entry_points.txt +2 -0
- dmart-1.4.17.dist-info/top_level.txt +24 -0
- dmart.py +623 -0
- dmart_migrations/README +1 -0
- dmart_migrations/__init__.py +0 -0
- dmart_migrations/__pycache__/__init__.cpython-314.pyc +0 -0
- dmart_migrations/__pycache__/env.cpython-314.pyc +0 -0
- dmart_migrations/env.py +100 -0
- dmart_migrations/notes.txt +11 -0
- dmart_migrations/script.py.mako +28 -0
- dmart_migrations/scripts/__init__.py +0 -0
- dmart_migrations/scripts/calculate_checksums.py +77 -0
- dmart_migrations/scripts/migration_f7a4949eed19.py +28 -0
- dmart_migrations/versions/0f3d2b1a7c21_add_authz_materialized_views.py +87 -0
- dmart_migrations/versions/10d2041b94d4_last_checksum_history.py +62 -0
- dmart_migrations/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py +33 -0
- dmart_migrations/versions/26bfe19b49d4_rm_failedloginattempts.py +42 -0
- dmart_migrations/versions/3c8bca2219cc_add_otp_table.py +38 -0
- dmart_migrations/versions/6675fd9dfe42_remove_unique_from_sessions_table.py +36 -0
- dmart_migrations/versions/71bc1df82e6a_adding_user_last_login_at.py +43 -0
- dmart_migrations/versions/74288ccbd3b5_initial.py +264 -0
- dmart_migrations/versions/7520a89a8467_rm_activesession_table.py +39 -0
- dmart_migrations/versions/848b623755a4_make_created_nd_updated_at_required.py +138 -0
- dmart_migrations/versions/8640dcbebf85_add_notes_to_users.py +32 -0
- dmart_migrations/versions/91c94250232a_adding_fk_on_owner_shortname.py +104 -0
- dmart_migrations/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py +66 -0
- dmart_migrations/versions/9aae9138c4ef_indexing_created_at_updated_at.py +80 -0
- dmart_migrations/versions/__init__.py +0 -0
- dmart_migrations/versions/__pycache__/0f3d2b1a7c21_add_authz_materialized_views.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/10d2041b94d4_last_checksum_history.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/26bfe19b49d4_rm_failedloginattempts.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/3c8bca2219cc_add_otp_table.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/6675fd9dfe42_remove_unique_from_sessions_table.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/71bc1df82e6a_adding_user_last_login_at.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/74288ccbd3b5_initial.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/7520a89a8467_rm_activesession_table.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/848b623755a4_make_created_nd_updated_at_required.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/8640dcbebf85_add_notes_to_users.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/91c94250232a_adding_fk_on_owner_shortname.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/98ecd6f56f9a_ext_meta_with_owner_group_shortname.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/9aae9138c4ef_indexing_created_at_updated_at.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/b53f916b3f6d_json_to_jsonb.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/eb5f1ec65156_adding_user_locked_to_device.cpython-314.pyc +0 -0
- dmart_migrations/versions/__pycache__/f7a4949eed19_adding_query_policies_to_meta.cpython-314.pyc +0 -0
- dmart_migrations/versions/b53f916b3f6d_json_to_jsonb.py +492 -0
- dmart_migrations/versions/eb5f1ec65156_adding_user_locked_to_device.py +36 -0
- dmart_migrations/versions/f7a4949eed19_adding_query_policies_to_meta.py +60 -0
- get_settings.py +7 -0
- info.json +1 -0
- languages/__init__.py +0 -0
- languages/arabic.json +15 -0
- languages/english.json +16 -0
- languages/kurdish.json +14 -0
- languages/loader.py +12 -0
- main.py +560 -0
- migrate.py +24 -0
- models/__init__.py +0 -0
- models/api.py +203 -0
- models/core.py +597 -0
- models/enums.py +255 -0
- password_gen.py +8 -0
- plugins/__init__.py +0 -0
- plugins/action_log/__init__.py +0 -0
- plugins/action_log/plugin.py +121 -0
- plugins/admin_notification_sender/__init__.py +0 -0
- plugins/admin_notification_sender/plugin.py +124 -0
- plugins/ldap_manager/__init__.py +0 -0
- plugins/ldap_manager/plugin.py +100 -0
- plugins/local_notification/__init__.py +0 -0
- plugins/local_notification/plugin.py +123 -0
- plugins/realtime_updates_notifier/__init__.py +0 -0
- plugins/realtime_updates_notifier/plugin.py +58 -0
- plugins/redis_db_update/__init__.py +0 -0
- plugins/redis_db_update/plugin.py +188 -0
- plugins/resource_folders_creation/__init__.py +0 -0
- plugins/resource_folders_creation/plugin.py +81 -0
- plugins/system_notification_sender/__init__.py +0 -0
- plugins/system_notification_sender/plugin.py +188 -0
- plugins/update_access_controls/__init__.py +0 -0
- plugins/update_access_controls/plugin.py +9 -0
- pytests/__init__.py +0 -0
- pytests/api_user_models_erros_test.py +16 -0
- pytests/api_user_models_requests_test.py +98 -0
- pytests/archive_test.py +72 -0
- pytests/base_test.py +300 -0
- pytests/get_settings_test.py +14 -0
- pytests/json_to_db_migration_test.py +237 -0
- pytests/service_test.py +26 -0
- pytests/test_info.py +55 -0
- pytests/test_status.py +15 -0
- run_notification_campaign.py +85 -0
- scheduled_notification_handler.py +121 -0
- schema_migration.py +208 -0
- schema_modulate.py +192 -0
- set_admin_passwd.py +55 -0
- sync.py +202 -0
- utils/__init__.py +0 -0
- utils/access_control.py +306 -0
- utils/async_request.py +8 -0
- utils/exporter.py +309 -0
- utils/firebase_notifier.py +57 -0
- utils/generate_email.py +37 -0
- utils/helpers.py +352 -0
- utils/hypercorn_config.py +12 -0
- utils/internal_error_code.py +60 -0
- utils/jwt.py +124 -0
- utils/logger.py +167 -0
- utils/middleware.py +99 -0
- utils/notification.py +75 -0
- utils/password_hashing.py +16 -0
- utils/plugin_manager.py +202 -0
- utils/query_policies_helper.py +128 -0
- utils/regex.py +44 -0
- utils/repository.py +529 -0
- utils/router_helper.py +19 -0
- utils/settings.py +166 -0
- utils/sms_notifier.py +21 -0
- utils/social_sso.py +67 -0
- utils/templates/activation.html.j2 +26 -0
- utils/templates/reminder.html.j2 +17 -0
- utils/ticket_sys_utils.py +203 -0
- utils/web_notifier.py +29 -0
- websocket.py +231 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
#!/usr/bin/env -S BACKEND_ENV=config.env python3
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from jsonschema.validators import Draft7Validator
|
|
14
|
+
from redis.commands.search.query import Query
|
|
15
|
+
from redis.commands.search.result import Result
|
|
16
|
+
|
|
17
|
+
from api.managed.router import serve_request
|
|
18
|
+
from models.core import Folder
|
|
19
|
+
from utils import repository
|
|
20
|
+
from data_adapters.adapter import data_adapter as db
|
|
21
|
+
from data_adapters.file.custom_validations import get_schema_path
|
|
22
|
+
from utils.helpers import camel_case
|
|
23
|
+
from data_adapters.file.redis_services import RedisServices
|
|
24
|
+
from models import core, api
|
|
25
|
+
from models.enums import ContentType, RequestType, ResourceType
|
|
26
|
+
from utils.settings import settings
|
|
27
|
+
|
|
28
|
+
duplicated_entries : dict= {}
|
|
29
|
+
|
|
30
|
+
key_entries: dict = {}
|
|
31
|
+
MAX_INVALID_SIZE = 100
|
|
32
|
+
|
|
33
|
+
# {"space_name": {"schema_name": SCHEMA_DATA_DICT}}
|
|
34
|
+
spaces_schemas: dict[str, dict[str, dict]] = {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def main(health_type: str, space_param: str, schemas_param: list):
|
|
38
|
+
try:
|
|
39
|
+
spaces = await db.get_spaces()
|
|
40
|
+
if space_param != "all" and space_param not in spaces:
|
|
41
|
+
print("space name is not found")
|
|
42
|
+
return
|
|
43
|
+
if space_param == "all":
|
|
44
|
+
for space in spaces:
|
|
45
|
+
await main(health_type, space, schemas_param)
|
|
46
|
+
return
|
|
47
|
+
space_obj = core.Space.model_validate_json(spaces[space_param])
|
|
48
|
+
if not space_obj.check_health:
|
|
49
|
+
print(f"EARLY EXIT, health check disabled for space {space_param}")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
await cleanup_spaces()
|
|
53
|
+
is_full: bool = True if not args.space or args.space == 'all' else False
|
|
54
|
+
print_header()
|
|
55
|
+
if health_type == 'soft':
|
|
56
|
+
print("Running soft healthcheck")
|
|
57
|
+
if not schemas_param and not is_full:
|
|
58
|
+
print('Add the space name and at least one schema')
|
|
59
|
+
return
|
|
60
|
+
if is_full:
|
|
61
|
+
params = await load_spaces_schemas(space_param)
|
|
62
|
+
else:
|
|
63
|
+
params = {space_param: schemas_param}
|
|
64
|
+
for space in params:
|
|
65
|
+
print(f'>>>> Processing {space:<10} <<<<')
|
|
66
|
+
before_time = time.time()
|
|
67
|
+
health_check : dict[str, list|dict] | None = {'invalid_folders': [], 'folders_report': {}}
|
|
68
|
+
for schema in params.get(space, []):
|
|
69
|
+
health_check_res = await soft_health_check(space, schema)
|
|
70
|
+
if health_check_res and health_check and isinstance(health_check['folders_report'], dict):
|
|
71
|
+
health_check['folders_report'].update(health_check_res.get('folders_report', {}))
|
|
72
|
+
print_health_check(health_check)
|
|
73
|
+
await save_health_check_entry(health_check, space)
|
|
74
|
+
print(f'Completed in: {"{:.2f}".format(time.time() - before_time)} sec')
|
|
75
|
+
|
|
76
|
+
elif not health_type or health_type == 'hard':
|
|
77
|
+
print("Running hard healthcheck")
|
|
78
|
+
spaces = {space_param : {}}
|
|
79
|
+
if is_full:
|
|
80
|
+
spaces = await db.get_spaces()
|
|
81
|
+
for space in spaces:
|
|
82
|
+
print(f'>>>> Processing {space:<10} <<<<')
|
|
83
|
+
before_time = time.time()
|
|
84
|
+
health_check = await hard_health_check(space)
|
|
85
|
+
if health_check:
|
|
86
|
+
await save_health_check_entry(health_check, space)
|
|
87
|
+
print_health_check(health_check)
|
|
88
|
+
print(f'Completed in: {"{:.2f}".format(time.time() - before_time)} sec')
|
|
89
|
+
else:
|
|
90
|
+
print("Wrong mode specify [soft or hard]")
|
|
91
|
+
return
|
|
92
|
+
await save_duplicated_entries()
|
|
93
|
+
finally:
|
|
94
|
+
await RedisServices().close_pool()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def print_header() -> None:
|
|
98
|
+
print("{:<32} {:<6} {:<6}".format(
|
|
99
|
+
'subpath',
|
|
100
|
+
'valid',
|
|
101
|
+
'invalid')
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def print_health_check(health_check):
|
|
106
|
+
if health_check:
|
|
107
|
+
for schema_path, val in health_check.get('folders_report', {}).items():
|
|
108
|
+
valid = val.get('valid_entries', 0)
|
|
109
|
+
invalid = len(val.get('invalid_entries', []))
|
|
110
|
+
print("{:<32} {:<6} {:<6}".format(
|
|
111
|
+
schema_path,
|
|
112
|
+
valid,
|
|
113
|
+
invalid)
|
|
114
|
+
)
|
|
115
|
+
for one in val.get("invalid_entries", []):
|
|
116
|
+
print(f"\t\t\t\tInvalid item/issues: {one.get('shortname', 'n/a')}/"
|
|
117
|
+
f"{','.join(one.get('issues', []))} - {one.get('exception','')}")
|
|
118
|
+
if health_check.get('invalid_folders'):
|
|
119
|
+
print('Invalid folders :')
|
|
120
|
+
for val in health_check.get('invalid_folders'):
|
|
121
|
+
print(f"\t\t\t\t {val}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
async def load_spaces_schemas(for_space: str | None = None) -> dict:
|
|
125
|
+
global spaces_schemas
|
|
126
|
+
if spaces_schemas:
|
|
127
|
+
return spaces_schemas
|
|
128
|
+
spaces = await db.get_spaces()
|
|
129
|
+
if for_space and for_space != "all" and for_space in spaces:
|
|
130
|
+
spaces_schemas[for_space] = load_space_schemas(for_space)
|
|
131
|
+
return spaces_schemas
|
|
132
|
+
for space_name in spaces:
|
|
133
|
+
schemas = load_space_schemas(space_name)
|
|
134
|
+
if schemas:
|
|
135
|
+
spaces_schemas[space_name] = schemas
|
|
136
|
+
return spaces_schemas
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def load_space_schemas(space_name: str) -> dict[str, dict]:
|
|
140
|
+
schemas: dict[str, dict] = {}
|
|
141
|
+
schemas_path = Path(settings.spaces_folder / space_name / "schema" / ".dm")
|
|
142
|
+
if not schemas_path.is_dir():
|
|
143
|
+
return {}
|
|
144
|
+
for entry in os.scandir(schemas_path):
|
|
145
|
+
if not entry.is_dir():
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
schema_path_meta = Path(schemas_path / entry.name / "meta.schema.json")
|
|
149
|
+
if not schema_path_meta.is_file():
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
schema_meta = json.loads(schema_path_meta.read_text())
|
|
153
|
+
if schema_meta.get("payload", {}).get('body'):
|
|
154
|
+
schema_path_body = get_schema_path(
|
|
155
|
+
space_name=space_name,
|
|
156
|
+
schema_shortname=schema_meta.get("payload").get('body'),
|
|
157
|
+
)
|
|
158
|
+
if schema_path_body.is_file():
|
|
159
|
+
schemas[schema_meta['shortname']] = json.loads(schema_path_body.read_text())
|
|
160
|
+
return schemas
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def soft_health_check(
|
|
164
|
+
space_name: str,
|
|
165
|
+
schema_name: str,
|
|
166
|
+
):
|
|
167
|
+
global spaces_schemas
|
|
168
|
+
if space_name not in spaces_schemas:
|
|
169
|
+
await load_spaces_schemas(space_name)
|
|
170
|
+
|
|
171
|
+
schemas = spaces_schemas[space_name]
|
|
172
|
+
|
|
173
|
+
limit = 1000
|
|
174
|
+
offset = 0
|
|
175
|
+
folders_report : dict = {}
|
|
176
|
+
async with RedisServices() as redis:
|
|
177
|
+
try:
|
|
178
|
+
ft_index = redis.ft(f"{space_name}:{schema_name}")
|
|
179
|
+
await ft_index.info()
|
|
180
|
+
except Exception:
|
|
181
|
+
if 'meta_schema' not in schema_name:
|
|
182
|
+
print(f"can't find index: `{space_name}:{schema_name}`")
|
|
183
|
+
return None
|
|
184
|
+
while True:
|
|
185
|
+
search_query = Query(query_string="*")
|
|
186
|
+
search_query.paging(offset, limit)
|
|
187
|
+
offset += limit
|
|
188
|
+
x = await ft_index.search(query=search_query) # type: ignore
|
|
189
|
+
if x and isinstance(x, dict) and "results" in x:
|
|
190
|
+
res_data : list = [one["extra_attributes"]["$"] for one in x["results"] if "extra_attributes" in one]
|
|
191
|
+
if not res_data:
|
|
192
|
+
break
|
|
193
|
+
else:
|
|
194
|
+
break
|
|
195
|
+
for redis_doc_dict in res_data:
|
|
196
|
+
redis_doc_dict = json.loads(redis_doc_dict)
|
|
197
|
+
subpath = redis_doc_dict['subpath']
|
|
198
|
+
meta_doc_content = {}
|
|
199
|
+
payload_doc_content = {}
|
|
200
|
+
resource_class = getattr(
|
|
201
|
+
sys.modules["models.core"],
|
|
202
|
+
camel_case(redis_doc_dict["resource_type"]),
|
|
203
|
+
)
|
|
204
|
+
system_attributes = [
|
|
205
|
+
"payload_string",
|
|
206
|
+
"query_policies",
|
|
207
|
+
"subpath",
|
|
208
|
+
"resource_type",
|
|
209
|
+
"meta_doc_id",
|
|
210
|
+
"payload_doc_id",
|
|
211
|
+
]
|
|
212
|
+
class_fields = resource_class.model_fields.keys()
|
|
213
|
+
for key, value in redis_doc_dict.items():
|
|
214
|
+
if key in class_fields:
|
|
215
|
+
meta_doc_content[key] = value
|
|
216
|
+
elif key not in system_attributes:
|
|
217
|
+
payload_doc_content[key] = value
|
|
218
|
+
|
|
219
|
+
if not payload_doc_content and redis_doc_dict.get("payload_doc_id"):
|
|
220
|
+
payload_redis_doc = await redis.get_doc_by_id(
|
|
221
|
+
redis_doc_dict["payload_doc_id"]
|
|
222
|
+
)
|
|
223
|
+
if payload_redis_doc:
|
|
224
|
+
not_payload_attr = system_attributes + list(class_fields)
|
|
225
|
+
for key, value in payload_redis_doc.items():
|
|
226
|
+
if key not in not_payload_attr:
|
|
227
|
+
payload_doc_content[key] = value
|
|
228
|
+
|
|
229
|
+
meta_doc_content["created_at"] = datetime.fromtimestamp(
|
|
230
|
+
meta_doc_content["created_at"]
|
|
231
|
+
)
|
|
232
|
+
meta_doc_content["updated_at"] = datetime.fromtimestamp(
|
|
233
|
+
meta_doc_content["updated_at"]
|
|
234
|
+
)
|
|
235
|
+
if not folders_report.get(subpath):
|
|
236
|
+
folders_report[subpath] = {}
|
|
237
|
+
|
|
238
|
+
meta = None
|
|
239
|
+
status = {
|
|
240
|
+
'is_valid': True,
|
|
241
|
+
"invalid": {
|
|
242
|
+
"issues": [],
|
|
243
|
+
"uuid": redis_doc_dict.get("uuid"),
|
|
244
|
+
"shortname": redis_doc_dict.get("shortname"),
|
|
245
|
+
"resource_type": redis_doc_dict["resource_type"],
|
|
246
|
+
"exception": ""
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
try:
|
|
250
|
+
meta = resource_class.model_validate(meta_doc_content)
|
|
251
|
+
except Exception as ex:
|
|
252
|
+
status['is_valid'] = False
|
|
253
|
+
if not isinstance(status, dict) and isinstance(status["invalid"], dict):
|
|
254
|
+
status['invalid']['exception'] = str(ex)
|
|
255
|
+
status['invalid']['issues'].append('meta')
|
|
256
|
+
if meta:
|
|
257
|
+
try:
|
|
258
|
+
if meta.payload and meta.payload.schema_shortname and payload_doc_content is not None:
|
|
259
|
+
schema_dict: dict = schemas.get(meta.payload.schema_shortname, {})
|
|
260
|
+
if schema_dict:
|
|
261
|
+
Draft7Validator(schema_dict).validate(payload_doc_content)
|
|
262
|
+
else:
|
|
263
|
+
continue
|
|
264
|
+
if(
|
|
265
|
+
meta.payload.checksum and
|
|
266
|
+
meta.payload.client_checksum and
|
|
267
|
+
meta.payload.checksum != meta.payload.client_checksum
|
|
268
|
+
):
|
|
269
|
+
raise Exception(
|
|
270
|
+
f"payload.checksum not equal payload.client_checksum {subpath}/{meta.shortname}"
|
|
271
|
+
)
|
|
272
|
+
if folders_report[subpath].get('valid_entries'):
|
|
273
|
+
folders_report[subpath]['valid_entries'] += 1
|
|
274
|
+
else:
|
|
275
|
+
folders_report[subpath]['valid_entries'] = 1
|
|
276
|
+
status['is_valid'] = True
|
|
277
|
+
except Exception as ex:
|
|
278
|
+
status['is_valid'] = False
|
|
279
|
+
if not isinstance(status, dict) and isinstance(status["invalid"], dict):
|
|
280
|
+
status['invalid']['exception'] = str(ex)
|
|
281
|
+
status['invalid']['issues'].append('payload')
|
|
282
|
+
|
|
283
|
+
if not status['is_valid']:
|
|
284
|
+
if not folders_report.get(subpath, {}).get('invalid_entries'):
|
|
285
|
+
folders_report[subpath]['invalid_entries'] = []
|
|
286
|
+
if meta_doc_content["shortname"] not in folders_report[redis_doc_dict['subpath']]["invalid_entries"]:
|
|
287
|
+
if len(folders_report[redis_doc_dict['subpath']]["invalid_entries"]) >= MAX_INVALID_SIZE:
|
|
288
|
+
break
|
|
289
|
+
folders_report[redis_doc_dict['subpath']]["invalid_entries"].append(status.get('invalid'))
|
|
290
|
+
|
|
291
|
+
# uuid = redis_doc_dict['uuid'][:8]
|
|
292
|
+
# await collect_duplicated_with_key('uuid', uuid)
|
|
293
|
+
# if redis_doc_dict.get('slug'):
|
|
294
|
+
# await collect_duplicated_with_key('slug', redis_doc_dict.get('slug'))
|
|
295
|
+
|
|
296
|
+
return {"invalid_folders": [], "folders_report": folders_report}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
async def collect_duplicated_with_key(key, value) -> None:
|
|
300
|
+
spaces = await db.get_spaces()
|
|
301
|
+
async with RedisServices() as redis:
|
|
302
|
+
for space_name, space_data in spaces.items():
|
|
303
|
+
space_data = json.loads(space_data)
|
|
304
|
+
try:
|
|
305
|
+
ft_index = redis.ft(f"{space_name}:meta")
|
|
306
|
+
await ft_index.info()
|
|
307
|
+
except Exception:
|
|
308
|
+
continue
|
|
309
|
+
search_query = Query(query_string=f"@{key}:{value}*")
|
|
310
|
+
search_query.paging(0, 1000)
|
|
311
|
+
x = await ft_index.search(query=search_query) # type: ignore
|
|
312
|
+
if x and isinstance(x, Result):
|
|
313
|
+
res_data: Result = x
|
|
314
|
+
for redis_doc_dict in res_data.docs:
|
|
315
|
+
redis_doc_dict = json.loads(redis_doc_dict.json)
|
|
316
|
+
if isinstance(redis_doc_dict, dict):
|
|
317
|
+
if redis_doc_dict['subpath'] == '/':
|
|
318
|
+
redis_doc_dict['subpath'] = ''
|
|
319
|
+
loc = space_name + "/" + redis_doc_dict['subpath']
|
|
320
|
+
if key not in key_entries:
|
|
321
|
+
key_entries[key] = {}
|
|
322
|
+
if not key_entries[key].get(value):
|
|
323
|
+
key_entries[key][value] = loc
|
|
324
|
+
else:
|
|
325
|
+
if not duplicated_entries.get(key):
|
|
326
|
+
duplicated_entries[key] = {}
|
|
327
|
+
if not duplicated_entries[key].get(value) or \
|
|
328
|
+
key_entries[key][value] not in duplicated_entries[key][value]['loc']:
|
|
329
|
+
duplicated_entries[key][value] = {}
|
|
330
|
+
duplicated_entries[key][value]['loc'] = [key_entries[key][value]]
|
|
331
|
+
duplicated_entries[key][value]['total'] = 1
|
|
332
|
+
if loc not in duplicated_entries[key][value]['loc']:
|
|
333
|
+
duplicated_entries[key][value]['total'] += 1
|
|
334
|
+
duplicated_entries[key][value]['loc'].append(loc)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
async def hard_health_check(space_name: str):
|
|
338
|
+
spaces = await db.get_spaces()
|
|
339
|
+
if space_name not in spaces:
|
|
340
|
+
print("space name is not found")
|
|
341
|
+
return None
|
|
342
|
+
# remove checking check_health value from meta space
|
|
343
|
+
# space_obj = core.Space.model_validate_json(spaces[space_name])
|
|
344
|
+
# if not space_obj.check_health:
|
|
345
|
+
# print(f"EARLY EXIT, health check disabled for space {space_name}")
|
|
346
|
+
# return None
|
|
347
|
+
|
|
348
|
+
invalid_folders : list = []
|
|
349
|
+
folders_report: dict[str, dict] = {}
|
|
350
|
+
meta_folders_health: list = []
|
|
351
|
+
|
|
352
|
+
path = settings.spaces_folder / space_name
|
|
353
|
+
|
|
354
|
+
subpaths = os.scandir(path)
|
|
355
|
+
# print(f"{path=} {subpaths=}")
|
|
356
|
+
for subpath in subpaths:
|
|
357
|
+
if subpath.is_file():
|
|
358
|
+
continue
|
|
359
|
+
|
|
360
|
+
await repository.validate_subpath_data(
|
|
361
|
+
space_name=space_name,
|
|
362
|
+
subpath=subpath.path,
|
|
363
|
+
user_shortname='dmart',
|
|
364
|
+
invalid_folders=invalid_folders,
|
|
365
|
+
folders_report=folders_report,
|
|
366
|
+
meta_folders_health=meta_folders_health,
|
|
367
|
+
max_invalid_size=MAX_INVALID_SIZE
|
|
368
|
+
)
|
|
369
|
+
res = {"invalid_folders": invalid_folders, "folders_report": folders_report}
|
|
370
|
+
if meta_folders_health:
|
|
371
|
+
res['invalid_folders'] = meta_folders_health
|
|
372
|
+
return res
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
async def save_health_check_entry(health_check, space_name: str):
|
|
376
|
+
meta_path = Path(settings.spaces_folder / "management/health_check/.dm" / space_name)
|
|
377
|
+
entry_path = Path(settings.spaces_folder / "management/health_check" / f"{space_name}.json")
|
|
378
|
+
if meta_path.is_dir():
|
|
379
|
+
shutil.rmtree(meta_path)
|
|
380
|
+
if entry_path.is_file():
|
|
381
|
+
os.remove(entry_path)
|
|
382
|
+
await serve_request(
|
|
383
|
+
request=api.Request(
|
|
384
|
+
space_name="management",
|
|
385
|
+
request_type=RequestType.create,
|
|
386
|
+
records=[
|
|
387
|
+
core.Record(
|
|
388
|
+
resource_type=ResourceType.content,
|
|
389
|
+
shortname=space_name,
|
|
390
|
+
subpath="/health_check",
|
|
391
|
+
attributes={
|
|
392
|
+
"is_active": True,
|
|
393
|
+
"updated_at": str(datetime.now()),
|
|
394
|
+
"payload": {
|
|
395
|
+
"schema_shortname": "health_check",
|
|
396
|
+
"content_type": ContentType.json,
|
|
397
|
+
"body": health_check
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
)
|
|
401
|
+
],
|
|
402
|
+
),
|
|
403
|
+
owner_shortname='dmart',
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
async def save_duplicated_entries() -> None:
|
|
408
|
+
print('>>>> Processing UUID duplication <<<<')
|
|
409
|
+
before_time = time.time()
|
|
410
|
+
uuid_scanned_entries: set = set()
|
|
411
|
+
uuid_duplicated_entries: dict = {}
|
|
412
|
+
|
|
413
|
+
slug_scanned_entries = set()
|
|
414
|
+
slug_duplicated_entries: dict = {}
|
|
415
|
+
spaces : dict = await db.get_spaces()
|
|
416
|
+
async with RedisServices() as redis:
|
|
417
|
+
for space_name, space_data in spaces.items():
|
|
418
|
+
space_data = json.loads(space_data)
|
|
419
|
+
try:
|
|
420
|
+
ft_index = redis.ft(f"{space_name}:meta")
|
|
421
|
+
index_info = await ft_index.info() # type: ignore
|
|
422
|
+
except Exception:
|
|
423
|
+
continue
|
|
424
|
+
for i in range(0, int(index_info["num_docs"]), 10000): # type: ignore
|
|
425
|
+
search_query = Query(query_string="*")
|
|
426
|
+
search_query.paging(i, 10000)
|
|
427
|
+
x = await ft_index.search(query=search_query) # type: ignore
|
|
428
|
+
if x and isinstance(x, dict) and "results" in x:
|
|
429
|
+
res_data : list = [ one["extra_attributes"]["$"] for one in x["results"] if 'extra_attributes' in one]
|
|
430
|
+
for redis_doc_dict in res_data:
|
|
431
|
+
redis_doc_dict = json.loads(redis_doc_dict)
|
|
432
|
+
if isinstance(redis_doc_dict, dict):
|
|
433
|
+
if "uuid" in redis_doc_dict:
|
|
434
|
+
# Handle UUID
|
|
435
|
+
if "uuid" in redis_doc_dict and redis_doc_dict["uuid"] in uuid_scanned_entries:
|
|
436
|
+
short_uuid = redis_doc_dict["uuid"][:8]
|
|
437
|
+
uuid_duplicated_entries.setdefault(
|
|
438
|
+
short_uuid, {"loc": [], "total": 0}
|
|
439
|
+
)
|
|
440
|
+
uuid_duplicated_entries[short_uuid]["loc"].append(
|
|
441
|
+
space_name + "/" + redis_doc_dict['subpath'] + "/" + redis_doc_dict['shortname']
|
|
442
|
+
)
|
|
443
|
+
uuid_duplicated_entries[short_uuid]["total"]+=1
|
|
444
|
+
else:
|
|
445
|
+
uuid_scanned_entries.add(redis_doc_dict["uuid"])
|
|
446
|
+
else:
|
|
447
|
+
print ("UUID is missing", redis_doc_dict)
|
|
448
|
+
|
|
449
|
+
# Handle Slug
|
|
450
|
+
if "slug" in redis_doc_dict and redis_doc_dict["slug"] in slug_scanned_entries:
|
|
451
|
+
slug_duplicated_entries.setdefault(
|
|
452
|
+
"slug", {"loc": [], "total": 0}
|
|
453
|
+
)
|
|
454
|
+
slug_duplicated_entries["slug"]["loc"].append(
|
|
455
|
+
space_name + "/" + redis_doc_dict['subpath'] + "/" + redis_doc_dict['shortname']
|
|
456
|
+
)
|
|
457
|
+
slug_duplicated_entries["slug"]["total"]+=1
|
|
458
|
+
elif "slug" in redis_doc_dict:
|
|
459
|
+
slug_scanned_entries.add(redis_doc_dict["slug"])
|
|
460
|
+
else:
|
|
461
|
+
print("Loaded document is not a proper dictionary")
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
entry_path = Path(settings.spaces_folder / "management/health_check/.dm/duplicated_entries/meta.content.json")
|
|
467
|
+
request_type = RequestType.create
|
|
468
|
+
if entry_path.is_file():
|
|
469
|
+
request_type = RequestType.update
|
|
470
|
+
await serve_request(
|
|
471
|
+
request=api.Request(
|
|
472
|
+
space_name=settings.management_space,
|
|
473
|
+
request_type=request_type,
|
|
474
|
+
records=[
|
|
475
|
+
core.Record(
|
|
476
|
+
resource_type=ResourceType.content,
|
|
477
|
+
shortname="duplicated_entries",
|
|
478
|
+
subpath="/health_check",
|
|
479
|
+
attributes={
|
|
480
|
+
"is_active": True,
|
|
481
|
+
"updated_at": str(datetime.now()),
|
|
482
|
+
"payload": {
|
|
483
|
+
"schema_shortname": "health_check",
|
|
484
|
+
"content_type": ContentType.json,
|
|
485
|
+
"body": {
|
|
486
|
+
"entries": {
|
|
487
|
+
"uuid": uuid_duplicated_entries,
|
|
488
|
+
"slug": slug_duplicated_entries
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
)
|
|
494
|
+
],
|
|
495
|
+
),
|
|
496
|
+
owner_shortname='dmart',
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
print(f'Completed in: {"{:.2f}".format(time.time() - before_time)} sec')
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
async def cleanup_spaces() -> None:
|
|
503
|
+
spaces = await db.get_spaces()
|
|
504
|
+
# create health check path meta
|
|
505
|
+
folder_path = Path(settings.spaces_folder / "management/health_check/.dm")
|
|
506
|
+
if not os.path.isdir(folder_path):
|
|
507
|
+
os.makedirs(folder_path)
|
|
508
|
+
file_path = Path(folder_path / "meta.folder.json")
|
|
509
|
+
# create meta folder
|
|
510
|
+
if not os.path.isfile(file_path):
|
|
511
|
+
meta_obj = Folder(shortname="health_check", is_active=True, owner_shortname='dmart')
|
|
512
|
+
with open(file_path, "w") as f:
|
|
513
|
+
f.write(meta_obj.model_dump_json(exclude_none=True))
|
|
514
|
+
# create health check schema
|
|
515
|
+
if (
|
|
516
|
+
not os.path.isfile(Path(settings.spaces_folder / "management/schema/health_check.json")) or
|
|
517
|
+
not os.path.isfile(Path(settings.spaces_folder / "management/schema/.dm/health_check/meta.schema.json"))
|
|
518
|
+
):
|
|
519
|
+
meta = core.Schema(
|
|
520
|
+
shortname="health_check",
|
|
521
|
+
is_active=True,
|
|
522
|
+
owner_shortname="dmart",
|
|
523
|
+
payload=core.Payload(
|
|
524
|
+
content_type=ContentType.json,
|
|
525
|
+
body="health_check.json",
|
|
526
|
+
),
|
|
527
|
+
)
|
|
528
|
+
schema = {
|
|
529
|
+
"type": "object",
|
|
530
|
+
"title": "health_check",
|
|
531
|
+
"additionalProperties": True,
|
|
532
|
+
"properties": {
|
|
533
|
+
},
|
|
534
|
+
"required": []
|
|
535
|
+
}
|
|
536
|
+
await db.save("management", "schema", meta)
|
|
537
|
+
await db.save_payload_from_json("management", "schema", meta, schema)
|
|
538
|
+
|
|
539
|
+
# clean up entries
|
|
540
|
+
for folder_name in os.listdir(folder_path):
|
|
541
|
+
if not os.path.isdir(os.path.join(folder_path, folder_name)):
|
|
542
|
+
continue
|
|
543
|
+
if folder_name not in spaces:
|
|
544
|
+
shutil.rmtree(Path(folder_path / folder_name))
|
|
545
|
+
os.remove(Path(settings.spaces_folder / "management/health_check" / f"{folder_name}.json"))
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
if __name__ == "__main__":
|
|
549
|
+
parser = argparse.ArgumentParser(
|
|
550
|
+
description="This created for doing health check functionality",
|
|
551
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
552
|
+
)
|
|
553
|
+
parser.add_argument("-t", "--type", help="type of health check (soft or hard)")
|
|
554
|
+
parser.add_argument("-s", "--space", help="hit the target space or pass (all) to make the full health check")
|
|
555
|
+
parser.add_argument("-m", "--schemas", nargs="*", help="hit the target schema inside the space")
|
|
556
|
+
|
|
557
|
+
args = parser.parse_args()
|
|
558
|
+
before_time = time.time()
|
|
559
|
+
asyncio.run(main(args.type, args.space or "all", args.schemas))
|
|
560
|
+
print(f'total time: {"{:.2f}".format(time.time() - before_time)} sec')
|