diff --git a/.dockerignore b/.dockerignore
deleted file mode 100644
index 1069b0df..00000000
--- a/.dockerignore
+++ /dev/null
@@ -1,10 +0,0 @@
-/docs/*
-/tests/*
-/vagrant/*
-INSTALL*
-COPYRIGHT
-LICENSE
-MANIFEST.md
-RELEASE-NOTES.md
-development.txt
-docreq.txt
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 11f1f000..00000000
--- a/Dockerfile
+++ /dev/null
@@ -1,30 +0,0 @@
-FROM alpine:3.4
-
-MAINTAINER ssteiner@juniper.net
-
-RUN mkdir /source \
-    && mkdir /scripts
-
-WORKDIR /source
-
-## Copy project inside the container
-ADD setup.py setup.py
-ADD requirements.txt requirements.txt
-ADD lib lib
-
-## Install dependancies and Pyez
-RUN apk update \
-    && apk upgrade \
-    && apk add build-base gcc g++ make python-dev py-pip py-lxml \
-    libxslt-dev libxml2-dev libffi-dev openssl-dev curl \
-    && pip install -r requirements.txt \
-    && apk del -r --purge gcc make g++ \
-    && python setup.py install \
-    && rm -rf /source/* \
-    && rm -rf /var/cache/apk/*
-
-WORKDIR /scripts
-
-VOLUME ["$PWD:/scripts"]
-
-CMD sh
diff --git a/lib/jnpr/junos/device.py b/lib/jnpr/junos/device.py
index 9c335e50..c2bd3712 100644
--- a/lib/jnpr/junos/device.py
+++ b/lib/jnpr/junos/device.py
@@ -919,7 +919,7 @@ class Device(_Connection):
             #    protocol: operation-failed
             #    error: device asdf not found
             # </rpc-reply>
-            if rpc_rsp_e.text.strip() is not '':
+            if rpc_rsp_e.text is not None and rpc_rsp_e.text.strip() is not '':
                 return rpc_rsp_e
             # no children, so assume it means we are OK
             return True
diff --git a/lib/jnpr/junos/utils/scp.py b/lib/jnpr/junos/utils/scp.py
index 8a150d6c..adc49fd3 100644
--- a/lib/jnpr/junos/utils/scp.py
+++ b/lib/jnpr/junos/utils/scp.py
@@ -73,8 +73,11 @@ class SCP(object):
 
         .. note:: This method uses the same username/password authentication
                    credentials as used by :class:`jnpr.junos.device.Device`.
-                   It can also use ``ssh_private_key_file`` option if provided
-                   to the :class:`jnpr.junos.device.Device` 
+
+        .. warning:: The :class:`jnpr.junos.device.Device` ``ssh_private_key_file``
+                     option is currently **not** supported.
+
+        .. todo:: add support for ``ssh_private_key_file``.
 
         :returns: SCPClient object
         """
@@ -89,7 +92,6 @@ class SCP(object):
         # through a jumphost.
 
         config = {}
-        kwargs = {}
         ssh_config = getattr(junos, '_sshconf_path')
         if ssh_config:
             config = paramiko.SSHConfig()
@@ -99,9 +101,6 @@ class SCP(object):
         if config.get("proxycommand"):
             sock = paramiko.proxy.ProxyCommand(config.get("proxycommand"))
 
-        if self._junos._ssh_private_key_file is not None:
-            kwargs['key_filename']=self._junos._ssh_private_key_file
-
         self._ssh.connect(hostname=junos._hostname,
                           port=(
                               22, int(
@@ -109,7 +108,7 @@ class SCP(object):
                               junos._hostname == 'localhost'],
                           username=junos._auth_user,
                           password=junos._auth_password,
-                          sock=sock, **kwargs
+                          sock=sock
                           )
         return SCPClient(self._ssh.get_transport(), **scpargs)
 
diff --git a/lib/jnpr/junos/utils/start_shell.py b/lib/jnpr/junos/utils/start_shell.py
index 07956c0e..67a0e1e1 100644
--- a/lib/jnpr/junos/utils/start_shell.py
+++ b/lib/jnpr/junos/utils/start_shell.py
@@ -1,7 +1,6 @@
 import paramiko
 from select import select
 import re
-import datetime
 
 _JUNOS_PROMPT = '> '
 _SHELL_PROMPT = '(%|#)\s'
@@ -22,28 +21,20 @@ class StartShell(object):
 
     """
 
-    def __init__(self, nc, timeout=30):
+    def __init__(self, nc):
         """
         Utility Constructor
 
         :param Device nc: The Device object
-
-        :param int timeout:
-          Timeout value in seconds to wait for expected string/pattern.
         """
         self._nc = nc
-        self.timeout = timeout
 
-    def wait_for(self, this=_SHELL_PROMPT, timeout=0):
+    def wait_for(self, this=_SHELL_PROMPT):
         """
         Wait for the result of the command, expecting **this** prompt.
 
         :param str this: expected string/pattern.
 
-        :param int timeout:
-          Timeout value in seconds to wait for expected string/pattern.
-          If not specified defaults to self.timeout.
-
         :returns: resulting string of data in a list
         :rtype: list
 
@@ -51,15 +42,12 @@ class StartShell(object):
         """
         chan = self._chan
         got = []
-        timeout = timeout or self.timeout
-        timeout = datetime.datetime.now()+datetime.timedelta(
-            seconds=timeout)
-        while timeout > datetime.datetime.now():
+        while True:
             rd, wr, err = select([chan], [], [], _SELECT_WAIT)
             if rd:
                 data = chan.recv(_RECVSZ)
                 if isinstance(data, bytes):
-                    data = data.decode('utf-8', 'replace')
+                    data = data.decode('utf-8')
                 got.append(data)
                 if re.search(r'{0}\s?$'.format(this), data):
                     break
@@ -106,7 +94,7 @@ class StartShell(object):
         self._chan.close()
         self._client.close()
 
-    def run(self, command, this=_SHELL_PROMPT, timeout=0):
+    def run(self, command, this=_SHELL_PROMPT):
         """
         Run a shell command and wait for the response.  The return is a
         tuple. The first item is True/False if exit-code is 0.  The second
@@ -114,30 +102,22 @@ class StartShell(object):
 
         :param str command: the shell command to execute
         :param str this: the exected shell-prompt to wait for
-        :param int timeout:
-          Timeout value in seconds to wait for expected string/pattern (this).
-          If not specified defaults to self.timeout. This timeout is specific
-          to individual run call.
 
         :returns: (last_ok, result of the executed shell command (str) )
 
         .. note:: as a *side-effect* this method will set the ``self.last_ok``
                   property.  This property is set to ``True`` if ``$?`` is
-                  "0"; indicating the last shell command was successful else
-                  False
+                  "0"; indicating the last shell command was successful.
         """
-        timeout = timeout or self.timeout
         # run the command and capture the output
         self.send(command)
-        got = ''.join(self.wait_for(this, timeout))
-        self.last_ok = False
-        if this != _SHELL_PROMPT:
-            self.last_ok = re.search(r'{0}\s?$'.format(this), got) is not None
-        elif re.search(r'{0}\s?$'.format(_SHELL_PROMPT), got) is not None:
-            # use $? to get the exit code of the command
-            self.send('echo $?')
-            rc = ''.join(self.wait_for(_SHELL_PROMPT))
-            self.last_ok = rc.find('0') > 0
+        got = ''.join(self.wait_for(this))
+
+        # use $? to get the exit code of the command
+        self.send('echo $?')
+        rc = ''.join(self.wait_for(this))
+        self.last_ok = True if rc.find('0') > 0 else False
+
         return (self.last_ok, got)
 
     # -------------------------------------------------------------------------
@@ -150,4 +130,3 @@ class StartShell(object):
 
     def __exit__(self, exc_ty, exc_val, exc_tb):
         self.close()
-
diff --git a/lib/jnpr/junos/version.py b/lib/jnpr/junos/version.py
index 55ee6d04..95a27f1a 100644
--- a/lib/jnpr/junos/version.py
+++ b/lib/jnpr/junos/version.py
@@ -1,2 +1,2 @@
-VERSION = "2.0.1"
+VERSION = "2.0.0"
 DATE = "2016-Sept-01"
diff --git a/setup.py b/setup.py
index 25a7e84c..53fa4682 100644
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@ if sys.version_info[:2] == (2, 6):
 setup(
     name="junos-eznc",
     namespace_packages=['jnpr'],
-    version="2.0.1",
+    version="2.0.0",
     author="Jeremy Schulman, Nitin Kumar",
     author_email="jnpr-community-netdev@juniper.net",
     description=("Junos 'EZ' automation for non-programmers"),

Config.lock() return exception when normalize is on
>>> dev=Device('172.27.62.164',user='root',passwd='lab123',normalize=True)
>>> dev.open()
Device(172.27.62.164)
>>> cu=Config(dev)
>>> cu.lock()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/root/pyez2.0/lib/python2.7/site-packages/jnpr/junos/utils/config.py", line 467, in lock
    raise LockError(rsp=JXML.remove_namespaces(err.xml))
AttributeError: 'exceptions.AttributeError' object has no attribute 'xml'

If normalize is not enabled, no problem
This problem is introduced in pyez 2.0
diff --git a/tests/unit/utils/test_scp.py b/tests/unit/utils/test_scp.py
index d72571c8..c0ddd22e 100644
--- a/tests/unit/utils/test_scp.py
+++ b/tests/unit/utils/test_scp.py
@@ -99,22 +99,6 @@ class TestScp(unittest.TestCase):
         self.assertEqual(mock_scpclient.mock_calls[0][2]['progress'].__name__,
                          '_scp_progress')
 
-    @patch('ncclient.manager.connect')
-    @patch('paramiko.SSHClient.connect')
-    @patch('scp.SCPClient.put')
-    @patch('scp.SCPClient.__init__')
-    def test_ssh_private_key_file(self, mock_scpclient, mock_put,
-                                  mock_sshclient, mock_ncclient):
-        mock_scpclient.return_value = None
-        package = 'test.tgz'
-        dev = Device(host='1.1.1.1', user='user',
-                     ssh_private_key_file='/Users/test/testkey')
-        dev.open(gather_facts=False)
-        with SCP(dev) as scp:
-            scp.put(package)
-        self.assertEqual(mock_sshclient.mock_calls[0][2]['key_filename'],
-                         '/Users/test/testkey')
-
     @contextmanager
     def capture(self, command, *args, **kwargs):
         out, sys.stdout = sys.stdout, StringIO()
diff --git a/tests/unit/utils/test_start_shell.py b/tests/unit/utils/test_start_shell.py
index d429748d..eb6dcdca 100644
--- a/tests/unit/utils/test_start_shell.py
+++ b/tests/unit/utils/test_start_shell.py
@@ -42,7 +42,6 @@ class TestStartShell(unittest.TestCase):
     @patch('jnpr.junos.utils.start_shell.StartShell.wait_for')
     def test_startshell_run(self, mock_wait):
         self.shell._chan = MagicMock()
-        mock_wait.return_value = ["user % "]
         self.shell.run('ls')
         self.assertTrue(call.send('echo $?') in self.shell._chan.mock_calls)
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
coverage mock nose pep8 pyflakes coveralls ntc_templates cryptography==3.2 pytest
apt-get update apt-get install -y gcc
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
bcrypt==4.2.1
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
charset-normalizer==3.4.1
coverage==6.5.0
coveralls==3.3.1
cryptography==44.0.2
docopt==0.6.2
exceptiongroup==1.2.2
future==1.0.0
idna==3.10
importlib-metadata==6.7.0
iniconfig==2.0.0
Jinja2==3.1.6
-e git+https://github.com/Juniper/py-junos-eznc.git@aa2d97698f9b741413a9b04b468fe78a168ac0ad#egg=junos_eznc
lxml==5.3.1
MarkupSafe==2.1.5
mock==5.2.0
ncclient==0.6.19
netaddr==1.3.0
nose==1.3.7
ntc_templates==4.0.1
packaging==24.0
paramiko==3.5.1
pep8==1.7.1
pluggy==1.2.0
pycparser==2.21
pyflakes==3.0.1
PyNaCl==1.5.0
pyserial==3.5
pytest==7.4.4
PyYAML==6.0.1
requests==2.31.0
scp==0.15.0
six==1.17.0
textfsm==1.1.3
tomli==2.0.1
typing_extensions==4.7.1
urllib3==2.0.7
zipp==3.15.0

name: py-junos-eznc
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - bcrypt==4.2.1
      - cffi==1.15.1
      - charset-normalizer==3.4.1
      - coverage==6.5.0
      - coveralls==3.3.1
      - cryptography==44.0.2
      - docopt==0.6.2
      - exceptiongroup==1.2.2
      - future==1.0.0
      - idna==3.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - jinja2==3.1.6
      - lxml==5.3.1
      - markupsafe==2.1.5
      - mock==5.2.0
      - ncclient==0.6.19
      - netaddr==1.3.0
      - nose==1.3.7
      - ntc-templates==4.0.1
      - packaging==24.0
      - paramiko==3.5.1
      - pep8==1.7.1
      - pluggy==1.2.0
      - pycparser==2.21
      - pyflakes==3.0.1
      - pynacl==1.5.0
      - pyserial==3.5
      - pytest==7.4.4
      - pyyaml==6.0.1
      - requests==2.31.0
      - scp==0.15.0
      - six==1.17.0
      - textfsm==1.1.3
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - zipp==3.15.0
prefix: /opt/conda/envs/py-junos-eznc

tests/unit/utils/test_start_shell.py::TestStartShell::test_startshell_run
tests/unit/utils/test_scp.py::TestScp::test_scp_proxycommand tests/unit/utils/test_start_shell.py::TestStartShell::test_startshell_context
tests/unit/utils/test_scp.py::TestScp::test_scp_close tests/unit/utils/test_scp.py::TestScp::test_scp_context tests/unit/utils/test_scp.py::TestScp::test_scp_open tests/unit/utils/test_scp.py::TestScp::test_scp_progress tests/unit/utils/test_scp.py::TestScp::test_scp_progress_true tests/unit/utils/test_scp.py::TestScp::test_scp_user_def_progress tests/unit/utils/test_scp.py::TestScp::test_scp_user_def_progress_args_2 tests/unit/utils/test_start_shell.py::TestStartShell::test_startshell_close tests/unit/utils/test_start_shell.py::TestStartShell::test_startshell_open_with_junos_term tests/unit/utils/test_start_shell.py::TestStartShell::test_startshell_open_with_shell_term tests/unit/utils/test_start_shell.py::TestStartShell::test_startshell_wait_for tests/unit/utils/test_start_shell.py::TestStartShell::test_startshell_wait_for_regex
lib/jnpr/junos/utils/scp.py setup.py lib/jnpr/junos/utils/start_shell.py lib/jnpr/junos/device.py lib/jnpr/junos/version.py Dockerfile .dockerignore
lib/jnpr/junos/utils/scp.py setup.py lib/jnpr/junos/utils/start_shell.py lib/jnpr/junos/device.py lib/jnpr/junos/version.py Dockerfile .dockerignore