xbot.plugins.ssh 0.1.0__tar.gz → 0.2.1__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.
Files changed (51) hide show
  1. {xbot.plugins.ssh-0.1.0/xbot.plugins.ssh.egg-info → xbot_plugins_ssh-0.2.1}/PKG-INFO +12 -17
  2. xbot_plugins_ssh-0.2.1/pyproject.toml +38 -0
  3. xbot_plugins_ssh-0.2.1/setup.py +26 -0
  4. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/tests/test_sftp.py +15 -4
  5. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/tests/test_ssh.py +2 -1
  6. xbot_plugins_ssh-0.2.1/xbot/plugins/ssh/py.typed +1 -0
  7. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/xbot/plugins/ssh/sftp.py +41 -20
  8. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/xbot/plugins/ssh/ssh.py +34 -23
  9. xbot_plugins_ssh-0.2.1/xbot/plugins/ssh/version.py +3 -0
  10. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1/xbot.plugins.ssh.egg-info}/PKG-INFO +12 -17
  11. xbot_plugins_ssh-0.2.1/xbot.plugins.ssh.egg-info/SOURCES.txt +20 -0
  12. xbot_plugins_ssh-0.2.1/xbot.plugins.ssh.egg-info/requires.txt +2 -0
  13. xbot_plugins_ssh-0.2.1/xbot.plugins.ssh.egg-info/top_level.txt +1 -0
  14. xbot.plugins.ssh-0.1.0/MANIFEST.in +0 -2
  15. xbot.plugins.ssh-0.1.0/demo/lib/testbed.py +0 -48
  16. xbot.plugins.ssh-0.1.0/demo/lib/testcase.py +0 -15
  17. xbot.plugins.ssh-0.1.0/demo/testcases/__init__.py +0 -0
  18. xbot.plugins.ssh-0.1.0/demo/testcases/tc_sftp.py +0 -99
  19. xbot.plugins.ssh-0.1.0/demo/testcases/tc_ssh.py +0 -73
  20. xbot.plugins.ssh-0.1.0/pyproject.toml +0 -3
  21. xbot.plugins.ssh-0.1.0/requirements.txt +0 -2
  22. xbot.plugins.ssh-0.1.0/setup.py +0 -64
  23. xbot.plugins.ssh-0.1.0/tests/__init__.py +0 -0
  24. xbot.plugins.ssh-0.1.0/tests/run.py +0 -39
  25. xbot.plugins.ssh-0.1.0/venv/bin/jp.py +0 -54
  26. xbot.plugins.ssh-0.1.0/venv/bin/rst2html.py +0 -23
  27. xbot.plugins.ssh-0.1.0/venv/bin/rst2html4.py +0 -26
  28. xbot.plugins.ssh-0.1.0/venv/bin/rst2html5.py +0 -34
  29. xbot.plugins.ssh-0.1.0/venv/bin/rst2latex.py +0 -26
  30. xbot.plugins.ssh-0.1.0/venv/bin/rst2man.py +0 -26
  31. xbot.plugins.ssh-0.1.0/venv/bin/rst2odt.py +0 -30
  32. xbot.plugins.ssh-0.1.0/venv/bin/rst2odt_prepstyles.py +0 -67
  33. xbot.plugins.ssh-0.1.0/venv/bin/rst2pseudoxml.py +0 -23
  34. xbot.plugins.ssh-0.1.0/venv/bin/rst2s5.py +0 -24
  35. xbot.plugins.ssh-0.1.0/venv/bin/rst2xetex.py +0 -27
  36. xbot.plugins.ssh-0.1.0/venv/bin/rst2xml.py +0 -23
  37. xbot.plugins.ssh-0.1.0/venv/bin/rstpep2html.py +0 -25
  38. xbot.plugins.ssh-0.1.0/xbot/plugins/ssh/__init__.py +0 -0
  39. xbot.plugins.ssh-0.1.0/xbot/plugins/ssh/version.py +0 -3
  40. xbot.plugins.ssh-0.1.0/xbot.plugins.ssh.egg-info/SOURCES.txt +0 -42
  41. xbot.plugins.ssh-0.1.0/xbot.plugins.ssh.egg-info/requires.txt +0 -4
  42. xbot.plugins.ssh-0.1.0/xbot.plugins.ssh.egg-info/top_level.txt +0 -6
  43. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/LICENSE +0 -0
  44. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/README.md +0 -0
  45. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/README.zh.md +0 -0
  46. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/setup.cfg +0 -0
  47. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/tests/test_utils.py +0 -0
  48. {xbot.plugins.ssh-0.1.0/demo/lib → xbot_plugins_ssh-0.2.1/xbot/plugins/ssh}/__init__.py +0 -0
  49. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/xbot/plugins/ssh/errors.py +0 -0
  50. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/xbot/plugins/ssh/utils.py +0 -0
  51. {xbot.plugins.ssh-0.1.0 → xbot_plugins_ssh-0.2.1}/xbot.plugins.ssh.egg-info/dependency_links.txt +0 -0
@@ -1,27 +1,24 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: xbot.plugins.ssh
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: SSH library for xbot.framework
5
- Home-page: https://github.com/zhaowcheng/xbot.plugins.ssh
6
- Author: zhaowcheng
7
- Author-email: zhaowcheng@163.com
8
- License: UNKNOWN
5
+ Author-email: zhaowcheng <zhaowcheng@163.com>
6
+ License-Expression: BSD-2-Clause
9
7
  Project-URL: Homepage, https://github.com/zhaowcheng/xbot.plugins.ssh
10
8
  Project-URL: Issues, https://github.com/zhaowcheng/xbot.plugins.ssh/issues
11
- Platform: UNKNOWN
12
- Classifier: License :: OSI Approved :: BSD License
13
9
  Classifier: Operating System :: OS Independent
14
10
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.6
16
- Classifier: Programming Language :: Python :: 3.7
17
- Classifier: Programming Language :: Python :: 3.8
18
- Classifier: Programming Language :: Python :: 3.9
19
11
  Classifier: Programming Language :: Python :: 3.10
20
12
  Classifier: Programming Language :: Python :: 3.11
21
13
  Classifier: Programming Language :: Python :: 3.12
22
- Requires-Python: >=3.6
14
+ Requires-Python: >=3.10
23
15
  Description-Content-Type: text/markdown
24
16
  License-File: LICENSE
17
+ Requires-Dist: xbot.framework>=0.5.0
18
+ Requires-Dist: paramiko
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: license-file
25
22
 
26
23
  ## Introduction
27
24
 
@@ -144,7 +141,7 @@ with sftpconn.open(p, 'r') as fp:
144
141
 
145
142
  ## Demo Project
146
143
 
147
- Here is a [demo](https://github.com/zhaowcheng/xbot.plugins.ssh/tree/v0.1.0/demo) project showing how to use `xbot.plugins.ssh` in a test project.
144
+ Here is a [demo](https://github.com/zhaowcheng/xbot.plugins.ssh/tree/v/demo) project showing how to use `xbot.plugins.ssh` in a test project.
148
145
 
149
146
  ***
150
147
 
@@ -269,6 +266,4 @@ with sftpconn.open(p, 'r') as fp:
269
266
 
270
267
  ## 示例项目
271
268
 
272
- 展示如何在一个测试项目中使用本插件的示例:[demo](https://github.com/zhaowcheng/xbot.plugins.ssh/tree/v0.1.0/demo)
273
-
274
-
269
+ 展示如何在一个测试项目中使用本插件的示例:[demo](https://github.com/zhaowcheng/xbot.plugins.ssh/tree/v/demo)
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "xbot.plugins.ssh"
7
+ description = "SSH library for xbot.framework"
8
+ authors = [
9
+ { name = "zhaowcheng", email = "zhaowcheng@163.com" },
10
+ ]
11
+ license = "BSD-2-Clause"
12
+ license-files = ["LICENSE"]
13
+ requires-python = ">=3.10"
14
+ dependencies = [
15
+ "xbot.framework>=0.5.0",
16
+ "paramiko",
17
+ ]
18
+ classifiers = [
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ ]
25
+ dynamic = ["version", "readme"]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/zhaowcheng/xbot.plugins.ssh"
29
+ Issues = "https://github.com/zhaowcheng/xbot.plugins.ssh/issues"
30
+
31
+ [tool.setuptools.dynamic]
32
+ version = { attr = "xbot.plugins.ssh.version.__version__" }
33
+
34
+ [tool.setuptools.packages.find]
35
+ include = ["xbot.plugins.ssh*"]
36
+
37
+ [tool.setuptools.package-data]
38
+ "xbot.plugins.ssh" = ["py.typed"]
@@ -0,0 +1,26 @@
1
+ # Build hook: generate long_description from README files,
2
+ # stripping language-switcher headers.
3
+ # All other metadata lives in pyproject.toml.
4
+ import re
5
+
6
+ from setuptools import setup
7
+
8
+
9
+ def _build_long_description() -> str:
10
+ desc = ""
11
+ for readme in ("README.md", "README.zh.md"):
12
+ if desc:
13
+ desc += "\n***\n\n"
14
+ with open(readme, encoding="utf8") as f:
15
+ desc += "".join(f.readlines()[6:])
16
+ with open("pyproject.toml", encoding="utf8") as f:
17
+ m = re.search(r'''^version\s*=\s*["']([^"']+)''', f.read(), re.M)
18
+ version = m.group(1) if m else ""
19
+ desc = desc.replace("/tree/master/", f"/tree/v{version}/")
20
+ return desc
21
+
22
+
23
+ setup(
24
+ long_description=_build_long_description(),
25
+ long_description_content_type="text/markdown",
26
+ )
@@ -35,10 +35,21 @@ class TestSFTPConnection(unittest.TestCase):
35
35
  os.system('whoami > %s' % os.path.join(cls.LPUTDIR, 'dir', 'subfile'))
36
36
 
37
37
  @classmethod
38
- def tearDownClass(cls):
39
- shutil.rmtree(cls.LPUTDIR)
40
- shutil.rmtree(cls.LGETDIR)
41
- cls.sftp.disconnect()
38
+ def tearDownClass(cls) -> None:
39
+ try:
40
+ if cls.sftp.exists(cls.RPUTDIR):
41
+ for top, _, files in reversed(
42
+ list(cls.sftp.walk(cls.RPUTDIR))
43
+ ):
44
+ for filename in files:
45
+ cls.sftp._client.remove(
46
+ cls.sftp.join(top, filename)
47
+ )
48
+ cls.sftp._client.rmdir(top)
49
+ finally:
50
+ shutil.rmtree(cls.LPUTDIR, ignore_errors=True)
51
+ shutil.rmtree(cls.LGETDIR, ignore_errors=True)
52
+ cls.sftp.disconnect()
42
53
 
43
54
  def test_01_putdir(self):
44
55
  l = os.path.join(self.LPUTDIR, 'dir')
@@ -56,7 +56,8 @@ class TestSSHConnection(unittest.TestCase):
56
56
  def test_connect_error_ip(self):
57
57
  conn = SSHConnection()
58
58
  with self.assertRaises(SSHConnectError) as cm:
59
- conn.connect('128.0.0.1', self.USER, self.PWD, self.PORT)
59
+ conn.connect('255.255.255.255',
60
+ self.USER, self.PWD, self.PORT)
60
61
  self.assertIn('please check whether the network is normal',
61
62
  str(cm.exception))
62
63
 
@@ -7,7 +7,7 @@ SFTP module
7
7
  import os
8
8
  import stat
9
9
 
10
- from typing import Generator
10
+ from typing import Iterator, cast
11
11
  from contextlib import contextmanager
12
12
 
13
13
  from paramiko import Transport, SFTPClient, SFTPFile
@@ -22,10 +22,14 @@ class SFTPConnection(object):
22
22
  """
23
23
  SFTP connection.
24
24
  """
25
- def __init__(self):
26
- self._sftpclient = None
25
+ def __init__(self) -> None:
26
+ self._sftpclient: SFTPClient | None = None
27
27
  self._logger = ExtraAdapter(logger, {})
28
28
 
29
+ @property
30
+ def _client(self) -> SFTPClient:
31
+ return cast(SFTPClient, self._sftpclient)
32
+
29
33
  def connect(
30
34
  self,
31
35
  host: str,
@@ -48,9 +52,14 @@ class SFTPConnection(object):
48
52
  """
49
53
  Close the connection.
50
54
  """
51
- self._sftpclient.close()
55
+ self._client.close()
52
56
 
53
- def getfile(self, rfile: str, ldir: str, filename: str = None) -> None:
57
+ def getfile(
58
+ self,
59
+ rfile: str,
60
+ ldir: str,
61
+ filename: str | None = None
62
+ ) -> None:
54
63
  """
55
64
  Get `rfile` from SFTP server into `ldir`.
56
65
 
@@ -66,9 +75,14 @@ class SFTPConnection(object):
66
75
  filename = filename or self.basename(rfile)
67
76
  lfile = os.path.join(ldir, filename)
68
77
  self._logger.info(f'Getting file {lfile} <= {rfile}')
69
- self._sftpclient.get(rfile, lfile)
78
+ self._client.get(rfile, lfile)
70
79
 
71
- def putfile(self, lfile: str, rdir: str, filename: str = None) -> None:
80
+ def putfile(
81
+ self,
82
+ lfile: str,
83
+ rdir: str,
84
+ filename: str | None = None
85
+ ) -> None:
72
86
  """
73
87
  Put `lfile` into the `rdir` of SFTP server.
74
88
 
@@ -84,7 +98,7 @@ class SFTPConnection(object):
84
98
  filename = filename or os.path.basename(lfile)
85
99
  rfile = self.join(rdir, filename)
86
100
  self._logger.info(f'Putting file {lfile} => {rfile}')
87
- self._sftpclient.put(lfile, rfile)
101
+ self._client.put(lfile, rfile)
88
102
 
89
103
  def getdir(self, rdir: str, ldir: str) -> None:
90
104
  """
@@ -107,7 +121,7 @@ class SFTPConnection(object):
107
121
  for f in files:
108
122
  r = self.join(top, f)
109
123
  l = os.path.join(ldir, f)
110
- self._sftpclient.get(r, l)
124
+ self._client.get(r, l)
111
125
  for d in dirs:
112
126
  l = os.path.join(ldir, d)
113
127
  if not os.path.exists(l):
@@ -134,7 +148,7 @@ class SFTPConnection(object):
134
148
  for f in files:
135
149
  l = os.path.join(top, f)
136
150
  r = self.join(rdir, f)
137
- self._sftpclient.put(l, r)
151
+ self._client.put(l, r)
138
152
  for d in dirs:
139
153
  r = self.join(rdir, d)
140
154
  if not self.exists(r):
@@ -144,8 +158,8 @@ class SFTPConnection(object):
144
158
  """
145
159
  Similar to os.path.join().
146
160
  """
147
- paths = [p.rstrip('/') for p in paths]
148
- return '/'.join(paths)
161
+ normalized_paths = [p.rstrip('/') for p in paths]
162
+ return '/'.join(normalized_paths)
149
163
 
150
164
  def normpath(self, path: str) -> str:
151
165
  """
@@ -161,22 +175,25 @@ class SFTPConnection(object):
161
175
  """
162
176
  return path.rsplit('/', 1)[-1]
163
177
 
164
- def exists(self, path: str) -> str:
178
+ def exists(self, path: str) -> bool:
165
179
  """
166
180
  Similar to os.path.exists().
167
181
  """
168
182
  try:
169
- self._sftpclient.stat(path)
183
+ self._client.stat(path)
170
184
  return True
171
185
  except FileNotFoundError:
172
186
  return False
173
187
 
174
- def walk(self, path: str):
188
+ def walk(
189
+ self,
190
+ path: str
191
+ ) -> Iterator[tuple[str, list[str], list[str]]]:
175
192
  """
176
193
  Similar to os.walk().
177
194
  """
178
195
  dirs, files = [], []
179
- for a in self._sftpclient.listdir_attr(path):
196
+ for a in self._client.listdir_attr(path):
180
197
  if stat.S_ISDIR(a.st_mode):
181
198
  dirs.append(a.filename)
182
199
  else:
@@ -187,7 +204,7 @@ class SFTPConnection(object):
187
204
  for w in self.walk(self.join(path, d)):
188
205
  yield w
189
206
 
190
- def makedirs(self, path: str) -> str:
207
+ def makedirs(self, path: str) -> None:
191
208
  """
192
209
  Similar to os.makedirs().
193
210
  """
@@ -196,15 +213,19 @@ class SFTPConnection(object):
196
213
  for p in path.split('/'):
197
214
  curpath = self.join(curpath, p)
198
215
  if not self.exists(curpath):
199
- self._sftpclient.mkdir(curpath)
216
+ self._client.mkdir(curpath)
200
217
 
201
218
  @contextmanager
202
- def open(self, filepath: str, mode: str = 'r') -> Generator[SFTPFile, str, None]:
219
+ def open(
220
+ self,
221
+ filepath: str,
222
+ mode: str = 'r'
223
+ ) -> Iterator[SFTPFile]:
203
224
  """
204
225
  Similar to builtin open().
205
226
  """
206
227
  self._logger.info('Open %s with mode=%s' % (filepath, mode))
207
- f = self._sftpclient.open(filepath, mode)
228
+ f = self._client.open(filepath, mode)
208
229
  try:
209
230
  yield f
210
231
  finally:
@@ -8,7 +8,7 @@ import textwrap
8
8
  import threading
9
9
  import socket
10
10
 
11
- from typing import Generator, Optional, Union
11
+ from typing import Any, Iterator, cast
12
12
  from datetime import datetime
13
13
  from select import select
14
14
  from contextlib import contextmanager
@@ -31,7 +31,12 @@ class SSHCommandResult(str):
31
31
  """
32
32
  Result of SSH command.
33
33
  """
34
- def __new__(cls, out: str, rc: int = 0, cmd: str = '') -> str:
34
+ def __new__(
35
+ cls: type['SSHCommandResult'],
36
+ out: str,
37
+ rc: int = 0,
38
+ cmd: str = ''
39
+ ) -> 'SSHCommandResult':
35
40
  """
36
41
  :param out: output.
37
42
  :param rc: return code.
@@ -40,7 +45,7 @@ class SSHCommandResult(str):
40
45
  out = remove_ansi_escape_chars(out)
41
46
  out = remove_unprintable_chars(out)
42
47
  out = '\n'.join(out.splitlines())
43
- o = str.__new__(cls, out.strip())
48
+ o = cast(SSHCommandResult, str.__new__(cls, out.strip()))
44
49
  o.__rc = rc
45
50
  o.__cmd = cmd
46
51
  return o
@@ -61,10 +66,10 @@ class SSHCommandResult(str):
61
66
 
62
67
  def getfield(
63
68
  self,
64
- key: str,
69
+ key: str | int,
65
70
  col: int,
66
- sep: str = None
67
- ) -> Optional[str]:
71
+ sep: str | None = None
72
+ ) -> str | None:
68
73
  """
69
74
  Get a specified field from the output.
70
75
 
@@ -98,8 +103,8 @@ class SSHCommandResult(str):
98
103
  def getcol(
99
104
  self,
100
105
  col: int,
101
- sep: str = None
102
- ) -> list:
106
+ sep: str | None = None
107
+ ) -> list[str]:
103
108
  """
104
109
  Get a specified column from the output.
105
110
 
@@ -127,7 +132,7 @@ class SSHConnection(object):
127
132
  """
128
133
  SSH connection.
129
134
  """
130
- def __init__(self, shenvs: dict = {}):
135
+ def __init__(self, shenvs: dict[str, str] = {}) -> None:
131
136
  """
132
137
  :param shenvs: shell environment variables for method `exec`.
133
138
  `LANG` defaults to `en_US.UTF-8`.
@@ -136,11 +141,11 @@ class SSHConnection(object):
136
141
  self._logger = ExtraAdapter(logger, {})
137
142
  self._sshclient = SSHClient()
138
143
  self._sshclient.set_missing_host_key_policy(AutoAddPolicy())
139
- self._shenvs = shenvs
144
+ self._shenvs: dict[str, str] = shenvs
140
145
  for k, v in {'LANG': 'en_US.UTF-8',
141
146
  'LANGUAGE': 'en_US.UTF-8'}.items():
142
147
  self._shenvs[k] = self._shenvs.get(k, v)
143
- self._password = None
148
+ self._password: str | None = None
144
149
  self._cdlock = threading.Lock()
145
150
  self._cwd = ''
146
151
 
@@ -175,16 +180,16 @@ class SSHConnection(object):
175
180
  f'Authentication failed when SSH connect to {host} with user `{user}`, '
176
181
  f'please check whether the username and password are correct.'
177
182
  ) from None
178
- except socket.timeout:
179
- raise SSHConnectError(
180
- f'Timed out when SSH connect to {host}({timeout}s), '
181
- 'please check whether the network is normal.'
182
- ) from None
183
183
  except NoValidConnectionsError:
184
184
  raise SSHConnectError(
185
185
  f'Could not connect to port {port} on {host}, '
186
186
  'please check whether the port is opened.'
187
187
  ) from None
188
+ except OSError:
189
+ raise SSHConnectError(
190
+ f'Could not connect to {host}, '
191
+ 'please check whether the network is normal.'
192
+ ) from None
188
193
  except SSHException as e:
189
194
  msg = str(e)
190
195
  if 'Error reading SSH protocol banner' in msg:
@@ -203,10 +208,10 @@ class SSHConnection(object):
203
208
  def exec(
204
209
  self,
205
210
  cmd: str,
206
- expect: Union[int, str, None] = 0,
211
+ expect: int | str | None = 0,
207
212
  timeout: int = 15,
208
- prompts: dict = {},
209
- shenvs: dict = {}
213
+ prompts: dict[str, str] = {},
214
+ shenvs: dict[str, str] = {}
210
215
  ) -> SSHCommandResult:
211
216
  """
212
217
  Execute a command on the SSH server.
@@ -281,15 +286,22 @@ class SSHConnection(object):
281
286
  raise SSHCommandError(msg)
282
287
  return result
283
288
 
284
- def sudo(self, cmd, *args, **kwargs) -> SSHCommandResult:
289
+ def sudo(
290
+ self,
291
+ cmd: str,
292
+ *args: Any,
293
+ **kwargs: Any
294
+ ) -> SSHCommandResult:
285
295
  """
286
296
  Execute a command with sudo, arguments are same to `exec`.
287
297
  """
288
- kwargs['prompts'] = {'[sudo] password': self._password}
298
+ kwargs['prompts'] = {
299
+ '[sudo] password': cast(str, self._password)
300
+ }
289
301
  return self.exec(f'sudo {cmd}', *args, **kwargs)
290
302
 
291
303
  @contextmanager
292
- def cd(self, path) -> Generator[None, str, None]:
304
+ def cd(self, path: str) -> Iterator[None]:
293
305
  """
294
306
  change current directory.
295
307
 
@@ -306,4 +318,3 @@ class SSHConnection(object):
306
318
  finally:
307
319
  self._cwd = ''
308
320
  self._cdlock.release()
309
-
@@ -0,0 +1,3 @@
1
+ """Package version."""
2
+
3
+ __version__: str = "0.2.1"
@@ -1,27 +1,24 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: xbot.plugins.ssh
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: SSH library for xbot.framework
5
- Home-page: https://github.com/zhaowcheng/xbot.plugins.ssh
6
- Author: zhaowcheng
7
- Author-email: zhaowcheng@163.com
8
- License: UNKNOWN
5
+ Author-email: zhaowcheng <zhaowcheng@163.com>
6
+ License-Expression: BSD-2-Clause
9
7
  Project-URL: Homepage, https://github.com/zhaowcheng/xbot.plugins.ssh
10
8
  Project-URL: Issues, https://github.com/zhaowcheng/xbot.plugins.ssh/issues
11
- Platform: UNKNOWN
12
- Classifier: License :: OSI Approved :: BSD License
13
9
  Classifier: Operating System :: OS Independent
14
10
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.6
16
- Classifier: Programming Language :: Python :: 3.7
17
- Classifier: Programming Language :: Python :: 3.8
18
- Classifier: Programming Language :: Python :: 3.9
19
11
  Classifier: Programming Language :: Python :: 3.10
20
12
  Classifier: Programming Language :: Python :: 3.11
21
13
  Classifier: Programming Language :: Python :: 3.12
22
- Requires-Python: >=3.6
14
+ Requires-Python: >=3.10
23
15
  Description-Content-Type: text/markdown
24
16
  License-File: LICENSE
17
+ Requires-Dist: xbot.framework>=0.5.0
18
+ Requires-Dist: paramiko
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: license-file
25
22
 
26
23
  ## Introduction
27
24
 
@@ -144,7 +141,7 @@ with sftpconn.open(p, 'r') as fp:
144
141
 
145
142
  ## Demo Project
146
143
 
147
- Here is a [demo](https://github.com/zhaowcheng/xbot.plugins.ssh/tree/v0.1.0/demo) project showing how to use `xbot.plugins.ssh` in a test project.
144
+ Here is a [demo](https://github.com/zhaowcheng/xbot.plugins.ssh/tree/v/demo) project showing how to use `xbot.plugins.ssh` in a test project.
148
145
 
149
146
  ***
150
147
 
@@ -269,6 +266,4 @@ with sftpconn.open(p, 'r') as fp:
269
266
 
270
267
  ## 示例项目
271
268
 
272
- 展示如何在一个测试项目中使用本插件的示例:[demo](https://github.com/zhaowcheng/xbot.plugins.ssh/tree/v0.1.0/demo)
273
-
274
-
269
+ 展示如何在一个测试项目中使用本插件的示例:[demo](https://github.com/zhaowcheng/xbot.plugins.ssh/tree/v/demo)
@@ -0,0 +1,20 @@
1
+ LICENSE
2
+ README.md
3
+ README.zh.md
4
+ pyproject.toml
5
+ setup.py
6
+ tests/test_sftp.py
7
+ tests/test_ssh.py
8
+ tests/test_utils.py
9
+ xbot.plugins.ssh.egg-info/PKG-INFO
10
+ xbot.plugins.ssh.egg-info/SOURCES.txt
11
+ xbot.plugins.ssh.egg-info/dependency_links.txt
12
+ xbot.plugins.ssh.egg-info/requires.txt
13
+ xbot.plugins.ssh.egg-info/top_level.txt
14
+ xbot/plugins/ssh/__init__.py
15
+ xbot/plugins/ssh/errors.py
16
+ xbot/plugins/ssh/py.typed
17
+ xbot/plugins/ssh/sftp.py
18
+ xbot/plugins/ssh/ssh.py
19
+ xbot/plugins/ssh/utils.py
20
+ xbot/plugins/ssh/version.py
@@ -0,0 +1,2 @@
1
+ xbot.framework>=0.5.0
2
+ paramiko
@@ -1,2 +0,0 @@
1
- include requirements.txt
2
- include README*
@@ -1,48 +0,0 @@
1
- from typing import Union
2
-
3
- from xbot.framework import testbed
4
- from xbot.plugins.ssh.ssh import SSHConnection
5
- from xbot.plugins.ssh.sftp import SFTPConnection
6
-
7
-
8
- class TestBed(testbed.TestBed):
9
- """
10
- TestBed for ssh demo.
11
- """
12
- def __init__(self, filepath: str):
13
- """
14
- :param filepath: testbed filepath.
15
- """
16
- super().__init__(filepath)
17
- self._conns = {
18
- 'ssh': {},
19
- 'sftp': {}
20
- }
21
-
22
- def get_conn(
23
- self,
24
- typ: str,
25
- role: str
26
- ) -> Union[SSHConnection, SFTPConnection]:
27
- """
28
- Get specific connection to the specified role.
29
-
30
- :param typ: connection type, 'ssh' or 'sftp'.
31
- :param role: user role.
32
- """
33
- typs = ('ssh', 'sftp')
34
- if typ not in typs:
35
- raise ValueError(f'Invalid connection type: {typ}, should be one of {typs}.')
36
- for user in self.get('host.users'):
37
- if user['role'] == role:
38
- if role not in self._conns[typ]:
39
- conn = SSHConnection() if typ == 'ssh' else SFTPConnection()
40
- conn.connect(self.get('host.ip'),
41
- user['name'],
42
- user['password'],
43
- port=self.get('host.sshport'))
44
- self._conns[typ][role] = conn
45
- return self._conns[typ][role]
46
- raise ValueError(f'No such user: role={role}')
47
-
48
-
@@ -1,15 +0,0 @@
1
- from xbot.framework import testcase
2
-
3
- from .testbed import TestBed
4
-
5
-
6
- class TestCase(testcase.TestCase):
7
- """
8
- TestCase for ssh demo.
9
- """
10
- @property
11
- def testbed(self) -> TestBed:
12
- """
13
- TestBed instance.
14
- """
15
- return self.__testbed
File without changes