dmart 1.4.40.post8__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 (489) hide show
  1. dmart/__init__.py +7 -0
  2. dmart/alembic/README +1 -0
  3. dmart/alembic/__init__.py +0 -0
  4. dmart/alembic/env.py +91 -0
  5. dmart/alembic/notes.txt +11 -0
  6. dmart/alembic/script.py.mako +28 -0
  7. dmart/alembic/scripts/__init__.py +0 -0
  8. dmart/alembic/scripts/calculate_checksums.py +77 -0
  9. dmart/alembic/scripts/migration_f7a4949eed19.py +28 -0
  10. dmart/alembic/versions/0f3d2b1a7c21_add_authz_materialized_views.py +87 -0
  11. dmart/alembic/versions/10d2041b94d4_last_checksum_history.py +62 -0
  12. dmart/alembic/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py +33 -0
  13. dmart/alembic/versions/26bfe19b49d4_rm_failedloginattempts.py +42 -0
  14. dmart/alembic/versions/3c8bca2219cc_add_otp_table.py +38 -0
  15. dmart/alembic/versions/6675fd9dfe42_remove_unique_from_sessions_table.py +36 -0
  16. dmart/alembic/versions/71bc1df82e6a_adding_user_last_login_at.py +43 -0
  17. dmart/alembic/versions/74288ccbd3b5_initial.py +264 -0
  18. dmart/alembic/versions/7520a89a8467_rm_activesession_table.py +39 -0
  19. dmart/alembic/versions/848b623755a4_make_created_nd_updated_at_required.py +138 -0
  20. dmart/alembic/versions/8640dcbebf85_add_notes_to_users.py +32 -0
  21. dmart/alembic/versions/91c94250232a_adding_fk_on_owner_shortname.py +104 -0
  22. dmart/alembic/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py +66 -0
  23. dmart/alembic/versions/9aae9138c4ef_indexing_created_at_updated_at.py +80 -0
  24. dmart/alembic/versions/__init__.py +0 -0
  25. dmart/alembic/versions/b53f916b3f6d_json_to_jsonb.py +492 -0
  26. dmart/alembic/versions/eb5f1ec65156_adding_user_locked_to_device.py +36 -0
  27. dmart/alembic/versions/f7a4949eed19_adding_query_policies_to_meta.py +60 -0
  28. dmart/alembic.ini +117 -0
  29. dmart/api/__init__.py +0 -0
  30. dmart/api/info/__init__.py +0 -0
  31. dmart/api/info/router.py +109 -0
  32. dmart/api/managed/__init__.py +0 -0
  33. dmart/api/managed/router.py +1541 -0
  34. dmart/api/managed/utils.py +1879 -0
  35. dmart/api/public/__init__.py +0 -0
  36. dmart/api/public/router.py +758 -0
  37. dmart/api/qr/__init__.py +0 -0
  38. dmart/api/qr/router.py +108 -0
  39. dmart/api/user/__init__.py +0 -0
  40. dmart/api/user/model/__init__.py +0 -0
  41. dmart/api/user/model/errors.py +14 -0
  42. dmart/api/user/model/requests.py +165 -0
  43. dmart/api/user/model/responses.py +11 -0
  44. dmart/api/user/router.py +1413 -0
  45. dmart/api/user/service.py +270 -0
  46. dmart/bundler.py +52 -0
  47. dmart/cli.py +1133 -0
  48. dmart/config/__init__.py +0 -0
  49. dmart/config/channels.json +11 -0
  50. dmart/config/notification.json +17 -0
  51. dmart/config.env.sample +27 -0
  52. dmart/config.ini.sample +7 -0
  53. dmart/conftest.py +13 -0
  54. dmart/curl.sh +196 -0
  55. dmart/cxb/__init__.py +0 -0
  56. dmart/cxb/assets/@codemirror-Rn7_6DkE.js +10 -0
  57. dmart/cxb/assets/@edraj-CS4NwVbD.js +1 -0
  58. dmart/cxb/assets/@floating-ui-BwwcF-xh.js +1 -0
  59. dmart/cxb/assets/@formatjs-yKEsAtjs.js +1 -0
  60. dmart/cxb/assets/@fortawesome-DRW1UCdr.js +9 -0
  61. dmart/cxb/assets/@jsonquerylang-laKNoFFq.js +12 -0
  62. dmart/cxb/assets/@lezer-za4Q-8Ew.js +1 -0
  63. dmart/cxb/assets/@marijn-DXwl3gUT.js +1 -0
  64. dmart/cxb/assets/@popperjs-l0sNRNKZ.js +1 -0
  65. dmart/cxb/assets/@replit--ERk53eB.js +1 -0
  66. dmart/cxb/assets/@roxi-CGMFK4i8.js +6 -0
  67. dmart/cxb/assets/@typewriter-cCzskkIv.js +17 -0
  68. dmart/cxb/assets/@zerodevx-BlBZjKxu.js +1 -0
  69. dmart/cxb/assets/@zerodevx-CVEpe6WZ.css +1 -0
  70. dmart/cxb/assets/BreadCrumbLite-DAhOx38v.js +1 -0
  71. dmart/cxb/assets/EntryRenderer-CCqV8Rkg.js +32 -0
  72. dmart/cxb/assets/EntryRenderer-DXytdFp9.css +1 -0
  73. dmart/cxb/assets/ListView-BQelo7vZ.js +16 -0
  74. dmart/cxb/assets/ListView-U8of-_c-.css +1 -0
  75. dmart/cxb/assets/Prism--hMplq-p.js +3 -0
  76. dmart/cxb/assets/Prism-Uh6uStUw.css +1 -0
  77. dmart/cxb/assets/Table2Cols-BsbwicQm.js +1 -0
  78. dmart/cxb/assets/_..-BvT6vdHa.css +1 -0
  79. dmart/cxb/assets/_...404_-fuLH_rX9.js +2 -0
  80. dmart/cxb/assets/_...fallback_-Ba_NLmAE.js +1 -0
  81. dmart/cxb/assets/_module-3HrtKAWo.js +3 -0
  82. dmart/cxb/assets/_module-DFKFq0AM.js +4 -0
  83. dmart/cxb/assets/_module-Dgq0ZVtz.js +1 -0
  84. dmart/cxb/assets/ajv-Cpj98o6Y.js +1 -0
  85. dmart/cxb/assets/axios-CG2WSiiR.js +6 -0
  86. dmart/cxb/assets/clsx-B-dksMZM.js +1 -0
  87. dmart/cxb/assets/codemirror-wrapped-line-indent-DPhKvljI.js +1 -0
  88. dmart/cxb/assets/compare-C3AjiGFR.js +1 -0
  89. dmart/cxb/assets/compute-scroll-into-view-Bl8rNFhg.js +1 -0
  90. dmart/cxb/assets/consolite-DlCuI0F9.js +1 -0
  91. dmart/cxb/assets/crelt-C8TCjufn.js +1 -0
  92. dmart/cxb/assets/date-fns-l0sNRNKZ.js +1 -0
  93. dmart/cxb/assets/deepmerge-rn4rBaHU.js +1 -0
  94. dmart/cxb/assets/dmart_services-AL6-IdDE.js +1 -0
  95. dmart/cxb/assets/downloadFile-D08i0YDh.js +1 -0
  96. dmart/cxb/assets/easy-signal-BiPFIK3O.js +1 -0
  97. dmart/cxb/assets/esm-env-rsSWfq8L.js +1 -0
  98. dmart/cxb/assets/export-OF_rTiXu.js +1 -0
  99. dmart/cxb/assets/fast-deep-equal-l0sNRNKZ.js +1 -0
  100. dmart/cxb/assets/fast-diff-C-IidNf4.js +1 -0
  101. dmart/cxb/assets/fast-uri-l0sNRNKZ.js +1 -0
  102. dmart/cxb/assets/flowbite-svelte-BLvjb-sa.js +1 -0
  103. dmart/cxb/assets/flowbite-svelte-CD54FDqW.css +1 -0
  104. dmart/cxb/assets/flowbite-svelte-icons-BI8GVhw_.js +1 -0
  105. dmart/cxb/assets/github-slugger-CQ4oX9Ud.js +1 -0
  106. dmart/cxb/assets/global-igKv-1g9.js +1 -0
  107. dmart/cxb/assets/hookar-BMRD9G9H.js +1 -0
  108. dmart/cxb/assets/immutable-json-patch-DtRO2E_S.js +1 -0
  109. dmart/cxb/assets/import-1vE3gBat.js +1 -0
  110. dmart/cxb/assets/index-B-eTh-ZX.js +1 -0
  111. dmart/cxb/assets/index-BSsK-X71.js +1 -0
  112. dmart/cxb/assets/index-BVyxzKtH.js +1 -0
  113. dmart/cxb/assets/index-BdeNM69f.js +1 -0
  114. dmart/cxb/assets/index-CC-A1ipE.js +1 -0
  115. dmart/cxb/assets/index-CQohGiYB.js +1 -0
  116. dmart/cxb/assets/index-ChjnkpdZ.js +4 -0
  117. dmart/cxb/assets/index-DLP7csA4.js +1 -0
  118. dmart/cxb/assets/index-DTfhnhwd.js +1 -0
  119. dmart/cxb/assets/index-DdXRK7n9.js +2 -0
  120. dmart/cxb/assets/index-DtiCmB4o.js +1 -0
  121. dmart/cxb/assets/index-NBrXBlLA.css +2 -0
  122. dmart/cxb/assets/index-X1uNehO7.js +1 -0
  123. dmart/cxb/assets/index-nrQW6Nrr.js +1 -0
  124. dmart/cxb/assets/info-B986lRiM.js +1 -0
  125. dmart/cxb/assets/intl-messageformat-Dc5UU-HB.js +3 -0
  126. dmart/cxb/assets/jmespath-l0sNRNKZ.js +1 -0
  127. dmart/cxb/assets/json-schema-traverse-l0sNRNKZ.js +1 -0
  128. dmart/cxb/assets/json-source-map-DRgZidqy.js +5 -0
  129. dmart/cxb/assets/jsonpath-plus-l0sNRNKZ.js +1 -0
  130. dmart/cxb/assets/jsonrepair-B30Dx381.js +8 -0
  131. dmart/cxb/assets/lodash-es-DZVAA2ox.js +1 -0
  132. dmart/cxb/assets/marked-DKjyhwJX.js +56 -0
  133. dmart/cxb/assets/marked-gfm-heading-id-U5zO829x.js +2 -0
  134. dmart/cxb/assets/marked-mangle-CDMeiHC6.js +1 -0
  135. dmart/cxb/assets/memoize-one-BdPwpGay.js +1 -0
  136. dmart/cxb/assets/natural-compare-lite-Bg2Xcf-o.js +7 -0
  137. dmart/cxb/assets/pagination-svelte-D5CyoiE_.js +13 -0
  138. dmart/cxb/assets/pagination-svelte-v10nAbbM.css +1 -0
  139. dmart/cxb/assets/plantuml-encoder-C47mzt9T.js +1 -0
  140. dmart/cxb/assets/prismjs-DTUiLGJu.js +9 -0
  141. dmart/cxb/assets/profile-BUf-tKMe.js +1 -0
  142. dmart/cxb/assets/query-CNmXTsgf.js +1 -0
  143. dmart/cxb/assets/queryHelpers-C9iBWwqe.js +1 -0
  144. dmart/cxb/assets/scroll-into-view-if-needed-KR58zyjF.js +1 -0
  145. dmart/cxb/assets/spaces-0oyGvpii.js +1 -0
  146. dmart/cxb/assets/style-mod-Bs6eFhZE.js +3 -0
  147. dmart/cxb/assets/svelte-B2XmcTi_.js +4 -0
  148. dmart/cxb/assets/svelte-awesome-COLlx0DN.css +1 -0
  149. dmart/cxb/assets/svelte-awesome-DhnMA6Q_.js +1 -0
  150. dmart/cxb/assets/svelte-datatables-net-CY7LBj6I.js +1 -0
  151. dmart/cxb/assets/svelte-floating-ui-BlS3sOAQ.js +1 -0
  152. dmart/cxb/assets/svelte-i18n-CT2KkQaN.js +3 -0
  153. dmart/cxb/assets/svelte-jsoneditor-BzfX6Usi.css +1 -0
  154. dmart/cxb/assets/svelte-jsoneditor-CUGSvWId.js +25 -0
  155. dmart/cxb/assets/svelte-select-CegQKzqH.css +1 -0
  156. dmart/cxb/assets/svelte-select-CjHAt_85.js +6 -0
  157. dmart/cxb/assets/tailwind-merge-CJvxXMcu.js +1 -0
  158. dmart/cxb/assets/tailwind-variants-Cj20BoQ3.js +1 -0
  159. dmart/cxb/assets/toast-B9WDyfyI.js +1 -0
  160. dmart/cxb/assets/tslib-pJfR_DrR.js +1 -0
  161. dmart/cxb/assets/typewriter-editor-DkTVIJdm.js +25 -0
  162. dmart/cxb/assets/user-DeK_NB5v.js +1 -0
  163. dmart/cxb/assets/vanilla-picker-l5rcX3cq.js +8 -0
  164. dmart/cxb/assets/w3c-keyname-Vcq4gwWv.js +1 -0
  165. dmart/cxb/config.json +11 -0
  166. dmart/cxb/config.sample.json +11 -0
  167. dmart/cxb/favicon.ico +0 -0
  168. dmart/cxb/favicon.png +0 -0
  169. dmart/cxb/index.html +28 -0
  170. dmart/data_adapters/__init__.py +0 -0
  171. dmart/data_adapters/adapter.py +16 -0
  172. dmart/data_adapters/base_data_adapter.py +467 -0
  173. dmart/data_adapters/file/__init__.py +0 -0
  174. dmart/data_adapters/file/adapter.py +2043 -0
  175. dmart/data_adapters/file/adapter_helpers.py +1013 -0
  176. dmart/data_adapters/file/archive.py +150 -0
  177. dmart/data_adapters/file/create_index.py +331 -0
  178. dmart/data_adapters/file/create_users_folders.py +52 -0
  179. dmart/data_adapters/file/custom_validations.py +68 -0
  180. dmart/data_adapters/file/drop_index.py +40 -0
  181. dmart/data_adapters/file/health_check.py +560 -0
  182. dmart/data_adapters/file/redis_services.py +1110 -0
  183. dmart/data_adapters/helpers.py +27 -0
  184. dmart/data_adapters/sql/__init__.py +0 -0
  185. dmart/data_adapters/sql/adapter.py +3218 -0
  186. dmart/data_adapters/sql/adapter_helpers.py +491 -0
  187. dmart/data_adapters/sql/create_tables.py +451 -0
  188. dmart/data_adapters/sql/create_users_folders.py +53 -0
  189. dmart/data_adapters/sql/db_to_json_migration.py +485 -0
  190. dmart/data_adapters/sql/health_check_sql.py +232 -0
  191. dmart/data_adapters/sql/json_to_db_migration.py +454 -0
  192. dmart/data_adapters/sql/update_query_policies.py +101 -0
  193. dmart/data_generator.py +81 -0
  194. dmart/dmart.py +761 -0
  195. dmart/get_settings.py +7 -0
  196. dmart/hypercorn_config.toml +3 -0
  197. dmart/info.json +1 -0
  198. dmart/languages/__init__.py +0 -0
  199. dmart/languages/arabic.json +15 -0
  200. dmart/languages/english.json +16 -0
  201. dmart/languages/kurdish.json +14 -0
  202. dmart/languages/loader.py +12 -0
  203. dmart/login_creds.sh +7 -0
  204. dmart/login_creds.sh.sample +7 -0
  205. dmart/main.py +563 -0
  206. dmart/manifest.sh +12 -0
  207. dmart/migrate.py +24 -0
  208. dmart/models/__init__.py +0 -0
  209. dmart/models/api.py +203 -0
  210. dmart/models/core.py +597 -0
  211. dmart/models/enums.py +255 -0
  212. dmart/password_gen.py +8 -0
  213. dmart/plugins/__init__.py +0 -0
  214. dmart/plugins/action_log/__init__.py +0 -0
  215. dmart/plugins/action_log/config.json +13 -0
  216. dmart/plugins/action_log/plugin.py +121 -0
  217. dmart/plugins/admin_notification_sender/__init__.py +0 -0
  218. dmart/plugins/admin_notification_sender/config.json +13 -0
  219. dmart/plugins/admin_notification_sender/plugin.py +124 -0
  220. dmart/plugins/ldap_manager/__init__.py +0 -0
  221. dmart/plugins/ldap_manager/config.json +12 -0
  222. dmart/plugins/ldap_manager/dmart.schema +146 -0
  223. dmart/plugins/ldap_manager/plugin.py +100 -0
  224. dmart/plugins/ldap_manager/slapd.conf +53 -0
  225. dmart/plugins/local_notification/__init__.py +0 -0
  226. dmart/plugins/local_notification/config.json +13 -0
  227. dmart/plugins/local_notification/plugin.py +123 -0
  228. dmart/plugins/realtime_updates_notifier/__init__.py +0 -0
  229. dmart/plugins/realtime_updates_notifier/config.json +12 -0
  230. dmart/plugins/realtime_updates_notifier/plugin.py +58 -0
  231. dmart/plugins/redis_db_update/__init__.py +0 -0
  232. dmart/plugins/redis_db_update/config.json +13 -0
  233. dmart/plugins/redis_db_update/plugin.py +188 -0
  234. dmart/plugins/resource_folders_creation/__init__.py +0 -0
  235. dmart/plugins/resource_folders_creation/config.json +12 -0
  236. dmart/plugins/resource_folders_creation/plugin.py +81 -0
  237. dmart/plugins/system_notification_sender/__init__.py +0 -0
  238. dmart/plugins/system_notification_sender/config.json +13 -0
  239. dmart/plugins/system_notification_sender/plugin.py +188 -0
  240. dmart/plugins/update_access_controls/__init__.py +0 -0
  241. dmart/plugins/update_access_controls/config.json +12 -0
  242. dmart/plugins/update_access_controls/plugin.py +9 -0
  243. dmart/publish.sh +57 -0
  244. dmart/pylint.sh +16 -0
  245. dmart/pyrightconfig.json +7 -0
  246. dmart/redis_connections.sh +13 -0
  247. dmart/reload.sh +56 -0
  248. dmart/run.sh +3 -0
  249. dmart/run_notification_campaign.py +85 -0
  250. dmart/sample/spaces/applications/.dm/meta.space.json +30 -0
  251. dmart/sample/spaces/applications/api/.dm/meta.folder.json +1 -0
  252. dmart/sample/spaces/applications/api/.dm/query_all_applications/meta.content.json +1 -0
  253. dmart/sample/spaces/applications/api/.dm/test_by_saad/attachments.media/meta.warframe.json +1 -0
  254. dmart/sample/spaces/applications/api/.dm/test_by_saad/attachments.media/warframe.png +0 -0
  255. dmart/sample/spaces/applications/api/.dm/test_by_saad/meta.content.json +1 -0
  256. dmart/sample/spaces/applications/api/.dm/user_profile/meta.content.json +1 -0
  257. dmart/sample/spaces/applications/api/applications/.dm/create_log/meta.content.json +1 -0
  258. dmart/sample/spaces/applications/api/applications/.dm/create_public_logs/meta.content.json +1 -0
  259. dmart/sample/spaces/applications/api/applications/.dm/meta.folder.json +1 -0
  260. dmart/sample/spaces/applications/api/applications/.dm/query_all_translated_data/meta.content.json +1 -0
  261. dmart/sample/spaces/applications/api/applications/.dm/query_logs/meta.content.json +1 -0
  262. dmart/sample/spaces/applications/api/applications/.dm/query_translated_enums/meta.content.json +1 -0
  263. dmart/sample/spaces/applications/api/applications/.dm/query_translated_others/meta.content.json +1 -0
  264. dmart/sample/spaces/applications/api/applications/.dm/query_translated_resolution/meta.content.json +1 -0
  265. dmart/sample/spaces/applications/api/applications/create_log.json +1 -0
  266. dmart/sample/spaces/applications/api/applications/create_public_logs.json +1 -0
  267. dmart/sample/spaces/applications/api/applications/query_all_translated_data.json +1 -0
  268. dmart/sample/spaces/applications/api/applications/query_logs.json +1 -0
  269. dmart/sample/spaces/applications/api/applications/query_translated_enums.json +1 -0
  270. dmart/sample/spaces/applications/api/applications/query_translated_others.json +1 -0
  271. dmart/sample/spaces/applications/api/applications/query_translated_resolution.json +1 -0
  272. dmart/sample/spaces/applications/api/applications.json +1 -0
  273. dmart/sample/spaces/applications/api/management/.dm/create_subaccount/meta.content.json +1 -0
  274. dmart/sample/spaces/applications/api/management/.dm/meta.folder.json +1 -0
  275. dmart/sample/spaces/applications/api/management/.dm/update_password/meta.content.json +1 -0
  276. dmart/sample/spaces/applications/api/management/create_subaccount.json +53 -0
  277. dmart/sample/spaces/applications/api/management/update_password.json +1 -0
  278. dmart/sample/spaces/applications/api/management.json +1 -0
  279. dmart/sample/spaces/applications/api/query_all_applications.json +15 -0
  280. dmart/sample/spaces/applications/api/test_by_saad.json +1 -0
  281. dmart/sample/spaces/applications/api/user/.dm/meta.folder.json +1 -0
  282. dmart/sample/spaces/applications/api/user/.dm/test_by_saad/meta.content.json +1 -0
  283. dmart/sample/spaces/applications/api/user/.dm/user_profile/meta.content.json +1 -0
  284. dmart/sample/spaces/applications/api/user/test_by_saad.json +1 -0
  285. dmart/sample/spaces/applications/api/user/user_profile.json +1 -0
  286. dmart/sample/spaces/applications/api/user_profile.json +1 -0
  287. dmart/sample/spaces/applications/api.json +1 -0
  288. dmart/sample/spaces/applications/collections/.dm/meta.folder.json +19 -0
  289. dmart/sample/spaces/applications/collections.json +1 -0
  290. dmart/sample/spaces/applications/configurations/.dm/meta.folder.json +1 -0
  291. dmart/sample/spaces/applications/configurations/time_out.json +1 -0
  292. dmart/sample/spaces/applications/configurations.json +19 -0
  293. dmart/sample/spaces/applications/errors.json +1 -0
  294. dmart/sample/spaces/applications/logs/.dm/meta.folder.json +1 -0
  295. dmart/sample/spaces/applications/logs.json +1 -0
  296. dmart/sample/spaces/applications/queries/.dm/meta.folder.json +1 -0
  297. dmart/sample/spaces/applications/queries/.dm/order/meta.content.json +1 -0
  298. dmart/sample/spaces/applications/queries/order.json +1 -0
  299. dmart/sample/spaces/applications/queries.json +1 -0
  300. dmart/sample/spaces/applications/schema/.dm/api/meta.schema.json +1 -0
  301. dmart/sample/spaces/applications/schema/.dm/configuration/meta.schema.json +1 -0
  302. dmart/sample/spaces/applications/schema/.dm/error/meta.schema.json +1 -0
  303. dmart/sample/spaces/applications/schema/.dm/log/meta.schema.json +1 -0
  304. dmart/sample/spaces/applications/schema/.dm/meta.folder.json +1 -0
  305. dmart/sample/spaces/applications/schema/.dm/query/meta.schema.json +16 -0
  306. dmart/sample/spaces/applications/schema/.dm/translation/meta.schema.json +1 -0
  307. dmart/sample/spaces/applications/schema/api.json +28 -0
  308. dmart/sample/spaces/applications/schema/configuration.json +1 -0
  309. dmart/sample/spaces/applications/schema/error.json +43 -0
  310. dmart/sample/spaces/applications/schema/log.json +1 -0
  311. dmart/sample/spaces/applications/schema/query.json +118 -0
  312. dmart/sample/spaces/applications/schema/translation.json +26 -0
  313. dmart/sample/spaces/applications/schema.json +1 -0
  314. dmart/sample/spaces/applications/translations/.dm/meta.folder.json +1 -0
  315. dmart/sample/spaces/applications/translations.json +1 -0
  316. dmart/sample/spaces/archive/.dm/meta.space.json +27 -0
  317. dmart/sample/spaces/custom_plugins/dummy/__pycache__/plugin.cpython-314.pyc +0 -0
  318. dmart/sample/spaces/custom_plugins/dummy/config.json +28 -0
  319. dmart/sample/spaces/custom_plugins/dummy/plugin.py +6 -0
  320. dmart/sample/spaces/custom_plugins/missed_entry/config.json +12 -0
  321. dmart/sample/spaces/custom_plugins/missed_entry/plugin.py +119 -0
  322. dmart/sample/spaces/custom_plugins/own_changed_notification/__pycache__/plugin.cpython-314.pyc +0 -0
  323. dmart/sample/spaces/custom_plugins/own_changed_notification/config.json +12 -0
  324. dmart/sample/spaces/custom_plugins/own_changed_notification/plugin.py +65 -0
  325. dmart/sample/spaces/custom_plugins/reports_stats/config.json +14 -0
  326. dmart/sample/spaces/custom_plugins/reports_stats/plugin.py +82 -0
  327. dmart/sample/spaces/custom_plugins/system_notification_sender/config.json +22 -0
  328. dmart/sample/spaces/custom_plugins/system_notification_sender/notification.py +268 -0
  329. dmart/sample/spaces/custom_plugins/system_notification_sender/plugin.py +98 -0
  330. dmart/sample/spaces/management/.dm/events.jsonl +32 -0
  331. dmart/sample/spaces/management/.dm/meta.space.json +48 -0
  332. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/admin.json +36 -0
  333. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/meta.admin.json +1 -0
  334. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/meta.system.json +1 -0
  335. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/system.json +32 -0
  336. dmart/sample/spaces/management/collections/.dm/meta.folder.json +1 -0
  337. dmart/sample/spaces/management/collections.json +1 -0
  338. dmart/sample/spaces/management/groups/.dm/meta.folder.json +1 -0
  339. dmart/sample/spaces/management/groups.json +1 -0
  340. dmart/sample/spaces/management/health_check/.dm/meta.folder.json +1 -0
  341. dmart/sample/spaces/management/health_check.json +1 -0
  342. dmart/sample/spaces/management/notifications/.dm/meta.folder.json +1 -0
  343. dmart/sample/spaces/management/notifications/admin/.dm/meta.folder.json +9 -0
  344. dmart/sample/spaces/management/notifications/system/.dm/meta.folder.json +9 -0
  345. dmart/sample/spaces/management/notifications.json +1 -0
  346. dmart/sample/spaces/management/permissions/.dm/access_applications/meta.permission.json +31 -0
  347. dmart/sample/spaces/management/permissions/.dm/access_applications_world/meta.permission.json +31 -0
  348. dmart/sample/spaces/management/permissions/.dm/access_messages/meta.permission.json +23 -0
  349. dmart/sample/spaces/management/permissions/.dm/access_personal/meta.permission.json +40 -0
  350. dmart/sample/spaces/management/permissions/.dm/access_protected/meta.permission.json +33 -0
  351. dmart/sample/spaces/management/permissions/.dm/access_public/meta.permission.json +24 -0
  352. dmart/sample/spaces/management/permissions/.dm/browse_all_folders/meta.permission.json +23 -0
  353. dmart/sample/spaces/management/permissions/.dm/create_log/meta.permission.json +24 -0
  354. dmart/sample/spaces/management/permissions/.dm/interviewer/meta.permission.json +1 -0
  355. dmart/sample/spaces/management/permissions/.dm/manage_applications/meta.permission.json +1 -0
  356. dmart/sample/spaces/management/permissions/.dm/manage_debug/meta.permission.json +25 -0
  357. dmart/sample/spaces/management/permissions/.dm/manage_spaces/meta.permission.json +24 -0
  358. dmart/sample/spaces/management/permissions/.dm/meta.folder.json +1 -0
  359. dmart/sample/spaces/management/permissions/.dm/rules_management_default/meta.permission.json +32 -0
  360. dmart/sample/spaces/management/permissions/.dm/super_manager/meta.permission.json +52 -0
  361. dmart/sample/spaces/management/permissions/.dm/view_activity_log/meta.permission.json +26 -0
  362. dmart/sample/spaces/management/permissions/.dm/view_collections/meta.permission.json +29 -0
  363. dmart/sample/spaces/management/permissions/.dm/view_logs/meta.permission.json +30 -0
  364. dmart/sample/spaces/management/permissions/.dm/view_roles/meta.permission.json +29 -0
  365. dmart/sample/spaces/management/permissions/.dm/view_users/meta.permission.json +25 -0
  366. dmart/sample/spaces/management/permissions/.dm/view_world/meta.permission.json +31 -0
  367. dmart/sample/spaces/management/permissions/.dm/world/meta.permission.json +35 -0
  368. dmart/sample/spaces/management/permissions.json +1 -0
  369. dmart/sample/spaces/management/requests.json +1 -0
  370. dmart/sample/spaces/management/roles/.dm/dummy/meta.role.json +12 -0
  371. dmart/sample/spaces/management/roles/.dm/logged_in/meta.role.json +18 -0
  372. dmart/sample/spaces/management/roles/.dm/manager/meta.role.json +13 -0
  373. dmart/sample/spaces/management/roles/.dm/meta.folder.json +1 -0
  374. dmart/sample/spaces/management/roles/.dm/moderator/meta.role.json +13 -0
  375. dmart/sample/spaces/management/roles/.dm/super_admin/meta.role.json +14 -0
  376. dmart/sample/spaces/management/roles/.dm/test_role/meta.role.json +13 -0
  377. dmart/sample/spaces/management/roles/.dm/world/meta.role.json +15 -0
  378. dmart/sample/spaces/management/roles.json +1 -0
  379. dmart/sample/spaces/management/schema/.dm/admin_notification_request/attachments.media/meta.ui_schema.json +10 -0
  380. dmart/sample/spaces/management/schema/.dm/admin_notification_request/attachments.media/ui_schema.json +32 -0
  381. dmart/sample/spaces/management/schema/.dm/admin_notification_request/meta.schema.json +1 -0
  382. dmart/sample/spaces/management/schema/.dm/api/meta.schema.json +1 -0
  383. dmart/sample/spaces/management/schema/.dm/folder_rendering/meta.schema.json +1 -0
  384. dmart/sample/spaces/management/schema/.dm/health_check/meta.schema.json +17 -0
  385. dmart/sample/spaces/management/schema/.dm/meta.folder.json +1 -0
  386. dmart/sample/spaces/management/schema/.dm/meta_schema/meta.schema.json +1 -0
  387. dmart/sample/spaces/management/schema/.dm/metafile/meta.schema.json +14 -0
  388. dmart/sample/spaces/management/schema/.dm/notification/meta.schema.json +1 -0
  389. dmart/sample/spaces/management/schema/.dm/system_notification_request/attachments.media/meta.ui_schema.json +10 -0
  390. dmart/sample/spaces/management/schema/.dm/system_notification_request/attachments.media/ui_schema.json +32 -0
  391. dmart/sample/spaces/management/schema/.dm/system_notification_request/meta.schema.json +1 -0
  392. dmart/sample/spaces/management/schema/.dm/view/meta.schema.json +1 -0
  393. dmart/sample/spaces/management/schema/.dm/workflow/meta.schema.json +1 -0
  394. dmart/sample/spaces/management/schema/admin_notification_request.json +89 -0
  395. dmart/sample/spaces/management/schema/api.json +1 -0
  396. dmart/sample/spaces/management/schema/folder_rendering.json +238 -0
  397. dmart/sample/spaces/management/schema/health_check.json +8 -0
  398. dmart/sample/spaces/management/schema/meta_schema.json +74 -0
  399. dmart/sample/spaces/management/schema/metafile.json +153 -0
  400. dmart/sample/spaces/management/schema/notification.json +28 -0
  401. dmart/sample/spaces/management/schema/system_notification_request.json +57 -0
  402. dmart/sample/spaces/management/schema/view.json +23 -0
  403. dmart/sample/spaces/management/schema/workflow.json +87 -0
  404. dmart/sample/spaces/management/schema.json +1 -0
  405. dmart/sample/spaces/management/users/.dm/alibaba/meta.user.json +23 -0
  406. dmart/sample/spaces/management/users/.dm/anonymous/meta.user.json +18 -0
  407. dmart/sample/spaces/management/users/.dm/dmart/meta.user.json +26 -0
  408. dmart/sample/spaces/management/users/.dm/meta.folder.json +14 -0
  409. dmart/sample/spaces/management/workflows/.dm/channel/meta.content.json +1 -0
  410. dmart/sample/spaces/management/workflows/.dm/meta.folder.json +1 -0
  411. dmart/sample/spaces/management/workflows/channel.json +148 -0
  412. dmart/sample/spaces/management/workflows.json +1 -0
  413. dmart/sample/spaces/maqola/.dm/meta.space.json +33 -0
  414. dmart/sample/spaces/personal/.dm/meta.space.json +24 -0
  415. dmart/sample/spaces/personal/people/.dm/meta.folder.json +1 -0
  416. dmart/sample/spaces/personal/people/dmart/.dm/meta.folder.json +1 -0
  417. dmart/sample/spaces/personal/people/dmart/messages/.dm/0b5f7e7f/meta.content.json +1 -0
  418. dmart/sample/spaces/personal/people/dmart/messages/.dm/meta.folder.json +1 -0
  419. dmart/sample/spaces/personal/people/dmart/messages/.dm/mytest/meta.content.json +1 -0
  420. dmart/sample/spaces/personal/people/dmart/messages/0b5f7e7f.json +1 -0
  421. dmart/sample/spaces/personal/people/dmart/messages/mytest.json +1 -0
  422. dmart/sample/spaces/personal/people/dmart/notifications/.dm/meta.folder.json +1 -0
  423. dmart/sample/spaces/personal/people/dmart/private/.dm/inner/meta.content.json +1 -0
  424. dmart/sample/spaces/personal/people/dmart/private/.dm/meta.folder.json +1 -0
  425. dmart/sample/spaces/personal/people/dmart/private/inner.json +1 -0
  426. dmart/sample/spaces/personal/people/dmart/protected/.dm/avatar/meta.content.json +1 -0
  427. dmart/sample/spaces/personal/people/dmart/protected/.dm/meta.folder.json +1 -0
  428. dmart/sample/spaces/personal/people/dmart/protected/avatar.png +0 -0
  429. dmart/sample/spaces/personal/people/dmart/public/.dm/meta.folder.json +1 -0
  430. dmart/sample/test/.gitignore +2 -0
  431. dmart/sample/test/createcontent.json +9 -0
  432. dmart/sample/test/createmedia.json +9 -0
  433. dmart/sample/test/createmedia_entry.json +6 -0
  434. dmart/sample/test/createschema.json +8 -0
  435. dmart/sample/test/createschemawork.json +11 -0
  436. dmart/sample/test/createticket.json +13 -0
  437. dmart/sample/test/data.json +4 -0
  438. dmart/sample/test/deletecontent.json +12 -0
  439. dmart/sample/test/logo.jpeg +0 -0
  440. dmart/sample/test/my.jpg +0 -0
  441. dmart/sample/test/myticket.json +23 -0
  442. dmart/sample/test/resources.csv +12 -0
  443. dmart/sample/test/schema.json +16 -0
  444. dmart/sample/test/temp.json +1 -0
  445. dmart/sample/test/test.dmart +45 -0
  446. dmart/sample/test/ticket_schema.json +23 -0
  447. dmart/sample/test/ticket_workflow.json +85 -0
  448. dmart/sample/test/ticketbody.json +4 -0
  449. dmart/sample/test/ticketcontent.json +14 -0
  450. dmart/sample/test/updatecontent.json +20 -0
  451. dmart/sample/test/workflow_schema.json +68 -0
  452. dmart/scheduled_notification_handler.py +121 -0
  453. dmart/schema_migration.py +208 -0
  454. dmart/schema_modulate.py +192 -0
  455. dmart/set_admin_passwd.py +75 -0
  456. dmart/sync.py +202 -0
  457. dmart/test_utils.py +34 -0
  458. dmart/utils/__init__.py +0 -0
  459. dmart/utils/access_control.py +306 -0
  460. dmart/utils/async_request.py +8 -0
  461. dmart/utils/exporter.py +309 -0
  462. dmart/utils/firebase_notifier.py +57 -0
  463. dmart/utils/generate_email.py +37 -0
  464. dmart/utils/helpers.py +352 -0
  465. dmart/utils/hypercorn_config.py +12 -0
  466. dmart/utils/internal_error_code.py +60 -0
  467. dmart/utils/jwt.py +124 -0
  468. dmart/utils/logger.py +167 -0
  469. dmart/utils/middleware.py +99 -0
  470. dmart/utils/notification.py +75 -0
  471. dmart/utils/password_hashing.py +16 -0
  472. dmart/utils/plugin_manager.py +202 -0
  473. dmart/utils/query_policies_helper.py +128 -0
  474. dmart/utils/regex.py +44 -0
  475. dmart/utils/repository.py +529 -0
  476. dmart/utils/router_helper.py +19 -0
  477. dmart/utils/settings.py +212 -0
  478. dmart/utils/sms_notifier.py +21 -0
  479. dmart/utils/social_sso.py +67 -0
  480. dmart/utils/templates/activation.html.j2 +26 -0
  481. dmart/utils/templates/reminder.html.j2 +17 -0
  482. dmart/utils/ticket_sys_utils.py +203 -0
  483. dmart/utils/web_notifier.py +29 -0
  484. dmart/websocket.py +231 -0
  485. dmart-1.4.40.post8.dist-info/METADATA +75 -0
  486. dmart-1.4.40.post8.dist-info/RECORD +489 -0
  487. dmart-1.4.40.post8.dist-info/WHEEL +5 -0
  488. dmart-1.4.40.post8.dist-info/entry_points.txt +2 -0
  489. dmart-1.4.40.post8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1110 @@
1
+ import re
2
+ import json
3
+ import sys
4
+ from typing import Any, Awaitable
5
+ from redis.asyncio import Redis
6
+ from redis.asyncio.connection import BlockingConnectionPool
7
+ from models.api import RedisReducer, SortType
8
+ import models.core as core
9
+ from models.enums import ActionType, RedisReducerName, ResourceType, LockAction
10
+ from redis.commands.json.path import Path
11
+ from redis.commands.search.field import TextField, NumericField, TagField, Field
12
+ from redis.commands.search.index_definition import IndexDefinition, IndexType
13
+ from datetime import datetime
14
+
15
+ from redis.commands.search import Search, aggregation
16
+ from redis.commands.search.query import Query
17
+ from utils.helpers import camel_case, resolve_schema_references
18
+ from utils.internal_error_code import InternalErrorCode
19
+ from utils.query_policies_helper import generate_query_policies
20
+ from utils.settings import settings
21
+ import models.api as api
22
+ from fastapi import status
23
+ from fastapi.logger import logger
24
+ import redis
25
+
26
+
27
+ class RedisServices(Redis):
28
+ META_SCHEMA : list[Field] = [
29
+ TextField("$.uuid", no_stem=True, as_name="uuid"), # type: ignore
30
+ TextField("$.shortname", sortable=True, no_stem=True, as_name="shortname"), # type: ignore
31
+ TextField("$.slug", sortable=True, no_stem=True, as_name="slug"), # type: ignore
32
+ TextField("$.subpath", sortable=True, no_stem=True, as_name="subpath"), # type: ignore
33
+ TagField("$.subpath", as_name="exact_subpath"), # type: ignore
34
+ TextField(
35
+ "$.resource_type",
36
+ sortable=True,
37
+ no_stem=True,
38
+ as_name="resource_type",
39
+ ), # type: ignore
40
+ TextField("$.displayname.en", sortable=True, as_name="displayname_en"), # type: ignore
41
+ TextField("$.displayname.ar", sortable=True, as_name="displayname_ar"), # type: ignore
42
+ TextField("$.displayname.ku", sortable=True, as_name="displayname_kd"), # type: ignore
43
+ TextField("$.description.en", sortable=True, as_name="description_en"), # type: ignore
44
+ TextField("$.description.ar", sortable=True, as_name="description_ar"), # type: ignore
45
+ TextField("$.description.ku", sortable=True, as_name="description_kd"), # type: ignore
46
+ TagField("$.is_active", as_name="is_active"), # type: ignore
47
+ TextField(
48
+ "$.payload.content_type",
49
+ no_stem=True,
50
+ as_name="payload_content_type",
51
+ ), # type: ignore
52
+ TextField(
53
+ "$.payload.schema_shortname",
54
+ no_stem=True,
55
+ as_name="schema_shortname",
56
+ ), # type: ignore
57
+ NumericField("$.created_at", sortable=True, as_name="created_at"), # type: ignore
58
+ NumericField("$.updated_at", sortable=True, as_name="updated_at"), # type: ignore
59
+ TagField("$.view_acl.*", as_name="view_acl"), # type: ignore
60
+ TagField("$.tags.*", as_name="tags"), # type: ignore
61
+ TextField(
62
+ "$.owner_shortname",
63
+ sortable=True,
64
+ no_stem=True,
65
+ as_name="owner_shortname",
66
+ ), # type: ignore
67
+ TagField("$.query_policies.*", as_name="query_policies"), # type: ignore
68
+ # User fields
69
+ TextField("$.msisdn", sortable=True, as_name="msisdn"), # type: ignore
70
+ TextField("$.email", sortable=True, as_name="email"), # type: ignore
71
+ TagField("$.email", as_name="email_unescaped"), # type: ignore
72
+ # Ticket fields
73
+ TextField("$.state", sortable=True, no_stem=True, as_name="state"), # type: ignore
74
+ TagField("$.is_open", as_name="is_open"), # type: ignore
75
+ TextField(
76
+ "$.workflow_shortname",
77
+ sortable=True,
78
+ no_stem=True,
79
+ as_name="workflow_shortname",
80
+ ), # type: ignore
81
+ TextField(
82
+ "$.collaborators.delivered_by",
83
+ sortable=True,
84
+ no_stem=True,
85
+ as_name="collaborators_delivered_by",
86
+ ), # type: ignore
87
+ TextField(
88
+ "$.collaborators.processed_by",
89
+ sortable=True,
90
+ no_stem=True,
91
+ as_name="collaborators_processed_by",
92
+ ), # type: ignore
93
+ TextField(
94
+ "$.resolution_reason",
95
+ sortable=True,
96
+ no_stem=True,
97
+ as_name="resolution_reason",
98
+ ), # type: ignore
99
+ # Notification fields
100
+ TextField("$.type", sortable=True, no_stem=True, as_name="type"), # type: ignore
101
+ TagField("$.is_read", as_name="is_read"), # type: ignore
102
+ TextField("$.priority", sortable=True, no_stem=True, as_name="priority"), # type: ignore
103
+ TextField("$.reporter.type", sortable=True, as_name="reporter_type"), # type: ignore
104
+ TextField("$.reporter.name", sortable=True, as_name="reporter_name"), # type: ignore
105
+ TextField("$.reporter.channel", sortable=True, as_name="reporter_channel"), # type: ignore
106
+ TextField(
107
+ "$.reporter.distributor",
108
+ sortable=True,
109
+ as_name="reporter_distributor",
110
+ ), # type: ignore
111
+ TextField(
112
+ "$.reporter.governorate",
113
+ sortable=True,
114
+ as_name="reporter_governorate",
115
+ ), # type: ignore
116
+ TextField(
117
+ "$.reporter.msisdn",
118
+ sortable=True,
119
+ as_name="reporter_msisdn",
120
+ ), # type: ignore
121
+ TextField(
122
+ "$.payload_string",
123
+ sortable=False,
124
+ as_name="payload_string",
125
+ ), # type: ignore
126
+ ] # type: ignore
127
+
128
+ CUSTOM_CLASSES: list[type[core.Meta]] = [
129
+ core.Role,
130
+ core.Group,
131
+ core.User,
132
+ core.Permission,
133
+ ]
134
+
135
+ CUSTOM_INDICES = [
136
+ {
137
+ "space": "management",
138
+ "subpath": "roles",
139
+ "class": core.Role,
140
+ "exclude_from_index": [
141
+ "relationships",
142
+ "acl",
143
+ "is_active",
144
+ "description",
145
+ "displayname",
146
+ "payload",
147
+ ],
148
+ },
149
+ {
150
+ "space": "management",
151
+ "subpath": "groups",
152
+ "class": core.Group,
153
+ "exclude_from_index": [
154
+ "relationships",
155
+ "acl",
156
+ "is_active",
157
+ "description",
158
+ "displayname",
159
+ "payload",
160
+ ],
161
+ },
162
+ {
163
+ "space": "management",
164
+ "subpath": "users",
165
+ "class": core.User,
166
+ "exclude_from_index": [
167
+ "relationships",
168
+ "acl",
169
+ "is_active",
170
+ "description",
171
+ "displayname",
172
+ "payload",
173
+ "password",
174
+ "is_email_verified",
175
+ "is_msisdn_verified",
176
+ "type",
177
+ "force_password_change",
178
+ "social_avatar_url",
179
+ ],
180
+ },
181
+ {
182
+ "space": "management",
183
+ "subpath": "permissions",
184
+ "class": core.Permission,
185
+ "exclude_from_index": [
186
+ "relationships",
187
+ "acl",
188
+ "is_active",
189
+ "description",
190
+ "displayname",
191
+ "payload",
192
+ "subpaths",
193
+ "resource_types",
194
+ "actions",
195
+ "conditions",
196
+ "restricted_fields",
197
+ "allowed_fields_values",
198
+ ],
199
+ },
200
+ ]
201
+
202
+ SYS_ATTRIBUTES = [
203
+ "payload_string",
204
+ "query_policies",
205
+ "subpath",
206
+ "resource_type",
207
+ "meta_doc_id",
208
+ "payload_doc_id",
209
+ "payload_string",
210
+ "view_acl",
211
+ ]
212
+ redis_indices: dict[str, dict[str, Search]] = {}
213
+ POOL: BlockingConnectionPool = BlockingConnectionPool(
214
+ timeout=10,
215
+ host=settings.redis_host,
216
+ port=settings.redis_port,
217
+ password=settings.redis_password,
218
+ protocol=3,
219
+ max_connections=settings.redis_pool_max_connections,
220
+ decode_responses=True)
221
+
222
+ def __new__(cls):
223
+ if not hasattr(cls, 'instance'):
224
+ cls.instance = super(RedisServices, cls).__new__(cls)
225
+ return cls.instance
226
+
227
+ def __init__(self):
228
+ try:
229
+ super().__init__(connection_pool=RedisServices.POOL)
230
+ except redis.exceptions.ConnectionError as e: # type: ignore
231
+ print("[!FATAL]", e)
232
+ sys.exit(127)
233
+
234
+ async def close_pool(self):
235
+ # print('{"Disconnecting connection pool":"initated"}')
236
+ await self.aclose()
237
+ await RedisServices.POOL.aclose()
238
+ await RedisServices.POOL.disconnect(True)
239
+
240
+ async def create_index(
241
+ self,
242
+ space_name: str,
243
+ schema_name: str,
244
+ redis_schema: list[Field],
245
+ del_docs: bool = True,
246
+ ):
247
+ """
248
+ create redis schema index, drop it if exist first
249
+ """
250
+ try:
251
+ await self.redis_indices[space_name][schema_name].dropindex(
252
+ delete_documents=del_docs
253
+ )
254
+ except Exception as _:
255
+ pass
256
+ # logger.error(f"Error at redis_services.create_index: {e}")
257
+
258
+ await self.redis_indices[space_name][schema_name].create_index(
259
+ redis_schema,
260
+ definition=IndexDefinition(
261
+ prefix=[
262
+ f"{space_name}:{schema_name}:",
263
+ f"{space_name}:{schema_name}/",
264
+ ],
265
+ index_type=IndexType.JSON,
266
+ ),
267
+ )
268
+ # print(f"Created new index named {space_name}:{schema_name}\n")
269
+
270
+ def get_redis_index_fields(self, key_chain, property, redis_schema_definition):
271
+ """
272
+ takes a key and a value of a schema definition, and returns the redis schema index
273
+ """
274
+ REDIS_SCHEMA_DATA_TYPES_MAPPER = {
275
+ "string": TextField,
276
+ "boolean": TagField,
277
+ "integer": NumericField,
278
+ "number": NumericField,
279
+ "array": TagField,
280
+ }
281
+ if not isinstance(property, dict) or key_chain.endswith("."):
282
+ return redis_schema_definition
283
+
284
+ if "type" in property and property["type"] != "object":
285
+ if property["type"] == "null" or not isinstance(
286
+ property["type"], str
287
+ ):
288
+ return redis_schema_definition
289
+
290
+ property_name = key_chain.replace(".", "_")
291
+ sortable = True
292
+
293
+ if (
294
+ property["type"] == "array"
295
+ and property.get("items", {}).get("type", None) == "object"
296
+ and "items" in property
297
+ and "properties" in property["items"]
298
+ ):
299
+ for property_key, property_value in property["items"][
300
+ "properties"
301
+ ].items():
302
+ if property_value["type"] != "string":
303
+ continue
304
+ redis_schema_definition.append(
305
+ TagField(
306
+ f"$.{key_chain}.*.{property_key}",
307
+ as_name=f"{key_chain}_{property_key}",
308
+ )
309
+ )
310
+ return redis_schema_definition
311
+
312
+ if property["type"] == "array":
313
+ key_chain += ".*"
314
+ sortable = False
315
+
316
+ redis_schema_definition.append(
317
+ REDIS_SCHEMA_DATA_TYPES_MAPPER[property["type"]](
318
+ f"$.{key_chain}", sortable=sortable, as_name=property_name
319
+ )
320
+ )
321
+ return redis_schema_definition
322
+
323
+ if "oneOf" in property:
324
+ for item in property["oneOf"]:
325
+ redis_schema_definition = self.get_redis_index_fields(
326
+ key_chain, item, redis_schema_definition
327
+ )
328
+ return redis_schema_definition
329
+
330
+ if "properties" not in property:
331
+ return redis_schema_definition
332
+
333
+ for property_key, property_value in property["properties"].items():
334
+ redis_schema_definition = self.get_redis_index_fields(
335
+ f"{key_chain}.{property_key}", property_value, redis_schema_definition
336
+ )
337
+
338
+ return redis_schema_definition
339
+
340
+ def generate_redis_index_from_class(
341
+ self, class_ref: type[core.Meta], exclude_from_index: list
342
+ ) -> list[Field]:
343
+ class_types_to_redis_fields_mapper = {
344
+ "str": TextField,
345
+ "bool": TextField,
346
+ "UUID": TextField,
347
+ "list": TagField,
348
+ "datetime": NumericField,
349
+ "set": TagField,
350
+ "dict": TextField,
351
+ }
352
+
353
+ redis_schema : list[Field] = []
354
+ for field_name, model_field in class_ref.model_fields.items():
355
+ if field_name in exclude_from_index:
356
+ continue
357
+
358
+ mapper_key = None
359
+ for allowed_type in list(class_types_to_redis_fields_mapper.keys()):
360
+ if str(model_field.annotation).startswith(allowed_type):
361
+ mapper_key = allowed_type
362
+ break
363
+ if not mapper_key:
364
+ continue
365
+
366
+ redis_index_column_type = class_types_to_redis_fields_mapper[mapper_key]
367
+
368
+ redis_key = (
369
+ f"$.{field_name}"
370
+ if redis_index_column_type != TagField
371
+ else f"$.{field_name}.*"
372
+ )
373
+ redis_schema.append(redis_index_column_type(redis_key, as_name=field_name))
374
+
375
+ return redis_schema
376
+
377
+ async def create_custom_indices(self, for_space: str | None = None):
378
+ redis_schemas: dict[str, list] = {}
379
+ for i, index in enumerate(self.CUSTOM_INDICES):
380
+ if (
381
+ for_space
382
+ and index["space"] != for_space
383
+ or not isinstance(index["exclude_from_index"], list)
384
+ ):
385
+ continue
386
+
387
+ exclude_from_index: list = index["exclude_from_index"]
388
+
389
+ redis_schemas.setdefault(f"{index['space']}", [])
390
+ self.redis_indices.setdefault(
391
+ f"{index['space']}:meta", {}
392
+ )
393
+
394
+ generated_schema_fields : list[Field] = self.generate_redis_index_from_class(
395
+ self.CUSTOM_CLASSES[i], exclude_from_index
396
+ )
397
+
398
+ redis_schemas[f"{index['space']}"] = (
399
+ self.append_unique_index_fields(
400
+ generated_schema_fields,
401
+ redis_schemas[f"{index['space']}"],
402
+ )
403
+ )
404
+
405
+ for space_name, redis_schema in redis_schemas.items():
406
+ redis_schema = self.append_unique_index_fields(
407
+ redis_schema,
408
+ self.META_SCHEMA,
409
+ )
410
+ await self.create_index(
411
+ f"{space_name}",
412
+ "meta",
413
+ redis_schema,
414
+ )
415
+
416
+ async def create_indices(
417
+ self,
418
+ for_space: str | None = None,
419
+ for_schemas: list | None = None,
420
+ for_custom_indices: bool = True,
421
+ del_docs: bool = True,
422
+ ):
423
+ """
424
+ Loop over all spaces, and for each one we create: (only if indexing_enabled is true for the space)
425
+ 1-index for meta file called space_name:meta
426
+ 2-indices for schema files called space_name:schema_shortname
427
+ """
428
+ spaces = await self.get_doc_by_id("spaces")
429
+ for space_name in spaces:
430
+ space_obj = core.Space.model_validate_json(spaces[space_name])
431
+ if (
432
+ for_space and for_space != space_name
433
+ ) or not space_obj.indexing_enabled:
434
+ continue
435
+
436
+ # CREATE REDIS INDEX FOR THE META FILES INSIDE THE SPACE
437
+ self.redis_indices[f"{space_name}"] = {}
438
+ self.redis_indices[f"{space_name}"]["meta"] = self.ft(
439
+ f"{space_name}:meta"
440
+ )
441
+
442
+ await self.create_index(
443
+ f"{space_name}", "meta", self.META_SCHEMA, del_docs
444
+ )
445
+
446
+ # CREATE REDIS INDEX FOR EACH SCHEMA DEFINITION INSIDE THE SPACE
447
+ schemas_file_pattern = re.compile(r"(\w*).json")
448
+ schemas_glob = "*.json"
449
+ path = (
450
+ settings.spaces_folder
451
+ / space_name
452
+ / "schema"
453
+ )
454
+
455
+ for schema_path in path.glob(schemas_glob):
456
+ # GET SCHEMA SHORTNAME
457
+ match = schemas_file_pattern.search(str(schema_path))
458
+ if not match or not schema_path.is_file():
459
+ continue
460
+ schema_shortname = match.group(1)
461
+
462
+ if for_schemas and schema_shortname not in for_schemas:
463
+ continue
464
+
465
+ if schema_shortname in ["meta_schema", "meta"]:
466
+ continue
467
+
468
+ # GET SCHEMA PROPERTIES AND
469
+ # GENERATE REDIS INDEX DEFINITION BY MAPPIN SCHEMA PROPERTIES TO REDIS INDEX FIELDS
470
+ schema_content = json.loads(schema_path.read_text())
471
+ schema_content = resolve_schema_references(schema_content)
472
+ redis_schema_definition : list[Field] = self.META_SCHEMA
473
+ if "properties" in schema_content:
474
+ for key, property in schema_content["properties"].items():
475
+ generated_schema_fields = self.get_redis_index_fields(
476
+ key, property, []
477
+ )
478
+ redis_schema_definition = self.append_unique_index_fields(
479
+ generated_schema_fields, redis_schema_definition
480
+ )
481
+
482
+ elif "oneOf" in schema_content:
483
+ for item in schema_content["oneOf"]:
484
+ for key, property in item["properties"].items():
485
+ generated_schema_fields = self.get_redis_index_fields(
486
+ key, property, []
487
+ )
488
+ redis_schema_definition = (
489
+ self.append_unique_index_fields(
490
+ generated_schema_fields, redis_schema_definition
491
+ )
492
+ )
493
+
494
+ if redis_schema_definition:
495
+ self.redis_indices[f"{space_name}"][
496
+ schema_shortname
497
+ ] = self.ft(f"{space_name}:{schema_shortname}")
498
+ field_names = [f.as_name for f in redis_schema_definition]
499
+ if "meta_doc_id" not in field_names:
500
+ redis_schema_definition.append(TextField("$.meta_doc_id", no_stem=True, as_name="meta_doc_id")) # type: ignore
501
+
502
+ await self.create_index(
503
+ f"{space_name}",
504
+ schema_shortname,
505
+ redis_schema_definition,
506
+ del_docs,
507
+ )
508
+
509
+ if for_custom_indices:
510
+ await self.create_custom_indices(for_space)
511
+
512
+ def append_unique_index_fields(self, new_index: list[Field], base_index: list[Field]):
513
+ base_index_clone = base_index.copy()
514
+ for field in new_index:
515
+ registered_field = False
516
+ for base_field in base_index_clone:
517
+ if (
518
+ field.redis_args()[2] # Compare field name
519
+ == base_field.redis_args()[2] # Compare AS name
520
+ ):
521
+ registered_field = True
522
+ break
523
+ if not registered_field:
524
+ base_index_clone.append(field)
525
+
526
+ return base_index_clone
527
+
528
+ def generate_doc_id(
529
+ self,
530
+ space_name: str,
531
+ schema_shortname: str,
532
+ shortname: str,
533
+ subpath: str,
534
+ ):
535
+ # if subpath[0] == "/":
536
+ # subpath = subpath[1:]
537
+ # if subpath[-1] == "/":
538
+ # subpath = subpath[:-1]
539
+ subpath = subpath.strip("/")
540
+ return f"{space_name}:{schema_shortname}:{subpath}/{shortname}"
541
+
542
+ def prepare_meta_doc(
543
+ self, space_name: str, subpath: str, meta: core.Meta
544
+ ):
545
+ resource_type = ResourceType(meta.__class__.__name__.lower())
546
+ meta_doc_id = self.generate_doc_id(
547
+ space_name, "meta", meta.shortname, subpath
548
+ )
549
+ payload_doc_id = None
550
+ if meta.payload and meta.payload.schema_shortname:
551
+ payload_doc_id = self.generate_doc_id(
552
+ space_name,
553
+ meta.payload.schema_shortname,
554
+ meta.shortname,
555
+ subpath,
556
+ )
557
+ meta.model_rebuild()
558
+ meta_json = json.loads(meta.model_dump_json(serialize_as_any=False, exclude_none=True,warnings="error"))
559
+ meta_json["query_policies"] = generate_query_policies(
560
+ space_name,
561
+ subpath,
562
+ resource_type,
563
+ meta.is_active,
564
+ meta.owner_shortname,
565
+ meta.owner_group_shortname,
566
+ meta.shortname,
567
+ )
568
+ meta_json["view_acl"] = self.generate_view_acl(meta_json.get("acl"))
569
+ meta_json["subpath"] = subpath
570
+ meta_json["resource_type"] = resource_type
571
+ meta_json["created_at"] = meta.created_at.timestamp()
572
+ meta_json["updated_at"] = meta.updated_at.timestamp()
573
+ meta_json["payload_doc_id"] = payload_doc_id
574
+
575
+ return meta_doc_id, meta_json
576
+
577
+ def generate_view_acl(self, acl: list[dict[str, Any]] | None) -> list[str] | None:
578
+ if not acl:
579
+ return None
580
+
581
+ view_acl: list[str] = []
582
+
583
+ for access in acl:
584
+ if ActionType.view in access.get("allowed_actions", []) or ActionType.query in access.get("allowed_actions", []):
585
+ view_acl.append(access["user_shortname"])
586
+
587
+ return view_acl
588
+
589
+ async def save_meta_doc(
590
+ self, space_name: str, subpath: str, meta: core.Meta
591
+ ):
592
+ meta_doc_id, meta_json = self.prepare_meta_doc(
593
+ space_name, subpath, meta
594
+ )
595
+ await self.save_doc(meta_doc_id, meta_json)
596
+ return meta_doc_id, meta_json
597
+
598
+ def prepare_payload_doc(
599
+ self,
600
+ space_name: str,
601
+ subpath: str,
602
+ meta: core.Meta,
603
+ payload: dict,
604
+ resource_type: ResourceType = ResourceType.content,
605
+ ):
606
+ if meta.payload is None:
607
+ print(
608
+ f"Missing payload for {space_name}/{subpath} of type {resource_type}"
609
+ )
610
+ return "", {}
611
+ if meta.payload.body is None:
612
+ print(
613
+ f"Missing body for {space_name}/{subpath} of type {resource_type}"
614
+ )
615
+ return "", {}
616
+ if not isinstance(meta.payload.body, str):
617
+ print("body should be type of string")
618
+ return "", {}
619
+ payload_shortname = meta.payload.body.split(".")[0]
620
+ meta_doc_id = self.generate_doc_id(
621
+ space_name, "meta", payload_shortname, subpath
622
+ )
623
+ docid = self.generate_doc_id(
624
+ space_name,
625
+ meta.payload.schema_shortname or "",
626
+ payload_shortname,
627
+ subpath,
628
+ )
629
+
630
+ payload["query_policies"] = generate_query_policies(
631
+ space_name,
632
+ subpath,
633
+ resource_type,
634
+ meta.is_active,
635
+ meta.owner_shortname,
636
+ meta.owner_group_shortname,
637
+ meta.shortname,
638
+ )
639
+ if not payload["query_policies"]:
640
+ print(
641
+ f"Warning: this entry `{space_name}/{subpath}/{meta.shortname}` can't be accessed"
642
+ )
643
+ payload["subpath"] = subpath
644
+ payload["resource_type"] = resource_type
645
+ payload["shortname"] = payload_shortname
646
+ payload["meta_doc_id"] = meta_doc_id
647
+
648
+ return docid, payload
649
+
650
+ async def save_payload_doc(
651
+ self,
652
+ space_name: str,
653
+ subpath: str,
654
+ meta: core.Meta,
655
+ payload: dict,
656
+ resource_type: ResourceType = ResourceType.content,
657
+ ):
658
+ docid, payload = self.prepare_payload_doc(
659
+ space_name, subpath, meta, payload, resource_type
660
+ )
661
+ if docid == "":
662
+ return
663
+ await self.save_doc(docid, payload)
664
+
665
+ async def get_payload_doc(self, doc_id: str, resource_type: ResourceType):
666
+ resource_class = getattr(
667
+ sys.modules["models.core"],
668
+ camel_case(resource_type),
669
+ )
670
+ payload_redis_doc = await self.get_doc_by_id(doc_id)
671
+ payload_doc_content: dict = {}
672
+ if not payload_redis_doc:
673
+ return payload_doc_content
674
+
675
+ not_payload_attr = RedisServices.SYS_ATTRIBUTES + list(
676
+ resource_class.model_fields.keys()
677
+ )
678
+ for key, value in payload_redis_doc.items():
679
+ if key not in not_payload_attr:
680
+ payload_doc_content[key] = value
681
+ return payload_doc_content
682
+
683
+ async def save_lock_doc(
684
+ self,
685
+ space_name: str,
686
+ subpath: str,
687
+ payload_shortname: str,
688
+ owner_shortname: str,
689
+ ttl: int,
690
+ ) -> LockAction:
691
+ lock_doc_id = self.generate_doc_id(
692
+ space_name, "lock", payload_shortname, subpath
693
+ )
694
+ lock_data = await self.get_lock_doc(
695
+ space_name, subpath, payload_shortname
696
+ )
697
+ if not lock_data:
698
+ payload = {
699
+ "owner_shortname": owner_shortname,
700
+ "lock_time": str(datetime.now().isoformat()),
701
+ }
702
+ result = await self.save_doc(lock_doc_id, payload, nx=True)
703
+ if result is None:
704
+ lock_payload = await self.get_lock_doc(
705
+ space_name, subpath, payload_shortname
706
+ )
707
+ if lock_payload["owner_shortname"] != owner_shortname:
708
+ raise api.Exception(
709
+ status_code=status.HTTP_403_FORBIDDEN,
710
+ error=api.Error(
711
+ type="lock",
712
+ code=InternalErrorCode.LOCKED_ENTRY,
713
+ message=f"Entry is already locked by {lock_payload['owner_shortname']}",
714
+ ),
715
+ )
716
+ lock_type = LockAction.lock
717
+ else:
718
+ lock_type = LockAction.extend
719
+ await self.set_ttl(lock_doc_id, ttl)
720
+ return lock_type
721
+
722
+ async def get_lock_doc(
723
+ self,
724
+ space_name: str,
725
+ subpath: str,
726
+ payload_shortname: str,
727
+ ):
728
+ lock_doc_id = self.generate_doc_id(
729
+ space_name, "lock", payload_shortname, subpath
730
+ )
731
+ return await self.get_doc_by_id(lock_doc_id)
732
+
733
+ async def delete_lock_doc(
734
+ self,
735
+ space_name: str,
736
+ subpath: str,
737
+ payload_shortname: str,
738
+ ):
739
+ await self.delete_doc(
740
+ space_name, "lock", payload_shortname, subpath
741
+ )
742
+
743
+ async def is_entry_locked(
744
+ self,
745
+ space_name: str,
746
+ subpath: str,
747
+ shortname: str,
748
+ user_shortname: str,
749
+ ):
750
+ lock_payload = await self.get_lock_doc(
751
+ space_name, subpath, shortname
752
+ )
753
+ if lock_payload:
754
+ if user_shortname:
755
+ return lock_payload["owner_shortname"] != user_shortname
756
+ else:
757
+ return True
758
+ return False
759
+
760
+ async def save_doc(
761
+ self, doc_id: str, payload: dict, path: str = Path.root_path(), nx: bool = False
762
+ ):
763
+ x = self.json().set(doc_id, path, payload, nx=nx)
764
+ if x and isinstance(x, Awaitable):
765
+ await x
766
+
767
+ async def save_bulk(self, data: list, path: str = Path.root_path()):
768
+ pipe = self.pipeline()
769
+ for document in data:
770
+ pipe.json().set(document["doc_id"], path, document["payload"])
771
+ return await pipe.execute()
772
+
773
+ async def get_count(self, space_name: str, schema_shortname: str):
774
+ ft_index = self.ft(f"{space_name}:{schema_shortname}")
775
+
776
+ try:
777
+ info = await ft_index.info()
778
+ return info["num_docs"] # type: ignore
779
+ except Exception as e:
780
+ logger.error(f"Error at redis_services.get_count: {e}")
781
+ return 0
782
+
783
+ # aggregate_request = AggregateRequest().group_by([], count_reducer().alias("counter"))
784
+ # aggregate = await ft_index.aggregate(aggregate_request)
785
+ # print("\n\n\n aggregate res: ", aggregate.rows, "\n\n")
786
+
787
+ async def search(
788
+ self,
789
+ space_name: str,
790
+ search: str,
791
+ filters: dict[str, str | list],
792
+ limit: int,
793
+ offset: int,
794
+ exact_subpath: bool = False,
795
+ sort_type: SortType = SortType.ascending,
796
+ sort_by: str | None = None,
797
+ highlight_fields: list[str] | None = None,
798
+ schema_name: str = "meta",
799
+ return_fields: list = [],
800
+ ):
801
+ # Tries to get the index from the provided space
802
+ try:
803
+ ft_index = self.ft(f"{space_name}:{schema_name}")
804
+ await ft_index.info()
805
+ except Exception as e:
806
+ logger.error(
807
+ f"Error accessing index: {space_name}:{schema_name}, at redis_services.search: {e}"
808
+ )
809
+ return {"data": [], "total": 0}
810
+
811
+ search_query = Query(
812
+ query_string=self.prepare_query_string(search, filters, exact_subpath)
813
+ )
814
+
815
+ if highlight_fields:
816
+ search_query.highlight(highlight_fields, ["", ""])
817
+
818
+ if sort_by:
819
+ search_query.sort_by(sort_by, sort_type == SortType.ascending)
820
+
821
+ if return_fields:
822
+ search_query.return_fields(*return_fields)
823
+
824
+ search_query.paging(offset, limit)
825
+
826
+ try:
827
+ # print(f"ARGS {search_query.get_args()} O {search_query.query_string()}")
828
+ search_res = await ft_index.search(query=search_query) # type: ignore
829
+ if (
830
+ isinstance(search_res, dict)
831
+ and "results" in search_res
832
+ and "total_results" in search_res
833
+ ):
834
+
835
+ return {
836
+ "data": [
837
+ one["extra_attributes"]["$"]
838
+ for one in search_res["results"]
839
+ if "extra_attributes" in one
840
+ ],
841
+ "total": search_res["total_results"],
842
+ }
843
+ else:
844
+ return {}
845
+ except Exception:
846
+ return {}
847
+
848
+ async def aggregate(
849
+ self,
850
+ space_name: str,
851
+ search: str,
852
+ filters: dict[str, str | list],
853
+ group_by: list[str],
854
+ reducers: list[RedisReducer],
855
+ max: int = 10,
856
+ exact_subpath: bool = False,
857
+ sort_type: SortType = SortType.ascending,
858
+ sort_by: str | None = None,
859
+ schema_name: str = "meta",
860
+ load: list = [],
861
+ ) -> list:
862
+ # Tries to get the index from the provided space
863
+ try:
864
+ ft_index = self.ft(f"{space_name}:{schema_name}")
865
+ await ft_index.info()
866
+ except Exception:
867
+ return []
868
+
869
+ aggr_request = aggregation.AggregateRequest(
870
+ self.prepare_query_string(search, filters, exact_subpath)
871
+ )
872
+ if group_by:
873
+ reducers_functions = [
874
+ RedisReducerName.mapper(reducer.reducer_name)(*reducer.args).alias(
875
+ reducer.alias
876
+ )
877
+ for reducer in reducers
878
+ ]
879
+ aggr_request.group_by(group_by, *reducers_functions)
880
+
881
+ if sort_by:
882
+ aggr_request.sort_by(
883
+ [# type: ignore
884
+ str(
885
+ aggregation.Desc(f"@{sort_by}")
886
+ if sort_type == SortType.ascending
887
+ else aggregation.Asc(f"@{sort_by}")
888
+ )
889
+ ],
890
+ max=max,
891
+ )
892
+
893
+ if load:
894
+ aggr_request.load(*load)
895
+
896
+ try:
897
+ aggr_res = await ft_index.aggregate(aggr_request) # type: ignore
898
+ if aggr_res.get("results") and isinstance(aggr_res["results"], list): # type: ignore
899
+ return aggr_res["results"] # type: ignore
900
+ except Exception:
901
+ pass
902
+ return []
903
+
904
+ def prepare_query_string(
905
+ self, search: str, filters: dict[str, str | list], exact_subpath: bool
906
+ ):
907
+ query_string = search
908
+
909
+ redis_escape_chars = str.maketrans(
910
+ {":": r"\:", "/": r"\/", "-": r"\-", " ": r"\ "}
911
+ )
912
+ if filters.get("query_policies", None) == []:
913
+ filters["query_policies"] = ["__NONE__"]
914
+
915
+ for item in filters.items():
916
+ if item[0] == "tags" and item[1]:
917
+ query_string += (
918
+ " @"
919
+ + item[0]
920
+ + ":{"
921
+ + "|".join(item[1]).translate(redis_escape_chars)
922
+ + "}"
923
+ )
924
+ elif item[0] == "query_policies" and item[1] is not None:
925
+ query_string += (
926
+ f" ((@{item[0]}:{{" + "|".join(item[1]).translate(redis_escape_chars) + "})"
927
+ )
928
+ if filters.get("user_shortname", None) is not None:
929
+ query_string += (
930
+ f" | (@view_acl:{{{filters['user_shortname']}}}) )"
931
+ )
932
+ else:
933
+ query_string += ")"
934
+ elif item[0] == "created_at" and item[1]:
935
+ query_string += f" @{item[0]}:{item[1]}"
936
+ elif item[0] == "subpath" and exact_subpath:
937
+ search_value = ""
938
+ for subpath in item[1]: # Handle existence/absence of `/`
939
+ search_value += "|" + subpath.strip("/")
940
+ search_value += "|" + f"/{subpath}".replace("//", "/")
941
+
942
+ exact_subpath_value = search_value.strip("|").translate(
943
+ redis_escape_chars
944
+ )
945
+ query_string += f" @exact_subpath:{{{exact_subpath_value}}}"
946
+ elif item[0] == "subpath" and item[1][0] == "/":
947
+ pass
948
+ elif item[1] and item[0] != "user_shortname":
949
+ query_string += " @" + item[0] + ":(" + "|".join(item[1]) + ")"
950
+
951
+ return query_string or "*"
952
+
953
+ async def get_doc_by_id(self, doc_id: str) -> Any:
954
+ try:
955
+ x = self.json().get(name=doc_id)
956
+ if x and isinstance(x, Awaitable):
957
+ value = await x
958
+ if isinstance(value, dict):
959
+ return value
960
+ if isinstance(value, str):
961
+ return json.loads(value)
962
+ else:
963
+ raise Exception(f"Not json dict at id: {doc_id}. data: {value=}")
964
+ else:
965
+ raise Exception(f"Not awaitable {x=}")
966
+ except Exception as e:
967
+ logger.warning(f"Error at redis_services.get_doc_by_id: {doc_id=} {e}")
968
+ return {}
969
+
970
+ async def get_docs_by_ids(self, docs_ids: list[str]) -> list:
971
+ try:
972
+ x = self.json().mget(docs_ids, "$")
973
+ if x and isinstance(x, Awaitable):
974
+ value = await x
975
+ if isinstance(value, list):
976
+ return value
977
+ except Exception as e:
978
+ logger.warning(f"Error at redis_services.get_docs_by_ids: {e}")
979
+ return []
980
+
981
+ async def get_content_by_id(self, doc_id: str) -> Any:
982
+ try:
983
+ return await self.get(doc_id)
984
+ except Exception as e:
985
+ logger.warning(f"Error at redis_services.get_content_by_id: {e}")
986
+ return ""
987
+
988
+ async def delete_doc(
989
+ self, space_name, schema_shortname, shortname, subpath
990
+ ):
991
+ docid = self.generate_doc_id(
992
+ space_name, schema_shortname, shortname, subpath
993
+ )
994
+ try:
995
+ x = self.json().delete(key=docid)
996
+ if x and isinstance(x, Awaitable):
997
+ await x
998
+ except Exception as e:
999
+ logger.warning(f"Error at redis_services.delete_doc: {e}")
1000
+
1001
+ async def move_payload_doc(
1002
+ self,
1003
+ space_name,
1004
+ schema_shortname,
1005
+ src_shortname,
1006
+ src_subpath,
1007
+ dest_shortname,
1008
+ dest_subpath,
1009
+ ):
1010
+ docid = self.generate_doc_id(
1011
+ space_name, schema_shortname, src_shortname, src_subpath
1012
+ )
1013
+
1014
+ try:
1015
+ doc_content = await self.get_doc_by_id(docid)
1016
+ await self.delete_doc(
1017
+ space_name, schema_shortname, src_shortname, src_subpath
1018
+ )
1019
+
1020
+ new_docid = self.generate_doc_id(
1021
+ space_name, schema_shortname, dest_shortname, dest_subpath
1022
+ )
1023
+ await self.save_doc(new_docid, doc_content)
1024
+
1025
+ except Exception as e:
1026
+ logger.warning(f"Error at redis_services.move_payload_doc: {e}")
1027
+
1028
+ async def move_meta_doc(
1029
+ self, space_name, src_shortname, src_subpath, dest_subpath, meta
1030
+ ):
1031
+ try:
1032
+ await self.delete_doc(
1033
+ space_name, "meta", src_shortname, src_subpath
1034
+ )
1035
+ await self.save_meta_doc(space_name, dest_subpath, meta)
1036
+ except Exception as e:
1037
+ logger.warning(f"Error at redis_services.move_meta_doc: {e}")
1038
+
1039
+ async def get_keys(self, pattern: str = "*") -> list:
1040
+ try:
1041
+ value = await self.keys(pattern)
1042
+ if isinstance(value, list):
1043
+ return value
1044
+ except Exception as e:
1045
+ logger.warning(f"Error at redis_services.get_keys: {e}")
1046
+ return []
1047
+
1048
+ async def del_keys(self, keys: list):
1049
+ try:
1050
+ return await self.delete(*keys)
1051
+ except Exception as e:
1052
+ logger.warning(f"Error at redis_services.del_keys {keys}: {e}")
1053
+ return False
1054
+
1055
+ async def get_key(self, key) -> str | None:
1056
+ value = await self.get(key)
1057
+ if isinstance(value, str):
1058
+ return value
1059
+ else:
1060
+ return None
1061
+
1062
+ async def getdel_key(self, key) -> str | None:
1063
+ value = await self.getdel(key)
1064
+ if isinstance(value, str):
1065
+ return value
1066
+ else:
1067
+ return None
1068
+
1069
+ async def set_key(self, key, value, ex=None, nx: bool = False):
1070
+ return await self.set(key, value, ex=ex, nx=nx)
1071
+
1072
+ async def set_ttl(self, key: str, ttl: int):
1073
+ return await self.expire(key, ttl)
1074
+
1075
+ async def drop_index(self, name: str, delete_docs: bool = False):
1076
+ try:
1077
+ ft_index = self.ft(name)
1078
+ await ft_index.dropindex(delete_docs)
1079
+ return True
1080
+ except Exception:
1081
+ return False
1082
+
1083
+ async def list_indices(self):
1084
+ x = self.ft().execute_command("FT._LIST")
1085
+ if x and isinstance(x, Awaitable):
1086
+ return await x
1087
+
1088
+
1089
+
1090
+ async def get_all_document_ids(self, index: str, search_str: str = "*") -> list[str]:
1091
+ # Initialize the list to hold document IDs
1092
+ document_ids = []
1093
+
1094
+ # Fetch all document IDs
1095
+ ft_index = self.ft(index)
1096
+ total_docs = int((await ft_index.info())['num_docs']) # type: ignore
1097
+
1098
+ batch_size = 10000 # You can adjust the batch size based on your needs
1099
+
1100
+ for offset in range(0, total_docs, batch_size):
1101
+ query = Query(search_str).paging(offset, batch_size)
1102
+ results = ft_index.search(query) # type: ignore
1103
+ if results and isinstance(results, Awaitable):
1104
+ results = await results
1105
+
1106
+ if 'results' not in results or not isinstance(results['results'], list): # type: ignore
1107
+ break
1108
+ document_ids.extend([doc['id'] for doc in results['results']]) #type: ignore
1109
+
1110
+ return document_ids