cyvest 0.1.0__py3-none-any.whl → 5.1.3__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.
- cyvest/__init__.py +48 -38
- cyvest/cli.py +487 -0
- cyvest/compare.py +318 -0
- cyvest/cyvest.py +1431 -0
- cyvest/investigation.py +1682 -0
- cyvest/io_rich.py +1153 -0
- cyvest/io_schema.py +35 -0
- cyvest/io_serialization.py +465 -0
- cyvest/io_visualization.py +358 -0
- cyvest/keys.py +237 -0
- cyvest/level_score_rules.py +78 -0
- cyvest/levels.py +175 -0
- cyvest/model.py +595 -0
- cyvest/model_enums.py +69 -0
- cyvest/model_schema.py +164 -0
- cyvest/proxies.py +595 -0
- cyvest/score.py +473 -0
- cyvest/shared.py +508 -0
- cyvest/stats.py +291 -0
- cyvest/ulid.py +36 -0
- cyvest-5.1.3.dist-info/METADATA +632 -0
- cyvest-5.1.3.dist-info/RECORD +24 -0
- {cyvest-0.1.0.dist-info → cyvest-5.1.3.dist-info}/WHEEL +1 -2
- cyvest-5.1.3.dist-info/entry_points.txt +3 -0
- cyvest/builder.py +0 -182
- cyvest/check_tree.py +0 -117
- cyvest/models.py +0 -785
- cyvest/observable_registry.py +0 -69
- cyvest/report_render.py +0 -306
- cyvest/report_serialization.py +0 -237
- cyvest/visitors.py +0 -332
- cyvest-0.1.0.dist-info/METADATA +0 -110
- cyvest-0.1.0.dist-info/RECORD +0 -13
- cyvest-0.1.0.dist-info/licenses/LICENSE +0 -21
- cyvest-0.1.0.dist-info/top_level.txt +0 -1
cyvest/builder.py
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import threading
|
|
4
|
-
from collections.abc import Iterable, Sequence
|
|
5
|
-
from contextlib import AbstractContextManager
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from .models import ContainableSLM, Container, Level, ResultCheck, Scope
|
|
10
|
-
from .visitors import Report
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@dataclass
|
|
14
|
-
class _ContainerNode:
|
|
15
|
-
container: Container
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class _ContainerContext(AbstractContextManager["BuilderContainerProxy"]):
|
|
19
|
-
def __init__(self, builder: ReportBuilder, node: _ContainerNode) -> None:
|
|
20
|
-
self._builder = builder
|
|
21
|
-
self._node = node
|
|
22
|
-
|
|
23
|
-
def __enter__(self) -> BuilderContainerProxy:
|
|
24
|
-
self._builder._enter_container(self._node)
|
|
25
|
-
return BuilderContainerProxy(self._builder, self._node)
|
|
26
|
-
|
|
27
|
-
def __exit__(self, exc_type, exc, exc_tb) -> None:
|
|
28
|
-
self._builder._exit_container(self._node)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class BuilderContainerProxy:
|
|
32
|
-
def __init__(self, builder: ReportBuilder, node: _ContainerNode) -> None:
|
|
33
|
-
self._builder = builder
|
|
34
|
-
self._node = node
|
|
35
|
-
|
|
36
|
-
def add_check(
|
|
37
|
-
self,
|
|
38
|
-
path: str,
|
|
39
|
-
*,
|
|
40
|
-
identifier: str | None = None,
|
|
41
|
-
description: str | None = None,
|
|
42
|
-
level: Level = Level.INFO,
|
|
43
|
-
score: float = 0.0,
|
|
44
|
-
details: dict | None = None,
|
|
45
|
-
observable_chain: Sequence[dict[str, Any]] | None = None,
|
|
46
|
-
) -> ResultCheck:
|
|
47
|
-
return self._builder.add_check(
|
|
48
|
-
path,
|
|
49
|
-
scope=self._node.container.scope,
|
|
50
|
-
identifier=identifier,
|
|
51
|
-
description=description,
|
|
52
|
-
level=level,
|
|
53
|
-
score=score,
|
|
54
|
-
details=details,
|
|
55
|
-
observable_chain=observable_chain,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
def add_existing(self, node: ContainableSLM) -> ContainableSLM:
|
|
59
|
-
return self._builder.add_existing(node)
|
|
60
|
-
|
|
61
|
-
def container(
|
|
62
|
-
self,
|
|
63
|
-
path: str,
|
|
64
|
-
*,
|
|
65
|
-
scope: Scope | None = None,
|
|
66
|
-
description: str | None = None,
|
|
67
|
-
identifier: str | None = None,
|
|
68
|
-
level: Level = Level.INFO,
|
|
69
|
-
details: dict | None = None,
|
|
70
|
-
) -> _ContainerContext:
|
|
71
|
-
container_scope = scope or self._node.container.scope
|
|
72
|
-
return self._builder.container(
|
|
73
|
-
path,
|
|
74
|
-
scope=container_scope,
|
|
75
|
-
description=description,
|
|
76
|
-
identifier=identifier,
|
|
77
|
-
level=level,
|
|
78
|
-
details=details,
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class ReportBuilder:
|
|
83
|
-
"""Helper for constructing reports with nested containers and checks."""
|
|
84
|
-
|
|
85
|
-
def __init__(self, report: Report | None = None, *, graph: bool = False) -> None:
|
|
86
|
-
self.report: Report = report or Report(graph=graph)
|
|
87
|
-
self._stack: list[_ContainerNode] = []
|
|
88
|
-
self._roots: list[ContainableSLM] = []
|
|
89
|
-
self._lock = threading.RLock()
|
|
90
|
-
|
|
91
|
-
def container(
|
|
92
|
-
self,
|
|
93
|
-
path: str,
|
|
94
|
-
*,
|
|
95
|
-
scope: Scope,
|
|
96
|
-
description: str | None = None,
|
|
97
|
-
identifier: str | None = None,
|
|
98
|
-
level: Level = Level.INFO,
|
|
99
|
-
details: dict | None = None,
|
|
100
|
-
) -> _ContainerContext:
|
|
101
|
-
container = Container(
|
|
102
|
-
path,
|
|
103
|
-
scope=scope,
|
|
104
|
-
identifier=identifier,
|
|
105
|
-
description=description,
|
|
106
|
-
score=0.0,
|
|
107
|
-
level=level,
|
|
108
|
-
details=details,
|
|
109
|
-
)
|
|
110
|
-
node = _ContainerNode(container)
|
|
111
|
-
return _ContainerContext(self, node)
|
|
112
|
-
|
|
113
|
-
def add_check(
|
|
114
|
-
self,
|
|
115
|
-
path: str,
|
|
116
|
-
*,
|
|
117
|
-
scope: Scope | None = None,
|
|
118
|
-
identifier: str | None = None,
|
|
119
|
-
description: str | None = None,
|
|
120
|
-
level: Level = Level.INFO,
|
|
121
|
-
score: float = 0.0,
|
|
122
|
-
details: dict | None = None,
|
|
123
|
-
observable_chain: Sequence[dict[str, Any]] | None = None,
|
|
124
|
-
) -> ResultCheck:
|
|
125
|
-
with self._lock:
|
|
126
|
-
check_scope = scope
|
|
127
|
-
if check_scope is None:
|
|
128
|
-
if not self._stack:
|
|
129
|
-
raise ValueError("Scope must be provided when adding a check outside a container context")
|
|
130
|
-
check_scope = self._stack[-1].container.scope
|
|
131
|
-
result_check = ResultCheck.create(
|
|
132
|
-
path,
|
|
133
|
-
scope=check_scope,
|
|
134
|
-
identifier=identifier,
|
|
135
|
-
description=description,
|
|
136
|
-
level=level,
|
|
137
|
-
score=score,
|
|
138
|
-
details=details,
|
|
139
|
-
)
|
|
140
|
-
if self._stack:
|
|
141
|
-
self._stack[-1].container.contain(result_check)
|
|
142
|
-
else:
|
|
143
|
-
self._roots.append(result_check)
|
|
144
|
-
if observable_chain:
|
|
145
|
-
result_check.add_observable_chain(observable_chain)
|
|
146
|
-
return result_check
|
|
147
|
-
|
|
148
|
-
def build(self) -> Report:
|
|
149
|
-
with self._lock:
|
|
150
|
-
roots = list(self._roots)
|
|
151
|
-
self._roots.clear()
|
|
152
|
-
for root in roots:
|
|
153
|
-
root.accept(self.report)
|
|
154
|
-
return self.report
|
|
155
|
-
|
|
156
|
-
def add_existing(self, node: ContainableSLM) -> ContainableSLM:
|
|
157
|
-
with self._lock:
|
|
158
|
-
if self._stack:
|
|
159
|
-
self._stack[-1].container.contain(node)
|
|
160
|
-
else:
|
|
161
|
-
self._roots.append(node)
|
|
162
|
-
return node
|
|
163
|
-
|
|
164
|
-
def extend_existing(self, nodes: Iterable[ContainableSLM]) -> None:
|
|
165
|
-
for node in nodes:
|
|
166
|
-
self.add_existing(node)
|
|
167
|
-
|
|
168
|
-
# Internal helpers -------------------------------------------------
|
|
169
|
-
def _enter_container(self, node: _ContainerNode) -> None:
|
|
170
|
-
with self._lock:
|
|
171
|
-
if self._stack:
|
|
172
|
-
parent = self._stack[-1].container
|
|
173
|
-
parent.contain(node.container)
|
|
174
|
-
else:
|
|
175
|
-
self._roots.append(node.container)
|
|
176
|
-
self._stack.append(node)
|
|
177
|
-
|
|
178
|
-
def _exit_container(self, node: _ContainerNode) -> None:
|
|
179
|
-
with self._lock:
|
|
180
|
-
if not self._stack or self._stack[-1] is not node:
|
|
181
|
-
raise RuntimeError("Container context stack is inconsistent")
|
|
182
|
-
self._stack.pop()
|
cyvest/check_tree.py
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections import OrderedDict, defaultdict
|
|
4
|
-
from collections.abc import Iterable
|
|
5
|
-
|
|
6
|
-
from .models import ContainableSLM, Container, Level, ResultCheck
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class CheckTree:
|
|
10
|
-
"""Canonical structure that stores containers/result checks and keeps their scores in sync."""
|
|
11
|
-
|
|
12
|
-
def __init__(self) -> None:
|
|
13
|
-
self._nodes: dict[str, ContainableSLM] = {}
|
|
14
|
-
self._roots: dict[str, OrderedDict[str, ContainableSLM]] = defaultdict(OrderedDict)
|
|
15
|
-
|
|
16
|
-
def integrate_container(self, container: Container) -> Container:
|
|
17
|
-
return self._integrate_container(container, seen=set())
|
|
18
|
-
|
|
19
|
-
def integrate_result_check(self, result_check: ResultCheck) -> ResultCheck:
|
|
20
|
-
return self._integrate_result_check(result_check, seen=set())
|
|
21
|
-
|
|
22
|
-
def _integrate_container(self, container: Container, seen: set[int]) -> Container:
|
|
23
|
-
if id(container) in seen:
|
|
24
|
-
existing = self._nodes.get(container.full_key)
|
|
25
|
-
if isinstance(existing, Container):
|
|
26
|
-
return existing
|
|
27
|
-
seen.add(id(container))
|
|
28
|
-
parent = container.parent
|
|
29
|
-
if parent is not None:
|
|
30
|
-
parent = self._integrate_container(parent, seen)
|
|
31
|
-
container.set_parent(parent)
|
|
32
|
-
key = container.full_key
|
|
33
|
-
existing = self._nodes.get(key)
|
|
34
|
-
if isinstance(existing, Container):
|
|
35
|
-
existing.merge_from(container)
|
|
36
|
-
target = existing
|
|
37
|
-
else:
|
|
38
|
-
target = container
|
|
39
|
-
self._nodes[key] = target
|
|
40
|
-
if target.parent is not None:
|
|
41
|
-
target.parent.attach_child(target)
|
|
42
|
-
else:
|
|
43
|
-
root_scope = target.scope.name if target.scope else "GLOBAL"
|
|
44
|
-
self._roots[root_scope][key] = target
|
|
45
|
-
# integrate children
|
|
46
|
-
for child in list(container.children):
|
|
47
|
-
if isinstance(child, Container):
|
|
48
|
-
child.set_parent(target)
|
|
49
|
-
self._integrate_container(child, seen)
|
|
50
|
-
else:
|
|
51
|
-
child.set_parent(target)
|
|
52
|
-
self._integrate_result_check(child, seen)
|
|
53
|
-
target.recompute()
|
|
54
|
-
self._recompute_upwards(target.parent)
|
|
55
|
-
return target
|
|
56
|
-
|
|
57
|
-
def _integrate_result_check(self, result_check: ResultCheck, seen: set[int]) -> ResultCheck:
|
|
58
|
-
if id(result_check) in seen:
|
|
59
|
-
existing = self._nodes.get(result_check.full_key)
|
|
60
|
-
if isinstance(existing, ResultCheck):
|
|
61
|
-
return existing
|
|
62
|
-
seen.add(id(result_check))
|
|
63
|
-
parent = result_check.parent
|
|
64
|
-
if parent is not None:
|
|
65
|
-
parent = self._integrate_container(parent, seen)
|
|
66
|
-
result_check.set_parent(parent)
|
|
67
|
-
key = result_check.full_key
|
|
68
|
-
existing = self._nodes.get(key)
|
|
69
|
-
if isinstance(existing, ResultCheck):
|
|
70
|
-
existing.merge_from(result_check)
|
|
71
|
-
target = existing
|
|
72
|
-
else:
|
|
73
|
-
target = result_check
|
|
74
|
-
self._nodes[key] = target
|
|
75
|
-
if target.parent is not None:
|
|
76
|
-
target.parent.attach_child(target)
|
|
77
|
-
else:
|
|
78
|
-
scope_name = target.scope.name if target.scope else "GLOBAL"
|
|
79
|
-
self._roots[scope_name][key] = target
|
|
80
|
-
self._recompute_upwards(target.parent)
|
|
81
|
-
return target
|
|
82
|
-
|
|
83
|
-
def _recompute_upwards(self, node: Container | None) -> None:
|
|
84
|
-
current = node
|
|
85
|
-
while current is not None:
|
|
86
|
-
current.recompute()
|
|
87
|
-
current = current.parent
|
|
88
|
-
|
|
89
|
-
def get(self, full_key: str) -> ContainableSLM | None:
|
|
90
|
-
return self._nodes.get(full_key)
|
|
91
|
-
|
|
92
|
-
def total_score(self) -> float:
|
|
93
|
-
return sum(node.score for node in self._nodes.values() if isinstance(node, ResultCheck))
|
|
94
|
-
|
|
95
|
-
def highest_level(self) -> Level:
|
|
96
|
-
highest = Level.INFO
|
|
97
|
-
for node in self._nodes.values():
|
|
98
|
-
if node.level > highest:
|
|
99
|
-
highest = node.level
|
|
100
|
-
return highest
|
|
101
|
-
|
|
102
|
-
def result_checks(self) -> Iterable[ResultCheck]:
|
|
103
|
-
for node in self._nodes.values():
|
|
104
|
-
if isinstance(node, ResultCheck):
|
|
105
|
-
yield node
|
|
106
|
-
|
|
107
|
-
def containers(self) -> Iterable[Container]:
|
|
108
|
-
for node in self._nodes.values():
|
|
109
|
-
if isinstance(node, Container):
|
|
110
|
-
yield node
|
|
111
|
-
|
|
112
|
-
def roots_for_scope(self, scope_name: str) -> list[ContainableSLM]:
|
|
113
|
-
return list(self._roots.get(scope_name, {}).values())
|
|
114
|
-
|
|
115
|
-
def iter_roots(self) -> Iterable[tuple[str, list[ContainableSLM]]]:
|
|
116
|
-
for scope_name, nodes in self._roots.items():
|
|
117
|
-
yield scope_name, list(nodes.values())
|