csc-cia-stne 0.0.43__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
csc_cia_stne/bc_sta.py ADDED
@@ -0,0 +1,416 @@
1
+ from requests.auth import HTTPBasicAuth
2
+ import requests
3
+ import xml.etree.ElementTree as ET
4
+ import hashlib
5
+ from pydantic import BaseModel, ValidationError, field_validator
6
+ from typing import Literal, Dict, Union, Optional
7
+
8
+ # Validações dos inputs
9
+ class InitParamsValidator(BaseModel):
10
+ """
11
+ Classe responsável por validar os parâmetros de inicialização.
12
+ Atributos:
13
+ usuario (str): O nome de usuário.
14
+ senha (str): A senha do usuário.
15
+ ambiente (Literal["prd", "hml"]): O ambiente, que pode ser "prd" (produção) ou "hml" (homologação).
16
+ Métodos:
17
+ check_non_empty_string(value, info): Método de validação para garantir que cada parâmetro é uma string não vazia.
18
+ """
19
+
20
+ usuario: str
21
+ senha: str
22
+ ambiente: Literal["prd", "hml"] # Aceita apenas "prd" ou "sdx"
23
+
24
+ # Validação para garantir que cada parâmetro é uma string não vazia
25
+ """
26
+ Método de validação para garantir que cada parâmetro é uma string não vazia.
27
+ Parâmetros:
28
+ value (Any): O valor do parâmetro a ser validado.
29
+ info (FieldInfo): As informações do campo a ser validado.
30
+ Retorna:
31
+ value (Any): O valor do parâmetro validado.
32
+ Lança:
33
+ ValueError: Se o valor do parâmetro não for uma string não vazia.
34
+ """
35
+ @field_validator('usuario', 'senha')
36
+ def check_non_empty_string(cls, value, info):
37
+ if not isinstance(value, str) or not value.strip():
38
+
39
+ raise ValueError(f"O parâmetro '{info.field_name}' deve ser uma string não vazia.")
40
+
41
+ return value
42
+
43
+ class EnviarArquivoValidator(BaseModel):
44
+ """
45
+ Classe responsável por validar os parâmetros de envio de arquivo.
46
+ Args:
47
+ tipo_arquivo (str): O código do tipo de envio a ser utilizado para o STA.
48
+ Verificar códigos disponíveis na documentação STA.
49
+ file_content (bytes): O conteúdo do arquivo a ser enviado, em formato de bytes.
50
+ nome_arquivo (str): O nome do arquivo a ser enviado.
51
+ Deve ser uma string não vazia e terminar com uma extensão de arquivo comum.
52
+ observacao (str): A observação relacionada ao envio do arquivo.
53
+ Deve ser uma string não vazia.
54
+ destinatarios (Optional[Union[Dict[str, str], str]], optional): Os destinatários do arquivo.
55
+ Pode ser um dicionário ou uma string XML. Defaults to None.
56
+ Raises:
57
+ ValueError: Se algum dos parâmetros não atender às condições de validação.
58
+ Returns:
59
+ O valor de cada parâmetro, se a validação for bem-sucedida.
60
+ """
61
+ tipo_arquivo:str
62
+ file_content:bytes
63
+ nome_arquivo:str
64
+ observacao:str
65
+ destinatarios:Optional[Union[Dict[str, str], str]] = None # Aceita um dicionário
66
+
67
+ @field_validator("tipo_arquivo")
68
+ def check_tipo_arquivo(cls, value):
69
+ if not isinstance(value, str) or not value.strip():
70
+
71
+ raise ValueError("O parâmetro 'tipo_arquivo' deve ser uma string não vazia, com o código do tipo de envio a ser utilizado para o STA, verificar codigos disponíveis na documentação STA")
72
+
73
+ return value
74
+
75
+ # Validador para file_content: deve ter conteúdo em bytes
76
+ @field_validator("file_content")
77
+ def check_file_content(cls, value):
78
+ if not isinstance(value, bytes) or len(value) == 0:
79
+ raise ValueError("O parâmetro 'file_content' deve ser um byte array não vazio.")
80
+ return value
81
+
82
+ # Validador para nome_arquivo: deve ser uma string não vazia e terminar com uma extensão de arquivo comum
83
+ @field_validator("nome_arquivo")
84
+ def check_nome_arquivo(cls, value):
85
+ if not isinstance(value, str) or not value.strip():
86
+ raise ValueError("O nome do arquivo deve ser uma string não vazia.")
87
+ if not value.lower().endswith(('.zip', '.xpto')):
88
+ raise ValueError("O nome do arquivo deve ter uma extensão válida.")
89
+ return value
90
+
91
+ @field_validator("observacao")
92
+ def check_observacao(cls, value):
93
+ if not isinstance(value, str) or not value.strip():
94
+
95
+ raise ValueError("O parâmetro 'observacao' deve ser uma string não vazia")
96
+
97
+ return value
98
+
99
+ # Validador para destinatarios: aceita um dicionário ou uma string XML opcional
100
+ @field_validator("destinatarios")
101
+ def check_destinatarios(cls, value):
102
+ if value is not None:
103
+
104
+ if not isinstance(value, list):
105
+
106
+ raise ValueError("O parâmetro 'destinatarios' deve ser uma lista de dicionários.")
107
+
108
+ for item in value:
109
+
110
+ if not isinstance(item, dict):
111
+
112
+ raise ValueError("Cada destinatário deve ser um dicionário.")
113
+
114
+ required_keys = {"unidade", "dependencia", "operador"}
115
+
116
+ if not required_keys.issubset(item.keys()):
117
+
118
+ raise ValueError("Cada destinatário deve conter as chaves 'unidade', 'dependencia' e 'operador'. Verifique a documentação da API BC STA para entender o que colocar cada campo")
119
+
120
+ return value
121
+
122
+ class BC_STA:
123
+
124
+ def __init__(self, usuario:str, senha:str, ambiente:str):
125
+ """
126
+ Inicializa uma instância da classe BC_STA.
127
+ Parâmetros:
128
+ - usuario (str): O nome de usuário para autenticação.
129
+ - senha (str): A senha para autenticação.
130
+ - ambiente (str): O ambiente de execução ('prd' para produção ou qualquer outro valor para ambiente de teste).
131
+ Raises:
132
+ - ValueError: Se houver erro na validação dos dados de input da inicialização da instância 'BC_STA'.
133
+ Exemplo de uso:
134
+ ```
135
+ bc_sta = BC_STA(usuario='meu_usuario', senha='minha_senha', ambiente='prd')
136
+ ```
137
+ """
138
+
139
+ try:
140
+
141
+ InitParamsValidator(usuario=usuario, senha=senha, ambiente=ambiente)
142
+
143
+ except ValidationError as e:
144
+
145
+ raise ValueError("Erro na validação dos dados de input da inicialização da instância 'BC_STA':", e.errors())
146
+
147
+ if ambiente == 'prd':
148
+
149
+ self.base_url = "https://sta.bcb.gov.br/staws"
150
+
151
+ else:
152
+
153
+ self.base_url = "https://sta-h.bcb.gov.br/staws"
154
+
155
+ try:
156
+
157
+ self.auth = HTTPBasicAuth(usuario,senha)
158
+ self.error = None
159
+ self.headers = {'Content-Type': 'application/xml'}
160
+ self.is_connected = self.verifica_conexao()
161
+
162
+ except Exception as e:
163
+
164
+ self.is_connected = False
165
+ self.error = e
166
+
167
+ def verifica_conexao(self):
168
+ """
169
+ Verifica a conexão com o servidor STA do Banco Central do Brasil.
170
+ Returns:
171
+ bool: True se a conexão for bem-sucedida, False caso contrário.
172
+ Raises:
173
+ Exception: Se ocorrer algum erro durante a verificação da conexão.
174
+ Example:
175
+ # Criando uma instância da classe
176
+ bc_sta = BCSTA()
177
+ # Verificando a conexão
178
+ conexao = bc_sta.verifica_conexao()
179
+ """
180
+ try:
181
+
182
+ response = requests.get("https://sta.bcb.gov.br/staws/arquivos?tipoConsulta=AVANC&nivelDetalhe=RES", auth=self.auth)
183
+
184
+ # Verificando o status e retornando a resposta
185
+ if response.status_code == 200:
186
+
187
+ return True
188
+
189
+ else:
190
+
191
+ return False
192
+
193
+
194
+ except Exception as e:
195
+
196
+ raise e
197
+
198
+ def enviar_arquivo(self, tipo_arquivo:str, file_content:bytes, nome_arquivo:str, observacao:str, destinatarios:dict=None):
199
+ """
200
+ Envia um arquivo para um determinado destino.
201
+ tipo_arquivo (str): O tipo de arquivo a ser enviado.
202
+ nome_arquivo (str): O nome do arquivo.
203
+ observacao (str): Uma observação opcional.
204
+ destinatarios (dict, optional): Um dicionário contendo informações sobre os destinatários do arquivo.
205
+ O dicionário deve ter a seguinte estrutura:
206
+ {
207
+ 'unidade': 'nome_da_unidade',
208
+ 'dependencia': 'nome_da_dependencia', # opcional
209
+ 'operador': 'nome_do_operador' # opcional
210
+ O campo 'dependencia' e 'operador' são opcionais.
211
+ - 'enviado': Um valor booleano indicando se o arquivo foi enviado com sucesso.
212
+ - 'protocolo': O protocolo gerado, caso o arquivo tenha sido enviado com sucesso.
213
+ - 'link': O link do arquivo, caso tenha sido enviado com sucesso.
214
+ - 'erro': Uma mensagem de erro, caso ocorra algum problema no envio do arquivo.
215
+ ValueError: Se ocorrer um erro na validação dos dados de entrada.
216
+ Exception: Se ocorrer um erro durante o envio do arquivo.
217
+ # Exemplo de chamada da função
218
+ tipo_arquivo = 'documento'
219
+ nome_arquivo = 'arquivo.txt'
220
+ observacao = 'Este é um arquivo de teste'
221
+ destinatarios = {
222
+ 'unidade': 'unidade_destino',
223
+ 'dependencia': 'dependencia_destino',
224
+ 'operador': 'operador_destino'
225
+ resposta = enviar_arquivo(tipo_arquivo, file_content, nome_arquivo, observacao, destinatarios)
226
+ """
227
+ try:
228
+
229
+ EnviarArquivoValidator(tipo_arquivo=tipo_arquivo, file_content=file_content, nome_arquivo=nome_arquivo, observacao=observacao, destinatarios=destinatarios)
230
+
231
+ except ValidationError as e:
232
+
233
+ raise ValueError("Erro na validação dos dados de input do método 'enviar_arquivo':", e.errors())
234
+
235
+ def generate_sha256_hash(file_content):
236
+ """
237
+ Gera o hash SHA-256 do conteúdo de um arquivo.
238
+ Args:
239
+ file_content (bytes): O conteúdo do arquivo como uma sequência de bytes.
240
+ Returns:
241
+ str: O hash SHA-256 do conteúdo do arquivo.
242
+ Example:
243
+ file_content = b'Lorem ipsum dolor sit amet'
244
+ hash_value = generate_sha256_hash(file_content)
245
+ print(hash_value)
246
+ """
247
+
248
+ # Gera o hash SHA-256 do arquivo
249
+ sha256_hash = hashlib.sha256()
250
+ sha256_hash.update(file_content)
251
+ return sha256_hash.hexdigest()
252
+
253
+ def process_response(xml_content):
254
+ """
255
+ Processa o conteúdo XML de resposta e retorna um dicionário com as informações relevantes.
256
+ Args:
257
+ xml_content (str): O conteúdo XML de resposta.
258
+ Returns:
259
+ dict: Um dicionário contendo as seguintes chaves:
260
+ - 'enviado': Um valor booleano indicando se o XML foi enviado com sucesso.
261
+ - 'protocolo': O protocolo extraído do XML, caso tenha sido enviado com sucesso.
262
+ - 'link': O link extraído do XML, caso tenha sido enviado com sucesso.
263
+ - 'erro': Uma mensagem de erro, caso ocorra algum problema no processamento do XML.
264
+ Raises:
265
+ None
266
+ Exemplo de uso:
267
+ xml = "<root>...</root>"
268
+ resposta = process_response(xml)
269
+ print(resposta)
270
+ """
271
+
272
+ try:
273
+
274
+ root = ET.fromstring(xml_content)
275
+
276
+ # Verifica se há um elemento <Erro> no XML
277
+ erro = None
278
+ erro_elem = root.find('Erro')
279
+
280
+ if erro_elem is not None:
281
+
282
+ codigo_erro = erro_elem.find('Codigo').text
283
+ descricao_erro = erro_elem.find('Descricao').text
284
+ erro = f"Erro {codigo_erro}: {descricao_erro}"
285
+ resposta = {
286
+ 'enviado':False,
287
+ 'protocolo': None,
288
+ 'link': None,
289
+ 'erro': erro
290
+ }
291
+
292
+ else:
293
+
294
+ protocolo = root.find('Protocolo').text
295
+ link = root.find('.//atom:link', namespaces={'atom': 'http://www.w3.org/2005/Atom'}).attrib['href']
296
+ resposta = {
297
+ 'enviado':True,
298
+ 'protocolo': protocolo,
299
+ 'link': link,
300
+ 'erro': None
301
+ }
302
+
303
+ return resposta
304
+
305
+ except ET.ParseError as e:
306
+
307
+ resposta = {
308
+ 'enviado': False,
309
+ 'protocolo': None,
310
+ 'link': None,
311
+ 'erro': f"Error processing XML: {str(e)}"
312
+ }
313
+ return resposta
314
+
315
+ url = self.base_url + '/arquivos'
316
+
317
+ # Calcula o hash SHA-256 do conteúdo do arquivo
318
+ hash_sha256 = generate_sha256_hash(file_content)
319
+ tamanho_arquivo = len(file_content) # Tamanho do arquivo em bytes
320
+
321
+ # Constrói o XML de requisição
322
+ parametros = ET.Element('Parametros')
323
+ ET.SubElement(parametros, 'IdentificadorDocumento').text = tipo_arquivo
324
+ ET.SubElement(parametros, 'Hash').text = hash_sha256
325
+ ET.SubElement(parametros, 'Tamanho').text = str(tamanho_arquivo)
326
+ ET.SubElement(parametros, 'NomeArquivo').text = nome_arquivo
327
+
328
+ # Campo observação é opcional
329
+ if observacao:
330
+ ET.SubElement(parametros, 'Observacao').text = observacao
331
+
332
+ # Campo destinatários é opcional
333
+ if destinatarios:
334
+
335
+ destinatarios_elem = ET.SubElement(parametros, 'Destinatarios')
336
+
337
+ for dest in destinatarios:
338
+
339
+ destinatario_elem = ET.SubElement(destinatarios_elem, 'Destinatario')
340
+ ET.SubElement(destinatario_elem, 'Unidade').text = dest['unidade']
341
+
342
+ if 'dependencia' in dest:
343
+
344
+ ET.SubElement(destinatario_elem, 'Dependencia').text = dest['dependencia']
345
+
346
+ if 'operador' in dest:
347
+
348
+ ET.SubElement(destinatario_elem, 'Operador').text = dest['operador']
349
+
350
+ # Converte o XML para string
351
+ xml_data = ET.tostring(parametros, encoding='utf-8', method='xml')
352
+
353
+ # Envia a requisição POST
354
+ response = requests.post(url, headers=self.headers, data=xml_data, auth=self.auth, timeout=60)
355
+
356
+ if response.status_code == 201: # Verifica se o protocolo foi criado com sucesso
357
+
358
+ resultado_protocolo = process_response(response.text)
359
+
360
+ # Protocolo gerado, prosseguir com envio do arquivo
361
+ if resultado_protocolo["enviado"]:
362
+
363
+ try:
364
+
365
+ # Solicita o envio
366
+ protocolo = resultado_protocolo["protocolo"]
367
+ # URL do endpoint, incluindo o protocolo
368
+ url = url + f"/{protocolo}/conteudo"
369
+
370
+ # Envia a requisição PUT com o conteúdo binário do arquivo
371
+ response = requests.put(url, data=file_content, auth=self.auth, timeout=60)
372
+
373
+ if response.status_code == 200:
374
+
375
+ return resultado_protocolo
376
+
377
+ else:
378
+
379
+ resposta = {
380
+ 'enviado':False,
381
+ 'protocolo': None,
382
+ 'link': None,
383
+ 'erro': f"Falha ao enviar arquivo. Status code: {response.status_code}, Text: {response.text}, Reason: {response.reason}"
384
+ }
385
+ return resposta
386
+
387
+ except Exception as e:
388
+
389
+ erro = str(e)
390
+ resposta = {
391
+ 'enviado':False,
392
+ 'protocolo': None,
393
+ 'link': None,
394
+ 'erro': erro
395
+ }
396
+ return resposta
397
+
398
+
399
+
400
+ # Protocolo não foi gerado, retornar erro
401
+ else:
402
+
403
+ return resultado_protocolo
404
+
405
+ else:
406
+
407
+ print(response.text)
408
+ resposta = {
409
+ 'enviado': False,
410
+ 'protocolo': None,
411
+ 'link': None,
412
+ 'erro': f"Failed to create protocol. Status code: {response.status_code}, Reason: {response.reason}"
413
+ }
414
+ #return f"Failed to create protocol. Status code: {response.status_code}, Reason: {response.reason}"
415
+ return resposta
416
+
csc_cia_stne/email.py ADDED
@@ -0,0 +1,239 @@
1
+ import smtplib
2
+ from email.mime.multipart import MIMEMultipart
3
+ from email.mime.text import MIMEText
4
+ from email.mime.base import MIMEBase
5
+ from email import encoders
6
+ from pydantic import BaseModel, ValidationError, field_validator
7
+
8
+ class InitParamsValidator(BaseModel):
9
+ """
10
+ Classe para validar os parâmetros de inicialização.
11
+ Atributos:
12
+ - email_sender (str): O endereço de e-mail do remetente.
13
+ - email_password (str): A senha do e-mail.
14
+ Métodos:
15
+ - check_str_input(value, info): Valida se o valor é uma string não vazia.
16
+ """
17
+ email_sender: str
18
+ email_password: str
19
+
20
+ """
21
+ Valida se o valor é uma string não vazia.
22
+ Parâmetros:
23
+ - value: O valor a ser validado.
24
+ - info: Informações sobre o campo.
25
+ Retorna:
26
+ - value: O valor validado.
27
+ Lança:
28
+ - ValueError: Se o valor não for uma string ou estiver vazio.
29
+ """
30
+ @field_validator('email_sender','email_password')
31
+ def check_str_input(cls, value, info):
32
+ if not isinstance(value, str) or not value.strip():
33
+ raise ValueError(f"O campo '{info.field_name}' deve ser strings e não {type(value)}")
34
+ return value
35
+
36
+ class SendEmailParamsValidator(BaseModel):
37
+ """
38
+ Classe para validar os parâmetros de envio de e-mail.
39
+ Atributos:
40
+ - to (list): Lista de destinatários do e-mail.
41
+ - message (str): Mensagem do e-mail.
42
+ - title (str): Título do e-mail.
43
+ - reply_to (str): Endereço de e-mail para resposta.
44
+ - attachments (list, opcional): Lista de anexos do e-mail.
45
+ - cc (list, opcional): Lista de destinatários em cópia do e-mail.
46
+ - cco (list, opcional): Lista de destinatários em cópia oculta do e-mail.
47
+ """
48
+
49
+ to: list
50
+ message: str
51
+ title: str
52
+ reply_to:str
53
+ attachments: list = []
54
+ cc: list = []
55
+ cco: list = []
56
+
57
+ """
58
+ Valida se o valor é uma string não vazia.
59
+ Parâmetros:
60
+ - value (str): Valor a ser validado.
61
+ - info (FieldInfo): Informações sobre o campo.
62
+ Retorna:
63
+ - str: O valor validado.
64
+ Lança:
65
+ - ValueError: Se o valor não for uma string ou estiver vazio.
66
+ """
67
+ @field_validator('message','title','reply_to')
68
+ def check_str_input(cls, value, info):
69
+ if not isinstance(value, str) or not value.strip():
70
+ raise ValueError(f"O campo '{info.field_name}' deve ser strings e não {type(value)}")
71
+ return value
72
+
73
+ """
74
+ Valida se o valor é uma lista.
75
+ Parâmetros:
76
+ - value (list): Valor a ser validado.
77
+ - info (FieldInfo): Informações sobre o campo.
78
+ Retorna:
79
+ - list: O valor validado.
80
+ Lança:
81
+ - ValueError: Se o valor não for uma lista.
82
+ """
83
+ @field_validator('to','attachments','cc','cco')
84
+ def check_list_input(cls, value, info):
85
+ if not isinstance(value, list):
86
+ raise ValueError(f"O parametro '{info.field_name}' deve ser uma lista")
87
+
88
+ return value
89
+
90
+ class Email():
91
+
92
+ def __init__(self, email_sender, email_password):
93
+ """
94
+ Inicializa uma instância da classe Email.
95
+ Args:
96
+ email_sender (str): O endereço de e-mail do remetente.
97
+ email_password (str): A senha do e-mail do remetente.
98
+ Raises:
99
+ ValueError: Se houver um erro na validação dos dados de entrada da inicialização da instância.
100
+ Returns:
101
+ None
102
+ """
103
+
104
+ self.email_sender = email_sender
105
+ self.email_password = email_password
106
+
107
+ try:
108
+
109
+ InitParamsValidator(email_sender=email_sender, email_password=email_password)
110
+
111
+ except ValidationError as e:
112
+
113
+ raise ValueError("Erro na validação dos dados de input da inicialização da instância:", e.errors())
114
+
115
+
116
+ self.server = self.login_email()
117
+
118
+ if not isinstance(self.server, smtplib.SMTP) and 'status' in self.server and not self.server['status']:
119
+
120
+ raise ValueError("Erro na validação dos dados de input da inicialização da instância:", self.server['error'])
121
+
122
+
123
+ def login_email(self):
124
+ """
125
+ Realiza o login no servidor de e-mail.
126
+ Returns:
127
+ smtplib.SMTP: Objeto que representa a conexão com o servidor de e-mail.
128
+ Raises:
129
+ dict: Dicionário contendo o status de erro caso ocorra uma exceção durante o login.
130
+ Example:
131
+ email_sender = 'seu_email@gmail.com'
132
+ email_password = 'sua_senha'
133
+ email = Email(email_sender, email_password)
134
+ server = email.login_email()
135
+ """
136
+
137
+ try:
138
+
139
+ server = smtplib.SMTP('smtp.gmail.com', 587)
140
+ server.starttls()
141
+ server.login(self.email_sender, self.email_password)
142
+
143
+ return server
144
+
145
+ except Exception as e:
146
+
147
+ return {
148
+ 'status':False,
149
+ 'error':str(e)
150
+ }
151
+
152
+
153
+ def send_email( self, to : list , message : str , title : str , reply_to: str, attachments : list = [] , cc : list = [] , cco : list = [] ) -> dict:
154
+ """
155
+ Envia um email com os parâmetros fornecidos.
156
+ Args:
157
+ to (list): Lista de destinatários do email.
158
+ message (str): Corpo do email.
159
+ title (str): Título do email.
160
+ reply_to (str): Endereço de email para resposta.
161
+ attachments (list, optional): Lista de caminhos dos arquivos anexos. Defaults to [].
162
+ cc (list, optional): Lista de destinatários em cópia. Defaults to [].
163
+ cco (list, optional): Lista de destinatários em cópia oculta. Defaults to [].
164
+ Returns:
165
+ dict: Dicionário com o status do envio do email. Se o envio for bem-sucedido, o dicionário terá a chave 'status' com valor True. Caso contrário, terá a chave 'status' com valor False e a chave 'error' com a descrição do erro.
166
+ Raises:
167
+ ValueError: Se houver erro na validação dos dados para o envio do email.
168
+ Example:
169
+ email = EmailSender()
170
+ to = ['example1@example.com', 'example2@example.com']
171
+ message = 'Olá, isso é um teste de email.'
172
+ title = 'Teste de Email'
173
+ reply_to = 'noreply@example.com'
174
+ attachments = ['/path/to/file1.txt', '/path/to/file2.txt']
175
+ cc = ['cc1@example.com', 'cc2@example.com']
176
+ cco = ['cco1@example.com', 'cco2@example.com']
177
+ result = email.send_email(to, message, title, reply_to, attachments, cc, cco)
178
+ print(result)
179
+ """
180
+
181
+ try:
182
+
183
+ SendEmailParamsValidator(to=to, message=message, title=title, reply_to=reply_to, attachments=attachments, cc=cc, cco=cco)
184
+
185
+ except ValidationError as e:
186
+
187
+ raise ValueError("Erro na validação dos dados para o envio do email:", e.errors())
188
+
189
+ try:
190
+
191
+ msg = MIMEMultipart()
192
+
193
+ msg["From"] = self.email_sender
194
+
195
+ msg["To"] = (",").join(to)
196
+
197
+ msg["cc"] = (",").join(cc)
198
+
199
+ msg['Reply-To'] = reply_to
200
+
201
+ msg["Subject"] = title
202
+
203
+ for file in attachments:
204
+
205
+ try:
206
+
207
+ attachment = open(file, "rb")
208
+
209
+ part = MIMEBase("application", "octet-stream")
210
+
211
+ part.set_payload(attachment.read())
212
+
213
+ encoders.encode_base64(part)
214
+
215
+ part.add_header("Content-Disposition", f"attachment; filename={file.split('/')[-1]}")
216
+
217
+ msg.attach(part)
218
+
219
+ attachment.close()
220
+
221
+ except Exception as e:
222
+
223
+ return {
224
+ 'status':False,
225
+ 'error':str(e)
226
+ }
227
+
228
+ msg.attach(MIMEText(message, 'html'))
229
+
230
+ self.server.sendmail(self.email_sender, to + cc + cco, msg.as_string())
231
+
232
+ return True
233
+
234
+ except Exception as e:
235
+
236
+ return {
237
+ 'status':False,
238
+ 'error':str(e)
239
+ }