howler-api 2.10.0.dev62__tar.gz → 2.10.0.dev67__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 (194) hide show
  1. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/PKG-INFO +1 -1
  2. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/collection.py +46 -25
  3. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/howler_store.py +4 -4
  4. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/lucene_service.py +8 -7
  5. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/dict_utils.py +27 -0
  6. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/pyproject.toml +1 -1
  7. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/README.md +0 -0
  8. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/__init__.py +0 -0
  9. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/__init__.py +0 -0
  10. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/add_label.py +0 -0
  11. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/add_to_bundle.py +0 -0
  12. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/change_field.py +0 -0
  13. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/demote.py +0 -0
  14. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/example_plugin.py +0 -0
  15. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/prioritization.py +0 -0
  16. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/promote.py +0 -0
  17. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/remove_from_bundle.py +0 -0
  18. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/remove_label.py +0 -0
  19. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/actions/transition.py +0 -0
  20. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/__init__.py +0 -0
  21. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/base.py +0 -0
  22. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/socket.py +0 -0
  23. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/__init__.py +0 -0
  24. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/action.py +0 -0
  25. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/analytic.py +0 -0
  26. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/auth.py +0 -0
  27. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/borealis.py +0 -0
  28. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/configs.py +0 -0
  29. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/dossier.py +0 -0
  30. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/help.py +0 -0
  31. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/hit.py +0 -0
  32. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/notebook.py +0 -0
  33. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/overview.py +0 -0
  34. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/search.py +0 -0
  35. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/template.py +0 -0
  36. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/tool.py +0 -0
  37. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/user.py +0 -0
  38. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/utils/__init__.py +0 -0
  39. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/utils/etag.py +0 -0
  40. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/api/v1/view.py +0 -0
  41. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/app.py +0 -0
  42. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/README.md +0 -0
  43. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/__init__.py +0 -0
  44. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/classification.py +0 -0
  45. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/classification.yml +0 -0
  46. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/exceptions.py +0 -0
  47. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/hexdump.py +0 -0
  48. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/iprange.py +0 -0
  49. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/loader.py +0 -0
  50. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/logging/__init__.py +0 -0
  51. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/logging/audit.py +0 -0
  52. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/logging/format.py +0 -0
  53. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/net.py +0 -0
  54. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/net_static.py +0 -0
  55. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/random_user.py +0 -0
  56. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/common/swagger.py +0 -0
  57. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/config.py +0 -0
  58. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/cronjobs/__init__.py +0 -0
  59. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/cronjobs/retention.py +0 -0
  60. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/cronjobs/rules.py +0 -0
  61. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/README.md +0 -0
  62. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/__init__.py +0 -0
  63. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/bulk.py +0 -0
  64. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/constants.py +0 -0
  65. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/exceptions.py +0 -0
  66. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/migrations/fix_process.py +0 -0
  67. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/operations.py +0 -0
  68. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/schemas.py +0 -0
  69. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/store.py +0 -0
  70. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/support/__init__.py +0 -0
  71. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/support/build.py +0 -0
  72. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/support/schemas.py +0 -0
  73. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/datastore/types.py +0 -0
  74. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/error.py +0 -0
  75. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/external/__init__.py +0 -0
  76. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/external/generate_mitre.py +0 -0
  77. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/external/generate_sigma_rules.py +0 -0
  78. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/external/generate_tlds.py +0 -0
  79. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/external/reindex_data.py +0 -0
  80. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/external/wipe_databases.py +0 -0
  81. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/gunicorn_config.py +0 -0
  82. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/healthz.py +0 -0
  83. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/helper/__init__.py +0 -0
  84. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/helper/azure.py +0 -0
  85. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/helper/discover.py +0 -0
  86. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/helper/hit.py +0 -0
  87. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/helper/oauth.py +0 -0
  88. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/helper/search.py +0 -0
  89. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/helper/workflow.py +0 -0
  90. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/helper/ws.py +0 -0
  91. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/README.md +0 -0
  92. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/__init__.py +0 -0
  93. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/base.py +0 -0
  94. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/charter.txt +0 -0
  95. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/helper.py +0 -0
  96. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/howler_enum.py +0 -0
  97. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/__init__.py +0 -0
  98. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/action.py +0 -0
  99. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/analytic.py +0 -0
  100. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/assemblyline.py +0 -0
  101. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/aws.py +0 -0
  102. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/azure.py +0 -0
  103. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/cbs.py +0 -0
  104. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/config.py +0 -0
  105. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/dossier.py +0 -0
  106. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/__init__.py +0 -0
  107. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/agent.py +0 -0
  108. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/autonomous_system.py +0 -0
  109. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/client.py +0 -0
  110. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/cloud.py +0 -0
  111. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/code_signature.py +0 -0
  112. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/container.py +0 -0
  113. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/dns.py +0 -0
  114. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/egress.py +0 -0
  115. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/elf.py +0 -0
  116. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/email.py +0 -0
  117. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/error.py +0 -0
  118. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/event.py +0 -0
  119. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/faas.py +0 -0
  120. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/file.py +0 -0
  121. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/geo.py +0 -0
  122. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/group.py +0 -0
  123. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/hash.py +0 -0
  124. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/host.py +0 -0
  125. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/http.py +0 -0
  126. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/ingress.py +0 -0
  127. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/interface.py +0 -0
  128. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/network.py +0 -0
  129. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/observer.py +0 -0
  130. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/organization.py +0 -0
  131. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/os.py +0 -0
  132. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/pe.py +0 -0
  133. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/process.py +0 -0
  134. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/registry.py +0 -0
  135. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/related.py +0 -0
  136. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/rule.py +0 -0
  137. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/server.py +0 -0
  138. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/threat.py +0 -0
  139. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/tls.py +0 -0
  140. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/url.py +0 -0
  141. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/user.py +0 -0
  142. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/user_agent.py +0 -0
  143. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/ecs/vulnerability.py +0 -0
  144. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/gcp.py +0 -0
  145. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/hit.py +0 -0
  146. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/howler_data.py +0 -0
  147. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/lead.py +0 -0
  148. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/localized_label.py +0 -0
  149. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/overview.py +0 -0
  150. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/pivot.py +0 -0
  151. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/template.py +0 -0
  152. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/user.py +0 -0
  153. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/models/view.py +0 -0
  154. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/random_data.py +0 -0
  155. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/odm/randomizer.py +0 -0
  156. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/patched.py +0 -0
  157. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/__init__.py +0 -0
  158. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/README.md +0 -0
  159. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/__init__.py +0 -0
  160. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/counters.py +0 -0
  161. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/events.py +0 -0
  162. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/hash.py +0 -0
  163. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/lock.py +0 -0
  164. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/queues/__init__.py +0 -0
  165. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/queues/comms.py +0 -0
  166. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/queues/multi.py +0 -0
  167. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/queues/named.py +0 -0
  168. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/queues/priority.py +0 -0
  169. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/set.py +0 -0
  170. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/remote/datatypes/user_quota_tracker.py +0 -0
  171. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/security/__init__.py +0 -0
  172. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/security/socket.py +0 -0
  173. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/security/utils.py +0 -0
  174. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/__init__.py +0 -0
  175. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/action_service.py +0 -0
  176. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/analytic_service.py +0 -0
  177. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/auth_service.py +0 -0
  178. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/config_service.py +0 -0
  179. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/dossier_service.py +0 -0
  180. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/event_service.py +0 -0
  181. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/hit_service.py +0 -0
  182. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/jwt_service.py +0 -0
  183. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/notebook_service.py +0 -0
  184. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/services/user_service.py +0 -0
  185. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/__init__.py +0 -0
  186. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/annotations.py +0 -0
  187. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/chunk.py +0 -0
  188. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/isotime.py +0 -0
  189. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/list_utils.py +0 -0
  190. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/lucene.py +0 -0
  191. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/path.py +0 -0
  192. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/socket_utils.py +0 -0
  193. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/str_utils.py +0 -0
  194. {howler_api-2.10.0.dev62 → howler_api-2.10.0.dev67}/howler/utils/uid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: howler-api
3
- Version: 2.10.0.dev62
3
+ Version: 2.10.0.dev67
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -57,7 +57,7 @@ if typing.TYPE_CHECKING:
57
57
 
58
58
  TRANSPORT_TIMEOUT = int(environ.get("HWL_DATASTORE_TRANSPORT_TIMEOUT", "10"))
59
59
 
60
- log = logging.getLogger("howler.api.datastore")
60
+ logger = logging.getLogger("howler.api.datastore")
61
61
  ModelType = TypeVar("ModelType", bound=Model)
62
62
  write_block_settings = {"settings": {"index.blocks.write": True}}
63
63
  write_unblock_settings = {"settings": {"index.blocks.write": None}}
@@ -219,7 +219,7 @@ class ESCollection(Generic[ModelType]):
219
219
  if not ESCollection.IGNORE_ENSURE_COLLECTION:
220
220
  self._ensure_collection()
221
221
  else:
222
- log.warning("Skipping ensure collection! This is dangerous. Waiting five seconds before continuing.")
222
+ logger.warning("Skipping ensure collection! This is dangerous. Waiting five seconds before continuing.")
223
223
  time.sleep(5)
224
224
 
225
225
  self.stored_fields = {}
@@ -297,7 +297,7 @@ class ESCollection(Generic[ModelType]):
297
297
  ignore=(404,),
298
298
  )
299
299
  if not resp.get("succeeded", False):
300
- log.warning(
300
+ logger.warning(
301
301
  f"Could not clear scroll ID {scroll_id}, there is potential "
302
302
  "memory leak in you Elastic cluster..."
303
303
  )
@@ -319,7 +319,7 @@ class ESCollection(Generic[ModelType]):
319
319
  ret_val = func(*args, **kwargs)
320
320
 
321
321
  if retries:
322
- log.info("Reconnected to elasticsearch!")
322
+ logger.info("Reconnected to elasticsearch!")
323
323
 
324
324
  if updated:
325
325
  ret_val["updated"] += updated
@@ -331,7 +331,7 @@ class ESCollection(Generic[ModelType]):
331
331
  except elasticsearch.exceptions.NotFoundError as e:
332
332
  if "index_not_found_exception" in str(e):
333
333
  time.sleep(min(retries, self.MAX_RETRY_BACKOFF))
334
- log.debug("The index does not exist. Trying to recreate it...")
334
+ logger.debug("The index does not exist. Trying to recreate it...")
335
335
  self._ensure_collection()
336
336
  self.datastore.connection_reset()
337
337
  retries += 1
@@ -351,7 +351,7 @@ class ESCollection(Generic[ModelType]):
351
351
  retries += 1
352
352
 
353
353
  except elasticsearch.exceptions.ConnectionTimeout:
354
- log.warning(
354
+ logger.warning(
355
355
  f"Elasticsearch connection timeout, server(s): "
356
356
  f"{' | '.join(self.datastore.get_hosts(safe=True))}"
357
357
  f", retrying {func.__name__}..."
@@ -366,7 +366,7 @@ class ESCollection(Generic[ModelType]):
366
366
  elasticsearch.exceptions.AuthenticationException,
367
367
  ) as e:
368
368
  if not isinstance(e, SearchRetryException):
369
- log.warning(
369
+ logger.warning(
370
370
  f"No connection to Elasticsearch server(s): "
371
371
  f"{' | '.join(self.datastore.get_hosts(safe=True))}"
372
372
  f", because [{e}] retrying {func.__name__}..."
@@ -379,19 +379,19 @@ class ESCollection(Generic[ModelType]):
379
379
  except elasticsearch.exceptions.TransportError as e:
380
380
  err_code, msg, cause = e.args
381
381
  if err_code == 503 or err_code == "503":
382
- log.warning(f"Looks like index {self.name} is not ready yet, retrying...")
382
+ logger.warning(f"Looks like index {self.name} is not ready yet, retrying...")
383
383
  time.sleep(min(retries, self.MAX_RETRY_BACKOFF))
384
384
  self.datastore.connection_reset()
385
385
  retries += 1
386
386
  elif err_code == 429 or err_code == "429":
387
- log.warning(
387
+ logger.warning(
388
388
  "Elasticsearch is too busy to perform the requested " f"task on index {self.name}, retrying..."
389
389
  )
390
390
  time.sleep(min(retries, self.MAX_RETRY_BACKOFF))
391
391
  self.datastore.connection_reset()
392
392
  retries += 1
393
393
  elif err_code == 403 or err_code == "403":
394
- log.warning(
394
+ logger.warning(
395
395
  "Elasticsearch cluster is preventing writing operations " f"on index {self.name}, retrying..."
396
396
  )
397
397
  time.sleep(min(retries, self.MAX_RETRY_BACKOFF))
@@ -445,7 +445,7 @@ class ESCollection(Generic[ModelType]):
445
445
  except elasticsearch.exceptions.TransportError as e:
446
446
  err_code, _, _ = e.args
447
447
  if err_code == 408 or err_code == "408":
448
- log.warning(f"Waiting for index {index} to get to status {min_status}...")
448
+ logger.warning(f"Waiting for index {index} to get to status {min_status}...")
449
449
  else:
450
450
  raise
451
451
 
@@ -534,15 +534,12 @@ class ESCollection(Generic[ModelType]):
534
534
  "acknowledged"
535
535
  ]
536
536
 
537
- def fix_shards(self, logger=None):
537
+ def fix_shards(self):
538
538
  """This function should be overloaded to fix the shard configuration of the index of all the different hosts
539
539
  specified in self.datastore.hosts.
540
540
 
541
541
  :return: Should return True of the fix was successful on all hosts
542
542
  """
543
- if logger is None:
544
- logger = log
545
-
546
543
  body = {"settings": self._get_index_settings()}
547
544
  clone_body = {"settings": {"index.number_of_replicas": 0}}
548
545
  clone_finish_settings = None
@@ -844,7 +841,7 @@ class ESCollection(Generic[ModelType]):
844
841
  key_list.remove(row["_id"])
845
842
  add_to_output(row["_source"], row["_id"])
846
843
  except ValueError:
847
- log.exception(f'MGet returned multiple documents for id: {row["_id"]}')
844
+ logger.exception(f'MGet returned multiple documents for id: {row["_id"]}')
848
845
 
849
846
  if key_list and error_on_missing:
850
847
  raise MultiKeyError(key_list, out)
@@ -1221,15 +1218,15 @@ class ESCollection(Generic[ModelType]):
1221
1218
  f"{res['_seq_no']}---{res['_primary_term']}",
1222
1219
  )
1223
1220
  except elasticsearch.NotFoundError as e:
1224
- log.warning("Update - elasticsearch.NotFoundError: %s %s", e.message, e.info)
1221
+ logger.warning("Update - elasticsearch.NotFoundError: %s %s", e.message, e.info)
1225
1222
  except elasticsearch.BadRequestError as e:
1226
- log.warning("Update - elasticsearch.BadRequestError: %s %s", e.message, e.info)
1223
+ logger.warning("Update - elasticsearch.BadRequestError: %s %s", e.message, e.info)
1227
1224
  return False
1228
1225
  except VersionConflictException as e:
1229
- log.warning("Update - elasticsearch.ConflictError: %s", e.message)
1226
+ logger.warning("Update - elasticsearch.ConflictError: %s", e.message)
1230
1227
  raise
1231
1228
  except Exception as e:
1232
- log.warning("Update - Generic Exception: %s", str(e))
1229
+ logger.warning("Update - Generic Exception: %s", str(e))
1233
1230
  return False
1234
1231
 
1235
1232
  return False
@@ -2147,7 +2144,12 @@ class ESCollection(Generic[ModelType]):
2147
2144
  if "total_fields" not in settings["index"]["mapping"]:
2148
2145
  settings["index"]["mapping"]["total_fields"] = {}
2149
2146
 
2150
- settings["index"]["mapping"]["total_fields"]["limit"] = 1500
2147
+ limit = len(self.model_class.flat_fields()) + 500 if self.model_class else 1500
2148
+ if limit < 1500:
2149
+ limit = 1500
2150
+ else:
2151
+ logger.warning("ODM field size is larger than 1500 - set to %s", limit)
2152
+ settings["index"]["mapping"]["total_fields"]["limit"] = limit
2151
2153
 
2152
2154
  return settings
2153
2155
 
@@ -2200,7 +2202,26 @@ class ESCollection(Generic[ModelType]):
2200
2202
 
2201
2203
  missing = set(model.keys()) - set(fields.keys())
2202
2204
  if missing:
2203
- self._add_fields({key: model[key] for key in missing})
2205
+ # TODO: Bump mapping limit
2206
+ try:
2207
+ self._add_fields({key: model[key] for key in missing})
2208
+ except elasticsearch.BadRequestError as err:
2209
+ handled = False
2210
+ if err.body and isinstance(err.body, dict) and "error" in err.body and "reason" in err.body["error"]:
2211
+ reason: str = err.body["error"]["reason"]
2212
+ if reason.startswith("Limit of total fields"):
2213
+ current_count = int(re.sub(r".+\[(\d+)].+", r"\1", reason))
2214
+ logger.warning(
2215
+ "Current field cap %s is too low, increasing to %s", current_count, current_count + 500
2216
+ )
2217
+ self.with_retries(
2218
+ self.datastore.client.indices.put_settings,
2219
+ body={"settings": {"index.mapping.total_fields.limit": current_count + 500}},
2220
+ )
2221
+ self._add_fields({key: model[key] for key in missing})
2222
+ handled = True
2223
+ if not handled:
2224
+ raise
2204
2225
 
2205
2226
  matching = set(fields.keys()) & set(model.keys())
2206
2227
  for field_name in matching:
@@ -2224,7 +2245,7 @@ class ESCollection(Generic[ModelType]):
2224
2245
  """
2225
2246
  # Create HOT index
2226
2247
  if not self.with_retries(self.datastore.client.indices.exists, index=self.name):
2227
- log.debug(f"Index {self.name.upper()} does not exists. Creating it now...")
2248
+ logger.debug(f"Index {self.name.upper()} does not exists. Creating it now...")
2228
2249
  try:
2229
2250
  self.with_retries(
2230
2251
  self.datastore.client.indices.create,
@@ -2235,7 +2256,7 @@ class ESCollection(Generic[ModelType]):
2235
2256
  except elasticsearch.exceptions.RequestError as e:
2236
2257
  if "resource_already_exists_exception" not in str(e):
2237
2258
  raise
2238
- log.warning(f"Tried to create an index template that already exists: {self.name.upper()}")
2259
+ logger.warning(f"Tried to create an index template that already exists: {self.name.upper()}")
2239
2260
 
2240
2261
  self.with_retries(
2241
2262
  self.datastore.client.indices.put_alias,
@@ -2310,7 +2331,7 @@ class ESCollection(Generic[ModelType]):
2310
2331
 
2311
2332
  :return:
2312
2333
  """
2313
- log.debug("Wipe operation started for collection: %s" % self.name.upper())
2334
+ logger.debug("Wipe operation started for collection: %s" % self.name.upper())
2314
2335
 
2315
2336
  for index in self.index_list:
2316
2337
  if self.with_retries(self.datastore.client.indices.exists, index=index):
@@ -5,7 +5,7 @@ import elasticapm
5
5
  import elasticsearch
6
6
 
7
7
  from howler.common.exceptions import HowlerAttributeError
8
- from howler.datastore.collection import ESCollection, log
8
+ from howler.datastore.collection import ESCollection, logger
9
9
  from howler.odm.models.action import Action
10
10
  from howler.odm.models.analytic import Analytic
11
11
  from howler.odm.models.dossier import Dossier
@@ -106,7 +106,7 @@ class HowlerDatastore(object):
106
106
  elasticsearch.exceptions.ConnectionTimeout,
107
107
  elasticsearch.exceptions.AuthenticationException,
108
108
  ):
109
- log.warning(
109
+ logger.warning(
110
110
  f"No connection to Elasticsearch server(s): "
111
111
  f"{' | '.join(self.ds.get_hosts(safe=True))}"
112
112
  f", retrying..."
@@ -118,12 +118,12 @@ class HowlerDatastore(object):
118
118
  except elasticsearch.exceptions.TransportError as e:
119
119
  err_code, msg, cause = e.args
120
120
  if err_code == 503 or err_code == "503":
121
- log.warning("Looks like index is not ready yet, retrying...")
121
+ logger.warning("Looks like index is not ready yet, retrying...")
122
122
  time.sleep(min(retries, max_retry_backoff))
123
123
  self.ds.connection_reset()
124
124
  retries += 1
125
125
  elif err_code == 429 or err_code == "429":
126
- log.warning(
126
+ logger.warning(
127
127
  "Elasticsearch is too busy to perform the requested task, " "we will wait a bit and retry..."
128
128
  )
129
129
  time.sleep(min(retries, max_retry_backoff))
@@ -12,13 +12,13 @@ from luqum.parser import parser
12
12
  from luqum.tree import AndOperation, BoolOperation, Phrase, Plus, Prohibit, Range, SearchField, Word
13
13
  from luqum.utils import UnknownOperationResolver
14
14
  from luqum.visitor import TreeVisitor
15
- from pydash import get
16
15
 
17
16
  from howler.api import get_logger
18
17
  from howler.common.exceptions import InvalidDataException
19
18
  from howler.common.loader import datastore
20
19
  from howler.config import redis
21
20
  from howler.remote.datatypes.hash import Hash
21
+ from howler.utils.dict_utils import flatten_deep
22
22
  from howler.utils.lucene import coerce, normalize_phrase, try_parse_date, try_parse_ip
23
23
 
24
24
  logger = get_logger(__file__)
@@ -108,7 +108,7 @@ class LuceneProcessor(TreeVisitor):
108
108
 
109
109
  def visit_range(self, node: Range, context: dict[str, Any]):
110
110
  "Handle range queries"
111
- low, value, high = self.__parse_range(node.high.value, get(context["hit"], context["field"]), node.low.value)
111
+ low, value, high = self.__parse_range(node.high.value, context["hit"].get(context["field"]), node.low.value)
112
112
 
113
113
  if isinstance(value, list):
114
114
  values = value
@@ -159,10 +159,10 @@ class LuceneProcessor(TreeVisitor):
159
159
 
160
160
  if "field" not in context:
161
161
  yield any(value == sanitized_value for value in context["hit"].values())
162
- elif context["field"] == "_exists_" or node.value == "*":
163
- yield get(context["hit"], node.value) is not None
162
+ elif context["field"] == "_exists_":
163
+ yield context["hit"].get(node.value) is not None
164
164
  else:
165
- candidates = self.__build_candidates(get(context["hit"], context["field"]), context["term_type"])
165
+ candidates = self.__build_candidates(context["hit"].get(context["field"]), context["term_type"])
166
166
 
167
167
  yield len(fnmatch.filter(candidates, sanitized_value)) > 0
168
168
 
@@ -246,7 +246,8 @@ def match(lucene: str, obj: dict[str, Any]):
246
246
  normalized_query = cast(str, result["explanations"][0]["explanation"])
247
247
 
248
248
  # Elastic's explanation mangles exists queries. Since we will handle them the normal way, reset their changes
249
- normalized_query = re.sub(r"ConstantScore\(.+?field=(.+?)]\)", r"_exists_:\1", normalized_query)
249
+ normalized_query = re.sub(r"FieldExistsQuery *\[.*?field=(.+?)]", r"_exists_:\1", normalized_query)
250
+ normalized_query = re.sub(r"ConstantScore", "", normalized_query)
250
251
  # try and reinsert any phrases we have replaced with sha256 hashes
251
252
  normalized_query = re.sub(r"([0-9a-f]{64})", try_reinsert_lucene_phrase, normalized_query)
252
253
 
@@ -266,7 +267,7 @@ def match(lucene: str, obj: dict[str, Any]):
266
267
  tree = UnknownOperationResolver(resolve_to=BoolOperation)(parser.parse(normalized_query))
267
268
 
268
269
  # Actually run the validation
269
- return LuceneProcessor(track_parents=True).visit(tree, {"hit": obj})
270
+ return LuceneProcessor(track_parents=True).visit(tree, {"hit": flatten_deep(obj)})
270
271
  except Exception:
271
272
  logger.exception("Exception on processing lucene:")
272
273
  return False
@@ -93,6 +93,33 @@ def flatten(data: _Mapping, parent_key: Optional[str] = None, odm: Optional[type
93
93
  return dict(items)
94
94
 
95
95
 
96
+ def flatten_deep(data: _Mapping):
97
+ "Aggressively and completely flatten an object."
98
+ partially_flattened = flatten(data)
99
+
100
+ final: dict[str, Any] = {}
101
+ for key, value in partially_flattened.items():
102
+ if not isinstance(value, list) or len(value) == 0 or all(not isinstance(entry, dict) for entry in value):
103
+ final[key] = value
104
+ else:
105
+ for entry in value:
106
+ flat_value = flatten_deep(entry)
107
+ for child_key, child_value in flat_value.items():
108
+ full_key = f"{key}.{child_key}"
109
+ if full_key not in final:
110
+ if isinstance(child_value, list):
111
+ final[full_key] = child_value
112
+ else:
113
+ final[full_key] = [child_value]
114
+ else:
115
+ if isinstance(child_value, list):
116
+ final[full_key].extend(child_value)
117
+ else:
118
+ final[full_key].append(child_value)
119
+
120
+ return final
121
+
122
+
96
123
  def unflatten(data: _Mapping) -> _Mapping:
97
124
  "Unflatten a nested dict"
98
125
  out: dict[str, Any] = dict()
@@ -147,7 +147,7 @@ suppress-none-returning = true
147
147
  [tool.poetry]
148
148
  package-mode = true
149
149
  name = "howler-api"
150
- version = "2.10.0.dev62"
150
+ version = "2.10.0.dev67"
151
151
  description = "Howler - API server"
152
152
  authors = [
153
153
  "Canadian Centre for Cyber Security <howler@cyber.gc.ca>",