howler-api 2.12.0.dev265__py3-none-any.whl → 2.12.0.dev272__py3-none-any.whl

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.
@@ -2,6 +2,7 @@ from typing import Any, Union
2
2
 
3
3
  from howler.common.loader import datastore
4
4
  from howler.common.logging import get_logger
5
+ from howler.datastore.exceptions import SearchException
5
6
  from howler.datastore.operations import OdmUpdateOperation
6
7
  from howler.odm.models.analytic import Analytic
7
8
  from howler.odm.models.hit import Hit
@@ -46,17 +47,24 @@ def get_matching_analytics(hits: Union[list[Hit], list[dict[str, Any]]]) -> list
46
47
  Returns:
47
48
  list[Analytic]: A list of Analytic objects that match the analytics referenced in the hits.
48
49
  """
50
+ if len(hits) < 1:
51
+ return []
52
+
49
53
  storage = datastore()
50
54
 
51
55
  analytic_names: set[str] = set()
52
56
  for hit in hits:
53
57
  analytic_names.add(f'"{sanitize_lucene_query(hit["howler"]["analytic"])}"')
54
58
 
55
- existing_analytics: list[Analytic] = storage.analytic.search(f'name:({" OR ".join(analytic_names)})', as_obj=True)[
56
- "items"
57
- ]
59
+ try:
60
+ existing_analytics: list[Analytic] = storage.analytic.search(
61
+ f'name:({" OR ".join(analytic_names)})', as_obj=True
62
+ )["items"]
58
63
 
59
- return existing_analytics
64
+ return existing_analytics
65
+ except SearchException:
66
+ logger.exception("Exception on analytic matching")
67
+ return []
60
68
 
61
69
 
62
70
  def save_from_hit(hit: Hit, user: User):
@@ -30,11 +30,11 @@ from howler.odm.models.ecs.event import Event
30
30
  from howler.odm.models.hit import Hit
31
31
  from howler.odm.models.howler_data import HitOperationType, HitStatus, HitStatusTransition, Log
32
32
  from howler.odm.models.user import User
33
- from howler.services import action_service, analytic_service, dossier_service
33
+ from howler.services import action_service, analytic_service, dossier_service, overview_service, template_service
34
34
  from howler.utils.dict_utils import extra_keys, flatten
35
35
  from howler.utils.uid import get_random_id
36
36
 
37
- log = get_logger(__file__)
37
+ logger = get_logger(__file__)
38
38
 
39
39
  odm_helper = OdmHelper(Hit)
40
40
 
@@ -535,7 +535,7 @@ def _update_hit(
535
535
  else:
536
536
  operation_type = HitOperationType.SET
537
537
 
538
- log.debug("%s - %s - %s -> %s", hit_id, operation.key, previous_value, operation.value)
538
+ logger.debug("%s - %s - %s -> %s", hit_id, operation.key, previous_value, operation.value)
539
539
  final_operations.append(operation)
540
540
 
541
541
  if not operation.silent:
@@ -640,7 +640,7 @@ def transition_hit(
640
640
 
641
641
  # Log all hits that will be transitioned
642
642
  all_hit_ids = [h["howler"]["id"] for h in ([primary_hit] + [ch for ch in child_hits if ch])]
643
- log.debug("Transitioning (%s)", ", ".join(all_hit_ids))
643
+ logger.debug("Transitioning (%s)", ", ".join(all_hit_ids))
644
644
 
645
645
  # Process each hit (primary + children) with the workflow transition
646
646
  for current_hit in [primary_hit] + [ch for ch in child_hits if ch]:
@@ -650,7 +650,7 @@ def transition_hit(
650
650
  # Skip hits that don't match the primary hit's status
651
651
  # This ensures consistent state transitions across bundles
652
652
  if current_hit_status != primary_hit_status:
653
- log.debug("Skipping %s (status mismatch)", current_hit_id)
653
+ logger.debug("Skipping %s (status mismatch)", current_hit_id)
654
654
  continue
655
655
 
656
656
  # Apply the workflow transition to get required updates
@@ -837,33 +837,37 @@ def augment_metadata(data: list[dict[str, Any]] | dict[str, Any], metadata: list
837
837
  This function modifies the input data in-place, adding metadata fields.
838
838
  Templates are filtered based on user permissions (global or owned by user).
839
839
  """
840
- hits = data if isinstance(data, list) else [data]
840
+ if isinstance(data, list):
841
+ hits = data
842
+ elif data is not None:
843
+ hits = [data]
844
+ else:
845
+ hits = []
846
+
847
+ if len(hits) < 1:
848
+ return
849
+
850
+ logger.debug("Augmenting %s hits with %s", len(hits), ",".join(metadata))
841
851
 
842
- analytics: set[str] = set()
843
- for hit in hits:
844
- analytics.add(f'"{hit["howler"]["analytic"]}"')
852
+ if "template" in metadata:
853
+ template_candidates = template_service.get_matching_templates(hits, user["uname"], as_odm=False)
845
854
 
846
- if len(analytics) > 0:
847
- if "template" in metadata:
848
- template_candidates = datastore().template.search(
849
- f"analytic:({' OR '.join(analytics)}) AND (type:global OR owner:{user['uname']})",
850
- as_obj=False,
851
- )["items"]
855
+ logger.debug("\tRetrieved %s matching templates", len(template_candidates))
852
856
 
853
- for hit in hits:
854
- hit["__template"] = __match_metadata(template_candidates, hit)
857
+ for hit in hits:
858
+ hit["__template"] = __match_metadata(cast(list[dict[str, Any]], template_candidates), hit)
859
+
860
+ if "overview" in metadata:
861
+ overview_candidates = overview_service.get_matching_overviews(hits, as_odm=False)
855
862
 
856
- if "overview" in metadata:
857
- overview_candidates = datastore().overview.search(
858
- f"analytic:({' OR '.join(analytics)})",
859
- as_obj=False,
860
- )["items"]
863
+ logger.debug("\tRetrieved %s matching overviews", len(overview_candidates))
861
864
 
862
- for hit in hits:
863
- hit["__overview"] = __match_metadata(overview_candidates, hit)
865
+ for hit in hits:
866
+ hit["__overview"] = __match_metadata(cast(list[dict[str, Any]], overview_candidates), hit)
864
867
 
865
868
  if "analytic" in metadata:
866
869
  matched_analytics = analytic_service.get_matching_analytics(hits)
870
+ logger.debug("\tRetrieved %s matching analytics", len(matched_analytics))
867
871
 
868
872
  for hit in hits:
869
873
  matched_analytic = next(
@@ -0,0 +1,44 @@
1
+ from typing import Any, Union
2
+
3
+ from howler.common.loader import datastore
4
+ from howler.common.logging import get_logger
5
+ from howler.datastore.exceptions import SearchException
6
+ from howler.odm.models.hit import Hit
7
+ from howler.odm.models.overview import Overview
8
+ from howler.utils.str_utils import sanitize_lucene_query
9
+
10
+ logger = get_logger(__file__)
11
+
12
+
13
+ def get_matching_overviews(
14
+ hits: Union[list[Hit], list[dict[str, Any]]], as_odm: bool = False
15
+ ) -> Union[list[dict[str, Any]], list[Overview]]:
16
+ """Generate a list of overviews matching a given list of analytic names from the provided hits.
17
+
18
+ Args:
19
+ hits (list[Hit] | list[dict[str, Any]]): A list of Hit objects or dictionaries containing analytic information.
20
+ as_odm (bool, optional): If True, return Overview objects; otherwise, return dictionaries. Defaults to False.
21
+
22
+ Returns:
23
+ list[dict[str, Any]] | list[Overview]: A list of matching overviews, either as dictionaries or Overview objects.
24
+ """
25
+ if len(hits) < 1:
26
+ return []
27
+
28
+ analytic_names: set[str] = set()
29
+ for hit in hits:
30
+ analytic_names.add(f'"{sanitize_lucene_query(hit["howler"]["analytic"])}"')
31
+
32
+ if len(analytic_names) < 1:
33
+ return []
34
+
35
+ try:
36
+ overview_candidates = datastore().overview.search(
37
+ f"analytic:({' OR '.join(analytic_names)})",
38
+ as_obj=as_odm,
39
+ )["items"]
40
+
41
+ return overview_candidates
42
+ except SearchException:
43
+ logger.exception("Exception on analytic matching")
44
+ return []
@@ -0,0 +1,45 @@
1
+ from typing import Any, Optional, Union
2
+
3
+ from howler.common.loader import datastore
4
+ from howler.common.logging import get_logger
5
+ from howler.datastore.exceptions import SearchException
6
+ from howler.odm.models.analytic import Analytic
7
+ from howler.odm.models.hit import Hit
8
+ from howler.utils.str_utils import sanitize_lucene_query
9
+
10
+ logger = get_logger(__file__)
11
+
12
+
13
+ def get_matching_templates(
14
+ hits: Union[list[Hit], list[dict[str, Any]]], uname: Optional[str] = None, as_odm: bool = False
15
+ ) -> Union[list[dict[str, Any]], list[Analytic]]:
16
+ """Generate a list of templates matching a given list of analytic names, and optionally a user.
17
+
18
+ Args:
19
+ hits (list[Hit] | list[dict[str, Any]]]: List of hits, each containing analytic information.
20
+ uname (Optional[str], optional): Username to filter templates by owner. Defaults to None.
21
+ as_odm (bool, optional): If True, return results as ODM objects. If False, return as dicts. Defaults to False.
22
+
23
+ Returns:
24
+ list[dict[str, Any]] | list[Analytic]: List of matching templates, either as dicts or Analytic ODM objects.
25
+ """
26
+ if len(hits) < 1:
27
+ return []
28
+
29
+ analytic_names: set[str] = set()
30
+ for hit in hits:
31
+ analytic_names.add(f'"{sanitize_lucene_query(hit["howler"]["analytic"])}"')
32
+
33
+ if len(analytic_names) < 1:
34
+ return []
35
+
36
+ try:
37
+ template_candidates = datastore().template.search(
38
+ f"analytic:({' OR '.join(analytic_names)}) AND (type:global OR owner:{uname or '*'})",
39
+ as_obj=as_odm,
40
+ )["items"]
41
+
42
+ return template_candidates
43
+ except SearchException:
44
+ logger.exception("Exception on analytic matching")
45
+ return []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: howler-api
3
- Version: 2.12.0.dev265
3
+ Version: 2.12.0.dev272
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -170,15 +170,17 @@ howler/security/socket.py,sha256=zEyWQh7IMINNpYMynV-PLy_-HQLpgouUvztAKrTHEfU,371
170
170
  howler/security/utils.py,sha256=MweKs9T--Z2w6hZNUfEAik4FsvXPNCskYF1vaQVt6_8,5309
171
171
  howler/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
172
172
  howler/services/action_service.py,sha256=AuNRvZs7sFGUveaoptD8ouzs39jdZJidmsp-fIocZo4,3977
173
- howler/services/analytic_service.py,sha256=eOV_R8RKoAP7P-5n-Iwd9c4vbKhiv2QdXYwyK3pFnXk,4045
173
+ howler/services/analytic_service.py,sha256=pOJRCDn6I523XSVLBo49fL70Mz5VDgjJ5CBYhemQeBk,4272
174
174
  howler/services/auth_service.py,sha256=TX51z-Fu4i3JvxIdU1JsJ3vzgGPVWNL1lNQJMxKpkZ8,11678
175
175
  howler/services/config_service.py,sha256=R-NvCplIo-42sLwhrWkhiq8FDlTIdrpKoMy4dDGobjU,4505
176
176
  howler/services/dossier_service.py,sha256=5jq7KZMgBNh165rN5pZD_oVuZP-oBsdACHDSV74ps-I,9839
177
177
  howler/services/event_service.py,sha256=4PG2iBXjh1V8QnXcbUZSiKJeHs6V9hRWT9SKrzQFIPY,2864
178
- howler/services/hit_service.py,sha256=eQY2sw2xjj6brEUgoHSIv8zkFcrGULNJVeMi2lVFkUo,32084
178
+ howler/services/hit_service.py,sha256=VNsEhi6Tcoqkjsw8G71FWBj7xcKwdvET9mITYAKmzsM,32278
179
179
  howler/services/jwt_service.py,sha256=O-M3k5wmjUF3GzD4FJC3o-hImKu1v8K5oSuD9zURcf0,5539
180
180
  howler/services/lucene_service.py,sha256=K_tS39hJCXUCfN_zgbIgbfcfM3gaQNheFXrwbV__z_0,12029
181
181
  howler/services/notebook_service.py,sha256=_MWllCnuVxt7lCcvWghXnaS926FbvBRE3DrBt--zO7U,3968
182
+ howler/services/overview_service.py,sha256=wDIm7DGjQ6Dvt4dNQ-57jbet8v6oGHxM857VezgHf7M,1539
183
+ howler/services/template_service.py,sha256=pwcHtYNoIkDuaXQ1bSXeDgxETP59PF1YCTwGzHsnXkA,1695
182
184
  howler/services/user_service.py,sha256=BWU9X75ofehRBozPrkOFlq5DIFbaPbmqC5-I-1Fv79g,12069
183
185
  howler/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
184
186
  howler/utils/annotations.py,sha256=GLuDbjbXp8esDji3qhQY_uQyOWVqfIdpF_zs2t0IaMI,878
@@ -191,7 +193,7 @@ howler/utils/path.py,sha256=DfOU4i4zSs4wchHoE8iE7aWVLkTxiC_JRGepF2hBYBk,690
191
193
  howler/utils/socket_utils.py,sha256=nz1SklC9xBHUSfHyTJjpq3mbozX1GDf01WzdGxfaUII,2212
192
194
  howler/utils/str_utils.py,sha256=HE8Hqh2HlOLaj16w0H9zKOyDJLp-f1LQ50y_WeGZaEk,8389
193
195
  howler/utils/uid.py,sha256=p9dsqyvZ-lpiAuzZWCPCeEM99kdk0Ly9czf04HNdSuw,1341
194
- howler_api-2.12.0.dev265.dist-info/METADATA,sha256=Tig_C-yIebVtpHvXdhk7l6j_TZk_tba4wtVtaGNSEUY,2764
195
- howler_api-2.12.0.dev265.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
196
- howler_api-2.12.0.dev265.dist-info/entry_points.txt,sha256=Lu9SBGvwe0wczJHmc-RudC24lmQk7tv3ZBXon9RIihg,259
197
- howler_api-2.12.0.dev265.dist-info/RECORD,,
196
+ howler_api-2.12.0.dev272.dist-info/METADATA,sha256=aLaGBLcwoyihNfErlFbBWqmDLG7-o2r6qDGqFBdJibs,2764
197
+ howler_api-2.12.0.dev272.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
198
+ howler_api-2.12.0.dev272.dist-info/entry_points.txt,sha256=Lu9SBGvwe0wczJHmc-RudC24lmQk7tv3ZBXon9RIihg,259
199
+ howler_api-2.12.0.dev272.dist-info/RECORD,,