Skip to content

Commit 401fdd9

Browse files
committed
Data model type hints
- Type hints on common data models - Standardised internal types to string-based enums - Updated `ParameterMode.DEFAULT` from `None` to `"default"` - Updated `ALLURE_UNIQUE_LABELS` to use `LabelType` values - Introduced missing 'Stage' enum - Corrected `TestResult` to initialise with required UUID
1 parent 2031724 commit 401fdd9

File tree

7 files changed

+117
-80
lines changed

7 files changed

+117
-80
lines changed

allure-python-commons/src/allure_commons/lifecycle.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ def _last_item_uuid(self, item_type=None):
3535

3636
@contextmanager
3737
def schedule_test_case(self, uuid=None):
38-
test_result = TestResult()
39-
test_result.uuid = uuid or uuid4()
38+
test_result = TestResult(uuid=uuid or uuid4())
4039
self._items[test_result.uuid] = test_result
4140
yield test_result
4241

allure-python-commons/src/allure_commons/logger.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@
44
import json
55
import uuid
66
import shutil
7-
from attr import asdict
7+
from enum import Enum
8+
from typing import Any
9+
10+
from attr import asdict, Attribute
811
from allure_commons import hookimpl
912

1013
INDENT = 4
1114

1215

16+
def _enum_value_serializer(inst: Any, field: Attribute, value: Any) -> Any:
17+
"""Convert enum values to their string representation for serialization."""
18+
if isinstance(value, Enum):
19+
return value.value
20+
return value
21+
22+
1323
class AllureFileLogger:
1424

1525
def __init__(self, report_dir, clean=False):
@@ -21,7 +31,7 @@ def __init__(self, report_dir, clean=False):
2131
def _report_item(self, item):
2232
indent = INDENT if os.environ.get("ALLURE_INDENT_OUTPUT") else None
2333
filename = item.file_pattern.format(prefix=uuid.uuid4())
24-
data = asdict(item, filter=lambda _, v: v or v is False)
34+
data = asdict(item, filter=lambda _, v: v or v is False, value_serializer=_enum_value_serializer)
2535
with io.open(self._report_dir / filename, 'w', encoding='utf8') as json_file:
2636
json.dump(data, json_file, indent=indent, ensure_ascii=False)
2737

@@ -57,12 +67,12 @@ def __init__(self):
5767

5868
@hookimpl
5969
def report_result(self, result):
60-
data = asdict(result, filter=lambda _, v: v or v is False)
70+
data = asdict(result, filter=lambda _, v: v or v is False, value_serializer=_enum_value_serializer)
6171
self.test_cases.append(data)
6272

6373
@hookimpl
6474
def report_container(self, container):
65-
data = asdict(container, filter=lambda _, v: v or v is False)
75+
data = asdict(container, filter=lambda _, v: v or v is False, value_serializer=_enum_value_serializer)
6676
self.test_containers.append(data)
6777

6878
@hookimpl

allure-python-commons/src/allure_commons/mapping.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Sequence
14
from itertools import chain, islice
25
import attr
36
import re
@@ -23,31 +26,35 @@ def __is(kind, t):
2326
return kind in [v for k, v in t.__dict__.items() if not k.startswith('__')]
2427

2528

26-
def parse_tag(tag, issue_pattern=None, link_pattern=None):
29+
def parse_tag(
30+
tag: str,
31+
issue_pattern: str | None = None,
32+
link_pattern: str | None = None,
33+
) -> Label | Link:
2734
"""
2835
>>> parse_tag("blocker")
29-
Label(name='severity', value='blocker')
36+
Label(name=<LabelType.SEVERITY: 'severity'>, value='blocker')
3037
3138
>>> parse_tag("allure.issue:http://example.com/BUG-42")
32-
Link(type='issue', url='http://example.com/BUG-42', name='http://example.com/BUG-42')
39+
Link(type=<LinkType.ISSUE: 'issue'>, url='http://example.com/BUG-42', name='http://example.com/BUG-42')
3340
3441
>>> parse_tag("allure.link.home:http://qameta.io")
35-
Link(type='link', url='http://qameta.io', name='home')
42+
Link(type=<LinkType.LINK: 'link'>, url='http://qameta.io', name='home')
3643
3744
>>> parse_tag("allure.suite:mapping")
38-
Label(name='suite', value='mapping')
45+
Label(name=<LabelType.SUITE: 'suite'>, value='mapping')
3946
4047
>>> parse_tag("allure.suite:mapping")
41-
Label(name='suite', value='mapping')
48+
Label(name=<LabelType.SUITE: 'suite'>, value='mapping')
4249
4350
>>> parse_tag("allure.label.owner:me")
4451
Label(name='owner', value='me')
4552
4653
>>> parse_tag("foo.label:1")
47-
Label(name='tag', value='foo.label:1')
54+
Label(name=<LabelType.TAG: 'tag'>, value='foo.label:1')
4855
4956
>>> parse_tag("allure.foo:1")
50-
Label(name='tag', value='allure.foo:1')
57+
Label(name=<LabelType.TAG: 'tag'>, value='allure.foo:1')
5158
"""
5259
sep = allure_tag_sep(tag)
5360
schema, value = islice(chain(tag.split(sep, 1), [None]), 2)
@@ -63,10 +70,10 @@ def parse_tag(tag, issue_pattern=None, link_pattern=None):
6370
value = issue_pattern.format(value)
6471
if link_pattern and kind == "link" and not value.startswith("http"):
6572
value = link_pattern.format(value)
66-
return Link(type=kind, name=name or value, url=value)
73+
return Link(type=LinkType(kind), name=name or value, url=value)
6774

6875
if __is(kind, LabelType):
69-
return Label(name=kind, value=value)
76+
return Label(name=LabelType(kind), value=value)
7077

7178
if kind == "id":
7279
return Label(name=LabelType.ID, value=value)
@@ -77,12 +84,12 @@ def parse_tag(tag, issue_pattern=None, link_pattern=None):
7784
return Label(name=LabelType.TAG, value=tag)
7885

7986

80-
def labels_set(labels):
87+
def labels_set(labels: Sequence[Label]) -> list[Label]:
8188
"""
8289
>>> labels_set([Label(name=LabelType.SEVERITY, value=Severity.NORMAL),
8390
... Label(name=LabelType.SEVERITY, value=Severity.BLOCKER)
8491
... ])
85-
[Label(name='severity', value=<Severity.BLOCKER: 'blocker'>)]
92+
[Label(name=<LabelType.SEVERITY: 'severity'>, value=<Severity.BLOCKER: 'blocker'>)]
8693
8794
>>> labels_set([Label(name=LabelType.SEVERITY, value=Severity.NORMAL),
8895
... Label(name='severity', value='minor')
@@ -92,12 +99,12 @@ def labels_set(labels):
9299
>>> labels_set([Label(name=LabelType.EPIC, value="Epic"),
93100
... Label(name=LabelType.EPIC, value="Epic")
94101
... ])
95-
[Label(name='epic', value='Epic')]
102+
[Label(name=<LabelType.EPIC: 'epic'>, value='Epic')]
96103
97104
>>> labels_set([Label(name=LabelType.EPIC, value="Epic1"),
98105
... Label(name=LabelType.EPIC, value="Epic2")
99106
... ])
100-
[Label(name='epic', value='Epic1'), Label(name='epic', value='Epic2')]
107+
[Label(name=<LabelType.EPIC: 'epic'>, value='Epic1'), Label(name=<LabelType.EPIC: 'epic'>, value='Epic2')]
101108
"""
102109
class Wl:
103110
def __init__(self, label):
Lines changed: 61 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
from __future__ import annotations
2+
3+
from enum import Enum
4+
from typing import TYPE_CHECKING
5+
16
from attr import attrs, attrib
27
from attr import Factory
38

9+
if TYPE_CHECKING:
10+
from allure_commons.types import AttachmentType, LabelType, LinkType, ParameterMode
411

512
TEST_GROUP_PATTERN = "{prefix}-container.json"
613
TEST_CASE_PATTERN = "{prefix}-result.json"
@@ -12,49 +19,49 @@
1219
class TestResultContainer:
1320
file_pattern = TEST_GROUP_PATTERN
1421

15-
uuid = attrib(default=None)
16-
name = attrib(default=None)
17-
children = attrib(default=Factory(list))
18-
description = attrib(default=None)
19-
descriptionHtml = attrib(default=None)
20-
befores = attrib(default=Factory(list))
21-
afters = attrib(default=Factory(list))
22-
links = attrib(default=Factory(list))
23-
start = attrib(default=None)
24-
stop = attrib(default=None)
22+
uuid: str = attrib(default=None)
23+
name: str | None = attrib(default=None)
24+
children: list[str] = attrib(default=Factory(list))
25+
description: str | None = attrib(default=None)
26+
descriptionHtml: str | None = attrib(default=None)
27+
befores: list[TestBeforeResult] = attrib(default=Factory(list))
28+
afters: list[TestAfterResult] = attrib(default=Factory(list))
29+
links: list[Link] = attrib(default=Factory(list))
30+
start: int | None = attrib(default=None)
31+
stop: int | None = attrib(default=None)
2532

2633

2734
@attrs
2835
class ExecutableItem:
29-
name = attrib(default=None)
30-
status = attrib(default=None)
31-
statusDetails = attrib(default=None)
32-
stage = attrib(default=None)
33-
description = attrib(default=None)
34-
descriptionHtml = attrib(default=None)
35-
steps = attrib(default=Factory(list))
36-
attachments = attrib(default=Factory(list))
37-
parameters = attrib(default=Factory(list))
38-
start = attrib(default=None)
39-
stop = attrib(default=None)
36+
name: str | None = attrib(default=None)
37+
status: Status | None = attrib(default=None)
38+
statusDetails: StatusDetails | None = attrib(default=None)
39+
stage: Stage | None = attrib(default=None)
40+
description: str | None = attrib(default=None)
41+
descriptionHtml: str | None = attrib(default=None)
42+
steps: list[TestStepResult] = attrib(default=Factory(list))
43+
attachments: list[Attachment] = attrib(default=Factory(list))
44+
parameters: list[Parameter] = attrib(default=Factory(list))
45+
start: int | None = attrib(default=None)
46+
stop: int | None = attrib(default=None)
4047

4148

4249
@attrs
4350
class TestResult(ExecutableItem):
4451
file_pattern = TEST_CASE_PATTERN
4552

46-
uuid = attrib(default=None)
47-
historyId = attrib(default=None)
48-
testCaseId = attrib(default=None)
49-
fullName = attrib(default=None)
50-
labels = attrib(default=Factory(list))
51-
links = attrib(default=Factory(list))
52-
titlePath = attrib(default=Factory(list))
53+
uuid: str = attrib(default=None)
54+
historyId: str | None = attrib(default=None)
55+
testCaseId: str | None = attrib(default=None)
56+
fullName: str | None = attrib(default=None)
57+
labels: list[Label] = attrib(default=Factory(list))
58+
links: list[Link] = attrib(default=Factory(list))
59+
titlePath: list[str] = attrib(default=Factory(list))
5360

5461

5562
@attrs
5663
class TestStepResult(ExecutableItem):
57-
id = attrib(default=None) # noqa: A003
64+
id: str | None = attrib(default=None) # noqa: A003
5865

5966

6067
@attrs
@@ -69,43 +76,51 @@ class TestAfterResult(ExecutableItem):
6976

7077
@attrs
7178
class Parameter:
72-
name = attrib(default=None)
73-
value = attrib(default=None)
74-
excluded = attrib(default=None)
75-
mode = attrib(default=None)
79+
name: str = attrib(default=None)
80+
value: str = attrib(default=None)
81+
excluded: bool | None = attrib(default=None)
82+
mode: ParameterMode | None = attrib(default=None)
7683

7784

7885
@attrs
7986
class Label:
80-
name = attrib(default=None)
81-
value = attrib(default=None)
87+
name: LabelType | str = attrib(default=None)
88+
value: str = attrib(default=None)
8289

8390

8491
@attrs
8592
class Link:
86-
type = attrib(default=None) # noqa: A003
87-
url = attrib(default=None)
88-
name = attrib(default=None)
93+
type: LinkType | str | None = attrib(default=None) # noqa: A003
94+
url: str = attrib(default=None)
95+
name: str | None = attrib(default=None)
8996

9097

9198
@attrs
9299
class StatusDetails:
93-
known = attrib(default=None)
94-
flaky = attrib(default=None)
95-
message = attrib(default=None)
96-
trace = attrib(default=None)
100+
known: bool | None = attrib(default=None)
101+
flaky: bool | None = attrib(default=None)
102+
message: str | None = attrib(default=None)
103+
trace: str | None = attrib(default=None)
97104

98105

99106
@attrs
100107
class Attachment:
101-
name = attrib(default=None)
102-
source = attrib(default=None)
103-
type = attrib(default=None) # noqa: A003
108+
name: str = attrib(default=None)
109+
source: str = attrib(default=None)
110+
type: AttachmentType | str | None = attrib(default=None) # noqa: A003
104111

105112

106-
class Status:
113+
class Status(str, Enum):
107114
FAILED = 'failed'
108115
BROKEN = 'broken'
109116
PASSED = 'passed'
110117
SKIPPED = 'skipped'
111118
UNKNOWN = 'unknown'
119+
120+
121+
class Stage(str, Enum):
122+
SCHEDULED = "scheduled"
123+
RUNNING = "running"
124+
FINISHED = "finished"
125+
PENDING = "pending"
126+
INTERRUPTED = "interrupted"

allure-python-commons/src/allure_commons/types.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from enum import Enum
1+
from __future__ import annotations
22

3-
ALLURE_UNIQUE_LABELS = ['severity', 'thread', 'host']
3+
from enum import Enum
44

55

66
class Severity(str, Enum):
@@ -11,13 +11,13 @@ class Severity(str, Enum):
1111
TRIVIAL = 'trivial'
1212

1313

14-
class LinkType:
14+
class LinkType(str, Enum):
1515
LINK = 'link'
1616
ISSUE = 'issue'
1717
TEST_CASE = 'tms'
1818

1919

20-
class LabelType(str):
20+
class LabelType(str, Enum):
2121
EPIC = 'epic'
2222
FEATURE = 'feature'
2323
STORY = 'story'
@@ -34,9 +34,12 @@ class LabelType(str):
3434
MANUAL = 'ALLURE_MANUAL'
3535

3636

37+
ALLURE_UNIQUE_LABELS = [LabelType.SEVERITY, LabelType.THREAD, LabelType.HOST]
38+
39+
3740
class AttachmentType(Enum):
3841

39-
def __init__(self, mime_type, extension):
42+
def __init__(self, mime_type: str, extension: str) -> None:
4043
self.mime_type = mime_type
4144
self.extension = extension
4245

@@ -66,7 +69,7 @@ def __init__(self, mime_type, extension):
6669
PDF = ("application/pdf", "pdf")
6770

6871

69-
class ParameterMode(Enum):
72+
class ParameterMode(str, Enum):
7073
HIDDEN = 'hidden'
7174
MASKED = 'masked'
72-
DEFAULT = None
75+
DEFAULT = 'default'

allure-robotframework/src/listener/allure_listener.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def stop_test(self, _, attributes, messages):
163163
test_result.labels.append(Label(name=LabelType.SEVERITY, value=Severity.CRITICAL))
164164

165165
for link_type in (LinkType.ISSUE, LinkType.TEST_CASE, LinkType.LINK):
166-
test_result.links.extend(allure_links(attributes, link_type))
166+
test_result.links.extend(allure_links(attributes, link_type.value))
167167

168168
self._current_tb, self._current_msg = None, None
169169

0 commit comments

Comments
 (0)