scrapli 2024.1.30__py3-none-any.whl → 2024.7.30.post1__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 +12 -7
  5. scrapli/channel/sync_channel.py +16 -7
  6. scrapli/decorators.py +2 -1
  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 +35 -38
  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 +2 -1
  15. scrapli/driver/core/arista_eos/base_driver.py +4 -2
  16. scrapli/driver/core/arista_eos/sync_driver.py +2 -1
  17. scrapli/driver/core/cisco_iosxe/__init__.py +1 -0
  18. scrapli/driver/core/cisco_iosxe/async_driver.py +2 -1
  19. scrapli/driver/core/cisco_iosxe/base_driver.py +1 -0
  20. scrapli/driver/core/cisco_iosxe/sync_driver.py +2 -1
  21. scrapli/driver/core/cisco_iosxr/__init__.py +1 -0
  22. scrapli/driver/core/cisco_iosxr/async_driver.py +2 -1
  23. scrapli/driver/core/cisco_iosxr/base_driver.py +1 -0
  24. scrapli/driver/core/cisco_iosxr/sync_driver.py +2 -1
  25. scrapli/driver/core/cisco_nxos/__init__.py +1 -0
  26. scrapli/driver/core/cisco_nxos/async_driver.py +2 -1
  27. scrapli/driver/core/cisco_nxos/base_driver.py +9 -4
  28. scrapli/driver/core/cisco_nxos/sync_driver.py +2 -1
  29. scrapli/driver/core/juniper_junos/__init__.py +1 -0
  30. scrapli/driver/core/juniper_junos/async_driver.py +2 -1
  31. scrapli/driver/core/juniper_junos/base_driver.py +1 -0
  32. scrapli/driver/core/juniper_junos/sync_driver.py +2 -1
  33. scrapli/driver/generic/__init__.py +1 -0
  34. scrapli/driver/generic/async_driver.py +24 -5
  35. scrapli/driver/generic/base_driver.py +6 -1
  36. scrapli/driver/generic/sync_driver.py +25 -6
  37. scrapli/driver/network/__init__.py +1 -0
  38. scrapli/driver/network/async_driver.py +2 -1
  39. scrapli/driver/network/base_driver.py +2 -1
  40. scrapli/driver/network/sync_driver.py +2 -1
  41. scrapli/exceptions.py +1 -0
  42. scrapli/factory.py +7 -6
  43. scrapli/helper.py +21 -7
  44. scrapli/logging.py +2 -3
  45. scrapli/response.py +13 -3
  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 +10 -8
  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 +10 -9
  59. {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.post1.dist-info}/METADATA +70 -49
  60. scrapli-2024.7.30.post1.dist-info/RECORD +74 -0
  61. {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.post1.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.post1.dist-info}/LICENSE +0 -0
  64. {scrapli-2024.1.30.dist-info → scrapli-2024.7.30.post1.dist-info}/top_level.txt +0 -0
scrapli/__init__.py CHANGED
@@ -1,8 +1,9 @@
1
1
  """scrapli"""
2
+
2
3
  from scrapli.driver.base import AsyncDriver, Driver
3
4
  from scrapli.factory import AsyncScrapli, Scrapli
4
5
 
5
- __version__ = "2024.01.30"
6
+ __version__ = "2024.07.30.post1"
6
7
 
7
8
  __all__ = (
8
9
  "AsyncDriver",
@@ -1,4 +1,5 @@
1
1
  """scrapli.channel"""
2
+
2
3
  from scrapli.channel.async_channel import AsyncChannel
3
4
  from scrapli.channel.sync_channel import Channel
4
5
 
@@ -1,4 +1,5 @@
1
1
  """scrapli.channel.async_channel"""
2
+
2
3
  import asyncio
3
4
  import re
4
5
  import time
@@ -70,7 +71,7 @@ class AsyncChannel(BaseChannel):
70
71
  buf = await self.transport.read()
71
72
  buf = buf.replace(b"\r", b"")
72
73
 
73
- self.logger.debug(f"read: {buf!r}")
74
+ self.logger.debug("read: %r", buf)
74
75
 
75
76
  if self.channel_log:
76
77
  self.channel_log.write(buf)
@@ -490,7 +491,10 @@ class AsyncChannel(BaseChannel):
490
491
  bytes_channel_input = channel_input.encode()
491
492
 
492
493
  self.logger.info(
493
- f"sending channel input: {channel_input}; strip_prompt: {strip_prompt}; eager: {eager}"
494
+ "sending channel input: %s; strip_prompt: %s; eager: %s",
495
+ channel_input,
496
+ strip_prompt,
497
+ eager,
494
498
  )
495
499
 
496
500
  async with self._channel_lock():
@@ -545,8 +549,12 @@ class AsyncChannel(BaseChannel):
545
549
  ]
546
550
 
547
551
  self.logger.info(
548
- f"sending channel input and read: {channel_input}; strip_prompt: {strip_prompt}; "
549
- f"expected_outputs: {expected_outputs}; read_duration: {read_duration}"
552
+ "sending channel input and read: %s; strip_prompt: %s; "
553
+ "expected_outputs: %s; read_duration: %s",
554
+ channel_input,
555
+ strip_prompt,
556
+ expected_outputs,
557
+ read_duration,
550
558
  )
551
559
 
552
560
  async with self._channel_lock():
@@ -656,9 +664,10 @@ class AsyncChannel(BaseChannel):
656
664
 
657
665
  _channel_input = channel_input if not hidden_input else "REDACTED"
658
666
  self.logger.info(
659
- f"sending interactive input: {_channel_input}; "
660
- f"expecting: {channel_response}; "
661
- f"hidden_input: {hidden_input}"
667
+ "sending interactive input: %s; expecting: %s; hidden_input: %s",
668
+ _channel_input,
669
+ channel_response,
670
+ hidden_input,
662
671
  )
663
672
 
664
673
  self.write(channel_input=channel_input, redacted=bool(hidden_input))
@@ -1,4 +1,5 @@
1
1
  """scrapli.channel.base_channel"""
2
+
2
3
  import re
3
4
  from dataclasses import dataclass
4
5
  from datetime import datetime
@@ -11,13 +12,15 @@ from scrapli.logging import get_instance_logger
11
12
  from scrapli.transport.base import AsyncTransport, Transport
12
13
 
13
14
  ANSI_ESCAPE_PATTERN = re.compile(
14
- pattern=rb"[\x1B\x9B\x9D](\s)?" # Prefix ESC (^) or CSI (^[) or OSC (^])
15
+ pattern=rb"[\x1B\x9B\x9D](\s)?" # Prefix ESC (^) or CSI (^[) or OSC (^)
15
16
  rb"("
16
17
  rb"([78ME])" # control cursor position
17
18
  rb"|"
18
- rb"([\x07])" # BEL (Terminal bell)
19
+ rb"((\]\d).*?[\x07])" # BEL (Terminal bell)
20
+ rb"|"
21
+ rb"(\[.*?[@-~])" # control codes starts with `[` e.x. ESC [2;37;41m
19
22
  rb"|"
20
- rb"(\[[{}();#=?0-9]*[A-Zhglnmsu~])" # control codes starts with `[` e.x. ESC [2;37;41m
23
+ rb"(\[.*?[0-9;]m)" # Select Graphic Rendition (SGR) control sequence
21
24
  rb")",
22
25
  flags=re.VERBOSE,
23
26
  )
@@ -299,7 +302,7 @@ class BaseChannel:
299
302
  # ignore here
300
303
  # note that this will *always* be binary mode, so there doesn't need to be any
301
304
  # encoding, hence ignoring that pylint message!
302
- self.channel_log = open( # pylint: disable=W1514,R1732
305
+ self.channel_log = open( # pylint: disable=W1514,R1732,W1501
303
306
  channel_log_destination,
304
307
  mode=f"{self._base_channel_args.channel_log_mode}b", # type: ignore
305
308
  )
@@ -365,8 +368,10 @@ class BaseChannel:
365
368
  N/A
366
369
 
367
370
  """
368
- log_output = "REDACTED" if redacted else repr(channel_input)
369
- self.logger.debug(f"write: {log_output}")
371
+ if redacted:
372
+ self.logger.debug("write: REDACTED")
373
+ else:
374
+ self.logger.debug("write: %r", channel_input)
370
375
 
371
376
  self.transport.write(channel_input=channel_input.encode())
372
377
 
@@ -410,7 +415,7 @@ class BaseChannel:
410
415
 
411
416
  return regex_channel_outputs_pattern
412
417
 
413
- def _ssh_message_handler(self, output: bytes) -> None: # noqa: C901
418
+ def _ssh_message_handler(self, output: bytes) -> None: # pylint:disable=too-many-branches
414
419
  """
415
420
  Parse EOF messages from _pty_authenticate and create log/stack exception message
416
421
 
@@ -1,4 +1,5 @@
1
1
  """scrapli.channel.sync_channel"""
2
+
2
3
  import re
3
4
  import time
4
5
  from contextlib import contextmanager, suppress
@@ -70,7 +71,7 @@ class Channel(BaseChannel):
70
71
  buf = self.transport.read()
71
72
  buf = buf.replace(b"\r", b"")
72
73
 
73
- self.logger.debug(f"read: {buf!r}")
74
+ self.logger.debug("read: %r", buf)
74
75
 
75
76
  if self.channel_log:
76
77
  self.channel_log.write(buf)
@@ -491,7 +492,10 @@ class Channel(BaseChannel):
491
492
  bytes_channel_input = channel_input.encode()
492
493
 
493
494
  self.logger.info(
494
- f"sending channel input: {channel_input}; strip_prompt: {strip_prompt}; eager: {eager}"
495
+ "sending channel input: %s; strip_prompt: %s; eager: %s",
496
+ channel_input,
497
+ strip_prompt,
498
+ eager,
495
499
  )
496
500
 
497
501
  with self._channel_lock():
@@ -546,8 +550,12 @@ class Channel(BaseChannel):
546
550
  ]
547
551
 
548
552
  self.logger.info(
549
- f"sending channel input and read: {channel_input}; strip_prompt: {strip_prompt}; "
550
- f"expected_outputs: {expected_outputs}; read_duration: {read_duration}"
553
+ "sending channel input and read: %s; strip_prompt: %s; "
554
+ "expected_outputs: %s; read_duration: %s",
555
+ channel_input,
556
+ strip_prompt,
557
+ expected_outputs,
558
+ read_duration,
551
559
  )
552
560
 
553
561
  with self._channel_lock():
@@ -657,9 +665,10 @@ class Channel(BaseChannel):
657
665
 
658
666
  _channel_input = channel_input if not hidden_input else "REDACTED"
659
667
  self.logger.info(
660
- f"sending interactive input: {_channel_input}; "
661
- f"expecting: {channel_response}; "
662
- f"hidden_input: {hidden_input}"
668
+ "sending interactive input: %s; expecting: %s; hidden_input: %s",
669
+ _channel_input,
670
+ channel_response,
671
+ hidden_input,
663
672
  )
664
673
 
665
674
  self.write(channel_input=channel_input, redacted=bool(hidden_input))
scrapli/decorators.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """scrapli.decorators"""
2
+
2
3
  import asyncio
3
4
  import signal
4
5
  import sys
@@ -76,7 +77,7 @@ def _signal_raise_exception(
76
77
  return _handle_timeout(transport=transport, logger=logger, message=message)
77
78
 
78
79
 
79
- def _multiprocessing_timeout(
80
+ def _multiprocessing_timeout( # pylint: disable=R0917
80
81
  transport: "BaseTransport",
81
82
  logger: LoggerAdapterT,
82
83
  timeout: float,
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver"""
2
+
2
3
  from scrapli.driver.base import AsyncDriver, Driver
3
4
  from scrapli.driver.generic import AsyncGenericDriver, GenericDriver
4
5
  from scrapli.driver.network import AsyncNetworkDriver, NetworkDriver
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.base"""
2
+
2
3
  from scrapli.driver.base.async_driver import AsyncDriver
3
4
  from scrapli.driver.base.sync_driver import Driver
4
5
 
@@ -1,11 +1,12 @@
1
1
  """scrapli.driver.base.async_driver"""
2
+
2
3
  from types import TracebackType
3
4
  from typing import Any, Optional, Type, TypeVar
4
5
 
5
6
  from scrapli.channel import AsyncChannel
6
7
  from scrapli.driver.base.base_driver import BaseDriver
7
- from scrapli.exceptions import ScrapliValueError
8
- from scrapli.transport import ASYNCIO_TRANSPORTS
8
+ from scrapli.exceptions import ScrapliConnectionError, ScrapliValueError
9
+ from scrapli.transport import ASYNCIO_TRANSPORTS, CORE_TRANSPORTS
9
10
 
10
11
  _T = TypeVar("_T", bound="AsyncDriver")
11
12
 
@@ -14,7 +15,7 @@ class AsyncDriver(BaseDriver):
14
15
  def __init__(self, **kwargs: Any):
15
16
  super().__init__(**kwargs)
16
17
 
17
- if self.transport_name not in ASYNCIO_TRANSPORTS:
18
+ if self.transport_name in CORE_TRANSPORTS and self.transport_name not in ASYNCIO_TRANSPORTS:
18
19
  raise ScrapliValueError(
19
20
  "provided transport is *not* an asyncio transport, must use an async transport with"
20
21
  " the AsyncDriver(s)"
@@ -39,10 +40,22 @@ class AsyncDriver(BaseDriver):
39
40
  _T: a concrete implementation of the opened AsyncDriver object
40
41
 
41
42
  Raises:
42
- N/A
43
+ ScrapliConnectionError: if an exception occurs during opening
43
44
 
44
45
  """
45
- await self.open()
46
+ try:
47
+ await self.open()
48
+ except Exception as exc:
49
+ self.logger.critical(
50
+ "encountered exception during open in context manager,"
51
+ " attempting to close transport and channel"
52
+ )
53
+
54
+ self.transport.close()
55
+ self.channel.close()
56
+
57
+ raise ScrapliConnectionError(exc) from exc
58
+
46
59
  return self
47
60
 
48
61
  async def __aexit__(
@@ -87,14 +100,7 @@ class AsyncDriver(BaseDriver):
87
100
  await self.transport.open()
88
101
  self.channel.open()
89
102
 
90
- if (
91
- self.transport_name
92
- in (
93
- "telnet",
94
- "asynctelnet",
95
- )
96
- and not self.auth_bypass
97
- ):
103
+ if "telnet" in self.transport_name and not self.auth_bypass:
98
104
  await self.channel.channel_authenticate_telnet(
99
105
  auth_username=self.auth_username, auth_password=self.auth_password
100
106
  )
@@ -1,4 +1,5 @@
1
- """scrapli.driver.base.base_driver""" # noqa: C0302
1
+ """scrapli.driver.base.base_driver""" # pylint:disable=too-many-lines
2
+
2
3
  import importlib
3
4
  from dataclasses import fields
4
5
  from io import BytesIO
@@ -13,10 +14,11 @@ from scrapli.logging import get_instance_logger
13
14
  from scrapli.ssh_config import ssh_config_factory
14
15
  from scrapli.transport import CORE_TRANSPORTS
15
16
  from scrapli.transport.base import BasePluginTransportArgs, BaseTransportArgs
17
+ from scrapli.transport.plugins.system.transport import SystemTransport
16
18
 
17
19
 
18
20
  class BaseDriver:
19
- def __init__(
21
+ def __init__( # pylint: disable=R0917
20
22
  self,
21
23
  host: str,
22
24
  port: Optional[int] = None,
@@ -371,7 +373,7 @@ class BaseDriver:
371
373
  cfg = ""
372
374
  else:
373
375
  cfg = ssh_config_file
374
- resolved_ssh_config_file = self._resolve_ssh_config(cfg)
376
+ resolved_ssh_config_file = self._resolve_ssh_config(cfg, transport=transport)
375
377
  else:
376
378
  resolved_ssh_config_file = ""
377
379
 
@@ -380,7 +382,9 @@ class BaseDriver:
380
382
  known_hosts = ""
381
383
  else:
382
384
  known_hosts = ssh_known_hosts_file
383
- resolved_ssh_known_hosts_file = self._resolve_ssh_known_hosts(known_hosts)
385
+ resolved_ssh_known_hosts_file = self._resolve_ssh_known_hosts(
386
+ known_hosts, transport=transport
387
+ )
384
388
  else:
385
389
  resolved_ssh_known_hosts_file = ""
386
390
 
@@ -556,7 +560,9 @@ class BaseDriver:
556
560
 
557
561
  return transport_class, plugin_transport_args
558
562
 
559
- def _load_non_core_transport_plugin(self) -> Tuple[Any, Type[BasePluginTransportArgs]]:
563
+ def _load_non_core_transport_plugin(
564
+ self,
565
+ ) -> Tuple[Any, Type[BasePluginTransportArgs]]:
560
566
  """
561
567
  Find non-core transport plugins and required plugin arguments
562
568
 
@@ -595,7 +601,7 @@ class BaseDriver:
595
601
 
596
602
  return transport_class, plugin_transport_args
597
603
 
598
- def _resolve_ssh_config(self, ssh_config_file: str) -> str:
604
+ def _resolve_ssh_config(self, ssh_config_file: str, transport: str) -> str:
599
605
  """
600
606
  Resolve ssh configuration file from provided string
601
607
 
@@ -604,6 +610,8 @@ class BaseDriver:
604
610
 
605
611
  Args:
606
612
  ssh_config_file: string representation of ssh config file to try to use
613
+ transport: string name of selected transport (so we can apply a bit of special handling
614
+ if system transport in use)
607
615
 
608
616
  Returns:
609
617
  str: string path to ssh config file or an empty string
@@ -612,27 +620,20 @@ class BaseDriver:
612
620
  N/A
613
621
 
614
622
  """
615
- self.logger.debug("attempting to resolve 'ssh_config_file' file")
616
-
617
- resolved_ssh_config_file = ""
623
+ self.logger.debug(f"attempting to resolve 'ssh_config_file' file {ssh_config_file}")
618
624
 
619
- if Path(ssh_config_file).is_file():
620
- resolved_ssh_config_file = str(Path(ssh_config_file))
621
- elif Path("~/.ssh/config").expanduser().is_file():
622
- resolved_ssh_config_file = str(Path("~/.ssh/config").expanduser())
623
- elif Path("/etc/ssh/ssh_config").is_file():
624
- resolved_ssh_config_file = str(Path("/etc/ssh/ssh_config"))
625
+ if ssh_config_file == "" and transport == "system":
626
+ return SystemTransport.SSH_SYSTEM_CONFIG_MAGIC_STRING
625
627
 
626
- if resolved_ssh_config_file:
627
- self.logger.debug(
628
- f"using '{resolved_ssh_config_file}' as resolved 'ssh_config_file' file'"
629
- )
630
- else:
631
- self.logger.debug("unable to resolve 'ssh_config_file' file")
628
+ for path in (ssh_config_file, "~/.ssh/config", "/etc/ssh/ssh_config"):
629
+ full_path = Path(path).expanduser()
630
+ if full_path.is_file():
631
+ return str(full_path)
632
632
 
633
- return resolved_ssh_config_file
633
+ self.logger.debug(f"unable to resolve 'ssh_config_file' file {ssh_config_file}")
634
+ return ""
634
635
 
635
- def _resolve_ssh_known_hosts(self, ssh_known_hosts: str) -> str:
636
+ def _resolve_ssh_known_hosts(self, ssh_known_hosts: str, transport: str) -> str:
636
637
  """
637
638
  Resolve ssh known hosts file from provided string
638
639
 
@@ -641,6 +642,8 @@ class BaseDriver:
641
642
 
642
643
  Args:
643
644
  ssh_known_hosts: string representation of ssh config file to try to use
645
+ transport: string name of selected transport (so we can apply a bit of special handling
646
+ if system transport in use)
644
647
 
645
648
  Returns:
646
649
  str: string path to ssh known hosts file or an empty string
@@ -651,23 +654,17 @@ class BaseDriver:
651
654
  """
652
655
  self.logger.debug("attempting to resolve 'ssh_known_hosts file'")
653
656
 
654
- resolved_ssh_known_hosts = ""
655
-
656
- if Path(ssh_known_hosts).is_file():
657
- resolved_ssh_known_hosts = str(Path(ssh_known_hosts))
658
- elif Path("~/.ssh/known_hosts").expanduser().is_file():
659
- resolved_ssh_known_hosts = str(Path("~/.ssh/known_hosts").expanduser())
660
- elif Path("/etc/ssh/ssh_known_hosts").is_file():
661
- resolved_ssh_known_hosts = str(Path("/etc/ssh/ssh_known_hosts"))
657
+ if ssh_known_hosts == "" and transport == "system":
658
+ self.logger.debug("Using system known hosts file as 'ssh_known_hosts' file")
659
+ return SystemTransport.SSH_SYSTEM_KNOWN_HOSTS_FILE_MAGIC_STRING
662
660
 
663
- if resolved_ssh_known_hosts:
664
- self.logger.debug(
665
- f"using '{resolved_ssh_known_hosts}' as resolved 'ssh_known_hosts' file'"
666
- )
667
- else:
668
- self.logger.debug("unable to resolve 'ssh_known_hosts' file")
661
+ for path in (ssh_known_hosts, "~/.ssh/known_hosts", "/etc/ssh/ssh_known_hosts"):
662
+ full_path = Path(path).expanduser()
663
+ if full_path.is_file():
664
+ return str(full_path)
669
665
 
670
- return resolved_ssh_known_hosts
666
+ self.logger.info(f"unable to resolve 'ssh_known_hosts' file {ssh_known_hosts}")
667
+ return ""
671
668
 
672
669
  @property
673
670
  def comms_prompt_pattern(self) -> str:
@@ -1,11 +1,12 @@
1
1
  """scrapli.driver.base.sync_driver"""
2
+
2
3
  from types import TracebackType
3
4
  from typing import Any, Optional, Type, TypeVar
4
5
 
5
6
  from scrapli.channel import Channel
6
7
  from scrapli.driver.base.base_driver import BaseDriver
7
- from scrapli.exceptions import ScrapliValueError
8
- from scrapli.transport import ASYNCIO_TRANSPORTS
8
+ from scrapli.exceptions import ScrapliConnectionError, ScrapliValueError
9
+ from scrapli.transport import ASYNCIO_TRANSPORTS, CORE_TRANSPORTS
9
10
 
10
11
  _T = TypeVar("_T", bound="Driver")
11
12
 
@@ -14,7 +15,7 @@ class Driver(BaseDriver):
14
15
  def __init__(self, **kwargs: Any):
15
16
  super().__init__(**kwargs)
16
17
 
17
- if self.transport_name in ASYNCIO_TRANSPORTS:
18
+ if self.transport_name in CORE_TRANSPORTS and self.transport_name in ASYNCIO_TRANSPORTS:
18
19
  raise ScrapliValueError(
19
20
  "provided transport is *not* an sync transport, must use an sync transport with"
20
21
  " the (sync)Driver(s)"
@@ -41,10 +42,22 @@ class Driver(BaseDriver):
41
42
  _T: a concrete implementation of the opened Driver object
42
43
 
43
44
  Raises:
44
- N/A
45
+ ScrapliConnectionError: if an exception occurs during opening
45
46
 
46
47
  """
47
- self.open()
48
+ try:
49
+ self.open()
50
+ except Exception as exc:
51
+ self.logger.critical(
52
+ "encountered exception during open in context manager, "
53
+ "attempting to close transport and channel"
54
+ )
55
+
56
+ self.transport.close()
57
+ self.channel.close()
58
+
59
+ raise ScrapliConnectionError(exc) from exc
60
+
48
61
  return self
49
62
 
50
63
  def __exit__(
@@ -94,14 +107,7 @@ class Driver(BaseDriver):
94
107
  auth_password=self.auth_password,
95
108
  auth_private_key_passphrase=self.auth_private_key_passphrase,
96
109
  )
97
- if (
98
- self.transport_name
99
- in (
100
- "telnet",
101
- "asynctelnet",
102
- )
103
- and not self.auth_bypass
104
- ):
110
+ if "telnet" in self.transport_name and not self.auth_bypass:
105
111
  self.channel.channel_authenticate_telnet(
106
112
  auth_username=self.auth_username, auth_password=self.auth_password
107
113
  )
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core"""
2
+
2
3
  from scrapli.driver.core.arista_eos import AsyncEOSDriver, EOSDriver
3
4
  from scrapli.driver.core.cisco_iosxe import AsyncIOSXEDriver, IOSXEDriver
4
5
  from scrapli.driver.core.cisco_iosxr import AsyncIOSXRDriver, IOSXRDriver
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.arista_eos"""
2
+
2
3
  from scrapli.driver.core.arista_eos.async_driver import AsyncEOSDriver
3
4
  from scrapli.driver.core.arista_eos.sync_driver import EOSDriver
4
5
 
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.arista_eos.async_driver"""
2
+
2
3
  from copy import deepcopy
3
4
  from io import BytesIO
4
5
  from typing import Any, Callable, Dict, List, Optional, Union
@@ -47,7 +48,7 @@ async def eos_on_close(conn: AsyncNetworkDriver) -> None:
47
48
 
48
49
 
49
50
  class AsyncEOSDriver(AsyncNetworkDriver, EOSDriverBase):
50
- def __init__(
51
+ def __init__( # pylint: disable=R0917
51
52
  self,
52
53
  host: str,
53
54
  privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.arista_eos.base_driver"""
2
+
2
3
  import re
3
4
  from typing import Dict
4
5
 
@@ -31,7 +32,7 @@ PRIVS = {
31
32
  ),
32
33
  "configuration": (
33
34
  PrivilegeLevel(
34
- pattern=r"^[\w.\-@()/: ]{1,63}\(config[\w.\-@/:]{0,63}\)#\s?$",
35
+ pattern=r"^[\w.\-@()/: ]{1,63}\(config[\w.\-@/:+]{0,63}\)#\s?$",
35
36
  name="configuration",
36
37
  previous_priv="privilege_exec",
37
38
  deescalate="end",
@@ -50,6 +51,7 @@ FAILED_WHEN_CONTAINS = [
50
51
  "% Invalid input",
51
52
  "% Cannot commit",
52
53
  "% Unavailable command",
54
+ "% Duplicate sequence number",
53
55
  ]
54
56
 
55
57
 
@@ -79,7 +81,7 @@ class EOSDriverBase:
79
81
  raise ScrapliValueError(msg)
80
82
  sess_prompt = re.escape(session_name[:6])
81
83
  pattern = (
82
- rf"^[a-z0-9.\-@()/: ]{{1,63}}\(config\-s\-{sess_prompt}[a-z0-9_.\-@/:]{{0,64}}\)#\s?$"
84
+ rf"^[a-z0-9.\-@()/: ]{{1,63}}\(config\-s\-{sess_prompt}[a-z0-9_.\-@/:+]{{0,64}}\)#\s?$"
83
85
  )
84
86
  name = session_name
85
87
  config_session = PrivilegeLevel(
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.arista_eos.sync_driver"""
2
+
2
3
  from copy import deepcopy
3
4
  from io import BytesIO
4
5
  from typing import Any, Callable, Dict, List, Optional, Union
@@ -47,7 +48,7 @@ def eos_on_close(conn: NetworkDriver) -> None:
47
48
 
48
49
 
49
50
  class EOSDriver(NetworkDriver, EOSDriverBase):
50
- def __init__(
51
+ def __init__( # pylint: disable=R0917
51
52
  self,
52
53
  host: str,
53
54
  privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.cisco_iosxe"""
2
+
2
3
  from scrapli.driver.core.cisco_iosxe.async_driver import AsyncIOSXEDriver
3
4
  from scrapli.driver.core.cisco_iosxe.sync_driver import IOSXEDriver
4
5
 
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.cisco_iosxe.async_driver"""
2
+
2
3
  from copy import deepcopy
3
4
  from io import BytesIO
4
5
  from typing import Any, Callable, Dict, List, Optional, Union
@@ -47,7 +48,7 @@ async def iosxe_on_close(conn: AsyncNetworkDriver) -> None:
47
48
 
48
49
 
49
50
  class AsyncIOSXEDriver(AsyncNetworkDriver):
50
- def __init__(
51
+ def __init__( # pylint: disable=R0917
51
52
  self,
52
53
  host: str,
53
54
  privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.cisco_iosxe.base_driver"""
2
+
2
3
  from scrapli.driver.network.base_driver import PrivilegeLevel
3
4
 
4
5
  PRIVS = {
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.cisco_iosxe.sync_driver"""
2
+
2
3
  from copy import deepcopy
3
4
  from io import BytesIO
4
5
  from typing import Any, Callable, Dict, List, Optional, Union
@@ -47,7 +48,7 @@ def iosxe_on_close(conn: NetworkDriver) -> None:
47
48
 
48
49
 
49
50
  class IOSXEDriver(NetworkDriver):
50
- def __init__(
51
+ def __init__( # pylint: disable=R0917
51
52
  self,
52
53
  host: str,
53
54
  privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.cisco_iosxr"""
2
+
2
3
  from scrapli.driver.core.cisco_iosxr.async_driver import AsyncIOSXRDriver
3
4
  from scrapli.driver.core.cisco_iosxr.sync_driver import IOSXRDriver
4
5
 
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.cisco_iosxr.async_driver"""
2
+
2
3
  from copy import deepcopy
3
4
  from io import BytesIO
4
5
  from typing import Any, Callable, Dict, List, Optional, Union
@@ -46,7 +47,7 @@ async def iosxr_on_close(conn: AsyncNetworkDriver) -> None:
46
47
 
47
48
 
48
49
  class AsyncIOSXRDriver(AsyncNetworkDriver):
49
- def __init__(
50
+ def __init__( # pylint: disable=R0917
50
51
  self,
51
52
  host: str,
52
53
  privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.cisco_iosxr.base_driver"""
2
+
2
3
  from scrapli.driver.network.base_driver import PrivilegeLevel
3
4
 
4
5
  PRIVS = {
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.cisco_iosxr.sync_driver"""
2
+
2
3
  from copy import deepcopy
3
4
  from io import BytesIO
4
5
  from typing import Any, Callable, Dict, List, Optional, Union
@@ -49,7 +50,7 @@ def iosxr_on_close(conn: NetworkDriver) -> None:
49
50
 
50
51
 
51
52
  class IOSXRDriver(NetworkDriver):
52
- def __init__(
53
+ def __init__( # pylint: disable=R0917
53
54
  self,
54
55
  host: str,
55
56
  privilege_levels: Optional[Dict[str, PrivilegeLevel]] = None,
@@ -1,4 +1,5 @@
1
1
  """scrapli.driver.core.cisco_nxos"""
2
+
2
3
  from scrapli.driver.core.cisco_nxos.async_driver import AsyncNXOSDriver
3
4
  from scrapli.driver.core.cisco_nxos.sync_driver import NXOSDriver
4
5