scrapli 2023.7.30__py3-none-any.whl → 2024.7.30__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scrapli/__init__.py +2 -1
- scrapli/channel/__init__.py +1 -0
- scrapli/channel/async_channel.py +35 -12
- scrapli/channel/base_channel.py +25 -3
- scrapli/channel/sync_channel.py +35 -12
- scrapli/decorators.py +1 -0
- scrapli/driver/__init__.py +1 -0
- scrapli/driver/base/__init__.py +1 -0
- scrapli/driver/base/async_driver.py +19 -13
- scrapli/driver/base/base_driver.py +121 -37
- scrapli/driver/base/sync_driver.py +19 -13
- scrapli/driver/core/__init__.py +1 -0
- scrapli/driver/core/arista_eos/__init__.py +1 -0
- scrapli/driver/core/arista_eos/async_driver.py +3 -0
- scrapli/driver/core/arista_eos/base_driver.py +3 -2
- scrapli/driver/core/arista_eos/sync_driver.py +3 -0
- scrapli/driver/core/cisco_iosxe/__init__.py +1 -0
- scrapli/driver/core/cisco_iosxe/async_driver.py +3 -0
- scrapli/driver/core/cisco_iosxe/base_driver.py +1 -0
- scrapli/driver/core/cisco_iosxe/sync_driver.py +3 -0
- scrapli/driver/core/cisco_iosxr/__init__.py +1 -0
- scrapli/driver/core/cisco_iosxr/async_driver.py +3 -0
- scrapli/driver/core/cisco_iosxr/base_driver.py +1 -0
- scrapli/driver/core/cisco_iosxr/sync_driver.py +3 -0
- scrapli/driver/core/cisco_nxos/__init__.py +1 -0
- scrapli/driver/core/cisco_nxos/async_driver.py +3 -0
- scrapli/driver/core/cisco_nxos/base_driver.py +9 -4
- scrapli/driver/core/cisco_nxos/sync_driver.py +3 -0
- scrapli/driver/core/juniper_junos/__init__.py +1 -0
- scrapli/driver/core/juniper_junos/async_driver.py +3 -0
- scrapli/driver/core/juniper_junos/base_driver.py +1 -0
- scrapli/driver/core/juniper_junos/sync_driver.py +3 -0
- scrapli/driver/generic/__init__.py +1 -0
- scrapli/driver/generic/async_driver.py +45 -3
- scrapli/driver/generic/base_driver.py +2 -1
- scrapli/driver/generic/sync_driver.py +45 -3
- scrapli/driver/network/__init__.py +1 -0
- scrapli/driver/network/async_driver.py +27 -0
- scrapli/driver/network/base_driver.py +1 -0
- scrapli/driver/network/sync_driver.py +27 -0
- scrapli/exceptions.py +1 -0
- scrapli/factory.py +22 -3
- scrapli/helper.py +76 -4
- scrapli/logging.py +1 -0
- scrapli/response.py +1 -0
- scrapli/ssh_config.py +1 -0
- scrapli/transport/base/__init__.py +1 -0
- scrapli/transport/base/async_transport.py +1 -0
- scrapli/transport/base/base_socket.py +1 -0
- scrapli/transport/base/base_transport.py +1 -0
- scrapli/transport/base/sync_transport.py +1 -0
- scrapli/transport/plugins/asyncssh/transport.py +4 -0
- scrapli/transport/plugins/asynctelnet/transport.py +13 -6
- scrapli/transport/plugins/paramiko/transport.py +1 -0
- scrapli/transport/plugins/ssh2/transport.py +6 -3
- scrapli/transport/plugins/system/ptyprocess.py +50 -13
- scrapli/transport/plugins/system/transport.py +27 -6
- scrapli/transport/plugins/telnet/transport.py +13 -7
- {scrapli-2023.7.30.dist-info → scrapli-2024.7.30.dist-info}/METADATA +74 -47
- scrapli-2024.7.30.dist-info/RECORD +74 -0
- {scrapli-2023.7.30.dist-info → scrapli-2024.7.30.dist-info}/WHEEL +1 -1
- scrapli-2023.7.30.dist-info/RECORD +0 -74
- {scrapli-2023.7.30.dist-info → scrapli-2024.7.30.dist-info}/LICENSE +0 -0
- {scrapli-2023.7.30.dist-info → scrapli-2024.7.30.dist-info}/top_level.txt +0 -0
scrapli/helper.py
CHANGED
@@ -1,19 +1,35 @@
|
|
1
1
|
"""scrapli.helper"""
|
2
|
+
|
2
3
|
import importlib
|
4
|
+
import importlib.resources
|
5
|
+
import sys
|
3
6
|
import urllib.request
|
4
7
|
from io import BytesIO, TextIOWrapper
|
5
8
|
from pathlib import Path
|
6
9
|
from shutil import get_terminal_size
|
7
|
-
from typing import Any, Dict, List, Optional, TextIO, Union
|
10
|
+
from typing import Any, Dict, List, Optional, TextIO, Tuple, Union
|
8
11
|
from warnings import warn
|
9
12
|
|
10
|
-
import pkg_resources
|
11
|
-
|
12
13
|
from scrapli.exceptions import ScrapliValueError
|
13
14
|
from scrapli.logging import logger
|
14
15
|
from scrapli.settings import Settings
|
15
16
|
|
16
17
|
|
18
|
+
def _textfsm_get_template_directory() -> str:
|
19
|
+
if sys.version_info >= (3, 9):
|
20
|
+
return f"{importlib.resources.files('ntc_templates')}/templates"
|
21
|
+
|
22
|
+
if sys.version_info >= (3, 11):
|
23
|
+
# https://docs.python.org/3/library/importlib.resources.html#importlib.resources.path
|
24
|
+
with importlib.resources.as_file(
|
25
|
+
importlib.resources.files("ntc_templates").joinpath("templates")
|
26
|
+
) as path:
|
27
|
+
return str(path)
|
28
|
+
|
29
|
+
with importlib.resources.path("ntc_templates", "templates") as path: # pylint: disable=W4902
|
30
|
+
return str(path)
|
31
|
+
|
32
|
+
|
17
33
|
def _textfsm_get_template(platform: str, command: str) -> Optional[TextIO]:
|
18
34
|
"""
|
19
35
|
Find correct TextFSM template based on platform and command executed
|
@@ -43,7 +59,9 @@ def _textfsm_get_template(platform: str, command: str) -> Optional[TextIO]:
|
|
43
59
|
)
|
44
60
|
user_warning(title=title, message=message)
|
45
61
|
return None
|
46
|
-
|
62
|
+
|
63
|
+
template_dir = _textfsm_get_template_directory()
|
64
|
+
|
47
65
|
cli_table = CliTable("index", template_dir)
|
48
66
|
template_index = cli_table.index.GetRowMatch({"Platform": platform, "Command": command})
|
49
67
|
if not template_index:
|
@@ -296,3 +314,57 @@ def user_warning(title: str, message: str) -> None:
|
|
296
314
|
|
297
315
|
if Settings.SUPPRESS_USER_WARNINGS is False:
|
298
316
|
warn(warning_message)
|
317
|
+
|
318
|
+
|
319
|
+
def output_roughly_contains_input(input_: bytes, output: bytes) -> bool:
|
320
|
+
"""
|
321
|
+
Return True if all characters in input are contained in order in the given output.
|
322
|
+
|
323
|
+
Args:
|
324
|
+
input_: the input presented to a device
|
325
|
+
output: the output echoed on the channel
|
326
|
+
|
327
|
+
Returns:
|
328
|
+
bool: True if the input is "roughly" contained in the output, otherwise False
|
329
|
+
|
330
|
+
Raises:
|
331
|
+
N/A
|
332
|
+
|
333
|
+
"""
|
334
|
+
if output in input_:
|
335
|
+
return True
|
336
|
+
|
337
|
+
if len(output) < len(input_):
|
338
|
+
return False
|
339
|
+
|
340
|
+
for char in input_:
|
341
|
+
should_continue, output = _roughly_contains_input_iter_output_for_input_char(char, output)
|
342
|
+
|
343
|
+
if not should_continue:
|
344
|
+
return False
|
345
|
+
|
346
|
+
return True
|
347
|
+
|
348
|
+
|
349
|
+
def _roughly_contains_input_iter_output_for_input_char(
|
350
|
+
char: int, output: bytes
|
351
|
+
) -> Tuple[bool, bytes]:
|
352
|
+
"""
|
353
|
+
Iterate over chars in the output to find input, returns remaining output bytes if input found.
|
354
|
+
|
355
|
+
Args:
|
356
|
+
char: input char to find in output
|
357
|
+
output: the output echoed on the channel
|
358
|
+
|
359
|
+
Returns:
|
360
|
+
output: bool indicating char was found, and remaining output chars to continue searching in
|
361
|
+
|
362
|
+
Raises:
|
363
|
+
N/A
|
364
|
+
|
365
|
+
"""
|
366
|
+
for index, output_char in enumerate(output):
|
367
|
+
if char == output_char:
|
368
|
+
return True, output[index + 1 :] # noqa: E203
|
369
|
+
|
370
|
+
return False, b""
|
scrapli/logging.py
CHANGED
scrapli/response.py
CHANGED
scrapli/ssh_config.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.transport.plugins.asyncssh.transport"""
|
2
|
+
|
2
3
|
import asyncio
|
3
4
|
from contextlib import suppress
|
4
5
|
from dataclasses import dataclass
|
@@ -259,6 +260,9 @@ class AsyncsshTransport(AsyncTransport):
|
|
259
260
|
if not self.stdout:
|
260
261
|
raise ScrapliConnectionNotOpened
|
261
262
|
|
263
|
+
if self.stdout.at_eof():
|
264
|
+
raise ScrapliConnectionError("transport at EOF; no more data to be read")
|
265
|
+
|
262
266
|
try:
|
263
267
|
buf: bytes = await self.stdout.read(65535)
|
264
268
|
except ConnectionLost as exc:
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.transport.plugins.asynctelnet.transport"""
|
2
|
+
|
2
3
|
import asyncio
|
3
4
|
import socket
|
4
5
|
from contextlib import suppress
|
@@ -116,6 +117,18 @@ class AsynctelnetTransport(AsyncTransport):
|
|
116
117
|
if not self.stdout:
|
117
118
|
raise ScrapliConnectionNotOpened
|
118
119
|
|
120
|
+
if self._raw_buf.find(NULL) != -1:
|
121
|
+
raise ScrapliConnectionNotOpened("server returned EOF, connection not opened")
|
122
|
+
|
123
|
+
index = self._raw_buf.find(IAC)
|
124
|
+
if index == -1:
|
125
|
+
self._cooked_buf = self._raw_buf
|
126
|
+
self._raw_buf = b""
|
127
|
+
return
|
128
|
+
|
129
|
+
self._cooked_buf = self._raw_buf[:index]
|
130
|
+
self._raw_buf = self._raw_buf[index:]
|
131
|
+
|
119
132
|
# control_buf is the buffer for control characters, we reset this after being "done" with
|
120
133
|
# responding to a control sequence, so it always represents the "current" control sequence
|
121
134
|
# we are working on responding to
|
@@ -123,9 +136,6 @@ class AsynctelnetTransport(AsyncTransport):
|
|
123
136
|
|
124
137
|
while self._raw_buf:
|
125
138
|
c, self._raw_buf = self._raw_buf[:1], self._raw_buf[1:]
|
126
|
-
if not c:
|
127
|
-
raise ScrapliConnectionNotOpened("server returned EOF, connection not opened")
|
128
|
-
|
129
139
|
control_buf = self._handle_control_chars_response(control_buf=control_buf, c=c)
|
130
140
|
|
131
141
|
async def open(self) -> None:
|
@@ -204,9 +214,6 @@ class AsynctelnetTransport(AsyncTransport):
|
|
204
214
|
if not self.stdout:
|
205
215
|
raise ScrapliConnectionNotOpened
|
206
216
|
|
207
|
-
if self._control_char_sent_counter < self._control_char_sent_limit:
|
208
|
-
self._handle_control_chars()
|
209
|
-
|
210
217
|
while not self._cooked_buf and not self._eof:
|
211
218
|
await self._read()
|
212
219
|
if self._control_char_sent_counter < self._control_char_sent_limit:
|
@@ -1,12 +1,15 @@
|
|
1
1
|
"""scrapli.transport.plugins.ssh2.transport"""
|
2
|
+
|
2
3
|
import base64
|
3
4
|
from contextlib import suppress
|
4
5
|
from dataclasses import dataclass
|
5
6
|
from typing import Optional
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
from ssh2.
|
8
|
+
# ignoring unable to import complaints for linters as ssh2 support is a bit lackluster due to
|
9
|
+
# upstream library staleness
|
10
|
+
from ssh2.channel import Channel # pylint: disable=E0401,E0611
|
11
|
+
from ssh2.exceptions import AuthenticationError, SSH2Error # pylint: disable=E0401,E0611
|
12
|
+
from ssh2.session import Session # pylint: disable=E0401,E0611
|
10
13
|
|
11
14
|
from scrapli.exceptions import (
|
12
15
|
ScrapliAuthenticationFailed,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.transport.plugins.system.ptyprocess"""
|
2
|
+
|
2
3
|
"""
|
3
4
|
Ptyprocess is under the ISC license, as code derived from Pexpect.
|
4
5
|
http://opensource.org/licenses/ISC
|
@@ -33,6 +34,7 @@ from shutil import which
|
|
33
34
|
from typing import List, Optional, Type, TypeVar
|
34
35
|
|
35
36
|
from scrapli.exceptions import ScrapliValueError
|
37
|
+
from scrapli.helper import user_warning
|
36
38
|
|
37
39
|
|
38
40
|
class PtyProcessError(Exception):
|
@@ -85,7 +87,7 @@ def _make_eof_intr() -> None:
|
|
85
87
|
raise ValueError("No stream has a fileno")
|
86
88
|
intr = ord(termios.tcgetattr(fd)[6][VINTR])
|
87
89
|
eof = ord(termios.tcgetattr(fd)[6][VEOF])
|
88
|
-
except (ImportError, OSError,
|
90
|
+
except (ImportError, OSError, ValueError, termios.error):
|
89
91
|
# unless the controlling process is also not a terminal,
|
90
92
|
# such as cron(1), or when stdin and stdout are both closed.
|
91
93
|
# Fall-back to using CEOF and CINTR. There
|
@@ -141,9 +143,9 @@ def _setecho(fd: int, state: bool) -> None:
|
|
141
143
|
None
|
142
144
|
|
143
145
|
Raises:
|
144
|
-
|
146
|
+
OSError: if termios raises an exception getting the fd or raises an exception setting the
|
147
|
+
echo state on the fd
|
145
148
|
termios.error: also if termios rasies an exception gettign fd... unclear why the two errors!
|
146
|
-
IOError: if termios raises an exception setting the echo state on the fd
|
147
149
|
|
148
150
|
"""
|
149
151
|
import termios
|
@@ -157,7 +159,7 @@ def _setecho(fd: int, state: bool) -> None:
|
|
157
159
|
attr = termios.tcgetattr(fd)
|
158
160
|
except termios.error as err:
|
159
161
|
if err.args[0] == errno.EINVAL:
|
160
|
-
raise
|
162
|
+
raise OSError(err.args[0], "{}: {}.".format(err.args[1], errmsg))
|
161
163
|
raise
|
162
164
|
|
163
165
|
if state:
|
@@ -169,9 +171,38 @@ def _setecho(fd: int, state: bool) -> None:
|
|
169
171
|
# I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and
|
170
172
|
# blocked on some platforms. TCSADRAIN would probably be ideal.
|
171
173
|
termios.tcsetattr(fd, termios.TCSANOW, attr)
|
172
|
-
except
|
174
|
+
except OSError as err:
|
175
|
+
if err.args[0] == errno.EINVAL:
|
176
|
+
raise OSError(err.args[0], "{}: {}.".format(err.args[1], errmsg))
|
177
|
+
raise
|
178
|
+
|
179
|
+
|
180
|
+
def _setonlcr(fd: int, state: bool) -> None:
|
181
|
+
import termios
|
182
|
+
|
183
|
+
try:
|
184
|
+
attr = termios.tcgetattr(fd)
|
185
|
+
except termios.error as err:
|
173
186
|
if err.args[0] == errno.EINVAL:
|
174
|
-
raise
|
187
|
+
raise OSError(err.args[0], "{}: {}.".format(err.args[1], errmsg))
|
188
|
+
raise
|
189
|
+
|
190
|
+
if state:
|
191
|
+
attr[1] = attr[1] | termios.ONLCR
|
192
|
+
else:
|
193
|
+
attr[1] = attr[1] & ~termios.ONLCR
|
194
|
+
|
195
|
+
try:
|
196
|
+
termios.tcsetattr(fd, termios.TCSANOW, attr)
|
197
|
+
except OSError as err:
|
198
|
+
if err.args[0] == errno.EINVAL:
|
199
|
+
title = "Set ONLCR!"
|
200
|
+
message = (
|
201
|
+
"_setonlcr() failed -- if you encounter this error please open an issue! unless you "
|
202
|
+
"are seeing this when using scrapli_netconf you can *probably* ignore this though!"
|
203
|
+
)
|
204
|
+
|
205
|
+
user_warning(title=title, message=message)
|
175
206
|
raise
|
176
207
|
|
177
208
|
|
@@ -196,8 +227,8 @@ class PtyProcess:
|
|
196
227
|
_make_eof_intr() # Ensure _EOF and _INTR are calculated
|
197
228
|
self.pid = pid
|
198
229
|
self.fd = fd
|
199
|
-
readf =
|
200
|
-
writef =
|
230
|
+
readf = open(fd, "rb", buffering=0)
|
231
|
+
writef = open(fd, "wb", buffering=0, closefd=False)
|
201
232
|
self.fileobj = io.BufferedRWPair(readf, writef) # type: ignore
|
202
233
|
|
203
234
|
self.terminated = False
|
@@ -244,7 +275,7 @@ class PtyProcess:
|
|
244
275
|
ScrapliValueError: if no ssh binary found on PATH
|
245
276
|
Exception: IOError - if unable to set window size of child process
|
246
277
|
Exception: OSError - if unable to spawn command in child process
|
247
|
-
|
278
|
+
OSError: failing to reset window size
|
248
279
|
exception: if we get an exception decoding output
|
249
280
|
|
250
281
|
"""
|
@@ -283,7 +314,7 @@ class PtyProcess:
|
|
283
314
|
if pid == CHILD:
|
284
315
|
try:
|
285
316
|
_setwinsize(fd=STDIN_FILENO, rows=rows, cols=cols)
|
286
|
-
except
|
317
|
+
except OSError as err:
|
287
318
|
if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
|
288
319
|
raise
|
289
320
|
|
@@ -291,7 +322,7 @@ class PtyProcess:
|
|
291
322
|
if echo is False:
|
292
323
|
try:
|
293
324
|
_setecho(STDIN_FILENO, False)
|
294
|
-
except (
|
325
|
+
except (OSError, termios.error) as err:
|
295
326
|
if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
|
296
327
|
raise
|
297
328
|
|
@@ -350,10 +381,16 @@ class PtyProcess:
|
|
350
381
|
|
351
382
|
try:
|
352
383
|
inst.setwinsize(rows=rows, cols=cols)
|
353
|
-
except
|
384
|
+
except OSError as err:
|
354
385
|
if err.args[0] not in (errno.EINVAL, errno.ENOTTY, errno.ENXIO):
|
355
386
|
raise
|
356
387
|
|
388
|
+
# attrs = termios.tcgetattr(fd)
|
389
|
+
# attrs[1] &= ~termios.ONLCR
|
390
|
+
# termios.tcsetattr(fd, termios.TCSANOW, attrs)
|
391
|
+
|
392
|
+
_setonlcr(fd, True)
|
393
|
+
|
357
394
|
return inst
|
358
395
|
|
359
396
|
def __repr__(self) -> str:
|
@@ -481,7 +518,7 @@ class PtyProcess:
|
|
481
518
|
"""
|
482
519
|
try:
|
483
520
|
s = self.fileobj.read1(size)
|
484
|
-
except
|
521
|
+
except OSError as err:
|
485
522
|
if err.args[0] == errno.EIO:
|
486
523
|
# Linux-style EOF
|
487
524
|
self.flag_eof = True
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.transport.plugins.system.transport"""
|
2
|
+
|
2
3
|
import sys
|
3
4
|
from dataclasses import dataclass
|
4
5
|
from typing import List, Optional
|
@@ -23,6 +24,9 @@ class PluginTransportArgs(BasePluginTransportArgs):
|
|
23
24
|
|
24
25
|
|
25
26
|
class SystemTransport(Transport):
|
27
|
+
SSH_SYSTEM_CONFIG_MAGIC_STRING: str = "SYSTEM_TRANSPORT_SSH_CONFIG_TRUE"
|
28
|
+
SSH_SYSTEM_KNOWN_HOSTS_FILE_MAGIC_STRING: str = "SYSTEM_TRANSPORT_KNOWN_HOSTS_TRUE"
|
29
|
+
|
26
30
|
def __init__(
|
27
31
|
self, base_transport_args: BaseTransportArgs, plugin_transport_args: PluginTransportArgs
|
28
32
|
) -> None:
|
@@ -99,15 +103,32 @@ class SystemTransport(Transport):
|
|
99
103
|
self.open_cmd.extend(["-o", "UserKnownHostsFile=/dev/null"])
|
100
104
|
else:
|
101
105
|
self.open_cmd.extend(["-o", "StrictHostKeyChecking=yes"])
|
102
|
-
|
106
|
+
|
107
|
+
if (
|
108
|
+
self.plugin_transport_args.ssh_known_hosts_file
|
109
|
+
== self.SSH_SYSTEM_KNOWN_HOSTS_FILE_MAGIC_STRING
|
110
|
+
):
|
111
|
+
self.logger.debug(
|
112
|
+
"Using system transport and ssh_known_hosts_file is True, not specifying any "
|
113
|
+
"known_hosts file"
|
114
|
+
)
|
115
|
+
elif self.plugin_transport_args.ssh_known_hosts_file:
|
103
116
|
self.open_cmd.extend(
|
104
|
-
[
|
117
|
+
[
|
118
|
+
"-o",
|
119
|
+
f"UserKnownHostsFile={self.plugin_transport_args.ssh_known_hosts_file}",
|
120
|
+
]
|
105
121
|
)
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
else:
|
122
|
+
else:
|
123
|
+
self.logger.debug("No known hosts file specified")
|
124
|
+
if not self.plugin_transport_args.ssh_config_file:
|
110
125
|
self.open_cmd.extend(["-F", "/dev/null"])
|
126
|
+
elif self.plugin_transport_args.ssh_config_file == self.SSH_SYSTEM_CONFIG_MAGIC_STRING:
|
127
|
+
self.logger.debug(
|
128
|
+
"Using system transport and ssh_config is True, not specifying any SSH config"
|
129
|
+
)
|
130
|
+
else:
|
131
|
+
self.open_cmd.extend(["-F", self.plugin_transport_args.ssh_config_file])
|
111
132
|
|
112
133
|
open_cmd_user_args = self._base_transport_args.transport_options.get("open_cmd", [])
|
113
134
|
if isinstance(open_cmd_user_args, str):
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.transport.plugins.telnet.transport"""
|
2
|
+
|
2
3
|
from dataclasses import dataclass
|
3
4
|
from typing import Optional
|
4
5
|
|
@@ -147,13 +148,21 @@ class TelnetTransport(Transport):
|
|
147
148
|
if not self.socket:
|
148
149
|
raise ScrapliConnectionNotOpened
|
149
150
|
|
151
|
+
if self._raw_buf.find(NULL) != -1:
|
152
|
+
raise ScrapliConnectionNotOpened("server returned EOF, connection not opened")
|
153
|
+
|
154
|
+
index = self._raw_buf.find(IAC)
|
155
|
+
if index == -1:
|
156
|
+
self._cooked_buf = self._raw_buf
|
157
|
+
self._raw_buf = b""
|
158
|
+
return
|
159
|
+
|
160
|
+
self._cooked_buf = self._raw_buf[:index]
|
161
|
+
self._raw_buf = self._raw_buf[index:]
|
150
162
|
control_buf = b""
|
151
163
|
|
152
164
|
while self._raw_buf:
|
153
165
|
c, self._raw_buf = self._raw_buf[:1], self._raw_buf[1:]
|
154
|
-
if not c:
|
155
|
-
raise ScrapliConnectionNotOpened("server returned EOF, connection not opened")
|
156
|
-
|
157
166
|
control_buf = self._handle_control_chars_response(control_buf=control_buf, c=c)
|
158
167
|
|
159
168
|
def open(self) -> None:
|
@@ -217,7 +226,7 @@ class TelnetTransport(Transport):
|
|
217
226
|
self._raw_buf += buf
|
218
227
|
else:
|
219
228
|
self._cooked_buf += buf
|
220
|
-
except
|
229
|
+
except EOFError as exc:
|
221
230
|
raise ScrapliConnectionError(
|
222
231
|
"encountered EOF reading from transport; typically means the device closed the "
|
223
232
|
"connection"
|
@@ -228,9 +237,6 @@ class TelnetTransport(Transport):
|
|
228
237
|
if not self.socket:
|
229
238
|
raise ScrapliConnectionNotOpened
|
230
239
|
|
231
|
-
if self._control_char_sent_counter < self._control_char_sent_limit:
|
232
|
-
self._handle_control_chars()
|
233
|
-
|
234
240
|
while not self._cooked_buf and not self._eof:
|
235
241
|
self._read()
|
236
242
|
if self._control_char_sent_counter < self._control_char_sent_limit:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: scrapli
|
3
|
-
Version:
|
3
|
+
Version: 2024.7.30
|
4
4
|
Summary: Fast, flexible, sync/async, Python 3.7+ screen scraping client specifically for network devices
|
5
5
|
Author-email: Carl Montanari <carl.r.montanari@gmail.com>
|
6
6
|
License: MIT License
|
@@ -33,69 +33,96 @@ Classifier: License :: OSI Approved :: MIT License
|
|
33
33
|
Classifier: Operating System :: POSIX :: Linux
|
34
34
|
Classifier: Operating System :: MacOS
|
35
35
|
Classifier: Programming Language :: Python
|
36
|
-
Classifier: Programming Language :: Python :: 3.7
|
37
36
|
Classifier: Programming Language :: Python :: 3.8
|
38
37
|
Classifier: Programming Language :: Python :: 3.9
|
39
38
|
Classifier: Programming Language :: Python :: 3.10
|
40
39
|
Classifier: Programming Language :: Python :: 3.11
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
41
41
|
Classifier: Programming Language :: Python :: 3 :: Only
|
42
42
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
43
|
-
Requires-Python: >=3.
|
43
|
+
Requires-Python: >=3.8
|
44
44
|
Description-Content-Type: text/markdown
|
45
45
|
License-File: LICENSE
|
46
46
|
Provides-Extra: asyncssh
|
47
|
-
Requires-Dist: asyncssh
|
47
|
+
Requires-Dist: asyncssh <3.0.0,>=2.2.1 ; extra == 'asyncssh'
|
48
48
|
Provides-Extra: community
|
49
|
-
Requires-Dist: scrapli-community
|
49
|
+
Requires-Dist: scrapli-community >=2021.01.30 ; extra == 'community'
|
50
50
|
Provides-Extra: dev
|
51
|
-
Requires-Dist: black
|
52
|
-
Requires-Dist: darglint
|
53
|
-
Requires-Dist: isort
|
54
|
-
Requires-Dist: mypy
|
55
|
-
Requires-Dist: nox
|
56
|
-
Requires-Dist: pycodestyle
|
57
|
-
Requires-Dist: pydocstyle
|
58
|
-
Requires-Dist: pyfakefs
|
59
|
-
Requires-Dist: pylama
|
60
|
-
Requires-Dist: pylint
|
61
|
-
Requires-Dist: pytest-asyncio
|
62
|
-
Requires-Dist: pytest-cov
|
63
|
-
Requires-Dist: pytest
|
64
|
-
Requires-Dist: scrapli-cfg
|
65
|
-
Requires-Dist: scrapli-replay
|
66
|
-
Requires-Dist: toml
|
67
|
-
Requires-Dist: types-paramiko
|
68
|
-
Requires-Dist: types-pkg-resources
|
69
|
-
Requires-Dist: ntc-templates
|
70
|
-
Requires-Dist: textfsm
|
71
|
-
Requires-Dist: ttp
|
72
|
-
Requires-Dist: paramiko
|
73
|
-
Requires-Dist: asyncssh
|
74
|
-
Requires-Dist: scrapli-community
|
75
|
-
|
76
|
-
Requires-Dist:
|
77
|
-
Requires-Dist:
|
51
|
+
Requires-Dist: black <25.0.0,>=23.3.0 ; extra == 'dev'
|
52
|
+
Requires-Dist: darglint <2.0.0,>=1.8.1 ; extra == 'dev'
|
53
|
+
Requires-Dist: isort <6.0.0,>=5.10.1 ; extra == 'dev'
|
54
|
+
Requires-Dist: mypy <2.0.0,>=1.4.1 ; extra == 'dev'
|
55
|
+
Requires-Dist: nox ==2024.4.15 ; extra == 'dev'
|
56
|
+
Requires-Dist: pycodestyle <3.0.0,>=2.8.0 ; extra == 'dev'
|
57
|
+
Requires-Dist: pydocstyle <7.0.0,>=6.1.1 ; extra == 'dev'
|
58
|
+
Requires-Dist: pyfakefs <6.0.0,>=5.4.1 ; extra == 'dev'
|
59
|
+
Requires-Dist: pylama <9.0.0,>=8.4.0 ; extra == 'dev'
|
60
|
+
Requires-Dist: pylint <4.0.0,>=3.0.0 ; extra == 'dev'
|
61
|
+
Requires-Dist: pytest-asyncio <1.0.0,>=0.17.0 ; extra == 'dev'
|
62
|
+
Requires-Dist: pytest-cov <5.0.0,>=3.0.0 ; extra == 'dev'
|
63
|
+
Requires-Dist: pytest <8.0.0,>=7.0.0 ; extra == 'dev'
|
64
|
+
Requires-Dist: scrapli-cfg ==2023.7.30 ; extra == 'dev'
|
65
|
+
Requires-Dist: scrapli-replay ==2023.7.30 ; extra == 'dev'
|
66
|
+
Requires-Dist: toml <1.0.0,>=0.10.2 ; extra == 'dev'
|
67
|
+
Requires-Dist: types-paramiko <4.0.0,>=2.8.6 ; extra == 'dev'
|
68
|
+
Requires-Dist: types-pkg-resources <1.0.0,>=0.1.3 ; extra == 'dev'
|
69
|
+
Requires-Dist: ntc-templates <5.0.0,>=1.1.0 ; extra == 'dev'
|
70
|
+
Requires-Dist: textfsm <2.0.0,>=1.1.0 ; extra == 'dev'
|
71
|
+
Requires-Dist: ttp <1.0.0,>=0.5.0 ; extra == 'dev'
|
72
|
+
Requires-Dist: paramiko <4.0.0,>=2.6.0 ; extra == 'dev'
|
73
|
+
Requires-Dist: asyncssh <3.0.0,>=2.2.1 ; extra == 'dev'
|
74
|
+
Requires-Dist: scrapli-community >=2021.01.30 ; extra == 'dev'
|
75
|
+
Provides-Extra: dev-darwin
|
76
|
+
Requires-Dist: black <25.0.0,>=23.3.0 ; extra == 'dev-darwin'
|
77
|
+
Requires-Dist: darglint <2.0.0,>=1.8.1 ; extra == 'dev-darwin'
|
78
|
+
Requires-Dist: isort <6.0.0,>=5.10.1 ; extra == 'dev-darwin'
|
79
|
+
Requires-Dist: mypy <2.0.0,>=1.4.1 ; extra == 'dev-darwin'
|
80
|
+
Requires-Dist: nox ==2024.4.15 ; extra == 'dev-darwin'
|
81
|
+
Requires-Dist: pycodestyle <3.0.0,>=2.8.0 ; extra == 'dev-darwin'
|
82
|
+
Requires-Dist: pydocstyle <7.0.0,>=6.1.1 ; extra == 'dev-darwin'
|
83
|
+
Requires-Dist: pyfakefs <6.0.0,>=5.4.1 ; extra == 'dev-darwin'
|
84
|
+
Requires-Dist: pylama <9.0.0,>=8.4.0 ; extra == 'dev-darwin'
|
85
|
+
Requires-Dist: pylint <4.0.0,>=3.0.0 ; extra == 'dev-darwin'
|
86
|
+
Requires-Dist: pytest-asyncio <1.0.0,>=0.17.0 ; extra == 'dev-darwin'
|
87
|
+
Requires-Dist: pytest-cov <5.0.0,>=3.0.0 ; extra == 'dev-darwin'
|
88
|
+
Requires-Dist: pytest <8.0.0,>=7.0.0 ; extra == 'dev-darwin'
|
89
|
+
Requires-Dist: scrapli-cfg ==2023.7.30 ; extra == 'dev-darwin'
|
90
|
+
Requires-Dist: scrapli-replay ==2023.7.30 ; extra == 'dev-darwin'
|
91
|
+
Requires-Dist: toml <1.0.0,>=0.10.2 ; extra == 'dev-darwin'
|
92
|
+
Requires-Dist: types-paramiko <4.0.0,>=2.8.6 ; extra == 'dev-darwin'
|
93
|
+
Requires-Dist: types-pkg-resources <1.0.0,>=0.1.3 ; extra == 'dev-darwin'
|
94
|
+
Requires-Dist: ntc-templates <5.0.0,>=1.1.0 ; extra == 'dev-darwin'
|
95
|
+
Requires-Dist: textfsm <2.0.0,>=1.1.0 ; extra == 'dev-darwin'
|
96
|
+
Requires-Dist: ttp <1.0.0,>=0.5.0 ; extra == 'dev-darwin'
|
97
|
+
Requires-Dist: paramiko <4.0.0,>=2.6.0 ; extra == 'dev-darwin'
|
98
|
+
Requires-Dist: asyncssh <3.0.0,>=2.2.1 ; extra == 'dev-darwin'
|
99
|
+
Requires-Dist: scrapli-community >=2021.01.30 ; extra == 'dev-darwin'
|
100
|
+
Requires-Dist: genie <24.4,>=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'dev-darwin'
|
101
|
+
Requires-Dist: pyats >=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'dev-darwin'
|
102
|
+
Requires-Dist: ssh2-python <2.0.0,>=0.23.0 ; (python_version < "3.12") and extra == 'dev'
|
103
|
+
Requires-Dist: genie <24.4,>=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'dev'
|
104
|
+
Requires-Dist: pyats >=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'dev'
|
78
105
|
Provides-Extra: docs
|
79
|
-
Requires-Dist: mdx-gh-links
|
80
|
-
Requires-Dist: mkdocs
|
81
|
-
Requires-Dist: mkdocs-gen-files
|
82
|
-
Requires-Dist: mkdocs-literate-nav
|
83
|
-
Requires-Dist: mkdocs-material
|
84
|
-
Requires-Dist: mkdocs-material-extensions
|
85
|
-
Requires-Dist: mkdocs-section-index
|
86
|
-
Requires-Dist: mkdocstrings[python]
|
106
|
+
Requires-Dist: mdx-gh-links <1.0,>=0.2 ; extra == 'docs'
|
107
|
+
Requires-Dist: mkdocs <2.0.0,>=1.2.3 ; extra == 'docs'
|
108
|
+
Requires-Dist: mkdocs-gen-files <1.0.0,>=0.4.0 ; extra == 'docs'
|
109
|
+
Requires-Dist: mkdocs-literate-nav <1.0.0,>=0.5.0 ; extra == 'docs'
|
110
|
+
Requires-Dist: mkdocs-material <10.0.0,>=8.1.6 ; extra == 'docs'
|
111
|
+
Requires-Dist: mkdocs-material-extensions <2.0.0,>=1.0.3 ; extra == 'docs'
|
112
|
+
Requires-Dist: mkdocs-section-index <1.0.0,>=0.3.4 ; extra == 'docs'
|
113
|
+
Requires-Dist: mkdocstrings[python] <1.0.0,>=0.19.0 ; extra == 'docs'
|
87
114
|
Provides-Extra: genie
|
88
|
-
Requires-Dist: genie
|
89
|
-
Requires-Dist: pyats
|
115
|
+
Requires-Dist: genie <24.4,>=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'genie'
|
116
|
+
Requires-Dist: pyats >=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'genie'
|
90
117
|
Provides-Extra: paramiko
|
91
|
-
Requires-Dist: paramiko
|
118
|
+
Requires-Dist: paramiko <4.0.0,>=2.6.0 ; extra == 'paramiko'
|
92
119
|
Provides-Extra: ssh2
|
93
|
-
Requires-Dist: ssh2-python
|
120
|
+
Requires-Dist: ssh2-python <2.0.0,>=0.23.0 ; (python_version < "3.12") and extra == 'ssh2'
|
94
121
|
Provides-Extra: textfsm
|
95
|
-
Requires-Dist: ntc-templates
|
96
|
-
Requires-Dist: textfsm
|
122
|
+
Requires-Dist: ntc-templates <5.0.0,>=1.1.0 ; extra == 'textfsm'
|
123
|
+
Requires-Dist: textfsm <2.0.0,>=1.1.0 ; extra == 'textfsm'
|
97
124
|
Provides-Extra: ttp
|
98
|
-
Requires-Dist: ttp
|
125
|
+
Requires-Dist: ttp <1.0.0,>=0.5.0 ; extra == 'ttp'
|
99
126
|
|
100
127
|
<p center><a href=""><img src=https://github.com/carlmontanari/scrapli/blob/main/scrapli.svg?sanitize=true/></a></p>
|
101
128
|
|