csc-cia-stne 0.0.30__py3-none-any.whl → 0.0.32__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 +49 -25
- csc_cia_stne/gcp_bigquery.py +2 -94
- csc_cia_stne/google_drive.py +238 -0
- csc_cia_stne/logger_json.py +43 -1
- csc_cia_stne/logger_rich.py +17 -8
- csc_cia_stne/provio.py +66 -0
- csc_cia_stne/servicenow.py +412 -218
- csc_cia_stne/utilitarios/functions/func_titulo.py +1 -3
- csc_cia_stne/utilitarios/validations/GcpBigQueryValidator.py +91 -0
- csc_cia_stne/utilitarios/validations/GoogleDriveValidator.py +39 -0
- csc_cia_stne/utilitarios/validations/ServiceNowValidator.py +164 -0
- csc_cia_stne/utilitarios/validations/__init__.py +2 -0
- {csc_cia_stne-0.0.30.dist-info → csc_cia_stne-0.0.32.dist-info}/METADATA +1 -1
- csc_cia_stne-0.0.32.dist-info/RECORD +27 -0
- csc_cia_stne-0.0.30.dist-info/RECORD +0 -21
- {csc_cia_stne-0.0.30.dist-info → csc_cia_stne-0.0.32.dist-info}/LICENCE +0 -0
- {csc_cia_stne-0.0.30.dist-info → csc_cia_stne-0.0.32.dist-info}/WHEEL +0 -0
- {csc_cia_stne-0.0.30.dist-info → csc_cia_stne-0.0.32.dist-info}/top_level.txt +0 -0
csc_cia_stne/servicenow.py
CHANGED
@@ -2,10 +2,35 @@ import requests
|
|
2
2
|
import base64, json
|
3
3
|
import os
|
4
4
|
import logging
|
5
|
+
from pydantic import ValidationError
|
6
|
+
from .utilitarios.validations.ServiceNowValidator import InitParamsValidator, RequestValidator, PutValidator, PostValidator, ListTicketValidator, UpdateTicketValidator, AttachFileTicketValidator, GetAttachValidator
|
5
7
|
|
6
8
|
class ServiceNow:
|
7
9
|
|
8
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
|
+
|
9
34
|
# Verifica se todos os parâmetros foram fornecidos e não estão vazios
|
10
35
|
if not all([username and username.strip(), password and password.strip(), env and env.strip()]):
|
11
36
|
raise ValueError("Todos os parâmetros precisam ser fornecidos para a criação da instância ServiceNow, ou algum dos valores esta nulo ou vazio (username, password, env)")
|
@@ -40,14 +65,48 @@ class ServiceNow:
|
|
40
65
|
"""
|
41
66
|
return (self.username, self.password)
|
42
67
|
|
43
|
-
|
68
|
+
|
69
|
+
def request (self, url : str , params : str = "", timout : int = 15):
|
70
|
+
"""
|
71
|
+
Realiza uma requisição GET para a URL especificada.
|
72
|
+
|
73
|
+
Parâmetros:
|
74
|
+
url (str): URL para a qual a requisição será enviada.
|
75
|
+
params (dict, opcional): Parâmetros de consulta (query parameters) a serem incluídos na requisição.
|
76
|
+
Padrão é uma string vazia.
|
77
|
+
|
78
|
+
Retorno:
|
79
|
+
dict: Um dicionário contendo:
|
80
|
+
- success (bool): Indica se a requisição foi bem-sucedida.
|
81
|
+
- result (dict ou None): Resultado da resposta, se disponível. Contém o conteúdo JSON do campo "result".
|
82
|
+
- error (Exception ou None): Objeto de exceção em caso de erro, ou None se não houver erros.
|
83
|
+
|
84
|
+
Tratamento de exceções:
|
85
|
+
- requests.exceptions.HTTPError: Lança um erro HTTP caso o código de status indique falha.
|
86
|
+
- requests.exceptions.RequestException: Captura outros erros relacionados à requisição, como timeout ou conexão.
|
87
|
+
- Exception: Captura erros inesperados.
|
88
|
+
|
89
|
+
Logs:
|
90
|
+
- Logs de depuração são gerados para erros HTTP, erros de requisição e exceções inesperadas.
|
91
|
+
- Um aviso é registrado caso a resposta não contenha o campo "result".
|
92
|
+
|
93
|
+
Observações:
|
94
|
+
- Utiliza autenticação fornecida pelo método privado `__auth()`.
|
95
|
+
- Define um tempo limite (`timeout`) de 15 segundos.
|
96
|
+
- Verifica o certificado SSL (`verify=True`).
|
97
|
+
"""
|
98
|
+
try:
|
99
|
+
RequestValidator(url=url, params=params, timeout=timout)
|
100
|
+
except ValidationError as e:
|
101
|
+
raise ValueError("Erro na validação dos dados de input do método:", e.errors())
|
102
|
+
|
44
103
|
try:
|
45
104
|
response = requests.get(
|
46
105
|
url,
|
47
106
|
params=params,
|
48
107
|
auth=self.__auth(),
|
49
108
|
headers=self.api_header,
|
50
|
-
timeout=
|
109
|
+
timeout=timout,
|
51
110
|
verify=True
|
52
111
|
)
|
53
112
|
|
@@ -57,22 +116,56 @@ class ServiceNow:
|
|
57
116
|
# Analisa o conteúdo da resposta JSON
|
58
117
|
result = response.json()
|
59
118
|
if "result" in result:
|
60
|
-
return result
|
119
|
+
return {"success" : True, "result" : result}
|
61
120
|
else:
|
62
|
-
logging.
|
63
|
-
return {}
|
121
|
+
logging.debug("A resposta não contém o campo 'result'.")
|
122
|
+
return {"success" : False, "result" : result}
|
64
123
|
|
65
124
|
except requests.exceptions.HTTPError as http_err:
|
66
|
-
logging.
|
67
|
-
|
125
|
+
logging.debug(f"Erro HTTP ao buscar os detalhes do ticket: {http_err}")
|
126
|
+
return {"success" : False, "result" : None, "error" : str(http_err)}
|
127
|
+
|
68
128
|
except requests.exceptions.RequestException as req_err:
|
69
|
-
logging.
|
70
|
-
|
129
|
+
logging.debug(f"Erro ao buscar os detalhes do ticket: {req_err}")
|
130
|
+
return {"success" : False, "result" : None, "error" : str(req_err)}
|
131
|
+
|
71
132
|
except Exception as e:
|
72
|
-
logging.
|
73
|
-
|
133
|
+
logging.debug(f"Erro inesperado: {e}")
|
134
|
+
return {"success" : False, "result" : None, "error" : str(e)}
|
74
135
|
|
75
|
-
|
136
|
+
|
137
|
+
def put(self, url, payload, timeout = 15):
|
138
|
+
"""
|
139
|
+
Realiza uma requisição PUT para a URL especificada.
|
140
|
+
|
141
|
+
Parâmetros:
|
142
|
+
url (str): URL para a qual a requisição será enviada.
|
143
|
+
payload (dict): Dados a serem enviados no corpo da requisição. Será convertido para JSON.
|
144
|
+
|
145
|
+
Retorno:
|
146
|
+
dict: Um dicionário contendo:
|
147
|
+
- success (bool): Indica se a requisição foi bem-sucedida.
|
148
|
+
- result (dict ou None): Resultado da resposta, se disponível. Contém o conteúdo JSON do campo "result".
|
149
|
+
|
150
|
+
Tratamento de exceções:
|
151
|
+
- requests.exceptions.HTTPError: Lança um erro HTTP caso o código de status indique falha. O erro é registrado nos logs.
|
152
|
+
- requests.exceptions.RequestException: Captura outros erros relacionados à requisição, como timeout ou problemas de conexão. O erro é registrado nos logs.
|
153
|
+
- Exception: Captura erros inesperados e os registra nos logs.
|
154
|
+
|
155
|
+
Logs:
|
156
|
+
- Registra mensagens de depuração detalhadas quando a atualização é bem-sucedida ou quando a resposta não contém o campo "result".
|
157
|
+
- Registra mensagens de erro para exceções HTTP, erros de requisição e outros erros inesperados.
|
158
|
+
|
159
|
+
Observações:
|
160
|
+
- Utiliza autenticação fornecida pelo método privado `__auth()`.
|
161
|
+
- Define um tempo limite (`timeout`) de 15 segundos.
|
162
|
+
- Verifica o certificado SSL (`verify=True`).
|
163
|
+
- O cabeçalho da requisição é definido com `self.api_header`.
|
164
|
+
"""
|
165
|
+
try:
|
166
|
+
PutValidator(url=url, payload=payload, timeout=timeout)
|
167
|
+
except ValidationError as e:
|
168
|
+
raise ValueError("Erro na validação dos dados de input do método:", e.errors())
|
76
169
|
|
77
170
|
payload = json.dumps(payload)
|
78
171
|
|
@@ -82,7 +175,7 @@ class ServiceNow:
|
|
82
175
|
auth=self.__auth(),
|
83
176
|
headers=self.api_header,
|
84
177
|
data=f"{payload}",
|
85
|
-
timeout=
|
178
|
+
timeout=timeout,
|
86
179
|
verify=True
|
87
180
|
)
|
88
181
|
|
@@ -93,24 +186,27 @@ class ServiceNow:
|
|
93
186
|
result = response.json()
|
94
187
|
if "result" in result:
|
95
188
|
update = result["result"]
|
96
|
-
logging.
|
189
|
+
logging.debug(f"Atualização concluída com sucesso. Registro atualizado: {update["sys_id"]} | Alterações: {payload}")
|
190
|
+
return {"success" : True, "result" : update}
|
97
191
|
else:
|
98
|
-
logging.
|
192
|
+
logging.debug(f"A Resposta da sua requisição não contém o campo 'Result'. Segue o retorno: \n {result} | Alterações: {payload}")
|
193
|
+
return {"success" : False, "result" : result}
|
99
194
|
|
100
195
|
#TRATAMENTOS DE ERRO
|
101
196
|
except requests.exceptions.HTTPError as http_err:
|
102
|
-
|
103
|
-
|
104
|
-
|
197
|
+
logging.debug(f"Erro HTTP ao tentar atualizar o ticket: {http_err} \n Reposta da solicitação: {response.json().get("error").get("message")}")
|
198
|
+
return {"success" : False, "error" : str(http_err) , "result" : None}
|
199
|
+
|
105
200
|
except requests.exceptions.RequestException as req_err:
|
106
|
-
|
107
|
-
|
108
|
-
|
201
|
+
logging.debug(f"Erro ao tentar atualizar o ticket: \n {req_err}")
|
202
|
+
return {"success" : False, "error" : str(req_err) , "result" : None}
|
203
|
+
|
109
204
|
except Exception as e:
|
110
|
-
logging.
|
111
|
-
|
205
|
+
logging.debug(f"Erro inesperado: \n {e}")
|
206
|
+
return {"success" : False, "error" : str(e) , "result" : None}
|
207
|
+
|
112
208
|
|
113
|
-
def post(self, url, variables, header_content_type = ""):
|
209
|
+
def post(self, url : str, variables : dict, header_content_type = "", timeout : int = 15):
|
114
210
|
"""
|
115
211
|
Função para criar um novo ticket no servicenow usando o API REST.
|
116
212
|
|
@@ -122,6 +218,12 @@ class ServiceNow:
|
|
122
218
|
- Exception: Se ocorrer um erro ao criar o ticket.
|
123
219
|
|
124
220
|
"""
|
221
|
+
try:
|
222
|
+
PostValidator(url=url, variables=variables)
|
223
|
+
except ValidationError as e:
|
224
|
+
raise ValueError("Erro na validação dos dados de input do método:", e.errors())
|
225
|
+
|
226
|
+
|
125
227
|
if header_content_type:
|
126
228
|
header = header_content_type
|
127
229
|
else:
|
@@ -137,7 +239,8 @@ class ServiceNow:
|
|
137
239
|
f"{url}",
|
138
240
|
auth=self.__auth(),
|
139
241
|
headers=header,
|
140
|
-
data=f"{payload}"
|
242
|
+
data=f"{payload}",
|
243
|
+
timeout=timeout
|
141
244
|
)
|
142
245
|
|
143
246
|
# VALIDA SE HOUVE SUCESSO NA REQUISIÇÃO
|
@@ -148,24 +251,27 @@ class ServiceNow:
|
|
148
251
|
if "result" in result:
|
149
252
|
ticket_number = result["result"].get("number")
|
150
253
|
ticket_sys_id = result["result"].get("sys_id")
|
151
|
-
logging.
|
152
|
-
return result["result"]
|
254
|
+
logging.debug(f"Ticket registrado com sucesso. Número: {ticket_number} | SYS_ID: {ticket_sys_id}")
|
255
|
+
return {"success" : True, "result" : result["result"]}
|
153
256
|
else:
|
154
|
-
logging.
|
257
|
+
logging.debug(f"A Resposta da sua requisição não contém o campo 'Result'. Segue o retorno: \n {result}")
|
258
|
+
return {"success" : False, "result" : result}
|
155
259
|
|
156
260
|
#TRATAMENTOS DE ERRO
|
157
261
|
except requests.exceptions.HTTPError as http_err:
|
158
262
|
|
159
|
-
logging.
|
160
|
-
|
263
|
+
logging.debug(f"Erro HTTP ao tentar registrar o ticket: {http_err} \n Reposta da solicitação: {response.json().get("error").get("message")}")
|
264
|
+
return {"success" : False, "error" : str(http_err) , "result" : None}
|
265
|
+
|
161
266
|
except requests.exceptions.RequestException as req_err:
|
162
|
-
|
163
|
-
|
164
|
-
|
267
|
+
logging.debug(f"Erro ao tentar registrar o ticket: \n {req_err}")
|
268
|
+
return {"success" : False, "error" : str(req_err) , "result" : None}
|
269
|
+
|
165
270
|
except Exception as e:
|
166
|
-
logging.
|
167
|
-
|
271
|
+
logging.debug(f"Erro inesperado: \n {e}")
|
272
|
+
return {"success" : False, "error" : str(e) , "result" : None}
|
168
273
|
|
274
|
+
|
169
275
|
def listar_tickets(self, tabela: str = None, campos: list = None, query: str = None, limite: int = 50, timeout:int=15)->dict:
|
170
276
|
"""lista tickets do ServiceNow
|
171
277
|
|
@@ -179,43 +285,54 @@ class ServiceNow:
|
|
179
285
|
Returns:
|
180
286
|
dict: dicionário com o resultado da query
|
181
287
|
"""
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
288
|
+
try:
|
289
|
+
ListTicketValidator(tabela=tabela, campos=campos, query=query, limite=limite, timeout=timeout)
|
290
|
+
except ValidationError as e:
|
291
|
+
raise ValueError("Erro na validação dos dados de input do método:", e.errors())
|
292
|
+
|
293
|
+
# # Validação de 'tabela'
|
294
|
+
# if not tabela or not tabela.strip():
|
295
|
+
# raise ValueError("O parâmetro 'tabela' precisa ser especificado com o nome da tabela de onde os tickets serão listados.")
|
296
|
+
|
297
|
+
# # Validação de 'campos'
|
298
|
+
# if not isinstance(campos, list) or not campos or not all(isinstance(campo, str) and campo.strip() for campo in campos):
|
299
|
+
# raise ValueError("O parâmetro 'campos' precisa ser uma lista não vazia de strings válidas com os nomes dos campos do formulário.")
|
300
|
+
|
301
|
+
# # Validação de 'query'
|
302
|
+
# if not query or not query.strip():
|
303
|
+
# raise ValueError("O parâmetro 'query' precisa ser uma string não vazia.")
|
304
|
+
|
305
|
+
# # Validação de 'timeout'
|
306
|
+
# if not isinstance(timeout, int):
|
307
|
+
# raise ValueError("O parâmetro 'timeout' precisa ser um valor (int) em segundos.")
|
308
|
+
|
309
|
+
# # Validação de 'limite'
|
310
|
+
# if not isinstance(limite, int):
|
311
|
+
# raise ValueError("O parâmetro 'limite' precisa ser um número inteiro.")
|
312
|
+
|
313
|
+
params = {
|
314
|
+
"sysparm_query" : query,
|
315
|
+
"sysparm_fields" : ','.join(campos),
|
316
|
+
"sysparm_display_value" : "all",
|
317
|
+
"sysparm_limit" : limite
|
318
|
+
}
|
319
|
+
# params["sysparm_query"] = query
|
320
|
+
# params["sysparm_fields"] = ','.join(campos)
|
321
|
+
# params["sysparm_display_value"] = "all"
|
322
|
+
# params["sysparm_limit"] = limite
|
207
323
|
|
208
324
|
url = f"{self.api_url}/now/table/{tabela}"
|
209
325
|
|
210
326
|
try:
|
211
|
-
response = self.request(url, params)
|
327
|
+
response = self.request(url=url, params=params, timout=timeout)
|
212
328
|
# response = requests.get(url, params=params, auth=(self.username, self.password), headers=self.api_header, verify=True, timeout=timeout)
|
213
|
-
return {"
|
329
|
+
return {"success":True, "result": response.json()}
|
214
330
|
|
215
331
|
except Exception as e:
|
216
|
-
return {}
|
332
|
+
return {"success" : False, "result" : None, "error" : str(e)}
|
333
|
+
|
217
334
|
|
218
|
-
def update_ticket(self, tabela: str = None, sys_id: str = None,
|
335
|
+
def update_ticket(self, tabela: str = None, sys_id: str = None, payload: dict = None, timeout:int=15)->dict:
|
219
336
|
"""Atualiza as informações de um ticket
|
220
337
|
|
221
338
|
Args:
|
@@ -227,34 +344,37 @@ class ServiceNow:
|
|
227
344
|
Returns:
|
228
345
|
dict: resposta do ServiceNow
|
229
346
|
"""
|
347
|
+
try:
|
348
|
+
UpdateTicketValidator(tabela=tabela, sys_id=sys_id, payload=payload, timeout=timeout)
|
349
|
+
except ValidationError as e:
|
350
|
+
raise ValueError("Erro na validação dos dados de input do método:", e.errors())
|
351
|
+
|
352
|
+
# # Validação de 'tabela'
|
353
|
+
# if not tabela or not tabela.strip():
|
354
|
+
# raise ValueError("O parâmetro 'tabela' precisa ser especificado com o nome da tabela de onde o ticket será atualizado.")
|
230
355
|
|
231
|
-
# Validação de '
|
232
|
-
if not
|
233
|
-
|
234
|
-
|
235
|
-
# Validação de 'sys_id'
|
236
|
-
if not sys_id or not sys_id.strip():
|
237
|
-
raise ValueError("O parâmetro 'sys_id' precisa ser especificado com o sys_id do ticket.")
|
356
|
+
# # Validação de 'sys_id'
|
357
|
+
# if not sys_id or not sys_id.strip():
|
358
|
+
# raise ValueError("O parâmetro 'sys_id' precisa ser especificado com o sys_id do ticket.")
|
238
359
|
|
239
|
-
# Validação de '
|
240
|
-
if not isinstance(
|
241
|
-
|
360
|
+
# # Validação de 'payload'
|
361
|
+
# if not isinstance(payload, dict):
|
362
|
+
# raise ValueError("O parâmetro 'campos' precisa ser um dicionário contendo os campos do ticket a serem atualizados.")
|
242
363
|
|
243
|
-
if "assigned_to" not in
|
364
|
+
if "assigned_to" not in payload:
|
244
365
|
|
245
|
-
|
366
|
+
payload["assigned_to"] = self.username
|
246
367
|
|
247
368
|
try:
|
248
369
|
|
249
370
|
url = f"{self.api_url}/now/table/{tabela}/{sys_id}"
|
250
|
-
response = self.put(url, payload=
|
371
|
+
response = self.put(url, payload=payload, timeout=timeout)
|
251
372
|
# response = requests.patch(url, auth=(self.username, self.password), headers=self.api_header, data=str(campos).encode('utf-8'), verify=True, timeout=timeout)
|
252
|
-
|
253
|
-
return response.json()
|
373
|
+
return {"success" : True, "result" : response.json() , "error" : None}
|
254
374
|
|
255
375
|
except Exception as e:
|
256
|
-
|
257
|
-
|
376
|
+
return {"success" : False, "result" : None, "error" : str(e) }
|
377
|
+
|
258
378
|
|
259
379
|
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):
|
260
380
|
"""Anexa arquivo em um ticket do ServiceNow
|
@@ -269,18 +389,22 @@ class ServiceNow:
|
|
269
389
|
Returns:
|
270
390
|
dict: resposta do ServiceNow
|
271
391
|
"""
|
392
|
+
try:
|
393
|
+
AttachFileTicketValidator(header_content_type=header_content_type, anexo_path=anexo_path, tabela=tabela, sys_id=sys_id, timeout=timeout)
|
394
|
+
except ValidationError as e:
|
395
|
+
raise ValueError("Erro na validação dos dados de input do método:", e.errors())
|
396
|
+
|
397
|
+
# # Validação de 'tabela'
|
398
|
+
# if not tabela or not tabela.strip():
|
399
|
+
# raise ValueError("O parâmetro 'tabela' precisa ser especificado com o nome da tabela do ticket pra onde o arquivo será anexado.")
|
272
400
|
|
273
|
-
# Validação de '
|
274
|
-
if not
|
275
|
-
|
276
|
-
|
277
|
-
# Validação de 'sys_id'
|
278
|
-
if not sys_id or not sys_id.strip():
|
279
|
-
raise ValueError("O parâmetro 'sys_id' precisa ser especificado com o sys_id do ticket que o arquivo será anexado.")
|
401
|
+
# # Validação de 'sys_id'
|
402
|
+
# if not sys_id or not sys_id.strip():
|
403
|
+
# raise ValueError("O parâmetro 'sys_id' precisa ser especificado com o sys_id do ticket que o arquivo será anexado.")
|
280
404
|
|
281
|
-
# Validação de 'anexo_path'
|
282
|
-
if not anexo_path or not anexo_path.strip():
|
283
|
-
|
405
|
+
# # Validação de 'anexo_path'
|
406
|
+
# if not anexo_path or not anexo_path.strip():
|
407
|
+
# raise ValueError("O parâmetro 'anexo_path' precisa ser especificado com o path para o arquivo a ser anexado.")
|
284
408
|
|
285
409
|
if not os.path.exists(anexo_path):
|
286
410
|
raise FileExistsError(f"O arquivo não foi encontrado ({anexo_path})")
|
@@ -304,15 +428,37 @@ class ServiceNow:
|
|
304
428
|
|
305
429
|
try:
|
306
430
|
url = f"{self.api_url}/now/attachment/file?table_name={tabela}&table_sys_id={sys_id}&file_name={nome_arquivo}"
|
307
|
-
response = self.post(url, file_data, header_content_type )
|
431
|
+
# response = self.post(url, file_data, header_content_type )
|
432
|
+
response = self.post(url=url, file_data=file_data, header_content_type=header_content_type_lower, timeout=timeout)
|
308
433
|
# response = requests.post(f"{self.api_url}/now/attachment/file?table_name={tabela}&table_sys_id={sys_id}&file_name={nome_arquivo}", headers=header_content_type, auth=(self.username, self.password), data=file_data, timeout=timeout)
|
309
|
-
return response.json()
|
434
|
+
return {"success" : True, "result" : response.json(), "error" : None}
|
310
435
|
|
311
436
|
except Exception as e:
|
312
|
-
|
313
|
-
raise e
|
437
|
+
return {"success" : False, "result" : None, "error" : str(e)}
|
314
438
|
|
439
|
+
|
315
440
|
def valida_header_content_type(self, anexo_path ):
|
441
|
+
"""
|
442
|
+
Valida e define o cabeçalho `Content-Type` baseado na extensão do arquivo anexado.
|
443
|
+
|
444
|
+
Parâmetros:
|
445
|
+
anexo_path (str): Caminho do arquivo que será validado e utilizado para determinar o `Content-Type`.
|
446
|
+
|
447
|
+
Funcionalidade:
|
448
|
+
- Baseado na extensão do arquivo especificado, define um valor apropriado para `header_content_type` caso esteja ausente:
|
449
|
+
- `.zip` → `application/zip`
|
450
|
+
- `.xlsx` → `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
|
451
|
+
- `.pdf` → `application/pdf`
|
452
|
+
- `.txt` → `text/plain`
|
453
|
+
- Gera um erro se `header_content_type` não for do tipo `dict` após a definição.
|
454
|
+
|
455
|
+
Erros:
|
456
|
+
- ValueError: Caso `header_content_type` não seja um dicionário válido após as validações.
|
457
|
+
|
458
|
+
Observação:
|
459
|
+
- 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.
|
460
|
+
|
461
|
+
"""
|
316
462
|
|
317
463
|
# Pré validando 'header_content_type'
|
318
464
|
if os.path.splitext(anexo_path)[1].lower() == ".zip" and header_content_type is None:
|
@@ -331,16 +477,17 @@ class ServiceNow:
|
|
331
477
|
|
332
478
|
header_content_type = {"Content-Type": "text/plain"}
|
333
479
|
|
334
|
-
# Validação de 'header_content_type'
|
335
|
-
if not isinstance(header_content_type, dict):
|
336
|
-
|
480
|
+
# # Validação de 'header_content_type'
|
481
|
+
# if not isinstance(header_content_type, dict):
|
482
|
+
# 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\"})")
|
337
483
|
|
338
|
-
# Validação de 'header_content_type'
|
339
|
-
if not isinstance(header_content_type, dict):
|
340
|
-
|
484
|
+
# # Validação de 'header_content_type'
|
485
|
+
# if not isinstance(header_content_type, dict):
|
486
|
+
# 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\"})")
|
341
487
|
|
342
488
|
return header_content_type
|
343
489
|
|
490
|
+
|
344
491
|
def get_anexo(self, sys_id: str = None, tabela: str = None, campo: str = 'default', download_dir:str=None, timeout:int=15)->dict:
|
345
492
|
"""Traz os anexos de um campo do ticket especificado
|
346
493
|
|
@@ -353,27 +500,31 @@ class ServiceNow:
|
|
353
500
|
Returns:
|
354
501
|
dict: dicionário com os anexos do ticket
|
355
502
|
"""
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
503
|
+
try:
|
504
|
+
GetAttachValidator(sys_id=sys_id,
|
505
|
+
tabela=tabela,
|
506
|
+
timeout=timeout,
|
507
|
+
download_dir=download_dir)
|
508
|
+
except ValidationError as e:
|
509
|
+
raise ValueError("Erro na validação dos dados de input do método:", e.errors())
|
510
|
+
|
511
|
+
# # Validação de 'sys_id'
|
512
|
+
# if not isinstance(sys_id, str) or not sys_id.strip():
|
513
|
+
# raise ValueError("O parâmetro 'sys_id' precisa ser uma string não vazia com o sys_id do ticket.")
|
360
514
|
|
361
|
-
# Validação de 'tabela'
|
362
|
-
if not isinstance(tabela, str) or not tabela.strip():
|
363
|
-
|
515
|
+
# # Validação de 'tabela'
|
516
|
+
# if not isinstance(tabela, str) or not tabela.strip():
|
517
|
+
# raise ValueError("O parâmetro 'tabela' precisa ser uma string não vazia com o nome da tabela do ticket.")
|
364
518
|
|
365
|
-
# Validação de 'timeout'
|
366
|
-
if not isinstance(timeout, int):
|
367
|
-
|
519
|
+
# # Validação de 'timeout'
|
520
|
+
# if not isinstance(timeout, int):
|
521
|
+
# raise ValueError("O parâmetro 'timeout' precisa ser um valor (int) em segundos.")
|
368
522
|
|
369
523
|
# Validação de 'download_dir'
|
370
524
|
if download_dir is not None:
|
371
525
|
if not isinstance(download_dir, str) or not download_dir.strip():
|
372
|
-
|
373
526
|
raise ValueError("O parâmetro 'download_dir' precisa ser a pasta pra onde o anexo será feito o download.")
|
374
|
-
|
375
|
-
if not os.path.exists(download_dir):
|
376
|
-
|
527
|
+
if not os.path.exists(download_dir):
|
377
528
|
raise NotADirectoryError(f"A pasta informada '{download_dir}' não existe")
|
378
529
|
|
379
530
|
# Validação de 'campo'
|
@@ -384,12 +535,13 @@ class ServiceNow:
|
|
384
535
|
|
385
536
|
# Convert bytes to base64
|
386
537
|
def __bytes_to_base64(image_bytes):
|
387
|
-
base64_encoded = base64.b64encode(image_bytes)
|
538
|
+
# base64_encoded = base64.b64encode(image_bytes)
|
388
539
|
|
389
|
-
# Decode the bytes to a string (UTF-8 encoding)
|
390
|
-
base64_string = base64_encoded.decode('utf-8')
|
540
|
+
# # Decode the bytes to a string (UTF-8 encoding)
|
541
|
+
# base64_string = base64_encoded.decode('utf-8')
|
391
542
|
|
392
|
-
return base64_string
|
543
|
+
# # return base64_string
|
544
|
+
return base64.b64encode(image_bytes).decode('utf-8')
|
393
545
|
|
394
546
|
def __formatar_tamanho(tamanho_bytes):
|
395
547
|
# Converte o valor de string para inteiro
|
@@ -408,122 +560,164 @@ class ServiceNow:
|
|
408
560
|
return f"{tamanho_bytes:.2f} PB" # Petabyte
|
409
561
|
|
410
562
|
anexo_dict = {"var_servicenow": campo, "anexos":[]}
|
563
|
+
|
564
|
+
try:
|
565
|
+
if campo == 'default':
|
566
|
+
url = f"{self.api_url}/now/attachment?sysparm_query=table_name={tabela}^table_sys_id={sys_id}"
|
567
|
+
response = self.request(url, timeout=timeout)
|
568
|
+
|
569
|
+
if response.status_code == 200:
|
570
|
+
for attachment in response.json().get("result", []):
|
571
|
+
arquivo = {
|
572
|
+
"file_name": attachment["file_name"],
|
573
|
+
"size": __formatar_tamanho(attachment["size_bytes"]),
|
574
|
+
"content_type": attachment["content_type"],
|
575
|
+
"base64": None
|
576
|
+
}
|
577
|
+
byte_response = self.request(attachment["download_link"], timeout=timeout)
|
578
|
+
if byte_response.status_code == 200:
|
579
|
+
arquivo["base64"] = __bytes_to_base64(byte_response.content)
|
580
|
+
if download_dir:
|
581
|
+
with open(os.path.join(download_dir, arquivo["file_name"]), 'wb') as f:
|
582
|
+
f.write(byte_response.content)
|
583
|
+
anexo_dict["anexos"].append(arquivo)
|
584
|
+
return {"success": True, "result": anexo_dict}
|
585
|
+
return {"success": False, "error": response.json(), "status_code": response.status_code}
|
411
586
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
587
|
+
else:
|
588
|
+
url = f"{self.api_url}/now/table/{tabela}?sysparm_query=sys_id={sys_id}&sysparm_fields={campo}&sysparm_display_value=all"
|
589
|
+
response = self.request(url, timeout=timeout)
|
590
|
+
if response.status_code == 200 and response.json().get("result"):
|
591
|
+
campo_data = response.json()["result"][0].get(campo)
|
592
|
+
if campo_data:
|
593
|
+
attachment_id = campo_data["value"]
|
594
|
+
attachment_url = f"{self.api_url}/now/attachment/{attachment_id}/file"
|
595
|
+
byte_response = self.request(attachment_url, timeout=timeout)
|
596
|
+
if byte_response.status_code == 200:
|
597
|
+
arquivo = {
|
598
|
+
"file_name": campo_data["display_value"],
|
599
|
+
"size": __formatar_tamanho(byte_response.headers.get("Content-Length", 0)),
|
600
|
+
"content_type": byte_response.headers.get("Content-Type"),
|
601
|
+
"base64": __bytes_to_base64(byte_response.content)
|
602
|
+
}
|
603
|
+
if download_dir:
|
604
|
+
with open(os.path.join(download_dir, arquivo["file_name"]), 'wb') as f:
|
605
|
+
f.write(byte_response.content)
|
606
|
+
anexo_dict["anexos"].append(arquivo)
|
607
|
+
return {"success": True, "result": anexo_dict, "error" : None}
|
608
|
+
return {"success": False, "error": response.json(), "status_code": response.status_code}
|
416
609
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
610
|
+
except Exception as e:
|
611
|
+
logging.debug("Erro ao obter anexos.")
|
612
|
+
return {"success": False, "error": str(e)}
|
613
|
+
|
614
|
+
# # Fazendo download do anexo do campo padrao do ticket
|
615
|
+
# if campo == 'default':
|
616
|
+
|
617
|
+
# logging.debug(f"verificando o campo '{campo}' de anexo")
|
618
|
+
|
619
|
+
# try:
|
620
|
+
# #url = f"{SERVICENOW_API_URL}/now/attachment?sysparm_query=table_name=sc_req_item^table_sys_id={sys_id}"
|
621
|
+
# url = f"{self.api_url}/now/attachment?sysparm_query=table_name={tabela}^table_sys_id={sys_id}"
|
622
|
+
# response = self.request(url)
|
623
|
+
# # response = requests.get(url, auth=(self.username, self.password), verify=True, timeout=timeout)
|
624
|
+
# if response.status_code == 200 and len(response.json()["result"]) >= 1:
|
625
|
+
|
626
|
+
# for attachment in response.json()["result"]:
|
627
|
+
|
628
|
+
# # print("attachment")
|
629
|
+
# # print(attachment)
|
630
|
+
# arquivo = {}
|
631
|
+
# arquivo["file_name"] = attachment["file_name"]
|
632
|
+
# arquivo["size"] = __formatar_tamanho(attachment["size_bytes"]["display_value"])
|
633
|
+
# arquivo["content_type"] = attachment["content_type"]
|
634
|
+
# #arquivo["table_sys_id"] = attachment["table_sys_id"]
|
635
|
+
|
636
|
+
# try:
|
637
|
+
# byte_response = self.request(attachment["download_link"])
|
638
|
+
# # byte_response = requests.get(attachment["download_link"], auth=(self.username, self.password), verify=True, timeout=timeout)
|
437
639
|
|
438
|
-
|
640
|
+
# if byte_response.status_code == 200:
|
439
641
|
|
440
|
-
|
642
|
+
# arquivo["base64"] = __bytes_to_base64(byte_response.content)
|
441
643
|
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
except Exception as e:
|
644
|
+
# if download_dir is not None:
|
645
|
+
# with open(arquivo["file_name"],'wb') as download_file:
|
646
|
+
# download_file.write(byte_response.content)
|
647
|
+
|
648
|
+
# #del arquivo["sys_id"]
|
649
|
+
# #del arquivo["download_link"]
|
650
|
+
# #del arquivo["table_sys_id"]
|
651
|
+
# anexo_dict["anexos"].append(arquivo)
|
652
|
+
# #return JSONResponse(content=arquivo, status_code=byte_response.status_code, media_type="application/json")
|
653
|
+
# # Requisicao não OK (!= 200)
|
654
|
+
# return {"success" : True, "result" : anexo_dict, "error": None, "status_code" : byte_response.status_code }
|
454
655
|
|
455
|
-
|
656
|
+
# except Exception as e:
|
657
|
+
# return {"success" : False, "result" : None, "error" : e, "status_code" : byte_response.status_code}
|
456
658
|
|
457
|
-
|
458
|
-
|
459
|
-
return response.json()
|
659
|
+
# elif response.status_code != 200:
|
660
|
+
# return {"success" : False , "result" : response.json(), "status_code" : response.status_code, "error" : None}
|
460
661
|
|
461
|
-
|
462
|
-
|
463
|
-
raise e
|
662
|
+
# except Exception as e:
|
663
|
+
# return {"success" : False, "result" : None, "error" : e}
|
464
664
|
|
465
|
-
# Fazendo download do anexo do campo especificado do ticket
|
466
|
-
else:
|
665
|
+
# # Fazendo download do anexo do campo especificado do ticket
|
666
|
+
# else:
|
467
667
|
|
468
|
-
|
668
|
+
# logging.debug(f"verificando o campo '{campo}' de anexo")
|
469
669
|
|
470
|
-
|
471
|
-
|
472
|
-
|
670
|
+
# url = f"{self.api_url}/now/table/{tabela}?sysparm_query=sys_id={sys_id}&sysparm_fields={campo}&sysparm_display_value=all&sysparam_limit=1"
|
671
|
+
# try:
|
672
|
+
# response = self.request(url)
|
673
|
+
# # response = requests.get(url, auth=(self.username, self.password), verify=True)
|
473
674
|
|
474
|
-
|
475
|
-
|
675
|
+
# # Requisicao OK e com anexo
|
676
|
+
# if response.status_code == 200 and len(response.json()["result"]) >= 1:
|
476
677
|
|
477
|
-
|
678
|
+
# for ticket_data in response.json()["result"]:
|
478
679
|
|
479
|
-
|
680
|
+
# if len(ticket_data) >= 1:
|
480
681
|
|
481
|
-
|
482
|
-
|
483
|
-
#temp_file_name = ticket_data[campo]["display_value"]
|
484
|
-
temp_sys_id = ticket_data[campo]["value"]
|
485
|
-
|
486
|
-
except Exception as e:
|
487
|
-
|
488
|
-
raise e
|
682
|
+
# try:
|
489
683
|
|
490
|
-
|
684
|
+
# #temp_file_name = ticket_data[campo]["display_value"]
|
685
|
+
# temp_sys_id = ticket_data[campo]["value"]
|
686
|
+
# url = f"{self.api_url}/now/table/sys_attachment?sysparm_query=sys_id={temp_sys_id}&sysparm_display_value=all&sysparam_limit=1"
|
687
|
+
# response = self.request(url=url)
|
491
688
|
|
492
|
-
|
689
|
+
# except Exception as e:
|
690
|
+
# return {"success" : False, "error" : e, "result" : None}
|
493
691
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
# Requisicao não OK (!= 200)
|
521
|
-
elif response.status_code != 200:
|
522
|
-
|
523
|
-
return response.json()
|
692
|
+
# if response.status_code == 200:
|
693
|
+
|
694
|
+
# attachment = response.json()["result"][0]
|
695
|
+
# arquivo = {}
|
696
|
+
# arquivo["file_name"] = attachment["file_name"]["display_value"]
|
697
|
+
# arquivo["size"] = __formatar_tamanho(attachment["size_bytes"]["display_value"])
|
698
|
+
# #arquivo["sys_id"] = attachment["sys_id"]
|
699
|
+
# arquivo["content_type"] = attachment["content_type"]["value"]
|
700
|
+
# #attach_sys_id = attachment["sys_id"]["display_value"]
|
701
|
+
# #attach_sys_id_table = attachment["table_sys_id"]["value"]
|
702
|
+
# #url = f"{self.api_url}/now/attachment/{sys_id}/file"
|
703
|
+
# try:
|
704
|
+
|
705
|
+
# url = f"{self.api_url}/now/attachment/{temp_sys_id}/file"
|
706
|
+
# byte_response = self.request(url)
|
707
|
+
# # byte_response = requests.get(url, auth=(self.username, self.password), verify=True)
|
708
|
+
# arquivo["base64"] = __bytes_to_base64(byte_response.content)
|
709
|
+
# anexo_dict["anexos"].append(arquivo)
|
710
|
+
# if download_dir is not None:
|
711
|
+
|
712
|
+
# with open(arquivo["file_name"],'wb') as download_file:
|
713
|
+
# download_file.write(byte_response.content)
|
714
|
+
# return {"success" : True, "status_code" : response.status_code, "error" : None, "result" : anexo_dict}
|
715
|
+
# except Exception as e:
|
716
|
+
# return {"success" : False, "status_code" : response.status_code, "error" : e, "result" : response.json()}
|
524
717
|
|
525
|
-
|
718
|
+
# # Requisicao não OK (!= 200)
|
719
|
+
# elif response.status_code != 200:
|
720
|
+
# return {"success" : False, "status_code" : response.status_code, "error" : e, "result" : response.json()}
|
526
721
|
|
527
|
-
|
528
|
-
|
529
|
-
return anexo_dict
|
722
|
+
# except Exception as e:
|
723
|
+
# return {"success" : False, "error" : e, "result" : None}
|