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.
@@ -0,0 +1,689 @@
1
+ import requests
2
+ import base64, json
3
+ import os
4
+ import logging
5
+ from pydantic import ValidationError
6
+ from .utilitarios.validations.ServiceNowValidator import *
7
+
8
+ class ServiceNow:
9
+
10
+ def __init__(self, username: str = None, password: str = None, env: str = None) -> None:
11
+ """
12
+ Inicializa uma instância da classe ServiceNow.
13
+
14
+ Parâmetros:
15
+ username (str): Nome de usuário para autenticação. Obrigatório e não pode ser nulo ou vazio.
16
+ password (str): Senha para autenticação. Obrigatória e não pode ser nula ou vazia.
17
+ env (str): Ambiente no qual a instância será utilizada. Deve ser 'dev', 'qa', 'qas' ou 'prod'.
18
+
19
+ Raises:
20
+ ValueError: Caso qualquer um dos parâmetros não seja fornecido, seja nulo ou vazio, ou se 'env' não for um valor válido.
21
+
22
+ Atributos:
23
+ username (str): Nome de usuário normalizado.
24
+ password (str): Senha normalizada.
25
+ env (str): Nome do ambiente em letras maiúsculas.
26
+ api_url (str): URL base da API correspondente ao ambiente.
27
+ api_header (dict): Cabeçalhos padrão para requisições API.
28
+ """
29
+ try:
30
+ InitParamsValidator(username=username, password=password, env=env )
31
+ except ValidationError as e:
32
+ raise ValueError("Erro na validação dos dados de input da inicialização da instância 'ServiceNow':", e.errors())
33
+
34
+ # Normaliza o valor de 'env' para maiúsculas
35
+ env = env.strip().upper()
36
+
37
+ # Dicionário de ambientes válidos e URLs correspondentes
38
+ valid_envs = {
39
+ 'DEV': 'https://stonedev.service-now.com/api',
40
+ 'QA': 'https://stoneqas.service-now.com/api',
41
+ 'QAS': 'https://stoneqas.service-now.com/api',
42
+ 'PROD': 'https://stone.service-now.com/api'
43
+ }
44
+
45
+ # Verifica se 'env' é válido
46
+ if env not in valid_envs:
47
+ raise ValueError("O valor de 'env' precisa ser 'dev', 'qa', 'qas' ou 'prod'.")
48
+
49
+ # Atribui as variáveis de instância
50
+ self.__username = username.strip()
51
+ self.__password = password.strip()
52
+ self.env = env
53
+ self.api_url = valid_envs[env]
54
+ self.api_header = {"Content-Type":"application/json","Accept":"application/json"}
55
+
56
+
57
+ def __auth(self):
58
+ """
59
+ Retorna o e-mail e senha para realizar a autenticação.
60
+ """
61
+ return (self.__username, self.__password)
62
+
63
+
64
+ def request (self, url : str , params : str = "", timeout : int = 15):
65
+ """
66
+ Realiza uma requisição GET para a URL especificada.
67
+
68
+ Parâmetros:
69
+ url (str): URL para a qual a requisição será enviada.
70
+ params (dict, opcional): Parâmetros de consulta (query parameters) a serem incluídos na requisição.
71
+ Padrão é uma string vazia.
72
+
73
+ Retorno:
74
+ dict: Um dicionário contendo:
75
+ - success (bool): Indica se a requisição foi bem-sucedida.
76
+ - result (dict ou None): Resultado da resposta, se disponível. Contém o conteúdo JSON do campo "result".
77
+ - error (Exception ou None): Objeto de exceção em caso de erro, ou None se não houver erros.
78
+
79
+ Tratamento de exceções:
80
+ - requests.exceptions.HTTPError: Lança um erro HTTP caso o código de status indique falha.
81
+ - requests.exceptions.RequestException: Captura outros erros relacionados à requisição, como timeout ou conexão.
82
+ - Exception: Captura erros inesperados.
83
+
84
+ Logs:
85
+ - Logs de depuração são gerados para erros HTTP, erros de requisição e exceções inesperadas.
86
+ - Um aviso é registrado caso a resposta não contenha o campo "result".
87
+
88
+ Observações:
89
+ - Utiliza autenticação fornecida pelo método privado `__auth()`.
90
+ - Define um tempo limite (`timeout`) de 15 segundos.
91
+ - Verifica o certificado SSL (`verify=True`).
92
+ """
93
+ try:
94
+ RequestValidator(url=url, params=params, timeout=timeout)
95
+ except ValidationError as e:
96
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
97
+
98
+ try:
99
+ response = requests.get(
100
+ url,
101
+ params=params,
102
+ auth=self.__auth(),
103
+ headers=self.api_header,
104
+ timeout=timeout,
105
+ verify=True
106
+ )
107
+
108
+ # VALIDA SE HOUVE SUCESSO NA REQUISIÇÃO
109
+ response.raise_for_status()
110
+
111
+ # Analisa o conteúdo da resposta JSON
112
+ result = response.json()
113
+ if "result" in result:
114
+ return {"success" : True, "result" : result.get("result")}
115
+ else:
116
+ logging.debug("A resposta não contém o campo 'result'.")
117
+ return {"success" : False, "result" : result}
118
+
119
+ except requests.exceptions.HTTPError as http_err:
120
+ logging.debug(f"Erro HTTP ao buscar os detalhes do ticket: {http_err}")
121
+ return {"success" : False, "result" : None, "error" : str(http_err)}
122
+
123
+ except requests.exceptions.RequestException as req_err:
124
+ logging.debug(f"Erro ao buscar os detalhes do ticket: {req_err}")
125
+ return {"success" : False, "result" : None, "error" : str(req_err)}
126
+
127
+ except Exception as e:
128
+ logging.debug(f"Erro inesperado: {e}")
129
+ return {"success" : False, "result" : None, "error" : str(e)}
130
+
131
+
132
+ def __request_download(self, url, params = "", timeout = 15):
133
+ """
134
+ Realiza uma requisição GET para a URL especificada.
135
+
136
+ Parâmetros:
137
+ url (str): URL para a qual a requisição será enviada.
138
+ params (dict, opcional): Parâmetros de consulta (query parameters) a serem incluídos na requisição.
139
+ Padrão é uma string vazia.
140
+
141
+ Retorno:
142
+ dict: Um dicionário contendo:
143
+ - success (bool): Indica se a requisição foi bem-sucedida.
144
+ - result (dict ou None): Resultado da resposta, se disponível. Contém o conteúdo JSON do campo "result".
145
+ - error (Exception ou None): Objeto de exceção em caso de erro, ou None se não houver erros.
146
+
147
+ Tratamento de exceções:
148
+ - requests.exceptions.HTTPError: Lança um erro HTTP caso o código de status indique falha.
149
+ - requests.exceptions.RequestException: Captura outros erros relacionados à requisição, como timeout ou conexão.
150
+ - Exception: Captura erros inesperados.
151
+
152
+ Logs:
153
+ - Logs de depuração são gerados para erros HTTP, erros de requisição e exceções inesperadas.
154
+ - Um aviso é registrado caso a resposta não contenha o campo "result".
155
+
156
+ Observações:
157
+ - Utiliza autenticação fornecida pelo método privado `__auth()`.
158
+ - Define um tempo limite (`timeout`) de 15 segundos.
159
+ - Verifica o certificado SSL (`verify=True`).
160
+ """
161
+ try:
162
+ response = requests.get(
163
+ url,
164
+ params=params,
165
+ auth=self.__auth(),
166
+ headers=self.api_header,
167
+ timeout=timeout,
168
+ verify=True
169
+ )
170
+
171
+ # VALIDA SE HOUVE SUCESSO NA REQUISIÇÃO
172
+ response.raise_for_status()
173
+ # Analisa o conteúdo da resposta JSON
174
+ if response.status_code == 200:
175
+ return {"success" : True, "result" : response}
176
+ else:
177
+ logging.debug("Erro ao realizar a consulta")
178
+ return {"success" : False, "result" : response.status_code}
179
+
180
+ except requests.exceptions.HTTPError as http_err:
181
+ logging.debug(f"Erro HTTP ao buscar os detalhes do ticket: {http_err}")
182
+ return {"success" : False, "error" : str(http_err), "result" : None}
183
+
184
+ except requests.exceptions.RequestException as req_err:
185
+ logging.debug(f"Erro ao buscar os detalhes do ticket: {req_err}")
186
+ return {"success" : False, "error" : str(req_err), "result" : None}
187
+
188
+ except Exception as e:
189
+ logging.debug(f"Erro inesperado: {e}")
190
+ return {"success" : False, "error" : str(e), "result" : None}
191
+
192
+
193
+ def put(self, url, payload, timeout = 15):
194
+ """
195
+ Realiza uma requisição PUT para a URL especificada.
196
+
197
+ Parâmetros:
198
+ url (str): URL para a qual a requisição será enviada.
199
+ payload (dict): Dados a serem enviados no corpo da requisição. Será convertido para JSON.
200
+
201
+ Retorno:
202
+ dict: Um dicionário contendo:
203
+ - success (bool): Indica se a requisição foi bem-sucedida.
204
+ - result (dict ou None): Resultado da resposta, se disponível. Contém o conteúdo JSON do campo "result".
205
+
206
+ Tratamento de exceções:
207
+ - requests.exceptions.HTTPError: Lança um erro HTTP caso o código de status indique falha. O erro é registrado nos logs.
208
+ - requests.exceptions.RequestException: Captura outros erros relacionados à requisição, como timeout ou problemas de conexão. O erro é registrado nos logs.
209
+ - Exception: Captura erros inesperados e os registra nos logs.
210
+
211
+ Logs:
212
+ - Registra mensagens de depuração detalhadas quando a atualização é bem-sucedida ou quando a resposta não contém o campo "result".
213
+ - Registra mensagens de erro para exceções HTTP, erros de requisição e outros erros inesperados.
214
+
215
+ Observações:
216
+ - Utiliza autenticação fornecida pelo método privado `__auth()`.
217
+ - Define um tempo limite (`timeout`) de 15 segundos.
218
+ - Verifica o certificado SSL (`verify=True`).
219
+ - O cabeçalho da requisição é definido com `self.api_header`.
220
+ """
221
+ try:
222
+ PutValidator(url=url, payload=payload, timeout=timeout)
223
+ except ValidationError as e:
224
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
225
+
226
+ payload = json.dumps(payload)
227
+
228
+ try:
229
+ response = requests.put(
230
+ f"{url}",
231
+ auth=self.__auth(),
232
+ headers=self.api_header,
233
+ data=f"{payload}",
234
+ timeout=timeout,
235
+ verify=True
236
+ )
237
+
238
+ # VALIDA SE HOUVE SUCESSO NA REQUISIÇÃO
239
+ response.raise_for_status()
240
+
241
+ # POSSUINDO 'RESULT', TEREMOS O RETORNO DO TICKET ABERTO.
242
+ result = response.json()
243
+ if "result" in result:
244
+ update = result["result"]
245
+ logging.debug(f"Atualização concluída com sucesso. Registro atualizado: {update["sys_id"]} | Alterações: {payload}")
246
+ return {"success" : True, "result" : update}
247
+ else:
248
+ logging.debug(f"A Resposta da sua requisição não contém o campo 'Result'. Segue o retorno: \n {result} | Alterações: {payload}")
249
+ return {"success" : False, "result" : result}
250
+
251
+ #TRATAMENTOS DE ERRO
252
+ except requests.exceptions.HTTPError as http_err:
253
+ logging.debug(f"Erro HTTP ao tentar atualizar o ticket: {http_err} \n Reposta da solicitação: {response.json().get("error").get("message")}")
254
+ return {"success" : False, "error" : str(http_err) , "result" : None}
255
+
256
+ except requests.exceptions.RequestException as req_err:
257
+ logging.debug(f"Erro ao tentar atualizar o ticket: \n {req_err}")
258
+ return {"success" : False, "error" : str(req_err) , "result" : None}
259
+
260
+ except Exception as e:
261
+ logging.debug(f"Erro inesperado: \n {e}")
262
+ return {"success" : False, "error" : str(e) , "result" : None}
263
+
264
+
265
+ def post(self, url : str, variables : dict, header_content_type = "", timeout : int = 15):
266
+ """
267
+ Função para criar um novo ticket no servicenow usando o API REST.
268
+
269
+ Parametros:
270
+ - Payload (Dict): Dicionário contendo os dados que serão utilizados para criar o ticket
271
+ Retorno:
272
+ - Dict: Um dicionário contendo os detalhes do ticket criado
273
+ Raises:
274
+ - Exception: Se ocorrer um erro ao criar o ticket.
275
+
276
+ """
277
+ try:
278
+ PostValidator(url=url, variables=variables)
279
+ except ValidationError as e:
280
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
281
+
282
+
283
+ if header_content_type:
284
+ header = header_content_type
285
+ else:
286
+ header = self.api_header
287
+
288
+ # Ajustar o Payload para a abertura do ticket
289
+ payload = {
290
+ "sysparm_quantity" : "1",
291
+ "variables" : json.dumps(variables)
292
+ }
293
+ try:
294
+ response = requests.post(
295
+ f"{url}",
296
+ auth=self.__auth(),
297
+ headers=header,
298
+ data=f"{payload}",
299
+ timeout=timeout
300
+ )
301
+
302
+ # VALIDA SE HOUVE SUCESSO NA REQUISIÇÃO
303
+ response.raise_for_status()
304
+
305
+ # POSSUINDO 'RESULT', TEREMOS O RETORNO DO TICKET ABERTO.
306
+ result = response.json()
307
+ if "result" in result:
308
+ ticket_number = result["result"].get("number")
309
+ ticket_sys_id = result["result"].get("sys_id")
310
+ logging.debug(f"Ticket registrado com sucesso. Número: {ticket_number} | SYS_ID: {ticket_sys_id}")
311
+ return {"success" : True, "result" : result["result"]}
312
+ else:
313
+ logging.debug(f"A Resposta da sua requisição não contém o campo 'Result'. Segue o retorno: \n {result}")
314
+ return {"success" : False, "result" : result}
315
+
316
+ #TRATAMENTOS DE ERRO
317
+ except requests.exceptions.HTTPError as http_err:
318
+
319
+ logging.debug(f"Erro HTTP ao tentar registrar o ticket: {http_err} \n Reposta da solicitação: {response.json().get("error").get("message")}")
320
+ return {"success" : False, "error" : str(http_err) , "result" : None}
321
+
322
+ except requests.exceptions.RequestException as req_err:
323
+ logging.debug(f"Erro ao tentar registrar o ticket: \n {req_err}")
324
+ return {"success" : False, "error" : str(req_err) , "result" : None}
325
+
326
+ except Exception as e:
327
+ logging.debug(f"Erro inesperado: \n {e}")
328
+ return {"success" : False, "error" : str(e) , "result" : None}
329
+
330
+
331
+ def listar_tickets(self, tabela: str = None, campos: list = None, query: str = None, limite: int = 50, timeout:int=15, sysparm_display_value: str = "")->dict:
332
+ """lista tickets do ServiceNow
333
+
334
+ Args:
335
+ tabela (str): tabela do ServiceNow de onde a query será feita
336
+ campos (list): lista de campos com valores a trazer
337
+ query (str): query do ServiceNow
338
+ limite (int, optional): quantidade máxima de tickets para trazer. Default=50
339
+ timeout (int, optional): segundos para a requisicao dar timeout. Default=15
340
+
341
+ Returns:
342
+ dict: dicionário com o resultado da query
343
+ """
344
+ try:
345
+ ListTicketValidator(tabela=tabela, campos=campos, query=query, limite=limite, timeout=timeout)
346
+ except ValidationError as e:
347
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
348
+
349
+
350
+ params = {
351
+ "sysparm_query" : query,
352
+ "sysparm_fields" : ','.join(campos),
353
+ "sysparm_display_value" : sysparm_display_value,
354
+ "sysparm_limit" : limite
355
+ }
356
+
357
+ url = f"{self.api_url}/now/table/{tabela}"
358
+
359
+ try:
360
+ response = self.request(url=url, params=params, timeout=timeout)
361
+ return response
362
+
363
+ except Exception as e:
364
+ logging.debug(f"erro: {e}")
365
+ return str(e)
366
+
367
+
368
+ def update_ticket(self, tabela: str = None, sys_id: str = None, payload: dict = None, timeout:int=15)->dict:
369
+ """Atualiza as informações de um ticket
370
+
371
+ Args:
372
+ tabela (str): Tabela do ServiceNow de onde o ticket pertence
373
+ sys_id (str): sys_id do ticket a ser atualizado
374
+ campos (dict): Dicionário com os dados a serem atualizados no ticket
375
+ timeout (int, optional): segundos para a requisicao dar timeout. Default=15
376
+
377
+ Returns:
378
+ dict: resposta do ServiceNow
379
+ """
380
+ try:
381
+ UpdateTicketValidator(tabela=tabela, sys_id=sys_id, payload=payload, timeout=timeout)
382
+ except ValidationError as e:
383
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
384
+
385
+ payload["assigned_to"] = self.__username
386
+
387
+ try:
388
+
389
+ url = f"{self.api_url}/now/table/{tabela}/{sys_id}"
390
+ response = self.put(url, payload=payload, timeout=timeout)
391
+ return response
392
+
393
+ except Exception as e:
394
+ return str(e)
395
+
396
+
397
+ def anexar_arquivo_no_ticket(self,header_content_type:dict=None, anexo_path:str=None, tabela:str=None, sys_id:str=None, timeout:int=15):
398
+ """Anexa arquivo em um ticket do ServiceNow
399
+
400
+ Args:
401
+ header_content_type (dict): Dicionário contendo a chave 'Content-Type' com a especificação do tipo do arquivo
402
+ anexo_path (str): Path do arquivo a ser anexado
403
+ tabela (str): Tabela do ServiceNow de onde o ticket pertence
404
+ sys_id (str): sys_id do ticket o qual o arquivo será anexado
405
+ timeout (int, optional): segundos para a requisicao dar timeout. Default=15
406
+
407
+ Returns:
408
+ dict: resposta do ServiceNow
409
+ """
410
+ if header_content_type is None:
411
+ header_content_type = self.__valida_header_content_type(anexo_path, header_content_type)
412
+
413
+ try:
414
+ AttachFileTicketValidator(header_content_type=header_content_type, anexo_path=anexo_path, tabela=tabela, sys_id=sys_id, timeout=timeout)
415
+ except ValidationError as e:
416
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
417
+
418
+ if not os.path.exists(anexo_path):
419
+ raise FileExistsError(f"O arquivo não foi encontrado ({anexo_path})")
420
+
421
+
422
+
423
+ # Converte as chaves do dicionário para minúsculas
424
+ header_content_type_lower = {k.lower(): v for k, v in header_content_type.items()}
425
+
426
+
427
+ if "content-type" not in header_content_type_lower:
428
+ raise ValueError("O parâmetro 'header_content_type' não possui a chave 'Content-Type' com o tipo do anexo")
429
+
430
+ nome_arquivo = os.path.basename(anexo_path)
431
+ try:
432
+ with open(anexo_path, 'rb') as f:
433
+ url = f"{self.api_url}/now/attachment/file?table_name={tabela}&table_sys_id={sys_id}&file_name={nome_arquivo}"
434
+ response = requests.post(url, headers=header_content_type, auth=(self.__username, self.__password), data=f, timeout=timeout)
435
+
436
+ logging.debug("Arquivo adicionado no ticket")
437
+ return {"success": True, "result" : response}
438
+
439
+ except Exception as e:
440
+ return {"success" : False, "result" : None, "error" : str(e)}
441
+
442
+
443
+ def __valida_header_content_type(self, anexo_path, header_content_type):
444
+ """
445
+ Valida e define o cabeçalho `Content-Type` baseado na extensão do arquivo anexado.
446
+
447
+ Parâmetros:
448
+ anexo_path (str): Caminho do arquivo que será validado e utilizado para determinar o `Content-Type`.
449
+
450
+ Funcionalidade:
451
+ - Baseado na extensão do arquivo especificado, define um valor apropriado para `header_content_type` caso esteja ausente:
452
+ - `.zip` → `application/zip`
453
+ - `.xlsx` → `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
454
+ - `.pdf` → `application/pdf`
455
+ - `.txt` → `text/plain`
456
+ - Gera um erro se `header_content_type` não for do tipo `dict` após a definição.
457
+
458
+ Erros:
459
+ - ValueError: Caso `header_content_type` não seja um dicionário válido após as validações.
460
+
461
+ Observação:
462
+ - O parâmetro `header_content_type` é uma variável local utilizada para compor os cabeçalhos HTTP, contendo informações do tipo de conteúdo do arquivo.
463
+
464
+ """
465
+
466
+ # Pré validando 'header_content_type'
467
+ if os.path.splitext(anexo_path)[1].lower() == ".zip" and header_content_type is None:
468
+
469
+ header_content_type = {"Content-Type": "application/zip"}
470
+
471
+ elif os.path.splitext(anexo_path)[1].lower() == ".xlsx" and header_content_type is None:
472
+
473
+ header_content_type = {"Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}
474
+
475
+ elif os.path.splitext(anexo_path)[1].lower() == ".pdf" and header_content_type is None:
476
+
477
+ header_content_type = {"Content-Type": "application/pdf"}
478
+
479
+ elif os.path.splitext(anexo_path)[1].lower() == ".txt" and header_content_type is None:
480
+
481
+ header_content_type = {"Content-Type": "text/plain"}
482
+
483
+ # Validação de 'header_content_type'
484
+ if not isinstance(header_content_type, dict):
485
+ raise ValueError("O parâmetro 'header_content_type' precisa ser um dicionário contendo o 'Content-Type' do arquivo a ser anexado. (Ex: {\"Content-Type\": \"application/zip\"})")
486
+
487
+
488
+ return header_content_type
489
+
490
+
491
+ def download_anexo(self, sys_id_file : str, file_path : str, timeout : int = 15):
492
+ """
493
+ Faz o download de um anexo do ServiceNow utilizando o `sys_id` do arquivo e salva no caminho especificado.
494
+
495
+ Parâmetros:
496
+ sys_id_file (str): O identificador único (sys_id) do arquivo no ServiceNow.
497
+ file_path (str): O caminho completo onde o arquivo será salvo localmente.
498
+ timeout (int, opcional): O tempo limite, em segundos, para a requisição HTTP. Padrão é 15 segundos.
499
+
500
+ Retorna:
501
+ bool: Retorna `True` se o arquivo foi salvo com sucesso no caminho especificado.
502
+ dict: Em caso de erro, retorna um dicionário com os seguintes campos:
503
+ - "success" (bool): Indica se a operação foi bem-sucedida (`False` em caso de falha).
504
+ - "error" (str): Mensagem de erro detalhada em caso de falha.
505
+ - "path" (str, opcional): O caminho do arquivo salvo, caso tenha sido criado após a criação de diretórios.
506
+
507
+ Comportamento:
508
+ - Em caso de sucesso na requisição HTTP (status code 200), o arquivo é salvo no `file_path`.
509
+ - Caso o caminho do arquivo não exista, tenta criá-lo automaticamente.
510
+ - Registra logs de erros ou informações em caso de falha.
511
+
512
+ Exceções Tratadas:
513
+ - FileNotFoundError: Se o caminho especificado não existir, tenta criar os diretórios necessários.
514
+ - Exception: Registra quaisquer outros erros inesperados durante o salvamento.
515
+
516
+ Logs:
517
+ - `logging.debug`: Para erros de salvamento no arquivo.
518
+ - `logging.error`: Para erros genéricos durante o processo.
519
+ - `logging.info`: Para informações sobre falhas na requisição HTTP.
520
+
521
+ Exemplo:
522
+ obj.download_anexo("abc123sysid", "/caminho/para/salvar/arquivo.txt")
523
+ {'success': False, 'error': 'Status code: 404'}
524
+ """
525
+ try:
526
+ DownloadFileValidator(sys_id_file=sys_id_file, file_path=file_path)
527
+ except ValidationError as e:
528
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
529
+
530
+ url = f"{self.api_url}/now/attachment/{sys_id_file}/file"
531
+ response = self.__request_download(url=url, timeout=timeout)
532
+ response = response.get("result")
533
+ if response.status_code == 200:
534
+ try:
535
+ if not os.path.exists(file_path):
536
+ diretorio = os.path.dirname(file_path)
537
+ print(diretorio)
538
+ os.makedirs(diretorio, exist_ok=True)
539
+ logging.debug(f"Diretorio criado: {diretorio}")
540
+ with open(file_path, 'wb') as f:
541
+ f.write(response.content)
542
+ return {"success" : True, "path" : file_path }
543
+
544
+ except FileNotFoundError:
545
+ try:
546
+ path = os.path.dirname(file_path)
547
+ os.makedirs(path)
548
+ with open(file_path, 'wb') as f:
549
+ f.write(response.content)
550
+ return {"success" : True, "path" : file_path }
551
+ except Exception as e:
552
+ logging.debug(f"Erro ao salvar o arquivo. Erro: {e}")
553
+ return {"success" : False, "error" : str(e) }
554
+ except Exception as e:
555
+ logging.error(f"Erro ao salvar o arquivo. Erro: {e}")
556
+ return {"success" : False, "error" : str(e) }
557
+ else:
558
+ logging.debug(f"{response.status_code}")
559
+ return {"success" : False, "error" : f"Status code: {response.status_code }"}
560
+
561
+ def get_variables_from_ticket(self, sys_id : str, campos : str = ''):
562
+ """
563
+ Obtém as variáveis associadas ao ticket.
564
+ args:
565
+ sys_id : STR - Id do ticket
566
+ """
567
+ if isinstance(campos, list):
568
+ campos = ','.join(campos)
569
+ if campos == "":
570
+ campos = 'name,question_text,type,default_value, sys_id'
571
+
572
+ logging.debug(f"Obtendo variáveis do ticket {sys_id}")
573
+ url = f"{self.api_url}/now/table/item_option_new"
574
+ params = {
575
+ 'sysparm_query': f'cat_item={sys_id}',
576
+ 'sysparm_display_value': 'true',
577
+ 'sysparm_fields': campos,
578
+ }
579
+ response = self.request(url, params=params)
580
+ return response
581
+
582
+
583
+ def get_anexo(self, sys_id: str = None, tabela: str = None, campo: str = 'default', download_dir:str=None, timeout:int=15)->dict:
584
+ """Traz os anexos de um campo do ticket especificado
585
+
586
+ Args:
587
+ sys_id (str): sys_id do ticket
588
+ tabela (str): tabela do ticket
589
+ campo (str, optional): campo do anexo
590
+ timeout (int, optional): segundos para a requisicao dar timeout. Default=15
591
+
592
+ Returns:
593
+ dict: dicionário com os anexos do ticket
594
+ """
595
+ try:
596
+ GetAttachValidator(sys_id=sys_id,
597
+ tabela=tabela,
598
+ timeout=timeout,
599
+ download_dir=download_dir)
600
+ except ValidationError as e:
601
+ raise ValueError("Erro na validação dos dados de input do método:", e.errors())
602
+
603
+ if download_dir is not None:
604
+ if not isinstance(download_dir, str) or not download_dir.strip():
605
+ raise ValueError("O parâmetro 'download_dir' precisa ser a pasta pra onde o anexo será feito o download.")
606
+ if not os.path.exists(download_dir):
607
+ raise NotADirectoryError(f"A pasta informada '{download_dir}' não existe")
608
+
609
+ # Validação de 'campo'
610
+ if not isinstance(campo, str) or not campo.strip():
611
+ raise ValueError("O parâmetro 'campo' precisa ser uma string não vazia com o nome do campo do anexo.")
612
+
613
+ campo = str(campo).strip().lower()
614
+
615
+ # Convert bytes to base64
616
+ def __bytes_to_base64(image_bytes):
617
+
618
+ return base64.b64encode(image_bytes).decode('utf-8')
619
+
620
+ def __formatar_tamanho(tamanho_bytes):
621
+ # Converte o valor de string para inteiro
622
+ tamanho_bytes = int(tamanho_bytes)
623
+
624
+ # Define os múltiplos de bytes
625
+ unidades = ['B', 'KB', 'MB', 'GB', 'TB']
626
+
627
+ # Itera sobre as unidades até encontrar a maior possível
628
+ for unidade in unidades:
629
+ if tamanho_bytes < 1024:
630
+ return f"{tamanho_bytes:.2f} {unidade}"
631
+ tamanho_bytes /= 1024
632
+
633
+ # Caso o valor seja maior que o esperado (Exabyte ou superior)
634
+ return f"{tamanho_bytes:.2f} PB" # Petabyte
635
+
636
+ anexo_dict = {"var_servicenow": campo, "anexos":[]}
637
+
638
+ try:
639
+ if campo == 'default':
640
+ url = f"{self.api_url}/now/attachment?sysparm_query=table_name={tabela}^table_sys_id={sys_id}"
641
+ response = self.__request_download(url, timeout=timeout)
642
+ response = response.get("result")
643
+ if response.status_code == 200:
644
+ for attachment in response.json().get("result", []):
645
+ arquivo = {
646
+ "file_name": attachment["file_name"],
647
+ "size": __formatar_tamanho(attachment["size_bytes"]),
648
+ "content_type": attachment["content_type"],
649
+ "base64": None
650
+ }
651
+ byte_response = self.__request_download(attachment["download_link"], timeout=timeout)
652
+ byte_response = byte_response.get("result")
653
+ if byte_response.status_code == 200:
654
+ arquivo["base64"] = __bytes_to_base64(byte_response.content)
655
+ if download_dir:
656
+ with open(os.path.join(download_dir, arquivo["file_name"]), 'wb') as f:
657
+ f.write(byte_response.content)
658
+ anexo_dict["anexos"].append(arquivo)
659
+ return {"success": True, "result": anexo_dict}
660
+ return {"success": False, "error": response.json(), "status_code": response.status_code}
661
+
662
+ else:
663
+ url = f"{self.api_url}/now/table/{tabela}?sysparm_query=sys_id={sys_id}&sysparm_fields={campo}&sysparm_display_value=all"
664
+ response = self.__request_download(url, timeout=timeout)
665
+ response = response.get("result")
666
+ if response.status_code == 200 and response.json().get("result"):
667
+ campo_data = response.json()["result"][0].get(campo)
668
+ if campo_data:
669
+ attachment_id = campo_data["value"]
670
+ attachment_url = f"{self.api_url}/now/attachment/{attachment_id}/file"
671
+ byte_response = self.__request_download(attachment["download_link"], timeout=timeout)
672
+ byte_response = byte_response.get("result")
673
+ if byte_response.status_code == 200:
674
+ arquivo = {
675
+ "file_name": campo_data["display_value"],
676
+ "size": __formatar_tamanho(byte_response.headers.get("Content-Length", 0)),
677
+ "content_type": byte_response.headers.get("Content-Type"),
678
+ "base64": __bytes_to_base64(byte_response.content)
679
+ }
680
+ if download_dir:
681
+ with open(os.path.join(download_dir, arquivo["file_name"]), 'wb') as f:
682
+ f.write(byte_response.content)
683
+ anexo_dict["anexos"].append(arquivo)
684
+ return {"success": True, "result": anexo_dict, "error" : None}
685
+ return {"success": False, "error": response.json(), "status_code": response.status_code}
686
+
687
+ except Exception as e:
688
+ logging.debug("Erro ao obter anexos.")
689
+ return {"success": False, "error": str(e)}