Skip to content

Commit a7bfc6b

Browse files
committed
feat: convert TagColor class to Enum
and improve plugin type hinting
1 parent c93cb37 commit a7bfc6b

File tree

5 files changed

+43
-35
lines changed

5 files changed

+43
-35
lines changed

src/analysis/plugin/plugin.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import abc
44
import time
5-
import typing
5+
from typing import TYPE_CHECKING, Type, TypeVar, final
66

77
import pydantic
88
import semver
99
from pydantic import BaseModel, ConfigDict, field_validator
1010

11-
if typing.TYPE_CHECKING:
11+
from helperFunctions.tag import TagColor # noqa: TCH001
12+
13+
if TYPE_CHECKING:
1214
import io
1315

1416

@@ -31,8 +33,7 @@ class Tag(BaseModel):
3133
#: In FACT_core this is shown as tooltip
3234
value: str
3335
#: The color of the tag
34-
#: See :py:class:`helperFunctions.tag.TagColor`.
35-
color: str
36+
color: TagColor
3637
#: Whether or not the tag should be shown in parent files.
3738
propagate: bool = False
3839

@@ -54,7 +55,7 @@ class MetaData(BaseModel):
5455
description: str
5556
#: Pydantic model of the object returned by :py:func:`analyse`.
5657
# Note that we cannot allow pydantic dataclasses because they lack the `schema` method
57-
Schema: typing.Type
58+
Schema: Type[BaseModel]
5859
#: The version of the plugin.
5960
#: It MUST be a `semver <https://semver.org/>`_ version.
6061
#: Here is a quick summary how semver relates to plugins.
@@ -67,13 +68,13 @@ class MetaData(BaseModel):
6768
version: semver.Version
6869
#: The version of the backing analysis system.
6970
#: E.g. for yara plugins this would be the yara version.
70-
system_version: typing.Optional[str] = None
71+
system_version: str | None = None
7172
#: A list of all plugins that this plugin depends on
72-
dependencies: typing.List = pydantic.Field(default_factory=list)
73+
dependencies: list[str] = pydantic.Field(default_factory=list)
7374
#: List of mimetypes that should not be processed
74-
mime_blacklist: list = pydantic.Field(default_factory=list)
75+
mime_blacklist: list[str] = pydantic.Field(default_factory=list)
7576
#: List of mimetypes that should be processed
76-
mime_whitelist: list = pydantic.Field(default_factory=list)
77+
mime_whitelist: list[str] = pydantic.Field(default_factory=list)
7778
#: The analysis in not expected to take longer than timeout seconds on any given file
7879
#: and will be aborted if the timeout is reached.
7980
timeout: int = 300
@@ -90,7 +91,7 @@ def __init__(self, metadata: MetaData):
9091
self.metadata: AnalysisPluginV0.MetaData = metadata
9192

9293
# The type MetaData.Schema
93-
Schema = typing.TypeVar('Schema')
94+
Schema = TypeVar('Schema')
9495

9596
def summarize(self, result: Schema) -> list[str]: # noqa: ARG002
9697
"""
@@ -115,7 +116,7 @@ def analyze(
115116
self,
116117
file_handle: io.FileIO,
117118
virtual_file_path: dict,
118-
analyses: dict[str, pydantic.BaseModel],
119+
analyses: dict[str, BaseModel],
119120
) -> Schema:
120121
"""Analyze a file.
121122
May return None if nothing was found.
@@ -127,8 +128,8 @@ def analyze(
127128
:return: The analysis if anything was found.
128129
"""
129130

130-
@typing.final
131-
def get_analysis(self, file_handle: io.FileIO, virtual_file_path: dict, analyses: dict[str, dict]) -> dict:
131+
@final
132+
def get_analysis(self, file_handle: io.FileIO, virtual_file_path: dict, analyses: dict[str, BaseModel]) -> dict:
132133
start_time = time.time()
133134
result = self.analyze(file_handle, virtual_file_path, analyses)
134135

src/helperFunctions/tag.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
class TagColor:
1+
from enum import Enum
2+
3+
4+
class TagColor(str, Enum):
25
"""
3-
A class containing the different colors the tags may have. `TagColor.ALL` contains a list of all colors.
6+
A class containing the different colors the tags may have.
47
"""
58

69
GRAY = 'secondary'
@@ -11,4 +14,6 @@ class TagColor:
1114
RED = 'danger'
1215
LIGHT = 'light'
1316
DARK = 'dark'
14-
ALL = [GRAY, BLUE, GREEN, LIGHT_BLUE, ORANGE, RED, LIGHT, DARK] # noqa: RUF012
17+
18+
def __str__(self) -> str:
19+
return self.value

src/plugins/analysis/device_tree/code/device_tree.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING, Dict
3+
from typing import TYPE_CHECKING
44

55
from semver import Version
66

@@ -13,6 +13,8 @@
1313
if TYPE_CHECKING:
1414
import io
1515

16+
from pydantic import BaseModel
17+
1618

1719
class AnalysisPlugin(AnalysisPluginV0):
1820
def __init__(self):
@@ -39,7 +41,7 @@ def analyze(
3941
self,
4042
file_handle: io.FileIO,
4143
virtual_file_path: dict,
42-
analyses: Dict[str, dict],
44+
analyses: dict[str, BaseModel],
4345
) -> Schema:
4446
del virtual_file_path, analyses
4547

src/plugins/analysis/example_plugin/code/example_plugin.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,40 @@
1-
import io
1+
from __future__ import annotations
2+
23
from pathlib import Path
4+
from typing import TYPE_CHECKING
35

4-
import pydantic
5-
from pydantic import Field
6+
from pydantic import BaseModel, Field
67
from semver import Version
78

89
from analysis.plugin.plugin import AnalysisFailedError, AnalysisPluginV0
910

11+
if TYPE_CHECKING:
12+
import io
13+
1014

1115
class AnalysisPlugin(AnalysisPluginV0):
12-
class Schema(pydantic.BaseModel):
16+
class Schema(BaseModel):
1317
"""Here goes the toplevel description of the plugin result"""
1418

15-
# fmt: off
1619
number: int = Field(
1720
description=(
18-
'This is a description of the field number.\n'
19-
'In an actual plugin all fields must have a description.'
21+
'This is a description of the field "number".\n'
22+
'In an actual plugin all fields should have a description.'
2023
),
2124
)
22-
# fmt: on
2325
name: str
2426
first_byte: str
2527
virtual_file_path: dict
2628
dependant_analysis: dict
2729

2830
def __init__(self):
2931
metadata = self.MetaData(
32+
# mandatory fields:
3033
name='ExamplePlugin',
3134
description='An example description',
3235
version=Version(0, 0, 0),
33-
Schema=AnalysisPlugin.Schema,
34-
# Note that you don't have to set these fields,
35-
# they are just here to show that you can.
36+
Schema=self.Schema,
37+
# optional fields:
3638
system_version=None,
3739
mime_blacklist=[],
3840
mime_whitelist=[],
@@ -41,11 +43,11 @@ def __init__(self):
4143
)
4244
super().__init__(metadata=metadata)
4345

44-
def summarize(self, result):
46+
def summarize(self, result: Schema) -> list[str]:
4547
del result
4648
return ['big-file', 'binary']
4749

48-
def analyze(self, file_handle: io.FileIO, virtual_file_path: dict, analyses: dict) -> Schema:
50+
def analyze(self, file_handle: io.FileIO, virtual_file_path: dict, analyses: dict[str, BaseModel]) -> Schema:
4951
first_byte = file_handle.read(1)
5052
if first_byte == b'\xff':
5153
raise AnalysisFailedError('reason for fail')

src/web_interface/filter.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def render_analysis_tags(tags, uid=None, root_uid=None, size=14):
302302
for key, tag in sorted(tags[plugin_name].items(), key=_sort_tags_key):
303303
if key == 'root_uid':
304304
continue
305-
color = tag['color'] if tag['color'] in TagColor.ALL else TagColor.BLUE
305+
color = tag['color'] if tag['color'] in set(TagColor) else TagColor.BLUE
306306
output += render_template(
307307
'generic_view/tags.html',
308308
color=color,
@@ -465,9 +465,7 @@ def render_changed_text_files(changed_text_files: dict) -> str:
465465
f' {inner}\n'
466466
'</div>\n'
467467
)
468-
elements.append(
469-
f'<div class="list-group-item border-top" style="padding: 0 0 0 25px">\n' f'{element}\n' '</div>'
470-
)
468+
elements.append(f'<div class="list-group-item border-top" style="padding: 0 0 0 25px">\n{element}\n</div>')
471469
return '\n'.join(elements)
472470

473471

0 commit comments

Comments
 (0)