qase-python-commons 3.1.3__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.

Files changed (36) hide show
  1. qase/__init__.py +3 -0
  2. qase/commons/client/api_v1_client.py +169 -143
  3. qase/commons/client/api_v2_client.py +77 -23
  4. qase/commons/client/base_api_client.py +12 -1
  5. qase/commons/config.py +159 -20
  6. qase/commons/logger.py +82 -13
  7. qase/commons/models/__init__.py +0 -2
  8. qase/commons/models/attachment.py +11 -8
  9. qase/commons/models/basemodel.py +12 -3
  10. qase/commons/models/config/framework.py +61 -0
  11. qase/commons/models/config/qaseconfig.py +34 -0
  12. qase/commons/models/config/run.py +19 -0
  13. qase/commons/models/config/testops.py +45 -3
  14. qase/commons/models/external_link.py +41 -0
  15. qase/commons/models/relation.py +16 -6
  16. qase/commons/models/result.py +16 -31
  17. qase/commons/models/run.py +17 -2
  18. qase/commons/models/runtime.py +15 -1
  19. qase/commons/models/step.py +43 -11
  20. qase/commons/profilers/__init__.py +4 -3
  21. qase/commons/profilers/db.py +965 -5
  22. qase/commons/profilers/network.py +5 -1
  23. qase/commons/reporters/core.py +50 -9
  24. qase/commons/reporters/report.py +11 -6
  25. qase/commons/reporters/testops.py +56 -22
  26. qase/commons/status_mapping/__init__.py +12 -0
  27. qase/commons/status_mapping/status_mapping.py +237 -0
  28. qase/commons/util/__init__.py +9 -0
  29. qase/commons/util/host_data.py +140 -0
  30. qase/commons/utils.py +95 -0
  31. {qase_python_commons-3.1.3.dist-info → qase_python_commons-4.1.3.dist-info}/METADATA +16 -11
  32. qase_python_commons-4.1.3.dist-info/RECORD +45 -0
  33. {qase_python_commons-3.1.3.dist-info → qase_python_commons-4.1.3.dist-info}/WHEEL +1 -1
  34. qase/commons/models/suite.py +0 -13
  35. qase_python_commons-3.1.3.dist-info/RECORD +0 -40
  36. {qase_python_commons-3.1.3.dist-info → qase_python_commons-4.1.3.dist-info}/top_level.txt +0 -0
qase/commons/config.py CHANGED
@@ -1,18 +1,28 @@
1
1
  import os
2
2
  import json
3
- from .logger import Logger
3
+ from .logger import Logger, LoggingOptions
4
4
  from .models.config.qaseconfig import QaseConfig, Mode
5
+ from .utils import QaseUtils
5
6
 
6
7
 
7
8
  class ConfigManager:
8
9
 
9
10
  def __init__(self, config_file='./qase.config.json'):
10
- self.logger = Logger()
11
11
  self.__config_file = config_file
12
12
  self.config = QaseConfig()
13
13
 
14
+ # Initialize temporary logger for error handling during config loading
15
+ self.logger = Logger(debug=False)
16
+
14
17
  self.__load_file_config()
15
18
  self.__load_env_config()
19
+
20
+ # Re-initialize logger with proper logging options after config is loaded
21
+ logging_options = LoggingOptions(
22
+ console=self.config.logging.console if self.config.logging.console is not None else True,
23
+ file=self.config.logging.file if self.config.logging.file is not None else self.config.debug
24
+ )
25
+ self.logger = Logger(debug=self.config.debug, logging_options=logging_options)
16
26
 
17
27
  def validate_config(self):
18
28
  errors: list[str] = []
@@ -53,15 +63,26 @@ class ConfigManager:
53
63
  if config.get("profilers"):
54
64
  self.config.set_profilers(config.get("profilers"))
55
65
 
56
- if config.get("debug"):
66
+ if config.get("debug") is not None:
57
67
  self.config.set_debug(
58
68
  config.get("debug")
59
69
  )
60
70
 
71
+ if config.get("excludeParams"):
72
+ self.config.set_exclude_params(
73
+ config.get("excludeParams")
74
+ )
75
+
76
+ if config.get("statusMapping"):
77
+ self.config.set_status_mapping(
78
+ config.get("statusMapping")
79
+ )
80
+
61
81
  if config.get("executionPlan"):
62
82
  execution_plan = config.get("executionPlan")
63
83
  if execution_plan.get("path"):
64
- self.config.execution_plan.set_path(execution_plan.get("path"))
84
+ self.config.execution_plan.set_path(
85
+ execution_plan.get("path"))
65
86
 
66
87
  if config.get("testops"):
67
88
  testops = config.get("testops")
@@ -70,24 +91,22 @@ class ConfigManager:
70
91
  api = testops.get("api")
71
92
 
72
93
  if api.get("host"):
73
- self.config.testops.api.set_host(api.get("host"))
94
+ self.config.testops.api.set_host(
95
+ api.get("host"))
74
96
 
75
97
  if api.get("token"):
76
- self.config.testops.api.set_token(api.get("token"))
98
+ self.config.testops.api.set_token(
99
+ api.get("token"))
77
100
 
78
101
  if testops.get("project"):
79
- self.config.testops.set_project(testops.get("project"))
102
+ self.config.testops.set_project(
103
+ testops.get("project"))
80
104
 
81
- if testops.get("defect"):
105
+ if testops.get("defect") is not None:
82
106
  self.config.testops.set_defect(
83
107
  testops.get("defect")
84
108
  )
85
109
 
86
- if testops.get("useV2"):
87
- self.config.testops.set_use_v2(
88
- testops.get("useV2")
89
- )
90
-
91
110
  if testops.get("plan"):
92
111
  plan = testops.get("plan")
93
112
 
@@ -101,21 +120,59 @@ class ConfigManager:
101
120
  self.config.testops.run.set_id(run.get("id"))
102
121
 
103
122
  if run.get("title"):
104
- self.config.testops.run.set_title(run.get("title"))
123
+ self.config.testops.run.set_title(
124
+ run.get("title"))
105
125
 
106
126
  if run.get("description"):
107
- self.config.testops.run.set_description(run.get("description"))
127
+ self.config.testops.run.set_description(
128
+ run.get("description"))
108
129
 
109
- if run.get("complete"):
130
+ if run.get("complete") is not None:
110
131
  self.config.testops.run.set_complete(
111
132
  run.get("complete")
112
133
  )
113
134
 
135
+ if run.get("tags"):
136
+ self.config.testops.run.set_tags(
137
+ [tag.strip() for tag in run.get("tags")])
138
+
139
+ if run.get("externalLink"):
140
+ self.config.testops.run.set_external_link(
141
+ run.get("externalLink"))
142
+
114
143
  if testops.get("batch"):
115
144
  batch = testops.get("batch")
116
145
 
117
146
  if batch.get("size"):
118
- self.config.testops.batch.set_size(batch.get("size"))
147
+ self.config.testops.batch.set_size(
148
+ batch.get("size"))
149
+
150
+ if testops.get("configurations"):
151
+ configurations = testops.get("configurations")
152
+
153
+ if configurations.get("values"):
154
+ values = configurations.get("values")
155
+ for value in values:
156
+ if value.get("name") and value.get("value"):
157
+ self.config.testops.configurations.add_value(
158
+ value.get("name"), value.get("value"))
159
+
160
+ if configurations.get("createIfNotExists") is not None:
161
+ self.config.testops.configurations.set_create_if_not_exists(
162
+ configurations.get("createIfNotExists"))
163
+
164
+ if testops.get("statusFilter"):
165
+ status_filter = testops.get("statusFilter")
166
+ if isinstance(status_filter, list):
167
+ self.config.testops.set_status_filter(status_filter)
168
+ elif isinstance(status_filter, str):
169
+ # Parse comma-separated string
170
+ self.config.testops.set_status_filter([s.strip() for s in status_filter.split(',')])
171
+
172
+ if testops.get("showPublicReportLink") is not None:
173
+ self.config.testops.set_show_public_report_link(
174
+ testops.get("showPublicReportLink")
175
+ )
119
176
 
120
177
  if config.get("report"):
121
178
  report = config.get("report")
@@ -127,7 +184,8 @@ class ConfigManager:
127
184
  connection = report.get("connection")
128
185
 
129
186
  if connection.get("path"):
130
- self.config.report.connection.set_path(connection.get("path"))
187
+ self.config.report.connection.set_path(
188
+ connection.get("path"))
131
189
 
132
190
  if connection.get("format"):
133
191
  self.config.report.connection.set_format(
@@ -140,11 +198,27 @@ class ConfigManager:
140
198
  if framework.get("pytest"):
141
199
  pytest = framework.get("pytest")
142
200
 
143
- if pytest.get("captureLogs"):
201
+ if pytest.get("captureLogs") is not None:
144
202
  self.config.framework.pytest.set_capture_logs(
145
203
  pytest.get("captureLogs")
146
204
  )
147
205
 
206
+ if pytest.get("xfailStatus"):
207
+ xfail_status = pytest.get("xfailStatus")
208
+
209
+ if xfail_status.get("xfail"):
210
+ self.config.framework.pytest.xfail_status.set_xfail(
211
+ xfail_status.get("xfail")
212
+ )
213
+
214
+ if xfail_status.get("xpass"):
215
+ self.config.framework.pytest.xfail_status.set_xpass(
216
+ xfail_status.get("xpass")
217
+ )
218
+
219
+ if config.get("logging"):
220
+ self.config.set_logging(config.get("logging"))
221
+
148
222
  except Exception as e:
149
223
  self.logger.log("Failed to load config from file", "error")
150
224
 
@@ -169,6 +243,23 @@ class ConfigManager:
169
243
  if key == 'QASE_DEBUG':
170
244
  self.config.set_debug(value)
171
245
 
246
+ if key == 'QASE_EXCLUDE_PARAMS':
247
+ self.config.set_exclude_params(
248
+ [param.strip() for param in value.split(',')])
249
+
250
+ if key == 'QASE_STATUS_MAPPING':
251
+ # Parse status mapping from environment variable
252
+ # Format: "source1=target1,source2=target2"
253
+ if value:
254
+ mapping_dict = {}
255
+ pairs = value.split(',')
256
+ for pair in pairs:
257
+ pair = pair.strip()
258
+ if pair and '=' in pair:
259
+ source_status, target_status = pair.split('=', 1)
260
+ mapping_dict[source_status.strip()] = target_status.strip()
261
+ self.config.set_status_mapping(mapping_dict)
262
+
172
263
  if key == 'QASE_EXECUTION_PLAN_PATH':
173
264
  self.config.execution_plan.set_path(value)
174
265
 
@@ -199,9 +290,45 @@ class ConfigManager:
199
290
  if key == 'QASE_TESTOPS_RUN_COMPLETE':
200
291
  self.config.testops.run.set_complete(value)
201
292
 
293
+ if key == 'QASE_TESTOPS_RUN_TAGS':
294
+ self.config.testops.run.set_tags(
295
+ [tag.strip() for tag in value.split(',')])
296
+
297
+ if key == 'QASE_TESTOPS_RUN_EXTERNAL_LINK_TYPE':
298
+ if not self.config.testops.run.external_link:
299
+ from .models.external_link import ExternalLinkConfig
300
+ self.config.testops.run.external_link = ExternalLinkConfig()
301
+ self.config.testops.run.external_link.set_type(value)
302
+
303
+ if key == 'QASE_TESTOPS_RUN_EXTERNAL_LINK_URL':
304
+ if not self.config.testops.run.external_link:
305
+ from .models.external_link import ExternalLinkConfig
306
+ self.config.testops.run.external_link = ExternalLinkConfig()
307
+ self.config.testops.run.external_link.set_link(value)
308
+
202
309
  if key == 'QASE_TESTOPS_BATCH_SIZE':
203
310
  self.config.testops.batch.set_size(value)
204
311
 
312
+ if key == 'QASE_TESTOPS_CONFIGURATIONS_VALUES':
313
+ # Parse configurations from environment variable
314
+ # Format: "group1=value1,group2=value2"
315
+ if value:
316
+ config_pairs = value.split(',')
317
+ for pair in config_pairs:
318
+ if '=' in pair:
319
+ name, config_value = pair.split('=', 1)
320
+ self.config.testops.configurations.add_value(name.strip(), config_value.strip())
321
+
322
+ if key == 'QASE_TESTOPS_CONFIGURATIONS_CREATE_IF_NOT_EXISTS':
323
+ self.config.testops.configurations.set_create_if_not_exists(value)
324
+
325
+ if key == 'QASE_TESTOPS_STATUS_FILTER':
326
+ # Parse comma-separated string
327
+ self.config.testops.set_status_filter([s.strip() for s in value.split(',')])
328
+
329
+ if key == 'QASE_TESTOPS_SHOW_PUBLIC_REPORT_LINK':
330
+ self.config.testops.set_show_public_report_link(value)
331
+
205
332
  if key == 'QASE_REPORT_DRIVER':
206
333
  self.config.report.set_driver(value)
207
334
 
@@ -214,5 +341,17 @@ class ConfigManager:
214
341
  if key == 'QASE_PYTEST_CAPTURE_LOGS':
215
342
  self.config.framework.pytest.set_capture_logs(value)
216
343
 
344
+ if key == 'QASE_PYTEST_XFAIL_STATUS_XFAIL':
345
+ self.config.framework.pytest.xfail_status.set_xfail(value)
346
+
347
+ if key == 'QASE_PYTEST_XFAIL_STATUS_XPASS':
348
+ self.config.framework.pytest.xfail_status.set_xpass(value)
349
+
350
+ if key == 'QASE_LOGGING_CONSOLE':
351
+ self.config.logging.set_console(QaseUtils.parse_bool(value))
352
+
353
+ if key == 'QASE_LOGGING_FILE':
354
+ self.config.logging.set_file(QaseUtils.parse_bool(value))
355
+
217
356
  except Exception as e:
218
- self.logger.log("Failed to load config from env vars {e}", "error")
357
+ self.logger.log(f"Failed to load config from env vars {e}", "error")
qase/commons/logger.py CHANGED
@@ -1,32 +1,101 @@
1
+ import os
1
2
  import datetime
3
+ import threading
4
+ from typing import Optional
2
5
 
3
- import os
6
+
7
+ class LoggingOptions:
8
+ def __init__(self, console: bool = True, file: bool = False):
9
+ self.console = console
10
+ self.file = file
4
11
 
5
12
 
6
13
  class Logger:
7
- def __init__(self, debug: bool = False, prefix: str = '', dir: str = os.path.join('.', 'logs')) -> None:
14
+ _log_file = None
15
+
16
+ def __init__(self, debug: bool = False, prefix: str = '', dir: str = os.path.join('.', 'logs'),
17
+ logging_options: Optional[LoggingOptions] = None) -> None:
8
18
  self.debug = debug
9
- if self.debug:
10
- filename = f'{prefix}_{self._get_timestamp()}.log'
11
- if not os.path.exists(dir):
12
- os.makedirs(dir)
13
- self.log_file = os.path.join(dir, f'{filename}')
14
- with open(self.log_file, 'w', encoding='utf-8'):
19
+ self.prefix = prefix
20
+ self.dir = dir
21
+
22
+ # Initialize logging options
23
+ if logging_options is None:
24
+ # Default behavior: console always enabled, file enabled only in debug mode
25
+ self.logging_options = LoggingOptions(
26
+ console=True,
27
+ file=debug
28
+ )
29
+ else:
30
+ self.logging_options = logging_options
31
+
32
+ # Override with environment variables if set
33
+ self._load_env_logging_options()
34
+
35
+ # Setup file logging if enabled
36
+ if self.logging_options.file:
37
+ if Logger._log_file is None:
38
+ timestamp = self._get_timestamp()
39
+ filename = f'{prefix}_{timestamp}.log'
40
+ if not os.path.exists(dir):
41
+ os.makedirs(dir)
42
+ Logger._log_file = os.path.join(dir, filename)
43
+
44
+ with open(Logger._log_file, 'a', encoding='utf-8'):
15
45
  pass
16
46
 
47
+ self.lock = threading.Lock()
48
+
49
+ def _load_env_logging_options(self):
50
+ """Load logging options from environment variables"""
51
+ # QASE_LOGGING_CONSOLE
52
+ console_env = os.environ.get('QASE_LOGGING_CONSOLE')
53
+ if console_env is not None:
54
+ self.logging_options.console = console_env.lower() in ('true', '1', 'yes', 'on')
55
+
56
+ # QASE_LOGGING_FILE
57
+ file_env = os.environ.get('QASE_LOGGING_FILE')
58
+ if file_env is not None:
59
+ self.logging_options.file = file_env.lower() in ('true', '1', 'yes', 'on')
60
+
61
+ # Legacy QASE_DEBUG support
62
+ debug_env = os.environ.get('QASE_DEBUG')
63
+ if debug_env is not None and debug_env.lower() in ('true', '1', 'yes', 'on'):
64
+ # When debug is enabled via env, enable file logging if not explicitly disabled
65
+ if not hasattr(self.logging_options, 'file') or self.logging_options.file is None:
66
+ self.logging_options.file = True
67
+
17
68
  def log(self, message: str, level: str = 'info'):
18
69
  time_str = self._get_timestamp("%H:%M:%S")
19
70
  log = f"[Qase][{time_str}][{level}] {message}\n"
20
- print(log)
21
- if self.debug:
22
- with open(self.log_file, 'a', encoding='utf-8') as f:
23
- f.write(log)
71
+
72
+ # Console output
73
+ if self.logging_options.console:
74
+ try:
75
+ print(log, end='')
76
+ except (OSError, IOError):
77
+ pass
78
+
79
+ # File output
80
+ if self.logging_options.file:
81
+ with self.lock:
82
+ with open(Logger._log_file, 'a', encoding='utf-8') as f:
83
+ f.write(log)
24
84
 
25
85
  def log_debug(self, message: str):
26
86
  if self.debug:
27
87
  self.log(message, 'debug')
28
88
 
89
+ def log_error(self, message: str):
90
+ self.log(message, 'error')
91
+
92
+ def log_warning(self, message: str):
93
+ self.log(message, 'warning')
94
+
95
+ def log_info(self, message: str):
96
+ self.log(message, 'info')
97
+
29
98
  @staticmethod
30
- def _get_timestamp(fmt: str = "%Y%m%d_%H_%M_%S"):
99
+ def _get_timestamp(fmt: str = "%Y%m%d"):
31
100
  now = datetime.datetime.now()
32
101
  return now.strftime(fmt)
@@ -1,7 +1,6 @@
1
1
  from .result import Result, Field
2
2
  from .run import Run
3
3
  from .attachment import Attachment
4
- from .suite import Suite
5
4
  from .relation import Relation
6
5
  from .step import Step
7
6
  from .runtime import Runtime
@@ -10,7 +9,6 @@ __all__ = [
10
9
  Result,
11
10
  Run,
12
11
  Attachment,
13
- Suite,
14
12
  Relation,
15
13
  Step,
16
14
  Runtime,
@@ -29,16 +29,19 @@ class Attachment(BaseModel):
29
29
  def get_id(self) -> str:
30
30
  return self.id
31
31
 
32
- def get_for_upload(self) -> BytesIO:
32
+ def get_for_upload(self) -> tuple:
33
+ """Returns attachment data in format expected by new API client: (filename, filedata)"""
34
+
33
35
  if self.file_path:
34
36
  with open(self.file_path, "rb") as fc:
35
- content = BytesIO(fc.read())
37
+ filedata = fc.read()
36
38
  else:
37
39
  if isinstance(self.content, str):
38
- content = BytesIO(bytes(self.content, 'utf-8'))
40
+ filedata = bytes(self.content, 'utf-8')
39
41
  elif isinstance(self.content, bytes):
40
- content = BytesIO(self.content)
41
- content.name = self.file_name
42
- content.mime = self.mime_type
43
-
44
- return content
42
+ filedata = self.content
43
+ else:
44
+ # Handle case where content is not str or bytes (e.g., JSON serialized)
45
+ filedata = bytes(self.content, 'utf-8')
46
+
47
+ return (self.file_name, filedata)
@@ -1,7 +1,16 @@
1
1
  import json
2
2
 
3
+ from enum import Enum
4
+
3
5
 
4
6
  class BaseModel:
5
- def __str__(self) -> str:
6
- return json.dumps(self, default=lambda o: o.__dict__ if hasattr(o, '__dict__') else str(o), indent=4,
7
- sort_keys=True)
7
+ def __str__(self, enum_as_name=False) -> str:
8
+ def serialize(o):
9
+ if isinstance(o, Enum):
10
+ return o.name if enum_as_name else o.value
11
+ elif hasattr(o, '__dict__'):
12
+ return o.__dict__
13
+ else:
14
+ return str(o)
15
+
16
+ return json.dumps(self, default=serialize, indent=4, sort_keys=True)
@@ -1,19 +1,80 @@
1
+ from enum import Enum
2
+ from typing import Type, Union
3
+
1
4
  from ..basemodel import BaseModel
2
5
  from ... import QaseUtils
3
6
 
4
7
 
8
+ class Video(Enum):
9
+ off = "off"
10
+ on = "on"
11
+ failed = "retain-on-failure"
12
+
13
+
14
+ class Trace(Enum):
15
+ off = "off"
16
+ on = "on"
17
+ failed = "retain-on-failure"
18
+
19
+
20
+ class XFailStatus(BaseModel):
21
+ xfail: str = None
22
+ xpass: str = None
23
+
24
+ def __init__(self):
25
+ self.xfail = 'skipped'
26
+ self.xpass = 'passed'
27
+
28
+ def set_xfail(self, value: str):
29
+ self.xfail = value
30
+
31
+ def set_xpass(self, value: str):
32
+ self.xpass = value
33
+
34
+
5
35
  class PytestConfig(BaseModel):
6
36
  capture_logs: bool = None
37
+ xfail_status: XFailStatus = None
7
38
 
8
39
  def __init__(self):
9
40
  self.capture_logs = False
41
+ self.xfail_status = XFailStatus()
10
42
 
11
43
  def set_capture_logs(self, capture_logs):
12
44
  self.capture_logs = QaseUtils.parse_bool(capture_logs)
13
45
 
14
46
 
47
+ class PlaywrightConfig(BaseModel):
48
+ output_dir: str = None
49
+ video: Video = None
50
+ trace: Trace = None
51
+
52
+ def __init__(self):
53
+ self.output_dir = "test-results"
54
+ self.video = Video.off
55
+ self.trace = Trace.off
56
+
57
+ def set_output_dir(self, output_dir: str):
58
+ self.output_dir = output_dir
59
+
60
+ def set_video(self, value: str):
61
+ self.video = self.__set_mode(Video, value)
62
+
63
+ def set_trace(self, value: str):
64
+ self.trace = self.__set_mode(Trace, value)
65
+
66
+ @staticmethod
67
+ def __set_mode(enum_class: Type[Union[Video, Trace]], value: str):
68
+ for mode in enum_class:
69
+ if mode.value == value:
70
+ return mode
71
+ return None
72
+
73
+
15
74
  class Framework(BaseModel):
16
75
  pytest: PytestConfig = None
76
+ playwright: PlaywrightConfig = None
17
77
 
18
78
  def __init__(self):
19
79
  self.pytest = PytestConfig()
80
+ self.playwright = PlaywrightConfig()
@@ -1,4 +1,5 @@
1
1
  from enum import Enum
2
+ from typing import List, Dict, Optional
2
3
 
3
4
  from .framework import Framework
4
5
  from .report import ReportConfig
@@ -13,6 +14,21 @@ class Mode(Enum):
13
14
  off = "off"
14
15
 
15
16
 
17
+ class LoggingConfig(BaseModel):
18
+ console: Optional[bool] = None
19
+ file: Optional[bool] = None
20
+
21
+ def __init__(self):
22
+ self.console = None
23
+ self.file = None
24
+
25
+ def set_console(self, console: bool):
26
+ self.console = console
27
+
28
+ def set_file(self, file: bool):
29
+ self.file = file
30
+
31
+
16
32
  class ExecutionPlan(BaseModel):
17
33
  path: str = None
18
34
 
@@ -34,6 +50,9 @@ class QaseConfig(BaseModel):
34
50
  report: ReportConfig = None
35
51
  profilers: list = None
36
52
  framework: Framework = None
53
+ exclude_params: list = None
54
+ status_mapping: Dict[str, str] = None
55
+ logging: LoggingConfig = None
37
56
 
38
57
  def __init__(self):
39
58
  self.mode = Mode.off
@@ -44,6 +63,9 @@ class QaseConfig(BaseModel):
44
63
  self.execution_plan = ExecutionPlan()
45
64
  self.framework = Framework()
46
65
  self.profilers = []
66
+ self.exclude_params = []
67
+ self.status_mapping = {}
68
+ self.logging = LoggingConfig()
47
69
 
48
70
  def set_mode(self, mode: str):
49
71
  if any(mode == e.value for e in Mode.__members__.values()):
@@ -64,3 +86,15 @@ class QaseConfig(BaseModel):
64
86
 
65
87
  def set_debug(self, debug):
66
88
  self.debug = QaseUtils.parse_bool(debug)
89
+
90
+ def set_exclude_params(self, exclude_params: List[str]):
91
+ self.exclude_params = exclude_params
92
+
93
+ def set_status_mapping(self, status_mapping: Dict[str, str]):
94
+ self.status_mapping = status_mapping
95
+
96
+ def set_logging(self, logging_config: dict):
97
+ if logging_config.get("console") is not None:
98
+ self.logging.set_console(QaseUtils.parse_bool(logging_config.get("console")))
99
+ if logging_config.get("file") is not None:
100
+ self.logging.set_file(QaseUtils.parse_bool(logging_config.get("file")))
@@ -1,4 +1,6 @@
1
+ from typing import List, Optional
1
2
  from ..basemodel import BaseModel
3
+ from ..external_link import ExternalLinkConfig
2
4
  from ... import QaseUtils
3
5
 
4
6
 
@@ -7,9 +9,14 @@ class RunConfig(BaseModel):
7
9
  description: str = None
8
10
  complete: bool = None
9
11
  id: int = None
12
+ tags: List[str] = None
13
+ external_link: Optional[ExternalLinkConfig] = None
14
+
10
15
 
11
16
  def __init__(self):
12
17
  self.complete = True
18
+ self.tags = []
19
+ self.external_link = None
13
20
 
14
21
  def set_title(self, title: str):
15
22
  self.title = title
@@ -22,3 +29,15 @@ class RunConfig(BaseModel):
22
29
 
23
30
  def set_id(self, id: int):
24
31
  self.id = id
32
+
33
+ def set_tags(self, tags: List[str]):
34
+ self.tags = tags
35
+
36
+ def set_external_link(self, external_link: dict):
37
+ """Set external link configuration from dictionary"""
38
+ if external_link:
39
+ self.external_link = ExternalLinkConfig()
40
+ if 'type' in external_link:
41
+ self.external_link.set_type(external_link['type'])
42
+ if 'link' in external_link:
43
+ self.external_link.set_link(external_link['link'])