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/__init__.py +89 -0
- csc_cia_stne/bc_correios.py +529 -0
- csc_cia_stne/bc_sta.py +416 -0
- csc_cia_stne/email.py +239 -0
- csc_cia_stne/gcp_bigquery.py +224 -0
- csc_cia_stne/google_drive.py +268 -0
- csc_cia_stne/karavela.py +135 -0
- csc_cia_stne/logger_json.py +92 -0
- csc_cia_stne/logger_rich.py +249 -0
- csc_cia_stne/provio.py +103 -0
- csc_cia_stne/servicenow.py +689 -0
- csc_cia_stne/slack.py +227 -0
- csc_cia_stne/stne_admin.py +597 -0
- csc_cia_stne/utilitarios/__init__.py +9 -0
- csc_cia_stne/utilitarios/functions/__init__.py +14 -0
- csc_cia_stne/utilitarios/functions/func_b64.py +50 -0
- csc_cia_stne/utilitarios/functions/func_converters.py +18 -0
- csc_cia_stne/utilitarios/functions/func_recriar_pastas.py +29 -0
- csc_cia_stne/utilitarios/functions/func_settings.py +66 -0
- csc_cia_stne/utilitarios/functions/func_titulo.py +141 -0
- csc_cia_stne/utilitarios/validations/GcpBigQueryValidator.py +133 -0
- csc_cia_stne/utilitarios/validations/GoogleDriveValidator.py +144 -0
- csc_cia_stne/utilitarios/validations/ServiceNowValidator.py +403 -0
- csc_cia_stne/utilitarios/validations/__init__.py +3 -0
- csc_cia_stne-0.0.43.dist-info/LICENCE +21 -0
- csc_cia_stne-0.0.43.dist-info/METADATA +33 -0
- csc_cia_stne-0.0.43.dist-info/RECORD +29 -0
- csc_cia_stne-0.0.43.dist-info/WHEEL +5 -0
- csc_cia_stne-0.0.43.dist-info/top_level.txt +1 -0
@@ -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)}
|