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..17f55f5a 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,14 +55,15 @@ 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 + 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 diff --git a/nodescraper/cli/cli.py b/nodescraper/cli/cli.py index 86dd68ee..c5e7abeb 100644 --- a/nodescraper/cli/cli.py +++ b/nodescraper/cli/cli.py @@ -264,17 +264,18 @@ 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 -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..87d0fe0f 100644 --- a/nodescraper/cli/dynamicparserbuilder.py +++ b/nodescraper/cli/dynamicparserbuilder.py @@ -24,8 +24,7 @@ # ############################################################################### import argparse -import types -from typing import Type +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) @@ -75,14 +74,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( ( @@ -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/cli/helper.py b/nodescraper/cli/helper.py index 525ac07f..5cb97a73 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: @@ -480,12 +480,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 ac0ba17f..7faa9c5c 100644 --- a/nodescraper/cli/inputargtypes.py +++ b/nodescraper/cli/inputargtypes.py @@ -25,22 +25,21 @@ ############################################################################### 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: 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/configbuilder.py b/nodescraper/configbuilder.py index 09c24dcc..354ebc43 100644 --- a/nodescraper/configbuilder.py +++ b/nodescraper/configbuilder.py @@ -25,8 +25,7 @@ ############################################################################### import enum import logging -import types -from typing import Any, Optional, Type +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( @@ -102,7 +101,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 563aca48..f9220ea9 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/connection/inband/inbandremote.py b/nodescraper/connection/inband/inbandremote.py index 56a5ba51..d5254468 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 ( @@ -99,14 +99,14 @@ 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. 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/interfaces/connectionmanager.py b/nodescraper/interfaces/connectionmanager.py index 988a83f2..ccb5e793 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,10 +89,10 @@ 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, - connection_args: Optional[TConnectArg | dict] = None, + task_result_hooks: Optional[list[TaskResultHook], None] = None, + connection_args: Optional[Union[TConnectArg, dict]] = None, **kwargs, ): super().__init__( @@ -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/dataanalyzertask.py b/nodescraper/interfaces/dataanalyzertask.py index 2dd49f60..0e6b3b06 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() diff --git a/nodescraper/interfaces/datacollectortask.py b/nodescraper/interfaces/datacollectortask.py index 7a778ed7..737a297c 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 @@ -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: @@ -122,8 +122,10 @@ def __init__( system_info: SystemInfo, connection: TConnection, logger: Optional[logging.Logger] = None, - system_interaction_level: SystemInteractionLevel | str = SystemInteractionLevel.INTERACTIVE, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, + 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, **kwargs, @@ -175,7 +177,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..1c5d7a8e 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, @@ -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,16 +118,16 @@ 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 @data.setter - def data(self, data: 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): @@ -137,18 +137,20 @@ 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. - system_interaction_level (SystemInteractionLevel | str, optional): system interaction level. Defaults to SystemInteractionLevel.INTERACTIVE. + max_event_priority_level (Union[EventPriority, str], optional): priority limit for events. Defaults to EventPriority.CRITICAL. + 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 @@ -227,16 +229,16 @@ def collect( def analyze( self, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, - analysis_args: Optional[TAnalyzeArg | dict] = None, - data: Optional[str | dict | TDataModel] = None, + max_event_priority_level: Optional[Union[EventPriority, str]] = EventPriority.CRITICAL, + analysis_args: Optional[Union[TAnalyzeArg, dict]] = 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. - 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. + max_event_priority_level (Union[EventPriority, str], optional): priority limit for events. Defaults to EventPriority.CRITICAL. + 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: TaskResult: result of data analysis @@ -276,24 +278,26 @@ def run( self, collection: bool = True, analysis: bool = True, - max_event_priority_level: EventPriority | str = EventPriority.CRITICAL, - system_interaction_level: SystemInteractionLevel | str = SystemInteractionLevel.INTERACTIVE, + max_event_priority_level: Union[EventPriority, str] = EventPriority.CRITICAL, + system_interaction_level: Union[ + SystemInteractionLevel, str + ] = SystemInteractionLevel.INTERACTIVE, preserve_connection: bool = False, - data: Optional[str | dict | TDataModel] = None, - collection_args: Optional[TCollectArg | dict] = None, - analysis_args: Optional[TAnalyzeArg | dict] = None, + data: Optional[Union[str, dict, TDataModel]] = None, + collection_args: Optional[Union[TCollectArg, dict]] = None, + analysis_args: Optional[Union[TAnalyzeArg, dict]] = None, ) -> PluginResult: """Run plugin 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. - system_interaction_level (SystemInteractionLevel | str, optional): System interaction level. Defaults to SystemInteractionLevel.INTERACTIVE. + max_event_priority_level (Union[EventPriority, str], optional): Max priority level to assign to events. Defaults to EventPriority.CRITICAL. + 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[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. + data (Optional[Union[str, dict, TDataModel]], optional): Input data. 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 8722c277..effd3029 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], @@ -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): @@ -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 9d169d9d..df6a61e1 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 @@ -81,7 +81,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 @@ -97,25 +97,25 @@ 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: 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: EventPriority: priority enum """ - if isinstance(priority, str): try: return getattr(EventPriority, priority.upper()) except AttributeError as e: raise ValueError( - f"priority must be one of {[priority_enum.name for priority_enum in EventPriority]}" + f"priority must be one of {[p.name for p in EventPriority]}" ) from e - - return priority + if isinstance(priority, EventPriority): + return priority + raise ValueError("priority must be an EventPriority or its name as a string") @field_serializer("priority") def serialize_priority(self, priority: EventPriority, _info) -> str: 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 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: diff --git a/nodescraper/pluginexecutor.py b/nodescraper/pluginexecutor.py index b54a3719..e47a6cc8 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 e67c85f3..e0ab1011 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 @@ -41,11 +42,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 b1d05622..f51bb6f8 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 @@ -42,12 +44,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(self.CMD) cmdline_data = None diff --git a/nodescraper/plugins/inband/dimm/dimm_collector.py b/nodescraper/plugins/inband/dimm/dimm_collector.py index 14c0c35a..167913b5 100644 --- a/nodescraper/plugins/inband/dimm/dimm_collector.py +++ b/nodescraper/plugins/inband/dimm/dimm_collector.py @@ -44,7 +44,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 4985600d..98c69f55 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 @@ -43,12 +45,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 032d404b..5e7148f1 100644 --- a/nodescraper/plugins/inband/dmesg/dmesg_collector.py +++ b/nodescraper/plugins/inband/dmesg/dmesg_collector.py @@ -136,11 +136,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..541b3ea0 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,11 +88,11 @@ 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: - 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/plugins/inband/journal/journal_collector.py b/nodescraper/plugins/inband/journal/journal_collector.py index c4789969..ecf48e0a 100644 --- a/nodescraper/plugins/inband/journal/journal_collector.py +++ b/nodescraper/plugins/inband/journal/journal_collector.py @@ -23,6 +23,7 @@ # SOFTWARE. # ############################################################################### +from typing import Optional from nodescraper.base import InBandDataCollector from nodescraper.enums import EventCategory, EventPriority, ExecutionStatus, OSFamily @@ -60,14 +61,14 @@ def _read_with_journalctl(self): return res.stdout - def collect_data(self, args=None) -> tuple[TaskResult, JournalData | None]: + def collect_data(self, args=None) -> tuple[TaskResult, Optional[JournalData]]: """Collect journal logs Args: args (_type_, optional): Collection args. Defaults to None. Returns: - tuple[TaskResult, JournalData | None]: Tuple of results and data model or none. + tuple[TaskResult, Optional[JournalData, None]]: Tuple of results and data model or none. """ journal_log = self._read_with_journalctl() if journal_log: 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 86ad2460..e84973ff 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 @@ -40,12 +42,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 diff --git a/nodescraper/plugins/inband/kernel_module/kernel_module_collector.py b/nodescraper/plugins/inband/kernel_module/kernel_module_collector.py index aba1369e..48d4de91 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 @@ -120,12 +122,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 3b8ad5b0..7f768c65 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 @@ -42,12 +43,12 @@ class MemoryCollector(InBandDataCollector[MemoryDataModel, None]): ) CMD = "free -b" - 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 3ab30a31..19463493 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 @@ -56,11 +57,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 791eecd7..42e435a9 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 @@ -74,11 +75,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 063cab27..16215086 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 """ not_found_keys = [] for exp_key, exp_value in exp_packge_data.items(): @@ -125,13 +125,13 @@ def package_regex_search( return not_found_keys 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 """ not_found_match = [] not_found_version = [] diff --git a/nodescraper/plugins/inband/package/package_collector.py b/nodescraper/plugins/inband/package/package_collector.py index eec77741..bc3a65b3 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 @@ -47,11 +47,11 @@ class PackageCollector(InBandDataCollector[PackageDataModel, None]): CMD_DNF = "dnf list --installed" CMD_PACMAN = "pacman -Q" - 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] = { @@ -181,11 +181,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 602f12e5..9c92080d 100644 --- a/nodescraper/plugins/inband/process/process_collector.py +++ b/nodescraper/plugins/inband/process/process_collector.py @@ -46,14 +46,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 0d7f2b17..37470f68 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 @@ -41,11 +43,11 @@ class RocmCollector(InBandDataCollector[RocmDataModel, None]): "/opt/rocm/.info/version", ] - 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 3f6013a9..e5373ebc 100644 --- a/nodescraper/plugins/inband/storage/storage_collector.py +++ b/nodescraper/plugins/inband/storage/storage_collector.py @@ -43,7 +43,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 a054ca10..6137d25b 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 @@ -40,7 +41,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/syslog/syslog_collector.py b/nodescraper/plugins/inband/syslog/syslog_collector.py index 3a9b1b54..dc3f7838 100644 --- a/nodescraper/plugins/inband/syslog/syslog_collector.py +++ b/nodescraper/plugins/inband/syslog/syslog_collector.py @@ -23,6 +23,7 @@ # SOFTWARE. # ############################################################################### +from typing import Optional from nodescraper.base import InBandDataCollector from nodescraper.connection.inband.inband import TextFileArtifact @@ -104,11 +105,11 @@ def _collect_syslog_rotations(self) -> list[TextFileArtifact]: def collect_data( self, args=None, - ) -> tuple[TaskResult, SyslogData | None]: + ) -> tuple[TaskResult, Optional[SyslogData]]: """Collect syslog data from the system Returns: - tuple[TaskResult | None]: tuple containing the result of the task and the syslog data if available + tuple[Optional[TaskResult, None]]: tuple containing the result of the task and the syslog data if available """ syslog_logs = self._collect_syslog_rotations() diff --git a/nodescraper/plugins/inband/uptime/uptime_collector.py b/nodescraper/plugins/inband/uptime/uptime_collector.py index 32023898..4bddb7c0 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]): 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/nodescraper/resultcollators/tablesummary.py b/nodescraper/resultcollators/tablesummary.py index 3fa944aa..44b723fb 100644 --- a/nodescraper/resultcollators/tablesummary.py +++ b/nodescraper/resultcollators/tablesummary.py @@ -24,6 +24,7 @@ # ############################################################################### from textwrap import wrap +from typing import Optional from nodescraper.interfaces import PluginResultCollator from nodescraper.models import PluginResult, TaskResult @@ -44,15 +45,15 @@ def collate_results( def gen_str_table( headers: list[str], - rows: list[list[str | None]], - max_widths: dict[str, int] | None = None, + rows: list[list[Optional[str]]], + max_widths: Optional[dict[str, int]] = None, ) -> str: """Wrap cells Args: headers (list[str]): table header - rows (list[list[str | None]]): table rows - max_widths (dict[str, int] | None, optional): width per col. Defaults to None. + rows (Optional[list[list[str]]]): table rows + max_widths (Optional[dict[str, int]], optional): width per col. Defaults to None. Returns: str: wrapped texed @@ -105,11 +106,7 @@ def gen_str_table( border = "+" + "+".join("-" * (w + 2) for w in col_widths) + "+" def render_physical_row(parts: list[str]) -> str: - return ( - "| " - + " | ".join(p.ljust(w) for p, w in zip(parts, col_widths, strict=False)) - + " |" - ) + return "| " + " | ".join(p.ljust(w) for p, w in zip(parts, col_widths)) + " |" table_lines: list[str] = [border, render_physical_row(headers), border] for wrow in wrapped_rows: @@ -124,7 +121,7 @@ def render_physical_row(parts: list[str]) -> str: tables = "" if connection_results: - conn_rows: list[list[str | None]] = [] + conn_rows: list[list[Optional[str]]] = [] for connection_result in connection_results: conn_rows.append( [ @@ -142,7 +139,7 @@ def render_physical_row(parts: list[str]) -> str: tables += f"\n\n{table}" if plugin_results: - plug_rows: list[list[str | None]] = [] + plug_rows: list[list[Optional[str]]] = [] for plugin_result in plugin_results: plug_rows.append( [ 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/pyproject.toml b/pyproject.toml index f533ed76..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.10" +requires-python = ">=3.9" keywords = [] @@ -51,7 +51,7 @@ node-scraper = "nodescraper.cli:cli_entry" [tool.black] line-length = 100 -target_version = ['py310'] +target_version = ['py39'] [tool.isort] profile = "black" 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 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