Skip to content

Commit 0af10b6

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

File tree

6 files changed

+37
-52
lines changed

6 files changed

+37
-52
lines changed

pyproject.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ select = [
4141
"A", # flake8-builtins
4242
"C4", # flake8-comprehensions
4343
"EXE", # flake8-executable
44-
"FA", # flake8-future-annotations
4544
"ISC", # flake8-implicit-str-concat
4645
"PIE", # flake8-pie
4746
"T20", # flake8-print
@@ -65,9 +64,6 @@ ignore = [
6564
"RUF002",
6665
"RUF003",
6766
"RUF015",
68-
# pydantic only supports these from python>=3.9
69-
"UP006",
70-
"UP007",
7167
# rules may cause conflicts when used with the formatter
7268
"ISC001",
7369
"Q001",

src/analysis/plugin/plugin.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
from __future__ import annotations
2-
31
import abc
2+
import io
43
import time
5-
import typing
4+
from typing import Type, TypeVar, final
65

76
import pydantic
87
import semver
98
from pydantic import BaseModel, ConfigDict, field_validator
109

11-
if typing.TYPE_CHECKING:
12-
import io
10+
from helperFunctions.tag import TagColor
1311

1412

1513
class AnalysisFailedError(Exception):
@@ -31,8 +29,7 @@ class Tag(BaseModel):
3129
#: In FACT_core this is shown as tooltip
3230
value: str
3331
#: The color of the tag
34-
#: See :py:class:`helperFunctions.tag.TagColor`.
35-
color: str
32+
color: TagColor
3633
#: Whether or not the tag should be shown in parent files.
3734
propagate: bool = False
3835

@@ -54,7 +51,7 @@ class MetaData(BaseModel):
5451
description: str
5552
#: Pydantic model of the object returned by :py:func:`analyse`.
5653
# Note that we cannot allow pydantic dataclasses because they lack the `schema` method
57-
Schema: typing.Type
54+
Schema: Type[BaseModel]
5855
#: The version of the plugin.
5956
#: It MUST be a `semver <https://semver.org/>`_ version.
6057
#: Here is a quick summary how semver relates to plugins.
@@ -67,13 +64,13 @@ class MetaData(BaseModel):
6764
version: semver.Version
6865
#: The version of the backing analysis system.
6966
#: E.g. for yara plugins this would be the yara version.
70-
system_version: typing.Optional[str] = None
67+
system_version: str | None = None
7168
#: A list of all plugins that this plugin depends on
72-
dependencies: typing.List = pydantic.Field(default_factory=list)
69+
dependencies: list[str] = pydantic.Field(default_factory=list)
7370
#: List of mimetypes that should not be processed
74-
mime_blacklist: list = pydantic.Field(default_factory=list)
71+
mime_blacklist: list[str] = pydantic.Field(default_factory=list)
7572
#: List of mimetypes that should be processed
76-
mime_whitelist: list = pydantic.Field(default_factory=list)
73+
mime_whitelist: list[str] = pydantic.Field(default_factory=list)
7774
#: The analysis in not expected to take longer than timeout seconds on any given file
7875
#: and will be aborted if the timeout is reached.
7976
timeout: int = 300
@@ -90,7 +87,7 @@ def __init__(self, metadata: MetaData):
9087
self.metadata: AnalysisPluginV0.MetaData = metadata
9188

9289
# The type MetaData.Schema
93-
Schema = typing.TypeVar('Schema')
90+
Schema = TypeVar('Schema')
9491

9592
def summarize(self, result: Schema) -> list[str]: # noqa: ARG002
9693
"""
@@ -115,7 +112,7 @@ def analyze(
115112
self,
116113
file_handle: io.FileIO,
117114
virtual_file_path: dict,
118-
analyses: dict[str, pydantic.BaseModel],
115+
analyses: dict[str, BaseModel],
119116
) -> Schema:
120117
"""Analyze a file.
121118
May return None if nothing was found.
@@ -127,8 +124,8 @@ def analyze(
127124
:return: The analysis if anything was found.
128125
"""
129126

130-
@typing.final
131-
def get_analysis(self, file_handle: io.FileIO, virtual_file_path: dict, analyses: dict[str, dict]) -> dict:
127+
@final
128+
def get_analysis(self, file_handle: io.FileIO, virtual_file_path: dict, analyses: dict[str, BaseModel]) -> dict:
132129
start_time = time.time()
133130
result = self.analyze(file_handle, virtual_file_path, analyses)
134131

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: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from __future__ import annotations
2-
3-
from typing import TYPE_CHECKING, Dict
1+
import io
42

3+
from pydantic import BaseModel
54
from semver import Version
65

76
from analysis.plugin import AnalysisPluginV0, Tag
@@ -10,9 +9,6 @@
109

1110
from ..internal.schema import DeviceTree, IllegalDeviceTreeError, Schema
1211

13-
if TYPE_CHECKING:
14-
import io
15-
1612

1713
class AnalysisPlugin(AnalysisPluginV0):
1814
def __init__(self):
@@ -39,7 +35,7 @@ def analyze(
3935
self,
4036
file_handle: io.FileIO,
4137
virtual_file_path: dict,
42-
analyses: Dict[str, dict],
38+
analyses: dict[str, BaseModel],
4339
) -> Schema:
4440
del virtual_file_path, analyses
4541

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,35 @@
11
import io
22
from pathlib import Path
33

4-
import pydantic
5-
from pydantic import Field
4+
from pydantic import BaseModel, Field
65
from semver import Version
76

87
from analysis.plugin.plugin import AnalysisFailedError, AnalysisPluginV0
98

109

1110
class AnalysisPlugin(AnalysisPluginV0):
12-
class Schema(pydantic.BaseModel):
11+
class Schema(BaseModel):
1312
"""Here goes the toplevel description of the plugin result"""
1413

15-
# fmt: off
1614
number: int = Field(
1715
description=(
18-
'This is a description of the field number.\n'
19-
'In an actual plugin all fields must have a description.'
16+
'This is a description of the field "number".\n'
17+
'In an actual plugin all fields should have a description.'
2018
),
2119
)
22-
# fmt: on
2320
name: str
2421
first_byte: str
2522
virtual_file_path: dict
2623
dependant_analysis: dict
2724

2825
def __init__(self):
2926
metadata = self.MetaData(
27+
# mandatory fields:
3028
name='ExamplePlugin',
3129
description='An example description',
3230
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.
31+
Schema=self.Schema,
32+
# optional fields:
3633
system_version=None,
3734
mime_blacklist=[],
3835
mime_whitelist=[],
@@ -41,11 +38,11 @@ def __init__(self):
4138
)
4239
super().__init__(metadata=metadata)
4340

44-
def summarize(self, result):
41+
def summarize(self, result: Schema) -> list[str]:
4542
del result
4643
return ['big-file', 'binary']
4744

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

src/web_interface/filter.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import annotations
2-
31
import binascii
42
import ipaddress
53
import json
@@ -16,7 +14,7 @@
1614
from re import Match
1715
from string import ascii_letters
1816
from time import localtime, strftime, struct_time, time
19-
from typing import TYPE_CHECKING, Any, Iterable, Union
17+
from typing import Any, Iterable, Union
2018

2119
import packaging.version
2220
import semver
@@ -27,13 +25,11 @@
2725
from helperFunctions.data_conversion import make_unicode_string
2826
from helperFunctions.tag import TagColor
2927
from helperFunctions.web_interface import get_alternating_color_list
28+
from objects.file import FileObject
3029
from web_interface.file_tree.file_tree import get_icon_for_mime, get_mime_for_text_file
3130
from web_interface.security.authentication import user_has_privilege
3231
from web_interface.security.privileges import PRIVILEGES
3332

34-
if TYPE_CHECKING:
35-
from objects.file import FileObject
36-
3733
BYTE_FORMAT_THRESHOLD = 2**10
3834

3935

@@ -302,7 +298,7 @@ def render_analysis_tags(tags, uid=None, root_uid=None, size=14):
302298
for key, tag in sorted(tags[plugin_name].items(), key=_sort_tags_key):
303299
if key == 'root_uid':
304300
continue
305-
color = tag['color'] if tag['color'] in TagColor.ALL else TagColor.BLUE
301+
color = tag['color'] if tag['color'] in set(TagColor) else TagColor.BLUE
306302
output += render_template(
307303
'generic_view/tags.html',
308304
color=color,
@@ -465,9 +461,7 @@ def render_changed_text_files(changed_text_files: dict) -> str:
465461
f' {inner}\n'
466462
'</div>\n'
467463
)
468-
elements.append(
469-
f'<div class="list-group-item border-top" style="padding: 0 0 0 25px">\n' f'{element}\n' '</div>'
470-
)
464+
elements.append(f'<div class="list-group-item border-top" style="padding: 0 0 0 25px">\n{element}\n</div>')
471465
return '\n'.join(elements)
472466

473467

0 commit comments

Comments
 (0)