scrapli 2023.7.30__tar.gz → 2024.7.30__tar.gz
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-2023.7.30/scrapli.egg-info → scrapli-2024.7.30}/PKG-INFO +75 -4
- {scrapli-2023.7.30 → scrapli-2024.7.30}/pyproject.toml +12 -2
- {scrapli-2023.7.30 → scrapli-2024.7.30}/requirements-dev.txt +7 -7
- scrapli-2024.7.30/requirements-genie.txt +2 -0
- scrapli-2024.7.30/requirements-paramiko.txt +1 -0
- scrapli-2024.7.30/requirements-textfsm.txt +2 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/__init__.py +2 -1
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/channel/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/channel/async_channel.py +35 -12
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/channel/base_channel.py +25 -3
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/channel/sync_channel.py +35 -12
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/decorators.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/base/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/base/async_driver.py +19 -13
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/base/base_driver.py +121 -37
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/base/sync_driver.py +19 -13
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/arista_eos/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/arista_eos/async_driver.py +3 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/arista_eos/base_driver.py +3 -2
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/arista_eos/sync_driver.py +3 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_iosxe/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_iosxe/async_driver.py +3 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_iosxe/base_driver.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_iosxe/sync_driver.py +3 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_iosxr/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_iosxr/async_driver.py +3 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_iosxr/base_driver.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_iosxr/sync_driver.py +3 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_nxos/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_nxos/async_driver.py +3 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_nxos/base_driver.py +9 -4
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/cisco_nxos/sync_driver.py +3 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/juniper_junos/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/juniper_junos/async_driver.py +3 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/juniper_junos/base_driver.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/core/juniper_junos/sync_driver.py +3 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/generic/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/generic/async_driver.py +45 -3
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/generic/base_driver.py +2 -1
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/generic/sync_driver.py +45 -3
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/network/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/network/async_driver.py +27 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/network/base_driver.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/driver/network/sync_driver.py +27 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/exceptions.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/factory.py +22 -3
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/helper.py +76 -4
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/logging.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/response.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/ssh_config.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/base/__init__.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/base/async_transport.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/base/base_socket.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/base/base_transport.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/base/sync_transport.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/asyncssh/transport.py +4 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/asynctelnet/transport.py +13 -6
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/paramiko/transport.py +1 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/ssh2/transport.py +6 -3
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/system/ptyprocess.py +50 -13
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/system/transport.py +27 -6
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/telnet/transport.py +13 -7
- {scrapli-2023.7.30 → scrapli-2024.7.30/scrapli.egg-info}/PKG-INFO +75 -4
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli.egg-info/requires.txt +43 -13
- scrapli-2023.7.30/requirements-genie.txt +0 -2
- scrapli-2023.7.30/requirements-paramiko.txt +0 -1
- scrapli-2023.7.30/requirements-textfsm.txt +0 -2
- {scrapli-2023.7.30 → scrapli-2024.7.30}/LICENSE +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/MANIFEST.in +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/README.md +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/requirements-asyncssh.txt +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/requirements-community.txt +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/requirements-docs.txt +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/requirements-ssh2.txt +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/requirements-ttp.txt +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/requirements.txt +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/py.typed +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/settings.py +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/__init__.py +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/base/telnet_common.py +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/__init__.py +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/asyncssh/__init__.py +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/asynctelnet/__init__.py +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/paramiko/__init__.py +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/ssh2/__init__.py +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/system/__init__.py +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli/transport/plugins/telnet/__init__.py +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli.egg-info/SOURCES.txt +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli.egg-info/dependency_links.txt +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/scrapli.egg-info/top_level.txt +0 -0
- {scrapli-2023.7.30 → scrapli-2024.7.30}/setup.cfg +0 -0
@@ -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,25 +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
|
+
License-File: LICENSE
|
46
|
+
Provides-Extra: dev-darwin
|
47
|
+
Requires-Dist: black<25.0.0,>=23.3.0; extra == "dev-darwin"
|
48
|
+
Requires-Dist: darglint<2.0.0,>=1.8.1; extra == "dev-darwin"
|
49
|
+
Requires-Dist: isort<6.0.0,>=5.10.1; extra == "dev-darwin"
|
50
|
+
Requires-Dist: mypy<2.0.0,>=1.4.1; extra == "dev-darwin"
|
51
|
+
Requires-Dist: nox==2024.4.15; extra == "dev-darwin"
|
52
|
+
Requires-Dist: pycodestyle<3.0.0,>=2.8.0; extra == "dev-darwin"
|
53
|
+
Requires-Dist: pydocstyle<7.0.0,>=6.1.1; extra == "dev-darwin"
|
54
|
+
Requires-Dist: pyfakefs<6.0.0,>=5.4.1; extra == "dev-darwin"
|
55
|
+
Requires-Dist: pylama<9.0.0,>=8.4.0; extra == "dev-darwin"
|
56
|
+
Requires-Dist: pylint<4.0.0,>=3.0.0; extra == "dev-darwin"
|
57
|
+
Requires-Dist: pytest-asyncio<1.0.0,>=0.17.0; extra == "dev-darwin"
|
58
|
+
Requires-Dist: pytest-cov<5.0.0,>=3.0.0; extra == "dev-darwin"
|
59
|
+
Requires-Dist: pytest<8.0.0,>=7.0.0; extra == "dev-darwin"
|
60
|
+
Requires-Dist: scrapli-cfg==2023.7.30; extra == "dev-darwin"
|
61
|
+
Requires-Dist: scrapli-replay==2023.7.30; extra == "dev-darwin"
|
62
|
+
Requires-Dist: toml<1.0.0,>=0.10.2; extra == "dev-darwin"
|
63
|
+
Requires-Dist: types-paramiko<4.0.0,>=2.8.6; extra == "dev-darwin"
|
64
|
+
Requires-Dist: types-pkg-resources<1.0.0,>=0.1.3; extra == "dev-darwin"
|
65
|
+
Requires-Dist: ntc-templates<5.0.0,>=1.1.0; extra == "dev-darwin"
|
66
|
+
Requires-Dist: textfsm<2.0.0,>=1.1.0; extra == "dev-darwin"
|
67
|
+
Requires-Dist: genie<24.4,>=20.2; (sys_platform != "win32" and python_version < "3.11") and extra == "dev-darwin"
|
68
|
+
Requires-Dist: pyats>=20.2; (sys_platform != "win32" and python_version < "3.11") and extra == "dev-darwin"
|
69
|
+
Requires-Dist: ttp<1.0.0,>=0.5.0; extra == "dev-darwin"
|
70
|
+
Requires-Dist: paramiko<4.0.0,>=2.6.0; extra == "dev-darwin"
|
71
|
+
Requires-Dist: asyncssh<3.0.0,>=2.2.1; extra == "dev-darwin"
|
72
|
+
Requires-Dist: scrapli_community>=2021.01.30; extra == "dev-darwin"
|
45
73
|
Provides-Extra: dev
|
74
|
+
Requires-Dist: black<25.0.0,>=23.3.0; extra == "dev"
|
75
|
+
Requires-Dist: darglint<2.0.0,>=1.8.1; extra == "dev"
|
76
|
+
Requires-Dist: isort<6.0.0,>=5.10.1; extra == "dev"
|
77
|
+
Requires-Dist: mypy<2.0.0,>=1.4.1; extra == "dev"
|
78
|
+
Requires-Dist: nox==2024.4.15; extra == "dev"
|
79
|
+
Requires-Dist: pycodestyle<3.0.0,>=2.8.0; extra == "dev"
|
80
|
+
Requires-Dist: pydocstyle<7.0.0,>=6.1.1; extra == "dev"
|
81
|
+
Requires-Dist: pyfakefs<6.0.0,>=5.4.1; extra == "dev"
|
82
|
+
Requires-Dist: pylama<9.0.0,>=8.4.0; extra == "dev"
|
83
|
+
Requires-Dist: pylint<4.0.0,>=3.0.0; extra == "dev"
|
84
|
+
Requires-Dist: pytest-asyncio<1.0.0,>=0.17.0; extra == "dev"
|
85
|
+
Requires-Dist: pytest-cov<5.0.0,>=3.0.0; extra == "dev"
|
86
|
+
Requires-Dist: pytest<8.0.0,>=7.0.0; extra == "dev"
|
87
|
+
Requires-Dist: scrapli-cfg==2023.7.30; extra == "dev"
|
88
|
+
Requires-Dist: scrapli-replay==2023.7.30; extra == "dev"
|
89
|
+
Requires-Dist: toml<1.0.0,>=0.10.2; extra == "dev"
|
90
|
+
Requires-Dist: types-paramiko<4.0.0,>=2.8.6; extra == "dev"
|
91
|
+
Requires-Dist: types-pkg-resources<1.0.0,>=0.1.3; extra == "dev"
|
92
|
+
Requires-Dist: ntc-templates<5.0.0,>=1.1.0; extra == "dev"
|
93
|
+
Requires-Dist: textfsm<2.0.0,>=1.1.0; extra == "dev"
|
94
|
+
Requires-Dist: genie<24.4,>=20.2; (sys_platform != "win32" and python_version < "3.11") and extra == "dev"
|
95
|
+
Requires-Dist: pyats>=20.2; (sys_platform != "win32" and python_version < "3.11") and extra == "dev"
|
96
|
+
Requires-Dist: ttp<1.0.0,>=0.5.0; extra == "dev"
|
97
|
+
Requires-Dist: paramiko<4.0.0,>=2.6.0; extra == "dev"
|
98
|
+
Requires-Dist: ssh2-python<2.0.0,>=0.23.0; python_version < "3.12" and extra == "dev"
|
99
|
+
Requires-Dist: asyncssh<3.0.0,>=2.2.1; extra == "dev"
|
100
|
+
Requires-Dist: scrapli_community>=2021.01.30; extra == "dev"
|
46
101
|
Provides-Extra: docs
|
102
|
+
Requires-Dist: mdx-gh-links<1.0,>=0.2; extra == "docs"
|
103
|
+
Requires-Dist: mkdocs<2.0.0,>=1.2.3; extra == "docs"
|
104
|
+
Requires-Dist: mkdocs-gen-files<1.0.0,>=0.4.0; extra == "docs"
|
105
|
+
Requires-Dist: mkdocs-literate-nav<1.0.0,>=0.5.0; extra == "docs"
|
106
|
+
Requires-Dist: mkdocs-material<10.0.0,>=8.1.6; extra == "docs"
|
107
|
+
Requires-Dist: mkdocs-material-extensions<2.0.0,>=1.0.3; extra == "docs"
|
108
|
+
Requires-Dist: mkdocs-section-index<1.0.0,>=0.3.4; extra == "docs"
|
109
|
+
Requires-Dist: mkdocstrings[python]<1.0.0,>=0.19.0; extra == "docs"
|
47
110
|
Provides-Extra: textfsm
|
111
|
+
Requires-Dist: ntc-templates<5.0.0,>=1.1.0; extra == "textfsm"
|
112
|
+
Requires-Dist: textfsm<2.0.0,>=1.1.0; extra == "textfsm"
|
48
113
|
Provides-Extra: genie
|
114
|
+
Requires-Dist: genie<24.4,>=20.2; (sys_platform != "win32" and python_version < "3.11") and extra == "genie"
|
115
|
+
Requires-Dist: pyats>=20.2; (sys_platform != "win32" and python_version < "3.11") and extra == "genie"
|
49
116
|
Provides-Extra: ttp
|
117
|
+
Requires-Dist: ttp<1.0.0,>=0.5.0; extra == "ttp"
|
50
118
|
Provides-Extra: paramiko
|
119
|
+
Requires-Dist: paramiko<4.0.0,>=2.6.0; extra == "paramiko"
|
51
120
|
Provides-Extra: ssh2
|
121
|
+
Requires-Dist: ssh2-python<2.0.0,>=0.23.0; python_version < "3.12" and extra == "ssh2"
|
52
122
|
Provides-Extra: asyncssh
|
123
|
+
Requires-Dist: asyncssh<3.0.0,>=2.2.1; extra == "asyncssh"
|
53
124
|
Provides-Extra: community
|
54
|
-
|
125
|
+
Requires-Dist: scrapli_community>=2021.01.30; extra == "community"
|
55
126
|
|
56
127
|
<p center><a href=""><img src=https://github.com/carlmontanari/scrapli/blob/main/scrapli.svg?sanitize=true/></a></p>
|
57
128
|
|
@@ -28,17 +28,17 @@ license = { file = "LICENSE" }
|
|
28
28
|
authors = [
|
29
29
|
{ name = "Carl Montanari", email = "carl.r.montanari@gmail.com" },
|
30
30
|
]
|
31
|
-
requires-python = ">=3.
|
31
|
+
requires-python = ">=3.8"
|
32
32
|
classifiers = [
|
33
33
|
"License :: OSI Approved :: MIT License",
|
34
34
|
"Operating System :: POSIX :: Linux",
|
35
35
|
"Operating System :: MacOS",
|
36
36
|
"Programming Language :: Python",
|
37
|
-
"Programming Language :: Python :: 3.7",
|
38
37
|
"Programming Language :: Python :: 3.8",
|
39
38
|
"Programming Language :: Python :: 3.9",
|
40
39
|
"Programming Language :: Python :: 3.10",
|
41
40
|
"Programming Language :: Python :: 3.11",
|
41
|
+
"Programming Language :: Python :: 3.12",
|
42
42
|
"Programming Language :: Python :: 3 :: Only",
|
43
43
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
44
44
|
]
|
@@ -55,6 +55,16 @@ Homepage = "https://github.com/carlmontanari/scrapli"
|
|
55
55
|
[tool.setuptools.dynamic]
|
56
56
|
version = { attr = "scrapli.__version__" }
|
57
57
|
dependencies = { file = "requirements.txt" }
|
58
|
+
# dev-darwin is same as dev but excludes ssh2-python
|
59
|
+
optional-dependencies.dev-darwin = { file = [
|
60
|
+
"requirements-dev.txt",
|
61
|
+
"requirements-textfsm.txt",
|
62
|
+
"requirements-genie.txt",
|
63
|
+
"requirements-ttp.txt",
|
64
|
+
"requirements-paramiko.txt",
|
65
|
+
"requirements-asyncssh.txt",
|
66
|
+
"requirements-community.txt",
|
67
|
+
] }
|
58
68
|
optional-dependencies.dev = { file = [
|
59
69
|
"requirements-dev.txt",
|
60
70
|
"requirements-textfsm.txt",
|
@@ -1,18 +1,18 @@
|
|
1
|
-
black>=23.3.0,<
|
1
|
+
black>=23.3.0,<25.0.0
|
2
2
|
darglint>=1.8.1,<2.0.0
|
3
3
|
isort>=5.10.1,<6.0.0
|
4
|
-
mypy
|
5
|
-
nox==
|
4
|
+
mypy>=1.4.1,<2.0.0
|
5
|
+
nox==2024.4.15
|
6
6
|
pycodestyle>=2.8.0,<3.0.0
|
7
7
|
pydocstyle>=6.1.1,<7.0.0
|
8
|
-
pyfakefs>=5.
|
8
|
+
pyfakefs>=5.4.1,<6.0.0
|
9
9
|
pylama>=8.4.0,<9.0.0
|
10
|
-
pylint
|
10
|
+
pylint>=3.0.0,<4.0.0
|
11
11
|
pytest-asyncio>=0.17.0,<1.0.0
|
12
12
|
pytest-cov>=3.0.0,<5.0.0
|
13
13
|
pytest>=7.0.0,<8.0.0
|
14
|
-
scrapli-cfg==
|
15
|
-
scrapli-replay==
|
14
|
+
scrapli-cfg==2023.7.30
|
15
|
+
scrapli-replay==2023.7.30
|
16
16
|
toml>=0.10.2,<1.0.0
|
17
17
|
types-paramiko>=2.8.6,<4.0.0
|
18
18
|
types-pkg-resources>=0.1.3,<1.0.0
|
@@ -0,0 +1 @@
|
|
1
|
+
paramiko>=2.6.0,<4.0.0
|
@@ -1,4 +1,5 @@
|
|
1
1
|
"""scrapli.channel.async_channel"""
|
2
|
+
|
2
3
|
import asyncio
|
3
4
|
import re
|
4
5
|
import time
|
@@ -10,6 +11,7 @@ from typing import AsyncIterator, List, Optional, Tuple
|
|
10
11
|
from scrapli.channel.base_channel import BaseChannel, BaseChannelArgs
|
11
12
|
from scrapli.decorators import timeout_wrapper
|
12
13
|
from scrapli.exceptions import ScrapliAuthenticationFailed, ScrapliTimeout
|
14
|
+
from scrapli.helper import output_roughly_contains_input
|
13
15
|
from scrapli.transport.base import AsyncTransport
|
14
16
|
|
15
17
|
|
@@ -69,7 +71,7 @@ class AsyncChannel(BaseChannel):
|
|
69
71
|
buf = await self.transport.read()
|
70
72
|
buf = buf.replace(b"\r", b"")
|
71
73
|
|
72
|
-
self.logger.debug(
|
74
|
+
self.logger.debug("read: %r", buf)
|
73
75
|
|
74
76
|
if self.channel_log:
|
75
77
|
self.channel_log.write(buf)
|
@@ -104,9 +106,16 @@ class AsyncChannel(BaseChannel):
|
|
104
106
|
while True:
|
105
107
|
buf += await self.read()
|
106
108
|
|
107
|
-
|
108
|
-
|
109
|
-
|
109
|
+
if not self._base_channel_args.comms_roughly_match_inputs:
|
110
|
+
# replace any backspace chars (particular problem w/ junos), and remove any added
|
111
|
+
# spaces this is just for comparison of the inputs to what was read from channel
|
112
|
+
# note (2024) this would be worked around by using the roughly contains search,
|
113
|
+
# *but* that is slower (probably immaterially for most people but... ya know...)
|
114
|
+
processed_buf = b"".join(buf.lower().replace(b"\x08", b"").split())
|
115
|
+
|
116
|
+
if processed_channel_input in processed_buf:
|
117
|
+
return buf
|
118
|
+
elif output_roughly_contains_input(input_=processed_channel_input, output=buf):
|
110
119
|
return buf
|
111
120
|
|
112
121
|
async def _read_until_prompt(self, buf: bytes = b"") -> bytes:
|
@@ -241,7 +250,7 @@ class AsyncChannel(BaseChannel):
|
|
241
250
|
|
242
251
|
if (time.time() - start) > read_duration:
|
243
252
|
break
|
244
|
-
if any(
|
253
|
+
if any(channel_output in search_buf for channel_output in channel_outputs):
|
245
254
|
break
|
246
255
|
if re.search(pattern=regex_channel_outputs_pattern, string=search_buf):
|
247
256
|
break
|
@@ -455,6 +464,7 @@ class AsyncChannel(BaseChannel):
|
|
455
464
|
*,
|
456
465
|
strip_prompt: bool = True,
|
457
466
|
eager: bool = False,
|
467
|
+
eager_input: bool = False,
|
458
468
|
) -> Tuple[bytes, bytes]:
|
459
469
|
"""
|
460
470
|
Primary entry point to send data to devices in shell mode; accept input and returns result
|
@@ -465,6 +475,8 @@ class AsyncChannel(BaseChannel):
|
|
465
475
|
eager: eager mode reads and returns the `_read_until_input` value, but does not attempt
|
466
476
|
to read to the prompt pattern -- this should not be used manually! (only used by
|
467
477
|
`send_configs` with the eager flag set)
|
478
|
+
eager_input: when true does *not* try to read our input off the channel -- generally
|
479
|
+
this should be left alone unless you know what you are doing!
|
468
480
|
|
469
481
|
Returns:
|
470
482
|
Tuple[bytes, bytes]: tuple of "raw" output and "processed" (cleaned up/stripped) output
|
@@ -479,12 +491,18 @@ class AsyncChannel(BaseChannel):
|
|
479
491
|
bytes_channel_input = channel_input.encode()
|
480
492
|
|
481
493
|
self.logger.info(
|
482
|
-
|
494
|
+
"sending channel input: %s; strip_prompt: %s; eager: %s",
|
495
|
+
channel_input,
|
496
|
+
strip_prompt,
|
497
|
+
eager,
|
483
498
|
)
|
484
499
|
|
485
500
|
async with self._channel_lock():
|
486
501
|
self.write(channel_input=channel_input)
|
487
|
-
|
502
|
+
|
503
|
+
if not eager_input:
|
504
|
+
_buf_until_input = await self._read_until_input(channel_input=bytes_channel_input)
|
505
|
+
|
488
506
|
self.send_return()
|
489
507
|
|
490
508
|
if not eager:
|
@@ -531,8 +549,12 @@ class AsyncChannel(BaseChannel):
|
|
531
549
|
]
|
532
550
|
|
533
551
|
self.logger.info(
|
534
|
-
|
535
|
-
|
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,
|
536
558
|
)
|
537
559
|
|
538
560
|
async with self._channel_lock():
|
@@ -642,9 +664,10 @@ class AsyncChannel(BaseChannel):
|
|
642
664
|
|
643
665
|
_channel_input = channel_input if not hidden_input else "REDACTED"
|
644
666
|
self.logger.info(
|
645
|
-
|
646
|
-
|
647
|
-
|
667
|
+
"sending interactive input: %s; expecting: %s; hidden_input: %s",
|
668
|
+
_channel_input,
|
669
|
+
channel_response,
|
670
|
+
hidden_input,
|
648
671
|
)
|
649
672
|
|
650
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
|
@@ -10,7 +11,19 @@ from scrapli.exceptions import ScrapliAuthenticationFailed, ScrapliTypeError, Sc
|
|
10
11
|
from scrapli.logging import get_instance_logger
|
11
12
|
from scrapli.transport.base import AsyncTransport, Transport
|
12
13
|
|
13
|
-
ANSI_ESCAPE_PATTERN = re.compile(
|
14
|
+
ANSI_ESCAPE_PATTERN = re.compile(
|
15
|
+
pattern=rb"[\x1B\x9B\x9D](\s)?" # Prefix ESC (^) or CSI (^[) or OSC (^)
|
16
|
+
rb"("
|
17
|
+
rb"([78ME])" # control cursor position
|
18
|
+
rb"|"
|
19
|
+
rb"((\]\d).*?[\x07])" # BEL (Terminal bell)
|
20
|
+
rb"|"
|
21
|
+
rb"(\[.*?[@-~])" # control codes starts with `[` e.x. ESC [2;37;41m
|
22
|
+
rb"|"
|
23
|
+
rb"(\[.*?[0-9;]m)" # Select Graphic Rendition (SGR) control sequence
|
24
|
+
rb")",
|
25
|
+
flags=re.VERBOSE,
|
26
|
+
)
|
14
27
|
|
15
28
|
|
16
29
|
@dataclass()
|
@@ -31,6 +44,12 @@ class BaseChannelArgs:
|
|
31
44
|
comms_prompt_search_depth: depth of the buffer to search in for searching for the prompt
|
32
45
|
in "read_until_prompt"; smaller number here will generally be faster, though may be less
|
33
46
|
reliable; default value is 1000
|
47
|
+
comms_roughly_match_inputs: indicates if the channel should "roughly" match inputs sent
|
48
|
+
to the device. If False (default) inputs are strictly checked, as in any input
|
49
|
+
*must* be read back exactly on the channel. When set to True all input chars *must*
|
50
|
+
be read back in order in the output and all chars must be present, but the *exact*
|
51
|
+
input string does not need to be seen. This can be useful if a device echoes back
|
52
|
+
extra characters or rewrites the terminal during command input.
|
34
53
|
timeout_ops: timeout_ops to assign to the channel, see above
|
35
54
|
channel_log: log "channel" output -- this would be the output you would normally see on a
|
36
55
|
terminal. If `True` logs to `scrapli_channel.log`, if a string is provided, logs to
|
@@ -53,6 +72,7 @@ class BaseChannelArgs:
|
|
53
72
|
comms_prompt_pattern: str = r"^[a-z0-9.\-@()/:]{1,32}[#>$]$"
|
54
73
|
comms_return_char: str = "\n"
|
55
74
|
comms_prompt_search_depth: int = 1000
|
75
|
+
comms_roughly_match_inputs: bool = False
|
56
76
|
timeout_ops: float = 30.0
|
57
77
|
channel_log: Union[str, bool, BytesIO] = False
|
58
78
|
channel_log_mode: str = "write"
|
@@ -348,8 +368,10 @@ class BaseChannel:
|
|
348
368
|
N/A
|
349
369
|
|
350
370
|
"""
|
351
|
-
|
352
|
-
|
371
|
+
if redacted:
|
372
|
+
self.logger.debug("write: REDACTED")
|
373
|
+
else:
|
374
|
+
self.logger.debug("write: %r", channel_input)
|
353
375
|
|
354
376
|
self.transport.write(channel_input=channel_input.encode())
|
355
377
|
|
@@ -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
|
@@ -10,6 +11,7 @@ from typing import Iterator, List, Optional, Tuple
|
|
10
11
|
from scrapli.channel.base_channel import BaseChannel, BaseChannelArgs
|
11
12
|
from scrapli.decorators import timeout_wrapper
|
12
13
|
from scrapli.exceptions import ScrapliAuthenticationFailed, ScrapliConnectionError, ScrapliTimeout
|
14
|
+
from scrapli.helper import output_roughly_contains_input
|
13
15
|
from scrapli.transport.base import Transport
|
14
16
|
|
15
17
|
|
@@ -69,7 +71,7 @@ class Channel(BaseChannel):
|
|
69
71
|
buf = self.transport.read()
|
70
72
|
buf = buf.replace(b"\r", b"")
|
71
73
|
|
72
|
-
self.logger.debug(
|
74
|
+
self.logger.debug("read: %r", buf)
|
73
75
|
|
74
76
|
if self.channel_log:
|
75
77
|
self.channel_log.write(buf)
|
@@ -104,9 +106,16 @@ class Channel(BaseChannel):
|
|
104
106
|
while True:
|
105
107
|
buf += self.read()
|
106
108
|
|
107
|
-
|
108
|
-
|
109
|
-
|
109
|
+
if not self._base_channel_args.comms_roughly_match_inputs:
|
110
|
+
# replace any backspace chars (particular problem w/ junos), and remove any added
|
111
|
+
# spaces this is just for comparison of the inputs to what was read from channel
|
112
|
+
# note (2024) this would be worked around by using the roughly contains search,
|
113
|
+
# *but* that is slower (probably immaterially for most people but... ya know...)
|
114
|
+
processed_buf = b"".join(buf.lower().replace(b"\x08", b"").split())
|
115
|
+
|
116
|
+
if processed_channel_input in processed_buf:
|
117
|
+
return buf
|
118
|
+
elif output_roughly_contains_input(input_=processed_channel_input, output=buf):
|
110
119
|
return buf
|
111
120
|
|
112
121
|
def _read_until_prompt(self, buf: bytes = b"") -> bytes:
|
@@ -238,7 +247,7 @@ class Channel(BaseChannel):
|
|
238
247
|
|
239
248
|
if (time.time() - start) > read_duration:
|
240
249
|
break
|
241
|
-
if any(
|
250
|
+
if any(channel_output in search_buf for channel_output in channel_outputs):
|
242
251
|
break
|
243
252
|
if re.search(pattern=regex_channel_outputs_pattern, string=search_buf):
|
244
253
|
break
|
@@ -456,6 +465,7 @@ class Channel(BaseChannel):
|
|
456
465
|
*,
|
457
466
|
strip_prompt: bool = True,
|
458
467
|
eager: bool = False,
|
468
|
+
eager_input: bool = False,
|
459
469
|
) -> Tuple[bytes, bytes]:
|
460
470
|
"""
|
461
471
|
Primary entry point to send data to devices in shell mode; accept input and returns result
|
@@ -466,6 +476,8 @@ class Channel(BaseChannel):
|
|
466
476
|
eager: eager mode reads and returns the `_read_until_input` value, but does not attempt
|
467
477
|
to read to the prompt pattern -- this should not be used manually! (only used by
|
468
478
|
`send_configs` with the eager flag set)
|
479
|
+
eager_input: when true does *not* try to read our input off the channel -- generally
|
480
|
+
this should be left alone unless you know what you are doing!
|
469
481
|
|
470
482
|
Returns:
|
471
483
|
Tuple[bytes, bytes]: tuple of "raw" output and "processed" (cleaned up/stripped) output
|
@@ -480,12 +492,18 @@ class Channel(BaseChannel):
|
|
480
492
|
bytes_channel_input = channel_input.encode()
|
481
493
|
|
482
494
|
self.logger.info(
|
483
|
-
|
495
|
+
"sending channel input: %s; strip_prompt: %s; eager: %s",
|
496
|
+
channel_input,
|
497
|
+
strip_prompt,
|
498
|
+
eager,
|
484
499
|
)
|
485
500
|
|
486
501
|
with self._channel_lock():
|
487
502
|
self.write(channel_input=channel_input)
|
488
|
-
|
503
|
+
|
504
|
+
if not eager_input:
|
505
|
+
_buf_until_input = self._read_until_input(channel_input=bytes_channel_input)
|
506
|
+
|
489
507
|
self.send_return()
|
490
508
|
|
491
509
|
if not eager:
|
@@ -532,8 +550,12 @@ class Channel(BaseChannel):
|
|
532
550
|
]
|
533
551
|
|
534
552
|
self.logger.info(
|
535
|
-
|
536
|
-
|
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,
|
537
559
|
)
|
538
560
|
|
539
561
|
with self._channel_lock():
|
@@ -643,9 +665,10 @@ class Channel(BaseChannel):
|
|
643
665
|
|
644
666
|
_channel_input = channel_input if not hidden_input else "REDACTED"
|
645
667
|
self.logger.info(
|
646
|
-
|
647
|
-
|
648
|
-
|
668
|
+
"sending interactive input: %s; expecting: %s; hidden_input: %s",
|
669
|
+
_channel_input,
|
670
|
+
channel_response,
|
671
|
+
hidden_input,
|
649
672
|
)
|
650
673
|
|
651
674
|
self.write(channel_input=channel_input, redacted=bool(hidden_input))
|
@@ -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
|
-
|
43
|
+
ScrapliConnectionError: if an exception occurs during opening
|
43
44
|
|
44
45
|
"""
|
45
|
-
|
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
|
)
|