qase-python-commons 3.3.1__py3-none-any.whl → 3.4.0__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.

@@ -1,17 +1,16 @@
1
1
  from datetime import datetime, timezone
2
- from typing import Dict, Union
2
+ from typing import Union
3
3
 
4
4
  import certifi
5
5
  from qase.api_client_v1 import ApiClient, ProjectsApi, Project, EnvironmentsApi, RunsApi, AttachmentsApi, \
6
- AttachmentGet, RunCreate, ResultsApi, ResultcreateBulk, AuthorsApi
6
+ AttachmentGet, RunCreate
7
7
  from qase.api_client_v1.configuration import Configuration
8
8
  from .. import Logger
9
9
  from .base_api_client import BaseApiClient
10
10
  from ..exceptions.reporter import ReporterException
11
- from ..models import Attachment, InternalResult, Step
11
+ from ..models import Attachment
12
12
  from ..models.config.framework import Video, Trace
13
13
  from ..models.config.qaseconfig import QaseConfig
14
- from ..models.step import StepType
15
14
 
16
15
 
17
16
  class ApiV1Client(BaseApiClient):
@@ -123,173 +122,6 @@ class ApiV1Client(BaseApiClient):
123
122
  return True
124
123
  return False
125
124
 
126
- def send_results(self, project_code: str, run_id: str, results: []) -> None:
127
- api_results = ResultsApi(self.client)
128
- results_to_send = [self._prepare_result(project_code, result) for result in results]
129
- self.logger.log_debug(f"Sending results for run {run_id}: {results_to_send}")
130
- api_results.create_result_bulk(
131
- code=project_code,
132
- id=run_id,
133
- resultcreate_bulk=ResultcreateBulk(
134
- results=results_to_send
135
- )
136
- )
137
- self.logger.log_debug(f"Results for run {run_id} sent successfully")
138
-
139
- def _prepare_result(self, project_code: str, result: InternalResult) -> Dict:
140
- attached = []
141
- if result.attachments:
142
- for attachment in result.attachments:
143
- if self.__should_skip_attachment(attachment, result):
144
- continue
145
- attach_id = self._upload_attachment(project_code, attachment)
146
- if attach_id:
147
- attached.extend(attach_id)
148
-
149
- steps = []
150
- for step in result.steps:
151
- prepared = self._prepare_step(project_code, step)
152
- steps.append(prepared)
153
-
154
- case_data = {
155
- "title": result.get_title(),
156
- "description": result.get_field('description'),
157
- "preconditions": result.get_field('preconditions'),
158
- "postconditions": result.get_field('postconditions'),
159
- }
160
-
161
- for key, param in result.params.items():
162
- # Hack to match old TestOps API
163
- if param == "":
164
- result.params[key] = "empty"
165
-
166
- if result.get_field('severity'):
167
- case_data["severity"] = result.get_field('severity')
168
-
169
- if result.get_field('priority'):
170
- case_data["priority"] = result.get_field('priority')
171
-
172
- if result.get_field('layer'):
173
- case_data["layer"] = result.get_field('layer')
174
-
175
- suite = None
176
- if result.relations is not None and result.relations.suite is not None and len(
177
- result.relations.suite.data) != 0:
178
- suites = []
179
-
180
- for raw in result.relations.suite.data:
181
- suites.append(raw.title)
182
-
183
- suite = "\t".join(suites)
184
-
185
- if result.get_field('suite'):
186
- suite = result.get_field('suite')
187
-
188
- root_suite = self.config.root_suite
189
- if root_suite:
190
- suite = f"{root_suite}\t{suite}"
191
-
192
- if suite:
193
- case_data["suite_title"] = suite
194
-
195
- result_model = {
196
- "status": result.execution.status,
197
- "stacktrace": result.execution.stacktrace,
198
- "time_ms": result.execution.duration,
199
- "comment": result.message,
200
- "attachments": [attach.hash for attach in attached],
201
- "steps": steps,
202
- "param": result.params,
203
- "param_groups": result.param_groups,
204
- "defect": self.config.testops.defect,
205
- "case": case_data
206
- }
207
-
208
- test_ops_id = result.get_testops_id()
209
-
210
- if test_ops_id:
211
- result_model["case_id"] = test_ops_id
212
-
213
- if result.get_field('author'):
214
- author_id = self._get_author_id(result.get_field('author'))
215
- if author_id:
216
- result_model["author_id"] = author_id
217
-
218
- self.logger.log_debug(f"Prepared result: {result_model}")
219
-
220
- return result_model
221
-
222
- def _prepare_step(self, project_code: str, step: Step) -> Dict:
223
- prepared_children = []
224
-
225
- try:
226
- prepared_step = {"time": step.execution.duration, "status": step.execution.status}
227
-
228
- if step.execution.status == 'untested':
229
- prepared_step["status"] = 'passed'
230
-
231
- if step.execution.status == 'skipped':
232
- prepared_step["status"] = 'blocked'
233
-
234
- if step.step_type == StepType.TEXT:
235
- prepared_step['action'] = step.data.action
236
- if step.data.expected_result:
237
- prepared_step['expected_result'] = step.data.expected_result
238
-
239
- if step.step_type == StepType.REQUEST:
240
- prepared_step['action'] = step.data.request_method + " " + step.data.request_url
241
- if step.data.request_body:
242
- step.attachments.append(
243
- Attachment(file_name='request_body.txt', content=step.data.request_body, mime_type='text/plain',
244
- temporary=True))
245
- if step.data.request_headers:
246
- step.attachments.append(
247
- Attachment(file_name='request_headers.txt', content=step.data.request_headers,
248
- mime_type='text/plain', temporary=True))
249
- if step.data.response_body:
250
- step.attachments.append(Attachment(file_name='response_body.txt', content=step.data.response_body,
251
- mime_type='text/plain', temporary=True))
252
- if step.data.response_headers:
253
- step.attachments.append(
254
- Attachment(file_name='response_headers.txt', content=step.data.response_headers,
255
- mime_type='text/plain', temporary=True))
256
-
257
- if step.step_type == StepType.GHERKIN:
258
- prepared_step['action'] = step.data.keyword
259
-
260
- if step.step_type == StepType.SLEEP:
261
- prepared_step['action'] = f"Sleep for {step.data.duration} seconds"
262
-
263
- if step.attachments:
264
- uploaded_attachments = []
265
- for file in step.attachments:
266
- attach_id = self._upload_attachment(project_code, file)
267
- if attach_id:
268
- uploaded_attachments.extend(attach_id)
269
- prepared_step['attachments'] = [attach.hash for attach in uploaded_attachments]
270
-
271
- if step.steps:
272
- for substep in step.steps:
273
- prepared_children.append(self._prepare_step(project_code, substep))
274
- prepared_step["steps"] = prepared_children
275
- return prepared_step
276
- except Exception as e:
277
- self.logger.log(f"Error at preparing step: {e}", "error")
278
- raise ReporterException(e)
279
-
280
- def _get_author_id(self, author: str) -> Union[str, None]:
281
- if author in self.__authors:
282
- return self.__authors[author]
283
-
284
- author_api = AuthorsApi(self.client)
285
- authors = author_api.get_authors(search=author)
286
- if authors.result.total == 0:
287
- return None
288
-
289
- self.__authors[author] = authors.result.entities[0].author_id
290
-
291
- return authors.result.entities[0].author_id
292
-
293
125
  def __should_skip_attachment(self, attachment, result):
294
126
  if (self.config.framework.playwright.video == Video.failed and
295
127
  result.execution.status != 'failed' and
@@ -300,3 +132,6 @@ class ApiV1Client(BaseApiClient):
300
132
  attachment.file_name == 'trace.zip'):
301
133
  return True
302
134
  return False
135
+
136
+ def send_results(self, project_code: str, run_id: str, results: []) -> None:
137
+ raise NotImplementedError("use ApiV2Client instead")
@@ -17,7 +17,7 @@ from .api_v1_client import ApiV1Client
17
17
  from .. import Logger
18
18
  from ..exceptions.reporter import ReporterException
19
19
  from ..models.config.framework import Video, Trace
20
- from ..models import Attachment, InternalResult
20
+ from ..models import Attachment, Result
21
21
  from ..models.config.qaseconfig import QaseConfig
22
22
  from ..models.step import StepType, Step
23
23
 
@@ -53,7 +53,7 @@ class ApiV2Client(ApiV1Client):
53
53
  create_results_request_v2=CreateResultsRequestV2(results=results_to_send))
54
54
  self.logger.log_debug(f"Results for run {run_id} sent successfully")
55
55
 
56
- def _prepare_result(self, project_code: str, result: InternalResult) -> ResultCreate:
56
+ def _prepare_result(self, project_code: str, result: Result) -> ResultCreate:
57
57
  attached = []
58
58
  if result.attachments:
59
59
  for attachment in result.attachments:
@@ -76,7 +76,7 @@ class ApiV2Client(ApiV1Client):
76
76
  result_model_v2 = ResultCreate(
77
77
  title=result.get_title(),
78
78
  signature=result.signature,
79
- testops_id=result.get_testops_id(),
79
+ testops_ids=result.get_testops_ids(),
80
80
  execution=ResultExecution(status=result.execution.status, duration=result.execution.duration,
81
81
  start_time=result.execution.start_time, end_time=result.execution.end_time,
82
82
  stacktrace=result.execution.stacktrace, thread=result.execution.thread),
qase/commons/config.py CHANGED
@@ -83,11 +83,6 @@ class ConfigManager:
83
83
  testops.get("defect")
84
84
  )
85
85
 
86
- if testops.get("useV2"):
87
- self.config.testops.set_use_v2(
88
- testops.get("useV2")
89
- )
90
-
91
86
  if testops.get("plan"):
92
87
  plan = testops.get("plan")
93
88
 
@@ -1,4 +1,4 @@
1
- from .result import Result, InternalResult, Field
1
+ from .result import Result, Field
2
2
  from .run import Run
3
3
  from .attachment import Attachment
4
4
  from .relation import Relation
@@ -12,6 +12,5 @@ __all__ = [
12
12
  Relation,
13
13
  Step,
14
14
  Runtime,
15
- Field,
16
- InternalResult
15
+ Field
17
16
  ]
@@ -20,13 +20,9 @@ class TestopsConfig(BaseModel):
20
20
  self.batch = BatchConfig()
21
21
  self.plan = PlanConfig()
22
22
  self.defect = False
23
- self.use_v2 = True
24
23
 
25
24
  def set_project(self, project: str):
26
25
  self.project = project
27
26
 
28
27
  def set_defect(self, defect):
29
28
  self.defect = QaseUtils.parse_bool(defect)
30
-
31
- def set_use_v2(self, use_v2):
32
- self.use_v2 = QaseUtils.parse_bool(use_v2)
@@ -1,4 +1,3 @@
1
- import copy
2
1
  import time
3
2
  import uuid
4
3
 
@@ -71,7 +70,7 @@ class Result(BaseModel):
71
70
  self.title: str = title
72
71
  self.signature: str = signature
73
72
  self.run_id: Optional[str] = None
74
- self.testops_id: Optional[List[int]] = None
73
+ self.testops_ids: Optional[List[int]] = None
75
74
  self.execution: Type[Execution] = Execution()
76
75
  self.fields: Dict[Type[Field]] = {}
77
76
  self.attachments: List[Attachment] = []
@@ -119,96 +118,11 @@ class Result(BaseModel):
119
118
  return self.fields[name]
120
119
  return None
121
120
 
122
- def get_testops_id(self) -> Optional[List[int]]:
123
- return self.testops_id
121
+ def get_testops_ids(self) -> Optional[List[int]]:
122
+ return self.testops_ids
124
123
 
125
124
  def get_duration(self) -> int:
126
125
  return self.execution.duration
127
126
 
128
127
  def set_run_id(self, run_id: str) -> None:
129
128
  self.run_id = run_id
130
-
131
-
132
- class InternalResult(BaseModel):
133
- def __init__(self, title: str, signature: str) -> None:
134
- self.id: str = str(uuid.uuid4())
135
- self.title: str = title
136
- self.signature: str = signature
137
- self.run_id: Optional[str] = None
138
- self.testops_id: Optional[int] = None
139
- self.execution: Type[Execution] = Execution()
140
- self.fields: Dict[Type[Field]] = {}
141
- self.attachments: List[Attachment] = []
142
- self.steps: List[Type[Step]] = []
143
- self.params: Optional[dict] = {}
144
- self.param_groups: Optional[List[List[str]]] = []
145
- self.author: Optional[str] = None
146
- self.relations: Type[Relation] = None
147
- self.muted: bool = False
148
- self.message: Optional[str] = None
149
- QaseUtils.get_host_data()
150
-
151
- def add_message(self, message: str) -> None:
152
- self.message = message
153
-
154
- def add_field(self, field: Type[Field]) -> None:
155
- self.fields[field.name] = field.value
156
-
157
- def add_steps(self, steps: List[Type[Step]]) -> None:
158
- self.steps = QaseUtils().build_tree(steps)
159
-
160
- def add_attachment(self, attachment: Attachment) -> None:
161
- self.attachments.append(attachment)
162
-
163
- def add_param(self, key: str, value: str) -> None:
164
- self.params[key] = value
165
-
166
- def add_param_groups(self, values: List[str]) -> None:
167
- self.param_groups.append(values)
168
-
169
- def set_relation(self, relation: Relation) -> None:
170
- self.relations = relation
171
-
172
- def get_status(self) -> Optional[str]:
173
- return self.execution.status
174
-
175
- def get_id(self) -> str:
176
- return self.id
177
-
178
- def get_title(self) -> str:
179
- return self.title
180
-
181
- def get_field(self, name: str) -> Optional[Type[Field]]:
182
- if name in self.fields:
183
- return self.fields[name]
184
- return None
185
-
186
- def get_testops_id(self) -> Optional[int]:
187
- return self.testops_id
188
-
189
- def get_duration(self) -> int:
190
- return self.execution.duration
191
-
192
- def set_run_id(self, run_id: str) -> None:
193
- self.run_id = run_id
194
-
195
- @classmethod
196
- def convert_from_result(cls, result: Result, testops_id: Optional[int] = None):
197
- int_result = cls(result.title, result.signature)
198
-
199
- int_result.id = result.id
200
- int_result.title = result.title
201
- int_result.signature = result.signature
202
- int_result.run_id = result.run_id
203
- int_result.testops_id = testops_id
204
- int_result.execution = copy.deepcopy(result.execution)
205
- int_result.fields = result.fields
206
- int_result.attachments = result.attachments
207
- int_result.steps = result.steps
208
- int_result.params = result.params
209
- int_result.author = result.author
210
- int_result.relations = result.relations
211
- int_result.muted = result.muted
212
- int_result.message = result.message
213
-
214
- return int_result
@@ -6,10 +6,12 @@ from ..logger import Logger
6
6
  from .report import QaseReport
7
7
  from .testops import QaseTestOps
8
8
 
9
- from ..models import InternalResult, Result, Attachment, Runtime
9
+ from ..models import Result, Attachment, Runtime
10
10
  from ..models.config.qaseconfig import Mode
11
11
  from typing import Union, List
12
12
 
13
+ from ..util import get_host_info
14
+
13
15
  """
14
16
  CoreReporter is a facade for all reporters and it is used to initialize and manage them.
15
17
  It is also used to pass configuration and logger to reporters, handle fallback logic and error handling.
@@ -17,7 +19,7 @@ from typing import Union, List
17
19
 
18
20
 
19
21
  class QaseCoreReporter:
20
- def __init__(self, config: ConfigManager):
22
+ def __init__(self, config: ConfigManager, framework: Union[str, None] = None, reporter_name: Union[str, None] = None):
21
23
  config.validate_config()
22
24
  self.config = config.config
23
25
  self.logger = Logger(self.config.debug)
@@ -30,6 +32,9 @@ class QaseCoreReporter:
30
32
 
31
33
  self.logger.log_debug(f"Config: {self.config}")
32
34
 
35
+ host_data = get_host_info(framework, reporter_name)
36
+ self.logger.log_debug(f"Host data: {host_data}")
37
+
33
38
  # Reading reporter mode from config file
34
39
  mode = self.config.mode
35
40
 
@@ -79,20 +84,8 @@ class QaseCoreReporter:
79
84
  try:
80
85
  ts = time.time()
81
86
  self.logger.log_debug(f"Adding result {result}")
82
- ids = result.get_testops_id()
83
-
84
- if ids is None:
85
- int_result = InternalResult.convert_from_result(result)
86
- self.reporter.add_result(int_result)
87
- else:
88
- first = True
89
- for testops_id in ids:
90
- int_result = InternalResult.convert_from_result(result, testops_id)
91
- if not first:
92
- int_result.execution.duration = 0
93
- else:
94
- first = False
95
- self.reporter.add_result(int_result)
87
+
88
+ self.reporter.add_result(result)
96
89
 
97
90
  self.logger.log_debug(f"Result {result.get_title()} added")
98
91
  self.overhead += time.time() - ts
@@ -3,7 +3,7 @@ import os
3
3
  import shutil
4
4
  import json
5
5
  import re
6
- from ..models import InternalResult, Run, Attachment
6
+ from ..models import Result, Run, Attachment
7
7
  from .. import QaseUtils, Logger
8
8
  from ..models.config.connection import Format
9
9
  from ..models.config.qaseconfig import QaseConfig
@@ -40,7 +40,7 @@ class QaseReport:
40
40
  def complete_worker(self):
41
41
  pass
42
42
 
43
- def add_result(self, result: InternalResult):
43
+ def add_result(self, result: Result):
44
44
  result.set_run_id(self.run_id)
45
45
  for attachment in result.attachments:
46
46
  self._persist_attachment(attachment)
@@ -91,7 +91,7 @@ class QaseReport:
91
91
  self._persist_attachments_in_steps(step.steps)
92
92
 
93
93
  # Method saves result to a file
94
- def _store_result(self, result: InternalResult):
94
+ def _store_result(self, result: Result):
95
95
  self._store_object(result, self.report_path + "/results/", result.id)
96
96
 
97
97
  def _check_report_path(self):
@@ -2,11 +2,11 @@ import threading
2
2
  import urllib.parse
3
3
 
4
4
  from datetime import datetime
5
- from typing import List
5
+ from typing import List, Union
6
6
  from .. import Logger, ReporterException
7
- from ..client.api_v1_client import ApiV1Client
7
+ from ..client.api_v2_client import ApiV2Client
8
8
  from ..client.base_api_client import BaseApiClient
9
- from ..models import InternalResult
9
+ from ..models import Result
10
10
  from ..models.config.qaseconfig import QaseConfig
11
11
 
12
12
  DEFAULT_BATCH_SIZE = 200
@@ -69,10 +69,7 @@ class QaseTestOps:
69
69
  self.client.get_project(self.project_code)
70
70
 
71
71
  def _prepare_client(self) -> BaseApiClient:
72
- if self.config.testops.use_v2:
73
- from ..client.api_v2_client import ApiV2Client
74
- return ApiV2Client(self.config, self.logger)
75
- return ApiV1Client(self.config, self.logger)
72
+ return ApiV2Client(self.config, self.logger)
76
73
 
77
74
  def _send_results_threaded(self, results):
78
75
  try:
@@ -129,9 +126,9 @@ class QaseTestOps:
129
126
  if len(self.results) > 0:
130
127
  self._send_results()
131
128
 
132
- def add_result(self, result: InternalResult) -> None:
129
+ def add_result(self, result: Result) -> None:
133
130
  if result.get_status() == 'failed':
134
- self.__show_link(result.testops_id, result.title)
131
+ self.__show_link(result.testops_ids, result.title)
135
132
  self.results.append(result)
136
133
  if len(self.results) >= self.batch_size:
137
134
  self._send_results()
@@ -142,15 +139,14 @@ class QaseTestOps:
142
139
  def set_results(self, results) -> None:
143
140
  self.results = results
144
141
 
145
- def __show_link(self, id, title: str):
146
- link = self.__prepare_link(id, title)
142
+ def __show_link(self, ids: Union[None, List[int]], title: str):
143
+ link = self.__prepare_link(ids, title)
147
144
  self.logger.log(f"See why this test failed: {link}", "info")
148
145
 
149
- def __prepare_link(self, id, title: str):
146
+ def __prepare_link(self, ids: Union[None, List[int]], title: str):
150
147
  link = f"{self.__baseUrl}/run/{self.project_code}/dashboard/{self.run_id}?source=logs&status=%5B2%5D&search="
151
- if id:
152
- return f"{link}{id}`"
153
-
148
+ if ids is not None and len(ids) > 0:
149
+ return f"{link}{ids[0]}"
154
150
  return f"{link}{urllib.parse.quote_plus(title)}"
155
151
 
156
152
  @staticmethod
@@ -0,0 +1,9 @@
1
+ from .host_data import get_host_info
2
+
3
+ __all__ = [
4
+ get_host_info
5
+ ]
6
+
7
+
8
+
9
+
@@ -0,0 +1,140 @@
1
+ import os
2
+ import platform
3
+ import subprocess
4
+ import json
5
+ import re
6
+ import sys
7
+ from typing import Optional, Dict
8
+
9
+ HostData = Dict[str, str]
10
+
11
+
12
+ def exec_command(command: str, default_value: str = "") -> str:
13
+ try:
14
+ result = subprocess.run(
15
+ command,
16
+ shell=True,
17
+ check=True,
18
+ stdout=subprocess.PIPE,
19
+ stderr=subprocess.PIPE,
20
+ text=True
21
+ )
22
+ return result.stdout.strip()
23
+ except Exception as e:
24
+ print(f"Error executing command '{command}': {e}", file=sys.stderr)
25
+ return default_value
26
+
27
+
28
+ def get_detailed_os_info() -> str:
29
+ system = platform.system().lower()
30
+ try:
31
+ if system == "windows":
32
+ return exec_command("ver")
33
+ elif system == "darwin":
34
+ return exec_command("sw_vers -productVersion")
35
+ else:
36
+ try:
37
+ if os.path.exists("/etc/os-release"):
38
+ with open("/etc/os-release", "r") as f:
39
+ os_release = f.read()
40
+ match = re.search(r'PRETTY_NAME="(.+?)"', os_release)
41
+ if match:
42
+ return match.group(1)
43
+ except Exception:
44
+ pass
45
+ return platform.release()
46
+ except Exception as e:
47
+ print(f"Error getting detailed OS info: {e}", file=sys.stderr)
48
+ return platform.release()
49
+
50
+
51
+ def get_package_version(package_name: Optional[str]) -> Optional[str]:
52
+ if not package_name:
53
+ return ""
54
+ try:
55
+ pip_output = exec_command(f"pip show {package_name}")
56
+ if pip_output:
57
+ version_match = re.search(r'Version:\s*(\S+)', pip_output)
58
+ if version_match:
59
+ return version_match.group(1)
60
+ pip_list_output = exec_command("pip list --format=json")
61
+ if pip_list_output:
62
+ packages = json.loads(pip_list_output)
63
+ for pkg in packages:
64
+ if pkg.get("name", "").lower() == package_name.lower():
65
+ return pkg.get("version")
66
+ try:
67
+ import importlib.metadata
68
+ return importlib.metadata.version(package_name)
69
+ except (ImportError, importlib.metadata.PackageNotFoundError):
70
+ pass
71
+ return ""
72
+ except Exception as e:
73
+ print(f"Error getting version for package {package_name}: {e}", file=sys.stderr)
74
+ return ""
75
+
76
+
77
+ def find_package_in_requirements(package_name: Optional[str]) -> Optional[str]:
78
+ if not package_name:
79
+ return ""
80
+ try:
81
+ possible_req_files = ['requirements.txt', 'requirements/base.txt', 'requirements/dev.txt']
82
+ for req_file in possible_req_files:
83
+ if os.path.exists(req_file):
84
+ with open(req_file, 'r') as f:
85
+ for line in f:
86
+ if line.startswith(package_name):
87
+ version_match = re.search(r'[=<>~]{1,2}([\d.]+)', line)
88
+ if version_match:
89
+ return version_match.group(1)
90
+ return ""
91
+ except Exception as e:
92
+ print(f"Error reading requirements for {package_name}: {e}", file=sys.stderr)
93
+ return ""
94
+
95
+
96
+ def get_host_info(framework: Optional[str], reporter_name: Optional[str]) -> HostData:
97
+ try:
98
+ python_version = platform.python_version()
99
+ pip_version = exec_command("pip --version")
100
+ if pip_version:
101
+ pip_match = re.search(r'pip\s+(\S+)', pip_version)
102
+ pip_version = pip_match.group(1) if pip_match else ""
103
+ framework_version = get_package_version(framework) or find_package_in_requirements(framework) or ""
104
+ reporter_version = get_package_version(reporter_name) or find_package_in_requirements(reporter_name) or ""
105
+ commons_version = get_package_version("qase-python-commons") or find_package_in_requirements(
106
+ "qase-python-commons") or ""
107
+ api_client_version_1 = get_package_version("qase-api-client") or find_package_in_requirements(
108
+ "qase-api-client") or ""
109
+ api_client_version_2 = get_package_version("qase-api-v2-client") or find_package_in_requirements(
110
+ "qase-api-v2-client") or ""
111
+ return {
112
+ "system": platform.system().lower(),
113
+ "machineName": platform.node(),
114
+ "release": platform.release(),
115
+ "version": get_detailed_os_info(),
116
+ "arch": platform.machine(),
117
+ "python": python_version,
118
+ "pip": pip_version,
119
+ "framework": framework_version,
120
+ "reporter": reporter_version,
121
+ "commons": commons_version,
122
+ "apiClientV1": api_client_version_1,
123
+ "apiClientV2": api_client_version_2
124
+ }
125
+ except Exception as e:
126
+ print(f"Error getting host info: {e}", file=sys.stderr)
127
+ return {
128
+ "system": platform.system().lower(),
129
+ "machineName": platform.node(),
130
+ "release": platform.release(),
131
+ "version": "",
132
+ "arch": platform.machine(),
133
+ "python": "",
134
+ "pip": "",
135
+ "framework": "",
136
+ "reporter": "",
137
+ "commons": "",
138
+ "apiClientV1": "",
139
+ "apiClientV2": ""
140
+ }
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: qase-python-commons
3
- Version: 3.3.1
3
+ Version: 3.4.0
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
@@ -22,8 +22,8 @@ Requires-Python: >=3.7
22
22
  Description-Content-Type: text/markdown
23
23
  Requires-Dist: certifi>=2024.2.2
24
24
  Requires-Dist: attrs>=23.2.0
25
- Requires-Dist: qase-api-client~=1.1.1
26
- Requires-Dist: qase-api-v2-client~=1.1.0
25
+ Requires-Dist: qase-api-client~=1.2.0
26
+ Requires-Dist: qase-api-v2-client~=1.2.0
27
27
  Requires-Dist: more_itertools
28
28
  Provides-Extra: testing
29
29
  Requires-Dist: pytest; extra == "testing"
@@ -1,17 +1,17 @@
1
1
  qase/commons/__init__.py,sha256=3HI65PJES4Q6YvtkSuRPh6tZboTETJo8wbdHlNYaePU,323
2
- qase/commons/config.py,sha256=dtF8JPHeGe4SxpR36WbvzX76yP-0nP43NpCf6sh9d-I,9245
2
+ qase/commons/config.py,sha256=oRIjwNogSRCKQEXzi3AvF-Rg4btEFzR521P-Degp7x4,9052
3
3
  qase/commons/loader.py,sha256=-MMY4HgSI6q1xq3NaJoq_w4liM73qdFKjYLVCT1E7Pc,1064
4
4
  qase/commons/logger.py,sha256=K_8kE0EqpFbF2RbRU5TBw4Hl6GfK6PacpwkRXqHabl0,1234
5
5
  qase/commons/utils.py,sha256=OOr6kQ5hPZEyHXwbwiTOTkonRxmmtkyZPPGqXQKp5vY,2799
6
- qase/commons/client/api_v1_client.py,sha256=rhPtQJZXt8aQjMpNoe910RC7cU6esdR1vyBjk5TOig8,12852
7
- qase/commons/client/api_v2_client.py,sha256=M17n7XWbLCNA83eUpvUXkf3Ai9KSxiDLygQUCUSpJns,8570
6
+ qase/commons/client/api_v1_client.py,sha256=9dJsUUM81LhmA2FWTGjWxS9tnBDcf9B6KFVZjCJDB98,6168
7
+ qase/commons/client/api_v2_client.py,sha256=X_T9LrDf4NIYjuv8nuVnNxENZDq1fWBJTJ5KsZLF5WU,8556
8
8
  qase/commons/client/base_api_client.py,sha256=H8JnjqSrBFNfghDd3T3zxYOyKYHYe9QiJQXol1JqdQk,2613
9
9
  qase/commons/exceptions/reporter.py,sha256=dP-Mwcq8HKBOjgu3YqhyULDmDGU09BmT6Fh9HjICaUc,45
10
- qase/commons/models/__init__.py,sha256=9RH4aKBEvE6vHLcb_o_ngn2jNfGfcwpQ4u7ABEis85k,308
10
+ qase/commons/models/__init__.py,sha256=FTt5dYASBX4r6-tQi-_JAUVx4uvJs9GTxROdAZEV6Jo,272
11
11
  qase/commons/models/attachment.py,sha256=Rjq1mAP11uk7TN2RrtImntw6DUMV7U0R-44TYj8O5j0,1432
12
12
  qase/commons/models/basemodel.py,sha256=nyDSXhpQUecKdzhB-eWqujmso20oXB9p_42qpOsGVuQ,213
13
13
  qase/commons/models/relation.py,sha256=HymHeh1uBcoQnTs4Vra7WJ_KFkhryj5o7cShjoGQImI,511
14
- qase/commons/models/result.py,sha256=59uEHgobscRiXJW8KoNoKyfHXnlj8S1zezoh4Gy3joQ,6819
14
+ qase/commons/models/result.py,sha256=0BWFGRacYtCQdno85kqFH2VAwwDh2ZWMm7WbZS77fxE,3968
15
15
  qase/commons/models/run.py,sha256=KkplvlHJNvVLORzVkdz5mHsLFBTUAdtuEYCqCy_RcvU,2469
16
16
  qase/commons/models/runtime.py,sha256=mfK-mOViD1orXOx-jP6nIgtnN0tUmRYY1aMH0qFDmXA,1287
17
17
  qase/commons/models/step.py,sha256=M-btRYZ4febYavDhwFqmKcCdLAgrhtuv7E_M3TKZdfg,4281
@@ -23,17 +23,19 @@ qase/commons/models/config/plan.py,sha256=JbAY7qfGXYreXOLa32OLxw6z0paeCCf87-2b1m
23
23
  qase/commons/models/config/qaseconfig.py,sha256=-tuTsd6s4YSAkSca7vvNaFZbqhbWkxuw-mjeqhwmjlc,1722
24
24
  qase/commons/models/config/report.py,sha256=g3Z2B3Tewaasjc1TMj2mkXxz0Zc1C39sHeTXH0MRM2Y,497
25
25
  qase/commons/models/config/run.py,sha256=QPOPq0__J5Pey2MfjnEgTuCCDYgjog-rjMJBjiMBgz4,540
26
- qase/commons/models/config/testops.py,sha256=N9fqcDEgfVr2n1xoNoNU2BwlD37Wf10MYZhKvZVSDkk,821
26
+ qase/commons/models/config/testops.py,sha256=_GT0Px04y2JqhmXdRbqC6vvSm4yRIU74L9Sr6eiYVLU,708
27
27
  qase/commons/profilers/__init__.py,sha256=GhKT5hRbHbhAC4GhdyChA8XoAsGQOnIb8S2Z4-fdS7Q,222
28
28
  qase/commons/profilers/db.py,sha256=Am1tvvLgJq4_A8JsuSeBGf47BD2lnSX-5KiMjSgr-Ko,391
29
29
  qase/commons/profilers/network.py,sha256=zKNBnTQG4BMg8dn8O--tQzQLpu-qs5ADhHEnqIas0gM,4950
30
30
  qase/commons/profilers/sleep.py,sha256=HT6h0R-2XHZAoBYRxS2T_KC8RrnEoVjP7MXusaE4Nec,1624
31
31
  qase/commons/reporters/__init__.py,sha256=J0aNLzb_MPPT_zF8BtX_w9nj_U7Ad06RGpyWK5Pxq1o,169
32
- qase/commons/reporters/core.py,sha256=eLn24JWPK6MoqGonPx7YnjVtpK1gSLM379Fmlxea3LE,8603
33
- qase/commons/reporters/report.py,sha256=50PDOBZJZA3_Z45bggBtkNfntycUkO9RaGYnzjdy8uA,4853
34
- qase/commons/reporters/testops.py,sha256=9GOXHTmuAX-kglXINyi4z0ul01IqaYQmJtTX-S2A2ek,6280
32
+ qase/commons/reporters/core.py,sha256=sirQ9CwLNtxLCrqXEm9dOQf8N9zDIVXofo9IKc0Sh88,8230
33
+ qase/commons/reporters/report.py,sha256=zyGeUOcJCOEoGtsewCOf3kypbPttJH7EM-LmZ_Y5huY,4829
34
+ qase/commons/reporters/testops.py,sha256=JRsAGZPtV8kr_fhsgM0cHqPdh9Ojp2qtpJvHGW9FLq4,6200
35
+ qase/commons/util/__init__.py,sha256=0sRRfrMOIPCHpk9tXM94Pj10qrk18B61qEcbLpRjw_I,74
36
+ qase/commons/util/host_data.py,sha256=n8o5PDs8kELCZZ5GR7Jug6LsgZHWJudU7iRmZHRdrlw,5264
35
37
  qase/commons/validators/base.py,sha256=wwSn-4YiuXtfGMGnSKgo9Vm5hAKevVmmfd2Ro6Q7MYQ,173
36
- qase_python_commons-3.3.1.dist-info/METADATA,sha256=twi8cruaJ7ETSS9D5d9sVLk7nNFA8na6KHHHogzo4yo,1857
37
- qase_python_commons-3.3.1.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
38
- qase_python_commons-3.3.1.dist-info/top_level.txt,sha256=Mn5aFk7H7Uia4s1NRDsvebu8vCrFy9nOuRIBfkIY5kQ,5
39
- qase_python_commons-3.3.1.dist-info/RECORD,,
38
+ qase_python_commons-3.4.0.dist-info/METADATA,sha256=mZqLZiNqmif93rQkb9RyYPH1lEn9burKxCVnA8BrshE,1857
39
+ qase_python_commons-3.4.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
40
+ qase_python_commons-3.4.0.dist-info/top_level.txt,sha256=Mn5aFk7H7Uia4s1NRDsvebu8vCrFy9nOuRIBfkIY5kQ,5
41
+ qase_python_commons-3.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.1.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5