From 9cdcde895ffcc8c3a2afefb7c2a457d8d2a563c3 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Sun, 9 May 2021 14:04:18 -0400 Subject: [PATCH 01/13] Move inventory into adapter and upgrade Nornir to 3.x --- .../adapters/netbox_api/inventory.py | 191 +++ network_importer/inventory.py | 335 ++--- poetry.lock | 1260 +++++++---------- pyproject.toml | 2 +- 4 files changed, 889 insertions(+), 899 deletions(-) create mode 100644 network_importer/adapters/netbox_api/inventory.py diff --git a/network_importer/adapters/netbox_api/inventory.py b/network_importer/adapters/netbox_api/inventory.py new file mode 100644 index 00000000..3a3fd399 --- /dev/null +++ b/network_importer/adapters/netbox_api/inventory.py @@ -0,0 +1,191 @@ +"""Norning Inventory for netbox. + +(c) 2020 Network To Code + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +# Disable too-many-arguments and too-many-locals pylint tests for this file. These are both necessary +# pylint: disable=R0913,R0914,E1101,W0613 + +import copy +from typing import Any, Dict, List, Optional, Union +from pydantic import BaseSettings, ValidationError +import requests +import pynetbox + +from nornir.core.inventory import Defaults, Group, Groups, Host, Hosts, Inventory, ParentGroups +from nornir.core.plugins.inventory import InventoryPluginRegister +from network_importer.inventory import NetworkImporterInventory, NetworkImporterHost + +# ------------------------------------------------------------ +# Network Importer Base Dict for device data +# status: +# ok: device is reachable +# fail-ip: Primary IP address not reachable +# fail-access: Unable to access the device management. The IP is reachable, but SSH or API is not enabled or +# responding. +# fail-login: Unable to login authenticate with device +# fail-other: Other general processing error (also catches traps/bug) +# is_reachable: Global Flag to indicate if we are able to connect to a device +# has_config: Indicate if the configuration is present and has been properly imported in Batfish +# ------------------------------------------------------------ + +class NetboxAPIParams(BaseSettings): + url: Optional[str] = None + token: Optional[str] = None + ssl_verify: Union[bool, str] = True + username: Optional[str] = None + password: Optional[str] = None + enable: Optional[bool] = True + use_primary_ip: Optional[bool] = True + fqdn: Optional[str] = None + supported_platforms: Optional[List[str]] = [] + filter_parameters: Optional[Dict[str, Any]] = None + global_delay_factor: Optional[int] = 5 + banner_timeout: Optional[int] = 15 + conn_timeout: Optional[int] = 5 + +class NetboxAPIInventory(NetworkImporterInventory): + """Netbox API Inventory Class.""" + + # pylint: disable=dangerous-default-value, too-many-branches, too-many-statements + def __init__( + self, + params: NetboxAPIParams = NetboxAPIParams(), + **kwargs: Any, + ) -> None: + """Nornir Inventory Plugin for Netbox API.""" + + self.params = params + self.filter_parameters = params.filter_parameters or {} + + if "exclude" not in self.filter_parameters.keys(): + self.filter_parameters["exclude"] = "config_context" + + # Instantiate netbox session using pynetbox + self.session = pynetbox.api(url=params.url, token=params.token) + if not params.ssl_verify: + session = requests.Session() + session.verify = False + self.session.http_session = session + + super().__init__(**kwargs) + + def load(self): + + # fetch devices from netbox + if self.filter_parameters: + devices: List[pynetbox.modules.dcim.Devices] = self.session.dcim.devices.filter(**self.filter_parameters) + else: + devices: List[pynetbox.modules.dcim.Devices] = self.session.dcim.devices.all() + + # fetch all platforms from Netbox and build mapping: platform: napalm_driver + platforms = self.session.dcim.platforms.all() + platforms_mapping = {platform.slug: platform.napalm_driver for platform in platforms if platform.napalm_driver} + + hosts = Hosts() + groups = Groups() + defaults = Defaults() + + global_group_options = {"netmiko": {"extras": {}}, "napalm": {"extras": {}}} + global_group = Group(name="global") + + # Pull the login and password from the NI config object if available + if self.params.username: + global_group.username = self.params.username + + if self.params.password: + global_group.password = self.params.password + if self.params.enable: + global_group_options["netmiko"]["extras"] = { + "secret": self.params.password, + "global_delay_factor": self.params.global_delay_factor, + "banner_timeout": self.params.banner_timeout, + "conn_timeout": self.params.conn_timeout, + } + global_group_options["napalm"]["extras"] = {"optional_args": {"secret": self.params.password}} + + global_group.connection_options = global_group_options + + for dev in devices: + + host = NetworkImporterHost() + + # Only add virtual chassis master as inventory element + if dev.virtual_chassis and dev.virtual_chassis.master: + if dev.id != dev.virtual_chassis.master.id: + continue + host["data"]["virtual_chassis"] = True + + else: + host["data"]["virtual_chassis"] = False + + # If supported_platforms is provided + # skip all devices that do not match the list of supported platforms + # TODO need to see if we can filter when doing the query directly + if self.params.supported_platforms: + if not dev.platform: + continue + + if dev.platform.slug not in self.params.supported_platforms: + continue + + # Add value for IP address + if self.params.use_primary_ip and dev.primary_ip: + host.hostname = dev.primary_ip.address.split("/")[0] + elif self.params.use_primary_ip and not dev.primary_ip: + host.is_reachable = False + host.not_reachable_reason = "primary ip not defined in Netbox" + elif not self.params.use_primary_ip and self.params.fqdn: + host.hostname = f"{dev.name}.{self.params.fqdn}" + elif not self.params.use_primary_ip: + host.hostname = dev.name + + host["data"]["serial"] = dev.serial + host["data"]["vendor"] = dev.device_type.manufacturer.slug + host["data"]["asset_tag"] = dev.asset_tag + host["data"]["custom_fields"] = dev.custom_fields + host["data"]["site"] = dev.site.slug + host["data"]["site_id"] = dev.site.id + host["data"]["device_id"] = dev.id + host["data"]["role"] = dev.device_role.slug + host["data"]["model"] = dev.device_type.slug + + # Attempt to add 'platform' based of value in 'slug' + if dev.platform and dev.platform.slug in platforms_mapping: + host.connection_options = {"napalm": {"platform": platforms_mapping[dev.platform.slug]}} + + if dev.platform: + host.platform = dev.platform.slug + else: + host.platform = None + + host.groups = ParentGroups([global_group]) + + if dev.site.slug not in groups.keys(): + groups[dev.site.slug] = {} + + if dev.device_role.slug not in groups.keys(): + groups[dev.device_role.slug] = {} + + if "hostname" in host and host["hostname"] and "platform" in host and host["platform"]: + host.is_reachable = True + + # Assign temporary dict to outer dict + # Netbox allows devices to be unnamed, but the Nornir model does not allow this + # If a device is unnamed we will set the name to the id of the device in netbox + hosts[dev.name or dev.id] = host + + return Inventory(hosts=hosts, groups=groups, defaults=defaults) + + +InventoryPluginRegister.register("NetboxAPIInventory", NetboxAPIInventory) + diff --git a/network_importer/inventory.py b/network_importer/inventory.py index b72d862f..44727ac3 100644 --- a/network_importer/inventory.py +++ b/network_importer/inventory.py @@ -16,14 +16,12 @@ # pylint: disable=R0913,R0914,E1101,W0613 import copy -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Union, List -import requests -import pynetbox +# from nornir.core.deserializer.inventory import Inventory, HostsDict +from nornir.core.inventory import Defaults, Group, Groups, Host, Hosts, Inventory, ParentGroups +from nornir.core.plugins.inventory import InventoryPluginRegister -from nornir.core.deserializer.inventory import Inventory, HostsDict - -# import network_importer.config as config # ------------------------------------------------------------ # Network Importer Base Dict for device data @@ -38,163 +36,174 @@ # has_config: Indicate if the configuration is present and has been properly imported in Batfish # ------------------------------------------------------------ -BASE_DATA = {"is_reachable": None, "status": "ok", "has_config": False} - - -class NetboxInventory(Inventory): - """Netbox Inventory Class.""" - - # pylint: disable=dangerous-default-value, too-many-branches, too-many-statements - def __init__( - self, - nb_url: Optional[str] = None, - nb_token: Optional[str] = None, - ssl_verify: Union[bool, str] = True, - username: Optional[str] = None, - password: Optional[str] = None, - enable: Optional[bool] = True, - use_primary_ip: Optional[bool] = True, - fqdn: Optional[str] = None, - supported_platforms: Optional[List[str]] = [], - filter_parameters: Optional[Dict[str, Any]] = None, - global_delay_factor: Optional[int] = 5, - banner_timeout: Optional[int] = 15, - conn_timeout: Optional[int] = 5, - **kwargs: Any, - ) -> None: - """Norning Inventory Plugin fir Netbox. - - Hard copy from https://github.com/nornir-automation/nornir/blob/develop/nornir/plugins/inventory/netbox.py, - Need to see how to contribute back some of these modifications - - Args: - filter_parameters: Key - nb_url: Optional[str]: (Default value = None) - nb_token: Optional[str]: (Default value = None) - ssl_verify: (Default value = True) - filter_parameters: Optional[Dict[str: Any]]: (Default value = None) - username: Optional[str] - password: Optional[str] - enable: Optional[bool] = True, - use_primary_ip: Optional[bool] = True, - fqdn: Optional[str] = None, - supported_platforms: Optional[List[str]] - global_delay_factor: Optional[int] Global Delay factor for netmiko - banner_timeout: Optional[int] Banner Timeout for netmiko/paramiko - conn_timeout: Optional[int] Connection timeout for netmiko/paramiko - **kwargs: Any: - """ - filter_parameters = filter_parameters or {} - - if "exclude" not in filter_parameters.keys(): - filter_parameters["exclude"] = "config_context" - - # Instantiate netbox session using pynetbox - nb_session = pynetbox.api(url=nb_url, token=nb_token) - if not ssl_verify: - session = requests.Session() - session.verify = False - nb_session.http_session = session - - # fetch devices from netbox - if filter_parameters: - nb_devices: List[pynetbox.modules.dcim.Devices] = nb_session.dcim.devices.filter(**filter_parameters) - else: - nb_devices: List[pynetbox.modules.dcim.Devices] = nb_session.dcim.devices.all() - - # fetch all platforms from Netbox and build mapping: platform: napalm_driver - platforms = nb_session.dcim.platforms.all() - platforms_mapping = {platform.slug: platform.napalm_driver for platform in platforms if platform.napalm_driver} - - hosts = {} - groups = {"global": {"connection_options": {"netmiko": {"extras": {}}, "napalm": {"extras": {}}}}} - - # Pull the login and password from the NI config object if available - if username: - groups["global"]["username"] = username - - if password: - groups["global"]["password"] = password - if enable: - groups["global"]["connection_options"]["netmiko"]["extras"] = { - "secret": password, - "global_delay_factor": global_delay_factor, - "banner_timeout": banner_timeout, - "conn_timeout": conn_timeout, - } - groups["global"]["connection_options"]["napalm"]["extras"] = {"optional_args": {"secret": password}} - - for dev in nb_devices: - - host: HostsDict = {"data": copy.deepcopy(BASE_DATA)} - - # Only add virtual chassis master as inventory element - if dev.virtual_chassis and dev.virtual_chassis.master: - if dev.id != dev.virtual_chassis.master.id: - continue - host["data"]["virtual_chassis"] = True - - else: - host["data"]["virtual_chassis"] = False - - # If supported_platforms is provided - # skip all devices that do not match the list of supported platforms - # TODO need to see if we can filter when doing the query directly - if supported_platforms: - if not dev.platform: - continue - - if dev.platform.slug not in supported_platforms: - continue - - # Add value for IP address - if use_primary_ip and dev.primary_ip: - host["hostname"] = dev.primary_ip.address.split("/")[0] - elif use_primary_ip and not dev.primary_ip: - host["data"]["is_reachable"] = False - host["data"]["not_reachable_reason"] = "primary ip not defined in Netbox" - elif not use_primary_ip and fqdn: - host["hostname"] = f"{dev.name}.{fqdn}" - elif not use_primary_ip: - host["hostname"] = dev.name - - host["data"]["serial"] = dev.serial - host["data"]["vendor"] = dev.device_type.manufacturer.slug - host["data"]["asset_tag"] = dev.asset_tag - host["data"]["custom_fields"] = dev.custom_fields - host["data"]["site"] = dev.site.slug - host["data"]["site_id"] = dev.site.id - host["data"]["device_id"] = dev.id - host["data"]["role"] = dev.device_role.slug - host["data"]["model"] = dev.device_type.slug - - # Attempt to add 'platform' based of value in 'slug' - if dev.platform and dev.platform.slug in platforms_mapping: - host["connection_options"] = {"napalm": {"platform": platforms_mapping[dev.platform.slug]}} - - if dev.platform: - host["platform"] = dev.platform.slug - else: - host["platform"] = None - - host["groups"] = ["global", dev.site.slug, dev.device_role.slug] - - if dev.site.slug not in groups.keys(): - groups[dev.site.slug] = {} - - if dev.device_role.slug not in groups.keys(): - groups[dev.device_role.slug] = {} - - if "hostname" in host and host["hostname"] and "platform" in host and host["platform"]: - host["data"]["is_reachable"] = True - - # Assign temporary dict to outer dict - # Netbox allows devices to be unnamed, but the Nornir model does not allow this - # If a device is unnamed we will set the name to the id of the device in netbox - hosts[dev.name or dev.id] = host - - # Pass the data back to the parent class - super().__init__(hosts=hosts, groups=groups, defaults={}, **kwargs) +# BASE_DATA = {"is_reachable": None, "status": "ok", "has_config": False} + + +class NetworkImporterHost(Host): + """Network Importer Host Class.""" + is_reacheable: Optional[bool] + status: Optional[str] = "ok" + has_config: Optional[bool] = False + not_reachable_reason: Optional[str] + +class NetworkImporterInventory: + def __init__(self, params: Optional[Dict] = None): + pass + +# class NetboxInventory(Inventory): +# """Netbox Inventory Class.""" + +# # pylint: disable=dangerous-default-value, too-many-branches, too-many-statements +# def __init__( +# self, +# nb_url: Optional[str] = None, +# nb_token: Optional[str] = None, +# ssl_verify: Union[bool, str] = True, +# username: Optional[str] = None, +# password: Optional[str] = None, +# enable: Optional[bool] = True, +# use_primary_ip: Optional[bool] = True, +# fqdn: Optional[str] = None, +# supported_platforms: Optional[List[str]] = [], +# filter_parameters: Optional[Dict[str, Any]] = None, +# global_delay_factor: Optional[int] = 5, +# banner_timeout: Optional[int] = 15, +# conn_timeout: Optional[int] = 5, +# **kwargs: Any, +# ) -> None: +# """Norning Inventory Plugin fir Netbox. + +# Hard copy from https://github.com/nornir-automation/nornir/blob/develop/nornir/plugins/inventory/netbox.py, +# Need to see how to contribute back some of these modifications + +# Args: +# filter_parameters: Key +# nb_url: Optional[str]: (Default value = None) +# nb_token: Optional[str]: (Default value = None) +# ssl_verify: (Default value = True) +# filter_parameters: Optional[Dict[str: Any]]: (Default value = None) +# username: Optional[str] +# password: Optional[str] +# enable: Optional[bool] = True, +# use_primary_ip: Optional[bool] = True, +# fqdn: Optional[str] = None, +# supported_platforms: Optional[List[str]] +# global_delay_factor: Optional[int] Global Delay factor for netmiko +# banner_timeout: Optional[int] Banner Timeout for netmiko/paramiko +# conn_timeout: Optional[int] Connection timeout for netmiko/paramiko +# **kwargs: Any: +# """ +# filter_parameters = filter_parameters or {} + +# if "exclude" not in filter_parameters.keys(): +# filter_parameters["exclude"] = "config_context" + +# # Instantiate netbox session using pynetbox +# nb_session = pynetbox.api(url=nb_url, token=nb_token) +# if not ssl_verify: +# session = requests.Session() +# session.verify = False +# nb_session.http_session = session + +# # fetch devices from netbox +# if filter_parameters: +# nb_devices: List[pynetbox.modules.dcim.Devices] = nb_session.dcim.devices.filter(**filter_parameters) +# else: +# nb_devices: List[pynetbox.modules.dcim.Devices] = nb_session.dcim.devices.all() + +# # fetch all platforms from Netbox and build mapping: platform: napalm_driver +# platforms = nb_session.dcim.platforms.all() +# platforms_mapping = {platform.slug: platform.napalm_driver for platform in platforms if platform.napalm_driver} + +# hosts = {} +# groups = {"global": {"connection_options": {"netmiko": {"extras": {}}, "napalm": {"extras": {}}}}} + +# # Pull the login and password from the NI config object if available +# if username: +# groups["global"]["username"] = username + +# if password: +# groups["global"]["password"] = password +# if enable: +# groups["global"]["connection_options"]["netmiko"]["extras"] = { +# "secret": password, +# "global_delay_factor": global_delay_factor, +# "banner_timeout": banner_timeout, +# "conn_timeout": conn_timeout, +# } +# groups["global"]["connection_options"]["napalm"]["extras"] = {"optional_args": {"secret": password}} + +# for dev in nb_devices: + +# host: HostsDict = {"data": copy.deepcopy(BASE_DATA)} + +# # Only add virtual chassis master as inventory element +# if dev.virtual_chassis and dev.virtual_chassis.master: +# if dev.id != dev.virtual_chassis.master.id: +# continue +# host["data"]["virtual_chassis"] = True + +# else: +# host["data"]["virtual_chassis"] = False + +# # If supported_platforms is provided +# # skip all devices that do not match the list of supported platforms +# # TODO need to see if we can filter when doing the query directly +# if supported_platforms: +# if not dev.platform: +# continue + +# if dev.platform.slug not in supported_platforms: +# continue + +# # Add value for IP address +# if use_primary_ip and dev.primary_ip: +# host["hostname"] = dev.primary_ip.address.split("/")[0] +# elif use_primary_ip and not dev.primary_ip: +# host["data"]["is_reachable"] = False +# host["data"]["not_reachable_reason"] = "primary ip not defined in Netbox" +# elif not use_primary_ip and fqdn: +# host["hostname"] = f"{dev.name}.{fqdn}" +# elif not use_primary_ip: +# host["hostname"] = dev.name + +# host["data"]["serial"] = dev.serial +# host["data"]["vendor"] = dev.device_type.manufacturer.slug +# host["data"]["asset_tag"] = dev.asset_tag +# host["data"]["custom_fields"] = dev.custom_fields +# host["data"]["site"] = dev.site.slug +# host["data"]["site_id"] = dev.site.id +# host["data"]["device_id"] = dev.id +# host["data"]["role"] = dev.device_role.slug +# host["data"]["model"] = dev.device_type.slug + +# # Attempt to add 'platform' based of value in 'slug' +# if dev.platform and dev.platform.slug in platforms_mapping: +# host["connection_options"] = {"napalm": {"platform": platforms_mapping[dev.platform.slug]}} + +# if dev.platform: +# host["platform"] = dev.platform.slug +# else: +# host["platform"] = None + +# host["groups"] = ["global", dev.site.slug, dev.device_role.slug] + +# if dev.site.slug not in groups.keys(): +# groups[dev.site.slug] = {} + +# if dev.device_role.slug not in groups.keys(): +# groups[dev.device_role.slug] = {} + +# if "hostname" in host and host["hostname"] and "platform" in host and host["platform"]: +# host["data"]["is_reachable"] = True + +# # Assign temporary dict to outer dict +# # Netbox allows devices to be unnamed, but the Nornir model does not allow this +# # If a device is unnamed we will set the name to the id of the device in netbox +# hosts[dev.name or dev.id] = host + +# # Pass the data back to the parent class +# super().__init__(hosts=hosts, groups=groups, defaults={}, **kwargs) # ----------------------------------------------------------------- diff --git a/poetry.lock b/poetry.lock index aa46d7c4..bf9f7de2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,13 +15,13 @@ optional = false python-versions = ">=3.6" [package.dependencies] +typing-extensions = ">=3.6.5" +yarl = ">=1.0,<2.0" async-timeout = ">=3.0,<4.0" -attrs = ">=17.3.0" -chardet = ">=2.0,<4.0" idna-ssl = {version = ">=1.0", markers = "python_version < \"3.7\""} +chardet = ">=2.0,<4.0" +attrs = ">=17.3.0" multidict = ">=4.5,<7.0" -typing-extensions = ">=3.6.5" -yarl = ">=1.0,<2.0" [package.extras] speedups = ["aiodns", "brotlipy", "cchardet"] @@ -35,10 +35,10 @@ optional = false python-versions = "*" [package.dependencies] -aiohttp = ">=2.3.10" -jinja2 = ">=2.11.2,<2.12.0" markupsafe = ">=1.1.1,<1.2.0" pyYAML = ">=5.1" +jinja2 = ">=2.11.2,<2.12.0" +aiohttp = ">=2.3.10" [package.extras] performance = ["ujson"] @@ -53,17 +53,16 @@ python-versions = "*" [[package]] name = "astroid" -version = "2.4.2" +version = "2.5.6" description = "An abstract syntax tree for Python with inference support." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = "~=3.6" [package.dependencies] -lazy-object-proxy = ">=1.4.0,<1.5.0" -six = ">=1.12,<2.0" +lazy-object-proxy = ">=1.4.0" typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} -wrapt = ">=1.11,<2.0" +wrapt = ">=1.11,<1.13" [[package]] name = "async-lru" @@ -91,17 +90,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.3.0" +version = "21.2.0" description = "Classes Without Boilerplate" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] [[package]] name = "bandit" @@ -112,11 +111,11 @@ optional = false python-versions = ">=3.5" [package.dependencies] -colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} -GitPython = ">=1.0.1" +stevedore = ">=1.20.0" PyYAML = ">=5.3.1" six = ">=1.10.0" -stevedore = ">=1.20.0" +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +GitPython = ">=1.0.1" [[package]] name = "bcrypt" @@ -127,12 +126,12 @@ optional = false python-versions = ">=3.6" [package.dependencies] -cffi = ">=1.1" six = ">=1.4.1" +cffi = ">=1.1" [package.extras] -tests = ["pytest (>=3.2.1,!=3.3.0)"] typecheck = ["mypy"] +tests = ["pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "bidict" @@ -143,11 +142,11 @@ optional = false python-versions = ">=3.6" [package.extras] +test = ["hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] +docs = ["Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] coverage = ["coverage (<6)", "pytest-cov (<3)"] dev = ["setuptools-scm", "hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)", "coverage (<6)", "pytest-cov (<3)", "pre-commit (<3)", "tox (<4)"] -docs = ["Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] precommit = ["pre-commit (<3)"] -test = ["hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] [[package]] name = "black" @@ -158,13 +157,13 @@ optional = false python-versions = ">=3.6" [package.dependencies] -appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" -pathspec = ">=0.6,<1" regex = "*" -toml = ">=0.9.4" typed-ast = ">=1.4.0" +toml = ">=0.9.4" +attrs = ">=18.1.0" +pathspec = ">=0.6,<1" +click = ">=6.5" +appdirs = "*" [package.extras] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] @@ -179,7 +178,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.4" +version = "1.14.5" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -196,19 +195,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "ciscoconfparse" -version = "1.5.22" -description = "Parse, Audit, Query, Build, and Modify Cisco IOS-style configurations" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -colorama = "*" -dnspython = "*" -passlib = "*" - [[package]] name = "click" version = "7.1.2" @@ -249,22 +235,22 @@ yaml = ["pyyaml"] [[package]] name = "cryptography" -version = "3.3.1" +version = "3.4.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +python-versions = ">=3.6" [package.dependencies] cffi = ">=1.12" -six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] +test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "dataclasses" @@ -276,21 +262,21 @@ python-versions = ">=3.6, <3.7" [[package]] name = "deepdiff" -version = "5.0.2" +version = "5.5.0" description = "Deep Difference and Search of any Python object/data." category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -ordered-set = ">=4.0.1" +ordered-set = "4.0.2" [package.extras] -murmur = ["mmh3"] +cli = ["click (==7.1.2)", "pyyaml (==5.4)", "toml (==0.10.2)", "clevercsv (==0.6.7)"] [[package]] name = "deprecated" -version = "1.2.10" +version = "1.2.12" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." category = "main" optional = false @@ -300,7 +286,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" wrapt = ">=1.10,<2" [package.extras] -dev = ["tox", "bumpversion (<1)", "sphinx (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] [[package]] name = "diffsync" @@ -312,9 +298,9 @@ python-versions = ">=3.6,<4.0" [package.dependencies] colorama = ">=0.4.3,<0.5.0" -dataclasses = {version = ">=0.7,<0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} -pydantic = ">=1.7.2,<2.0.0" structlog = ">=20.1.0,<21.0.0" +pydantic = ">=1.7.2,<2.0.0" +dataclasses = {version = ">=0.7,<0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} [[package]] name = "dill" @@ -335,21 +321,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "dnspython" -version = "2.0.0" -description = "DNS toolkit" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -dnssec = ["cryptography (>=2.6)"] -doh = ["requests", "requests-toolbelt"] -idna = ["idna (>=2.1)"] -curio = ["curio (>=1.2)", "sniffio (>=1.1)"] -trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"] - [[package]] name = "fancycompleter" version = "0.9.1" @@ -359,22 +330,22 @@ optional = false python-versions = "*" [package.dependencies] -pyreadline = {version = "*", markers = "platform_system == \"Windows\""} pyrepl = ">=0.8.2" +pyreadline = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "flake8" -version = "3.8.4" +version = "3.9.1" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.6.0a1,<2.7.0" -pyflakes = ">=2.2.0,<2.3.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "future" @@ -393,27 +364,27 @@ optional = false python-versions = ">=3.5" [package.dependencies] -dill = "*" -"genie.libs.clean" = ">=20.12.0,<20.13.0" +tqdm = "*" "genie.libs.conf" = ">=20.12.0,<20.13.0" -"genie.libs.filetransferutils" = ">=20.12.0,<20.13.0" -"genie.libs.health" = ">=20.12.0,<20.13.0" "genie.libs.ops" = ">=20.12.0,<20.13.0" -"genie.libs.parser" = ">=20.12.0,<20.13.0" -"genie.libs.sdk" = ">=20.12.0,<20.13.0" -jsonpickle = "*" +dill = "*" netaddr = "*" +jsonpickle = "*" +"genie.libs.filetransferutils" = ">=20.12.0,<20.13.0" +"genie.libs.clean" = ">=20.12.0,<20.13.0" +"genie.libs.parser" = ">=20.12.0,<20.13.0" PrettyTable = "*" -tqdm = "*" +"genie.libs.sdk" = ">=20.12.0,<20.13.0" +"genie.libs.health" = ">=20.12.0,<20.13.0" [package.extras] -dev = ["coverage", "restview", "sphinx", "sphinx-rtd-theme"] full = ["genie.libs.conf", "genie.libs.clean", "genie.libs.health", "genie.libs.filetransferutils", "genie.libs.ops", "genie.libs.parser", "genie.libs.sdk", "pyats.robot (>=20.12.0,<20.13.0)", "genie.libs.robot (>=20.12.0,<20.13.0)", "genie.telemetry (>=20.12.0,<20.13.0)", "genie.trafficgen (>=20.12.0,<20.13.0)"] robot = ["pyats.robot (>=20.12.0,<20.13.0)", "genie.libs.robot (>=20.12.0,<20.13.0)"] +dev = ["coverage", "restview", "sphinx", "sphinx-rtd-theme"] [[package]] name = "genie.libs.clean" -version = "20.12.1" +version = "20.12.2" description = "Genie Library for device clean support" category = "main" optional = false @@ -439,16 +410,16 @@ dev = ["coverage", "restview", "sphinx", "sphinx-rtd-theme"] [[package]] name = "genie.libs.filetransferutils" -version = "20.12" +version = "20.12.1" description = "Genie libs FileTransferUtils: Genie FileTransferUtils Libraries" category = "main" optional = false python-versions = "*" [package.dependencies] +unicon = "*" pyftpdlib = "*" tftpy = "*" -unicon = "*" [package.extras] dev = ["coverage", "restview", "sphinx", "sphinx-rtd-theme"] @@ -508,24 +479,25 @@ dev = ["coverage", "restview", "sphinx", "sphinxcontrib-napoleon", "sphinx-rtd-t [[package]] name = "gitdb" -version = "4.0.5" +version = "4.0.7" description = "Git Object Database" category = "dev" optional = false python-versions = ">=3.4" [package.dependencies] -smmap = ">=3.0.1,<4" +smmap = ">=3.0.1,<5" [[package]] name = "gitpython" -version = "3.1.11" +version = "3.1.15" description = "Python Git Library" category = "dev" optional = false -python-versions = ">=3.4" +python-versions = ">=3.5" [package.dependencies] +typing-extensions = ">=3.7.4.0" gitdb = ">=4.0.1,<5" [[package]] @@ -549,7 +521,7 @@ idna = ">=2.0" [[package]] name = "importlib-metadata" -version = "3.3.0" +version = "4.0.1" description = "Read metadata from Python packages" category = "main" optional = false @@ -560,12 +532,12 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "importlib-resources" -version = "4.1.1" +version = "5.1.2" description = "Read resources from Python packages" category = "main" optional = false @@ -575,12 +547,12 @@ python-versions = ">=3.6" zipp = {version = ">=0.4", markers = "python_version < \"3.8\""} [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] name = "invoke" -version = "1.4.1" +version = "1.5.0" description = "Pythonic task execution" category = "dev" optional = false @@ -596,16 +568,16 @@ python-versions = "*" [[package]] name = "isort" -version = "5.6.4" +version = "5.8.0" description = "A Python utility / library to sort Python imports." category = "dev" optional = false python-versions = ">=3.6,<4.0" [package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] -colors = ["colorama (>=0.4.3,<0.5.0)"] [[package]] name = "jinja2" @@ -623,7 +595,7 @@ i18n = ["Babel (>=0.8)"] [[package]] name = "jsonpickle" -version = "1.4.2" +version = "2.0.0" description = "Python library for serializing any arbitrary object graph into JSON" category = "main" optional = false @@ -634,8 +606,8 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["coverage (<5)", "pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sqlalchemy", "enum34", "jsonlib"] "testing.libs" = ["demjson", "simplejson", "ujson", "yajl"] +testing = ["coverage (<5)", "pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] [[package]] name = "junit-xml" @@ -648,49 +620,13 @@ python-versions = "*" [package.dependencies] six = "*" -[[package]] -name = "junos-eznc" -version = "2.5.4" -description = "Junos 'EZ' automation for non-programmers" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -jinja2 = ">=2.7.1" -lxml = ">=3.2.4" -ncclient = ">=0.6.3" -netaddr = "*" -paramiko = ">=1.15.2" -pyparsing = "*" -pyserial = "*" -PyYAML = ">=5.1" -scp = ">=0.7.0" -six = "*" -transitions = "*" -yamlordereddictloader = "*" - [[package]] name = "lazy-object-proxy" -version = "1.4.3" +version = "1.6.0" description = "A fast and thorough lazy object proxy." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "lxml" -version = "4.6.2" -description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" - -[package.extras] -cssselect = ["cssselect (>=0.7)"] -html5 = ["html5lib"] -htmlsoup = ["beautifulsoup4"] -source = ["Cython (>=0.29.7)"] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "markupsafe" @@ -710,7 +646,7 @@ python-versions = "*" [[package]] name = "more-itertools" -version = "8.6.0" +version = "8.7.0" description = "More routines for operating on iterables, beyond itertools" category = "dev" optional = false @@ -732,43 +668,6 @@ category = "main" optional = false python-versions = "*" -[[package]] -name = "napalm" -version = "3.2.0" -description = "Network Automation and Programmability Abstraction Layer with Multivendor support" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -cffi = ">=1.11.3" -ciscoconfparse = "*" -future = "*" -jinja2 = "*" -junos-eznc = ">=2.2.1" -lxml = ">=4.3.0" -netaddr = "*" -netmiko = ">=3.1.0" -paramiko = ">=2.6.0" -pyeapi = ">=0.8.2" -pyYAML = "*" -requests = ">=2.7.0" -scp = "*" -textfsm = "*" - -[[package]] -name = "ncclient" -version = "0.6.9" -description = "Python library for NETCONF clients" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -lxml = ">=3.3.0" -paramiko = ">=1.15.0" -six = "*" - [[package]] name = "netaddr" version = "0.8.0" @@ -782,26 +681,26 @@ importlib-resources = {version = "*", markers = "python_version < \"3.7\""} [[package]] name = "netconan" -version = "0.11.3" +version = "0.12.2" description = "Netconan network configuration anonymization utilities" category = "main" optional = false python-versions = "*" [package.dependencies] +six = "<2.0.0" +passlib = "<2.0.0" +ipaddress = "<2.0.0" bidict = "<1.0.0" configargparse = "<1.0.0" -ipaddress = "<2.0.0" -passlib = "<2.0.0" -six = "<2.0.0" [package.extras] -dev = ["flake8 (<4.0.0)", "flake8-docstrings (<2.0.0)", "pydocstyle (<4.0.0)"] test = ["pytest (>=4.6.0,<5.0.0)", "pytest-cov (<3.0.0)", "requests-mock (<2.0.0)", "testfixtures (<7.0.0)", "zipp (<2.2)"] +dev = ["flake8 (<4.0.0)", "flake8-docstrings (<2.0.0)", "pydocstyle (<4.0.0)"] [[package]] name = "netmiko" -version = "3.3.2" +version = "3.4.0" description = "Multi-vendor library to simplify Paramiko SSH connections to network devices" category = "main" optional = false @@ -809,42 +708,35 @@ python-versions = "*" [package.dependencies] importlib-resources = {version = "*", markers = "python_version < \"3.7\""} -ntc-templates = "*" -paramiko = ">=2.6.0" pyserial = "*" -scp = ">=0.13.2" tenacity = "*" +ntc-templates = "*" +scp = ">=0.13.2" +paramiko = ">=2.6.0" [package.extras] test = ["pyyaml (>=5.1.2)", "pytest (>=5.1.2)"] [[package]] name = "nornir" -version = "2.5.0" +version = "3.1.1" description = "Pluggable multi-threaded framework with inventory management to help operate collections of devices" category = "main" optional = false python-versions = ">=3.6,<4.0" [package.dependencies] -colorama = ">=0.4.1,<0.5.0" -dataclasses = {version = ">=0.7,<0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} -jinja2 = ">=2,<3" +typing_extensions = ">=3.7,<4.0" mypy_extensions = ">=0.4.1,<0.5.0" -napalm = ">=2.5.0" -ncclient = ">=0.6.6,<0.7.0" -netmiko = ">=2.4.2" -paramiko = ">=2.7,<3.0" -requests = ">=2,<3" "ruamel.yaml" = ">=0.16,<0.17" -typing_extensions = ">=3.7,<4.0" +dataclasses = {version = ">=0.7,<0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} [package.extras] -docs = ["sphinx (>=1,<2)", "sphinx_rtd_theme (>=0.4,<0.5)", "sphinxcontrib-napoleon (>=0.7,<0.8)", "jupyter (>=1,<2)", "nbsphinx (>=0.5,<0.6)", "pygments (>=2,<3)", "sphinx-issues (>=1.2,<2.0)"] +docs = ["jupyter (>=1,<2)", "nbsphinx (>=0.5,<0.6)", "pygments (>=2,<3)", "sphinx (>=1,<2)", "sphinx-issues (>=1.2,<2.0)", "sphinx_rtd_theme (>=0.4,<0.5)", "sphinxcontrib-napoleon (>=0.7,<0.8)"] [[package]] name = "ntc-templates" -version = "1.6.0" +version = "1.7.0" description = "Package to return structured data from the output of network devices." category = "main" optional = false @@ -858,7 +750,7 @@ dev = ["pytest", "pyyaml", "black", "yamllint", "ruamel.yaml"] [[package]] name = "numpy" -version = "1.19.4" +version = "1.19.5" description = "NumPy is the fundamental package for array computing with Python." category = "main" optional = false @@ -874,7 +766,7 @@ python-versions = ">=3.5" [[package]] name = "packaging" -version = "20.8" +version = "20.9" description = "Core utilities for Python packages" category = "dev" optional = false @@ -892,9 +784,9 @@ optional = false python-versions = ">=3.5.3" [package.dependencies] -numpy = ">=1.13.3" python-dateutil = ">=2.6.1" pytz = ">=2017.2" +numpy = ">=1.13.3" [package.extras] test = ["pytest (>=4.0.2)", "pytest-xdist", "hypothesis (>=3.58)"] @@ -908,13 +800,13 @@ optional = false python-versions = "*" [package.dependencies] +pynacl = ">=1.0.1" bcrypt = ">=3.1.3" cryptography = ">=2.5" -pynacl = ">=1.0.1" [package.extras] -all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"] +all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] invoke = ["invoke (>=1.3)"] @@ -929,8 +821,8 @@ python-versions = "*" [package.extras] argon2 = ["argon2-cffi (>=18.2.0)"] bcrypt = ["bcrypt (>=3.1.0)"] -build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"] totp = ["cryptography"] +build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"] [[package]] name = "pathspec" @@ -942,7 +834,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pbr" -version = "5.5.1" +version = "5.6.0" description = "Python Build Reasonableness" category = "dev" optional = false @@ -962,8 +854,8 @@ pygments = "*" wmctrl = "*" [package.extras] -funcsigs = ["funcsigs"] testing = ["funcsigs", "pytest"] +funcsigs = ["funcsigs"] [[package]] name = "pluggy" @@ -989,13 +881,14 @@ python-versions = "*" [[package]] name = "prettytable" -version = "2.0.0" +version = "2.1.0" description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} wcwidth = "*" [package.extras] @@ -1037,24 +930,24 @@ optional = false python-versions = ">=3.5" [package.dependencies] -"pyats.aereport" = ">=20.12.0,<20.13.0" -"pyats.aetest" = ">=20.12.0,<20.13.0" -"pyats.async" = ">=20.12.0,<20.13.0" -"pyats.connections" = ">=20.12.0,<20.13.0" -"pyats.datastructures" = ">=20.12.0,<20.13.0" +"pyats.log" = ">=20.12.0,<20.13.0" +"pyats.topology" = ">=20.12.0,<20.13.0" "pyats.easypy" = ">=20.12.0,<20.13.0" "pyats.kleenex" = ">=20.12.0,<20.13.0" -"pyats.log" = ">=20.12.0,<20.13.0" +"pyats.connections" = ">=20.12.0,<20.13.0" "pyats.reporter" = ">=20.12.0,<20.13.0" -"pyats.results" = ">=20.12.0,<20.13.0" +"pyats.aetest" = ">=20.12.0,<20.13.0" "pyats.tcl" = ">=20.12.0,<20.13.0" -"pyats.topology" = ">=20.12.0,<20.13.0" +"pyats.async" = ">=20.12.0,<20.13.0" "pyats.utils" = ">=20.12.0,<20.13.0" +"pyats.aereport" = ">=20.12.0,<20.13.0" +"pyats.results" = ">=20.12.0,<20.13.0" +"pyats.datastructures" = ">=20.12.0,<20.13.0" [package.extras] full = ["cookiecutter", "genie (>=20.12.0,<20.13.0)", "pyats.robot (>=20.12.0,<20.13.0)", "genie.libs.robot (>=20.12.0,<20.13.0)", "genie.telemetry (>=20.12.0,<20.13.0)", "genie.trafficgen (>=20.12.0,<20.13.0)", "pyats.contrib (>=20.12.0,<20.13.0)"] -library = ["genie (>=20.12.0,<20.13.0)"] robot = ["pyats.robot (>=20.12.0,<20.13.0)", "genie.libs.robot (>=20.12.0,<20.13.0)"] +library = ["genie (>=20.12.0,<20.13.0)"] template = ["cookiecutter"] [[package]] @@ -1066,11 +959,11 @@ optional = false python-versions = ">=3.5" [package.dependencies] -jinja2 = "*" -junit-xml = "*" -psutil = "*" "pyats.log" = ">=20.12.0,<20.13.0" +psutil = "*" "pyats.results" = ">=20.12.0,<20.13.0" +jinja2 = "*" +junit-xml = "*" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1084,13 +977,13 @@ optional = false python-versions = ">=3.5" [package.dependencies] +"pyats.log" = ">=20.12.0,<20.13.0" +pyyaml = "*" jinja2 = "2.11.2" +"pyats.utils" = ">=20.12.0,<20.13.0" "pyats.aereport" = ">=20.12.0,<20.13.0" -"pyats.datastructures" = ">=20.12.0,<20.13.0" -"pyats.log" = ">=20.12.0,<20.13.0" "pyats.results" = ">=20.12.0,<20.13.0" -"pyats.utils" = ">=20.12.0,<20.13.0" -pyyaml = "*" +"pyats.datastructures" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1118,9 +1011,9 @@ optional = false python-versions = ">=3.5" [package.dependencies] +unicon = ">=20.12.0,<20.13.0" "pyats.async" = ">=20.12.0,<20.13.0" "pyats.datastructures" = ">=20.12.0,<20.13.0" -unicon = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1145,16 +1038,16 @@ optional = false python-versions = ">=3.5" [package.dependencies] -distro = "*" -jinja2 = "*" -psutil = "*" -"pyats.aereport" = ">=20.12.0,<20.13.0" -"pyats.datastructures" = ">=20.12.0,<20.13.0" -"pyats.kleenex" = ">=20.12.0,<20.13.0" "pyats.log" = ">=20.12.0,<20.13.0" -"pyats.results" = ">=20.12.0,<20.13.0" "pyats.topology" = ">=20.12.0,<20.13.0" +"pyats.kleenex" = ">=20.12.0,<20.13.0" +psutil = "*" +jinja2 = "*" +distro = "*" "pyats.utils" = ">=20.12.0,<20.13.0" +"pyats.aereport" = ">=20.12.0,<20.13.0" +"pyats.results" = ">=20.12.0,<20.13.0" +"pyats.datastructures" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1168,14 +1061,14 @@ optional = false python-versions = ">=3.5" [package.dependencies] -distro = "*" -"pyats.aetest" = ">=20.12.0,<20.13.0" -"pyats.async" = ">=20.12.0,<20.13.0" -"pyats.datastructures" = ">=20.12.0,<20.13.0" "pyats.log" = ">=20.12.0,<20.13.0" "pyats.topology" = ">=20.12.0,<20.13.0" +"pyats.async" = ">=20.12.0,<20.13.0" +distro = "*" "pyats.utils" = ">=20.12.0,<20.13.0" requests = "*" +"pyats.aetest" = ">=20.12.0,<20.13.0" +"pyats.datastructures" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1189,16 +1082,16 @@ optional = false python-versions = ">=3.5" [package.dependencies] -aiofiles = "0.6.0" -aiohttp = "3.7.2" +pyyaml = "*" aiohttp-swagger = "1.0.15" +aiohttp = "3.7.2" async-lru = "1.0.2" -chardet = "3.0.4" -Jinja2 = "2.11.2" -"pyats.datastructures" = ">=20.12.0,<20.13.0" python-engineio = "3.13.2" +Jinja2 = "2.11.2" +aiofiles = "0.6.0" +chardet = "3.0.4" python-socketio = "4.6.0" -pyyaml = "*" +"pyats.datastructures" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1212,11 +1105,11 @@ optional = false python-versions = ">=3.5" [package.dependencies] -"pyats.aereport" = ">=20.12.0,<20.13.0" "pyats.log" = ">=20.12.0,<20.13.0" +pyyaml = "*" +"pyats.aereport" = ">=20.12.0,<20.13.0" "pyats.results" = ">=20.12.0,<20.13.0" "pyats.utils" = ">=20.12.0,<20.13.0" -pyyaml = "*" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1241,8 +1134,8 @@ optional = false python-versions = ">=3.5" [package.dependencies] -"pyats.datastructures" = ">=20.12.0,<20.13.0" "pyats.log" = ">=20.12.0,<20.13.0" +"pyats.datastructures" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1257,10 +1150,10 @@ python-versions = ">=3.5" [package.dependencies] "pyats.connections" = ">=20.12.0,<20.13.0" -"pyats.datastructures" = ">=20.12.0,<20.13.0" -"pyats.utils" = ">=20.12.0,<20.13.0" pyyaml = "*" +"pyats.utils" = ">=20.12.0,<20.13.0" yamllint = "*" +"pyats.datastructures" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1274,9 +1167,9 @@ optional = false python-versions = ">=3.5" [package.dependencies] -distro = "*" -"pyats.datastructures" = ">=20.12.0,<20.13.0" "pyats.topology" = ">=20.12.0,<20.13.0" +"pyats.datastructures" = ">=20.12.0,<20.13.0" +distro = "*" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1290,16 +1183,16 @@ optional = false python-versions = ">=3.6" [package.dependencies] +PyYAML = "*" +simplejson = "*" +netconan = ">=0.11.0" +deprecated = "*" attrs = ">=18.1.0" deepdiff = "*" -deprecated = "*" -netconan = ">=0.11.0" -pandas = "<1.2" python-dateutil = "*" -PyYAML = "*" requests = "*" +pandas = "<1.2" requests-toolbelt = "*" -simplejson = "*" [package.extras] capirca = ["capirca", "absl-py (>=0.8.0)"] @@ -1307,7 +1200,7 @@ dev = ["cerberus (==1.3.2)", "check-manifest", "coverage", "inflection", "jupyte [[package]] name = "pycodestyle" -version = "2.6.0" +version = "2.7.0" description = "Python style guide checker" category = "dev" optional = false @@ -1323,11 +1216,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pycryptodomex" -version = "3.9.9" +version = "3.10.1" description = "Cryptographic library for Python" category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pydantic" @@ -1342,8 +1235,8 @@ dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] typing_extensions = ["typing-extensions (>=3.7.2)"] +email = ["email-validator (>=1.0.3)"] [[package]] name = "pydocstyle" @@ -1356,24 +1249,9 @@ python-versions = ">=3.5" [package.dependencies] snowballstemmer = "*" -[[package]] -name = "pyeapi" -version = "0.8.4" -description = "Python Client for eAPI" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -netaddr = "*" - -[package.extras] -dev = ["check-manifest", "pep8", "pyflakes", "twine"] -test = ["coverage", "mock"] - [[package]] name = "pyflakes" -version = "2.2.0" +version = "2.3.1" description = "passive checker of Python programs" category = "dev" optional = false @@ -1392,7 +1270,7 @@ ssl = ["pyopenssl"] [[package]] name = "pygments" -version = "2.7.3" +version = "2.9.0" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false @@ -1400,18 +1278,18 @@ python-versions = ">=3.5" [[package]] name = "pylint" -version = "2.6.0" +version = "2.8.2" description = "python code static checker" category = "dev" optional = false -python-versions = ">=3.5.*" +python-versions = "~=3.6" [package.dependencies] -astroid = ">=2.4.0,<=2.5" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -isort = ">=4.2.5,<6" +astroid = ">=2.5.6,<2.7" mccabe = ">=0.6,<0.7" +colorama = {version = "*", markers = "sys_platform == \"win32\""} toml = ">=0.7.1" +isort = ">=4.2.5,<6" [[package]] name = "pyment" @@ -1430,8 +1308,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] -cffi = ">=1.4.1" six = "*" +cffi = ">=1.4.1" [package.extras] docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] @@ -1453,7 +1331,7 @@ docs = ["Sphinx (>=3.5.1,<4.0.0)"] [[package]] name = "pynetbox" -version = "5.1.2" +version = "5.3.1" description = "NetBox API client library" category = "main" optional = false @@ -1467,7 +1345,7 @@ six = ">=1.0.0,<2.0.0" name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "main" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -1518,9 +1396,9 @@ optional = false python-versions = "*" [package.dependencies] -pyasn1 = ">=0.2.3" pycryptodomex = "*" pysmi = "*" +pyasn1 = ">=0.2.3" [[package]] name = "pytest" @@ -1531,15 +1409,15 @@ optional = false python-versions = ">=3.5" [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=17.4.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} more-itertools = ">=4.0.0" -packaging = "*" -pluggy = ">=0.12,<1.0" py = ">=1.5.0" wcwidth = "*" +packaging = "*" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +colorama = {version = "*", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +pluggy = ">=0.12,<1.0" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} [package.extras] checkqa-mypy = ["mypy (==v0.761)"] @@ -1568,8 +1446,8 @@ python-versions = "*" six = ">=1.9.0" [package.extras] -asyncio_client = ["aiohttp (>=3.4)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] +asyncio_client = ["aiohttp (>=3.4)"] [[package]] name = "python-socketio" @@ -1580,16 +1458,16 @@ optional = false python-versions = "*" [package.dependencies] -python-engineio = ">=3.13.0" six = ">=1.9.0" +python-engineio = ">=3.13.0" [package.extras] -asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"] client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] +asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"] [[package]] name = "pytz" -version = "2020.5" +version = "2021.1" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -1597,15 +1475,15 @@ python-versions = "*" [[package]] name = "pyyaml" -version = "5.3.1" +version = "5.4.1" description = "YAML parser and emitter for Python" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "regex" -version = "2020.11.13" +version = "2021.4.4" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -1620,9 +1498,9 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] +idna = ">=2.5,<3" certifi = ">=2017.4.17" chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" [package.extras] @@ -1631,7 +1509,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] name = "requests-mock" -version = "1.8.0" +version = "1.9.2" description = "Mock out responses from the requests package" category = "dev" optional = false @@ -1642,8 +1520,8 @@ requests = ">=2.3,<3" six = "*" [package.extras] -fixture = ["fixtures"] test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.18)", "testtools"] +fixture = ["fixtures"] [[package]] name = "requests-toolbelt" @@ -1658,32 +1536,32 @@ requests = ">=2.0.1,<3.0.0" [[package]] name = "rich" -version = "9.5.1" +version = "9.13.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" category = "main" optional = false python-versions = ">=3.6,<4.0" [package.dependencies] +typing-extensions = ">=3.7.4,<4.0.0" +pygments = ">=2.6.0,<3.0.0" colorama = ">=0.4.0,<0.5.0" commonmark = ">=0.9.0,<0.10.0" dataclasses = {version = ">=0.7,<0.9", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} -pygments = ">=2.6.0,<3.0.0" -typing-extensions = ">=3.7.4,<4.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] [[package]] name = "ruamel.yaml" -version = "0.16.12" +version = "0.16.13" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" category = "main" optional = false python-versions = "*" [package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.1.2", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.9\""} +"ruamel.yaml.clib" = {version = ">=0.1.2", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.10\""} [package.extras] docs = ["ryd"] @@ -1718,7 +1596,7 @@ python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "six" -version = "1.15.0" +version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" optional = false @@ -1726,16 +1604,16 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "smmap" -version = "3.0.4" +version = "4.0.0" description = "A pure Python implementation of a sliding window memory map manager" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "snowballstemmer" -version = "2.0.0" -description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." +version = "2.1.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "dev" optional = false python-versions = "*" @@ -1754,24 +1632,23 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "structlog" -version = "20.1.0" +version = "20.2.0" description = "Structured Logging for Python" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [package.dependencies] -six = "*" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -azure-pipelines = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest (>=3.3.0)", "simplejson", "pytest-azurepipelines", "python-rapidjson", "pytest-asyncio"] -dev = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest (>=3.3.0)", "simplejson", "sphinx", "twisted", "pre-commit", "python-rapidjson", "pytest-asyncio"] -docs = ["sphinx", "twisted"] -tests = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest (>=3.3.0)", "simplejson", "python-rapidjson", "pytest-asyncio"] +docs = ["furo", "sphinx", "sphinx-toolbox", "twisted"] +tests = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio", "pytest-randomly", "pytest (>=6.0)", "simplejson"] +dev = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio", "pytest-randomly", "pytest (>=6.0)", "simplejson", "furo", "sphinx", "sphinx-toolbox", "twisted", "pre-commit"] [[package]] name = "tenacity" -version = "6.3.1" +version = "7.0.0" description = "Retry code until it succeeds" category = "main" optional = false @@ -1821,34 +1698,20 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tqdm" -version = "4.55.0" +version = "4.60.0" description = "Fast, Extensible Progress Meter" category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" [package.extras] -dev = ["py-make (>=0.1.0)", "twine", "wheel"] telegram = ["requests"] - -[[package]] -name = "transitions" -version = "0.8.6" -description = "A lightweight, object-oriented Python state machine implementation with many extensions." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" - -[package.extras] -diagrams = ["pygraphviz"] -test = ["pytest"] +notebook = ["ipywidgets (>=6)"] +dev = ["py-make (>=0.1.0)", "twine", "wheel"] [[package]] name = "typed-ast" -version = "1.4.1" +version = "1.4.3" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -1856,7 +1719,7 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.7.4.3" +version = "3.10.0.0" description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" optional = false @@ -1876,9 +1739,9 @@ pyyaml = "*" "unicon.plugins" = ">=20.12.0,<20.13.0" [package.extras] -dev = ["cisco-distutils", "coverage", "restview", "sphinx", "sphinxcontrib-napoleon", "sphinxcontrib-mockautodoc", "sphinx-rtd-theme"] pyats = ["pyats"] robot = ["robotframework"] +dev = ["cisco-distutils", "coverage", "restview", "sphinx", "sphinxcontrib-napoleon", "sphinxcontrib-mockautodoc", "sphinx-rtd-theme"] [[package]] name = "unicon.plugins" @@ -1889,24 +1752,24 @@ optional = false python-versions = "*" [package.dependencies] -pyyaml = "*" unicon = ">=20.12.0,<20.13.0" +pyyaml = "*" [package.extras] dev = ["setuptools", "pip", "wheel", "coverage", "restview", "sphinx", "sphinxcontrib-napoleon", "sphinxcontrib-mockautodoc", "sphinx-rtd-theme"] [[package]] name = "urllib3" -version = "1.26.2" +version = "1.26.4" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotlipy (>=0.6.0)"] [[package]] name = "wcwidth" @@ -1918,7 +1781,7 @@ python-versions = "*" [[package]] name = "wmctrl" -version = "0.3" +version = "0.4" description = "A tool to programmatically control windows inside X" category = "dev" optional = false @@ -1942,27 +1805,16 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "yamllint" -version = "1.25.0" +version = "1.26.1" description = "A linter for YAML files." category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = ">=3.5.*" [package.dependencies] pathspec = ">=0.5.3" pyyaml = "*" -[[package]] -name = "yamlordereddictloader" -version = "0.4.0" -description = "YAML loader and dump for PyYAML allowing to keep keys order." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pyyaml = "*" - [[package]] name = "yarl" version = "1.6.3" @@ -1972,26 +1824,26 @@ optional = false python-versions = ">=3.6" [package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} +multidict = ">=4.0" +idna = ">=2.0" [[package]] name = "zipp" -version = "3.4.0" +version = "3.4.1" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.6" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "ba73a0ea2c8b802f74aa0d96cd1228aa8fb9cca07fd3e1a310e8f5a602f6fd89" +content-hash = "a68308a62ec5fef915be82af72042b652c502d2f7882fd5179edd74c2be6ad43" [metadata.files] aiofiles = [ @@ -2041,8 +1893,8 @@ appdirs = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] astroid = [ - {file = "astroid-2.4.2-py3-none-any.whl", hash = "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"}, - {file = "astroid-2.4.2.tar.gz", hash = "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703"}, + {file = "astroid-2.5.6-py3-none-any.whl", hash = "sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e"}, + {file = "astroid-2.5.6.tar.gz", hash = "sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975"}, ] async-lru = [ {file = "async_lru-1.0.2.tar.gz", hash = "sha256:baa898027619f5cc31b7966f96f00e4fc0df43ba206a8940a5d1af5336a477cb"}, @@ -2056,8 +1908,8 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, ] bandit = [ {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"}, @@ -2085,49 +1937,48 @@ certifi = [ {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] cffi = [ - {file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"}, - {file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"}, - {file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"}, - {file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"}, - {file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"}, - {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"}, - {file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"}, - {file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"}, - {file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"}, - {file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"}, - {file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"}, - {file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"}, - {file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"}, - {file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"}, - {file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"}, - {file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"}, - {file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"}, - {file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"}, - {file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"}, - {file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"}, - {file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"}, - {file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"}, - {file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"}, - {file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"}, - {file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"}, - {file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"}, - {file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"}, - {file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"}, - {file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"}, + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] -ciscoconfparse = [ - {file = "ciscoconfparse-1.5.22-py3-none-any.whl", hash = "sha256:008c3738febb75181799bf8506f941e61c09acd1c72faf3deb136da0feef4239"}, - {file = "ciscoconfparse-1.5.22.tar.gz", hash = "sha256:df1d6d27918cd8229d341bfbb2a2389d5caf7523e71733bc89f7bf3251fbbaeb"}, -] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, @@ -2144,32 +1995,30 @@ configargparse = [ {file = "ConfigArgParse-0.15.2.tar.gz", hash = "sha256:558738aff623d6667aa5b85df6093ad3828867de8a82b66a6d458fb42567beb3"}, ] cryptography = [ - {file = "cryptography-3.3.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030"}, - {file = "cryptography-3.3.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0"}, - {file = "cryptography-3.3.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812"}, - {file = "cryptography-3.3.1-cp27-cp27m-win32.whl", hash = "sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e"}, - {file = "cryptography-3.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901"}, - {file = "cryptography-3.3.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d"}, - {file = "cryptography-3.3.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5"}, - {file = "cryptography-3.3.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302"}, - {file = "cryptography-3.3.1-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244"}, - {file = "cryptography-3.3.1-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c"}, - {file = "cryptography-3.3.1-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c"}, - {file = "cryptography-3.3.1-cp36-abi3-win32.whl", hash = "sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a"}, - {file = "cryptography-3.3.1-cp36-abi3-win_amd64.whl", hash = "sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7"}, - {file = "cryptography-3.3.1.tar.gz", hash = "sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"}, + {file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"}, + {file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"}, + {file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"}, + {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"}, + {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"}, + {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"}, + {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"}, ] dataclasses = [ {file = "dataclasses-0.7-py3-none-any.whl", hash = "sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836"}, {file = "dataclasses-0.7.tar.gz", hash = "sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6"}, ] deepdiff = [ - {file = "deepdiff-5.0.2-py3-none-any.whl", hash = "sha256:273b18d32bb9b956548290b2e3ddf79c515def2dd5738965f4348ae813e710c5"}, - {file = "deepdiff-5.0.2.tar.gz", hash = "sha256:e2b74af4da0ef9cd338bb6e8c97242c1ec9d81fcb28298d7bb24acdc19ea79d7"}, + {file = "deepdiff-5.5.0-py3-none-any.whl", hash = "sha256:e054fed9dfe0d83d622921cbb3a3d0b3a6dd76acd2b6955433a0a2d35147774a"}, + {file = "deepdiff-5.5.0.tar.gz", hash = "sha256:dd79b81c2d84bfa33aa9d94d456b037b68daff6bb87b80dfaa1eca04da68b349"}, ] deprecated = [ - {file = "Deprecated-1.2.10-py2.py3-none-any.whl", hash = "sha256:a766c1dccb30c5f6eb2b203f87edd1d8588847709c78589e1521d769addc8218"}, - {file = "Deprecated-1.2.10.tar.gz", hash = "sha256:525ba66fb5f90b07169fdd48b6373c18f1ee12728ca277ca44567a367d9d7f74"}, + {file = "Deprecated-1.2.12-py2.py3-none-any.whl", hash = "sha256:08452d69b6b5bc66e8330adde0a4f8642e969b9e1702904d137eeb29c8ffc771"}, + {file = "Deprecated-1.2.12.tar.gz", hash = "sha256:6d2de2de7931a968874481ef30208fd4e08da39177d61d3d4ebdf4366e7dbca1"}, ] diffsync = [ {file = "diffsync-1.2.0-py3-none-any.whl", hash = "sha256:a1fc61de07083eaab2858057f27dc4f093947940c4b7b6b8a51062d9cf83445a"}, @@ -2183,17 +2032,13 @@ distro = [ {file = "distro-1.5.0-py2.py3-none-any.whl", hash = "sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799"}, {file = "distro-1.5.0.tar.gz", hash = "sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92"}, ] -dnspython = [ - {file = "dnspython-2.0.0-py3-none-any.whl", hash = "sha256:40bb3c24b9d4ec12500f0124288a65df232a3aa749bb0c39734b782873a2544d"}, - {file = "dnspython-2.0.0.zip", hash = "sha256:044af09374469c3a39eeea1a146e8cac27daec951f1f1f157b1962fc7cb9d1b7"}, -] fancycompleter = [ {file = "fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080"}, {file = "fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272"}, ] flake8 = [ - {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, - {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, + {file = "flake8-3.9.1-py2.py3-none-any.whl", hash = "sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a"}, + {file = "flake8-3.9.1.tar.gz", hash = "sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378"}, ] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, @@ -2213,13 +2058,13 @@ genie = [ {file = "genie-20.12.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:440d38c6bb278c8d3b8caf9256ec4c2906b003f579b715f333066808d3bdd188"}, ] "genie.libs.clean" = [ - {file = "genie.libs.clean-20.12.1-py3-none-any.whl", hash = "sha256:343358ce01d721efa686db9d6065947db61dc2248ef233866cd89581b61bb254"}, + {file = "genie.libs.clean-20.12.2-py3-none-any.whl", hash = "sha256:2da611170d825045df0877700fce3c2e19182e4409133b2f18d1ab6aea240f52"}, ] "genie.libs.conf" = [ {file = "genie.libs.conf-20.12-py3-none-any.whl", hash = "sha256:7dc02609fcc450401bc366519ed446018f3f5f992e79f2c05a12d1ed27251986"}, ] "genie.libs.filetransferutils" = [ - {file = "genie.libs.filetransferutils-20.12-py3-none-any.whl", hash = "sha256:24bd20de622946339c02f3c41a60dd3f86db5507e610827789300026c4b9f53f"}, + {file = "genie.libs.filetransferutils-20.12.1-py3-none-any.whl", hash = "sha256:5fee12be9ebc0a2982eaa1e86f68d70c2edae9096ac5b36c83048d653b776aba"}, ] "genie.libs.health" = [ {file = "genie.libs.health-20.12-py3-none-any.whl", hash = "sha256:ceb82f4ce6a50d9d5d2c6a9f3b9f3034765bfaeb927d3130bf21798922afb295"}, @@ -2234,12 +2079,12 @@ genie = [ {file = "genie.libs.sdk-20.12-py3-none-any.whl", hash = "sha256:39f53bbbae4aabb68a9d493fda9bea025603c108ffc5a5c8265c67a00aaa4c72"}, ] gitdb = [ - {file = "gitdb-4.0.5-py3-none-any.whl", hash = "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac"}, - {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"}, + {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"}, + {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"}, ] gitpython = [ - {file = "GitPython-3.1.11-py3-none-any.whl", hash = "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b"}, - {file = "GitPython-3.1.11.tar.gz", hash = "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8"}, + {file = "GitPython-3.1.15-py3-none-any.whl", hash = "sha256:a77824e516d3298b04fb36ec7845e92747df8fcfee9cacc32dd6239f9652f867"}, + {file = "GitPython-3.1.15.tar.gz", hash = "sha256:05af150f47a5cca3f4b0af289b73aef8cf3c4fe2385015b06220cbcdee48bb6e"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, @@ -2249,102 +2094,60 @@ idna-ssl = [ {file = "idna-ssl-1.1.0.tar.gz", hash = "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"}, - {file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"}, + {file = "importlib_metadata-4.0.1-py3-none-any.whl", hash = "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d"}, + {file = "importlib_metadata-4.0.1.tar.gz", hash = "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581"}, ] importlib-resources = [ - {file = "importlib_resources-4.1.1-py3-none-any.whl", hash = "sha256:0a948d0c8c3f9344de62997e3f73444dbba233b1eaf24352933c2d264b9e4182"}, - {file = "importlib_resources-4.1.1.tar.gz", hash = "sha256:6b45007a479c4ec21165ae3ffbe37faf35404e2041fac6ae1da684f38530ca73"}, + {file = "importlib_resources-5.1.2-py3-none-any.whl", hash = "sha256:ebab3efe74d83b04d6bf5cd9a17f0c5c93e60fb60f30c90f56265fce4682a469"}, + {file = "importlib_resources-5.1.2.tar.gz", hash = "sha256:642586fc4740bd1cad7690f836b3321309402b20b332529f25617ff18e8e1370"}, ] invoke = [ - {file = "invoke-1.4.1-py2-none-any.whl", hash = "sha256:93e12876d88130c8e0d7fd6618dd5387d6b36da55ad541481dfa5e001656f134"}, - {file = "invoke-1.4.1-py3-none-any.whl", hash = "sha256:87b3ef9d72a1667e104f89b159eaf8a514dbf2f3576885b2bbdefe74c3fb2132"}, - {file = "invoke-1.4.1.tar.gz", hash = "sha256:de3f23bfe669e3db1085789fd859eb8ca8e0c5d9c20811e2407fa042e8a5e15d"}, + {file = "invoke-1.5.0-py2-none-any.whl", hash = "sha256:da7c2d0be71be83ffd6337e078ef9643f41240024d6b2659e7b46e0b251e339f"}, + {file = "invoke-1.5.0-py3-none-any.whl", hash = "sha256:7e44d98a7dc00c91c79bac9e3007276965d2c96884b3c22077a9f04042bd6d90"}, + {file = "invoke-1.5.0.tar.gz", hash = "sha256:f0c560075b5fb29ba14dad44a7185514e94970d1b9d57dcd3723bec5fed92650"}, ] ipaddress = [ {file = "ipaddress-1.0.23-py2.py3-none-any.whl", hash = "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc"}, {file = "ipaddress-1.0.23.tar.gz", hash = "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"}, ] isort = [ - {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, - {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, + {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, + {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, ] jinja2 = [ {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, ] jsonpickle = [ - {file = "jsonpickle-1.4.2-py2.py3-none-any.whl", hash = "sha256:2ac5863099864c63d7f0c367af5e512c94f3384977dd367f2eae5f2303f7b92c"}, - {file = "jsonpickle-1.4.2.tar.gz", hash = "sha256:c9b99b28a9e6a3043ec993552db79f4389da11afcb1d0246d93c79f4b5e64062"}, + {file = "jsonpickle-2.0.0-py2.py3-none-any.whl", hash = "sha256:c1010994c1fbda87a48f8a56698605b598cb0fc6bb7e7927559fc1100e69aeac"}, + {file = "jsonpickle-2.0.0.tar.gz", hash = "sha256:0be49cba80ea6f87a168aa8168d717d00c6ca07ba83df3cec32d3b30bfe6fb9a"}, ] junit-xml = [ {file = "junit_xml-1.9-py2.py3-none-any.whl", hash = "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732"}, ] -junos-eznc = [ - {file = "junos-eznc-2.5.4.tar.gz", hash = "sha256:bf036d0af9ee5c5e4f517cb5fc902fe891fa120e18f459805862c53d4a97193a"}, - {file = "junos_eznc-2.5.4-py2.py3-none-any.whl", hash = "sha256:e05c36d56d8b8d13b1fb3bb763828bb3ee80fa1dcadc3a6762e8e2568504676d"}, -] lazy-object-proxy = [ - {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"}, - {file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"}, - {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"}, - {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"}, - {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"}, - {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"}, - {file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"}, -] -lxml = [ - {file = "lxml-4.6.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a9d6bc8642e2c67db33f1247a77c53476f3a166e09067c0474facb045756087f"}, - {file = "lxml-4.6.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:791394449e98243839fa822a637177dd42a95f4883ad3dec2a0ce6ac99fb0a9d"}, - {file = "lxml-4.6.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:68a5d77e440df94011214b7db907ec8f19e439507a70c958f750c18d88f995d2"}, - {file = "lxml-4.6.2-cp27-cp27m-win32.whl", hash = "sha256:fc37870d6716b137e80d19241d0e2cff7a7643b925dfa49b4c8ebd1295eb506e"}, - {file = "lxml-4.6.2-cp27-cp27m-win_amd64.whl", hash = "sha256:69a63f83e88138ab7642d8f61418cf3180a4d8cd13995df87725cb8b893e950e"}, - {file = "lxml-4.6.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:42ebca24ba2a21065fb546f3e6bd0c58c3fe9ac298f3a320147029a4850f51a2"}, - {file = "lxml-4.6.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f83d281bb2a6217cd806f4cf0ddded436790e66f393e124dfe9731f6b3fb9afe"}, - {file = "lxml-4.6.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:535f067002b0fd1a4e5296a8f1bf88193080ff992a195e66964ef2a6cfec5388"}, - {file = "lxml-4.6.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:366cb750140f221523fa062d641393092813b81e15d0e25d9f7c6025f910ee80"}, - {file = "lxml-4.6.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:97db258793d193c7b62d4e2586c6ed98d51086e93f9a3af2b2034af01450a74b"}, - {file = "lxml-4.6.2-cp35-cp35m-win32.whl", hash = "sha256:648914abafe67f11be7d93c1a546068f8eff3c5fa938e1f94509e4a5d682b2d8"}, - {file = "lxml-4.6.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4e751e77006da34643ab782e4a5cc21ea7b755551db202bc4d3a423b307db780"}, - {file = "lxml-4.6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:681d75e1a38a69f1e64ab82fe4b1ed3fd758717bed735fb9aeaa124143f051af"}, - {file = "lxml-4.6.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:127f76864468d6630e1b453d3ffbbd04b024c674f55cf0a30dc2595137892d37"}, - {file = "lxml-4.6.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4fb85c447e288df535b17ebdebf0ec1cf3a3f1a8eba7e79169f4f37af43c6b98"}, - {file = "lxml-4.6.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5be4a2e212bb6aa045e37f7d48e3e1e4b6fd259882ed5a00786f82e8c37ce77d"}, - {file = "lxml-4.6.2-cp36-cp36m-win32.whl", hash = "sha256:8c88b599e226994ad4db29d93bc149aa1aff3dc3a4355dd5757569ba78632bdf"}, - {file = "lxml-4.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:6e4183800f16f3679076dfa8abf2db3083919d7e30764a069fb66b2b9eff9939"}, - {file = "lxml-4.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d8d3d4713f0c28bdc6c806a278d998546e8efc3498949e3ace6e117462ac0a5e"}, - {file = "lxml-4.6.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:8246f30ca34dc712ab07e51dc34fea883c00b7ccb0e614651e49da2c49a30711"}, - {file = "lxml-4.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:923963e989ffbceaa210ac37afc9b906acebe945d2723e9679b643513837b089"}, - {file = "lxml-4.6.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:1471cee35eba321827d7d53d104e7b8c593ea3ad376aa2df89533ce8e1b24a01"}, - {file = "lxml-4.6.2-cp37-cp37m-win32.whl", hash = "sha256:2363c35637d2d9d6f26f60a208819e7eafc4305ce39dc1d5005eccc4593331c2"}, - {file = "lxml-4.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4822c0660c3754f1a41a655e37cb4dbbc9be3d35b125a37fab6f82d47674ebc"}, - {file = "lxml-4.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0448576c148c129594d890265b1a83b9cd76fd1f0a6a04620753d9a6bcfd0a4d"}, - {file = "lxml-4.6.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:60a20bfc3bd234d54d49c388950195d23a5583d4108e1a1d47c9eef8d8c042b3"}, - {file = "lxml-4.6.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2e5cc908fe43fe1aa299e58046ad66981131a66aea3129aac7770c37f590a644"}, - {file = "lxml-4.6.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:50c348995b47b5a4e330362cf39fc503b4a43b14a91c34c83b955e1805c8e308"}, - {file = "lxml-4.6.2-cp38-cp38-win32.whl", hash = "sha256:94d55bd03d8671686e3f012577d9caa5421a07286dd351dfef64791cf7c6c505"}, - {file = "lxml-4.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:7a7669ff50f41225ca5d6ee0a1ec8413f3a0d8aa2b109f86d540887b7ec0d72a"}, - {file = "lxml-4.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0bfe9bb028974a481410432dbe1b182e8191d5d40382e5b8ff39cdd2e5c5931"}, - {file = "lxml-4.6.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6fd8d5903c2e53f49e99359b063df27fdf7acb89a52b6a12494208bf61345a03"}, - {file = "lxml-4.6.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7e9eac1e526386df7c70ef253b792a0a12dd86d833b1d329e038c7a235dfceb5"}, - {file = "lxml-4.6.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7ee8af0b9f7de635c61cdd5b8534b76c52cd03536f29f51151b377f76e214a1a"}, - {file = "lxml-4.6.2-cp39-cp39-win32.whl", hash = "sha256:2e6fd1b8acd005bd71e6c94f30c055594bbd0aa02ef51a22bbfa961ab63b2d75"}, - {file = "lxml-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:535332fe9d00c3cd455bd3dd7d4bacab86e2d564bdf7606079160fa6251caacf"}, - {file = "lxml-4.6.2.tar.gz", hash = "sha256:cd11c7e8d21af997ee8079037fff88f16fda188a9776eb4b81c7e4c9c0a7d7fc"}, + {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93"}, + {file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f"}, + {file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd"}, + {file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8"}, + {file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, + {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, ] markupsafe = [ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, @@ -2386,8 +2189,8 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] more-itertools = [ - {file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"}, - {file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"}, + {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, + {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, ] multidict = [ {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, @@ -2432,75 +2235,68 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] -napalm = [ - {file = "napalm-3.2.0-py2.py3-none-any.whl", hash = "sha256:6ab07a8a360150198b3c33c2fa4e92a9658cda9821736d24f2399343425d2af3"}, - {file = "napalm-3.2.0.tar.gz", hash = "sha256:6e69a505f5d4a678c42f048d13153afabf10b7fec72868e3bfb53ff4089206fc"}, -] -ncclient = [ - {file = "ncclient-0.6.9.tar.gz", hash = "sha256:0112f2ad41fb658f52446d870853a63691d69299c73c7351c520d38dbd8dc0c4"}, -] netaddr = [ {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"}, {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"}, ] netconan = [ - {file = "netconan-0.11.3-py2.py3-none-any.whl", hash = "sha256:adee9995cfbe1ed70a1a181a687aaefd7dee221c9d8aeccf0e66dbc4ec3e04db"}, - {file = "netconan-0.11.3.tar.gz", hash = "sha256:cb050e04cc519324fffd073d7fc24beb839b405d33931a87a37567244b70cc8f"}, + {file = "netconan-0.12.2-py2.py3-none-any.whl", hash = "sha256:d7a3804a12b67783d398737089a776892cba0b4d590f020ad5c76b37f1c64438"}, + {file = "netconan-0.12.2.tar.gz", hash = "sha256:c8aa7406f67be0ca3dfa8db3e3d59432d53b70eb65c8476f0b7c47e4cdd1bc09"}, ] netmiko = [ - {file = "netmiko-3.3.2-py2.py3-none-any.whl", hash = "sha256:1f8b86432fde2fb3c15ed86e130a05e7da358aa1de23fc7c520d8dbac2d4781a"}, - {file = "netmiko-3.3.2.tar.gz", hash = "sha256:84832d2b7d19ef28b7255a6e0217a68ed5727c03206a5e74abc101dc42550d97"}, + {file = "netmiko-3.4.0-py3-none-any.whl", hash = "sha256:b66f25717db3609878f83c85604349dd40a0ab494d8eafd817dcde8388131136"}, + {file = "netmiko-3.4.0.tar.gz", hash = "sha256:acadb9dd97864ee848e2032f1f0e301c7b31e7a4153757d98f5c8ba1b9614993"}, ] nornir = [ - {file = "nornir-2.5.0-py3-none-any.whl", hash = "sha256:c67ea392869df7538b81094c44c23d4f330a65920916e1b799e12555abb51fc8"}, - {file = "nornir-2.5.0.tar.gz", hash = "sha256:5f82bece03b15ba00b46e87a7960cd19d96cb18e02a5b7db1127b20b528e2609"}, + {file = "nornir-3.1.1-py3-none-any.whl", hash = "sha256:217199f923c810f4a54dec8d440eb08682c8a4ea4746325bd3067dca2e32cf9f"}, + {file = "nornir-3.1.1.tar.gz", hash = "sha256:6abf8ca245aab8e36b5770ec94fdb456b6bf53696c6d68f2ee0882898630a392"}, ] ntc-templates = [ - {file = "ntc_templates-1.6.0-py3-none-any.whl", hash = "sha256:a45ca5bf20d8b4585d63f732f3f605761e20279734dab76d1960c082fd6f2417"}, - {file = "ntc_templates-1.6.0.tar.gz", hash = "sha256:a4420beee9cc14797d945b51bffd6e0126913b2dbc672d2e0a1530d3fec5b28d"}, + {file = "ntc_templates-1.7.0-py3-none-any.whl", hash = "sha256:08ffeafd02a1d3e42f4f12f910f0b4c64b93783d76ece186c9a63389403a450b"}, + {file = "ntc_templates-1.7.0.tar.gz", hash = "sha256:4e098f83b35624751f4f6aaa4eed9eefd50a742f3d009c86f4bb22843a6a72fc"}, ] numpy = [ - {file = "numpy-1.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9b30d4bd69498fc0c3fe9db5f62fffbb06b8eb9321f92cc970f2969be5e3949"}, - {file = "numpy-1.19.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fedbd128668ead37f33917820b704784aff695e0019309ad446a6d0b065b57e4"}, - {file = "numpy-1.19.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8ece138c3a16db8c1ad38f52eb32be6086cc72f403150a79336eb2045723a1ad"}, - {file = "numpy-1.19.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:64324f64f90a9e4ef732be0928be853eee378fd6a01be21a0a8469c4f2682c83"}, - {file = "numpy-1.19.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:ad6f2ff5b1989a4899bf89800a671d71b1612e5ff40866d1f4d8bcf48d4e5764"}, - {file = "numpy-1.19.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d6c7bb82883680e168b55b49c70af29b84b84abb161cbac2800e8fcb6f2109b6"}, - {file = "numpy-1.19.4-cp36-cp36m-win32.whl", hash = "sha256:13d166f77d6dc02c0a73c1101dd87fdf01339febec1030bd810dcd53fff3b0f1"}, - {file = "numpy-1.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:448ebb1b3bf64c0267d6b09a7cba26b5ae61b6d2dbabff7c91b660c7eccf2bdb"}, - {file = "numpy-1.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:27d3f3b9e3406579a8af3a9f262f5339005dd25e0ecf3cf1559ff8a49ed5cbf2"}, - {file = "numpy-1.19.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:16c1b388cc31a9baa06d91a19366fb99ddbe1c7b205293ed072211ee5bac1ed2"}, - {file = "numpy-1.19.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e5b6ed0f0b42317050c88022349d994fe72bfe35f5908617512cd8c8ef9da2a9"}, - {file = "numpy-1.19.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:18bed2bcb39e3f758296584337966e68d2d5ba6aab7e038688ad53c8f889f757"}, - {file = "numpy-1.19.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:fe45becb4c2f72a0907c1d0246ea6449fe7a9e2293bb0e11c4e9a32bb0930a15"}, - {file = "numpy-1.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6d7593a705d662be5bfe24111af14763016765f43cb6923ed86223f965f52387"}, - {file = "numpy-1.19.4-cp37-cp37m-win32.whl", hash = "sha256:6ae6c680f3ebf1cf7ad1d7748868b39d9f900836df774c453c11c5440bc15b36"}, - {file = "numpy-1.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9eeb7d1d04b117ac0d38719915ae169aa6b61fca227b0b7d198d43728f0c879c"}, - {file = "numpy-1.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb1017eec5257e9ac6209ac172058c430e834d5d2bc21961dceeb79d111e5909"}, - {file = "numpy-1.19.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:edb01671b3caae1ca00881686003d16c2209e07b7ef8b7639f1867852b948f7c"}, - {file = "numpy-1.19.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f29454410db6ef8126c83bd3c968d143304633d45dc57b51252afbd79d700893"}, - {file = "numpy-1.19.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:ec149b90019852266fec2341ce1db513b843e496d5a8e8cdb5ced1923a92faab"}, - {file = "numpy-1.19.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1aeef46a13e51931c0b1cf8ae1168b4a55ecd282e6688fdb0a948cc5a1d5afb9"}, - {file = "numpy-1.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:08308c38e44cc926bdfce99498b21eec1f848d24c302519e64203a8da99a97db"}, - {file = "numpy-1.19.4-cp38-cp38-win32.whl", hash = "sha256:5734bdc0342aba9dfc6f04920988140fb41234db42381cf7ccba64169f9fe7ac"}, - {file = "numpy-1.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:09c12096d843b90eafd01ea1b3307e78ddd47a55855ad402b157b6c4862197ce"}, - {file = "numpy-1.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e452dc66e08a4ce642a961f134814258a082832c78c90351b75c41ad16f79f63"}, - {file = "numpy-1.19.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a5d897c14513590a85774180be713f692df6fa8ecf6483e561a6d47309566f37"}, - {file = "numpy-1.19.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a09f98011236a419ee3f49cedc9ef27d7a1651df07810ae430a6b06576e0b414"}, - {file = "numpy-1.19.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:50e86c076611212ca62e5a59f518edafe0c0730f7d9195fec718da1a5c2bb1fc"}, - {file = "numpy-1.19.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f0d3929fe88ee1c155129ecd82f981b8856c5d97bcb0d5f23e9b4242e79d1de3"}, - {file = "numpy-1.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c42c4b73121caf0ed6cd795512c9c09c52a7287b04d105d112068c1736d7c753"}, - {file = "numpy-1.19.4-cp39-cp39-win32.whl", hash = "sha256:8cac8790a6b1ddf88640a9267ee67b1aee7a57dfa2d2dd33999d080bc8ee3a0f"}, - {file = "numpy-1.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:4377e10b874e653fe96985c05feed2225c912e328c8a26541f7fc600fb9c637b"}, - {file = "numpy-1.19.4-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:2a2740aa9733d2e5b2dfb33639d98a64c3b0f24765fed86b0fd2aec07f6a0a08"}, - {file = "numpy-1.19.4.zip", hash = "sha256:141ec3a3300ab89c7f2b0775289954d193cc8edb621ea05f99db9cb181530512"}, + {file = "numpy-1.19.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc6bd4fd593cb261332568485e20a0712883cf631f6f5e8e86a52caa8b2b50ff"}, + {file = "numpy-1.19.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aeb9ed923be74e659984e321f609b9ba54a48354bfd168d21a2b072ed1e833ea"}, + {file = "numpy-1.19.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8b5e972b43c8fc27d56550b4120fe6257fdc15f9301914380b27f74856299fea"}, + {file = "numpy-1.19.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:43d4c81d5ffdff6bae58d66a3cd7f54a7acd9a0e7b18d97abb255defc09e3140"}, + {file = "numpy-1.19.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a4646724fba402aa7504cd48b4b50e783296b5e10a524c7a6da62e4a8ac9698d"}, + {file = "numpy-1.19.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2e55195bc1c6b705bfd8ad6f288b38b11b1af32f3c8289d6c50d47f950c12e76"}, + {file = "numpy-1.19.5-cp36-cp36m-win32.whl", hash = "sha256:39b70c19ec771805081578cc936bbe95336798b7edf4732ed102e7a43ec5c07a"}, + {file = "numpy-1.19.5-cp36-cp36m-win_amd64.whl", hash = "sha256:dbd18bcf4889b720ba13a27ec2f2aac1981bd41203b3a3b27ba7a33f88ae4827"}, + {file = "numpy-1.19.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:603aa0706be710eea8884af807b1b3bc9fb2e49b9f4da439e76000f3b3c6ff0f"}, + {file = "numpy-1.19.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cae865b1cae1ec2663d8ea56ef6ff185bad091a5e33ebbadd98de2cfa3fa668f"}, + {file = "numpy-1.19.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:36674959eed6957e61f11c912f71e78857a8d0604171dfd9ce9ad5cbf41c511c"}, + {file = "numpy-1.19.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:06fab248a088e439402141ea04f0fffb203723148f6ee791e9c75b3e9e82f080"}, + {file = "numpy-1.19.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6149a185cece5ee78d1d196938b2a8f9d09f5a5ebfbba66969302a778d5ddd1d"}, + {file = "numpy-1.19.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:50a4a0ad0111cc1b71fa32dedd05fa239f7fb5a43a40663269bb5dc7877cfd28"}, + {file = "numpy-1.19.5-cp37-cp37m-win32.whl", hash = "sha256:d051ec1c64b85ecc69531e1137bb9751c6830772ee5c1c426dbcfe98ef5788d7"}, + {file = "numpy-1.19.5-cp37-cp37m-win_amd64.whl", hash = "sha256:a12ff4c8ddfee61f90a1633a4c4afd3f7bcb32b11c52026c92a12e1325922d0d"}, + {file = "numpy-1.19.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf2402002d3d9f91c8b01e66fbb436a4ed01c6498fffed0e4c7566da1d40ee1e"}, + {file = "numpy-1.19.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1ded4fce9cfaaf24e7a0ab51b7a87be9038ea1ace7f34b841fe3b6894c721d1c"}, + {file = "numpy-1.19.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:012426a41bc9ab63bb158635aecccc7610e3eff5d31d1eb43bc099debc979d94"}, + {file = "numpy-1.19.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:759e4095edc3c1b3ac031f34d9459fa781777a93ccc633a472a5468587a190ff"}, + {file = "numpy-1.19.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9d17f2be3b427fbb2bce61e596cf555d6f8a56c222bd2ca148baeeb5e5c783c"}, + {file = "numpy-1.19.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:99abf4f353c3d1a0c7a5f27699482c987cf663b1eac20db59b8c7b061eabd7fc"}, + {file = "numpy-1.19.5-cp38-cp38-win32.whl", hash = "sha256:384ec0463d1c2671170901994aeb6dce126de0a95ccc3976c43b0038a37329c2"}, + {file = "numpy-1.19.5-cp38-cp38-win_amd64.whl", hash = "sha256:811daee36a58dc79cf3d8bdd4a490e4277d0e4b7d103a001a4e73ddb48e7e6aa"}, + {file = "numpy-1.19.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c843b3f50d1ab7361ca4f0b3639bf691569493a56808a0b0c54a051d260b7dbd"}, + {file = "numpy-1.19.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d6631f2e867676b13026e2846180e2c13c1e11289d67da08d71cacb2cd93d4aa"}, + {file = "numpy-1.19.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7fb43004bce0ca31d8f13a6eb5e943fa73371381e53f7074ed21a4cb786c32f8"}, + {file = "numpy-1.19.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2ea52bd92ab9f768cc64a4c3ef8f4b2580a17af0a5436f6126b08efbd1838371"}, + {file = "numpy-1.19.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:400580cbd3cff6ffa6293df2278c75aef2d58d8d93d3c5614cd67981dae68ceb"}, + {file = "numpy-1.19.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:df609c82f18c5b9f6cb97271f03315ff0dbe481a2a02e56aeb1b1a985ce38e60"}, + {file = "numpy-1.19.5-cp39-cp39-win32.whl", hash = "sha256:ab83f24d5c52d60dbc8cd0528759532736b56db58adaa7b5f1f76ad551416a1e"}, + {file = "numpy-1.19.5-cp39-cp39-win_amd64.whl", hash = "sha256:0eef32ca3132a48e43f6a0f5a82cb508f22ce5a3d6f67a8329c81c8e226d3f6e"}, + {file = "numpy-1.19.5-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a0d53e51a6cb6f0d9082decb7a4cb6dfb33055308c4c44f53103c073f649af73"}, + {file = "numpy-1.19.5.zip", hash = "sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4"}, ] ordered-set = [ {file = "ordered-set-4.0.2.tar.gz", hash = "sha256:ba93b2df055bca202116ec44b9bead3df33ea63a7d5827ff8e16738b97f33a95"}, ] packaging = [ - {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, - {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] pandas = [ {file = "pandas-0.25.3-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:df8864824b1fe488cf778c3650ee59c3a0d8f42e53707de167ba6b4f7d35f133"}, @@ -2536,8 +2332,8 @@ pathspec = [ {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, ] pbr = [ - {file = "pbr-5.5.1-py2.py3-none-any.whl", hash = "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"}, - {file = "pbr-5.5.1.tar.gz", hash = "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9"}, + {file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"}, + {file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"}, ] pdbpp = [ {file = "pdbpp-0.10.2.tar.gz", hash = "sha256:73ff220d5006e0ecdc3e2705d8328d8aa5ac27fef95cc06f6e42cd7d22d55eb8"}, @@ -2551,8 +2347,8 @@ ply = [ {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, ] prettytable = [ - {file = "prettytable-2.0.0-py3-none-any.whl", hash = "sha256:5dc7fcaca227f48deacd86958ae9d7b8c31a186fcb354bbb066763f20adf3eb9"}, - {file = "prettytable-2.0.0.tar.gz", hash = "sha256:e37acd91976fe6119172771520e58d1742c8479703489321dc1d9c85e7259922"}, + {file = "prettytable-2.1.0-py3-none-any.whl", hash = "sha256:bb5abc72bdfae6f3cdadb04fb7726f6915af0ddb7c897a41d4ad7736d9bfd8fd"}, + {file = "prettytable-2.1.0.tar.gz", hash = "sha256:5882ed9092b391bb8f6e91f59bcdbd748924ff556bb7c634089d5519be87baa0"}, ] psutil = [ {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, @@ -2803,49 +2599,44 @@ pybatfish = [ {file = "pybatfish-2020.12.23.763-py2.py3-none-any.whl", hash = "sha256:6f95723e17f86e74c6b1975b5be32782cd7285db11f162706ee77c998f411bf9"}, ] pycodestyle = [ - {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, - {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] pycryptodomex = [ - {file = "pycryptodomex-3.9.9-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:5e486cab2dfcfaec934dd4f5d5837f4a9428b690f4d92a3b020fd31d1497ca64"}, - {file = "pycryptodomex-3.9.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:42669638e4f7937b7141044a2fbd1019caca62bd2cdd8b535f731426ab07bde1"}, - {file = "pycryptodomex-3.9.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4ce1fc1e6d2fd2d6dc197607153327989a128c093e0e94dca63408f506622c3e"}, - {file = "pycryptodomex-3.9.9-cp27-cp27m-win32.whl", hash = "sha256:d2d1388595cb5d27d9220d5cbaff4f37c6ec696a25882eb06d224d241e6e93fb"}, - {file = "pycryptodomex-3.9.9-cp27-cp27m-win_amd64.whl", hash = "sha256:a1d38a96da57e6103423a446079ead600b450cf0f8ebf56a231895abf77e7ffc"}, - {file = "pycryptodomex-3.9.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:934e460c5058346c6f1d62fdf3db5680fbdfbfd212722d24d8277bf47cd9ebdc"}, - {file = "pycryptodomex-3.9.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:3642252d7bfc4403a42050e18ba748bedebd5a998a8cba89665a4f42aea4c380"}, - {file = "pycryptodomex-3.9.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:a385fceaa0cdb97f0098f1c1e9ec0b46cc09186ddf60ec23538e871b1dddb6dc"}, - {file = "pycryptodomex-3.9.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73240335f4a1baf12880ebac6df66ab4d3a9212db9f3efe809c36a27280d16f8"}, - {file = "pycryptodomex-3.9.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:305e3c46f20d019cd57543c255e7ba49e432e275d7c0de8913b6dbe57a851bc8"}, - {file = "pycryptodomex-3.9.9-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:871852044f55295449fbf225538c2c4118525093c32f0a6c43c91bed0452d7e3"}, - {file = "pycryptodomex-3.9.9-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:4632d55a140b28e20be3cd7a3057af52fb747298ff0fd3290d4e9f245b5004ba"}, - {file = "pycryptodomex-3.9.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a9aac1a30b00b5038d3d8e48248f3b58ea15c827b67325c0d18a447552e30fc8"}, - {file = "pycryptodomex-3.9.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:a7cf1c14e47027d9fb9d26aa62e5d603994227bd635e58a8df4b1d2d1b6a8ed7"}, - {file = "pycryptodomex-3.9.9-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:20fb7f4efc494016eab1bc2f555bc0a12dd5ca61f35c95df8061818ffb2c20a3"}, - {file = "pycryptodomex-3.9.9-cp36-cp36m-win32.whl", hash = "sha256:892e93f3e7e10c751d6c17fa0dc422f7984cfd5eb6690011f9264dc73e2775fc"}, - {file = "pycryptodomex-3.9.9-cp36-cp36m-win_amd64.whl", hash = "sha256:28ee3bcb4d609aea3040cad995a8e2c9c6dc57c12183dadd69e53880c35333b9"}, - {file = "pycryptodomex-3.9.9-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:d62fbab185a6b01c5469eda9f0795f3d1a5bba24f5a5813f362e4b73a3c4dc70"}, - {file = "pycryptodomex-3.9.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bef9e9d39393dc7baec39ba4bac6c73826a4db02114cdeade2552a9d6afa16e2"}, - {file = "pycryptodomex-3.9.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f20a62397e09704049ce9007bea4f6bad965ba9336a760c6f4ef1b4192e12d6d"}, - {file = "pycryptodomex-3.9.9-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c885fe4d5f26ce8ca20c97d02e88f5fdd92c01e1cc771ad0951b21e1641faf6d"}, - {file = "pycryptodomex-3.9.9-cp37-cp37m-win32.whl", hash = "sha256:f81f7311250d9480e36dec819127897ae772e7e8de07abfabe931b8566770b8e"}, - {file = "pycryptodomex-3.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:55cf4e99b3ba0122dee570dc7661b97bf35c16aab3e2ccb5070709d282a1c7ab"}, - {file = "pycryptodomex-3.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:15c03ffdac17731b126880622823d30d0a3cc7203cd219e6b9814140a44e7fab"}, - {file = "pycryptodomex-3.9.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3547b87b16aad6afb28c9b3a9cd870e11b5e7b5ac649b74265258d96d8de1130"}, - {file = "pycryptodomex-3.9.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:65ec88c8271448d2ea109d35c1f297b09b872c57214ab7e832e413090d3469a9"}, - {file = "pycryptodomex-3.9.9-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:404faa3e518f8bea516aae2aac47d4d960397199a15b4bd6f66cad97825469a0"}, - {file = "pycryptodomex-3.9.9-cp38-cp38-win32.whl", hash = "sha256:d2e853e0f9535e693fade97768cf7293f3febabecc5feb1e9b2ffdfe1044ab96"}, - {file = "pycryptodomex-3.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:836fe39282e75311ce4c38468be148f7fac0df3d461c5de58c5ff1ddb8966bac"}, - {file = "pycryptodomex-3.9.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4a88c9383d273bdce3afc216020282c9c5c39ec0bd9462b1a206af6afa377cf0"}, - {file = "pycryptodomex-3.9.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:9736f3f3e1761024200637a080a4f922f5298ad5d780e10dbb5634fe8c65b34c"}, - {file = "pycryptodomex-3.9.9-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:6c95a3361ce70068cf69526a58751f73ddac5ba27a3c2379b057efa2f5338c8c"}, - {file = "pycryptodomex-3.9.9-cp39-cp39-win32.whl", hash = "sha256:b696876ee583d15310be57311e90e153a84b7913ac93e6b99675c0c9867926d0"}, - {file = "pycryptodomex-3.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:7651211e15109ac0058a49159265d9f6e6423c8a81c65434d3c56d708417a05b"}, - {file = "pycryptodomex-3.9.9.tar.gz", hash = "sha256:7b5b7c5896f8172ea0beb283f7f9428e0ab88ec248ce0a5b8c98d73e26267d51"}, + {file = "pycryptodomex-3.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:4344ab16faf6c2d9df2b6772995623698fb2d5f114dace4ab2ff335550cf71d5"}, + {file = "pycryptodomex-3.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f933ecf4cb736c7af60a6a533db2bf569717f2318b265f92907acff1db43bc34"}, + {file = "pycryptodomex-3.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0bd35af6a18b724c689e56f2dbbdd8e409288be71952d271ba3d9614b31d188c"}, + {file = "pycryptodomex-3.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ec9901d19cadb80d9235ee41cc58983f18660314a0eb3fc7b11b0522ac3b6c4a"}, + {file = "pycryptodomex-3.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:c2b680987f418858e89dbb4f09c8c919ece62811780a27051ace72b2f69fb1be"}, + {file = "pycryptodomex-3.10.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:a6584ae58001d17bb4dc0faa8a426919c2c028ef4d90ceb4191802ca6edb8204"}, + {file = "pycryptodomex-3.10.1-cp27-cp27m-win32.whl", hash = "sha256:4195604f75cdc1db9bccdb9e44d783add3c817319c30aaff011670c9ed167690"}, + {file = "pycryptodomex-3.10.1-cp27-cp27m-win_amd64.whl", hash = "sha256:9f713ffb4e27b5575bd917c70bbc3f7b348241a351015dbbc514c01b7061ff7e"}, + {file = "pycryptodomex-3.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:418f51c61eab52d9920f4ef468d22c89dab1be5ac796f71cf3802f6a6e667df0"}, + {file = "pycryptodomex-3.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8a98e02cbf8f624add45deff444539bf26345b479fc04fa0937b23cd84078d91"}, + {file = "pycryptodomex-3.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:dbd2c361db939a4252589baa94da4404d45e3fc70da1a31e541644cdf354336e"}, + {file = "pycryptodomex-3.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:564063e3782474c92cbb333effd06e6eb718471783c6e67f28c63f0fc3ac7b23"}, + {file = "pycryptodomex-3.10.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:e4a1245e7b846e88ba63e7543483bda61b9acbaee61eadbead5a1ce479d94740"}, + {file = "pycryptodomex-3.10.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3b8eb85b3cc7f083d87978c264d10ff9de3b4bfc46f1c6fdc2792e7d7ebc87bb"}, + {file = "pycryptodomex-3.10.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:f3bb267df679f70a9f40f17d62d22fe12e8b75e490f41807e7560de4d3e6bf9f"}, + {file = "pycryptodomex-3.10.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:04265a7a84ae002001249bd1de2823bcf46832bd4b58f6965567cb8a07cf4f00"}, + {file = "pycryptodomex-3.10.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:72f44b5be46faef2a1bf2a85902511b31f4dd7b01ce0c3978e92edb2cc812a82"}, + {file = "pycryptodomex-3.10.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:e090a8609e2095aa86978559b140cf8968af99ee54b8791b29ff804838f29f10"}, + {file = "pycryptodomex-3.10.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:20c45a30f3389148f94edb77f3b216c677a277942f62a2b81a1cc0b6b2dde7fc"}, + {file = "pycryptodomex-3.10.1-cp35-abi3-win32.whl", hash = "sha256:fc9c55dc1ed57db76595f2d19a479fc1c3a1be2c9da8de798a93d286c5f65f38"}, + {file = "pycryptodomex-3.10.1-cp35-abi3-win_amd64.whl", hash = "sha256:3dfce70c4e425607ae87b8eae67c9c7dbba59a33b62d70f79417aef0bc5c735b"}, + {file = "pycryptodomex-3.10.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:940db96449d7b2ebb2c7bf190be1514f3d67914bd37e54e8d30a182bd375a1a9"}, + {file = "pycryptodomex-3.10.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:d8fae5ba3d34c868ae43614e0bd6fb61114b2687ac3255798791ce075d95aece"}, + {file = "pycryptodomex-3.10.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:f2abeb4c4ce7584912f4d637b2c57f23720d35dd2892bfeb1b2c84b6fb7a8c88"}, + {file = "pycryptodomex-3.10.1-pp27-pypy_73-win32.whl", hash = "sha256:36dab7f506948056ceba2d57c1ade74e898401960de697cefc02f3519bd26c1b"}, + {file = "pycryptodomex-3.10.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37ec1b407ec032c7a0c1fdd2da12813f560bad38ae61ad9c7ce3c0573b3e5e30"}, + {file = "pycryptodomex-3.10.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:00a584ee52bf5e27d540129ca9bf7c4a7e7447f24ff4a220faa1304ad0c09bcd"}, + {file = "pycryptodomex-3.10.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:961333e7ee896651f02d4692242aa36b787b8e8e0baa2256717b2b9d55ae0a3c"}, + {file = "pycryptodomex-3.10.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:2959304d1ce31ab303d9fb5db2b294814278b35154d9b30bf7facc52d6088d0a"}, + {file = "pycryptodomex-3.10.1.tar.gz", hash = "sha256:541cd3e3e252fb19a7b48f420b798b53483302b7fe4d9954c947605d0a263d62"}, ] pydantic = [ {file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"}, @@ -2875,23 +2666,20 @@ pydocstyle = [ {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"}, {file = "pydocstyle-5.1.1.tar.gz", hash = "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325"}, ] -pyeapi = [ - {file = "pyeapi-0.8.4.tar.gz", hash = "sha256:c33ad1eadd8ebac75f63488df9412081ce0b024c9e1da12a37196a5c60427c54"}, -] pyflakes = [ - {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, - {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pyftpdlib = [ {file = "pyftpdlib-1.5.6.tar.gz", hash = "sha256:fda655d81f29af52885ca2f8a2704134baed540f16d66a0b26e8fdfafd12db5e"}, ] pygments = [ - {file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"}, - {file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"}, + {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, + {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, ] pylint = [ - {file = "pylint-2.6.0-py3-none-any.whl", hash = "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"}, - {file = "pylint-2.6.0.tar.gz", hash = "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210"}, + {file = "pylint-2.8.2-py3-none-any.whl", hash = "sha256:f7e2072654a6b6afdf5e2fb38147d3e2d2d43c89f648637baab63e026481279b"}, + {file = "pylint-2.8.2.tar.gz", hash = "sha256:586d8fa9b1891f4b725f587ef267abe2a1bad89d6b184520c7f07a253dd6e217"}, ] pyment = [ {file = "Pyment-0.3.3-py2.py3-none-any.whl", hash = "sha256:a0c6ec59d06d24aeec3eaecb22115d0dc95d09e14209b2df838381fdf47a78cc"}, @@ -2920,8 +2708,8 @@ pynautobot = [ {file = "pynautobot-1.0.2.tar.gz", hash = "sha256:8cb0afe97a48f16301a7a4bd58e409b4517fb5c9c9fc808f2cce6ea4e99e1408"}, ] pynetbox = [ - {file = "pynetbox-5.1.2-py3-none-any.whl", hash = "sha256:085444974d772dd84a2a124a0c63930d90c6256b8372c2f3360ed5c8be85de0c"}, - {file = "pynetbox-5.1.2.tar.gz", hash = "sha256:cd4045fd90754f123bd36902b87ddfbe23bb85dceb6d3c133e622ab3f0b4b0db"}, + {file = "pynetbox-5.3.1-py3-none-any.whl", hash = "sha256:b5326f3483b873ba1cb230e36ef5a7cd732b6f5428a0005b2978f3b2c40105db"}, + {file = "pynetbox-5.3.1.tar.gz", hash = "sha256:80743328f51f28827825fb2c9cb1993175da37c88988f02075f6f50e16333e0c"}, ] pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, @@ -2986,84 +2774,94 @@ python-socketio = [ {file = "python_socketio-4.6.0-py2.py3-none-any.whl", hash = "sha256:d437f797c44b6efba2f201867cf02b8c96b97dff26d4e4281ac08b45817cd522"}, ] pytz = [ - {file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"}, - {file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"}, + {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, + {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] pyyaml = [ - {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, - {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, - {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, - {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, - {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, + {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, + {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, + {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, + {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, + {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, + {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, + {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, + {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, + {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, + {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, + {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, + {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, + {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, + {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, + {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, + {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, + {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, + {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] regex = [ - {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, - {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, - {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, - {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, - {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, - {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, - {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, - {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, - {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, - {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, - {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, - {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, - {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, - {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, - {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, - {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, - {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, + {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, + {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, + {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, + {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, + {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, + {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, + {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, + {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, + {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, + {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, + {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, + {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, + {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, ] requests = [ {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] requests-mock = [ - {file = "requests-mock-1.8.0.tar.gz", hash = "sha256:e68f46844e4cee9d447150343c9ae875f99fa8037c6dcf5f15bf1fe9ab43d226"}, - {file = "requests_mock-1.8.0-py2.py3-none-any.whl", hash = "sha256:11215c6f4df72702aa357f205cf1e537cffd7392b3e787b58239bde5fb3db53b"}, + {file = "requests-mock-1.9.2.tar.gz", hash = "sha256:33296f228d8c5df11a7988b741325422480baddfdf5dd9318fd0eb40c3ed8595"}, + {file = "requests_mock-1.9.2-py2.py3-none-any.whl", hash = "sha256:5c8ef0254c14a84744be146e9799dc13ebc4f6186058112d9aeed96b131b58e2"}, ] requests-toolbelt = [ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, ] rich = [ - {file = "rich-9.5.1-py3-none-any.whl", hash = "sha256:0f4359df97670c1981599690458c4c9ede02c56f59ae3a648b7154cdba21b0cc"}, - {file = "rich-9.5.1.tar.gz", hash = "sha256:8b937e2d2c4ff9dcfda8a5910a8cd384bd30f50ec92346d616f62065c662df5f"}, + {file = "rich-9.13.0-py3-none-any.whl", hash = "sha256:9004f6449c89abadf689dad6f92393e760b8c3a8a8c4ea6d8d474066307c0e66"}, + {file = "rich-9.13.0.tar.gz", hash = "sha256:d59e94a0e3e686f0d268fe5c7060baa1bd6744abca71b45351f5850a3aaa6764"}, ] "ruamel.yaml" = [ - {file = "ruamel.yaml-0.16.12-py2.py3-none-any.whl", hash = "sha256:012b9470a0ea06e4e44e99e7920277edf6b46eee0232a04487ea73a7386340a5"}, - {file = "ruamel.yaml-0.16.12.tar.gz", hash = "sha256:076cc0bc34f1966d920a49f18b52b6ad559fbe656a0748e3535cf7b3f29ebf9e"}, + {file = "ruamel.yaml-0.16.13-py2.py3-none-any.whl", hash = "sha256:64b06e7873eb8e1125525ecef7345447d786368cadca92a7cd9b59eae62e95a3"}, + {file = "ruamel.yaml-0.16.13.tar.gz", hash = "sha256:bb48c514222702878759a05af96f4b7ecdba9b33cd4efcf25c86b882cef3a942"}, ] "ruamel.yaml.clib" = [ {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc"}, @@ -3073,29 +2871,20 @@ rich = [ {file = "ruamel.yaml.clib-0.2.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b"}, - {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win32.whl", hash = "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988"}, - {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win32.whl", hash = "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e"}, - {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win32.whl", hash = "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99"}, - {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win32.whl", hash = "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win32.whl", hash = "sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb"}, {file = "ruamel.yaml.clib-0.2.2.tar.gz", hash = "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7"}, ] scp = [ @@ -3150,28 +2939,28 @@ simplejson = [ {file = "simplejson-3.17.2.tar.gz", hash = "sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] smmap = [ - {file = "smmap-3.0.4-py2.py3-none-any.whl", hash = "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4"}, - {file = "smmap-3.0.4.tar.gz", hash = "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"}, + {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"}, + {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"}, ] snowballstemmer = [ - {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, - {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, + {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, + {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, ] stevedore = [ {file = "stevedore-3.3.0-py3-none-any.whl", hash = "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"}, {file = "stevedore-3.3.0.tar.gz", hash = "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee"}, ] structlog = [ - {file = "structlog-20.1.0-py2.py3-none-any.whl", hash = "sha256:8a672be150547a93d90a7d74229a29e765be05bd156a35cdcc527ebf68e9af92"}, - {file = "structlog-20.1.0.tar.gz", hash = "sha256:7a48375db6274ed1d0ae6123c486472aa1d0890b08d314d2b016f3aa7f35990b"}, + {file = "structlog-20.2.0-py2.py3-none-any.whl", hash = "sha256:33dd6bd5f49355e52c1c61bb6a4f20d0b48ce0328cc4a45fe872d38b97a05ccd"}, + {file = "structlog-20.2.0.tar.gz", hash = "sha256:af79dfa547d104af8d60f86eac12fb54825f54a46bc998e4504ef66177103174"}, ] tenacity = [ - {file = "tenacity-6.3.1-py2.py3-none-any.whl", hash = "sha256:baed357d9f35ec64264d8a4bbf004c35058fad8795c5b0d8a7dc77ecdcbb8f39"}, - {file = "tenacity-6.3.1.tar.gz", hash = "sha256:e14d191fb0a309b563904bbc336582efe2037de437e543b38da749769b544d7f"}, + {file = "tenacity-7.0.0-py2.py3-none-any.whl", hash = "sha256:a0ce48587271515db7d3a5e700df9ae69cce98c4b57c23a4886da15243603dd8"}, + {file = "tenacity-7.0.0.tar.gz", hash = "sha256:5bd16ef5d3b985647fe28dfa6f695d343aa26479a04e8792b9d3c8f49e361ae1"}, ] termcolor = [ {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, @@ -3187,40 +2976,45 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tqdm = [ - {file = "tqdm-4.55.0-py2.py3-none-any.whl", hash = "sha256:0cd81710de29754bf17b6fee07bdb86f956b4fa20d3078f02040f83e64309416"}, - {file = "tqdm-4.55.0.tar.gz", hash = "sha256:f4f80b96e2ceafea69add7bf971b8403b9cba8fb4451c1220f91c79be4ebd208"}, -] -transitions = [ - {file = "transitions-0.8.6-py2.py3-none-any.whl", hash = "sha256:188f83a5b194c8b7b65e04a54bdde3e1bac8bfe223190d54b2084d166028ba1b"}, - {file = "transitions-0.8.6.tar.gz", hash = "sha256:032e10113139852ffb3ecfa4c2a5138f87441d85c3d6ad9122bb4b0978180a8d"}, + {file = "tqdm-4.60.0-py2.py3-none-any.whl", hash = "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3"}, + {file = "tqdm-4.60.0.tar.gz", hash = "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae"}, ] typed-ast = [ - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, - {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, - {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, - {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, - {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, ] unicon = [ {file = "unicon-20.12-cp35-cp35m-macosx_10_10_x86_64.whl", hash = "sha256:13fb35f034268874ddd0ae017493b7dd345a82f46f1659192ba5a4cb7900d62d"}, @@ -3240,15 +3034,15 @@ unicon = [ {file = "unicon.plugins-20.12-py3-none-any.whl", hash = "sha256:663e4b569993cd73b4d3dddbbbefae0b878afaf0461535449b663abef745811b"}, ] urllib3 = [ - {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, - {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, + {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, + {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] wmctrl = [ - {file = "wmctrl-0.3.tar.gz", hash = "sha256:d806f65ac1554366b6e31d29d7be2e8893996c0acbb2824bbf2b1f49cf628a13"}, + {file = "wmctrl-0.4.tar.gz", hash = "sha256:66cbff72b0ca06a22ec3883ac3a4d7c41078bdae4fb7310f52951769b10e14e0"}, ] wrapt = [ {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, @@ -3258,11 +3052,7 @@ xmltodict = [ {file = "xmltodict-0.12.0.tar.gz", hash = "sha256:50d8c638ed7ecb88d90561beedbf720c9b4e851a9fa6c47ebd64e99d166d8a21"}, ] yamllint = [ - {file = "yamllint-1.25.0-py2.py3-none-any.whl", hash = "sha256:c7be4d0d2584a1b561498fa9acb77ad22eb434a109725c7781373ae496d823b3"}, - {file = "yamllint-1.25.0.tar.gz", hash = "sha256:b1549cbe5b47b6ba67bdeea31720f5c51431a4d0c076c1557952d841f7223519"}, -] -yamlordereddictloader = [ - {file = "yamlordereddictloader-0.4.0.tar.gz", hash = "sha256:7f30f0b99ea3f877f7cb340c570921fa9d639b7f69cba18be051e27f8de2080e"}, + {file = "yamllint-1.26.1.tar.gz", hash = "sha256:87d9462b3ed7e9dfa19caa177f7a77cd9888b3dc4044447d6ae0ab233bcd1324"}, ] yarl = [ {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, @@ -3304,6 +3094,6 @@ yarl = [ {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, ] zipp = [ - {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, - {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, + {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, + {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, ] diff --git a/pyproject.toml b/pyproject.toml index 6ccc43a2..71956d53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ packages = [ python = "^3.6" pynetbox = "^5.0" toml = "^0.10" -nornir = "^2.4" +nornir = "^3.0" termcolor = "^1.1" click = "^7.1" pydantic = "^1.6.1" From 761ebac29c66118b6f5ae1b0e095c2e32a26145a Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Sun, 9 May 2021 17:37:29 -0400 Subject: [PATCH 02/13] Refactor inventory settings to move everything to the adapter --- .../adapters/netbox_api/adapter.py | 34 +- .../adapters/netbox_api/config.py | 30 +- .../adapters/netbox_api/inventory.py | 140 +++-- network_importer/adapters/netbox_api/tasks.py | 7 +- .../adapters/network_importer/adapter.py | 4 +- network_importer/cli.py | 10 +- network_importer/config.py | 70 +-- network_importer/drivers/cisco_default.py | 2 +- network_importer/drivers/default.py | 5 +- network_importer/inventory.py | 209 ++------ network_importer/main.py | 30 +- network_importer/processors/get_config.py | 8 +- network_importer/processors/get_neighbors.py | 2 +- network_importer/tasks.py | 66 ++- network_importer/utils.py | 20 - poetry.lock | 506 +++++++++++++----- pyproject.toml | 3 + 17 files changed, 644 insertions(+), 502 deletions(-) diff --git a/network_importer/adapters/netbox_api/adapter.py b/network_importer/adapters/netbox_api/adapter.py index 58e500ae..d0f7ad33 100644 --- a/network_importer/adapters/netbox_api/adapter.py +++ b/network_importer/adapters/netbox_api/adapter.py @@ -34,6 +34,7 @@ NetboxVlanPre29, ) from network_importer.adapters.netbox_api.tasks import query_device_info_from_netbox +from .config import InventorySettings warnings.filterwarnings("ignore", category=DeprecationWarning) @@ -76,24 +77,26 @@ def _is_tag_present(netbox_obj): elif not netbox_obj["tags"]: return False - for tag in config.SETTINGS.netbox.model_flag_tags: - if tag in netbox_obj["tags"]: - LOGGER.debug( - "Tag (%s) found for object %s. Marked for diffsync flag assignment.", tag, netbox_obj, - ) - return True + # FIXME dgarros, need to reenable this one + # for tag in config.SETTINGS.netbox.model_flag_tags: + # if tag in netbox_obj["tags"]: + # LOGGER.debug( + # "Tag (%s) found for object %s. Marked for diffsync flag assignment.", tag, netbox_obj, + # ) + # return True return False @staticmethod def apply_model_flag(diffsync_obj, netbox_obj): """Helper function for DiffSync Flag assignment.""" - model_flag = config.SETTINGS.netbox.model_flag - - if model_flag and NetBoxAPIAdapter._is_tag_present(netbox_obj): - LOGGER.info( - "DiffSync model flag (%s) applied to object %s", model_flag, netbox_obj, - ) - diffsync_obj.model_flags = model_flag + # FIXME need to re-enable this one + # model_flag = config.SETTINGS.netbox.model_flag + + # if model_flag and NetBoxAPIAdapter._is_tag_present(netbox_obj): + # LOGGER.info( + # "DiffSync model flag (%s) applied to object %s", model_flag, netbox_obj, + # ) + # diffsync_obj.model_flags = model_flag return diffsync_obj def _check_netbox_version(self): @@ -115,9 +118,10 @@ def _check_netbox_version(self): def load(self): """Initialize pynetbox and load all data from netbox in the local cache.""" - self.netbox = pynetbox.api(url=config.SETTINGS.netbox.address, token=config.SETTINGS.netbox.token) + inventory_settings = InventorySettings(**config.SETTINGS.inventory.inventory_params) + self.netbox = pynetbox.api(url=inventory_settings.address, token=inventory_settings.token) - if not config.SETTINGS.netbox.verify_ssl: + if not inventory_settings.verify_ssl: session = requests.Session() session.verify = False self.netbox.http_session = session diff --git a/network_importer/adapters/netbox_api/config.py b/network_importer/adapters/netbox_api/config.py index de0193d5..a9f4abd3 100644 --- a/network_importer/adapters/netbox_api/config.py +++ b/network_importer/adapters/netbox_api/config.py @@ -12,10 +12,36 @@ See the License for the specific language governing permissions and limitations under the License. """ -from pydantic import BaseSettings +from typing import Any, Dict, List, Optional, Union +from pydantic import BaseSettings, ValidationError -# pylint: disable=too-few-public-methods,no-self-argument,no-self-use +from diffsync import DiffSyncModelFlags class AdapterSettings(BaseSettings): """Config settings for the netbox_api adapter. Not used currently.""" + + model_flag_tags: List[str] = list() # List of tags that defines what objects to assign the model_flag to. + model_flag: Optional[DiffSyncModelFlags] # The model flag that will be applied to objects based on tag. + + +class InventorySettings(BaseSettings): + address: Optional[str] = "http://localhost" + token: Optional[str] = None + verify_ssl: Union[bool, str] = True + + use_primary_ip: Optional[bool] = True + fqdn: Optional[str] = None + filter: Optional[str] = None + global_delay_factor: Optional[int] = 5 + banner_timeout: Optional[int] = 15 + conn_timeout: Optional[int] = 5 + + class Config: + """Additional parameters to automatically map environment variable to some settings.""" + + fields = { + "address": {"env": "NETBOX_ADDRESS"}, + "token": {"env": "NETBOX_TOKEN"}, + "verify_ssl": {"env": "NETBOX_VERIFY_SSL"}, + } diff --git a/network_importer/adapters/netbox_api/inventory.py b/network_importer/adapters/netbox_api/inventory.py index 3a3fd399..963a0201 100644 --- a/network_importer/adapters/netbox_api/inventory.py +++ b/network_importer/adapters/netbox_api/inventory.py @@ -21,10 +21,12 @@ import requests import pynetbox -from nornir.core.inventory import Defaults, Group, Groups, Host, Hosts, Inventory, ParentGroups +from nornir.core.inventory import Defaults, Group, Groups, Host, Hosts, Inventory, ParentGroups, ConnectionOptions from nornir.core.plugins.inventory import InventoryPluginRegister from network_importer.inventory import NetworkImporterInventory, NetworkImporterHost +from .config import InventorySettings + # ------------------------------------------------------------ # Network Importer Base Dict for device data # status: @@ -38,20 +40,26 @@ # has_config: Indicate if the configuration is present and has been properly imported in Batfish # ------------------------------------------------------------ -class NetboxAPIParams(BaseSettings): - url: Optional[str] = None - token: Optional[str] = None - ssl_verify: Union[bool, str] = True - username: Optional[str] = None - password: Optional[str] = None - enable: Optional[bool] = True - use_primary_ip: Optional[bool] = True - fqdn: Optional[str] = None - supported_platforms: Optional[List[str]] = [] - filter_parameters: Optional[Dict[str, Any]] = None - global_delay_factor: Optional[int] = 5 - banner_timeout: Optional[int] = 15 - conn_timeout: Optional[int] = 5 + +def build_filter_params(filter_params, params): + """Update params dict() with filter args in required format for pynetbox. + + Args: + filter_params (list): split string from cli or config + params (dict): object to hold params + """ + for param_value in filter_params: + if "=" not in param_value: + continue + key, value = param_value.split("=", 1) + existing_value = params.get(key) + if existing_value and isinstance(existing_value, list): + params[key].append(value) + elif existing_value and isinstance(existing_value, str): + params[key] = [existing_value, value] + else: + params[key] = value + class NetboxAPIInventory(NetworkImporterInventory): """Netbox API Inventory Class.""" @@ -59,26 +67,42 @@ class NetboxAPIInventory(NetworkImporterInventory): # pylint: disable=dangerous-default-value, too-many-branches, too-many-statements def __init__( self, - params: NetboxAPIParams = NetboxAPIParams(), + username: Optional[str], + password: Optional[str], + enable: Optional[bool], + supported_platforms: Optional[List[str]], + limit=Optional[str], + params: InventorySettings = InventorySettings(), **kwargs: Any, ) -> None: """Nornir Inventory Plugin for Netbox API.""" - self.params = params - self.filter_parameters = params.filter_parameters or {} + self.username = username + self.password = password + self.enable = enable + self.supported_platforms = supported_platforms + + self.params = InventorySettings(**params) + + # Build Filter based on inventory_params filter and on limit + self.filter_parameters = {} + build_filter_params(self.params.filter.split((",")), self.filter_parameters) + if limit: + if "=" not in limit: + self.filter_parameters["name"] = limit + else: + build_filter_params(limit.split((",")), self.filter_parameters) if "exclude" not in self.filter_parameters.keys(): self.filter_parameters["exclude"] = "config_context" # Instantiate netbox session using pynetbox - self.session = pynetbox.api(url=params.url, token=params.token) - if not params.ssl_verify: + self.session = pynetbox.api(url=self.params.address, token=self.params.token) + if not self.params.verify_ssl: session = requests.Session() session.verify = False self.session.http_session = session - super().__init__(**kwargs) - def load(self): # fetch devices from netbox @@ -95,47 +119,48 @@ def load(self): groups = Groups() defaults = Defaults() - global_group_options = {"netmiko": {"extras": {}}, "napalm": {"extras": {}}} - global_group = Group(name="global") - + global_group = Group( + name="global", connection_options={"netmiko": ConnectionOptions(), "napalm": ConnectionOptions()} + ) + # Pull the login and password from the NI config object if available - if self.params.username: - global_group.username = self.params.username - - if self.params.password: - global_group.password = self.params.password - if self.params.enable: - global_group_options["netmiko"]["extras"] = { - "secret": self.params.password, + if self.username: + global_group.username = self.username + + if self.password: + global_group.password = self.password + if self.enable: + global_group.connection_options["netmiko"].extras = { + "secret": self.password, "global_delay_factor": self.params.global_delay_factor, "banner_timeout": self.params.banner_timeout, "conn_timeout": self.params.conn_timeout, } - global_group_options["napalm"]["extras"] = {"optional_args": {"secret": self.params.password}} - - global_group.connection_options = global_group_options + global_group.connection_options["napalm"].extras = {"optional_args": {"secret": self.password}} for dev in devices: - - host = NetworkImporterHost() + # Netbox allows devices to be unnamed, but the Nornir model does not allow this + # If a device is unnamed we will set the name to the id of the device in netbox + dev_name = dev.name or dev.id + host = NetworkImporterHost(name=dev_name, connection_options=ConnectionOptions()) # Only add virtual chassis master as inventory element if dev.virtual_chassis and dev.virtual_chassis.master: if dev.id != dev.virtual_chassis.master.id: continue - host["data"]["virtual_chassis"] = True + host.data["virtual_chassis"] = True else: - host["data"]["virtual_chassis"] = False + host.data["virtual_chassis"] = False # If supported_platforms is provided # skip all devices that do not match the list of supported platforms # TODO need to see if we can filter when doing the query directly - if self.params.supported_platforms: + if self.supported_platforms: if not dev.platform: continue - if dev.platform.slug not in self.params.supported_platforms: + if dev.platform.slug not in self.supported_platforms: continue # Add value for IP address @@ -148,20 +173,23 @@ def load(self): host.hostname = f"{dev.name}.{self.params.fqdn}" elif not self.params.use_primary_ip: host.hostname = dev.name + else: + host.hostname = dev_name + + host.site_name = dev.site.slug - host["data"]["serial"] = dev.serial - host["data"]["vendor"] = dev.device_type.manufacturer.slug - host["data"]["asset_tag"] = dev.asset_tag - host["data"]["custom_fields"] = dev.custom_fields - host["data"]["site"] = dev.site.slug - host["data"]["site_id"] = dev.site.id - host["data"]["device_id"] = dev.id - host["data"]["role"] = dev.device_role.slug - host["data"]["model"] = dev.device_type.slug + host.data["serial"] = dev.serial + host.data["vendor"] = dev.device_type.manufacturer.slug + host.data["asset_tag"] = dev.asset_tag + host.data["custom_fields"] = dev.custom_fields + host.data["site_id"] = dev.site.id + host.data["device_id"] = dev.id + host.data["role"] = dev.device_role.slug + host.data["model"] = dev.device_type.slug # Attempt to add 'platform' based of value in 'slug' if dev.platform and dev.platform.slug in platforms_mapping: - host.connection_options = {"napalm": {"platform": platforms_mapping[dev.platform.slug]}} + host.connection_options = {"napalm": ConnectionOptions(platform=platforms_mapping[dev.platform.slug])} if dev.platform: host.platform = dev.platform.slug @@ -176,16 +204,14 @@ def load(self): if dev.device_role.slug not in groups.keys(): groups[dev.device_role.slug] = {} - if "hostname" in host and host["hostname"] and "platform" in host and host["platform"]: + if host.hostname and host.platform: host.is_reachable = True # Assign temporary dict to outer dict - # Netbox allows devices to be unnamed, but the Nornir model does not allow this - # If a device is unnamed we will set the name to the id of the device in netbox - hosts[dev.name or dev.id] = host + + hosts[dev_name] = host return Inventory(hosts=hosts, groups=groups, defaults=defaults) InventoryPluginRegister.register("NetboxAPIInventory", NetboxAPIInventory) - diff --git a/network_importer/adapters/netbox_api/tasks.py b/network_importer/adapters/netbox_api/tasks.py index 80eadca2..0562809a 100644 --- a/network_importer/adapters/netbox_api/tasks.py +++ b/network_importer/adapters/netbox_api/tasks.py @@ -20,6 +20,8 @@ import network_importer.config as config # pylint: disable=import-error +from .config import InventorySettings + LOGGER = logging.getLogger("network-importer") @@ -37,9 +39,10 @@ def query_device_info_from_netbox(task: Task) -> Result: Returns: Result: Nornir Result object with the result in a dict format """ - netbox = pynetbox.api(url=config.SETTINGS.netbox.address, token=config.SETTINGS.netbox.token) + inventory_params = InventorySettings(**config.SETTINGS.inventory.inventory_params) + netbox = pynetbox.api(url=inventory_params.address, token=inventory_params.token) - if not config.SETTINGS.netbox.verify_ssl: + if not inventory_params.verify_ssl: session = requests.Session() session.verify = False netbox.http_session = session diff --git a/network_importer/adapters/network_importer/adapter.py b/network_importer/adapters/network_importer/adapter.py index 60c46a08..60acde4e 100644 --- a/network_importer/adapters/network_importer/adapter.py +++ b/network_importer/adapters/network_importer/adapter.py @@ -62,11 +62,11 @@ def load(self): for hostname, host in self.nornir.inventory.hosts.items(): if len(self.bfi.q.nodeProperties(nodes=hostname).answer()) == 0: - self.nornir.inventory.hosts[hostname].data["has_config"] = False + self.nornir.inventory.hosts[hostname].has_config = False LOGGER.warning("Unable to find information for %s in Batfish, SKIPPING", hostname) continue - self.nornir.inventory.hosts[hostname].data["has_config"] = True + self.nornir.inventory.hosts[hostname].has_config = True if host.data["site"] not in sites.keys(): site = self.site(name=host.data["site"]) diff --git a/network_importer/cli.py b/network_importer/cli.py index dbcf9268..42985802 100644 --- a/network_importer/cli.py +++ b/network_importer/cli.py @@ -191,18 +191,24 @@ def inventory(config_file, limit, debug, update_configs): table.add_column("Reason") for hostname, host in ni.nornir.inventory.hosts.items(): - if host.data["is_reachable"]: + if host.is_reachable: is_reachable = "[green]True" reason = None else: is_reachable = "[red]False" - reason = f"[red]{host.data['not_reachable_reason']}" + reason = f"[red]{host.not_reachable_reason}" table.add_row(hostname, host.data["vendor"], is_reachable, reason) console = Console() console.print(table) + if update_configs: + ni.update_configurations() + + if debug: + pdb.set_trace() + if __name__ == "__main__": main() diff --git a/network_importer/config.py b/network_importer/config.py index a6b5ab9c..631bfe79 100644 --- a/network_importer/config.py +++ b/network_importer/config.py @@ -22,7 +22,7 @@ import toml from pydantic import BaseSettings, ValidationError from typing_extensions import Literal -from diffsync import DiffSyncModelFlags + SETTINGS = None @@ -37,7 +37,6 @@ # pylint: disable=too-few-public-methods,global-statement - class BatfishSettings(BaseSettings): """Settings definition for the Batfish section of the configuration.""" @@ -57,59 +56,6 @@ class Config: "api_key": {"env": "BATFISH_API_KEY"}, } - -class NautobotSettings(BaseSettings): - """Settings definition for the Nautobot section of the configuration.""" - - address: str = "http://localhost" - token: Optional[str] - verify_ssl: bool = True - - model_flag_tags: List[str] = list() # List of tags that defines what objects to assign the model_flag to. - model_flag: Optional[DiffSyncModelFlags] # The model flag that will be applied to objects based on tag. - - """Define a list of supported platform, - if defined all devices without platform or with a different platforms will be removed from the inventory""" - supported_platforms: List[str] = list() - - class Config: - """Additional parameters to automatically map environment variable to some settings.""" - - fields = { - "address": {"env": "NAUTOBOT_ADDRESS"}, - "token": {"env": "NAUTOBOT_TOKEN"}, - "verify_ssl": {"env": "NAUTOBOT_VERIFY_SSL"}, - } - - -class NetboxSettings(BaseSettings): - """Settings definition for the Netbox section of the configuration.""" - - address: str = "http://localhost" - token: Optional[str] - verify_ssl: bool = True - - model_flag_tags: List[str] = list() # List of tags that defines what objects to assign the model_flag to. - model_flag: Optional[DiffSyncModelFlags] # The model flag that will be applied to objects based on tag. - - """Define a list of supported platform, - if defined all devices without platform or with a different platforms will be removed from the inventory""" - supported_platforms: List[str] = list() - - # Currently not used in 2.x, need to add them back - # cacert: Optional[str] - # request_ssl_verify: bool = False - - class Config: - """Additional parameters to automatically map environment variable to some settings.""" - - fields = { - "address": {"env": "NETBOX_ADDRESS"}, - "token": {"env": "NETBOX_TOKEN"}, - "verify_ssl": {"env": "NETBOX_VERIFY_SSL"}, - } - - class NetworkSettings(BaseSettings): """Settings definition for the Network section of the configuration.""" @@ -187,16 +133,10 @@ class InventorySettings(BaseSettings): if the use_primary_ip flag is disabled, the inventory will try to use the hostname to the device """ - use_primary_ip: bool = True - fqdn: Optional[str] - - inventory_class: str = "network_importer.inventory.NetboxInventory" - filter: str = "" + inventory_class: str = "NetboxAPIInventory" + inventory_params: Optional[dict] - class Config: - """Additional parameters to automatically map environment variable to some settings.""" - - fields = {"inventory_filter": {"env": "INVENTORY_FILTER"}} + supported_platforms: List[str] = list() class Settings(BaseSettings): @@ -207,8 +147,6 @@ class Settings(BaseSettings): """ main: MainSettings = MainSettings() - nautobot: NautobotSettings = NautobotSettings() - netbox: NetboxSettings = NetboxSettings() batfish: BatfishSettings = BatfishSettings() logs: LogsSettings = LogsSettings() network: NetworkSettings = NetworkSettings() diff --git a/network_importer/drivers/cisco_default.py b/network_importer/drivers/cisco_default.py index 03906db0..ed64c946 100644 --- a/network_importer/drivers/cisco_default.py +++ b/network_importer/drivers/cisco_default.py @@ -14,7 +14,7 @@ """ import logging -from nornir.plugins.tasks.networking import netmiko_send_command +from nornir_netmiko.tasks import netmiko_send_command from nornir.core.task import Result, Task from nornir.core.exceptions import NornirSubTaskError diff --git a/network_importer/drivers/default.py b/network_importer/drivers/default.py index 8aa52510..43e1ca3f 100644 --- a/network_importer/drivers/default.py +++ b/network_importer/drivers/default.py @@ -13,7 +13,10 @@ limitations under the License. """ import logging -from nornir.plugins.tasks.networking import napalm_get, netmiko_send_command + +from nornir_napalm.plugins.tasks import napalm_get +from nornir_netmiko.tasks import netmiko_send_command + from nornir.core.task import Result, Task from nornir.core.exceptions import NornirSubTaskError diff --git a/network_importer/inventory.py b/network_importer/inventory.py index 44727ac3..dcdecdb2 100644 --- a/network_importer/inventory.py +++ b/network_importer/inventory.py @@ -23,188 +23,43 @@ from nornir.core.plugins.inventory import InventoryPluginRegister -# ------------------------------------------------------------ -# Network Importer Base Dict for device data -# status: -# ok: device is reachable -# fail-ip: Primary IP address not reachable -# fail-access: Unable to access the device management. The IP is reachable, but SSH or API is not enabled or -# responding. -# fail-login: Unable to login authenticate with device -# fail-other: Other general processing error (also catches traps/bug) -# is_reachable: Global Flag to indicate if we are able to connect to a device -# has_config: Indicate if the configuration is present and has been properly imported in Batfish -# ------------------------------------------------------------ - -# BASE_DATA = {"is_reachable": None, "status": "ok", "has_config": False} - - class NetworkImporterHost(Host): """Network Importer Host Class.""" + + site_name: Optional[str] + """Name of the site this device belong to.""" + is_reacheable: Optional[bool] + """Global Flag to indicate if we are able to connect to a device""" + status: Optional[str] = "ok" + """ Valid Statuses + ok: device is reachable + fail-ip: Primary IP address not reachable + fail-access: Unable to access the device management. + The IP is reachable, but SSH or API is not enabled or responding. + fail-login: Unable to login authenticate with device + fail-other: Other general processing error (also catches traps/bug) + """ + has_config: Optional[bool] = False + """ Indicate if the configuration is present and has been properly imported in Batfish.""" + not_reachable_reason: Optional[str] + class NetworkImporterInventory: - def __init__(self, params: Optional[Dict] = None): + def __init__( + self, + username: Optional[str], + password: Optional[str], + enable: Optional[bool], + supported_platforms: Optional[List[str]], + limit: Optional[str], + params: Optional[Dict] = None, + ): pass -# class NetboxInventory(Inventory): -# """Netbox Inventory Class.""" - -# # pylint: disable=dangerous-default-value, too-many-branches, too-many-statements -# def __init__( -# self, -# nb_url: Optional[str] = None, -# nb_token: Optional[str] = None, -# ssl_verify: Union[bool, str] = True, -# username: Optional[str] = None, -# password: Optional[str] = None, -# enable: Optional[bool] = True, -# use_primary_ip: Optional[bool] = True, -# fqdn: Optional[str] = None, -# supported_platforms: Optional[List[str]] = [], -# filter_parameters: Optional[Dict[str, Any]] = None, -# global_delay_factor: Optional[int] = 5, -# banner_timeout: Optional[int] = 15, -# conn_timeout: Optional[int] = 5, -# **kwargs: Any, -# ) -> None: -# """Norning Inventory Plugin fir Netbox. - -# Hard copy from https://github.com/nornir-automation/nornir/blob/develop/nornir/plugins/inventory/netbox.py, -# Need to see how to contribute back some of these modifications - -# Args: -# filter_parameters: Key -# nb_url: Optional[str]: (Default value = None) -# nb_token: Optional[str]: (Default value = None) -# ssl_verify: (Default value = True) -# filter_parameters: Optional[Dict[str: Any]]: (Default value = None) -# username: Optional[str] -# password: Optional[str] -# enable: Optional[bool] = True, -# use_primary_ip: Optional[bool] = True, -# fqdn: Optional[str] = None, -# supported_platforms: Optional[List[str]] -# global_delay_factor: Optional[int] Global Delay factor for netmiko -# banner_timeout: Optional[int] Banner Timeout for netmiko/paramiko -# conn_timeout: Optional[int] Connection timeout for netmiko/paramiko -# **kwargs: Any: -# """ -# filter_parameters = filter_parameters or {} - -# if "exclude" not in filter_parameters.keys(): -# filter_parameters["exclude"] = "config_context" - -# # Instantiate netbox session using pynetbox -# nb_session = pynetbox.api(url=nb_url, token=nb_token) -# if not ssl_verify: -# session = requests.Session() -# session.verify = False -# nb_session.http_session = session - -# # fetch devices from netbox -# if filter_parameters: -# nb_devices: List[pynetbox.modules.dcim.Devices] = nb_session.dcim.devices.filter(**filter_parameters) -# else: -# nb_devices: List[pynetbox.modules.dcim.Devices] = nb_session.dcim.devices.all() - -# # fetch all platforms from Netbox and build mapping: platform: napalm_driver -# platforms = nb_session.dcim.platforms.all() -# platforms_mapping = {platform.slug: platform.napalm_driver for platform in platforms if platform.napalm_driver} - -# hosts = {} -# groups = {"global": {"connection_options": {"netmiko": {"extras": {}}, "napalm": {"extras": {}}}}} - -# # Pull the login and password from the NI config object if available -# if username: -# groups["global"]["username"] = username - -# if password: -# groups["global"]["password"] = password -# if enable: -# groups["global"]["connection_options"]["netmiko"]["extras"] = { -# "secret": password, -# "global_delay_factor": global_delay_factor, -# "banner_timeout": banner_timeout, -# "conn_timeout": conn_timeout, -# } -# groups["global"]["connection_options"]["napalm"]["extras"] = {"optional_args": {"secret": password}} - -# for dev in nb_devices: - -# host: HostsDict = {"data": copy.deepcopy(BASE_DATA)} - -# # Only add virtual chassis master as inventory element -# if dev.virtual_chassis and dev.virtual_chassis.master: -# if dev.id != dev.virtual_chassis.master.id: -# continue -# host["data"]["virtual_chassis"] = True - -# else: -# host["data"]["virtual_chassis"] = False - -# # If supported_platforms is provided -# # skip all devices that do not match the list of supported platforms -# # TODO need to see if we can filter when doing the query directly -# if supported_platforms: -# if not dev.platform: -# continue - -# if dev.platform.slug not in supported_platforms: -# continue - -# # Add value for IP address -# if use_primary_ip and dev.primary_ip: -# host["hostname"] = dev.primary_ip.address.split("/")[0] -# elif use_primary_ip and not dev.primary_ip: -# host["data"]["is_reachable"] = False -# host["data"]["not_reachable_reason"] = "primary ip not defined in Netbox" -# elif not use_primary_ip and fqdn: -# host["hostname"] = f"{dev.name}.{fqdn}" -# elif not use_primary_ip: -# host["hostname"] = dev.name - -# host["data"]["serial"] = dev.serial -# host["data"]["vendor"] = dev.device_type.manufacturer.slug -# host["data"]["asset_tag"] = dev.asset_tag -# host["data"]["custom_fields"] = dev.custom_fields -# host["data"]["site"] = dev.site.slug -# host["data"]["site_id"] = dev.site.id -# host["data"]["device_id"] = dev.id -# host["data"]["role"] = dev.device_role.slug -# host["data"]["model"] = dev.device_type.slug - -# # Attempt to add 'platform' based of value in 'slug' -# if dev.platform and dev.platform.slug in platforms_mapping: -# host["connection_options"] = {"napalm": {"platform": platforms_mapping[dev.platform.slug]}} - -# if dev.platform: -# host["platform"] = dev.platform.slug -# else: -# host["platform"] = None - -# host["groups"] = ["global", dev.site.slug, dev.device_role.slug] - -# if dev.site.slug not in groups.keys(): -# groups[dev.site.slug] = {} - -# if dev.device_role.slug not in groups.keys(): -# groups[dev.device_role.slug] = {} - -# if "hostname" in host and host["hostname"] and "platform" in host and host["platform"]: -# host["data"]["is_reachable"] = True - -# # Assign temporary dict to outer dict -# # Netbox allows devices to be unnamed, but the Nornir model does not allow this -# # If a device is unnamed we will set the name to the id of the device in netbox -# hosts[dev.name or dev.id] = host - -# # Pass the data back to the parent class -# super().__init__(hosts=hosts, groups=groups, defaults={}, **kwargs) - # ----------------------------------------------------------------- # Inventory Filter functions @@ -220,7 +75,7 @@ def valid_devs(host): Returns: bool: True if the device has a config, False otherwise. """ - if host.data["has_config"]: + if host.has_config: return True return False @@ -237,7 +92,7 @@ def non_valid_devs(host): Returns: bool: True if the device do not have a config, False otherwise. """ - if host.data["has_config"]: + if host.has_config: return False return True @@ -254,7 +109,7 @@ def reachable_devs(host): Returns: bool: True if the device is reachable, False otherwise. """ - if host.data["is_reachable"]: + if host.is_reachable: return True return False @@ -271,7 +126,7 @@ def non_reachable_devs(host): Returns: bool: True if the device is not reachable, False otherwise. """ - if host.data["is_reachable"]: + if host.is_reachable: return False return True @@ -288,7 +143,7 @@ def valid_and_reachable_devs(host): Returns: bool: True if the device is reachable and has a config, False otherwise. """ - if host.data["is_reachable"] and host.data["has_config"]: + if host.is_reachable and host.has_config: return True return False diff --git a/network_importer/main.py b/network_importer/main.py index d52389c5..771b6854 100644 --- a/network_importer/main.py +++ b/network_importer/main.py @@ -20,22 +20,27 @@ import network_importer.config as config from network_importer.exceptions import AdapterLoadFatalError -from network_importer.utils import patch_http_connection_pool, build_filter_params +from network_importer.utils import patch_http_connection_pool from network_importer.processors.get_config import GetConfig from network_importer.drivers import dispatcher from network_importer.diff import NetworkImporterDiff from network_importer.tasks import check_if_reachable, warning_not_reachable from network_importer.performance import timeit from network_importer.inventory import reachable_devs +from network_importer.adapters.netbox_api.inventory import NetboxAPIInventory warnings.filterwarnings("ignore", category=DeprecationWarning) with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=DeprecationWarning) from nornir import InitNornir + from nornir.core.plugins.inventory import InventoryPluginRegister __author__ = "Damien Garros " +InventoryPluginRegister.register("NetboxAPIInventory", NetboxAPIInventory) + + LOGGER = logging.getLogger("network-importer") @@ -52,35 +57,20 @@ def __init__(self, check_mode=False, nornir=None): @timeit def build_inventory(self, limit=None): """Build the inventory for the Network Importer in Nornir format.""" - # Filters can be defined at the configuration level or in CLI or both - params = {} - build_filter_params(config.SETTINGS.inventory.filter.split((",")), params) - if limit: - if "=" not in limit: - params["name"] = limit - else: - build_filter_params(limit.split((",")), params) # TODO Cleanup config file and allow user defined inventory self.nornir = InitNornir( - core={"num_workers": config.SETTINGS.main.nbr_workers}, + runner={"plugin": "threaded", "options": {"num_workers": config.SETTINGS.main.nbr_workers}}, logging={"enabled": False}, inventory={ "plugin": config.SETTINGS.inventory.inventory_class, "options": { - "nb_url": config.SETTINGS.netbox.address, - "nb_token": config.SETTINGS.netbox.token, - "filter_parameters": params, - "ssl_verify": config.SETTINGS.netbox.verify_ssl, "username": config.SETTINGS.network.login, "password": config.SETTINGS.network.password, "enable": config.SETTINGS.network.enable, - "use_primary_ip": config.SETTINGS.inventory.use_primary_ip, - "fqdn": config.SETTINGS.inventory.fqdn, - "supported_platforms": config.SETTINGS.netbox.supported_platforms, - "global_delay_factor": config.SETTINGS.network.global_delay_factor, - "banner_timeout": config.SETTINGS.network.banner_timeout, - "conn_timeout": config.SETTINGS.network.conn_timeout, + "supported_platforms": config.SETTINGS.inventory.supported_platforms, + "limit": limit, + "params": config.SETTINGS.inventory.inventory_params, }, }, ) diff --git a/network_importer/processors/get_config.py b/network_importer/processors/get_config.py index b1c1d105..e99d4cb1 100644 --- a/network_importer/processors/get_config.py +++ b/network_importer/processors/get_config.py @@ -111,21 +111,21 @@ def subtask_instance_completed(self, task: Task, host: Host, result: MultiResult LOGGER.warning("%s | %s", task.host.name, result[0].exception) else: LOGGER.warning("%s | Something went wrong while trying to update the configuration ", task.host.name) - host.data["status"] = "fail-other" + host.status = "fail-other" return conf = result[0].result.get("config", None) if not conf: LOGGER.warning("%s | No configuration return ", task.host.name) - host.data["status"] = "fail-other" + host.status = "fail-other" return # Count the number of lines in the config file, if less than 10 report an error # mostlikely something went wrong while pulling the config if conf.count("\n") < 10: LOGGER.warning("%s | Less than 10 configuration lines returned", task.host.name) - host.data["status"] = "fail-other" + host.status = "fail-other" return if host.name in self.existing_config_hostnames: @@ -135,7 +135,7 @@ def subtask_instance_completed(self, task: Task, host: Host, result: MultiResult with open(self.config_filename[host.name], "w") as config_: config_.write(conf) - host.data["has_config"] = True + host.has_config = True self.current_md5[host.name] = hashlib.md5(conf.encode("utf-8")).hexdigest() # changed = False diff --git a/network_importer/processors/get_neighbors.py b/network_importer/processors/get_neighbors.py index 73f8fe57..6d1d7645 100644 --- a/network_importer/processors/get_neighbors.py +++ b/network_importer/processors/get_neighbors.py @@ -103,7 +103,7 @@ def subtask_instance_completed(self, task: Task, host: Host, result: MultiResult if result[0].failed: LOGGER.warning("%s | Something went wrong while trying to pull the neighbor information", host.name) - host.data["status"] = "fail-other" + host.status = "fail-other" return if not isinstance(result[0].result, dict) or "neighbors" not in result[0].result: diff --git a/network_importer/tasks.py b/network_importer/tasks.py index 4ed0bb1d..0950c62c 100644 --- a/network_importer/tasks.py +++ b/network_importer/tasks.py @@ -18,7 +18,6 @@ import yaml from nornir.core.task import Result, Task -from nornir.plugins.tasks.networking import tcp_ping import network_importer.config as config @@ -72,10 +71,63 @@ def device_save_hostvars(task: Task) -> Result: # # hostvars_str = template.render(dev_facts) +import socket +from typing import Optional, List + +from nornir.core.task import Result, Task + + +def tcp_ping(task: Task, ports: List[int], timeout: int = 2, host: Optional[str] = None) -> Result: + """ + Tests connection to a tcp port and tries to establish a three way + handshake. To be used for network discovery or testing. + Arguments: + ports (list of int): tcp ports to ping + timeout (int, optional): defaults to 2 + host (string, optional): defaults to ``hostname`` + Returns: + Result object with the following attributes set: + * result (``dict``): Contains port numbers as keys with True/False as values + + Code copied from https://github.com/nornir-automation/nornir/blob/v2.5.0/nornir/plugins/tasks/networking/tcp_ping.py + Need to open a PR to https://github.com/nornir-automation/nornir_utils + """ + + if isinstance(ports, int): + ports = [ports] + + if isinstance(ports, list): + if not all(isinstance(port, int) for port in ports): + raise ValueError("Invalid value for 'ports'") + + else: + raise ValueError("Invalid value for 'ports'") + + host = host or task.host.hostname + + result = {} + for port in ports: + s = socket.socket() + s.settimeout(timeout) + try: + status = s.connect_ex((host, port)) + if status == 0: + connection = True + else: + connection = False + except (socket.gaierror, socket.timeout, socket.error): + connection = False + finally: + s.close() + result[port] = connection + + return Result(host=task.host, result=result) + + def check_if_reachable(task: Task) -> Result: """Check if a device is reachable by doing a TCP ping it on port 22. - Will change the status of the variable `is_reachable` in host.data based on the results + Will change the status of the variable `host.is_reachable` based on the results Args: task: Nornir Task @@ -96,17 +148,17 @@ def check_if_reachable(task: Task) -> Result: if not is_reachable: LOGGER.debug("%s | device is not reachable on port %s", task.host.name, port_to_check) - task.host.data["is_reachable"] = False - task.host.data["not_reachable_reason"] = f"device not reachable on port {port_to_check}" - task.host.data["status"] = "fail-ip" + task.host.is_reachable = False + task.host.not_reachable_reason = f"device not reachable on port {port_to_check}" + task.host.status = "fail-ip" return Result(host=task.host, result=is_reachable) def warning_not_reachable(task: Task) -> Result: """Generate warning logs for each unreachable device.""" - if task.host.data.get("is_reachable"): + if task.host.is_reachable: return - reason = task.host.data.get("not_reachable_reason", "reason not defined") + reason = task.host.not_reachable_reason or "reason not defined" LOGGER.warning("%s device is not reachable, %s", task.host.name, reason) diff --git a/network_importer/utils.py b/network_importer/utils.py index db543d08..95be0cfc 100644 --- a/network_importer/utils.py +++ b/network_importer/utils.py @@ -205,23 +205,3 @@ def expand_vlans_list(vlans: str) -> list: LOGGER.debug("expand_vlans_list() Unable to convert %s as integer .. skipping (%s)", vlan_, exc) return sorted(clean_vlans_list) - - -def build_filter_params(filter_params, params): - """Update params dict() with filter args in required format for pynetbox. - - Args: - filter_params (list): split string from cli or config - params (dict): object to hold params - """ - for param_value in filter_params: - if "=" not in param_value: - continue - key, value = param_value.split("=", 1) - existing_value = params.get(key) - if existing_value and isinstance(existing_value, list): - params[key].append(value) - elif existing_value and isinstance(existing_value, str): - params[key] = [existing_value, value] - else: - params[key] = value diff --git a/poetry.lock b/poetry.lock index bf9f7de2..b147bbe3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,13 +15,13 @@ optional = false python-versions = ">=3.6" [package.dependencies] -typing-extensions = ">=3.6.5" -yarl = ">=1.0,<2.0" async-timeout = ">=3.0,<4.0" -idna-ssl = {version = ">=1.0", markers = "python_version < \"3.7\""} -chardet = ">=2.0,<4.0" attrs = ">=17.3.0" +chardet = ">=2.0,<4.0" +idna-ssl = {version = ">=1.0", markers = "python_version < \"3.7\""} multidict = ">=4.5,<7.0" +typing-extensions = ">=3.6.5" +yarl = ">=1.0,<2.0" [package.extras] speedups = ["aiodns", "brotlipy", "cchardet"] @@ -35,10 +35,10 @@ optional = false python-versions = "*" [package.dependencies] +aiohttp = ">=2.3.10" +jinja2 = ">=2.11.2,<2.12.0" markupsafe = ">=1.1.1,<1.2.0" pyYAML = ">=5.1" -jinja2 = ">=2.11.2,<2.12.0" -aiohttp = ">=2.3.10" [package.extras] performance = ["ujson"] @@ -97,10 +97,10 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] [[package]] name = "bandit" @@ -111,11 +111,11 @@ optional = false python-versions = ">=3.5" [package.dependencies] -stevedore = ">=1.20.0" -PyYAML = ">=5.3.1" -six = ">=1.10.0" colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} GitPython = ">=1.0.1" +PyYAML = ">=5.3.1" +six = ">=1.10.0" +stevedore = ">=1.20.0" [[package]] name = "bcrypt" @@ -126,12 +126,12 @@ optional = false python-versions = ">=3.6" [package.dependencies] -six = ">=1.4.1" cffi = ">=1.1" +six = ">=1.4.1" [package.extras] -typecheck = ["mypy"] tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] [[package]] name = "bidict" @@ -142,11 +142,11 @@ optional = false python-versions = ">=3.6" [package.extras] -test = ["hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] -docs = ["Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] coverage = ["coverage (<6)", "pytest-cov (<3)"] dev = ["setuptools-scm", "hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)", "coverage (<6)", "pytest-cov (<3)", "pre-commit (<3)", "tox (<4)"] +docs = ["Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] precommit = ["pre-commit (<3)"] +test = ["hypothesis (<6)", "py (<2)", "pytest (<7)", "pytest-benchmark (>=3.2.0,<4)", "sortedcollections (<2)", "sortedcontainers (<3)", "Sphinx (<4)", "sphinx-autodoc-typehints (<2)"] [[package]] name = "black" @@ -157,13 +157,13 @@ optional = false python-versions = ">=3.6" [package.dependencies] -regex = "*" -typed-ast = ">=1.4.0" -toml = ">=0.9.4" +appdirs = "*" attrs = ">=18.1.0" -pathspec = ">=0.6,<1" click = ">=6.5" -appdirs = "*" +pathspec = ">=0.6,<1" +regex = "*" +toml = ">=0.9.4" +typed-ast = ">=1.4.0" [package.extras] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] @@ -195,6 +195,19 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "ciscoconfparse" +version = "1.5.30" +description = "Parse, Audit, Query, Build, and Modify Cisco IOS-style configurations" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +colorama = "*" +dnspython = "*" +passlib = "*" + [[package]] name = "click" version = "7.1.2" @@ -245,11 +258,11 @@ python-versions = ">=3.6" cffi = ">=1.12" [package.extras] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] @@ -298,9 +311,9 @@ python-versions = ">=3.6,<4.0" [package.dependencies] colorama = ">=0.4.3,<0.5.0" -structlog = ">=20.1.0,<21.0.0" -pydantic = ">=1.7.2,<2.0.0" dataclasses = {version = ">=0.7,<0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} +pydantic = ">=1.7.2,<2.0.0" +structlog = ">=20.1.0,<21.0.0" [[package]] name = "dill" @@ -321,6 +334,21 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "dnspython" +version = "2.1.0" +description = "DNS toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dnssec = ["cryptography (>=2.6)"] +doh = ["requests", "requests-toolbelt"] +idna = ["idna (>=2.1)"] +curio = ["curio (>=1.2)", "sniffio (>=1.1)"] +trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"] + [[package]] name = "fancycompleter" version = "0.9.1" @@ -330,8 +358,8 @@ optional = false python-versions = "*" [package.dependencies] -pyrepl = ">=0.8.2" pyreadline = {version = "*", markers = "platform_system == \"Windows\""} +pyrepl = ">=0.8.2" [[package]] name = "flake8" @@ -364,23 +392,23 @@ optional = false python-versions = ">=3.5" [package.dependencies] -tqdm = "*" -"genie.libs.conf" = ">=20.12.0,<20.13.0" -"genie.libs.ops" = ">=20.12.0,<20.13.0" dill = "*" -netaddr = "*" -jsonpickle = "*" -"genie.libs.filetransferutils" = ">=20.12.0,<20.13.0" "genie.libs.clean" = ">=20.12.0,<20.13.0" +"genie.libs.conf" = ">=20.12.0,<20.13.0" +"genie.libs.filetransferutils" = ">=20.12.0,<20.13.0" +"genie.libs.health" = ">=20.12.0,<20.13.0" +"genie.libs.ops" = ">=20.12.0,<20.13.0" "genie.libs.parser" = ">=20.12.0,<20.13.0" -PrettyTable = "*" "genie.libs.sdk" = ">=20.12.0,<20.13.0" -"genie.libs.health" = ">=20.12.0,<20.13.0" +jsonpickle = "*" +netaddr = "*" +PrettyTable = "*" +tqdm = "*" [package.extras] +dev = ["coverage", "restview", "sphinx", "sphinx-rtd-theme"] full = ["genie.libs.conf", "genie.libs.clean", "genie.libs.health", "genie.libs.filetransferutils", "genie.libs.ops", "genie.libs.parser", "genie.libs.sdk", "pyats.robot (>=20.12.0,<20.13.0)", "genie.libs.robot (>=20.12.0,<20.13.0)", "genie.telemetry (>=20.12.0,<20.13.0)", "genie.trafficgen (>=20.12.0,<20.13.0)"] robot = ["pyats.robot (>=20.12.0,<20.13.0)", "genie.libs.robot (>=20.12.0,<20.13.0)"] -dev = ["coverage", "restview", "sphinx", "sphinx-rtd-theme"] [[package]] name = "genie.libs.clean" @@ -417,9 +445,9 @@ optional = false python-versions = "*" [package.dependencies] -unicon = "*" pyftpdlib = "*" tftpy = "*" +unicon = "*" [package.extras] dev = ["coverage", "restview", "sphinx", "sphinx-rtd-theme"] @@ -497,8 +525,8 @@ optional = false python-versions = ">=3.5" [package.dependencies] -typing-extensions = ">=3.7.4.0" gitdb = ">=4.0.1,<5" +typing-extensions = ">=3.7.4.0" [[package]] name = "idna" @@ -575,9 +603,9 @@ optional = false python-versions = ">=3.6,<4.0" [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] [[package]] name = "jinja2" @@ -606,8 +634,8 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -"testing.libs" = ["demjson", "simplejson", "ujson", "yajl"] testing = ["coverage (<5)", "pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov", "ecdsa", "feedparser", "numpy", "pandas", "pymongo", "sklearn", "sqlalchemy", "enum34", "jsonlib"] +"testing.libs" = ["demjson", "simplejson", "ujson", "yajl"] [[package]] name = "junit-xml" @@ -620,6 +648,28 @@ python-versions = "*" [package.dependencies] six = "*" +[[package]] +name = "junos-eznc" +version = "2.6.0" +description = "Junos 'EZ' automation for non-programmers" +category = "main" +optional = false +python-versions = ">=3.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +jinja2 = ">=2.7.1" +lxml = ">=3.2.4" +ncclient = "0.6.9" +netaddr = "*" +paramiko = ">=1.15.2" +pyparsing = "*" +pyserial = "*" +PyYAML = ">=5.1" +scp = ">=0.7.0" +six = "*" +transitions = "*" +yamlordereddictloader = "*" + [[package]] name = "lazy-object-proxy" version = "1.6.0" @@ -628,6 +678,20 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +[[package]] +name = "lxml" +version = "4.6.3" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["beautifulsoup4"] +source = ["Cython (>=0.29.7)"] + [[package]] name = "markupsafe" version = "1.1.1" @@ -668,6 +732,43 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "napalm" +version = "3.2.0" +description = "Network Automation and Programmability Abstraction Layer with Multivendor support" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +cffi = ">=1.11.3" +ciscoconfparse = "*" +future = "*" +jinja2 = "*" +junos-eznc = ">=2.2.1" +lxml = ">=4.3.0" +netaddr = "*" +netmiko = ">=3.1.0" +paramiko = ">=2.6.0" +pyeapi = ">=0.8.2" +pyYAML = "*" +requests = ">=2.7.0" +scp = "*" +textfsm = "*" + +[[package]] +name = "ncclient" +version = "0.6.9" +description = "Python library for NETCONF clients" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +lxml = ">=3.3.0" +paramiko = ">=1.15.0" +six = "*" + [[package]] name = "netaddr" version = "0.8.0" @@ -688,15 +789,15 @@ optional = false python-versions = "*" [package.dependencies] -six = "<2.0.0" -passlib = "<2.0.0" -ipaddress = "<2.0.0" bidict = "<1.0.0" configargparse = "<1.0.0" +ipaddress = "<2.0.0" +passlib = "<2.0.0" +six = "<2.0.0" [package.extras] -test = ["pytest (>=4.6.0,<5.0.0)", "pytest-cov (<3.0.0)", "requests-mock (<2.0.0)", "testfixtures (<7.0.0)", "zipp (<2.2)"] dev = ["flake8 (<4.0.0)", "flake8-docstrings (<2.0.0)", "pydocstyle (<4.0.0)"] +test = ["pytest (>=4.6.0,<5.0.0)", "pytest-cov (<3.0.0)", "requests-mock (<2.0.0)", "testfixtures (<7.0.0)", "zipp (<2.2)"] [[package]] name = "netmiko" @@ -708,11 +809,11 @@ python-versions = "*" [package.dependencies] importlib-resources = {version = "*", markers = "python_version < \"3.7\""} -pyserial = "*" -tenacity = "*" ntc-templates = "*" -scp = ">=0.13.2" paramiko = ">=2.6.0" +pyserial = "*" +scp = ">=0.13.2" +tenacity = "*" [package.extras] test = ["pyyaml (>=5.1.2)", "pytest (>=5.1.2)"] @@ -726,14 +827,49 @@ optional = false python-versions = ">=3.6,<4.0" [package.dependencies] -typing_extensions = ">=3.7,<4.0" +dataclasses = {version = ">=0.7,<0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} mypy_extensions = ">=0.4.1,<0.5.0" "ruamel.yaml" = ">=0.16,<0.17" -dataclasses = {version = ">=0.7,<0.8", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} +typing_extensions = ">=3.7,<4.0" [package.extras] docs = ["jupyter (>=1,<2)", "nbsphinx (>=0.5,<0.6)", "pygments (>=2,<3)", "sphinx (>=1,<2)", "sphinx-issues (>=1.2,<2.0)", "sphinx_rtd_theme (>=0.4,<0.5)", "sphinxcontrib-napoleon (>=0.7,<0.8)"] +[[package]] +name = "nornir-napalm" +version = "0.1.2" +description = "NAPALM's plugins for nornir" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +napalm = ">=3,<4" +nornir = ">=3,<4" + +[[package]] +name = "nornir-netmiko" +version = "0.1.1" +description = "Netmiko's plugins for nornir" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +netmiko = ">=3.1.0,<4.0.0" + +[[package]] +name = "nornir-utils" +version = "0.1.2" +description = "Collection of plugins and functions for nornir that don't require external dependencies" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +colorama = ">=0.4.3,<0.5.0" +nornir = ">=3,<4" + [[package]] name = "ntc-templates" version = "1.7.0" @@ -784,9 +920,9 @@ optional = false python-versions = ">=3.5.3" [package.dependencies] +numpy = ">=1.13.3" python-dateutil = ">=2.6.1" pytz = ">=2017.2" -numpy = ">=1.13.3" [package.extras] test = ["pytest (>=4.0.2)", "pytest-xdist", "hypothesis (>=3.58)"] @@ -800,13 +936,13 @@ optional = false python-versions = "*" [package.dependencies] -pynacl = ">=1.0.1" bcrypt = ">=3.1.3" cryptography = ">=2.5" +pynacl = ">=1.0.1" [package.extras] -ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"] all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] +ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"] gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] invoke = ["invoke (>=1.3)"] @@ -821,8 +957,8 @@ python-versions = "*" [package.extras] argon2 = ["argon2-cffi (>=18.2.0)"] bcrypt = ["bcrypt (>=3.1.0)"] -totp = ["cryptography"] build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"] +totp = ["cryptography"] [[package]] name = "pathspec" @@ -854,8 +990,8 @@ pygments = "*" wmctrl = "*" [package.extras] -testing = ["funcsigs", "pytest"] funcsigs = ["funcsigs"] +testing = ["funcsigs", "pytest"] [[package]] name = "pluggy" @@ -930,24 +1066,24 @@ optional = false python-versions = ">=3.5" [package.dependencies] -"pyats.log" = ">=20.12.0,<20.13.0" -"pyats.topology" = ">=20.12.0,<20.13.0" +"pyats.aereport" = ">=20.12.0,<20.13.0" +"pyats.aetest" = ">=20.12.0,<20.13.0" +"pyats.async" = ">=20.12.0,<20.13.0" +"pyats.connections" = ">=20.12.0,<20.13.0" +"pyats.datastructures" = ">=20.12.0,<20.13.0" "pyats.easypy" = ">=20.12.0,<20.13.0" "pyats.kleenex" = ">=20.12.0,<20.13.0" -"pyats.connections" = ">=20.12.0,<20.13.0" +"pyats.log" = ">=20.12.0,<20.13.0" "pyats.reporter" = ">=20.12.0,<20.13.0" -"pyats.aetest" = ">=20.12.0,<20.13.0" +"pyats.results" = ">=20.12.0,<20.13.0" "pyats.tcl" = ">=20.12.0,<20.13.0" -"pyats.async" = ">=20.12.0,<20.13.0" +"pyats.topology" = ">=20.12.0,<20.13.0" "pyats.utils" = ">=20.12.0,<20.13.0" -"pyats.aereport" = ">=20.12.0,<20.13.0" -"pyats.results" = ">=20.12.0,<20.13.0" -"pyats.datastructures" = ">=20.12.0,<20.13.0" [package.extras] full = ["cookiecutter", "genie (>=20.12.0,<20.13.0)", "pyats.robot (>=20.12.0,<20.13.0)", "genie.libs.robot (>=20.12.0,<20.13.0)", "genie.telemetry (>=20.12.0,<20.13.0)", "genie.trafficgen (>=20.12.0,<20.13.0)", "pyats.contrib (>=20.12.0,<20.13.0)"] -robot = ["pyats.robot (>=20.12.0,<20.13.0)", "genie.libs.robot (>=20.12.0,<20.13.0)"] library = ["genie (>=20.12.0,<20.13.0)"] +robot = ["pyats.robot (>=20.12.0,<20.13.0)", "genie.libs.robot (>=20.12.0,<20.13.0)"] template = ["cookiecutter"] [[package]] @@ -959,11 +1095,11 @@ optional = false python-versions = ">=3.5" [package.dependencies] -"pyats.log" = ">=20.12.0,<20.13.0" -psutil = "*" -"pyats.results" = ">=20.12.0,<20.13.0" jinja2 = "*" junit-xml = "*" +psutil = "*" +"pyats.log" = ">=20.12.0,<20.13.0" +"pyats.results" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -977,13 +1113,13 @@ optional = false python-versions = ">=3.5" [package.dependencies] -"pyats.log" = ">=20.12.0,<20.13.0" -pyyaml = "*" jinja2 = "2.11.2" -"pyats.utils" = ">=20.12.0,<20.13.0" "pyats.aereport" = ">=20.12.0,<20.13.0" -"pyats.results" = ">=20.12.0,<20.13.0" "pyats.datastructures" = ">=20.12.0,<20.13.0" +"pyats.log" = ">=20.12.0,<20.13.0" +"pyats.results" = ">=20.12.0,<20.13.0" +"pyats.utils" = ">=20.12.0,<20.13.0" +pyyaml = "*" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1011,9 +1147,9 @@ optional = false python-versions = ">=3.5" [package.dependencies] -unicon = ">=20.12.0,<20.13.0" "pyats.async" = ">=20.12.0,<20.13.0" "pyats.datastructures" = ">=20.12.0,<20.13.0" +unicon = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1038,16 +1174,16 @@ optional = false python-versions = ">=3.5" [package.dependencies] -"pyats.log" = ">=20.12.0,<20.13.0" -"pyats.topology" = ">=20.12.0,<20.13.0" -"pyats.kleenex" = ">=20.12.0,<20.13.0" -psutil = "*" -jinja2 = "*" distro = "*" -"pyats.utils" = ">=20.12.0,<20.13.0" +jinja2 = "*" +psutil = "*" "pyats.aereport" = ">=20.12.0,<20.13.0" -"pyats.results" = ">=20.12.0,<20.13.0" "pyats.datastructures" = ">=20.12.0,<20.13.0" +"pyats.kleenex" = ">=20.12.0,<20.13.0" +"pyats.log" = ">=20.12.0,<20.13.0" +"pyats.results" = ">=20.12.0,<20.13.0" +"pyats.topology" = ">=20.12.0,<20.13.0" +"pyats.utils" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1061,14 +1197,14 @@ optional = false python-versions = ">=3.5" [package.dependencies] +distro = "*" +"pyats.aetest" = ">=20.12.0,<20.13.0" +"pyats.async" = ">=20.12.0,<20.13.0" +"pyats.datastructures" = ">=20.12.0,<20.13.0" "pyats.log" = ">=20.12.0,<20.13.0" "pyats.topology" = ">=20.12.0,<20.13.0" -"pyats.async" = ">=20.12.0,<20.13.0" -distro = "*" "pyats.utils" = ">=20.12.0,<20.13.0" requests = "*" -"pyats.aetest" = ">=20.12.0,<20.13.0" -"pyats.datastructures" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1082,16 +1218,16 @@ optional = false python-versions = ">=3.5" [package.dependencies] -pyyaml = "*" -aiohttp-swagger = "1.0.15" +aiofiles = "0.6.0" aiohttp = "3.7.2" +aiohttp-swagger = "1.0.15" async-lru = "1.0.2" -python-engineio = "3.13.2" -Jinja2 = "2.11.2" -aiofiles = "0.6.0" chardet = "3.0.4" -python-socketio = "4.6.0" +Jinja2 = "2.11.2" "pyats.datastructures" = ">=20.12.0,<20.13.0" +python-engineio = "3.13.2" +python-socketio = "4.6.0" +pyyaml = "*" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1105,11 +1241,11 @@ optional = false python-versions = ">=3.5" [package.dependencies] -"pyats.log" = ">=20.12.0,<20.13.0" -pyyaml = "*" "pyats.aereport" = ">=20.12.0,<20.13.0" +"pyats.log" = ">=20.12.0,<20.13.0" "pyats.results" = ">=20.12.0,<20.13.0" "pyats.utils" = ">=20.12.0,<20.13.0" +pyyaml = "*" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1134,8 +1270,8 @@ optional = false python-versions = ">=3.5" [package.dependencies] -"pyats.log" = ">=20.12.0,<20.13.0" "pyats.datastructures" = ">=20.12.0,<20.13.0" +"pyats.log" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1150,10 +1286,10 @@ python-versions = ">=3.5" [package.dependencies] "pyats.connections" = ">=20.12.0,<20.13.0" -pyyaml = "*" +"pyats.datastructures" = ">=20.12.0,<20.13.0" "pyats.utils" = ">=20.12.0,<20.13.0" +pyyaml = "*" yamllint = "*" -"pyats.datastructures" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1167,9 +1303,9 @@ optional = false python-versions = ">=3.5" [package.dependencies] -"pyats.topology" = ">=20.12.0,<20.13.0" -"pyats.datastructures" = ">=20.12.0,<20.13.0" distro = "*" +"pyats.datastructures" = ">=20.12.0,<20.13.0" +"pyats.topology" = ">=20.12.0,<20.13.0" [package.extras] dev = ["sphinx", "sphinx-rtd-theme"] @@ -1183,16 +1319,16 @@ optional = false python-versions = ">=3.6" [package.dependencies] -PyYAML = "*" -simplejson = "*" -netconan = ">=0.11.0" -deprecated = "*" attrs = ">=18.1.0" deepdiff = "*" +deprecated = "*" +netconan = ">=0.11.0" +pandas = "<1.2" python-dateutil = "*" +PyYAML = "*" requests = "*" -pandas = "<1.2" requests-toolbelt = "*" +simplejson = "*" [package.extras] capirca = ["capirca", "absl-py (>=0.8.0)"] @@ -1235,8 +1371,8 @@ dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] -typing_extensions = ["typing-extensions (>=3.7.2)"] email = ["email-validator (>=1.0.3)"] +typing_extensions = ["typing-extensions (>=3.7.2)"] [[package]] name = "pydocstyle" @@ -1249,6 +1385,21 @@ python-versions = ">=3.5" [package.dependencies] snowballstemmer = "*" +[[package]] +name = "pyeapi" +version = "0.8.4" +description = "Python Client for eAPI" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +netaddr = "*" + +[package.extras] +dev = ["check-manifest", "pep8", "pyflakes", "twine"] +test = ["coverage", "mock"] + [[package]] name = "pyflakes" version = "2.3.1" @@ -1286,10 +1437,10 @@ python-versions = "~=3.6" [package.dependencies] astroid = ">=2.5.6,<2.7" -mccabe = ">=0.6,<0.7" colorama = {version = "*", markers = "sys_platform == \"win32\""} -toml = ">=0.7.1" isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.7" +toml = ">=0.7.1" [[package]] name = "pyment" @@ -1308,8 +1459,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] -six = "*" cffi = ">=1.4.1" +six = "*" [package.extras] docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] @@ -1345,7 +1496,7 @@ six = ">=1.0.0,<2.0.0" name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "dev" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -1396,9 +1547,9 @@ optional = false python-versions = "*" [package.dependencies] +pyasn1 = ">=0.2.3" pycryptodomex = "*" pysmi = "*" -pyasn1 = ">=0.2.3" [[package]] name = "pytest" @@ -1409,15 +1560,15 @@ optional = false python-versions = ">=3.5" [package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} more-itertools = ">=4.0.0" -py = ">=1.5.0" -wcwidth = "*" packaging = "*" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -colorama = {version = "*", markers = "sys_platform == \"win32\""} -attrs = ">=17.4.0" pluggy = ">=0.12,<1.0" -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +py = ">=1.5.0" +wcwidth = "*" [package.extras] checkqa-mypy = ["mypy (==v0.761)"] @@ -1446,8 +1597,8 @@ python-versions = "*" six = ">=1.9.0" [package.extras] -client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] asyncio_client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] [[package]] name = "python-socketio" @@ -1458,12 +1609,12 @@ optional = false python-versions = "*" [package.dependencies] -six = ">=1.9.0" python-engineio = ">=3.13.0" +six = ">=1.9.0" [package.extras] -client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] [[package]] name = "pytz" @@ -1498,9 +1649,9 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -idna = ">=2.5,<3" certifi = ">=2017.4.17" chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" [package.extras] @@ -1520,8 +1671,8 @@ requests = ">=2.3,<3" six = "*" [package.extras] -test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.18)", "testtools"] fixture = ["fixtures"] +test = ["fixtures", "mock", "purl", "pytest", "sphinx", "testrepository (>=0.0.18)", "testtools"] [[package]] name = "requests-toolbelt" @@ -1543,11 +1694,11 @@ optional = false python-versions = ">=3.6,<4.0" [package.dependencies] -typing-extensions = ">=3.7.4,<4.0.0" -pygments = ">=2.6.0,<3.0.0" colorama = ">=0.4.0,<0.5.0" commonmark = ">=0.9.0,<0.10.0" dataclasses = {version = ">=0.7,<0.9", markers = "python_version >= \"3.6\" and python_version < \"3.7\""} +pygments = ">=2.6.0,<3.0.0" +typing-extensions = ">=3.7.4,<4.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] @@ -1642,9 +1793,9 @@ python-versions = ">=3.6" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] +dev = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio", "pytest-randomly", "pytest (>=6.0)", "simplejson", "furo", "sphinx", "sphinx-toolbox", "twisted", "pre-commit"] docs = ["furo", "sphinx", "sphinx-toolbox", "twisted"] tests = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio", "pytest-randomly", "pytest (>=6.0)", "simplejson"] -dev = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest-asyncio", "pytest-randomly", "pytest (>=6.0)", "simplejson", "furo", "sphinx", "sphinx-toolbox", "twisted", "pre-commit"] [[package]] name = "tenacity" @@ -1705,9 +1856,24 @@ optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" [package.extras] -telegram = ["requests"] -notebook = ["ipywidgets (>=6)"] dev = ["py-make (>=0.1.0)", "twine", "wheel"] +notebook = ["ipywidgets (>=6)"] +telegram = ["requests"] + +[[package]] +name = "transitions" +version = "0.8.8" +description = "A lightweight, object-oriented Python state machine implementation with many extensions." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[package.extras] +diagrams = ["pygraphviz"] +test = ["pytest"] [[package]] name = "typed-ast" @@ -1739,9 +1905,9 @@ pyyaml = "*" "unicon.plugins" = ">=20.12.0,<20.13.0" [package.extras] +dev = ["cisco-distutils", "coverage", "restview", "sphinx", "sphinxcontrib-napoleon", "sphinxcontrib-mockautodoc", "sphinx-rtd-theme"] pyats = ["pyats"] robot = ["robotframework"] -dev = ["cisco-distutils", "coverage", "restview", "sphinx", "sphinxcontrib-napoleon", "sphinxcontrib-mockautodoc", "sphinx-rtd-theme"] [[package]] name = "unicon.plugins" @@ -1752,8 +1918,8 @@ optional = false python-versions = "*" [package.dependencies] -unicon = ">=20.12.0,<20.13.0" pyyaml = "*" +unicon = ">=20.12.0,<20.13.0" [package.extras] dev = ["setuptools", "pip", "wheel", "coverage", "restview", "sphinx", "sphinxcontrib-napoleon", "sphinxcontrib-mockautodoc", "sphinx-rtd-theme"] @@ -1767,8 +1933,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] brotli = ["brotlipy (>=0.6.0)"] [[package]] @@ -1815,6 +1981,17 @@ python-versions = ">=3.5.*" pathspec = ">=0.5.3" pyyaml = "*" +[[package]] +name = "yamlordereddictloader" +version = "0.4.0" +description = "YAML loader and dump for PyYAML allowing to keep keys order." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pyyaml = "*" + [[package]] name = "yarl" version = "1.6.3" @@ -1824,9 +2001,9 @@ optional = false python-versions = ">=3.6" [package.dependencies] -typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} -multidict = ">=4.0" idna = ">=2.0" +multidict = ">=4.0" +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] name = "zipp" @@ -1843,7 +2020,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "a68308a62ec5fef915be82af72042b652c502d2f7882fd5179edd74c2be6ad43" +content-hash = "40eb9bd3a0add904a9dcd02724724314f8426253cd1f98c339a967b1b6e57ae3" [metadata.files] aiofiles = [ @@ -1979,6 +2156,10 @@ chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] +ciscoconfparse = [ + {file = "ciscoconfparse-1.5.30-py3-none-any.whl", hash = "sha256:1b7b98c0b1b1cb05fadc9c27711d79d4faee9cf4770a72b5798d7167bb97ccc2"}, + {file = "ciscoconfparse-1.5.30.tar.gz", hash = "sha256:e3c9ad5ff7563c6d3b4542d2826f4b07feb0e46ea5c698886ab8c4d55cc28815"}, +] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, @@ -2032,6 +2213,10 @@ distro = [ {file = "distro-1.5.0-py2.py3-none-any.whl", hash = "sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799"}, {file = "distro-1.5.0.tar.gz", hash = "sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92"}, ] +dnspython = [ + {file = "dnspython-2.1.0-py3-none-any.whl", hash = "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216"}, + {file = "dnspython-2.1.0.zip", hash = "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"}, +] fancycompleter = [ {file = "fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080"}, {file = "fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272"}, @@ -2125,6 +2310,10 @@ jsonpickle = [ junit-xml = [ {file = "junit_xml-1.9-py2.py3-none-any.whl", hash = "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732"}, ] +junos-eznc = [ + {file = "junos-eznc-2.6.0.tar.gz", hash = "sha256:761a85a1485f8dc41b983ae7296889c7a78394b78d2de68f0831b7efa5f6ca34"}, + {file = "junos_eznc-2.6.0-py2.py3-none-any.whl", hash = "sha256:598c3569b21f362f0bad626ad3756f9f8a3f4d507a0ff37ff76e33b3ed73859e"}, +] lazy-object-proxy = [ {file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726"}, {file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b"}, @@ -2149,6 +2338,44 @@ lazy-object-proxy = [ {file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61"}, {file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"}, ] +lxml = [ + {file = "lxml-4.6.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2"}, + {file = "lxml-4.6.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f"}, + {file = "lxml-4.6.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d"}, + {file = "lxml-4.6.3-cp27-cp27m-win32.whl", hash = "sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106"}, + {file = "lxml-4.6.3-cp27-cp27m-win_amd64.whl", hash = "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee"}, + {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f"}, + {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"}, + {file = "lxml-4.6.3-cp35-cp35m-win32.whl", hash = "sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2"}, + {file = "lxml-4.6.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4"}, + {file = "lxml-4.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec"}, + {file = "lxml-4.6.3-cp36-cp36m-win32.whl", hash = "sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04"}, + {file = "lxml-4.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a"}, + {file = "lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2"}, + {file = "lxml-4.6.3-cp37-cp37m-win32.whl", hash = "sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade"}, + {file = "lxml-4.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b"}, + {file = "lxml-4.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791"}, + {file = "lxml-4.6.3-cp38-cp38-win32.whl", hash = "sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28"}, + {file = "lxml-4.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7"}, + {file = "lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969"}, + {file = "lxml-4.6.3-cp39-cp39-win32.whl", hash = "sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f"}, + {file = "lxml-4.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83"}, + {file = "lxml-4.6.3.tar.gz", hash = "sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468"}, +] markupsafe = [ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, @@ -2235,6 +2462,13 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +napalm = [ + {file = "napalm-3.2.0-py2.py3-none-any.whl", hash = "sha256:6ab07a8a360150198b3c33c2fa4e92a9658cda9821736d24f2399343425d2af3"}, + {file = "napalm-3.2.0.tar.gz", hash = "sha256:6e69a505f5d4a678c42f048d13153afabf10b7fec72868e3bfb53ff4089206fc"}, +] +ncclient = [ + {file = "ncclient-0.6.9.tar.gz", hash = "sha256:0112f2ad41fb658f52446d870853a63691d69299c73c7351c520d38dbd8dc0c4"}, +] netaddr = [ {file = "netaddr-0.8.0-py2.py3-none-any.whl", hash = "sha256:9666d0232c32d2656e5e5f8d735f58fd6c7457ce52fc21c98d45f2af78f990ac"}, {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"}, @@ -2251,6 +2485,18 @@ nornir = [ {file = "nornir-3.1.1-py3-none-any.whl", hash = "sha256:217199f923c810f4a54dec8d440eb08682c8a4ea4746325bd3067dca2e32cf9f"}, {file = "nornir-3.1.1.tar.gz", hash = "sha256:6abf8ca245aab8e36b5770ec94fdb456b6bf53696c6d68f2ee0882898630a392"}, ] +nornir-napalm = [ + {file = "nornir_napalm-0.1.2-py3-none-any.whl", hash = "sha256:313986bbb16eeaf3a80daf61f6a97e93ecebc47d0c586a2eb856ea91edc2cc1d"}, + {file = "nornir_napalm-0.1.2.tar.gz", hash = "sha256:be7808a990242987500a65701edb626197c5d0b87f35d9eb5da7ce7e4d60fdd5"}, +] +nornir-netmiko = [ + {file = "nornir_netmiko-0.1.1-py3-none-any.whl", hash = "sha256:c6eadb81f6f3b2f0c27bae151cc62673303f9d085ec3c773ecdc98f20ef30f91"}, + {file = "nornir_netmiko-0.1.1.tar.gz", hash = "sha256:fc41ded40923d23c6155b92c6749170629b4cc2649c340d24bff9f49315836c6"}, +] +nornir-utils = [ + {file = "nornir_utils-0.1.2-py3-none-any.whl", hash = "sha256:98915e781d4135e424fe1d4617dc231ada9a7c732c8e51e408f53fd3d37a13b0"}, + {file = "nornir_utils-0.1.2.tar.gz", hash = "sha256:23ae95c4805b0ce8a5ed32935f3f86027e5701175e7740ab8b3a79946c5d90b2"}, +] ntc-templates = [ {file = "ntc_templates-1.7.0-py3-none-any.whl", hash = "sha256:08ffeafd02a1d3e42f4f12f910f0b4c64b93783d76ece186c9a63389403a450b"}, {file = "ntc_templates-1.7.0.tar.gz", hash = "sha256:4e098f83b35624751f4f6aaa4eed9eefd50a742f3d009c86f4bb22843a6a72fc"}, @@ -2666,6 +2912,9 @@ pydocstyle = [ {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"}, {file = "pydocstyle-5.1.1.tar.gz", hash = "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325"}, ] +pyeapi = [ + {file = "pyeapi-0.8.4.tar.gz", hash = "sha256:c33ad1eadd8ebac75f63488df9412081ce0b024c9e1da12a37196a5c60427c54"}, +] pyflakes = [ {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, @@ -2979,6 +3228,10 @@ tqdm = [ {file = "tqdm-4.60.0-py2.py3-none-any.whl", hash = "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3"}, {file = "tqdm-4.60.0.tar.gz", hash = "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae"}, ] +transitions = [ + {file = "transitions-0.8.8-py2.py3-none-any.whl", hash = "sha256:f35efa070fbdf9a0f3f093b19f1258068786af75786a8cbcc884444f3d1a66d4"}, + {file = "transitions-0.8.8.tar.gz", hash = "sha256:e7a86b31a161a76133f189b3ae9dad2755a80ea4c1e0eee1805648d021fb677d"}, +] typed-ast = [ {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, @@ -3054,6 +3307,9 @@ xmltodict = [ yamllint = [ {file = "yamllint-1.26.1.tar.gz", hash = "sha256:87d9462b3ed7e9dfa19caa177f7a77cd9888b3dc4044447d6ae0ab233bcd1324"}, ] +yamlordereddictloader = [ + {file = "yamlordereddictloader-0.4.0.tar.gz", hash = "sha256:7f30f0b99ea3f877f7cb340c570921fa9d639b7f69cba18be051e27f8de2080e"}, +] yarl = [ {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, diff --git a/pyproject.toml b/pyproject.toml index 71956d53..4a28618b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,9 @@ structlog = "^20.1.0" diffsync = "~1.2.0" rich = "^9.2.0" pynautobot = "^1.0.2" +nornir-napalm = "^0.1.2" +nornir-utils = "^0.1.2" +nornir-netmiko = "^0.1.1" [tool.poetry.dev-dependencies] bandit = "^1.6" From 85cbc36b1028c2d1567fee1cb3d676999d13403c Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Sun, 9 May 2021 18:05:54 -0400 Subject: [PATCH 03/13] Add Nautobot Inventory and update adapter --- .../adapters/nautobot_api/adapter.py | 34 +-- .../adapters/nautobot_api/config.py | 31 ++- .../adapters/nautobot_api/inventory.py | 215 ++++++++++++++++++ .../adapters/nautobot_api/models.py | 2 + .../adapters/nautobot_api/tasks.py | 8 +- .../adapters/network_importer/adapter.py | 10 +- network_importer/main.py | 12 +- 7 files changed, 282 insertions(+), 30 deletions(-) create mode 100644 network_importer/adapters/nautobot_api/inventory.py diff --git a/network_importer/adapters/nautobot_api/adapter.py b/network_importer/adapters/nautobot_api/adapter.py index 950f9b45..61fab562 100644 --- a/network_importer/adapters/nautobot_api/adapter.py +++ b/network_importer/adapters/nautobot_api/adapter.py @@ -31,6 +31,7 @@ NautobotVlan, ) from network_importer.adapters.nautobot_api.tasks import query_device_info_from_nautobot +from .config import InventorySettings warnings.filterwarnings("ignore", category=DeprecationWarning) @@ -73,24 +74,26 @@ def _is_tag_present(nautobot_obj): elif not nautobot_obj["tags"]: return False - for tag in config.SETTINGS.nautobot.model_flag_tags: - if tag in nautobot_obj["tags"]: - LOGGER.debug( - "Tag (%s) found for object %s. Marked for diffsync flag assignment.", tag, nautobot_obj, - ) - return True + # FIXME dgarros need to re-enable + # for tag in config.SETTINGS.nautobot.model_flag_tags: + # if tag in nautobot_obj["tags"]: + # LOGGER.debug( + # "Tag (%s) found for object %s. Marked for diffsync flag assignment.", tag, nautobot_obj, + # ) + # return True return False @staticmethod def apply_model_flag(diffsync_obj, nautobot_obj): """Helper function for DiffSync Flag assignment.""" - model_flag = config.SETTINGS.nautobot.model_flag - - if model_flag and NautobotAPIAdapter._is_tag_present(nautobot_obj): - LOGGER.info( - "DiffSync model flag (%s) applied to object %s", model_flag, nautobot_obj, - ) - diffsync_obj.model_flags = model_flag + # FIXME dgarros need to re-enable + # model_flag = config.SETTINGS.nautobot.model_flag + + # if model_flag and NautobotAPIAdapter._is_tag_present(nautobot_obj): + # LOGGER.info( + # "DiffSync model flag (%s) applied to object %s", model_flag, nautobot_obj, + # ) + # diffsync_obj.model_flags = model_flag return diffsync_obj def _check_nautobot_version(self): @@ -109,9 +112,10 @@ def _check_nautobot_version(self): def load(self): """Initialize pynautobot and load all data from nautobot in the local cache.""" - self.nautobot = pynautobot.api(url=config.SETTINGS.nautobot.address, token=config.SETTINGS.nautobot.token) + inventory_params = InventorySettings(**config.SETTINGS.inventory.inventory_params) + self.nautobot = pynautobot.api(url=inventory_params.address, token=inventory_params.token) - if not config.SETTINGS.nautobot.verify_ssl: + if not inventory_params.verify_ssl: self.nautobot.http_session.verify_ssl = False else: self.nautobot.http_session.verify_ssl = True diff --git a/network_importer/adapters/nautobot_api/config.py b/network_importer/adapters/nautobot_api/config.py index a2e769ee..14c2f744 100644 --- a/network_importer/adapters/nautobot_api/config.py +++ b/network_importer/adapters/nautobot_api/config.py @@ -12,10 +12,37 @@ See the License for the specific language governing permissions and limitations under the License. """ +from typing import List, Optional, Union from pydantic import BaseSettings -# pylint: disable=too-few-public-methods,no-self-argument,no-self-use +from diffsync import DiffSyncModelFlags +# pylint: disable=too-few-public-methods,no-self-argument,no-self-use class AdapterSettings(BaseSettings): - """Config settings for the nautobot_api adapter. Not used currently.""" + """Config settings for the netbox_api adapter. Not used currently.""" + + model_flag_tags: List[str] = list() # List of tags that defines what objects to assign the model_flag to. + model_flag: Optional[DiffSyncModelFlags] # The model flag that will be applied to objects based on tag. + + +class InventorySettings(BaseSettings): + address: Optional[str] = "http://localhost" + token: Optional[str] = None + verify_ssl: Union[bool, str] = True + + use_primary_ip: Optional[bool] = True + fqdn: Optional[str] = None + filter: Optional[str] = None + global_delay_factor: Optional[int] = 5 + banner_timeout: Optional[int] = 15 + conn_timeout: Optional[int] = 5 + + class Config: + """Additional parameters to automatically map environment variable to some settings.""" + + fields = { + "address": {"env": "NAUTOBOT_ADDRESS"}, + "token": {"env": "NAUTOBOT_TOKEN"}, + "verify_ssl": {"env": "NAUTOBOT_VERIFY_SSL"}, + } diff --git a/network_importer/adapters/nautobot_api/inventory.py b/network_importer/adapters/nautobot_api/inventory.py new file mode 100644 index 00000000..231e591e --- /dev/null +++ b/network_importer/adapters/nautobot_api/inventory.py @@ -0,0 +1,215 @@ +"""Norning Inventory for netbox. + +(c) 2020 Network To Code + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +# Disable too-many-arguments and too-many-locals pylint tests for this file. These are both necessary +# pylint: disable=R0913,R0914,E1101,W0613 + +from typing import Any, List, Optional +import requests +import pynautobot + +from nornir.core.inventory import Defaults, Group, Groups, Hosts, Inventory, ParentGroups, ConnectionOptions +from nornir.core.plugins.inventory import InventoryPluginRegister +from network_importer.inventory import NetworkImporterInventory, NetworkImporterHost + +from .config import InventorySettings + +# ------------------------------------------------------------ +# Network Importer Base Dict for device data +# status: +# ok: device is reachable +# fail-ip: Primary IP address not reachable +# fail-access: Unable to access the device management. The IP is reachable, but SSH or API is not enabled or +# responding. +# fail-login: Unable to login authenticate with device +# fail-other: Other general processing error (also catches traps/bug) +# is_reachable: Global Flag to indicate if we are able to connect to a device +# has_config: Indicate if the configuration is present and has been properly imported in Batfish +# ------------------------------------------------------------ + + +def build_filter_params(filter_params, params): + """Update params dict() with filter args in required format for pynautobot. + + Args: + filter_params (list): split string from cli or config + params (dict): object to hold params + """ + for param_value in filter_params: + if "=" not in param_value: + continue + key, value = param_value.split("=", 1) + existing_value = params.get(key) + if existing_value and isinstance(existing_value, list): + params[key].append(value) + elif existing_value and isinstance(existing_value, str): + params[key] = [existing_value, value] + else: + params[key] = value + + +class NautobotAPIInventory(NetworkImporterInventory): + """Nautobot API Inventory Class.""" + + # pylint: disable=dangerous-default-value, too-many-branches, too-many-statements + def __init__( + self, + username: Optional[str], + password: Optional[str], + enable: Optional[bool], + supported_platforms: Optional[List[str]], + limit=Optional[str], + params: InventorySettings = InventorySettings(), + **kwargs: Any, + ) -> None: + """Nornir Inventory Plugin for Nautobot API.""" + + self.username = username + self.password = password + self.enable = enable + self.supported_platforms = supported_platforms + + self.params = InventorySettings(**params) + + # Build Filter based on inventory_params filter and on limit + self.filter_parameters = {} + build_filter_params(self.params.filter.split((",")), self.filter_parameters) + if limit: + if "=" not in limit: + self.filter_parameters["name"] = limit + else: + build_filter_params(limit.split((",")), self.filter_parameters) + + if "exclude" not in self.filter_parameters.keys(): + self.filter_parameters["exclude"] = "config_context" + + # Instantiate nautobot session using pynautobot + self.session = pynautobot.api(url=self.params.address, token=self.params.token) + if not self.params.verify_ssl: + session = requests.Session() + session.verify = False + self.session.http_session = session + + def load(self): + + # fetch devices from netbox + if self.filter_parameters: + devices: List[pynautobot.modules.dcim.Devices] = self.session.dcim.devices.filter(**self.filter_parameters) + else: + devices: List[pynautobot.modules.dcim.Devices] = self.session.dcim.devices.all() + + # fetch all platforms from nautobot and build mapping: platform: napalm_driver + platforms = self.session.dcim.platforms.all() + platforms_mapping = {platform.slug: platform.napalm_driver for platform in platforms if platform.napalm_driver} + + hosts = Hosts() + groups = Groups() + defaults = Defaults() + + global_group = Group( + name="global", connection_options={"netmiko": ConnectionOptions(), "napalm": ConnectionOptions()} + ) + + # Pull the login and password from the NI config object if available + if self.username: + global_group.username = self.username + + if self.password: + global_group.password = self.password + if self.enable: + global_group.connection_options["netmiko"].extras = { + "secret": self.password, + "global_delay_factor": self.params.global_delay_factor, + "banner_timeout": self.params.banner_timeout, + "conn_timeout": self.params.conn_timeout, + } + global_group.connection_options["napalm"].extras = {"optional_args": {"secret": self.password}} + + for dev in devices: + # nautobot allows devices to be unnamed, but the Nornir model does not allow this + # If a device is unnamed we will set the name to the id of the device in nautobot + dev_name = dev.name or dev.id + host = NetworkImporterHost(name=dev_name, connection_options=ConnectionOptions()) + + # Only add virtual chassis master as inventory element + if dev.virtual_chassis and dev.virtual_chassis.master: + if dev.id != dev.virtual_chassis.master.id: + continue + host.data["virtual_chassis"] = True + + else: + host.data["virtual_chassis"] = False + + # If supported_platforms is provided + # skip all devices that do not match the list of supported platforms + # TODO need to see if we can filter when doing the query directly + if self.supported_platforms: + if not dev.platform: + continue + + if dev.platform.slug not in self.supported_platforms: + continue + + # Add value for IP address + if self.params.use_primary_ip and dev.primary_ip: + host.hostname = dev.primary_ip.address.split("/")[0] + elif self.params.use_primary_ip and not dev.primary_ip: + host.is_reachable = False + host.not_reachable_reason = "primary ip not defined in nautobot" + elif not self.params.use_primary_ip and self.params.fqdn: + host.hostname = f"{dev.name}.{self.params.fqdn}" + elif not self.params.use_primary_ip: + host.hostname = dev.name + else: + host.hostname = dev_name + + host.site_name = dev.site.slug + + host.data["serial"] = dev.serial + host.data["vendor"] = dev.device_type.manufacturer.slug + host.data["asset_tag"] = dev.asset_tag + host.data["custom_fields"] = dev.custom_fields + host.data["site_id"] = dev.site.id + host.data["device_id"] = dev.id + host.data["role"] = dev.device_role.slug + host.data["model"] = dev.device_type.slug + + # Attempt to add 'platform' based of value in 'slug' + if dev.platform and dev.platform.slug in platforms_mapping: + host.connection_options = {"napalm": ConnectionOptions(platform=platforms_mapping[dev.platform.slug])} + + if dev.platform: + host.platform = dev.platform.slug + else: + host.platform = None + + host.groups = ParentGroups([global_group]) + + if dev.site.slug not in groups.keys(): + groups[dev.site.slug] = {} + + if dev.device_role.slug not in groups.keys(): + groups[dev.device_role.slug] = {} + + if host.hostname and host.platform: + host.is_reachable = True + + # Assign temporary dict to outer dict + + hosts[dev_name] = host + + return Inventory(hosts=hosts, groups=groups, defaults=defaults) + + +InventoryPluginRegister.register("NautobotAPIInventory", NautobotAPIInventory) diff --git a/network_importer/adapters/nautobot_api/models.py b/network_importer/adapters/nautobot_api/models.py index d9cee043..2a2e8de1 100644 --- a/network_importer/adapters/nautobot_api/models.py +++ b/network_importer/adapters/nautobot_api/models.py @@ -344,6 +344,8 @@ def create(cls, diffsync: "DiffSync", ids: dict, attrs: dict) -> Optional["DiffS try: item = super().create(ids=ids, diffsync=diffsync, attrs=attrs) nb_params = item.translate_attrs_for_nautobot(attrs) + # Add status because it's a mandatory field. + nb_params["status"] = "active" ip_address = diffsync.nautobot.ipam.ip_addresses.create(**nb_params) except pynautobot.core.query.RequestError as exc: LOGGER.warning("Unable to create the ip address %s in %s (%s)", ids["address"], diffsync.name, exc.error) diff --git a/network_importer/adapters/nautobot_api/tasks.py b/network_importer/adapters/nautobot_api/tasks.py index eb117a95..0402ca02 100644 --- a/network_importer/adapters/nautobot_api/tasks.py +++ b/network_importer/adapters/nautobot_api/tasks.py @@ -18,6 +18,7 @@ from nornir.core.task import Result, Task import network_importer.config as config # pylint: disable=import-error +from .config import InventorySettings LOGGER = logging.getLogger("network-importer") @@ -36,11 +37,11 @@ def query_device_info_from_nautobot(task: Task) -> Result: Returns: Result: Nornir Result object with the result in a dict format """ - # Create a pynautobot instance for use later - nautobot = pynautobot.api(url=config.SETTINGS.nautobot.address, token=config.SETTINGS.nautobot.token) + inventory_params = InventorySettings(**config.SETTINGS.inventory.inventory_params) + nautobot = pynautobot.api(url=inventory_params.address, token=inventory_params.token) # Check for SSL Verification, set it to false if not. Else set to true - if not config.SETTINGS.nautobot.verify_ssl: + if not inventory_params.verify_ssl: # No manual session is required for this, pynautobot will automatically create one nautobot.http_session.verify = False else: @@ -70,5 +71,4 @@ def query_device_info_from_nautobot(task: Task) -> Result: # TODO move the logic to pull the interface and potentially IP here # interfaces = netbox.dcim.interfaces.filter(device=task.host.name) # results["interfaces"] = [ dict(intf) for intf in interfaces ] - return Result(host=task.host, result=results) diff --git a/network_importer/adapters/network_importer/adapter.py b/network_importer/adapters/network_importer/adapter.py index 60acde4e..b3865c91 100644 --- a/network_importer/adapters/network_importer/adapter.py +++ b/network_importer/adapters/network_importer/adapter.py @@ -68,14 +68,14 @@ def load(self): self.nornir.inventory.hosts[hostname].has_config = True - if host.data["site"] not in sites.keys(): - site = self.site(name=host.data["site"]) - sites[host.data["site"]] = site + if host.site_name not in sites.keys(): + site = self.site(name=host.site_name) + sites[host.site_name] = site self.add(site) else: - site = sites[host.data["site"]] + site = sites[host.site_name] - device = self.device(name=hostname, site_name=host.data["site"]) + device = self.device(name=hostname, site_name=host.site_name) self.add(device) if config.SETTINGS.main.import_cabling in ["lldp", "cdp"] or config.SETTINGS.main.import_vlans in [True, "cli"]: diff --git a/network_importer/main.py b/network_importer/main.py index 771b6854..62e95c34 100644 --- a/network_importer/main.py +++ b/network_importer/main.py @@ -27,7 +27,6 @@ from network_importer.tasks import check_if_reachable, warning_not_reachable from network_importer.performance import timeit from network_importer.inventory import reachable_devs -from network_importer.adapters.netbox_api.inventory import NetboxAPIInventory warnings.filterwarnings("ignore", category=DeprecationWarning) @@ -38,8 +37,6 @@ __author__ = "Damien Garros " -InventoryPluginRegister.register("NetboxAPIInventory", NetboxAPIInventory) - LOGGER = logging.getLogger("network-importer") @@ -58,7 +55,14 @@ def __init__(self, check_mode=False, nornir=None): def build_inventory(self, limit=None): """Build the inventory for the Network Importer in Nornir format.""" - # TODO Cleanup config file and allow user defined inventory + # Load build-in Inventories as needed + if config.SETTINGS.inventory.inventory_class == "NetboxAPIInventory": + from network_importer.adapters.netbox_api.inventory import NetboxAPIInventory + InventoryPluginRegister.register("NetboxAPIInventory", NetboxAPIInventory) + elif config.SETTINGS.inventory.inventory_class == "NautobotAPIInventory": + from network_importer.adapters.nautobot_api.inventory import NautobotAPIInventory + InventoryPluginRegister.register("NautobotAPIInventory", NautobotAPIInventory) + self.nornir = InitNornir( runner={"plugin": "threaded", "options": {"num_workers": config.SETTINGS.main.nbr_workers}}, logging={"enabled": False}, From 6ff88bcd57b7c0a6c0a4b23a9b14b10b99a44432 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Sun, 9 May 2021 18:06:31 -0400 Subject: [PATCH 04/13] format with black --- network_importer/adapters/nautobot_api/config.py | 1 + network_importer/config.py | 2 ++ network_importer/main.py | 2 ++ 3 files changed, 5 insertions(+) diff --git a/network_importer/adapters/nautobot_api/config.py b/network_importer/adapters/nautobot_api/config.py index 14c2f744..d3f8f1d0 100644 --- a/network_importer/adapters/nautobot_api/config.py +++ b/network_importer/adapters/nautobot_api/config.py @@ -19,6 +19,7 @@ # pylint: disable=too-few-public-methods,no-self-argument,no-self-use + class AdapterSettings(BaseSettings): """Config settings for the netbox_api adapter. Not used currently.""" diff --git a/network_importer/config.py b/network_importer/config.py index 631bfe79..43d1ab96 100644 --- a/network_importer/config.py +++ b/network_importer/config.py @@ -37,6 +37,7 @@ # pylint: disable=too-few-public-methods,global-statement + class BatfishSettings(BaseSettings): """Settings definition for the Batfish section of the configuration.""" @@ -56,6 +57,7 @@ class Config: "api_key": {"env": "BATFISH_API_KEY"}, } + class NetworkSettings(BaseSettings): """Settings definition for the Network section of the configuration.""" diff --git a/network_importer/main.py b/network_importer/main.py index 62e95c34..cdc45a53 100644 --- a/network_importer/main.py +++ b/network_importer/main.py @@ -58,9 +58,11 @@ def build_inventory(self, limit=None): # Load build-in Inventories as needed if config.SETTINGS.inventory.inventory_class == "NetboxAPIInventory": from network_importer.adapters.netbox_api.inventory import NetboxAPIInventory + InventoryPluginRegister.register("NetboxAPIInventory", NetboxAPIInventory) elif config.SETTINGS.inventory.inventory_class == "NautobotAPIInventory": from network_importer.adapters.nautobot_api.inventory import NautobotAPIInventory + InventoryPluginRegister.register("NautobotAPIInventory", NautobotAPIInventory) self.nornir = InitNornir( From 30c811001ee935cb2e59d49e63e982eaed0424b0 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Mon, 10 May 2021 07:57:51 -0400 Subject: [PATCH 05/13] Cont refactoring of inventory and nornir 3.x --- examples/multi_site_02/network_importer.toml | 2 +- examples/spine_leaf_01/network_importer.toml | 2 +- network_importer/adapters/base.py | 34 +- .../adapters/nautobot_api/adapter.py | 53 +-- .../adapters/nautobot_api/exceptions.py | 15 +- .../adapters/nautobot_api/inventory.py | 97 ++--- .../adapters/nautobot_api/models.py | 15 +- .../nautobot_api/{config.py => settings.py} | 19 +- .../adapters/nautobot_api/tasks.py | 17 +- .../adapters/netbox_api/adapter.py | 53 +-- .../adapters/netbox_api/exceptions.py | 15 +- .../adapters/netbox_api/inventory.py | 116 ++---- .../adapters/netbox_api/models.py | 15 +- .../netbox_api/{config.py => settings.py} | 23 +- network_importer/adapters/netbox_api/tasks.py | 17 +- .../adapters/network_importer/adapter.py | 15 +- .../adapters/network_importer/exceptions.py | 15 +- network_importer/config.py | 4 +- network_importer/drivers/__init__.py | 14 +- network_importer/inventory.py | 47 +-- network_importer/main.py | 4 +- network_importer/processors/get_neighbors.py | 2 - network_importer/processors/get_vlans.py | 2 - network_importer/tasks.py | 27 +- network_importer/utils.py | 20 + pyproject.toml | 3 +- .../fixtures/inventory/devices.json | 364 ++++++++++++++++++ .../fixtures/inventory/filtered_devices.json | 64 +++ .../fixtures/inventory/platforms.json | 52 +++ .../fixtures/inventory/stack_devices.json | 217 +++++++++++ .../netbox_api}/test_inventory.py | 36 +- tests/unit/conftest.py | 12 +- tests/unit/processors/test_get_neighbors.py | 6 +- 33 files changed, 907 insertions(+), 490 deletions(-) rename network_importer/adapters/nautobot_api/{config.py => settings.py} (62%) rename network_importer/adapters/netbox_api/{config.py => settings.py} (58%) create mode 100644 tests/unit/adapters/netbox_api/fixtures/inventory/devices.json create mode 100644 tests/unit/adapters/netbox_api/fixtures/inventory/filtered_devices.json create mode 100644 tests/unit/adapters/netbox_api/fixtures/inventory/platforms.json create mode 100644 tests/unit/adapters/netbox_api/fixtures/inventory/stack_devices.json rename tests/unit/{ => adapters/netbox_api}/test_inventory.py (84%) diff --git a/examples/multi_site_02/network_importer.toml b/examples/multi_site_02/network_importer.toml index 827a994e..a18c30a4 100644 --- a/examples/multi_site_02/network_importer.toml +++ b/examples/multi_site_02/network_importer.toml @@ -8,7 +8,7 @@ import_vlans = "config" nbr_workers = 1 -[inventory] +[inventory.settings] filter="region=ni_multi_site_02" [logs] diff --git a/examples/spine_leaf_01/network_importer.toml b/examples/spine_leaf_01/network_importer.toml index af9d77d2..41a9fad0 100644 --- a/examples/spine_leaf_01/network_importer.toml +++ b/examples/spine_leaf_01/network_importer.toml @@ -6,7 +6,7 @@ import_prefixes = true import_cabling = "config" import_vlans = "config" -[inventory] +[inventory.settings] filter = "site=ni_spine_leaf_01" [logs] diff --git a/network_importer/adapters/base.py b/network_importer/adapters/base.py index 79f2d71c..2fb767d5 100644 --- a/network_importer/adapters/base.py +++ b/network_importer/adapters/base.py @@ -1,17 +1,4 @@ -"""BaseAdapter for the network importer. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""BaseAdapter for the network importer.""" from diffsync import DiffSync from network_importer.models import Site, Device, Interface, IPAddress, Cable, Vlan, Prefix @@ -27,16 +14,21 @@ class BaseAdapter(DiffSync): vlan = Vlan prefix = Prefix - def __init__(self, nornir, config): - """Initialize the base adapter and store the nornir object locally.""" + settings_class = None + settings = None + + def __init__(self, nornir, settings): + """Initialize the base adapter and store the Nornir object locally.""" super().__init__() self.nornir = nornir - self.config = self._validate_config(config) + self.settings = self._validate_settings(settings) + + def _validate_settings(self, settings): + """Load and validate the configuration based on the settings_class.""" + if self.settings_class: + return self.settings_class(**settings) # pylint: disable=not-callable - @classmethod - def _validate_config(cls, config): - """Placeholder method that can be reimplemented by each adapter to validate if its config is valid.""" - return config + return settings def load(self): """Load the local cache with data from the remove system.""" diff --git a/network_importer/adapters/nautobot_api/adapter.py b/network_importer/adapters/nautobot_api/adapter.py index 61fab562..dc87271a 100644 --- a/network_importer/adapters/nautobot_api/adapter.py +++ b/network_importer/adapters/nautobot_api/adapter.py @@ -1,17 +1,4 @@ -"""NautobotAPIAdapter class. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""NautobotAPIAdapter class.""" import logging import warnings @@ -31,7 +18,7 @@ NautobotVlan, ) from network_importer.adapters.nautobot_api.tasks import query_device_info_from_nautobot -from .config import InventorySettings +from network_importer.adapters.nautobot_api.settings import InventorySettings, AdapterSettings warnings.filterwarnings("ignore", category=DeprecationWarning) @@ -57,12 +44,13 @@ class NautobotAPIAdapter(BaseAdapter): nautobot = None nautobot_version = None + settings_class = AdapterSettings + type = "Nautobot" query_device_info_from_nautobot = query_device_info_from_nautobot - @staticmethod - def _is_tag_present(nautobot_obj): + def _is_tag_present(self, nautobot_obj): """Find if tag is present for a given object.""" if isinstance(nautobot_obj, dict) and not nautobot_obj.get("tags", None): # pylint: disable=no-else-return return False @@ -74,26 +62,23 @@ def _is_tag_present(nautobot_obj): elif not nautobot_obj["tags"]: return False - # FIXME dgarros need to re-enable - # for tag in config.SETTINGS.nautobot.model_flag_tags: - # if tag in nautobot_obj["tags"]: - # LOGGER.debug( - # "Tag (%s) found for object %s. Marked for diffsync flag assignment.", tag, nautobot_obj, - # ) - # return True + for tag in self.settings.model_flag_tags: + if tag in nautobot_obj["tags"]: + LOGGER.debug( + "Tag (%s) found for object %s. Marked for diffsync flag assignment.", tag, nautobot_obj, + ) + return True return False - @staticmethod - def apply_model_flag(diffsync_obj, nautobot_obj): + def apply_model_flag(self, diffsync_obj, nautobot_obj): """Helper function for DiffSync Flag assignment.""" - # FIXME dgarros need to re-enable - # model_flag = config.SETTINGS.nautobot.model_flag - - # if model_flag and NautobotAPIAdapter._is_tag_present(nautobot_obj): - # LOGGER.info( - # "DiffSync model flag (%s) applied to object %s", model_flag, nautobot_obj, - # ) - # diffsync_obj.model_flags = model_flag + model_flag = self.settings.model_flag + + if model_flag and self._is_tag_present(nautobot_obj): + LOGGER.info( + "DiffSync model flag (%s) applied to object %s", model_flag, nautobot_obj, + ) + diffsync_obj.model_flags = model_flag return diffsync_obj def _check_nautobot_version(self): diff --git a/network_importer/adapters/nautobot_api/exceptions.py b/network_importer/adapters/nautobot_api/exceptions.py index 118a4db2..bb6c6f8c 100644 --- a/network_importer/adapters/nautobot_api/exceptions.py +++ b/network_importer/adapters/nautobot_api/exceptions.py @@ -1,17 +1,4 @@ -"""Custom Exceptions for the NautobotAPIAdapter. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Custom Exceptions for the NautobotAPIAdapter.""" class NautobotObjectNotValid(Exception): diff --git a/network_importer/adapters/nautobot_api/inventory.py b/network_importer/adapters/nautobot_api/inventory.py index 231e591e..0cac4803 100644 --- a/network_importer/adapters/nautobot_api/inventory.py +++ b/network_importer/adapters/nautobot_api/inventory.py @@ -1,17 +1,4 @@ -"""Norning Inventory for netbox. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Norning Inventory for nautobot.""" # Disable too-many-arguments and too-many-locals pylint tests for this file. These are both necessary # pylint: disable=R0913,R0914,E1101,W0613 @@ -22,46 +9,15 @@ from nornir.core.inventory import Defaults, Group, Groups, Hosts, Inventory, ParentGroups, ConnectionOptions from nornir.core.plugins.inventory import InventoryPluginRegister from network_importer.inventory import NetworkImporterInventory, NetworkImporterHost - -from .config import InventorySettings - -# ------------------------------------------------------------ -# Network Importer Base Dict for device data -# status: -# ok: device is reachable -# fail-ip: Primary IP address not reachable -# fail-access: Unable to access the device management. The IP is reachable, but SSH or API is not enabled or -# responding. -# fail-login: Unable to login authenticate with device -# fail-other: Other general processing error (also catches traps/bug) -# is_reachable: Global Flag to indicate if we are able to connect to a device -# has_config: Indicate if the configuration is present and has been properly imported in Batfish -# ------------------------------------------------------------ - - -def build_filter_params(filter_params, params): - """Update params dict() with filter args in required format for pynautobot. - - Args: - filter_params (list): split string from cli or config - params (dict): object to hold params - """ - for param_value in filter_params: - if "=" not in param_value: - continue - key, value = param_value.split("=", 1) - existing_value = params.get(key) - if existing_value and isinstance(existing_value, list): - params[key].append(value) - elif existing_value and isinstance(existing_value, str): - params[key] = [existing_value, value] - else: - params[key] = value +from network_importer.utils import build_filter_params +from network_importer.adapters.nautobot_api.settings import InventorySettings class NautobotAPIInventory(NetworkImporterInventory): """Nautobot API Inventory Class.""" + settings_class = InventorySettings + # pylint: disable=dangerous-default-value, too-many-branches, too-many-statements def __init__( self, @@ -70,21 +26,25 @@ def __init__( enable: Optional[bool], supported_platforms: Optional[List[str]], limit=Optional[str], - params: InventorySettings = InventorySettings(), + settings: InventorySettings = InventorySettings(), **kwargs: Any, ) -> None: """Nornir Inventory Plugin for Nautobot API.""" + super().__init__( + username=username, + password=password, + enable=enable, + limit=limit, + supported_platforms=supported_platforms, + settings=settings, + **kwargs, + ) - self.username = username - self.password = password - self.enable = enable - self.supported_platforms = supported_platforms - - self.params = InventorySettings(**params) + self.settings = self.settings_class(**settings) # Build Filter based on inventory_params filter and on limit self.filter_parameters = {} - build_filter_params(self.params.filter.split((",")), self.filter_parameters) + build_filter_params(self.settings.filter.split((",")), self.filter_parameters) if limit: if "=" not in limit: self.filter_parameters["name"] = limit @@ -95,15 +55,14 @@ def __init__( self.filter_parameters["exclude"] = "config_context" # Instantiate nautobot session using pynautobot - self.session = pynautobot.api(url=self.params.address, token=self.params.token) - if not self.params.verify_ssl: + self.session = pynautobot.api(url=self.settings.address, token=self.settings.token) + if not self.settings.verify_ssl: session = requests.Session() session.verify = False self.session.http_session = session def load(self): - - # fetch devices from netbox + """Load inventory by fetching devices from nautobot.""" if self.filter_parameters: devices: List[pynautobot.modules.dcim.Devices] = self.session.dcim.devices.filter(**self.filter_parameters) else: @@ -130,9 +89,9 @@ def load(self): if self.enable: global_group.connection_options["netmiko"].extras = { "secret": self.password, - "global_delay_factor": self.params.global_delay_factor, - "banner_timeout": self.params.banner_timeout, - "conn_timeout": self.params.conn_timeout, + "global_delay_factor": self.settings.global_delay_factor, + "banner_timeout": self.settings.banner_timeout, + "conn_timeout": self.settings.conn_timeout, } global_group.connection_options["napalm"].extras = {"optional_args": {"secret": self.password}} @@ -162,14 +121,14 @@ def load(self): continue # Add value for IP address - if self.params.use_primary_ip and dev.primary_ip: + if self.settings.use_primary_ip and dev.primary_ip: host.hostname = dev.primary_ip.address.split("/")[0] - elif self.params.use_primary_ip and not dev.primary_ip: + elif self.settings.use_primary_ip and not dev.primary_ip: host.is_reachable = False host.not_reachable_reason = "primary ip not defined in nautobot" - elif not self.params.use_primary_ip and self.params.fqdn: - host.hostname = f"{dev.name}.{self.params.fqdn}" - elif not self.params.use_primary_ip: + elif not self.settings.use_primary_ip and self.settings.fqdn: + host.hostname = f"{dev.name}.{self.settings.fqdn}" + elif not self.settings.use_primary_ip: host.hostname = dev.name else: host.hostname = dev_name diff --git a/network_importer/adapters/nautobot_api/models.py b/network_importer/adapters/nautobot_api/models.py index 2a2e8de1..18c20979 100644 --- a/network_importer/adapters/nautobot_api/models.py +++ b/network_importer/adapters/nautobot_api/models.py @@ -1,17 +1,4 @@ -"""Extension of the base Models for the NautobotAPIAdapter. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Extension of the base Models for the NautobotAPIAdapter.""" from typing import Optional import logging diff --git a/network_importer/adapters/nautobot_api/config.py b/network_importer/adapters/nautobot_api/settings.py similarity index 62% rename from network_importer/adapters/nautobot_api/config.py rename to network_importer/adapters/nautobot_api/settings.py index d3f8f1d0..886e3ace 100644 --- a/network_importer/adapters/nautobot_api/config.py +++ b/network_importer/adapters/nautobot_api/settings.py @@ -1,24 +1,9 @@ -"""Settings definition for the NetboxAPIAdapter. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Settings definition for the NetboxAPIAdapter.""" from typing import List, Optional, Union from pydantic import BaseSettings from diffsync import DiffSyncModelFlags -# pylint: disable=too-few-public-methods,no-self-argument,no-self-use - class AdapterSettings(BaseSettings): """Config settings for the netbox_api adapter. Not used currently.""" @@ -28,6 +13,8 @@ class AdapterSettings(BaseSettings): class InventorySettings(BaseSettings): + """Config settings for the NautobotAPI inventory.""" + address: Optional[str] = "http://localhost" token: Optional[str] = None verify_ssl: Union[bool, str] = True diff --git a/network_importer/adapters/nautobot_api/tasks.py b/network_importer/adapters/nautobot_api/tasks.py index 0402ca02..a06c6d66 100644 --- a/network_importer/adapters/nautobot_api/tasks.py +++ b/network_importer/adapters/nautobot_api/tasks.py @@ -1,24 +1,11 @@ -"""Collection of nornir tasks for the NetboxAPIAdapter. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Collection of nornir tasks for the NetboxAPIAdapter.""" import logging import pynautobot from nornir.core.task import Result, Task import network_importer.config as config # pylint: disable=import-error -from .config import InventorySettings +from network_importer.adapters.nautobot_api.settings import InventorySettings LOGGER = logging.getLogger("network-importer") diff --git a/network_importer/adapters/netbox_api/adapter.py b/network_importer/adapters/netbox_api/adapter.py index d0f7ad33..5b05c903 100644 --- a/network_importer/adapters/netbox_api/adapter.py +++ b/network_importer/adapters/netbox_api/adapter.py @@ -1,17 +1,4 @@ -"""NetBoxAPIAdapter class. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""NetBoxAPIAdapter class.""" import logging import warnings @@ -34,7 +21,7 @@ NetboxVlanPre29, ) from network_importer.adapters.netbox_api.tasks import query_device_info_from_netbox -from .config import InventorySettings +from network_importer.adapters.netbox_api.settings import InventorySettings, AdapterSettings warnings.filterwarnings("ignore", category=DeprecationWarning) @@ -60,12 +47,13 @@ class NetBoxAPIAdapter(BaseAdapter): netbox = None netbox_version = None + settings_class = AdapterSettings + type = "Netbox" query_device_info_from_netbox = query_device_info_from_netbox - @staticmethod - def _is_tag_present(netbox_obj): + def _is_tag_present(self, netbox_obj): """Find if tag is present for a given object.""" if isinstance(netbox_obj, dict) and not netbox_obj.get("tags", None): # pylint: disable=no-else-return return False @@ -77,26 +65,23 @@ def _is_tag_present(netbox_obj): elif not netbox_obj["tags"]: return False - # FIXME dgarros, need to reenable this one - # for tag in config.SETTINGS.netbox.model_flag_tags: - # if tag in netbox_obj["tags"]: - # LOGGER.debug( - # "Tag (%s) found for object %s. Marked for diffsync flag assignment.", tag, netbox_obj, - # ) - # return True + for tag in self.settings.model_flag_tags: + if tag in netbox_obj["tags"]: + LOGGER.debug( + "Tag (%s) found for object %s. Marked for diffsync flag assignment.", tag, netbox_obj, + ) + return True return False - @staticmethod - def apply_model_flag(diffsync_obj, netbox_obj): + def apply_model_flag(self, diffsync_obj, netbox_obj): """Helper function for DiffSync Flag assignment.""" - # FIXME need to re-enable this one - # model_flag = config.SETTINGS.netbox.model_flag - - # if model_flag and NetBoxAPIAdapter._is_tag_present(netbox_obj): - # LOGGER.info( - # "DiffSync model flag (%s) applied to object %s", model_flag, netbox_obj, - # ) - # diffsync_obj.model_flags = model_flag + model_flag = self.settings.model_flag + + if model_flag and self._is_tag_present(netbox_obj): + LOGGER.info( + "DiffSync model flag (%s) applied to object %s", model_flag, netbox_obj, + ) + diffsync_obj.model_flags = model_flag return diffsync_obj def _check_netbox_version(self): diff --git a/network_importer/adapters/netbox_api/exceptions.py b/network_importer/adapters/netbox_api/exceptions.py index 0d058557..6a6cacc2 100644 --- a/network_importer/adapters/netbox_api/exceptions.py +++ b/network_importer/adapters/netbox_api/exceptions.py @@ -1,17 +1,4 @@ -"""Custom Exceptions for the NetboxAPIAdapter. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Custom Exceptions for the NetboxAPIAdapter.""" class NetboxObjectNotValid(Exception): diff --git a/network_importer/adapters/netbox_api/inventory.py b/network_importer/adapters/netbox_api/inventory.py index 963a0201..b21f3592 100644 --- a/network_importer/adapters/netbox_api/inventory.py +++ b/network_importer/adapters/netbox_api/inventory.py @@ -1,111 +1,49 @@ -"""Norning Inventory for netbox. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -# Disable too-many-arguments and too-many-locals pylint tests for this file. These are both necessary -# pylint: disable=R0913,R0914,E1101,W0613 - -import copy -from typing import Any, Dict, List, Optional, Union -from pydantic import BaseSettings, ValidationError +"""Nornir Inventory for netbox.""" + +# pylint: disable=no-member,too-many-arguments,unused-argument,too-many-branches,too-many-statements + +from typing import Any, List import requests import pynetbox -from nornir.core.inventory import Defaults, Group, Groups, Host, Hosts, Inventory, ParentGroups, ConnectionOptions +from nornir.core.inventory import Defaults, Group, Groups, Hosts, Inventory, ParentGroups, ConnectionOptions from nornir.core.plugins.inventory import InventoryPluginRegister from network_importer.inventory import NetworkImporterInventory, NetworkImporterHost +from network_importer.utils import build_filter_params -from .config import InventorySettings - -# ------------------------------------------------------------ -# Network Importer Base Dict for device data -# status: -# ok: device is reachable -# fail-ip: Primary IP address not reachable -# fail-access: Unable to access the device management. The IP is reachable, but SSH or API is not enabled or -# responding. -# fail-login: Unable to login authenticate with device -# fail-other: Other general processing error (also catches traps/bug) -# is_reachable: Global Flag to indicate if we are able to connect to a device -# has_config: Indicate if the configuration is present and has been properly imported in Batfish -# ------------------------------------------------------------ - - -def build_filter_params(filter_params, params): - """Update params dict() with filter args in required format for pynetbox. - - Args: - filter_params (list): split string from cli or config - params (dict): object to hold params - """ - for param_value in filter_params: - if "=" not in param_value: - continue - key, value = param_value.split("=", 1) - existing_value = params.get(key) - if existing_value and isinstance(existing_value, list): - params[key].append(value) - elif existing_value and isinstance(existing_value, str): - params[key] = [existing_value, value] - else: - params[key] = value +from network_importer.adapters.netbox_api.settings import InventorySettings class NetboxAPIInventory(NetworkImporterInventory): """Netbox API Inventory Class.""" - # pylint: disable=dangerous-default-value, too-many-branches, too-many-statements - def __init__( - self, - username: Optional[str], - password: Optional[str], - enable: Optional[bool], - supported_platforms: Optional[List[str]], - limit=Optional[str], - params: InventorySettings = InventorySettings(), - **kwargs: Any, - ) -> None: + def __init__(self, *args, **kwargs: Any,) -> None: """Nornir Inventory Plugin for Netbox API.""" + super().__init__(*args, **kwargs) - self.username = username - self.password = password - self.enable = enable - self.supported_platforms = supported_platforms - - self.params = InventorySettings(**params) + self.settings = InventorySettings(**self.settings) # Build Filter based on inventory_params filter and on limit self.filter_parameters = {} - build_filter_params(self.params.filter.split((",")), self.filter_parameters) - if limit: - if "=" not in limit: - self.filter_parameters["name"] = limit + build_filter_params(self.settings.filter.split((",")), self.filter_parameters) + if self.limit: + if "=" not in self.limit: + self.filter_parameters["name"] = self.limit else: - build_filter_params(limit.split((",")), self.filter_parameters) + build_filter_params(self.limit.split((",")), self.filter_parameters) if "exclude" not in self.filter_parameters.keys(): self.filter_parameters["exclude"] = "config_context" # Instantiate netbox session using pynetbox - self.session = pynetbox.api(url=self.params.address, token=self.params.token) - if not self.params.verify_ssl: + self.session = pynetbox.api(url=self.settings.address, token=self.settings.token) + if not self.settings.verify_ssl: session = requests.Session() session.verify = False self.session.http_session = session def load(self): - - # fetch devices from netbox + """Load inventory by fetching devices from netbox.""" if self.filter_parameters: devices: List[pynetbox.modules.dcim.Devices] = self.session.dcim.devices.filter(**self.filter_parameters) else: @@ -132,9 +70,9 @@ def load(self): if self.enable: global_group.connection_options["netmiko"].extras = { "secret": self.password, - "global_delay_factor": self.params.global_delay_factor, - "banner_timeout": self.params.banner_timeout, - "conn_timeout": self.params.conn_timeout, + "global_delay_factor": self.settings.global_delay_factor, + "banner_timeout": self.settings.banner_timeout, + "conn_timeout": self.settings.conn_timeout, } global_group.connection_options["napalm"].extras = {"optional_args": {"secret": self.password}} @@ -164,14 +102,14 @@ def load(self): continue # Add value for IP address - if self.params.use_primary_ip and dev.primary_ip: + if self.settings.use_primary_ip and dev.primary_ip: host.hostname = dev.primary_ip.address.split("/")[0] - elif self.params.use_primary_ip and not dev.primary_ip: + elif self.settings.use_primary_ip and not dev.primary_ip: host.is_reachable = False host.not_reachable_reason = "primary ip not defined in Netbox" - elif not self.params.use_primary_ip and self.params.fqdn: - host.hostname = f"{dev.name}.{self.params.fqdn}" - elif not self.params.use_primary_ip: + elif not self.settings.use_primary_ip and self.settings.fqdn: + host.hostname = f"{dev.name}.{self.settings.fqdn}" + elif not self.settings.use_primary_ip: host.hostname = dev.name else: host.hostname = dev_name diff --git a/network_importer/adapters/netbox_api/models.py b/network_importer/adapters/netbox_api/models.py index 45d33f16..24dcf74c 100644 --- a/network_importer/adapters/netbox_api/models.py +++ b/network_importer/adapters/netbox_api/models.py @@ -1,17 +1,4 @@ -"""Extension of the base Models for the NetboxAPIAdapter. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Extension of the base Models for the NetboxAPIAdapter.""" from typing import Optional import logging diff --git a/network_importer/adapters/netbox_api/config.py b/network_importer/adapters/netbox_api/settings.py similarity index 58% rename from network_importer/adapters/netbox_api/config.py rename to network_importer/adapters/netbox_api/settings.py index a9f4abd3..288c5690 100644 --- a/network_importer/adapters/netbox_api/config.py +++ b/network_importer/adapters/netbox_api/settings.py @@ -1,19 +1,6 @@ -"""Settings definition for the NetboxAPIAdapter. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -from typing import Any, Dict, List, Optional, Union -from pydantic import BaseSettings, ValidationError +"""Settings definition for the NetboxAPIAdapter.""" +from typing import List, Union, Optional +from pydantic import BaseSettings from diffsync import DiffSyncModelFlags @@ -26,13 +13,15 @@ class AdapterSettings(BaseSettings): class InventorySettings(BaseSettings): + """Config settings for the NetboxAPI inventory.""" + address: Optional[str] = "http://localhost" token: Optional[str] = None verify_ssl: Union[bool, str] = True use_primary_ip: Optional[bool] = True fqdn: Optional[str] = None - filter: Optional[str] = None + filter: Optional[str] = "" global_delay_factor: Optional[int] = 5 banner_timeout: Optional[int] = 15 conn_timeout: Optional[int] = 5 diff --git a/network_importer/adapters/netbox_api/tasks.py b/network_importer/adapters/netbox_api/tasks.py index 0562809a..faaf8554 100644 --- a/network_importer/adapters/netbox_api/tasks.py +++ b/network_importer/adapters/netbox_api/tasks.py @@ -1,17 +1,4 @@ -"""Collection of nornir tasks for the NetboxAPIAdapter. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Collection of nornir tasks for the NetboxAPIAdapter.""" import logging import pynetbox @@ -20,7 +7,7 @@ import network_importer.config as config # pylint: disable=import-error -from .config import InventorySettings +from network_importer.adapters.netbox_api.settings import InventorySettings LOGGER = logging.getLogger("network-importer") diff --git a/network_importer/adapters/network_importer/adapter.py b/network_importer/adapters/network_importer/adapter.py index b3865c91..e3b4d77d 100644 --- a/network_importer/adapters/network_importer/adapter.py +++ b/network_importer/adapters/network_importer/adapter.py @@ -1,17 +1,4 @@ -"""Custom Exceptions for the NetworkImporterAdapter. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Custom Exceptions for the NetworkImporterAdapter.""" import re import ipaddress import json diff --git a/network_importer/adapters/network_importer/exceptions.py b/network_importer/adapters/network_importer/exceptions.py index e04bc529..f9fb256f 100644 --- a/network_importer/adapters/network_importer/exceptions.py +++ b/network_importer/adapters/network_importer/exceptions.py @@ -1,17 +1,4 @@ -"""Custom Exceptions for the NetworkImporterAdapter. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Custom Exceptions for the NetworkImporterAdapter.""" class BatfishObjectNotValid(Exception): diff --git a/network_importer/config.py b/network_importer/config.py index 43d1ab96..364c636f 100644 --- a/network_importer/config.py +++ b/network_importer/config.py @@ -35,7 +35,7 @@ "arista_eos": "network_importer.drivers.arista_eos", } -# pylint: disable=too-few-public-methods,global-statement +# pylint: disable=global-statement class BatfishSettings(BaseSettings): @@ -136,7 +136,7 @@ class InventorySettings(BaseSettings): """ inventory_class: str = "NetboxAPIInventory" - inventory_params: Optional[dict] + settings: Optional[dict] supported_platforms: List[str] = list() diff --git a/network_importer/drivers/__init__.py b/network_importer/drivers/__init__.py index 3ae9e569..e5a72aa8 100644 --- a/network_importer/drivers/__init__.py +++ b/network_importer/drivers/__init__.py @@ -1,16 +1,4 @@ -""" -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Main dispatcher for nornir.""" import logging import importlib diff --git a/network_importer/inventory.py b/network_importer/inventory.py index dcdecdb2..c7e54d84 100644 --- a/network_importer/inventory.py +++ b/network_importer/inventory.py @@ -1,26 +1,10 @@ -"""Norning Inventory for netbox. - -(c) 2020 Network To Code - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +"""Base Inventory and Host class for Network Importer.""" # Disable too-many-arguments and too-many-locals pylint tests for this file. These are both necessary # pylint: disable=R0913,R0914,E1101,W0613 -import copy -from typing import Any, Dict, List, Optional, Union, List +from typing import Dict, List, Optional -# from nornir.core.deserializer.inventory import Inventory, HostsDict -from nornir.core.inventory import Defaults, Group, Groups, Host, Hosts, Inventory, ParentGroups -from nornir.core.plugins.inventory import InventoryPluginRegister +from nornir.core.inventory import Host class NetworkImporterHost(Host): @@ -36,7 +20,7 @@ class NetworkImporterHost(Host): """ Valid Statuses ok: device is reachable fail-ip: Primary IP address not reachable - fail-access: Unable to access the device management. + fail-access: Unable to access the device management. The IP is reachable, but SSH or API is not enabled or responding. fail-login: Unable to login authenticate with device fail-other: Other general processing error (also catches traps/bug) @@ -49,16 +33,25 @@ class NetworkImporterHost(Host): class NetworkImporterInventory: + """Base inventory class for the Network Importer.""" + def __init__( self, - username: Optional[str], - password: Optional[str], - enable: Optional[bool], - supported_platforms: Optional[List[str]], - limit: Optional[str], - params: Optional[Dict] = None, + username: Optional[str] = None, + password: Optional[str] = None, + enable: Optional[bool] = None, + supported_platforms: Optional[List[str]] = None, + limit: Optional[str] = None, + settings: Optional[Dict] = None, ): - pass + """Initialize class and store all top level arguments locally.""" + self.username = username + self.password = password + self.enable = enable + self.supported_platforms = supported_platforms + self.limit = limit + + self.settings = settings # ----------------------------------------------------------------- diff --git a/network_importer/main.py b/network_importer/main.py index cdc45a53..4899fb71 100644 --- a/network_importer/main.py +++ b/network_importer/main.py @@ -54,7 +54,7 @@ def __init__(self, check_mode=False, nornir=None): @timeit def build_inventory(self, limit=None): """Build the inventory for the Network Importer in Nornir format.""" - + # pylint: disable=import-outside-toplevel # Load build-in Inventories as needed if config.SETTINGS.inventory.inventory_class == "NetboxAPIInventory": from network_importer.adapters.netbox_api.inventory import NetboxAPIInventory @@ -76,7 +76,7 @@ def build_inventory(self, limit=None): "enable": config.SETTINGS.network.enable, "supported_platforms": config.SETTINGS.inventory.supported_platforms, "limit": limit, - "params": config.SETTINGS.inventory.inventory_params, + "settings": config.SETTINGS.inventory.settings, }, }, ) diff --git a/network_importer/processors/get_neighbors.py b/network_importer/processors/get_neighbors.py index 6d1d7645..ad6366d2 100644 --- a/network_importer/processors/get_neighbors.py +++ b/network_importer/processors/get_neighbors.py @@ -32,8 +32,6 @@ # Match the incorrectly capitalized interface names JUNOS_INTERFACE_PATTERN = re.compile(r"^(Xe|Ge|Et|Em|Sxe|Fte|Me|Fc|Xle)-\d+/\d+/\d+[.:]*\d*$") -# pylint: disable=too-few-public-methods - # ----------------------------------------------------------------- # Inventory Filter functions diff --git a/network_importer/processors/get_vlans.py b/network_importer/processors/get_vlans.py index 0d4bef24..a22f7c19 100644 --- a/network_importer/processors/get_vlans.py +++ b/network_importer/processors/get_vlans.py @@ -21,8 +21,6 @@ LOGGER = logging.getLogger("network-importer") -# pylint: disable=too-few-public-methods - # ------------------------------------------------------------ # Standard model to return for get_vlans diff --git a/network_importer/tasks.py b/network_importer/tasks.py index 0950c62c..d10477b4 100644 --- a/network_importer/tasks.py +++ b/network_importer/tasks.py @@ -15,8 +15,10 @@ import logging import os -import yaml +import socket +from typing import Optional, List +import yaml from nornir.core.task import Result, Task import network_importer.config as config @@ -71,20 +73,14 @@ def device_save_hostvars(task: Task) -> Result: # # hostvars_str = template.render(dev_facts) -import socket -from typing import Optional, List - -from nornir.core.task import Result, Task - - def tcp_ping(task: Task, ports: List[int], timeout: int = 2, host: Optional[str] = None) -> Result: - """ - Tests connection to a tcp port and tries to establish a three way - handshake. To be used for network discovery or testing. + """Tests connection to a tcp port and tries to establish a three way handshake. + Arguments: ports (list of int): tcp ports to ping timeout (int, optional): defaults to 2 host (string, optional): defaults to ``hostname`` + Returns: Result object with the following attributes set: * result (``dict``): Contains port numbers as keys with True/False as values @@ -92,7 +88,6 @@ def tcp_ping(task: Task, ports: List[int], timeout: int = 2, host: Optional[str] Code copied from https://github.com/nornir-automation/nornir/blob/v2.5.0/nornir/plugins/tasks/networking/tcp_ping.py Need to open a PR to https://github.com/nornir-automation/nornir_utils """ - if isinstance(ports, int): ports = [ports] @@ -107,18 +102,18 @@ def tcp_ping(task: Task, ports: List[int], timeout: int = 2, host: Optional[str] result = {} for port in ports: - s = socket.socket() - s.settimeout(timeout) + skt = socket.socket() + skt.settimeout(timeout) try: - status = s.connect_ex((host, port)) - if status == 0: + status = skt.connect_ex((host, port)) + if status == 0: # pylint: disable=simplifiable-if-statement connection = True else: connection = False except (socket.gaierror, socket.timeout, socket.error): connection = False finally: - s.close() + skt.close() result[port] = connection return Result(host=task.host, result=result) diff --git a/network_importer/utils.py b/network_importer/utils.py index 95be0cfc..b7165b09 100644 --- a/network_importer/utils.py +++ b/network_importer/utils.py @@ -205,3 +205,23 @@ def expand_vlans_list(vlans: str) -> list: LOGGER.debug("expand_vlans_list() Unable to convert %s as integer .. skipping (%s)", vlan_, exc) return sorted(clean_vlans_list) + + +def build_filter_params(filter_params, params): + """Update params dict() with filter args in required format for pynetbox/pynautobot. + + Args: + filter_params (list): split string from cli or config + params (dict): object to hold params + """ + for param_value in filter_params: + if "=" not in param_value: + continue + key, value = param_value.split("=", 1) + existing_value = params.get(key) + if existing_value and isinstance(existing_value, list): + params[key].append(value) + elif existing_value and isinstance(existing_value, str): + params[key] = [existing_value, value] + else: + params[key] = value diff --git a/pyproject.toml b/pyproject.toml index 4a28618b..e88d0f2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,8 @@ no-docstring-rgx="^(_|test_)" disable = """, line-too-long, bad-continuation, - duplicate-code + duplicate-code, + too-few-public-methods """ [tool.pylint.miscellaneous] diff --git a/tests/unit/adapters/netbox_api/fixtures/inventory/devices.json b/tests/unit/adapters/netbox_api/fixtures/inventory/devices.json new file mode 100644 index 00000000..1f614f0d --- /dev/null +++ b/tests/unit/adapters/netbox_api/fixtures/inventory/devices.json @@ -0,0 +1,364 @@ +{ + "count": 6, + "next": null, + "previous": null, + "results": [ + { + "id": 7, + "name": "amarillo", + "display_name": "amarillo", + "device_type": { + "id": 1, + "url": "http://localhost/api/dcim/device-types/1/", + "manufacturer": { + "id": 1, + "url": "http://localhost/api/dcim/manufacturers/1/", + "name": "juniper", + "slug": "juniper" + }, + "model": "junos", + "slug": "junos", + "display_name": "juniper junos" + }, + "device_role": { + "id": 1, + "url": "http://localhost/api/dcim/device-roles/1/", + "name": "spine", + "slug": "spine" + }, + "tenant": null, + "platform": null, + "serial": "", + "asset_tag": null, + "site": { + "id": 1, + "url": "http://localhost/api/dcim/sites/1/", + "name": "ni_example_01", + "slug": "ni_example_01" + }, + "rack": null, + "position": null, + "face": null, + "parent_device": null, + "status": { + "value": "active", + "label": "Active", + "id": 1 + }, + "primary_ip": null, + "primary_ip4": null, + "primary_ip6": null, + "cluster": null, + "virtual_chassis": null, + "vc_position": null, + "vc_priority": null, + "comments": "", + "local_context_data": null, + "tags": [], + "custom_fields": {}, + "config_context": {}, + "created": "2020-02-10", + "last_updated": "2020-02-11T22:35:16.062047Z" + }, + { + "id": 13, + "name": "austin", + "display_name": "austin", + "device_type": { + "id": 2, + "url": "http://localhost/api/dcim/device-types/2/", + "manufacturer": { + "id": 2, + "url": "http://localhost/api/dcim/manufacturers/2/", + "name": "cisco", + "slug": "cisco" + }, + "model": "iosxr", + "slug": "iosxr", + "display_name": "cisco iosxr" + }, + "device_role": { + "id": 1, + "url": "http://localhost/api/dcim/device-roles/1/", + "name": "spine", + "slug": "spine" + }, + "tenant": null, + "platform": { + "id": 2, + "url": "http://localhost/api/dcim/platforms/2/", + "name": "ios", + "slug": "ios" + }, + "serial": "", + "asset_tag": null, + "site": { + "id": 1, + "url": "http://localhost/api/dcim/sites/1/", + "name": "ni_example_01", + "slug": "ni_example_01" + }, + "rack": null, + "position": null, + "face": null, + "parent_device": null, + "status": { + "value": "active", + "label": "Active", + "id": 1 + }, + "primary_ip": null, + "primary_ip4": null, + "primary_ip6": null, + "cluster": null, + "virtual_chassis": null, + "vc_position": null, + "vc_priority": null, + "comments": "", + "local_context_data": null, + "tags": [], + "custom_fields": {}, + "config_context": {}, + "created": "2020-02-11", + "last_updated": "2020-02-11T00:03:10.763181Z" + }, + { + "id": 16, + "name": "dallas", + "display_name": "dallas", + "device_type": { + "id": 2, + "url": "http://localhost/api/dcim/device-types/2/", + "manufacturer": { + "id": 2, + "url": "http://localhost/api/dcim/manufacturers/2/", + "name": "cisco", + "slug": "cisco" + }, + "model": "iosxr", + "slug": "iosxr", + "display_name": "cisco iosxr" + }, + "device_role": { + "id": 2, + "url": "http://localhost/api/dcim/device-roles/2/", + "name": "leaf", + "slug": "leaf" + }, + "tenant": null, + "platform": { + "id": 3, + "url": "http://localhost/api/dcim/platforms/3/", + "name": "nxos", + "slug": "nxos" + }, + "serial": "", + "asset_tag": null, + "site": { + "id": 1, + "url": "http://localhost/api/dcim/sites/1/", + "name": "ni_example_01", + "slug": "ni_example_01" + }, + "rack": null, + "position": null, + "face": null, + "parent_device": null, + "status": { + "value": "active", + "label": "Active", + "id": 1 + }, + "primary_ip": null, + "primary_ip4": null, + "primary_ip6": null, + "cluster": null, + "virtual_chassis": null, + "vc_position": null, + "vc_priority": null, + "comments": "", + "local_context_data": null, + "tags": [], + "custom_fields": {}, + "config_context": {}, + "created": "2020-02-11", + "last_updated": "2020-02-11T00:03:11.077543Z" + }, + { + "id": 14, + "name": "el-paso", + "display_name": "el-paso", + "device_type": { + "id": 2, + "url": "http://localhost/api/dcim/device-types/2/", + "manufacturer": { + "id": 2, + "url": "http://localhost/api/dcim/manufacturers/2/", + "name": "cisco", + "slug": "cisco" + }, + "model": "iosxr", + "slug": "iosxr", + "display_name": "cisco iosxr" + }, + "device_role": { + "id": 1, + "url": "http://localhost/api/dcim/device-roles/1/", + "name": "spine", + "slug": "spine" + }, + "tenant": null, + "platform": { + "id": 4, + "url": "http://localhost/api/dcim/platforms/4/", + "name": "asa", + "slug": "asa" + }, + "serial": "", + "asset_tag": null, + "site": { + "id": 1, + "url": "http://localhost/api/dcim/sites/1/", + "name": "ni_example_01", + "slug": "ni_example_01" + }, + "rack": null, + "position": null, + "face": null, + "parent_device": null, + "status": { + "value": "active", + "label": "Active", + "id": 1 + }, + "primary_ip": null, + "primary_ip4": null, + "primary_ip6": null, + "cluster": null, + "virtual_chassis": null, + "vc_position": null, + "vc_priority": null, + "comments": "", + "local_context_data": null, + "tags": [], + "custom_fields": {}, + "config_context": {}, + "created": "2020-02-11", + "last_updated": "2020-02-11T00:03:10.768410Z" + }, + { + "id": 17, + "name": "houston", + "display_name": "houston", + "device_type": { + "id": 3, + "url": "http://localhost/api/dcim/device-types/3/", + "manufacturer": { + "id": 3, + "url": "http://localhost/api/dcim/manufacturers/3/", + "name": "arista", + "slug": "arista" + }, + "model": "eos", + "slug": "eos", + "display_name": "arista eos" + }, + "device_role": { + "id": 2, + "url": "http://localhost/api/dcim/device-roles/2/", + "name": "leaf", + "slug": "leaf" + }, + "tenant": null, + "platform": null, + "serial": "", + "asset_tag": null, + "site": { + "id": 1, + "url": "http://localhost/api/dcim/sites/1/", + "name": "ni_example_01", + "slug": "ni_example_01" + }, + "rack": null, + "position": null, + "face": null, + "parent_device": null, + "status": { + "value": "active", + "label": "Active", + "id": 1 + }, + "primary_ip": null, + "primary_ip4": null, + "primary_ip6": null, + "cluster": null, + "virtual_chassis": null, + "vc_position": null, + "vc_priority": null, + "comments": "", + "local_context_data": null, + "tags": [], + "custom_fields": {}, + "config_context": {}, + "created": "2020-02-11", + "last_updated": "2020-02-11T00:03:12.036015Z" + }, + { + "id": 15, + "name": "san-antonio", + "display_name": "san-antonio", + "device_type": { + "id": 2, + "url": "http://localhost/api/dcim/device-types/2/", + "manufacturer": { + "id": 2, + "url": "http://localhost/api/dcim/manufacturers/2/", + "name": "cisco", + "slug": "cisco" + }, + "model": "iosxr", + "slug": "iosxr", + "display_name": "cisco iosxr" + }, + "device_role": { + "id": 1, + "url": "http://localhost/api/dcim/device-roles/1/", + "name": "spine", + "slug": "spine" + }, + "tenant": null, + "platform": null, + "serial": "", + "asset_tag": null, + "site": { + "id": 1, + "url": "http://localhost/api/dcim/sites/1/", + "name": "ni_example_01", + "slug": "ni_example_01" + }, + "rack": null, + "position": null, + "face": null, + "parent_device": null, + "status": { + "value": "active", + "label": "Active", + "id": 1 + }, + "primary_ip": null, + "primary_ip4": null, + "primary_ip6": null, + "cluster": null, + "virtual_chassis": null, + "vc_position": null, + "vc_priority": null, + "comments": "", + "local_context_data": null, + "tags": [], + "custom_fields": {}, + "config_context": {}, + "created": "2020-02-11", + "last_updated": "2020-02-11T00:03:11.055575Z" + } + ] +} \ No newline at end of file diff --git a/tests/unit/adapters/netbox_api/fixtures/inventory/filtered_devices.json b/tests/unit/adapters/netbox_api/fixtures/inventory/filtered_devices.json new file mode 100644 index 00000000..c981ba8a --- /dev/null +++ b/tests/unit/adapters/netbox_api/fixtures/inventory/filtered_devices.json @@ -0,0 +1,64 @@ +{ + "count": 6, + "next": null, + "previous": null, + "results": [ + { + "id": 14, + "name": "el-paso", + "display_name": "el-paso", + "device_type": { + "id": 2, + "url": "http://localhost/api/dcim/device-types/2/", + "manufacturer": { + "id": 2, + "url": "http://localhost/api/dcim/manufacturers/2/", + "name": "cisco", + "slug": "cisco" + }, + "model": "iosxr", + "slug": "iosxr", + "display_name": "cisco iosxr" + }, + "device_role": { + "id": 1, + "url": "http://localhost/api/dcim/device-roles/1/", + "name": "spine", + "slug": "spine" + }, + "tenant": null, + "platform": null, + "serial": "", + "asset_tag": null, + "site": { + "id": 1, + "url": "http://localhost/api/dcim/sites/1/", + "name": "ni_example_01", + "slug": "ni_example_01" + }, + "rack": null, + "position": null, + "face": null, + "parent_device": null, + "status": { + "value": "active", + "label": "Active", + "id": 1 + }, + "primary_ip": null, + "primary_ip4": null, + "primary_ip6": null, + "cluster": null, + "virtual_chassis": null, + "vc_position": null, + "vc_priority": null, + "comments": "", + "local_context_data": null, + "tags": [], + "custom_fields": {}, + "config_context": {}, + "created": "2020-02-11", + "last_updated": "2020-02-11T00:03:10.768410Z" + } + ] +} \ No newline at end of file diff --git a/tests/unit/adapters/netbox_api/fixtures/inventory/platforms.json b/tests/unit/adapters/netbox_api/fixtures/inventory/platforms.json new file mode 100644 index 00000000..b156046c --- /dev/null +++ b/tests/unit/adapters/netbox_api/fixtures/inventory/platforms.json @@ -0,0 +1,52 @@ +{ + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "id": 4, + "name": "cisco_asa", + "slug": "cisco_asa", + "manufacturer": { + "id": 1, + "url": "http://localhost/api/dcim/manufacturers/1/", + "name": "Cisco", + "slug": "cisco" + }, + "napalm_driver": "", + "napalm_args": null, + "device_count": 0, + "virtualmachine_count": null + }, + { + "id": 2, + "name": "ios", + "slug": "ios", + "manufacturer": { + "id": 1, + "url": "http://localhost/api/dcim/manufacturers/1/", + "name": "Cisco", + "slug": "cisco" + }, + "napalm_driver": "ios_naplam", + "napalm_args": null, + "device_count": 0, + "virtualmachine_count": null + }, + { + "id": 3, + "name": "nxos", + "slug": "nxos", + "manufacturer": { + "id": 1, + "url": "http://localhost/api/dcim/manufacturers/1/", + "name": "Cisco", + "slug": "cisco" + }, + "napalm_driver": "nxos_naplam", + "napalm_args": null, + "device_count": 0, + "virtualmachine_count": null + } + ] + } \ No newline at end of file diff --git a/tests/unit/adapters/netbox_api/fixtures/inventory/stack_devices.json b/tests/unit/adapters/netbox_api/fixtures/inventory/stack_devices.json new file mode 100644 index 00000000..8eb08a4c --- /dev/null +++ b/tests/unit/adapters/netbox_api/fixtures/inventory/stack_devices.json @@ -0,0 +1,217 @@ +{ + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": 5, + "name": "test_dev1_2", + "display_name": "test_dev1_2", + "device_type": { + "id": 1, + "url": "http://localhost:32768/api/dcim/device-types/1/", + "manufacturer": { + "id": 1, + "url": "http://localhost:32768/api/dcim/manufacturers/1/", + "name": "cisco", + "slug": "cisco" + }, + "model": "ios", + "slug": "ios", + "display_name": "cisco ios" + }, + "device_role": { + "id": 1, + "url": "http://localhost:32768/api/dcim/device-roles/1/", + "name": "access", + "slug": "access" + }, + "tenant": null, + "platform": { + "id": 1, + "url": "http://localhost:32768/api/dcim/platforms/1/", + "name": "cisco_ios", + "slug": "cisco_ios" + }, + "serial": "", + "asset_tag": null, + "site": { + "id": 1, + "url": "http://localhost:32768/api/dcim/sites/1/", + "name": "test_site", + "slug": "test_site" + }, + "rack": null, + "position": null, + "face": null, + "parent_device": null, + "status": { + "value": 1, + "label": "Active" + }, + "primary_ip": null, + "primary_ip4": null, + "primary_ip6": null, + "cluster": null, + "virtual_chassis": { + "id": 1, + "url": "http://localhost:32768/api/dcim/virtual-chassis/1/", + "master": { + "id": 4, + "url": "http://localhost:32768/api/dcim/devices/4/", + "name": "test_dev1", + "display_name": "test_dev1" + } + }, + "vc_position": 2, + "vc_priority": 14, + "comments": "", + "local_context_data": null, + "tags": [], + "custom_fields": {}, + "config_context": {}, + "created": "2020-02-19", + "last_updated": "2020-02-19T19:35:45.568873Z" + }, + { + "id": 4, + "name": "test_dev1", + "display_name": "test_dev1", + "device_type": { + "id": 1, + "url": "http://localhost:32768/api/dcim/device-types/1/", + "manufacturer": { + "id": 1, + "url": "http://localhost:32768/api/dcim/manufacturers/1/", + "name": "cisco", + "slug": "cisco" + }, + "model": "ios", + "slug": "ios", + "display_name": "cisco ios" + }, + "device_role": { + "id": 1, + "url": "http://localhost:32768/api/dcim/device-roles/1/", + "name": "access", + "slug": "access" + }, + "tenant": null, + "platform": { + "id": 1, + "url": "http://localhost:32768/api/dcim/platforms/1/", + "name": "cisco_ios", + "slug": "cisco_ios" + }, + "serial": "", + "asset_tag": null, + "site": { + "id": 1, + "url": "http://localhost:32768/api/dcim/sites/1/", + "name": "test_site", + "slug": "test_site" + }, + "rack": null, + "position": null, + "face": null, + "parent_device": null, + "status": { + "value": 1, + "label": "Active" + }, + "primary_ip": { + "id": 3, + "url": "http://localhost:32768/api/ipam/ip-addresses/3/", + "family": 4, + "address": "10.50.3.2/32" + }, + "primary_ip4": { + "id": 3, + "url": "http://localhost:32768/api/ipam/ip-addresses/3/", + "family": 4, + "address": "10.50.3.2/32" + }, + "primary_ip6": null, + "cluster": null, + "virtual_chassis": { + "id": 1, + "url": "http://localhost:32768/api/dcim/virtual-chassis/1/", + "master": { + "id": 4, + "url": "http://localhost:32768/api/dcim/devices/4/", + "name": "test_dev1", + "display_name": "test_dev1" + } + }, + "vc_position": 1, + "vc_priority": 15, + "comments": "", + "local_context_data": null, + "tags": [], + "custom_fields": {}, + "config_context": {}, + "created": "2020-02-19", + "last_updated": "2020-02-19T19:38:47.113568Z" + }, + { + "id": 10, + "name": "amarillo", + "display_name": "amarillo", + "device_type": { + "id": 2, + "url": "http://localhost:32768/api/dcim/device-types/2/", + "manufacturer": { + "id": 3, + "url": "http://localhost:32768/api/dcim/manufacturers/3/", + "name": "juniper", + "slug": "juniper" + }, + "model": "junos", + "slug": "junos", + "display_name": "juniper junos" + }, + "device_role": { + "id": 2, + "url": "http://localhost:32768/api/dcim/device-roles/2/", + "name": "spine", + "slug": "spine" + }, + "tenant": null, + "platform": null, + "serial": "", + "asset_tag": null, + "site": { + "id": 3, + "url": "http://localhost:32768/api/dcim/sites/3/", + "name": "ni_example_01", + "slug": "ni_example_01" + }, + "rack": null, + "position": null, + "face": null, + "parent_device": null, + "status": { + "value": 1, + "label": "Active" + }, + "primary_ip": null, + "primary_ip4": null, + "primary_ip6": null, + "cluster": null, + "virtual_chassis": { + "id": 1, + "url": "http://localhost:32768/api/dcim/virtual-chassis/1/", + "master": null + }, + "vc_position": null, + "vc_priority": null, + "comments": "", + "local_context_data": null, + "tags": [], + "custom_fields": {}, + "config_context": {}, + "created": "2020-02-19", + "last_updated": "2020-02-19T19:31:54.203479Z" + } + ] +} \ No newline at end of file diff --git a/tests/unit/test_inventory.py b/tests/unit/adapters/netbox_api/test_inventory.py similarity index 84% rename from tests/unit/test_inventory.py rename to tests/unit/adapters/netbox_api/test_inventory.py index 7c15e9ac..6f89cc3d 100644 --- a/tests/unit/test_inventory.py +++ b/tests/unit/adapters/netbox_api/test_inventory.py @@ -15,7 +15,7 @@ from os import path import yaml -from network_importer.inventory import NetboxInventory +from network_importer.adapters.netbox_api.inventory import NetboxAPIInventory HERE = path.abspath(path.dirname(__file__)) FIXTURES = "fixtures/inventory" @@ -38,7 +38,7 @@ def test_nb_inventory_all(requests_mock): data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json")) requests_mock.get("http://mock/api/dcim/platforms/", json=data2) - inv = NetboxInventory(nb_url="http://mock", nb_token="12349askdnfanasdf") # nosec + inv = NetboxAPIInventory(settings=dict(address="http://mock", token="12349askdnfanasdf")).load() # nosec assert len(inv.hosts.keys()) == 6 assert "austin" in inv.hosts.keys() @@ -68,11 +68,9 @@ def test_nb_inventory_filtered(requests_mock): data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json")) requests_mock.get("http://mock/api/dcim/platforms/", json=data2) - inv_filtered = NetboxInventory( # nosec - nb_url="http://mock", # nosec - nb_token="12349askdnfanasdf", # nosec - filter_parameters={"name": "el-paso"}, # nosec - ) # nosec + inv_filtered = NetboxAPIInventory( + limit="el-paso", settings=dict(address="http://mock", token="12349askdnfanasdf",) # nosec + ).load() # nosec assert len(inv_filtered.hosts.keys()) == 1 assert "el-paso" in inv_filtered.hosts.keys() @@ -96,11 +94,9 @@ def test_nb_inventory_exclude(requests_mock): data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json")) requests_mock.get("http://mock/api/dcim/platforms/", json=data2) - inv = NetboxInventory( # nosec - nb_url="http://mock", # nosec - nb_token="12349askdnfanasdf", # nosec - filter_parameters={"exclude": "platform"}, # nosec - ) # nosec + inv = NetboxAPIInventory( + settings=dict(address="http://mock", token="12349askdnfanasdf", filter="exclude=platform",) # nosec # nosec + ).load() # nosec assert len(inv.hosts.keys()) == 6 @@ -122,7 +118,7 @@ def test_nb_inventory_virtual_chassis(requests_mock): data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json")) requests_mock.get("http://mock/api/dcim/platforms/", json=data2) - inv = NetboxInventory(nb_url="http://mock", nb_token="12349askdnfanasdf") # nosec + inv = NetboxAPIInventory(settings=dict(address="http://mock", token="12349askdnfanasdf")).load() # nosec assert len(inv.hosts.keys()) == 2 assert "test_dev1_2" not in inv.hosts.keys() @@ -148,19 +144,19 @@ def test_nb_inventory_supported_platforms(requests_mock): data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json")) requests_mock.get("http://mock/api/dcim/platforms/", json=data2) - inv = NetboxInventory( # nosec - nb_url="http://mock", # nosec - nb_token="12349askdnfanasdf", # nosec + inv = NetboxAPIInventory( supported_platforms=["ios", "nxos"], # nosec - ) # nosec + settings=dict(address="http://mock", token="12349askdnfanasdf") # nosec # nosec + # nosec + ).load() # nosec assert len(inv.hosts.keys()) == 2 assert "austin" in inv.hosts.keys() assert "dallas" in inv.hosts.keys() - inv = NetboxInventory( # nosec - nb_url="http://mock", nb_token="12349askdnfanasdf", supported_platforms=["ios"], # nosec # nosec # nosec - ) # nosec + inv = NetboxAPIInventory( # nosec + supported_platforms=["ios"], settings=dict(address="http://mock", token="12349askdnfanasdf") # nosec + ).load() # nosec assert len(inv.hosts.keys()) == 1 assert "austin" in inv.hosts.keys() diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index c736ab47..8c1fb5ae 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -208,7 +208,7 @@ def diff_children_nyc_dev1(): @pytest.fixture def netbox_api_empty(): """Provide an instance of NetBoxAPIAdapter with pynetbox initiliazed.""" - diffsync = NetBoxAPIAdapter(nornir=None, config={}) + diffsync = NetBoxAPIAdapter(nornir=None, settings={}) diffsync.netbox = pynetbox.api(url="http://mock", token="1234567890") return diffsync @@ -217,7 +217,7 @@ def netbox_api_empty(): @pytest.fixture def netbox_api_base(): """Provide an instance of NetBoxAPIAdapter with pynetbox initiliazed.""" - diffsync = NetBoxAPIAdapter(nornir=None, config={}) + diffsync = NetBoxAPIAdapter(nornir=None, settings={}) diffsync.netbox = pynetbox.api(url="http://mock", token="1234567890") diffsync.add(NetboxSite(name="HQ", remote_id=10)) @@ -231,7 +231,7 @@ def netbox_api_base(): @pytest.fixture def network_importer_base(): """Provide an instance of NetworkImporterAdapter with pynetbox initiliazed.""" - diffsync = NetworkImporterAdapter(nornir=None, config={}) + diffsync = NetworkImporterAdapter(nornir=None, settings={}) diffsync.add(Site(name="HQ")) diffsync.add(Device(name="HQ-CORE-SW02", site_name="HQ", remote_id=29)) @@ -256,7 +256,7 @@ def empty_netbox_query(): @pytest.fixture def nautobot_api_base(): """Provide an instance of NautobotAPIAdapter with pynautoboot initiliazed.""" - diffsync = NautobotAPIAdapter(nornir=None, config={}) + diffsync = NautobotAPIAdapter(nornir=None, settings={}) diffsync.nautobot = pynautobot.api(url="http://mock_nautobot", token="1234567890") diffsync.add(NautobotSite(name="HQ", remote_id="a325e477-62fe-47f0-8b67-acf411b1868f")) @@ -273,8 +273,8 @@ def nautobot_api_base(): @pytest.fixture def nautobot_api_empty(): - """Provide an instance of NetBoxAPIAdapter with pynetbox initiliazed.""" - diffsync = NautobotAPIAdapter(nornir=None, config={}) + """Provide an instance of NautobotAPIAdapter with pynautobot initiliazed.""" + diffsync = NautobotAPIAdapter(nornir=None, settings={}) diffsync.nautobot = pynautobot.api(url="http://mock", token="1234567890") return diffsync diff --git a/tests/unit/processors/test_get_neighbors.py b/tests/unit/processors/test_get_neighbors.py index f14a0db2..059dc932 100644 --- a/tests/unit/processors/test_get_neighbors.py +++ b/tests/unit/processors/test_get_neighbors.py @@ -40,11 +40,11 @@ def nornir(requests_mock): requests_mock.get("http://mock/api/dcim/platforms/", json=data2) nornir = InitNornir( - core={"num_workers": 1}, + runner={"plugin": "threaded", "options": {"num_workers": 1}}, logging={"enabled": False}, inventory={ - "plugin": "network_importer.inventory.NetboxInventory", - "options": {"nb_url": "http://mock", "nb_token": "12349askdnfanasdf"}, + "plugin": "NetboxAPIInventory", + "options": {"settings": {"address": "http://mock", "token": "12349askdnfanasdf"}}, }, ) From 27487e88f59bda55e18c874c1d4214d19c9f001f Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Mon, 10 May 2021 09:16:58 -0400 Subject: [PATCH 06/13] Remvoe sot_params to settings --- network_importer/config.py | 5 +++-- network_importer/main.py | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/network_importer/config.py b/network_importer/config.py index 364c636f..f80adaa7 100644 --- a/network_importer/config.py +++ b/network_importer/config.py @@ -117,9 +117,10 @@ class AdaptersSettings(BaseSettings): """Settings definition for the Adapters section of the configuration.""" network_class: str = "network_importer.adapters.network_importer.adapter.NetworkImporterAdapter" + network_settings: Optional[dict] + sot_class: str = "network_importer.adapters.netbox_api.adapter.NetBoxAPIAdapter" - sot_params: Optional[dict] - network_params: Optional[dict] + sot_settings: Optional[dict] class DriversSettings(BaseSettings): diff --git a/network_importer/main.py b/network_importer/main.py index 4899fb71..6bcfe2c2 100644 --- a/network_importer/main.py +++ b/network_importer/main.py @@ -111,11 +111,11 @@ def init(self, limit=None): # -------------------------------------------------------- LOGGER.info("Import SOT Model") sot_path = config.SETTINGS.adapters.sot_class.split(".") - sot_params = config.SETTINGS.adapters.sot_params + sot_settings = config.SETTINGS.adapters.sot_settings sot_adapter = getattr(importlib.import_module(".".join(sot_path[0:-1])), sot_path[-1]) try: - self.sot = sot_adapter(nornir=self.nornir, config=sot_params) + self.sot = sot_adapter(nornir=self.nornir, settings=sot_settings) self.sot.load() except AdapterLoadFatalError as exc: LOGGER.error("Unable to load the SOT Adapter : %s", exc) @@ -123,12 +123,12 @@ def init(self, limit=None): LOGGER.info("Import Network Model") network_adapter_path = config.SETTINGS.adapters.network_class.split(".") - network_adapter_params = config.SETTINGS.adapters.network_params + network_adapter_settings = config.SETTINGS.adapters.network_settings network_adapter = getattr( importlib.import_module(".".join(network_adapter_path[0:-1])), network_adapter_path[-1] ) try: - self.network = network_adapter(nornir=self.nornir, config=network_adapter_params) + self.network = network_adapter(nornir=self.nornir, settings=network_adapter_settings) self.network.load() except AdapterLoadFatalError as exc: LOGGER.error("Unable to load the SOT Adapter : %s", exc) From 92d99e29bfd915dcdd19e944fcc99dba286436df Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Mon, 10 May 2021 09:19:59 -0400 Subject: [PATCH 07/13] Rename inventory params to inventory settings --- network_importer/adapters/nautobot_api/adapter.py | 6 +++--- network_importer/adapters/netbox_api/adapter.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/network_importer/adapters/nautobot_api/adapter.py b/network_importer/adapters/nautobot_api/adapter.py index dc87271a..385beca0 100644 --- a/network_importer/adapters/nautobot_api/adapter.py +++ b/network_importer/adapters/nautobot_api/adapter.py @@ -97,10 +97,10 @@ def _check_nautobot_version(self): def load(self): """Initialize pynautobot and load all data from nautobot in the local cache.""" - inventory_params = InventorySettings(**config.SETTINGS.inventory.inventory_params) - self.nautobot = pynautobot.api(url=inventory_params.address, token=inventory_params.token) + inventory_settings = InventorySettings(**config.SETTINGS.inventory.settings) + self.nautobot = pynautobot.api(url=inventory_settings.address, token=inventinventory_settingsory_params.token) - if not inventory_params.verify_ssl: + if not inventory_settings.verify_ssl: self.nautobot.http_session.verify_ssl = False else: self.nautobot.http_session.verify_ssl = True diff --git a/network_importer/adapters/netbox_api/adapter.py b/network_importer/adapters/netbox_api/adapter.py index 5b05c903..127379fe 100644 --- a/network_importer/adapters/netbox_api/adapter.py +++ b/network_importer/adapters/netbox_api/adapter.py @@ -103,7 +103,7 @@ def _check_netbox_version(self): def load(self): """Initialize pynetbox and load all data from netbox in the local cache.""" - inventory_settings = InventorySettings(**config.SETTINGS.inventory.inventory_params) + inventory_settings = InventorySettings(**config.SETTINGS.inventory.settings) self.netbox = pynetbox.api(url=inventory_settings.address, token=inventory_settings.token) if not inventory_settings.verify_ssl: From 05a992e88d83447aec98e6a286054748a35d6f29 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Mon, 10 May 2021 09:40:24 -0400 Subject: [PATCH 08/13] Fix var name --- network_importer/adapters/nautobot_api/adapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network_importer/adapters/nautobot_api/adapter.py b/network_importer/adapters/nautobot_api/adapter.py index 385beca0..8066d983 100644 --- a/network_importer/adapters/nautobot_api/adapter.py +++ b/network_importer/adapters/nautobot_api/adapter.py @@ -98,7 +98,7 @@ def _check_nautobot_version(self): def load(self): """Initialize pynautobot and load all data from nautobot in the local cache.""" inventory_settings = InventorySettings(**config.SETTINGS.inventory.settings) - self.nautobot = pynautobot.api(url=inventory_settings.address, token=inventinventory_settingsory_params.token) + self.nautobot = pynautobot.api(url=inventory_settings.address, token=inventory_settings.token) if not inventory_settings.verify_ssl: self.nautobot.http_session.verify_ssl = False From d7130c1aba54b4e43382317318f0d7957c622d77 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Mon, 10 May 2021 11:07:58 -0400 Subject: [PATCH 09/13] ensure settings is defined --- network_importer/adapters/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/network_importer/adapters/base.py b/network_importer/adapters/base.py index 2fb767d5..a154983c 100644 --- a/network_importer/adapters/base.py +++ b/network_importer/adapters/base.py @@ -26,7 +26,10 @@ def __init__(self, nornir, settings): def _validate_settings(self, settings): """Load and validate the configuration based on the settings_class.""" if self.settings_class: - return self.settings_class(**settings) # pylint: disable=not-callable + if settings and isinstance(dict, settings): + return self.settings_class(**settings) # pylint: disable=not-callable + else: + return self.settings_class() # pylint: disable=not-callable return settings From caca44d25585122c13571685347025b2b24928c5 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Mon, 10 May 2021 11:27:12 -0400 Subject: [PATCH 10/13] simplify if else statement --- network_importer/adapters/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network_importer/adapters/base.py b/network_importer/adapters/base.py index a154983c..f17961fb 100644 --- a/network_importer/adapters/base.py +++ b/network_importer/adapters/base.py @@ -28,8 +28,8 @@ def _validate_settings(self, settings): if self.settings_class: if settings and isinstance(dict, settings): return self.settings_class(**settings) # pylint: disable=not-callable - else: - return self.settings_class() # pylint: disable=not-callable + + return self.settings_class() # pylint: disable=not-callable return settings From 5d00be7f4a75a4682f52bf0128f449077272a9c8 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Mon, 10 May 2021 14:00:06 -0400 Subject: [PATCH 11/13] Update name of NetBox inventory class to match adapter name --- network_importer/adapters/nautobot_api/inventory.py | 2 +- network_importer/adapters/nautobot_api/tasks.py | 6 +++--- network_importer/adapters/netbox_api/inventory.py | 6 +++--- network_importer/adapters/netbox_api/tasks.py | 6 +++--- network_importer/config.py | 2 +- network_importer/main.py | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/network_importer/adapters/nautobot_api/inventory.py b/network_importer/adapters/nautobot_api/inventory.py index 0cac4803..d74d0581 100644 --- a/network_importer/adapters/nautobot_api/inventory.py +++ b/network_importer/adapters/nautobot_api/inventory.py @@ -42,7 +42,7 @@ def __init__( self.settings = self.settings_class(**settings) - # Build Filter based on inventory_params filter and on limit + # Build Filter based on inventory_settings filter and on limit self.filter_parameters = {} build_filter_params(self.settings.filter.split((",")), self.filter_parameters) if limit: diff --git a/network_importer/adapters/nautobot_api/tasks.py b/network_importer/adapters/nautobot_api/tasks.py index a06c6d66..cf4fbd62 100644 --- a/network_importer/adapters/nautobot_api/tasks.py +++ b/network_importer/adapters/nautobot_api/tasks.py @@ -24,11 +24,11 @@ def query_device_info_from_nautobot(task: Task) -> Result: Returns: Result: Nornir Result object with the result in a dict format """ - inventory_params = InventorySettings(**config.SETTINGS.inventory.inventory_params) - nautobot = pynautobot.api(url=inventory_params.address, token=inventory_params.token) + inventory_settings = InventorySettings(**config.SETTINGS.inventory.settings) + nautobot = pynautobot.api(url=inventory_settings.address, token=inventory_settings.token) # Check for SSL Verification, set it to false if not. Else set to true - if not inventory_params.verify_ssl: + if not inventory_settings.verify_ssl: # No manual session is required for this, pynautobot will automatically create one nautobot.http_session.verify = False else: diff --git a/network_importer/adapters/netbox_api/inventory.py b/network_importer/adapters/netbox_api/inventory.py index b21f3592..e4ad145e 100644 --- a/network_importer/adapters/netbox_api/inventory.py +++ b/network_importer/adapters/netbox_api/inventory.py @@ -14,7 +14,7 @@ from network_importer.adapters.netbox_api.settings import InventorySettings -class NetboxAPIInventory(NetworkImporterInventory): +class NetBoxAPIInventory(NetworkImporterInventory): """Netbox API Inventory Class.""" def __init__(self, *args, **kwargs: Any,) -> None: @@ -23,7 +23,7 @@ def __init__(self, *args, **kwargs: Any,) -> None: self.settings = InventorySettings(**self.settings) - # Build Filter based on inventory_params filter and on limit + # Build Filter based on inventory_settings filter and on limit self.filter_parameters = {} build_filter_params(self.settings.filter.split((",")), self.filter_parameters) if self.limit: @@ -152,4 +152,4 @@ def load(self): return Inventory(hosts=hosts, groups=groups, defaults=defaults) -InventoryPluginRegister.register("NetboxAPIInventory", NetboxAPIInventory) +InventoryPluginRegister.register("NetBoxAPIInventory", NetBoxAPIInventory) diff --git a/network_importer/adapters/netbox_api/tasks.py b/network_importer/adapters/netbox_api/tasks.py index faaf8554..e352600d 100644 --- a/network_importer/adapters/netbox_api/tasks.py +++ b/network_importer/adapters/netbox_api/tasks.py @@ -26,10 +26,10 @@ def query_device_info_from_netbox(task: Task) -> Result: Returns: Result: Nornir Result object with the result in a dict format """ - inventory_params = InventorySettings(**config.SETTINGS.inventory.inventory_params) - netbox = pynetbox.api(url=inventory_params.address, token=inventory_params.token) + inventory_settings = InventorySettings(**config.SETTINGS.inventory.settings) + netbox = pynetbox.api(url=inventory_settings.address, token=inventory_settings.token) - if not inventory_params.verify_ssl: + if not inventory_settings.verify_ssl: session = requests.Session() session.verify = False netbox.http_session = session diff --git a/network_importer/config.py b/network_importer/config.py index f80adaa7..13137d80 100644 --- a/network_importer/config.py +++ b/network_importer/config.py @@ -136,7 +136,7 @@ class InventorySettings(BaseSettings): if the use_primary_ip flag is disabled, the inventory will try to use the hostname to the device """ - inventory_class: str = "NetboxAPIInventory" + inventory_class: str = "NetBoxAPIInventory" settings: Optional[dict] supported_platforms: List[str] = list() diff --git a/network_importer/main.py b/network_importer/main.py index 6bcfe2c2..657a0ab4 100644 --- a/network_importer/main.py +++ b/network_importer/main.py @@ -56,10 +56,10 @@ def build_inventory(self, limit=None): """Build the inventory for the Network Importer in Nornir format.""" # pylint: disable=import-outside-toplevel # Load build-in Inventories as needed - if config.SETTINGS.inventory.inventory_class == "NetboxAPIInventory": - from network_importer.adapters.netbox_api.inventory import NetboxAPIInventory + if config.SETTINGS.inventory.inventory_class == "NetBoxAPIInventory": + from network_importer.adapters.netbox_api.inventory import NetBoxAPIInventory - InventoryPluginRegister.register("NetboxAPIInventory", NetboxAPIInventory) + InventoryPluginRegister.register("NetBoxAPIInventory", NetBoxAPIInventory) elif config.SETTINGS.inventory.inventory_class == "NautobotAPIInventory": from network_importer.adapters.nautobot_api.inventory import NautobotAPIInventory From e657132cbc80004b5379b609e282af33fab74212 Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Mon, 10 May 2021 16:49:45 -0400 Subject: [PATCH 12/13] Update NetBoxAPIInventory in unit tests --- tests/unit/adapters/netbox_api/test_inventory.py | 14 +++++++------- tests/unit/processors/test_get_neighbors.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/unit/adapters/netbox_api/test_inventory.py b/tests/unit/adapters/netbox_api/test_inventory.py index 6f89cc3d..c4a9a252 100644 --- a/tests/unit/adapters/netbox_api/test_inventory.py +++ b/tests/unit/adapters/netbox_api/test_inventory.py @@ -15,7 +15,7 @@ from os import path import yaml -from network_importer.adapters.netbox_api.inventory import NetboxAPIInventory +from network_importer.adapters.netbox_api.inventory import NetBoxAPIInventory HERE = path.abspath(path.dirname(__file__)) FIXTURES = "fixtures/inventory" @@ -38,7 +38,7 @@ def test_nb_inventory_all(requests_mock): data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json")) requests_mock.get("http://mock/api/dcim/platforms/", json=data2) - inv = NetboxAPIInventory(settings=dict(address="http://mock", token="12349askdnfanasdf")).load() # nosec + inv = NetBoxAPIInventory(settings=dict(address="http://mock", token="12349askdnfanasdf")).load() # nosec assert len(inv.hosts.keys()) == 6 assert "austin" in inv.hosts.keys() @@ -68,7 +68,7 @@ def test_nb_inventory_filtered(requests_mock): data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json")) requests_mock.get("http://mock/api/dcim/platforms/", json=data2) - inv_filtered = NetboxAPIInventory( + inv_filtered = NetBoxAPIInventory( limit="el-paso", settings=dict(address="http://mock", token="12349askdnfanasdf",) # nosec ).load() # nosec @@ -94,7 +94,7 @@ def test_nb_inventory_exclude(requests_mock): data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json")) requests_mock.get("http://mock/api/dcim/platforms/", json=data2) - inv = NetboxAPIInventory( + inv = NetBoxAPIInventory( settings=dict(address="http://mock", token="12349askdnfanasdf", filter="exclude=platform",) # nosec # nosec ).load() # nosec @@ -118,7 +118,7 @@ def test_nb_inventory_virtual_chassis(requests_mock): data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json")) requests_mock.get("http://mock/api/dcim/platforms/", json=data2) - inv = NetboxAPIInventory(settings=dict(address="http://mock", token="12349askdnfanasdf")).load() # nosec + inv = NetBoxAPIInventory(settings=dict(address="http://mock", token="12349askdnfanasdf")).load() # nosec assert len(inv.hosts.keys()) == 2 assert "test_dev1_2" not in inv.hosts.keys() @@ -144,7 +144,7 @@ def test_nb_inventory_supported_platforms(requests_mock): data2 = yaml.safe_load(open(f"{HERE}/{FIXTURES}/platforms.json")) requests_mock.get("http://mock/api/dcim/platforms/", json=data2) - inv = NetboxAPIInventory( + inv = NetBoxAPIInventory( supported_platforms=["ios", "nxos"], # nosec settings=dict(address="http://mock", token="12349askdnfanasdf") # nosec # nosec # nosec @@ -154,7 +154,7 @@ def test_nb_inventory_supported_platforms(requests_mock): assert "austin" in inv.hosts.keys() assert "dallas" in inv.hosts.keys() - inv = NetboxAPIInventory( # nosec + inv = NetBoxAPIInventory( # nosec supported_platforms=["ios"], settings=dict(address="http://mock", token="12349askdnfanasdf") # nosec ).load() # nosec diff --git a/tests/unit/processors/test_get_neighbors.py b/tests/unit/processors/test_get_neighbors.py index 059dc932..6f2f46eb 100644 --- a/tests/unit/processors/test_get_neighbors.py +++ b/tests/unit/processors/test_get_neighbors.py @@ -43,7 +43,7 @@ def nornir(requests_mock): runner={"plugin": "threaded", "options": {"num_workers": 1}}, logging={"enabled": False}, inventory={ - "plugin": "NetboxAPIInventory", + "plugin": "NetBoxAPIInventory", "options": {"settings": {"address": "http://mock", "token": "12349askdnfanasdf"}}, }, ) From 91c9d641bd014762308ffe4b4272a5e7767f45db Mon Sep 17 00:00:00 2001 From: Damien Garros Date: Mon, 10 May 2021 17:28:24 -0400 Subject: [PATCH 13/13] Update network_importer/adapters/nautobot_api/inventory.py Co-authored-by: Josh VanDeraa --- network_importer/adapters/nautobot_api/inventory.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/network_importer/adapters/nautobot_api/inventory.py b/network_importer/adapters/nautobot_api/inventory.py index d74d0581..7a51f5ca 100644 --- a/network_importer/adapters/nautobot_api/inventory.py +++ b/network_importer/adapters/nautobot_api/inventory.py @@ -57,9 +57,7 @@ def __init__( # Instantiate nautobot session using pynautobot self.session = pynautobot.api(url=self.settings.address, token=self.settings.token) if not self.settings.verify_ssl: - session = requests.Session() - session.verify = False - self.session.http_session = session + self.session.http_session.verify = False def load(self): """Load inventory by fetching devices from nautobot."""