maintenance.espace
Module d'interface avec un espace.
Modifié le 12/03/23 @author: nicolair
Un "espace" est un service de stockage analogue au s3 de Amazon mais proposé
par DigitalOcean. Ce module définit la classe Espace. Une instanciation de
cette classe réalise la mise à jour de l'espace pour refléter l'état local du
dépôt.
1# -*- coding: utf-8 2""" 3Module d'interface avec un espace. 4 5Modifié le 12/03/23 @author: nicolair 6 7Un "espace" est un service de stockage analogue au s3 de Amazon mais proposé 8par DigitalOcean. Ce module définit la classe `Espace`. Une instanciation de 9cette classe réalise la mise à jour de l'espace pour refléter l'état local du 10dépôt. 11""" 12import boto3 13import os.path 14# import copy 15import mimetypes 16 17 18class Espace: 19 """ 20 Classe Espace. 21 22 """ 23 24 def __init__(self, data, apublier_data): 25 """ 26 Initialise la classe et met à jour l'espace. 27 28 Pour les fichiers publiables cad ceux de "apublier_data": 29 30 - récupère les clés associées et leur timestamp dans l'espace 31 - si le fichier local est plus récent: 32 upload sur l'espace 33 #### Paramètres: 34 - data = `manifeste['espace']: dictionnaire codant les données de connexion. Exemple : 35 36 { 37 "region_name" : "fra1", 38 "endpoint_url" : "https://fra1.digitaloceanspaces.com", 39 "bucket" : "maquisdoc-math", 40 "prefix" : "math-exos/" 41 } 42 43 Les données secrètes de connexion sont dans le fichier local 44 `~/.aws/credentials`. 45 46 - apublier_data : liste de dictionnaires donnant les timestamps 47 des fichiers susceptibles d'être publiés. 48 49 [{chemin de fichier local : timestamp du fichier},] 50 51 #### Renvoie 52 53 None. 54 55 """ 56 connect_data = data['credentials'] 57 self.log = "\t Initialisation de la classe Espace \n" 58 r_n = connect_data['region_name'] 59 e_u = connect_data['endpoint_url'] 60 self.bck_nom = connect_data['bucket'] 61 self.client = boto3.client('s3', region_name=r_n, endpoint_url=e_u) 62 s3 = boto3.resource('s3', region_name=r_n, endpoint_url=e_u) 63 self.bucket = s3.Bucket(connect_data['bucket']) 64 self.prefix = connect_data['prefix'] 65 self.apublier = apublier_data # dictio path: date 66 67 self.bck_times = self.get_times() # dictio key: date 68 69 self.MAJ() 70 71 def get_times(self): 72 """ 73 Renvoie un dictionnaire donnant les dates de modifications des 74 fichiers de l'espace. 75 76 clé = key d'un fichier du dépôt dans l'espace 77 valeur = date modification 78 """ 79 key_times = {} 80 for obj in self.bucket.objects.all(): 81 if (obj.key.startswith(self.prefix) 82 and len(obj.key) > len(self.prefix)): 83 key_times[obj.key] = obj.last_modified 84 self.log += '\t \t ' + str(len(key_times)) + " clés dans l'espace \n" 85 return key_times 86 87 def del_objs(self, keys): 88 """ 89 Supprime des objets et rend compte dans le journal. 90 91 #### Paramètres: 92 93 - `keys`: liste des clés à supprimer dans l'espace' 94 95 #### Renvoie 96 97 None 98 """ 99 objs = [] 100 for key in keys: 101 objs.append({'Key': key}) 102 response = self.bucket.delete_objects(Delete={'Objects': objs}) 103 if 'Deleted' in response: 104 self.log += "\n Journal des suppressions de l'espace: \n" 105 self.log += 'Deleted : ' 106 for deleted in response['Deleted']: 107 self.log += deleted['Key'] + ', ' 108 109 def upload_file(self, path, key): 110 """ 111 Upload un fichier dans l'espace. 112 113 Avec un ACL public et un Content-Type déduit de l'extension. 114 115 #### Parametres 116 117 - path : chemin du fichier à uploader dans l'espace. 118 - key : clé du fichier uploadé dans l'espace. 119 120 Renvoie 121 ------- 122 None. 123 124 """ 125 file_name = os.path.basename(path) 126 file_mime_type, encoding = mimetypes.guess_type(file_name) 127 self.bucket.upload_file(path, key, 128 ExtraArgs={'ACL': 'public-read', 129 'ContentType': file_mime_type}) 130 131 def MAJ(self): 132 """ 133 Mise à jour de l'espace. 134 135 Avec self.bck_times et self.apublier: 136 - supprime dans l'espace les clés 137 qui ne correspondent pas à un fichier 138 - si fichier plus récent que clé 139 upload du fichier dans la clé 140 """ 141 key_times = self.bck_times 142 fic_times = self.apublier 143 144 # supprimer les keys qui ne sont pas des fics 145 fics = [os.path.basename(t) for t in fic_times] 146 a_supprimer = [] 147 for key in key_times: 148 if os.path.basename(key) not in fics: 149 a_supprimer.append(key) 150 self.log += "\t \t " + str(len(a_supprimer)) + " clés à supprimer \n" 151 self.del_objs(a_supprimer) 152 153 # mettre à jour les keys correspondant à des fics 154 self.log += "\t \t clés à créer ou mettre à jour \n" 155 for path in fic_times: 156 file_name = os.path.basename(path) 157 key_a_updater = self.prefix + file_name 158 fic_a_updater_time = fic_times[path] 159 #self.log += '\t \t ' ajoute des espaces inutiles si rien à updater 160 if key_a_updater in key_times: 161 key_a_updater_time = key_times[key_a_updater].timestamp() 162 if fic_a_updater_time > key_a_updater_time: 163 self.log += key_a_updater + " : upload (maj)\n " 164 self.upload_file(path, key_a_updater) 165 else: 166 self.log += key_a_updater + " : upload (créer) \n " 167 self.upload_file(path, key_a_updater) 168 self.log += '\n' 169 170 """ 171 Méthodes qui ne sont pas utilisées 172 173 def put_objs(self, keys): 174 #Place (put) les objets de la liste keys dans l'espace. 175 #Cette méthode n'est pas utilisé actuellement, remplacée par upload' 176 177 jrnl = '' 178 bck = self.bck_nom 179 acl = 'public-read' 180 ct = 'application/pdf' 181 for key in keys: 182 bdy = open(key, mode='rb') 183 response = self.client.put_object(ACL=acl, Body=bdy, Bucket=bck, 184 Key=key, ContentType=ct) 185 print(str(response)) 186 jrnl += '\n ' + key + ' : ' + str(response) + '\n' 187 return jrnl 188 189 def get_private_objs(self): 190 #renvoie la liste des objets avec un acl privé 191 192 bck = self.bck_nom 193 response = self.client.list_objects_v2(Bucket=bck, MaxKeys=1000) 194 objs = [content['Key']for content in response['Contents']] 195 objs_p = copy.copy(objs) 196 for key in objs: 197 response = self.client.get_object_acl(Bucket=bck, Key=key) 198 for grant in response['Grants']: 199 permission = grant['Permission'] 200 grantee = grant['Grantee'] 201 # print(key, grantee) 202 if 'URI' in grantee: 203 grp = os.path.basename(grantee['URI']) 204 # print(key, grp, permission) 205 if (grp == 'AllUsers') and (permission == 'READ'): 206 objs_p.remove(key) 207 return objs_p 208 209 def list_buckets(self): 210 response = self.client.list_buckets() 211 spaces = [space['Name'] for space in response['Buckets']] 212 print("Spaces List: %s" % spaces) 213 214 def get_objs(self): 215 bck = self.bck_nom 216 response = self.client.list_objects_v2(Bucket=bck, MaxKeys=1000) 217 objs_t = [[content['Key'], content['LastModified'].timestamp()] 218 for content in response['Contents']] 219 return objs_t 220 221 """
19class Espace: 20 """ 21 Classe Espace. 22 23 """ 24 25 def __init__(self, data, apublier_data): 26 """ 27 Initialise la classe et met à jour l'espace. 28 29 Pour les fichiers publiables cad ceux de "apublier_data": 30 31 - récupère les clés associées et leur timestamp dans l'espace 32 - si le fichier local est plus récent: 33 upload sur l'espace 34 #### Paramètres: 35 - data = `manifeste['espace']: dictionnaire codant les données de connexion. Exemple : 36 37 { 38 "region_name" : "fra1", 39 "endpoint_url" : "https://fra1.digitaloceanspaces.com", 40 "bucket" : "maquisdoc-math", 41 "prefix" : "math-exos/" 42 } 43 44 Les données secrètes de connexion sont dans le fichier local 45 `~/.aws/credentials`. 46 47 - apublier_data : liste de dictionnaires donnant les timestamps 48 des fichiers susceptibles d'être publiés. 49 50 [{chemin de fichier local : timestamp du fichier},] 51 52 #### Renvoie 53 54 None. 55 56 """ 57 connect_data = data['credentials'] 58 self.log = "\t Initialisation de la classe Espace \n" 59 r_n = connect_data['region_name'] 60 e_u = connect_data['endpoint_url'] 61 self.bck_nom = connect_data['bucket'] 62 self.client = boto3.client('s3', region_name=r_n, endpoint_url=e_u) 63 s3 = boto3.resource('s3', region_name=r_n, endpoint_url=e_u) 64 self.bucket = s3.Bucket(connect_data['bucket']) 65 self.prefix = connect_data['prefix'] 66 self.apublier = apublier_data # dictio path: date 67 68 self.bck_times = self.get_times() # dictio key: date 69 70 self.MAJ() 71 72 def get_times(self): 73 """ 74 Renvoie un dictionnaire donnant les dates de modifications des 75 fichiers de l'espace. 76 77 clé = key d'un fichier du dépôt dans l'espace 78 valeur = date modification 79 """ 80 key_times = {} 81 for obj in self.bucket.objects.all(): 82 if (obj.key.startswith(self.prefix) 83 and len(obj.key) > len(self.prefix)): 84 key_times[obj.key] = obj.last_modified 85 self.log += '\t \t ' + str(len(key_times)) + " clés dans l'espace \n" 86 return key_times 87 88 def del_objs(self, keys): 89 """ 90 Supprime des objets et rend compte dans le journal. 91 92 #### Paramètres: 93 94 - `keys`: liste des clés à supprimer dans l'espace' 95 96 #### Renvoie 97 98 None 99 """ 100 objs = [] 101 for key in keys: 102 objs.append({'Key': key}) 103 response = self.bucket.delete_objects(Delete={'Objects': objs}) 104 if 'Deleted' in response: 105 self.log += "\n Journal des suppressions de l'espace: \n" 106 self.log += 'Deleted : ' 107 for deleted in response['Deleted']: 108 self.log += deleted['Key'] + ', ' 109 110 def upload_file(self, path, key): 111 """ 112 Upload un fichier dans l'espace. 113 114 Avec un ACL public et un Content-Type déduit de l'extension. 115 116 #### Parametres 117 118 - path : chemin du fichier à uploader dans l'espace. 119 - key : clé du fichier uploadé dans l'espace. 120 121 Renvoie 122 ------- 123 None. 124 125 """ 126 file_name = os.path.basename(path) 127 file_mime_type, encoding = mimetypes.guess_type(file_name) 128 self.bucket.upload_file(path, key, 129 ExtraArgs={'ACL': 'public-read', 130 'ContentType': file_mime_type}) 131 132 def MAJ(self): 133 """ 134 Mise à jour de l'espace. 135 136 Avec self.bck_times et self.apublier: 137 - supprime dans l'espace les clés 138 qui ne correspondent pas à un fichier 139 - si fichier plus récent que clé 140 upload du fichier dans la clé 141 """ 142 key_times = self.bck_times 143 fic_times = self.apublier 144 145 # supprimer les keys qui ne sont pas des fics 146 fics = [os.path.basename(t) for t in fic_times] 147 a_supprimer = [] 148 for key in key_times: 149 if os.path.basename(key) not in fics: 150 a_supprimer.append(key) 151 self.log += "\t \t " + str(len(a_supprimer)) + " clés à supprimer \n" 152 self.del_objs(a_supprimer) 153 154 # mettre à jour les keys correspondant à des fics 155 self.log += "\t \t clés à créer ou mettre à jour \n" 156 for path in fic_times: 157 file_name = os.path.basename(path) 158 key_a_updater = self.prefix + file_name 159 fic_a_updater_time = fic_times[path] 160 #self.log += '\t \t ' ajoute des espaces inutiles si rien à updater 161 if key_a_updater in key_times: 162 key_a_updater_time = key_times[key_a_updater].timestamp() 163 if fic_a_updater_time > key_a_updater_time: 164 self.log += key_a_updater + " : upload (maj)\n " 165 self.upload_file(path, key_a_updater) 166 else: 167 self.log += key_a_updater + " : upload (créer) \n " 168 self.upload_file(path, key_a_updater) 169 self.log += '\n' 170 171 """ 172 Méthodes qui ne sont pas utilisées 173 174 def put_objs(self, keys): 175 #Place (put) les objets de la liste keys dans l'espace. 176 #Cette méthode n'est pas utilisé actuellement, remplacée par upload' 177 178 jrnl = '' 179 bck = self.bck_nom 180 acl = 'public-read' 181 ct = 'application/pdf' 182 for key in keys: 183 bdy = open(key, mode='rb') 184 response = self.client.put_object(ACL=acl, Body=bdy, Bucket=bck, 185 Key=key, ContentType=ct) 186 print(str(response)) 187 jrnl += '\n ' + key + ' : ' + str(response) + '\n' 188 return jrnl 189 190 def get_private_objs(self): 191 #renvoie la liste des objets avec un acl privé 192 193 bck = self.bck_nom 194 response = self.client.list_objects_v2(Bucket=bck, MaxKeys=1000) 195 objs = [content['Key']for content in response['Contents']] 196 objs_p = copy.copy(objs) 197 for key in objs: 198 response = self.client.get_object_acl(Bucket=bck, Key=key) 199 for grant in response['Grants']: 200 permission = grant['Permission'] 201 grantee = grant['Grantee'] 202 # print(key, grantee) 203 if 'URI' in grantee: 204 grp = os.path.basename(grantee['URI']) 205 # print(key, grp, permission) 206 if (grp == 'AllUsers') and (permission == 'READ'): 207 objs_p.remove(key) 208 return objs_p 209 210 def list_buckets(self): 211 response = self.client.list_buckets() 212 spaces = [space['Name'] for space in response['Buckets']] 213 print("Spaces List: %s" % spaces) 214 215 def get_objs(self): 216 bck = self.bck_nom 217 response = self.client.list_objects_v2(Bucket=bck, MaxKeys=1000) 218 objs_t = [[content['Key'], content['LastModified'].timestamp()] 219 for content in response['Contents']] 220 return objs_t 221 222 """
Classe Espace.
25 def __init__(self, data, apublier_data): 26 """ 27 Initialise la classe et met à jour l'espace. 28 29 Pour les fichiers publiables cad ceux de "apublier_data": 30 31 - récupère les clés associées et leur timestamp dans l'espace 32 - si le fichier local est plus récent: 33 upload sur l'espace 34 #### Paramètres: 35 - data = `manifeste['espace']: dictionnaire codant les données de connexion. Exemple : 36 37 { 38 "region_name" : "fra1", 39 "endpoint_url" : "https://fra1.digitaloceanspaces.com", 40 "bucket" : "maquisdoc-math", 41 "prefix" : "math-exos/" 42 } 43 44 Les données secrètes de connexion sont dans le fichier local 45 `~/.aws/credentials`. 46 47 - apublier_data : liste de dictionnaires donnant les timestamps 48 des fichiers susceptibles d'être publiés. 49 50 [{chemin de fichier local : timestamp du fichier},] 51 52 #### Renvoie 53 54 None. 55 56 """ 57 connect_data = data['credentials'] 58 self.log = "\t Initialisation de la classe Espace \n" 59 r_n = connect_data['region_name'] 60 e_u = connect_data['endpoint_url'] 61 self.bck_nom = connect_data['bucket'] 62 self.client = boto3.client('s3', region_name=r_n, endpoint_url=e_u) 63 s3 = boto3.resource('s3', region_name=r_n, endpoint_url=e_u) 64 self.bucket = s3.Bucket(connect_data['bucket']) 65 self.prefix = connect_data['prefix'] 66 self.apublier = apublier_data # dictio path: date 67 68 self.bck_times = self.get_times() # dictio key: date 69 70 self.MAJ()
Initialise la classe et met à jour l'espace.
Pour les fichiers publiables cad ceux de "apublier_data":
- récupère les clés associées et leur timestamp dans l'espace
si le fichier local est plus récent: upload sur l'espace
Paramètres:
data = `manifeste['espace']: dictionnaire codant les données de connexion. Exemple :
{ "region_name" : "fra1", "endpoint_url" : "https://fra1.digitaloceanspaces.com", "bucket" : "maquisdoc-math", "prefix" : "math-exos/" }Les données secrètes de connexion sont dans le fichier local
~/.aws/credentials.apublier_data : liste de dictionnaires donnant les timestamps des fichiers susceptibles d'être publiés.
[{chemin de fichier local : timestamp du fichier},]
Renvoie
None.
72 def get_times(self): 73 """ 74 Renvoie un dictionnaire donnant les dates de modifications des 75 fichiers de l'espace. 76 77 clé = key d'un fichier du dépôt dans l'espace 78 valeur = date modification 79 """ 80 key_times = {} 81 for obj in self.bucket.objects.all(): 82 if (obj.key.startswith(self.prefix) 83 and len(obj.key) > len(self.prefix)): 84 key_times[obj.key] = obj.last_modified 85 self.log += '\t \t ' + str(len(key_times)) + " clés dans l'espace \n" 86 return key_times
Renvoie un dictionnaire donnant les dates de modifications des fichiers de l'espace.
clé = key d'un fichier du dépôt dans l'espace
valeur = date modification
88 def del_objs(self, keys): 89 """ 90 Supprime des objets et rend compte dans le journal. 91 92 #### Paramètres: 93 94 - `keys`: liste des clés à supprimer dans l'espace' 95 96 #### Renvoie 97 98 None 99 """ 100 objs = [] 101 for key in keys: 102 objs.append({'Key': key}) 103 response = self.bucket.delete_objects(Delete={'Objects': objs}) 104 if 'Deleted' in response: 105 self.log += "\n Journal des suppressions de l'espace: \n" 106 self.log += 'Deleted : ' 107 for deleted in response['Deleted']: 108 self.log += deleted['Key'] + ', '
Supprime des objets et rend compte dans le journal.
Paramètres:
keys: liste des clés à supprimer dans l'espace'
Renvoie
None
110 def upload_file(self, path, key): 111 """ 112 Upload un fichier dans l'espace. 113 114 Avec un ACL public et un Content-Type déduit de l'extension. 115 116 #### Parametres 117 118 - path : chemin du fichier à uploader dans l'espace. 119 - key : clé du fichier uploadé dans l'espace. 120 121 Renvoie 122 ------- 123 None. 124 125 """ 126 file_name = os.path.basename(path) 127 file_mime_type, encoding = mimetypes.guess_type(file_name) 128 self.bucket.upload_file(path, key, 129 ExtraArgs={'ACL': 'public-read', 130 'ContentType': file_mime_type})
Upload un fichier dans l'espace.
Avec un ACL public et un Content-Type déduit de l'extension.
Parametres
- path : chemin du fichier à uploader dans l'espace.
- key : clé du fichier uploadé dans l'espace.
Renvoie
None.
132 def MAJ(self): 133 """ 134 Mise à jour de l'espace. 135 136 Avec self.bck_times et self.apublier: 137 - supprime dans l'espace les clés 138 qui ne correspondent pas à un fichier 139 - si fichier plus récent que clé 140 upload du fichier dans la clé 141 """ 142 key_times = self.bck_times 143 fic_times = self.apublier 144 145 # supprimer les keys qui ne sont pas des fics 146 fics = [os.path.basename(t) for t in fic_times] 147 a_supprimer = [] 148 for key in key_times: 149 if os.path.basename(key) not in fics: 150 a_supprimer.append(key) 151 self.log += "\t \t " + str(len(a_supprimer)) + " clés à supprimer \n" 152 self.del_objs(a_supprimer) 153 154 # mettre à jour les keys correspondant à des fics 155 self.log += "\t \t clés à créer ou mettre à jour \n" 156 for path in fic_times: 157 file_name = os.path.basename(path) 158 key_a_updater = self.prefix + file_name 159 fic_a_updater_time = fic_times[path] 160 #self.log += '\t \t ' ajoute des espaces inutiles si rien à updater 161 if key_a_updater in key_times: 162 key_a_updater_time = key_times[key_a_updater].timestamp() 163 if fic_a_updater_time > key_a_updater_time: 164 self.log += key_a_updater + " : upload (maj)\n " 165 self.upload_file(path, key_a_updater) 166 else: 167 self.log += key_a_updater + " : upload (créer) \n " 168 self.upload_file(path, key_a_updater) 169 self.log += '\n'
Mise à jour de l'espace.
Avec self.bck_times et self.apublier: - supprime dans l'espace les clés qui ne correspondent pas à un fichier - si fichier plus récent que clé upload du fichier dans la clé