1- #
2- # Licensed to the Apache Software Foundation (ASF) under one
3- # or more contributor license agreements. See the NOTICE file
4- # distributed with this work for additional information
5- # regarding copyright ownership. The ASF licenses this file
6- # to you under the Apache License, Version 2.0 (the
7- # "License"); you may not use this file except in compliance
8- # with the License. You may obtain a copy of the License at
9- #
10- # http://www.apache.org/licenses/LICENSE-2.0
11- #
12- # Unless required by applicable law or agreed to in writing,
13- # software distributed under the License is distributed on an
14- # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15- # KIND, either express or implied. See the License for the
16- # specific language governing permissions and limitations
17- # under the License.
181from __future__ import annotations
192
203from tempfile import NamedTemporaryFile
236
247from airflow .models import BaseOperator
258from airflow .providers .amazon .aws .hooks .s3 import S3Hook
26- from airflow .providers .ssh .hooks .ssh import SSHHook
9+ from airflow .providers .sftp .hooks .sftp import SFTPHook
2710
2811if TYPE_CHECKING :
2912 from airflow .utils .context import Context
3013
3114
3215class S3ToSFTPOperator (BaseOperator ):
3316 """
34- This operator enables the transferring of files from S3 to a SFTP server.
17+ Transfer files from Amazon S3 to an SFTP server using SFTPHook .
3518
36- .. seealso::
37- For more information on how to use this operator, take a look at the guide:
38- :ref:`howto/operator:S3ToSFTPOperator`
39-
40- :param sftp_conn_id: The sftp connection id. The name or identifier for
41- establishing a connection to the SFTP server.
42- :param sftp_path: The sftp remote path. This is the specified file path for
43- uploading file to the SFTP server.
44- :param aws_conn_id: aws connection to use
45- :param s3_bucket: The targeted s3 bucket. This is the S3 bucket from
46- where the file is downloaded.
47- :param s3_key: The targeted s3 key. This is the specified file path for
48- downloading the file from S3.
19+ :param sftp_conn_id: The SFTP connection id.
20+ :param sftp_path: The SFTP remote path for uploading files.
21+ :param aws_conn_id: The AWS connection id for S3.
22+ :param s3_bucket: The source S3 bucket.
23+ :param s3_key: The source S3 key. Can be a single key or a list of keys.
24+ :param sftp_filenames: Optional list of filenames to use when saving files to the SFTP server.
25+ If not provided, the original S3 filenames are used.
26+ :param replace: If True, will replace files on SFTP if they exist. Note: This functionality
27+ depends on the behavior of the SFTPHook and the SFTP server configuration.
4928 """
5029
5130 template_fields : Sequence [str ] = ("s3_key" , "sftp_path" , "s3_bucket" )
@@ -54,33 +33,49 @@ def __init__(
5433 self ,
5534 * ,
5635 s3_bucket : str ,
57- s3_key : str ,
36+ s3_key : str | list [ str ] ,
5837 sftp_path : str ,
59- sftp_conn_id : str = "ssh_default" ,
38+ sftp_filenames : str | list [str ] | None = None ,
39+ sftp_conn_id : str = "sftp_default" ,
6040 aws_conn_id : str = "aws_default" ,
41+ replace : bool = False , # Note: This parameter may not be relevant with SFTPHook's put method.
6142 ** kwargs ,
6243 ) -> None :
6344 super ().__init__ (** kwargs )
6445 self .sftp_conn_id = sftp_conn_id
6546 self .sftp_path = sftp_path
6647 self .s3_bucket = s3_bucket
6748 self .s3_key = s3_key
49+ self .sftp_filenames = sftp_filenames
6850 self .aws_conn_id = aws_conn_id
51+ self .replace = replace
6952
7053 @staticmethod
7154 def get_s3_key (s3_key : str ) -> str :
72- """Parse the correct format for S3 keys regardless of how the S3 url is passed."""
55+ """Parse the correct format for S3 keys regardless of how the S3 URL is passed."""
7356 parsed_s3_key = urlsplit (s3_key )
57+ print (parsed_s3_key )
7458 return parsed_s3_key .path .lstrip ("/" )
7559
7660 def execute (self , context : Context ) -> None :
77- self .s3_key = self .get_s3_key (self .s3_key )
78- ssh_hook = SSHHook (ssh_conn_id = self .sftp_conn_id )
7961 s3_hook = S3Hook (self .aws_conn_id )
62+ sftp_hook = SFTPHook (self .sftp_conn_id )
63+
64+ if isinstance (self .s3_key , str ):
65+ print ("s3_key is a string" )
66+ print (self .s3_key )
67+ self .s3_key = [self .s3_key ]
68+ self .sftp_filenames = [self .sftp_filenames ] if self .sftp_filenames else [s3_key .split ('/' )[- 1 ] for s3_key in self .s3_key ]
8069
81- s3_client = s3_hook .get_conn ()
82- sftp_client = ssh_hook .get_conn ().open_sftp ()
70+ for index , s3_key in enumerate (self .s3_key ):
71+ print ("s3_key is a list" )
72+ print (s3_key )
73+ s3_file_key = self .get_s3_key (s3_key )
74+ sftp_filename = self .sftp_filenames [index ] if self .sftp_filenames else s3_file_key .split ('/' )[- 1 ]
75+ sftp_full_path = f"{ self .sftp_path .rstrip ('/' )} /{ sftp_filename } "
8376
84- with NamedTemporaryFile ("w" ) as f :
85- s3_client .download_file (self .s3_bucket , self .s3_key , f .name )
86- sftp_client .put (f .name , self .sftp_path )
77+ with NamedTemporaryFile ("wb" , delete = True ) as tmp_file :
78+ print (f"Downloading file { s3_file_key } from S3 to { tmp_file .name } " )
79+ s3_hook .download_file (bucket_name = self .s3_bucket , key = s3_file_key , local_path = tmp_file .name )
80+ sftp_hook .put (local_filepath = tmp_file .name , remote_filepath = sftp_full_path )
81+ self .log .info (f"File { s3_file_key } transferred to SFTP as { sftp_full_path } " )
0 commit comments