Skip to content

Commit 0a8e795

Browse files
committed
added global configs support
1 parent 66a3cb7 commit 0a8e795

File tree

5 files changed

+157
-15
lines changed

5 files changed

+157
-15
lines changed

nodescraper/interfaces/dataplugin.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
SystemInfo,
3939
TaskResult,
4040
)
41+
from nodescraper.utils import normalize_enum
4142

4243
from .connectionmanager import TConnectArg, TConnectionManager
4344
from .task import SystemCompatibilityError
@@ -162,6 +163,10 @@ def collect(
162163
return self.collection_result
163164

164165
try:
166+
max_event_priority_level = normalize_enum(max_event_priority_level, EventPriority)
167+
system_interaction_level = normalize_enum(
168+
system_interaction_level, SystemInteractionLevel
169+
)
165170
if not self.connection_manager:
166171
if not self.CONNECTION_TYPE:
167172
self.collection_result = TaskResult(
@@ -241,6 +246,9 @@ def analyze(
241246
Returns:
242247
TaskResult: result of data analysis
243248
"""
249+
250+
max_event_priority_level = normalize_enum(max_event_priority_level, EventPriority)
251+
244252
if self.ANALYZER is None:
245253
self.analysis_result = TaskResult(
246254
status=ExecutionStatus.NOT_RAN,
@@ -298,6 +306,8 @@ def run(
298306
PluginResult: Plugin result
299307
"""
300308
self.logger.info("Running plugin %s", self.__class__.__name__)
309+
max_event_priority_level = normalize_enum(max_event_priority_level, EventPriority)
310+
system_interaction_level = normalize_enum(system_interaction_level, SystemInteractionLevel)
301311
if collection:
302312
self.collect(
303313
max_event_priority_level=max_event_priority_level,

nodescraper/pluginexecutor.py

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
from pydantic import BaseModel
3434

3535
from nodescraper.constants import DEFAULT_LOGGER
36-
from nodescraper.interfaces import ConnectionManager, DataPlugin
36+
from nodescraper.enums.eventpriority import EventPriority
37+
from nodescraper.enums.systeminteraction import SystemInteractionLevel
38+
from nodescraper.interfaces import ConnectionManager, DataPlugin, PluginInterface
3739
from nodescraper.models import PluginConfig, SystemInfo
3840
from nodescraper.models.pluginresult import PluginResult
3941
from nodescraper.pluginregistry import PluginRegistry
@@ -167,14 +169,29 @@ def run_queue(self) -> list[PluginResult]:
167169
run_payload = copy.deepcopy(plugin_args)
168170

169171
run_args = TypeUtils.get_func_arg_types(plugin_class.run, plugin_class)
172+
173+
enum_fields = {
174+
"max_event_priority_level": EventPriority,
175+
"system_interaction_level": SystemInteractionLevel,
176+
}
177+
170178
for arg in run_args.keys():
171179
if arg == "preserve_connection" and issubclass(plugin_class, DataPlugin):
172180
run_payload[arg] = True
173181
elif arg in self.plugin_config.global_args:
174182
run_payload[arg] = self.plugin_config.global_args[arg]
183+
try:
184+
self.apply_global_args_to_plugin(
185+
plugin_inst, plugin_class, self.plugin_config.global_args, enum_fields
186+
)
187+
except ValueError as ve:
188+
self.logger.error(
189+
"Invalid global_args for plugin %s: %s. Skipping plugin.",
190+
plugin_name,
191+
str(ve),
192+
)
193+
continue
175194

176-
# TODO
177-
# enable global substitution in collection and analysis args
178195
self.logger.info("-" * 50)
179196
plugin_results.append(plugin_inst.run(**run_payload))
180197
except Exception as e:
@@ -210,3 +227,68 @@ def run_queue(self) -> list[PluginResult]:
210227
)
211228

212229
return plugin_results
230+
231+
def apply_global_args_to_plugin(
232+
self, plugin_inst: PluginInterface, plugin_class: type, global_args: dict, enum_fields: dict
233+
) -> None:
234+
"""
235+
Applies global arguments to the plugin instance, including standard attributes
236+
and merging Pydantic model arguments (collection_args, analysis_args).
237+
238+
Args:
239+
plugin_inst: The plugin instance to update.
240+
plugin_class: The plugin class (needed for model instantiation).
241+
global_args: Dict of global argument overrides.
242+
"""
243+
244+
simple_keys = [
245+
"collection",
246+
"analysis",
247+
"max_event_priority_level",
248+
"system_interaction_level",
249+
"preserve_connection",
250+
"data",
251+
]
252+
253+
for key in simple_keys:
254+
if key in global_args:
255+
value = global_args[key]
256+
257+
if key in enum_fields:
258+
enum_type = enum_fields[key]
259+
if isinstance(value, str):
260+
try:
261+
value = enum_type[value.upper()]
262+
except KeyError as kerr:
263+
raise ValueError(
264+
f"Invalid enum name '{value}' for {enum_type.__name__}"
265+
) from kerr
266+
elif isinstance(value, int):
267+
try:
268+
value = enum_type(value)
269+
except ValueError as verr:
270+
raise ValueError(
271+
f"Invalid enum value '{value}' for {enum_type.__name__}"
272+
) from verr
273+
274+
setattr(plugin_inst, key, value)
275+
276+
if "collection_args" in global_args:
277+
update_data = global_args["collection_args"]
278+
existing = getattr(plugin_inst, "COLLECTION_ARGS", None)
279+
model_class = getattr(plugin_class, "COLLECTOR_ARGS", None)
280+
281+
if isinstance(existing, BaseModel):
282+
plugin_inst.COLLECTION_ARGS = existing.model_copy(update=update_data)
283+
elif model_class and issubclass(model_class, BaseModel):
284+
plugin_inst.COLLECTION_ARGS = model_class.model_validate(update_data)
285+
286+
if "analysis_args" in global_args:
287+
update_data = global_args["analysis_args"]
288+
existing = getattr(plugin_inst, "ANALYSIS_ARGS", None)
289+
model_class = getattr(plugin_class, "ANALYZER_ARGS", None)
290+
291+
if isinstance(existing, BaseModel):
292+
plugin_inst.ANALYSIS_ARGS = existing.model_copy(update=update_data)
293+
elif model_class and issubclass(model_class, BaseModel):
294+
plugin_inst.ANALYSIS_ARGS = model_class.model_validate(update_data)

nodescraper/utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,13 @@ def bytes_to_human_readable(input_bytes: int) -> str:
169169

170170
gb = round(mb / 1000, 2)
171171
return f"{gb}GB"
172+
173+
174+
def normalize_enum(value, enum_type):
175+
if isinstance(value, enum_type):
176+
return value
177+
elif isinstance(value, int):
178+
return enum_type(value)
179+
elif isinstance(value, str):
180+
return enum_type[value.upper()]
181+
raise ValueError(f"Invalid value '{value}' for enum {enum_type.__name__}")

test/unit/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class DummyDataModel(DataModel):
5555

5656
class DummyArg(BaseModel):
5757
value: int
58+
regex_match: bool = True
5859

5960

6061
class DummyResult:

test/unit/framework/test_plugin_executor.py

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,40 @@
2424
#
2525
###############################################################################
2626
import pytest
27-
from common.shared_utils import MockConnectionManager
27+
from common.shared_utils import DummyDataModel, MockConnectionManager
28+
from pydantic import BaseModel
2829

2930
from nodescraper.enums import ExecutionStatus
31+
from nodescraper.enums.eventpriority import EventPriority
32+
from nodescraper.enums.systeminteraction import SystemInteractionLevel
3033
from nodescraper.interfaces import PluginInterface
3134
from nodescraper.models import PluginConfig, PluginResult
3235
from nodescraper.pluginexecutor import PluginExecutor
3336
from nodescraper.pluginregistry import PluginRegistry
3437

3538

36-
class TestPluginA(PluginInterface[MockConnectionManager, None]):
39+
class DummyArgs(BaseModel):
40+
foo: str = "bar"
41+
regex_match: bool = True
42+
3743

44+
class TestPluginA(PluginInterface[MockConnectionManager, None]):
3845
CONNECTION_TYPE = MockConnectionManager
46+
COLLECTION_ARGS = DummyArgs(foo="initial")
47+
ANALYSIS_ARGS = DummyArgs(foo="initial")
48+
collection = False
49+
analysis = False
50+
preserve_connection = False
51+
data = DummyDataModel(some_version="1")
52+
max_event_priority_level = EventPriority.INFO
53+
system_interaction_level = SystemInteractionLevel.PASSIVE
3954

4055
def run(self):
4156
self._update_queue(("TestPluginB", {}))
42-
return PluginResult(
43-
source="testA",
44-
status=ExecutionStatus.ERROR,
45-
)
57+
return PluginResult(source="testA", status=ExecutionStatus.ERROR)
4658

4759

4860
class TestPluginB(PluginInterface[MockConnectionManager, None]):
49-
5061
CONNECTION_TYPE = MockConnectionManager
5162

5263
def run(self, test_arg=None):
@@ -67,10 +78,7 @@ def plugin_registry():
6778
"input_configs, output_config",
6879
[
6980
(
70-
[
71-
PluginConfig(plugins={"Plugin1": {}}),
72-
PluginConfig(plugins={"Plugin2": {}}),
73-
],
81+
[PluginConfig(plugins={"Plugin1": {}}), PluginConfig(plugins={"Plugin2": {}})],
7482
PluginConfig(plugins={"Plugin1": {}, "Plugin2": {}}),
7583
),
7684
(
@@ -109,7 +117,8 @@ def test_plugin_queue(plugin_registry):
109117

110118
def test_queue_callback(plugin_registry):
111119
executor = PluginExecutor(
112-
plugin_configs=[PluginConfig(plugins={"TestPluginA": {}})], plugin_registry=plugin_registry
120+
plugin_configs=[PluginConfig(plugins={"TestPluginA": {}})],
121+
plugin_registry=plugin_registry,
113122
)
114123

115124
results = executor.run_queue()
@@ -119,3 +128,33 @@ def test_queue_callback(plugin_registry):
119128
assert results[0].status == ExecutionStatus.ERROR
120129
assert results[1].source == "testB"
121130
assert results[1].status == ExecutionStatus.OK
131+
132+
133+
def test_apply_global_args_to_plugin():
134+
plugin = TestPluginA()
135+
global_args = {
136+
"collection": True,
137+
"analysis": True,
138+
"preserve_connection": True,
139+
"data": {"some_version": "1"},
140+
"max_event_priority_level": 4,
141+
"system_interaction_level": "INTERACTIVE",
142+
"collection_args": {"foo": "collected", "regex_match": False},
143+
"analysis_args": {"foo": "analyzed", "regex_match": False},
144+
}
145+
enum_fields = {
146+
"max_event_priority_level": EventPriority,
147+
"system_interaction_level": SystemInteractionLevel,
148+
}
149+
150+
executor = PluginExecutor(plugin_configs=[])
151+
executor.apply_global_args_to_plugin(plugin, TestPluginA, global_args, enum_fields)
152+
153+
assert plugin.collection is True
154+
assert plugin.analysis is True
155+
assert plugin.preserve_connection is True
156+
assert plugin.data["some_version"] == "1"
157+
assert plugin.max_event_priority_level == EventPriority.CRITICAL
158+
assert plugin.system_interaction_level == SystemInteractionLevel.INTERACTIVE
159+
assert plugin.COLLECTION_ARGS.foo == "collected"
160+
assert plugin.ANALYSIS_ARGS.foo == "analyzed"

0 commit comments

Comments
 (0)