qase-python-commons 3.4.1__py3-none-any.whl → 4.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.

Potentially problematic release.


This version of qase-python-commons might be problematic. Click here for more details.

@@ -11,6 +11,7 @@ from ..models.config.qaseconfig import Mode
11
11
  from typing import Union, List
12
12
 
13
13
  from ..util import get_host_info
14
+ from ..status_mapping.status_mapping import StatusMapping
14
15
 
15
16
  """
16
17
  CoreReporter is a facade for all reporters and it is used to initialize and manage them.
@@ -19,14 +20,21 @@ from ..util import get_host_info
19
20
 
20
21
 
21
22
  class QaseCoreReporter:
22
- def __init__(self, config: ConfigManager, framework: Union[str, None] = None, reporter_name: Union[str, None] = None):
23
+ def __init__(self, config: ConfigManager, framework: Union[str, None] = None,
24
+ reporter_name: Union[str, None] = None):
23
25
  config.validate_config()
24
26
  self.config = config.config
25
- self.logger = Logger(self.config.debug)
27
+ # Use the logger from ConfigManager instead of creating a new one
28
+ self.logger = config.logger
26
29
  self._execution_plan = None
27
30
  self.profilers = []
28
31
  self.overhead = 0
29
32
 
33
+ # Initialize status mapping
34
+ self.status_mapping = StatusMapping.from_dict(self.config.status_mapping)
35
+ if not self.status_mapping.is_empty():
36
+ self.logger.log_debug(f"Status mapping initialized: {self.status_mapping}")
37
+
30
38
  # self._selective_execution_setup()
31
39
  self.fallback = self._fallback_setup()
32
40
 
@@ -64,14 +72,15 @@ class QaseCoreReporter:
64
72
  self.logger.log('Failed to start run, disabling reporting', 'info')
65
73
  self.logger.log(e, 'error')
66
74
  self.reporter = None
75
+ return None
76
+
77
+ return None
67
78
 
68
79
  def complete_run(self) -> None:
69
80
  if self.reporter:
70
81
  try:
71
82
  ts = time.time()
72
- self.logger.log_debug("Completing run")
73
83
  self.reporter.complete_run()
74
- self.logger.log_debug("Run completed")
75
84
  self.overhead += time.time() - ts
76
85
  self.logger.log(f"Overhead for Qase Report: {round(self.overhead * 1000)}ms", 'info')
77
86
  except Exception as e:
@@ -85,6 +94,9 @@ class QaseCoreReporter:
85
94
  ts = time.time()
86
95
  self.logger.log_debug(f"Adding result {result}")
87
96
 
97
+ # Apply status mapping before adding result
98
+ self._apply_status_mapping(result)
99
+
88
100
  self.reporter.add_result(result)
89
101
 
90
102
  self.logger.log_debug(f"Result {result.get_title()} added")
@@ -123,8 +135,9 @@ class QaseCoreReporter:
123
135
  from ..profilers import SleepProfiler
124
136
  self.profilers.append(SleepProfiler(runtime=runtime))
125
137
  if profiler == "db":
126
- from ..profilers import DbProfiler
127
- self.profilers.append(DbProfiler(runtime=runtime))
138
+ from ..profilers import DatabaseProfilerSingleton
139
+ DatabaseProfilerSingleton.init(runtime=runtime)
140
+ self.profilers.append(DatabaseProfilerSingleton.get_instance())
128
141
 
129
142
  def enable_profilers(self) -> None:
130
143
  if self.reporter:
@@ -206,3 +219,26 @@ class QaseCoreReporter:
206
219
  if self.config.fallback == Mode.report:
207
220
  return QaseReport(config=self.config, logger=self.logger)
208
221
  return None
222
+
223
+ def _apply_status_mapping(self, result: Result) -> None:
224
+ """
225
+ Apply status mapping to a test result.
226
+
227
+ This method applies the configured status mapping to the result's execution status.
228
+ The mapping is applied before the result is sent to the reporter.
229
+
230
+ Args:
231
+ result: Test result to apply status mapping to
232
+ """
233
+ if self.status_mapping.is_empty():
234
+ return
235
+
236
+ original_status = result.get_status()
237
+ if not original_status:
238
+ return
239
+
240
+ mapped_status = self.status_mapping.apply_mapping(original_status)
241
+
242
+ if mapped_status != original_status:
243
+ result.execution.set_status(mapped_status)
244
+ self.logger.log_debug(f"Status mapped for '{result.get_title()}': {original_status} -> {mapped_status}")
@@ -31,10 +31,10 @@ class QaseReport:
31
31
 
32
32
  def start_run(self):
33
33
  self._check_report_path()
34
- self.start_time = str(time.time())
34
+ self.start_time = str(QaseUtils.get_real_time())
35
35
 
36
36
  def complete_run(self):
37
- self.end_time = str(time.time())
37
+ self.end_time = str(QaseUtils.get_real_time())
38
38
  self._compile_report()
39
39
 
40
40
  def complete_worker(self):
@@ -86,15 +86,34 @@ class QaseTestOps:
86
86
 
87
87
  def _send_results(self) -> None:
88
88
  if self.results:
89
- # Acquire semaphore before starting the send operation
90
- self.send_semaphore.acquire()
91
- self.count_running_threads += 1
89
+ # Filter results by status if status_filter is configured
92
90
  results_to_send = self.results.copy()
91
+
92
+ if self.config.testops.status_filter and len(self.config.testops.status_filter) > 0:
93
+ filtered_results = []
94
+ for result in results_to_send:
95
+ result_status = result.get_status()
96
+ if result_status and result_status not in self.config.testops.status_filter:
97
+ filtered_results.append(result)
98
+ else:
99
+ self.logger.log_debug(f"Filtering out result '{result.title}' with status '{result_status}'")
100
+
101
+ results_to_send = filtered_results
102
+ self.logger.log_debug(f"Filtered {len(self.results) - len(results_to_send)} results by status filter")
103
+
104
+ if results_to_send:
105
+ # Acquire semaphore before starting the send operation
106
+ self.send_semaphore.acquire()
107
+ self.count_running_threads += 1
108
+
109
+ # Start a new thread for sending results
110
+ send_thread = threading.Thread(target=self._send_results_threaded, args=(results_to_send,))
111
+ send_thread.start()
112
+ else:
113
+ self.logger.log("No results to send after filtering", "info")
114
+
115
+ # Clear results regardless of filtering
93
116
  self.results = []
94
-
95
- # Start a new thread for sending results
96
- send_thread = threading.Thread(target=self._send_results_threaded, args=(results_to_send,))
97
- send_thread.start()
98
117
  else:
99
118
  self.logger.log("No results to send", "info")
100
119
 
@@ -117,14 +136,33 @@ class QaseTestOps:
117
136
  def complete_run(self) -> None:
118
137
  if len(self.results) > 0:
119
138
  self._send_results()
139
+
140
+ while self.count_running_threads > 0:
141
+ pass
142
+
120
143
  if self.complete_after_run:
121
- while self.count_running_threads > 0:
122
- pass
144
+ self.logger.log_debug("Completing run")
123
145
  self.client.complete_run(self.project_code, self.run_id)
146
+ self.logger.log_debug("Run completed")
147
+
148
+ # Enable public report if configured
149
+ if self.config.testops.show_public_report_link:
150
+ try:
151
+ self.logger.log_debug("Enabling public report")
152
+ public_url = self.client.enable_public_report(self.project_code, self.run_id)
153
+ if public_url:
154
+ self.logger.log(f"Public report link: {public_url}", "info")
155
+ else:
156
+ self.logger.log("Failed to generate public report link", "warning")
157
+ except Exception as e:
158
+ self.logger.log(f"Failed to generate public report link: {e}", "warning")
124
159
 
125
160
  def complete_worker(self) -> None:
126
161
  if len(self.results) > 0:
127
162
  self._send_results()
163
+ while self.count_running_threads > 0:
164
+ pass
165
+ self.logger.log_debug("Worker completed")
128
166
 
129
167
  def add_result(self, result: Result) -> None:
130
168
  if result.get_status() == 'failed':
@@ -146,7 +184,7 @@ class QaseTestOps:
146
184
  def __prepare_link(self, ids: Union[None, List[int]], title: str):
147
185
  link = f"{self.__baseUrl}/run/{self.project_code}/dashboard/{self.run_id}?source=logs&status=%5B2%5D&search="
148
186
  if ids is not None and len(ids) > 0:
149
- return f"{link}{ids[0]}"
187
+ return f"{link}{self.project_code}-{ids[0]}"
150
188
  return f"{link}{urllib.parse.quote_plus(title)}"
151
189
 
152
190
  @staticmethod
@@ -0,0 +1,12 @@
1
+ """
2
+ Utilities package for Qase Python Commons.
3
+ """
4
+
5
+ from .status_mapping import StatusMapping, StatusMappingError, create_status_mapping_from_config, create_status_mapping_from_env
6
+
7
+ __all__ = [
8
+ 'StatusMapping',
9
+ 'StatusMappingError',
10
+ 'create_status_mapping_from_config',
11
+ 'create_status_mapping_from_env'
12
+ ]
@@ -0,0 +1,237 @@
1
+ """
2
+ Status mapping utilities for Qase Python Commons.
3
+
4
+ This module provides functionality to map test result statuses from one value to another
5
+ based on configuration. This is useful for standardizing status values across different
6
+ testing frameworks or for custom status transformations.
7
+ """
8
+
9
+ from typing import Dict, Optional, List
10
+ import os
11
+ import logging
12
+
13
+
14
+ class StatusMappingError(Exception):
15
+ """Exception raised when status mapping encounters an error."""
16
+ pass
17
+
18
+
19
+ class StatusMapping:
20
+ """
21
+ Handles mapping of test result statuses.
22
+
23
+ This class provides functionality to:
24
+ - Parse status mapping from configuration
25
+ - Validate status mappings
26
+ - Apply status mappings to test results
27
+ - Support both JSON configuration and environment variables
28
+ """
29
+
30
+ # Valid statuses that can be mapped
31
+ VALID_STATUSES = {
32
+ 'passed', 'failed', 'skipped', 'disabled', 'blocked', 'invalid'
33
+ }
34
+
35
+ def __init__(self, mapping: Optional[Dict[str, str]] = None):
36
+ """
37
+ Initialize StatusMapping with optional mapping dictionary.
38
+
39
+ Args:
40
+ mapping: Dictionary mapping source status to target status
41
+ """
42
+ self.mapping = mapping or {}
43
+ self.logger = logging.getLogger(__name__)
44
+
45
+ @classmethod
46
+ def from_dict(cls, mapping_dict: Dict[str, str]) -> 'StatusMapping':
47
+ """
48
+ Create StatusMapping from dictionary.
49
+
50
+ Args:
51
+ mapping_dict: Dictionary with status mappings
52
+
53
+ Returns:
54
+ StatusMapping instance
55
+
56
+ Raises:
57
+ StatusMappingError: If mapping contains invalid statuses
58
+ """
59
+ instance = cls()
60
+ instance.set_mapping(mapping_dict)
61
+ return instance
62
+
63
+ @classmethod
64
+ def from_env_string(cls, env_string: str) -> 'StatusMapping':
65
+ """
66
+ Create StatusMapping from environment variable string.
67
+
68
+ Expected format: "source1=target1,source2=target2"
69
+
70
+ Args:
71
+ env_string: Environment variable string
72
+
73
+ Returns:
74
+ StatusMapping instance
75
+
76
+ Raises:
77
+ StatusMappingError: If string format is invalid
78
+ """
79
+ instance = cls()
80
+ instance.parse_env_string(env_string)
81
+ return instance
82
+
83
+ def set_mapping(self, mapping_dict: Dict[str, str]) -> None:
84
+ """
85
+ Set status mapping from dictionary.
86
+
87
+ Args:
88
+ mapping_dict: Dictionary with status mappings
89
+
90
+ Raises:
91
+ StatusMappingError: If mapping contains invalid statuses
92
+ """
93
+ if not isinstance(mapping_dict, dict):
94
+ raise StatusMappingError("Mapping must be a dictionary")
95
+
96
+ # Validate all statuses in the mapping
97
+ for source_status, target_status in mapping_dict.items():
98
+ if source_status not in self.VALID_STATUSES:
99
+ raise StatusMappingError(f"Invalid source status: {source_status}")
100
+ if target_status not in self.VALID_STATUSES:
101
+ raise StatusMappingError(f"Invalid target status: {target_status}")
102
+
103
+ self.mapping = mapping_dict.copy()
104
+ self.logger.debug(f"Status mapping set: {self.mapping}")
105
+
106
+ def parse_env_string(self, env_string: str) -> None:
107
+ """
108
+ Parse status mapping from environment variable string.
109
+
110
+ Expected format: "source1=target1,source2=target2"
111
+
112
+ Args:
113
+ env_string: Environment variable string
114
+
115
+ Raises:
116
+ StatusMappingError: If string format is invalid
117
+ """
118
+ if not env_string or not env_string.strip():
119
+ self.mapping = {}
120
+ return
121
+
122
+ mapping_dict = {}
123
+ pairs = env_string.split(',')
124
+
125
+ for pair in pairs:
126
+ pair = pair.strip()
127
+ if not pair:
128
+ continue
129
+
130
+ if '=' not in pair:
131
+ raise StatusMappingError(f"Invalid mapping format: {pair}. Expected 'source=target'")
132
+
133
+ source_status, target_status = pair.split('=', 1)
134
+ source_status = source_status.strip()
135
+ target_status = target_status.strip()
136
+
137
+ if not source_status or not target_status:
138
+ raise StatusMappingError(f"Empty status in mapping: {pair}")
139
+
140
+ mapping_dict[source_status] = target_status
141
+
142
+ self.set_mapping(mapping_dict)
143
+
144
+ def apply_mapping(self, status: str) -> str:
145
+ """
146
+ Apply status mapping to a given status.
147
+
148
+ Args:
149
+ status: Original status
150
+
151
+ Returns:
152
+ Mapped status if mapping exists, otherwise original status
153
+ """
154
+ if not status:
155
+ return status
156
+
157
+ if status in self.mapping:
158
+ mapped_status = self.mapping[status]
159
+ self.logger.debug(f"Status mapped: {status} -> {mapped_status}")
160
+ return mapped_status
161
+
162
+ return status
163
+
164
+ def get_mapping(self) -> Dict[str, str]:
165
+ """
166
+ Get current status mapping.
167
+
168
+ Returns:
169
+ Dictionary with current status mappings
170
+ """
171
+ return self.mapping.copy()
172
+
173
+ def is_empty(self) -> bool:
174
+ """
175
+ Check if mapping is empty.
176
+
177
+ Returns:
178
+ True if no mappings are defined
179
+ """
180
+ return len(self.mapping) == 0
181
+
182
+ def validate(self) -> List[str]:
183
+ """
184
+ Validate current mapping and return any issues.
185
+
186
+ Returns:
187
+ List of validation error messages
188
+ """
189
+ errors = []
190
+
191
+ for source_status, target_status in self.mapping.items():
192
+ if source_status not in self.VALID_STATUSES:
193
+ errors.append(f"Invalid source status: {source_status}")
194
+ if target_status not in self.VALID_STATUSES:
195
+ errors.append(f"Invalid target status: {target_status}")
196
+
197
+ return errors
198
+
199
+ def __str__(self) -> str:
200
+ """String representation of the mapping."""
201
+ return str(self.mapping)
202
+
203
+ def __repr__(self) -> str:
204
+ """Detailed string representation."""
205
+ return f"StatusMapping({self.mapping})"
206
+
207
+
208
+ def create_status_mapping_from_config(config_value: Optional[Dict[str, str]]) -> StatusMapping:
209
+ """
210
+ Create StatusMapping from configuration value.
211
+
212
+ Args:
213
+ config_value: Configuration dictionary or None
214
+
215
+ Returns:
216
+ StatusMapping instance
217
+ """
218
+ if config_value is None:
219
+ return StatusMapping()
220
+
221
+ return StatusMapping.from_dict(config_value)
222
+
223
+
224
+ def create_status_mapping_from_env(env_var_name: str = 'STATUS_MAPPING') -> StatusMapping:
225
+ """
226
+ Create StatusMapping from environment variable.
227
+
228
+ Args:
229
+ env_var_name: Name of environment variable
230
+
231
+ Returns:
232
+ StatusMapping instance
233
+ """
234
+ env_value = os.getenv(env_var_name)
235
+ if env_value:
236
+ return StatusMapping.from_env_string(env_value)
237
+ return StatusMapping()
qase/commons/utils.py CHANGED
@@ -2,13 +2,92 @@ import os
2
2
  import platform
3
3
  import threading
4
4
  import sys
5
+ from typing import Union, List
5
6
  import pip
6
7
  import string
7
8
  import uuid
9
+ import time
8
10
 
9
11
 
10
12
  class QaseUtils:
11
13
 
14
+ @staticmethod
15
+ def get_real_time() -> float:
16
+ """
17
+ Get real system time, bypassing time mocking libraries like freezegun.
18
+
19
+ This is necessary when reporting test results to external systems that validate
20
+ timestamps against current time, even when tests are using time mocking.
21
+
22
+ Returns:
23
+ float: Current Unix timestamp in seconds with microsecond precision
24
+ """
25
+ # Try to get the original time function if it was wrapped by freezegun
26
+ # freezegun stores the original function in __wrapped__ attribute
27
+ if hasattr(time.time, '__wrapped__'):
28
+ return time.time.__wrapped__()
29
+
30
+ # Fallback: use direct system call via ctypes
31
+ # This works on Unix-like systems and Windows
32
+ try:
33
+ import ctypes
34
+ import ctypes.util
35
+
36
+ if sys.platform == 'win32':
37
+ # Windows: use GetSystemTimeAsFileTime
38
+ class FILETIME(ctypes.Structure):
39
+ _fields_ = [("dwLowDateTime", ctypes.c_uint32),
40
+ ("dwHighDateTime", ctypes.c_uint32)]
41
+
42
+ kernel32 = ctypes.windll.kernel32
43
+ ft = FILETIME()
44
+ kernel32.GetSystemTimeAsFileTime(ctypes.byref(ft))
45
+
46
+ # Convert FILETIME to Unix timestamp
47
+ # FILETIME is 100-nanosecond intervals since January 1, 1601
48
+ timestamp = (ft.dwHighDateTime << 32) + ft.dwLowDateTime
49
+ # Convert to seconds and adjust epoch (1601 -> 1970)
50
+ return (timestamp / 10000000.0) - 11644473600.0
51
+ else:
52
+ # Unix-like systems: use gettimeofday for microsecond precision
53
+ # Try multiple approaches to find libc
54
+ libc = None
55
+
56
+ # Method 1: Use find_library (works on most systems)
57
+ libc_path = ctypes.util.find_library('c')
58
+ if libc_path:
59
+ try:
60
+ libc = ctypes.CDLL(libc_path)
61
+ except OSError:
62
+ pass
63
+
64
+ # Method 2: Try common library names directly (for Alpine Linux, musl libc, etc.)
65
+ if libc is None:
66
+ for lib_name in ['libc.so.6', 'libc.so', 'libc.dylib']:
67
+ try:
68
+ libc = ctypes.CDLL(lib_name)
69
+ break
70
+ except OSError:
71
+ continue
72
+
73
+ if libc is None:
74
+ raise OSError("Could not load C library")
75
+
76
+ class timeval(ctypes.Structure):
77
+ _fields_ = [("tv_sec", ctypes.c_long),
78
+ ("tv_usec", ctypes.c_long)]
79
+
80
+ tv = timeval()
81
+ libc.gettimeofday(ctypes.byref(tv), None)
82
+
83
+ return float(tv.tv_sec) + (float(tv.tv_usec) / 1000000.0)
84
+ except Exception:
85
+ # Last resort: return the potentially mocked time
86
+ # This will still work in normal cases without freezegun
87
+ # If freezegun is active, the user might see timestamp validation errors
88
+ # but the core functionality will continue to work
89
+ return time.time()
90
+
12
91
  @staticmethod
13
92
  def build_tree(items):
14
93
  nodes = {item.id: item for item in items}
@@ -64,6 +143,22 @@ class QaseUtils:
64
143
  def get_filename(path) -> str:
65
144
  return os.path.basename(path)
66
145
 
146
+ @staticmethod
147
+ def get_signature(testops_ids: Union[List[int], None], suites: List[str], params: dict) -> str:
148
+ signature_parts = []
149
+
150
+ if testops_ids:
151
+ signature_parts.append(
152
+ f"{'-'.join(map(str, testops_ids))}")
153
+
154
+ for suite in suites:
155
+ signature_parts.append(suite.lower().replace(" ", "_"))
156
+
157
+ for key, val in params.items():
158
+ signature_parts.append(f"{{{key}:{val}}}")
159
+
160
+ return "::".join(signature_parts)
161
+
67
162
 
68
163
  class StringFormatter(string.Formatter):
69
164
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qase-python-commons
3
- Version: 3.4.1
3
+ Version: 4.1.3
4
4
  Summary: A library for Qase TestOps and Qase Report
5
5
  Author-email: Qase Team <support@qase.io>
6
6
  Project-URL: Homepage, https://github.com/qase-tms/qase-python/tree/main/qase-python-commons
@@ -12,18 +12,17 @@ Classifier: Topic :: Software Development :: Quality Assurance
12
12
  Classifier: Topic :: Software Development :: Testing
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3 :: Only
15
- Classifier: Programming Language :: Python :: 3.7
16
- Classifier: Programming Language :: Python :: 3.8
17
15
  Classifier: Programming Language :: Python :: 3.9
18
16
  Classifier: Programming Language :: Python :: 3.10
19
17
  Classifier: Programming Language :: Python :: 3.11
20
18
  Classifier: Programming Language :: Python :: 3.12
21
- Requires-Python: >=3.7
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Python: >=3.9
22
21
  Description-Content-Type: text/markdown
23
22
  Requires-Dist: certifi>=2024.2.2
24
23
  Requires-Dist: attrs>=23.2.0
25
- Requires-Dist: qase-api-client~=1.2.0
26
- Requires-Dist: qase-api-v2-client~=1.2.0
24
+ Requires-Dist: qase-api-client~=2.0.1
25
+ Requires-Dist: qase-api-v2-client~=2.0.0
27
26
  Requires-Dist: more_itertools
28
27
  Provides-Extra: testing
29
28
  Requires-Dist: pytest; extra == "testing"
@@ -32,12 +31,18 @@ Requires-Dist: mock; extra == "testing"
32
31
  Requires-Dist: more_itertools; extra == "testing"
33
32
  Requires-Dist: requests; extra == "testing"
34
33
  Requires-Dist: urllib3; extra == "testing"
34
+ Requires-Dist: freezegun; extra == "testing"
35
35
 
36
36
  # Qase Python Commons
37
37
 
38
38
  ## Description
39
39
 
40
- This package contains reporters for Qase TestOps and Qase Report that are used in [qase-pytest](https://github.com/qase-tms/qase-python/tree/master/qase-pytest) and [qase-robotframework](https://github.com/qase-tms/qase-python/tree/master/qase-robotframework).
40
+ This package contains reporters for Qase TestOps and Qase Report that are used in following projects:
41
+
42
+ - [qase-pytest](https://github.com/qase-tms/qase-python/tree/main/qase-pytest)
43
+ - [qase-robotframework](https://github.com/qase-tms/qase-python/tree/main/qase-robotframework)
44
+ - [qase-behave](https://github.com/qase-tms/qase-python/tree/main/qase-behave)
45
+ - [qase-tavern](https://github.com/qase-tms/qase-python/tree/main/qase-tavern)
41
46
 
42
47
  ## How to install
43
48
 
@@ -0,0 +1,45 @@
1
+ qase/__init__.py,sha256=FLefSJnT6MTsTfZSDjMbMImI6674fAoJ5ZT3PNWGcRo,37
2
+ qase/commons/__init__.py,sha256=3HI65PJES4Q6YvtkSuRPh6tZboTETJo8wbdHlNYaePU,323
3
+ qase/commons/config.py,sha256=SyG1-2RfKQAhQLUbq7szbUUp9ABYvbSiaT8WdQEjMcA,15556
4
+ qase/commons/loader.py,sha256=-MMY4HgSI6q1xq3NaJoq_w4liM73qdFKjYLVCT1E7Pc,1064
5
+ qase/commons/logger.py,sha256=V_QTSDWNnUgd0j58rydYYN1AYOu3wa9T2fWjpOyxyuA,3430
6
+ qase/commons/utils.py,sha256=0TtpLvyAdh7fLQW4HA_6aQM8kWy4a8VtSD_KdkRVZlw,6775
7
+ qase/commons/client/api_v1_client.py,sha256=BaE9Ix-k5Xc7Wiz8ILKKsWb5G5J3hqgjwaIcrocvCdY,12879
8
+ qase/commons/client/api_v2_client.py,sha256=4iZ3RA-PPUHaW_X1llA7Gvsr4i-5Oq5k8PbffXI2tg8,10271
9
+ qase/commons/client/base_api_client.py,sha256=4NtXhUxrxEPOBIzEAE-b50KHMwlhxwnSLGcYx8NMjbY,2961
10
+ qase/commons/exceptions/reporter.py,sha256=dP-Mwcq8HKBOjgu3YqhyULDmDGU09BmT6Fh9HjICaUc,45
11
+ qase/commons/models/__init__.py,sha256=FTt5dYASBX4r6-tQi-_JAUVx4uvJs9GTxROdAZEV6Jo,272
12
+ qase/commons/models/attachment.py,sha256=cGfB0BaTDVfA7euJubKvi2cXXNvlSm_dy5x-ak3H3ec,1625
13
+ qase/commons/models/basemodel.py,sha256=0j8E-LE6hxAKQPYLNM9qThor9s2ZndZys_kibeoLImo,426
14
+ qase/commons/models/external_link.py,sha256=bdXj7pNHHkfb3dcYtXN8sNp-HFxHvZeYz8QIPGcvX0w,1317
15
+ qase/commons/models/relation.py,sha256=HymHeh1uBcoQnTs4Vra7WJ_KFkhryj5o7cShjoGQImI,511
16
+ qase/commons/models/result.py,sha256=53udABcu_Gzm-vvnvvb9mFui8-el-N41vCU2Dsual84,4047
17
+ qase/commons/models/run.py,sha256=ANbljW1mgua4JF1wGZG9S-jnUpI7FC4F15sAQOoNnko,3096
18
+ qase/commons/models/runtime.py,sha256=h0kJcPYRo8dglIQVFyzh-cFpWb3bAaQaIq5wV8VfClM,1508
19
+ qase/commons/models/step.py,sha256=rHzJ_B5APgW0TDgDO2oftOsnrmpPrQcIbyW7Yr1unAg,5866
20
+ qase/commons/models/config/api.py,sha256=IyYY2f3ncESUAEGvkE3-meatebBFJdvgo7KNOQnxLE0,288
21
+ qase/commons/models/config/batch.py,sha256=X0H8SVOCCD2pV6LSMqjI-tIjRcLifnrM5MareK2FhQw,321
22
+ qase/commons/models/config/connection.py,sha256=wK2fGjc0G0rMVVhPnjw_t_M1YWZwANlhwl-awmI7XSo,516
23
+ qase/commons/models/config/framework.py,sha256=sSWKQp18zxiS_79_XDH2hkHLpYEQnyH3bRquaT8XYYY,1803
24
+ qase/commons/models/config/plan.py,sha256=JbAY7qfGXYreXOLa32OLxw6z0paeCCf87-2b1m8xkks,137
25
+ qase/commons/models/config/qaseconfig.py,sha256=oWc03PKV8-jb3JNgs_tqQP-22wsKZW25eSaAacYl4no,2838
26
+ qase/commons/models/config/report.py,sha256=g3Z2B3Tewaasjc1TMj2mkXxz0Zc1C39sHeTXH0MRM2Y,497
27
+ qase/commons/models/config/run.py,sha256=UGE5PA_EyiFpfIev-TOob1BocxT1vdUdWW3ELMD58zQ,1239
28
+ qase/commons/models/config/testops.py,sha256=LdylcNG1ctxjNHXwrwcerAKY3h0WByKWYIrKOzEV2K8,2133
29
+ qase/commons/profilers/__init__.py,sha256=-ZuUylfpYeh5YinSnbyZyAZfmS9fUGOGlyooD4KQ-lE,293
30
+ qase/commons/profilers/db.py,sha256=DpHr7DktiBBiCv_9i4v-0lzKCsJiDdC4_A0mkDTqDMs,40438
31
+ qase/commons/profilers/network.py,sha256=zKNBnTQG4BMg8dn8O--tQzQLpu-qs5ADhHEnqIas0gM,4950
32
+ qase/commons/profilers/sleep.py,sha256=HT6h0R-2XHZAoBYRxS2T_KC8RrnEoVjP7MXusaE4Nec,1624
33
+ qase/commons/reporters/__init__.py,sha256=J0aNLzb_MPPT_zF8BtX_w9nj_U7Ad06RGpyWK5Pxq1o,169
34
+ qase/commons/reporters/core.py,sha256=Td4F8jlzTe3Ilti_IvD_ZMPdoDe0NILna1b_T71CTt4,9631
35
+ qase/commons/reporters/report.py,sha256=2hrpQGhtgEaDphRIiAiVtC1C1YWz3--jy4mw-kvO8lY,4838
36
+ qase/commons/reporters/testops.py,sha256=k3c91l_xuLq6NRLkzfwRqRTLfnpSxEQe38HNH8XMOTk,8161
37
+ qase/commons/status_mapping/__init__.py,sha256=NPsWKDC7rGSCYfVunnGnHxL0_HmKWH7RBaMjWTH_Mtk,322
38
+ qase/commons/status_mapping/status_mapping.py,sha256=kohSLNK6_qbMSP-M8gxHTTmOECgzDE3XvLqOzidPlYI,7213
39
+ qase/commons/util/__init__.py,sha256=0sRRfrMOIPCHpk9tXM94Pj10qrk18B61qEcbLpRjw_I,74
40
+ qase/commons/util/host_data.py,sha256=n8o5PDs8kELCZZ5GR7Jug6LsgZHWJudU7iRmZHRdrlw,5264
41
+ qase/commons/validators/base.py,sha256=wwSn-4YiuXtfGMGnSKgo9Vm5hAKevVmmfd2Ro6Q7MYQ,173
42
+ qase_python_commons-4.1.3.dist-info/METADATA,sha256=gf_ALF0O_WTlumZ4DuL89Pnt8ZanNq0JvRLnUV6yW98,2027
43
+ qase_python_commons-4.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
+ qase_python_commons-4.1.3.dist-info/top_level.txt,sha256=Mn5aFk7H7Uia4s1NRDsvebu8vCrFy9nOuRIBfkIY5kQ,5
45
+ qase_python_commons-4.1.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5