csc-essentials 1.2.2__tar.gz → 1.2.4__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.
- {csc_essentials-1.2.2/src/csc_essentials.egg-info → csc_essentials-1.2.4}/PKG-INFO +1 -1
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/pyproject.toml +1 -1
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials/utils.py +61 -83
- {csc_essentials-1.2.2 → csc_essentials-1.2.4/src/csc_essentials.egg-info}/PKG-INFO +1 -1
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/LICENSE +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/README.md +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/setup.cfg +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials/__init__.py +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials/core.py +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials/database.py +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials/logging.py +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials/oracle.py +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials/secrets.py +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials.egg-info/SOURCES.txt +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials.egg-info/dependency_links.txt +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials.egg-info/requires.txt +0 -0
- {csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials.egg-info/top_level.txt +0 -0
|
@@ -25,17 +25,12 @@ def ajusta_nome_coluna(nome_coluna):
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def converter_valor(row, schema_map, gerador_log):
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
- Recebe `row` (dict coluna->valor) e `schema_map` (coluna->TIPO BigQuery).
|
|
31
|
-
- Converte tipos básicos (STRING, INTEGER, FLOAT, BOOLEAN, DATE, DATETIME).
|
|
32
|
-
- Sanitiza NaN/Inf, strings nulas do Pandas e formata datas no padrão aceito pelo BigQuery.
|
|
33
|
-
- Trata formatos de moeda BR/US, datas BR (DD/MM/YYYY), datetime BR e padrão MMM-AA.
|
|
34
|
-
- Em falhas de conversão registra avisos e armazena `None` no campo.
|
|
28
|
+
"""
|
|
29
|
+
Converte os valores e sanitiza para evitar erros de JSON parsing no BigQuery (NaN, Infinity).
|
|
35
30
|
"""
|
|
36
31
|
|
|
37
32
|
if not schema_map:
|
|
38
|
-
return row
|
|
33
|
+
return row
|
|
39
34
|
|
|
40
35
|
nova_linha = {}
|
|
41
36
|
|
|
@@ -47,17 +42,15 @@ def converter_valor(row, schema_map, gerador_log):
|
|
|
47
42
|
)
|
|
48
43
|
|
|
49
44
|
for col, val in row.items():
|
|
50
|
-
#
|
|
45
|
+
# MELHORIA: Capturar "nan" vindo do Pandas ou strings que representam nulo
|
|
51
46
|
val_str_check = str(val).strip().lower() if val is not None else ""
|
|
52
47
|
if val is None or val_str_check in ("", "nan", "none", "null", "undefined"):
|
|
53
48
|
nova_linha[col] = None
|
|
54
49
|
continue
|
|
55
|
-
|
|
56
50
|
if col == "data_import":
|
|
57
51
|
nova_linha[col] = val
|
|
58
52
|
continue
|
|
59
53
|
|
|
60
|
-
# Serializa estruturas complexas para JSON
|
|
61
54
|
if isinstance(val, (dict, list)):
|
|
62
55
|
try:
|
|
63
56
|
nova_linha[col] = json.dumps(val, ensure_ascii=False)
|
|
@@ -68,9 +61,9 @@ def converter_valor(row, schema_map, gerador_log):
|
|
|
68
61
|
tipo_bq = schema_map.get(col, "STRING")
|
|
69
62
|
|
|
70
63
|
try:
|
|
71
|
-
if tipo_bq == "STRING":
|
|
64
|
+
if tipo_bq == "STRING":
|
|
72
65
|
# Remove quebras de linha e caracteres de controle que corrompem o JSONL
|
|
73
|
-
val_str = str(val).replace(
|
|
66
|
+
val_str = str(val).replace('\n', ' ').replace('\r', '').replace('\t', ' ').strip()
|
|
74
67
|
# Remove caracteres não-imprimíveis (ASCII < 32) exceto espaço
|
|
75
68
|
val_str = "".join(ch for ch in val_str if ord(ch) >= 32)
|
|
76
69
|
nova_linha[col] = val_str
|
|
@@ -80,28 +73,27 @@ def converter_valor(row, schema_map, gerador_log):
|
|
|
80
73
|
nova_linha[col] = int(float(str(val)))
|
|
81
74
|
|
|
82
75
|
elif tipo_bq in ("FLOAT", "FLOAT64", "NUMERIC"):
|
|
83
|
-
#
|
|
84
|
-
val_str = str(val).replace(
|
|
76
|
+
# 1. Limpa moeda e espaços
|
|
77
|
+
val_str = str(val).replace('R$', '').replace(' ', '').strip()
|
|
85
78
|
|
|
86
|
-
|
|
87
|
-
if val_str in ("-", ""):
|
|
79
|
+
if val_str in ('-', ''):
|
|
88
80
|
nova_linha[col] = 0.0
|
|
89
81
|
else:
|
|
90
|
-
# Lógica inteligente para vírgulas e pontos
|
|
91
|
-
if
|
|
92
|
-
# Se a vírgula vem antes do ponto (ex: 1,510,000.00)
|
|
93
|
-
if val_str.find(
|
|
94
|
-
val_str = val_str.replace(
|
|
82
|
+
# 2. Lógica inteligente para vírgulas e pontos
|
|
83
|
+
if ',' in val_str and '.' in val_str:
|
|
84
|
+
# Se tem os dois, e a vírgula vem antes do ponto (ex: 1,510,000.00)
|
|
85
|
+
if val_str.find(',') < val_str.rfind('.'):
|
|
86
|
+
val_str = val_str.replace(',', '') # Padrão US: remove vírgula
|
|
95
87
|
else:
|
|
96
|
-
# Padrão BR
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
#
|
|
100
|
-
partes = val_str.split(
|
|
88
|
+
val_str = val_str.replace('.', '').replace(',', '.') # Padrão BR
|
|
89
|
+
elif ',' in val_str:
|
|
90
|
+
# Se só tem vírgula, checamos se parece decimal (ex: 10,50) ou milhar (1,000)
|
|
91
|
+
# Se houver apenas uma vírgula e ela estiver nas últimas 3 posições: decimal
|
|
92
|
+
partes = val_str.split(',')
|
|
101
93
|
if len(partes[-1]) <= 2:
|
|
102
|
-
val_str = val_str.replace(
|
|
94
|
+
val_str = val_str.replace('.', '').replace(',', '.')
|
|
103
95
|
else:
|
|
104
|
-
val_str = val_str.replace(
|
|
96
|
+
val_str = val_str.replace(',', '')
|
|
105
97
|
|
|
106
98
|
try:
|
|
107
99
|
valor_float = float(val_str)
|
|
@@ -110,85 +102,71 @@ def converter_valor(row, schema_map, gerador_log):
|
|
|
110
102
|
else:
|
|
111
103
|
nova_linha[col] = valor_float
|
|
112
104
|
except ValueError:
|
|
113
|
-
gerador_log.add(
|
|
114
|
-
f"AVISO: Não foi possível converter '{val}' para FLOAT. Coluna: {col}. Enviando NULL.",
|
|
115
|
-
severity="WARNING",
|
|
116
|
-
)
|
|
105
|
+
gerador_log.add(f"AVISO: Não foi possível converter '{val}' para FLOAT. Coluna: {col}. Enviando NULL.")
|
|
117
106
|
nova_linha[col] = None
|
|
118
107
|
|
|
119
108
|
elif tipo_bq in ("BOOLEAN", "BOOL"):
|
|
120
109
|
nova_linha[col] = str(val).lower() in ("true", "1", "t", "yes")
|
|
121
110
|
|
|
122
111
|
elif tipo_bq == "DATE":
|
|
123
|
-
val_str = str(val).strip().upper()
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
match_br = regex_data_br.match(val_str)
|
|
139
|
-
|
|
140
|
-
if match_dma:
|
|
141
|
-
mes = meses_pt_en.get(match_dma.group(2), "01")
|
|
142
|
-
ano = f"20{match_dma.group(3)}"
|
|
143
|
-
nova_linha[col] = f"{ano}-{mes}-{match_dma.group(1)}"
|
|
144
|
-
elif match_ma:
|
|
145
|
-
mes = meses_pt_en.get(match_ma.group(1), "01")
|
|
146
|
-
ano = f"20{match_ma.group(2)}"
|
|
112
|
+
val_str = str(val).strip().upper() # Garante que FEB-26 fique maiúsculo
|
|
113
|
+
|
|
114
|
+
# 1. Trata o caso 'FEB-26' (Mês-Ano)
|
|
115
|
+
# Regex para capturar algo como AAA-99
|
|
116
|
+
regex_mes_ano = re.compile(r"^([A-Z]{3})-(\d{2})$")
|
|
117
|
+
match_ma = regex_mes_ano.match(val_str)
|
|
118
|
+
|
|
119
|
+
if match_ma:
|
|
120
|
+
meses = {
|
|
121
|
+
'JAN': '01', 'FEB': '02', 'MAR': '03', 'APR': '04', 'MAY': '05', 'JUN': '06',
|
|
122
|
+
'JUL': '07', 'AUG': '08', 'SEP': '09', 'OCT': '10', 'NOV': '11', 'DEC': '12'
|
|
123
|
+
}
|
|
124
|
+
mes = meses.get(match_ma.group(1), '01')
|
|
125
|
+
ano = f"20{match_ma.group(2)}" # Assume 20xx
|
|
126
|
+
# Como o BQ exige dia, vamos fixar no dia 01 do mês
|
|
147
127
|
nova_linha[col] = f"{ano}-{mes}-01"
|
|
148
|
-
|
|
149
|
-
nova_linha[col] = f"{match_br.group(3)}-{match_br.group(2)}-{match_br.group(1)}"
|
|
128
|
+
|
|
150
129
|
else:
|
|
151
|
-
|
|
130
|
+
# 2. Lógica que já tínhamos para DD/MM/YYYY
|
|
131
|
+
val_str = val_str.replace('0224','2024').replace('0202','2022').replace('0204','2024')
|
|
132
|
+
match_br = regex_data_br.match(val_str)
|
|
133
|
+
if match_br:
|
|
134
|
+
nova_linha[col] = f"{match_br.group(3)}-{match_br.group(2)}-{match_br.group(1)}"
|
|
135
|
+
else:
|
|
136
|
+
# Tenta pegar apenas os primeiros 10 caracteres (YYYY-MM-DD)
|
|
137
|
+
nova_linha[col] = val_str[:10] if len(val_str) >= 10 else None
|
|
152
138
|
|
|
153
139
|
elif tipo_bq == "DATETIME":
|
|
154
140
|
val_str = str(val).strip()
|
|
155
|
-
|
|
156
|
-
# Tenta formato brasileiro DD/MM/YYYY HH:MM:SS
|
|
157
|
-
regex_dt_br = re.compile(
|
|
158
|
-
r"^(\d{2})/(\d{2})/(\d{4})\s+(\d{2}:\d{2}:\d{2})(?:\.\d+)?$"
|
|
159
|
-
)
|
|
141
|
+
|
|
142
|
+
# Tenta detectar formato brasileiro DD/MM/YYYY HH:MM:SS
|
|
143
|
+
regex_dt_br = re.compile(r"^(\d{2})/(\d{2})/(\d{4})\s+(\d{2}:\d{2}:\d{2})(?:\.\d+)?$")
|
|
160
144
|
match_dt_br = regex_dt_br.match(val_str)
|
|
145
|
+
|
|
161
146
|
if match_dt_br:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
f" {match_dt_br.group(4)}"
|
|
165
|
-
)
|
|
147
|
+
# Converte para YYYY-MM-DD HH:MM:SS
|
|
148
|
+
nova_linha[col] = f"{match_dt_br.group(3)}-{match_dt_br.group(2)}-{match_dt_br.group(1)} {match_dt_br.group(4)}"
|
|
166
149
|
else:
|
|
167
150
|
match_iso = regex_iso_tz.match(val_str)
|
|
168
151
|
if match_iso:
|
|
169
152
|
nova_linha[col] = f"{match_iso.group(1)} {match_iso.group(2)}"
|
|
170
153
|
else:
|
|
171
|
-
# Limpeza robusta
|
|
154
|
+
# Limpeza robusta existente
|
|
172
155
|
limpo = val_str.replace("T", " ").replace("Z", "")
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
limpo = limpo.rsplit("-", 1)[0]
|
|
156
|
+
if '+' in limpo: limpo = limpo.split('+')[0]
|
|
157
|
+
if '-' in limpo and len(limpo.split('-')[-1]) == 5:
|
|
158
|
+
limpo = limpo.rsplit('-', 1)[0]
|
|
177
159
|
nova_linha[col] = limpo.strip()[:23]
|
|
178
160
|
|
|
179
|
-
else:
|
|
180
|
-
# Tipo não tratado: devolve o valor como veio
|
|
181
|
-
nova_linha[col] = val
|
|
182
|
-
|
|
183
161
|
except Exception:
|
|
184
|
-
#
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
)
|
|
162
|
+
# CORREÇÃO DE FALLBACK:
|
|
163
|
+
# Se falhar a conversão (ex: texto em campo Inteiro), envia NULL
|
|
164
|
+
# Enviar str(val) causaria erro de "Type Mismatch" no BigQuery
|
|
165
|
+
gerador_log.add(f"AVISO: Falha conversão coluna '{col}' valor '{val}'. Enviando NULL.")
|
|
188
166
|
nova_linha[col] = None
|
|
189
167
|
|
|
190
168
|
return nova_linha
|
|
191
|
-
|
|
169
|
+
|
|
192
170
|
|
|
193
171
|
def executar_com_fallback(func, bq_client, timeout, project, dataset, tabela, gerador_log):
|
|
194
172
|
"""Executa `func` em um executor com timeout e captura falhas.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{csc_essentials-1.2.2 → csc_essentials-1.2.4}/src/csc_essentials.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|