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.
- howler_client/__init__.py +46 -0
- howler_client/client.py +32 -0
- howler_client/common/__init__.py +0 -0
- howler_client/common/dict_utils.py +138 -0
- howler_client/common/utils.py +113 -0
- howler_client/connection.py +204 -0
- howler_client/logger.py +14 -0
- howler_client/module/__init__.py +0 -0
- howler_client/module/bundle.py +132 -0
- howler_client/module/comment.py +59 -0
- howler_client/module/help.py +23 -0
- howler_client/module/hit.py +299 -0
- howler_client/module/search/__init__.py +84 -0
- howler_client/module/search/chunk.py +38 -0
- howler_client/module/search/facet.py +41 -0
- howler_client/module/search/fields.py +19 -0
- howler_client/module/search/grouped.py +67 -0
- howler_client/module/search/histogram.py +63 -0
- howler_client/module/search/stats.py +39 -0
- howler_client/module/search/stream.py +81 -0
- howler_client/module/user.py +97 -0
- howler_client/utils/__init__.py +0 -0
- howler_client/utils/json_encoders.py +36 -0
- howler_client-2.4.0.dev37.dist-info/LICENSE +23 -0
- howler_client-2.4.0.dev37.dist-info/METADATA +61 -0
- howler_client-2.4.0.dev37.dist-info/RECORD +28 -0
- howler_client-2.4.0.dev37.dist-info/WHEEL +4 -0
- howler_client-2.4.0.dev37.dist-info/entry_points.txt +5 -0
|
@@ -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,,
|