csc-cia-stne 0.0.43__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- csc_cia_stne/__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)}
|