forked from eclipse-basyx/basyx-python-sdk
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathstate_manager.py
More file actions
221 lines (180 loc) · 8.75 KB
/
Copy pathstate_manager.py
File metadata and controls
221 lines (180 loc) · 8.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# Copyright (c) 2026 the Eclipse BaSyx Authors
#
# This program and the accompanying materials are made available under the terms of the MIT License, available in
# the LICENSE file of this project.
#
# SPDX-License-Identifier: MIT
"""
This module defines a :class:`~.ComplianceToolStateManager` to store :class:`LogRecords <logging.LogRecord>`
for single steps in a compliance check of the compliance tool
"""
import enum
import logging
import pprint
from typing import Dict, List
from basyx.aas.examples.data._helper import DataChecker
@enum.unique
class Status(enum.IntEnum):
"""
Possible Status States:
:cvar SUCCESS:
:cvar SUCCESS_WITH_WARNINGS:
:cvar FAILED:
:cvar NOT_EXECUTED:
"""
SUCCESS = 0
SUCCESS_WITH_WARNINGS = 1 # never used
FAILED = 2
NOT_EXECUTED = 3
class Step:
"""
A step represents a single test stage in a test protocol of a :class:`~.ComplianceToolStateManager`
:ivar name: Name of the step
:ivar status: Status of the step from type Status
:ivar log_list: List of :class:`LogRecords <logging.LogRecord>` which belong to this step
"""
def __init__(self, name: str, status: Status, log_list: List[logging.LogRecord]):
self.name = name
self.status = status
self.log_list = log_list
class ComplianceToolStateManager(logging.Handler):
"""
A ComplianceToolStateManager is used to create a report of a compliance check, divided into single
:class:`Steps <.Step>` with status and log. The manager provides methods to:
- Add a new step
- Set the step status
- Set the step status from log
- Add logs to a step by hand
- Add logs to a step from a data checker
- Be used as a :class:`logging.Handler` which adds logs to the current step
Example of a ComplianceTest for a schema check:
* Step 1: `Open file`
* Step 2: `Read file and check if it is conform to the json syntax`
* Step 3: `Validate file against official json schema`
:ivar steps: List of :class:`Steps <.Step>`
"""
def __init__(self):
"""
steps: List of steps. Each step consist of a step name, a step status and LogRecords belong to to this step.
The step name have to be unique in the list.
"""
super().__init__()
self.steps: List[Step] = []
self.setLevel(logging.INFO)
@property
def status(self) -> Status:
"""
Determine the status of all steps in following way:
1. If there is at least one step with status = NOT_EXECUTED than NOT_EXECUTED will be returned
2. If there is at least one step with status = FAILED than FAILED will be returned
3. Else status SUCCESS will be returned
:return: status of the manager
"""
status: Status = Status.SUCCESS
for step in self.steps:
if status < step.status:
status = step.status
return status
def add_step(self, name: str) -> None:
"""
Adding a new :class:`~.Step` to the manager with a given name, status = NOT_EXECUTED and an empty list of
records
:param name: Name of the :class:`~.Step`
"""
self.steps.append(Step(name, Status.NOT_EXECUTED, []))
def add_log_record(self, record: logging.LogRecord) -> None:
"""
Adds a :class:`~logging.LogRecord` to the log list of the actual :class:`~.Step`
:param record: :class:`~logging.LogRecord` which should be added to the current :class:`~.Step`
"""
self.steps[-1].log_list.append(record)
def set_step_status(self, status: Status) -> None:
"""
Sets the status of the current step
:param status: status which should be set
"""
self.steps[-1].status = status
def set_step_status_from_log(self) -> None:
"""
Sets the status of the current step based on the log entries
"""
self.steps[-1].status = Status.FAILED if len(self.steps[-1].log_list) > 0 else Status.SUCCESS
def add_log_records_from_data_checker(self, data_checker: DataChecker) -> None:
"""
Sets the status of the current :class:`~.Step` and convert the checks to
:class:`LogRecords <logging.LogRecord>` and adds these to the current :class:`~.Step`
:class:`~.Step`: FAILED if the :class:`~basyx.aas.examples.data._helper.DataChecker` consist at least one failed
check otherwise SUCCESS
:param data_checker: :class:`~basyx.aas.examples.data._helper.DataChecker` which checks should be added to the
current :class:`~.Step`
"""
self.steps[-1].status = Status.SUCCESS if not any(True for _ in data_checker.failed_checks) else Status.FAILED
for check in data_checker.checks:
self.steps[-1].log_list.append(logging.LogRecord(name=__name__,
level=logging.INFO if check.result else logging.ERROR,
pathname='',
lineno=0,
msg="{} ({})".format(
check.expectation,
", ".join("{}={}".format(
k, pprint.pformat(
v, depth=2, width=2 ** 14, compact=True))
for k, v in check.data.items())),
args=(),
exc_info=None))
def get_error_logs_from_step(self, index: int) -> List[logging.LogRecord]:
"""
Returns a list of :class:`LogRecords <logging.LogRecord>` of a step where the log level
is :data:`~logging.ERROR` or :data:`~logging.WARNING`
:param index: Step index in the Step list of the manager
:return: List of LogRecords with log level :data:`~logging.ERROR` or :data:`~logging.WARNING`
"""
return [x for x in self.steps[index].log_list if x.levelno >= logging.WARNING]
def format_step(self, index: int, verbose_level: int = 0) -> str:
"""
Creates a string for the step containing the status, the step name and
the :class:`LogRecords <logging.LogRecord>` if wanted
:param index: Step index in the step list of the manager
:param verbose_level: Decision which kind of LogRecords should be in the string
- 0: No LogRecords
- 1: Only LogRecords with log level >= :data:`~logging.WARNING`
- 2: All LogRecords
:return: formatted string of the step
"""
STEP_STATUS: Dict[Status, str] = {
Status.SUCCESS: '{:14}'.format('SUCCESS:'),
Status.SUCCESS_WITH_WARNINGS: '{:14}'.format('WARNINGS:'),
Status.FAILED: '{:14}'.format('FAILED:'),
Status.NOT_EXECUTED: '{:14}'.format('NOT_EXECUTED:'),
}
if self.steps[index].status not in STEP_STATUS:
raise NotImplementedError
string = STEP_STATUS[self.steps[index].status]
string += self.steps[index].name
if verbose_level > 0:
for log in self.steps[index].log_list:
if log.levelno < logging.WARNING:
if verbose_level == 1:
continue
string += '\n'+' - {:6} {}'.format(log.levelname + ':', log.getMessage())
return string
def format_state_manager(self, verbose_level: int = 0) -> str:
"""
Creates a report with all executed steps: Containing the status, the step name and
the :class:`LogRecords <logging.LogRecord>` if wanted
:param verbose_level: Decision which kind of LogRecords should be in the string
- 0: No LogRecords
- 1: Only LogRecords with log level >= :data:`~logging.WARNING`
- 2: All LogRecords
:return: formatted report
"""
string = 'Compliance Test executed:\n'
string += "\n".join(self.format_step(x, verbose_level) for x in range(len(self.steps)))
return string
def emit(self, record: logging.LogRecord):
"""
:class:`~logging.Handler` function for adding :class:`LogRecords <logging.LogRecord>` from a ``logger``
to the current :class:`~.Step`
:param record: :class:`~logging.LogRecord` which should be added
"""
self.steps[-1].log_list.append(record)