howler-api 4.0.0.dev1198__tar.gz → 4.0.0.dev1246__tar.gz

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 (228) hide show
  1. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/PKG-INFO +2 -2
  2. howler_api-4.0.0.dev1246/howler/actions/add_to_bundle.py +171 -0
  3. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/actions/demote.py +2 -2
  4. howler_api-4.0.0.dev1246/howler/actions/remove_from_bundle.py +144 -0
  5. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/actions/transition.py +7 -5
  6. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/__init__.py +1 -2
  7. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/socket.py +16 -56
  8. howler_api-4.0.0.dev1198/howler/services/docs_service.py → howler_api-4.0.0.dev1246/howler/api/v1/__init__.py +46 -38
  9. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/action.py +2 -1
  10. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/analytic.py +106 -17
  11. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/hit.py +128 -52
  12. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/tool.py +22 -47
  13. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/user.py +1 -1
  14. howler_api-4.0.0.dev1246/howler/api/v1/utils/etag.py +84 -0
  15. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/app.py +0 -17
  16. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/classification.py +0 -1
  17. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/loader.py +6 -5
  18. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/swagger.py +80 -23
  19. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/cronjobs/retention.py +67 -2
  20. howler_api-4.0.0.dev1246/howler/cronjobs/rules.py +279 -0
  21. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/collection.py +29 -47
  22. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/howler_store.py +14 -29
  23. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/types.py +6 -1
  24. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/helper/discover.py +8 -12
  25. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/helper/hit.py +4 -4
  26. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/helper/search.py +3 -13
  27. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/base.py +2 -26
  28. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/helper.py +8 -117
  29. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/config.py +27 -14
  30. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/event.py +1 -1
  31. howler_api-4.0.0.dev1198/howler/odm/models/record.py → howler_api-4.0.0.dev1246/howler/odm/models/hit.py +28 -15
  32. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/howler_data.py +35 -3
  33. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/view.py +0 -8
  34. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/random_data.py +50 -481
  35. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/randomizer.py +2 -6
  36. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/__init__.py +4 -11
  37. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/queues/comms.py +1 -2
  38. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/security/socket.py +2 -27
  39. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/analytic_service.py +2 -6
  40. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/config_service.py +3 -5
  41. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/dossier_service.py +2 -11
  42. howler_api-4.0.0.dev1246/howler/services/event_service.py +96 -0
  43. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/hit_service.py +177 -94
  44. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/user_service.py +1 -7
  45. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/socket_utils.py +26 -5
  46. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/str_utils.py +1 -1
  47. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/pyproject.toml +5 -5
  48. howler_api-4.0.0.dev1198/howler/actions/add_to_bundle.py +0 -152
  49. howler_api-4.0.0.dev1198/howler/actions/add_to_case.py +0 -141
  50. howler_api-4.0.0.dev1198/howler/actions/remove_from_bundle.py +0 -166
  51. howler_api-4.0.0.dev1198/howler/api/v1/__init__.py +0 -44
  52. howler_api-4.0.0.dev1198/howler/api/v1/utils/etag.py +0 -106
  53. howler_api-4.0.0.dev1198/howler/api/v2/__init__.py +0 -44
  54. howler_api-4.0.0.dev1198/howler/api/v2/case.py +0 -471
  55. howler_api-4.0.0.dev1198/howler/api/v2/fuzzy.py +0 -110
  56. howler_api-4.0.0.dev1198/howler/api/v2/ingest.py +0 -369
  57. howler_api-4.0.0.dev1198/howler/api/v2/search.py +0 -333
  58. howler_api-4.0.0.dev1198/howler/cronjobs/correlation.py +0 -36
  59. howler_api-4.0.0.dev1198/howler/odm/constants.py +0 -37
  60. howler_api-4.0.0.dev1198/howler/odm/mixins.py +0 -97
  61. howler_api-4.0.0.dev1198/howler/odm/models/case.py +0 -269
  62. howler_api-4.0.0.dev1198/howler/odm/models/event.py +0 -124
  63. howler_api-4.0.0.dev1198/howler/odm/models/hit.py +0 -31
  64. howler_api-4.0.0.dev1198/howler/services/bundle_compat_service.py +0 -282
  65. howler_api-4.0.0.dev1198/howler/services/case_service.py +0 -1094
  66. howler_api-4.0.0.dev1198/howler/services/comms_service.py +0 -134
  67. howler_api-4.0.0.dev1198/howler/services/correlation_service.py +0 -219
  68. howler_api-4.0.0.dev1198/howler/services/event_service.py +0 -140
  69. howler_api-4.0.0.dev1198/howler/services/fuzzy_service.py +0 -392
  70. howler_api-4.0.0.dev1198/howler/services/search_service.py +0 -238
  71. howler_api-4.0.0.dev1198/howler/services/viewer_service.py +0 -43
  72. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/README.md +0 -0
  73. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/__init__.py +0 -0
  74. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/actions/__init__.py +0 -0
  75. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/actions/add_label.py +0 -0
  76. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/actions/change_field.py +0 -0
  77. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/actions/example_plugin.py +0 -0
  78. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/actions/prioritization.py +0 -0
  79. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/actions/promote.py +0 -0
  80. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/actions/remove_label.py +0 -0
  81. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/base.py +0 -0
  82. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/auth.py +0 -0
  83. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/clue.py +0 -0
  84. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/configs.py +0 -0
  85. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/dossier.py +0 -0
  86. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/help.py +0 -0
  87. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/notebook.py +0 -0
  88. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/overview.py +0 -0
  89. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/search.py +0 -0
  90. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/template.py +0 -0
  91. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/utils/__init__.py +0 -0
  92. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/utils/params.py +0 -0
  93. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/api/v1/view.py +0 -0
  94. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/README.md +0 -0
  95. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/__init__.py +0 -0
  96. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/classification.yml +0 -0
  97. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/exceptions.py +0 -0
  98. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/logging/__init__.py +0 -0
  99. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/logging/audit.py +0 -0
  100. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/logging/format.py +0 -0
  101. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/net.py +0 -0
  102. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/net_static.py +0 -0
  103. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/common/random_user.py +0 -0
  104. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/config.py +0 -0
  105. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/cronjobs/__init__.py +0 -0
  106. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/cronjobs/action_queue_worker.py +0 -0
  107. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/cronjobs/view_cleanup.py +0 -0
  108. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/README.md +0 -0
  109. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/__init__.py +0 -0
  110. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/bulk.py +0 -0
  111. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/constants.py +0 -0
  112. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/exceptions.py +0 -0
  113. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/migrations/fix_process.py +0 -0
  114. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/operations.py +0 -0
  115. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/schemas.py +0 -0
  116. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/store.py +0 -0
  117. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/support/__init__.py +0 -0
  118. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/support/build.py +0 -0
  119. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/datastore/support/schemas.py +0 -0
  120. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/error.py +0 -0
  121. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/external/README.md +0 -0
  122. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/external/__init__.py +0 -0
  123. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/external/generate_mitre.py +0 -0
  124. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/external/generate_sigma_rules.py +0 -0
  125. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/external/generate_tlds.py +0 -0
  126. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/external/reindex_data.py +0 -0
  127. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/external/wipe_databases.py +0 -0
  128. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/gunicorn_config.py +0 -0
  129. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/healthz.py +0 -0
  130. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/helper/__init__.py +0 -0
  131. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/helper/azure.py +0 -0
  132. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/helper/oauth.py +0 -0
  133. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/helper/workflow.py +0 -0
  134. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/helper/ws.py +0 -0
  135. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/README.md +0 -0
  136. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/__init__.py +0 -0
  137. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/charter.txt +0 -0
  138. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/howler_enum.py +0 -0
  139. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/__init__.py +0 -0
  140. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/action.py +0 -0
  141. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/analytic.py +0 -0
  142. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/assemblyline.py +0 -0
  143. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/aws.py +0 -0
  144. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/azure.py +0 -0
  145. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/cbs.py +0 -0
  146. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/clue.py +0 -0
  147. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/dossier.py +0 -0
  148. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/__init__.py +0 -0
  149. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/agent.py +0 -0
  150. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/autonomous_system.py +0 -0
  151. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/client.py +0 -0
  152. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/cloud.py +0 -0
  153. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/code_signature.py +0 -0
  154. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/container.py +0 -0
  155. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/dns.py +0 -0
  156. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/egress.py +0 -0
  157. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/elf.py +0 -0
  158. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/email.py +0 -0
  159. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/error.py +0 -0
  160. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/faas.py +0 -0
  161. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/file.py +0 -0
  162. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/geo.py +0 -0
  163. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/group.py +0 -0
  164. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/hash.py +0 -0
  165. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/host.py +0 -0
  166. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/http.py +0 -0
  167. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/ingress.py +0 -0
  168. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/interface.py +0 -0
  169. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/network.py +0 -0
  170. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/observer.py +0 -0
  171. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/organization.py +0 -0
  172. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/os.py +0 -0
  173. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/pe.py +0 -0
  174. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/process.py +0 -0
  175. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/registry.py +0 -0
  176. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/related.py +0 -0
  177. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/rule.py +0 -0
  178. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/server.py +0 -0
  179. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/threat.py +0 -0
  180. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/tls.py +0 -0
  181. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/url.py +0 -0
  182. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/user.py +0 -0
  183. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/user_agent.py +0 -0
  184. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/ecs/vulnerability.py +0 -0
  185. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/gcp.py +0 -0
  186. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/lead.py +0 -0
  187. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/localized_label.py +0 -0
  188. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/overview.py +0 -0
  189. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/pivot.py +0 -0
  190. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/template.py +0 -0
  191. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/odm/models/user.py +0 -0
  192. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/patched.py +0 -0
  193. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/plugins/__init__.py +0 -0
  194. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/plugins/config.py +0 -0
  195. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/__init__.py +0 -0
  196. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/README.md +0 -0
  197. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/counters.py +0 -0
  198. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/events.py +0 -0
  199. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/hash.py +0 -0
  200. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/lock.py +0 -0
  201. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/queues/__init__.py +0 -0
  202. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/queues/multi.py +0 -0
  203. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/queues/named.py +0 -0
  204. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/queues/priority.py +0 -0
  205. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/set.py +0 -0
  206. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/remote/datatypes/user_quota_tracker.py +0 -0
  207. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/security/__init__.py +0 -0
  208. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/security/utils.py +0 -0
  209. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/__init__.py +0 -0
  210. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/action_service.py +0 -0
  211. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/auth_service.py +0 -0
  212. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/jwt_service.py +0 -0
  213. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/lucene_service.py +0 -0
  214. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/notebook_service.py +0 -0
  215. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/overview_service.py +0 -0
  216. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/services/template_service.py +0 -0
  217. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/telemetry.py +0 -0
  218. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/__init__.py +0 -0
  219. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/annotations.py +0 -0
  220. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/chunk.py +0 -0
  221. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/compat.py +0 -0
  222. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/constants.py +0 -0
  223. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/dict_utils.py +0 -0
  224. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/isotime.py +0 -0
  225. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/list_utils.py +0 -0
  226. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/lucene.py +0 -0
  227. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/path.py +0 -0
  228. {howler_api-4.0.0.dev1198 → howler_api-4.0.0.dev1246}/howler/utils/uid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: howler-api
3
- Version: 4.0.0.dev1198
3
+ Version: 4.0.0.dev1246
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -29,7 +29,7 @@ Requires-Dist: elasticsearch (==8.19.3)
29
29
  Requires-Dist: flasgger (>=0.9.7.1,<0.10.0.0)
30
30
  Requires-Dist: flask (==3.1.3)
31
31
  Requires-Dist: flask-caching (==2.4.0)
32
- Requires-Dist: gevent (>=25.9.1,<26.0.0)
32
+ Requires-Dist: gevent (==23.9.1)
33
33
  Requires-Dist: gunicorn (==23.0.0)
34
34
  Requires-Dist: luqum (>=1.0.0,<2.0.0)
35
35
  Requires-Dist: mergedeep (>=1.3.4,<2.0.0)
@@ -0,0 +1,171 @@
1
+ from typing import Optional
2
+
3
+ from howler.actions import check_hit_limit
4
+ from howler.common.loader import datastore
5
+ from howler.datastore.operations import OdmHelper
6
+ from howler.odm.models.action import VALID_TRIGGERS
7
+ from howler.odm.models.hit import Hit
8
+ from howler.odm.models.user import User
9
+ from howler.services import hit_service
10
+ from howler.utils.str_utils import sanitize_lucene_query
11
+
12
+ hit_helper = OdmHelper(Hit)
13
+
14
+ OPERATION_ID = "add_to_bundle"
15
+ MAX_HITS_BASIC = 10
16
+ MAX_HITS_ADVANCED = 1000
17
+ SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
18
+
19
+
20
+ def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs):
21
+ """Add a set of hits matching the query to the specified bundle.
22
+
23
+ Args:
24
+ query (str): The query containing the matching hits
25
+ bundle_id (str): The `howler.id` of the bundle to add the hits to.
26
+ """
27
+ report = []
28
+
29
+ if not bundle_id:
30
+ return [
31
+ {
32
+ "query": query,
33
+ "outcome": "error",
34
+ "title": "Invalid Bundle ID",
35
+ "message": "Bundle ID cannot be empty.",
36
+ }
37
+ ]
38
+
39
+ try:
40
+ bundle_hit = hit_service.get_hit(bundle_id, as_odm=True)
41
+ if not bundle_hit or not bundle_hit.howler.is_bundle:
42
+ report.append(
43
+ {
44
+ "query": query,
45
+ "outcome": "error",
46
+ "title": "Invalid Bundle",
47
+ "message": f"Either a hit with ID {bundle_id} does not exist, or it is not a bundle.",
48
+ }
49
+ )
50
+ return report
51
+
52
+ ds = datastore()
53
+
54
+ skipped_hits_bundles = ds.hit.search(
55
+ f"({query}) AND howler.is_bundle:true",
56
+ fl="howler.id",
57
+ )["items"]
58
+
59
+ if len(skipped_hits_bundles) > 0:
60
+ report.append(
61
+ {
62
+ "query": f"({query}) AND howler.is_bundle:true",
63
+ "outcome": "skipped",
64
+ "title": "Skipped Bundles",
65
+ "message": "Bundles cannot be added to a bundle.",
66
+ }
67
+ )
68
+
69
+ skipped_hits_already_added = ds.hit.search(
70
+ f"({query}) AND (howler.bundles:{sanitize_lucene_query(bundle_id)})",
71
+ fl="howler.id",
72
+ )["items"]
73
+
74
+ if len(skipped_hits_already_added) > 0:
75
+ report.append(
76
+ {
77
+ "query": f"({query}) AND (howler.bundles:{sanitize_lucene_query(bundle_id)})",
78
+ "outcome": "skipped",
79
+ "title": "Skipped Hits",
80
+ "message": "These hits have already been added to the specified bundle.",
81
+ }
82
+ )
83
+
84
+ safe_query = f"({query}) AND (-howler.bundles:({sanitize_lucene_query(bundle_id)}) AND howler.is_bundle:false)"
85
+
86
+ # Check hit limit against the effective query (not raw query)
87
+ if user:
88
+ limit_error = check_hit_limit(safe_query, user, MAX_HITS_BASIC, MAX_HITS_ADVANCED)
89
+ if limit_error:
90
+ return [limit_error]
91
+
92
+ matching_hits = ds.hit.search(safe_query, rows=MAX_HITS_ADVANCED, fl="howler.id")["items"]
93
+ if len(matching_hits) < 1:
94
+ report.append(
95
+ {
96
+ "query": safe_query,
97
+ "outcome": "skipped",
98
+ "title": "No Matching Hits",
99
+ "message": "There were no hits matching this query.",
100
+ }
101
+ )
102
+ return report
103
+
104
+ ds.hit.update_by_query(
105
+ safe_query,
106
+ [hit_helper.list_add("howler.bundles", sanitize_lucene_query(bundle_id), if_missing=True)],
107
+ )
108
+
109
+ operations = [
110
+ hit_helper.list_add(
111
+ "howler.hits",
112
+ hit["howler"]["id"],
113
+ if_missing=True,
114
+ )
115
+ for hit in matching_hits
116
+ ]
117
+
118
+ operations.append(hit_helper.update("howler.bundle_size", len(operations)))
119
+ hit_service.update_hit(
120
+ bundle_id,
121
+ operations,
122
+ )
123
+ bundle_hit = hit_service.get_hit(bundle_id, as_odm=True)
124
+ report.append(
125
+ {
126
+ "query": safe_query.replace("-howler.bundles", "howler.bundles"),
127
+ "outcome": "success",
128
+ "title": "Executed Successfully",
129
+ "message": "The specified bundle has had all matching hits added.",
130
+ }
131
+ )
132
+ except Exception as e:
133
+ report.append(
134
+ {
135
+ "query": query,
136
+ "outcome": "error",
137
+ "title": "Failed to Execute",
138
+ "message": f"Unknown exception occurred: {str(e)}",
139
+ }
140
+ )
141
+
142
+ return report
143
+
144
+
145
+ def specification():
146
+ """Specify various properties of the action, such as title, descriptions, permissions and input steps."""
147
+ return {
148
+ "id": OPERATION_ID,
149
+ "title": "Add to Bundle",
150
+ "priority": 6,
151
+ "i18nKey": f"operations.{OPERATION_ID}",
152
+ "description": {
153
+ "short": "Add a set of hits to a bundle",
154
+ "long": execute.__doc__,
155
+ },
156
+ "roles": ["automation_basic", "actionrunner_basic"],
157
+ "steps": [
158
+ {
159
+ "args": {"bundle_id": []},
160
+ "options": {},
161
+ "validation": {
162
+ "warn": {"query": "howler.bundles:($bundle_id) OR howler.is_bundle:true"},
163
+ "error": {
164
+ "query": "howler.id:$bundle_id AND howler.is_bundle:false",
165
+ "message": "The bundle id given must be a bundle.",
166
+ },
167
+ },
168
+ }
169
+ ],
170
+ "triggers": VALID_TRIGGERS,
171
+ }
@@ -9,7 +9,7 @@ from howler.odm.models.howler_data import (
9
9
  Assessment,
10
10
  AssessmentEscalationMap,
11
11
  Escalation,
12
- Status,
12
+ HitStatus,
13
13
  )
14
14
  from howler.odm.models.user import User
15
15
  from howler.utils.str_utils import sanitize_lucene_query
@@ -99,7 +99,7 @@ def execute(
99
99
  "howler.assignment",
100
100
  user.get("uname", "automation") if user else "automation",
101
101
  ),
102
- odm_helper.update("howler.status", Status.RESOLVED),
102
+ odm_helper.update("howler.status", HitStatus.RESOLVED),
103
103
  ],
104
104
  )
105
105
 
@@ -0,0 +1,144 @@
1
+ from typing import Optional
2
+
3
+ from howler.actions import check_hit_limit
4
+ from howler.common.exceptions import HowlerException
5
+ from howler.common.loader import datastore
6
+ from howler.datastore.operations import OdmHelper
7
+ from howler.odm.models.action import VALID_TRIGGERS
8
+ from howler.odm.models.hit import Hit
9
+ from howler.odm.models.user import User
10
+ from howler.services import hit_service
11
+ from howler.utils.str_utils import sanitize_lucene_query
12
+
13
+ hit_helper = OdmHelper(Hit)
14
+
15
+ OPERATION_ID = "remove_from_bundle"
16
+ MAX_HITS_BASIC = 10
17
+ MAX_HITS_ADVANCED = 1000
18
+ SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
19
+
20
+
21
+ def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs):
22
+ """Remove a set of hits matching the query from the specified bundle.
23
+
24
+ Args:
25
+ query (str): The query containing the matching hits
26
+ bundle_id (str): The `howler.id` of the bundle to remove the hits from.
27
+ """
28
+ report = []
29
+
30
+ if not bundle_id:
31
+ return [
32
+ {
33
+ "query": query,
34
+ "outcome": "error",
35
+ "title": "Invalid Bundle ID",
36
+ "message": "Bundle ID cannot be empty.",
37
+ }
38
+ ]
39
+
40
+ try:
41
+ bundle_hit = hit_service.get_hit(bundle_id, as_odm=True)
42
+ if not bundle_hit or not bundle_hit.howler.is_bundle:
43
+ report.append(
44
+ {
45
+ "query": query,
46
+ "outcome": "error",
47
+ "title": "Invalid Bundle",
48
+ "message": f"Either a hit with ID {bundle_id} does not exist, or it is not a bundle.",
49
+ }
50
+ )
51
+ return report
52
+
53
+ ds = datastore()
54
+
55
+ skipped_hits = ds.hit.search(
56
+ f"({query}) AND -howler.bundles:{sanitize_lucene_query(bundle_id)}",
57
+ fl="howler.id",
58
+ )["items"]
59
+
60
+ if len(skipped_hits) > 0:
61
+ report.append(
62
+ {
63
+ "query": f"howler.id:({' OR '.join(h.howler.id for h in skipped_hits)})",
64
+ "outcome": "skipped",
65
+ "title": "Skipped Hit not in Bundle",
66
+ "message": "These hits already are not in the bundle.",
67
+ }
68
+ )
69
+
70
+ safe_query = f"{query} AND (howler.bundles:{sanitize_lucene_query(bundle_id)})"
71
+
72
+ # Check hit limit against the effective query (not raw query)
73
+ if user:
74
+ limit_error = check_hit_limit(safe_query, user, MAX_HITS_BASIC, MAX_HITS_ADVANCED)
75
+ if limit_error:
76
+ return [limit_error]
77
+
78
+ matching_hits = ds.hit.search(safe_query, rows=MAX_HITS_ADVANCED, fl="howler.id")["items"]
79
+ if len(matching_hits) < 1:
80
+ report.append(
81
+ {
82
+ "query": safe_query,
83
+ "outcome": "skipped",
84
+ "title": "No Matching Hits",
85
+ "message": "There were no hits matching this query.",
86
+ }
87
+ )
88
+ return report
89
+
90
+ ds.hit.update_by_query(
91
+ safe_query,
92
+ [hit_helper.list_remove("howler.bundles", bundle_id)],
93
+ )
94
+
95
+ hit_service.update_hit(
96
+ bundle_id,
97
+ [hit_helper.list_remove("howler.hits", h["howler"]["id"]) for h in matching_hits],
98
+ )
99
+
100
+ if len(ds.hit.get(bundle_id).howler.hits) < 1:
101
+ hit_service.update_hit(bundle_id, [hit_helper.update("howler.is_bundle", False)])
102
+
103
+ report.append(
104
+ {
105
+ "query": query,
106
+ "outcome": "success",
107
+ "title": "Executed Successfully",
108
+ "message": f"Matching hits removed from bundle with id {bundle_id}",
109
+ }
110
+ )
111
+ except HowlerException as e:
112
+ report.append(
113
+ {
114
+ "query": query,
115
+ "outcome": "error",
116
+ "title": "Failed to Execute",
117
+ "message": f"Unknown exception occurred: {str(e)}",
118
+ }
119
+ )
120
+
121
+ return report
122
+
123
+
124
+ def specification():
125
+ """Specify various properties of the action, such as title, descriptions, permissions and input steps."""
126
+ return {
127
+ "id": OPERATION_ID,
128
+ "title": "Remove from Bundle",
129
+ "priority": 5,
130
+ "i18nKey": f"operations.{OPERATION_ID}",
131
+ "description": {
132
+ "short": "Remove a set of hits from a bundle",
133
+ "long": execute.__doc__,
134
+ },
135
+ "roles": ["automation_basic", "actionrunner_basic"],
136
+ "steps": [
137
+ {
138
+ "args": {"bundle_id": []},
139
+ "options": {},
140
+ "validation": {"error": {"query": "-howler.bundles:$bundle_id"}},
141
+ }
142
+ ],
143
+ "triggers": VALID_TRIGGERS,
144
+ }
@@ -9,12 +9,12 @@ from howler.helper.workflow import Workflow, WorkflowException
9
9
  from howler.odm.models.action import VALID_TRIGGERS
10
10
  from howler.odm.models.howler_data import (
11
11
  Assessment,
12
+ HitStatus,
12
13
  HitStatusTransition,
13
- Status,
14
14
  Vote,
15
15
  )
16
16
  from howler.odm.models.user import User
17
- from howler.services import comms_service, hit_service
17
+ from howler.services import event_service, hit_service
18
18
  from howler.utils.list_utils import flatten_list
19
19
 
20
20
  OPERATION_ID = "transition"
@@ -147,7 +147,7 @@ def execute(
147
147
  if total_processed % 10 == 0:
148
148
  log.debug("Transition executed on %s hits", total_processed)
149
149
  if request_id is not None:
150
- comms_service.emit(
150
+ event_service.emit(
151
151
  "automation",
152
152
  {
153
153
  "request_id": request_id,
@@ -193,13 +193,15 @@ def specification():
193
193
  "steps": [
194
194
  {
195
195
  "args": {"status": []},
196
- "options": {"status": Status.list()},
196
+ "options": {"status": HitStatus.list()},
197
197
  "validation": {"error": {"query": "-howler.status:$status"}},
198
198
  },
199
199
  {
200
200
  "args": {"transition": []},
201
201
  "options": {
202
- "transition": {f"status:{status}": hit_service.get_transitions(status) for status in Status.list()},
202
+ "transition": {
203
+ f"status:{status}": hit_service.get_transitions(status) for status in HitStatus.list()
204
+ },
203
205
  },
204
206
  },
205
207
  {
@@ -25,8 +25,7 @@ logger = get_logger(__file__)
25
25
 
26
26
  def make_subapi_blueprint(name, api_version=1):
27
27
  """Create a flask Blueprint for a subapi in a standard way."""
28
- full_name = f"v{api_version}_{name}"
29
- return Blueprint(full_name, full_name, url_prefix="/".join([API_PREFIX, f"v{api_version}", name]))
28
+ return Blueprint(name, name, url_prefix="/".join([API_PREFIX, f"v{api_version}", name]))
30
29
 
31
30
 
32
31
  def _make_api_response(
@@ -6,12 +6,12 @@ from typing import Any
6
6
  from flask import Blueprint, request
7
7
  from opentelemetry import trace
8
8
 
9
- import howler.services.comms_service as comms_service
10
- import howler.services.viewer_service as viewer_service
9
+ import howler.services.event_service as event_service
11
10
  from howler.api import ok, unauthorized
12
11
  from howler.common.logging import get_logger
12
+ from howler.datastore.operations import OdmHelper
13
13
  from howler.helper.ws import ConnectionClosed, Server
14
- from howler.security import api_login
14
+ from howler.odm.models.hit import Hit
15
15
  from howler.security.socket import websocket_auth, ws_response
16
16
  from howler.utils.socket_utils import check_action
17
17
 
@@ -24,16 +24,13 @@ socket_api._doc = "Endpoints concerning websocket connectivity between the clien
24
24
  logger = get_logger(__file__)
25
25
  tracer = trace.get_tracer(__name__)
26
26
 
27
+ hit_helper = OdmHelper(Hit)
28
+
27
29
 
28
30
  @tracer.start_as_current_span(f"{__name__}.emit")
29
31
  @socket_api.route("/emit/<event>", methods=["POST"])
30
32
  def emit(event: str):
31
- """Emit an event to all listening websockets.
32
-
33
- .. deprecated::
34
- This endpoint is deprecated. Events are now propagated via Redis pubsub
35
- and no longer require a dedicated websocket pod.
36
- """
33
+ """Emit an event to all listening websockets"""
37
34
  if "Authorization" not in request.headers:
38
35
  return unauthorized(err="Missing authorization header")
39
36
 
@@ -47,30 +44,15 @@ def emit(event: str):
47
44
 
48
45
  return unauthorized(err="Invalid auth data")
49
46
 
50
- comms_service.emit(event, request.json)
47
+ event_service.emit(event, request.json)
51
48
 
52
49
  return ok()
53
50
 
54
51
 
55
- @tracer.start_as_current_span(f"{__name__}.get_viewers")
56
- @socket_api.route("/viewers/<entity_id>", methods=["GET"])
57
- @api_login(audit=False, required_priv=["R"])
58
- def get_viewers(entity_id: str, **kwargs): # pragma: no cover
59
- """Get the list of users currently viewing the specified entity
60
-
61
- Variables:
62
- entity_id => The ID of the entity to get viewers for
63
-
64
- Result Example:
65
- ["user1", "user2"]
66
- """
67
- return ok(viewer_service.get_viewers(entity_id))
68
-
69
-
70
52
  @tracer.start_as_current_span(f"{__name__}.connect")
71
53
  @socket_api.route("/connect", websocket=True) # type: ignore
72
54
  @websocket_auth(required_priv=["R"])
73
- def connect(ws: Server, *args: Any, ws_id: str, **kwargs): # noqa: C901
55
+ def connect(ws: Server, *args: Any, ws_id: str, **kwargs):
74
56
  """Connect to the server to monitor for updates via websocket
75
57
 
76
58
  Variables:
@@ -82,7 +64,6 @@ def connect(ws: Server, *args: Any, ws_id: str, **kwargs): # noqa: C901
82
64
  Result Example:
83
65
  A continuous websocket connection
84
66
  """
85
- logger.info("%s: WS connect handler started", ws_id)
86
67
  outstanding_actions: list[tuple[str, str, bool]] = []
87
68
 
88
69
  def send_hit(data: dict[str, Any]):
@@ -97,29 +78,14 @@ def connect(ws: Server, *args: Any, ws_id: str, **kwargs): # noqa: C901
97
78
  logger.debug("Sending action: %s", data)
98
79
  ws.send(ws_response("action", data))
99
80
 
100
- def send_case(data: dict[str, Any]): # pragma: no cover
101
- logger.debug("Sending case update: %s", data.get("case", {}).get("case_id", "unknown"))
102
- ws.send(ws_response("cases", data))
103
-
104
- def send_viewers_update(data: dict[str, Any]): # pragma: no cover
105
- logger.debug("Sending viewers update: %s", data.get("id", "unknown"))
106
- ws.send(ws_response("viewers_update", data))
107
-
108
81
  try:
109
- logger.debug(
110
- "%s: Registering comms listeners: hits, broadcast, action, cases, viewers_update",
111
- ws_id,
112
- )
113
- comms_service.on("hits", send_hit)
114
- comms_service.on("broadcast", send_broadcast)
115
- comms_service.on("action", send_action)
116
- comms_service.on("cases", send_case)
117
- comms_service.on("viewers_update", send_viewers_update)
82
+ event_service.on("hits", send_hit)
83
+ event_service.on("broadcast", send_broadcast)
84
+ event_service.on("action", send_action)
118
85
  while ws.connected:
119
86
  data = ws.receive(10)
120
87
  if data:
121
88
  obj = json.loads(data)
122
- logger.debug("%s: Received message: keys=%s", ws_id, list(obj.keys()))
123
89
 
124
90
  if "id" not in obj or "action" not in obj or "broadcast" not in obj:
125
91
  ws.close(
@@ -131,28 +97,22 @@ def connect(ws: Server, *args: Any, ws_id: str, **kwargs): # noqa: C901
131
97
  message="Sent data is invalid.",
132
98
  ),
133
99
  )
134
- logger.warning("%s: Closed due to invalid payload: %s", ws_id, obj)
135
100
  return
136
101
 
137
102
  outstanding_actions = check_action(
138
103
  obj["id"], obj["action"], obj["broadcast"], outstanding_actions=outstanding_actions, **kwargs
139
104
  )
140
- logger.debug("%s: Outstanding actions count=%s", ws_id, len(outstanding_actions))
141
105
  else:
142
- logger.debug("%s listening", ws_id) # pragma: no cover
106
+ logger.debug(ws_id + " listening")
143
107
  except Exception as e:
144
108
  if isinstance(e, ConnectionClosed):
145
109
  raise
146
110
  else:
147
- logger.exception("%s: Exception in connect loop", ws_id) # pragma: no cover
111
+ logger.exception("Exception on connect.")
148
112
  finally:
149
- logger.debug("%s: Deregistering comms listeners", ws_id)
150
- comms_service.off("hits", send_hit)
151
- comms_service.off("broadcast", send_broadcast)
152
- comms_service.off("action", send_action)
153
- comms_service.off("cases", send_case)
154
- comms_service.off("viewers_update", send_viewers_update)
113
+ event_service.off("hits", send_hit)
114
+ event_service.off("broadcast", send_broadcast)
115
+ event_service.off("action", send_action)
155
116
 
156
117
  for id, action, broadcast in outstanding_actions:
157
118
  outstanding_actions = check_action(id, action, broadcast, outstanding_actions=outstanding_actions, **kwargs)
158
- logger.info("%s: WS connect handler finished", ws_id)
@@ -1,39 +1,47 @@
1
1
  from textwrap import dedent
2
2
 
3
- from flask import current_app, request
4
-
5
-
6
- def build_route_docs(version: str, user_types: list[str]):
7
- """This function iterates through all registered Flask routes.
8
-
9
- It generates documentation for those accessible by the specified user types. It collects
10
- route metadata including permissions, methods, descriptions, and completion status.
11
-
12
- Args:
13
- user_types (list[str]): List of user types to filter API routes by.
14
- Only routes that grant access to at least one of these user types
15
- will be included in the output.
16
-
17
- Returns:
18
- dict: A dictionary containing:
19
- - 'apis' (list[dict]): List of API endpoint documentation, each dict includes:
20
- - 'protected' (bool): Whether the endpoint requires authentication.
21
- - 'required_type' (list[str]): User types allowed to access this endpoint.
22
- - 'name' (str): Human-readable name of the endpoint.
23
- - 'id' (str): Unique identifier for the endpoint.
24
- - 'function' (str): Full Python function path.
25
- - 'path' (str): URL path of the endpoint.
26
- - 'ui_only' (bool): Whether endpoint is UI-only.
27
- - 'methods' (list[str]): HTTP methods supported (excluding OPTIONS and HEAD).
28
- - 'description' (str): Docstring documentation or incomplete marker.
29
- - 'complete' (bool): Whether the endpoint is fully documented.
30
- - 'required_priv' (list[str]): Required privileges for the endpoint.
31
- - 'blueprints' (dict[str, str]): Map of blueprint names to their documentation strings.
32
-
33
- Note:
34
- Endpoints without documentation are marked with "[INCOMPLETE]" prefix
35
- in their description and complete field set to False.
3
+ from flask import Blueprint, current_app, request
4
+
5
+ from howler.api import ok
6
+ from howler.security import api_login
7
+
8
+ API_PREFIX = "/api/v1"
9
+ apiv1 = Blueprint("apiv1", __name__, url_prefix=API_PREFIX)
10
+ apiv1._doc = "Api Documentation Version 1" # type: ignore[attr-defined] # type: ignore
11
+
12
+
13
+ @apiv1.route("/")
14
+ @api_login(audit=False, required_priv=["R", "W"], required_type=["user", "admin"])
15
+ def get_api_documentation(**kwargs):
16
+ """Full API doc.
17
+
18
+ Loop through all registered API paths and display their documentation.
19
+ Returns a list of API definition.
20
+
21
+ Variables:
22
+ None
23
+
24
+ Arguments:
25
+ None
26
+
27
+ Result Example:
28
+ [
29
+ {
30
+ 'name': "Api Doc", # Name of the api
31
+ 'path': "/api/path/<variable>/", # API path
32
+ 'ui_only': false, # Is UI only API
33
+ 'methods': ["GET", "POST"], # Allowed HTTP methods
34
+ 'description': "API doc.", # API documentation
35
+ 'id': "api_doc", # Unique ID for the API
36
+ 'function': "apiv1.api_doc", # Function called in the code
37
+ 'protected': False, # Does the API require login?
38
+ 'required_type': ['user'], # Type of users allowed to use API
39
+ 'complete' : True # Is the API stable?
40
+ },
41
+ ]
36
42
  """
43
+ user_types = kwargs["user"]["type"]
44
+
37
45
  api_blueprints = {}
38
46
  api_list = []
39
47
  for rule in current_app.url_map.iter_rules():
@@ -50,13 +58,13 @@ def build_route_docs(version: str, user_types: list[str]):
50
58
  [x.capitalize() for x in rule.endpoint[rule.endpoint.rindex(".") + 1 :].split("_")]
51
59
  )
52
60
  blueprint = rule.endpoint[: rule.endpoint.rindex(".")]
53
- if blueprint == f"api{version}":
61
+ if blueprint == "apiv1":
54
62
  blueprint = "documentation"
55
63
 
56
64
  if blueprint not in api_blueprints:
57
65
  try:
58
66
  doc = current_app.blueprints[rule.endpoint[: rule.endpoint.rindex(".")]]._doc # type: ignore[attr-defined]
59
- except Exception: # pragma: no cover
67
+ except Exception:
60
68
  doc = ""
61
69
 
62
70
  api_blueprints[blueprint] = doc
@@ -66,7 +74,7 @@ def build_route_docs(version: str, user_types: list[str]):
66
74
  else:
67
75
  description = "[INCOMPLETE]\n\nTHIS API HAS NOT BEEN DOCUMENTED YET!"
68
76
 
69
- api_id = rule.endpoint.replace(f"api{version}.", "").replace(".", "_")
77
+ api_id = rule.endpoint.replace("apiv1.", "").replace(".", "_")
70
78
 
71
79
  api_list.append(
72
80
  {
@@ -74,7 +82,7 @@ def build_route_docs(version: str, user_types: list[str]):
74
82
  "required_type": sorted(required_type),
75
83
  "name": func_title,
76
84
  "id": api_id,
77
- "function": f"api.{version}.{rule.endpoint}",
85
+ "function": f"api.v1.{rule.endpoint}",
78
86
  "path": rule.rule,
79
87
  "ui_only": rule.rule.startswith("%sui/" % request.path),
80
88
  "methods": sorted(methods),
@@ -86,4 +94,4 @@ def build_route_docs(version: str, user_types: list[str]):
86
94
 
87
95
  break
88
96
 
89
- return {"apis": api_list, "blueprints": api_blueprints}
97
+ return ok({"apis": api_list, "blueprints": api_blueprints})