Merging upstream version 1.3.0.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-03-17 07:33:51 +01:00
parent 5b922100c9
commit 8a6a3342fc
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
337 changed files with 16571 additions and 4891 deletions

View file

@ -1,4 +1,4 @@
# Copyright (c) 2024 Arista Networks, Inc.
# Copyright (c) 2024-2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
# Initially written by Jeremy Schulman at https://github.com/jeremyschulman/aio-eapi

22
asynceapi/_constants.py Normal file
View file

@ -0,0 +1,22 @@
# Copyright (c) 2024-2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
"""Constants and Enums for the asynceapi package."""
from __future__ import annotations
from enum import Enum
class EapiCommandFormat(str, Enum):
"""Enum for the eAPI command format.
NOTE: This could be updated to StrEnum when Python 3.11 is the minimum supported version in ANTA.
"""
JSON = "json"
TEXT = "text"
def __str__(self) -> str:
"""Override the __str__ method to return the value of the Enum, mimicking the behavior of StrEnum."""
return self.value

36
asynceapi/_errors.py Normal file
View file

@ -0,0 +1,36 @@
# Copyright (c) 2024-2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
"""Exceptions for the asynceapi package."""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from asynceapi._models import EapiResponse
class EapiReponseError(Exception):
"""Exception raised when an eAPI response contains errors.
Attributes
----------
response : EapiResponse
The eAPI response that contains the error.
"""
def __init__(self, response: EapiResponse) -> None:
"""Initialize the EapiReponseError exception."""
self.response = response
# Build a descriptive error message
message = "Error in eAPI response"
if response.error_code is not None:
message += f" (code: {response.error_code})"
if response.error_message is not None:
message += f": {response.error_message}"
super().__init__(message)

238
asynceapi/_models.py Normal file
View file

@ -0,0 +1,238 @@
# Copyright (c) 2024-2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
"""Models for the asynceapi package."""
from __future__ import annotations
from dataclasses import dataclass, field
from logging import getLogger
from typing import TYPE_CHECKING, Any, Literal
from uuid import uuid4
from ._constants import EapiCommandFormat
from ._errors import EapiReponseError
if TYPE_CHECKING:
from collections.abc import Iterator
from ._types import EapiComplexCommand, EapiJsonOutput, EapiSimpleCommand, EapiTextOutput, JsonRpc
LOGGER = getLogger(__name__)
# pylint: disable=too-many-instance-attributes
@dataclass(frozen=True)
class EapiRequest:
"""Model for an eAPI request.
Attributes
----------
commands : list[EapiSimpleCommand | EapiComplexCommand]
A list of commands to execute.
version : int | Literal["latest"]
The eAPI version to use. Defaults to "latest".
format : EapiCommandFormat
The command output format. Defaults "json".
timestamps : bool
Include timestamps in the command output. Defaults to False.
auto_complete : bool
Enable command auto-completion. Defaults to False.
expand_aliases : bool
Expand command aliases. Defaults to False.
stop_on_error : bool
Stop command execution on first error. Defaults to True.
id : int | str
The request ID. Defaults to a random hex string.
"""
commands: list[EapiSimpleCommand | EapiComplexCommand]
version: int | Literal["latest"] = "latest"
format: EapiCommandFormat = EapiCommandFormat.JSON
timestamps: bool = False
auto_complete: bool = False
expand_aliases: bool = False
stop_on_error: bool = True
id: int | str = field(default_factory=lambda: uuid4().hex)
def to_jsonrpc(self) -> JsonRpc:
"""Return the JSON-RPC dictionary payload for the request."""
return {
"jsonrpc": "2.0",
"method": "runCmds",
"params": {
"version": self.version,
"cmds": self.commands,
"format": self.format,
"timestamps": self.timestamps,
"autoComplete": self.auto_complete,
"expandAliases": self.expand_aliases,
"stopOnError": self.stop_on_error,
},
"id": self.id,
}
@dataclass(frozen=True)
class EapiResponse:
"""Model for an eAPI response.
Construct an EapiResponse from a JSON-RPC response dictionary using the `from_jsonrpc` class method.
Can be iterated over to access command results in order of execution.
Attributes
----------
request_id : str
The ID of the original request this response corresponds to.
_results : dict[int, EapiCommandResult]
Dictionary mapping request command indices to their respective results.
error_code : int | None
The JSON-RPC error code, if any.
error_message : str | None
The JSON-RPC error message, if any.
"""
request_id: str
_results: dict[int, EapiCommandResult] = field(default_factory=dict)
error_code: int | None = None
error_message: str | None = None
@property
def success(self) -> bool:
"""Return True if the response has no errors."""
return self.error_code is None
@property
def results(self) -> list[EapiCommandResult]:
"""Get all results as a list. Results are ordered by the command indices in the request."""
return list(self._results.values())
def __len__(self) -> int:
"""Return the number of results."""
return len(self._results)
def __iter__(self) -> Iterator[EapiCommandResult]:
"""Enable iteration over the results. Results are yielded in the same order as provided in the request."""
yield from self._results.values()
@classmethod
def from_jsonrpc(cls, response: dict[str, Any], request: EapiRequest, *, raise_on_error: bool = False) -> EapiResponse:
"""Build an EapiResponse from a JSON-RPC eAPI response.
Parameters
----------
response
The JSON-RPC eAPI response dictionary.
request
The corresponding EapiRequest.
raise_on_error
Raise an EapiReponseError if the response contains errors, by default False.
Returns
-------
EapiResponse
The EapiResponse object.
"""
has_error = "error" in response
response_data = response["error"]["data"] if has_error else response["result"]
# Handle case where we have fewer results than commands (stop_on_error=True)
executed_count = min(len(response_data), len(request.commands))
# Process the results we have
results = {}
for i in range(executed_count):
cmd = request.commands[i]
cmd_str = cmd["cmd"] if isinstance(cmd, dict) else cmd
data = response_data[i]
output = None
errors = []
success = True
start_time = None
duration = None
# Parse the output based on the data type, no output when errors are present
if isinstance(data, dict):
if "errors" in data:
errors = data["errors"]
success = False
else:
output = data["output"] if request.format == EapiCommandFormat.TEXT and "output" in data else data
# Add timestamps if available
if request.timestamps and "_meta" in data:
meta = data.pop("_meta")
start_time = meta.get("execStartTime")
duration = meta.get("execDuration")
elif isinstance(data, str):
# Handle case where eAPI returns a JSON string response (serialized JSON) for certain commands
try:
from json import JSONDecodeError, loads
output = loads(data)
except (JSONDecodeError, TypeError):
# If it's not valid JSON, store as is
LOGGER.warning("Invalid JSON response for command: %s. Storing as text: %s", cmd_str, data)
output = data
results[i] = EapiCommandResult(
command=cmd_str,
output=output,
errors=errors,
success=success,
start_time=start_time,
duration=duration,
)
# If stop_on_error is True and we have an error, indicate commands not executed
if has_error and request.stop_on_error and executed_count < len(request.commands):
for i in range(executed_count, len(request.commands)):
cmd = request.commands[i]
cmd_str = cmd["cmd"] if isinstance(cmd, dict) else cmd
results[i] = EapiCommandResult(command=cmd_str, output=None, errors=["Command not executed due to previous error"], success=False, executed=False)
response_obj = cls(
request_id=response["id"],
_results=results,
error_code=response["error"]["code"] if has_error else None,
error_message=response["error"]["message"] if has_error else None,
)
if raise_on_error and has_error:
raise EapiReponseError(response_obj)
return response_obj
@dataclass(frozen=True)
class EapiCommandResult:
"""Model for an eAPI command result.
Attributes
----------
command : str
The command that was executed.
output : EapiJsonOutput | EapiTextOutput | None
The command result output. None if the command returned errors.
errors : list[str]
A list of error messages, if any.
success : bool
True if the command was successful.
executed : bool
True if the command was executed. When `stop_on_error` is True in the request, some commands may not be executed.
start_time : float | None
Command execution start time in seconds. Uses Unix epoch format. `timestamps` must be True in the request.
duration : float | None
Command execution duration in seconds. `timestamps` must be True in the request.
"""
command: str
output: EapiJsonOutput | EapiTextOutput | None
errors: list[str] = field(default_factory=list)
success: bool = True
executed: bool = True
start_time: float | None = None
duration: float | None = None

53
asynceapi/_types.py Normal file
View file

@ -0,0 +1,53 @@
# Copyright (c) 2024-2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
"""Type definitions used for the asynceapi package."""
from __future__ import annotations
import sys
from typing import TYPE_CHECKING, Any, Literal
if TYPE_CHECKING:
from ._constants import EapiCommandFormat
if sys.version_info >= (3, 11):
from typing import NotRequired, TypedDict
else:
from typing_extensions import NotRequired, TypedDict
EapiJsonOutput = dict[str, Any]
"""Type definition of an eAPI JSON output response."""
EapiTextOutput = str
"""Type definition of an eAPI text output response."""
EapiSimpleCommand = str
"""Type definition of an eAPI simple command. A simple command is the CLI command to run as a string."""
class EapiComplexCommand(TypedDict):
"""Type definition of an eAPI complex command. A complex command is a dictionary with the CLI command to run with additional parameters."""
cmd: str
input: NotRequired[str]
revision: NotRequired[int]
class JsonRpc(TypedDict):
"""Type definition of a JSON-RPC payload."""
jsonrpc: Literal["2.0"]
method: Literal["runCmds"]
params: JsonRpcParams
id: NotRequired[int | str]
class JsonRpcParams(TypedDict):
"""Type definition of JSON-RPC parameters."""
version: NotRequired[int | Literal["latest"]]
cmds: list[EapiSimpleCommand | EapiComplexCommand]
format: NotRequired[EapiCommandFormat]
autoComplete: NotRequired[bool]
expandAliases: NotRequired[bool]
timestamps: NotRequired[bool]
stopOnError: NotRequired[bool]

View file

@ -1,4 +1,4 @@
# Copyright (c) 2024 Arista Networks, Inc.
# Copyright (c) 2024-2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
# Initially written by Jeremy Schulman at https://github.com/jeremyschulman/aio-eapi

View file

@ -1,4 +1,4 @@
# Copyright (c) 2024 Arista Networks, Inc.
# Copyright (c) 2024-2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
# Initially written by Jeremy Schulman at https://github.com/jeremyschulman/aio-eapi
@ -10,9 +10,10 @@
from __future__ import annotations
import re
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ._types import EapiComplexCommand, EapiJsonOutput, EapiSimpleCommand
from .device import Device
# -----------------------------------------------------------------------------
@ -78,7 +79,7 @@ class SessionConfig:
# Public Methods
# -------------------------------------------------------------------------
async def status_all(self) -> dict[str, Any]:
async def status_all(self) -> EapiJsonOutput:
"""Get the status of all the session config on the device.
Run the following command on the device:
@ -86,7 +87,7 @@ class SessionConfig:
Returns
-------
dict[str, Any]
EapiJsonOutput
Dictionary of native EOS eAPI response; see `status` method for
details.
@ -116,9 +117,9 @@ class SessionConfig:
}
```
"""
return await self._cli("show configuration sessions detail") # type: ignore[return-value] # json outformat returns dict[str, Any]
return await self._cli(command="show configuration sessions detail")
async def status(self) -> dict[str, Any] | None:
async def status(self) -> EapiJsonOutput | None:
"""Get the status of a session config on the device.
Run the following command on the device:
@ -129,7 +130,7 @@ class SessionConfig:
Returns
-------
dict[str, Any] | None
EapiJsonOutput | None
Dictionary instance of the session status. If the session does not exist,
then this method will return None.
@ -201,7 +202,7 @@ class SessionConfig:
# prepare the initial set of command to enter the config session and
# rollback clean if the `replace` argument is True.
commands: list[str | dict[str, Any]] = [self._cli_config_session]
commands: list[EapiSimpleCommand | EapiComplexCommand] = [self._cli_config_session]
if replace:
commands.append(self.CLI_CFG_FACTORY_RESET)
@ -232,7 +233,7 @@ class SessionConfig:
if timer:
command += f" timer {timer}"
await self._cli(command)
await self._cli(command=command)
async def abort(self) -> None:
"""Abort the configuration session.
@ -240,7 +241,7 @@ class SessionConfig:
Run the following command on the device:
# configure session <name> abort
"""
await self._cli(f"{self._cli_config_session} abort")
await self._cli(command=f"{self._cli_config_session} abort")
async def diff(self) -> str:
"""Return the "diff" of the session config relative to the running config.
@ -257,7 +258,7 @@ class SessionConfig:
----------
* https://www.gnu.org/software/diffutils/manual/diffutils.txt
"""
return await self._cli(f"show session-config named {self.name} diffs", ofmt="text") # type: ignore[return-value] # text outformat returns str
return await self._cli(command=f"show session-config named {self.name} diffs", ofmt="text")
async def load_file(self, filename: str, *, replace: bool = False) -> None:
"""Load the configuration from <filename> into the session configuration.
@ -281,12 +282,12 @@ class SessionConfig:
If there are any issues with loading the configuration file then a
RuntimeError is raised with the error messages content.
"""
commands: list[str | dict[str, Any]] = [self._cli_config_session]
commands: list[EapiSimpleCommand | EapiComplexCommand] = [self._cli_config_session]
if replace:
commands.append(self.CLI_CFG_FACTORY_RESET)
commands.append(f"copy {filename} session-config")
res: list[dict[str, Any]] = await self._cli(commands=commands) # type: ignore[assignment] # JSON outformat of multiple commands returns list[dict[str, Any]]
res = await self._cli(commands=commands)
checks_re = re.compile(r"error|abort|invalid", flags=re.IGNORECASE)
messages = res[-1]["messages"]
@ -295,4 +296,4 @@ class SessionConfig:
async def write(self) -> None:
"""Save the running config to the startup config by issuing the command "write" to the device."""
await self._cli("write")
await self._cli(command="write")

View file

@ -1,4 +1,4 @@
# Copyright (c) 2024 Arista Networks, Inc.
# Copyright (c) 2024-2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
# Initially written by Jeremy Schulman at https://github.com/jeremyschulman/aio-eapi
@ -10,7 +10,7 @@
from __future__ import annotations
from socket import getservbyname
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Literal, overload
# -----------------------------------------------------------------------------
# Public Imports
@ -20,12 +20,13 @@ import httpx
# -----------------------------------------------------------------------------
# Private Imports
# -----------------------------------------------------------------------------
from ._constants import EapiCommandFormat
from .aio_portcheck import port_check_url
from .config_session import SessionConfig
from .errors import EapiCommandError
if TYPE_CHECKING:
from collections.abc import Sequence
from ._types import EapiComplexCommand, EapiJsonOutput, EapiSimpleCommand, EapiTextOutput, JsonRpc
# -----------------------------------------------------------------------------
# Exports
@ -121,18 +122,139 @@ class Device(httpx.AsyncClient):
"""
return await port_check_url(self.base_url)
# Single command, JSON output, no suppression
@overload
async def cli(
self,
command: str | dict[str, Any] | None = None,
commands: Sequence[str | dict[str, Any]] | None = None,
ofmt: str | None = None,
version: int | str | None = "latest",
*,
command: EapiSimpleCommand | EapiComplexCommand,
commands: None = None,
ofmt: Literal["json"] = "json",
version: int | Literal["latest"] = "latest",
suppress_error: Literal[False] = False,
auto_complete: bool = False,
expand_aliases: bool = False,
req_id: int | str | None = None,
) -> EapiJsonOutput: ...
# Multiple commands, JSON output, no suppression
@overload
async def cli(
self,
*,
command: None = None,
commands: list[EapiSimpleCommand | EapiComplexCommand],
ofmt: Literal["json"] = "json",
version: int | Literal["latest"] = "latest",
suppress_error: Literal[False] = False,
auto_complete: bool = False,
expand_aliases: bool = False,
req_id: int | str | None = None,
) -> list[EapiJsonOutput]: ...
# Single command, TEXT output, no suppression
@overload
async def cli(
self,
*,
command: EapiSimpleCommand | EapiComplexCommand,
commands: None = None,
ofmt: Literal["text"],
version: int | Literal["latest"] = "latest",
suppress_error: Literal[False] = False,
auto_complete: bool = False,
expand_aliases: bool = False,
req_id: int | str | None = None,
) -> EapiTextOutput: ...
# Multiple commands, TEXT output, no suppression
@overload
async def cli(
self,
*,
command: None = None,
commands: list[EapiSimpleCommand | EapiComplexCommand],
ofmt: Literal["text"],
version: int | Literal["latest"] = "latest",
suppress_error: Literal[False] = False,
auto_complete: bool = False,
expand_aliases: bool = False,
req_id: int | str | None = None,
) -> list[EapiTextOutput]: ...
# Single command, JSON output, with suppression
@overload
async def cli(
self,
*,
command: EapiSimpleCommand | EapiComplexCommand,
commands: None = None,
ofmt: Literal["json"] = "json",
version: int | Literal["latest"] = "latest",
suppress_error: Literal[True],
auto_complete: bool = False,
expand_aliases: bool = False,
req_id: int | str | None = None,
) -> EapiJsonOutput | None: ...
# Multiple commands, JSON output, with suppression
@overload
async def cli(
self,
*,
command: None = None,
commands: list[EapiSimpleCommand | EapiComplexCommand],
ofmt: Literal["json"] = "json",
version: int | Literal["latest"] = "latest",
suppress_error: Literal[True],
auto_complete: bool = False,
expand_aliases: bool = False,
req_id: int | str | None = None,
) -> list[EapiJsonOutput] | None: ...
# Single command, TEXT output, with suppression
@overload
async def cli(
self,
*,
command: EapiSimpleCommand | EapiComplexCommand,
commands: None = None,
ofmt: Literal["text"],
version: int | Literal["latest"] = "latest",
suppress_error: Literal[True],
auto_complete: bool = False,
expand_aliases: bool = False,
req_id: int | str | None = None,
) -> EapiTextOutput | None: ...
# Multiple commands, TEXT output, with suppression
@overload
async def cli(
self,
*,
command: None = None,
commands: list[EapiSimpleCommand | EapiComplexCommand],
ofmt: Literal["text"],
version: int | Literal["latest"] = "latest",
suppress_error: Literal[True],
auto_complete: bool = False,
expand_aliases: bool = False,
req_id: int | str | None = None,
) -> list[EapiTextOutput] | None: ...
# Actual implementation
async def cli(
self,
command: EapiSimpleCommand | EapiComplexCommand | None = None,
commands: list[EapiSimpleCommand | EapiComplexCommand] | None = None,
ofmt: Literal["json", "text"] = "json",
version: int | Literal["latest"] = "latest",
*,
suppress_error: bool = False,
auto_complete: bool = False,
expand_aliases: bool = False,
req_id: int | str | None = None,
) -> list[dict[str, Any] | str] | dict[str, Any] | str | None:
) -> EapiJsonOutput | EapiTextOutput | list[EapiJsonOutput] | list[EapiTextOutput] | None:
"""Execute one or more CLI commands.
Parameters
@ -143,6 +265,7 @@ class Device(httpx.AsyncClient):
A list of commands to execute; results in a list of output responses.
ofmt
Either 'json' or 'text'; indicates the output format for the CLI commands.
eAPI defaults to 'json'.
version
By default the eAPI will use "version 1" for all API object models.
This driver will, by default, always set version to "latest" so
@ -158,13 +281,13 @@ class Device(httpx.AsyncClient):
response = dev.cli(..., suppress_error=True)
auto_complete
Enabled/disables the command auto-compelete feature of the EAPI. Per the
Enabled/disables the command auto-compelete feature of the eAPI. Per the
documentation:
Allows users to use shorthand commands in eAPI calls. With this
parameter included a user can send 'sh ver' via eAPI to get the
output of 'show version'.
expand_aliases
Enables/disables the command use of User defined alias. Per the
Enables/disables the command use of user-defined alias. Per the
documentation:
Allowed users to provide the expandAliases parameter to eAPI
calls. This allows users to use aliased commands via the API.
@ -176,15 +299,34 @@ class Device(httpx.AsyncClient):
Returns
-------
list[dict[str, Any] | str] | dict[str, Any] | str | None
One or List of output responses, per the description above.
dict[str, Any]
Single command, JSON output, suppress_error=False
list[dict[str, Any]]
Multiple commands, JSON output, suppress_error=False
str
Single command, TEXT output, suppress_error=False
list[str]
Multiple commands, TEXT output, suppress_error=False
dict[str, Any] | None
Single command, JSON output, suppress_error=True
list[dict[str, Any]] | None
Multiple commands, JSON output, suppress_error=True
str | None
Single command, TEXT output, suppress_error=True
list[str] | None
Multiple commands, TEXT output, suppress_error=True
"""
if not any((command, commands)):
msg = "Required 'command' or 'commands'"
raise RuntimeError(msg)
jsonrpc = self._jsonrpc_command(
commands=[command] if command else commands, ofmt=ofmt, version=version, auto_complete=auto_complete, expand_aliases=expand_aliases, req_id=req_id
commands=[command] if command else commands if commands else [],
ofmt=ofmt,
version=version,
auto_complete=auto_complete,
expand_aliases=expand_aliases,
req_id=req_id,
)
try:
@ -197,14 +339,14 @@ class Device(httpx.AsyncClient):
def _jsonrpc_command(
self,
commands: Sequence[str | dict[str, Any]] | None = None,
ofmt: str | None = None,
version: int | str | None = "latest",
commands: list[EapiSimpleCommand | EapiComplexCommand],
ofmt: Literal["json", "text"] = "json",
version: int | Literal["latest"] = "latest",
*,
auto_complete: bool = False,
expand_aliases: bool = False,
req_id: int | str | None = None,
) -> dict[str, Any]:
) -> JsonRpc:
"""Create the JSON-RPC command dictionary object.
Parameters
@ -213,6 +355,7 @@ class Device(httpx.AsyncClient):
A list of commands to execute; results in a list of output responses.
ofmt
Either 'json' or 'text'; indicates the output format for the CLI commands.
eAPI defaults to 'json'.
version
By default the eAPI will use "version 1" for all API object models.
This driver will, by default, always set version to "latest" so
@ -241,25 +384,20 @@ class Device(httpx.AsyncClient):
dict containing the JSON payload to run the command.
"""
cmd: dict[str, Any] = {
return {
"jsonrpc": "2.0",
"method": "runCmds",
"params": {
"version": version,
"cmds": commands,
"format": ofmt or self.EAPI_DEFAULT_OFMT,
"format": EapiCommandFormat(ofmt),
"autoComplete": auto_complete,
"expandAliases": expand_aliases,
},
"id": req_id or id(self),
}
if auto_complete is not None:
cmd["params"].update({"autoComplete": auto_complete})
if expand_aliases is not None:
cmd["params"].update({"expandAliases": expand_aliases})
return cmd
async def jsonrpc_exec(self, jsonrpc: dict[str, Any]) -> list[dict[str, Any] | str]:
async def jsonrpc_exec(self, jsonrpc: JsonRpc) -> list[EapiJsonOutput] | list[EapiTextOutput]:
"""Execute the JSON-RPC dictionary object.
Parameters
@ -315,7 +453,7 @@ class Device(httpx.AsyncClient):
failed_cmd = commands[err_at]
raise EapiCommandError(
passed=[get_output(cmd_data[cmd_i]) for cmd_i, cmd in enumerate(commands[:err_at])],
passed=[get_output(cmd_data[i]) for i in range(err_at)],
failed=failed_cmd["cmd"] if isinstance(failed_cmd, dict) else failed_cmd,
errors=cmd_data[err_at]["errors"],
errmsg=err_msg,

View file

@ -1,4 +1,4 @@
# Copyright (c) 2024 Arista Networks, Inc.
# Copyright (c) 2024-2025 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the LICENSE file.
# Initially written by Jeremy Schulman at https://github.com/jeremyschulman/aio-eapi
@ -6,13 +6,16 @@
from __future__ import annotations
from typing import Any
from typing import TYPE_CHECKING
import httpx
if TYPE_CHECKING:
from ._types import EapiComplexCommand, EapiJsonOutput, EapiSimpleCommand, EapiTextOutput
class EapiCommandError(RuntimeError):
"""Exception class for EAPI command errors.
"""Exception class for eAPI command errors.
Attributes
----------
@ -23,7 +26,14 @@ class EapiCommandError(RuntimeError):
not_exec: a list of commands that were not executed
"""
def __init__(self, failed: str, errors: list[str], errmsg: str, passed: list[str | dict[str, Any]], not_exec: list[dict[str, Any]]) -> None:
def __init__(
self,
failed: str,
errors: list[str],
errmsg: str,
passed: list[EapiJsonOutput] | list[EapiTextOutput],
not_exec: list[EapiSimpleCommand | EapiComplexCommand],
) -> None:
"""Initialize for the EapiCommandError exception."""
self.failed = failed
self.errmsg = errmsg