howler-api 4.0.0.dev669__tar.gz → 4.0.0.dev679__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 (209) hide show
  1. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/PKG-INFO +1 -1
  2. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/view.py +1 -1
  3. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/collection.py +33 -22
  4. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/store.py +0 -4
  5. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/base.py +22 -2
  6. howler_api-4.0.0.dev679/howler/odm/mixins.py +97 -0
  7. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/case.py +2 -1
  8. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/hit.py +3 -1
  9. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/observable.py +3 -1
  10. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/__init__.py +2 -2
  11. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/pyproject.toml +1 -1
  12. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/README.md +0 -0
  13. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/__init__.py +0 -0
  14. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/actions/__init__.py +0 -0
  15. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/actions/add_label.py +0 -0
  16. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/actions/change_field.py +0 -0
  17. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/actions/demote.py +0 -0
  18. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/actions/example_plugin.py +0 -0
  19. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/actions/prioritization.py +0 -0
  20. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/actions/promote.py +0 -0
  21. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/actions/remove_label.py +0 -0
  22. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/actions/transition.py +0 -0
  23. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/__init__.py +0 -0
  24. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/base.py +0 -0
  25. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/socket.py +0 -0
  26. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/__init__.py +0 -0
  27. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/action.py +0 -0
  28. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/analytic.py +0 -0
  29. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/auth.py +0 -0
  30. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/clue.py +0 -0
  31. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/configs.py +0 -0
  32. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/dossier.py +0 -0
  33. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/help.py +0 -0
  34. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/hit.py +0 -0
  35. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/notebook.py +0 -0
  36. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/overview.py +0 -0
  37. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/search.py +0 -0
  38. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/template.py +0 -0
  39. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/tool.py +0 -0
  40. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/user.py +0 -0
  41. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/utils/__init__.py +0 -0
  42. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v1/utils/etag.py +0 -0
  43. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v2/__init__.py +0 -0
  44. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v2/case.py +0 -0
  45. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v2/ingest.py +0 -0
  46. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/api/v2/search.py +0 -0
  47. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/app.py +0 -0
  48. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/README.md +0 -0
  49. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/__init__.py +0 -0
  50. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/classification.py +0 -0
  51. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/classification.yml +0 -0
  52. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/exceptions.py +0 -0
  53. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/loader.py +0 -0
  54. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/logging/__init__.py +0 -0
  55. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/logging/audit.py +0 -0
  56. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/logging/format.py +0 -0
  57. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/net.py +0 -0
  58. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/net_static.py +0 -0
  59. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/random_user.py +0 -0
  60. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/common/swagger.py +0 -0
  61. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/config.py +0 -0
  62. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/cronjobs/__init__.py +0 -0
  63. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/cronjobs/retention.py +0 -0
  64. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/cronjobs/view_cleanup.py +0 -0
  65. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/README.md +0 -0
  66. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/__init__.py +0 -0
  67. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/bulk.py +0 -0
  68. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/constants.py +0 -0
  69. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/exceptions.py +0 -0
  70. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/howler_store.py +0 -0
  71. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/migrations/fix_process.py +0 -0
  72. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/operations.py +0 -0
  73. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/schemas.py +0 -0
  74. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/support/__init__.py +0 -0
  75. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/support/build.py +0 -0
  76. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/support/schemas.py +0 -0
  77. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/datastore/types.py +0 -0
  78. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/error.py +0 -0
  79. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/external/__init__.py +0 -0
  80. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/external/generate_mitre.py +0 -0
  81. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/external/generate_sigma_rules.py +0 -0
  82. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/external/generate_tlds.py +0 -0
  83. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/external/reindex_data.py +0 -0
  84. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/external/wipe_databases.py +0 -0
  85. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/gunicorn_config.py +0 -0
  86. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/healthz.py +0 -0
  87. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/helper/__init__.py +0 -0
  88. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/helper/azure.py +0 -0
  89. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/helper/discover.py +0 -0
  90. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/helper/hit.py +0 -0
  91. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/helper/oauth.py +0 -0
  92. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/helper/search.py +0 -0
  93. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/helper/workflow.py +0 -0
  94. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/helper/ws.py +0 -0
  95. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/README.md +0 -0
  96. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/__init__.py +0 -0
  97. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/charter.txt +0 -0
  98. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/constants.py +0 -0
  99. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/helper.py +0 -0
  100. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/howler_enum.py +0 -0
  101. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/__init__.py +0 -0
  102. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/action.py +0 -0
  103. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/analytic.py +0 -0
  104. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/assemblyline.py +0 -0
  105. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/aws.py +0 -0
  106. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/azure.py +0 -0
  107. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/cbs.py +0 -0
  108. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/clue.py +0 -0
  109. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/config.py +0 -0
  110. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/dossier.py +0 -0
  111. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/__init__.py +0 -0
  112. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/agent.py +0 -0
  113. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/autonomous_system.py +0 -0
  114. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/client.py +0 -0
  115. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/cloud.py +0 -0
  116. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/code_signature.py +0 -0
  117. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/container.py +0 -0
  118. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/dns.py +0 -0
  119. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/egress.py +0 -0
  120. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/elf.py +0 -0
  121. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/email.py +0 -0
  122. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/error.py +0 -0
  123. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/event.py +0 -0
  124. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/faas.py +0 -0
  125. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/file.py +0 -0
  126. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/geo.py +0 -0
  127. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/group.py +0 -0
  128. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/hash.py +0 -0
  129. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/host.py +0 -0
  130. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/http.py +0 -0
  131. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/ingress.py +0 -0
  132. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/interface.py +0 -0
  133. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/network.py +0 -0
  134. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/observer.py +0 -0
  135. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/organization.py +0 -0
  136. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/os.py +0 -0
  137. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/pe.py +0 -0
  138. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/process.py +0 -0
  139. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/registry.py +0 -0
  140. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/related.py +0 -0
  141. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/rule.py +0 -0
  142. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/server.py +0 -0
  143. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/threat.py +0 -0
  144. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/tls.py +0 -0
  145. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/url.py +0 -0
  146. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/user.py +0 -0
  147. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/user_agent.py +0 -0
  148. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/ecs/vulnerability.py +0 -0
  149. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/gcp.py +0 -0
  150. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/howler_data.py +0 -0
  151. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/lead.py +0 -0
  152. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/localized_label.py +0 -0
  153. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/overview.py +0 -0
  154. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/pivot.py +0 -0
  155. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/record.py +0 -0
  156. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/template.py +0 -0
  157. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/user.py +0 -0
  158. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/models/view.py +0 -0
  159. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/random_data.py +0 -0
  160. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/odm/randomizer.py +0 -0
  161. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/patched.py +0 -0
  162. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/plugins/__init__.py +0 -0
  163. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/plugins/config.py +0 -0
  164. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/__init__.py +0 -0
  165. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/README.md +0 -0
  166. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/counters.py +0 -0
  167. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/events.py +0 -0
  168. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/hash.py +0 -0
  169. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/lock.py +0 -0
  170. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/queues/__init__.py +0 -0
  171. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/queues/comms.py +0 -0
  172. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/queues/multi.py +0 -0
  173. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/queues/named.py +0 -0
  174. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/queues/priority.py +0 -0
  175. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/set.py +0 -0
  176. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/remote/datatypes/user_quota_tracker.py +0 -0
  177. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/security/__init__.py +0 -0
  178. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/security/socket.py +0 -0
  179. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/security/utils.py +0 -0
  180. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/__init__.py +0 -0
  181. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/action_service.py +0 -0
  182. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/analytic_service.py +0 -0
  183. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/auth_service.py +0 -0
  184. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/case_service.py +0 -0
  185. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/config_service.py +0 -0
  186. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/docs_service.py +0 -0
  187. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/dossier_service.py +0 -0
  188. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/event_service.py +0 -0
  189. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/hit_service.py +0 -0
  190. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/jwt_service.py +0 -0
  191. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/lucene_service.py +0 -0
  192. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/notebook_service.py +0 -0
  193. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/observable_service.py +0 -0
  194. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/overview_service.py +0 -0
  195. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/search_service.py +0 -0
  196. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/template_service.py +0 -0
  197. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/services/user_service.py +0 -0
  198. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/__init__.py +0 -0
  199. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/annotations.py +0 -0
  200. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/chunk.py +0 -0
  201. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/compat.py +0 -0
  202. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/dict_utils.py +0 -0
  203. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/isotime.py +0 -0
  204. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/list_utils.py +0 -0
  205. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/lucene.py +0 -0
  206. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/path.py +0 -0
  207. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/socket_utils.py +0 -0
  208. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/howler/utils/str_utils.py +0 -0
  209. {howler_api-4.0.0.dev669 → howler_api-4.0.0.dev679}/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.dev669
3
+ Version: 4.0.0.dev679
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -261,7 +261,7 @@ def remove_as_favourite(view_id: str, **kwargs):
261
261
  """Remove a view from a list of the user's favourites
262
262
 
263
263
  Variables:
264
- id => The id of the view to remove as a favourite
264
+ view_id => The id of the view to remove as a favourite
265
265
 
266
266
  Optional Arguments:
267
267
  None
@@ -270,14 +270,17 @@ class ESCollection(Generic[ModelType]):
270
270
  if index is None:
271
271
  index = self.index_name
272
272
 
273
+ client = self.datastore.client
274
+ if request_timeout is not None:
275
+ client = client.options(request_timeout=request_timeout)
276
+
273
277
  # initial search
274
278
  resp = self.with_retries(
275
- self.datastore.client.search,
279
+ client.search,
276
280
  index=index,
277
281
  query=query,
278
282
  scroll=scroll,
279
283
  size=size,
280
- request_timeout=request_timeout,
281
284
  sort=sort,
282
285
  _source=source,
283
286
  )
@@ -305,16 +308,18 @@ class ESCollection(Generic[ModelType]):
305
308
 
306
309
  finally:
307
310
  if scroll_id:
308
- resp = self.with_retries(
309
- self.datastore.client.clear_scroll,
310
- scroll_id=[scroll_id],
311
- ignore=(404,),
312
- )
313
- if not resp.get("succeeded", False):
314
- logger.warning(
315
- f"Could not clear scroll ID {scroll_id}, there is potential "
316
- "memory leak in you Elastic cluster..."
311
+ try:
312
+ resp = self.with_retries(
313
+ self.datastore.client.clear_scroll,
314
+ scroll_id=[scroll_id],
317
315
  )
316
+ if not resp.get("succeeded", False):
317
+ logger.warning(
318
+ f"Could not clear scroll ID {scroll_id}, there is potential "
319
+ "memory leak in your Elastic cluster..."
320
+ )
321
+ except elasticsearch.exceptions.NotFoundError:
322
+ pass
318
323
 
319
324
  def with_retries(self, func, *args, raise_conflicts=False, **kwargs):
320
325
  """This function performs the passed function with the given args and kwargs and reconnect if it fails
@@ -470,7 +475,9 @@ class ESCollection(Generic[ModelType]):
470
475
  raise
471
476
 
472
477
  def _safe_index_copy(self, copy_function, src, target, settings=None, min_status="yellow"):
473
- ret = copy_function(index=src, target=target, settings=settings, request_timeout=60)
478
+ options_client = self.datastore.client.options(request_timeout=60)
479
+ timed_function = getattr(options_client.indices, copy_function.__name__)
480
+ ret = timed_function(index=src, target=target, settings=settings)
474
481
  if not ret["acknowledged"]:
475
482
  raise DataStoreException(f"Failed to create index {target} from {src}.")
476
483
 
@@ -1657,19 +1664,23 @@ class ESCollection(Generic[ModelType]):
1657
1664
 
1658
1665
  # Check if the scroll is finished and close it
1659
1666
  if deep_paging_id is not None and new_deep_paging_id is None:
1660
- self.with_retries(
1661
- self.datastore.client.clear_scroll,
1662
- scroll_id=[deep_paging_id],
1663
- ignore=(404,),
1664
- )
1667
+ try:
1668
+ self.with_retries(
1669
+ self.datastore.client.clear_scroll,
1670
+ scroll_id=[deep_paging_id],
1671
+ )
1672
+ except elasticsearch.exceptions.NotFoundError:
1673
+ pass
1665
1674
 
1666
1675
  # Check if we can tell from inspection that we have finished the scroll
1667
1676
  if new_deep_paging_id is not None and len(ret_data["items"]) < ret_data["rows"]:
1668
- self.with_retries(
1669
- self.datastore.client.clear_scroll,
1670
- scroll_id=[new_deep_paging_id],
1671
- ignore=(404,),
1672
- )
1677
+ try:
1678
+ self.with_retries(
1679
+ self.datastore.client.clear_scroll,
1680
+ scroll_id=[new_deep_paging_id],
1681
+ )
1682
+ except elasticsearch.exceptions.NotFoundError:
1683
+ pass
1673
1684
  new_deep_paging_id = None
1674
1685
 
1675
1686
  if new_deep_paging_id is not None:
@@ -9,7 +9,6 @@ from typing import Any, Optional, cast
9
9
  from urllib.parse import urlparse
10
10
 
11
11
  import elasticsearch
12
- import elasticsearch.client
13
12
 
14
13
  from howler.common.logging.format import HWL_DATE_FORMAT, HWL_LOG_FORMAT
15
14
  from howler.datastore.collection import ESCollection
@@ -138,7 +137,6 @@ class ESStore(object):
138
137
  max_retries=0,
139
138
  request_timeout=TRANSPORT_TIMEOUT,
140
139
  )
141
- self.eql = elasticsearch.client.EqlClient(self.client)
142
140
  self.archive_access = archive_access
143
141
  self.url_path = "elastic"
144
142
 
@@ -218,14 +216,12 @@ class ESStore(object):
218
216
  max_retries=0,
219
217
  request_timeout=TRANSPORT_TIMEOUT,
220
218
  )
221
- self.eql = elasticsearch.client.EqlClient(self.client)
222
219
 
223
220
  def close(self):
224
221
  self._closed = True
225
222
  # Flatten the client object so that attempts to access without reconnecting errors hard
226
223
  # But 'cast' it so that mypy and other linters don't think that its normal for client to be None
227
224
  self.client = cast(elasticsearch.Elasticsearch, None)
228
- self.eql = cast(elasticsearch.client.EqlClient, None)
229
225
 
230
226
  def get_hosts(self, safe=False):
231
227
  if not safe:
@@ -1503,11 +1503,31 @@ def recursive_set_name(field, name, to_parent=False):
1503
1503
  recursive_set_name(field.child_type, name, to_parent=True)
1504
1504
 
1505
1505
 
1506
- def model(index=None, store=None, description=None):
1507
- """Decorator to create model objects."""
1506
+ def model(index=None, store=None, description=None, id_field=None):
1507
+ """Decorator that finalizes a Model subclass for use with the datastore.
1508
+ Assigns metadata to the class (description, id field), validates that all
1509
+ declared field names are legal, recursively sets each field's name, and
1510
+ applies default index/store settings to every field.
1511
+ If ``id_field`` is not provided, it defaults to ``<classname_lower>_id``.
1512
+ Args:
1513
+ index: Default index setting applied to all fields on the model.
1514
+ store: Default store setting applied to all fields on the model.
1515
+ description: Human-readable description of the model.
1516
+ id_field: Name of the field used as the primary key. Defaults to
1517
+ ``<classname_lower>_id`` when not specified.
1518
+ Returns:
1519
+ A class decorator that configures and returns the decorated Model subclass.
1520
+ Raises:
1521
+ HowlerValueError: If any field name fails the ``FIELD_SANITIZER`` regex
1522
+ or appears in ``BANNED_FIELDS``.
1523
+ """
1508
1524
 
1509
1525
  def _finish_model(cls):
1510
1526
  cls._Model__description = description
1527
+ cls._Model__id_field = id_field
1528
+
1529
+ if cls._Model__id_field is None:
1530
+ cls._Model__id_field = f"{cls.__name__.lower()}_id"
1511
1531
 
1512
1532
  for name, field_data in cls.fields().items():
1513
1533
  if not FIELD_SANITIZER.match(name) or name in BANNED_FIELDS:
@@ -0,0 +1,97 @@
1
+ """Datastore convenience mixins for Howler ODM Model classes.
2
+
3
+ Provides :class:`DatastoreMixin`, a generic mixin that adds a class-level
4
+ ``store`` property (returning a typed :class:`ESCollection`) and instance-level
5
+ ``ds`` / ``save`` helpers so that Model subclasses can interact with the
6
+ Elasticsearch datastore without boilerplate.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from operator import attrgetter
12
+ from typing import Generic, TypeVar, overload
13
+
14
+ from howler.common.exceptions import HowlerRuntimeError
15
+ from howler.common.loader import datastore
16
+ from howler.datastore.collection import ESCollection
17
+ from howler.odm.base import Model
18
+
19
+ ModelType = TypeVar("ModelType", bound=Model)
20
+
21
+
22
+ class _ObjectsDescriptor(Generic[ModelType]):
23
+ """Descriptor that provides class-level-only access to the model's ESCollection.
24
+
25
+ Intended to be accessed exclusively via the class (e.g. ``Case.store``).
26
+ Raises ``AttributeError`` if accessed from an instance to enforce
27
+ cla # noqa: D205
28
+ """
29
+
30
+ @overload
31
+ def __get__(self, obj: None, objtype: type[ModelType]) -> ESCollection[ModelType]: ...
32
+
33
+ @overload
34
+ def __get__(self, obj: ModelType, objtype: type[ModelType]) -> ESCollection[ModelType]: ...
35
+
36
+ def __get__(self, obj: ModelType | None, objtype: type[ModelType] | None = None) -> ESCollection[ModelType]:
37
+ """Return the ESCollection for the owner class.
38
+
39
+ Args:
40
+ obj: The instance the descriptor was accessed from, or ``None``
41
+ when accessed via the class.
42
+ objtype: The owner class (e.g. ``Case``, ``Hit``).
43
+ Returns:
44
+ ESCollection[ModelType]: The datastore collection for *objtype*.
45
+ Raises:
46
+ AttributeError: If accessed from an instance (*obj* is not ``None``)
47
+ or if *objtype* cannot be determined.
48
+ """
49
+ if obj is not None:
50
+ raise HowlerRuntimeError(
51
+ f"'{type(obj).__name__}.store' is a class-level property and cannot be accessed from an instance. "
52
+ f"Use '{type(obj).__name__}.store' instead."
53
+ )
54
+
55
+ if objtype is None:
56
+ raise HowlerRuntimeError("Cannot resolve owner class for 'store' descriptor.")
57
+
58
+ index_name = objtype.__name__.lower()
59
+ return datastore()[index_name]
60
+
61
+
62
+ class DatastoreMixin(Generic[ModelType]):
63
+ """Mixin that provides convenience datastore access to Model instances.
64
+
65
+ Generic over ``ModelType`` so that the ``store`` class property returns a
66
+ correctly-typed ``ESCollection[ModelType]``. Adds a ``ds`` property for
67
+ retrieving the shared datastore connection, a ``store`` class-only property
68
+ for retrieving the model's ESCollection (raises ``AttributeError`` if accessed
69
+ from an instance), and a ``save`` method that persists the current model
70
+ instance using its class name as the index and its configured ID field as the
71
+ document key.
72
+ """
73
+
74
+ store: _ObjectsDescriptor = _ObjectsDescriptor()
75
+
76
+ @property
77
+ def ds(self):
78
+ """Return the shared datastore instance.
79
+
80
+ Returns:
81
+ The singleton datastore connection used for all persistence operations.
82
+ """
83
+ return datastore()
84
+
85
+ def save(self) -> bool:
86
+ """Persist the current model instance to the datastore.
87
+
88
+ Determines the target index from the lowercase class name, extracts the
89
+ model's ID from the configured ID field, and saves the instance.
90
+ Returns:
91
+ bool: True if the save operation succeeded, False otherwise.
92
+ """
93
+ index_name = self.__class__.__name__.lower()
94
+ id_field = self.__class__._Model__id_field # type: ignore[attr-defined]
95
+ current_id = attrgetter(id_field)(self)
96
+
97
+ return self.ds[index_name].save(current_id, self)
@@ -3,6 +3,7 @@ from typing import Any, Optional
3
3
  from howler import odm
4
4
  from howler.common.exceptions import HowlerValueError
5
5
  from howler.odm.constants import Status
6
+ from howler.odm.mixins import DatastoreMixin
6
7
  from howler.utils.compat import StrEnum
7
8
 
8
9
  CASE_ITEM_TYPES = {"observable", "hit", "case", "lead", "reference"}
@@ -92,7 +93,7 @@ class CaseEnrichment(odm.Model):
92
93
 
93
94
 
94
95
  @odm.model(index=True, store=True, description="Case model with path-based items, enrichments, rules, and tasks.")
95
- class Case(odm.Model):
96
+ class Case(DatastoreMixin["Case"], odm.Model):
96
97
  case_id: str = odm.UUID(description="A unique identifier for this case.")
97
98
  title: str = odm.Keyword(description="Case title.")
98
99
  summary: str = odm.Text(description="Short case summary.")
@@ -2,6 +2,7 @@
2
2
 
3
3
  from howler import odm
4
4
  from howler.common.logging import get_logger
5
+ from howler.odm.mixins import DatastoreMixin
5
6
  from howler.odm.models.howler_data import HowlerData
6
7
  from howler.odm.models.record import Record
7
8
 
@@ -12,8 +13,9 @@ logger = get_logger(__file__)
12
13
  index=True,
13
14
  store=True,
14
15
  description="Howler Outline schema which is an extended version of Elastic Common Schema (ECS)",
16
+ id_field="howler.id",
15
17
  )
16
- class Hit(Record):
18
+ class Hit(DatastoreMixin["Hit"], Record):
17
19
  # Howler extended fields. Deviates from ECS
18
20
  howler: HowlerData = odm.Compound(
19
21
  HowlerData,
@@ -4,6 +4,7 @@ from howler import odm
4
4
  from howler.common.exceptions import HowlerValueError
5
5
  from howler.common.logging import get_logger
6
6
  from howler.odm.howler_enum import HowlerEnum
7
+ from howler.odm.mixins import DatastoreMixin
7
8
  from howler.odm.models.record import Record
8
9
 
9
10
  logger = get_logger(__file__)
@@ -110,8 +111,9 @@ class ObservableData(odm.Model):
110
111
  index=True,
111
112
  store=True,
112
113
  description="Observable schema which is an extended version of Elastic Common Schema (ECS)",
114
+ id_field="howler.id",
113
115
  )
114
- class Observable(Record):
116
+ class Observable(DatastoreMixin["Observable"], Record):
115
117
  # Howler extended fields. Deviates from ECS
116
118
  howler: ObservableData = odm.Compound(
117
119
  ObservableData,
@@ -4,7 +4,7 @@ import json
4
4
  import logging
5
5
  import os
6
6
  import time
7
- from datetime import datetime
7
+ from datetime import datetime, timezone
8
8
  from pathlib import Path
9
9
 
10
10
  import redis
@@ -30,7 +30,7 @@ pool: dict[tuple[str, str, bool], redis.BlockingConnectionPool] = {}
30
30
 
31
31
 
32
32
  def now_as_iso():
33
- s = datetime.utcfromtimestamp(time.time()).isoformat()
33
+ s = datetime.now(timezone.utc).replace(tzinfo=None).isoformat()
34
34
  return f"{s}Z"
35
35
 
36
36
 
@@ -152,7 +152,7 @@ suppress-none-returning = true
152
152
  [tool.poetry]
153
153
  package-mode = true
154
154
  name = "howler-api"
155
- version = "4.0.0.dev669"
155
+ version = "4.0.0.dev679"
156
156
  description = "Howler - API server"
157
157
  authors = [
158
158
  "Canadian Centre for Cyber Security <howler@cyber.gc.ca>",