|
16 | 16 | # KIND, either express or implied. See the License for the
|
17 | 17 | # specific language governing permissions and limitations
|
18 | 18 | # under the License.
|
| 19 | +""" |
| 20 | +This module is deprecated. Please use `airflow.providers.sftp.hooks.sftp_hook`. |
| 21 | +""" |
19 | 22 |
|
20 |
| -import datetime |
21 |
| -import stat |
22 |
| -from typing import Dict, List, Optional, Tuple |
| 23 | +import warnings |
23 | 24 |
|
24 |
| -import pysftp |
| 25 | +# pylint: disable=unused-import |
| 26 | +from airflow.providers.sftp.hooks.sftp_hook import SFTPHook # noqa |
25 | 27 |
|
26 |
| -from airflow.contrib.hooks.ssh_hook import SSHHook |
27 |
| - |
28 |
| - |
29 |
| -class SFTPHook(SSHHook): |
30 |
| - """ |
31 |
| - This hook is inherited from SSH hook. Please refer to SSH hook for the input |
32 |
| - arguments. |
33 |
| -
|
34 |
| - Interact with SFTP. Aims to be interchangeable with FTPHook. |
35 |
| -
|
36 |
| - :Pitfalls:: |
37 |
| -
|
38 |
| - - In contrast with FTPHook describe_directory only returns size, type and |
39 |
| - modify. It doesn't return unix.owner, unix.mode, perm, unix.group and |
40 |
| - unique. |
41 |
| - - retrieve_file and store_file only take a local full path and not a |
42 |
| - buffer. |
43 |
| - - If no mode is passed to create_directory it will be created with 777 |
44 |
| - permissions. |
45 |
| -
|
46 |
| - Errors that may occur throughout but should be handled downstream. |
47 |
| - """ |
48 |
| - |
49 |
| - def __init__(self, ftp_conn_id: str = 'sftp_default', *args, **kwargs) -> None: |
50 |
| - kwargs['ssh_conn_id'] = ftp_conn_id |
51 |
| - super().__init__(*args, **kwargs) |
52 |
| - |
53 |
| - self.conn = None |
54 |
| - self.private_key_pass = None |
55 |
| - |
56 |
| - # Fail for unverified hosts, unless this is explicitly allowed |
57 |
| - self.no_host_key_check = False |
58 |
| - |
59 |
| - if self.ssh_conn_id is not None: |
60 |
| - conn = self.get_connection(self.ssh_conn_id) |
61 |
| - if conn.extra is not None: |
62 |
| - extra_options = conn.extra_dejson |
63 |
| - if 'private_key_pass' in extra_options: |
64 |
| - self.private_key_pass = extra_options.get('private_key_pass', None) |
65 |
| - |
66 |
| - # For backward compatibility |
67 |
| - # TODO: remove in Airflow 2.1 |
68 |
| - import warnings |
69 |
| - if 'ignore_hostkey_verification' in extra_options: |
70 |
| - warnings.warn( |
71 |
| - 'Extra option `ignore_hostkey_verification` is deprecated.' |
72 |
| - 'Please use `no_host_key_check` instead.' |
73 |
| - 'This option will be removed in Airflow 2.1', |
74 |
| - DeprecationWarning, |
75 |
| - stacklevel=2, |
76 |
| - ) |
77 |
| - self.no_host_key_check = str( |
78 |
| - extra_options['ignore_hostkey_verification'] |
79 |
| - ).lower() == 'true' |
80 |
| - |
81 |
| - if 'no_host_key_check' in extra_options: |
82 |
| - self.no_host_key_check = str( |
83 |
| - extra_options['no_host_key_check']).lower() == 'true' |
84 |
| - |
85 |
| - if 'private_key' in extra_options: |
86 |
| - warnings.warn( |
87 |
| - 'Extra option `private_key` is deprecated.' |
88 |
| - 'Please use `key_file` instead.' |
89 |
| - 'This option will be removed in Airflow 2.1', |
90 |
| - DeprecationWarning, |
91 |
| - stacklevel=2, |
92 |
| - ) |
93 |
| - self.key_file = extra_options.get('private_key') |
94 |
| - |
95 |
| - def get_conn(self) -> pysftp.Connection: |
96 |
| - """ |
97 |
| - Returns an SFTP connection object |
98 |
| - """ |
99 |
| - if self.conn is None: |
100 |
| - cnopts = pysftp.CnOpts() |
101 |
| - if self.no_host_key_check: |
102 |
| - cnopts.hostkeys = None |
103 |
| - cnopts.compression = self.compress |
104 |
| - conn_params = { |
105 |
| - 'host': self.remote_host, |
106 |
| - 'port': self.port, |
107 |
| - 'username': self.username, |
108 |
| - 'cnopts': cnopts |
109 |
| - } |
110 |
| - if self.password and self.password.strip(): |
111 |
| - conn_params['password'] = self.password |
112 |
| - if self.key_file: |
113 |
| - conn_params['private_key'] = self.key_file |
114 |
| - if self.private_key_pass: |
115 |
| - conn_params['private_key_pass'] = self.private_key_pass |
116 |
| - |
117 |
| - self.conn = pysftp.Connection(**conn_params) |
118 |
| - return self.conn |
119 |
| - |
120 |
| - def close_conn(self) -> None: |
121 |
| - """ |
122 |
| - Closes the connection. An error will occur if the |
123 |
| - connection wasnt ever opened. |
124 |
| - """ |
125 |
| - conn = self.conn |
126 |
| - conn.close() # type: ignore |
127 |
| - self.conn = None |
128 |
| - |
129 |
| - def describe_directory(self, path: str) -> Dict[str, Dict[str, str]]: |
130 |
| - """ |
131 |
| - Returns a dictionary of {filename: {attributes}} for all files |
132 |
| - on the remote system (where the MLSD command is supported). |
133 |
| -
|
134 |
| - :param path: full path to the remote directory |
135 |
| - :type path: str |
136 |
| - """ |
137 |
| - conn = self.get_conn() |
138 |
| - flist = conn.listdir_attr(path) |
139 |
| - files = {} |
140 |
| - for f in flist: |
141 |
| - modify = datetime.datetime.fromtimestamp( |
142 |
| - f.st_mtime).strftime('%Y%m%d%H%M%S') |
143 |
| - files[f.filename] = { |
144 |
| - 'size': f.st_size, |
145 |
| - 'type': 'dir' if stat.S_ISDIR(f.st_mode) else 'file', |
146 |
| - 'modify': modify} |
147 |
| - return files |
148 |
| - |
149 |
| - def list_directory(self, path: str) -> List[str]: |
150 |
| - """ |
151 |
| - Returns a list of files on the remote system. |
152 |
| -
|
153 |
| - :param path: full path to the remote directory to list |
154 |
| - :type path: str |
155 |
| - """ |
156 |
| - conn = self.get_conn() |
157 |
| - files = conn.listdir(path) |
158 |
| - return files |
159 |
| - |
160 |
| - def create_directory(self, path: str, mode: int = 777) -> None: |
161 |
| - """ |
162 |
| - Creates a directory on the remote system. |
163 |
| -
|
164 |
| - :param path: full path to the remote directory to create |
165 |
| - :type path: str |
166 |
| - :param mode: int representation of octal mode for directory |
167 |
| - """ |
168 |
| - conn = self.get_conn() |
169 |
| - conn.makedirs(path, mode) |
170 |
| - |
171 |
| - def delete_directory(self, path: str) -> None: |
172 |
| - """ |
173 |
| - Deletes a directory on the remote system. |
174 |
| -
|
175 |
| - :param path: full path to the remote directory to delete |
176 |
| - :type path: str |
177 |
| - """ |
178 |
| - conn = self.get_conn() |
179 |
| - conn.rmdir(path) |
180 |
| - |
181 |
| - def retrieve_file(self, remote_full_path: str, local_full_path: str) -> None: |
182 |
| - """ |
183 |
| - Transfers the remote file to a local location. |
184 |
| - If local_full_path is a string path, the file will be put |
185 |
| - at that location |
186 |
| -
|
187 |
| - :param remote_full_path: full path to the remote file |
188 |
| - :type remote_full_path: str |
189 |
| - :param local_full_path: full path to the local file |
190 |
| - :type local_full_path: str |
191 |
| - """ |
192 |
| - conn = self.get_conn() |
193 |
| - self.log.info('Retrieving file from FTP: %s', remote_full_path) |
194 |
| - conn.get(remote_full_path, local_full_path) |
195 |
| - self.log.info('Finished retrieving file from FTP: %s', remote_full_path) |
196 |
| - |
197 |
| - def store_file(self, remote_full_path: str, local_full_path: str) -> None: |
198 |
| - """ |
199 |
| - Transfers a local file to the remote location. |
200 |
| - If local_full_path_or_buffer is a string path, the file will be read |
201 |
| - from that location |
202 |
| -
|
203 |
| - :param remote_full_path: full path to the remote file |
204 |
| - :type remote_full_path: str |
205 |
| - :param local_full_path: full path to the local file |
206 |
| - :type local_full_path: str |
207 |
| - """ |
208 |
| - conn = self.get_conn() |
209 |
| - conn.put(local_full_path, remote_full_path) |
210 |
| - |
211 |
| - def delete_file(self, path: str) -> None: |
212 |
| - """ |
213 |
| - Removes a file on the FTP Server |
214 |
| -
|
215 |
| - :param path: full path to the remote file |
216 |
| - :type path: str |
217 |
| - """ |
218 |
| - conn = self.get_conn() |
219 |
| - conn.remove(path) |
220 |
| - |
221 |
| - def get_mod_time(self, path: str) -> str: |
222 |
| - conn = self.get_conn() |
223 |
| - ftp_mdtm = conn.stat(path).st_mtime |
224 |
| - return datetime.datetime.fromtimestamp(ftp_mdtm).strftime('%Y%m%d%H%M%S') |
225 |
| - |
226 |
| - def path_exists(self, path: str) -> bool: |
227 |
| - """ |
228 |
| - Returns True if a remote entity exists |
229 |
| -
|
230 |
| - :param path: full path to the remote file or directory |
231 |
| - :type path: str |
232 |
| - """ |
233 |
| - conn = self.get_conn() |
234 |
| - return conn.exists(path) |
235 |
| - |
236 |
| - @staticmethod |
237 |
| - def _is_path_match(path: str, prefix: Optional[str] = None, delimiter: Optional[str] = None) -> bool: |
238 |
| - """ |
239 |
| - Return True if given path starts with prefix (if set) and ends with delimiter (if set). |
240 |
| -
|
241 |
| - :param path: path to be checked |
242 |
| - :type path: str |
243 |
| - :param prefix: if set path will be checked is starting with prefix |
244 |
| - :type prefix: str |
245 |
| - :param delimiter: if set path will be checked is ending with suffix |
246 |
| - :type delimiter: str |
247 |
| - :return: bool |
248 |
| - """ |
249 |
| - if prefix is not None and not path.startswith(prefix): |
250 |
| - return False |
251 |
| - if delimiter is not None and not path.endswith(delimiter): |
252 |
| - return False |
253 |
| - return True |
254 |
| - |
255 |
| - def get_tree_map( |
256 |
| - self, path: str, prefix: Optional[str] = None, delimiter: Optional[str] = None |
257 |
| - ) -> Tuple[List[str], List[str], List[str]]: |
258 |
| - """ |
259 |
| - Return tuple with recursive lists of files, directories and unknown paths from given path. |
260 |
| - It is possible to filter results by giving prefix and/or delimiter parameters. |
261 |
| -
|
262 |
| - :param path: path from which tree will be built |
263 |
| - :type path: str |
264 |
| - :param prefix: if set paths will be added if start with prefix |
265 |
| - :type prefix: str |
266 |
| - :param delimiter: if set paths will be added if end with delimiter |
267 |
| - :type delimiter: str |
268 |
| - :return: tuple with list of files, dirs and unknown items |
269 |
| - :rtype: Tuple[List[str], List[str], List[str]] |
270 |
| - """ |
271 |
| - conn = self.get_conn() |
272 |
| - files, dirs, unknowns = [], [], [] # type: List[str], List[str], List[str] |
273 |
| - |
274 |
| - def append_matching_path_callback(list_): |
275 |
| - return ( |
276 |
| - lambda item: list_.append(item) |
277 |
| - if self._is_path_match(item, prefix, delimiter) |
278 |
| - else None |
279 |
| - ) |
280 |
| - |
281 |
| - conn.walktree( |
282 |
| - remotepath=path, |
283 |
| - fcallback=append_matching_path_callback(files), |
284 |
| - dcallback=append_matching_path_callback(dirs), |
285 |
| - ucallback=append_matching_path_callback(unknowns), |
286 |
| - recurse=True, |
287 |
| - ) |
288 |
| - |
289 |
| - return files, dirs, unknowns |
| 28 | +warnings.warn( |
| 29 | + "This module is deprecated. Please use `airflow.providers.sftp.hooks.sftp_hook`.", |
| 30 | + DeprecationWarning, |
| 31 | + stacklevel=2, |
| 32 | +) |
0 commit comments