lbxtoolkit 2.2.9__tar.gz → 2.2.10__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lbxtoolkit
3
- Version: 2.2.9
3
+ Version: 2.2.10
4
4
  Summary:
5
5
  Author: Cristiano P. Ferrari
6
6
  Author-email: cristiano.ferrari@lbx.com.br
@@ -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
  #
@@ -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
- Lixo = r'/\\?%§ªº°`´^~*:|"<>!@#$%¨&*()_+=-[]{}"\' '
49
- Normalizar = normalize('NFKD', Original).encode('ASCII', 'ignore').decode('ASCII')
47
+ def normaliza(Original, Case=None, 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.upper() == '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
- return Limpo.lower()
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, CsvPath, CsvDelim=';', **kwargs): # Le arquivo CSV e gera Dataframe do Pandas
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,22 +170,59 @@ 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): # 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
186
  Colunas = Colunas or DataFrame.columns.tolist() # Caso não seja fornecida a lista de colunas, assume as colunas do DataFrame
171
- Valores = [tuple(Linha) for Linha in DataFrame[Colunas].values]
172
- Schema = Schema or 'public'
187
+ Schema = Schema or 'public'
188
+ #Valores = [tuple(Linha) for Linha in DataFrame[Colunas].values] ## código anterior para definir os valores. alterado em 05/02/25 para tratar o typecast das colunas
189
+ #Definição dos valores com typecast das colunas
190
+ TypeCast = TypeCast or {}
191
+
192
+ # Converter JSON de string para objeto Python, se necessário
193
+ # for coluna, tipo in TypeCast.items():
194
+ # if tipo == "jsonb" and coluna in DataFrame.columns:
195
+ # DataFrame[coluna] = DataFrame[coluna].apply(json.loads) # Transforma string JSON em objeto Python
196
+
197
+ # Gerar valores tratados, aplicando casts se necessário
198
+ Valores = []
199
+ for _, Linha in DataFrame.iterrows():
200
+ Linha_convertida = []
201
+ for coluna in Colunas:
202
+ valor = Linha[coluna]
203
+
204
+ # Tratamento especial para JSONB
205
+ if coluna in TypeCast and TypeCast[coluna] == "jsonb":
206
+ valor = json.dumps(valor) # Converte de volta para string JSON válida
207
+ #valor += "::jsonb" # Adiciona typecast explícito
208
+
209
+ # Aplicar typecast para outros tipos de dados
210
+ elif coluna in TypeCast:
211
+ valor = f"{valor}::{TypeCast[coluna]}"
212
+
213
+ Linha_convertida.append(valor)
214
+ Valores.append(tuple(Linha_convertida))
215
+
173
216
  Query = f'insert into {Schema}.{Tabela} ({', '.join(Colunas)}) values %s '
174
- if not OnConflict is None:
175
- Query = Query + OnConflict
217
+ Query = Query + OnConflict if OnConflict else Query
176
218
 
177
219
  try:
178
220
  self.Cursor = self.Conexao.cursor()
179
221
  execute_values(self.Cursor, Query, Valores)
180
222
  self.Conexao.commit()
181
223
  except Exception as Err:
224
+ self.logger.erro(f'{__name__}: Erro [{Err}] ao inserir dados na tabela {Schema}.{Tabela}!')
182
225
  self.Conexao.rollback()
183
- raise
184
226
  finally:
185
227
  self.Cursor.close()
186
228
  #Conexao.close() ## conexão precisa ser fechada explicitamente fora da classe
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "lbxtoolkit"
3
- version = "2.2.9"
3
+ version = "2.2.10"
4
4
  description = ""
5
5
  authors = ["Cristiano P. Ferrari <cristiano.ferrari@lbx.com.br>"]
6
6
  readme = "README.md"
File without changes