xbot.plugins.ssh 0.1.0__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.
- demo/lib/__init__.py +0 -0
- demo/lib/testbed.py +48 -0
- demo/lib/testcase.py +15 -0
- demo/testcases/__init__.py +0 -0
- demo/testcases/tc_sftp.py +99 -0
- demo/testcases/tc_ssh.py +73 -0
- tests/__init__.py +0 -0
- tests/run.py +39 -0
- tests/test_sftp.py +88 -0
- tests/test_ssh.py +99 -0
- tests/test_utils.py +18 -0
- venv/bin/jp.py +54 -0
- venv/bin/rst2html.py +23 -0
- venv/bin/rst2html4.py +26 -0
- venv/bin/rst2html5.py +34 -0
- venv/bin/rst2latex.py +26 -0
- venv/bin/rst2man.py +26 -0
- venv/bin/rst2odt.py +30 -0
- venv/bin/rst2odt_prepstyles.py +67 -0
- venv/bin/rst2pseudoxml.py +23 -0
- venv/bin/rst2s5.py +24 -0
- venv/bin/rst2xetex.py +27 -0
- venv/bin/rst2xml.py +23 -0
- venv/bin/rstpep2html.py +25 -0
- xbot/plugins/ssh/__init__.py +0 -0
- xbot/plugins/ssh/errors.py +19 -0
- xbot/plugins/ssh/sftp.py +211 -0
- xbot/plugins/ssh/ssh.py +309 -0
- xbot/plugins/ssh/utils.py +30 -0
- xbot/plugins/ssh/version.py +3 -0
- xbot.plugins.ssh-0.1.0.dist-info/LICENSE +24 -0
- xbot.plugins.ssh-0.1.0.dist-info/METADATA +276 -0
- xbot.plugins.ssh-0.1.0.dist-info/RECORD +35 -0
- xbot.plugins.ssh-0.1.0.dist-info/WHEEL +5 -0
- xbot.plugins.ssh-0.1.0.dist-info/top_level.txt +4 -0
venv/bin/rst2odt.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# $Id: rst2odt.py 5839 2009-01-07 19:09:28Z dkuhlman $
|
|
4
|
+
# Author: Dave Kuhlman <dkuhlman@rexx.com>
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A front end to the Docutils Publisher, producing OpenOffice documents.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
try:
|
|
13
|
+
import locale
|
|
14
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
15
|
+
except:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
from docutils.core import publish_cmdline_to_binary, default_description
|
|
19
|
+
from docutils.writers.odf_odt import Writer, Reader
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
description = ('Generates OpenDocument/OpenOffice/ODF documents from '
|
|
23
|
+
'standalone reStructuredText sources. ' + default_description)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
writer = Writer()
|
|
27
|
+
reader = Reader()
|
|
28
|
+
output = publish_cmdline_to_binary(reader=reader, writer=writer,
|
|
29
|
+
description=description)
|
|
30
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# $Id: rst2odt_prepstyles.py 8346 2019-08-26 12:11:32Z milde $
|
|
4
|
+
# Author: Dave Kuhlman <dkuhlman@rexx.com>
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
Fix a word-processor-generated styles.odt for odtwriter use: Drop page size
|
|
9
|
+
specifications from styles.xml in STYLE_FILE.odt.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# Author: Michael Schutte <michi@uiae.at>
|
|
13
|
+
|
|
14
|
+
from __future__ import print_function
|
|
15
|
+
|
|
16
|
+
from lxml import etree
|
|
17
|
+
import sys
|
|
18
|
+
import zipfile
|
|
19
|
+
from tempfile import mkstemp
|
|
20
|
+
import shutil
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
NAMESPACES = {
|
|
24
|
+
"style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
|
|
25
|
+
"fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def prepstyle(filename):
|
|
30
|
+
|
|
31
|
+
zin = zipfile.ZipFile(filename)
|
|
32
|
+
styles = zin.read("styles.xml")
|
|
33
|
+
|
|
34
|
+
root = etree.fromstring(styles)
|
|
35
|
+
for el in root.xpath("//style:page-layout-properties",
|
|
36
|
+
namespaces=NAMESPACES):
|
|
37
|
+
for attr in el.attrib:
|
|
38
|
+
if attr.startswith("{%s}" % NAMESPACES["fo"]):
|
|
39
|
+
del el.attrib[attr]
|
|
40
|
+
|
|
41
|
+
tempname = mkstemp()
|
|
42
|
+
zout = zipfile.ZipFile(os.fdopen(tempname[0], "w"), "w",
|
|
43
|
+
zipfile.ZIP_DEFLATED)
|
|
44
|
+
|
|
45
|
+
for item in zin.infolist():
|
|
46
|
+
if item.filename == "styles.xml":
|
|
47
|
+
zout.writestr(item, etree.tostring(root))
|
|
48
|
+
else:
|
|
49
|
+
zout.writestr(item, zin.read(item.filename))
|
|
50
|
+
|
|
51
|
+
zout.close()
|
|
52
|
+
zin.close()
|
|
53
|
+
shutil.move(tempname[1], filename)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main():
|
|
57
|
+
args = sys.argv[1:]
|
|
58
|
+
if len(args) != 1:
|
|
59
|
+
print(__doc__, file=sys.stderr)
|
|
60
|
+
print("Usage: %s STYLE_FILE.odt\n" % sys.argv[0], file=sys.stderr)
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
filename = args[0]
|
|
63
|
+
prepstyle(filename)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == '__main__':
|
|
67
|
+
main()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# $Id: rst2pseudoxml.py 4564 2006-05-21 20:44:42Z wiemann $
|
|
4
|
+
# Author: David Goodger <goodger@python.org>
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A minimal front end to the Docutils Publisher, producing pseudo-XML.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import locale
|
|
13
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
14
|
+
except:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
from docutils.core import publish_cmdline, default_description
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
description = ('Generates pseudo-XML from standalone reStructuredText '
|
|
21
|
+
'sources (for testing purposes). ' + default_description)
|
|
22
|
+
|
|
23
|
+
publish_cmdline(description=description)
|
venv/bin/rst2s5.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# $Id: rst2s5.py 4564 2006-05-21 20:44:42Z wiemann $
|
|
4
|
+
# Author: Chris Liechti <cliechti@gmx.net>
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A minimal front end to the Docutils Publisher, producing HTML slides using
|
|
9
|
+
the S5 template system.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import locale
|
|
14
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
15
|
+
except:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
from docutils.core import publish_cmdline, default_description
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
description = ('Generates S5 (X)HTML slideshow documents from standalone '
|
|
22
|
+
'reStructuredText sources. ' + default_description)
|
|
23
|
+
|
|
24
|
+
publish_cmdline(writer_name='s5', description=description)
|
venv/bin/rst2xetex.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# $Id: rst2xetex.py 7847 2015-03-17 17:30:47Z milde $
|
|
4
|
+
# Author: Guenter Milde
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A minimal front end to the Docutils Publisher, producing Lua/XeLaTeX code.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import locale
|
|
13
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
14
|
+
except:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
from docutils.core import publish_cmdline
|
|
18
|
+
|
|
19
|
+
description = ('Generates LaTeX documents from standalone reStructuredText '
|
|
20
|
+
'sources for compilation with the Unicode-aware TeX variants '
|
|
21
|
+
'XeLaTeX or LuaLaTeX. '
|
|
22
|
+
'Reads from <source> (default is stdin) and writes to '
|
|
23
|
+
'<destination> (default is stdout). See '
|
|
24
|
+
'<http://docutils.sourceforge.net/docs/user/latex.html> for '
|
|
25
|
+
'the full reference.')
|
|
26
|
+
|
|
27
|
+
publish_cmdline(writer_name='xetex', description=description)
|
venv/bin/rst2xml.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# $Id: rst2xml.py 4564 2006-05-21 20:44:42Z wiemann $
|
|
4
|
+
# Author: David Goodger <goodger@python.org>
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A minimal front end to the Docutils Publisher, producing Docutils XML.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import locale
|
|
13
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
14
|
+
except:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
from docutils.core import publish_cmdline, default_description
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
description = ('Generates Docutils-native XML from standalone '
|
|
21
|
+
'reStructuredText sources. ' + default_description)
|
|
22
|
+
|
|
23
|
+
publish_cmdline(writer_name='xml', description=description)
|
venv/bin/rstpep2html.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/Users/wan/CodeProjects/xbot.plugins.ssh/venv/bin/python3.6
|
|
2
|
+
|
|
3
|
+
# $Id: rstpep2html.py 4564 2006-05-21 20:44:42Z wiemann $
|
|
4
|
+
# Author: David Goodger <goodger@python.org>
|
|
5
|
+
# Copyright: This module has been placed in the public domain.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
A minimal front end to the Docutils Publisher, producing HTML from PEP
|
|
9
|
+
(Python Enhancement Proposal) documents.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import locale
|
|
14
|
+
locale.setlocale(locale.LC_ALL, '')
|
|
15
|
+
except:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
from docutils.core import publish_cmdline, default_description
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
description = ('Generates (X)HTML from reStructuredText-format PEP files. '
|
|
22
|
+
+ default_description)
|
|
23
|
+
|
|
24
|
+
publish_cmdline(reader_name='pep', writer_name='pep_html',
|
|
25
|
+
description=description)
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright (c) 2023-2024, zhaowcheng <zhaowcheng@163.com>
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Exceptions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SSHConnectError(Exception):
|
|
9
|
+
"""
|
|
10
|
+
SSH connection error.
|
|
11
|
+
"""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SSHCommandError(Exception):
|
|
16
|
+
"""
|
|
17
|
+
SSH command error.
|
|
18
|
+
"""
|
|
19
|
+
pass
|
xbot/plugins/ssh/sftp.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Copyright (c) 2023-2024, zhaowcheng <zhaowcheng@163.com>
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
SFTP module
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import stat
|
|
9
|
+
|
|
10
|
+
from typing import Generator
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
|
|
13
|
+
from paramiko import Transport, SFTPClient, SFTPFile
|
|
14
|
+
|
|
15
|
+
from xbot.framework.logger import getlogger, ExtraAdapter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = getlogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SFTPConnection(object):
|
|
22
|
+
"""
|
|
23
|
+
SFTP connection.
|
|
24
|
+
"""
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self._sftpclient = None
|
|
27
|
+
self._logger = ExtraAdapter(logger, {})
|
|
28
|
+
|
|
29
|
+
def connect(
|
|
30
|
+
self,
|
|
31
|
+
host: str,
|
|
32
|
+
user: str,
|
|
33
|
+
password: str,
|
|
34
|
+
port: int = 22
|
|
35
|
+
) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Open the connection.
|
|
38
|
+
"""
|
|
39
|
+
if self._sftpclient and self._sftpclient.sock.active:
|
|
40
|
+
return
|
|
41
|
+
self._logger.extra['prefix'] = f'sftp://{user}@{host}:{port}'
|
|
42
|
+
self._logger.info('Connecting...')
|
|
43
|
+
t = Transport((host, port))
|
|
44
|
+
t.connect(username=user, password=password)
|
|
45
|
+
self._sftpclient = SFTPClient.from_transport(t)
|
|
46
|
+
|
|
47
|
+
def disconnect(self) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Close the connection.
|
|
50
|
+
"""
|
|
51
|
+
self._sftpclient.close()
|
|
52
|
+
|
|
53
|
+
def getfile(self, rfile: str, ldir: str, filename: str = None) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Get `rfile` from SFTP server into `ldir`.
|
|
56
|
+
|
|
57
|
+
:param rfile: remote file.
|
|
58
|
+
:param ldir: local dir.
|
|
59
|
+
:param filename: specify when you want to rename.
|
|
60
|
+
|
|
61
|
+
>>> getfile('/tmp/myfile', '/home') # /home/myfile
|
|
62
|
+
>>> getfile('/tmp/myfile', 'D:\\') # D:\\myfile
|
|
63
|
+
>>> getfile('/tmp/myfile', '/home', 'newfile') # /home/newfile
|
|
64
|
+
"""
|
|
65
|
+
ldir = os.path.join(ldir, '')
|
|
66
|
+
filename = filename or self.basename(rfile)
|
|
67
|
+
lfile = os.path.join(ldir, filename)
|
|
68
|
+
self._logger.info(f'Getting file {lfile} <= {rfile}')
|
|
69
|
+
self._sftpclient.get(rfile, lfile)
|
|
70
|
+
|
|
71
|
+
def putfile(self, lfile: str, rdir: str, filename: str = None) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Put `lfile` into the `rdir` of SFTP server.
|
|
74
|
+
|
|
75
|
+
:param lfile: local file.
|
|
76
|
+
:param rdir: remote dir.
|
|
77
|
+
:param filename: specify when you want to rename.
|
|
78
|
+
|
|
79
|
+
>>> putfile('/home/myfile', '/tmp') # /tmp/myfile
|
|
80
|
+
>>> putfile('D:\\myfile', '/tmp') # /tmp/myfile
|
|
81
|
+
>>> putfile('/home/myfile', '/tmp', 'newfile') # /tmp/newfile
|
|
82
|
+
"""
|
|
83
|
+
rdir = self.join(rdir, '')
|
|
84
|
+
filename = filename or os.path.basename(lfile)
|
|
85
|
+
rfile = self.join(rdir, filename)
|
|
86
|
+
self._logger.info(f'Putting file {lfile} => {rfile}')
|
|
87
|
+
self._sftpclient.put(lfile, rfile)
|
|
88
|
+
|
|
89
|
+
def getdir(self, rdir: str, ldir: str) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Get `rdir` from SFTP server into `ldir`.
|
|
92
|
+
|
|
93
|
+
:param rdir: remote dir.
|
|
94
|
+
:param ldir: local dir.
|
|
95
|
+
|
|
96
|
+
>>> getdir('/tmp/mydir', '/home') # /home/mydir
|
|
97
|
+
>>> getdir('/tmp/mydir', 'D:\\') # D:\\mydir
|
|
98
|
+
"""
|
|
99
|
+
rdir = self.normpath(rdir)
|
|
100
|
+
ldir = os.path.join(ldir, '')
|
|
101
|
+
self._logger.info(f'Getting dir {ldir} <= {rdir}')
|
|
102
|
+
for top, dirs, files in self.walk(rdir):
|
|
103
|
+
basename = self.basename(top)
|
|
104
|
+
ldir = os.path.join(ldir, basename)
|
|
105
|
+
if not os.path.exists(ldir):
|
|
106
|
+
os.makedirs(ldir)
|
|
107
|
+
for f in files:
|
|
108
|
+
r = self.join(top, f)
|
|
109
|
+
l = os.path.join(ldir, f)
|
|
110
|
+
self._sftpclient.get(r, l)
|
|
111
|
+
for d in dirs:
|
|
112
|
+
l = os.path.join(ldir, d)
|
|
113
|
+
if not os.path.exists(l):
|
|
114
|
+
os.makedirs(l)
|
|
115
|
+
|
|
116
|
+
def putdir(self, ldir: str, rdir: str) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Put `ldir` into the `rdir` of SFTP server.
|
|
119
|
+
|
|
120
|
+
:param ldir: local dir.
|
|
121
|
+
:param rdir: remote dir.
|
|
122
|
+
|
|
123
|
+
>>> putdir('/tmp/mydir', '/home') # /home/mydir
|
|
124
|
+
>>> putdir('D:\\mydir', '/home') # /home/mydir
|
|
125
|
+
"""
|
|
126
|
+
ldir = os.path.normpath(ldir)
|
|
127
|
+
rdir = self.join(rdir, '')
|
|
128
|
+
self._logger.info(f'Putting dir {ldir} => {rdir}')
|
|
129
|
+
for top, dirs, files in os.walk(os.path.normpath(ldir)):
|
|
130
|
+
basename = os.path.basename(top)
|
|
131
|
+
rdir = self.join(rdir, basename)
|
|
132
|
+
if not self.exists(rdir):
|
|
133
|
+
self.makedirs(rdir)
|
|
134
|
+
for f in files:
|
|
135
|
+
l = os.path.join(top, f)
|
|
136
|
+
r = self.join(rdir, f)
|
|
137
|
+
self._sftpclient.put(l, r)
|
|
138
|
+
for d in dirs:
|
|
139
|
+
r = self.join(rdir, d)
|
|
140
|
+
if not self.exists(r):
|
|
141
|
+
self.makedirs(r)
|
|
142
|
+
|
|
143
|
+
def join(self, *paths: str) -> str:
|
|
144
|
+
"""
|
|
145
|
+
Similar to os.path.join().
|
|
146
|
+
"""
|
|
147
|
+
paths = [p.rstrip('/') for p in paths]
|
|
148
|
+
return '/'.join(paths)
|
|
149
|
+
|
|
150
|
+
def normpath(self, path: str) -> str:
|
|
151
|
+
"""
|
|
152
|
+
Similar to os.path.normpath().
|
|
153
|
+
"""
|
|
154
|
+
segs = [s.strip('/') for s in path.split('/')]
|
|
155
|
+
path = self.join(*segs)
|
|
156
|
+
return path.rstrip('/')
|
|
157
|
+
|
|
158
|
+
def basename(self, path: str) -> str:
|
|
159
|
+
"""
|
|
160
|
+
Similar to os.path.basename().
|
|
161
|
+
"""
|
|
162
|
+
return path.rsplit('/', 1)[-1]
|
|
163
|
+
|
|
164
|
+
def exists(self, path: str) -> str:
|
|
165
|
+
"""
|
|
166
|
+
Similar to os.path.exists().
|
|
167
|
+
"""
|
|
168
|
+
try:
|
|
169
|
+
self._sftpclient.stat(path)
|
|
170
|
+
return True
|
|
171
|
+
except FileNotFoundError:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
def walk(self, path: str):
|
|
175
|
+
"""
|
|
176
|
+
Similar to os.walk().
|
|
177
|
+
"""
|
|
178
|
+
dirs, files = [], []
|
|
179
|
+
for a in self._sftpclient.listdir_attr(path):
|
|
180
|
+
if stat.S_ISDIR(a.st_mode):
|
|
181
|
+
dirs.append(a.filename)
|
|
182
|
+
else:
|
|
183
|
+
files.append(a.filename)
|
|
184
|
+
yield path, dirs, files
|
|
185
|
+
|
|
186
|
+
for d in dirs:
|
|
187
|
+
for w in self.walk(self.join(path, d)):
|
|
188
|
+
yield w
|
|
189
|
+
|
|
190
|
+
def makedirs(self, path: str) -> str:
|
|
191
|
+
"""
|
|
192
|
+
Similar to os.makedirs().
|
|
193
|
+
"""
|
|
194
|
+
self._logger.info('Makedirs %s' % path)
|
|
195
|
+
curpath = '/'
|
|
196
|
+
for p in path.split('/'):
|
|
197
|
+
curpath = self.join(curpath, p)
|
|
198
|
+
if not self.exists(curpath):
|
|
199
|
+
self._sftpclient.mkdir(curpath)
|
|
200
|
+
|
|
201
|
+
@contextmanager
|
|
202
|
+
def open(self, filepath: str, mode: str = 'r') -> Generator[SFTPFile, str, None]:
|
|
203
|
+
"""
|
|
204
|
+
Similar to builtin open().
|
|
205
|
+
"""
|
|
206
|
+
self._logger.info('Open %s with mode=%s' % (filepath, mode))
|
|
207
|
+
f = self._sftpclient.open(filepath, mode)
|
|
208
|
+
try:
|
|
209
|
+
yield f
|
|
210
|
+
finally:
|
|
211
|
+
f.close()
|