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.

@@ -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,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'])
@@ -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,28 @@ 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
52
+ show_public_report_link: bool = None
16
53
 
17
54
  def __init__(self):
18
55
  self.api = ApiConfig()
19
56
  self.run = RunConfig()
20
57
  self.batch = BatchConfig()
21
58
  self.plan = PlanConfig()
59
+ self.configurations = ConfigurationsConfig()
22
60
  self.defect = False
61
+ self.status_filter = []
62
+ self.show_public_report_link = False
23
63
 
24
64
  def set_project(self, project: str):
25
65
  self.project = project
26
66
 
27
67
  def set_defect(self, defect):
28
68
  self.defect = QaseUtils.parse_bool(defect)
69
+
70
+ def set_status_filter(self, status_filter: List[str]):
71
+ self.status_filter = status_filter
72
+
73
+ def set_show_public_report_link(self, show_public_report_link):
74
+ self.show_public_report_link = QaseUtils.parse_bool(show_public_report_link)
@@ -0,0 +1,41 @@
1
+ from enum import Enum
2
+ from typing import Optional
3
+ from .basemodel import BaseModel
4
+
5
+
6
+ class ExternalLinkType(Enum):
7
+ """External link types supported by Qase TestOps"""
8
+ JIRA_CLOUD = 'jiraCloud'
9
+ JIRA_SERVER = 'jiraServer'
10
+
11
+
12
+ class ExternalLinkConfig(BaseModel):
13
+ """Configuration for external link"""
14
+ type: ExternalLinkType = None
15
+ link: str = None
16
+
17
+ def __init__(self, type: ExternalLinkType = None, link: str = None):
18
+ self.type = type
19
+ self.link = link
20
+
21
+ def set_type(self, type: str):
22
+ """Set external link type from string"""
23
+ if type == 'jiraCloud':
24
+ self.type = ExternalLinkType.JIRA_CLOUD
25
+ elif type == 'jiraServer':
26
+ self.type = ExternalLinkType.JIRA_SERVER
27
+ else:
28
+ raise ValueError(f"Invalid external link type: {type}. Supported types: jiraCloud, jiraServer")
29
+
30
+ def set_link(self, link: str):
31
+ """Set external link URL or identifier"""
32
+ self.link = link
33
+
34
+ def to_api_type(self) -> str:
35
+ """Convert to API enum value"""
36
+ if self.type == ExternalLinkType.JIRA_CLOUD:
37
+ return 'jira-cloud'
38
+ elif self.type == ExternalLinkType.JIRA_SERVER:
39
+ return 'jira-server'
40
+ else:
41
+ raise ValueError(f"Invalid external link type: {self.type}")
@@ -25,7 +25,7 @@ class Execution(BaseModel):
25
25
  stacktrace: Optional[str] = None,
26
26
  thread: Optional[str] = QaseUtils.get_thread_name()
27
27
  ):
28
- self.start_time = time.time()
28
+ self.start_time = QaseUtils.get_real_time()
29
29
  self.status = status
30
30
  self.end_time = end_time
31
31
  self.duration = duration
@@ -33,16 +33,22 @@ class Execution(BaseModel):
33
33
  self.thread = thread
34
34
 
35
35
  def set_status(self, status: Optional[str]):
36
- if status in ['passed', 'failed', 'skipped', 'untested']:
36
+ if status is None:
37
37
  self.status = status
38
+ return
39
+
40
+ # Convert to lowercase for validation
41
+ status_lower = status.lower()
42
+ if status_lower in ['passed', 'failed', 'skipped', 'untested', 'invalid', 'disabled', 'blocked']:
43
+ self.status = status_lower
38
44
  else:
39
- raise ValueError('Step status must be one of: passed, failed, skipped, untested')
45
+ raise ValueError('Step status must be one of: passed, failed, skipped, untested, invalid, disabled, blocked')
40
46
 
41
47
  def get_status(self):
42
48
  return self.status
43
49
 
44
50
  def complete(self):
45
- self.end_time = time.time()
51
+ self.end_time = QaseUtils.get_real_time()
46
52
  self.duration = (int)((self.end_time - self.start_time) * 1000)
47
53
 
48
54
 
@@ -24,6 +24,7 @@ class RunStats(BaseModel):
24
24
  self.failed = 0
25
25
  self.skipped = 0
26
26
  self.broken = 0
27
+ self.invalid = 0
27
28
  self.muted = 0
28
29
  self.total = 0
29
30
 
@@ -37,6 +38,8 @@ class RunStats(BaseModel):
37
38
  self.skipped += 1
38
39
  elif status == "broken":
39
40
  self.broken += 1
41
+ elif status == "invalid":
42
+ self.invalid += 1
40
43
  self.total += 1
41
44
  if result.get('muted', False):
42
45
  self.muted += 1
@@ -1,3 +1,4 @@
1
+ from . import Result
1
2
  from .step import Step
2
3
  from .attachment import Attachment
3
4
 
@@ -42,6 +43,14 @@ class Runtime:
42
43
  except Exception as e:
43
44
  raise QaseRuntimeException(e)
44
45
 
46
+
47
+ def add_param(self, key: str, value: str):
48
+ """
49
+ Add a parameter to the current result.
50
+ """
51
+ if self.result is not None:
52
+ self.result.params[key] = value
53
+
45
54
  def clear(self):
46
55
  self.result = None
47
56
  self.steps = {}
@@ -5,6 +5,7 @@ from enum import Enum
5
5
  from typing import Optional, Union, Dict, List, Type
6
6
  from .attachment import Attachment
7
7
  from .basemodel import BaseModel
8
+ from .. import QaseUtils
8
9
 
9
10
 
10
11
  class StepType(Enum):
@@ -43,10 +44,20 @@ class StepRequestData(BaseModel):
43
44
  self.response_body = None
44
45
  self.status_code = None
45
46
  if isinstance(request_body, bytes):
46
- request_body = request_body.decode('utf-8')
47
+ try:
48
+ request_body = request_body.decode('utf-8')
49
+ except UnicodeDecodeError:
50
+ # For binary data (like file uploads), keep as base64 encoded string
51
+ import base64
52
+ request_body = base64.b64encode(request_body).decode('ascii')
47
53
  self.request_body = request_body
48
54
  if isinstance(request_headers, bytes):
49
- request_headers = request_headers.decode('utf-8')
55
+ try:
56
+ request_headers = request_headers.decode('utf-8')
57
+ except UnicodeDecodeError:
58
+ # For binary headers, keep as base64 encoded string
59
+ import base64
60
+ request_headers = base64.b64encode(request_headers).decode('ascii')
50
61
  self.request_headers = request_headers
51
62
  self.request_method = request_method
52
63
  self.request_url = request_url
@@ -56,16 +67,33 @@ class StepRequestData(BaseModel):
56
67
  self.status_code = status_code
57
68
 
58
69
  if isinstance(response_body, bytes):
59
- response_body = response_body.decode('utf-8')
70
+ try:
71
+ response_body = response_body.decode('utf-8')
72
+ except UnicodeDecodeError:
73
+ # For binary data (like file downloads), keep as base64 encoded string
74
+ import base64
75
+ response_body = base64.b64encode(response_body).decode('ascii')
60
76
  self.response_body = response_body
61
77
  if isinstance(response_headers, bytes):
62
- response_headers = response_headers.decode('utf-8')
78
+ try:
79
+ response_headers = response_headers.decode('utf-8')
80
+ except UnicodeDecodeError:
81
+ # For binary headers, keep as base64 encoded string
82
+ import base64
83
+ response_headers = base64.b64encode(response_headers).decode('ascii')
63
84
  self.response_headers = response_headers
64
85
 
65
86
 
66
87
  class StepDbQueryData(BaseModel):
67
- def __init__(self, query: str, expected_result: str):
88
+ def __init__(self, query: str, expected_result: str = None,
89
+ database_type: str = None, execution_time: float = None,
90
+ rows_affected: int = None, connection_info: str = None):
68
91
  self.query = query
92
+ self.expected_result = expected_result
93
+ self.database_type = database_type
94
+ self.execution_time = execution_time
95
+ self.rows_affected = rows_affected
96
+ self.connection_info = connection_info
69
97
 
70
98
 
71
99
  class StepSleepData(BaseModel):
@@ -75,20 +103,20 @@ class StepSleepData(BaseModel):
75
103
 
76
104
  class StepExecution(BaseModel):
77
105
  def __init__(self, status: Optional[str] = 'untested', end_time: int = 0, duration: int = 0):
78
- self.start_time = time.time()
106
+ self.start_time = QaseUtils.get_real_time()
79
107
  self.status = status
80
108
  self.end_time = end_time
81
109
  self.duration = duration
82
110
  self.attachments = []
83
111
 
84
112
  def set_status(self, status: Optional[str]):
85
- if status in ['passed', 'failed', 'skipped', 'blocked', 'untested']:
113
+ if status in ['passed', 'failed', 'skipped', 'blocked', 'untested', 'invalid']:
86
114
  self.status = status
87
115
  else:
88
- raise ValueError('Step status must be one of: passed, failed, skipped, blocked, untested')
116
+ raise ValueError('Step status must be one of: passed, failed, skipped, blocked, untested, invalid')
89
117
 
90
118
  def complete(self):
91
- self.end_time = time.time()
119
+ self.end_time = QaseUtils.get_real_time()
92
120
  self.duration = int((self.end_time - self.start_time) * 1000)
93
121
 
94
122
  def add_attachment(self, attachment: Attachment):
@@ -1,10 +1,11 @@
1
1
  from .network import NetworkProfiler, NetworkProfilerSingleton
2
2
  from .sleep import SleepProfiler
3
- from .db import DbProfiler
3
+ from .db import DatabaseProfiler, DatabaseProfilerSingleton
4
4
 
5
5
  __all__ = [
6
6
  NetworkProfiler,
7
7
  NetworkProfilerSingleton,
8
8
  SleepProfiler,
9
- DbProfiler
10
- ]
9
+ DatabaseProfiler,
10
+ DatabaseProfilerSingleton
11
+ ]