Hello community,

here is the log from the commit of package python-junos-eznc for 
openSUSE:Factory checked in at 2020-06-15 20:28:05
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-junos-eznc (Old)
 and      /work/SRC/openSUSE:Factory/.python-junos-eznc.new.3606 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-junos-eznc"

Mon Jun 15 20:28:05 2020 rev:3 rq:811738 version:2.4.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-junos-eznc/python-junos-eznc.changes      
2019-12-10 22:47:11.409703008 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-junos-eznc.new.3606/python-junos-eznc.changes
    2020-06-15 20:28:10.125887567 +0200
@@ -1,0 +2,83 @@
+Thu Jun  4 11:04:08 UTC 2020 - [email protected]
+
+- version update to 2.4.1
+  ## [1.4.0]
+   ### Fixed
+    - [#617] - IOS show ip ospf database router: Removed reliance on static 
spacing
+    - [#620] - NXOS show fex: Allow spaces in descriptions
+    - [#621] - Juniper show arp, etc.: Account for virtual chassis output 
(`{master:0}`)
+    - [#626] - ASA show vpn-sessiondb anyconnect: Require index, capture 
different format style
+    - [#650] - IOS show ip ospf database network: Change to allow one or more 
whitespace at the beginning of the line rather than 1 or more due to different 
output
+    - [#647] - ASA show route: Allow multiline route statements
+    - [#659] - IOS show mac address-table: Allow VLAN to be non-whitespace to 
allow N/A as an option
+   ### Added
+    - [#618] - IOS show ip ospf database network: New template
+    - [#619] - HP Comware display lldp neighbor information verbose: New 
template
+    - [#625] - ASA show vpn-sessiondb anyconnect: New template
+    - [#628] - Cisco WLC show mobility sum: New template
+    - [#631] - ASA show vpn-sessiondb anyconnect: Account for new data for 
assigned/public IP, group policy, and tunnel group
+    - [#629] - ASA show crypto ipsec sa - Add LOCAL_ADDRESS_NAME, 
CURRENT_PEER_NAME, DYNAMIC_PEER_NAME, LOCAL_CRYPTO_ENDPOINT_NAME, 
REMOTE_CRYPTO_ENDPOINT_NAME
+    - [#632] - ASA show nat: Added SERVICE_PROTOCOL
+    - [#635] - IOS show ip route summary: New template
+    - [#636] - ASA show vpn-sessiondb: New template
+    - [#638] - ASA show inventory: Capture PID and VID withoout serial
+    - [#637] - Cisco WLC show band select: New template
+  ## [1.4.0]
+   ### Fixed
+    - [#548] IOS show mac address-table: Account for Total Mac Addresses
+    - [#565] IOS show license: Avoid trailing spaces for features
+    - [#575] NXOS show version: Match N5K PLATFORM & LAST_REBOOT captures 
split words
+    - [#574] ASA show failover: Account for new output (IPS)
+    - [#577] IOS show mac address-table: Account for Multicast Entries
+    - [#582] NXOS show interface transceiver: Remove requirement for TYPE
+    - [#585] IOS show mac address-table: Fixed ordering for TYPE2
+    - [#587] IOS show interfaces switchport: Account for Vepa Enabled and 
Operational Dot1q Ethertype
+    - [#584] IOS show switch detail: Account for Mac persistency wait time
+    - [#589] EOS show ip route: Filldown for DISTANCE and METRIC - Added new 
data formats for VRF and NEXT_HOP and INTERFACE
+    - [#592] Fortinet get router info bgp summary: Account for more data, fix 
UP_DOWN regex from word to non-whitespace
+    - [#603] IOS show ip access-list: Update PROTOCOL to capture numbered 
protocols
+    - [610] Aruba os show arp: Fix tests to have the full output from the 
command and device
+    - [#608] Vyatta VyOS show interfaces: Capture IP_ADDRESS with or without 
netmask
+    - [#614] IOS show interfaces status: Remove reliance on whitespaces
+   ### Added
+    - [#406] Testing: Add yamllint to test suite
+    - [#407] Testing: Add python black to test suite
+    - [#553] IOS show lldp neighbors: Added CAPABILITIES capture group
+    - [#554] IOS show logging: New template
+    - [#563] IOS show interfaces switchport: Added ADMIN_MDOE capture group
+    - [#562] ASA show logging: New template
+    - [#564] NXOS show interface transceiver: New template
+    - [#567] XR show arp: New template
+    - [#572] IOS show lldp neighbors detail: Added SERIAL capture group
+    - [#573] ASA show arp: New template
+    - [#578] Fortinet get system interface: New template
+    - [#576] Huawei VRP display lldp neighbor: New template
+    - [#581] Cisco WLC show vlan sum: New template
+    - [#580] XR show interfaces summary: New template
+    - [#590] IOS show ip bgp neighbors: New template
+    - [#591] NXOS show vdc: New template
+    - [#595] Checkpoint GAIA show arp dynamic all: New template
+    - [#593] IOS show module: New template
+    - [#597] Huwai VRP display version: New template
+    - [#602] NXOS show vrf interface: New template
+    - [#598] IOS show running-config partition access list: Added TCP_FLAG 
capture group
+    - [#598] IOS show running-config partition access list: Convert COMMENT to 
list
+    - [#598] IOS show running-config partition access list: Update PROTOCOL to 
include numbered protocols
+    - [#596] XR admin show environment power: New template
+    - [#594] Aruba os show arp: New template
+    - [#605] SG300 show version: New template
+    - [#604] NXOS show vlan: Added INTERFACES capture group, Require VLAN_ID
+    - [#600] IOS show mpls interfaces: New template
+    - [#599] IOS show etherchannel summary: New template
+    - [#611] NXOS show interface: Added MODE capture group
+    - [#612] NXOS show interfaces switchport: Added ACCESS_VLAN_NAME and 
NATIVE_VLAN_NAME capture groups
+    - [#609] HP Comware display ip interface: New template
+    - [#606] IOS show ip ospf database router: New template
+   ### Changed
+    - [#406] Helpers: Added development_helpers cli utility to replace 
existing helpers
+- python3 package only, as ntc-templates is python3 only
+- added patches
+  https://github.com/Juniper/py-junos-eznc/pull/1040
+  + python-junos-eznc-no-unittest2.patch
+
+-------------------------------------------------------------------

Old:
----
  python-junos-eznc-2.3.1.tar.gz

New:
----
  python-junos-eznc-2.4.1.tar.gz
  python-junos-eznc-no-unittest2.patch

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

Other differences:
------------------
++++++ python-junos-eznc.spec ++++++
--- /var/tmp/diff_new_pack.B02tZm/_old  2020-06-15 20:28:11.173891198 +0200
+++ /var/tmp/diff_new_pack.B02tZm/_new  2020-06-15 20:28:11.173891198 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-junos-eznc
 #
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 # Copyright (c) 2017, Martin Hauke <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
@@ -18,13 +18,16 @@
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
+%define skip_python2 1
 Name:           python-junos-eznc
-Version:        2.3.1
+Version:        2.4.1
 Release:        0
 Summary:        Junos 'EZ' automation for non-programmers
 License:        Apache-2.0
 URL:            https://www.github.com/Juniper/py-junos-eznc
 Source:         
https://github.com/Juniper/py-junos-eznc/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz
+# https://github.com/Juniper/py-junos-eznc/pull/1040
+Patch0:         python-junos-eznc-no-unittest2.patch
 BuildRequires:  %{python_module Jinja2 >= 2.7.1}
 BuildRequires:  %{python_module PyYAML >= 5.1}
 BuildRequires:  %{python_module lxml >= 3.2.4}
@@ -32,6 +35,7 @@
 BuildRequires:  %{python_module ncclient >= 0.6.3}
 BuildRequires:  %{python_module netaddr}
 BuildRequires:  %{python_module nose}
+BuildRequires:  %{python_module ntc-templates}
 BuildRequires:  %{python_module paramiko >= 1.15.2}
 BuildRequires:  %{python_module pyparsing}
 BuildRequires:  %{python_module pyserial}
@@ -40,7 +44,6 @@
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module six}
 BuildRequires:  %{python_module transitions}
-BuildRequires:  %{python_module unittest2 >= 0.5.1}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-Jinja2 >= 2.7.1
@@ -48,6 +51,7 @@
 Requires:       python-lxml >= 3.2.4
 Requires:       python-ncclient >= 0.6.3
 Requires:       python-netaddr
+Requires:       python-ntc-templates
 Requires:       python-paramiko >= 1.15.2
 Requires:       python-pyparsing
 Requires:       python-pyserial
@@ -72,6 +76,7 @@
 
 %prep
 %setup -q -n py-junos-eznc-%{version}
+%patch0 -p1
 sed -i -e '/yamlordereddictloader/d' requirements.txt
 # requires deprecated and not working yamlordereddictloader
 rm tests/unit/factory/test_cmdtable.py

++++++ python-junos-eznc-2.3.1.tar.gz -> python-junos-eznc-2.4.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/.travis.yml 
new/py-junos-eznc-2.4.1/.travis.yml
--- old/py-junos-eznc-2.3.1/.travis.yml 2019-12-10 07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/.travis.yml 2020-04-29 18:00:59.000000000 +0200
@@ -3,17 +3,22 @@
     include:
         - python: 2.7
           dist: trusty
-          sudo: false
         - python: 3.5
           dist: trusty
-          sudo: false
         - python: 3.6
           dist: trusty
-          sudo: false
         - python: 3.7
           dist: xenial
-          sudo: true
-
+        - python: 3.8
+          dist: xenial
+        - os: windows
+          language: shell
+          python: 3.7
+          before_install:
+            - choco install python3 --version=3.7.4
+          env:
+            - PATH=/c/Python37:/c/Python37/Scripts:$PATH
+            - TRAVIS_PYTHON_VERSION=3.7
 addons:
   apt:
     packages:
@@ -24,7 +29,7 @@
 install:
   - "pip install -r development.txt"
   - "pip install -r requirements.txt"
-  - "pip install -U 
git+https://github.com/vnitinv/ncclient.git@junos-sax-parser";
+  - "pip install ."
 
 script: nosetests -v --with-coverage --cover-package=jnpr.junos 
--cover-inclusive -a unit
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/README.md 
new/py-junos-eznc-2.4.1/README.md
--- old/py-junos-eznc-2.3.1/README.md   2019-12-10 07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/README.md   2020-04-29 18:00:59.000000000 +0200
@@ -140,6 +140,8 @@
 
 [Nitin Kumar](https://github.com/vnitinv), [Stacy 
Smith](https://github.com/stacywsmith), [Stephen 
Steiner](https://github.com/ntwrkguru)
 
+* v2.4.1: [Nitin Kumar](https://github.com/vnitinv)
+* v2.4.0: [Nitin Kumar](https://github.com/vnitinv)
 * v2.3.0: [Nitin Kumar](https://github.com/vnitinv), [Raja Shekar 
Mekala](https://github.com/rsmekala), [Dinesh 
Babu](https://github.com/dineshbaburam91), [Chris 
Jenn](https://github.com/ipmonk), [Shigechika](https://github.com/shigechika)
 * v2.2.1: [Nitin Kumar](https://github.com/vnitinv), [Raja Shekar 
Mekala](https://github.com/rsmekala), [Dinesh 
Babu](https://github.com/dineshbaburam91), [Marcel 
Wiget](https://github.com/mwiget), [John Tishey](https://github.com/jtishey), 
[Alex Carp](https://github.com/carpalex), [Cory 
Councilman](https://github.com/dragonballbw3) 
 * v2.2.0: [Nitin Kumar](https://github.com/vnitinv), [Raja Shekar 
Mekala](https://github.com/rsmekala), [Marek](https://github.com/mzbroch), 
[Marcel Wiget](https://github.com/mwiget)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/RELEASE-NOTES.md 
new/py-junos-eznc-2.4.1/RELEASE-NOTES.md
--- old/py-junos-eznc-2.3.1/RELEASE-NOTES.md    2019-12-10 07:54:12.000000000 
+0100
+++ new/py-junos-eznc-2.4.1/RELEASE-NOTES.md    2020-04-29 18:00:59.000000000 
+0200
@@ -1,3 +1,28 @@
+## Release 2.4.1 - 29 APRIL 2020
+### Features Added
+- None
+
+### Bugs fixed:
+- Latest `textfsm` doesn’t support in windows. Hence, supporting `textfsm 
0.4.1` for windows user #1019
+- Convert `port` argument when passed  as `str` to `int` data type #1020
+- Return type of `sw.install` function going to change in the upcoming major 
release. 
+  So, added a deprecation warning in `sw.install` #1025
+
+## Release 2.4.0 - 1 APRIL 2020
+### Features Added
+- Added TableView Null Key support #983
+- Added timeout support for commit_check() #998
+- Added Win serial COM support #1000
+- Added load patch support #1001
+- Added textfsm support for table/view #1009
+
+### Bugs fixed:
+- Fixed table/view issue w.r.t to get() call #981
+- Fixed documentation typo #986
+- Handled sax parser input for nested fields #997
+- Fixed outbound ssh issue #1007
+- Fixed xpath issue when defined with a string function #1008
+
 ## Release 2.3.1 - 10 December 2019
 ### Features Added 
 - None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/lib/jnpr/junos/console.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/console.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/console.py   2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/console.py   2020-04-29 
18:00:59.000000000 +0200
@@ -133,6 +133,7 @@
         self.junos_dev_handler = JunosDeviceHandler(
                                      device_params={'name': 'junos',
                                                     'local': False})
+        self._conn = None
         self._j2ldr = _Jinja2ldr
         if self._fact_style == 'old':
             self.facts = self.ofacts
@@ -237,6 +238,7 @@
             logger.info('facts: retrieving device facts...')
             self.facts_refresh()
             self.results['facts'] = self.facts
+        self._conn = self._tty
         return self
 
     def close(self, skip_logout=False):
@@ -333,7 +335,7 @@
     # -----------------------------------------------------------------------
 
     def __enter__(self):
-        self._conn = self.open()
+        self.open()
         return self
 
     def __exit__(self, exc_type, exc_val, exc_tb):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/lib/jnpr/junos/device.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/device.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/device.py    2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/device.py    2020-04-29 
18:00:59.000000000 +0200
@@ -428,7 +428,6 @@
             with open(sshconf_path, 'r') as fp:
                 sshconf.parse(fp)
                 found = sshconf.lookup(self._hostname)
-                self._hostname = found.get('hostname', self._hostname)
                 self._port = found.get('port', self._port)
                 self._conf_auth_user = found.get('user')
                 self._conf_ssh_private_key_file = found.get('identityfile')
@@ -458,6 +457,8 @@
                 encode = None if sys.version < '3' else 'unicode'
                 return etree.tostring(rsp[0], encoding=encode)
             return rsp[0]
+        except TypeError:
+            return "No RPC equivalent found for: " + command
         except:
             return "invalid command: " + command
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/cmdtable.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/cmdtable.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/cmdtable.py  2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/cmdtable.py  2020-04-29 
18:00:59.000000000 +0200
@@ -2,37 +2,44 @@
 import copy
 # 
https://stackoverflow.com/questions/5121931/in-python-how-can-you-load-yaml-mappings-as-ordereddicts
 
+# stdlib
+import os
+from inspect import isclass
+from lxml import etree
+import json
+
 # local
 from jnpr.junos.exception import RpcError
 from jnpr.junos.utils.start_shell import StartShell
 from jnpr.junos.factory.state_machine import StateMachine
 from jnpr.junos.factory.to_json import TableJSONEncoder
 
-# stdlib
-from inspect import isclass
-from lxml import etree
-
-import json
-
 from jinja2 import Template
+from ntc_templates import parse as ntc_parse
+
+import logging
+logger = logging.getLogger("jnpr.junos.factory.cmdtable")
 
 
 class CMDTable(object):
 
-    def __init__(self, dev=None, output=None, path=None):
+    def __init__(self, dev=None, raw=None, path=None, template_dir=None):
         """
         :dev: Device instance
-        :output: string blob of the command output
+        :raw: string blob of the command output
         :path: file path to XML, to be used rather than :dev:
+        :template_dir: To look for textfsm templates in this folder first
         """
         self._dev = dev
-        self.xml = output
+        self.xml = None
         self.view = None
         self.ITEM_FILTER = 'name'
         self._key_list = []
         self._path = path
         self._parser = None
         self.output = None
+        self.data = raw
+        self.template_dir = template_dir
 
     # -------------------------------------------------------------------------
     # PUBLIC METHODS
@@ -69,6 +76,8 @@
         """
         self._clearkeys()
 
+        if self._path and self.data:
+            raise AttributeError('path and data are mutually exclusive')
         if self._path is not None:
             # for loading from local file-path
             with open(self._path, 'r') as fp:
@@ -76,9 +85,6 @@
             if self.data.startswith('<output>') and self.data.endswith(
                     '</output>'):
                 self.data = etree.fromstring(self.data).text
-            sm = StateMachine(self)
-            self.output = sm.parse(self.data.splitlines())
-            return self
 
         if 'target' in kvargs:
             self.TARGET = kvargs['target']
@@ -95,54 +101,60 @@
                                                                 str) else \
                 kvargs['filters']
 
+        cmd_args = self.CMD_ARGS.copy()
         if 'args' in kvargs and isinstance(kvargs['args'], dict):
-            self.CMD_ARGS.update(kvargs['args'])
+            cmd_args.update(kvargs['args'])
 
-        if len(self.CMD_ARGS) > 0:
-            self.GET_CMD = Template(self.GET_CMD).render(**self.CMD_ARGS)
+        if len(cmd_args) > 0:
+            self.GET_CMD = Template(self.GET_CMD).render(**cmd_args)
 
-        # execute the Junos RPC to retrieve the table
-        if hasattr(self, 'TARGET'):
-            if self.TARGET is None:
-                raise ValueError('"target" value not provided')
-            rpc_args = {'target': self.TARGET, 'command': self.GET_CMD,
-                        'timeout': '0'}
-            try:
-                self.xml = getattr(self.RPC, 'request_pfe_execute')(**rpc_args)
+        if self.data is None:
+            # execute the Junos RPC to retrieve the table
+            if hasattr(self, 'TARGET'):
+                if self.TARGET is None:
+                    raise ValueError('"target" value not provided')
+                rpc_args = {'target': self.TARGET, 'command': self.GET_CMD,
+                            'timeout': '0'}
+                try:
+                    self.xml = getattr(self.RPC, 
'request_pfe_execute')(**rpc_args)
+                    self.data = self.xml.text
+                    ver_info = self._dev.facts.get('version_info')
+                    if ver_info and ver_info.major[0] <= 15:
+                        # Junos <=15.x output has output something like below
+                        #
+                        # <rpc-reply>
+                        # <output>
+                        # SENT: Ukern command: show memory
+                        # GOT:
+                        # GOT: ID      Base      Total(b)       Free(b)     
Used(b)
+                        # GOT: --  --------     ---------     ---------   
---------
+                        # GOT:  0  44e72078    1882774284    1689527364   
193246920
+                        # GOT:  1  b51ffb88      67108860      57651900     
9456960
+                        # GOT:  2  bcdfffe0      52428784      52428784        
   0
+                        # GOT:  3  b91ffb88      62914556      62914556        
   0
+                        # LOCAL: End of file
+                        # </output>
+                        # </rpc-reply>
+                        # hence need to do cleanup
+                        self.data = self.data.replace("GOT: ", "")
+                except RpcError:
+                    with StartShell(self.D) as ss:
+                        ret = ss.run('cprod -A %s -c "%s"' % (self.TARGET,
+                                                              self.GET_CMD))
+                        if ret[0]:
+                            self.data = ret[1]
+            else:
+                self.xml = self.RPC.cli(self.GET_CMD)
                 self.data = self.xml.text
-                ver_info = self._dev.facts.get('version_info')
-                if ver_info and ver_info.major[0] <= 15:
-                    # Junos <=15.x output has output something like below
-                    #
-                    # <rpc-reply>
-                    # <output>
-                    # SENT: Ukern command: show memory
-                    # GOT:
-                    # GOT: ID      Base      Total(b)       Free(b)     Used(b)
-                    # GOT: --  --------     ---------     ---------   ---------
-                    # GOT:  0  44e72078    1882774284    1689527364   193246920
-                    # GOT:  1  b51ffb88      67108860      57651900     9456960
-                    # GOT:  2  bcdfffe0      52428784      52428784           0
-                    # GOT:  3  b91ffb88      62914556      62914556           0
-                    # LOCAL: End of file
-                    # </output>
-                    # </rpc-reply>
-                    # hence need to do cleanup
-                    self.data = self.data.replace("GOT: ", "")
-            except RpcError:
-                with StartShell(self.D) as ss:
-                    ret = ss.run('cprod -A %s -c "%s"' % (self.TARGET,
-                                                          self.GET_CMD))
-                    if ret[0]:
-                        self.data = ret[1]
-        else:
-            self.xml = self.RPC.cli(self.GET_CMD)
-            self.data = self.xml.text
 
-        # state machine
-        sm = StateMachine(self)
-
-        self.output = sm.parse(self.data.splitlines())
+        if self.USE_TEXTFSM:
+            self.output = self._parse_textfsm(platform=self.PLATFORM,
+                                              command=self.GET_CMD,
+                                              raw=self.data)
+        else:
+            # state machine
+            sm = StateMachine(self)
+            self.output = sm.parse(self.data.splitlines())
 
         # returning self for call-chaining purposes, yo!
         return self
@@ -296,3 +308,112 @@
     def __contains__(self, key):
         """ membership for use with 'in' """
         return bool(key in self.keys())
+
+    # ------------------------------------------------------------------------
+    # textfsm
+    # ------------------------------------------------------------------------
+
+    def _parse_textfsm(self, platform=None, command=None, raw=None):
+        """
+        textfsm returns list of dict, make it JSON/dict
+
+        :param platform: vendor platform, for ex cisco_xr
+        :param command: cli command to be parsed
+        :param raw: string blob output from the cli command execution
+        :return: dict of parsed data.
+        """
+        attrs = dict(
+            Command=command,
+            Platform=platform
+        )
+
+        template = None
+        template_dir = None
+        if self.template_dir is not None:
+            # we dont need index file for lookup
+            index = None
+            template_path = os.path.join(self.template_dir,
+                                         '{}_{}.textfsm'.format(
+                                             platform,
+                                             '_'.join(command.split())))
+            if not os.path.exists(template_path):
+                msg = 'Template file %s missing' % template_path
+                logger.error(msg)
+                raise FileNotFoundError(msg)
+            else:
+                template = template_path
+                template_dir = self.template_dir
+        if template_dir is None:
+            index = 'index'
+            template_dir = ntc_parse._get_template_dir()
+
+        cli_table = ntc_parse.clitable.CliTable(index, template_dir)
+        try:
+            cli_table.ParseCmd(raw, attrs, template)
+        except ntc_parse.clitable.CliTableError as ex:
+            logger.error('Unable to parse command "%s" on platform %s' % (
+                command, platform))
+            raise ex
+        return self._filter_output(cli_table)
+
+    def _filter_output(self, cli_table):
+        """
+        textfsm return list of list, covert it into more consumable format
+
+        :param cli_table: CLiTable object from textfsm
+        :return: dict of key, fields and its values, list of dict when key is 
None
+        """
+        self._set_key(cli_table)
+
+        fields = self.VIEW.FIELDS if self.VIEW is not None else {}
+        reverse_fields = {val: key for key, val in fields.items()}
+        if self.KEY is None:
+            cli_table_size = cli_table.size
+            if cli_table_size > 1:
+                raise KeyError("Key is Mandatory for parsed o/p of %s "
+                               "length" % cli_table_size)
+            elif cli_table_size == 1:
+                temp_dict = self._parse_row(cli_table[1], cli_table,
+                                            reverse_fields)
+                logger.debug("For Null Key, data returned: 
{}".format(temp_dict))
+                return temp_dict
+        output = {}
+        for row in cli_table:
+            temp_dict = self._parse_row(row, cli_table, reverse_fields)
+            logger.debug("data at index {} is {}".format(row.row, temp_dict))
+            if self.KEY in temp_dict:
+                if self.KEY not in fields:
+                    output[temp_dict.pop(self.KEY)] = temp_dict
+                else:
+                    output[temp_dict[self.KEY]] = temp_dict
+            else:
+                logger.debug("Key {} not present in {}".format(self.KEY,
+                                                               temp_dict))
+        return output
+
+    def _parse_row(self, row, cli_table, reverse_fields):
+        temp_dict = {}
+        for index, element in enumerate(row):
+            key = cli_table.header[index]
+            if self.KEY and key in self.KEY:
+                temp_dict[key] = element
+            if reverse_fields:
+                if key in reverse_fields:
+                    temp_dict[reverse_fields[key]] = element
+            else:
+                temp_dict[key] = element
+        return temp_dict
+
+    def _set_key(self, cli_table):
+        """
+        Preference to user provided key, then template and at last default
+        Checks and update if we need KEY from template file
+
+        :param cli_table: CLiTable object from textfsm
+        :return:
+        """
+        if self.KEY == 'name' and len(cli_table._keys) > 0:
+            template_keys = list(cli_table._keys)
+            self.KEY = template_keys[0] if len(template_keys) == 1 else \
+                template_keys
+        logger.debug("KEY being used: {}".format(self.KEY))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/factory_cls.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/factory_cls.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/factory_cls.py       
2019-12-10 07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/factory_cls.py       
2020-04-29 18:00:59.000000000 +0200
@@ -45,9 +45,8 @@
 
 def FactoryCMDTable(cmd, args=None, item=None, key_items=None,
                     key='name', view=None, table_name=None, title=None,
-                    delimiter=None, eval=None, **kwargs):
-    # if table_name is None:
-    #     table_name = "CMDTable." + cmd
+                    delimiter=None, eval=None, platform='juniper_junos', 
use_textfsm=False,
+                    **kwargs):
     new_cls = type(table_name, (CMDTable,), {})
     new_cls.GET_CMD = cmd
     if 'target' in kwargs:
@@ -60,6 +59,8 @@
     new_cls.TITLE = title
     new_cls.DELIMITER = delimiter
     new_cls.EVAL = eval
+    new_cls.PLATFORM = platform
+    new_cls.USE_TEXTFSM = use_textfsm
     new_cls.__module__ = __name__.replace('factory_cls', 'CMDTable')
     return new_cls
 
@@ -67,8 +68,6 @@
 def FactoryCMDChildTable(title=None, regex=None,
                          key='name', delimiter=None, table_name=None, 
view=None,
                          key_items=None, item=None, eval=None):
-    # if table_name is None:
-    #     table_name = "CMDTable." + title
     new_cls = type(table_name, (CMDTable,), {})
     new_cls.DELIMITER = delimiter
     new_cls.KEY = key
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/factory_loader.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/factory_loader.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/factory_loader.py    
2019-12-10 07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/factory_loader.py    
2020-04-29 18:00:59.000000000 +0200
@@ -176,31 +176,14 @@
     def _add_cmd_view_fields(self, view_dict, fields_name, fields):
         """ add a group of fields to the view """
         fields_dict = view_dict[fields_name]
-        # try:
-        #     # see if this is a 'fields_<group>' collection, and if so
-        #     # then we automatically setup using the group mechanism
-        #     mark = fields_name.index('_')
-        #     group = {'group': fields_name[mark + 1:]}
-        # except:
-        #     # otherwise, no group, just standard 'fields'
-        #     group = {}
-
         for f_name, f_data in fields_dict.items():
-            # each field could have its own unique set of properties
-            # so create a kvargs <dict> each time.  but copy in the
-            # groups <dict> (single item) generically.
-            # kvargs = {}
-            # kvargs.update(group)
-
-            # if isinstance(f_data, dict):
-            #     self._add_dictfield(fields, f_name, f_data, kvargs)
-            #     continue
-
             if f_data in self._catalog_dict:
-                # f_data is the table name
                 cls_tbl = self.catalog.get(f_data, 
self._build_cmdtable(f_data))
                 fields.table(f_name, cls_tbl)
                 continue
+
+            # if we are here, it means we need to filter fields from textfsm
+            fields._fields.update({f_name: f_data})
     # -------------------------------------------------------------------------
 
     def _build_view(self, view_name):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/optable.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/optable.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/optable.py   2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/optable.py   2020-04-29 
18:00:59.000000000 +0200
@@ -133,17 +133,23 @@
                     # fields:
                     #    input-bytes: traffic-statistics/input-bytes
                     #    output-bytes: traffic-statistics/output-bytes
-                    existing_elem = parser_ingest.xpath(tags[0])
-                    if existing_elem:
-                        obj = existing_elem[0]
-                        for tag in tags[1:]:
-                            obj.append(E(tag))
+                    # or
+                    # fields:
+                    #     prefix-count: 
bgp-option-information/prefix-limit/prefix-count
+                    #     prefix-dummy: 
bgp-option-information/prefix-limit/prefix-dummy
+                    local_obj = parser_ingest
+                    for tag in tags[:-1]:
+                        existing_elem = local_obj.xpath(tag)
+                        if existing_elem:
+                            local_obj = existing_elem[0]
+                        else:
+                            continue
                     else:
-                        continue
+                        local_obj.append(E(tags[-1]))
                 else:
-                    obj = E(tags[0])
-                    for tag in tags[1:]:
-                        obj.append(E(tag))
+                    obj = E(tags[-1])
+                    for tag in tags[:-1][::-1]:
+                        obj = E(tag, obj)
                     map_multilayer_fields[tags[0]] = obj
                 parser_ingest.insert(i + 1, obj)
             else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/table.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/table.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/table.py     2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/table.py     2020-04-29 
18:00:59.000000000 +0200
@@ -99,6 +99,9 @@
             try:
                 keys.append(this.xpath(k)[0].text)
             except:
+                # Case where key is provided like key: re-name | Null
+                if ' | ' in k and 'Null' in k:
+                    continue
                 keys.append(None)
         return tuple(keys)
 
@@ -134,8 +137,9 @@
             # Check if pipe is in the key_value, if so append xpath
             # to each value
             if ' | ' in key_value:
-                return self._keys_simple(' | '.join([xpath + '/' + x for x in
-                                                     key_value.split(' | ')]))
+                return self._keys_simple(' | '.join(
+                    [xpath + '/' + x for x in key_value.split(' | ') if
+                     x != 'Null']))
             return self._keys_simple(xpath + '/' + key_value)
 
         # user explicitly passed key as Null in Table
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/view.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/view.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/factory/view.py      2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/factory/view.py      2020-04-29 
18:00:59.000000000 +0200
@@ -85,6 +85,8 @@
         if isinstance(self.ITEM_NAME_XPATH, str):
             # xpath union key
             if ' | ' in self.ITEM_NAME_XPATH:
+                if 'Null' in self.ITEM_NAME_XPATH:
+                    return self._check_key_delimiter_null(self._xml, 
self.ITEM_NAME_XPATH)
                 return self._xml.xpath(self.ITEM_NAME_XPATH)[0].text.strip()
             # simple key
             return self._xml.findtext(self.ITEM_NAME_XPATH).strip()
@@ -92,16 +94,44 @@
             # composite key
             # keys with missing XPATH nodes are set to None
             keys = []
-            for i in self.ITEM_NAME_XPATH:
-                try:
-                    keys.append(self.xml.xpath(i)[0].text.strip())
-                except:
-                    keys.append(None)
-            return tuple(keys)
+            for item_name_xpath in self.ITEM_NAME_XPATH:
+                if ' | ' in item_name_xpath:
+                    key_with_null_cleaned = \
+                        self._check_key_delimiter_null(self._xml,
+                                                       item_name_xpath)
+                    if key_with_null_cleaned:
+                        keys.append(key_with_null_cleaned)
+                else:
+                    try:
+                        
keys.append(self.xml.xpath(item_name_xpath)[0].text.strip())
+                    except:
+                        keys.append(None)
+            if keys:
+                return tuple(keys)
+            else:
+                return keys
 
     # ALIAS key <=> name
     key = name
 
+    def _check_key_delimiter_null(self, xml, item_name_xpath):
+        """
+        Case where key is provided like key: re-name | Null
+
+        :param xml: xml object retrieved from device
+        :param item_name_xpath: key xpath
+        :return: key if fetched else []
+        """
+        if 'Null' in item_name_xpath:
+            # Let try get value for valid xpath key
+            xpath_key = [x for x in item_name_xpath.split(' | ') if x != 
'Null']
+            if xpath_key:
+                val = xml.xpath(xpath_key[0])
+                if val:
+                    return val[0].text.strip()
+                else:
+                    # To handle Null key
+                    return []
     @property
     def xml(self):
         """ returns the XML associated to the item """
@@ -281,6 +311,10 @@
 
             if 1 == len_found:
                 return _munch(found[0])
+            # -- 2020-March-26, if  string function (like string-before or 
string-after) is used as xpath (instead of as xpath condition), lxml will 
return ElementUnicodeResult object, which will be converted wrongly by the next 
interation, we should return the original UnicodeResult
+            if isinstance(found, etree._ElementUnicodeResult):
+                return found
+
             return [_munch(this) for this in found]
 
         except:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/lib/jnpr/junos/rpcmeta.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/rpcmeta.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/rpcmeta.py   2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/rpcmeta.py   2020-04-29 
18:00:59.000000000 +0200
@@ -263,6 +263,8 @@
             pass
         elif ('action' in options) and (options['action'] == 'set'):
             rpc.append(E('configuration-set', contents))
+        elif ('action' in options) and (options['action'] == 'patch'):
+            rpc.append(E('configuration-patch', contents))
         elif ('format' in options) and (options['format'] == 'text'):
             rpc.append(E('configuration-text', contents))
         elif ('format' in options) and (options['format'] == 'json'):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/lib/jnpr/junos/transport/tty_netconf.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/transport/tty_netconf.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/transport/tty_netconf.py     
2019-12-10 07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/transport/tty_netconf.py     
2020-04-29 18:00:59.000000000 +0200
@@ -132,6 +132,14 @@
     # -------------------------------------------------------------------------
 
     def _receive(self):
+        # On windows select.select throws io.UnsupportedOperation: fileno
+        # so use read function for windows serial COM ports
+        if hasattr(self._tty, 'port') and 
str(self._tty.port).startswith('COM'):
+            return self._receive_serial_win()
+        else:
+            return self._receive_serial()
+
+    def _receive_serial(self):
         """ process the XML response into an XML object """
         rxbuf = PY6.EMPTY_STR
         line = PY6.EMPTY_STR
@@ -153,10 +161,36 @@
                     rxbuf = rxbuf + line
                     if _NETCONF_EOM in rxbuf:
                         break
+        return self._parse_buffer(rxbuf)
+
+    # -------------------------------------------------------------------------
+    # Read message from windows COM ports
+    # -------------------------------------------------------------------------
+
+    def _receive_serial_win(self):
+        """ process incoming data from windows port"""
+        rxbuf = PY6.EMPTY_STR
+        line = PY6.EMPTY_STR
+        while True:
+            line, lastline = self._tty.read().strip(), line
+            if not line:
+                continue
+            if _NETCONF_EOM in line or _NETCONF_EOM in lastline + line:
+                rxbuf = rxbuf + line
+                break
+            else:
+                rxbuf = rxbuf + line
+                if _NETCONF_EOM in rxbuf:
+                    break
+        return self._parse_buffer(rxbuf)
+
+    def _parse_buffer(self, rxbuf):
         rxbuf = rxbuf.splitlines()
         if _NETCONF_EOM in rxbuf[-1]:
-            rxbuf.pop()
-
+            if rxbuf[-1] == _NETCONF_EOM:
+                rxbuf.pop()
+            else:
+                rxbuf[-1] = rxbuf[-1].split(_NETCONF_EOM)[0]
         try:
             rxbuf = [i.strip() for i in rxbuf if i.strip() != PY6.EMPTY_STR]
             rcvd_data = PY6.NEW_LINE.join(rxbuf)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/lib/jnpr/junos/utils/config.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/utils/config.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/utils/config.py      2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/utils/config.py      2020-04-29 
18:00:59.000000000 +0200
@@ -172,11 +172,13 @@
     # commit check
     # -------------------------------------------------------------------------
 
-    def commit_check(self):
+    def commit_check(self, **kvargs):
         """
         Perform a commit check.  If the commit check passes, this function
         will return ``True``.  If the commit-check results in warnings, they
         are reported and available in the Exception errs.
+        :param int timeout: If provided the command will wait for completion
+                            using the provided value as timeout (seconds).     
  
 
         :returns: ``True`` if commit-check is successful (no errors)
         :raises CommitError: When errors detected in candidate configuration.
@@ -184,8 +186,16 @@
                              to identify the specific problems
         :raises RpcError: When underlying ncclient has an error
         """
+        rpc_args = {}
+
+        # if a timeout is provided, then include that in the RPC
+        
+        timeout = kvargs.get('timeout')
+        if timeout:
+            rpc_args['dev_timeout'] = timeout
+
         try:
-            self.rpc.commit_configuration(check=True)
+            self.rpc.commit_configuration(check=True, **rpc_args)
         except RpcTimeoutError:
             raise
         except RpcError as err:        # jnpr.junos exception
@@ -208,7 +218,7 @@
     # show | compare rollback <number|0*>
     # -------------------------------------------------------------------------
 
-    def diff(self, rb_id=0):
+    def diff(self, rb_id=0, ignore_warning=False):
         """
         Retrieve a diff (patch-format) report of the candidate config against
         either the current active config, or a different rollback.
@@ -224,9 +234,9 @@
             raise ValueError("Invalid rollback #" + str(rb_id))
 
         try:
-            rsp = self.rpc.get_configuration(dict(
-                compare='rollback', rollback=str(rb_id), format='text'
-            ))
+            rsp = self.rpc.get_configuration(
+                    dict(compare='rollback', rollback=str(rb_id), 
format='text'),
+                    ignore_warning=ignore_warning)
         except RpcTimeoutError:
             raise 
         except RpcError as err:
@@ -311,6 +321,9 @@
 
           .. note:: This option cannot be used if **format** is "set".
 
+        :param bool patch:
+          If set to ``True`` will set the load-config action to load patch.
+
         :param str template_path:
           Similar to the **path** parameter, but this indicates that
           the file contents are ``Jinja2`` format and will require
@@ -382,7 +395,7 @@
         rpc_contents = None
 
         actions = filter(lambda item: kvargs.get(item, False),
-                         ('overwrite', 'merge', 'update'))
+                         ('overwrite', 'merge', 'update', 'patch'))
         if len(list(actions)) >= 2:
             raise ValueError('actions can be only one among %s'
                              % ', '.join(actions))
@@ -397,6 +410,8 @@
             rpc_xattrs['action'] = 'update'
         elif kvargs.get('merge') is True:
             del rpc_xattrs['action']
+        elif kvargs.get('patch') is True:
+            rpc_xattrs['action'] = 'patch'
 
         ignore_warning = kvargs.get('ignore_warning', False)
 
@@ -498,7 +513,12 @@
 
         elif 'path' in kvargs:
             # then this is a static-config file.  load that as our rpc_contents
-            rpc_contents = open(kvargs['path'], 'rU').read()
+            try:
+                # Explicitly request Python 3.x universal newline
+                rpc_contents = open(kvargs['path'], 'r', newline=None).read()
+            except TypeError:
+                # Fallback to Python 2.x universal newline
+                rpc_contents = open(kvargs['path'], 'rU').read()
             _lset_fromfile(kvargs['path'])
             if rpc_xattrs['format'] == 'xml':
                 # covert the XML string into XML structure
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/lib/jnpr/junos/utils/ssh_client.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/utils/ssh_client.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/utils/ssh_client.py  2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/utils/ssh_client.py  2020-04-29 
18:00:59.000000000 +0200
@@ -35,8 +35,10 @@
     if dev._ssh_private_key_file is not None:
         kwargs['key_filename'] = dev._ssh_private_key_file
 
-    ssh_client.connect(hostname=dev._hostname,
-                       port=(22, int(dev._port))[dev._hostname == 'localhost'],
+    # pick hostname from .ssh config if any
+    hostname = config.get('hostname', dev._hostname)
+    ssh_client.connect(hostname=hostname,
+                       port=(22, int(dev._port))[hostname == 'localhost'],
                        username=dev._auth_user,
                        password=dev._auth_password,
                        sock=sock, **kwargs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/lib/jnpr/junos/utils/sw.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/utils/sw.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/utils/sw.py  2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/utils/sw.py  2020-04-29 
18:00:59.000000000 +0200
@@ -11,6 +11,8 @@
     # Python 2.x
     from urlparse import urlparse
 
+import warnings
+warnings.simplefilter('default', PendingDeprecationWarning)
 
 # 3rd-party modules
 from lxml.builder import E
@@ -781,9 +783,9 @@
           checksum, then skip the copy to optimize time.
 
         :param bool all_re:
-          (Optional) When ``True`` (default) perform the software install on
-          all Routing Engines of the Junos device. When ``False``  if the
-          only preform the software install on the current Routing Engine.
+          (Optional) When ``True`` (default) install the package on
+          all Routing Engines of the Junos device. When ``False`` perform
+          the software install only on the current Routing Engine.
 
         :param bool vmhost:
           (Optional) A boolean indicating if this is a software update of the
@@ -797,6 +799,8 @@
             * ``True`` when the installation is successful
             * ``False`` otherwise
         """
+        warnings.warn("sw.install interface bool response is going to change "
+                      "in next release.", PendingDeprecationWarning)
         if issu is True and nssu is True:
             raise TypeError(
                 'install function can either take issu or nssu not both')
@@ -1045,7 +1049,7 @@
         Perform a system shutdown, with optional delay (in minutes) .
 
         If the device is equipped with dual-RE, then both RE will be
-        rebooted.  This code also handles EX/QFX VC.
+        shut down.  This code also handles EX/QFX VC.
 
         :param int in_min: time (minutes) before shutting down the device.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/lib/jnpr/junos/version.py 
new/py-junos-eznc-2.4.1/lib/jnpr/junos/version.py
--- old/py-junos-eznc-2.3.1/lib/jnpr/junos/version.py   2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/lib/jnpr/junos/version.py   2020-04-29 
18:00:59.000000000 +0200
@@ -1,5 +1,5 @@
-VERSION = "2.3.1"
-DATE = "2019-Dec-10"
+VERSION = "2.4.1"
+DATE = "2020-Apr-29"
 
 # Augment with the internal version if present
 try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/requirements.txt 
new/py-junos-eznc-2.4.1/requirements.txt
--- old/py-junos-eznc-2.3.1/requirements.txt    2019-12-10 07:54:12.000000000 
+0100
+++ new/py-junos-eznc-2.4.1/requirements.txt    2020-04-29 18:00:59.000000000 
+0200
@@ -10,3 +10,4 @@
 yamlordereddictloader
 pyparsing
 transitions
+ntc_templates
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/setup.py 
new/py-junos-eznc-2.4.1/setup.py
--- old/py-junos-eznc-2.3.1/setup.py    2019-12-10 07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/setup.py    2020-04-29 18:00:59.000000000 +0200
@@ -5,13 +5,16 @@
 req_lines = [line.strip() for line in open(
     'requirements.txt').readlines()]
 install_reqs = list(filter(None, req_lines))
-if sys.version_info[:2] == (2, 6):
-    install_reqs.append('importlib>=1.0.3')
+
+# refer: https://github.com/Juniper/py-junos-eznc/issues/1015
+# should be removed when textfsm releases >=1.1.1
+if sys.platform == 'win32':
+    install_reqs.append('textfsm==0.4.1')
 
 setup(
     name="junos-eznc",
     namespace_packages=['jnpr'],
-    version="2.3.1",
+    version="2.4.1",
     author="Jeremy Schulman, Nitin Kumar, Rick Sherman, Stacy Smith",
     author_email="[email protected]",
     description=("Junos 'EZ' automation for non-programmers"),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/tests/unit/factory/rpc-reply/show-utmd-status-use-Null.xml
 
new/py-junos-eznc-2.4.1/tests/unit/factory/rpc-reply/show-utmd-status-use-Null.xml
--- 
old/py-junos-eznc-2.3.1/tests/unit/factory/rpc-reply/show-utmd-status-use-Null.xml
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/py-junos-eznc-2.4.1/tests/unit/factory/rpc-reply/show-utmd-status-use-Null.xml
  2020-04-29 18:00:59.000000000 +0200
@@ -0,0 +1,5 @@
+<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" 
xmlns:junos="http://xml.juniper.net/junos/19.4I0/junos"; 
xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" 
message-id="urn:uuid:d002acd0-0f28-4ba2-a78c-042aeb5cc910">
+<utmd-status xmlns="http://xml.juniper.net/junos/19.4I0/junos-utmd";>
+<running/>
+</utmd-status>
+</rpc-reply>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/tests/unit/factory/rpc-reply/show-utmd-status.xml 
new/py-junos-eznc-2.4.1/tests/unit/factory/rpc-reply/show-utmd-status.xml
--- old/py-junos-eznc-2.3.1/tests/unit/factory/rpc-reply/show-utmd-status.xml   
1970-01-01 01:00:00.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/factory/rpc-reply/show-utmd-status.xml   
2020-04-29 18:00:59.000000000 +0200
@@ -0,0 +1,21 @@
+<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" 
xmlns:junos="http://xml.juniper.net/junos/20.2I0/junos"; 
xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" 
message-id="urn:uuid:c9c57ecd-dc9f-44cf-803b-5487182b9475">
+<multi-routing-engine-results>
+
+<multi-routing-engine-item>
+
+<re-name>node0</re-name>
+
+<utmd-status xmlns="http://xml.juniper.net/junos/20.2I0/junos-utmd";><running/>
+</utmd-status>
+</multi-routing-engine-item>
+
+<multi-routing-engine-item>
+
+<re-name>node1</re-name>
+
+<utmd-status xmlns="http://xml.juniper.net/junos/20.2I0/junos-utmd";><running/>
+</utmd-status>
+</multi-routing-engine-item>
+
+</multi-routing-engine-results>
+</rpc-reply>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/tests/unit/factory/test_cfgtable.py 
new/py-junos-eznc-2.4.1/tests/unit/factory/test_cfgtable.py
--- old/py-junos-eznc-2.3.1/tests/unit/factory/test_cfgtable.py 2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/factory/test_cfgtable.py 2020-04-29 
18:00:59.000000000 +0200
@@ -3,6 +3,8 @@
 
 import unittest
 import os
+import sys
+
 from nose.plugins.attrib import attr
 import yaml
 
@@ -83,6 +85,8 @@
 
 
 @attr('unit')
[email protected](sys.platform == 'win32',
+                     "will work for windows in coming days")
 class TestFactoryCfgTable(unittest.TestCase):
 
     @patch('ncclient.manager.connect')
@@ -604,6 +608,7 @@
 
         fpath = os.path.join(os.path.dirname(__file__),
                              'rpc-reply', fname)
+
         foo = open(fpath).read()
 
         if fname == 'user.xml':
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/tests/unit/factory/test_cmdtable.py 
new/py-junos-eznc-2.4.1/tests/unit/factory/test_cmdtable.py
--- old/py-junos-eznc-2.3.1/tests/unit/factory/test_cmdtable.py 2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/factory/test_cmdtable.py 2020-04-29 
18:00:59.000000000 +0200
@@ -469,7 +469,7 @@
   key_items:
     - High
     - Low
-  view: _FPCTTPQueueSizesView
+  # view: _FPCTTPQueueSizesView
 
 _FPCTTPQueueSizesTable2:
   title: TTP Receive Queue Sizes
@@ -477,7 +477,7 @@
   key_items:
     - High
     - Low
-  view: _FPCTTPQueueSizesView
+  # view: _FPCTTPQueueSizesView
 
 _FPCTTPQueueSizesView:
   fields:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/tests/unit/factory/test_optable.py 
new/py-junos-eznc-2.4.1/tests/unit/factory/test_optable.py
--- old/py-junos-eznc-2.3.1/tests/unit/factory/test_optable.py  2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/factory/test_optable.py  2020-04-29 
18:00:59.000000000 +0200
@@ -4,6 +4,7 @@
 import unittest
 import os
 import yaml
+import json
 from nose.plugins.attrib import attr
 
 from jnpr.junos import Device
@@ -94,6 +95,29 @@
 
         self.assertRaises(ValueError, bad, 'bunk')
 
+    def test_generate_sax_parser_fields_with_many_slash(self):
+        yaml_data = """
+---
+bgpNeighborTable:
+    rpc: get-bgp-neighbor-information
+    item: bgp-peer
+    key: peer-address
+    view: bgpNeighborView
+bgpNeighborView:
+    fields:
+        prefix-count: bgp-option-information/prefix-limit/prefix-count
+        prefix-dummy: bgp-option-information/prefix-limit/prefix-dummy
+"""
+        globals().update(FactoryLoader().load(yaml.load(yaml_data,
+                                                        Loader=yaml.Loader)))
+        tbl = bgpNeighborTable(self.dev)
+        data = generate_sax_parser_input(tbl)
+        self.assertEqual(data.tag, 'bgp-peer')
+        self.assertEqual(len(etree.tostring(data)), len(
+            b'<bgp-peer><peer-address/><bgp-option-information><prefix-limit>'
+            b'<prefix-count/><prefix-dummy/></prefix-limit>'
+            b'</bgp-option-information></bgp-peer>'))
+
     def test_generate_sax_parser_item_with_many_slash(self):
         yaml_data = """
 ---
@@ -175,6 +199,71 @@
             b'</traffic-statistics></logical-interface></physical-interface>')
         )
 
+    @patch('jnpr.junos.Device.execute')
+    def test_key_pipe_delim_with_Null(self, mock_execute):
+        mock_execute.side_effect = self._mock_manager
+        yaml_data = """
+---
+UTMStatusTable:
+    rpc: show-utmd-status
+    item: //utmd-status
+    view: UTMStatusView
+    key: ../re-name | Null
+
+UTMStatusView:
+    fields:
+        running: { running: flag }
+    """
+        globals().update(FactoryLoader().load(yaml.load(yaml_data,
+                                                        Loader=yaml.Loader)))
+        tbl = UTMStatusTable(self.dev)
+        data = tbl.get()
+        self.assertEqual(json.loads(data.to_json()),
+                         {'node0': {'running': True}, 'node1': {'running': 
True}})
+
+    @patch('jnpr.junos.Device.execute')
+    def test_key_pipe_delim_with_Null_use_Null(self, mock_execute):
+        mock_execute.side_effect = self._mock_manager
+        yaml_data = """
+---
+UTMStatusTable:
+    rpc: show-utmd-status_use_Null
+    item: //utmd-status
+    view: UTMStatusView
+    key: ../re-name | Null
+
+UTMStatusView:
+    fields:
+        running: { running: flag }
+    """
+        globals().update(FactoryLoader().load(yaml.load(yaml_data,
+                                                        Loader=yaml.Loader)))
+        tbl = UTMStatusTable(self.dev)
+        data = tbl.get()
+        self.assertEqual(json.loads(data.to_json()), {'running': True})
+
+    @patch('jnpr.junos.Device.execute')
+    def test_key_and_item_pipe_delim_with_Null_use_Null(self, mock_execute):
+        mock_execute.side_effect = self._mock_manager
+        yaml_data = """
+---
+UTMStatusTable:
+    rpc: show-utmd-status_use_Null
+    item: //multi-routing-engine-item/utmd-status | //utmd-status
+    view: UTMStatusView
+    key: 
+      - ../re-name | Null
+
+UTMStatusView:
+    fields:
+        running: { running: flag }
+    """
+        globals().update(FactoryLoader().load(yaml.load(yaml_data,
+                                                        Loader=yaml.Loader)))
+        tbl = UTMStatusTable(self.dev)
+        data = tbl.get()
+        self.assertEqual(json.loads(data.to_json()), {'running': True})
+
     def _read_file(self, fname):
         from ncclient.xml_ import NCElement
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/tests/unit/factory/test_table.py 
new/py-junos-eznc-2.4.1/tests/unit/factory/test_table.py
--- old/py-junos-eznc-2.3.1/tests/unit/factory/test_table.py    2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/factory/test_table.py    2020-04-29 
18:00:59.000000000 +0200
@@ -125,7 +125,6 @@
     def test_table_items(self, mock_execute):
         mock_execute.side_effect = self._mock_manager
         self.ppt.get('ge-0/0/0')
-        print (self.ppt.items())
         self.assertEqual(len(self.ppt.items()[1][1]), 8)
 
     def test_table_get_return_none(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/tests/unit/facts/test_get_software_information.py 
new/py-junos-eznc-2.4.1/tests/unit/facts/test_get_software_information.py
--- old/py-junos-eznc-2.3.1/tests/unit/facts/test_get_software_information.py   
2019-12-10 07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/facts/test_get_software_information.py   
2020-04-29 18:00:59.000000000 +0200
@@ -110,7 +110,6 @@
     @patch('jnpr.junos.Device.execute')
     def test_sw_info_dual_other_re_off(self, mock_execute):
         mock_execute.side_effect = self._mock_manager_dual_other_re_off
-        print (self.dev.facts)
         self.assertEqual(self.dev.facts['junos_info']['re1']['text'],
                          '18.3I20180716_1639')
         self.assertEqual(self.dev.facts['hostname'], 'R1_re01')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/tests/unit/facts/test_get_virtual_chassis_information.py
 
new/py-junos-eznc-2.4.1/tests/unit/facts/test_get_virtual_chassis_information.py
--- 
old/py-junos-eznc-2.3.1/tests/unit/facts/test_get_virtual_chassis_information.py
    2019-12-10 07:54:12.000000000 +0100
+++ 
new/py-junos-eznc-2.4.1/tests/unit/facts/test_get_virtual_chassis_information.py
    2020-04-29 18:00:59.000000000 +0200
@@ -5,6 +5,7 @@
 from nose.plugins.attrib import attr
 from mock import patch, MagicMock
 import os
+import sys
 from lxml import etree
 
 from jnpr.junos import Device
@@ -57,6 +58,8 @@
         self.assertEqual(self.dev.facts['vc_master'], None)
 
     @patch('jnpr.junos.Device.execute')
+    @unittest.skipIf(sys.platform == 'win32',
+                     "will work for windows in coming days")
     def test_vc_mmvcf(self, mock_execute):
         mock_execute.side_effect = self._mock_manager_vc_mmvcf
         self.assertEqual(self.dev.facts['vc_capable'], True)
@@ -65,6 +68,8 @@
         self.assertEqual(self.dev.facts['vc_master'], '0')
 
     @patch('jnpr.junos.Device.execute')
+    @unittest.skipIf(sys.platform == 'win32',
+                     "will work for windows in coming days")
     def test_vc_mmvc(self, mock_execute):
         mock_execute.side_effect = self._mock_manager_vc_mmvc
         self.assertEqual(self.dev.facts['vc_capable'], True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/tests/unit/ofacts/test_routing_engines.py 
new/py-junos-eznc-2.4.1/tests/unit/ofacts/test_routing_engines.py
--- old/py-junos-eznc-2.3.1/tests/unit/ofacts/test_routing_engines.py   
2019-12-10 07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/ofacts/test_routing_engines.py   
2020-04-29 18:00:59.000000000 +0200
@@ -5,6 +5,7 @@
 from nose.plugins.attrib import attr
 from mock import patch, MagicMock
 import os
+import sys
 
 from jnpr.junos import Device
 from jnpr.junos.ofacts.routing_engines import facts_routing_engines as 
routing_engines
@@ -30,6 +31,8 @@
         self.vcf = False
 
     @patch('jnpr.junos.Device.execute')
+    @unittest.skipIf(sys.platform == 'win32',
+                     "will work for windows in coming days")
     def test_multi_re_vc(self, mock_execute):
         mock_execute.side_effect = self._mock_manager
         self.mode = 'multi'
@@ -52,6 +55,8 @@
         self.assertEqual(self.facts['RE1']['mastership_state'], 'backup')
 
     @patch('jnpr.junos.Device.execute')
+    @unittest.skipIf(sys.platform == 'win32',
+                     "will work for windows in coming days")
     def test_mixed_mode_vcf(self, mock_execute):
         mock_execute.side_effect = self._mock_manager
         self.mode = 'multi'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/tests/unit/test_device.py 
new/py-junos-eznc-2.4.1/tests/unit/test_device.py
--- old/py-junos-eznc-2.3.1/tests/unit/test_device.py   2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/test_device.py   2020-04-29 
18:00:59.000000000 +0200
@@ -680,6 +680,8 @@
         self.dev._conn.rpc = MagicMock(side_effect=self._mock_manager)
         self.assertRaises(RpcError, self.dev.rpc.get_rpc_error)
 
+    @unittest.skipIf(sys.platform == 'win32',
+                     "will work for windows in coming days")
     def test_device_execute_permission_error(self):
         self.dev._conn.rpc = MagicMock(side_effect=self._mock_manager)
         self.assertRaises(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/tests/unit/transport/test_serial.py 
new/py-junos-eznc-2.4.1/tests/unit/transport/test_serial.py
--- old/py-junos-eznc-2.3.1/tests/unit/transport/test_serial.py 2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/transport/test_serial.py 2020-04-29 
18:00:59.000000000 +0200
@@ -2,6 +2,7 @@
 from nose.plugins.attrib import attr
 from mock import MagicMock, patch
 import sys
+import six
 
 from jnpr.junos.console import Console
 
@@ -33,7 +34,6 @@
     @patch('jnpr.junos.transport.tty_serial.Serial._tty_close')
     def tearDown(self, mock_serial_close, mock_write, mock_read, mock_close,
                  mock_sleep):
-        # mock_read.side_effect = [('shell', 'shell'), ('login', 'login'),
         mock_read.side_effect = [('shell', 'shell'), ('login', 'login'),
                                  ('cli', 'cli'), ]
         self.dev.close()
@@ -67,3 +67,90 @@
         self.dev._tty.EXPECT_TIMEOUT = 0.1
         self.dev._tty._ser.readline.side_effect = ['', 'test']
         self.assertEqual(self.dev._tty.read_prompt()[0], None)
+
+
+@attr('unit')
+class TestSerialWin(unittest.TestCase):
+
+    @patch('jnpr.junos.transport.tty_serial.serial.Serial.open')
+    @patch('jnpr.junos.transport.tty_serial.serial.Serial.read')
+    @patch('jnpr.junos.transport.tty_serial.serial.Serial.write')
+    @patch('jnpr.junos.transport.tty_serial.serial.Serial.flush')
+    @patch('jnpr.junos.transport.tty_serial.Serial.read_prompt')
+    def setUp(self, mock_read, mock_flush, mock_write, mock_serial_read,
+              mock_open):
+        self.dev = Console(port='COM4', baud=9600, mode='Serial')
+        mock_read.side_effect = [
+            ('login', 'login'), ('passwd', 'passwd'), ('shell', 'shell')]
+        mock_serial_read.side_effect = [
+            six.b("<!-- No zombies were killed during the creation of this 
user interface -->"),
+            six.b(''), six.b("""<!-- user root, class super-user -->
+<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <capabilities>
+    <capability>urn:ietf:params:netconf:base:1.0</capability>
+    <capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>
+    
<capability>urn:ietf:params:netconf:capability:confirmed-commit:1.0</capability>
+    <capability>urn:ietf:params:netconf:capability:validate:1.0</capability>
+    
<capability>urn:ietf:params:netconf:capability:url:1.0?scheme=http,ftp,file</capability>
+    <capability>urn:ietf:params:xml:ns:netconf:base:1.0</capability>
+    
<capability>urn:ietf:params:xml:ns:netconf:capability:candidate:1.0</capability>
+    
<capability>urn:ietf:params:xml:ns:netconf:capability:confirmed-commit:1.0</capability>
+    
<capability>urn:ietf:params:xml:ns:netconf:capability:validate:1.0</capability>
+    
<capability>urn:ietf:params:xml:ns:netconf:capability:url:1.0?scheme=http,ftp,file</capability>
+    
<capability>urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring</capability>
+    <capability>http://xml.juniper.net/netconf/junos/1.0</capability>
+    <capability>http://xml.juniper.net/dmi/system/1.0</capability>
+  </capabilities>
+  <session-id>7478</session-id>
+</hello>
+]]>]]>"""), six.b('')]
+        self.dev.open()
+
+    @patch('jnpr.junos.transport.tty.sleep')
+    @patch('jnpr.junos.transport.tty.tty_netconf.close')
+    @patch('jnpr.junos.transport.tty_serial.Serial.read_prompt')
+    @patch('jnpr.junos.transport.tty_serial.Serial.write')
+    @patch('jnpr.junos.transport.tty_serial.Serial._tty_close')
+    def tearDown(self, mock_serial_close, mock_write, mock_read, mock_close,
+                 mock_sleep):
+        mock_read.side_effect = [('shell', 'shell'), ('login', 'login'),
+                                 ('cli', 'cli'), ]
+        self.dev.close()
+
+    def test_tty_serial_win_connected(self):
+        self.assertTrue(self.dev.connected)
+
+    @patch('jnpr.junos.transport.tty.tty_netconf.close')
+    @patch('jnpr.junos.transport.tty_serial.Serial._tty_close')
+    def test_tty_serial_win_rpc_call(self, mock_serial_close, mock_close):
+        self.dev._tty.read = MagicMock()
+        self.dev._tty.rawwrite = MagicMock()
+        self.dev._tty.read.side_effect = \
+            [six.b('<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"'
+                   ' xmlns:junos="http://xml.juniper.net/junos/15.1X49/junos";>'
+                   '<route-engine-information xmlns="http://xml.juniper.net/ju'
+                   'nos/15.1X49/junos-chassis"><route-engine><status>OK</statu'
+                   's><temperature junos:celsius="45">45 degrees C / 113 degre'
+                   'es F</temperature><cpu-temperature junos:celsius="61">61 d'
+                   'egrees C / 141 degrees F</cpu-temperature><memory-system-t'
+                   'otal>4096</memory-system-total><memory-system-total-used>1'
+                   '024</memory-system-total-used><memory-system-total-util>25'
+                   '</memory-system-total-util><memory-control-plane>2624</mem'
+                   'ory-control-plane><memory-control-plane-used>682</memory-c'
+                   'ontrol-plane-used><memory-control-plane-util>26</memory-co'
+                   'ntrol-plane-util><memory-data-plane>1472</memory-data-plan'
+                   'e><memory-data-plane-used>353</memory-data-plane-used><mem'
+                   'ory-data-plane-util>24</memory-data-plane-util><cpu-user>1'
+                   '2</cpu-user><cpu-background>0</cpu-background><cpu-system>'
+                   '6</cpu-system><cpu-interrupt>0</cpu-interrupt><cpu-idle>83'
+                   '</cpu-idle><model>RE-SRX300</model><serial-number>CV0918AF'
+                   '1022</serial-number><start-time junos:seconds="1584539305"'
+                   '>2020-03-18 08:48:25 CDT</start-time><up-time junos:second'
+                   's="137925">1 day, 14 hours, 18 minutes, 45 seconds</up-tim'
+                   'e><last-reboot-reason>0x1:power cycle/failure</last-reboot'
+                   '-reason><load-average-one>0.12</load-average-one><load-ave'
+                   'rage-five>0.08</load-average-five><load-average-fifteen>0.'
+                   '06</load-average-fifteen></route-engine></route-engine-inf'
+                   'ormation></rpc-reply>]]>]]>')]
+        res = self.dev.rpc.get_route_engine_information()
+        self.assertEqual(res.tag, 'route-engine-information')
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/py-junos-eznc-2.3.1/tests/unit/transport/test_tty_netconf.py 
new/py-junos-eznc-2.4.1/tests/unit/transport/test_tty_netconf.py
--- old/py-junos-eznc-2.3.1/tests/unit/transport/test_tty_netconf.py    
2019-12-10 07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/transport/test_tty_netconf.py    
2020-04-29 18:00:59.000000000 +0200
@@ -14,6 +14,7 @@
 
     def setUp(self):
         self.tty_net = tty_netconf(MagicMock())
+        self.tty_net._tty.port = '/dev/tty'
 
     @patch('jnpr.junos.transport.tty_netconf.tty_netconf._receive')
     def test_open_at_shell_true(self, mock_rcv):
@@ -108,6 +109,7 @@
     @patch('jnpr.junos.transport.tty_netconf.select.select')
     def test_tty_netconf_receive_XMLSyntaxError(self, mock_select):
         rx = MagicMock()
+
         rx.read_until.side_effect = iter([
             six.b('<rpc-reply>ok<dummy></rpc-reply>'),
             six.b('\n]]>]]>')])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/tests/unit/utils/test_config.py 
new/py-junos-eznc-2.4.1/tests/unit/utils/test_config.py
--- old/py-junos-eznc-2.3.1/tests/unit/utils/test_config.py     2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/utils/test_config.py     2020-04-29 
18:00:59.000000000 +0200
@@ -184,7 +184,8 @@
         self.conf.diff()
         self.conf.rpc.get_configuration.\
             assert_called_with(
-                {'compare': 'rollback', 'rollback': '0', 'format': 'text'})
+                {'compare': 'rollback', 'rollback': '0', 'format': 'text'},
+                ignore_warning=False)
 
     def test_config_diff_exception_severity_warning(self):
         rpc_xml = '''
@@ -809,6 +810,43 @@
         self.assertEqual(self.conf.rpc.load_config.call_args[1]['url'],
                          '/var/home/user/golden.conf')
 
+    @patch('jnpr.junos.Device.execute')
+    def test_load_config_patch(self, mock_exec):
+        conf = \
+            """[edit system]
+            -  host-name pakzds904;
+            +  host-name pakzds904_set;
+            """
+        self.conf.load(conf, format='text', patch=True)
+        self.assertEqual(mock_exec.call_args[0][0].tag, 'load-configuration')
+        self.assertEqual(mock_exec.call_args[0][0].attrib,
+                         {'format': 'text', 'action': 'patch'})
+
+    @patch('jnpr.junos.Device.execute')
+    def test_load_config_text(self, mock_exec):
+        textdata = """policy-options {
+            prefix-list TEST1-NETS {
+                100.0.0.0/24;
+            }
+            policy-statement TEST1-NETS {
+                term TEST1 {
+                    from {
+                        prefix-list TEST1-NETS;
+                    }
+                    then accept;
+                }
+                term REJECT {
+                    then reject;
+                }
+            }
+        }"""
+        self.conf.load(textdata, overwrite=True)
+        self.assertEqual(mock_exec.call_args[0][0].tag, 'load-configuration')
+        self.assertEqual(mock_exec.call_args[0][0].getchildren()[0].tag,
+                         'configuration-text')
+        self.assertEqual(mock_exec.call_args[0][0].attrib,
+                         {'format': 'text', 'action': 'override'})
+
     def _read_file(self, fname):
         fpath = os.path.join(os.path.dirname(__file__),
                              'rpc-reply', fname)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/tests/unit/utils/test_ftp.py 
new/py-junos-eznc-2.4.1/tests/unit/utils/test_ftp.py
--- old/py-junos-eznc-2.3.1/tests/unit/utils/test_ftp.py        2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/utils/test_ftp.py        2020-04-29 
18:00:59.000000000 +0200
@@ -3,6 +3,7 @@
 from nose.plugins.attrib import attr
 import ftplib
 import sys
+import os
 
 from jnpr.junos import Device
 import jnpr.junos.utils.ftp
@@ -16,6 +17,8 @@
 
 
 @attr('unit')
[email protected](sys.platform == 'win32',
+                 "will work for windows in coming days")
 class TestFtp(unittest.TestCase):
 
     @patch('ftplib.FTP.connect')
@@ -121,15 +124,18 @@
     @patch('ftplib.FTP.storbinary')
     @patch(builtin_string + '.open')
     def test_ftp_upload_file_rem_full_path(self, mock_open, mock_ftpstore):
-        self.assertEqual(self.dev_ftp.put(local_file="/var/tmp/conf.txt",
-                                          remote_path="/var/tmp/test.txt"), 
True)
+        self.assertEqual(self.dev_ftp.put(local_file=os.path.abspath(
+            "/var/tmp/conf.txt"),
+                                          remote_path=os.path.abspath(
+                                              "/var/tmp/test.txt")), True)
         self.assertEqual(tuple(mock_ftpstore.call_args)[1]['cmd'],
-                         'STOR /var/tmp/test.txt')
+                         'STOR '+os.path.abspath("/var/tmp/test.txt"))
 
     @patch('ftplib.FTP.storbinary')
     @patch(builtin_string + '.open')
     def test_ftp_upload_file_rem_path_create(self, mock_open, mock_ftpstore):
         self.assertEqual(self.dev_ftp.put(local_file="conf.txt",
-                                          remote_path="/var/tmp"), True)
+                                          remote_path=os.path.abspath(
+                                              "/var/tmp")), True)
         self.assertEqual(tuple(mock_ftpstore.call_args)[1]['cmd'],
-                         'STOR /var/tmp/conf.txt')
+                         'STOR '+os.path.abspath("/var/tmp/conf.txt"))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/py-junos-eznc-2.3.1/tests/unit/utils/test_sw.py 
new/py-junos-eznc-2.4.1/tests/unit/utils/test_sw.py
--- old/py-junos-eznc-2.3.1/tests/unit/utils/test_sw.py 2019-12-10 
07:54:12.000000000 +0100
+++ new/py-junos-eznc-2.4.1/tests/unit/utils/test_sw.py 2020-04-29 
18:00:59.000000000 +0200
@@ -756,6 +756,8 @@
         self.assertEqual(eval(self.sw.rollback()), msg)
 
     @patch('jnpr.junos.Device.execute')
+    @unittest.skipIf(sys.platform == 'win32',
+                     "will work for windows in coming days")
     def test_sw_rollback_multi_exception(self, mock_execute):
         fname = 'request-package-rollback-multi-error.xml'
         mock_execute.side_effect = self._read_file(fname)

++++++ python-junos-eznc-no-unittest2.patch ++++++
Index: py-junos-eznc-2.3.1/development.txt
===================================================================
--- py-junos-eznc-2.3.1.orig/development.txt    2019-12-10 07:54:12.000000000 
+0100
+++ py-junos-eznc-2.3.1/development.txt 2020-06-02 09:22:40.050714175 +0200
@@ -6,4 +6,3 @@ nose         # http://nose.readthedocs.o
 pep8        # https://github.com/jcrocholl/pep8
 pyflakes        # https://launchpad.net/pyflakes
 coveralls       # https://coveralls.io/
-unittest2>=0.5.1  # https://pypi.python.org/pypi/unittest2
Index: py-junos-eznc-2.3.1/tests/functional/test_core.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/functional/test_core.py      2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/functional/test_core.py   2020-06-02 
09:24:17.503246419 +0200
@@ -1,6 +1,9 @@
 __author__ = "rsherman, vnitinv"
 
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 from jnpr.junos.exception import RpcTimeoutError
 
Index: py-junos-eznc-2.3.1/tests/functional/test_table.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/functional/test_table.py     2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/functional/test_table.py  2020-06-02 
09:24:46.555405079 +0200
@@ -1,6 +1,9 @@
 __author__ = "rsherman, vnitinv"
 
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 
 from jnpr.junos.op.routes import RouteTable
Index: py-junos-eznc-2.3.1/tests/unit/factory/test_to_json.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/factory/test_to_json.py 2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/factory/test_to_json.py      2020-06-02 
09:37:16.499457233 +0200
@@ -1,6 +1,9 @@
 __author__ = "Rick Sherman"
 
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 from mock import patch
 import os
Index: py-junos-eznc-2.3.1/tests/unit/facts/test__init__.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/facts/test__init__.py   2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/facts/test__init__.py        2020-06-02 
09:38:06.663731374 +0200
@@ -1,7 +1,10 @@
 __author__ = "Stacy Smith"
 __credits__ = "Jeremy Schulman, Nitin Kumar"
 
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 import importlib
 import sys
Index: py-junos-eznc-2.3.1/tests/unit/facts/test_swver.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/facts/test_swver.py     2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/facts/test_swver.py  2020-06-04 
12:31:16.270468805 +0200
@@ -1,7 +1,11 @@
 __author__ = "Stacy Smith"
 __credits__ = "Jeremy Schulman, Nitin Kumar"
 
-import unittest2 as unittest
+import six
+try:
+    import unittest2 as unittest
+except:
+    import unittest
 from nose.plugins.attrib import attr
 
 from jnpr.junos.facts.swver import version_info, get_facts
@@ -9,17 +13,18 @@ from jnpr.junos.facts.swver import versi
 
 @attr('unit')
 class TestVersionInfo(unittest.TestCase):
-
+    if six.PY2:
+        assertCountEqual = unittest.TestCase.assertItemsEqual
     def test_version_info_after_type_len_else(self):
         self.assertEqual(version_info('12.1X46-D10').build, None)
 
     def test_version_info_X_type_non_hyphenated(self):
-        self.assertItemsEqual(
+        self.assertCountEqual(
             version_info('11.4X12.2'),
             [('build', 2), ('major', (11, 4)), ('minor', '12'), ('type', 'X')])
 
     def test_version_info_X_type_non_hyphenated_nobuild(self):
-        self.assertItemsEqual(
+        self.assertCountEqual(
             version_info('11.4X12'),
             [('build', None), ('major', (11, 4)), ('minor', '12'), ('type', 
'X')])
 
@@ -61,12 +66,12 @@ class TestVersionInfo(unittest.TestCase)
             "build: 5\nmajor: !!python/tuple\n- 11\n- 4\nminor: '7'\ntype: 
R\n")
 
     def test_version_iter(self):
-        self.assertItemsEqual(
+        self.assertCountEqual(
             version_info('11.4R7.5'),
             [('build', 5), ('major', (11, 4)), ('minor', '7'), ('type', 'R')])
 
     def test_version_feature_velocity(self):
-        self.assertItemsEqual(
+        self.assertCountEqual(
             version_info('15.4F7.5'),
             [('build', 5), ('major', (15, 4)), ('minor', '7'), ('type', 'F')])
 
Index: py-junos-eznc-2.3.1/tests/unit/ofacts/test_swver.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/ofacts/test_swver.py    2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/ofacts/test_swver.py 2020-06-02 
09:39:36.380221690 +0200
@@ -1,7 +1,10 @@
 __author__ = "Nitin Kumar, Rick Sherman"
 __credits__ = "Jeremy Schulman"
 
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 from mock import patch, MagicMock
 import os
Index: py-junos-eznc-2.3.1/tests/unit/test_console.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/test_console.py 2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/test_console.py      2020-06-02 
09:40:33.860535824 +0200
@@ -1,4 +1,7 @@
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from jnpr.junos.utils.config import Config
 from nose.plugins.attrib import attr
 from mock import patch, MagicMock, call
Index: py-junos-eznc-2.3.1/tests/unit/test_decorators.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/test_decorators.py      2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/test_decorators.py   2020-06-02 
09:40:56.432659185 +0200
@@ -1,4 +1,7 @@
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 
 from lxml.etree import XML
Index: py-junos-eznc-2.3.1/tests/unit/test_device.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/test_device.py  2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/test_device.py       2020-06-02 
09:41:17.232772861 +0200
@@ -1,4 +1,7 @@
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 from mock import MagicMock, patch, mock_open, call
 import os
Index: py-junos-eznc-2.3.1/tests/unit/test_factcache.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/test_factcache.py       2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/test_factcache.py    2020-06-02 
09:42:15.129089270 +0200
@@ -1,4 +1,7 @@
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 from mock import patch, MagicMock, call
 from jnpr.junos.exception import FactLoopError
Index: py-junos-eznc-2.3.1/tests/unit/transport/test_serial.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/transport/test_serial.py        
2019-12-10 07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/transport/test_serial.py     2020-06-02 
09:44:13.877738198 +0200
@@ -1,4 +1,7 @@
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 from mock import MagicMock, patch
 import sys
Index: py-junos-eznc-2.3.1/tests/unit/transport/test_tty_netconf.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/transport/test_tty_netconf.py   
2019-12-10 07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/transport/test_tty_netconf.py        
2020-06-02 09:44:54.209958559 +0200
@@ -1,4 +1,7 @@
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 from mock import MagicMock, patch
 from jnpr.junos.transport.tty_netconf import tty_netconf
Index: py-junos-eznc-2.3.1/tests/unit/transport/test_tty.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/transport/test_tty.py   2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/transport/test_tty.py        2020-06-02 
09:44:28.917820369 +0200
@@ -1,5 +1,8 @@
 import logging
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 
 from nose.plugins.attrib import attr
 from mock import MagicMock, patch
Index: py-junos-eznc-2.3.1/tests/unit/transport/test_tty_ssh.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/transport/test_tty_ssh.py       
2019-12-10 07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/transport/test_tty_ssh.py    2020-06-02 
09:45:28.666146801 +0200
@@ -1,6 +1,9 @@
 import socket
 import sys
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 from mock import MagicMock, patch
 from jnpr.junos.transport.tty_ssh import SSH
Index: py-junos-eznc-2.3.1/tests/unit/transport/test_tty_telnet.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/transport/test_tty_telnet.py    
2019-12-10 07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/transport/test_tty_telnet.py 2020-06-02 
09:48:57.923290082 +0200
@@ -1,5 +1,8 @@
 import sys
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 from mock import MagicMock, patch
 from jnpr.junos.transport.tty_telnet import Telnet
Index: py-junos-eznc-2.3.1/tests/unit/utils/test_sw.py
===================================================================
--- py-junos-eznc-2.3.1.orig/tests/unit/utils/test_sw.py        2019-12-10 
07:54:12.000000000 +0100
+++ py-junos-eznc-2.3.1/tests/unit/utils/test_sw.py     2020-06-02 
09:43:36.313532958 +0200
@@ -2,7 +2,10 @@ from __future__ import print_function
 import os
 import sys
 from six import StringIO
-import unittest2 as unittest
+try:
+    import unittest2 as unittest
+except ImportError:
+    import unittest
 from nose.plugins.attrib import attr
 from contextlib import contextmanager
 from jnpr.junos import Device

Reply via email to