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

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.
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
+ }