scrapli 2024.1.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.
Files changed (64) hide show
  1. scrapli/__init__.py +2 -1
  2. scrapli/channel/__init__.py +1 -0
  3. scrapli/channel/async_channel.py +16 -7
  4. scrapli/channel/base_channel.py +10 -5
  5. scrapli/channel/sync_channel.py +16 -7
  6. scrapli/decorators.py +1 -0
  7. scrapli/driver/__init__.py +1 -0
  8. scrapli/driver/base/__init__.py +1 -0
  9. scrapli/driver/base/async_driver.py +19 -13
  10. scrapli/driver/base/base_driver.py +33 -36
  11. scrapli/driver/base/sync_driver.py +19 -13
  12. scrapli/driver/core/__init__.py +1 -0
  13. scrapli/driver/core/arista_eos/__init__.py +1 -0
  14. scrapli/driver/core/arista_eos/async_driver.py +1 -0
  15. scrapli/driver/core/arista_eos/base_driver.py +1 -0
  16. scrapli/driver/core/arista_eos/sync_driver.py +1 -0
  17. scrapli/driver/core/cisco_iosxe/__init__.py +1 -0
  18. scrapli/driver/core/cisco_iosxe/async_driver.py +1 -0
  19. scrapli/driver/core/cisco_iosxe/base_driver.py +1 -0
  20. scrapli/driver/core/cisco_iosxe/sync_driver.py +1 -0
  21. scrapli/driver/core/cisco_iosxr/__init__.py +1 -0
  22. scrapli/driver/core/cisco_iosxr/async_driver.py +1 -0
  23. scrapli/driver/core/cisco_iosxr/base_driver.py +1 -0
  24. scrapli/driver/core/cisco_iosxr/sync_driver.py +1 -0
  25. scrapli/driver/core/cisco_nxos/__init__.py +1 -0
  26. scrapli/driver/core/cisco_nxos/async_driver.py +1 -0
  27. scrapli/driver/core/cisco_nxos/base_driver.py +9 -4
  28. scrapli/driver/core/cisco_nxos/sync_driver.py +1 -0
  29. scrapli/driver/core/juniper_junos/__init__.py +1 -0
  30. scrapli/driver/core/juniper_junos/async_driver.py +1 -0
  31. scrapli/driver/core/juniper_junos/base_driver.py +1 -0
  32. scrapli/driver/core/juniper_junos/sync_driver.py +1 -0
  33. scrapli/driver/generic/__init__.py +1 -0
  34. scrapli/driver/generic/async_driver.py +19 -1
  35. scrapli/driver/generic/base_driver.py +1 -0
  36. scrapli/driver/generic/sync_driver.py +19 -1
  37. scrapli/driver/network/__init__.py +1 -0
  38. scrapli/driver/network/async_driver.py +1 -0
  39. scrapli/driver/network/base_driver.py +1 -0
  40. scrapli/driver/network/sync_driver.py +1 -0
  41. scrapli/exceptions.py +1 -0
  42. scrapli/factory.py +4 -3
  43. scrapli/helper.py +9 -1
  44. scrapli/logging.py +1 -0
  45. scrapli/response.py +1 -0
  46. scrapli/ssh_config.py +1 -0
  47. scrapli/transport/base/__init__.py +1 -0
  48. scrapli/transport/base/async_transport.py +1 -0
  49. scrapli/transport/base/base_socket.py +1 -0
  50. scrapli/transport/base/base_transport.py +1 -0
  51. scrapli/transport/base/sync_transport.py +1 -0
  52. scrapli/transport/plugins/asyncssh/transport.py +4 -0
  53. scrapli/transport/plugins/asynctelnet/transport.py +13 -6
  54. scrapli/transport/plugins/paramiko/transport.py +1 -0
  55. scrapli/transport/plugins/ssh2/transport.py +6 -3
  56. scrapli/transport/plugins/system/ptyprocess.py +37 -0
  57. scrapli/transport/plugins/system/transport.py +27 -6
  58. scrapli/transport/plugins/telnet/transport.py +13 -7
  59. {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.dist-info}/METADATA +33 -6
  60. scrapli-2024.7.30.dist-info/RECORD +74 -0
  61. {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.dist-info}/WHEEL +1 -1
  62. scrapli-2024.1.30.dist-info/RECORD +0 -74
  63. {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.dist-info}/LICENSE +0 -0
  64. {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.generic.async_driver"""
2
+
2
3
  import asyncio
3
4
  from io import BytesIO
4
5
  from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union
@@ -16,6 +17,23 @@ if TYPE_CHECKING:
16
17
  )
17
18
 
18
19
 
20
+ async def generic_on_open(conn: "AsyncGenericDriver") -> None:
21
+ """
22
+ GenericDriver default on-open -- drains initial login by running a simple get_prompt
23
+
24
+ Args:
25
+ conn: GenericDriver object
26
+
27
+ Returns:
28
+ None
29
+
30
+ Raises:
31
+ N/A
32
+
33
+ """
34
+ await conn.get_prompt()
35
+
36
+
19
37
  class AsyncGenericDriver(AsyncDriver, BaseGenericDriver):
20
38
  def __init__(
21
39
  self,
@@ -39,7 +57,7 @@ class AsyncGenericDriver(AsyncDriver, BaseGenericDriver):
39
57
  ssh_config_file: Union[str, bool] = False,
40
58
  ssh_known_hosts_file: Union[str, bool] = False,
41
59
  on_init: Optional[Callable[..., Any]] = None,
42
- on_open: Optional[Callable[..., Any]] = None,
60
+ on_open: Optional[Callable[..., Any]] = generic_on_open,
43
61
  on_close: Optional[Callable[..., Any]] = None,
44
62
  transport: str = "system",
45
63
  transport_options: Optional[Dict[str, Any]] = None,
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.generic.base_driver"""
2
+
2
3
  import re
3
4
  from typing import (
4
5
  TYPE_CHECKING,
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.generic.sync_driver"""
2
+
2
3
  import time
3
4
  from io import BytesIO
4
5
  from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union
@@ -16,6 +17,23 @@ if TYPE_CHECKING:
16
17
  )
17
18
 
18
19
 
20
+ def generic_on_open(conn: "GenericDriver") -> None:
21
+ """
22
+ GenericDriver default on-open -- drains initial login by running a simple get_prompt
23
+
24
+ Args:
25
+ conn: GenericDriver object
26
+
27
+ Returns:
28
+ None
29
+
30
+ Raises:
31
+ N/A
32
+
33
+ """
34
+ conn.get_prompt()
35
+
36
+
19
37
  class GenericDriver(Driver, BaseGenericDriver):
20
38
  def __init__(
21
39
  self,
@@ -39,7 +57,7 @@ class GenericDriver(Driver, BaseGenericDriver):
39
57
  ssh_config_file: Union[str, bool] = False,
40
58
  ssh_known_hosts_file: Union[str, bool] = False,
41
59
  on_init: Optional[Callable[..., Any]] = None,
42
- on_open: Optional[Callable[..., Any]] = None,
60
+ on_open: Optional[Callable[..., Any]] = generic_on_open,
43
61
  on_close: Optional[Callable[..., Any]] = None,
44
62
  transport: str = "system",
45
63
  transport_options: Optional[Dict[str, Any]] = None,
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.network"""
2
+
2
3
  from scrapli.driver.network.async_driver import AsyncNetworkDriver
3
4
  from scrapli.driver.network.sync_driver import NetworkDriver
4
5
 
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.network.async_driver"""
2
+
2
3
  from collections import defaultdict
3
4
  from io import BytesIO
4
5
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.network.base_driver"""
2
+
2
3
  import re
3
4
  from collections import defaultdict
4
5
  from datetime import datetime
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.network.sync_driver"""
2
+
2
3
  from collections import defaultdict
3
4
  from io import BytesIO
4
5
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
scrapli/exceptions.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """scrapli.exceptions"""
2
+
2
3
  from typing import Optional
3
4
 
4
5
 
scrapli/factory.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """scrapli.factory"""
2
+
2
3
  import importlib
3
4
  from copy import deepcopy
4
5
  from io import BytesIO
@@ -26,7 +27,7 @@ from scrapli.exceptions import (
26
27
  )
27
28
  from scrapli.helper import format_user_warning
28
29
  from scrapli.logging import logger
29
- from scrapli.transport import ASYNCIO_TRANSPORTS
30
+ from scrapli.transport import ASYNCIO_TRANSPORTS, CORE_TRANSPORTS
30
31
 
31
32
 
32
33
  def _build_provided_kwargs_dict( # pylint: disable=R0914
@@ -464,7 +465,7 @@ class Scrapli(NetworkDriver):
464
465
  """
465
466
  logger.debug("Scrapli factory initialized")
466
467
 
467
- if transport in ASYNCIO_TRANSPORTS:
468
+ if transport in CORE_TRANSPORTS and transport in ASYNCIO_TRANSPORTS:
468
469
  raise ScrapliValueError("Use 'AsyncScrapli' if using an async transport!")
469
470
 
470
471
  if not isinstance(platform, str):
@@ -763,7 +764,7 @@ class AsyncScrapli(AsyncNetworkDriver):
763
764
  """
764
765
  logger.debug("AsyncScrapli factory initialized")
765
766
 
766
- if transport not in ASYNCIO_TRANSPORTS:
767
+ if transport in CORE_TRANSPORTS and transport not in ASYNCIO_TRANSPORTS:
767
768
  raise ScrapliValueError("Use 'Scrapli' if using a synchronous transport!")
768
769
 
769
770
  if not isinstance(platform, str):
scrapli/helper.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """scrapli.helper"""
2
+
2
3
  import importlib
3
4
  import importlib.resources
4
5
  import sys
@@ -18,7 +19,14 @@ def _textfsm_get_template_directory() -> str:
18
19
  if sys.version_info >= (3, 9):
19
20
  return f"{importlib.resources.files('ntc_templates')}/templates"
20
21
 
21
- with importlib.resources.path("ntc_templates", "templates") as path:
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
22
30
  return str(path)
23
31
 
24
32
 
scrapli/logging.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """scrapli.logging"""
2
+
2
3
  from ast import literal_eval
3
4
 
4
5
  # slightly irritating renaming to prevent a cyclic lookup in griffe for mkdocstrings
scrapli/response.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """scrapli.response"""
2
+
2
3
  from collections import UserList
3
4
  from datetime import datetime
4
5
  from io import TextIOWrapper
scrapli/ssh_config.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """scrapli.ssh_config"""
2
+
2
3
  import base64
3
4
  import hmac
4
5
  import os
@@ -1,4 +1,5 @@
1
1
  """scrapli.transport.base"""
2
+
2
3
  from scrapli.transport.base.async_transport import AsyncTransport
3
4
  from scrapli.transport.base.base_transport import BasePluginTransportArgs, BaseTransportArgs
4
5
  from scrapli.transport.base.sync_transport import Transport
@@ -1,4 +1,5 @@
1
1
  """scrapli.transport.async_transport"""
2
+
2
3
  from abc import ABC, abstractmethod
3
4
 
4
5
  from scrapli.transport.base.base_transport import BaseTransport
@@ -1,4 +1,5 @@
1
1
  """scrapli.transport.base.base_socket"""
2
+
2
3
  import socket
3
4
  from contextlib import suppress
4
5
  from typing import Optional, Set
@@ -1,4 +1,5 @@
1
1
  """scrapli.transport.base_transport"""
2
+
2
3
  from abc import ABC, abstractmethod
3
4
  from dataclasses import dataclass
4
5
  from typing import Any, Dict
@@ -1,4 +1,5 @@
1
1
  """scrapli.transport.base_transport"""
2
+
2
3
  from abc import ABC, abstractmethod
3
4
 
4
5
  from scrapli.transport.base.base_transport import BaseTransport
@@ -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,4 +1,5 @@
1
1
  """scrapli.transport.plugins.paramiko.transport"""
2
+
2
3
  from contextlib import suppress
3
4
  from dataclasses import dataclass
4
5
  from typing import Optional
@@ -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
- from ssh2.channel import Channel
8
- from ssh2.exceptions import AuthenticationError, SSH2Error
9
- from ssh2.session import Session
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):
@@ -175,6 +177,35 @@ def _setecho(fd: int, state: bool) -> None:
175
177
  raise
176
178
 
177
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:
186
+ if err.args[0] == errno.EINVAL:
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)
206
+ raise
207
+
208
+
178
209
  class PtyProcess:
179
210
  def __init__(self, pid: int, fd: int) -> None:
180
211
  """
@@ -354,6 +385,12 @@ class PtyProcess:
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:
@@ -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
- if self.plugin_transport_args.ssh_known_hosts_file:
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
- ["-o", f"UserKnownHostsFile={self.plugin_transport_args.ssh_known_hosts_file}"]
117
+ [
118
+ "-o",
119
+ f"UserKnownHostsFile={self.plugin_transport_args.ssh_known_hosts_file}",
120
+ ]
105
121
  )
106
-
107
- if self.plugin_transport_args.ssh_config_file:
108
- self.open_cmd.extend(["-F", self.plugin_transport_args.ssh_config_file])
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 Exception as exc:
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: 2024.1.30
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
@@ -48,14 +48,14 @@ Requires-Dist: asyncssh <3.0.0,>=2.2.1 ; extra == 'asyncssh'
48
48
  Provides-Extra: community
49
49
  Requires-Dist: scrapli-community >=2021.01.30 ; extra == 'community'
50
50
  Provides-Extra: dev
51
- Requires-Dist: black <24.0.0,>=23.3.0 ; extra == 'dev'
51
+ Requires-Dist: black <25.0.0,>=23.3.0 ; extra == 'dev'
52
52
  Requires-Dist: darglint <2.0.0,>=1.8.1 ; extra == 'dev'
53
53
  Requires-Dist: isort <6.0.0,>=5.10.1 ; extra == 'dev'
54
54
  Requires-Dist: mypy <2.0.0,>=1.4.1 ; extra == 'dev'
55
- Requires-Dist: nox ==2023.4.22 ; extra == 'dev'
55
+ Requires-Dist: nox ==2024.4.15 ; extra == 'dev'
56
56
  Requires-Dist: pycodestyle <3.0.0,>=2.8.0 ; extra == 'dev'
57
57
  Requires-Dist: pydocstyle <7.0.0,>=6.1.1 ; extra == 'dev'
58
- Requires-Dist: pyfakefs <6.0.0,>=5.0.0 ; extra == 'dev'
58
+ Requires-Dist: pyfakefs <6.0.0,>=5.4.1 ; extra == 'dev'
59
59
  Requires-Dist: pylama <9.0.0,>=8.4.0 ; extra == 'dev'
60
60
  Requires-Dist: pylint <4.0.0,>=3.0.0 ; extra == 'dev'
61
61
  Requires-Dist: pytest-asyncio <1.0.0,>=0.17.0 ; extra == 'dev'
@@ -72,8 +72,35 @@ Requires-Dist: ttp <1.0.0,>=0.5.0 ; extra == 'dev'
72
72
  Requires-Dist: paramiko <4.0.0,>=2.6.0 ; extra == 'dev'
73
73
  Requires-Dist: asyncssh <3.0.0,>=2.2.1 ; extra == 'dev'
74
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'
75
102
  Requires-Dist: ssh2-python <2.0.0,>=0.23.0 ; (python_version < "3.12") and extra == 'dev'
76
- Requires-Dist: genie >=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'dev'
103
+ Requires-Dist: genie <24.4,>=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'dev'
77
104
  Requires-Dist: pyats >=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'dev'
78
105
  Provides-Extra: docs
79
106
  Requires-Dist: mdx-gh-links <1.0,>=0.2 ; extra == 'docs'
@@ -85,7 +112,7 @@ Requires-Dist: mkdocs-material-extensions <2.0.0,>=1.0.3 ; extra == 'docs'
85
112
  Requires-Dist: mkdocs-section-index <1.0.0,>=0.3.4 ; extra == 'docs'
86
113
  Requires-Dist: mkdocstrings[python] <1.0.0,>=0.19.0 ; extra == 'docs'
87
114
  Provides-Extra: genie
88
- Requires-Dist: genie >=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'genie'
115
+ Requires-Dist: genie <24.4,>=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'genie'
89
116
  Requires-Dist: pyats >=20.2 ; (sys_platform != "win32" and python_version < "3.11") and extra == 'genie'
90
117
  Provides-Extra: paramiko
91
118
  Requires-Dist: paramiko <4.0.0,>=2.6.0 ; extra == 'paramiko'
@@ -0,0 +1,74 @@
1
+ scrapli/__init__.py,sha256=uxxnFUBmyzthxJtctiapxFoYkCiphAypRtRkdTqxrzU,228
2
+ scrapli/decorators.py,sha256=KlxKugMnfpAUgbkR_Ok7MPDRY98BNvCorXnpeakQcFE,10422
3
+ scrapli/exceptions.py,sha256=AlWmOIGpdZhcEGgv4H7Tpw9B9QdoXBG-DPYooSKHlxM,1938
4
+ scrapli/factory.py,sha256=SDVtvhqF8tAj5-cbwu3NWY31rkb508wHolMpGyDp3x0,37673
5
+ scrapli/helper.py,sha256=M4TiGiLYqdpaPVLP_ffK87eG20Uwzd8dLbG9f0JBi4o,11553
6
+ scrapli/logging.py,sha256=HJkDXhL9j9HfHkcEap3WSfKO3FnLHBg4v7wCFqgV99k,9802
7
+ scrapli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ scrapli/response.py,sha256=bQGuH-cKlOIlCfBHf3QVYzz852INUstiS_ivzKUMbqM,8866
9
+ scrapli/settings.py,sha256=bbbNa5Aj4aR-rMaxGCLwogpdedadrQfar6sj9y95rlw,235
10
+ scrapli/ssh_config.py,sha256=NJ0t4Waq1D5-k27Q93AU4FJGie0pDiag2PvJFMVhhG4,17911
11
+ scrapli/channel/__init__.py,sha256=U06VyCKvvjUU4VDdwt5IrciwSvoigxClwQ72bVwvs7A,177
12
+ scrapli/channel/async_channel.py,sha256=z3BbNt_l6wO_yvi-nkE90ZcbYtbupZsiE21OSRNeRa8,23234
13
+ scrapli/channel/base_channel.py,sha256=hXKfzjkP1VD2v6hOgb2MUQuhksfkTdT1BEN2VVMeiKY,22033
14
+ scrapli/channel/sync_channel.py,sha256=mSlAdvkX6GklOoHsoB-GUfB477arLRyPpeDVPdeidLE,23217
15
+ scrapli/driver/__init__.py,sha256=oXZkMs5nZlDsLsUMdEShxxtx5-bakQ2xzf8EZLIQAfE,354
16
+ scrapli/driver/base/__init__.py,sha256=-RPF8z1Qo7wXBCUZZMLjDYBTaXg4qZ0ofB_uMWi6hD4,183
17
+ scrapli/driver/base/async_driver.py,sha256=pEf0B6TmkdOm6ytNmhgUHbUcyFasn9b5hMmcqQ2IPWM,9843
18
+ scrapli/driver/base/base_driver.py,sha256=-owg7qqbmrFtFqQn7Lncc4t0hV1630sRf7RK04cw5bY,36622
19
+ scrapli/driver/base/sync_driver.py,sha256=KsYvueu_gFFL0VQ0t4NFXLWHYjfLxFCsiT00JsDdrc8,6156
20
+ scrapli/driver/core/__init__.py,sha256=dnxS1ibXoma7FMzb_6iMDdoC0ioivleDrzOAq6pD7BQ,615
21
+ scrapli/driver/core/arista_eos/__init__.py,sha256=HoOP-do1Nb-6AAGEprUbJFdE3cziBzSB3so6tZASRKk,228
22
+ scrapli/driver/core/arista_eos/async_driver.py,sha256=siHyelArMaV_TgOcrNwkciHUXrPpXYAxwRN1kXSX1Ic,7412
23
+ scrapli/driver/core/arista_eos/base_driver.py,sha256=06BRyGH_iZT3cDccBdwmm5ElFMQz8FsljsgUsuAbpLs,2794
24
+ scrapli/driver/core/arista_eos/sync_driver.py,sha256=rzdWaGbb14hOW-O19co1azDXzNnifST9RxMTX_OzIKQ,7367
25
+ scrapli/driver/core/cisco_iosxe/__init__.py,sha256=gAIKcWEyuCKD7U6s7C8Tr60YRVpT2h3RMgbeqcXwKLQ,239
26
+ scrapli/driver/core/cisco_iosxe/async_driver.py,sha256=beWsOkNRrdb8etTsdYZYe60BXomEMwMyMkuKdFI6Z70,6357
27
+ scrapli/driver/core/cisco_iosxe/base_driver.py,sha256=qewpK9mJPDM_WEIJnqCRPJ3Lp1F243WtJ6CrA9tW3bc,1568
28
+ scrapli/driver/core/cisco_iosxe/sync_driver.py,sha256=YcYFcuakjIFRSyzLXTR_pQjYcIyyl0vE7kMNyizgt0g,6280
29
+ scrapli/driver/core/cisco_iosxr/__init__.py,sha256=mCyGrWDxDsCK-2lPidOkmgWz7p6ibISxkbmJTnLrNqc,239
30
+ scrapli/driver/core/cisco_iosxr/async_driver.py,sha256=9efUWy-xJ3tXS274QuGmvYhbqK-JVZIxClfqgDIWzXc,6699
31
+ scrapli/driver/core/cisco_iosxr/base_driver.py,sha256=TWkI6uBzfCniPPpK4HpSeJzpbKMjTgLfLHfCHPU-nBQ,1262
32
+ scrapli/driver/core/cisco_iosxr/sync_driver.py,sha256=fskRc_ZQMXnyu9sIHhEh44VAwcr3YQXAx5SbQLHP-uY,6733
33
+ scrapli/driver/core/cisco_nxos/__init__.py,sha256=vtq65EDKjYqhg9HUGnZrdaRqpYVuz24Oe_WWdzqzQJs,232
34
+ scrapli/driver/core/cisco_nxos/async_driver.py,sha256=92DkZkiSy3PXY5pr1BwMRlQWZRKzqMS71sjEYtmhQaE,7443
35
+ scrapli/driver/core/cisco_nxos/base_driver.py,sha256=eNjYkMjWyOn6oLZ4vTc0rHpqOHUU8Yt09tZZQwFyUVg,3519
36
+ scrapli/driver/core/cisco_nxos/sync_driver.py,sha256=icih-fn9yrU23XAH-J8BOLjSt2QXb5-yE53wPisRYU8,7359
37
+ scrapli/driver/core/juniper_junos/__init__.py,sha256=kKVB77xdHTVyL3sGzQFUPYGshMCqxoIJAHSxkw_wfZA,245
38
+ scrapli/driver/core/juniper_junos/async_driver.py,sha256=b33RLXu1BWPy019w-T_ozO0IhhvqW96FpPdn8XjBZi4,6746
39
+ scrapli/driver/core/juniper_junos/base_driver.py,sha256=uZMW5F8zOOE5TeAqVIA71OyiYh4gOUN4t5vj63glteM,2326
40
+ scrapli/driver/core/juniper_junos/sync_driver.py,sha256=_3HmTv6Kz66x83KxBJ7Y6zkjFqrxAywy-H8vX0VrlGA,6683
41
+ scrapli/driver/generic/__init__.py,sha256=0mtOYfBKHqcjSwswPs0Bj2QQnmzW4ugaCAPgx1d9ddU,300
42
+ scrapli/driver/generic/async_driver.py,sha256=oUb4RyTetu6eWzXDPAJB07jwUYGOsi_9Z2_2f8YFnRg,25951
43
+ scrapli/driver/generic/base_driver.py,sha256=k-3FjbiGVG5QZKrE4ZGFkBpNuI0zqiMG7OMMWfVIabc,11715
44
+ scrapli/driver/generic/sync_driver.py,sha256=1K4u-rd-xyFssap_AkS_UnH0g4nmDZdmaZNZIPPEXUY,25674
45
+ scrapli/driver/network/__init__.py,sha256=RAF9vpgJgcpkFgogfSyoE6P_JMqf-ycLLcoBMaRFdeY,220
46
+ scrapli/driver/network/async_driver.py,sha256=sMR3xb5iaKT7Je6LDmw-UJT6qwXo3q6t4KJnTCFeLwE,28099
47
+ scrapli/driver/network/base_driver.py,sha256=P5G95FVoqpHrewcP0Pfcth8ICSBvssTHeauch_ZE0fY,21166
48
+ scrapli/driver/network/sync_driver.py,sha256=B49sUt_1CJyOmbdnAn0n1pmrdCGJ0C7qmfQZjMt28Xg,27901
49
+ scrapli/transport/__init__.py,sha256=wHs6v4o_a_EWYfbItBgCy8lZOsOH3wFSTeImrPJhEcs,235
50
+ scrapli/transport/base/__init__.py,sha256=VW-80qXrzoCRhiWXY2yLTRBZT-6q1RSc34-1dbLNiPw,359
51
+ scrapli/transport/base/async_transport.py,sha256=yOBv6VnsPwZ-H_0PNhe_w-fUq20-z084atwFrrFNM2U,624
52
+ scrapli/transport/base/base_socket.py,sha256=eSPBWCEJvNCE0hTvBAkHM4LCzOqwHqPxbevSbfKooOI,5897
53
+ scrapli/transport/base/base_transport.py,sha256=Ft7nbtwVdgcbCGZTe7yEwidh9eE0HZmhqQvNIuP8BDI,2952
54
+ scrapli/transport/base/sync_transport.py,sha256=LpTtsZPLSw8_ZiSQB3NzS4PiVFB5Z0vXee2PW-LaEbQ,606
55
+ scrapli/transport/base/telnet_common.py,sha256=Wm0D3wOmRfj4jlqEuAIth85KrLiVYHbdeyfbLZ77zZ4,214
56
+ scrapli/transport/plugins/__init__.py,sha256=ZHtpJzgUgiuNI7-24rp-TcTRvpq8fpef9iOKHxNUmx0,32
57
+ scrapli/transport/plugins/asyncssh/__init__.py,sha256=0Gk32fKg-7uPTOQLP_kqj6T5GSt26zE86fzRAqhCjtc,41
58
+ scrapli/transport/plugins/asyncssh/transport.py,sha256=4kIuXJspknLLGgzJwwDYEZ1FIEs5BpIbIHGHbC1_avU,10402
59
+ scrapli/transport/plugins/asynctelnet/__init__.py,sha256=eR0je4izyYOZtlyKkUTXwi_FtzJhzSoLZvCaJx31E70,44
60
+ scrapli/transport/plugins/asynctelnet/transport.py,sha256=toAKK7jRd-p8cuupueSqRvke4RM7bak8W-LYAcQC7FI,8582
61
+ scrapli/transport/plugins/paramiko/__init__.py,sha256=6TD1vWG7vxDrSu33Olcs0Q_9mU2lita0Au6gTtq6-NE,41
62
+ scrapli/transport/plugins/paramiko/transport.py,sha256=vFkVC_NJuKcalxdRirzsXnY9sSRCZ5iFdrDuqQYSS3I,9869
63
+ scrapli/transport/plugins/ssh2/__init__.py,sha256=uM4oUBrI1lhw6IAhfRa_ixhwVqMeb9RoEe157QM4XIA,37
64
+ scrapli/transport/plugins/ssh2/transport.py,sha256=qTVJpYXNE_wtHndPIR2JtUDOyegOiQ14ZZ8Rj8bt23Y,9073
65
+ scrapli/transport/plugins/system/__init__.py,sha256=Fhudt-5pwvGZxbzb9JSzX2BkyPHU6SjmyNaaD00-510,39
66
+ scrapli/transport/plugins/system/ptyprocess.py,sha256=wzy-z3UkKZY1ADZQVAOjsEdNaJ74b5LVPD2amP-dE8U,25165
67
+ scrapli/transport/plugins/system/transport.py,sha256=asuq7tU_D9fWJEIvblKOELNbSdoUkvSMVpdTF71TptI,6935
68
+ scrapli/transport/plugins/telnet/__init__.py,sha256=ce0syarhrpeD1NHtqbazPI09VLvF2n5oC2UE9G_Rbr0,39
69
+ scrapli/transport/plugins/telnet/transport.py,sha256=VyIamvT_I8IyEqTv-DabJErbYZrBKDscHNFY1kto2wU,8293
70
+ scrapli-2024.7.30.dist-info/LICENSE,sha256=IeyhkG2h_dAvJ7nAF0S4GSUg045jwI27YADV_KSJi50,1071
71
+ scrapli-2024.7.30.dist-info/METADATA,sha256=qwRvwRUyCiYCpXTQ-ny2lsRoA7FHiBSIAcXn7ZGhAL4,11664
72
+ scrapli-2024.7.30.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
73
+ scrapli-2024.7.30.dist-info/top_level.txt,sha256=piC9TaaPt0z-1Ryrs69hDvsIQkmE5ceDAAW5donUvsI,8
74
+ scrapli-2024.7.30.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (72.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5