From 204f209b1ebb82f83e53c5dd2232115c33f5c484 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Thu, 21 Aug 2025 09:57:56 -0500 Subject: [PATCH 01/10] test --- nodescraper/plugins/inband/dmesg/dmesg_collector.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nodescraper/plugins/inband/dmesg/dmesg_collector.py b/nodescraper/plugins/inband/dmesg/dmesg_collector.py index 9be59793..6c472b5a 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_collector.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_collector.py @@ -40,7 +40,7 @@ class DmesgCollector(InBandDataCollector[DmesgData, DmesgCollectorArgs]): DATA_MODEL = DmesgData - DMESG_CMD = "dmesg --time-format iso -x" + DMESG_CMD = "dmesg --time-format iso -x " def _get_dmesg_content(self) -> str: """run dmesg command on system and return output diff --git a/pyproject.toml b/pyproject.toml index 888b1e9f..3b3d9d1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.0.1" description = "A framework for automated error detection and data collection" authors = [] readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.9" keywords = [] From 459e526bc6c11b8b1a07d2b8b29e4861cdf00a6f Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 10 Oct 2025 11:56:18 -0500 Subject: [PATCH 02/10] downgraded to py3.9 --- nodescraper/cli/inputargtypes.py | 4 ++-- pyproject.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nodescraper/cli/inputargtypes.py b/nodescraper/cli/inputargtypes.py index ac0ba17f..19692350 100644 --- a/nodescraper/cli/inputargtypes.py +++ b/nodescraper/cli/inputargtypes.py @@ -26,14 +26,14 @@ import argparse import json import types -from typing import Generic, Type +from typing import Generic, Optional, Type from pydantic import ValidationError from nodescraper.generictypes import TModelType -def log_path_arg(log_path: str) -> str | None: +def log_path_arg(log_path: str) -> Optional[str]: """Type function for a log path arg, allows 'none' to be specified to disable logging Args: diff --git a/pyproject.toml b/pyproject.toml index 3b3d9d1a..4526dfe9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.0.1" description = "A framework for automated error detection and data collection" authors = [] readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.8" keywords = [] @@ -50,7 +50,7 @@ node-scraper = "nodescraper.cli:cli_entry" [tool.black] line-length = 100 -target_version = ['py310'] +target_version = ['py39'] [tool.isort] profile = "black" From 283893a9cf5f5963abbe4a4772a95b34d164d24e Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 10 Oct 2025 12:02:00 -0500 Subject: [PATCH 03/10] some updates --- nodescraper/cli/cli.py | 4 ++-- nodescraper/cli/dynamicparserbuilder.py | 6 +++--- nodescraper/cli/helper.py | 6 +++--- nodescraper/cli/inputargtypes.py | 5 ++--- nodescraper/interfaces/connectionmanager.py | 2 +- nodescraper/interfaces/datacollectortask.py | 8 ++++---- nodescraper/interfaces/dataplugin.py | 6 +++--- nodescraper/models/taskresult.py | 2 +- 8 files changed, 19 insertions(+), 20 deletions(-) diff --git a/nodescraper/cli/cli.py b/nodescraper/cli/cli.py index 727c9570..5b35a9d4 100644 --- a/nodescraper/cli/cli.py +++ b/nodescraper/cli/cli.py @@ -262,12 +262,12 @@ def build_parser( return parser, plugin_subparser_map -def setup_logger(log_level: str = "INFO", log_path: str | None = None) -> logging.Logger: +def setup_logger(log_level: str = "INFO", log_path: Optional[str] = None) -> logging.Logger: """set up root logger when using the CLI Args: log_level (str): log level to use - log_path (str | None): optional path to filesystem log location + log_path (Optional[str]): optional path to filesystem log location Returns: logging.Logger: logger intstance diff --git a/nodescraper/cli/dynamicparserbuilder.py b/nodescraper/cli/dynamicparserbuilder.py index 266201de..6df22c6b 100644 --- a/nodescraper/cli/dynamicparserbuilder.py +++ b/nodescraper/cli/dynamicparserbuilder.py @@ -25,7 +25,7 @@ ############################################################################### import argparse import types -from typing import Type +from typing import Optional, Type from pydantic import BaseModel @@ -75,14 +75,14 @@ def build_plugin_parser(self) -> dict: return model_type_map @classmethod - def get_model_arg(cls, type_class_map: dict) -> Type[BaseModel] | None: + def get_model_arg(cls, type_class_map: dict) -> Optional[Type[BaseModel]]: """Get the first type which is a pydantic model from a type class map Args: type_class_map (dict): mapping of type classes Returns: - Type[BaseModel] | None: pydantic model type + Optional[Type[BaseModel]]: pydantic model type """ return next( ( diff --git a/nodescraper/cli/helper.py b/nodescraper/cli/helper.py index 994f752c..bb81400e 100644 --- a/nodescraper/cli/helper.py +++ b/nodescraper/cli/helper.py @@ -267,7 +267,7 @@ def parse_gen_plugin_config( sys.exit(1) -def log_system_info(log_path: str | None, system_info: SystemInfo, logger: logging.Logger): +def log_system_info(log_path: Optional[str], system_info: SystemInfo, logger: logging.Logger): """dump system info object to json log Args: @@ -479,12 +479,12 @@ def dump_to_csv(all_rows: list, filename: str, fieldnames: list[str], logger: lo logger.info("Data written to csv file: %s", filename) -def generate_summary(search_path: str, output_path: str | None, logger: logging.Logger): +def generate_summary(search_path: str, output_path: Optional[str], logger: logging.Logger): """Concatenate csv files into 1 summary csv file Args: search_path (str): Path for previous runs - output_path (str | None): Path for new summary csv file + output_path (Optional[str]): Path for new summary csv file logger (logging.Logger): instance of logger """ diff --git a/nodescraper/cli/inputargtypes.py b/nodescraper/cli/inputargtypes.py index 19692350..7faa9c5c 100644 --- a/nodescraper/cli/inputargtypes.py +++ b/nodescraper/cli/inputargtypes.py @@ -25,7 +25,6 @@ ############################################################################### import argparse import json -import types from typing import Generic, Optional, Type from pydantic import ValidationError @@ -40,7 +39,7 @@ def log_path_arg(log_path: str) -> Optional[str]: log_path (str): log path string Returns: - str | None: log path or None + Optional[str]: log path or None """ if log_path.lower() == "none": return None @@ -84,7 +83,7 @@ def dict_arg(str_input: str) -> dict: class ModelArgHandler(Generic[TModelType]): """Class to handle loading json files into pydantic models""" - def __init__(self, model: Type[TModelType]) -> types.NoneType: + def __init__(self, model: Type[TModelType]) -> None: self.model = model def process_file_arg(self, file_path: str) -> TModelType: diff --git a/nodescraper/interfaces/connectionmanager.py b/nodescraper/interfaces/connectionmanager.py index 988a83f2..ad3f735d 100644 --- a/nodescraper/interfaces/connectionmanager.py +++ b/nodescraper/interfaces/connectionmanager.py @@ -113,7 +113,7 @@ def __init__( connection_args = connection_arg_model(**connection_args) self.connection_args = connection_args - self.connection: TConnection | None = None + self.connection: Optional[TConnection] = None def __init_subclass__(cls, **kwargs) -> None: super().__init_subclass__(**kwargs) diff --git a/nodescraper/interfaces/datacollectortask.py b/nodescraper/interfaces/datacollectortask.py index 7a778ed7..27a37ae1 100644 --- a/nodescraper/interfaces/datacollectortask.py +++ b/nodescraper/interfaces/datacollectortask.py @@ -48,12 +48,12 @@ def collect_decorator( - func: Callable[..., tuple[TaskResult, TDataModel | None]], -) -> Callable[..., tuple[TaskResult, TDataModel | None]]: + func: Callable[..., tuple[TaskResult, Optional[TDataModel]]], +) -> Callable[..., tuple[TaskResult, Optional[TDataModel]]]: @wraps(func) def wrapper( collector: "DataCollector", args: Optional[TCollectArg] = None - ) -> tuple[TaskResult, TDataModel | None]: + ) -> tuple[TaskResult, Optional[TDataModel]]: collector.logger.info("Running data collector: %s", collector.__class__.__name__) collector.result = collector._init_result() try: @@ -175,7 +175,7 @@ def __init_subclass__(cls, **kwargs) -> None: @abc.abstractmethod def collect_data( self, args: Optional[TCollectArg] = None - ) -> tuple[TaskResult, TDataModel | None]: + ) -> tuple[TaskResult, Optional[TDataModel]]: """Collect data from a target system Returns: diff --git a/nodescraper/interfaces/dataplugin.py b/nodescraper/interfaces/dataplugin.py index 2a9830be..05e81c4e 100644 --- a/nodescraper/interfaces/dataplugin.py +++ b/nodescraper/interfaces/dataplugin.py @@ -87,7 +87,7 @@ def __init__( status=ExecutionStatus.NOT_RAN, message=f"Data analysis not ran for {self.__class__.__name__}", ) - self._data: TDataModel | None = None + self._data: Optional[TDataModel] = None @classmethod def _validate_class_var(cls): @@ -118,11 +118,11 @@ def is_valid(cls) -> bool: return super().is_valid() @property - def data(self) -> TDataModel | None: + def data(self) -> Optional[TDataModel]: """Retrieve data model Returns: - TDataModel | None: data model + Optional[TDataModel]: data model """ return self._data diff --git a/nodescraper/models/taskresult.py b/nodescraper/models/taskresult.py index c260779d..f4dbb251 100644 --- a/nodescraper/models/taskresult.py +++ b/nodescraper/models/taskresult.py @@ -89,7 +89,7 @@ def validate_status(cls, v: Any): return v @property - def duration(self) -> str | None: + def duration(self) -> Optional[str]: """return duration of time as a string Returns: From d0f1852fa4eab56b2020145d8f1c8dc6ed737437 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 10 Oct 2025 12:17:22 -0500 Subject: [PATCH 04/10] more updates --- nodescraper/base/inbandcollectortask.py | 4 +-- nodescraper/base/regexanalyzer.py | 18 ++++++------ nodescraper/cli/dynamicparserbuilder.py | 5 ++-- nodescraper/interfaces/connectionmanager.py | 6 ++-- nodescraper/interfaces/datacollectortask.py | 4 +-- nodescraper/interfaces/dataplugin.py | 32 +++++++++++---------- nodescraper/interfaces/task.py | 8 +++--- nodescraper/models/datamodel.py | 8 +++--- nodescraper/models/event.py | 6 ++-- nodescraper/models/pluginresult.py | 4 +-- 10 files changed, 49 insertions(+), 46 deletions(-) diff --git a/nodescraper/base/inbandcollectortask.py b/nodescraper/base/inbandcollectortask.py index 85630592..16039bda 100644 --- a/nodescraper/base/inbandcollectortask.py +++ b/nodescraper/base/inbandcollectortask.py @@ -24,7 +24,7 @@ # ############################################################################### import logging -from typing import Generic, Optional +from typing import Generic, Optional, Union from nodescraper.connection.inband import InBandConnection from nodescraper.connection.inband.inband import BaseFileArtifact, CommandArtifact @@ -49,7 +49,7 @@ def __init__( connection: InBandConnection, logger: Optional[logging.Logger] = None, system_interaction_level: SystemInteractionLevel = SystemInteractionLevel.INTERACTIVE, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, + max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL, parent: Optional[str] = None, task_result_hooks: Optional[list[TaskResultHook]] = None, **kwargs, diff --git a/nodescraper/base/regexanalyzer.py b/nodescraper/base/regexanalyzer.py index b5cdc5e4..0a37b2aa 100644 --- a/nodescraper/base/regexanalyzer.py +++ b/nodescraper/base/regexanalyzer.py @@ -24,6 +24,7 @@ # ############################################################################### import re +from typing import Union from pydantic import BaseModel @@ -36,7 +37,7 @@ class ErrorRegex(BaseModel): regex: re.Pattern message: str - event_category: str | EventCategory = EventCategory.UNKNOWN + event_category: Union[str, EventCategory] = EventCategory.UNKNOWN event_priority: EventPriority = EventPriority.ERROR @@ -54,17 +55,18 @@ class RegexAnalyzer(DataAnalyzer[TDataModel, TAnalyzeArg]): """Parent class for all regex based data analyzers.""" def _build_regex_event( - self, regex_obj: ErrorRegex, match: str | list[str], source: str + self, regex_obj: ErrorRegex, match: Union[str, list[str]], source: str ) -> RegexEvent: """Build a RegexEvent object from a regex match and source. - Args: - regex_obj (ErrorRegex): regex object containing the regex pattern, message, category, and priorit - match (str | list[str]): matched content from the regex - source (str): descriptor for the content where the match was found + Args: + regex_obj (ErrorRegex): regex object containing the regex pattern, message, category, and priorit + match ( + Union[str, list[str]]): matched content from the regex + source (str): descriptor for the content where the match was found - Returns: - RegexEvent: an instance of RegexEvent containing the match details + Returns: + RegexEvent: an instance of RegexEvent containing the match details """ return RegexEvent( description=regex_obj.message, diff --git a/nodescraper/cli/dynamicparserbuilder.py b/nodescraper/cli/dynamicparserbuilder.py index 6df22c6b..87d0fe0f 100644 --- a/nodescraper/cli/dynamicparserbuilder.py +++ b/nodescraper/cli/dynamicparserbuilder.py @@ -24,7 +24,6 @@ # ############################################################################### import argparse -import types from typing import Optional, Type from pydantic import BaseModel @@ -59,7 +58,7 @@ def build_plugin_parser(self) -> dict: } # skip args where generic type has been set to None - if types.NoneType in type_class_map: + if type(None) in type_class_map: continue model_arg = self.get_model_arg(type_class_map) @@ -164,7 +163,7 @@ def build_model_arg_parser(self, model: type[BaseModel], required: bool) -> list type_class.type_class: type_class for type_class in attr_data.type_classes } - if types.NoneType in type_class_map and len(attr_data.type_classes) == 1: + if type(None) in type_class_map and len(attr_data.type_classes) == 1: continue self.add_argument(type_class_map, attr.replace("_", "-"), required) diff --git a/nodescraper/interfaces/connectionmanager.py b/nodescraper/interfaces/connectionmanager.py index ad3f735d..abf3d548 100644 --- a/nodescraper/interfaces/connectionmanager.py +++ b/nodescraper/interfaces/connectionmanager.py @@ -29,7 +29,7 @@ import logging import types from functools import wraps -from typing import Callable, Generic, Optional, TypeVar +from typing import Callable, Generic, Optional, TypeVar, Union from pydantic import BaseModel @@ -89,9 +89,9 @@ def __init__( self, system_info: SystemInfo, logger: Optional[logging.Logger] = None, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, + max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL, parent: Optional[str] = None, - task_result_hooks: list[TaskResultHook] | types.NoneType = None, + task_result_hooks: Optional[list[TaskResultHook], None] = None, connection_args: Optional[TConnectArg | dict] = None, **kwargs, ): diff --git a/nodescraper/interfaces/datacollectortask.py b/nodescraper/interfaces/datacollectortask.py index 27a37ae1..ae59a1b0 100644 --- a/nodescraper/interfaces/datacollectortask.py +++ b/nodescraper/interfaces/datacollectortask.py @@ -27,7 +27,7 @@ import inspect import logging from functools import wraps -from typing import Callable, ClassVar, Generic, Optional, Type +from typing import Callable, ClassVar, Generic, Optional, Type, Union from pydantic import BaseModel, ValidationError @@ -123,7 +123,7 @@ def __init__( connection: TConnection, logger: Optional[logging.Logger] = None, system_interaction_level: SystemInteractionLevel | str = SystemInteractionLevel.INTERACTIVE, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, + max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL, parent: Optional[str] = None, task_result_hooks: Optional[list[TaskResultHook]] = None, **kwargs, diff --git a/nodescraper/interfaces/dataplugin.py b/nodescraper/interfaces/dataplugin.py index 05e81c4e..95cc07b2 100644 --- a/nodescraper/interfaces/dataplugin.py +++ b/nodescraper/interfaces/dataplugin.py @@ -24,7 +24,7 @@ # ############################################################################### import logging -from typing import Generic, Optional, Type +from typing import Generic, Optional, Type, Union from nodescraper.enums import EventPriority, ExecutionStatus, SystemInteractionLevel from nodescraper.generictypes import TAnalyzeArg, TCollectArg, TDataModel @@ -64,7 +64,7 @@ def __init__( system_info: SystemInfo, logger: Optional[logging.Logger] = None, connection_manager: Optional[TConnectionManager] = None, - connection_args: Optional[TConnectArg | dict] = None, + connection_args: Optional[Union[TConnectArg, dict]] = None, task_result_hooks: Optional[list[TaskResultHook]] = None, log_path: Optional[str] = None, **kwargs, @@ -127,7 +127,7 @@ def data(self) -> Optional[TDataModel]: return self._data @data.setter - def data(self, data: str | dict | TDataModel): + def data(self, data: Optional[str, dict, TDataModel]): if isinstance(data, (str, dict)): self._data = self.DATA_MODEL.import_model(data) elif not isinstance(data, self.DATA_MODEL): @@ -137,15 +137,17 @@ def data(self, data: str | dict | TDataModel): def collect( self, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, - system_interaction_level: SystemInteractionLevel | str = SystemInteractionLevel.INTERACTIVE, + max_event_priority_level: Optional[Union[EventPriority, str]] = EventPriority.CRITICAL, + system_interaction_level: Optional[ + Union[SystemInteractionLevel, str] + ] = SystemInteractionLevel.INTERACTIVE, preserve_connection: bool = False, - collection_args: Optional[TCollectArg | dict] = None, + collection_args: Optional[Union[TCollectArg, dict]] = None, ) -> TaskResult: """Run data collector task Args: - max_event_priority_level (EventPriority | str, optional): priority limit for events. Defaults to EventPriority.CRITICAL. + max_event_priority_level (Union[EventPriority, str], optional): priority limit for events. Defaults to EventPriority.CRITICAL. system_interaction_level (SystemInteractionLevel | str, optional): system interaction level. Defaults to SystemInteractionLevel.INTERACTIVE. preserve_connection (bool, optional): whether we should close the connection after data collection. Defaults to False. collection_args (Optional[TCollectArg | dict], optional): args for data collection. Defaults to None. @@ -227,16 +229,16 @@ def collect( def analyze( self, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, + max_event_priority_level: Optional[Union[EventPriority, str]] = EventPriority.CRITICAL, analysis_args: Optional[TAnalyzeArg | dict] = None, - data: Optional[str | dict | TDataModel] = None, + data: Optional[Union[str, dict, TDataModel]] = None, ) -> TaskResult: """Run data analyzer task Args: - max_event_priority_level (EventPriority | str, optional): priority limit for events. Defaults to EventPriority.CRITICAL. + max_event_priority_level (Union[EventPriority, str], optional): priority limit for events. Defaults to EventPriority.CRITICAL. analysis_args (Optional[TAnalyzeArg | dict], optional): args for data analysis. Defaults to None. - data (Optional[str | dict | TDataModel], optional): data to analyze. Defaults to None. + data (Optional[Union[str, dict, TDataModel]], optional): data to analyze. Defaults to None. Returns: TaskResult: result of data analysis @@ -276,10 +278,10 @@ def run( self, collection: bool = True, analysis: bool = True, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, + max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL, system_interaction_level: SystemInteractionLevel | str = SystemInteractionLevel.INTERACTIVE, preserve_connection: bool = False, - data: Optional[str | dict | TDataModel] = None, + data: Optional[Union[str, dict, TDataModel]] = None, collection_args: Optional[TCollectArg | dict] = None, analysis_args: Optional[TAnalyzeArg | dict] = None, ) -> PluginResult: @@ -288,10 +290,10 @@ def run( Args: collection (bool, optional): Enable data collection. Defaults to True. analysis (bool, optional): Enable data analysis. Defaults to True. - max_event_priority_level (EventPriority | str, optional): Max priority level to assign to events. Defaults to EventPriority.CRITICAL. + max_event_priority_level (Union[EventPriority, str], optional): Max priority level to assign to events. Defaults to EventPriority.CRITICAL. system_interaction_level (SystemInteractionLevel | str, optional): System interaction level. Defaults to SystemInteractionLevel.INTERACTIVE. preserve_connection (bool, optional): Whether to close the connection when data collection is complete. Defaults to False. - data (Optional[str | dict | TDataModel], optional): Input data. Defaults to None. + data (Optional[Union[str, dict, TDataModel]], optional): Input data. Defaults to None. collection_args (Optional[TCollectArg | dict], optional): Arguments for data collection. Defaults to None. analysis_args (Optional[TAnalyzeArg | dict], optional): Arguments for data analysis. Defaults to None. diff --git a/nodescraper/interfaces/task.py b/nodescraper/interfaces/task.py index 82b2252c..8f9dd399 100644 --- a/nodescraper/interfaces/task.py +++ b/nodescraper/interfaces/task.py @@ -27,7 +27,7 @@ import copy import datetime import logging -from typing import Any, Optional +from typing import Any, Optional, Union from nodescraper.constants import DEFAULT_LOGGER from nodescraper.enums import EventCategory, EventPriority @@ -51,7 +51,7 @@ def __init__( self, system_info: SystemInfo, logger: Optional[logging.Logger] = None, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, + max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL, parent: Optional[str] = None, task_result_hooks: Optional[list[TaskResultHook]] = None, **kwargs: dict[str, Any], @@ -96,7 +96,7 @@ def __init_subclass__(cls, **kwargs) -> None: def _build_event( self, - category: EventCategory | str, + category: Union[EventCategory, str], description: str, priority: EventPriority, data: Optional[dict] = None, @@ -133,7 +133,7 @@ def _build_event( def _log_event( self, - category: EventCategory | str, + category: Union[EventCategory, str], description: str, priority: EventPriority, data: Optional[dict] = None, diff --git a/nodescraper/models/datamodel.py b/nodescraper/models/datamodel.py index 32211c45..78a5df06 100644 --- a/nodescraper/models/datamodel.py +++ b/nodescraper/models/datamodel.py @@ -27,7 +27,7 @@ import json import os import tarfile -from typing import TypeVar +from typing import TypeVar, Union from pydantic import BaseModel, Field, field_validator @@ -42,7 +42,7 @@ class FileModel(BaseModel): @field_validator("file_contents", mode="before") @classmethod - def file_contents_conformer(cls, value: io.BytesIO | str | bytes) -> bytes: + def file_contents_conformer(cls, value: Union[io.BytesIO, str, bytes]) -> bytes: if isinstance(value, io.BytesIO): return value.getvalue() if isinstance(value, str): @@ -92,7 +92,7 @@ def merge_data(self, input_data: "DataModel") -> None: pass @classmethod - def import_model(cls: type[TDataModel], model_input: dict | str) -> TDataModel: + def import_model(cls: type[TDataModel], model_input: Union[dict, str]) -> TDataModel: """import a data model if the input is a string attempt to read data from file using the string as a file name if input is a dict, pass key value pairs directly to init function @@ -100,7 +100,7 @@ def import_model(cls: type[TDataModel], model_input: dict | str) -> TDataModel: Args: cls (type[DataModel]): Data model class - model_input (dict | str): model data input + model_input (Union[dict, str]): model data input Raises: ValueError: if model_input has an invalid type diff --git a/nodescraper/models/event.py b/nodescraper/models/event.py index 947be3d8..c65f082b 100644 --- a/nodescraper/models/event.py +++ b/nodescraper/models/event.py @@ -28,7 +28,7 @@ import re import uuid from enum import Enum -from typing import Optional +from typing import Optional, Union from pydantic import BaseModel, Field, field_serializer, field_validator @@ -80,7 +80,7 @@ def validate_timestamp(cls, timestamp: datetime.datetime) -> datetime.datetime: @field_validator("category", mode="before") @classmethod - def validate_category(cls, category: str | Enum) -> str: + def validate_category(cls, category: Optional[Union[str, Enum]]) -> str: """ensure category is has consistent formatting Args: category (str | Enum): category string @@ -96,7 +96,7 @@ def validate_category(cls, category: str | Enum) -> str: @field_validator("priority", mode="before") @classmethod - def validate_priority(cls, priority: str | EventPriority) -> EventPriority: + def validate_priority(cls, priority: Optional[Union[str, EventPriority]]) -> EventPriority: """Allow priority to be set via string priority name Args: priority (str | EventPriority): event priority string or enum diff --git a/nodescraper/models/pluginresult.py b/nodescraper/models/pluginresult.py index c3f65cee..0781243b 100644 --- a/nodescraper/models/pluginresult.py +++ b/nodescraper/models/pluginresult.py @@ -23,7 +23,7 @@ # SOFTWARE. # ############################################################################### -from typing import Optional +from typing import Optional, Union from pydantic import BaseModel @@ -36,4 +36,4 @@ class PluginResult(BaseModel): status: ExecutionStatus source: str message: Optional[str] = None - result_data: Optional[dict | BaseModel] = None + result_data: Optional[Union[dict, BaseModel]] = None From 037272dccd6890848ff7459bc989f949e55790ed Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 10 Oct 2025 12:23:43 -0500 Subject: [PATCH 05/10] more updates --- nodescraper/connection/inband/inbandremote.py | 4 ++-- nodescraper/interfaces/connectionmanager.py | 2 +- nodescraper/interfaces/dataanalyzertask.py | 8 +++---- nodescraper/interfaces/datacollectortask.py | 4 +++- nodescraper/interfaces/dataplugin.py | 24 ++++++++++--------- nodescraper/interfaces/plugin.py | 6 ++--- nodescraper/interfaces/task.py | 2 +- nodescraper/models/event.py | 2 +- 8 files changed, 28 insertions(+), 24 deletions(-) diff --git a/nodescraper/connection/inband/inbandremote.py b/nodescraper/connection/inband/inbandremote.py index a32e15ca..62f43229 100644 --- a/nodescraper/connection/inband/inbandremote.py +++ b/nodescraper/connection/inband/inbandremote.py @@ -25,7 +25,7 @@ ############################################################################### import os import socket -from typing import Type +from typing import Type, Union import paramiko from paramiko.ssh_exception import ( @@ -98,7 +98,7 @@ def connect_ssh(self): def read_file( self, filename: str, - encoding: str | None = "utf-8", + encoding: Union[str, None] = "utf-8", strip: bool = True, ) -> BaseFileArtifact: """Read a remote file into a BaseFileArtifact. diff --git a/nodescraper/interfaces/connectionmanager.py b/nodescraper/interfaces/connectionmanager.py index abf3d548..ccb5e793 100644 --- a/nodescraper/interfaces/connectionmanager.py +++ b/nodescraper/interfaces/connectionmanager.py @@ -92,7 +92,7 @@ def __init__( max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL, parent: Optional[str] = None, task_result_hooks: Optional[list[TaskResultHook], None] = None, - connection_args: Optional[TConnectArg | dict] = None, + connection_args: Optional[Union[TConnectArg, dict]] = None, **kwargs, ): super().__init__( diff --git a/nodescraper/interfaces/dataanalyzertask.py b/nodescraper/interfaces/dataanalyzertask.py index 116ae029..cef93e83 100644 --- a/nodescraper/interfaces/dataanalyzertask.py +++ b/nodescraper/interfaces/dataanalyzertask.py @@ -28,7 +28,7 @@ import abc import inspect from functools import wraps -from typing import Any, Callable, Generic, Optional, Type +from typing import Any, Callable, Generic, Optional, Type, Union from pydantic import BaseModel, ValidationError @@ -46,7 +46,7 @@ def analyze_decorator(func: Callable[..., TaskResult]) -> Callable[..., TaskResu def wrapper( analyzer: "DataAnalyzer", data: DataModel, - args: Optional[TAnalyzeArg | dict] = None, + args: Optional[Union[TAnalyzeArg, dict]] = None, ) -> TaskResult: analyzer.logger.info("Running data analyzer: %s", analyzer.__class__.__name__) analyzer.result = analyzer._init_result() @@ -125,13 +125,13 @@ def __init_subclass__(cls, **kwargs: dict[str, Any]) -> None: def analyze_data( self, data: TDataModel, - args: Optional[TAnalyzeArg | dict], + args: Optional[Union[TAnalyzeArg, dict]], ) -> TaskResult: """Analyze the provided data and return a TaskResult Args: data (TDataModel): data to analyze - args (Optional[TAnalyzeArg | dict]): Optional arguments for analysis, can be a model or dict + args (Optional[Union[TAnalyzeArg , dict]]): Optional arguments for analysis, can be a model or dict Returns: TaskResult: Task result containing the analysis outcome diff --git a/nodescraper/interfaces/datacollectortask.py b/nodescraper/interfaces/datacollectortask.py index ae59a1b0..737a297c 100644 --- a/nodescraper/interfaces/datacollectortask.py +++ b/nodescraper/interfaces/datacollectortask.py @@ -122,7 +122,9 @@ def __init__( system_info: SystemInfo, connection: TConnection, logger: Optional[logging.Logger] = None, - system_interaction_level: SystemInteractionLevel | str = SystemInteractionLevel.INTERACTIVE, + system_interaction_level: Union[ + SystemInteractionLevel, str + ] = SystemInteractionLevel.INTERACTIVE, max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL, parent: Optional[str] = None, task_result_hooks: Optional[list[TaskResultHook]] = None, diff --git a/nodescraper/interfaces/dataplugin.py b/nodescraper/interfaces/dataplugin.py index 95cc07b2..1c5d7a8e 100644 --- a/nodescraper/interfaces/dataplugin.py +++ b/nodescraper/interfaces/dataplugin.py @@ -127,7 +127,7 @@ def data(self) -> Optional[TDataModel]: return self._data @data.setter - def data(self, data: Optional[str, dict, TDataModel]): + def data(self, data: Optional[Union[str, dict, TDataModel]]): if isinstance(data, (str, dict)): self._data = self.DATA_MODEL.import_model(data) elif not isinstance(data, self.DATA_MODEL): @@ -148,9 +148,9 @@ def collect( Args: max_event_priority_level (Union[EventPriority, str], optional): priority limit for events. Defaults to EventPriority.CRITICAL. - system_interaction_level (SystemInteractionLevel | str, optional): system interaction level. Defaults to SystemInteractionLevel.INTERACTIVE. + system_interaction_level (Union[SystemInteractionLevel, str], optional): system interaction level. Defaults to SystemInteractionLevel.INTERACTIVE. preserve_connection (bool, optional): whether we should close the connection after data collection. Defaults to False. - collection_args (Optional[TCollectArg | dict], optional): args for data collection. Defaults to None. + collection_args (Optional[Union[TCollectArg , dict]], optional): args for data collection. Defaults to None. Returns: TaskResult: task result for data collection @@ -230,14 +230,14 @@ def collect( def analyze( self, max_event_priority_level: Optional[Union[EventPriority, str]] = EventPriority.CRITICAL, - analysis_args: Optional[TAnalyzeArg | dict] = None, + analysis_args: Optional[Union[TAnalyzeArg, dict]] = None, data: Optional[Union[str, dict, TDataModel]] = None, ) -> TaskResult: """Run data analyzer task Args: max_event_priority_level (Union[EventPriority, str], optional): priority limit for events. Defaults to EventPriority.CRITICAL. - analysis_args (Optional[TAnalyzeArg | dict], optional): args for data analysis. Defaults to None. + analysis_args (Optional[Union[TAnalyzeArg , dict]], optional): args for data analysis. Defaults to None. data (Optional[Union[str, dict, TDataModel]], optional): data to analyze. Defaults to None. Returns: @@ -279,11 +279,13 @@ def run( collection: bool = True, analysis: bool = True, max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL, - system_interaction_level: SystemInteractionLevel | str = SystemInteractionLevel.INTERACTIVE, + system_interaction_level: Union[ + SystemInteractionLevel, str + ] = SystemInteractionLevel.INTERACTIVE, preserve_connection: bool = False, data: Optional[Union[str, dict, TDataModel]] = None, - collection_args: Optional[TCollectArg | dict] = None, - analysis_args: Optional[TAnalyzeArg | dict] = None, + collection_args: Optional[Union[TCollectArg, dict]] = None, + analysis_args: Optional[Union[TAnalyzeArg, dict]] = None, ) -> PluginResult: """Run plugin @@ -291,11 +293,11 @@ def run( collection (bool, optional): Enable data collection. Defaults to True. analysis (bool, optional): Enable data analysis. Defaults to True. max_event_priority_level (Union[EventPriority, str], optional): Max priority level to assign to events. Defaults to EventPriority.CRITICAL. - system_interaction_level (SystemInteractionLevel | str, optional): System interaction level. Defaults to SystemInteractionLevel.INTERACTIVE. + system_interaction_level (Union[SystemInteractionLevel, str], optional): System interaction level. Defaults to SystemInteractionLevel.INTERACTIVE. preserve_connection (bool, optional): Whether to close the connection when data collection is complete. Defaults to False. data (Optional[Union[str, dict, TDataModel]], optional): Input data. Defaults to None. - collection_args (Optional[TCollectArg | dict], optional): Arguments for data collection. Defaults to None. - analysis_args (Optional[TAnalyzeArg | dict], optional): Arguments for data analysis. Defaults to None. + collection_args (Optional[Union[TCollectArg , dict]], optional): Arguments for data collection. Defaults to None. + analysis_args (Optional[Union[TAnalyzeArg , dict]], optional): Arguments for data analysis. Defaults to None. Returns: PluginResult: Plugin result diff --git a/nodescraper/interfaces/plugin.py b/nodescraper/interfaces/plugin.py index 82edcacf..0194ef2d 100644 --- a/nodescraper/interfaces/plugin.py +++ b/nodescraper/interfaces/plugin.py @@ -26,7 +26,7 @@ import abc import inspect import logging -from typing import Callable, Generic, Optional, Type +from typing import Callable, Generic, Optional, Type, Union from nodescraper.constants import DEFAULT_LOGGER from nodescraper.models import PluginResult, SystemInfo @@ -46,7 +46,7 @@ def __init__( system_info: Optional[SystemInfo] = None, logger: Optional[logging.Logger] = None, connection_manager: Optional[TConnectionManager] = None, - connection_args: Optional[TConnectArg | dict] = None, + connection_args: Optional[Union[TConnectArg, dict]] = None, task_result_hooks: Optional[list[TaskResultHook]] = None, log_path: Optional[str] = None, queue_callback: Optional[Callable] = None, @@ -58,7 +58,7 @@ def __init__( system_info (Optional[SystemInfo], optional): system info object. Defaults to None. logger (Optional[logging.Logger], optional): python logger instance. Defaults to None. connection_manager (Optional[TConnectionManager], optional): connection manager instance. Defaults to None. - connection_args (Optional[TConnectArg | dict], optional): connection args. Defaults to None. + connection_args (Optional[Union[TConnectArg , dict]], optional): connection args. Defaults to None. task_result_hooks (Optional[list[TaskResultHook]], optional): list of task result hooks. Defaults to None. log_path (Optional[str], optional): path for file system logs. Defaults to None. queue_callback (Optional[Callable], optional): function to add additional plugins to plugin executor queue. Defaults to None. diff --git a/nodescraper/interfaces/task.py b/nodescraper/interfaces/task.py index 8f9dd399..6e5ded45 100644 --- a/nodescraper/interfaces/task.py +++ b/nodescraper/interfaces/task.py @@ -77,7 +77,7 @@ def max_event_priority_level(self) -> EventPriority: return self._max_event_priority_level @max_event_priority_level.setter - def max_event_priority_level(self, input_value: str | EventPriority): + def max_event_priority_level(self, input_value: Union[str, EventPriority]): if isinstance(input_value, str): value: EventPriority = getattr(EventPriority, input_value) elif isinstance(input_value, int): diff --git a/nodescraper/models/event.py b/nodescraper/models/event.py index c65f082b..5b68b46c 100644 --- a/nodescraper/models/event.py +++ b/nodescraper/models/event.py @@ -99,7 +99,7 @@ def validate_category(cls, category: Optional[Union[str, Enum]]) -> str: def validate_priority(cls, priority: Optional[Union[str, EventPriority]]) -> EventPriority: """Allow priority to be set via string priority name Args: - priority (str | EventPriority): event priority string or enum + priority (Union[str, EventPriority]): event priority string or enum Raises: ValueError: if priority string is an invalid value Returns: From 01cd455efc9b92bf0a4d11e21534e617056e5787 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 10 Oct 2025 12:42:21 -0500 Subject: [PATCH 06/10] more updates --- nodescraper/cli/cli.py | 1 + nodescraper/configbuilder.py | 4 ++-- nodescraper/connection/inband/inbandmanager.py | 11 ++++++----- nodescraper/pluginexecutor.py | 4 ++-- .../plugins/inband/bios/analyzer_args.py | 6 ++++-- .../plugins/inband/bios/bios_collector.py | 5 +++-- .../plugins/inband/cmdline/analyzer_args.py | 14 ++++++++------ .../inband/cmdline/cmdline_collector.py | 6 ++++-- .../plugins/inband/dimm/dimm_collector.py | 2 +- .../plugins/inband/dkms/analyzer_args.py | 14 +++++++------- .../plugins/inband/dkms/dkms_collector.py | 6 ++++-- .../plugins/inband/dmesg/dmesg_collector.py | 4 ++-- nodescraper/plugins/inband/dmesg/dmesgdata.py | 3 ++- .../plugins/inband/kernel/analyzer_args.py | 8 +++++--- .../plugins/inband/kernel/kernel_collector.py | 6 ++++-- .../kernel_module/kernel_module_collector.py | 6 ++++-- .../plugins/inband/memory/memory_collector.py | 5 +++-- .../plugins/inband/nvme/nvme_collector.py | 5 +++-- nodescraper/plugins/inband/nvme/nvmedata.py | 18 ++++++++++-------- nodescraper/plugins/inband/os/analyzer_args.py | 8 +++++--- nodescraper/plugins/inband/os/os_collector.py | 5 +++-- .../plugins/inband/package/analyzer_args.py | 4 +++- .../plugins/inband/package/package_analyzer.py | 14 +++++++------- .../inband/package/package_collector.py | 10 +++++----- .../inband/process/process_collector.py | 4 ++-- .../plugins/inband/rocm/analyzer_args.py | 8 +++++--- .../plugins/inband/rocm/rocm_collector.py | 6 ++++-- .../inband/storage/storage_collector.py | 2 +- .../plugins/inband/sysctl/sysctl_collector.py | 3 ++- .../plugins/inband/uptime/uptime_collector.py | 5 +++-- test/unit/conftest.py | 3 ++- 31 files changed, 117 insertions(+), 83 deletions(-) diff --git a/nodescraper/cli/cli.py b/nodescraper/cli/cli.py index 5b35a9d4..0999cee9 100644 --- a/nodescraper/cli/cli.py +++ b/nodescraper/cli/cli.py @@ -257,6 +257,7 @@ def build_parser( model_type_map = parser_builder.build_plugin_parser() except Exception as e: print(f"Exception building arg parsers for {plugin_name}: {str(e)}") # noqa: T201 + continue plugin_subparser_map[plugin_name] = (plugin_subparser, model_type_map) return parser, plugin_subparser_map diff --git a/nodescraper/configbuilder.py b/nodescraper/configbuilder.py index 09c24dcc..27ced826 100644 --- a/nodescraper/configbuilder.py +++ b/nodescraper/configbuilder.py @@ -26,7 +26,7 @@ import enum import logging import types -from typing import Any, Optional, Type +from typing import Any, Optional, Type, Union from pydantic import BaseModel @@ -102,7 +102,7 @@ def _update_config(cls, config_key, type_data: TypeData, config: dict): config[config_key] = cls._process_value(type_data.default) @classmethod - def _process_value(cls, value: Any) -> dict | str | int | float | list | None: + def _process_value(cls, value: Any) -> Optional[Union[dict, str, int, float, list]]: if isinstance(value, enum.Enum): return value.name diff --git a/nodescraper/connection/inband/inbandmanager.py b/nodescraper/connection/inband/inbandmanager.py index ae4d71a0..b7be0126 100644 --- a/nodescraper/connection/inband/inbandmanager.py +++ b/nodescraper/connection/inband/inbandmanager.py @@ -26,6 +26,7 @@ from __future__ import annotations from logging import Logger +from typing import Optional, Union from nodescraper.enums import ( EventCategory, @@ -50,11 +51,11 @@ class InBandConnectionManager(ConnectionManager[InBandConnection, SSHConnectionP def __init__( self, system_info: SystemInfo, - logger: Logger | None = None, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, - parent: str | None = None, - task_result_hooks: list[TaskResultHook] | None = None, - connection_args: SSHConnectionParams | None = None, + logger: Optional[Logger] = None, + max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL, + parent: Optional[str] = None, + task_result_hooks: Optional[list[TaskResultHook]] = None, + connection_args: Optional[SSHConnectionParams] = None, **kwargs, ): super().__init__( diff --git a/nodescraper/pluginexecutor.py b/nodescraper/pluginexecutor.py index ed5374d7..c0dcaa72 100644 --- a/nodescraper/pluginexecutor.py +++ b/nodescraper/pluginexecutor.py @@ -28,7 +28,7 @@ import copy import logging from collections import deque -from typing import Optional, Type +from typing import Optional, Type, Union from pydantic import BaseModel @@ -47,7 +47,7 @@ class PluginExecutor: def __init__( self, plugin_configs: list[PluginConfig], - connections: Optional[dict[str, dict | BaseModel]] = None, + connections: Optional[dict[str, Union[dict, BaseModel]]] = None, system_info: Optional[SystemInfo] = None, logger: Optional[logging.Logger] = None, plugin_registry: Optional[PluginRegistry] = None, diff --git a/nodescraper/plugins/inband/bios/analyzer_args.py b/nodescraper/plugins/inband/bios/analyzer_args.py index 2d4a5003..2775b74a 100644 --- a/nodescraper/plugins/inband/bios/analyzer_args.py +++ b/nodescraper/plugins/inband/bios/analyzer_args.py @@ -23,6 +23,8 @@ # SOFTWARE. # ############################################################################### +from typing import Union + from pydantic import Field, field_validator from nodescraper.models import AnalyzerArgs @@ -35,11 +37,11 @@ class BiosAnalyzerArgs(AnalyzerArgs): @field_validator("exp_bios_version", mode="before") @classmethod - def validate_exp_bios_version(cls, exp_bios_version: str | list) -> list: + def validate_exp_bios_version(cls, exp_bios_version: Union[str, list]) -> list: """support str or list input for exp_bios_version Args: - exp_bios_version (str | list): expected BIOS version(s) to match against + exp_bios_version (Union[str, list]): expected BIOS version(s) to match against Returns: list: a list of expected BIOS versions diff --git a/nodescraper/plugins/inband/bios/bios_collector.py b/nodescraper/plugins/inband/bios/bios_collector.py index 6d14a0b6..5436b882 100644 --- a/nodescraper/plugins/inband/bios/bios_collector.py +++ b/nodescraper/plugins/inband/bios/bios_collector.py @@ -23,6 +23,7 @@ # SOFTWARE. # ############################################################################### +from typing import Optional from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily @@ -39,11 +40,11 @@ class BiosCollector(InBandDataCollector[BiosDataModel, None]): def collect_data( self, args=None, - ) -> tuple[TaskResult, BiosDataModel | None]: + ) -> tuple[TaskResult, Optional[BiosDataModel]]: """Collect BIOS version information from the system. Returns: - tuple[TaskResult, BiosDataModel | None]: tuple containing the task result and an instance of BiosDataModel + tuple[TaskResult, Optional[BiosDataModel]]: tuple containing the task result and an instance of BiosDataModel or None if the BIOS version could not be determined. """ bios = None diff --git a/nodescraper/plugins/inband/cmdline/analyzer_args.py b/nodescraper/plugins/inband/cmdline/analyzer_args.py index cbd9b16f..88fd431d 100644 --- a/nodescraper/plugins/inband/cmdline/analyzer_args.py +++ b/nodescraper/plugins/inband/cmdline/analyzer_args.py @@ -23,6 +23,8 @@ # SOFTWARE. # ############################################################################### +from typing import Union + from pydantic import Field, field_validator from nodescraper.models import AnalyzerArgs @@ -30,16 +32,16 @@ class CmdlineAnalyzerArgs(AnalyzerArgs): - required_cmdline: str | list = Field(default_factory=list) - banned_cmdline: str | list = Field(default_factory=list) + required_cmdline: Union[str, list] = Field(default_factory=list) + banned_cmdline: Union[str, list] = Field(default_factory=list) @field_validator("required_cmdline", mode="before") @classmethod - def validate_required_cmdline(cls, required_cmdline: str | list) -> list: + def validate_required_cmdline(cls, required_cmdline: Union[str, list]) -> list: """support str or list input for required_cmdline Args: - required_cmdline (str | list): required command line arguments + required_cmdline (Union[str, list]): required command line arguments Returns: list: list of required command line arguments @@ -51,11 +53,11 @@ def validate_required_cmdline(cls, required_cmdline: str | list) -> list: @field_validator("banned_cmdline", mode="before") @classmethod - def validate_banned_cmdline(cls, banned_cmdline: str | list) -> list: + def validate_banned_cmdline(cls, banned_cmdline: Union[str, list]) -> list: """support str or list input for banned_cmdline Args: - banned_cmdline (str | list): banned command line arguments + banned_cmdline (Union[str, list]): banned command line arguments Returns: list: a list of banned command line arguments diff --git a/nodescraper/plugins/inband/cmdline/cmdline_collector.py b/nodescraper/plugins/inband/cmdline/cmdline_collector.py index d06b8b1f..2ad9cee3 100644 --- a/nodescraper/plugins/inband/cmdline/cmdline_collector.py +++ b/nodescraper/plugins/inband/cmdline/cmdline_collector.py @@ -23,6 +23,8 @@ # SOFTWARE. # ############################################################################### +from typing import Optional + from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult @@ -40,12 +42,12 @@ class CmdlineCollector(InBandDataCollector[CmdlineDataModel, None]): def collect_data( self, args=None, - ) -> tuple[TaskResult, CmdlineDataModel | None]: + ) -> tuple[TaskResult, Optional[CmdlineDataModel]]: """ Collects the cmdline data from the system. Returns: - tuple[TaskResult, CmdlineDataModel | None]: tuple containing the task result and the cmdline data model if successful, otherwise None. + tuple[TaskResult, Optional[CmdlineDataModel]]: tuple containing the task result and the cmdline data model if successful, otherwise None. """ res = self._run_sut_cmd("cat /proc/cmdline") cmdline_data = None diff --git a/nodescraper/plugins/inband/dimm/dimm_collector.py b/nodescraper/plugins/inband/dimm/dimm_collector.py index a7af7117..f480c846 100644 --- a/nodescraper/plugins/inband/dimm/dimm_collector.py +++ b/nodescraper/plugins/inband/dimm/dimm_collector.py @@ -41,7 +41,7 @@ class DimmCollector(InBandDataCollector[DimmDataModel, DimmCollectorArgs]): def collect_data( self, args: Optional[DimmCollectorArgs] = None, - ) -> tuple[TaskResult, DimmDataModel | None]: + ) -> tuple[TaskResult, Optional[DimmDataModel]]: """Collect data on installed DIMMs""" if args is None: args = DimmCollectorArgs() diff --git a/nodescraper/plugins/inband/dkms/analyzer_args.py b/nodescraper/plugins/inband/dkms/analyzer_args.py index 0d4ab6da..d6a3a8db 100644 --- a/nodescraper/plugins/inband/dkms/analyzer_args.py +++ b/nodescraper/plugins/inband/dkms/analyzer_args.py @@ -23,7 +23,7 @@ # SOFTWARE. # ############################################################################### -from typing import Any +from typing import Any, Union from pydantic import Field, field_validator @@ -32,8 +32,8 @@ class DkmsAnalyzerArgs(AnalyzerArgs): - dkms_status: str | list = Field(default_factory=list) - dkms_version: str | list = Field(default_factory=list) + dkms_status: Union[str, list] = Field(default_factory=list) + dkms_version: Union[str, list] = Field(default_factory=list) regex_match: bool = False def model_post_init(self, __context: Any) -> None: @@ -42,11 +42,11 @@ def model_post_init(self, __context: Any) -> None: @field_validator("dkms_status", mode="before") @classmethod - def validate_dkms_status(cls, dkms_status: str | list) -> list: + def validate_dkms_status(cls, dkms_status: Union[str, list]) -> list: """support str or list input for dkms_status Args: - dkms_status (str | list): dkms status to check + dkms_status (Union[str, list]): dkms status to check Returns: list: list of dkms status @@ -58,11 +58,11 @@ def validate_dkms_status(cls, dkms_status: str | list) -> list: @field_validator("dkms_version", mode="before") @classmethod - def validate_dkms_version(cls, dkms_version: str | list) -> list: + def validate_dkms_version(cls, dkms_version: Union[str, list]) -> list: """support str or list input for dkms_version Args: - dkms_version (str | list): dkms version to check + dkms_version (Union[str, list]): dkms version to check Returns: list: list of dkms version diff --git a/nodescraper/plugins/inband/dkms/dkms_collector.py b/nodescraper/plugins/inband/dkms/dkms_collector.py index 65954f5a..8046d4a8 100644 --- a/nodescraper/plugins/inband/dkms/dkms_collector.py +++ b/nodescraper/plugins/inband/dkms/dkms_collector.py @@ -23,6 +23,8 @@ # SOFTWARE. # ############################################################################### +from typing import Optional + from nodescraper.base import InBandDataCollector from nodescraper.enums import EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult @@ -40,12 +42,12 @@ class DkmsCollector(InBandDataCollector[DkmsDataModel, None]): def collect_data( self, args=None, - ) -> tuple[TaskResult, DkmsDataModel | None]: + ) -> tuple[TaskResult, Optional[DkmsDataModel]]: """ Collect DKMS status and version information. Returns: - tuple[TaskResult, DkmsDataModel | None]: tuple containing the task result and DKMS data model if available. + tuple[TaskResult, Optional[DkmsDataModel]]: tuple containing the task result and DKMS data model if available. """ dkms_data = DkmsDataModel() diff --git a/nodescraper/plugins/inband/dmesg/dmesg_collector.py b/nodescraper/plugins/inband/dmesg/dmesg_collector.py index 6c472b5a..9380f466 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_collector.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_collector.py @@ -64,11 +64,11 @@ def _get_dmesg_content(self) -> str: def collect_data( self, args: Optional[DmesgCollectorArgs] = None, - ) -> tuple[TaskResult, DmesgData | None]: + ) -> tuple[TaskResult, Optional[DmesgData]]: """Collect dmesg data from the system Returns: - tuple[TaskResult, DmesgData | None]: tuple containing the result of the task and the dmesg data if available + tuple[TaskResult, Optional[DmesgData]]: tuple containing the result of the task and the dmesg data if available """ if args is None: args = DmesgCollectorArgs() diff --git a/nodescraper/plugins/inband/dmesg/dmesgdata.py b/nodescraper/plugins/inband/dmesg/dmesgdata.py index e23a88cd..19eda856 100644 --- a/nodescraper/plugins/inband/dmesg/dmesgdata.py +++ b/nodescraper/plugins/inband/dmesg/dmesgdata.py @@ -25,6 +25,7 @@ ############################################################################### import os import re +from typing import Union from nodescraper.models import DataModel from nodescraper.utils import get_unique_filename @@ -87,7 +88,7 @@ def log_model(self, log_path: str): log_file.write(self.dmesg_content) @classmethod - def import_model(cls, model_input: dict | str) -> "DmesgData": + def import_model(cls, model_input: Union[dict, str]) -> "DmesgData": """Load dmesg data Args: diff --git a/nodescraper/plugins/inband/kernel/analyzer_args.py b/nodescraper/plugins/inband/kernel/analyzer_args.py index f8ba1a28..e8f4cd61 100644 --- a/nodescraper/plugins/inband/kernel/analyzer_args.py +++ b/nodescraper/plugins/inband/kernel/analyzer_args.py @@ -23,6 +23,8 @@ # SOFTWARE. # ############################################################################### +from typing import Union + from pydantic import Field, field_validator from nodescraper.models import AnalyzerArgs @@ -30,16 +32,16 @@ class KernelAnalyzerArgs(AnalyzerArgs): - exp_kernel: str | list = Field(default_factory=list) + exp_kernel: Union[str, list] = Field(default_factory=list) regex_match: bool = False @field_validator("exp_kernel", mode="before") @classmethod - def validate_exp_kernel(cls, exp_kernel: str | list) -> list: + def validate_exp_kernel(cls, exp_kernel: Union[str, list]) -> list: """support str or list input for exp_kernel Args: - exp_kernel (str | list): exp kernel input + exp_kernel (Union[str, list]): exp kernel input Returns: list: exp kernel list diff --git a/nodescraper/plugins/inband/kernel/kernel_collector.py b/nodescraper/plugins/inband/kernel/kernel_collector.py index cdb60c23..2dcb5aca 100644 --- a/nodescraper/plugins/inband/kernel/kernel_collector.py +++ b/nodescraper/plugins/inband/kernel/kernel_collector.py @@ -23,6 +23,8 @@ # SOFTWARE. # ############################################################################### +from typing import Optional + from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult @@ -38,12 +40,12 @@ class KernelCollector(InBandDataCollector[KernelDataModel, None]): def collect_data( self, args=None, - ) -> tuple[TaskResult, KernelDataModel | None]: + ) -> tuple[TaskResult, Optional[KernelDataModel]]: """ Collect kernel version data. Returns: - tuple[TaskResult, KernelDataModel | None]: tuple containing the task result and kernel data model or None if not found. + tuple[TaskResult, Optional[KernelDataModel]]: tuple containing the task result and kernel data model or None if not found. """ kernel = None if self.system_info.os_family == OSFamily.WINDOWS: diff --git a/nodescraper/plugins/inband/kernel_module/kernel_module_collector.py b/nodescraper/plugins/inband/kernel_module/kernel_module_collector.py index 4d190b91..a60cb9b4 100644 --- a/nodescraper/plugins/inband/kernel_module/kernel_module_collector.py +++ b/nodescraper/plugins/inband/kernel_module/kernel_module_collector.py @@ -23,6 +23,8 @@ # SOFTWARE. # ############################################################################### +from typing import Optional + from nodescraper.base import InBandDataCollector from nodescraper.connection.inband.inband import CommandArtifact from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily @@ -118,12 +120,12 @@ def collect_all_module_info(self) -> tuple[dict, CommandArtifact]: return modules - def collect_data(self, args=None) -> tuple[TaskResult, KernelModuleDataModel | None]: + def collect_data(self, args=None) -> tuple[TaskResult, Optional[KernelModuleDataModel]]: """ Collect kernel modules data. Returns: - tuple[TaskResult, KernelModuleDataModel | None]: tuple containing the task result and kernel data model or None if not found. + tuple[TaskResult, Optional[KernelModuleDataModel]]: tuple containing the task result and kernel data model or None if not found. """ kernel_modules = {} km_data: KernelModuleDataModel | None = None diff --git a/nodescraper/plugins/inband/memory/memory_collector.py b/nodescraper/plugins/inband/memory/memory_collector.py index 8ff6ea3b..6494a4a0 100644 --- a/nodescraper/plugins/inband/memory/memory_collector.py +++ b/nodescraper/plugins/inband/memory/memory_collector.py @@ -24,6 +24,7 @@ # ############################################################################### import re +from typing import Optional from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily @@ -37,12 +38,12 @@ class MemoryCollector(InBandDataCollector[MemoryDataModel, None]): DATA_MODEL = MemoryDataModel - def collect_data(self, args=None) -> tuple[TaskResult, MemoryDataModel | None]: + def collect_data(self, args=None) -> tuple[TaskResult, Optional[MemoryDataModel]]: """ Collects memory usage details from the system. Returns: - tuple[TaskResult, MemoryDataModel | None]: tuple containing the task result and memory data model or None if data is not available. + tuple[TaskResult, Optional[MemoryDataModel]]: tuple containing the task result and memory data model or None if data is not available. """ mem_free, mem_total = None, None if self.system_info.os_family == OSFamily.WINDOWS: diff --git a/nodescraper/plugins/inband/nvme/nvme_collector.py b/nodescraper/plugins/inband/nvme/nvme_collector.py index 7971ea5e..7570c787 100644 --- a/nodescraper/plugins/inband/nvme/nvme_collector.py +++ b/nodescraper/plugins/inband/nvme/nvme_collector.py @@ -25,6 +25,7 @@ ############################################################################### import os import re +from typing import Optional from pydantic import ValidationError @@ -43,11 +44,11 @@ class NvmeCollector(InBandDataCollector[NvmeDataModel, None]): def collect_data( self, args=None, - ) -> tuple[TaskResult, NvmeDataModel | None]: + ) -> tuple[TaskResult, Optional[NvmeDataModel]]: """Collect detailed NVMe information from all NVMe devices. Returns: - tuple[TaskResult, NvmeDataModel | None]: Task result and data model with NVMe command outputs. + tuple[TaskResult, Optional[NvmeDataModel]]: Task result and data model with NVMe command outputs. """ if self.system_info.os_family == OSFamily.WINDOWS: self._log_event( diff --git a/nodescraper/plugins/inband/nvme/nvmedata.py b/nodescraper/plugins/inband/nvme/nvmedata.py index fd660912..10452fa3 100644 --- a/nodescraper/plugins/inband/nvme/nvmedata.py +++ b/nodescraper/plugins/inband/nvme/nvmedata.py @@ -23,20 +23,22 @@ # SOFTWARE. # ############################################################################### +from typing import Optional + from pydantic import BaseModel from nodescraper.models import DataModel class DeviceNvmeData(BaseModel): - smart_log: str | None = None - error_log: str | None = None - id_ctrl: str | None = None - id_ns: str | None = None - fw_log: str | None = None - self_test_log: str | None = None - get_log: str | None = None - telemetry_log: str | None = None + smart_log: Optional[str] = None + error_log: Optional[str] = None + id_ctrl: Optional[str] = None + id_ns: Optional[str] = None + fw_log: Optional[str] = None + self_test_log: Optional[str] = None + get_log: Optional[str] = None + telemetry_log: Optional[str] = None class NvmeDataModel(DataModel): diff --git a/nodescraper/plugins/inband/os/analyzer_args.py b/nodescraper/plugins/inband/os/analyzer_args.py index 351d3fa4..366bb8d3 100644 --- a/nodescraper/plugins/inband/os/analyzer_args.py +++ b/nodescraper/plugins/inband/os/analyzer_args.py @@ -23,6 +23,8 @@ # SOFTWARE. # ############################################################################### +from typing import Union + from pydantic import Field, field_validator from nodescraper.models import AnalyzerArgs @@ -30,16 +32,16 @@ class OsAnalyzerArgs(AnalyzerArgs): - exp_os: str | list = Field(default_factory=list) + exp_os: Union[str, list] = Field(default_factory=list) exact_match: bool = True @field_validator("exp_os", mode="before") @classmethod - def validate_exp_os(cls, exp_os: str | list) -> list: + def validate_exp_os(cls, exp_os: Union[str, list]) -> list: """support str or list input for exp_os Args: - exp_os (str | list): exp_os input + exp_os (Union[str, list]): exp_os input Returns: list: exp_os list diff --git a/nodescraper/plugins/inband/os/os_collector.py b/nodescraper/plugins/inband/os/os_collector.py index 2a95d84f..d9284777 100644 --- a/nodescraper/plugins/inband/os/os_collector.py +++ b/nodescraper/plugins/inband/os/os_collector.py @@ -24,6 +24,7 @@ # ############################################################################### import re +from typing import Optional from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily @@ -70,11 +71,11 @@ def collect_version(self) -> str: os_version = "" return os_version - def collect_data(self, args=None) -> tuple[TaskResult, OsDataModel | None]: + def collect_data(self, args=None) -> tuple[TaskResult, Optional[OsDataModel]]: """Collect OS name and version. Returns: - tuple[TaskResult, OsDataModel | None]: tuple containing the task result and OS data model or None if not found. + tuple[TaskResult, Optional[OsDataModel]]: tuple containing the task result and OS data model or None if not found. """ os_name = None if self.system_info.os_family == OSFamily.WINDOWS: diff --git a/nodescraper/plugins/inband/package/analyzer_args.py b/nodescraper/plugins/inband/package/analyzer_args.py index a246745d..cbd7ebad 100644 --- a/nodescraper/plugins/inband/package/analyzer_args.py +++ b/nodescraper/plugins/inband/package/analyzer_args.py @@ -23,6 +23,8 @@ # SOFTWARE. # ############################################################################### +from typing import Dict, Optional + from pydantic import Field from nodescraper.models import AnalyzerArgs @@ -30,7 +32,7 @@ class PackageAnalyzerArgs(AnalyzerArgs): - exp_package_ver: dict[str, str | None] = Field(default_factory=dict) + exp_package_ver: Dict[str, Optional[str]] = Field(default_factory=dict) regex_match: bool = False @classmethod diff --git a/nodescraper/plugins/inband/package/package_analyzer.py b/nodescraper/plugins/inband/package/package_analyzer.py index e68adc5f..3f502c9b 100644 --- a/nodescraper/plugins/inband/package/package_analyzer.py +++ b/nodescraper/plugins/inband/package/package_analyzer.py @@ -24,7 +24,7 @@ # ############################################################################### import re -from typing import Optional +from typing import Optional, Pattern from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus from nodescraper.interfaces import DataAnalyzer @@ -43,14 +43,14 @@ def regex_version_data( self, package_data: dict[str, str], key_search: re.Pattern[str], - value_search: re.Pattern[str] | None, + value_search: Optional[Pattern[str]], ) -> bool: """Searches the package values for the key and value search patterns Args: package_data (dict[str, str]): a dictionary of package names and versions key_search (re.Pattern[str]): a compiled regex pattern to search for the package name - value_search (re.Pattern[str] | None): a compiled regex pattern to search for the package version, if None then any version is accepted + value_search (Optional[Pattern[str]]): a compiled regex pattern to search for the package version, if None then any version is accepted Returns: bool: A boolean indicating if the value was found @@ -79,13 +79,13 @@ def regex_version_data( return value_found def package_regex_search( - self, package_data: dict[str, str], exp_packge_data: dict[str, str | None] + self, package_data: dict[str, str], exp_packge_data: dict[str, Optional[str]] ): """Searches the package data for the expected package and version using regex Args: package_data (dict[str, str]): a dictionary of package names and versions - exp_packge_data (dict[str, str | None]): a dictionary of expected package names and versions + exp_packge_data (dict[str, Optional[str]]): a dictionary of expected package names and versions """ for exp_key, exp_value in exp_packge_data.items(): try: @@ -122,13 +122,13 @@ def package_regex_search( ) def package_exact_match( - self, package_data: dict[str, str], exp_packge_data: dict[str, str | None] + self, package_data: dict[str, str], exp_packge_data: dict[str, Optional[str]] ): """Checks the package data for the expected package and version using exact match Args: package_data (dict[str, str]): a dictionary of package names and versions - exp_packge_data (dict[str, str | None]): a dictionary of expected package names and versions + exp_packge_data (dict[str, Optional[str]]): a dictionary of expected package names and versions """ for exp_key, exp_value in exp_packge_data.items(): self.logger.info(exp_key) diff --git a/nodescraper/plugins/inband/package/package_collector.py b/nodescraper/plugins/inband/package/package_collector.py index 84dba0d8..c38b061f 100644 --- a/nodescraper/plugins/inband/package/package_collector.py +++ b/nodescraper/plugins/inband/package/package_collector.py @@ -24,7 +24,7 @@ # ############################################################################### import re -from typing import Callable +from typing import Callable, Optional from pydantic import ValidationError @@ -42,11 +42,11 @@ class PackageCollector(InBandDataCollector[PackageDataModel, None]): DATA_MODEL = PackageDataModel - def _detect_package_manager(self) -> Callable | None: + def _detect_package_manager(self) -> Optional[Callable]: """Detect the package manager based on the OS release information. Returns: - Callable | None: A callable function that dumps the packages for the detected package manager, + Optional[Callable]: A callable function that dumps the packages for the detected package manager, or None if the package manager is not supported. """ package_manger_map: dict[str, Callable] = { @@ -176,11 +176,11 @@ def _handle_command_failure(self, command_artifact: CommandArtifact): self.result.message = "Failed to run Package Manager command" self.result.status = ExecutionStatus.EXECUTION_FAILURE - def collect_data(self, args=None) -> tuple[TaskResult, PackageDataModel | None]: + def collect_data(self, args=None) -> tuple[TaskResult, Optional[PackageDataModel]]: """Collect package information from the system. Returns: - tuple[TaskResult, PackageDataModel | None]: tuple containing the task result and a PackageDataModel instance + tuple[TaskResult, Optional[PackageDataModel]]: tuple containing the task result and a PackageDataModel instance with the collected package information, or None if there was an error. """ packages = {} diff --git a/nodescraper/plugins/inband/process/process_collector.py b/nodescraper/plugins/inband/process/process_collector.py index 1a8c515f..d49b76fa 100644 --- a/nodescraper/plugins/inband/process/process_collector.py +++ b/nodescraper/plugins/inband/process/process_collector.py @@ -43,14 +43,14 @@ class ProcessCollector(InBandDataCollector[ProcessDataModel, ProcessCollectorArg def collect_data( self, args: Optional[ProcessCollectorArgs] = None - ) -> tuple[TaskResult, ProcessDataModel | None]: + ) -> tuple[TaskResult, Optional[ProcessDataModel]]: """Collect process data from the system. Args: args (Optional[ProcessCollectorArgs], optional): process collection arguments. Defaults to None. Returns: - tuple[TaskResult, ProcessDataModel | None]: tuple containing the task result and the collected process data model or None if no data was collected. + tuple[TaskResult, Optional[ProcessDataModel]]: tuple containing the task result and the collected process data model or None if no data was collected. """ if args is None: args = ProcessCollectorArgs() diff --git a/nodescraper/plugins/inband/rocm/analyzer_args.py b/nodescraper/plugins/inband/rocm/analyzer_args.py index 190e5f4c..40a11ebc 100644 --- a/nodescraper/plugins/inband/rocm/analyzer_args.py +++ b/nodescraper/plugins/inband/rocm/analyzer_args.py @@ -23,21 +23,23 @@ # SOFTWARE. # ############################################################################### +from typing import Union + from pydantic import BaseModel, Field, field_validator from nodescraper.plugins.inband.rocm.rocmdata import RocmDataModel class RocmAnalyzerArgs(BaseModel): - exp_rocm: str | list = Field(default_factory=list) + exp_rocm: Union[str, list] = Field(default_factory=list) @field_validator("exp_rocm", mode="before") @classmethod - def validate_exp_rocm(cls, exp_rocm: str | list) -> list: + def validate_exp_rocm(cls, exp_rocm: Union[str, list]) -> list: """support str or list input for exp_rocm Args: - exp_rocm (str | list): exp_rocm input + exp_rocm (Union[str, list]): exp_rocm input Returns: list: exp_rocm list diff --git a/nodescraper/plugins/inband/rocm/rocm_collector.py b/nodescraper/plugins/inband/rocm/rocm_collector.py index 3c62e012..7099ff78 100644 --- a/nodescraper/plugins/inband/rocm/rocm_collector.py +++ b/nodescraper/plugins/inband/rocm/rocm_collector.py @@ -23,6 +23,8 @@ # SOFTWARE. # ############################################################################### +from typing import Optional + from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily from nodescraper.models import TaskResult @@ -37,11 +39,11 @@ class RocmCollector(InBandDataCollector[RocmDataModel, None]): DATA_MODEL = RocmDataModel - def collect_data(self, args=None) -> tuple[TaskResult, RocmDataModel | None]: + def collect_data(self, args=None) -> tuple[TaskResult, Optional[RocmDataModel]]: """Collect ROCm version data from the system. Returns: - tuple[TaskResult, RocmDataModel | None]: tuple containing the task result and ROCm data model if available. + tuple[TaskResult, Optional[RocmDataModel]]: tuple containing the task result and ROCm data model if available. """ version_paths = [ "/opt/rocm/.info/version-rocm", diff --git a/nodescraper/plugins/inband/storage/storage_collector.py b/nodescraper/plugins/inband/storage/storage_collector.py index 30cf2163..9fe6e62a 100644 --- a/nodescraper/plugins/inband/storage/storage_collector.py +++ b/nodescraper/plugins/inband/storage/storage_collector.py @@ -41,7 +41,7 @@ class StorageCollector(InBandDataCollector[StorageDataModel, None]): def collect_data( self, args: Optional[StorageCollectorArgs] = None - ) -> tuple[TaskResult, StorageDataModel | None]: + ) -> tuple[TaskResult, Optional[StorageDataModel]]: """read storage usage data""" if args is None: args = StorageCollectorArgs() diff --git a/nodescraper/plugins/inband/sysctl/sysctl_collector.py b/nodescraper/plugins/inband/sysctl/sysctl_collector.py index 0546dad4..ea63a642 100644 --- a/nodescraper/plugins/inband/sysctl/sysctl_collector.py +++ b/nodescraper/plugins/inband/sysctl/sysctl_collector.py @@ -23,6 +23,7 @@ # SOFTWARE. # ############################################################################### +from typing import Optional from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily @@ -39,7 +40,7 @@ class SysctlCollector(InBandDataCollector[SysctlDataModel, None]): def collect_data( self, args=None, - ) -> tuple[TaskResult, SysctlDataModel | None]: + ) -> tuple[TaskResult, Optional[SysctlDataModel]]: """Collect sysctl VM tuning values from the system.""" values = {} diff --git a/nodescraper/plugins/inband/uptime/uptime_collector.py b/nodescraper/plugins/inband/uptime/uptime_collector.py index 87fe08b8..ba8bbd93 100644 --- a/nodescraper/plugins/inband/uptime/uptime_collector.py +++ b/nodescraper/plugins/inband/uptime/uptime_collector.py @@ -24,6 +24,7 @@ # ############################################################################### import re +from typing import Optional from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily @@ -41,11 +42,11 @@ class UptimeCollector(InBandDataCollector[UptimeDataModel, None]): UPTIME_CMD = "uptime" - def collect_data(self, args=None) -> tuple[TaskResult, UptimeDataModel | None]: + def collect_data(self, args=None) -> tuple[TaskResult, Optional[UptimeDataModel]]: """Collect uptime data from the system. Returns: - tuple[TaskResult, UptimeDataModel | None]: tuple containing the task result and uptime data model or None if failed. + tuple[TaskResult, Optional[UptimeDataModel]]: tuple containing the task result and uptime data model or None if failed. """ uptime_pattern = re.compile( diff --git a/test/unit/conftest.py b/test/unit/conftest.py index a478b9f7..f44899c0 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -25,6 +25,7 @@ ############################################################################### import logging from pathlib import Path +from typing import Optional, Union from unittest.mock import MagicMock import pytest @@ -91,7 +92,7 @@ class MockAnalyzer(DataAnalyzer[DummyDataModel, DummyArg]): events: list[dict] = [] def analyze_data( - self, data: DummyDataModel, args: DummyArg | dict | None = None + self, data: DummyDataModel, args: Optional[Union[DummyArg, dict]] = None ) -> TaskResult: self.result.status = ExecutionStatus.OK return self.result From 82066b3aec5686e45e5cfd0f1a09a2245b98d2f8 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 10 Oct 2025 14:27:49 -0500 Subject: [PATCH 07/10] pytests mostly --- nodescraper/configbuilder.py | 3 +-- nodescraper/connection/inband/inbandremote.py | 2 +- nodescraper/connection/inband/sshparams.py | 3 ++- nodescraper/plugins/inband/dmesg/dmesgdata.py | 2 +- nodescraper/resultcollators/tablesummary.py | 2 +- nodescraper/typeutils.py | 10 +++++----- test/unit/framework/test_type_utils.py | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/nodescraper/configbuilder.py b/nodescraper/configbuilder.py index 27ced826..354ebc43 100644 --- a/nodescraper/configbuilder.py +++ b/nodescraper/configbuilder.py @@ -25,7 +25,6 @@ ############################################################################### import enum import logging -import types from typing import Any, Optional, Type, Union from pydantic import BaseModel @@ -80,7 +79,7 @@ def _update_config(cls, config_key, type_data: TypeData, config: dict): type_class_map = { type_class.type_class: type_class for type_class in type_data.type_classes } - if types.NoneType in type_class_map: + if type(None) in type_class_map: return model_arg = next( diff --git a/nodescraper/connection/inband/inbandremote.py b/nodescraper/connection/inband/inbandremote.py index 62f43229..82a2f9ea 100644 --- a/nodescraper/connection/inband/inbandremote.py +++ b/nodescraper/connection/inband/inbandremote.py @@ -105,7 +105,7 @@ def read_file( Args: filename (str): Path to file on remote host - encoding (str | None, optional): If None, file is read as binary. If str, decode using that encoding. Defaults to "utf-8". + encoding Optional[Union[str, None]]: If None, file is read as binary. If str, decode using that encoding. Defaults to "utf-8". strip (bool): Strip whitespace for text files. Ignored for binary. Returns: diff --git a/nodescraper/connection/inband/sshparams.py b/nodescraper/connection/inband/sshparams.py index ff07a956..a83be573 100644 --- a/nodescraper/connection/inband/sshparams.py +++ b/nodescraper/connection/inband/sshparams.py @@ -34,9 +34,10 @@ class SSHConnectionParams(BaseModel): """Class which holds info for an SSH connection""" model_config = ConfigDict(arbitrary_types_allowed=True) + hostname: Union[IPvAnyAddress, str] username: str password: Optional[SecretStr] = None pkey: Optional[PKey] = None key_filename: Optional[str] = None - port: Annotated[int, Field(strict=True, gt=0, lt=65536)] = 22 + port: Annotated[int, Field(strict=True, gt=0, le=65535)] = 22 diff --git a/nodescraper/plugins/inband/dmesg/dmesgdata.py b/nodescraper/plugins/inband/dmesg/dmesgdata.py index 19eda856..541b3ea0 100644 --- a/nodescraper/plugins/inband/dmesg/dmesgdata.py +++ b/nodescraper/plugins/inband/dmesg/dmesgdata.py @@ -92,7 +92,7 @@ def import_model(cls, model_input: Union[dict, str]) -> "DmesgData": """Load dmesg data Args: - model_input (dict | str): dmesg file name or dmesg data dict + model_input Union[dict, str]: dmesg file name or dmesg data dict Raises: ValueError: id model data has an invalid value diff --git a/nodescraper/resultcollators/tablesummary.py b/nodescraper/resultcollators/tablesummary.py index 4d1fdbe9..8232a9e9 100644 --- a/nodescraper/resultcollators/tablesummary.py +++ b/nodescraper/resultcollators/tablesummary.py @@ -51,7 +51,7 @@ def gen_str_table(headers: list[str], rows: list[str]): border = f"+{'+'.join('-' * (width + 2) for width in column_widths)}+" def gen_row(row): - return f"| {' | '.join(str(cell).ljust(width) for cell, width in zip(row, column_widths, strict=False))} |" + return f"| {' | '.join(str(cell).ljust(width) for cell, width in zip(row, column_widths))} |" table = [border, gen_row(headers), border, *[gen_row(row) for row in rows], border] return "\n".join(table) diff --git a/nodescraper/typeutils.py b/nodescraper/typeutils.py index 010eddd7..4760d530 100644 --- a/nodescraper/typeutils.py +++ b/nodescraper/typeutils.py @@ -61,7 +61,7 @@ def get_generic_map(cls, class_type: Type[Any]) -> dict: gen_base = class_type.__orig_bases__[0] class_org = get_origin(gen_base) args = get_args(gen_base) - generic_map = dict(zip(class_org.__parameters__, args, strict=False)) + generic_map = dict(zip(class_org.__parameters__, args)) else: generic_map = {} @@ -122,9 +122,9 @@ def process_type(cls, input_type: type[Any]) -> list[TypeClass]: origin = get_origin(input_type) if origin is None: return [TypeClass(type_class=input_type)] - if origin in [Union, types.UnionType]: + if origin is Union or getattr(types, "UnionType", None) is origin: type_classes = [] - input_types = [arg for arg in input_type.__args__ if arg != types.NoneType] + input_types = [arg for arg in input_type.__args__ if arg is not type(None)] for type_item in input_types: origin = get_origin(type_item) if origin is None: @@ -134,7 +134,7 @@ def process_type(cls, input_type: type[Any]) -> list[TypeClass]: TypeClass( type_class=origin, inner_type=next( - (arg for arg in get_args(type_item) if arg != types.NoneType), None + (arg for arg in get_args(type_item) if arg is not type(None)), None ), ) ) @@ -145,7 +145,7 @@ def process_type(cls, input_type: type[Any]) -> list[TypeClass]: TypeClass( type_class=origin, inner_type=next( - (arg for arg in get_args(input_type) if arg != types.NoneType), None + (arg for arg in get_args(input_type) if arg is not type(None)), None ), ) ] diff --git a/test/unit/framework/test_type_utils.py b/test/unit/framework/test_type_utils.py index a76dd1f3..be14d7ee 100644 --- a/test/unit/framework/test_type_utils.py +++ b/test/unit/framework/test_type_utils.py @@ -23,7 +23,7 @@ # SOFTWARE. # ############################################################################### -from typing import Generic, Optional, TypeVar +from typing import Generic, Optional, TypeVar, Union from pydantic import BaseModel @@ -37,7 +37,7 @@ class TestGenericBase(Generic[T]): def __init__(self, generic_type: T): self.generic_type = generic_type - def test_func(self, arg: list[str], arg2: bool | str, arg3: Optional[int] = None) -> T: + def test_func(self, arg: list[str], arg2: Union[bool, str], arg3: Optional[int] = None) -> T: return self.generic_type From f6997264db9905f5707eea697691e4a594b49fc1 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 10 Oct 2025 15:08:12 -0500 Subject: [PATCH 08/10] fixed odd indent --- nodescraper/base/regexanalyzer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nodescraper/base/regexanalyzer.py b/nodescraper/base/regexanalyzer.py index 0a37b2aa..17f55f5a 100644 --- a/nodescraper/base/regexanalyzer.py +++ b/nodescraper/base/regexanalyzer.py @@ -59,14 +59,14 @@ def _build_regex_event( ) -> RegexEvent: """Build a RegexEvent object from a regex match and source. - Args: - regex_obj (ErrorRegex): regex object containing the regex pattern, message, category, and priorit - match ( + Args: + regex_obj (ErrorRegex): regex object containing the regex pattern, message, category, and priorit + match ( Union[str, list[str]]): matched content from the regex source (str): descriptor for the content where the match was found - Returns: - RegexEvent: an instance of RegexEvent containing the match details + Returns: + RegexEvent: an instance of RegexEvent containing the match details """ return RegexEvent( description=regex_obj.message, From 714654b6b3480a4f3803464fa04d10507bcfb3b6 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Tue, 21 Oct 2025 07:41:54 -0500 Subject: [PATCH 09/10] specifying py3.9 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b7bc54be..fd2eadf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.0.1" description = "A framework for automated error detection and data collection" authors = [] readme = "README.md" -requires-python = ">=3.8" +requires-python = "==3.9.*" keywords = [] From 9ce6dd15bb7b95f2df28a47dfe42afbf9fc330f1 Mon Sep 17 00:00:00 2001 From: Alexandra Bara Date: Fri, 24 Oct 2025 12:30:12 -0500 Subject: [PATCH 10/10] version fix --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fd2eadf8..cef078bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.0.1" description = "A framework for automated error detection and data collection" authors = [] readme = "README.md" -requires-python = "==3.9.*" +requires-python = ">=3.9" keywords = []