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.
@@ -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)}