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

@@ -3,7 +3,7 @@ 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
6
+ AttachmentGet, RunCreate, ConfigurationsApi, ConfigurationCreate, ConfigurationGroupCreate
7
7
  from qase.api_client_v1.configuration import Configuration
8
8
  from .. import Logger
9
9
  from .base_api_client import BaseApiClient
@@ -11,6 +11,7 @@ from ..exceptions.reporter import ReporterException
11
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.config.testops import ConfigurationValue
14
15
 
15
16
 
16
17
  class ApiV1Client(BaseApiClient):
@@ -66,6 +67,73 @@ class ApiV1Client(BaseApiClient):
66
67
  self.logger.log("Exception when calling EnvironmentsApi->get_environments: %s\n" % e, "error")
67
68
  raise ReporterException(e)
68
69
 
70
+ def get_configurations(self, project_code: str):
71
+ """Get all configurations for the project"""
72
+ try:
73
+ self.logger.log_debug(f"Getting configurations for project {project_code}")
74
+ api_instance = ConfigurationsApi(self.client)
75
+ response = api_instance.get_configurations(code=project_code)
76
+ if hasattr(response, 'result') and hasattr(response.result, 'entities'):
77
+ return response.result.entities
78
+ return []
79
+ except Exception as e:
80
+ self.logger.log(f"Exception when calling ConfigurationsApi->get_configurations: {e}", "error")
81
+ return []
82
+
83
+ def find_or_create_configuration(self, project_code: str, config_value: ConfigurationValue) -> Union[int, None]:
84
+ """Find existing configuration or create new one if createIfNotExists is True"""
85
+ try:
86
+ configurations = self.get_configurations(project_code)
87
+
88
+ # Search for existing configuration
89
+ for group in configurations:
90
+ if hasattr(group, 'configurations'):
91
+ for config in group.configurations:
92
+ # API returns configurations with 'title' field, not 'name' and 'value'
93
+ # We need to match group.title with config_value.name and config.title with config_value.value
94
+ config_title = config.title if hasattr(config, 'title') else 'No title'
95
+ group_title = group.title if hasattr(group, 'title') else 'No title'
96
+
97
+ if (group_title == config_value.name and config_title == config_value.value):
98
+ return config.id
99
+
100
+ # Configuration not found
101
+ if not self.config.testops.configurations.create_if_not_exists:
102
+ return None
103
+
104
+ # Create new configuration
105
+ # First, try to find existing group or create new one
106
+ group_id = None
107
+ for group in configurations:
108
+ if hasattr(group, 'title') and group.title == config_value.name:
109
+ group_id = group.id
110
+ break
111
+
112
+ if group_id is None:
113
+ # Create new group
114
+ group_create = ConfigurationGroupCreate(title=config_value.name)
115
+ group_response = ConfigurationsApi(self.client).create_configuration_group(
116
+ code=project_code,
117
+ configuration_group_create=group_create
118
+ )
119
+ group_id = group_response.result.id
120
+
121
+ # Create configuration in the group
122
+ config_create = ConfigurationCreate(
123
+ title=config_value.value,
124
+ group_id=group_id
125
+ )
126
+ config_response = ConfigurationsApi(self.client).create_configuration(
127
+ code=project_code,
128
+ configuration_create=config_create
129
+ )
130
+ config_id = config_response.result.id
131
+ return config_id
132
+
133
+ except Exception as e:
134
+ self.logger.log(f"Error at finding/creating configuration {config_value.name}={config_value.value}: {e}", "error")
135
+ return None
136
+
69
137
  def complete_run(self, project_code: str, run_id: int) -> None:
70
138
  api_runs = RunsApi(self.client)
71
139
  self.logger.log_debug(f"Completing run {run_id}")
@@ -94,6 +162,15 @@ class ApiV1Client(BaseApiClient):
94
162
 
95
163
  def create_test_run(self, project_code: str, title: str, description: str, plan_id=None,
96
164
  environment_id=None) -> str:
165
+ # Process configurations
166
+ configuration_ids = []
167
+
168
+ if self.config.testops.configurations and self.config.testops.configurations.values:
169
+ for config_value in self.config.testops.configurations.values:
170
+ config_id = self.find_or_create_configuration(project_code, config_value)
171
+ if config_id:
172
+ configuration_ids.append(config_id)
173
+
97
174
  kwargs = dict(
98
175
  title=title,
99
176
  description=description,
@@ -103,7 +180,11 @@ class ApiV1Client(BaseApiClient):
103
180
  start_time=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
104
181
  tags=self.config.testops.run.tags
105
182
  )
106
- self.logger.log_debug(f"Creating test run with parameters: {kwargs}")
183
+
184
+ # Add configurations if any found
185
+ if configuration_ids:
186
+ kwargs['configurations'] = configuration_ids
187
+
107
188
  try:
108
189
  result = RunsApi(self.client).create_run(
109
190
  code=project_code,
qase/commons/config.py CHANGED
@@ -128,6 +128,28 @@ class ConfigManager:
128
128
  self.config.testops.batch.set_size(
129
129
  batch.get("size"))
130
130
 
131
+ if testops.get("configurations"):
132
+ configurations = testops.get("configurations")
133
+
134
+ if configurations.get("values"):
135
+ values = configurations.get("values")
136
+ for value in values:
137
+ if value.get("name") and value.get("value"):
138
+ self.config.testops.configurations.add_value(
139
+ value.get("name"), value.get("value"))
140
+
141
+ if configurations.get("createIfNotExists") is not None:
142
+ self.config.testops.configurations.set_create_if_not_exists(
143
+ configurations.get("createIfNotExists"))
144
+
145
+ if testops.get("statusFilter"):
146
+ status_filter = testops.get("statusFilter")
147
+ if isinstance(status_filter, list):
148
+ self.config.testops.set_status_filter(status_filter)
149
+ elif isinstance(status_filter, str):
150
+ # Parse comma-separated string
151
+ self.config.testops.set_status_filter([s.strip() for s in status_filter.split(',')])
152
+
131
153
  if config.get("report"):
132
154
  report = config.get("report")
133
155
 
@@ -235,6 +257,23 @@ class ConfigManager:
235
257
  if key == 'QASE_TESTOPS_BATCH_SIZE':
236
258
  self.config.testops.batch.set_size(value)
237
259
 
260
+ if key == 'QASE_TESTOPS_CONFIGURATIONS_VALUES':
261
+ # Parse configurations from environment variable
262
+ # Format: "group1=value1,group2=value2"
263
+ if value:
264
+ config_pairs = value.split(',')
265
+ for pair in config_pairs:
266
+ if '=' in pair:
267
+ name, config_value = pair.split('=', 1)
268
+ self.config.testops.configurations.add_value(name.strip(), config_value.strip())
269
+
270
+ if key == 'QASE_TESTOPS_CONFIGURATIONS_CREATE_IF_NOT_EXISTS':
271
+ self.config.testops.configurations.set_create_if_not_exists(value)
272
+
273
+ if key == 'QASE_TESTOPS_STATUS_FILTER':
274
+ # Parse comma-separated string
275
+ self.config.testops.set_status_filter([s.strip() for s in value.split(',')])
276
+
238
277
  if key == 'QASE_REPORT_DRIVER':
239
278
  self.config.report.set_driver(value)
240
279
 
@@ -4,6 +4,40 @@ from .plan import PlanConfig
4
4
  from .run import RunConfig
5
5
  from ..basemodel import BaseModel
6
6
  from ... import QaseUtils
7
+ from typing import List
8
+
9
+
10
+ class ConfigurationValue(BaseModel):
11
+ name: str = None
12
+ value: str = None
13
+
14
+ def __init__(self, name: str = None, value: str = None):
15
+ self.name = name
16
+ self.value = value
17
+
18
+ def set_name(self, name: str):
19
+ self.name = name
20
+
21
+ def set_value(self, value: str):
22
+ self.value = value
23
+
24
+
25
+ class ConfigurationsConfig(BaseModel):
26
+ values: List[ConfigurationValue] = None
27
+ create_if_not_exists: bool = None
28
+
29
+ def __init__(self):
30
+ self.values = []
31
+ self.create_if_not_exists = False
32
+
33
+ def set_values(self, values: List[ConfigurationValue]):
34
+ self.values = values
35
+
36
+ def set_create_if_not_exists(self, create_if_not_exists):
37
+ self.create_if_not_exists = QaseUtils.parse_bool(create_if_not_exists)
38
+
39
+ def add_value(self, name: str, value: str):
40
+ self.values.append(ConfigurationValue(name=name, value=value))
7
41
 
8
42
 
9
43
  class TestopsConfig(BaseModel):
@@ -13,16 +47,23 @@ class TestopsConfig(BaseModel):
13
47
  run: RunConfig = None
14
48
  plan: PlanConfig = None
15
49
  batch: BatchConfig = None
50
+ configurations: ConfigurationsConfig = None
51
+ status_filter: List[str] = None
16
52
 
17
53
  def __init__(self):
18
54
  self.api = ApiConfig()
19
55
  self.run = RunConfig()
20
56
  self.batch = BatchConfig()
21
57
  self.plan = PlanConfig()
58
+ self.configurations = ConfigurationsConfig()
22
59
  self.defect = False
60
+ self.status_filter = []
23
61
 
24
62
  def set_project(self, project: str):
25
63
  self.project = project
26
64
 
27
65
  def set_defect(self, defect):
28
66
  self.defect = QaseUtils.parse_bool(defect)
67
+
68
+ def set_status_filter(self, status_filter: List[str]):
69
+ self.status_filter = status_filter
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qase-python-commons
3
- Version: 3.5.2
3
+ Version: 3.5.4
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.2.0
26
- Requires-Dist: qase-api-v2-client~=1.2.0
25
+ Requires-Dist: qase-api-client~=1.2.3
26
+ Requires-Dist: qase-api-v2-client~=1.2.2
27
27
  Requires-Dist: more_itertools
28
28
  Provides-Extra: testing
29
29
  Requires-Dist: pytest; extra == "testing"
@@ -1,9 +1,9 @@
1
1
  qase/commons/__init__.py,sha256=3HI65PJES4Q6YvtkSuRPh6tZboTETJo8wbdHlNYaePU,323
2
- qase/commons/config.py,sha256=tRxuaRaTrng5q0fiknlmNSK8I0Rd9Oj_4yPcdKSG1j8,10117
2
+ qase/commons/config.py,sha256=X7PsggEZRPbrE1eb1JBM0ZmhtR4k06dqaFzcNmTG-Rw,12394
3
3
  qase/commons/loader.py,sha256=-MMY4HgSI6q1xq3NaJoq_w4liM73qdFKjYLVCT1E7Pc,1064
4
4
  qase/commons/logger.py,sha256=KEQr8G0eFZxlI3LJIaaNWOKD8o3NhKsZD06swXFn3FI,1313
5
5
  qase/commons/utils.py,sha256=utPRoYyThLs2tgD1lmjkwJ9KZuE7ZjliUiZkJJWFca0,3330
6
- qase/commons/client/api_v1_client.py,sha256=8neslPdfbtICTtVZZ6Dqd7VFudVz0kdG30CgeP0PVwk,6215
6
+ qase/commons/client/api_v1_client.py,sha256=cxECfjM68KrjRVNr6nsTY4uOc_bBlo5qQJ10C5roEyk,10130
7
7
  qase/commons/client/api_v2_client.py,sha256=GsIrXJcBw6GtzvJjbjMYa0tvUIxEEe4pALRN2LBMKPM,9043
8
8
  qase/commons/client/base_api_client.py,sha256=qiK93rXXeLOmp6e3cgsLA4lmrV_rCL1BIi_xUeqFrDE,2613
9
9
  qase/commons/exceptions/reporter.py,sha256=dP-Mwcq8HKBOjgu3YqhyULDmDGU09BmT6Fh9HjICaUc,45
@@ -23,7 +23,7 @@ qase/commons/models/config/plan.py,sha256=JbAY7qfGXYreXOLa32OLxw6z0paeCCf87-2b1m
23
23
  qase/commons/models/config/qaseconfig.py,sha256=ho_22bcouoJb1f68EGffeBs_ovK3DVyfbFrYXwQFrWs,1918
24
24
  qase/commons/models/config/report.py,sha256=g3Z2B3Tewaasjc1TMj2mkXxz0Zc1C39sHeTXH0MRM2Y,497
25
25
  qase/commons/models/config/run.py,sha256=UeZ_1khhKSLbER3pzAl__5iKfDMErvUsXikelc31iKo,682
26
- qase/commons/models/config/testops.py,sha256=_GT0Px04y2JqhmXdRbqC6vvSm4yRIU74L9Sr6eiYVLU,708
26
+ qase/commons/models/config/testops.py,sha256=rJ9wW-VMt-5XaoPdRUKeM9rlV0eTiRINEhEulFVczv0,1893
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
@@ -31,11 +31,11 @@ qase/commons/profilers/sleep.py,sha256=HT6h0R-2XHZAoBYRxS2T_KC8RrnEoVjP7MXusaE4N
31
31
  qase/commons/reporters/__init__.py,sha256=J0aNLzb_MPPT_zF8BtX_w9nj_U7Ad06RGpyWK5Pxq1o,169
32
32
  qase/commons/reporters/core.py,sha256=JyFMGhcDOr3nBlhh0Ca8Dx6BmvNBoG8m1Y4hpC0qIog,8185
33
33
  qase/commons/reporters/report.py,sha256=ZLwtVn5gjwgJFtfbpLUO-vW3M3skEq3AhKJwtmM0nUw,4810
34
- qase/commons/reporters/testops.py,sha256=pYSLjSSvDeN3K1DN_7pFHataZHQEjU3ul-8x8-9uotY,6446
34
+ qase/commons/reporters/testops.py,sha256=uph_8upIt5bu4ncqyK7ehXR9NqtpZ34wGUBFLVJKOpE,7481
35
35
  qase/commons/util/__init__.py,sha256=0sRRfrMOIPCHpk9tXM94Pj10qrk18B61qEcbLpRjw_I,74
36
36
  qase/commons/util/host_data.py,sha256=n8o5PDs8kELCZZ5GR7Jug6LsgZHWJudU7iRmZHRdrlw,5264
37
37
  qase/commons/validators/base.py,sha256=wwSn-4YiuXtfGMGnSKgo9Vm5hAKevVmmfd2Ro6Q7MYQ,173
38
- qase_python_commons-3.5.2.dist-info/METADATA,sha256=mo-mgFlC9juQ_JrN-p2WXPEHegIGu75_i8dVF8bJTnw,1857
39
- qase_python_commons-3.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
- qase_python_commons-3.5.2.dist-info/top_level.txt,sha256=Mn5aFk7H7Uia4s1NRDsvebu8vCrFy9nOuRIBfkIY5kQ,5
41
- qase_python_commons-3.5.2.dist-info/RECORD,,
38
+ qase_python_commons-3.5.4.dist-info/METADATA,sha256=VRTDQNbqx1qizieGvflYnqMV_EFvabeMe0ltKn2OpJ4,1857
39
+ qase_python_commons-3.5.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
+ qase_python_commons-3.5.4.dist-info/top_level.txt,sha256=Mn5aFk7H7Uia4s1NRDsvebu8vCrFy9nOuRIBfkIY5kQ,5
41
+ qase_python_commons-3.5.4.dist-info/RECORD,,