Module netmiko.ssh_autodetect
The ssh_autodetect module is used to auto-detect the netmiko device_type to use to further initiate a new SSH connection with a remote host. This auto-detection is based on a unique class called SSHDetect.
Notes
The SSHDetect class is instantiated using the same parameters than a standard Netmiko connection (see the netmiko.ssh_dispatacher.ConnectHandler function). The only acceptable value for the 'device_type' argument is 'autodetect'.
The auto-detection is solely based on SSH_MAPPER_BASE. The keys are the name of the 'device_type' supported for auto-detection and the value is another dictionary describing how to handle the auto-detection.
- "cmd" : The command to send to the remote device. The command output must not require paging.
- "search_patterns" : A list of regex to compare with the output of the command
- "priority" : An integer (0-99) which specifies the confidence of the match above
- "dispatch" : The function to call to try the autodetection (per default SSHDetect._autodetect_std)
Examples
Auto-detection section
>>> from netmiko.ssh_autodetect import SSHDetect
>>> from netmiko.ssh_dispatcher import ConnectHandler
>>> remote_device = {'device_type': 'autodetect',
'host': 'remote.host',
'username': 'test',
'password': 'foo'}
>>> guesser = SSHDetect(**remote_device)
>>> best_match = guesser.autodetect()
>>> print(best_match) # Name of the best device_type to use further
>>> print(guesser.potential_matches) # Dictionary of the whole matching result
Netmiko connection creation section
>>> remote_device['device_type'] = best_match
>>> connection = ConnectHandler(**remote_device)
Expand source code
"""
The ssh_autodetect module is used to auto-detect the netmiko device_type to use to further initiate
a new SSH connection with a remote host. This auto-detection is based on a unique class called
**SSHDetect**.
Notes
-----
The **SSHDetect** class is instantiated using the same parameters than a standard Netmiko
connection (see the *netmiko.ssh_dispatacher.ConnectHandler* function). The only acceptable value
for the 'device_type' argument is 'autodetect'.
The auto-detection is solely based on *SSH_MAPPER_BASE*. The keys are the name of
the 'device_type' supported for auto-detection and the value is another dictionary describing how
to handle the auto-detection.
* "cmd" : The command to send to the remote device. **The command output must not require paging.**
* "search_patterns" : A list of regex to compare with the output of the command
* "priority" : An integer (0-99) which specifies the confidence of the match above
* "dispatch" : The function to call to try the autodetection (per default SSHDetect._autodetect_std)
Examples
--------
# Auto-detection section
>>> from netmiko.ssh_autodetect import SSHDetect
>>> from netmiko.ssh_dispatcher import ConnectHandler
>>> remote_device = {'device_type': 'autodetect',
'host': 'remote.host',
'username': 'test',
'password': 'foo'}
>>> guesser = SSHDetect(**remote_device)
>>> best_match = guesser.autodetect()
>>> print(best_match) # Name of the best device_type to use further
>>> print(guesser.potential_matches) # Dictionary of the whole matching result
# Netmiko connection creation section
>>> remote_device['device_type'] = best_match
>>> connection = ConnectHandler(**remote_device)
"""
from typing import Any, List, Optional, Union, Dict
import re
import time
import paramiko
from netmiko.ssh_dispatcher import ConnectHandler
from netmiko.base_connection import BaseConnection
# 'dispatch' key is the SSHDetect method to call. dispatch key will be popped off dictionary
# remaining keys indicate kwargs that will be passed to dispatch method.
# Note, the 'cmd' needs to avoid output paging.
SSH_MAPPER_DICT = {
"alcatel_aos": {
"cmd": "show system",
"search_patterns": [r"Alcatel-Lucent"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"alcatel_sros": {
"cmd": "show version",
"search_patterns": ["Nokia", "Alcatel"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"allied_telesis_awplus": {
"cmd": "show version",
"search_patterns": ["AlliedWare Plus"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"apresia_aeos": {
"cmd": "show system",
"search_patterns": ["Apresia"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"arista_eos": {
"cmd": "show version",
"search_patterns": [r"Arista", r"vEOS"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"ciena_saos": {
"cmd": "software show",
"search_patterns": [r"saos"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"cisco_asa": {
"cmd": "show version",
"search_patterns": [r"Cisco Adaptive Security Appliance", r"Cisco ASA"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"cisco_ftd": {
"cmd": "show version",
"search_patterns": [r"Cisco Firepower"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"cisco_ios": {
"cmd": "show version",
"search_patterns": [
"Cisco IOS Software",
"Cisco Internetwork Operating System Software",
],
"priority": 99,
"dispatch": "_autodetect_std",
},
"cisco_xe": {
"cmd": "show version",
"search_patterns": [r"Cisco IOS XE Software"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"cisco_nxos": {
"cmd": "show version",
"search_patterns": [r"Cisco Nexus Operating System", r"NX-OS"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"cisco_xr": {
"cmd": "show version",
"search_patterns": [r"Cisco IOS XR"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"cisco_xr_2": {
"cmd": "show version brief",
"search_patterns": [r"Cisco IOS XR"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"dell_force10": {
"cmd": "show version",
"search_patterns": [r"Real Time Operating System Software"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"dell_os9": {
"cmd": "show system",
"search_patterns": [
r"Dell Application Software Version: 9",
r"Dell Networking OS Version : 9",
],
"priority": 99,
"dispatch": "_autodetect_std",
},
"dell_os10": {
"cmd": "show version",
"search_patterns": [r"Dell EMC Networking OS10.Enterprise"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"dell_powerconnect": {
"cmd": "show system",
"search_patterns": [r"PowerConnect"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"f5_tmsh": {
"cmd": "show sys version",
"search_patterns": [r"BIG-IP"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"f5_linux": {
"cmd": "cat /etc/issue",
"search_patterns": [r"BIG-IP"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"hp_comware": {
"cmd": "display version",
"search_patterns": ["HPE Comware", "HP Comware"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"hp_procurve": {
"cmd": "show version",
"search_patterns": [r"Image stamp.*/code/build"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"huawei": {
"cmd": "display version",
"search_patterns": [
r"Huawei Technologies",
r"Huawei Versatile Routing Platform Software",
],
"priority": 99,
"dispatch": "_autodetect_std",
},
"juniper_junos": {
"cmd": "show version",
"search_patterns": [
r"JUNOS Software Release",
r"JUNOS .+ Software",
r"JUNOS OS Kernel",
r"JUNOS Base Version",
],
"priority": 99,
"dispatch": "_autodetect_std",
},
"linux": {
"cmd": "uname -a",
"search_patterns": [r"Linux"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"ericsson_ipos": {
"cmd": "show version",
"search_patterns": [r"Ericsson IPOS Version"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"extreme_exos": {
"cmd": "show version",
"search_patterns": [r"ExtremeXOS"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"extreme_netiron": {
"cmd": "show version",
"search_patterns": [r"(NetIron|MLX)"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"extreme_slx": {
"cmd": "show version",
"search_patterns": [
r"SLX-OS Operating System",
],
"priority": 99,
"dispatch": "_autodetect_std",
},
"extreme_tierra": {
"cmd": "show version",
"search_patterns": [r"TierraOS Software"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"ubiquiti_edgeswitch": {
"cmd": "show version",
"search_patterns": [r"EdgeSwitch"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"cisco_wlc": {
"cmd": "",
"dispatch": "_autodetect_remote_version",
"search_patterns": [r"CISCO_WLC"],
"priority": 99,
},
"cisco_wlc_85": {
"cmd": "show inventory",
"dispatch": "_autodetect_std",
"search_patterns": [r"Cisco.*Wireless.*Controller"],
"priority": 99,
},
"mellanox_mlnxos": {
"cmd": "show version",
"search_patterns": [r"Onyx", r"SX_PPC_M460EX"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"yamaha": {
"cmd": "show copyright",
"search_patterns": [r"Yamaha Corporation"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"fortinet": {
"cmd": "get system status",
"search_patterns": [r"FortiOS", r"FortiGate"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"paloalto_panos": {
"cmd": "show system info",
"search_patterns": [r"model:\s+PA"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"supermicro_smis": {
"cmd": "show system info",
"search_patterns": [r"Super Micro Computer"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"flexvnf": {
"cmd": "show system package-info",
"search_patterns": [r"Versa FlexVNF"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"cisco_viptela": {
"cmd": "show system status",
"search_patterns": [r"Viptela, Inc"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"oneaccess_oneos": {
"cmd": "show version",
"search_patterns": [r"OneOS"],
"priority": 99,
"dispatch": "_autodetect_std",
},
"netgear_prosafe": {
"cmd": "show version",
"search_patterns": [r"ProSAFE"],
"priority": 99,
"dispatch": "_autodetect_std",
},
}
# Sort SSH_MAPPER_DICT such that the most common commands are first
cmd_count: Dict[str, int] = {}
for k, v in SSH_MAPPER_DICT.items():
my_cmd = v["cmd"]
assert isinstance(my_cmd, str)
count = cmd_count.setdefault(my_cmd, 0)
cmd_count[my_cmd] = count + 1
cmd_count = {k: v for k, v in sorted(cmd_count.items(), key=lambda item: item[1])}
# SSH_MAPPER_BASE is a list
SSH_MAPPER_BASE = sorted(
SSH_MAPPER_DICT.items(), key=lambda item: int(cmd_count[str(item[1]["cmd"])])
)
SSH_MAPPER_BASE.reverse()
class SSHDetect(object):
"""
The SSHDetect class tries to automatically guess the device type running on the SSH remote end.
Be careful that the kwargs 'device_type' must be set to 'autodetect', otherwise it won't work at
all.
Parameters
----------
*args : list
The same *args that you might provide to the netmiko.ssh_dispatcher.ConnectHandler.
*kwargs : dict
The same *kwargs that you might provide to the netmiko.ssh_dispatcher.ConnectHandler.
Attributes
----------
connection : netmiko.terminal_server.TerminalServerSSH
A basic connection to the remote SSH end.
potential_matches: dict
Dict of (device_type, accuracy) that is populated through an interaction with the
remote end.
Methods
-------
autodetect()
Try to determine the device type.
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""
Constructor of the SSHDetect class
"""
if kwargs["device_type"] != "autodetect":
raise ValueError("The connection device_type must be 'autodetect'")
# Always set cmd_verify to False for autodetect
kwargs["global_cmd_verify"] = False
self.connection = ConnectHandler(*args, **kwargs)
# Add additional sleep to let the login complete.
time.sleep(3)
# Call the _test_channel_read() in base to clear initial data
output = BaseConnection._test_channel_read(self.connection)
self.initial_buffer = output
self.potential_matches: Dict[str, int] = {}
self._results_cache: Dict[str, str] = {}
def autodetect(self) -> Union[str, None]:
"""
Try to guess the best 'device_type' based on patterns defined in SSH_MAPPER_BASE
Returns
-------
best_match : str or None
The device type that is currently the best to use to interact with the device
"""
for device_type, autodetect_dict in SSH_MAPPER_BASE:
tmp_dict = autodetect_dict.copy()
call_method = tmp_dict.pop("dispatch")
assert isinstance(call_method, str)
autodetect_method = getattr(self, call_method)
accuracy = autodetect_method(**tmp_dict)
if accuracy:
self.potential_matches[device_type] = accuracy
if accuracy >= 99: # Stop the loop as we are sure of our match
best_match = sorted(
self.potential_matches.items(), key=lambda t: t[1], reverse=True
)
# WLC needs two different auto-dectect solutions
if "cisco_wlc_85" in best_match[0]:
best_match[0] = ("cisco_wlc", 99)
# IOS XR needs two different auto-dectect solutions
if "cisco_xr_2" in best_match[0]:
best_match[0] = ("cisco_xr", 99)
self.connection.disconnect()
return best_match[0][0]
if not self.potential_matches:
self.connection.disconnect()
return None
best_match = sorted(
self.potential_matches.items(), key=lambda t: t[1], reverse=True
)
self.connection.disconnect()
return best_match[0][0]
def _send_command(self, cmd: str = "") -> str:
"""
Handle reading/writing channel directly. It is also sanitizing the output received.
Parameters
----------
cmd : str, optional
The command to send to the remote device (default : "", just send a new line)
Returns
-------
output : str
The output from the command sent
"""
self.connection.write_channel(cmd + "\n")
time.sleep(1)
output = self.connection.read_channel_timing(last_read=6.0)
output = self.connection.strip_backspaces(output)
return output
def _send_command_wrapper(self, cmd: str) -> str:
"""
Send command to the remote device with a caching feature to avoid sending the same command
twice based on the SSH_MAPPER_BASE dict cmd key.
Parameters
----------
cmd : str
The command to send to the remote device after checking cache.
Returns
-------
response : str
The response from the remote device.
"""
cached_results = self._results_cache.get(cmd)
if not cached_results:
response = self._send_command(cmd)
self._results_cache[cmd] = response
return response
else:
return cached_results
def _autodetect_remote_version(
self,
search_patterns: Optional[List[str]] = None,
re_flags: int = re.IGNORECASE,
priority: int = 99,
**kwargs: Any
) -> int:
"""
Method to try auto-detect the device type, by matching a regular expression on the reported
remote version of the SSH server.
Parameters
----------
search_patterns : list
A list of regular expression to look for in the reported remote SSH version
(default: None).
re_flags: re.flags, optional
Any flags from the python re module to modify the regular expression (default: re.I).
priority: int, optional
The confidence the match is right between 0 and 99 (default: 99).
"""
invalid_responses = [r"^$"]
if not search_patterns:
return 0
try:
remote_conn = self.connection.remote_conn
assert isinstance(remote_conn, paramiko.Channel)
assert remote_conn.transport is not None
remote_version = remote_conn.transport.remote_version
for pattern in invalid_responses:
match = re.search(pattern, remote_version, flags=re.I)
if match:
return 0
for pattern in search_patterns:
match = re.search(pattern, remote_version, flags=re_flags)
if match:
return priority
except Exception:
return 0
return 0
def _autodetect_std(
self,
cmd: str = "",
search_patterns: Optional[List[str]] = None,
re_flags: int = re.IGNORECASE,
priority: int = 99,
) -> int:
"""
Standard method to try to auto-detect the device type. This method will be called for each
device_type present in SSH_MAPPER_BASE dict ('dispatch' key). It will attempt to send a
command and match some regular expression from the ouput for each entry in SSH_MAPPER_BASE
('cmd' and 'search_pattern' keys).
Parameters
----------
cmd : str
The command to send to the remote device after checking cache.
search_patterns : list
A list of regular expression to look for in the command's output (default: None).
re_flags: re.flags, optional
Any flags from the python re module to modify the regular expression (default: re.I).
priority: int, optional
The confidence the match is right between 0 and 99 (default: 99).
"""
invalid_responses = [
r"% Invalid input detected",
r"syntax error, expecting",
r"Error: Unrecognized command",
r"%Error",
r"command not found",
r"Syntax Error: unexpected argument",
r"% Unrecognized command found at",
]
if not cmd or not search_patterns:
return 0
try:
# _send_command_wrapper will use already cached results if available
response = self._send_command_wrapper(cmd)
# Look for error conditions in output
for pattern in invalid_responses:
match = re.search(pattern, response, flags=re.I)
if match:
return 0
for pattern in search_patterns:
match = re.search(pattern, response, flags=re_flags)
if match:
return priority
except Exception:
return 0
return 0
Classes
class SSHDetect (*args: Any, **kwargs: Any)
-
The SSHDetect class tries to automatically guess the device type running on the SSH remote end. Be careful that the kwargs 'device_type' must be set to 'autodetect', otherwise it won't work at all.
Parameters
*args
:list
- The same *args that you might provide to the netmiko.ssh_dispatcher.ConnectHandler.
*kwargs
:dict
- The same *kwargs that you might provide to the netmiko.ssh_dispatcher.ConnectHandler.
Attributes
connection
:TerminalServerSSH
- A basic connection to the remote SSH end.
potential_matches
:dict
- Dict of (device_type, accuracy) that is populated through an interaction with the remote end.
Methods
autodetect() Try to determine the device type.
Constructor of the SSHDetect class
Expand source code
class SSHDetect(object): """ The SSHDetect class tries to automatically guess the device type running on the SSH remote end. Be careful that the kwargs 'device_type' must be set to 'autodetect', otherwise it won't work at all. Parameters ---------- *args : list The same *args that you might provide to the netmiko.ssh_dispatcher.ConnectHandler. *kwargs : dict The same *kwargs that you might provide to the netmiko.ssh_dispatcher.ConnectHandler. Attributes ---------- connection : netmiko.terminal_server.TerminalServerSSH A basic connection to the remote SSH end. potential_matches: dict Dict of (device_type, accuracy) that is populated through an interaction with the remote end. Methods ------- autodetect() Try to determine the device type. """ def __init__(self, *args: Any, **kwargs: Any) -> None: """ Constructor of the SSHDetect class """ if kwargs["device_type"] != "autodetect": raise ValueError("The connection device_type must be 'autodetect'") # Always set cmd_verify to False for autodetect kwargs["global_cmd_verify"] = False self.connection = ConnectHandler(*args, **kwargs) # Add additional sleep to let the login complete. time.sleep(3) # Call the _test_channel_read() in base to clear initial data output = BaseConnection._test_channel_read(self.connection) self.initial_buffer = output self.potential_matches: Dict[str, int] = {} self._results_cache: Dict[str, str] = {} def autodetect(self) -> Union[str, None]: """ Try to guess the best 'device_type' based on patterns defined in SSH_MAPPER_BASE Returns ------- best_match : str or None The device type that is currently the best to use to interact with the device """ for device_type, autodetect_dict in SSH_MAPPER_BASE: tmp_dict = autodetect_dict.copy() call_method = tmp_dict.pop("dispatch") assert isinstance(call_method, str) autodetect_method = getattr(self, call_method) accuracy = autodetect_method(**tmp_dict) if accuracy: self.potential_matches[device_type] = accuracy if accuracy >= 99: # Stop the loop as we are sure of our match best_match = sorted( self.potential_matches.items(), key=lambda t: t[1], reverse=True ) # WLC needs two different auto-dectect solutions if "cisco_wlc_85" in best_match[0]: best_match[0] = ("cisco_wlc", 99) # IOS XR needs two different auto-dectect solutions if "cisco_xr_2" in best_match[0]: best_match[0] = ("cisco_xr", 99) self.connection.disconnect() return best_match[0][0] if not self.potential_matches: self.connection.disconnect() return None best_match = sorted( self.potential_matches.items(), key=lambda t: t[1], reverse=True ) self.connection.disconnect() return best_match[0][0] def _send_command(self, cmd: str = "") -> str: """ Handle reading/writing channel directly. It is also sanitizing the output received. Parameters ---------- cmd : str, optional The command to send to the remote device (default : "", just send a new line) Returns ------- output : str The output from the command sent """ self.connection.write_channel(cmd + "\n") time.sleep(1) output = self.connection.read_channel_timing(last_read=6.0) output = self.connection.strip_backspaces(output) return output def _send_command_wrapper(self, cmd: str) -> str: """ Send command to the remote device with a caching feature to avoid sending the same command twice based on the SSH_MAPPER_BASE dict cmd key. Parameters ---------- cmd : str The command to send to the remote device after checking cache. Returns ------- response : str The response from the remote device. """ cached_results = self._results_cache.get(cmd) if not cached_results: response = self._send_command(cmd) self._results_cache[cmd] = response return response else: return cached_results def _autodetect_remote_version( self, search_patterns: Optional[List[str]] = None, re_flags: int = re.IGNORECASE, priority: int = 99, **kwargs: Any ) -> int: """ Method to try auto-detect the device type, by matching a regular expression on the reported remote version of the SSH server. Parameters ---------- search_patterns : list A list of regular expression to look for in the reported remote SSH version (default: None). re_flags: re.flags, optional Any flags from the python re module to modify the regular expression (default: re.I). priority: int, optional The confidence the match is right between 0 and 99 (default: 99). """ invalid_responses = [r"^$"] if not search_patterns: return 0 try: remote_conn = self.connection.remote_conn assert isinstance(remote_conn, paramiko.Channel) assert remote_conn.transport is not None remote_version = remote_conn.transport.remote_version for pattern in invalid_responses: match = re.search(pattern, remote_version, flags=re.I) if match: return 0 for pattern in search_patterns: match = re.search(pattern, remote_version, flags=re_flags) if match: return priority except Exception: return 0 return 0 def _autodetect_std( self, cmd: str = "", search_patterns: Optional[List[str]] = None, re_flags: int = re.IGNORECASE, priority: int = 99, ) -> int: """ Standard method to try to auto-detect the device type. This method will be called for each device_type present in SSH_MAPPER_BASE dict ('dispatch' key). It will attempt to send a command and match some regular expression from the ouput for each entry in SSH_MAPPER_BASE ('cmd' and 'search_pattern' keys). Parameters ---------- cmd : str The command to send to the remote device after checking cache. search_patterns : list A list of regular expression to look for in the command's output (default: None). re_flags: re.flags, optional Any flags from the python re module to modify the regular expression (default: re.I). priority: int, optional The confidence the match is right between 0 and 99 (default: 99). """ invalid_responses = [ r"% Invalid input detected", r"syntax error, expecting", r"Error: Unrecognized command", r"%Error", r"command not found", r"Syntax Error: unexpected argument", r"% Unrecognized command found at", ] if not cmd or not search_patterns: return 0 try: # _send_command_wrapper will use already cached results if available response = self._send_command_wrapper(cmd) # Look for error conditions in output for pattern in invalid_responses: match = re.search(pattern, response, flags=re.I) if match: return 0 for pattern in search_patterns: match = re.search(pattern, response, flags=re_flags) if match: return priority except Exception: return 0 return 0
Methods
def autodetect(self) ‑> Optional[str]
-
Try to guess the best 'device_type' based on patterns defined in SSH_MAPPER_BASE
Returns
best_match
:str
orNone
- The device type that is currently the best to use to interact with the device
Expand source code
def autodetect(self) -> Union[str, None]: """ Try to guess the best 'device_type' based on patterns defined in SSH_MAPPER_BASE Returns ------- best_match : str or None The device type that is currently the best to use to interact with the device """ for device_type, autodetect_dict in SSH_MAPPER_BASE: tmp_dict = autodetect_dict.copy() call_method = tmp_dict.pop("dispatch") assert isinstance(call_method, str) autodetect_method = getattr(self, call_method) accuracy = autodetect_method(**tmp_dict) if accuracy: self.potential_matches[device_type] = accuracy if accuracy >= 99: # Stop the loop as we are sure of our match best_match = sorted( self.potential_matches.items(), key=lambda t: t[1], reverse=True ) # WLC needs two different auto-dectect solutions if "cisco_wlc_85" in best_match[0]: best_match[0] = ("cisco_wlc", 99) # IOS XR needs two different auto-dectect solutions if "cisco_xr_2" in best_match[0]: best_match[0] = ("cisco_xr", 99) self.connection.disconnect() return best_match[0][0] if not self.potential_matches: self.connection.disconnect() return None best_match = sorted( self.potential_matches.items(), key=lambda t: t[1], reverse=True ) self.connection.disconnect() return best_match[0][0]