lbxtoolkit 2.2.9__tar.gz → 2.2.11__tar.gz
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.
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/PKG-INFO +1 -1
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/lbxtoolkit/lbx_logger.py +23 -4
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/lbxtoolkit/misc.py +13 -4
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/lbxtoolkit/postgresql.py +66 -11
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/lbxtoolkit/service_windows.py +40 -0
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/pyproject.toml +1 -1
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/README.md +0 -0
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/lbxtoolkit/__init__.py +0 -0
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/lbxtoolkit/api_rest.py +0 -0
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/lbxtoolkit/auth_entra_id.py +0 -0
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/lbxtoolkit/config_manager.py +0 -0
- {lbxtoolkit-2.2.9 → lbxtoolkit-2.2.11}/lbxtoolkit/service.py +0 -0
|
@@ -3,6 +3,7 @@ import sys
|
|
|
3
3
|
import re
|
|
4
4
|
import datetime
|
|
5
5
|
import logging
|
|
6
|
+
import traceback
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
class lbx_logger: # Classe para gerenciar a saída para log
|
|
@@ -75,6 +76,9 @@ Considerando que os parametros são posicionais, caso queira omitir apenas um do
|
|
|
75
76
|
|
|
76
77
|
A saída dessa função retorna um objeto, que pode ser salvo em disco ou impresso na tela.
|
|
77
78
|
|
|
79
|
+
5. O método `.traceback_log`, quando acionado, grava, de maneira incremental, o tracback do último erro no arquivo `traceback.log` no mesmo diretório de log onde o arquivo principal de log está salvo. Se o log não está sendo salvo, a mensagem será exibida na tela.
|
|
80
|
+
|
|
81
|
+
Os métodos `.erro()` e `.critico()` fazem isso automaticamente por padrão.
|
|
78
82
|
|
|
79
83
|
5. Exemplos de uso:
|
|
80
84
|
|
|
@@ -168,7 +172,7 @@ for line in filtered_lines:
|
|
|
168
172
|
|
|
169
173
|
if log_file_path:
|
|
170
174
|
# Criando um handler para escrever em um arquivo de log
|
|
171
|
-
file_handler = logging.FileHandler(self.log_file_path)
|
|
175
|
+
file_handler = logging.FileHandler(self.log_file_path, encoding="utf-8")
|
|
172
176
|
file_handler.setLevel(log_level) # Sempre registrar tudo no arquivo
|
|
173
177
|
|
|
174
178
|
# Criando um handler para exibir no console
|
|
@@ -187,6 +191,9 @@ for line in filtered_lines:
|
|
|
187
191
|
# Adicionando os handlers ao logger
|
|
188
192
|
self.logger.addHandler(file_handler)
|
|
189
193
|
self.logger.addHandler(console_handler)
|
|
194
|
+
|
|
195
|
+
# Saida para o traceback
|
|
196
|
+
self.traceback_file = Path(self.log_file_path.parent, 'traceback.log')
|
|
190
197
|
else:
|
|
191
198
|
# Tudo direcionado para o console
|
|
192
199
|
console_handler = logging.StreamHandler()
|
|
@@ -245,6 +252,7 @@ for line in filtered_lines:
|
|
|
245
252
|
#
|
|
246
253
|
#
|
|
247
254
|
def write(self, message):
|
|
255
|
+
message=message
|
|
248
256
|
if message.strip(): # Ignorar mensagens vazias
|
|
249
257
|
self.logger.info(message.strip())
|
|
250
258
|
#
|
|
@@ -285,6 +293,7 @@ for line in filtered_lines:
|
|
|
285
293
|
msg = self.msg[:corte] if corte else self.msg
|
|
286
294
|
self.logger.error(msg)
|
|
287
295
|
self.msg = ''
|
|
296
|
+
self.log_traceback()
|
|
288
297
|
if exit:
|
|
289
298
|
os._exit(exit)
|
|
290
299
|
#
|
|
@@ -294,6 +303,7 @@ for line in filtered_lines:
|
|
|
294
303
|
msg = self.msg[:corte] if corte else self.msg
|
|
295
304
|
self.logger.critical(msg)
|
|
296
305
|
self.msg = ''
|
|
306
|
+
self.log_traceback()
|
|
297
307
|
if exit:
|
|
298
308
|
os._exit(exit)
|
|
299
309
|
#
|
|
@@ -308,7 +318,7 @@ for line in filtered_lines:
|
|
|
308
318
|
self.logger.removeHandler(handler)
|
|
309
319
|
#
|
|
310
320
|
#
|
|
311
|
-
def filtra(self, log_file, dh_ini, dh_fim):
|
|
321
|
+
def filtra(self, log_file, dh_ini, dh_fim, logEncode='ISO-8859-1'):
|
|
312
322
|
# Validar parametros de entrada
|
|
313
323
|
if dh_ini:
|
|
314
324
|
if not isinstance(dh_ini, datetime.datetime):
|
|
@@ -364,7 +374,7 @@ for line in filtered_lines:
|
|
|
364
374
|
return dh_inicio <= timestamp <= dh_fim
|
|
365
375
|
|
|
366
376
|
# Ler e filtrar o arquivo de log com a codificação ISO-8859-1
|
|
367
|
-
with open(log_file_path, 'r', encoding=
|
|
377
|
+
with open(log_file_path, 'r', encoding=logEncode) as log_file:
|
|
368
378
|
log_lines = log_file.readlines()
|
|
369
379
|
|
|
370
380
|
# Variável para armazenar o último timestamp válido
|
|
@@ -387,7 +397,16 @@ for line in filtered_lines:
|
|
|
387
397
|
# Retornar o objeto contendo as linhas filtradas
|
|
388
398
|
return filtered_lines
|
|
389
399
|
#
|
|
390
|
-
#
|
|
400
|
+
#
|
|
401
|
+
def log_traceback(self):
|
|
402
|
+
if self.traceback_file:
|
|
403
|
+
with open(self.traceback_file, "a", encoding="utf-8") as f:
|
|
404
|
+
f.write("\n" + "-"*50 + "\n") # Separador entre erros
|
|
405
|
+
f.write(f"\n{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]}")
|
|
406
|
+
f.write(traceback.format_exc())
|
|
407
|
+
else:
|
|
408
|
+
traceback.format_exc()
|
|
409
|
+
|
|
391
410
|
class SafeLogger: ## intercepta as entradas de log para parar o daemon/watcher quando parametro EXIT existir
|
|
392
411
|
"""
|
|
393
412
|
Essa classe tem o propoósito de interceptar as ordens de saída (exit) disparadas pelos métodos do lbx_logger e interromper um daemon/serviço que esteja em execução antes.
|
|
@@ -44,14 +44,23 @@ Sintaxe e exemplos de uso. Parametros omissos assume-se os valores padrão indic
|
|
|
44
44
|
return Diretorio
|
|
45
45
|
#
|
|
46
46
|
#
|
|
47
|
-
def normaliza(Original): # Limpa e padroniza nomes
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
def normaliza(Original, Case='keep', KeepSpaces=False, KeepDelim=False, KeepDirTree=False): # Limpa e padroniza nomes
|
|
48
|
+
|
|
49
|
+
Lixo = r'?%§ªº°`´^~*|"<>!@#$%¨&*_+=-"\''
|
|
50
|
+
Lixo = Lixo + r' ' if not KeepSpaces else Lixo
|
|
51
|
+
Lixo = Lixo + r'(){}[]' if not KeepDelim else Lixo
|
|
52
|
+
Lixo = Lixo + r':/\\' if not KeepDirTree else Lixo
|
|
53
|
+
|
|
54
|
+
if Case and not Case.lower() in ['upper', 'lower', 'keep']:
|
|
55
|
+
raise ValueError('Parametro opcioanl "Case" deve ser ["upper", "lower", "keep"]. Se omisso (None), string será convertida para caixa baixa.')
|
|
56
|
+
|
|
57
|
+
Normalizar = (''.join(c for c in normalize('NFKD', Original) if c.encode('ascii', 'ignore').decode('ascii'))) if Case.lower() == 'keep' else normalize('NFKD', Original).encode('ASCII', 'ignore').decode('ASCII')
|
|
50
58
|
RemoverLixo = [c if c not in Lixo else '_' for c in Normalizar]
|
|
51
59
|
Limpo = "".join(RemoverLixo)
|
|
52
60
|
Limpo = re.sub(r'\.(?=.*\.)', '_', Limpo) # troca todos os pontos por underline
|
|
53
61
|
Limpo = re.sub(r'_+', '_', Limpo) # limpa as reptições do underline
|
|
54
|
-
|
|
62
|
+
Limpo = Limpo.lower() if not Case or Case.lower() == 'lower' else Limpo.upper() if Case.lower() == 'upper' else Limpo
|
|
63
|
+
return Limpo
|
|
55
64
|
#
|
|
56
65
|
#
|
|
57
66
|
def get_cmd_window(): # Captura a referencia da janela atual para retornar o foco à ela depois de chamar os pickers
|
|
@@ -4,6 +4,9 @@ import psycopg2
|
|
|
4
4
|
from psycopg2.extras import execute_values
|
|
5
5
|
import pandas as pd
|
|
6
6
|
import numpy as np
|
|
7
|
+
from io import StringIO
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
7
10
|
|
|
8
11
|
class postgreSQL: # Classe de acesso e interação com banco PostgreSQL
|
|
9
12
|
"""
|
|
@@ -153,11 +156,13 @@ conexao.close()
|
|
|
153
156
|
raise
|
|
154
157
|
#
|
|
155
158
|
#
|
|
156
|
-
def csv_df(self,
|
|
159
|
+
def csv_df(self, Csv, CsvDelim=';', **kwargs): # Le arquivo CSV e gera Dataframe do Pandas
|
|
157
160
|
"""
|
|
161
|
+
Parametro 'Csv' pode ser um caminho do pathlib [Path()] ou uma string com o conteúdo do CSV.
|
|
158
162
|
Os parametros de read_csv() do Pandas podem ser passados opcionalmente para a função para ajustar a importação
|
|
159
163
|
"""
|
|
160
164
|
try:
|
|
165
|
+
CsvPath = Csv if isinstance(Csv,Path) else StringIO(Csv)
|
|
161
166
|
DataFrame = pd.read_csv(CsvPath, delimiter=CsvDelim, **kwargs) # Verifique se o delimitador é ';'
|
|
162
167
|
DataFrame.replace({np.nan: None}, inplace=True) ## troca 'NaN' por None (null no postgresql)
|
|
163
168
|
return DataFrame
|
|
@@ -165,25 +170,75 @@ conexao.close()
|
|
|
165
170
|
raise
|
|
166
171
|
#
|
|
167
172
|
#
|
|
168
|
-
def db_insert_df(self, DataFrame, Tabela, Schema=None, Colunas=None, OnConflict=None): # Insere os dados de um dataframe em uma tabela equivalente no banco (exige mesma estrutura de colunas)
|
|
173
|
+
def db_insert_df(self, DataFrame, Tabela, Schema=None, Colunas=None, OnConflict=None, TypeCast=None, Returning=None): # Insere os dados de um dataframe em uma tabela equivalente no banco (exige mesma estrutura de colunas)
|
|
174
|
+
"""
|
|
175
|
+
Insere um DataFrame no PostgreSQL usando psycopg2 e execute_values().
|
|
176
|
+
|
|
177
|
+
Parâmetros:
|
|
178
|
+
- DataFrame: O DataFrame do pandas contendo os dados.
|
|
179
|
+
- Tabela: Nome da tabela no PostgreSQL.
|
|
180
|
+
- Schema: (Opcional) Schema da tabela (padrão: 'public').
|
|
181
|
+
- Colunas: (Opcional) Lista de colunas para inserir (padrão: todas as colunas do DataFrame).
|
|
182
|
+
- OnConflict: (Opcional) Cláusula ON CONFLICT para tratamento de duplicatas.
|
|
183
|
+
- TypeCast: (Opcional) Dicionário mapeando colunas para seus respectivos tipos de dado no PostgreSQL.
|
|
184
|
+
"""
|
|
169
185
|
# Essa função exige que os nomes dos cabeçalhos das colunas do CSV sejam os mesmos das colunas da tabela de destino
|
|
170
|
-
Colunas = Colunas or DataFrame.columns.tolist()
|
|
171
|
-
Valores = [tuple(Linha) for Linha in DataFrame[Colunas].values]
|
|
186
|
+
Colunas = Colunas or DataFrame.columns.tolist()
|
|
172
187
|
Schema = Schema or 'public'
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
188
|
+
TypeCast = TypeCast or {}
|
|
189
|
+
|
|
190
|
+
Valores = []
|
|
191
|
+
|
|
192
|
+
for _, Linha in DataFrame.iterrows():
|
|
193
|
+
Linha_convertida = []
|
|
194
|
+
for coluna in Colunas:
|
|
195
|
+
valor = Linha[coluna]
|
|
196
|
+
|
|
197
|
+
# 🔹 Converte Timestamp para string ISO 8601
|
|
198
|
+
if isinstance(valor, pd.Timestamp):
|
|
199
|
+
valor = valor.strftime('%Y-%m-%d %H:%M:%S')
|
|
200
|
+
|
|
201
|
+
# 🔹 Converte JSON para string válida
|
|
202
|
+
if coluna in TypeCast and TypeCast[coluna] == "jsonb":
|
|
203
|
+
if isinstance(valor, str):
|
|
204
|
+
try:
|
|
205
|
+
json.loads(valor) # Valida se já é um JSON válido
|
|
206
|
+
except json.JSONDecodeError:
|
|
207
|
+
valor = json.dumps(valor) # Serializa para JSON
|
|
208
|
+
else:
|
|
209
|
+
valor = json.dumps(valor) # Converte para JSON corretamente
|
|
210
|
+
|
|
211
|
+
Linha_convertida.append(valor)
|
|
212
|
+
|
|
213
|
+
Valores.append(tuple(Linha_convertida)) # Garante que cada linha seja uma tupla
|
|
214
|
+
|
|
215
|
+
Retorno = None
|
|
216
|
+
|
|
217
|
+
Retornar = f"RETURNING {', '.join(Returning)}" if Returning else ''
|
|
218
|
+
Conflito = OnConflict if OnConflict else ''
|
|
219
|
+
|
|
220
|
+
Parametros = ", ".join(["%s"] * len(Colunas))
|
|
221
|
+
Query = f'INSERT INTO {Schema}.{Tabela} ({", ".join(Colunas)}) VALUES ({Parametros}) {Conflito} {Retornar}'
|
|
176
222
|
|
|
177
223
|
try:
|
|
178
|
-
self.Cursor = self.Conexao.cursor()
|
|
179
|
-
|
|
180
|
-
|
|
224
|
+
self.Cursor = self.Conexao.cursor()
|
|
225
|
+
|
|
226
|
+
if Returning:
|
|
227
|
+
Retorno = []
|
|
228
|
+
for valor in Valores:
|
|
229
|
+
self.Cursor.execute(Query, valor)
|
|
230
|
+
Retorno.append(self.Cursor.fetchone())
|
|
231
|
+
else:
|
|
232
|
+
self.Cursor.executemany(Query, Valores)
|
|
233
|
+
|
|
234
|
+
self.Conexao.commit()
|
|
181
235
|
except Exception as Err:
|
|
236
|
+
self.logger.erro(f'{__name__}: Erro [{Err}] ao inserir dados na tabela {Schema}.{Tabela}!')
|
|
182
237
|
self.Conexao.rollback()
|
|
183
|
-
raise
|
|
184
238
|
finally:
|
|
185
239
|
self.Cursor.close()
|
|
186
240
|
#Conexao.close() ## conexão precisa ser fechada explicitamente fora da classe
|
|
241
|
+
return Retorno if Retorno else None
|
|
187
242
|
#
|
|
188
243
|
#
|
|
189
244
|
def db_select(self, Query): # Retorna um cursor à partir de um select
|
|
@@ -195,6 +195,46 @@ Gerencia a instalação e execução de um daemon como um serviço do windows. T
|
|
|
195
195
|
self.RunAsDaemon()
|
|
196
196
|
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
|
|
197
197
|
servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ''))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def SvcStatus(ServiceName=None): # Verifica o status de um serviço específico ou do próprio serviço
|
|
202
|
+
"""
|
|
203
|
+
Retorna o status do serviço do Windows.
|
|
204
|
+
Se um nome for passado como parâmetro, verifica o status do serviço informado.
|
|
205
|
+
Caso contrário, retorna o status do próprio serviço.
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
# Recuperar nome do serviço, se não for passado
|
|
209
|
+
if not ServiceName:
|
|
210
|
+
config = ConfigManager.get('config')
|
|
211
|
+
if not config or 'name' not in config:
|
|
212
|
+
raise ValueError("Configuração do serviço inválida. O nome do serviço não pode ser determinado.")
|
|
213
|
+
ServiceName = config['name']
|
|
214
|
+
|
|
215
|
+
# Executa o comando SC QUERY para verificar o status
|
|
216
|
+
resultado = subprocess.run(["sc", "query", ServiceName], capture_output=True, text=True)
|
|
217
|
+
saida = resultado.stdout
|
|
218
|
+
|
|
219
|
+
# Buscar o status na saída do comando
|
|
220
|
+
if "RUNNING" in saida:
|
|
221
|
+
return "RUNNING"
|
|
222
|
+
elif "STOPPED" in saida:
|
|
223
|
+
return "STOPPED"
|
|
224
|
+
elif "PAUSED" in saida:
|
|
225
|
+
return "PAUSED"
|
|
226
|
+
elif "START_PENDING" in saida:
|
|
227
|
+
return "START_PENDING"
|
|
228
|
+
elif "STOP_PENDING" in saida:
|
|
229
|
+
return "STOP_PENDING"
|
|
230
|
+
else:
|
|
231
|
+
return f"UNKNOWN STATUS: {saida.strip()}"
|
|
232
|
+
|
|
233
|
+
except Exception as Err:
|
|
234
|
+
log = ConfigManager.get('log')
|
|
235
|
+
if log:
|
|
236
|
+
log.erro(f"Erro ao obter status do serviço [{ServiceName}]: {Err}")
|
|
237
|
+
return f"ERROR: {Err}"
|
|
198
238
|
#
|
|
199
239
|
#
|
|
200
240
|
@staticmethod
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|