Hello community,

here is the log from the commit of package python-netmiko for openSUSE:Factory 
checked in at 2020-03-27 21:57:59
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-netmiko (Old)
 and      /work/SRC/openSUSE:Factory/.python-netmiko.new.3160 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-netmiko"

Fri Mar 27 21:57:59 2020 rev:8 rq:788896 version:3.1.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-netmiko/python-netmiko.changes    
2020-01-16 18:22:34.105019870 +0100
+++ /work/SRC/openSUSE:Factory/.python-netmiko.new.3160/python-netmiko.changes  
2020-03-27 21:58:41.882841635 +0100
@@ -1,0 +2,13 @@
+Mon Mar 23 19:35:36 UTC 2020 - Martin Hauke <[email protected]>
+
+- Update to version 3.1.0:
+  New Platforms / Transports
+  * D-Link DGS/DES
+  * WatchGuard Firebox
+  * Sophos SG Firewalls
+  * Huawei Telnet Support
+  Bug Fixes/Enhancements
+  * Add cmd_verify argument to send_command
+  * Add global_cmd_verify argument to ConnectHandler
+
+-------------------------------------------------------------------

Old:
----
  netmiko-3.0.0.tar.gz

New:
----
  netmiko-3.1.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-netmiko.spec ++++++
--- /var/tmp/diff_new_pack.ewDe2C/_old  2020-03-27 21:58:44.198842982 +0100
+++ /var/tmp/diff_new_pack.ewDe2C/_new  2020-03-27 21:58:44.214842992 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-netmiko
 #
-# Copyright (c) 2020 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define skip_python2 1
 Name:           python-netmiko
-Version:        3.0.0
+Version:        3.1.0
 Release:        0
 Summary:        Multi-vendor library to simplify Paramiko SSH connections to 
network devices
 License:        MIT

++++++ netmiko-3.0.0.tar.gz -> netmiko-3.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/PKG-INFO new/netmiko-3.1.0/PKG-INFO
--- old/netmiko-3.0.0/PKG-INFO  2020-01-16 05:32:35.000000000 +0100
+++ new/netmiko-3.1.0/PKG-INFO  2020-03-23 17:49:02.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: netmiko
-Version: 3.0.0
+Version: 3.1.0
 Summary: Multi-vendor library to simplify Paramiko SSH connections to network 
devices
 Home-page: https://github.com/ktbyers/netmiko
 Author: Kirk Byers
@@ -203,5 +203,6 @@
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Description-Content-Type: text/markdown
 Provides-Extra: test
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/PLATFORMS.md 
new/netmiko-3.1.0/PLATFORMS.md
--- old/netmiko-3.0.0/PLATFORMS.md      2020-01-06 05:30:41.000000000 +0100
+++ new/netmiko-3.1.0/PLATFORMS.md      2020-03-23 17:47:24.000000000 +0100
@@ -35,6 +35,7 @@
 - MikroTik RouterOS
 - MikroTik SwitchOS
 - NetApp cDOT
+- Nokia/Alcatel SR OS
 - OneAccess
 - Palo Alto PAN-OS
 - Pluribus
@@ -69,5 +70,6 @@
 - Nokia/Alcatel SR-OS
 - QuantaMesh
 - Rad ETX
+- Sophos SFOS
 - Versa Networks FlexVNF
-
+- Watchguard Firebox
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/__init__.py 
new/netmiko-3.1.0/netmiko/__init__.py
--- old/netmiko-3.0.0/netmiko/__init__.py       2019-12-11 19:37:51.000000000 
+0100
+++ new/netmiko-3.1.0/netmiko/__init__.py       2020-03-23 17:27:20.000000000 
+0100
@@ -23,7 +23,7 @@
 # Alternate naming
 Netmiko = ConnectHandler
 
-__version__ = "3.0.0"
+__version__ = "3.1.0"
 __all__ = (
     "ConnectHandler",
     "ssh_dispatcher",
@@ -34,8 +34,6 @@
     "NetmikoAuthenticationException",
     "NetMikoTimeoutException",
     "NetMikoAuthenticationException",
-    "NetmikoTimeoutError",
-    "NetmikoAuthError",
     "InLineTransfer",
     "redispatch",
     "SSHDetect",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/base_connection.py 
new/netmiko-3.1.0/netmiko/base_connection.py
--- old/netmiko-3.0.0/netmiko/base_connection.py        2020-01-15 
22:50:05.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/base_connection.py        2020-03-20 
16:58:01.000000000 +0100
@@ -29,6 +29,7 @@
     check_serial_port,
     get_structured_data,
     get_structured_data_genie,
+    select_cmd_verify,
 )
 
 
@@ -50,6 +51,7 @@
         device_type="",
         verbose=False,
         global_delay_factor=1,
+        global_cmd_verify=None,
         use_keys=False,
         key_file=None,
         pkey=None,
@@ -196,6 +198,12 @@
         :param sock: An open socket or socket-like object (such as a 
`.Channel`) to use for
                 communication to the target host (default: None).
         :type sock: socket
+
+        :param global_cmd_verify: Control whether command echo verification is 
enabled or disabled
+                (default: None). Global attribute takes precedence over 
function `cmd_verify`
+                argument. Value of `None` indicates to use function 
`cmd_verify` argument.
+        :type global_cmd_verify: bool|None
+
         """
         self.remote_conn = None
 
@@ -279,6 +287,7 @@
 
         self.fast_cli = fast_cli
         self.global_delay_factor = global_delay_factor
+        self.global_cmd_verify = global_cmd_verify
         if self.fast_cli and self.global_delay_factor == 1:
             self.global_delay_factor = 0.1
 
@@ -1130,6 +1139,7 @@
                 sleep_time *= 2
                 sleep_time = 3 if sleep_time >= 3 else sleep_time
 
+    @select_cmd_verify
     def send_command_timing(
         self,
         command_string,
@@ -1141,7 +1151,8 @@
         use_textfsm=False,
         textfsm_template=None,
         use_genie=False,
-        cmd_echo=False,
+        cmd_verify=False,
+        cmd_echo=None,
     ):
         """Execute command_string on the SSH channel using a delay-based 
mechanism. Generally
         used for show commands.
@@ -1175,9 +1186,16 @@
         :param use_genie: Process command output through PyATS/Genie parser 
(default: False).
         :type use_genie: bool
 
-        :param cmd_echo: Verify command echo before proceeding (default: 
False).
+        :param cmd_verify: Verify command echo before proceeding (default: 
False).
+        :type cmd_verify: bool
+
+        :param cmd_echo: Deprecated (use cmd_verify instead)
         :type cmd_echo: bool
         """
+        # For compatibility remove cmd_echo in Netmiko 4.x.x
+        if cmd_echo is not None:
+            cmd_verify = cmd_echo
+
         output = ""
         delay_factor = self.select_delay_factor(delay_factor)
         self.clear_buffer()
@@ -1188,7 +1206,7 @@
 
         cmd = command_string.strip()
         # if cmd is just an "enter" skip this section
-        if cmd and cmd_echo:
+        if cmd and cmd_verify:
             # Make sure you read until you detect the command echo (avoid 
getting out of sync)
             new_data = self.read_until_pattern(pattern=re.escape(cmd))
             new_data = self.normalize_linefeeds(new_data)
@@ -1277,6 +1295,7 @@
         except IndexError:
             return (data, False)
 
+    @select_cmd_verify
     def send_command(
         self,
         command_string,
@@ -1290,6 +1309,7 @@
         use_textfsm=False,
         textfsm_template=None,
         use_genie=False,
+        cmd_verify=True,
     ):
         """Execute command_string on the SSH channel using a pattern-based 
mechanism. Generally
         used for show commands. By default this method will keep waiting to 
receive data until the
@@ -1327,6 +1347,9 @@
 
         :param use_genie: Process command output through PyATS/Genie parser 
(default: False).
         :type normalize: bool
+
+        :param cmd_verify: Verify command echo before proceeding (default: 
True).
+        :type cmd_verify: bool
         """
         # Time to delay in each read loop
         loop_delay = 0.2
@@ -1360,8 +1383,8 @@
         new_data = ""
 
         cmd = command_string.strip()
-        # if cmd is just and "enter" skip this section
-        if cmd:
+        # if cmd is just an "enter" skip this section
+        if cmd and cmd_verify:
             # Make sure you read until you detect the command echo (avoid 
getting out of sync)
             new_data = self.read_until_pattern(pattern=re.escape(cmd))
             new_data = self.normalize_linefeeds(new_data)
@@ -1699,9 +1722,10 @@
             raise ValueError("Invalid argument passed into send_config_set")
 
         # Send config commands
+        output = ""
         if enter_config_mode:
             cfg_mode_args = (config_mode_command,) if config_mode_command else 
tuple()
-            output = self.config_mode(*cfg_mode_args)
+            output += self.config_mode(*cfg_mode_args)
 
         if self.fast_cli:
             for cmd in config_commands:
@@ -1765,6 +1789,7 @@
         ESC[00;32m   Color Green (30 to 37 are different colors) more general 
pattern is
                      ESC[\d\d;\d\dm and ESC[\d\d;\d\d;\d\dm
         ESC[6n       Get cursor position
+        ESC[1D       Move cursor position leftward by x characters (1 in this 
case)
 
         HP ProCurve and Cisco SG300 require this (possible others).
 
@@ -1796,6 +1821,7 @@
         code_erase_display = chr(27) + r"\[J"
         code_attrs_off = chr(27) + r"\[0m"
         code_reverse = chr(27) + r"\[7m"
+        code_cursor_left = chr(27) + r"\[\d+D"
 
         code_set = [
             code_position_cursor,
@@ -1819,6 +1845,7 @@
             code_erase_display,
             code_attrs_off,
             code_reverse,
+            code_cursor_left,
         ]
 
         output = string_buffer
@@ -1834,8 +1861,8 @@
 
         return output
 
-    def cleanup(self):
-        """Any needed cleanup before closing connection."""
+    def cleanup(self, command=""):
+        """Logout of the session on the network device plus any additional 
cleanup."""
         pass
 
     def paramiko_cleanup(self):
@@ -1844,7 +1871,7 @@
         del self.remote_conn_pre
 
     def disconnect(self):
-        """Try to gracefully close the SSH connection."""
+        """Try to gracefully close the session."""
         try:
             self.cleanup()
             if self.protocol == "ssh":
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/cisco/cisco_nxos_ssh.py 
new/netmiko-3.1.0/netmiko/cisco/cisco_nxos_ssh.py
--- old/netmiko-3.0.0/netmiko/cisco/cisco_nxos_ssh.py   2019-12-11 
19:37:51.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/cisco/cisco_nxos_ssh.py   2020-03-20 
16:58:01.000000000 +0100
@@ -12,6 +12,7 @@
         self.ansi_escape_codes = True
         self.set_base_prompt()
         self.disable_paging()
+        self.set_terminal_width(command="terminal width 511")
         # Clear the read buffer
         time.sleep(0.3 * self.global_delay_factor)
         self.clear_buffer()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/cisco/cisco_wlc_ssh.py 
new/netmiko-3.1.0/netmiko/cisco/cisco_wlc_ssh.py
--- old/netmiko-3.0.0/netmiko/cisco/cisco_wlc_ssh.py    2019-12-11 
19:37:51.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/cisco/cisco_wlc_ssh.py    2020-03-20 
16:58:01.000000000 +0100
@@ -1,6 +1,7 @@
 """Netmiko Cisco WLC support."""
 import time
 import re
+import socket
 
 from netmiko.ssh_exception import NetmikoAuthenticationException
 from netmiko.base_connection import BaseConnection
@@ -109,10 +110,42 @@
         time.sleep(0.3 * self.global_delay_factor)
         self.clear_buffer()
 
-    def cleanup(self):
-        """Reset WLC back to normal paging."""
+    def cleanup(self, command="logout"):
+        """Reset WLC back to normal paging and gracefully close session."""
         self.send_command_timing("config paging enable")
 
+        # Exit configuration mode
+        try:
+            # The pattern="" forces use of send_command_timing
+            if self.check_config_mode(pattern=""):
+                self.exit_config_mode()
+        except Exception:
+            pass
+
+        # End SSH/telnet session
+        self.write_channel(command + self.RETURN)
+        count = 0
+        output = ""
+        while count <= 5:
+            time.sleep(0.5)
+
+            # The connection might be dead at this point.
+            try:
+                output += self.read_channel()
+            except socket.error:
+                break
+
+            # Don't automatically save the config (user's responsibility)
+            if "Would you like to save them now" in output:
+                self._session_log_fin = True
+                self.write_channel("n" + self.RETURN)
+
+            try:
+                self.write_channel(self.RETURN)
+            except socket.error:
+                break
+            count += 1
+
     def check_config_mode(self, check_string="config", pattern=""):
         """Checks if the device is in configuration mode or not."""
         if not pattern:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/cisco_base_connection.py 
new/netmiko-3.1.0/netmiko/cisco_base_connection.py
--- old/netmiko-3.0.0/netmiko/cisco_base_connection.py  2019-12-11 
19:37:51.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/cisco_base_connection.py  2020-03-20 
16:58:01.000000000 +0100
@@ -157,15 +157,17 @@
         msg = f"Login failed: {self.host}"
         raise NetmikoAuthenticationException(msg)
 
-    def cleanup(self):
+    def cleanup(self, command="exit"):
         """Gracefully exit the SSH session."""
         try:
-            self.exit_config_mode()
+            # The pattern="" forces use of send_command_timing
+            if self.check_config_mode(pattern=""):
+                self.exit_config_mode()
         except Exception:
             pass
-        # Always try to send final 'exit' regardless of whether 
exit_config_mode works or not.
+        # Always try to send final 'exit' (command)
         self._session_log_fin = True
-        self.write_channel("exit" + self.RETURN)
+        self.write_channel(command + self.RETURN)
 
     def _autodetect_fs(self, cmd="dir", pattern=r"Directory of (.*)/"):
         """Autodetect the file system on the remote device. Used by SCP 
operations."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/dlink/__init__.py 
new/netmiko-3.1.0/netmiko/dlink/__init__.py
--- old/netmiko-3.0.0/netmiko/dlink/__init__.py 1970-01-01 01:00:00.000000000 
+0100
+++ new/netmiko-3.1.0/netmiko/dlink/__init__.py 2020-03-21 00:33:27.000000000 
+0100
@@ -0,0 +1,3 @@
+from netmiko.dlink.dlink_ds import DlinkDSTelnet, DlinkDSSSH
+
+__all__ = ["DlinkDSTelnet", "DlinkDSSSH"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/dlink/dlink_ds.py 
new/netmiko-3.1.0/netmiko/dlink/dlink_ds.py
--- old/netmiko-3.0.0/netmiko/dlink/dlink_ds.py 1970-01-01 01:00:00.000000000 
+0100
+++ new/netmiko-3.1.0/netmiko/dlink/dlink_ds.py 2020-03-21 00:33:27.000000000 
+0100
@@ -0,0 +1,65 @@
+from netmiko.cisco_base_connection import CiscoSSHConnection
+import time
+
+
+class DlinkDSBase(CiscoSSHConnection):
+    """Supports D-Link DGS/DES device series (there are some DGS/DES devices 
that are web-only)"""
+
+    def session_preparation(self):
+        """Prepare the session after the connection has been established."""
+        self.ansi_escape_codes = True
+        self._test_channel_read()
+        self.set_base_prompt()
+        self.disable_paging()
+        # Clear the read buffer
+        time.sleep(0.3 * self.global_delay_factor)
+        self.clear_buffer()
+
+    def disable_paging(self, command="disable clipaging", delay_factor=1):
+        return super().disable_paging(command=command, 
delay_factor=delay_factor)
+
+    def enable(self, *args, **kwargs):
+        """No implemented enable mode on D-Link yet"""
+        return ""
+
+    def check_enable_mode(self, *args, **kwargs):
+        """No implemented enable mode on D-Link yet"""
+        return True
+
+    def exit_enable_mode(self, *args, **kwargs):
+        """No implemented enable mode on D-Link yet"""
+        return ""
+
+    def check_config_mode(self, *args, **kwargs):
+        """No config mode on D-Link"""
+        return False
+
+    def config_mode(self, *args, **kwargs):
+        """No config mode on D-Link"""
+        return ""
+
+    def exit_config_mode(self, *args, **kwargs):
+        """No config mode on D-Link"""
+        return ""
+
+    def save_config(self, cmd="save", confirm=False, confirm_response=""):
+        """Saves configuration."""
+        return super().save_config(
+            cmd=cmd, confirm=confirm, confirm_response=confirm_response
+        )
+
+    def cleanup(self):
+        """Return paging before disconnect"""
+        self.send_command_timing("enable clipaging")
+        return super().cleanup()
+
+
+class DlinkDSSSH(DlinkDSBase):
+    pass
+
+
+class DlinkDSTelnet(DlinkDSBase):
+    def __init__(self, *args, **kwargs):
+        default_enter = kwargs.get("default_enter")
+        kwargs["default_enter"] = "\r\n" if default_enter is None else 
default_enter
+        super().__init__(*args, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/fortinet/fortinet_ssh.py 
new/netmiko-3.1.0/netmiko/fortinet/fortinet_ssh.py
--- old/netmiko-3.0.0/netmiko/fortinet/fortinet_ssh.py  2020-01-06 
05:31:27.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/fortinet/fortinet_ssh.py  2020-03-20 
16:58:01.000000000 +0100
@@ -85,7 +85,7 @@
             if result_mode in ["more", "standard"]:
                 self._output_mode = result_mode
 
-    def cleanup(self):
+    def cleanup(self, command="exit"):
         """Re-enable paging globally."""
         if self.allow_disable_global:
             # Return paging state
@@ -96,6 +96,7 @@
             # Should test output is valid
             for command in enable_paging_commands:
                 self.send_command_timing(command)
+        return super().cleanup(command=command)
 
     def config_mode(self, config_command=""):
         """No config mode for Fortinet devices."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/hp/hp_procurve.py 
new/netmiko-3.1.0/netmiko/hp/hp_procurve.py
--- old/netmiko-3.0.0/netmiko/hp/hp_procurve.py 2019-12-12 04:00:03.000000000 
+0100
+++ new/netmiko-3.1.0/netmiko/hp/hp_procurve.py 2020-03-20 16:58:01.000000000 
+0100
@@ -75,20 +75,38 @@
             raise ValueError(msg)
         return output
 
-    def cleanup(self):
+    def cleanup(self, command="logout"):
         """Gracefully exit the SSH session."""
-        self.exit_config_mode()
-        self.write_channel("logout" + self.RETURN)
+
+        # Exit configuration mode.
+        try:
+            # The pattern="" forces use of send_command_timing
+            if self.check_config_mode(pattern=""):
+                self.exit_config_mode()
+        except Exception:
+            pass
+
+        # Terminate SSH/telnet session
+        self.write_channel(command + self.RETURN)
         count = 0
+        output = ""
         while count <= 5:
             time.sleep(0.5)
-            output = self.read_channel()
-            if "Do you want to log out" in output:
-                self._session_log_fin = True
+
+            # The connection might be dead here.
+            try:
+                new_output = self.read_channel()
+                output += new_output
+            except socket.error:
+                break
+
+            if "Do you want to log out" in new_output:
                 self.write_channel("y" + self.RETURN)
+                time.sleep(0.5)
+                output += self.read_channel()
+
             # Don't automatically save the config (user's responsibility)
-            elif "Do you want to save the current" in output:
-                self._session_log_fin = True
+            if "Do you want to save the current" in output:
                 self.write_channel("n" + self.RETURN)
 
             try:
@@ -97,6 +115,9 @@
                 break
             count += 1
 
+        # Set outside of loop
+        self._session_log_fin = True
+
     def save_config(self, cmd="write memory", confirm=False, 
confirm_response=""):
         """Save Config."""
         return super().save_config(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/huawei/huawei.py 
new/netmiko-3.1.0/netmiko/huawei/huawei.py
--- old/netmiko-3.0.0/netmiko/huawei/huawei.py  2020-01-15 22:50:05.000000000 
+0100
+++ new/netmiko-3.1.0/netmiko/huawei/huawei.py  2020-03-20 16:58:01.000000000 
+0100
@@ -8,6 +8,7 @@
 class HuaweiBase(CiscoBaseConnection):
     def session_preparation(self):
         """Prepare the session after the connection has been established."""
+        self.ansi_escape_codes = True
         self._test_channel_read()
         self.set_base_prompt()
         self.disable_paging(command="screen-length 0 temporary")
@@ -15,6 +16,23 @@
         time.sleep(0.3 * self.global_delay_factor)
         self.clear_buffer()
 
+    def strip_ansi_escape_codes(self, string_buffer):
+        """
+        Huawei does a strange thing where they add a space and then add ESC[1D
+        to move the cursor to the left one.
+
+        The extra space is problematic.
+        """
+        code_cursor_left = chr(27) + r"\[\d+D"
+        output = string_buffer
+        pattern = rf" {code_cursor_left}"
+        output = re.sub(pattern, "", output)
+
+        log.debug("Stripping ANSI escape codes")
+        log.debug(f"new_output = {output}")
+        log.debug(f"repr = {repr(output)}")
+        return super().strip_ansi_escape_codes(output)
+
     def config_mode(self, config_command="system-view"):
         """Enter configuration mode."""
         return super().config_mode(config_command=config_command)
@@ -47,12 +65,12 @@
 
         Used as delimiter for stripping of trailing prompt in output.
 
-        Should be set to something that is general and applies in multiple 
contexts. For Comware
-        this will be the router prompt with < > or [ ] stripped off.
+        Should be set to something that is general and applies in multiple 
contexts.
+        For Huawei this will be the router prompt with < > or [ ] stripped off.
 
         This will be set on logging in, but not when entering system-view
         """
-        log.debug("In set_base_prompt")
+        # log.debug("In set_base_prompt")
         delay_factor = self.select_delay_factor(delay_factor)
         self.clear_buffer()
         self.write_channel(self.RETURN)
@@ -126,26 +144,21 @@
         i = 1
         while i <= max_loops:
             try:
-                output = self.read_channel()
-                return_msg += output
-
                 # Search for username pattern / send username
-                if re.search(username_pattern, output, flags=re.I):
-                    self.write_channel(self.username + self.TELNET_RETURN)
-                    time.sleep(1 * delay_factor)
-                    output = self.read_channel()
-                    return_msg += output
+                output = self.read_until_pattern(
+                    pattern=username_pattern, re_flags=re.I
+                )
+                return_msg += output
+                self.write_channel(self.username + self.TELNET_RETURN)
 
                 # Search for password pattern / send password
-                if re.search(pwd_pattern, output, flags=re.I):
-                    self.write_channel(self.password + self.TELNET_RETURN)
-                    time.sleep(0.5 * delay_factor)
-                    output = self.read_channel()
-                    return_msg += output
-                    if re.search(
-                        pri_prompt_terminator, output, flags=re.M
-                    ) or re.search(alt_prompt_terminator, output, flags=re.M):
-                        return return_msg
+                output = self.read_until_pattern(pattern=pwd_pattern, 
re_flags=re.I)
+                return_msg += output
+                self.write_channel(self.password + self.TELNET_RETURN)
+
+                # Waiting for combined output
+                output = self.read_until_pattern(pattern=combined_pattern)
+                return_msg += output
 
                 # Search for password change prompt, send "N"
                 if re.search(password_change_prompt, output):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/linux/linux_ssh.py 
new/netmiko-3.1.0/netmiko/linux/linux_ssh.py
--- old/netmiko-3.0.0/netmiko/linux/linux_ssh.py        2019-12-11 
19:37:51.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/linux/linux_ssh.py        2020-03-20 
16:58:01.000000000 +0100
@@ -51,7 +51,7 @@
             config_commands=config_commands, 
exit_config_mode=exit_config_mode, **kwargs
         )
 
-    def check_config_mode(self, check_string=LINUX_PROMPT_ROOT):
+    def check_config_mode(self, check_string=LINUX_PROMPT_ROOT, pattern=""):
         """Verify root"""
         return self.check_enable_mode(check_string=check_string)
 
@@ -102,10 +102,9 @@
                 raise ValueError(msg)
         return output
 
-    def cleanup(self):
+    def cleanup(self, command="exit"):
         """Try to Gracefully exit the SSH session."""
-        self._session_log_fin = True
-        self.write_channel("exit" + self.RETURN)
+        return super().cleanup(command=command)
 
     def save_config(self, *args, **kwargs):
         """Not Implemented"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/nokia/nokia_sros_ssh.py 
new/netmiko-3.1.0/netmiko/nokia/nokia_sros_ssh.py
--- old/netmiko-3.0.0/netmiko/nokia/nokia_sros_ssh.py   2019-12-11 
19:37:57.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/nokia/nokia_sros_ssh.py   2020-03-20 
16:58:01.000000000 +0100
@@ -156,3 +156,15 @@
             return re.sub(strips, "", output)
         else:
             return output
+
+    def cleanup(self, command="logout"):
+        """Gracefully exit the SSH session."""
+        try:
+            # The pattern="" forces use of send_command_timing
+            if self.check_config_mode(pattern=""):
+                self.exit_config_mode()
+        except Exception:
+            pass
+        # Always try to send final 'logout'.
+        self._session_log_fin = True
+        self.write_channel(command + self.RETURN)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/paloalto/paloalto_panos.py 
new/netmiko-3.1.0/netmiko/paloalto/paloalto_panos.py
--- old/netmiko-3.0.0/netmiko/paloalto/paloalto_panos.py        2019-12-11 
19:37:51.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/paloalto/paloalto_panos.py        2020-03-20 
16:58:01.000000000 +0100
@@ -159,15 +159,17 @@
         kwargs["delay_factor"] = kwargs.get("delay_factor", 2.5)
         return super().send_command(*args, **kwargs)
 
-    def cleanup(self):
+    def cleanup(self, command="exit"):
         """Gracefully exit the SSH session."""
         try:
-            self.exit_config_mode()
+            # The pattern="" forces use of send_command_timing
+            if self.check_config_mode(pattern=""):
+                self.exit_config_mode()
         except Exception:
-            # Always try to send 'exit' regardless of whether exit_config_mode 
works or not.
             pass
+        # Always try to send final 'exit' (command)
         self._session_log_fin = True
-        self.write_channel("exit" + self.RETURN)
+        self.write_channel(command + self.RETURN)
 
 
 class PaloAltoPanosSSH(PaloAltoPanosBase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/sophos/__init__.py 
new/netmiko-3.1.0/netmiko/sophos/__init__.py
--- old/netmiko-3.0.0/netmiko/sophos/__init__.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/sophos/__init__.py        2020-03-22 
01:38:04.000000000 +0100
@@ -0,0 +1,3 @@
+from netmiko.sophos.sophos_sfos_ssh import SophosSfosSSH
+
+__all__ = ["SophosSfosSSH"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/sophos/sophos_sfos_ssh.py 
new/netmiko-3.1.0/netmiko/sophos/sophos_sfos_ssh.py
--- old/netmiko-3.0.0/netmiko/sophos/sophos_sfos_ssh.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/sophos/sophos_sfos_ssh.py 2020-03-22 
01:38:04.000000000 +0100
@@ -0,0 +1,59 @@
+"""SophosXG (SFOS) Firewall support"""
+import time
+from netmiko.cisco_base_connection import CiscoSSHConnection
+
+
+class SophosSfosSSH(CiscoSSHConnection):
+    def session_preparation(self):
+        """Prepare the session after the connection has been established."""
+        self._test_channel_read()
+        """
+        Sophos Firmware Version SFOS 18.0.0 GA-Build339
+
+        Main Menu
+
+            1.  Network  Configuration
+            2.  System   Configuration
+            3.  Route    Configuration
+            4.  Device Console
+            5.  Device Management
+            6.  VPN Management
+            7.  Shutdown/Reboot Device
+            0.  Exit
+
+            Select Menu Number [0-7]:
+        """
+        self.write_channel("4" + self.RETURN)
+        self._test_channel_read(pattern=r"[console>]")
+        self.set_base_prompt()
+        # Clear the read buffer
+        time.sleep(0.3 * self.global_delay_factor)
+        self.clear_buffer()
+
+    def check_enable_mode(self, *args, **kwargs):
+        """No enable mode on SFOS"""
+        return True
+
+    def enable(self, *args, **kwargs):
+        """No enable mode on SFOS"""
+        return ""
+
+    def exit_enable_mode(self, *args, **kwargs):
+        """No enable mode on SFOS"""
+        return ""
+
+    def check_config_mode(self, *args, **kwargs):
+        """No config mode on SFOS"""
+        return False
+
+    def config_mode(self, *args, **kwargs):
+        """No config mode on SFOS"""
+        return ""
+
+    def exit_config_mode(self, *args, **kwargs):
+        """No config mode on SFOS"""
+        return ""
+
+    def save_config(self, *args, **kwargs):
+        """Not Implemented"""
+        raise NotImplementedError
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/ssh_autodetect.py 
new/netmiko-3.1.0/netmiko/ssh_autodetect.py
--- old/netmiko-3.0.0/netmiko/ssh_autodetect.py 2019-12-11 19:37:51.000000000 
+0100
+++ new/netmiko-3.1.0/netmiko/ssh_autodetect.py 2020-03-23 17:10:22.000000000 
+0100
@@ -203,6 +203,8 @@
         """
         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)
         # Call the _test_channel_read() in base to clear initial data
         output = BaseConnection._test_channel_read(self.connection)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/ssh_dispatcher.py 
new/netmiko-3.1.0/netmiko/ssh_dispatcher.py
--- old/netmiko-3.0.0/netmiko/ssh_dispatcher.py 2020-01-06 05:30:41.000000000 
+0100
+++ new/netmiko-3.1.0/netmiko/ssh_dispatcher.py 2020-03-22 23:23:29.000000000 
+0100
@@ -31,6 +31,7 @@
 from netmiko.dell import DellPowerConnectSSH
 from netmiko.dell import DellPowerConnectTelnet
 from netmiko.dell import DellIsilonSSH
+from netmiko.dlink import DlinkDSTelnet, DlinkDSSSH
 from netmiko.eltex import EltexSSH, EltexEsrSSH
 from netmiko.endace import EndaceSSH
 from netmiko.enterasys import EnterasysSSH
@@ -72,10 +73,12 @@
 from netmiko.ruckus import RuckusFastironSSH
 from netmiko.ruckus import RuckusFastironTelnet
 from netmiko.ruijie import RuijieOSSSH, RuijieOSTelnet
+from netmiko.sophos import SophosSfosSSH
 from netmiko.terminal_server import TerminalServerSSH
 from netmiko.terminal_server import TerminalServerTelnet
 from netmiko.ubiquiti import UbiquitiEdgeSSH
 from netmiko.vyos import VyOSSSH
+from netmiko.watchguard import WatchguardFirewareSSH
 
 
 # The keys of this dictionary are the supported device_types
@@ -114,6 +117,7 @@
     "dell_os10": DellOS10SSH,
     "dell_powerconnect": DellPowerConnectSSH,
     "dell_isilon": DellIsilonSSH,
+    "dlink_ds": DlinkDSSSH,
     "endace": EndaceSSH,
     "eltex": EltexSSH,
     "eltex_esr": EltexEsrSSH,
@@ -161,10 +165,12 @@
     "rad_etx": RadETXSSH,
     "ruckus_fastiron": RuckusFastironSSH,
     "ruijie_os": RuijieOSSSH,
+    "sophos_sfos": SophosSfosSSH,
     "ubiquiti_edge": UbiquitiEdgeSSH,
     "ubiquiti_edgeswitch": UbiquitiEdgeSSH,
     "vyatta_vyos": VyOSSSH,
     "vyos": VyOSSSH,
+    "watchguard_fireware": WatchguardFirewareSSH,
 }
 
 FILE_TRANSFER_MAP = {
@@ -206,6 +212,7 @@
 CLASS_MAPPER["cisco_xr_telnet"] = CiscoXrTelnet
 CLASS_MAPPER["dell_dnos6_telnet"] = DellDNOS6Telnet
 CLASS_MAPPER["dell_powerconnect_telnet"] = DellPowerConnectTelnet
+CLASS_MAPPER["dlink_ds_telnet"] = DlinkDSTelnet
 CLASS_MAPPER["extreme_telnet"] = ExtremeExosTelnet
 CLASS_MAPPER["extreme_exos_telnet"] = ExtremeExosTelnet
 CLASS_MAPPER["extreme_netiron_telnet"] = ExtremeNetironTelnet
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/utilities.py 
new/netmiko-3.1.0/netmiko/utilities.py
--- old/netmiko-3.0.0/netmiko/utilities.py      2019-12-11 19:37:51.000000000 
+0100
+++ new/netmiko-3.1.0/netmiko/utilities.py      2020-03-20 16:58:01.000000000 
+0100
@@ -4,6 +4,7 @@
 import io
 import os
 from pathlib import Path
+import functools
 import serial.tools.list_ports
 from netmiko._textfsm import _clitable as clitable
 from netmiko._textfsm._clitable import CliTableError
@@ -334,3 +335,15 @@
         return parsed_output
     except Exception:
         return raw_output
+
+
+def select_cmd_verify(func):
+    """Override function cmd_verify argument with global setting."""
+
+    @functools.wraps(func)
+    def wrapper_decorator(self, *args, **kwargs):
+        if self.global_cmd_verify is not None:
+            kwargs["cmd_verify"] = self.global_cmd_verify
+        return func(self, *args, **kwargs)
+
+    return wrapper_decorator
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/watchguard/__init__.py 
new/netmiko-3.1.0/netmiko/watchguard/__init__.py
--- old/netmiko-3.0.0/netmiko/watchguard/__init__.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/watchguard/__init__.py    2020-03-20 
16:58:01.000000000 +0100
@@ -0,0 +1,3 @@
+from netmiko.watchguard.fireware_ssh import WatchguardFirewareSSH
+
+__all__ = ["WatchguardFirewareSSH"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko/watchguard/fireware_ssh.py 
new/netmiko-3.1.0/netmiko/watchguard/fireware_ssh.py
--- old/netmiko-3.0.0/netmiko/watchguard/fireware_ssh.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/netmiko-3.1.0/netmiko/watchguard/fireware_ssh.py        2020-03-20 
16:58:01.000000000 +0100
@@ -0,0 +1,36 @@
+import time
+from netmiko.base_connection import BaseConnection
+
+
+class WatchguardFirewareSSH(BaseConnection):
+    """
+    Implements methods for communicating with Watchguard Firebox firewalls.
+    """
+
+    def session_preparation(self):
+        """
+        Prepare the session after the connection has been established.
+
+        Set the base prompt for interaction ('#').
+        """
+        self._test_channel_read()
+        self.set_base_prompt()
+        # Clear the read buffer
+        time.sleep(0.3 * self.global_delay_factor)
+        self.clear_buffer()
+
+    def check_config_mode(self, check_string=")#", pattern="#"):
+        """
+        Checks if the device is in configuration mode or not.
+        """
+        return super().check_config_mode(check_string=check_string, 
pattern=pattern)
+
+    def config_mode(self, config_command="configure", pattern="#"):
+        return super().config_mode(config_command=config_command, 
pattern=pattern)
+
+    def exit_config_mode(self, exit_config="exit", pattern="#"):
+        return super().exit_config_mode(exit_config=exit_config, 
pattern=pattern)
+
+    def save_config(self, *args, **kwargs):
+        """No save config on Watchguard."""
+        pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko.egg-info/PKG-INFO 
new/netmiko-3.1.0/netmiko.egg-info/PKG-INFO
--- old/netmiko-3.0.0/netmiko.egg-info/PKG-INFO 2020-01-16 05:32:35.000000000 
+0100
+++ new/netmiko-3.1.0/netmiko.egg-info/PKG-INFO 2020-03-23 17:49:02.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: netmiko
-Version: 3.0.0
+Version: 3.1.0
 Summary: Multi-vendor library to simplify Paramiko SSH connections to network 
devices
 Home-page: https://github.com/ktbyers/netmiko
 Author: Kirk Byers
@@ -203,5 +203,6 @@
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Description-Content-Type: text/markdown
 Provides-Extra: test
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/netmiko.egg-info/SOURCES.txt 
new/netmiko-3.1.0/netmiko.egg-info/SOURCES.txt
--- old/netmiko-3.0.0/netmiko.egg-info/SOURCES.txt      2020-01-16 
05:32:35.000000000 +0100
+++ new/netmiko-3.1.0/netmiko.egg-info/SOURCES.txt      2020-03-23 
17:49:02.000000000 +0100
@@ -66,6 +66,8 @@
 netmiko/dell/dell_isilon_ssh.py
 netmiko/dell/dell_os10_ssh.py
 netmiko/dell/dell_powerconnect.py
+netmiko/dlink/__init__.py
+netmiko/dlink/dlink_ds.py
 netmiko/eltex/__init__.py
 netmiko/eltex/eltex_esr_ssh.py
 netmiko/eltex/eltex_ssh.py
@@ -130,12 +132,16 @@
 netmiko/ruckus/ruckus_fastiron.py
 netmiko/ruijie/__init__.py
 netmiko/ruijie/ruijie_os.py
+netmiko/sophos/__init__.py
+netmiko/sophos/sophos_sfos_ssh.py
 netmiko/terminal_server/__init__.py
 netmiko/terminal_server/terminal_server.py
 netmiko/ubiquiti/__init__.py
 netmiko/ubiquiti/edge_ssh.py
 netmiko/vyos/__init__.py
 netmiko/vyos/vyos_ssh.py
+netmiko/watchguard/__init__.py
+netmiko/watchguard/fireware_ssh.py
 tests/__init__.py
 tests/add_delay.sh
 tests/brocade_fastiron_commands.txt
@@ -189,11 +195,11 @@
 tests/test_oneaccess_oneos_telnet.sh
 tests/test_s300.sh
 tests/test_scp.sh
+tests/test_sophos_sfos.sh
 tests/test_suite_alt.sh
 tests/test_suite_tmp.sh
 tests/test_ubiquiti_edgeswitch.sh
 tests/test_utils.py
-tests/testx.txt
 tests/SLOG/cisco881_slog.log
 tests/SLOG/cisco881_slog_append.log
 tests/SLOG/cisco881_slog_append_compare.log
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/setup.py new/netmiko-3.1.0/setup.py
--- old/netmiko-3.0.0/setup.py  2019-12-11 19:37:51.000000000 +0100
+++ new/netmiko-3.1.0/setup.py  2020-03-20 16:58:01.000000000 +0100
@@ -42,6 +42,7 @@
         "Programming Language :: Python :: 3",
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
     ],
     packages=find_packages(exclude=("test*",)),
     install_requires=[
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/netmiko-3.0.0/tests/SLOG/cisco881_slog_wr_compare.log 
new/netmiko-3.1.0/tests/SLOG/cisco881_slog_wr_compare.log
--- old/netmiko-3.0.0/tests/SLOG/cisco881_slog_wr_compare.log   2019-12-11 
19:37:51.000000000 +0100
+++ new/netmiko-3.1.0/tests/SLOG/cisco881_slog_wr_compare.log   2020-03-20 
16:58:01.000000000 +0100
@@ -1,3 +1,4 @@
+
 cisco1#
 
 cisco1#terminal length 0
Binary files 
old/netmiko-3.0.0/tests/__pycache__/conftest.cpython-36-pytest-5.1.2.pyc and 
new/netmiko-3.1.0/tests/__pycache__/conftest.cpython-36-pytest-5.1.2.pyc differ
Binary files 
old/netmiko-3.0.0/tests/__pycache__/conftest.cpython-36-pytest-5.2.1.pyc and 
new/netmiko-3.1.0/tests/__pycache__/conftest.cpython-36-pytest-5.2.1.pyc differ
Binary files 
old/netmiko-3.0.0/tests/__pycache__/test_netmiko_autodetect.cpython-36-pytest-5.1.2.pyc
 and 
new/netmiko-3.1.0/tests/__pycache__/test_netmiko_autodetect.cpython-36-pytest-5.1.2.pyc
 differ
Binary files 
old/netmiko-3.0.0/tests/__pycache__/test_netmiko_commit.cpython-36-pytest-5.1.2.pyc
 and 
new/netmiko-3.1.0/tests/__pycache__/test_netmiko_commit.cpython-36-pytest-5.1.2.pyc
 differ
Binary files 
old/netmiko-3.0.0/tests/__pycache__/test_netmiko_config.cpython-36-pytest-5.1.2.pyc
 and 
new/netmiko-3.1.0/tests/__pycache__/test_netmiko_config.cpython-36-pytest-5.1.2.pyc
 differ
Binary files 
old/netmiko-3.0.0/tests/__pycache__/test_netmiko_config_acl.cpython-36-pytest-5.1.2.pyc
 and 
new/netmiko-3.1.0/tests/__pycache__/test_netmiko_config_acl.cpython-36-pytest-5.1.2.pyc
 differ
Binary files 
old/netmiko-3.0.0/tests/__pycache__/test_netmiko_scp.cpython-36-pytest-5.1.2.pyc
 and 
new/netmiko-3.1.0/tests/__pycache__/test_netmiko_scp.cpython-36-pytest-5.1.2.pyc
 differ
Binary files 
old/netmiko-3.0.0/tests/__pycache__/test_netmiko_session_log.cpython-36-pytest-5.1.2.pyc
 and 
new/netmiko-3.1.0/tests/__pycache__/test_netmiko_session_log.cpython-36-pytest-5.1.2.pyc
 differ
Binary files 
old/netmiko-3.0.0/tests/__pycache__/test_netmiko_show.cpython-36-pytest-5.1.2.pyc
 and 
new/netmiko-3.1.0/tests/__pycache__/test_netmiko_show.cpython-36-pytest-5.1.2.pyc
 differ
Binary files 
old/netmiko-3.0.0/tests/__pycache__/test_netmiko_show.cpython-36-pytest-5.2.1.pyc
 and 
new/netmiko-3.1.0/tests/__pycache__/test_netmiko_show.cpython-36-pytest-5.2.1.pyc
 differ
Binary files 
old/netmiko-3.0.0/tests/__pycache__/test_netmiko_tcl.cpython-36-pytest-5.1.2.pyc
 and 
new/netmiko-3.1.0/tests/__pycache__/test_netmiko_tcl.cpython-36-pytest-5.1.2.pyc
 differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/cisco3-out.txt 
new/netmiko-3.1.0/tests/cisco3-out.txt
--- old/netmiko-3.0.0/tests/cisco3-out.txt      2020-01-16 04:48:43.000000000 
+0100
+++ new/netmiko-3.1.0/tests/cisco3-out.txt      2020-03-23 17:13:55.000000000 
+0100
@@ -1,10 +1,5 @@
 
 
-
-cisco3#
-cisco3#
-cisco3#
-cisco3#
 cisco3#
 cisco3#terminal length 0
 cisco3#terminal width 511
@@ -125,10 +120,6 @@
 cisco3(config-ext-nacl)#end
 cisco3#
 cisco3#
-cisco3#
-cisco3#
-cisco3#
-cisco3#
 cisco3#show ip access-lists netmiko_test_large_acl
 Extended IP access list netmiko_test_large_acl
     10 permit ip host 192.168.0.1 any
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/conftest.py 
new/netmiko-3.1.0/tests/conftest.py
--- old/netmiko-3.0.0/tests/conftest.py 2019-12-11 19:37:51.000000000 +0100
+++ new/netmiko-3.1.0/tests/conftest.py 2020-03-20 16:58:01.000000000 +0100
@@ -38,6 +38,40 @@
     return conn
 
 
[email protected](scope="module")
+def net_connect_cmd_verify(request):
+    """
+    Create the SSH connection to the remote device
+
+    Return the netmiko connection object
+
+    Set global_cmd_verify = False
+    """
+    device_under_test = request.config.getoption("test_device")
+    test_devices = parse_yaml(PWD + "/etc/test_devices.yml")
+    device = test_devices[device_under_test]
+    device["verbose"] = False
+    device["global_cmd_verify"] = False
+    conn = ConnectHandler(**device)
+    return conn
+
+
[email protected](scope="function")
+def net_connect_newconn(request):
+    """
+    Create the SSH connection to the remote device
+
+    Return the netmiko connection object.
+    Force a new connection for each test.
+    """
+    device_under_test = request.config.getoption("test_device")
+    test_devices = parse_yaml(PWD + "/etc/test_devices.yml")
+    device = test_devices[device_under_test]
+    device["verbose"] = False
+    conn = ConnectHandler(**device)
+    return conn
+
+
 @pytest.fixture()
 def net_connect_cm(request):
     """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/etc/commands.yml 
new/netmiko-3.1.0/tests/etc/commands.yml
--- old/netmiko-3.0.0/tests/etc/commands.yml    2019-10-15 21:01:37.000000000 
+0200
+++ new/netmiko-3.1.0/tests/etc/commands.yml    2020-02-22 00:55:42.000000000 
+0100
@@ -110,6 +110,8 @@
   version: "uname -a"
   basic: "ifconfig -a | grep inet | grep -v inet6 "
   extended_output: "netstat -an"   # requires paging to be disabled
+  # config_long_command: "ls 
verylongcommandnamethatwillcauselinewrapissuesasitdoesnotfitonesinglescreengreaterthan127charsandthensomesothingsreallyarennotright"
+  # config_verification: "ls"
 
 dell_force10:
   version: "show version"
@@ -131,6 +133,7 @@
     - "no logging console"
     - "logging console 4"           # something you can verify has changed
   config_verification: "show run | inc logging"
+  config_long_command: "snmp-server location 
verylongsnmplocationnamethatwillcauselinewrapissuesasitdoesnotfitonesinglescreengreaterthan127charsandthensomesothingsreallyarennotright"
 
 cisco_xe:
   version: "show version"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/etc/commands.yml.example 
new/netmiko-3.1.0/tests/etc/commands.yml.example
--- old/netmiko-3.0.0/tests/etc/commands.yml.example    2020-01-06 
05:30:41.000000000 +0100
+++ new/netmiko-3.1.0/tests/etc/commands.yml.example    2020-03-22 
01:38:04.000000000 +0100
@@ -282,6 +282,22 @@
   save_config_confirm: True
   save_config_response: '[OK]'
 
+dlink_ds:
+  version: "show greeting_message"
+  basic: "show ipif"
+  config:
+    - enable command logging
+  config_verification: "show config current_config include \"logging\""
+  extended_output: "show config current_config"   # requires paging to be 
disabled
+
+dlink_ds_telnet:
+  version: "show greeting_message"
+  basic: "show ipif"
+  config:
+    - enable command logging
+  config_verification: "show config current_config include \"logging\""
+  extended_output: "show config current_config"   # requires paging to be 
disabled
+
 ruijie_os:
   version: "show version"
   basic: "show ip interface brief"
@@ -294,3 +310,9 @@
   save_config_cmd: 'write'
   save_config_confirm: False
   save_config_response: 'OK'
+
+sophos_sfos:
+  version: "system diagnostics show version-info"
+  basic: "system diagnostics utilities route lookup 172.16.16.16"
+  extended_output: "system diagnostics show version-info"
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/etc/responses.yml.example 
new/netmiko-3.1.0/tests/etc/responses.yml.example
--- old/netmiko-3.0.0/tests/etc/responses.yml.example   2020-01-06 
05:30:41.000000000 +0100
+++ new/netmiko-3.1.0/tests/etc/responses.yml.example   2020-03-22 
01:38:04.000000000 +0100
@@ -223,6 +223,22 @@
   interface_ip: 1.2.3.5 
   version_banner: "NOS version 2.09 #0001"
   multiple_line_output: "Interface br328"
+  
+dlink_ds:
+  base_prompt: DGS-3120-24TC:admin
+  router_prompt: DGS-3120-24TC:admin#
+  enable_prompt: DGS-3120-24TC:admin#
+  version_banner: "D-Link Corporation"
+  multiple_line_output: "End of configuration file"
+  interface_ip: 192.168.50.10
+
+dlink_ds_telnet:
+  base_prompt: DGS-3120-24TC:admin
+  router_prompt: DGS-3120-24TC:admin#
+  enable_prompt: DGS-3120-24TC:admin#
+  version_banner: "D-Link Corporation"
+  multiple_line_output: "End of configuration file"
+  interface_ip: 192.168.50.10
 
 ruijie_os:
   base_prompt: Ruijie
@@ -233,3 +249,12 @@
   multiple_line_output: ""
   file_check_cmd: "logging buffered 8880"
   save_config: 'OK'
+
+sophos_sfos:
+  base_prompt: "console"
+  router_prompt: "console>"
+  enable_prompt: "console>"
+  interface_ip: 172.16.16.16
+  version_banner: "Serial Number"
+  multiple_line_output: ""
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/etc/test_devices.yml.example 
new/netmiko-3.1.0/tests/etc/test_devices.yml.example
--- old/netmiko-3.0.0/tests/etc/test_devices.yml.example        2020-01-06 
05:30:41.000000000 +0100
+++ new/netmiko-3.1.0/tests/etc/test_devices.yml.example        2020-03-22 
01:38:04.000000000 +0100
@@ -178,9 +178,29 @@
   username: TEST
   password: TEST
 
+dlink_ds:
+  device_type: dlink_ds
+  ip: 192.168.50.10
+  username: admin
+  password: admin
+
+dlink_ds_telnet:
+  device_type: dlink_ds_telnet
+  ip: 192.168.50.10
+  username: admin
+  password: admin
+
 ruijie_os:
   device_type: ruijie_os
   ip: 1.1.1.1
   username: ruijie
   password: ruijie
   secret: ruijie
+
+sophos_sfos:
+  device_type: sophos_sfos
+  ip: 172.16.16.16
+  username: admin
+  password: admin
+
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/test_netmiko_config.py 
new/netmiko-3.1.0/tests/test_netmiko_config.py
--- old/netmiko-3.0.0/tests/test_netmiko_config.py      2019-12-11 
19:37:57.000000000 +0100
+++ new/netmiko-3.1.0/tests/test_netmiko_config.py      2020-03-21 
00:33:27.000000000 +0100
@@ -1,4 +1,6 @@
 #!/usr/bin/env python
+
+
 def test_ssh_connect(net_connect, commands, expected_responses):
     """
     Verify the connection was established successfully
@@ -40,7 +42,7 @@
     assert net_connect.check_config_mode() is False
 
 
-def test_command_set(net_connect, commands, expected_responses):
+def test_config_set(net_connect, commands, expected_responses):
     """Test sending configuration commands."""
     config_commands = commands["config"]
     support_commit = commands.get("support_commit")
@@ -50,20 +52,15 @@
     net_connect.send_config_set(config_commands[0])
     if support_commit:
         net_connect.commit()
-
     cmd_response = expected_responses.get("cmd_response_init")
     config_commands_output = net_connect.send_command(config_verify)
-    print(config_verify)
-    print(config_commands_output)
     if cmd_response:
         assert cmd_response in config_commands_output
     else:
         assert config_commands[0] in config_commands_output
-
     net_connect.send_config_set(config_commands)
     if support_commit:
         net_connect.commit()
-
     cmd_response = expected_responses.get("cmd_response_final")
     config_commands_output = net_connect.send_command_expect(config_verify)
     if cmd_response:
@@ -72,7 +69,18 @@
         assert config_commands[-1] in config_commands_output
 
 
-def test_commands_from_file(net_connect, commands, expected_responses):
+def test_config_set_longcommand(net_connect, commands, expected_responses):
+    """Test sending configuration commands using long commands"""
+    config_commands = commands.get("config_long_command")
+    config_verify = commands["config_verification"]  # noqa
+    if not config_commands:
+        assert True
+        return
+    output = net_connect.send_config_set(config_commands)  # noqa
+    assert True
+
+
+def test_config_from_file(net_connect, commands, expected_responses):
     """
     Test sending configuration commands from a file
     """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/test_netmiko_show.py 
new/netmiko-3.1.0/tests/test_netmiko_show.py
--- old/netmiko-3.0.0/tests/test_netmiko_show.py        2019-12-14 
19:57:44.000000000 +0100
+++ new/netmiko-3.1.0/tests/test_netmiko_show.py        2020-03-21 
00:33:27.000000000 +0100
@@ -16,11 +16,14 @@
 """
 import pytest
 import time
+from datetime import datetime
+from netmiko.utilities import select_cmd_verify
 
-# import logging
 
-# logging.basicConfig(filename="test.log", level=logging.DEBUG)
-# logger = logging.getLogger("netmiko")
+@select_cmd_verify
+def bogus_func(obj, *args, **kwargs):
+    """Function that just returns the arguments modified by the decorator."""
+    return (obj, args, kwargs)
 
 
 def test_disable_paging(net_connect, commands, expected_responses):
@@ -51,16 +54,62 @@
     show_ip = net_connect.send_command_timing(commands["basic"])
     assert expected_responses["interface_ip"] in show_ip
     # Force verification of command echo
-    show_ip = net_connect.send_command_timing(commands["basic"], cmd_echo=True)
+    show_ip = net_connect.send_command_timing(commands["basic"], 
cmd_verify=True)
     assert expected_responses["interface_ip"] in show_ip
 
 
 def test_send_command(net_connect, commands, expected_responses):
     """Verify a command can be sent down the channel successfully using 
send_command method."""
-    time.sleep(1)
     net_connect.clear_buffer()
     show_ip_alt = net_connect.send_command(commands["basic"])
     assert expected_responses["interface_ip"] in show_ip_alt
+    show_ip_alt = net_connect.send_command(commands["basic"], cmd_verify=False)
+    assert expected_responses["interface_ip"] in show_ip_alt
+
+
+def test_cmd_verify_decorator(net_connect_cmd_verify):
+    obj = net_connect_cmd_verify
+    # Global False should have precedence
+    assert obj.global_cmd_verify is False
+    (obj, args, kwargs) = bogus_func(net_connect_cmd_verify, cmd_verify=True)
+    assert kwargs["cmd_verify"] is False
+    (obj, args, kwargs) = bogus_func(net_connect_cmd_verify, cmd_verify=False)
+    assert kwargs["cmd_verify"] is False
+
+    # Global True should have precedence
+    obj.global_cmd_verify = True
+    assert obj.global_cmd_verify is True
+    (obj, args, kwargs) = bogus_func(net_connect_cmd_verify, cmd_verify=True)
+    assert kwargs["cmd_verify"] is True
+    (obj, args, kwargs) = bogus_func(net_connect_cmd_verify, cmd_verify=False)
+    assert kwargs["cmd_verify"] is True
+
+    # None should track the local argument
+    obj.global_cmd_verify = None
+    assert obj.global_cmd_verify is None
+    (obj, args, kwargs) = bogus_func(net_connect_cmd_verify, cmd_verify=True)
+    assert kwargs["cmd_verify"] is True
+    (obj, args, kwargs) = bogus_func(net_connect_cmd_verify, cmd_verify=False)
+    assert kwargs["cmd_verify"] is False
+
+    # Set it back to proper False value (so later tests aren't messed up).
+    obj.global_cmd_verify = False
+
+
+def test_send_command_global_cmd_verify(
+    net_connect_cmd_verify, commands, expected_responses
+):
+    """
+    Verify a command can be sent down the channel successfully using 
send_command method.
+
+    Disable cmd_verify globally.
+    """
+    net_connect = net_connect_cmd_verify
+    net_connect.clear_buffer()
+    # cmd_verify should be disabled globally at this point
+    assert net_connect.global_cmd_verify is False
+    show_ip_alt = net_connect.send_command(commands["basic"])
+    assert expected_responses["interface_ip"] in show_ip_alt
 
 
 def test_send_command_juniper(net_connect, commands, expected_responses):
@@ -82,7 +131,6 @@
         # Strip off the _ssh, _telnet, _serial
         base_platform = base_platform.split("_")[:-1]
         base_platform = "_".join(base_platform)
-
     if base_platform not in [
         "cisco_ios",
         "cisco_xe",
@@ -111,7 +159,6 @@
         # Strip off the _ssh, _telnet, _serial
         base_platform = base_platform.split("_")[:-1]
         base_platform = "_".join(base_platform)
-
     if base_platform not in [
         "cisco_ios",
         "cisco_xe",
@@ -149,6 +196,11 @@
     """Ensure that the command that was executed does not show up in the 
command output."""
     show_ip = net_connect.send_command_timing(commands["basic"])
     show_ip_alt = net_connect.send_command(commands["basic"])
+
+    # dlink_ds has an echo of the command in the command output
+    if "dlink_ds" in net_connect.device_type:
+        show_ip = "\n".join(show_ip.split("\n")[2:])
+        show_ip_alt = "\n".join(show_ip_alt.split("\n")[2:])
     assert commands["basic"] not in show_ip
     assert commands["basic"] not in show_ip_alt
 
@@ -189,4 +241,24 @@
 
 def test_disconnect(net_connect, commands, expected_responses):
     """Terminate the SSH session."""
+    start_time = datetime.now()
     net_connect.disconnect()
+    end_time = datetime.now()
+    time_delta = end_time - start_time
+    assert net_connect.remote_conn is None
+    assert time_delta.total_seconds() < 8
+
+
+def test_disconnect_no_enable(net_connect_newconn, commands, 
expected_responses):
+    """Terminate the SSH session from privilege level1"""
+    net_connect = net_connect_newconn
+    if "cisco_ios" in net_connect.device_type:
+        net_connect.send_command_timing("disable")
+        start_time = datetime.now()
+        net_connect.disconnect()
+        end_time = datetime.now()
+        time_delta = end_time - start_time
+        assert net_connect.remote_conn is None
+        assert time_delta.total_seconds() < 5
+    else:
+        assert True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/test_sophos_sfos.sh 
new/netmiko-3.1.0/tests/test_sophos_sfos.sh
--- old/netmiko-3.0.0/tests/test_sophos_sfos.sh 1970-01-01 01:00:00.000000000 
+0100
+++ new/netmiko-3.1.0/tests/test_sophos_sfos.sh 2020-03-22 01:38:04.000000000 
+0100
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+RETURN_CODE=0
+
+# Exit on the first test failure and set RETURN_CODE = 1
+echo "Sophos SFOS SSH" \
+&& date \
+&& py.test -v test_netmiko_show.py --test_device sophos_sfos \
+&& date \
+|| RETURN_CODE=1
+
+exit $RETURN_CODE
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/testx.txt 
new/netmiko-3.1.0/tests/testx.txt
--- old/netmiko-3.0.0/tests/testx.txt   2020-01-16 00:03:09.000000000 +0100
+++ new/netmiko-3.1.0/tests/testx.txt   1970-01-01 01:00:00.000000000 +0100
@@ -1 +0,0 @@
-no logging console
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/netmiko-3.0.0/tests/unit/test_utilities.py 
new/netmiko-3.1.0/tests/unit/test_utilities.py
--- old/netmiko-3.0.0/tests/unit/test_utilities.py      2019-12-11 
19:37:51.000000000 +0100
+++ new/netmiko-3.1.0/tests/unit/test_utilities.py      2020-03-20 
16:58:01.000000000 +0100
@@ -2,9 +2,6 @@
 
 import os
 from os.path import dirname, join, relpath
-import sys
-
-import pytest
 
 from netmiko import utilities
 from netmiko._textfsm import _clitable as clitable
@@ -251,10 +248,6 @@
     assert result == raw_output
 
 
[email protected](
-    sys.version_info >= (3, 8),
-    reason="The genie package is not available for Python 3.8 yet",
-)
 def test_get_structured_data_genie():
     """Convert raw CLI output to structured data using Genie"""
 


Reply via email to