brynq-sdk-ftp 2.0.1__tar.gz → 2.0.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: brynq_sdk_ftp
3
- Version: 2.0.1
3
+ Version: 2.0.4
4
4
  Summary: FTP wrapper from BrynQ
5
5
  Home-page: UNKNOWN
6
6
  Author: BrynQ
@@ -0,0 +1,2 @@
1
+ from .ftps import FTPS
2
+ from .sftp import SFTP
@@ -0,0 +1,227 @@
1
+ from brynq_sdk_brynq import BrynQ
2
+ import os
3
+ import ftplib
4
+ import time
5
+ from ftplib import FTP
6
+ from typing import Union, List
7
+ from tenacity import retry, stop_after_attempt, wait_exponential_jitter, retry_if_exception
8
+
9
+
10
+ def is_ftp_exception(e: BaseException) -> bool:
11
+ if isinstance(e, ftplib.all_errors):
12
+ error = str(e)[:400].replace('\'', '').replace('\"', '')
13
+ print(f"{error}, retrying")
14
+ return True
15
+ else:
16
+ return False
17
+
18
+
19
+ class FTPS(BrynQ):
20
+ def __init__(self, label: Union[str, List] = None, debug=False):
21
+ super().__init__()
22
+ if label is not None:
23
+ credentials = self.get_system_credential(system='ftps', label=label)
24
+ self.host = credentials['host']
25
+ self.username = credentials['username']
26
+ self.password = credentials['password']
27
+ elif os.getenv("QLIK_HOST") is not None and os.getenv("QLIK_USER") is not None and os.getenv("QLIK_PASSWORD") is not None:
28
+ self.host = os.getenv("QLIK_HOST")
29
+ self.username = os.getenv("QLIK_USER")
30
+ self.password = os.getenv("QLIK_PASSWORD")
31
+ else:
32
+ raise ValueError("Set the environment variables QLIK_HOST, QLIK_USER and QLIK_PASSWORD or provide the label parameter for a credential from BrynQ")
33
+ self.debug = debug
34
+
35
+ @retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=60, max=900), retry=retry_if_exception(is_ftp_exception), reraise=True)
36
+ def upload_file(self, local_path, filename, remote_path, remove_after_upload=False):
37
+ """
38
+ Upload a file from the client to another client or server
39
+ :param local_path: the path where the upload file is located
40
+ :param filename: The file which should be uploaded
41
+ :param remote_path: The path on the destination client where the file should be saved
42
+ :param remove_after_upload: If true, remove the file after a succesfull upload
43
+ :return: a status if the upload is succesfull or not
44
+ """
45
+ with FTP(host=self.host, user=self.username, passwd=self.password) as ftp:
46
+ ftp.cwd(remote_path)
47
+ with open(local_path + filename, 'rb') as fp:
48
+ # This runs until upload is successful, then breaks
49
+ while True:
50
+ try:
51
+ ftp.storbinary("STOR " + filename, fp)
52
+ # If the file should be removed after upload, remove it from the local path
53
+ if remove_after_upload:
54
+ os.remove(f'{local_path}{filename}')
55
+ except ftplib.error_temp as e:
56
+ # this catches 421 errors (socket timeout), sleeps 10 seconds and tries again. If any other exception is encountered, breaks.
57
+ if str(e).split()[0] == '421':
58
+ time.sleep(10)
59
+ continue
60
+ else:
61
+ raise
62
+ break
63
+ ftp.close()
64
+ return 'File is transferred'
65
+
66
+ @retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=60, max=900), retry=retry_if_exception(is_ftp_exception), reraise=True)
67
+ def upload_multiple_files(self, local_path, remote_path, remove_after_upload=False):
68
+ """
69
+ Upload all files in a directory from the client to another client or server
70
+ :param local_path: the path from where all the files should be uploaded
71
+ :param remote_path: The path on the destination client where the file should be saved
72
+ :param remove_after_upload: If true, remove the file after a succesfull upload
73
+ :return: a status if the upload is succesfull or not
74
+ """
75
+ ftp = FTP(host=self.host, user=self.username, passwd=self.password)
76
+ ftp.cwd(remote_path)
77
+ for filename in os.listdir(local_path):
78
+ file = local_path + filename
79
+ if self.debug:
80
+ print(f"Remote path: {remote_path}, local file: {file}")
81
+ if os.path.isfile(file):
82
+ with open(file, 'rb') as fp:
83
+ # This runs until upload is successful, then breaks
84
+ while True:
85
+ try:
86
+ ftp.storbinary("STOR " + filename, fp)
87
+ # If the file should be removed after upload, remove it from the local path
88
+ if remove_after_upload:
89
+ os.remove(file)
90
+ except ftplib.error_temp as e:
91
+ # this catches 421 errors (socket timeout), sleeps 10 seconds and tries again. If any other exception is encountered, breaks.
92
+ if str(e).split()[0] == '421':
93
+ time.sleep(10)
94
+ continue
95
+ else:
96
+ raise
97
+ break
98
+ ftp.close()
99
+ return 'All files are transferred'
100
+
101
+ @retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=60, max=900), retry=retry_if_exception(is_ftp_exception), reraise=True)
102
+ def download_file(self, local_path, remote_path, filename, remove_after_download=False):
103
+ """
104
+ Returns a single file from a given remote path
105
+ :param local_path: the folder where the downloaded file should be stored
106
+ :param remote_path: the folder on the server where the file should be downloaded from
107
+ :param filename: the filename itself
108
+ :param remove_after_download: Should the file be removed on the server after the download or not
109
+ :return: a status
110
+ """
111
+ if self.debug:
112
+ print(f"Remote path: {remote_path}, local file path: {filename}")
113
+ with FTP(host=self.host, user=self.username, passwd=self.password) as ftp:
114
+ with open('{}/{}'.format(local_path, filename), 'wb') as fp:
115
+ res = ftp.retrbinary('RETR {}/{}'.format(remote_path, filename), fp.write)
116
+ if not res.startswith('226 Successfully transferred'):
117
+ # Remove the created file on the local client if the download is failed
118
+ if os.path.isfile(f'{local_path}/{filename}'):
119
+ os.remove(f'{local_path}/{filename}')
120
+ else:
121
+ if remove_after_download:
122
+ ftp.delete(filename)
123
+
124
+ return res
125
+
126
+ @retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=60, max=900), retry=retry_if_exception(is_ftp_exception), reraise=True)
127
+ def make_dir(self, dir_name):
128
+ """
129
+ Create a directory on a remote machine
130
+ :param dir_name: give the path name which should be created
131
+ :return: the status if the creation is successfull or not
132
+ """
133
+ with FTP(host=self.host, user=self.username, passwd=self.password) as ftp:
134
+ status = ftp.mkd(dir_name)
135
+ return status
136
+
137
+ @retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=60, max=900), retry=retry_if_exception(is_ftp_exception), reraise=True)
138
+ def list_directories(self, remote_path=''):
139
+ """
140
+ Give a NoneType of directories and files in a given directory. This one is only for information. The Nonetype
141
+ can't be iterated or something like that
142
+ :param remote_path: give the folder where to start in
143
+ :return: a NoneType with folders and files
144
+ """
145
+ with FTP(host=self.host, user=self.username, passwd=self.password) as ftp:
146
+ ftp.cwd(remote_path)
147
+ if self.debug:
148
+ print(ftp.dir())
149
+ return ftp.dir()
150
+
151
+ @retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=60, max=900), retry=retry_if_exception(is_ftp_exception), reraise=True)
152
+ def make_dirs(self, filepath: str):
153
+ """
154
+ shadows os.makedirs but for ftp
155
+ :param filepath: filepath that you want to create
156
+ :return: nothing
157
+ """
158
+ filepath = filepath.split('/')
159
+ with FTP(host=self.host, user=self.username, passwd=self.password) as ftp:
160
+ ftp.cwd('/')
161
+ for subpath in filepath:
162
+ if subpath != '':
163
+ file_list = []
164
+ ftp.retrlines('NLST', file_list.append)
165
+ if subpath in file_list:
166
+ ftp.cwd(subpath)
167
+ else:
168
+ ftp.mkd(subpath)
169
+ ftp.cwd(subpath)
170
+
171
+ @retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=60, max=900), retry=retry_if_exception(is_ftp_exception), reraise=True)
172
+ def list_files(self, remote_path=''):
173
+ """
174
+ Give a list with files in a certain folder
175
+ :param remote_path: give the folder where to look in
176
+ :return: a list with files
177
+ """
178
+ with FTP(host=self.host, user=self.username, passwd=self.password) as ftp:
179
+ ftp.cwd(remote_path)
180
+ if self.debug:
181
+ print(ftp.nlst())
182
+ return ftp.nlst()
183
+
184
+ @retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=60, max=900), retry=retry_if_exception(is_ftp_exception), reraise=True)
185
+ def remove_file(self, remote_filepath):
186
+ """
187
+ Remove a file on a remote location
188
+ :param remote_file: the full path of the file that needs to be removed
189
+ """
190
+ with FTP(host=self.host, user=self.username, passwd=self.password) as ftp:
191
+ ftp.delete(remote_filepath)
192
+
193
+ @retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=60, max=900), retry=retry_if_exception(is_ftp_exception), reraise=True)
194
+ def remove_directory(self, remote_directory, recursive=False):
195
+ """
196
+ Remove a file on a remote location
197
+ :param remote_directory: the directory you want to remove
198
+ :param recursive: if you want to remove the directory recursively (including all files and subdirectories)
199
+ """
200
+ ftp = FTP(host=self.host, user=self.username, passwd=self.password)
201
+ if recursive:
202
+ ftp.cwd(remote_directory)
203
+ files = ftp.nlst() # List directory contents
204
+ for file_name in files:
205
+ if file_name in ('.', '..'):
206
+ continue
207
+ try:
208
+ ftp.delete(file_name) # Delete file
209
+ except:
210
+ self.remove_directory(f"{remote_directory}/{file_name}", recursive=True) # Recursive call for subdirectories
211
+ ftp.cwd('..') # Move back to the parent directory
212
+ ftp.rmd(remote_directory.split('/')[-1]) # Remove the empty directory
213
+ else:
214
+ ftp.rmd(remote_directory)
215
+
216
+
217
+ @retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=60, max=900), retry=retry_if_exception(is_ftp_exception), reraise=True)
218
+ def send_command(self, command):
219
+ """
220
+ Send a command to the ftp server
221
+ :param command: the command that needs to be send
222
+ :return: the response of the server
223
+ """
224
+ with FTP(host=self.host, user=self.username, passwd=self.password) as ftp:
225
+ response = ftp.sendcmd(command)
226
+ return response
227
+
@@ -0,0 +1,120 @@
1
+ from brynq_sdk_brynq import BrynQ
2
+ from io import StringIO
3
+ from paramiko.client import SSHClient, AutoAddPolicy
4
+ from paramiko import RSAKey
5
+ from paramiko.sftp_attr import SFTPAttributes
6
+ import pysftp
7
+ from typing import Union, List
8
+ from stat import S_ISREG
9
+ import os
10
+
11
+
12
+ class SFTP(BrynQ):
13
+ def __init__(self, label: Union[str, List], debug=False):
14
+ """
15
+ Init the SFTP class
16
+ :param label: The label of the connector
17
+ :param debug: If you want to see debug messages
18
+ """
19
+ super().__init__()
20
+ credentials = self.get_system_credential(system='sftp', label=label)
21
+ self.debug = debug
22
+ if self.debug:
23
+ print(credentials)
24
+ self.host = credentials['host']
25
+ self.port = 22 if credentials['port'] is None else credentials['port']
26
+ self.username = credentials['username']
27
+ self.password = credentials['password']
28
+ self.cnopts = pysftp.CnOpts()
29
+ self.cnopts.hostkeys = None
30
+ self.private_key_path = credentials.get('private_key_password', None)
31
+ self.private_key_passphrase = credentials.get('private_key_password', None)
32
+ self.private_key = RSAKey(file_obj=StringIO(credentials.get('private_key')), password=self.private_key_passphrase)
33
+ self.client = SSHClient()
34
+ self.client.set_missing_host_key_policy(AutoAddPolicy())
35
+
36
+
37
+
38
+
39
+
40
+ def upload_file(self, local_filepath, remote_filepath, confirm=True) -> SFTPAttributes:
41
+ """
42
+ Upload a single file to a remote location. If there is no Private key
43
+ :param local_filepath: The file and the full path on your local machine
44
+ :param remote_filepath: The path and filename on the remote location
45
+ :param confirm: If you want to confirm the upload
46
+ :return: status
47
+ """
48
+ self.client.connect(hostname=self.host, port=self.port, username=self.username, password=self.password, pkey=self.private_key, passphrase=self.private_key_passphrase)
49
+ sftp = self.client.open_sftp()
50
+ response = sftp.put(local_filepath, remote_filepath, confirm=confirm)
51
+ self.client.close()
52
+
53
+ return response
54
+
55
+ def list_dir(self, remote_filepath, get_folders: bool = False) -> List[str]:
56
+ """
57
+ Read the files and folders an a certain location
58
+ :param remote_filepath: The full path where you want to get the content from
59
+ :return: a list with files and folders in the given location
60
+ """
61
+ self.client.connect(hostname=self.host, port=self.port, username=self.username, pkey=self.private_key, password=self.password)
62
+ sftp = self.client.open_sftp()
63
+ sftp.chdir(remote_filepath)
64
+ list_files = sftp.listdir_attr()
65
+ list_files = [file.filename for file in list_files if S_ISREG(file.st_mode) or get_folders]
66
+ self.client.close()
67
+
68
+ return list_files
69
+
70
+ def download_file(self, remote_path, remote_file, local_path):
71
+ """
72
+ Download a single file
73
+ :param remote_path: the path where the remote file exists
74
+ :param remote_file: the remote file itself
75
+ :param local_path: the path where the file needs to be downloaded to
76
+ :return: a file object
77
+ """
78
+ self.client.connect(hostname=self.host, port=self.port, username=self.username, pkey=self.private_key, password=self.password)
79
+ sftp = self.client.open_sftp()
80
+ sftp.get(remotepath=f'{remote_path}{remote_file}', localpath=f'{local_path}/{remote_file}')
81
+ self.client.close()
82
+
83
+ def make_dir(self, remote_path, new_dir_name):
84
+ """
85
+ Create a new folder on a remote location
86
+ :param remote_path: The location where you want to create the new folder
87
+ :param new_dir_name: The name of the new folder
88
+ :return: a status if creating succeeded or not
89
+ """
90
+ self.client.connect(hostname=self.host, port=self.port, username=self.username, pkey=self.private_key, password=self.password)
91
+ sftp = self.client.open_sftp()
92
+ sftp.chdir(remote_path)
93
+ sftp.mkdir(new_dir_name)
94
+ self.client.close()
95
+
96
+ def remove_file(self, remote_file):
97
+ """
98
+ Remove a file on a remote location
99
+ :param remote_file: the full path of the file that needs to be removed
100
+ :return: a status if deleting succeeded or not
101
+ """
102
+ self.client.connect(hostname=self.host, port=self.port, username=self.username, pkey=self.private_key, password=self.password)
103
+ sftp = self.client.open_sftp()
104
+ sftp.remove(remote_file)
105
+ self.client.close()
106
+
107
+ def move_file(self, old_file_path: str, new_file_path: str):
108
+ """
109
+ Move or rename a file on a remote location
110
+ :param old_file_path: the full path of the file that needs to be moved or renamed
111
+ :param new_file_path: the full path of the new location of the file
112
+ :return:
113
+ """
114
+ self.client.connect(hostname=self.host, port=self.port, username=self.username, pkey=self.private_key, password=self.password)
115
+ sftp = self.client.open_sftp()
116
+ sftp.rename(oldpath=old_file_path, newpath=new_file_path)
117
+ self.client.close()
118
+
119
+ def rename_file(self, old_file_path: str, new_file_path: str):
120
+ self.move_file(old_file_path=old_file_path, new_file_path=new_file_path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: brynq-sdk-ftp
3
- Version: 2.0.1
3
+ Version: 2.0.4
4
4
  Summary: FTP wrapper from BrynQ
5
5
  Home-page: UNKNOWN
6
6
  Author: BrynQ
@@ -1,4 +1,7 @@
1
1
  setup.py
2
+ brynq_sdk_ftp/__init__.py
3
+ brynq_sdk_ftp/ftps.py
4
+ brynq_sdk_ftp/sftp.py
2
5
  brynq_sdk_ftp.egg-info/PKG-INFO
3
6
  brynq_sdk_ftp.egg-info/SOURCES.txt
4
7
  brynq_sdk_ftp.egg-info/dependency_links.txt
@@ -0,0 +1 @@
1
+ brynq_sdk_ftp
@@ -2,7 +2,7 @@ from setuptools import setup, find_namespace_packages
2
2
 
3
3
  setup(
4
4
  name='brynq_sdk_ftp',
5
- version='2.0.1',
5
+ version='2.0.4',
6
6
  description='FTP wrapper from BrynQ',
7
7
  long_description='FTP wrapper from Brynq',
8
8
  author='BrynQ',
File without changes