{"id":692,"date":"2024-10-05T12:46:01","date_gmt":"2024-10-05T12:46:01","guid":{"rendered":"https:\/\/eipsoftware.com\/musings\/?p=692"},"modified":"2024-10-05T14:12:38","modified_gmt":"2024-10-05T14:12:38","slug":"connect-to-sftp","status":"publish","type":"post","link":"https:\/\/eipsoftware.com\/musings\/connect-to-sftp\/","title":{"rendered":"Connect to sFTP"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">sFTP is still used on a daily basis.  Many times the simpler well proven technologies just do the job, just like a hammer. Today&#8217;s post will show how to use Python to connect to an sFTP site and securely upload or download your files.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">Many times on <a href=\"https:\/\/stackoverflow.com\/\" data-type=\"link\" data-id=\"https:\/\/stackoverflow.com\/\">stackoverflow <\/a> you tend to get incomplete or partial samples of code that don&#8217;t explain step by step what you need to do. And with sFTP every step is critical.  The code presented is not production ready; i.e. no <a href=\"https:\/\/docs.python.org\/3\/library\/exceptions.html\" data-type=\"link\" data-id=\"https:\/\/docs.python.org\/3\/library\/exceptions.html\">Exception <\/a>blocks but it wouldn&#8217;t take much more effort to adapt the code to be production ready.  Instead we will concentrate on the important parts and get you downloading files quickly.  Let&#8217;s go!<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<div class=\"wp-block-getwid-table-of-contents is-style-ordered\"><ol class=\"wp-block-getwid-table-of-contents__list\"><li><a href=\"#gce040e20b33f\">Directory Structure<\/a><\/li><li><a href=\"#g3827cd31069d\">main.py<\/a><\/li><li><a href=\"#config_file\">Configuration File<\/a><\/li><li><a href=\"#gc8c3c156f2e4\">sftp_config.yaml<\/a><\/li><li><a href=\"#sftp_config\">config<\/a><\/li><li><a href=\"#gf392a9f6177b\">File:  __init__.py<\/a><\/li><li><a href=\"#g57eb7d384019\">File:  file_upload.py<\/a><\/li><li><a href=\"#g9cdcc8c350c2\">__set_file_paths<\/a><\/li><li><a href=\"#g74b9e8588af7\">__connect_client<\/a><\/li><li><a href=\"#known_hosts\">known_hosts file<\/a><\/li><li><a href=\"#g455b0787e4be\">send_files<\/a><\/li><\/ol><\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"gce040e20b33f\">Directory Structure<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The directory structure will look like the following:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"289\" height=\"265\" src=\"https:\/\/eipsoftware.com\/musings\/wp-content\/uploads\/2024\/10\/sftp_file_structure-1.png\" alt=\"\" class=\"wp-image-700\"\/><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"g3827cd31069d\">main.py<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is the file we will invoke to start the program.  It is rather simple.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" ># -- LIBRARY -----------------------------------------------------------------\nimport sys\n\n# -- LOCAL FILES -------------------------------------------------------------\nfrom src.file_upload import FileUpload\n\ndef main(argv) -&gt; None:\n    \n    file_list = ['file_one.txt', 'file_two.txt']\n    \n    # upload files to sftp\n    if len(file_list) &gt; 0:\n        fp = FileUpload()\n        fp.send_files(file_list=file_list)\n\n\n# Start the program\nif __name__ == '__main__':\n    main(sys.argv[1:])<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python range:17-19 decode:true \" ># -- LIBRARY -----------------------------------------------------------------\nimport sys\n\n# -- LOCAL FILES -------------------------------------------------------------\nfrom src.file_upload import FileUpload\n\ndef main(argv) -&gt; None:\n    \n    file_list = ['file_one.txt', 'file_two.txt']\n    \n    # upload files to sftp\n    if len(file_list) &gt; 0:\n        fp = FileUpload()\n        fp.send_files(file_list=file_list)\n\n\n# Start the program\nif __name__ == '__main__':\n    main(sys.argv[1:])<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The file is using the sys.argv[1:] to allow you to pass in arguments when you are running the program.  In an effort to keep focus, we  won&#8217;t  being using  arguments, and will just hard code the file names in the main function.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now let&#8217;s go line by line.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python range:5-6 decode:true \" ># -- LIBRARY -----------------------------------------------------------------\nimport sys\n\n# -- LOCAL FILES -------------------------------------------------------------\nfrom src.file_upload import FileUpload\n\ndef main(argv) -&gt; None:\n    \n    file_list = ['file_one.txt', 'file_two.txt']\n    \n    # upload files to sftp\n    if len(file_list) &gt; 0:\n        fp = FileUpload()\n        fp.send_files(file_list=file_list)\n\n\n# Start the program\nif __name__ == '__main__':\n    main(sys.argv[1:])<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This is the FileUpload class we will create later.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python range:7-19 decode:true \" ># -- LIBRARY -----------------------------------------------------------------\nimport sys\n\n# -- LOCAL FILES -------------------------------------------------------------\nfrom src.file_upload import FileUpload\n\ndef main(argv) -&gt; None:\n    \n    file_list = ['file_one.txt', 'file_two.txt']\n    \n    # upload files to sftp\n    if len(file_list) &gt; 0:\n        fp = FileUpload()\n        fp.send_files(file_list=file_list)\n\n\n# Start the program\nif __name__ == '__main__':\n    main(sys.argv[1:])<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The main function, pretty  simple isn&#8217;t it?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">file_list is a list of files we will upload to the sFTP server.  Conversely you could have a list of files you wish to download.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next we check if the file_list has any values in it; in case the list is being  passed in via sys.argv.   <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next we assign fp to the FileUpload class<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Last step is we send the files, by calling the send_files  method in the class.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"config_file\">Configuration File<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In the configuration directory we have three files. <\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>__init__.py<\/li>\n\n\n\n<li><a href=\"#known_hosts\">known_hosts<\/a><\/li>\n\n\n\n<li>sftp_config.yaml<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">We are going to look at the sftp_config.yaml configuration file and the __init__.py file first.  We will come back to the <a href=\"#known_hosts\">known_hosts<\/a> file later on.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"gc8c3c156f2e4\">sftp_config.yaml<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The configuration file will store the details on how to  connect to the sFTP server and what files we want to upload.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:yaml decode:true \" ># ----------------------------------------------------------------------------\n# SFTP CONFIGURATION OPTIONS\n# ----------------------------------------------------------------------------\n# sftp connection values\n---\nSERVER:\n  HOSTNAME: 'THESFTP-SERVER'\n  USERNAME: 'myusername'\n  KEY_FILENAME: 'C:\\\\Users\\\\my_name\\\\.ssh\\\\thekey.pem'\n  KNOWN_HOSTS: '\/config\/known_hosts'\n\nFILE:\n  LOCAL_DIRECTORY:  'main\/assets\/'\n  REMOTE_DIRECTORY: '\/home\/username\/directory\/directory\/main\/assets\/'\n\nFILE_NAMES:\n  ONE:      'image_one.png'\n  TWO:      'image_two.png'\n  THREE:    'image_three.png'\n\nWAIT_PERIOD: &amp;waitperiod \"10\"<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The  configuration file is in plain text, the values are pretty self-explanatory.  We will talk about the KEY_FILENAME and <a href=\"#known_hosts\">KNOWN_HOSTS<\/a> later on.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Make sure to specify your details, including HOSTNAME, USERNAME, LOCAL_DIRECTORY, REMOTE_DIRECTORY, etc&#8230;<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"sftp_config\">config<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The init file will be used to load the .yaml configuration file.  It is handy way of being able to store the sFTP configuration parameters without having to change the main parts of the code. <\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" >import pathlib, sys\nfrom box import Box<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">We need three libraries the standard pathlib and sys, Additionally I use <a href=\"https:\/\/pypi.org\/project\/python-box\/\">python-box<\/a> to help referencing values in the configuration. Python-box has a dependency on yaml parser.  I use ruamel, finding it easier and quicker.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If not already installed, from your command line you can use pip to install them.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install python-box\npip install ruamel.yaml<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading has-large-font-size\" id=\"gf392a9f6177b\">File:  __init__.py<\/h2>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" ># ----------------------------------------------------------------------------\n# load configuration\n# ----------------------------------------------------------------------------\ndef load_config(file_name: str) -&gt; Box:\n    path = pathlib.Path(__file__).parent \/ file_name\n    try: \n        with path.open(mode=\"r\") as config_file:\n            return Box(frozen_box=True).from_yaml(config_file.read())\n\n    except FileNotFoundError as e:\n        print(f\"The directory {path.name} does not exist \\n\\n {e}\")\n        sys.exit(1)\n    except PermissionError as e:\n        print(f\"Permission denied to access the directory {path.name}\\n\\n {e}\")\n        sys.exit(1)\n    except OSError as e:\n        print(f\"An OS error occurred: {e}\")\n        sys.exit(1)\n\n    finally:\n        path = None<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The function load_config will take a file name as an argument and return the python-box object.  <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next we get the path of the file and store it in the path variable.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" >    try: \n        with path.open(mode=\"r\") as config_file:\n            return Box(frozen_box=True).from_yaml(config_file.read())<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Now we will open the file, and convert the .yaml file into a Box object to be used by the FileUpload class later on.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" >sftp_config:Box     = load_config(\"sftp_config.yaml\")<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">And last step, we call  the load_config function.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"g57eb7d384019\">File:  file_upload.py<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The FileUpload class will be used to connect to the sFTP server and than upload files.  Additional methods could be added to download files,  I will leave that exercise to you.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I am going to use the <a href=\"https:\/\/www.paramiko.org\/\">paramiko <\/a>package. Again if not installed, from the command line use pip to install.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install paramiko\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" ># -- LIBRARY -----------------------------------------------------------------\nimport os\nfrom pathlib import Path\n\nimport paramiko as pa\n\n# -- LOCAL FILES -------------------------------------------------------------\nfrom config import sftp_config\n\nclass FileUpload():\n    def __init__(self) -&gt; None:\n        self._file_list: list = None\n        self._ssh_client: pa.SSHClient = None\n        self._sftp_client: pa.SFTPClient = None\n        self._known_hosts_file_path: Path = None\n\n        # set the file paths\n        self.__set_file_paths()\n\n    def __set_file_paths(self) -&gt; None:\n        self._known_hosts_file_path = os.path.normpath(\"\".join([os.getcwd(),sftp_config.SERVER.KNOWN_HOSTS]))\n\n        if os.path.isfile(self._known_hosts_file_path) == False:\n            raise Exception(f\"Known_Hosts file can't be found: {self._known_hosts_file_path}\")\n\n\n    def __connect_client(self) -&gt; None:\n        \n        # get the ssh key\n        sftp_key = pa.RSAKey.from_private_key_file(sftp_config.SERVER.KEY_FILENAME)\n\n        # set the ssh client\n        self._ssh_client = pa.SSHClient()\n\n        self._ssh_client.load_host_keys(self._known_hosts_file_path)\n\n        # connect to server\n        self._ssh_client.connect(hostname=sftp_config.SERVER.HOSTNAME,\n                                 username=sftp_config.SERVER.USERNAME,\n                                 pkey=sftp_key)\n\n\n    def send_files(self, file_list:list) -&gt; None:\n\n        # connect to ssh client\n        self.__connect_client()\n               \n        # init the sftp client\n        self._sftp_client = self._ssh_client.open_sftp()\n\n        self._sftp_client.chdir(sftp_config.FILE.REMOTE_DIRECTORY)\n        \n        # cycle through each file and put them on server\n        for file in file_list:\n            local_file_path = os.path.join(file)\n            remote_file_path = \"\".join([sftp_config.FILE.REMOTE_DIRECTORY, os.path.basename(file)])\n            try:\n                self._sftp_client.put(localpath=local_file_path,\n                                      remotepath=remote_file_path)\n            \n            except FileNotFoundError as err:\n                print(f\"File {local_file_path} not found locally\")\n        \n        self._sftp_client.close()\n        self._ssh_client.close()\n<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" ># -- LIBRARY -----------------------------------------------------------------\nimport os\nfrom pathlib import Path\n\nimport paramiko as pa\n\n# -- LOCAL FILES -------------------------------------------------------------\nfrom config import sftp_config<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Here we will import in standard libraries and the <a href=\"#sftp_config\" data-type=\"internal\" data-id=\"#sftp_config\">sftp_config <\/a>file we  created earlier. <\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" >class FileUpload():\n    def __init__(self) -&gt; None:\n        self._file_list: list = None\n        self._ssh_client: pa.SSHClient = None\n        self._sftp_client: pa.SFTPClient = None\n        self._known_hosts_file_path: Path = None\n\n        # set the file paths\n        self.__set_file_paths()<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Here we are defining the class, and setting some variables to hold, the ssh client, sftp client, and known_hosts file_path.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading has-large-font-size\" id=\"g9cdcc8c350c2\">__set_file_paths<\/h2>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" >    def __set_file_paths(self) -&gt; None:\n        self._known_hosts_file_path = os.path.normpath(\"\".join([os.getcwd(),sftp_config.SERVER.KNOWN_HOSTS]))\n\n        if os.path.isfile(self._known_hosts_file_path) == False:\n            raise Exception(f\"Known_Hosts file can't be found: {self._known_hosts_file_path}\")<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Now we will read the contents of the <a href=\"#known_hosts\">known_hosts<\/a> file. If the file doesn&#8217;t exist, we will raise an Exception. <\/p>\n\n\n\n<h2 class=\"wp-block-heading has-large-font-size\" id=\"g74b9e8588af7\">__connect_client<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When we connect to the ssh server, we will need a key.  The user private key should be stored locally on your server.  The key needs to be in the .pem file format.  The private key should be created on the ssh_server or ask the ssh server&#8217;s admin to send one to you.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:zsh decode:true \" title=\"Create User private key in pem format\" >$ ssh-keygen -b 4096\n$ cat .ssh\/id_rsa.pub &gt;&gt; .ssh\/authorized_keys\n<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Then  copy the .pem file to your local machine via <a href=\"https:\/\/filezilla-project.org\/\">FileZilla <\/a> or another sftp tool.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \">    def __connect_client(self) -&gt; None:\n        \n        # get the ssh key\n        sftp_key = pa.RSAKey.from_private_key_file(sftp_config.SERVER.KEY_FILENAME)\n\n        # set the ssh client\n        self._ssh_client = pa.SSHClient()\n\n        self._ssh_client.load_host_keys(self._known_hosts_file_path)\n\n        # connect to server\n        self._ssh_client.connect(hostname=sftp_config.SERVER.HOSTNAME,\n                                 username=sftp_config.SERVER.USERNAME,\n                                 pkey=sftp_key)<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">We use paramiko function to read the key from the file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next we connect to the client and store the client object in self._ssh_client.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Know we need to load a list of known hosts to avoid a connection error.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lastly we connect to the ssh server. <\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading has-larger-font-size\" id=\"known_hosts\">known_hosts file<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">One of the more difficult issues I saw with using the paramiko package was many folks were struggling with the error generated when there was not  a known hosts file.  Often the &#8220;solution&#8221; proposed by others was to use following line of code.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" ># auto add to known hosts\nself._ssh_client.set_missing_host_key_policy(pa.AutoAddPolicy())<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Using the option will leave  you open to man-in-the-middle attacks. The proper way is to use a known_hosts file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The file is a simple text file, with no file extension. You can have one if you want but&#8230; why?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your known_hosts file should look like the following.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MYSERVER-ONE ssh-rsa AAAAB3NzaC.....=\nMYSERVER-ONE ecdsa-sha2-nistp256 AAAAE2VjZHN.....=\nMYSERVER-ONE ssh-ed25519 AAAAC3...\/O<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">With the &#8220;&#8230;.&#8221; replaced with many more characters.  You should get the known hosts from the ssh server admin.   However you can generate it if you are positive it is secure.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ ssh-keyscan MYSERVER-ONE<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"g455b0787e4be\">send_files<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here is the method for sending the files.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" >    def send_files(self, file_list:list) -&gt; None:\n\n        # connect to ssh client\n        self.__connect_client()\n               \n        # init the sftp client\n        self._sftp_client = self._ssh_client.open_sftp()\n\n        self._sftp_client.chdir(sftp_config.FILE.REMOTE_DIRECTORY)\n        \n        # cycle through each file and put them on server\n        for file in file_list:\n            local_file_path = os.path.join(file)\n            remote_file_path = \"\".join([sftp_config.FILE.REMOTE_DIRECTORY, os.path.basename(file)])\n            try:\n                self._sftp_client.put(localpath=local_file_path,\n                                      remotepath=remote_file_path)\n            \n            except FileNotFoundError as err:\n                print(f\"File {local_file_path} not found locally\")\n        \n        self._sftp_client.close()\n        self._ssh_client.close()<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" >        # connect to ssh client\n        self.__connect_client()\n               \n        # init the sftp client\n        self._sftp_client = self._ssh_client.open_sftp()\n\n        self._sftp_client.chdir(sftp_config.FILE.REMOTE_DIRECTORY)<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">First call the private method to connect to the ssh_client.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next we set the sftp client object.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next we change the directory to the remote directory we set in the <a href=\"#config_file\" data-type=\"internal\" data-id=\"#config_file\">configuration file<\/a> earlier.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" >        # cycle through each file and put them on server\n        for file in file_list:\n            local_file_path = os.path.join(file)\n            remote_file_path = \"\".join([sftp_config.FILE.REMOTE_DIRECTORY, os.path.basename(file)])\n            try:\n                self._sftp_client.put(localpath=local_file_path,\n                                      remotepath=remote_file_path)\n            \n            except FileNotFoundError as err:\n                print(f\"File {local_file_path} not found locally\")<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Now we have a simple loop, that will read the name of each file in the file_list, and try to upload (PUT)  the file to the sFTP server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An exception will be raised if the local file path is invalid.<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:python decode:true \" >        self._sftp_client.close()\n        self._ssh_client.close()<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">And at the end we close the sftp_client and ssh_client.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s it.  Connecting to an  sFTP server.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Use Python to connect to an sFTP site and upload or download files.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_crdt_document":"","footnotes":""},"categories":[3,4],"tags":[65,66],"series":[],"class_list":["post-692","post","type-post","status-publish","format-standard","hentry","category-python","category-code","tag-sftp","tag-known_hosts"],"_links":{"self":[{"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/posts\/692","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/comments?post=692"}],"version-history":[{"count":18,"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/posts\/692\/revisions"}],"predecessor-version":[{"id":715,"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/posts\/692\/revisions\/715"}],"wp:attachment":[{"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/media?parent=692"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/categories?post=692"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/tags?post=692"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/eipsoftware.com\/musings\/wp-json\/wp\/v2\/series?post=692"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}