cloudbeat-common 0.0.1__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.
- cloudbeat.py +8 -0
- cloudbeat_common/__init__.py +0 -0
- cloudbeat_common/json_util.py +82 -0
- cloudbeat_common/models.py +212 -0
- cloudbeat_common/reporter.py +151 -0
- cloudbeat_common-0.0.1.dist-info/METADATA +38 -0
- cloudbeat_common-0.0.1.dist-info/RECORD +9 -0
- cloudbeat_common-0.0.1.dist-info/WHEEL +5 -0
- cloudbeat_common-0.0.1.dist-info/top_level.txt +2 -0
cloudbeat.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from cloudbeat_common.models import TestResult, SuiteResult, CaseResult, StepResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def to_json(result: TestResult):
|
|
7
|
+
return json.dumps(result, cls=CbResultEncoder, indent=4)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _test_result_to_json(tr: TestResult):
|
|
11
|
+
if not isinstance(tr, TestResult):
|
|
12
|
+
return None
|
|
13
|
+
return {
|
|
14
|
+
"runId": tr.run_id,
|
|
15
|
+
"instanceId": tr.instance_id,
|
|
16
|
+
"startTime": tr.start_time,
|
|
17
|
+
"endTime": tr.end_time,
|
|
18
|
+
"duration": tr.duration,
|
|
19
|
+
"capabilities": tr.capabilities,
|
|
20
|
+
"options": tr.options,
|
|
21
|
+
"metaData": tr.meta_data,
|
|
22
|
+
"environmentVariables": tr.environment_variables,
|
|
23
|
+
"testAttributes": tr.test_attributes,
|
|
24
|
+
"suites": list(map(lambda s: _suite_result_to_json(s), tr.suites))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _suite_result_to_json(r: SuiteResult):
|
|
29
|
+
if not isinstance(r, SuiteResult):
|
|
30
|
+
return None
|
|
31
|
+
return {
|
|
32
|
+
"id": r.id,
|
|
33
|
+
"name": r.name,
|
|
34
|
+
"fqn": r.fqn,
|
|
35
|
+
"startTime": r.start_time,
|
|
36
|
+
"endTime": r.end_time,
|
|
37
|
+
"duration": r.duration,
|
|
38
|
+
"status": r.status,
|
|
39
|
+
"cases": list(map(lambda c: _case_result_to_json(c), r.cases))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _case_result_to_json(r: CaseResult):
|
|
44
|
+
if not isinstance(r, CaseResult):
|
|
45
|
+
return None
|
|
46
|
+
return {
|
|
47
|
+
"id": r.id,
|
|
48
|
+
"name": r.name,
|
|
49
|
+
"display_name": r.display_name,
|
|
50
|
+
"description": r.description,
|
|
51
|
+
"fqn": r.fqn,
|
|
52
|
+
"startTime": r.start_time,
|
|
53
|
+
"endTime": r.end_time,
|
|
54
|
+
"duration": r.duration,
|
|
55
|
+
"status": r.status,
|
|
56
|
+
"context": r.context,
|
|
57
|
+
"arguments": r.arguments,
|
|
58
|
+
"steps": list(map(lambda s: _step_result_to_json(s), r.steps)),
|
|
59
|
+
"hooks": list(map(lambda s: _step_result_to_json(s), r.hooks))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _step_result_to_json(r: StepResult):
|
|
64
|
+
if not isinstance(r, StepResult):
|
|
65
|
+
return None
|
|
66
|
+
return {
|
|
67
|
+
"id": r.id,
|
|
68
|
+
"name": r.name,
|
|
69
|
+
"fqn": r.fqn,
|
|
70
|
+
"startTime": r.start_time,
|
|
71
|
+
"endTime": r.end_time,
|
|
72
|
+
"duration": r.duration,
|
|
73
|
+
"status": r.status,
|
|
74
|
+
"steps": list(map(lambda s: _step_result_to_json(s), r.steps))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CbResultEncoder(json.JSONEncoder):
|
|
79
|
+
def default(self, obj):
|
|
80
|
+
if isinstance(obj, TestResult):
|
|
81
|
+
return _test_result_to_json(obj)
|
|
82
|
+
return super().default(obj)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from attr import attrs, attrib
|
|
5
|
+
from attr import Factory
|
|
6
|
+
from collections import OrderedDict, defaultdict
|
|
7
|
+
import uuid
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestStatus:
|
|
11
|
+
FAILED = 'failed'
|
|
12
|
+
BROKEN = 'broken'
|
|
13
|
+
PASSED = 'passed'
|
|
14
|
+
SKIPPED = 'skipped'
|
|
15
|
+
UNKNOWN = 'unknown'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StepType:
|
|
19
|
+
GENERAL = 'general'
|
|
20
|
+
HOOK = 'hook'
|
|
21
|
+
TRANSACTION = 'transaction'
|
|
22
|
+
HTTP = 'http'
|
|
23
|
+
ASSERT = 'assert'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@attrs
|
|
27
|
+
class CbConfig:
|
|
28
|
+
is_ready = attrib(default=False)
|
|
29
|
+
run_id = attrib(default=None)
|
|
30
|
+
run_group = attrib(default=None)
|
|
31
|
+
instance_id = attrib(default=None)
|
|
32
|
+
project_id = attrib(default=None)
|
|
33
|
+
api_token = attrib(default=None)
|
|
34
|
+
api_endpoint_url = attrib(default=None)
|
|
35
|
+
selenium_url = attrib(default=None)
|
|
36
|
+
appium_url = attrib(default=None)
|
|
37
|
+
capabilities = attrib(default=defaultdict(OrderedDict))
|
|
38
|
+
options = attrib(default=defaultdict(OrderedDict))
|
|
39
|
+
metadata = attrib(default=defaultdict(OrderedDict))
|
|
40
|
+
env_vars = attrib(default=defaultdict(OrderedDict))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@attrs
|
|
44
|
+
class TestResult:
|
|
45
|
+
start_time = attrib(default=None)
|
|
46
|
+
end_time = attrib(default=None)
|
|
47
|
+
duration = attrib(default=None)
|
|
48
|
+
|
|
49
|
+
run_id = attrib(default=None)
|
|
50
|
+
instance_id = attrib(default=None)
|
|
51
|
+
capabilities = attrib(default=defaultdict(OrderedDict))
|
|
52
|
+
options = attrib(default=defaultdict(OrderedDict))
|
|
53
|
+
meta_data = attrib(default=defaultdict(OrderedDict))
|
|
54
|
+
environment_variables = attrib(default=defaultdict(OrderedDict))
|
|
55
|
+
test_attributes = attrib(default=defaultdict(OrderedDict))
|
|
56
|
+
|
|
57
|
+
hooks = attrib(default=Factory(list))
|
|
58
|
+
suites = attrib(default=Factory(list))
|
|
59
|
+
failures = attrib(default=Factory(list))
|
|
60
|
+
status: TestStatus = attrib(default=None)
|
|
61
|
+
|
|
62
|
+
def __iter__(self):
|
|
63
|
+
yield from {
|
|
64
|
+
"startTime": self.start_time,
|
|
65
|
+
"endTime": self.end_time,
|
|
66
|
+
"duration": self.duration,
|
|
67
|
+
"runId": self.run_id,
|
|
68
|
+
"instanceId": self.instance_id
|
|
69
|
+
}.items()
|
|
70
|
+
|
|
71
|
+
def __str__(self):
|
|
72
|
+
return json.dumps(dict(self), cls=MyJSONEncoder, ensure_ascii=False)
|
|
73
|
+
|
|
74
|
+
def start(self, run_id, instance_id, options, caps, metadata, env_vars):
|
|
75
|
+
self.run_id = run_id
|
|
76
|
+
self.instance_id = instance_id
|
|
77
|
+
self.start_time = int(time.time() * 1000)
|
|
78
|
+
self.options = options
|
|
79
|
+
self.capabilities = caps
|
|
80
|
+
self.meta_data = metadata
|
|
81
|
+
self.environment_variables = env_vars
|
|
82
|
+
|
|
83
|
+
def end(self):
|
|
84
|
+
self.end_time = int(time.time() * 1000)
|
|
85
|
+
self.duration = self.end_time - self.start_time
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@attrs
|
|
89
|
+
class TestableResultBase:
|
|
90
|
+
id = attrib(default=str(uuid.uuid4()))
|
|
91
|
+
name = attrib(default=None)
|
|
92
|
+
display_name = attrib(default=None)
|
|
93
|
+
description = attrib(default=None)
|
|
94
|
+
fqn = attrib(default=None)
|
|
95
|
+
status = attrib(default=TestStatus.PASSED)
|
|
96
|
+
attachments = attrib(default=Factory(list))
|
|
97
|
+
arguments = attrib(default=Factory(list))
|
|
98
|
+
hooks = attrib(default=Factory(list))
|
|
99
|
+
start_time = attrib(default=None)
|
|
100
|
+
end_time = attrib(default=None)
|
|
101
|
+
duration = attrib(default=None)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@attrs
|
|
105
|
+
class SuiteResult(TestableResultBase):
|
|
106
|
+
cases = attrib(default=Factory(list))
|
|
107
|
+
|
|
108
|
+
def start(self, name, fqn=None):
|
|
109
|
+
self.name = name
|
|
110
|
+
self.fqn = fqn
|
|
111
|
+
self.start_time = int(time.time() * 1000)
|
|
112
|
+
|
|
113
|
+
def end(self, status=None):
|
|
114
|
+
self.end_time = int(time.time() * 1000)
|
|
115
|
+
self.duration = self.end_time - self.start_time
|
|
116
|
+
# Mark suite as FAILED if at least one of the test case has failed
|
|
117
|
+
has_failed_cases = any(c.status == TestStatus.FAILED for c in self.cases)
|
|
118
|
+
if has_failed_cases and status is None:
|
|
119
|
+
self.status = TestStatus.FAILED
|
|
120
|
+
elif status is not None:
|
|
121
|
+
self.status = status
|
|
122
|
+
|
|
123
|
+
def add_case(self, case_result):
|
|
124
|
+
self.cases.append(case_result)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@attrs
|
|
128
|
+
class StepResult(TestableResultBase):
|
|
129
|
+
type: StepType = attrib(default=None)
|
|
130
|
+
steps = attrib(default=Factory(list))
|
|
131
|
+
logs = attrib(default=Factory(list))
|
|
132
|
+
|
|
133
|
+
def start(self, name, fqn=None):
|
|
134
|
+
self.name = name
|
|
135
|
+
self.fqn = fqn
|
|
136
|
+
self.start_time = int(time.time() * 1000)
|
|
137
|
+
|
|
138
|
+
def end(self, status=TestStatus.PASSED):
|
|
139
|
+
self.end_time = int(time.time() * 1000)
|
|
140
|
+
self.duration = self.end_time - self.start_time
|
|
141
|
+
self.status = status if status is not None else TestStatus.PASSED
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@attrs
|
|
145
|
+
class CaseResult(TestableResultBase):
|
|
146
|
+
context = defaultdict(OrderedDict)
|
|
147
|
+
steps = attrib(default=Factory(list))
|
|
148
|
+
_started_steps_stack: List[StepResult] = []
|
|
149
|
+
|
|
150
|
+
def start(self, name, fqn=None):
|
|
151
|
+
self.name = name
|
|
152
|
+
self.fqn = fqn
|
|
153
|
+
self.start_time = int(time.time() * 1000)
|
|
154
|
+
|
|
155
|
+
def end(self, status=None):
|
|
156
|
+
# End all unfinished steps
|
|
157
|
+
for _ in range(len(self._started_steps_stack)):
|
|
158
|
+
step_result = self._started_steps_stack.pop()
|
|
159
|
+
step_result.end()
|
|
160
|
+
# Do not override the calculated FAILED status by the status function argument
|
|
161
|
+
if status is not None and self.status != TestStatus.FAILED:
|
|
162
|
+
self.status = status
|
|
163
|
+
self.end_time = int(time.time() * 1000)
|
|
164
|
+
self.duration = self.end_time - self.start_time
|
|
165
|
+
|
|
166
|
+
def start_hook(self, name):
|
|
167
|
+
hook_result = StepResult()
|
|
168
|
+
hook_result.start(name)
|
|
169
|
+
hook_result.type = StepType.HOOK
|
|
170
|
+
self.hooks.append(hook_result)
|
|
171
|
+
self._started_steps_stack.append(hook_result)
|
|
172
|
+
return hook_result
|
|
173
|
+
|
|
174
|
+
def end_hook(self, status=TestStatus.PASSED):
|
|
175
|
+
if len(self.hooks) == 0:
|
|
176
|
+
return None
|
|
177
|
+
last_hook = self.hooks[len(self.hooks) - 1]
|
|
178
|
+
# End hook's child steps, if remain open
|
|
179
|
+
if len(self._started_steps_stack) > 0:
|
|
180
|
+
while last_step := self._started_steps_stack.pop():
|
|
181
|
+
if last_step == last_hook:
|
|
182
|
+
break
|
|
183
|
+
last_step.end()
|
|
184
|
+
last_hook.end(status)
|
|
185
|
+
return last_hook
|
|
186
|
+
|
|
187
|
+
def start_step(self, name, fqn=None):
|
|
188
|
+
# Check if there is a started step
|
|
189
|
+
parent_step = self._started_steps_stack[len(self._started_steps_stack) - 1] \
|
|
190
|
+
if len(self._started_steps_stack) > 0 else None
|
|
191
|
+
step_result = StepResult()
|
|
192
|
+
step_result.start(name, fqn)
|
|
193
|
+
if parent_step is None:
|
|
194
|
+
self.steps.append(step_result)
|
|
195
|
+
else:
|
|
196
|
+
parent_step.steps.append(step_result)
|
|
197
|
+
self._started_steps_stack.append(step_result)
|
|
198
|
+
return step_result
|
|
199
|
+
|
|
200
|
+
def end_step(self, status=TestStatus.PASSED):
|
|
201
|
+
if len(self._started_steps_stack) == 0 or len(self.steps) == 0:
|
|
202
|
+
return None
|
|
203
|
+
last_step = self._started_steps_stack.pop() \
|
|
204
|
+
if len(self._started_steps_stack) > 0 else self.steps[len(self.steps) - 1]
|
|
205
|
+
last_step.end(status)
|
|
206
|
+
if status == TestStatus.FAILED:
|
|
207
|
+
self.status = TestStatus.FAILED
|
|
208
|
+
return last_step
|
|
209
|
+
|
|
210
|
+
def add_parameters(self, parameters):
|
|
211
|
+
# TODO: merge with the existing parameters
|
|
212
|
+
self.context["params"] = parameters
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from collections import OrderedDict, defaultdict
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
from cloudbeat_common.models import TestResult, CbConfig, SuiteResult, CaseResult, StepResult
|
|
6
|
+
from cloudbeat_common.json_util import to_json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ThreadContext:
|
|
10
|
+
_thread_context = defaultdict(OrderedDict)
|
|
11
|
+
_init_thread: threading.Thread
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def thread_context(self):
|
|
15
|
+
context = self._thread_context[threading.current_thread()]
|
|
16
|
+
if not context and threading.current_thread() is not self._init_thread:
|
|
17
|
+
uuid, last_item = next(reversed(self._thread_context[self._init_thread].items()))
|
|
18
|
+
context[uuid] = last_item
|
|
19
|
+
return context
|
|
20
|
+
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
self._init_thread = threading.current_thread()
|
|
23
|
+
super().__init__(*args, **kwargs)
|
|
24
|
+
|
|
25
|
+
def __setitem__(self, key, value):
|
|
26
|
+
self.thread_context.__setitem__(key, value)
|
|
27
|
+
|
|
28
|
+
def __getitem__(self, item):
|
|
29
|
+
return self.thread_context.__getitem__(item)
|
|
30
|
+
|
|
31
|
+
def __iter__(self):
|
|
32
|
+
return self.thread_context.__iter__()
|
|
33
|
+
|
|
34
|
+
def __reversed__(self):
|
|
35
|
+
return self.thread_context.__reversed__()
|
|
36
|
+
|
|
37
|
+
def get(self, key):
|
|
38
|
+
return self.thread_context.get(key)
|
|
39
|
+
|
|
40
|
+
def pop(self, key):
|
|
41
|
+
return self.thread_context.pop(key)
|
|
42
|
+
|
|
43
|
+
def cleanup(self):
|
|
44
|
+
stopped_threads = []
|
|
45
|
+
for thread in self._thread_context.keys():
|
|
46
|
+
if not thread.is_alive():
|
|
47
|
+
stopped_threads.append(thread)
|
|
48
|
+
for thread in stopped_threads:
|
|
49
|
+
del self._thread_context[thread]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CbTestReporter:
|
|
53
|
+
_result: TestResult = None
|
|
54
|
+
_config: CbConfig = None
|
|
55
|
+
|
|
56
|
+
def __init__(self, config: CbConfig):
|
|
57
|
+
self._context = ThreadContext()
|
|
58
|
+
self._config = config
|
|
59
|
+
|
|
60
|
+
def start_instance(self):
|
|
61
|
+
self._result = TestResult()
|
|
62
|
+
self._result.start(
|
|
63
|
+
self._config.run_id,
|
|
64
|
+
self._config.instance_id,
|
|
65
|
+
self._config.options,
|
|
66
|
+
self._config.capabilities,
|
|
67
|
+
self._config.metadata,
|
|
68
|
+
self._config.env_vars)
|
|
69
|
+
self._add_system_attributes()
|
|
70
|
+
|
|
71
|
+
def end_instance(self) -> None:
|
|
72
|
+
if self._result is None:
|
|
73
|
+
return
|
|
74
|
+
self._result.end()
|
|
75
|
+
# Serializing json
|
|
76
|
+
json_str = to_json(self._result)
|
|
77
|
+
|
|
78
|
+
# Writing to sample.json
|
|
79
|
+
with open(".CB_TEST_RESULTS.json", "w") as outfile:
|
|
80
|
+
outfile.write(json_str)
|
|
81
|
+
|
|
82
|
+
def start_suite(self, name, fqn=None):
|
|
83
|
+
if self._result is None:
|
|
84
|
+
return None
|
|
85
|
+
suite_result = SuiteResult()
|
|
86
|
+
suite_result.start(name, fqn)
|
|
87
|
+
self._result.suites.append(suite_result)
|
|
88
|
+
self._context["suite"] = suite_result
|
|
89
|
+
self._context["case"] = None
|
|
90
|
+
return suite_result
|
|
91
|
+
|
|
92
|
+
def end_suite(self):
|
|
93
|
+
if self._context["suite"] is None:
|
|
94
|
+
return None
|
|
95
|
+
suite_result: SuiteResult = self._context["suite"]
|
|
96
|
+
suite_result.end()
|
|
97
|
+
return suite_result
|
|
98
|
+
|
|
99
|
+
def start_case(self, name, fqn=None):
|
|
100
|
+
if self._context["suite"] is None:
|
|
101
|
+
return None
|
|
102
|
+
case_result = CaseResult()
|
|
103
|
+
case_result.start(name, fqn)
|
|
104
|
+
suite_result: SuiteResult = self._context["suite"]
|
|
105
|
+
suite_result.add_case(case_result)
|
|
106
|
+
self._context["case"] = case_result
|
|
107
|
+
return case_result
|
|
108
|
+
|
|
109
|
+
def end_case(self, status=None):
|
|
110
|
+
case_result: CaseResult = self._context["case"] if "case" in self._context else None
|
|
111
|
+
if case_result is None:
|
|
112
|
+
return None
|
|
113
|
+
# TODO: end started steps of the case
|
|
114
|
+
case_result.end(status)
|
|
115
|
+
return case_result
|
|
116
|
+
|
|
117
|
+
def start_case_hook(self, name):
|
|
118
|
+
case_result: CaseResult = self._context["case"] if "case" in self._context else None
|
|
119
|
+
if case_result is None:
|
|
120
|
+
return None
|
|
121
|
+
return case_result.start_hook(name)
|
|
122
|
+
|
|
123
|
+
def end_case_hook(self, status=None):
|
|
124
|
+
case_result: CaseResult = self._context["case"] if "case" in self._context else None
|
|
125
|
+
if case_result is None:
|
|
126
|
+
return None
|
|
127
|
+
return case_result.end_hook(status)
|
|
128
|
+
|
|
129
|
+
def start_step(self, name, fqn=None):
|
|
130
|
+
if self._context["case"] is None:
|
|
131
|
+
return None
|
|
132
|
+
case_result: CaseResult = self._context["case"]
|
|
133
|
+
step_result = case_result.start_step(name, fqn)
|
|
134
|
+
return step_result
|
|
135
|
+
|
|
136
|
+
def end_step(self):
|
|
137
|
+
if self._context["case"] is None:
|
|
138
|
+
return None
|
|
139
|
+
case_result: CaseResult = self._context["case"]
|
|
140
|
+
return case_result.end_step()
|
|
141
|
+
|
|
142
|
+
def _add_system_attributes(self):
|
|
143
|
+
self._result.test_attributes["agent.hostname"] = platform.node()
|
|
144
|
+
self._result.test_attributes["agent.os.name"] = platform.system()
|
|
145
|
+
# Determine OS version
|
|
146
|
+
os_version = platform.version() if platform.system() != "Darwin" else platform.mac_ver()[0]
|
|
147
|
+
self._result.test_attributes["agent.os.version"] = os_version
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def result(self):
|
|
151
|
+
return self._result
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cloudbeat-common
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Contains the common types and API client for CloudBeat
|
|
5
|
+
Home-page: https://cloudbeat.io/
|
|
6
|
+
Author: CBNR Cloud Solutions LTD
|
|
7
|
+
Author-email: info@cloudbeat.io
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
Project-URL: Source, https://github.com/cloudbeat-io/cb-kit-python
|
|
10
|
+
Keywords: cloudbeat testing reporting python
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Requires-Python: >=3.6
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: attrs>=16.0.0
|
|
27
|
+
Requires-Dist: pluggy>=0.4.0
|
|
28
|
+
Dynamic: author
|
|
29
|
+
Dynamic: author-email
|
|
30
|
+
Dynamic: classifier
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: home-page
|
|
33
|
+
Dynamic: keywords
|
|
34
|
+
Dynamic: license
|
|
35
|
+
Dynamic: project-url
|
|
36
|
+
Dynamic: requires-dist
|
|
37
|
+
Dynamic: requires-python
|
|
38
|
+
Dynamic: summary
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
cloudbeat.py,sha256=xoOOX7taBh6JFaZD0GkhhLZ6D_YDZBkxW-9AltftrEY,154
|
|
2
|
+
cloudbeat_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
cloudbeat_common/json_util.py,sha256=HK7PKx29do4-9Ag9v5p92-N_1PCk4CWbfEvFq-jMh2M,2324
|
|
4
|
+
cloudbeat_common/models.py,sha256=ZO-UItGQplnUVGI0dcStrSUqBosrueB3wTkpcSxKyEM,6975
|
|
5
|
+
cloudbeat_common/reporter.py,sha256=on3r0LNce-LqciUnXzW_SXqbunE9bWAg3eK5SMC69vc,4955
|
|
6
|
+
cloudbeat_common-0.0.1.dist-info/METADATA,sha256=02-nRkXKZpXN0-IHc8gxqddp2v0Z1CVeGa8Gs6D_dqQ,1431
|
|
7
|
+
cloudbeat_common-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
cloudbeat_common-0.0.1.dist-info/top_level.txt,sha256=hBP28A3_p63b7ge819viyVcCd0QajP-CEJJVrWp-QrA,27
|
|
9
|
+
cloudbeat_common-0.0.1.dist-info/RECORD,,
|