howler-api 4.0.3.dev1397__tar.gz → 4.0.4.dev1401__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 (226) hide show
  1. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/PKG-INFO +1 -1
  2. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/action.py +2 -2
  3. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v2/ingest.py +2 -2
  4. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/collection.py +60 -8
  5. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/mixins.py +10 -5
  6. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/case_service.py +11 -10
  7. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/dossier_service.py +2 -2
  8. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/pyproject.toml +1 -1
  9. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/README.md +0 -0
  10. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/__init__.py +0 -0
  11. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/__init__.py +0 -0
  12. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/add_label.py +0 -0
  13. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/add_to_bundle.py +0 -0
  14. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/add_to_case.py +0 -0
  15. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/change_field.py +0 -0
  16. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/demote.py +0 -0
  17. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/example_plugin.py +0 -0
  18. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/prioritization.py +0 -0
  19. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/promote.py +0 -0
  20. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/remove_from_bundle.py +0 -0
  21. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/remove_label.py +0 -0
  22. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/actions/transition.py +0 -0
  23. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/__init__.py +0 -0
  24. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/base.py +0 -0
  25. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/socket.py +0 -0
  26. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/__init__.py +0 -0
  27. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/analytic.py +0 -0
  28. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/auth.py +0 -0
  29. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/clue.py +0 -0
  30. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/configs.py +0 -0
  31. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/dossier.py +0 -0
  32. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/help.py +0 -0
  33. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/hit.py +0 -0
  34. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/notebook.py +0 -0
  35. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/overview.py +0 -0
  36. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/search.py +0 -0
  37. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/template.py +0 -0
  38. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/tool.py +0 -0
  39. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/user.py +0 -0
  40. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/utils/__init__.py +0 -0
  41. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/utils/etag.py +0 -0
  42. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/utils/params.py +0 -0
  43. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v1/view.py +0 -0
  44. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v2/__init__.py +0 -0
  45. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v2/case.py +0 -0
  46. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v2/fuzzy.py +0 -0
  47. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/api/v2/search.py +0 -0
  48. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/app.py +0 -0
  49. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/README.md +0 -0
  50. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/__init__.py +0 -0
  51. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/classification.py +0 -0
  52. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/classification.yml +0 -0
  53. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/exceptions.py +0 -0
  54. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/loader.py +0 -0
  55. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/logging/__init__.py +0 -0
  56. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/logging/audit.py +0 -0
  57. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/logging/format.py +0 -0
  58. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/net.py +0 -0
  59. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/net_static.py +0 -0
  60. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/random_user.py +0 -0
  61. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/common/swagger.py +0 -0
  62. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/config.py +0 -0
  63. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/cronjobs/__init__.py +0 -0
  64. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/cronjobs/action_queue_worker.py +0 -0
  65. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/cronjobs/correlation.py +0 -0
  66. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/cronjobs/retention.py +0 -0
  67. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/cronjobs/view_cleanup.py +0 -0
  68. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/README.md +0 -0
  69. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/__init__.py +0 -0
  70. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/bulk.py +0 -0
  71. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/constants.py +0 -0
  72. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/exceptions.py +0 -0
  73. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/howler_store.py +0 -0
  74. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/migrations/fix_process.py +0 -0
  75. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/operations.py +0 -0
  76. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/schemas.py +0 -0
  77. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/store.py +0 -0
  78. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/support/__init__.py +0 -0
  79. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/support/build.py +0 -0
  80. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/support/schemas.py +0 -0
  81. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/types.py +0 -0
  82. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/datastore/utils.py +0 -0
  83. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/error.py +0 -0
  84. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/external/README.md +0 -0
  85. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/external/__init__.py +0 -0
  86. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/external/generate_mitre.py +0 -0
  87. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/external/generate_sigma_rules.py +0 -0
  88. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/external/generate_tlds.py +0 -0
  89. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/external/reindex_data.py +0 -0
  90. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/external/wipe_databases.py +0 -0
  91. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/gunicorn_config.py +0 -0
  92. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/healthz.py +0 -0
  93. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/helper/__init__.py +0 -0
  94. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/helper/azure.py +0 -0
  95. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/helper/discover.py +0 -0
  96. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/helper/hit.py +0 -0
  97. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/helper/oauth.py +0 -0
  98. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/helper/search.py +0 -0
  99. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/helper/workflow.py +0 -0
  100. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/helper/ws.py +0 -0
  101. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/README.md +0 -0
  102. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/__init__.py +0 -0
  103. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/base.py +0 -0
  104. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/charter.txt +0 -0
  105. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/constants.py +0 -0
  106. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/helper.py +0 -0
  107. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/howler_enum.py +0 -0
  108. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/__init__.py +0 -0
  109. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/action.py +0 -0
  110. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/analytic.py +0 -0
  111. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/assemblyline.py +0 -0
  112. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/aws.py +0 -0
  113. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/azure.py +0 -0
  114. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/case.py +0 -0
  115. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/cbs.py +0 -0
  116. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/clue.py +0 -0
  117. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/config.py +0 -0
  118. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/dossier.py +0 -0
  119. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/__init__.py +0 -0
  120. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/agent.py +0 -0
  121. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/autonomous_system.py +0 -0
  122. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/client.py +0 -0
  123. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/cloud.py +0 -0
  124. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/code_signature.py +0 -0
  125. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/container.py +0 -0
  126. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/dns.py +0 -0
  127. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/egress.py +0 -0
  128. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/elf.py +0 -0
  129. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/email.py +0 -0
  130. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/error.py +0 -0
  131. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/event.py +0 -0
  132. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/faas.py +0 -0
  133. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/file.py +0 -0
  134. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/geo.py +0 -0
  135. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/group.py +0 -0
  136. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/hash.py +0 -0
  137. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/host.py +0 -0
  138. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/http.py +0 -0
  139. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/ingress.py +0 -0
  140. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/interface.py +0 -0
  141. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/network.py +0 -0
  142. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/observer.py +0 -0
  143. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/organization.py +0 -0
  144. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/os.py +0 -0
  145. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/pe.py +0 -0
  146. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/process.py +0 -0
  147. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/registry.py +0 -0
  148. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/related.py +0 -0
  149. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/rule.py +0 -0
  150. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/server.py +0 -0
  151. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/threat.py +0 -0
  152. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/tls.py +0 -0
  153. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/url.py +0 -0
  154. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/user.py +0 -0
  155. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/user_agent.py +0 -0
  156. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/ecs/vulnerability.py +0 -0
  157. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/event.py +0 -0
  158. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/gcp.py +0 -0
  159. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/hit.py +0 -0
  160. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/howler_data.py +0 -0
  161. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/lead.py +0 -0
  162. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/localized_label.py +0 -0
  163. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/overview.py +0 -0
  164. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/pivot.py +0 -0
  165. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/record.py +0 -0
  166. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/template.py +0 -0
  167. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/user.py +0 -0
  168. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/models/view.py +0 -0
  169. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/random_data.py +0 -0
  170. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/odm/randomizer.py +0 -0
  171. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/patched.py +0 -0
  172. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/plugins/__init__.py +0 -0
  173. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/plugins/config.py +0 -0
  174. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/__init__.py +0 -0
  175. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/README.md +0 -0
  176. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/__init__.py +0 -0
  177. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/counters.py +0 -0
  178. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/events.py +0 -0
  179. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/hash.py +0 -0
  180. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/lock.py +0 -0
  181. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/queues/__init__.py +0 -0
  182. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/queues/comms.py +0 -0
  183. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/queues/multi.py +0 -0
  184. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/queues/named.py +0 -0
  185. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/queues/priority.py +0 -0
  186. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/set.py +0 -0
  187. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/remote/datatypes/user_quota_tracker.py +0 -0
  188. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/security/__init__.py +0 -0
  189. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/security/socket.py +0 -0
  190. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/security/utils.py +0 -0
  191. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/__init__.py +0 -0
  192. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/action_service.py +0 -0
  193. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/analytic_service.py +0 -0
  194. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/auth_service.py +0 -0
  195. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/bundle_compat_service.py +0 -0
  196. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/comms_service.py +0 -0
  197. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/config_service.py +0 -0
  198. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/correlation_service.py +0 -0
  199. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/docs_service.py +0 -0
  200. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/event_service.py +0 -0
  201. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/fuzzy_service.py +0 -0
  202. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/hit_service.py +0 -0
  203. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/jwt_service.py +0 -0
  204. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/lucene_service.py +0 -0
  205. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/notebook_service.py +0 -0
  206. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/overview_service.py +0 -0
  207. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/search_service.py +0 -0
  208. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/template_service.py +0 -0
  209. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/user_service.py +0 -0
  210. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/services/viewer_service.py +0 -0
  211. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/telemetry.py +0 -0
  212. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/__init__.py +0 -0
  213. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/annotations.py +0 -0
  214. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/chunk.py +0 -0
  215. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/compat.py +0 -0
  216. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/constants.py +0 -0
  217. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/dict_utils.py +0 -0
  218. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/indexes.py +0 -0
  219. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/isotime.py +0 -0
  220. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/list_utils.py +0 -0
  221. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/lucene.py +0 -0
  222. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/net_utils.py +0 -0
  223. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/path.py +0 -0
  224. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/socket_utils.py +0 -0
  225. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/howler/utils/str_utils.py +0 -0
  226. {howler_api-4.0.3.dev1397 → howler_api-4.0.4.dev1401}/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.3.dev1397
3
+ Version: 4.0.4.dev1401
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -143,7 +143,7 @@ def update_action(id: str, user: User, **kwargs) -> Response:
143
143
 
144
144
  ds = datastore()
145
145
 
146
- existing_action = ds.action.get(id, as_obj=False)
146
+ existing_action, server_version = ds.action.get(id, as_obj=False, version=True)
147
147
 
148
148
  if not existing_action:
149
149
  return not_found(err="The specified automation does not exist")
@@ -166,7 +166,7 @@ def update_action(id: str, user: User, **kwargs) -> Response:
166
166
  action_obj = Action(updated_action)
167
167
  action_obj.action_id = id
168
168
 
169
- ds.action.save(action_obj.action_id, action_obj, refresh=refresh)
169
+ ds.action.save(action_obj.action_id, action_obj, version=server_version, refresh=refresh)
170
170
  except HowlerException as e:
171
171
  return bad_request(err=str(e))
172
172
 
@@ -265,7 +265,7 @@ def overwrite(index: str, id: str, **kwargs):
265
265
 
266
266
  ds = datastore()
267
267
 
268
- record = ds[index].get(id, as_obj=False, version=False)
268
+ record, server_version = ds[index].get(id, as_obj=False, version=True)
269
269
  if not record:
270
270
  return not_found(err="Record %s does not exist" % id)
271
271
 
@@ -292,7 +292,7 @@ def overwrite(index: str, id: str, **kwargs):
292
292
  ds[index].save(
293
293
  id,
294
294
  odm(new_record) if odm else new_record,
295
- version=kwargs.get("server_version"),
295
+ version=server_version,
296
296
  refresh=refresh,
297
297
  )
298
298
 
@@ -16,6 +16,7 @@ from typing import Any, Callable, Dict, Generic, Literal, Optional, TypeVar, Uni
16
16
  import elasticsearch
17
17
  from datemath import dm
18
18
  from datemath.helpers import DateMathException
19
+ from elastic_transport import ApiResponseMeta
19
20
  from opentelemetry import trace
20
21
 
21
22
  from howler import odm
@@ -1166,6 +1167,17 @@ class ESCollection(Generic[ModelType]):
1166
1167
  )
1167
1168
  return self._search_exists(key)
1168
1169
 
1170
+ def _raise_document_not_found(self, key: str, result: Any) -> typing.NoReturn:
1171
+ """Raise the same error shape as an Elasticsearch document lookup for an empty search."""
1172
+ meta = ApiResponseMeta(
1173
+ status=404,
1174
+ http_version="1.1",
1175
+ headers=cast(Any, {}),
1176
+ duration=0.0,
1177
+ node=cast(Any, None),
1178
+ )
1179
+ raise elasticsearch.exceptions.NotFoundError(f"Document with id {key} not found", meta, result)
1180
+
1169
1181
  @overload
1170
1182
  def _get(self, key, retries, version: Literal[False]) -> dict[str, Any]: ...
1171
1183
 
@@ -1176,7 +1188,7 @@ class ESCollection(Generic[ModelType]):
1176
1188
  """Versioned get-save for atomic update has two paths:
1177
1189
  1. Document doesn't exist at all. Create token will be returned for version.
1178
1190
  This way only the first query to try and create the document will succeed.
1179
- 2. Document exists in hot. A version string with the info needed to do a versioned save is returned.
1191
+ 2. Document exists. A version string with the info needed to do a versioned save is returned.
1180
1192
 
1181
1193
  The create token is needed to differentiate between "I'm saving a new
1182
1194
  document non-atomic (version=None)" and "I'm saving a new document
@@ -1195,11 +1207,29 @@ class ESCollection(Generic[ModelType]):
1195
1207
  done = False
1196
1208
  while not done:
1197
1209
  try:
1198
- doc = self.with_retries(self.datastore.client.get, index=self.name, id=key)
1210
+ if self.ilm_config:
1211
+ result = self.with_retries(
1212
+ self.datastore.client.search,
1213
+ index=self.name,
1214
+ query={"ids": {"values": [key]}},
1215
+ size=1,
1216
+ seq_no_primary_term=True,
1217
+ )
1218
+ hits = result["hits"]["hits"]
1219
+ if not hits:
1220
+ self._raise_document_not_found(key, result)
1221
+ doc = hits[0]
1222
+ else:
1223
+ doc = self.with_retries(self.datastore.client.get, index=self.name, id=key)
1224
+
1199
1225
  if version:
1226
+ if self.ilm_config:
1227
+ version_token = f"{doc['_index']}---{doc['_seq_no']}---{doc['_primary_term']}"
1228
+ else:
1229
+ version_token = f"{doc['_seq_no']}---{doc['_primary_term']}"
1200
1230
  return (
1201
1231
  normalize_output(doc["_source"]),
1202
- f"{doc['_seq_no']}---{doc['_primary_term']}",
1232
+ version_token,
1203
1233
  )
1204
1234
  return normalize_output(doc["_source"])
1205
1235
  except elasticsearch.exceptions.NotFoundError:
@@ -1218,6 +1248,19 @@ class ESCollection(Generic[ModelType]):
1218
1248
 
1219
1249
  return None
1220
1250
 
1251
+ def _get_version_write_target(self, version: str) -> tuple[str, str, str]:
1252
+ """Return the concrete write target and optimistic-concurrency values from a version token."""
1253
+ version_parts = version.split("---")
1254
+ if len(version_parts) == 3:
1255
+ index, seq_no, primary_term = version_parts
1256
+ return index, seq_no, primary_term
1257
+
1258
+ if len(version_parts) == 2:
1259
+ seq_no, primary_term = version_parts
1260
+ return self.name, seq_no, primary_term
1261
+
1262
+ raise DataStoreException(f"Invalid version token for {self.name}: {version!r}")
1263
+
1221
1264
  @overload
1222
1265
  def get(self, key, as_obj: Literal[True], version: Literal[True]) -> tuple[ModelType | None, str]: ...
1223
1266
 
@@ -1347,13 +1390,17 @@ class ESCollection(Generic[ModelType]):
1347
1390
 
1348
1391
  saved_data["id"] = key
1349
1392
  operation = "index"
1393
+ index = self.name
1350
1394
  seq_no = None
1351
1395
  primary_term = None
1352
1396
 
1397
+ if version is None and self.ilm_config:
1398
+ _, version = self.get_if_exists(key, as_obj=False, version=True)
1399
+
1353
1400
  if version == CREATE_TOKEN:
1354
1401
  operation = "create"
1355
1402
  elif version:
1356
- seq_no, primary_term = version.split("---")
1403
+ index, seq_no, primary_term = self._get_version_write_target(version)
1357
1404
 
1358
1405
  if refresh == "true":
1359
1406
  logger.warning(
@@ -1363,7 +1410,7 @@ class ESCollection(Generic[ModelType]):
1363
1410
  try:
1364
1411
  self.with_retries(
1365
1412
  self.datastore.client.index,
1366
- index=self.name,
1413
+ index=index,
1367
1414
  id=key,
1368
1415
  document=json.dumps(saved_data),
1369
1416
  op_type=operation,
@@ -1563,15 +1610,16 @@ class ESCollection(Generic[ModelType]):
1563
1610
  """
1564
1611
  operations = self._validate_operations(operations)
1565
1612
  script = self._create_scripts_from_operations(operations)
1613
+ index = self.name
1566
1614
  seq_no = None
1567
1615
  primary_term = None
1568
1616
  if version:
1569
- seq_no, primary_term = version.split("---")
1617
+ index, seq_no, primary_term = self._get_version_write_target(version)
1570
1618
 
1571
1619
  try:
1572
1620
  res = self.with_retries(
1573
1621
  self.datastore.client.update,
1574
- index=self.name,
1622
+ index=index,
1575
1623
  id=key,
1576
1624
  script=script,
1577
1625
  if_seq_no=seq_no,
@@ -1581,7 +1629,11 @@ class ESCollection(Generic[ModelType]):
1581
1629
  )
1582
1630
  return (
1583
1631
  res["result"] == "updated",
1584
- f"{res['_seq_no']}---{res['_primary_term']}",
1632
+ (
1633
+ f"{res['_index']}---{res['_seq_no']}---{res['_primary_term']}"
1634
+ if self.ilm_config
1635
+ else f"{res['_seq_no']}---{res['_primary_term']}"
1636
+ ),
1585
1637
  )
1586
1638
  except elasticsearch.NotFoundError as e:
1587
1639
  logger.warning("Update - elasticsearch.NotFoundError: %s %s", e.message, e.info)
@@ -9,7 +9,7 @@ Elasticsearch datastore without boilerplate.
9
9
  from __future__ import annotations
10
10
 
11
11
  from operator import attrgetter
12
- from typing import Generic, Literal, TypeVar, overload
12
+ from typing import Generic, Literal, Self, TypeVar, overload
13
13
 
14
14
  from howler.common.exceptions import HowlerRuntimeError
15
15
  from howler.common.loader import datastore
@@ -28,10 +28,10 @@ class _ObjectsDescriptor(Generic[ModelType]):
28
28
  """
29
29
 
30
30
  @overload
31
- def __get__(self, obj: None, objtype: type[ModelType]) -> ESCollection[ModelType]: ...
31
+ def __get__(self: Self, obj: None, objtype: type[ModelType]) -> ESCollection[ModelType]: ...
32
32
 
33
33
  @overload
34
- def __get__(self, obj: ModelType, objtype: type[ModelType]) -> ESCollection[ModelType]: ...
34
+ def __get__(self: Self, obj: ModelType, objtype: type[ModelType]) -> ESCollection[ModelType]: ...
35
35
 
36
36
  def __get__(self, obj: ModelType | None, objtype: type[ModelType] | None = None) -> ESCollection[ModelType]:
37
37
  """Return the ESCollection for the owner class.
@@ -82,7 +82,11 @@ class DatastoreMixin(Generic[ModelType]):
82
82
  """
83
83
  return datastore()
84
84
 
85
- def save(self, refresh: Literal["true", "false", "wait_for"] | None = None) -> bool:
85
+ def save(
86
+ self: Self,
87
+ refresh: Literal["true", "false", "wait_for"] | None = None,
88
+ version: str | None = None,
89
+ ) -> bool:
86
90
  """Persist the current model instance to the datastore.
87
91
 
88
92
  Determines the target index from the lowercase class name, extracts the
@@ -96,5 +100,6 @@ class DatastoreMixin(Generic[ModelType]):
96
100
  index_name = self.__class__.__name__.lower()
97
101
  id_field = self.__class__._Model__id_field # type: ignore[attr-defined]
98
102
  current_id = attrgetter(id_field)(self)
103
+ collection = self.ds[index_name]
99
104
 
100
- return self.ds[index_name].save(current_id, self, refresh=refresh)
105
+ return collection.save(current_id, self, version=version, refresh=refresh)
@@ -19,6 +19,7 @@ from howler.common.exceptions import (
19
19
  from howler.common.loader import APP_NAME, datastore
20
20
  from howler.common.logging import get_logger
21
21
  from howler.config import CLASSIFICATION
22
+ from howler.datastore.collection import CREATE_TOKEN
22
23
  from howler.datastore.exceptions import DataStoreException
23
24
  from howler.odm.models.case import Case, CaseItem, CaseItemTypes, CaseLog, CaseRule
24
25
  from howler.odm.models.ecs.related import Related
@@ -56,7 +57,7 @@ def create_case(
56
57
 
57
58
  case = Case(case_data)
58
59
  case.log = [CaseLog({"timestamp": "NOW", "explanation": "Case created", "user": user.uname if user else "system"})]
59
- case.save(refresh="wait_for")
60
+ case.save(refresh="wait_for", version=CREATE_TOKEN)
60
61
  CREATED_CASES.inc()
61
62
 
62
63
  for item in items:
@@ -693,18 +694,18 @@ def remove_case_items( # noqa: C901
693
694
 
694
695
  # Resolve backing objects for back-reference cleanup
695
696
  items_to_remove = [items_by_id[iid] for iid in ids_to_remove if iid in items_by_id]
696
- backing_objs: list[Hit | Event] = []
697
+ backing_objs: list[tuple[Hit | Event, str]] = []
697
698
  for item in items_to_remove:
698
699
  if item.type in [CaseItemTypes.HIT, CaseItemTypes.EVENT]:
699
- obj = ds[item.type].get(item.value)
700
+ obj, version = ds[item.type].get(item.value, as_obj=True, version=True)
700
701
  if obj:
701
- backing_objs.append(obj)
702
+ backing_objs.append((obj, version))
702
703
 
703
704
  case.items = [item for item in case.items if item.id not in ids_to_remove]
704
705
 
705
- for backing_obj in backing_objs:
706
+ for backing_obj, version in backing_objs:
706
707
  remove_backreference(backing_obj, case.case_id)
707
- backing_obj.save()
708
+ backing_obj.save(version=version)
708
709
 
709
710
  recompute_case_metadata(case)
710
711
 
@@ -745,7 +746,7 @@ def append_hit(case: Case, item: CaseItem, refresh: Literal["true", "false", "wa
745
746
  """
746
747
  ds = datastore()
747
748
 
748
- hit = ds.hit.get(item.value)
749
+ hit, version = ds.hit.get(item.value, as_obj=True, version=True)
749
750
  if hit is None:
750
751
  raise NotFoundException(f"Hit {item.value} not found, cannot be added to case")
751
752
 
@@ -754,7 +755,7 @@ def append_hit(case: Case, item: CaseItem, refresh: Literal["true", "false", "wa
754
755
  case.items.append(item)
755
756
 
756
757
  add_backreference(hit, case.case_id)
757
- hit.save()
758
+ hit.save(version=version)
758
759
 
759
760
  recompute_case_metadata(case)
760
761
  if not case.save(refresh=refresh): # pragma: no cover
@@ -783,7 +784,7 @@ def append_event(case: Case, item: CaseItem, refresh: Literal["true", "false", "
783
784
  """
784
785
  ds = datastore()
785
786
 
786
- event = ds.event.get(key=item.value)
787
+ event, version = ds.event.get(key=item.value, as_obj=True, version=True)
787
788
 
788
789
  if event is None:
789
790
  raise NotFoundException(f"Event {item.value} not found, cannot be added to case")
@@ -793,7 +794,7 @@ def append_event(case: Case, item: CaseItem, refresh: Literal["true", "false", "
793
794
  case.items.append(item)
794
795
 
795
796
  add_backreference(event, case.case_id)
796
- event.save()
797
+ event.save(version=version)
797
798
 
798
799
  recompute_case_metadata(case)
799
800
  if not case.save(refresh=refresh): # pragma: no cover
@@ -199,7 +199,7 @@ def update_dossier( # noqa: C901
199
199
  storage = datastore()
200
200
 
201
201
  # Retrieve the existing dossier for access control checks
202
- existing_dossier: Dossier = get_dossier(dossier_id, as_odm=True)
202
+ existing_dossier, server_version = get_dossier(dossier_id, as_odm=True, version=True)
203
203
 
204
204
  # Enforce access control for personal dossiers
205
205
  # Only the owner or admin users can modify personal dossiers
@@ -228,7 +228,7 @@ def update_dossier( # noqa: C901
228
228
  # Merge the new data with existing dossier data
229
229
  new_data = Dossier(cast(dict, merge({}, existing_dossier.as_primitives(), dossier_data)))
230
230
 
231
- storage.dossier.save(dossier_id, new_data, refresh=refresh)
231
+ storage.dossier.save(dossier_id, new_data, version=server_version, refresh=refresh)
232
232
 
233
233
  return new_data
234
234
  except SearchException:
@@ -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.3.dev1397"
155
+ version = "4.0.4.dev1401"
156
156
  description = "Howler - API server"
157
157
  authors = [
158
158
  "Canadian Centre for Cyber Security <howler@cyber.gc.ca>",