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.
- howler/services/analytic_service.py +12 -4
- howler/services/hit_service.py +28 -24
- howler/services/overview_service.py +44 -0
- howler/services/template_service.py +45 -0
- {howler_api-2.12.0.dev265.dist-info → howler_api-2.12.0.dev272.dist-info}/METADATA +1 -1
- {howler_api-2.12.0.dev265.dist-info → howler_api-2.12.0.dev272.dist-info}/RECORD +8 -6
- {howler_api-2.12.0.dev265.dist-info → howler_api-2.12.0.dev272.dist-info}/WHEEL +0 -0
- {howler_api-2.12.0.dev265.dist-info → howler_api-2.12.0.dev272.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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):
|
howler/services/hit_service.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
843
|
-
|
|
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
|
-
|
|
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
|
-
|
|
854
|
-
|
|
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
|
-
|
|
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
|
-
|
|
863
|
-
|
|
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 []
|
|
@@ -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=
|
|
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=
|
|
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.
|
|
195
|
-
howler_api-2.12.0.
|
|
196
|
-
howler_api-2.12.0.
|
|
197
|
-
howler_api-2.12.0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|