Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type hinting #1476

Merged
merged 46 commits into from
Feb 12, 2022
Merged
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
be14dc1
Add models and annotate NetworkDriver class with types
Kircheneer Mar 22, 2021
49791a9
Make tests pass by ignoring the annotations part of the arg spec
Kircheneer Mar 22, 2021
ba7b56f
Unpack optional in NXOSDevice.load_merge_candidate()
Kircheneer Mar 22, 2021
4df5ff2
Add lots of type hints, asserts and some abstract methods. Enable typ…
Kircheneer Mar 22, 2021
f4604a2
Solve type errors
Kircheneer Mar 27, 2021
aca0ebb
Add type checker to github actions
Kircheneer Mar 27, 2021
12f9da7
Specify mypy config file in github action
Kircheneer Mar 27, 2021
d8eaed7
Fix unused imports
Kircheneer Mar 27, 2021
8b8986b
Black formatting
Kircheneer Mar 27, 2021
80f9a98
Fix further pipeline problems
Kircheneer Mar 27, 2021
18907d3
Fix further pipeline errors
Kircheneer Mar 27, 2021
4ee35ba
Put type in quotes
Kircheneer Mar 27, 2021
97e0cae
Convert all typing_extensions try/except imports to pure typing_exten…
Kircheneer Mar 27, 2021
e373e03
Import TypedDict from typing_extensions in nxos.py
Kircheneer Mar 27, 2021
72c3e92
Ignore typing errors from napalm.iosxr_netconf
Kircheneer Apr 30, 2021
5ced7a3
Fix Callable import in mock.py
Kircheneer May 1, 2021
d52a2cb
Add models and annotate NetworkDriver class with types
Kircheneer Mar 22, 2021
066da2d
Make tests pass by ignoring the annotations part of the arg spec
Kircheneer Mar 22, 2021
67aef96
Add lots of type hints, asserts and some abstract methods. Enable typ…
Kircheneer Mar 22, 2021
39b6348
Solve type errors
Kircheneer Mar 27, 2021
c528848
Add type checker to github actions
Kircheneer Mar 27, 2021
d6ed6e2
Fix unused imports
Kircheneer Mar 27, 2021
ce4962c
Black formatting
Kircheneer Mar 27, 2021
3f4c6ad
Fix further pipeline problems
Kircheneer Mar 27, 2021
91dd630
Fix further pipeline errors
Kircheneer Mar 27, 2021
229820f
Convert all typing_extensions try/except imports to pure typing_exten…
Kircheneer Mar 27, 2021
3d09940
Import TypedDict from typing_extensions in nxos.py
Kircheneer Mar 27, 2021
53d7995
Fix Callable import in mock.py
Kircheneer May 1, 2021
10c0c38
Black
Kircheneer Jun 4, 2021
f7c36a0
Black
Kircheneer Jun 4, 2021
37e409a
Black
Kircheneer Jul 25, 2021
81c7a44
Fix linter issues
Kircheneer Jul 25, 2021
5c38927
Fix MyPy issues
Kircheneer Jul 25, 2021
f74e05f
Remove unused import
Kircheneer Jul 25, 2021
47b93ae
Solve type errors
Kircheneer Mar 27, 2021
431ebf4
Add type checker to github actions
Kircheneer Mar 27, 2021
4485899
Fix MyPy issues
Kircheneer Jul 25, 2021
9260bfa
Fix pylama issues
Kircheneer Feb 11, 2022
3bc3ed8
Remove future import.
Kircheneer Feb 11, 2022
a978096
Bump pytest-pythonpath from 0.7.3 to 0.7.4
dependabot[bot] Feb 10, 2022
92119dc
Fix outstanding Mypy errors
Kircheneer Feb 12, 2022
4e675ff
Merge branch 'develop' of https://github.com/napalm-automation/napalm…
Kircheneer Feb 12, 2022
6731176
Fix new type errors from develop rebase.
Kircheneer Feb 12, 2022
ee7e0bb
Re-add new type checking requirements to dev-requirements.
Kircheneer Feb 12, 2022
8c324ef
Apply black.
Kircheneer Feb 12, 2022
e4061c6
Elaborate on assert statement supporting Mypy in convert helper funct…
Kircheneer Feb 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Solve type errors
Kircheneer committed Feb 12, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit f4604a2cfb823297d401707d4362dcedb252175f
9 changes: 9 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -8,9 +8,18 @@ disallow_untyped_defs = True
[mypy-napalm.nxos.*]
disallow_untyped_defs = True

[mypy-napalm.eos.*]
ignore_errors = True

[mypy-napalm.ios.*]
ignore_errors = True

[mypy-napalm.nxapi_plumbing.*]
disallow_untyped_defs = True

[mypy-napalm.base.clitools.*]
ignore_errors = True

[mypy-napalm.base.test.*]
ignore_errors = True

4 changes: 3 additions & 1 deletion napalm/base/__init__.py
Original file line number Diff line number Diff line change
@@ -19,6 +19,8 @@
import importlib

# NAPALM base
from typing import Type

from napalm.base.base import NetworkDriver
from napalm.base.exceptions import ModuleImportError
from napalm.base.mock import MockDriver
@@ -29,7 +31,7 @@
]


def get_network_driver(name, prepend=True):
def get_network_driver(name: str, prepend: bool = True) -> Type[NetworkDriver]:
"""
Searches for a class derived form the base NAPALM class NetworkDriver in a specific library.
The library name must repect the following pattern: napalm_[DEVICE_OS].
15 changes: 11 additions & 4 deletions napalm/base/base.py
Original file line number Diff line number Diff line change
@@ -31,6 +31,13 @@


class NetworkDriver(object):
hostname: str
username: str
password: str
timeout: int
force_no_enable: bool
use_canonical_interface: bool

def __init__(
self,
hostname: str,
@@ -54,7 +61,7 @@ def __init__(
"""
raise NotImplementedError

def __enter__(self) -> "NetworkDriver":
def __enter__(self) -> "NetworkDriver": # type: ignore
try:
self.open()
return self
@@ -65,7 +72,7 @@ def __enter__(self) -> "NetworkDriver":
else:
raise

def __exit__(
def __exit__( # type: ignore
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
@@ -74,7 +81,7 @@ def __exit__(
self.close()
if exc_type is not None and (
exc_type.__name__ not in dir(napalm.base.exceptions)
and exc_type.__name__ not in __builtins__.keys()
and exc_type.__name__ not in __builtins__.keys() # type: ignore
):
epilog = (
"NAPALM didn't catch this exception. Please, fill a bugfix on "
@@ -1776,7 +1783,7 @@ def compliance_report(
self,
validation_file: Optional[str] = None,
validation_source: Optional[str] = None,
) -> Dict:
) -> models.ReportResult:
"""
Return a compliance report.

86 changes: 57 additions & 29 deletions napalm/base/helpers.py
Original file line number Diff line number Diff line change
@@ -8,8 +8,11 @@
from collections.abc import Iterable

# third party libs
from typing import Optional, Dict, Any, List, Union, Tuple, TypeVar, Callable, Type

import jinja2
import textfsm
from lxml import etree
from netaddr import EUI
from netaddr import mac_unix
from netaddr import IPAddress
@@ -18,9 +21,13 @@
# local modules
import napalm.base.exceptions
from napalm.base import constants
from napalm.base.test.models import ConfigDict
from napalm.base.utils.jinja_filters import CustomJinjaFilters
from napalm.base.canonical_map import base_interfaces, reverse_mapping

T = TypeVar("T")
R = TypeVar("R")

# -------------------------------------------------------------------
# Functional Global
# -------------------------------------------------------------------
@@ -41,14 +48,14 @@ class _MACFormat(mac_unix):
# callable helpers
# -------------------------------------------------------------------
def load_template(
cls,
template_name,
template_source=None,
template_path=None,
openconfig=False,
jinja_filters={},
**template_vars,
):
cls: "napalm.base.NetworkDriver",
template_name: str,
template_source: Optional[str] = None,
template_path: Optional[str] = None,
openconfig: bool = False,
jinja_filters: Dict = {},
**template_vars: Any,
) -> None:
try:
search_path = []
if isinstance(template_source, str):
@@ -111,7 +118,9 @@ def load_template(
return cls.load_merge_candidate(config=configuration)


def cisco_conf_parse_parents(parent, child, config):
def cisco_conf_parse_parents(
parent: str, child: str, config: Union[str, List[str]]
) -> List[str]:
"""
Use CiscoConfParse to find parent lines that contain a specific child line.

@@ -120,13 +129,15 @@ def cisco_conf_parse_parents(parent, child, config):
:param config: The device running/startup config
"""
if type(config) == str:
config = config.splitlines()
config = config.splitlines() # type: ignore
parse = CiscoConfParse(config)
cfg_obj = parse.find_parents_w_child(parent, child)
return cfg_obj


def cisco_conf_parse_objects(cfg_section, config):
def cisco_conf_parse_objects(
cfg_section: str, config: Union[str, List[str]]
) -> List[str]:
"""
Use CiscoConfParse to find and return a section of Cisco IOS config.
Similar to "show run | section <cfg_section>"
@@ -136,7 +147,7 @@ def cisco_conf_parse_objects(cfg_section, config):
"""
return_config = []
if type(config) is str:
config = config.splitlines()
config = config.splitlines() # type: ignore
parse = CiscoConfParse(config)
cfg_obj = parse.find_objects(cfg_section)
for parent in cfg_obj:
@@ -146,7 +157,7 @@ def cisco_conf_parse_objects(cfg_section, config):
return return_config


def regex_find_txt(pattern, text, default=""):
def regex_find_txt(pattern: str, text: str, default: str = "") -> Any:
""" ""
RegEx search for pattern in text. Will try to match the data type of the "default" value
or return the default value if no match is found.
@@ -167,19 +178,21 @@ def regex_find_txt(pattern, text, default=""):
if not isinstance(value, type(default)):
if isinstance(value, list) and len(value) == 1:
value = value[0]
value = type(default)(value)
value = type(default)(value) # type: ignore
except Exception as regexFindTxtErr01: # in case of any exception, returns default
logger.error(
'errorCode="regexFindTxtErr01" in napalm.base.helpers with systemMessage="%s"\
message="Error while attempting to find regex pattern, \
default to empty string"'
% (regexFindTxtErr01)
)
value = default
value = default # type: ignore
return value


def textfsm_extractor(cls, template_name, raw_text):
def textfsm_extractor(
cls: "napalm.base.NetworkDriver", template_name: str, raw_text: str
) -> List[Dict]:
"""
Applies a TextFSM template over a raw text and return the matching table.

@@ -245,7 +258,12 @@ def textfsm_extractor(cls, template_name, raw_text):
)


def find_txt(xml_tree, path, default="", namespaces=None):
def find_txt(
xml_tree: etree._Element,
path: str,
default: str = "",
namespaces: Optional[Dict] = None,
) -> str:
"""
Extracts the text value from an XML tree, using XPath.
In case of error or text element unavailability, will return a default value.
@@ -284,7 +302,7 @@ def find_txt(xml_tree, path, default="", namespaces=None):
return str(value)


def convert(to, who, default=""):
def convert(to: Callable[[T], R], who: Optional[T], default: R = "") -> R:
"""
Converts data to a specific datatype.
In case of error, will return a default value.
@@ -302,7 +320,7 @@ def convert(to, who, default=""):
return default


def mac(raw):
def mac(raw: str) -> str:
"""
Converts a raw string to a standardised MAC Address EUI Format.

@@ -339,7 +357,7 @@ def mac(raw):
return str(EUI(raw, dialect=_MACFormat))


def ip(addr, version=None):
def ip(addr: str, version: Optional[int] = None) -> str:
"""
Converts a raw string to a valid IP address. Optional version argument will detect that \
object matches specified version.
@@ -368,7 +386,7 @@ def ip(addr, version=None):
return str(addr_obj)


def as_number(as_number_val):
def as_number(as_number_val: str) -> int:
"""Convert AS Number to standardized asplain notation as an integer."""
as_number_str = str(as_number_val)
if "." in as_number_str:
@@ -378,14 +396,16 @@ def as_number(as_number_val):
return int(as_number_str)


def split_interface(intf_name):
def split_interface(intf_name: str) -> Tuple[str, str]:
"""Split an interface name based on first digit, slash, or space match."""
head = intf_name.rstrip(r"/\0123456789. ")
tail = intf_name[len(head) :].lstrip()
return (head, tail)


def canonical_interface_name(interface, addl_name_map=None):
def canonical_interface_name(
interface: str, addl_name_map: Optional[Dict[str, str]] = None
) -> str:
"""Function to return an interface's canonical name (fully expanded name).

Use of explicit matches used to indicate a clear understanding on any potential
@@ -410,13 +430,18 @@ def canonical_interface_name(interface, addl_name_map=None):
# check in dict for mapping
if name_map.get(interface_type):
long_int = name_map.get(interface_type)
assert isinstance(long_int, str)
return long_int + str(interface_number)
# if nothing matched, return the original name
else:
return interface


def abbreviated_interface_name(interface, addl_name_map=None, addl_reverse_map=None):
def abbreviated_interface_name(
interface: str,
addl_name_map: Optional[Dict[str, str]] = None,
addl_reverse_map: Optional[Dict[str, str]] = None,
) -> str:
"""Function to return an abbreviated representation of the interface name.

:param interface: The interface you are attempting to abbreviate.
@@ -449,6 +474,8 @@ def abbreviated_interface_name(interface, addl_name_map=None, addl_reverse_map=N
else:
canonical_type = interface_type

assert isinstance(canonical_type, str)

try:
abbreviated_name = rev_name_map[canonical_type] + str(interface_number)
return abbreviated_name
@@ -459,7 +486,7 @@ def abbreviated_interface_name(interface, addl_name_map=None, addl_reverse_map=N
return interface


def transform_lldp_capab(capabilities):
def transform_lldp_capab(capabilities: Union[str, Any]) -> List[str]:
if capabilities and isinstance(capabilities, str):
capabilities = capabilities.strip().lower().split(",")
return sorted(
@@ -469,7 +496,7 @@ def transform_lldp_capab(capabilities):
return []


def generate_regex_or(filters):
def generate_regex_or(filters: Iterable) -> str:
"""
Build a regular expression logical-or from a list/tuple of regex patterns.

@@ -490,7 +517,7 @@ def generate_regex_or(filters):
return return_pattern


def sanitize_config(config, filters):
def sanitize_config(config: str, filters: Dict) -> str:
"""
Given a dictionary of filters, remove sensitive data from the provided config.
"""
@@ -499,12 +526,13 @@ def sanitize_config(config, filters):
return config


def sanitize_configs(configs, filters):
def sanitize_configs(configs: ConfigDict, filters: Dict) -> ConfigDict:
"""
Apply sanitize_config on the dictionary of configs typically returned by
the get_config method.
"""
for cfg_name, config in configs.items():
assert isinstance(config, str)
if config.strip():
configs[cfg_name] = sanitize_config(config, filters)
configs[cfg_name] = sanitize_config(config, filters) # type: ignore
return configs
Loading