howler-client 2.4.0.dev37__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.
@@ -0,0 +1,63 @@
1
+ from howler_client.common.utils import SEARCHABLE, ClientError, api_path
2
+
3
+
4
+ class Histogram(object):
5
+ "Module for getting histogram data of a given index"
6
+
7
+ def __init__(self, connection):
8
+ self._connection = connection
9
+
10
+ def _do_histogram(self, index, field, **kwargs):
11
+ if index not in SEARCHABLE:
12
+ raise ClientError("Index %s is not searchable" % index, 400)
13
+
14
+ filters = kwargs.pop("filters", None)
15
+ if filters is not None:
16
+ if isinstance(filters, str):
17
+ filters = [filters]
18
+
19
+ filters = [("filters", fq) for fq in filters]
20
+
21
+ kwargs = {k: v for k, v in kwargs.items() if v is not None and k != "filters"}
22
+ if filters is not None:
23
+ kwargs["params_tuples"] = filters
24
+ path = api_path("search", "histogram", index, field, **kwargs)
25
+ return self._connection.get(path)
26
+
27
+ def hit(
28
+ self,
29
+ field,
30
+ query=None,
31
+ mincount=None,
32
+ filters=None,
33
+ start=None,
34
+ end=None,
35
+ gap=None,
36
+ ):
37
+ """Create an histogram of data from a given field in the hit index.
38
+
39
+ The frequency of the data is split between a given gap size.
40
+
41
+ Required:
42
+ field : field to create the histograms with (only work on date or number fields)
43
+
44
+ Optional:
45
+ query : Initial query to filter the data (default: 'id:*')
46
+ filters : Additional lucene queries used to filter the data (list of strings)
47
+ mincount : Minimum amount of hits for the value to be returned
48
+ start : Beginning of the histogram range (Default: now-1d or 0)
49
+ end : End of the histogram range (Default: now or 1000)
50
+ gap : Interval in between each histogram points (Default: 1h or 100)
51
+
52
+ Returns all results.
53
+ """
54
+ return self._do_histogram(
55
+ "hit",
56
+ field,
57
+ query=query,
58
+ mincount=mincount,
59
+ filters=filters,
60
+ start=start,
61
+ end=end,
62
+ gap=gap,
63
+ )
@@ -0,0 +1,39 @@
1
+ from howler_client.common.utils import SEARCHABLE, ClientError, api_path
2
+
3
+
4
+ class Stats(object):
5
+ "Module for getting statistics on various indexes"
6
+
7
+ def __init__(self, connection):
8
+ self._connection = connection
9
+
10
+ def _do_stats(self, index, field, **kwargs):
11
+ if index not in SEARCHABLE:
12
+ raise ClientError("Index %s is not searchable" % index, 400)
13
+
14
+ filters = kwargs.pop("filters", None)
15
+ if filters is not None:
16
+ if isinstance(filters, str):
17
+ filters = [filters]
18
+
19
+ filters = [("filters", fq) for fq in filters]
20
+
21
+ kwargs = {k: v for k, v in kwargs.items() if v is not None and k != "filters"}
22
+ if filters is not None:
23
+ kwargs["params_tuples"] = filters
24
+ path = api_path("search", "stats", index, field, **kwargs)
25
+ return self._connection.get(path)
26
+
27
+ def hit(self, field, query=None, filters=None):
28
+ """Generates statistics about the distribution of an integer field of the hit index.
29
+
30
+ Required:
31
+ field : field to create the stats on (only work on number fields)
32
+
33
+ Optional:
34
+ query : Initial query to filter the data (default: 'id:*')
35
+ filters : Additional lucene queries used to filter the data (list of strings)
36
+
37
+ Returns statistics about the field.
38
+ """
39
+ return self._do_stats("hit", field, query=query, filters=filters)
@@ -0,0 +1,81 @@
1
+ import threading
2
+ import time
3
+ from typing import Any
4
+
5
+ from howler_client.common.utils import INVALID_STREAM_SEARCH_PARAMS, SEARCHABLE, ClientError
6
+
7
+
8
+ class Stream(object):
9
+ "Module for streaming search results"
10
+
11
+ def __init__(self, connection, do_search):
12
+ self._connection = connection
13
+ self._do_search = do_search
14
+ self._page_size = 100
15
+ self._max_yield_cache = 100
16
+
17
+ def _auto_fill(self, items, lock, index, query, **kwargs):
18
+ done = False
19
+ while not done:
20
+ skip = False
21
+ with lock:
22
+ if len(items) > self._max_yield_cache:
23
+ skip = True
24
+
25
+ if skip:
26
+ time.sleep(0.01)
27
+ continue
28
+
29
+ j = self._do_search(index, query, **kwargs)
30
+
31
+ # Replace cursorMark.
32
+ kwargs["deep_paging_id"] = j.get("next_deep_paging_id", "*")
33
+
34
+ with lock:
35
+ items.extend(j["items"])
36
+
37
+ done = bool(self._page_size - len(j["items"]))
38
+
39
+ def _do_stream(self, index, query, **kwargs):
40
+ if index not in SEARCHABLE:
41
+ raise ClientError("Index %s is not searchable" % index, 400)
42
+
43
+ for arg in list(kwargs.keys()):
44
+ if arg in INVALID_STREAM_SEARCH_PARAMS:
45
+ raise ClientError(
46
+ "The following parameters cannot be used with stream search: %s",
47
+ ", ".join(INVALID_STREAM_SEARCH_PARAMS),
48
+ )
49
+
50
+ kwargs.update({"rows": str(self._page_size), "deep_paging_id": "*"})
51
+
52
+ yield_done = False
53
+ items: list[Any] = []
54
+ lock = threading.Lock()
55
+ sf_t = threading.Thread(target=self._auto_fill, args=[items, lock, index, query], kwargs=kwargs)
56
+ sf_t.setDaemon(True)
57
+ sf_t.start()
58
+ while not yield_done:
59
+ try:
60
+ with lock:
61
+ item = items.pop(0)
62
+
63
+ yield item
64
+ except IndexError:
65
+ if not sf_t.is_alive() and len(items) == 0:
66
+ yield_done = True
67
+ time.sleep(0.01)
68
+
69
+ def hit(self, query, filters=None, fl=None):
70
+ """Get all hits from a lucene query.
71
+
72
+ Required:
73
+ query : lucene query (string)
74
+
75
+ Optional:
76
+ filters : Additional lucene queries used to filter the data (list of strings)
77
+ fl : List of fields to return (comma separated string of fields)
78
+
79
+ Returns a generator that transparently and efficiently pages through results.
80
+ """
81
+ return self._do_stream("hit", query, filters=filters, fl=fl)
@@ -0,0 +1,97 @@
1
+ import sys
2
+ from typing import Any, Literal
3
+
4
+ from howler_client.common.utils import api_path
5
+
6
+ if sys.version_info >= (3, 11):
7
+ from typing import Self
8
+ else:
9
+ from typing_extensions import Self
10
+
11
+
12
+ class User(object):
13
+ """Methods related to Howler users"""
14
+
15
+ def __init__(self, connection):
16
+ self._connection = connection
17
+
18
+ def __call__(self: Self, username: str) -> dict[str, Any]:
19
+ """Return the profile for the given username
20
+
21
+ Args:
22
+ username (str): User key. (string).
23
+
24
+ Returns:
25
+ dict[str, Any]: The user account corresponding to the given username
26
+ """
27
+ return self._connection.get(api_path("user", username))
28
+
29
+ def add(self: Self, username: str, user_data: dict[str, Any]) -> dict[Literal["success"], bool]:
30
+ """Add a user to the system
31
+
32
+ Args:
33
+ username (str): Name of the user to add to the system
34
+ user_data (dict[str, Any]): Profile data of the user to add
35
+
36
+ Returns:
37
+ dict[Literal["success"], bool]: Whether creating the user succeeded
38
+ """
39
+ return self._connection.post(api_path("user", username), json=user_data)
40
+
41
+ def delete(self: Self, username: str) -> dict[Literal["success"], bool]:
42
+ """Remove the account specified by the username.
43
+
44
+ Args:
45
+ str: Name of the user to remove from the system
46
+
47
+ Returns:
48
+ dict[Literal["success"], bool]: Whether the delete succeeded
49
+ """
50
+ return self._connection.delete(api_path("user", username))
51
+
52
+ def list(
53
+ self: Self,
54
+ query: str = "*:*",
55
+ rows: int = 10,
56
+ offset: int = 0,
57
+ sort: str = "uname asc",
58
+ **kwargs,
59
+ ) -> dict[str, Any]:
60
+ """List users of the system
61
+
62
+ Args:
63
+ query (_type_, optional): Filter to apply to the user list. Defaults to "*:*".
64
+ rows (int, optional): Total number of users returned. Defaults to 10.
65
+ offset (int, optional): Offset in the user index. Defaults to 0.
66
+ sort (str, optional): Sort order. Defaults to "uname asc".
67
+
68
+ Returns:
69
+ dict[str, Any]: Result of listing the users
70
+ """
71
+ return self._connection.get(
72
+ api_path(
73
+ "search",
74
+ "user",
75
+ query=query,
76
+ rows=rows,
77
+ offset=offset,
78
+ sort=sort,
79
+ **kwargs,
80
+ )
81
+ )
82
+
83
+ def update(self: Self, username: str, user_data: dict[str, Any]) -> dict[Literal["success"], bool]:
84
+ """Update a user profile in the system.
85
+
86
+ Args:
87
+ username (str): Name of the user to update in the system
88
+ user_data (dict[str, Any]): Profile data of the user to update
89
+
90
+ Returns:
91
+ dict[Literal["success"], bool]: Whether the update succeeded
92
+ """
93
+ return self._connection.put(api_path("user", username), json=user_data)
94
+
95
+ def whoami(self: Self) -> dict[str, Any]:
96
+ "Return the currently logged in user"
97
+ return self._connection.get(api_path("user", "whoami"))
File without changes
@@ -0,0 +1,36 @@
1
+ import json
2
+ from datetime import datetime, timezone
3
+
4
+ from howler_client.logger import get_logger
5
+
6
+ logger = get_logger("json.encoding")
7
+
8
+
9
+ class DatetimeEncoder(json.JSONEncoder):
10
+ "JSON Encoder that supports encoding datetime objects into iso format"
11
+
12
+ def default(self, o):
13
+ "Default encoder function"
14
+ if isinstance(o, datetime):
15
+ logger.debug("Encoding %s to ISO Format", repr(o))
16
+
17
+ return o.astimezone(timezone.utc).isoformat()
18
+ else:
19
+ return super().default(o)
20
+
21
+
22
+ class BytesDatetimeEncoder(DatetimeEncoder):
23
+ "JSON Encoder that supports encoding datetime objects into iso format, and decoding bytes objects"
24
+
25
+ def default(self, o):
26
+ "Default encoder function"
27
+ if isinstance(o, bytes):
28
+ logger.debug("Decoding bytes object")
29
+
30
+ return o.decode("utf-8")
31
+ elif isinstance(o, bytearray):
32
+ logger.debug("Decoding bytearray object")
33
+
34
+ return bytes(o).decode("utf-8", errors="replace")
35
+ else:
36
+ return super().default(o)
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Crown Copyright, Government of Canada (Canadian Centre for Cyber Security / Communications Security Establishment)
4
+
5
+ Copyright title to all 3rd party software distributed with Howler is held by the respective copyright holders as noted in those files. Users are asked to read the 3rd Party Licenses referenced with those assets.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.3
2
+ Name: howler-client
3
+ Version: 2.4.0.dev37
4
+ Summary: The Howler client library facilitates issuing requests to Howler
5
+ License: MIT
6
+ Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
7
+ Author: Canadian Centre for Cyber Security
8
+ Author-email: howler@cyber.gc.ca
9
+ Requires-Python: >=3.9,<4.0
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Dist: coverage[toml] (>=7.6.1,<8.0.0)
21
+ Requires-Dist: diff-cover (>=9.2.0,<10.0.0)
22
+ Requires-Dist: pycryptodome (>=3.20.0,<4.0.0)
23
+ Requires-Dist: python-baseconv (>=1.2.2,<2.0.0)
24
+ Requires-Dist: requests[security] (>=2.32.0,<3.0.0)
25
+ Project-URL: Documentation, https://cybercentrecanada.github.io/howler-docs/developer/client/
26
+ Project-URL: Homepage, https://cybercentrecanada.github.io/howler-docs/
27
+ Project-URL: Repository, https://github.com/CybercentreCanada/howler-client
28
+ Description-Content-Type: text/markdown
29
+
30
+ # Howler Client Library
31
+
32
+ The Howler client library facilitates issuing requests to Howler.
33
+
34
+ ## Requirements
35
+
36
+ 1. Python 3.9 and up
37
+
38
+ ## Running the Tests
39
+
40
+ 1. Prepare the howler-api:
41
+ 1. Start dependencies
42
+ 1. `howler-api > python howler/app.py`
43
+ 1. `howler-api > python howler/odm/random_data.py`
44
+ 2. Run python integration tests:
45
+ 1. `python -m venv env`
46
+ 1. `. env/bin/activate`
47
+ 1. `pip install -r requirements.txt`
48
+ 1. `pip install -r test/requirements.txt`
49
+ 1. `pip install -e .`
50
+ 1. `pytest -s -v test`
51
+
52
+ ## \_sqlite3 error
53
+
54
+ You'll likely have to reinstall python3.9 while libsqlite3-dev is installed
55
+
56
+ 1. libsqlite3-dev
57
+ `sudo apt install libsqlite3-dev`
58
+ 2. Python3.9 with loadable-sqlite-extensions enabled
59
+ - `./configure --enable-loadable-sqlite-extensions --enable-optimizations`
60
+ - `make altinstall`
61
+
@@ -0,0 +1,28 @@
1
+ howler_client/__init__.py,sha256=Hmtp3jTL-1_dhfZevss1Q9GwUHd6ruihgXfCUVIuxq4,962
2
+ howler_client/client.py,sha256=ipbmQxHrVDvtvlLNVudUmHokErK5jHhkQmVZ8RYhIeo,1057
3
+ howler_client/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ howler_client/common/dict_utils.py,sha256=qmqbVBE1QX8aF99SjjR7ZaY4uIuv5UPYEUxKKALUMwM,4069
5
+ howler_client/common/utils.py,sha256=qf6CYb2ywEt0xPQzvvZBa2y459t2cB_x2ZxPOonmNWg,3150
6
+ howler_client/connection.py,sha256=M0Ybcl3_otCzm0KCrGVbbbGGlOBcfgMkQ6ioz9OIRB8,7685
7
+ howler_client/logger.py,sha256=2zhNynfVVeI6cADuLttphwe4NQyKI5i3H7zcFiWmQFg,440
8
+ howler_client/module/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ howler_client/module/bundle.py,sha256=EIhX8t1mGgcINGvewOY-i9wOs_SZvQTFLl4vWZnTT6Q,4754
10
+ howler_client/module/comment.py,sha256=BDImhDfA__8-PzWoFy0DyJ5RzeGa4YA8LErYtEnggUI,1820
11
+ howler_client/module/help.py,sha256=SCqObTmGOSGX-v9H0D6-PEpbgWKQnMXcJxUE6c883a8,600
12
+ howler_client/module/hit.py,sha256=ed0JlwVCsqssMFSqCdjQWhTMKN-Cz-1C6DyF314e5x8,10741
13
+ howler_client/module/search/__init__.py,sha256=dOeFBJNdOe9FVsZJA3W7YjE-pLImR_31YOkwSIbZIxI,2978
14
+ howler_client/module/search/chunk.py,sha256=NoVDKzZkO8O92xXk0s0Pny2g7It9BX0PqWbknmnRSFg,895
15
+ howler_client/module/search/facet.py,sha256=F74PVGrMVoHIpr5GcLE6-i-uDa98WZM-_AjWVhm6Scs,1561
16
+ howler_client/module/search/fields.py,sha256=b5_HOZ_AbM_S--WXcj52HBu5PtfwNp845CxG9jV12IA,568
17
+ howler_client/module/search/grouped.py,sha256=GSEFaCg8rkyOpV9rMf_XZRd8Erk7R-6ewel-G4SVABg,2248
18
+ howler_client/module/search/histogram.py,sha256=3uEVK7bTDE0K8vnV5MbQh1dK7DSWZIP9pt1ChyF9Ixw,2066
19
+ howler_client/module/search/stats.py,sha256=PsAUtewemPF5hDbaEbnjwUjyfaqTMh7QUoFXC6reDqY,1423
20
+ howler_client/module/search/stream.py,sha256=Tgr_tfsrsHUPo2uibiADL-IKupVw7WE5s3xVoYskDVc,2599
21
+ howler_client/module/user.py,sha256=Dcosi_zrZzI0edOpH1ZzKoTa_Q4S0NZM2aWmMHmbDMc,3125
22
+ howler_client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ howler_client/utils/json_encoders.py,sha256=wMbNZx4X-K0pKKusrSCem0bpe37e7J7JvTaAQfA5avI,1066
24
+ howler_client-2.4.0.dev37.dist-info/LICENSE,sha256=1Sbl0uFuetnPtKrTbzHYw9cu2_83HlqVM3mpC_5SdT4,1384
25
+ howler_client-2.4.0.dev37.dist-info/METADATA,sha256=XK6Ze_Z7bGTV4EkdyPgAcMpKkrL5ACtP763_XLhRId8,2177
26
+ howler_client-2.4.0.dev37.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
27
+ howler_client-2.4.0.dev37.dist-info/entry_points.txt,sha256=O-HDLAR3572yUDhj7s33f5DgGLedxfaGiUEi0BWaKXs,146
28
+ howler_client-2.4.0.dev37.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.1.3
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ coverage_report=build_scripts.coverage_reports:main
3
+ test=build_scripts.run_tests:main
4
+ type_check=build_scripts.type_check:main
5
+