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é