howler-api 2.13.0.dev329__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.
Potentially problematic release.
This version of howler-api might be problematic. Click here for more details.
- howler/__init__.py +0 -0
- howler/actions/__init__.py +167 -0
- howler/actions/add_label.py +111 -0
- howler/actions/add_to_bundle.py +159 -0
- howler/actions/change_field.py +76 -0
- howler/actions/demote.py +160 -0
- howler/actions/example_plugin.py +104 -0
- howler/actions/prioritization.py +93 -0
- howler/actions/promote.py +147 -0
- howler/actions/remove_from_bundle.py +133 -0
- howler/actions/remove_label.py +111 -0
- howler/actions/transition.py +200 -0
- howler/api/__init__.py +249 -0
- howler/api/base.py +88 -0
- howler/api/socket.py +114 -0
- howler/api/v1/__init__.py +97 -0
- howler/api/v1/action.py +372 -0
- howler/api/v1/analytic.py +748 -0
- howler/api/v1/auth.py +382 -0
- howler/api/v1/borealis.py +101 -0
- howler/api/v1/configs.py +55 -0
- howler/api/v1/dossier.py +222 -0
- howler/api/v1/help.py +28 -0
- howler/api/v1/hit.py +1181 -0
- howler/api/v1/notebook.py +82 -0
- howler/api/v1/overview.py +191 -0
- howler/api/v1/search.py +715 -0
- howler/api/v1/template.py +206 -0
- howler/api/v1/tool.py +183 -0
- howler/api/v1/user.py +414 -0
- howler/api/v1/utils/__init__.py +0 -0
- howler/api/v1/utils/etag.py +84 -0
- howler/api/v1/view.py +288 -0
- howler/app.py +235 -0
- howler/common/README.md +144 -0
- howler/common/__init__.py +0 -0
- howler/common/classification.py +979 -0
- howler/common/classification.yml +107 -0
- howler/common/exceptions.py +167 -0
- howler/common/hexdump.py +48 -0
- howler/common/iprange.py +171 -0
- howler/common/loader.py +154 -0
- howler/common/logging/__init__.py +241 -0
- howler/common/logging/audit.py +138 -0
- howler/common/logging/format.py +38 -0
- howler/common/net.py +79 -0
- howler/common/net_static.py +1494 -0
- howler/common/random_user.py +316 -0
- howler/common/swagger.py +117 -0
- howler/config.py +64 -0
- howler/cronjobs/__init__.py +29 -0
- howler/cronjobs/retention.py +61 -0
- howler/cronjobs/rules.py +274 -0
- howler/cronjobs/view_cleanup.py +88 -0
- howler/datastore/README.md +112 -0
- howler/datastore/__init__.py +0 -0
- howler/datastore/bulk.py +72 -0
- howler/datastore/collection.py +2327 -0
- howler/datastore/constants.py +117 -0
- howler/datastore/exceptions.py +41 -0
- howler/datastore/howler_store.py +105 -0
- howler/datastore/migrations/fix_process.py +41 -0
- howler/datastore/operations.py +130 -0
- howler/datastore/schemas.py +90 -0
- howler/datastore/store.py +231 -0
- howler/datastore/support/__init__.py +0 -0
- howler/datastore/support/build.py +214 -0
- howler/datastore/support/schemas.py +90 -0
- howler/datastore/types.py +22 -0
- howler/error.py +91 -0
- howler/external/__init__.py +0 -0
- howler/external/generate_mitre.py +96 -0
- howler/external/generate_sigma_rules.py +31 -0
- howler/external/generate_tlds.py +47 -0
- howler/external/reindex_data.py +46 -0
- howler/external/wipe_databases.py +58 -0
- howler/gunicorn_config.py +25 -0
- howler/healthz.py +47 -0
- howler/helper/__init__.py +0 -0
- howler/helper/azure.py +50 -0
- howler/helper/discover.py +59 -0
- howler/helper/hit.py +236 -0
- howler/helper/oauth.py +247 -0
- howler/helper/search.py +92 -0
- howler/helper/workflow.py +110 -0
- howler/helper/ws.py +378 -0
- howler/odm/README.md +102 -0
- howler/odm/__init__.py +1 -0
- howler/odm/base.py +1504 -0
- howler/odm/charter.txt +146 -0
- howler/odm/helper.py +416 -0
- howler/odm/howler_enum.py +25 -0
- howler/odm/models/__init__.py +0 -0
- howler/odm/models/action.py +33 -0
- howler/odm/models/analytic.py +90 -0
- howler/odm/models/assemblyline.py +48 -0
- howler/odm/models/aws.py +23 -0
- howler/odm/models/azure.py +16 -0
- howler/odm/models/cbs.py +44 -0
- howler/odm/models/config.py +558 -0
- howler/odm/models/dossier.py +33 -0
- howler/odm/models/ecs/__init__.py +0 -0
- howler/odm/models/ecs/agent.py +17 -0
- howler/odm/models/ecs/autonomous_system.py +16 -0
- howler/odm/models/ecs/client.py +149 -0
- howler/odm/models/ecs/cloud.py +141 -0
- howler/odm/models/ecs/code_signature.py +27 -0
- howler/odm/models/ecs/container.py +32 -0
- howler/odm/models/ecs/dns.py +62 -0
- howler/odm/models/ecs/egress.py +10 -0
- howler/odm/models/ecs/elf.py +74 -0
- howler/odm/models/ecs/email.py +122 -0
- howler/odm/models/ecs/error.py +14 -0
- howler/odm/models/ecs/event.py +140 -0
- howler/odm/models/ecs/faas.py +24 -0
- howler/odm/models/ecs/file.py +84 -0
- howler/odm/models/ecs/geo.py +30 -0
- howler/odm/models/ecs/group.py +18 -0
- howler/odm/models/ecs/hash.py +16 -0
- howler/odm/models/ecs/host.py +17 -0
- howler/odm/models/ecs/http.py +37 -0
- howler/odm/models/ecs/ingress.py +12 -0
- howler/odm/models/ecs/interface.py +21 -0
- howler/odm/models/ecs/network.py +30 -0
- howler/odm/models/ecs/observer.py +45 -0
- howler/odm/models/ecs/organization.py +12 -0
- howler/odm/models/ecs/os.py +21 -0
- howler/odm/models/ecs/pe.py +17 -0
- howler/odm/models/ecs/process.py +216 -0
- howler/odm/models/ecs/registry.py +26 -0
- howler/odm/models/ecs/related.py +45 -0
- howler/odm/models/ecs/rule.py +51 -0
- howler/odm/models/ecs/server.py +24 -0
- howler/odm/models/ecs/threat.py +247 -0
- howler/odm/models/ecs/tls.py +58 -0
- howler/odm/models/ecs/url.py +51 -0
- howler/odm/models/ecs/user.py +57 -0
- howler/odm/models/ecs/user_agent.py +20 -0
- howler/odm/models/ecs/vulnerability.py +41 -0
- howler/odm/models/gcp.py +16 -0
- howler/odm/models/hit.py +356 -0
- howler/odm/models/howler_data.py +328 -0
- howler/odm/models/lead.py +33 -0
- howler/odm/models/localized_label.py +13 -0
- howler/odm/models/overview.py +16 -0
- howler/odm/models/pivot.py +40 -0
- howler/odm/models/template.py +24 -0
- howler/odm/models/user.py +83 -0
- howler/odm/models/view.py +34 -0
- howler/odm/random_data.py +888 -0
- howler/odm/randomizer.py +606 -0
- howler/patched.py +5 -0
- howler/plugins/__init__.py +25 -0
- howler/plugins/config.py +123 -0
- howler/remote/__init__.py +0 -0
- howler/remote/datatypes/README.md +355 -0
- howler/remote/datatypes/__init__.py +98 -0
- howler/remote/datatypes/counters.py +63 -0
- howler/remote/datatypes/events.py +66 -0
- howler/remote/datatypes/hash.py +206 -0
- howler/remote/datatypes/lock.py +42 -0
- howler/remote/datatypes/queues/__init__.py +0 -0
- howler/remote/datatypes/queues/comms.py +59 -0
- howler/remote/datatypes/queues/multi.py +32 -0
- howler/remote/datatypes/queues/named.py +93 -0
- howler/remote/datatypes/queues/priority.py +215 -0
- howler/remote/datatypes/set.py +118 -0
- howler/remote/datatypes/user_quota_tracker.py +54 -0
- howler/security/__init__.py +253 -0
- howler/security/socket.py +108 -0
- howler/security/utils.py +185 -0
- howler/services/__init__.py +0 -0
- howler/services/action_service.py +111 -0
- howler/services/analytic_service.py +128 -0
- howler/services/auth_service.py +323 -0
- howler/services/config_service.py +128 -0
- howler/services/dossier_service.py +252 -0
- howler/services/event_service.py +93 -0
- howler/services/hit_service.py +893 -0
- howler/services/jwt_service.py +158 -0
- howler/services/lucene_service.py +286 -0
- howler/services/notebook_service.py +119 -0
- howler/services/overview_service.py +44 -0
- howler/services/template_service.py +45 -0
- howler/services/user_service.py +330 -0
- howler/utils/__init__.py +0 -0
- howler/utils/annotations.py +28 -0
- howler/utils/chunk.py +38 -0
- howler/utils/dict_utils.py +200 -0
- howler/utils/isotime.py +17 -0
- howler/utils/list_utils.py +11 -0
- howler/utils/lucene.py +77 -0
- howler/utils/path.py +27 -0
- howler/utils/socket_utils.py +61 -0
- howler/utils/str_utils.py +256 -0
- howler/utils/uid.py +47 -0
- howler_api-2.13.0.dev329.dist-info/METADATA +71 -0
- howler_api-2.13.0.dev329.dist-info/RECORD +200 -0
- howler_api-2.13.0.dev329.dist-info/WHEEL +4 -0
- howler_api-2.13.0.dev329.dist-info/entry_points.txt +8 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from howler.odm import (
|
|
2
|
+
IP,
|
|
3
|
+
MAC,
|
|
4
|
+
MD5,
|
|
5
|
+
SHA1,
|
|
6
|
+
SHA256,
|
|
7
|
+
URI,
|
|
8
|
+
UUID,
|
|
9
|
+
Any,
|
|
10
|
+
Boolean,
|
|
11
|
+
CaseInsensitiveKeyword,
|
|
12
|
+
Classification,
|
|
13
|
+
ClassificationString,
|
|
14
|
+
Date,
|
|
15
|
+
Domain,
|
|
16
|
+
Email,
|
|
17
|
+
Enum,
|
|
18
|
+
FlattenedObject,
|
|
19
|
+
Float,
|
|
20
|
+
HowlerHash,
|
|
21
|
+
Integer,
|
|
22
|
+
Json,
|
|
23
|
+
Keyword,
|
|
24
|
+
LowerKeyword,
|
|
25
|
+
PhoneNumber,
|
|
26
|
+
Platform,
|
|
27
|
+
Processor,
|
|
28
|
+
SSDeepHash,
|
|
29
|
+
Text,
|
|
30
|
+
UpperKeyword,
|
|
31
|
+
URIPath,
|
|
32
|
+
ValidatedKeyword,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Simple types can be resolved by a direct mapping
|
|
36
|
+
BASE_TYPE_MAPPING = {
|
|
37
|
+
Keyword: "keyword",
|
|
38
|
+
Boolean: "boolean",
|
|
39
|
+
Integer: "integer",
|
|
40
|
+
Float: "float",
|
|
41
|
+
Date: "date",
|
|
42
|
+
Text: "text",
|
|
43
|
+
Classification: "keyword",
|
|
44
|
+
ClassificationString: "keyword",
|
|
45
|
+
Enum: "keyword",
|
|
46
|
+
UUID: "keyword",
|
|
47
|
+
IP: "ip",
|
|
48
|
+
Domain: "keyword",
|
|
49
|
+
Email: "keyword",
|
|
50
|
+
URI: "keyword",
|
|
51
|
+
URIPath: "keyword",
|
|
52
|
+
MAC: "keyword",
|
|
53
|
+
PhoneNumber: "keyword",
|
|
54
|
+
SSDeepHash: "text",
|
|
55
|
+
SHA1: "keyword",
|
|
56
|
+
SHA256: "keyword",
|
|
57
|
+
HowlerHash: "keyword",
|
|
58
|
+
MD5: "keyword",
|
|
59
|
+
Platform: "keyword",
|
|
60
|
+
Processor: "keyword",
|
|
61
|
+
FlattenedObject: "nested",
|
|
62
|
+
Any: "keyword",
|
|
63
|
+
UpperKeyword: "keyword",
|
|
64
|
+
LowerKeyword: "keyword",
|
|
65
|
+
Json: "keyword",
|
|
66
|
+
ValidatedKeyword: "keyword",
|
|
67
|
+
CaseInsensitiveKeyword: "keyword",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
TYPE_MAPPING = {_class.__name__: mapping for _class, mapping in BASE_TYPE_MAPPING.items()}
|
|
71
|
+
|
|
72
|
+
ANALYZER_MAPPING = {
|
|
73
|
+
SSDeepHash: "text_fuzzy",
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
NORMALIZER_MAPPING = {
|
|
77
|
+
SHA1: "lowercase_normalizer",
|
|
78
|
+
SHA256: "lowercase_normalizer",
|
|
79
|
+
HowlerHash: "lowercase_normalizer",
|
|
80
|
+
MD5: "lowercase_normalizer",
|
|
81
|
+
CaseInsensitiveKeyword: "lowercase_normalizer",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# TODO: We might want to use custom analyzers for Classification and Enum and not create special backmapping cases
|
|
85
|
+
BACK_MAPPING = {
|
|
86
|
+
v: k
|
|
87
|
+
for k, v in BASE_TYPE_MAPPING.items()
|
|
88
|
+
if k
|
|
89
|
+
not in [
|
|
90
|
+
Enum,
|
|
91
|
+
Classification,
|
|
92
|
+
UUID,
|
|
93
|
+
IP,
|
|
94
|
+
Domain,
|
|
95
|
+
URI,
|
|
96
|
+
URIPath,
|
|
97
|
+
MAC,
|
|
98
|
+
PhoneNumber,
|
|
99
|
+
SSDeepHash,
|
|
100
|
+
Email,
|
|
101
|
+
SHA1,
|
|
102
|
+
SHA256,
|
|
103
|
+
HowlerHash,
|
|
104
|
+
MD5,
|
|
105
|
+
Platform,
|
|
106
|
+
Processor,
|
|
107
|
+
ClassificationString,
|
|
108
|
+
Any,
|
|
109
|
+
UpperKeyword,
|
|
110
|
+
LowerKeyword,
|
|
111
|
+
CaseInsensitiveKeyword,
|
|
112
|
+
Json,
|
|
113
|
+
ValidatedKeyword,
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
BACK_MAPPING.update({x: Keyword for x in set(ANALYZER_MAPPING.values())})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Iterable, Optional
|
|
2
|
+
|
|
3
|
+
from elasticsearch.helpers.errors import ScanError
|
|
4
|
+
|
|
5
|
+
from howler.common.exceptions import HowlerException, HowlerKeyError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SearchRetryException(HowlerException):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DataStoreException(HowlerException):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SearchException(HowlerException):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SearchDepthException(HowlerException):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ILMException(HowlerException):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class VersionConflictException(HowlerException):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MultiKeyError(HowlerKeyError):
|
|
33
|
+
def __init__(self, keys: Iterable[str], partial_output):
|
|
34
|
+
super().__init__(str(keys))
|
|
35
|
+
self.keys = set(keys)
|
|
36
|
+
self.partial_output = partial_output
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class HowlerScanError(HowlerException, ScanError):
|
|
40
|
+
def __init__(self, message: str = "Something went wrong", cause: Optional[Exception] = None) -> None:
|
|
41
|
+
HowlerException.__init__(self, message, cause if cause is not None else ScanError(message))
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from howler.common.exceptions import HowlerAttributeError
|
|
4
|
+
from howler.datastore.collection import ESCollection, logger
|
|
5
|
+
from howler.odm.models.action import Action
|
|
6
|
+
from howler.odm.models.analytic import Analytic
|
|
7
|
+
from howler.odm.models.dossier import Dossier
|
|
8
|
+
from howler.odm.models.hit import Hit
|
|
9
|
+
from howler.odm.models.overview import Overview
|
|
10
|
+
from howler.odm.models.template import Template
|
|
11
|
+
from howler.odm.models.user import User
|
|
12
|
+
from howler.odm.models.view import View
|
|
13
|
+
from howler.plugins import get_plugins
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from howler.datastore.store import ESStore
|
|
17
|
+
|
|
18
|
+
INDEXES = [
|
|
19
|
+
("hit", Hit),
|
|
20
|
+
("template", Template),
|
|
21
|
+
("overview", Overview),
|
|
22
|
+
("analytic", Analytic),
|
|
23
|
+
("action", Action),
|
|
24
|
+
("user", User),
|
|
25
|
+
("view", View),
|
|
26
|
+
("dossier", Dossier),
|
|
27
|
+
("user_avatar", None),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class HowlerDatastore(object):
|
|
32
|
+
def __init__(self, datastore_object: "ESStore"):
|
|
33
|
+
self.ds = datastore_object
|
|
34
|
+
|
|
35
|
+
for plugin in get_plugins():
|
|
36
|
+
for _index, _odm in INDEXES:
|
|
37
|
+
if _odm is None:
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
if modify_odm := plugin.modules.odm.modify_odm.get(_index):
|
|
41
|
+
logger.info("Modifying %s odm with function from plugin %s", _index, plugin.name)
|
|
42
|
+
modify_odm(_odm)
|
|
43
|
+
|
|
44
|
+
for _index, _odm in INDEXES:
|
|
45
|
+
self.ds.register(_index, _odm)
|
|
46
|
+
|
|
47
|
+
def __enter__(self):
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
51
|
+
self.ds.close()
|
|
52
|
+
|
|
53
|
+
def stop_model_validation(self):
|
|
54
|
+
self.ds.validate = False
|
|
55
|
+
|
|
56
|
+
def start_model_validation(self):
|
|
57
|
+
self.ds.validate = True
|
|
58
|
+
|
|
59
|
+
def enable_archive_access(self):
|
|
60
|
+
self.ds.archive_access = True
|
|
61
|
+
|
|
62
|
+
def disable_archive_access(self):
|
|
63
|
+
self.ds.archive_access = False
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def hit(self) -> ESCollection[Hit]:
|
|
67
|
+
return self.ds.hit
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def template(self) -> ESCollection[Template]:
|
|
71
|
+
return self.ds.template
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def overview(self) -> ESCollection[Overview]:
|
|
75
|
+
return self.ds.overview
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def view(self) -> ESCollection[View]:
|
|
79
|
+
return self.ds.view
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def analytic(self) -> ESCollection[Analytic]:
|
|
83
|
+
return self.ds.analytic
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def action(self) -> ESCollection[Action]:
|
|
87
|
+
return self.ds.action
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def user(self) -> ESCollection[User]:
|
|
91
|
+
return self.ds.user
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def dossier(self) -> ESCollection[Dossier]:
|
|
95
|
+
return self.ds.dossier
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def user_avatar(self) -> ESCollection:
|
|
99
|
+
return self.ds.user_avatar
|
|
100
|
+
|
|
101
|
+
def get_collection(self, collection_name: str) -> ESCollection:
|
|
102
|
+
if collection_name in self.ds.get_models():
|
|
103
|
+
return getattr(self, collection_name)
|
|
104
|
+
else:
|
|
105
|
+
raise HowlerAttributeError(f"Collection {collection_name} does not exist.")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from howler.common.loader import datastore
|
|
2
|
+
from howler.common.logging import get_logger
|
|
3
|
+
|
|
4
|
+
logger = get_logger(__file__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def migrate():
|
|
8
|
+
logger.info("Checking for migration preconditions")
|
|
9
|
+
|
|
10
|
+
collection = datastore().hit
|
|
11
|
+
|
|
12
|
+
result = collection.search("_exists_:process.parent.start", as_obj=False, track_total_hits=True, rows=0)
|
|
13
|
+
|
|
14
|
+
if result["total"] > 0:
|
|
15
|
+
logger.info("Preconditions met, continuing.")
|
|
16
|
+
|
|
17
|
+
db_size = collection.search("howler.id:*", track_total_hits=True, rows=0)["total"]
|
|
18
|
+
logger.info(f"Database size pre-migration: {db_size}")
|
|
19
|
+
else:
|
|
20
|
+
logger.info("Preconditions not met, stopping")
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
logger.info(f"We will delete {result['total']} hits. Continue?")
|
|
24
|
+
result = input("y/[n]")
|
|
25
|
+
|
|
26
|
+
if result.lower() != "y":
|
|
27
|
+
logger.warning("Did not receive an OK, stopping")
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
logger.info("Deleting...")
|
|
31
|
+
collection.delete_by_query("_exists_:process.parent.start")
|
|
32
|
+
collection.commit()
|
|
33
|
+
|
|
34
|
+
db_size_after = collection.search("howler.id:*", track_total_hits=True, rows=0)["total"]
|
|
35
|
+
logger.info(f"Database size post-migration: {db_size_after}")
|
|
36
|
+
|
|
37
|
+
logger.info("Migration complete")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
migrate()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from typing import Any, Iterator, Optional, Type
|
|
2
|
+
|
|
3
|
+
from howler.common.exceptions import HowlerTypeError, HowlerValueError
|
|
4
|
+
from howler.datastore.collection import ESCollection
|
|
5
|
+
from howler.odm.base import Model
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OdmHelper:
|
|
9
|
+
def __init__(self, obj: Optional[Type[Model]] = None) -> None:
|
|
10
|
+
self.model_name = obj.__name__ if obj else None
|
|
11
|
+
self.valid_fields = list(obj.flat_fields().keys()) if obj else None
|
|
12
|
+
self.fields = obj.flat_fields() if obj else {}
|
|
13
|
+
|
|
14
|
+
def list_add(
|
|
15
|
+
self,
|
|
16
|
+
key: str,
|
|
17
|
+
value: Any,
|
|
18
|
+
explanation: Optional[str] = None,
|
|
19
|
+
silent: bool = False,
|
|
20
|
+
if_missing: bool = False,
|
|
21
|
+
):
|
|
22
|
+
if self.valid_fields and not any(
|
|
23
|
+
field for field in self.valid_fields if field.startswith(key) and self.fields[field].multivalued
|
|
24
|
+
):
|
|
25
|
+
raise HowlerValueError(f"Key {key} not found in {self.model_name}")
|
|
26
|
+
|
|
27
|
+
return OdmUpdateOperation(
|
|
28
|
+
ESCollection.UPDATE_APPEND_IF_MISSING if if_missing else ESCollection.UPDATE_APPEND,
|
|
29
|
+
key,
|
|
30
|
+
value,
|
|
31
|
+
explanation,
|
|
32
|
+
silent,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def list_remove(
|
|
36
|
+
self,
|
|
37
|
+
key: str,
|
|
38
|
+
value: Any,
|
|
39
|
+
explanation: Optional[str] = None,
|
|
40
|
+
silent: bool = False,
|
|
41
|
+
):
|
|
42
|
+
if self.valid_fields and not any(
|
|
43
|
+
field for field in self.valid_fields if field.startswith(key) and self.fields[field].multivalued
|
|
44
|
+
):
|
|
45
|
+
raise HowlerValueError(f"Key {key} not found in {self.model_name}")
|
|
46
|
+
|
|
47
|
+
return OdmUpdateOperation(
|
|
48
|
+
ESCollection.UPDATE_REMOVE,
|
|
49
|
+
key,
|
|
50
|
+
value,
|
|
51
|
+
explanation,
|
|
52
|
+
silent,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def update(
|
|
56
|
+
self,
|
|
57
|
+
key: str,
|
|
58
|
+
value: Any,
|
|
59
|
+
explanation: Optional[str] = None,
|
|
60
|
+
silent: bool = False,
|
|
61
|
+
):
|
|
62
|
+
if self.valid_fields and key not in self.valid_fields:
|
|
63
|
+
raise HowlerValueError(f"Key {key} not found in {self.model_name}")
|
|
64
|
+
|
|
65
|
+
return OdmUpdateOperation(
|
|
66
|
+
ESCollection.UPDATE_SET,
|
|
67
|
+
key,
|
|
68
|
+
value,
|
|
69
|
+
explanation,
|
|
70
|
+
silent,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class OdmUpdateOperation:
|
|
75
|
+
"""Provide typing for ODM update operations.
|
|
76
|
+
|
|
77
|
+
Attributes:
|
|
78
|
+
operation: A string defining which operation to perform.
|
|
79
|
+
key: A string defining which value on the ODM to update.
|
|
80
|
+
value: The new value to set.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
__slots__ = ["operation", "key", "value", "explanation", "silent"]
|
|
84
|
+
|
|
85
|
+
operation: str
|
|
86
|
+
key: str
|
|
87
|
+
value: Any
|
|
88
|
+
explanation: Optional[str]
|
|
89
|
+
silent: bool
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
operation: str,
|
|
94
|
+
key: str,
|
|
95
|
+
value: Any,
|
|
96
|
+
explanation: Optional[str] = None,
|
|
97
|
+
silent: bool = False,
|
|
98
|
+
) -> None:
|
|
99
|
+
if operation not in ESCollection.UPDATE_OPERATIONS:
|
|
100
|
+
raise HowlerValueError(f"Operation {operation} not found in ESCollection.UPDATE_OPERATIONS")
|
|
101
|
+
|
|
102
|
+
self.operation = operation
|
|
103
|
+
|
|
104
|
+
if key is not None and not isinstance(key, str):
|
|
105
|
+
raise HowlerTypeError("Key must be of type str")
|
|
106
|
+
self.key = key
|
|
107
|
+
self.value = value
|
|
108
|
+
|
|
109
|
+
if explanation is not None and not isinstance(explanation, str):
|
|
110
|
+
raise HowlerTypeError("Explanation must be of type str")
|
|
111
|
+
self.explanation = explanation
|
|
112
|
+
self.silent = silent
|
|
113
|
+
|
|
114
|
+
def __iter__(self) -> Iterator[Any]:
|
|
115
|
+
"""Returns generator function for iterating over slots.
|
|
116
|
+
|
|
117
|
+
Enables object destructuring:
|
|
118
|
+
operation, key, value = odmOperation
|
|
119
|
+
"""
|
|
120
|
+
for slot in self.__slots__:
|
|
121
|
+
if slot in ["explanation", "silent"]:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
yield self.__getattribute__(slot)
|
|
125
|
+
|
|
126
|
+
def __repr__(self) -> str:
|
|
127
|
+
return (
|
|
128
|
+
f"OdmUpdateOperation: operation={self.operation}, key={self.key}, "
|
|
129
|
+
+ f"value={self.value}, explanation={self.explanation}"
|
|
130
|
+
)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
default_index = {
|
|
2
|
+
"settings": {
|
|
3
|
+
"analysis": {
|
|
4
|
+
"filter": {
|
|
5
|
+
"text_ws_dsplit": {
|
|
6
|
+
"type": "pattern_replace",
|
|
7
|
+
"pattern": r"(\.)",
|
|
8
|
+
"replacement": " ",
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"analyzer": {
|
|
12
|
+
"string_ci": {
|
|
13
|
+
"type": "custom",
|
|
14
|
+
"tokenizer": "keyword",
|
|
15
|
+
"filter": ["lowercase"],
|
|
16
|
+
},
|
|
17
|
+
"text_fuzzy": {
|
|
18
|
+
"type": "pattern",
|
|
19
|
+
"pattern": r"\s*:\s*",
|
|
20
|
+
"lowercase": False,
|
|
21
|
+
},
|
|
22
|
+
"text_whitespace": {"type": "whitespace"},
|
|
23
|
+
"text_ws_dsplit": {
|
|
24
|
+
"type": "custom",
|
|
25
|
+
"tokenizer": "whitespace",
|
|
26
|
+
"filters": ["text_ws_dsplit"],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
"normalizer": {
|
|
30
|
+
"lowercase_normalizer": {
|
|
31
|
+
"type": "custom",
|
|
32
|
+
"char_filter": [],
|
|
33
|
+
"filter": ["lowercase"],
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"mappings": {},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
default_mapping = {
|
|
42
|
+
"dynamic": True,
|
|
43
|
+
"properties": {
|
|
44
|
+
"__text__": {"type": "text"},
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
default_dynamic_strings = {
|
|
49
|
+
"strings_as_keywords": {
|
|
50
|
+
"match_mapping_type": "string",
|
|
51
|
+
"mapping": {"type": "keyword", "ignore_above": 8191},
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
default_dynamic_templates = [
|
|
56
|
+
{"int": {"path_match": "*_i", "mapping": {"type": "integer", "store": True}}},
|
|
57
|
+
{"ints": {"path_match": "*_is", "mapping": {"type": "integer", "store": True}}},
|
|
58
|
+
{"long": {"path_match": "*_l", "mapping": {"type": "long", "store": True}}},
|
|
59
|
+
{"longs": {"path_match": "*_ls", "mapping": {"type": "long", "store": True}}},
|
|
60
|
+
{"double": {"path_match": "*_d", "mapping": {"type": "float", "store": True}}},
|
|
61
|
+
{"doubles": {"path_match": "*_ds", "mapping": {"type": "float", "store": True}}},
|
|
62
|
+
{"float": {"path_match": "*_f", "mapping": {"type": "float", "store": True}}},
|
|
63
|
+
{"floats": {"path_match": "*_fs", "mapping": {"type": "float", "store": True}}},
|
|
64
|
+
{"string": {"path_match": "*_s", "mapping": {"type": "keyword", "store": True}}},
|
|
65
|
+
{"strings": {"path_match": "*_ss", "mapping": {"type": "keyword", "store": True}}},
|
|
66
|
+
{"text": {"path_match": "*_t", "mapping": {"type": "text", "store": True}}},
|
|
67
|
+
{"texts": {"path_match": "*_ts", "mapping": {"type": "text", "store": True}}},
|
|
68
|
+
{"boolean": {"path_match": "*_b", "mapping": {"type": "boolean", "store": True}}},
|
|
69
|
+
{"booleans": {"path_match": "*_bs", "mapping": {"type": "boolean", "store": True}}},
|
|
70
|
+
{
|
|
71
|
+
"date": {
|
|
72
|
+
"path_match": "*_dt",
|
|
73
|
+
"mapping": {
|
|
74
|
+
"type": "date",
|
|
75
|
+
"format": "date_optional_time||epoch_millis",
|
|
76
|
+
"store": True,
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"date": {
|
|
82
|
+
"path_match": "*_dts",
|
|
83
|
+
"mapping": {
|
|
84
|
+
"type": "date",
|
|
85
|
+
"format": "date_optional_time||epoch_millis",
|
|
86
|
+
"store": True,
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
]
|