howler-api 2.10.0.dev131__tar.gz → 2.10.0.dev155__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 (196) hide show
  1. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/PKG-INFO +2 -2
  2. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/__init__.py +37 -36
  3. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/borealis.py +10 -9
  4. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/app.py +12 -9
  5. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/cronjobs/__init__.py +2 -1
  6. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/collection.py +7 -0
  7. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/howler_store.py +25 -9
  8. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/helper/search.py +8 -10
  9. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/helper.py +4 -6
  10. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/hit.py +0 -12
  11. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/random_data.py +62 -4
  12. howler_api-2.10.0.dev155/howler/plugins/__init__.py +25 -0
  13. howler_api-2.10.0.dev155/howler/plugins/config.py +123 -0
  14. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/config_service.py +3 -3
  15. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/notebook_service.py +5 -15
  16. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/pyproject.toml +2 -2
  17. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/README.md +0 -0
  18. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/__init__.py +0 -0
  19. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/add_label.py +0 -0
  20. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/add_to_bundle.py +0 -0
  21. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/change_field.py +0 -0
  22. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/demote.py +0 -0
  23. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/example_plugin.py +0 -0
  24. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/prioritization.py +0 -0
  25. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/promote.py +0 -0
  26. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/remove_from_bundle.py +0 -0
  27. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/remove_label.py +0 -0
  28. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/actions/transition.py +0 -0
  29. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/__init__.py +0 -0
  30. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/base.py +0 -0
  31. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/socket.py +0 -0
  32. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/__init__.py +0 -0
  33. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/action.py +0 -0
  34. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/analytic.py +0 -0
  35. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/auth.py +0 -0
  36. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/configs.py +0 -0
  37. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/dossier.py +0 -0
  38. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/help.py +0 -0
  39. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/hit.py +0 -0
  40. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/notebook.py +0 -0
  41. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/overview.py +0 -0
  42. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/search.py +0 -0
  43. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/template.py +0 -0
  44. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/tool.py +0 -0
  45. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/user.py +0 -0
  46. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/utils/__init__.py +0 -0
  47. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/utils/etag.py +0 -0
  48. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/api/v1/view.py +0 -0
  49. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/README.md +0 -0
  50. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/__init__.py +0 -0
  51. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/classification.py +0 -0
  52. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/classification.yml +0 -0
  53. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/exceptions.py +0 -0
  54. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/hexdump.py +0 -0
  55. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/iprange.py +0 -0
  56. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/loader.py +0 -0
  57. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/logging/__init__.py +0 -0
  58. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/logging/audit.py +0 -0
  59. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/logging/format.py +0 -0
  60. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/net.py +0 -0
  61. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/net_static.py +0 -0
  62. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/random_user.py +0 -0
  63. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/common/swagger.py +0 -0
  64. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/config.py +0 -0
  65. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/cronjobs/retention.py +0 -0
  66. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/cronjobs/rules.py +0 -0
  67. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/README.md +0 -0
  68. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/__init__.py +0 -0
  69. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/bulk.py +0 -0
  70. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/constants.py +0 -0
  71. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/exceptions.py +0 -0
  72. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/migrations/fix_process.py +0 -0
  73. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/operations.py +0 -0
  74. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/schemas.py +0 -0
  75. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/store.py +0 -0
  76. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/support/__init__.py +0 -0
  77. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/support/build.py +0 -0
  78. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/support/schemas.py +0 -0
  79. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/datastore/types.py +0 -0
  80. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/error.py +0 -0
  81. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/external/__init__.py +0 -0
  82. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/external/generate_mitre.py +0 -0
  83. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/external/generate_sigma_rules.py +0 -0
  84. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/external/generate_tlds.py +0 -0
  85. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/external/reindex_data.py +0 -0
  86. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/external/wipe_databases.py +0 -0
  87. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/gunicorn_config.py +0 -0
  88. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/healthz.py +0 -0
  89. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/helper/__init__.py +0 -0
  90. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/helper/azure.py +0 -0
  91. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/helper/discover.py +0 -0
  92. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/helper/hit.py +0 -0
  93. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/helper/oauth.py +0 -0
  94. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/helper/workflow.py +0 -0
  95. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/helper/ws.py +0 -0
  96. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/README.md +0 -0
  97. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/__init__.py +0 -0
  98. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/base.py +0 -0
  99. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/charter.txt +0 -0
  100. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/howler_enum.py +0 -0
  101. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/__init__.py +0 -0
  102. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/action.py +0 -0
  103. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/analytic.py +0 -0
  104. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/assemblyline.py +0 -0
  105. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/aws.py +0 -0
  106. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/azure.py +0 -0
  107. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/cbs.py +0 -0
  108. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/config.py +0 -0
  109. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/dossier.py +0 -0
  110. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/__init__.py +0 -0
  111. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/agent.py +0 -0
  112. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/autonomous_system.py +0 -0
  113. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/client.py +0 -0
  114. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/cloud.py +0 -0
  115. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/code_signature.py +0 -0
  116. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/container.py +0 -0
  117. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/dns.py +0 -0
  118. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/egress.py +0 -0
  119. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/elf.py +0 -0
  120. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/email.py +0 -0
  121. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/error.py +0 -0
  122. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/event.py +0 -0
  123. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/faas.py +0 -0
  124. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/file.py +0 -0
  125. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/geo.py +0 -0
  126. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/group.py +0 -0
  127. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/hash.py +0 -0
  128. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/host.py +0 -0
  129. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/http.py +0 -0
  130. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/ingress.py +0 -0
  131. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/interface.py +0 -0
  132. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/network.py +0 -0
  133. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/observer.py +0 -0
  134. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/organization.py +0 -0
  135. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/os.py +0 -0
  136. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/pe.py +0 -0
  137. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/process.py +0 -0
  138. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/registry.py +0 -0
  139. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/related.py +0 -0
  140. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/rule.py +0 -0
  141. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/server.py +0 -0
  142. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/threat.py +0 -0
  143. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/tls.py +0 -0
  144. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/url.py +0 -0
  145. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/user.py +0 -0
  146. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/user_agent.py +0 -0
  147. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/ecs/vulnerability.py +0 -0
  148. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/gcp.py +0 -0
  149. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/howler_data.py +0 -0
  150. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/lead.py +0 -0
  151. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/localized_label.py +0 -0
  152. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/overview.py +0 -0
  153. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/pivot.py +0 -0
  154. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/template.py +0 -0
  155. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/user.py +0 -0
  156. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/models/view.py +0 -0
  157. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/odm/randomizer.py +0 -0
  158. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/patched.py +0 -0
  159. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/__init__.py +0 -0
  160. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/README.md +0 -0
  161. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/__init__.py +0 -0
  162. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/counters.py +0 -0
  163. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/events.py +0 -0
  164. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/hash.py +0 -0
  165. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/lock.py +0 -0
  166. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/queues/__init__.py +0 -0
  167. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/queues/comms.py +0 -0
  168. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/queues/multi.py +0 -0
  169. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/queues/named.py +0 -0
  170. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/queues/priority.py +0 -0
  171. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/set.py +0 -0
  172. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/remote/datatypes/user_quota_tracker.py +0 -0
  173. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/security/__init__.py +0 -0
  174. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/security/socket.py +0 -0
  175. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/security/utils.py +0 -0
  176. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/__init__.py +0 -0
  177. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/action_service.py +0 -0
  178. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/analytic_service.py +0 -0
  179. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/auth_service.py +0 -0
  180. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/dossier_service.py +0 -0
  181. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/event_service.py +0 -0
  182. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/hit_service.py +0 -0
  183. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/jwt_service.py +0 -0
  184. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/lucene_service.py +0 -0
  185. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/services/user_service.py +0 -0
  186. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/utils/__init__.py +0 -0
  187. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/utils/annotations.py +0 -0
  188. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/utils/chunk.py +0 -0
  189. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/utils/dict_utils.py +0 -0
  190. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/utils/isotime.py +0 -0
  191. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/utils/list_utils.py +0 -0
  192. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/utils/lucene.py +0 -0
  193. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/utils/path.py +0 -0
  194. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/utils/socket_utils.py +0 -0
  195. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/howler/utils/str_utils.py +0 -0
  196. {howler_api-2.10.0.dev131 → howler_api-2.10.0.dev155}/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.dev131
3
+ Version: 2.10.0.dev155
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -50,7 +50,7 @@ Requires-Dist: python-datemath (==3.0.3)
50
50
  Requires-Dist: python-dotenv (>=1.1.0,<2.0.0)
51
51
  Requires-Dist: pyyaml (==6.0.2)
52
52
  Requires-Dist: redis (==4.5.4)
53
- Requires-Dist: requests (==2.32.2)
53
+ Requires-Dist: requests (==2.32.4)
54
54
  Requires-Dist: typing-extensions (>=4.12.2,<5.0.0)
55
55
  Requires-Dist: validators (>=0.34.0,<0.35.0)
56
56
  Requires-Dist: wsproto (==1.2.0)
@@ -5,8 +5,8 @@ from pathlib import Path
5
5
  from typing import Any, Optional
6
6
 
7
7
  from howler.common.logging import get_logger
8
- from howler.config import config
9
8
  from howler.odm.models.user import User
9
+ from howler.plugins import get_plugins
10
10
 
11
11
  logger = get_logger(__file__)
12
12
 
@@ -89,20 +89,25 @@ def execute(
89
89
  Returns:
90
90
  list[dict[str, Any]]: A report on the execution
91
91
  """
92
- automation = None
93
- for module_name in ["howler", *config.core.plugins]:
94
- try:
95
- automation = importlib.import_module(f"{module_name}.actions.{operation_id}")
96
- break
97
- except ImportError as err:
98
- if f"No module named '{module_name}'" in str(err):
99
- raise
100
- else:
101
- logger.info("Plugin %s does not expose operation %s.", module_name, operation_id)
102
- except Exception as err:
103
- logger.critical("Error when importing %s - %s", operation_id, err)
104
-
105
- if not automation:
92
+ operation = None
93
+ try:
94
+ operation = importlib.import_module(f"howler.actions.{operation_id}")
95
+ except ImportError:
96
+ pass
97
+
98
+ if not operation:
99
+ for plugin in get_plugins():
100
+ if not plugin.modules.operations:
101
+ continue
102
+
103
+ operation = next(
104
+ (operation for operation in plugin.modules.operations if operation.OPERATION_ID == operation_id), None
105
+ )
106
+
107
+ if operation:
108
+ break
109
+
110
+ if not operation:
106
111
  return [
107
112
  {
108
113
  "query": query,
@@ -112,7 +117,7 @@ def execute(
112
117
  }
113
118
  ]
114
119
 
115
- missing_roles = set(automation.specification()["roles"]) - set(user["type"])
120
+ missing_roles = set(operation.specification()["roles"]) - set(user["type"])
116
121
  if missing_roles:
117
122
  return [
118
123
  {
@@ -126,7 +131,7 @@ def execute(
126
131
  }
127
132
  ]
128
133
 
129
- report = automation.execute(query=query, request_id=request_id, user=user, **kwargs)
134
+ report = operation.execute(query=query, request_id=request_id, user=user, **kwargs)
130
135
 
131
136
  return __sanitize_report(report)
132
137
 
@@ -139,28 +144,24 @@ def specifications() -> list[dict[str, Any]]:
139
144
  """
140
145
  specifications = []
141
146
 
142
- module_paths = {"howler": Path(__file__).parent}
143
-
144
- for plugin in config.core.plugins:
145
- plugin_module_path = PLUGIN_PATH / plugin / "actions"
146
- if plugin_module_path.exists():
147
- module_paths[plugin] = plugin_module_path
147
+ for module in (
148
+ _file
149
+ for _file in Path(__file__).parent.iterdir()
150
+ if _file.suffix == ".py" and _file.name not in ["__init__.py", "example_plugin.py"]
151
+ ):
152
+ try:
153
+ operation = importlib.import_module(f"howler.actions.{module.stem}")
148
154
 
149
- for module_name, module_path in module_paths.items():
150
- for module in (
151
- _file
152
- for _file in module_path.iterdir()
153
- if _file.suffix == ".py" and _file.name not in ["__init__.py", "example_plugin.py"]
154
- ):
155
- try:
156
- automation = importlib.import_module(f"{module_name}.actions.{module.stem}")
155
+ specifications.append(__sanitize_specification(operation.specification()))
157
156
 
158
- if module_name != "howler":
159
- logger.info("Enabling action %s from plugin %s", automation.specification()["id"], module_name)
157
+ except Exception: # pragma: no cover
158
+ logger.exception("Error when initializing %s", module)
160
159
 
161
- specifications.append(__sanitize_specification(automation.specification()))
160
+ for plugin in get_plugins():
161
+ if not plugin.modules.operations:
162
+ continue
162
163
 
163
- except Exception: # pragma: no cover
164
- logger.exception("Error when initializing %s", module)
164
+ for operation in plugin.modules.operations:
165
+ specifications.append(__sanitize_specification(operation.specification()))
165
166
 
166
167
  return specifications
@@ -1,4 +1,4 @@
1
- import importlib
1
+ import sys
2
2
  import time
3
3
  from typing import Callable, Optional
4
4
 
@@ -11,6 +11,7 @@ from howler.common.exceptions import AuthenticationException
11
11
  from howler.common.logging import get_logger
12
12
  from howler.common.swagger import generate_swagger_docs
13
13
  from howler.config import cache, config
14
+ from howler.plugins import get_plugins
14
15
  from howler.security import api_login
15
16
 
16
17
  SUB_API = "borealis"
@@ -20,19 +21,19 @@ borealis_api._doc = "Proxy enrichment requests to borealis"
20
21
  logger = get_logger(__file__)
21
22
 
22
23
 
23
- @cache.memoize(15 * 60)
24
+ def skip_cache(*args):
25
+ "Function to skip cache in testing mode"
26
+ return "pytest" in sys.modules
27
+
28
+
29
+ @cache.memoize(15 * 60, unless=skip_cache)
24
30
  def get_token(access_token: str) -> str:
25
31
  """Get a borealis token based on the current howler token"""
26
32
  get_borealis_token: Optional[Callable[[str], str]] = None
27
33
 
28
- for plugin in config.core.plugins:
29
- try:
30
- module = importlib.import_module(f"{plugin}.token.borealis")
31
-
32
- get_borealis_token = module.get_borealis_token
34
+ for plugin in get_plugins():
35
+ if get_borealis_token := plugin.modules.token_functions.get("borealis", None):
33
36
  break
34
- except ImportError:
35
- logger.info("Plugin %s does not modify the borealis access token.")
36
37
 
37
38
  if get_borealis_token:
38
39
  borealis_access_token = get_borealis_token(access_token)
@@ -4,20 +4,22 @@ from pathlib import Path
4
4
 
5
5
  from dotenv import load_dotenv
6
6
 
7
- from howler.odm.models.config import config
7
+ from howler.plugins import get_plugins
8
8
 
9
9
  load_dotenv()
10
10
 
11
11
  # We append the plugin directory for howler to the python part
12
12
  PLUGIN_PATH = Path(os.environ.get("HWL_PLUGIN_DIRECTORY", "/etc/howler/plugins"))
13
13
  sys.path.insert(0, str(PLUGIN_PATH))
14
+
15
+ from howler.odm.models.config import config
16
+
14
17
  if config.ui.debug and PLUGIN_PATH.exists():
15
18
  for _plugin in PLUGIN_PATH.iterdir():
16
19
  sys.path.append(
17
20
  str(Path(os.path.realpath(_plugin)) / f"../.venv/lib/python3.{sys.version_info.minor}/site-packages")
18
21
  )
19
22
 
20
- import importlib
21
23
  import logging
22
24
  from typing import Any, cast
23
25
 
@@ -142,13 +144,14 @@ if HWL_USE_REST_API or DEBUG:
142
144
  logger.debug("Enabled Borealis Integration")
143
145
  app.register_blueprint(borealis_api)
144
146
 
145
- for plugin in config.core.plugins:
146
- try:
147
- for route in cast(list[Blueprint], importlib.import_module(f"{plugin}.routes").ROUTES):
148
- logger.info("Enabling additional endpoint: %s", route.url_prefix)
149
- app.register_blueprint(route)
150
- except ImportError:
151
- logger.info("Plugin %s does not export additional endpoints.", plugin)
147
+ logger.info("Checking plugins for additional routes")
148
+ for plugin in get_plugins():
149
+ if not plugin.modules.routes:
150
+ continue
151
+
152
+ for route in cast(list[Blueprint], plugin.modules.routes):
153
+ logger.info("Enabling additional endpoint: %s", route.url_prefix)
154
+ app.register_blueprint(route)
152
155
 
153
156
 
154
157
  else:
@@ -25,4 +25,5 @@ def setup_jobs():
25
25
  except Exception as e:
26
26
  logger.critical("Error when initializing %s - %s", module, e)
27
27
 
28
- scheduler.start()
28
+ if scheduler.state != 1:
29
+ scheduler.start()
@@ -19,6 +19,7 @@ from datemath.helpers import DateMathException
19
19
  from howler import odm
20
20
  from howler.common.exceptions import HowlerRuntimeError, HowlerValueError, NonRecoverableError
21
21
  from howler.common.loader import APP_NAME
22
+ from howler.common.logging.format import HWL_DATE_FORMAT, HWL_LOG_FORMAT
22
23
  from howler.datastore.bulk import ElasticBulkPlan
23
24
  from howler.datastore.constants import BACK_MAPPING, TYPE_MAPPING
24
25
  from howler.datastore.exceptions import (
@@ -58,6 +59,12 @@ if typing.TYPE_CHECKING:
58
59
  TRANSPORT_TIMEOUT = int(environ.get("HWL_DATASTORE_TRANSPORT_TIMEOUT", "10"))
59
60
 
60
61
  logger = logging.getLogger("howler.api.datastore")
62
+ logger.setLevel(logging.INFO)
63
+ console = logging.StreamHandler()
64
+ console.setLevel(logging.INFO)
65
+ console.setFormatter(logging.Formatter(HWL_LOG_FORMAT, HWL_DATE_FORMAT))
66
+ logger.addHandler(console)
67
+
61
68
  ModelType = TypeVar("ModelType", bound=Model)
62
69
  write_block_settings = {"settings": {"index.blocks.write": True}}
63
70
  write_unblock_settings = {"settings": {"index.blocks.write": None}}
@@ -14,23 +14,39 @@ from howler.odm.models.overview import Overview
14
14
  from howler.odm.models.template import Template
15
15
  from howler.odm.models.user import User
16
16
  from howler.odm.models.view import View
17
+ from howler.plugins import get_plugins
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from howler.datastore.store import ESStore
20
21
 
22
+ INDEXES = [
23
+ ("hit", Hit),
24
+ ("template", Template),
25
+ ("overview", Overview),
26
+ ("analytic", Analytic),
27
+ ("action", Action),
28
+ ("user", User),
29
+ ("view", View),
30
+ ("dossier", Dossier),
31
+ ("user_avatar", None),
32
+ ]
33
+
21
34
 
22
35
  class HowlerDatastore(object):
23
36
  def __init__(self, datastore_object: "ESStore"):
24
37
  self.ds = datastore_object
25
- self.ds.register("hit", Hit)
26
- self.ds.register("template", Template)
27
- self.ds.register("overview", Overview)
28
- self.ds.register("analytic", Analytic)
29
- self.ds.register("action", Action)
30
- self.ds.register("user", User)
31
- self.ds.register("view", View)
32
- self.ds.register("dossier", Dossier)
33
- self.ds.register("user_avatar")
38
+
39
+ for plugin in get_plugins():
40
+ for _index, _odm in INDEXES:
41
+ if _odm is None:
42
+ continue
43
+
44
+ if modify_odm := plugin.modules.odm.modify_odm.get(_index):
45
+ logger.info("Modifying %s odm with function from plugin %s", _index, plugin.name)
46
+ modify_odm(_odm)
47
+
48
+ for _index, _odm in INDEXES:
49
+ self.ds.register(_index, _odm)
34
50
 
35
51
  def __enter__(self):
36
52
  return self
@@ -11,17 +11,15 @@ ADMIN_INDEX_MAP: dict[str, Callable[[], ESCollection]] = {}
11
11
 
12
12
  ADMIN_INDEX_ORDER_MAP: dict[str, str] = {}
13
13
 
14
- ds = datastore()
15
-
16
14
  INDEX_MAP: dict[str, Callable[[], ESCollection]] = {
17
- "action": lambda: ds.action,
18
- "analytic": lambda: ds.analytic,
19
- "dossier": lambda: ds.dossier,
20
- "hit": lambda: ds.hit,
21
- "overview": lambda: ds.overview,
22
- "template": lambda: ds.template,
23
- "user": lambda: ds.user,
24
- "view": lambda: ds.view,
15
+ "action": lambda: datastore().action,
16
+ "analytic": lambda: datastore().analytic,
17
+ "dossier": lambda: datastore().dossier,
18
+ "hit": lambda: datastore().hit,
19
+ "overview": lambda: datastore().overview,
20
+ "template": lambda: datastore().template,
21
+ "user": lambda: datastore().user,
22
+ "view": lambda: datastore().view,
25
23
  }
26
24
 
27
25
  INDEX_ORDER_MAP: dict[str, str] = {
@@ -1,4 +1,3 @@
1
- import importlib
2
1
  import json
3
2
  import random
4
3
  import sys
@@ -28,6 +27,7 @@ from howler.odm.randomizer import (
28
27
  random_department,
29
28
  random_model_obj,
30
29
  )
30
+ from howler.plugins import get_plugins
31
31
  from howler.security.utils import get_password_hash
32
32
  from howler.utils.uid import get_random_id
33
33
 
@@ -285,12 +285,10 @@ def generate_useful_hit(lookups: dict[str, dict[str, Any]], users: list[User], p
285
285
  log.previous_version = get_random_id()
286
286
 
287
287
  new_keys: list[str] = []
288
- for plugin in config.core.plugins:
289
- try:
290
- _new_keys, hit = importlib.import_module(f"{plugin}.odm.hit").generate_useful_hit(hit)
288
+ for plugin in get_plugins(): # pragma: no cover
289
+ if generate := plugin.modules.odm.generation.get("hit", None):
290
+ _new_keys, hit = generate(hit)
291
291
  new_keys += _new_keys
292
- except (ImportError, AttributeError):
293
- logger.exception("Plugin does not expose useful hit generation")
294
292
 
295
293
  if len(new_keys) > 0:
296
294
  logger.debug("%s new top-level fields configured")
@@ -1,10 +1,8 @@
1
1
  # mypy: ignore-errors
2
- import importlib
3
2
  from typing import Optional
4
3
 
5
4
  from howler import odm
6
5
  from howler.common.logging import get_logger
7
- from howler.config import config
8
6
  from howler.odm.models.assemblyline import AssemblyLine
9
7
  from howler.odm.models.aws import AWS
10
8
  from howler.odm.models.azure import Azure
@@ -351,16 +349,6 @@ class Hit(odm.Model):
351
349
  )
352
350
 
353
351
 
354
- for plugin in config.core.plugins:
355
- try:
356
- importlib.import_module(f"{plugin}.odm.hit").modify_odm(Hit)
357
- except (ImportError, AttributeError) as err:
358
- if f"No module named '{plugin}'" in str(err):
359
- raise
360
- else:
361
- logger.info("Plugin %s does not modify the ODM.", plugin)
362
-
363
-
364
352
  if __name__ == "__main__":
365
353
  from pprint import pprint
366
354
 
@@ -4,6 +4,8 @@ from pathlib import Path
4
4
 
5
5
  from dotenv import load_dotenv
6
6
 
7
+ from howler.plugins import get_plugins
8
+
7
9
  load_dotenv()
8
10
 
9
11
  # We append the plugin directory for howler to the python part
@@ -50,6 +52,20 @@ logger = get_logger(__file__)
50
52
  hit_helper = OdmHelper(Hit)
51
53
 
52
54
 
55
+ def run_modifications(odm: str, data: Any, log: bool = False):
56
+ "Running modifications"
57
+ new_keys: list[str] = []
58
+ for plugin in get_plugins(): # pragma: no cover
59
+ if generate := plugin.modules.odm.generation.get(odm, None):
60
+ _new_keys, data = generate(data)
61
+ new_keys += _new_keys
62
+
63
+ if len(new_keys) > 0 and log:
64
+ logger.debug("%s new top-level fields configured for %s", len(new_keys), odm)
65
+
66
+ return data
67
+
68
+
53
69
  def create_users(ds):
54
70
  """Create number of user accounts"""
55
71
  admin_pass = os.getenv("DEV_ADMIN_PASS", "admin") or "admin"
@@ -68,6 +84,9 @@ def create_users(ds):
68
84
  "owner": "admin",
69
85
  }
70
86
  )
87
+
88
+ admin_view = run_modifications("view", admin_view)
89
+
71
90
  user_data = User(
72
91
  {
73
92
  "apikeys": {
@@ -106,6 +125,9 @@ def create_users(ds):
106
125
  "favourite_views": [admin_view.view_id],
107
126
  }
108
127
  )
128
+
129
+ user_data = run_modifications("view", user_data, True)
130
+
109
131
  ds.user.save("admin", user_data)
110
132
  ds.user_avatar.save(
111
133
  "admin",
@@ -149,6 +171,10 @@ def create_users(ds):
149
171
  "favourite_views": [user_view.view_id],
150
172
  }
151
173
  )
174
+
175
+ user_view = run_modifications("view", user_view)
176
+ user_data = run_modifications("user", user_data)
177
+
152
178
  ds.user.save("user", user_data)
153
179
  ds.user_avatar.save(
154
180
  "user",
@@ -192,6 +218,10 @@ def create_users(ds):
192
218
  "favourite_views": [huey_view.view_id],
193
219
  }
194
220
  )
221
+
222
+ huey_view = run_modifications("view", huey_view)
223
+ huey_data = run_modifications("user", huey_data)
224
+
195
225
  ds.user.save("huey", huey_data)
196
226
  ds.user_avatar.save(
197
227
  "huey",
@@ -223,7 +253,9 @@ def create_users(ds):
223
253
  }
224
254
  )
225
255
 
226
- shawn_data.favourite_views.append(shawnh_view.view_id)
256
+ shawnh_view = run_modifications("view", shawnh_view)
257
+ shawn_data = run_modifications("user", shawn_data)
258
+
227
259
  ds.user.save("shawn-h", shawn_data)
228
260
  ds.view.save(shawnh_view.view_id, shawnh_view)
229
261
 
@@ -251,7 +283,9 @@ def create_users(ds):
251
283
  }
252
284
  )
253
285
 
254
- goose_data.favourite_views.append(goose_view.view_id)
286
+ goose_view = run_modifications("view", goose_view)
287
+ goose_data = run_modifications("user", goose_data)
288
+
255
289
  ds.user.save("goose", goose_data)
256
290
  ds.view.save(goose_view.view_id, goose_view)
257
291
 
@@ -271,7 +305,7 @@ def wipe_users(ds):
271
305
 
272
306
  def create_templates(ds: HowlerDatastore):
273
307
  """Create some random templates"""
274
- for _ in range(2):
308
+ for i in range(2):
275
309
  keys = sample(list(Hit.flat_fields().keys()), 5)
276
310
 
277
311
  for detection in ["Detection 1", "Detection 2"]:
@@ -284,6 +318,8 @@ def create_templates(ds: HowlerDatastore):
284
318
  }
285
319
  )
286
320
 
321
+ template = run_modifications("template", template, i == 0)
322
+
287
323
  ds.template.save(
288
324
  template.template_id,
289
325
  template,
@@ -298,6 +334,8 @@ def create_templates(ds: HowlerDatastore):
298
334
  }
299
335
  )
300
336
 
337
+ template = run_modifications("template", template)
338
+
301
339
  ds.template.save(
302
340
  template.template_id,
303
341
  template,
@@ -312,6 +350,8 @@ def create_templates(ds: HowlerDatastore):
312
350
  }
313
351
  )
314
352
 
353
+ template = run_modifications("template", template)
354
+
315
355
  ds.template.save(
316
356
  template.template_id,
317
357
  template,
@@ -341,7 +381,7 @@ def wipe_templates(ds):
341
381
 
342
382
  def create_overviews(ds: HowlerDatastore):
343
383
  """Create some random overviews"""
344
- for _ in range(2):
384
+ for i in range(2):
345
385
  keys = sample(list(Hit.flat_fields().keys()), 5)
346
386
 
347
387
  for detection in ["Detection 1", "Detection 2"]:
@@ -355,6 +395,8 @@ def create_overviews(ds: HowlerDatastore):
355
395
  }
356
396
  )
357
397
 
398
+ overview = run_modifications("overview", overview, i == 0)
399
+
358
400
  ds.overview.save(
359
401
  overview.overview_id,
360
402
  overview,
@@ -399,6 +441,8 @@ def create_overviews(ds: HowlerDatastore):
399
441
  }
400
442
  )
401
443
 
444
+ overview = run_modifications("overview", overview)
445
+
402
446
  ds.overview.save(
403
447
  overview.overview_id,
404
448
  overview,
@@ -423,6 +467,8 @@ def create_views(ds: HowlerDatastore):
423
467
  }
424
468
  )
425
469
 
470
+ view = run_modifications("view", view)
471
+
426
472
  ds.view.save(
427
473
  view.view_id,
428
474
  view,
@@ -437,6 +483,8 @@ def create_views(ds: HowlerDatastore):
437
483
  }
438
484
  )
439
485
 
486
+ view = run_modifications("view", view)
487
+
440
488
  ds.view.save(
441
489
  view.view_id,
442
490
  view,
@@ -455,6 +503,8 @@ def create_views(ds: HowlerDatastore):
455
503
  }
456
504
  )
457
505
 
506
+ view = run_modifications("view", view)
507
+
458
508
  ds.view.save(
459
509
  view.view_id,
460
510
  view,
@@ -598,6 +648,8 @@ def create_analytics(ds: HowlerDatastore, num_analytics: int = 10):
598
648
  )
599
649
  )
600
650
 
651
+ analytic = run_modifications("analytic", analytic)
652
+
601
653
  ds.analytic.save(analytic.analytic_id, analytic)
602
654
 
603
655
  fields = Hit.flat_fields()
@@ -618,6 +670,8 @@ def create_analytics(ds: HowlerDatastore, num_analytics: int = 10):
618
670
  set(random.sample(assessments, counts=([3] * len(assessments)), k=random.randint(1, len(assessments) * 3)))
619
671
  )
620
672
 
673
+ a = run_modifications("analytic", a)
674
+
621
675
  ds.analytic.save(a.analytic_id, a)
622
676
 
623
677
  for rule_type in ["lucene", "eql", "sigma"]:
@@ -670,6 +724,8 @@ def create_analytics(ds: HowlerDatastore, num_analytics: int = 10):
670
724
  "For better test data using sigma rules, execute howler/external/generate_sigma_rules.py."
671
725
  )
672
726
 
727
+ a = run_modifications("analytic", a)
728
+
673
729
  ds.analytic.save(a.analytic_id, a)
674
730
 
675
731
  ds.analytic.commit()
@@ -735,6 +791,8 @@ def create_actions(ds: HowlerDatastore, num_actions: int = 30):
735
791
  }
736
792
  )
737
793
 
794
+ action = run_modifications("action", action)
795
+
738
796
  ds.action.save(action.action_id, action)
739
797
 
740
798
  ds.action.commit()
@@ -0,0 +1,25 @@
1
+ import importlib
2
+ from typing import Optional
3
+
4
+ from howler.common.logging import get_logger
5
+ from howler.config import config as _config # Python gets BIG mad if we don't alias this
6
+ from howler.plugins.config import BasePluginConfig
7
+
8
+ logger = get_logger(__file__)
9
+
10
+ PLUGINS: dict[str, Optional[BasePluginConfig]] = {}
11
+
12
+
13
+ def get_plugins() -> list[BasePluginConfig]:
14
+ "Get a set of plugin configurations based on the howler settings."
15
+ for plugin in _config.core.plugins:
16
+ if plugin in PLUGINS:
17
+ continue
18
+
19
+ try:
20
+ PLUGINS[plugin] = importlib.import_module(f"{plugin}.config").config
21
+ except (ImportError, ModuleNotFoundError):
22
+ logger.exception("Exception when loading plugin %s", plugin)
23
+ PLUGINS[plugin] = None
24
+
25
+ return [plugin for plugin in PLUGINS.values() if plugin]