csc-cia-stne 0.0.13__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 +8 -0
- csc_cia_stne/bc_correios.py +516 -0
- csc_cia_stne/bc_sta.py +301 -0
- csc_cia_stne/cia_logging.py +149 -0
- csc_cia_stne/gcp_bigquery.py +182 -0
- csc_cia_stne/karavela.py +123 -0
- csc_cia_stne/logger.py +155 -0
- csc_cia_stne/servicenow.py +529 -0
- csc_cia_stne/stne_admin.py +423 -0
- csc_cia_stne/utilitarios.py +72 -0
- csc_cia_stne-0.0.13.dist-info/LICENCE +21 -0
- csc_cia_stne-0.0.13.dist-info/METADATA +25 -0
- csc_cia_stne-0.0.13.dist-info/RECORD +15 -0
- csc_cia_stne-0.0.13.dist-info/WHEEL +5 -0
- csc_cia_stne-0.0.13.dist-info/top_level.txt +1 -0
csc_cia_stne/bc_sta.py
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
from requests.auth import HTTPBasicAuth
|
2
|
+
import sys
|
3
|
+
from pydantic import BaseModel
|
4
|
+
import requests
|
5
|
+
import xml.etree.ElementTree as ET
|
6
|
+
import hashlib
|
7
|
+
from pydantic import BaseModel, StrictStr, StrictInt, ValidationError, field_validator, FieldValidationInfo
|
8
|
+
from typing import Literal, Dict, Union, Optional
|
9
|
+
|
10
|
+
# Validações dos inputs
|
11
|
+
class InitParamsValidator(BaseModel):
|
12
|
+
usuario: str
|
13
|
+
senha: str
|
14
|
+
ambiente: Literal["prd", "hml"] # Aceita apenas "prd" ou "sdx"
|
15
|
+
|
16
|
+
# Validação para garantir que cada parâmetro é uma string não vazia
|
17
|
+
@field_validator('usuario', 'senha')
|
18
|
+
def check_non_empty_string(cls, value, info):
|
19
|
+
if not isinstance(value, str) or not value.strip():
|
20
|
+
|
21
|
+
raise ValueError(f"O parâmetro '{info.field_name}' deve ser uma string não vazia.")
|
22
|
+
|
23
|
+
return value
|
24
|
+
|
25
|
+
class EnviarArquivoValidator(BaseModel):
|
26
|
+
tipo_arquivo:str
|
27
|
+
file_content:bytes
|
28
|
+
nome_arquivo:str
|
29
|
+
observacao:str
|
30
|
+
destinatarios:Optional[Union[Dict[str, str], str]] = None # Aceita um dicionário
|
31
|
+
|
32
|
+
@field_validator("tipo_arquivo")
|
33
|
+
def check_tipo_arquivo(cls, value):
|
34
|
+
if not isinstance(value, str) or not value.strip():
|
35
|
+
|
36
|
+
raise ValueError("O parâmetro 'tipo_arquivo' deve ser uma string não vazia, com o código do tipo de envio a ser utilizado para o STA, verificar codigos disponíveis na documentação STA")
|
37
|
+
|
38
|
+
return value
|
39
|
+
|
40
|
+
# Validador para file_content: deve ter conteúdo em bytes
|
41
|
+
@field_validator("file_content")
|
42
|
+
def check_file_content(cls, value):
|
43
|
+
if not isinstance(value, bytes) or len(value) == 0:
|
44
|
+
raise ValueError("O parâmetro 'file_content' deve ser um byte array não vazio.")
|
45
|
+
return value
|
46
|
+
|
47
|
+
# Validador para nome_arquivo: deve ser uma string não vazia e terminar com uma extensão de arquivo comum
|
48
|
+
@field_validator("nome_arquivo")
|
49
|
+
def check_nome_arquivo(cls, value):
|
50
|
+
if not isinstance(value, str) or not value.strip():
|
51
|
+
raise ValueError("O nome do arquivo deve ser uma string não vazia.")
|
52
|
+
if not value.lower().endswith(('.zip', '.xpto')):
|
53
|
+
raise ValueError("O nome do arquivo deve ter uma extensão válida.")
|
54
|
+
return value
|
55
|
+
|
56
|
+
@field_validator("observacao")
|
57
|
+
def check_observacao(cls, value):
|
58
|
+
if not isinstance(value, str) or not value.strip():
|
59
|
+
|
60
|
+
raise ValueError("O parâmetro 'observacao' deve ser uma string não vazia")
|
61
|
+
|
62
|
+
return value
|
63
|
+
|
64
|
+
# Validador para destinatarios: aceita um dicionário ou uma string XML opcional
|
65
|
+
@field_validator("destinatarios")
|
66
|
+
def check_destinatarios(cls, value):
|
67
|
+
if value is not None:
|
68
|
+
|
69
|
+
if not isinstance(value, list):
|
70
|
+
|
71
|
+
raise ValueError("O parâmetro 'destinatarios' deve ser uma lista de dicionários.")
|
72
|
+
|
73
|
+
for item in value:
|
74
|
+
|
75
|
+
if not isinstance(item, dict):
|
76
|
+
|
77
|
+
raise ValueError("Cada destinatário deve ser um dicionário.")
|
78
|
+
|
79
|
+
required_keys = {"unidade", "dependencia", "operador"}
|
80
|
+
|
81
|
+
if not required_keys.issubset(item.keys()):
|
82
|
+
|
83
|
+
raise ValueError("Cada destinatário deve conter as chaves 'unidade', 'dependencia' e 'operador'. Verifique a documentação da API BC STA para entender o que colocar cada campo")
|
84
|
+
|
85
|
+
return value
|
86
|
+
|
87
|
+
class BC_STA:
|
88
|
+
|
89
|
+
def __init__(self, usuario:str, senha:str, ambiente:str):
|
90
|
+
|
91
|
+
try:
|
92
|
+
|
93
|
+
InitParamsValidator(usuario=usuario, senha=senha, ambiente=ambiente)
|
94
|
+
|
95
|
+
except ValidationError as e:
|
96
|
+
|
97
|
+
raise ValueError("Erro na validação dos dados de input da inicialização da instância 'BC_STA':", e.errors())
|
98
|
+
|
99
|
+
if ambiente == 'prd':
|
100
|
+
|
101
|
+
self.base_url = "https://sta.bcb.gov.br/staws"
|
102
|
+
|
103
|
+
else:
|
104
|
+
|
105
|
+
self.base_url = "https://sta-h.bcb.gov.br/staws"
|
106
|
+
|
107
|
+
try:
|
108
|
+
|
109
|
+
self.auth = HTTPBasicAuth(usuario,senha)
|
110
|
+
self.error = None
|
111
|
+
self.headers = {'Content-Type': 'application/xml'}
|
112
|
+
self.is_connected = self.verifica_conexao()
|
113
|
+
|
114
|
+
except Exception as e:
|
115
|
+
|
116
|
+
self.is_connected = False
|
117
|
+
self.error = e
|
118
|
+
|
119
|
+
def verifica_conexao(self):
|
120
|
+
try:
|
121
|
+
|
122
|
+
response = requests.get("https://sta.bcb.gov.br/staws/arquivos?tipoConsulta=AVANC&nivelDetalhe=RES", auth=self.auth)
|
123
|
+
|
124
|
+
# Verificando o status e retornando a resposta
|
125
|
+
if response.status_code == 200:
|
126
|
+
|
127
|
+
return True
|
128
|
+
|
129
|
+
else:
|
130
|
+
|
131
|
+
return False
|
132
|
+
|
133
|
+
|
134
|
+
except Exception as e:
|
135
|
+
|
136
|
+
raise e
|
137
|
+
|
138
|
+
def enviar_arquivo(self, tipo_arquivo:str, file_content:bytes, nome_arquivo:str, observacao:str, destinatarios:dict=None):
|
139
|
+
|
140
|
+
try:
|
141
|
+
|
142
|
+
EnviarArquivoValidator(tipo_arquivo=tipo_arquivo, file_content=file_content, nome_arquivo=nome_arquivo, observacao=observacao, destinatarios=destinatarios)
|
143
|
+
|
144
|
+
except ValidationError as e:
|
145
|
+
|
146
|
+
raise ValueError("Erro na validação dos dados de input do método 'enviar_arquivo':", e.errors())
|
147
|
+
|
148
|
+
def generate_sha256_hash(file_content):
|
149
|
+
|
150
|
+
# Gera o hash SHA-256 do arquivo
|
151
|
+
sha256_hash = hashlib.sha256()
|
152
|
+
sha256_hash.update(file_content)
|
153
|
+
return sha256_hash.hexdigest()
|
154
|
+
|
155
|
+
def process_response(xml_content):
|
156
|
+
|
157
|
+
try:
|
158
|
+
|
159
|
+
root = ET.fromstring(xml_content)
|
160
|
+
|
161
|
+
# Verifica se há um elemento <Erro> no XML
|
162
|
+
erro = None
|
163
|
+
erro_elem = root.find('Erro')
|
164
|
+
|
165
|
+
if erro_elem is not None:
|
166
|
+
|
167
|
+
codigo_erro = erro_elem.find('Codigo').text
|
168
|
+
descricao_erro = erro_elem.find('Descricao').text
|
169
|
+
erro = f"Erro {codigo_erro}: {descricao_erro}"
|
170
|
+
resposta = {
|
171
|
+
'enviado':False,
|
172
|
+
'protocolo': None,
|
173
|
+
'link': None,
|
174
|
+
'erro': erro
|
175
|
+
}
|
176
|
+
|
177
|
+
else:
|
178
|
+
|
179
|
+
protocolo = root.find('Protocolo').text
|
180
|
+
link = root.find('.//atom:link', namespaces={'atom': 'http://www.w3.org/2005/Atom'}).attrib['href']
|
181
|
+
resposta = {
|
182
|
+
'enviado':True,
|
183
|
+
'protocolo': protocolo,
|
184
|
+
'link': link,
|
185
|
+
'erro': None
|
186
|
+
}
|
187
|
+
|
188
|
+
return resposta
|
189
|
+
|
190
|
+
except ET.ParseError as e:
|
191
|
+
|
192
|
+
resposta = {
|
193
|
+
'enviado': False,
|
194
|
+
'protocolo': None,
|
195
|
+
'link': None,
|
196
|
+
'erro': f"Error processing XML: {str(e)}"
|
197
|
+
}
|
198
|
+
return resposta
|
199
|
+
|
200
|
+
url = self.base_url + '/arquivos'
|
201
|
+
|
202
|
+
# Calcula o hash SHA-256 do conteúdo do arquivo
|
203
|
+
hash_sha256 = generate_sha256_hash(file_content)
|
204
|
+
tamanho_arquivo = len(file_content) # Tamanho do arquivo em bytes
|
205
|
+
|
206
|
+
# Constrói o XML de requisição
|
207
|
+
parametros = ET.Element('Parametros')
|
208
|
+
ET.SubElement(parametros, 'IdentificadorDocumento').text = tipo_arquivo
|
209
|
+
ET.SubElement(parametros, 'Hash').text = hash_sha256
|
210
|
+
ET.SubElement(parametros, 'Tamanho').text = str(tamanho_arquivo)
|
211
|
+
ET.SubElement(parametros, 'NomeArquivo').text = nome_arquivo
|
212
|
+
|
213
|
+
# Campo observação é opcional
|
214
|
+
if observacao:
|
215
|
+
ET.SubElement(parametros, 'Observacao').text = observacao
|
216
|
+
|
217
|
+
# Campo destinatários é opcional
|
218
|
+
if destinatarios:
|
219
|
+
|
220
|
+
destinatarios_elem = ET.SubElement(parametros, 'Destinatarios')
|
221
|
+
|
222
|
+
for dest in destinatarios:
|
223
|
+
|
224
|
+
destinatario_elem = ET.SubElement(destinatarios_elem, 'Destinatario')
|
225
|
+
ET.SubElement(destinatario_elem, 'Unidade').text = dest['unidade']
|
226
|
+
|
227
|
+
if 'dependencia' in dest:
|
228
|
+
|
229
|
+
ET.SubElement(destinatario_elem, 'Dependencia').text = dest['dependencia']
|
230
|
+
|
231
|
+
if 'operador' in dest:
|
232
|
+
|
233
|
+
ET.SubElement(destinatario_elem, 'Operador').text = dest['operador']
|
234
|
+
|
235
|
+
# Converte o XML para string
|
236
|
+
xml_data = ET.tostring(parametros, encoding='utf-8', method='xml')
|
237
|
+
|
238
|
+
# Envia a requisição POST
|
239
|
+
response = requests.post(url, headers=self.headers, data=xml_data, auth=self.auth, timeout=60)
|
240
|
+
|
241
|
+
if response.status_code == 201: # Verifica se o protocolo foi criado com sucesso
|
242
|
+
|
243
|
+
resultado_protocolo = process_response(response.text)
|
244
|
+
|
245
|
+
# Protocolo gerado, prosseguir com envio do arquivo
|
246
|
+
if resultado_protocolo["enviado"]:
|
247
|
+
|
248
|
+
try:
|
249
|
+
|
250
|
+
# Solicita o envio
|
251
|
+
protocolo = resultado_protocolo["protocolo"]
|
252
|
+
# URL do endpoint, incluindo o protocolo
|
253
|
+
url = url + f"/{protocolo}/conteudo"
|
254
|
+
|
255
|
+
# Envia a requisição PUT com o conteúdo binário do arquivo
|
256
|
+
response = requests.put(url, data=file_content, auth=self.auth, timeout=60)
|
257
|
+
|
258
|
+
if response.status_code == 200:
|
259
|
+
|
260
|
+
return resultado_protocolo
|
261
|
+
|
262
|
+
else:
|
263
|
+
|
264
|
+
resposta = {
|
265
|
+
'enviado':False,
|
266
|
+
'protocolo': None,
|
267
|
+
'link': None,
|
268
|
+
'erro': f"Falha ao enviar arquivo. Status code: {response.status_code}, Text: {response.text}, Reason: {response.reason}"
|
269
|
+
}
|
270
|
+
return resposta
|
271
|
+
|
272
|
+
except Exception as e:
|
273
|
+
|
274
|
+
erro = str(e)
|
275
|
+
resposta = {
|
276
|
+
'enviado':False,
|
277
|
+
'protocolo': None,
|
278
|
+
'link': None,
|
279
|
+
'erro': erro
|
280
|
+
}
|
281
|
+
return resposta
|
282
|
+
|
283
|
+
|
284
|
+
|
285
|
+
# Protocolo não foi gerado, retornar erro
|
286
|
+
else:
|
287
|
+
|
288
|
+
return resultado_protocolo
|
289
|
+
|
290
|
+
else:
|
291
|
+
|
292
|
+
print(response.text)
|
293
|
+
resposta = {
|
294
|
+
'enviado': False,
|
295
|
+
'protocolo': None,
|
296
|
+
'link': None,
|
297
|
+
'erro': f"Failed to create protocol. Status code: {response.status_code}, Reason: {response.reason}"
|
298
|
+
}
|
299
|
+
#return f"Failed to create protocol. Status code: {response.status_code}, Reason: {response.reason}"
|
300
|
+
return resposta
|
301
|
+
|
@@ -0,0 +1,149 @@
|
|
1
|
+
import logging
|
2
|
+
from rich.logging import RichHandler
|
3
|
+
from rich.theme import Theme
|
4
|
+
from rich.console import Console
|
5
|
+
import re
|
6
|
+
from pythonjsonlogger import jsonlogger
|
7
|
+
from rich.traceback import install
|
8
|
+
|
9
|
+
def logger(env:str = "local", log_level:str = "DEBUG"):
|
10
|
+
|
11
|
+
def get_log_level(log_level):
|
12
|
+
if log_level.upper().strip() == "DEBUG":
|
13
|
+
|
14
|
+
log_config_level = logging.DEBUG
|
15
|
+
|
16
|
+
elif log_level.upper().strip() == "INFO":
|
17
|
+
|
18
|
+
log_config_level = logging.INFO
|
19
|
+
|
20
|
+
elif log_level.upper().strip() == "WARNING":
|
21
|
+
|
22
|
+
log_config_level = logging.WARNING
|
23
|
+
|
24
|
+
elif log_level.upper().strip() == "ERROR":
|
25
|
+
|
26
|
+
log_config_level = logging.ERROR
|
27
|
+
|
28
|
+
elif log_level.upper().strip() == "CRITICAL":
|
29
|
+
|
30
|
+
log_config_level = logging.CRITICAL
|
31
|
+
|
32
|
+
else:
|
33
|
+
|
34
|
+
log_config_level = logging.INFO # ou outro nível padrão
|
35
|
+
|
36
|
+
return log_config_level
|
37
|
+
|
38
|
+
def add_log_level(level_name, level_num, method_name=None):
|
39
|
+
"""
|
40
|
+
Adiciona um log level
|
41
|
+
|
42
|
+
Parâmetros:
|
43
|
+
level_name (str): Nome do level
|
44
|
+
level_num (int): Número do level
|
45
|
+
"""
|
46
|
+
if not method_name:
|
47
|
+
|
48
|
+
method_name = level_name.lower()
|
49
|
+
|
50
|
+
if hasattr(logging, level_name):
|
51
|
+
|
52
|
+
raise AttributeError('{} already defined in logging module'.format(level_name))
|
53
|
+
|
54
|
+
if hasattr(logging, method_name):
|
55
|
+
|
56
|
+
raise AttributeError('{} already defined in logging module'.format(method_name))
|
57
|
+
|
58
|
+
if hasattr(logging.getLoggerClass(), method_name):
|
59
|
+
|
60
|
+
raise AttributeError('{} already defined in logger class'.format(method_name))
|
61
|
+
|
62
|
+
def log_for_level(self, message, *args, **kwargs):
|
63
|
+
|
64
|
+
if self.isEnabledFor(level_num):
|
65
|
+
|
66
|
+
self._log(level_num, message, args, **kwargs)
|
67
|
+
|
68
|
+
def log_to_root(message, *args, **kwargs):
|
69
|
+
|
70
|
+
logging.log(level_num, message, *args, **kwargs)
|
71
|
+
|
72
|
+
logging.addLevelName(level_num, level_name)
|
73
|
+
setattr(logging, level_name, level_num)
|
74
|
+
setattr(logging.getLoggerClass(), method_name, log_for_level)
|
75
|
+
setattr(logging, method_name, log_to_root)
|
76
|
+
|
77
|
+
add_log_level("SUCCESS",21)
|
78
|
+
|
79
|
+
if env.upper().strip() != "KARAVELA":
|
80
|
+
|
81
|
+
install()
|
82
|
+
|
83
|
+
# Definindo o tema customizado
|
84
|
+
custom_theme = Theme({
|
85
|
+
# python -m rich.color - cores
|
86
|
+
# python -m rich.default_styles - item + cor padrão
|
87
|
+
"logging.level.debug": "bold bright_cyan",
|
88
|
+
"logging.level.info": "bold bright_white",
|
89
|
+
"logging.level.warning": "bold orange1",
|
90
|
+
"logging.level.error": "bold red blink",
|
91
|
+
"logging.level.critical": "bold white on red blink",
|
92
|
+
"logging.level.success": "bold bright_green",
|
93
|
+
"log.time":"bold white",
|
94
|
+
"log.message":"bold gray70",
|
95
|
+
"repr.str":"dark_olive_green3",
|
96
|
+
"inspect.value.border":"blue",
|
97
|
+
})
|
98
|
+
|
99
|
+
console = Console(theme=custom_theme)
|
100
|
+
|
101
|
+
class CustomRichHandler(RichHandler):
|
102
|
+
def __init__(self, *args, rich_tracebacks=True, show_time=True, show_level=True, show_path=True, console=console, **kwargs):
|
103
|
+
super().__init__(rich_tracebacks=rich_tracebacks, show_time=show_time, show_level=show_level, show_path=show_path, console=console, *args, **kwargs)
|
104
|
+
self.show_time = show_time
|
105
|
+
|
106
|
+
def format(self, record: logging.LogRecord) -> str:
|
107
|
+
|
108
|
+
def remover_prefixo_log(mensagem):
|
109
|
+
# Expressão regular que encontra o padrão 'log_level:nivel: '
|
110
|
+
padrao = r'^(ERROR|WARNING|INFO|DEBUG|SUCCESS|CRITICAL):[^:]*:'
|
111
|
+
|
112
|
+
# Substitui o padrão encontrado por uma string vazia
|
113
|
+
return re.sub(padrao, '', mensagem).strip()
|
114
|
+
|
115
|
+
msg = f"| {record.getMessage()}"
|
116
|
+
|
117
|
+
return(str(msg))
|
118
|
+
|
119
|
+
# Configurando o logging com o CustomRichHandler
|
120
|
+
logging.basicConfig(
|
121
|
+
level=get_log_level(log_level),
|
122
|
+
handlers=[CustomRichHandler()],
|
123
|
+
datefmt="%d/%m/%Y %H:%M:%S |",
|
124
|
+
)
|
125
|
+
|
126
|
+
return logging.getLogger()
|
127
|
+
|
128
|
+
else:
|
129
|
+
|
130
|
+
def setup_json_logger():
|
131
|
+
logger = logging.getLogger()
|
132
|
+
logger.setLevel(get_log_level(log_level))
|
133
|
+
|
134
|
+
# Remove handlers anteriores, se houver
|
135
|
+
if logger.hasHandlers():
|
136
|
+
logger.handlers.clear()
|
137
|
+
|
138
|
+
log_handler = logging.StreamHandler()
|
139
|
+
formatter = jsonlogger.JsonFormatter(
|
140
|
+
fmt='%(asctime)s %(levelname)s %(name)s %(message)s %(pathname)s %(lineno)d %(exc_info)s %(stack_info)s %(funcName)s %(module)s',
|
141
|
+
json_ensure_ascii=False
|
142
|
+
)
|
143
|
+
log_handler.setFormatter(formatter)
|
144
|
+
logger.addHandler(log_handler)
|
145
|
+
|
146
|
+
return logger
|
147
|
+
|
148
|
+
# Chama a função para configurar o logger
|
149
|
+
return setup_json_logger()
|
@@ -0,0 +1,182 @@
|
|
1
|
+
from google.cloud import bigquery
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
from google.oauth2 import service_account
|
5
|
+
|
6
|
+
class BigQuery():
|
7
|
+
|
8
|
+
def __init__(self, client: bigquery.Client, limit: int = 3):
|
9
|
+
"""
|
10
|
+
Inicializa a classe BigQuery.
|
11
|
+
Parâmetros:
|
12
|
+
limit (int): O limite de resultados a serem retornados. O valor padrão é 3.
|
13
|
+
client (bigquery.Client): O cliente BigQuery a ser utilizado.
|
14
|
+
"""
|
15
|
+
|
16
|
+
self.limit = limit
|
17
|
+
self.client = client
|
18
|
+
|
19
|
+
def create_client(id_project: str, creds_dict: dict = None, creds_file: str = "") -> bigquery.Client:
|
20
|
+
"""
|
21
|
+
Cria um cliente BigQuery com base nas credenciais fornecidas.
|
22
|
+
Parâmetros:
|
23
|
+
- creds_dict (dict): Dicionário contendo as informações das credenciais. Opcional se creds_file for fornecido.
|
24
|
+
- creds_file (str): Caminho do arquivo de credenciais. Opcional se creds_dict for fornecido.
|
25
|
+
- id_project (str): ID do projeto BigQuery.
|
26
|
+
Retorna:
|
27
|
+
- client (bigquery.Client): Cliente BigQuery criado com base nas credenciais fornecidas.
|
28
|
+
Exceções:
|
29
|
+
- Retorna um dicionário com as seguintes chaves em caso de erro:
|
30
|
+
- 'status' (bool): False
|
31
|
+
- 'error' (str): Mensagem de erro
|
32
|
+
- 'details' (str): Detalhes do erro
|
33
|
+
"""
|
34
|
+
|
35
|
+
try:
|
36
|
+
|
37
|
+
if(creds_dict not None):
|
38
|
+
|
39
|
+
credentials = service_account.Credentials.from_service_account_info(
|
40
|
+
creds_dict,
|
41
|
+
scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
42
|
+
)
|
43
|
+
|
44
|
+
client = bigquery.Client(credentials=credentials, project=id_project)
|
45
|
+
|
46
|
+
elif(str(creds_file) > 0):
|
47
|
+
|
48
|
+
credentials = service_account.Credentials.from_service_account_file(
|
49
|
+
creds_file,
|
50
|
+
scopes=["https://www.googleapis.com/auth/cloud-platform"],
|
51
|
+
)
|
52
|
+
|
53
|
+
else:
|
54
|
+
|
55
|
+
return {
|
56
|
+
'status':False,
|
57
|
+
'error':"Credenciais não fornecidas"
|
58
|
+
}
|
59
|
+
|
60
|
+
return client
|
61
|
+
|
62
|
+
except Exception as e:
|
63
|
+
|
64
|
+
return {
|
65
|
+
'status':False,
|
66
|
+
'error':'Problema ao tentar gerar o client do big query',
|
67
|
+
'details':str(e)
|
68
|
+
}
|
69
|
+
|
70
|
+
def try_query(self, query_to_execute:str,organize:bool=True) -> dict:
|
71
|
+
"""
|
72
|
+
Executa uma consulta no BigQuery e retorna o resultado.
|
73
|
+
Args:
|
74
|
+
query_to_execute (str): A consulta a ser executada.
|
75
|
+
organize (bool, optional): Indica se o resultado deve ser organizado em um formato específico.
|
76
|
+
O padrão é True.
|
77
|
+
Returns:
|
78
|
+
dict: Um dicionário contendo o status da consulta e o resultado, se houver.
|
79
|
+
Exemplo:
|
80
|
+
{
|
81
|
+
'status': True,
|
82
|
+
'resultado': result_query
|
83
|
+
Se ocorrer um erro durante a execução da consulta, o dicionário de retorno terá o seguinte formato:
|
84
|
+
{
|
85
|
+
'status': False,
|
86
|
+
"""
|
87
|
+
|
88
|
+
error = ""
|
89
|
+
|
90
|
+
for try_out in range(self.limit):
|
91
|
+
|
92
|
+
try:
|
93
|
+
|
94
|
+
result_query = self.client.query(query_to_execute).result()
|
95
|
+
|
96
|
+
error = False
|
97
|
+
|
98
|
+
if organize:
|
99
|
+
|
100
|
+
result_rows = [dict(row) for row in result_query['resultado']]
|
101
|
+
|
102
|
+
result_query = result_rows
|
103
|
+
|
104
|
+
break
|
105
|
+
|
106
|
+
except Exception as e:
|
107
|
+
|
108
|
+
error = e
|
109
|
+
|
110
|
+
if not error:
|
111
|
+
|
112
|
+
return {
|
113
|
+
'status':True,
|
114
|
+
'resultado':result_query
|
115
|
+
}
|
116
|
+
|
117
|
+
else:
|
118
|
+
|
119
|
+
return {
|
120
|
+
'status':False,
|
121
|
+
'error': str(error)
|
122
|
+
}
|
123
|
+
|
124
|
+
def insert_list(self,table:str,dict_to_insert:dict=[]) -> dict:
|
125
|
+
|
126
|
+
"""
|
127
|
+
Insere uma lista de dicionários em uma tabela do BigQuery.
|
128
|
+
Args:
|
129
|
+
client (bigquery.Client): Cliente do BigQuery.
|
130
|
+
table (str): Nome da tabela onde os dados serão inseridos.
|
131
|
+
dict_to_insert (dict, optional): Lista de dicionários a serem inseridos. O padrão é [].
|
132
|
+
limit_trys (int, optional): Número máximo de tentativas de inserção. O padrão é 3.
|
133
|
+
Returns:
|
134
|
+
dict: Dicionário contendo o status da inserção e informações adicionais.
|
135
|
+
- Se a inserção for bem-sucedida:
|
136
|
+
{'status': True, 'inserted': inserted}
|
137
|
+
- Se ocorrer um erro durante a inserção:
|
138
|
+
{'status': False, 'error': error, 'last_try': list_to_insert, 'inserted': inserted}
|
139
|
+
"""
|
140
|
+
|
141
|
+
table_ref = self.client.get_table(table)
|
142
|
+
|
143
|
+
error = ""
|
144
|
+
|
145
|
+
inserted = []
|
146
|
+
|
147
|
+
for data in range(0, len(dict_to_insert), 10000):
|
148
|
+
|
149
|
+
# listagem que será inserida no big query
|
150
|
+
list_to_insert = dict_to_insert[data:data+10000]
|
151
|
+
|
152
|
+
for try_out in range(self.limit):
|
153
|
+
|
154
|
+
try:
|
155
|
+
|
156
|
+
self.client.insert_rows(table_ref, list_to_insert)
|
157
|
+
|
158
|
+
error = False
|
159
|
+
|
160
|
+
except Exception as e:
|
161
|
+
|
162
|
+
error = e
|
163
|
+
|
164
|
+
if not error:
|
165
|
+
|
166
|
+
inserted.extend(list_to_insert)
|
167
|
+
|
168
|
+
continue
|
169
|
+
|
170
|
+
else:
|
171
|
+
|
172
|
+
return{
|
173
|
+
'status':False,
|
174
|
+
'error':str(error),
|
175
|
+
'last_try':list_to_insert,
|
176
|
+
'inserted':inserted
|
177
|
+
}
|
178
|
+
|
179
|
+
return {
|
180
|
+
'status':True,
|
181
|
+
'inserted':inserted
|
182
|
+
}
|