biatoolkit 1.2.0__tar.gz → 1.2.1__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.
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/PKG-INFO +1 -1
- biatoolkit-1.2.1/biatoolkit/validation/__init__.py +0 -0
- biatoolkit-1.2.1/biatoolkit/validation/coercion.py +175 -0
- biatoolkit-1.2.1/biatoolkit/validation/validation.py +135 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit.egg-info/PKG-INFO +1 -1
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit.egg-info/SOURCES.txt +4 -1
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/setup.py +1 -1
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/README.md +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit/__init__.py +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit/basic_client.py +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit/sankhya_call.py +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit/schema/__init__.py +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit/schema/header.py +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit/settings.py +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit/test_sankhya.py +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit/util.py +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit.egg-info/dependency_links.txt +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/biatoolkit.egg-info/top_level.txt +0 -0
- {biatoolkit-1.2.0 → biatoolkit-1.2.1}/setup.cfg +0 -0
|
File without changes
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# biatoolkit/validation/coercion.py
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Iterable, Mapping, Optional
|
|
6
|
+
import math
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def ensure_list(value: Any) -> list:
|
|
10
|
+
"""
|
|
11
|
+
Normaliza um valor para sempre retornar uma lista.
|
|
12
|
+
|
|
13
|
+
Regras:
|
|
14
|
+
- None -> []
|
|
15
|
+
- list -> a própria lista
|
|
16
|
+
- tuple/set -> list(value)
|
|
17
|
+
- dict (ou Mapping) -> [value] (não "explode" dict em chaves)
|
|
18
|
+
- qualquer outro -> [value]
|
|
19
|
+
|
|
20
|
+
Por que existe:
|
|
21
|
+
- Muitas APIs retornam ora um item único (dict), ora uma lista.
|
|
22
|
+
- Este helper padroniza o consumo e reduz if/else espalhado.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
value: qualquer valor.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
list: lista normalizada.
|
|
29
|
+
"""
|
|
30
|
+
if value is None:
|
|
31
|
+
return []
|
|
32
|
+
|
|
33
|
+
if isinstance(value, list):
|
|
34
|
+
return value
|
|
35
|
+
|
|
36
|
+
if isinstance(value, (tuple, set)):
|
|
37
|
+
return list(value)
|
|
38
|
+
|
|
39
|
+
# Dict/Mapping deve ser tratado como item único, não iterável de chaves.
|
|
40
|
+
if isinstance(value, Mapping):
|
|
41
|
+
return [value]
|
|
42
|
+
|
|
43
|
+
return [value]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def unwrap_dollar_value(value: Any) -> Any:
|
|
47
|
+
"""
|
|
48
|
+
"Desembrulha" valores no formato {"$": "..."} (comum em integrações legadas).
|
|
49
|
+
|
|
50
|
+
Exemplo:
|
|
51
|
+
{"$": "123"} -> "123"
|
|
52
|
+
{"$": 123} -> 123
|
|
53
|
+
|
|
54
|
+
Se não for dict com a chave "$", retorna o valor original.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
value: valor de entrada.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Any: valor desembrulhado ou original.
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(value, dict) and "$" in value:
|
|
63
|
+
return value.get("$")
|
|
64
|
+
return value
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def to_int(value: Any, default: int = 0) -> int:
|
|
68
|
+
"""
|
|
69
|
+
Converte um valor para int de forma segura.
|
|
70
|
+
|
|
71
|
+
Regras:
|
|
72
|
+
- None / "" -> default
|
|
73
|
+
- strings com espaços são aceitas (" 12 ")
|
|
74
|
+
- strings numéricas com sinal são aceitas ("-3")
|
|
75
|
+
- floats numéricos -> int(value) (trunca)
|
|
76
|
+
- NaN/inf -> default
|
|
77
|
+
|
|
78
|
+
Obs:
|
|
79
|
+
- Não tenta "extrair número do meio do texto" (isso é responsabilidade
|
|
80
|
+
de parse específico, ex: parse_int_list).
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
value: valor a converter.
|
|
84
|
+
default: fallback.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
int: convertido ou default.
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
if value is None:
|
|
91
|
+
return default
|
|
92
|
+
|
|
93
|
+
if isinstance(value, bool):
|
|
94
|
+
# Evita True->1 / False->0 de forma "surpresa"
|
|
95
|
+
return default
|
|
96
|
+
|
|
97
|
+
if isinstance(value, str):
|
|
98
|
+
s = value.strip()
|
|
99
|
+
if s == "":
|
|
100
|
+
return default
|
|
101
|
+
return int(s)
|
|
102
|
+
|
|
103
|
+
if isinstance(value, float):
|
|
104
|
+
if math.isnan(value) or math.isinf(value):
|
|
105
|
+
return default
|
|
106
|
+
return int(value)
|
|
107
|
+
|
|
108
|
+
return int(value)
|
|
109
|
+
except (TypeError, ValueError):
|
|
110
|
+
return default
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def to_float(value: Any, default: float = 0.0) -> float:
|
|
114
|
+
"""
|
|
115
|
+
Converte um valor para float de forma segura.
|
|
116
|
+
|
|
117
|
+
Regras:
|
|
118
|
+
- None / "" -> default
|
|
119
|
+
- aceita strings com vírgula decimal ("12,34")
|
|
120
|
+
- NaN/inf -> default
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
value: valor a converter.
|
|
124
|
+
default: fallback.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
float: convertido ou default.
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
if value is None:
|
|
131
|
+
return default
|
|
132
|
+
|
|
133
|
+
if isinstance(value, bool):
|
|
134
|
+
return default
|
|
135
|
+
|
|
136
|
+
if isinstance(value, str):
|
|
137
|
+
s = value.strip()
|
|
138
|
+
if s == "":
|
|
139
|
+
return default
|
|
140
|
+
s = s.replace(",", ".")
|
|
141
|
+
v = float(s)
|
|
142
|
+
else:
|
|
143
|
+
v = float(value)
|
|
144
|
+
|
|
145
|
+
if math.isnan(v) or math.isinf(v):
|
|
146
|
+
return default
|
|
147
|
+
|
|
148
|
+
return v
|
|
149
|
+
except (TypeError, ValueError):
|
|
150
|
+
return default
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Classe fachada para coerção
|
|
156
|
+
class BiaCoercion:
|
|
157
|
+
"""
|
|
158
|
+
Fachada estática para funções de coerção do Bia Toolkit.
|
|
159
|
+
Permite referenciar e utilizar as utilidades de coerção de forma padronizada.
|
|
160
|
+
"""
|
|
161
|
+
@staticmethod
|
|
162
|
+
def ensure_list(*args, **kwargs):
|
|
163
|
+
return ensure_list(*args, **kwargs)
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def unwrap_dollar_value(*args, **kwargs):
|
|
167
|
+
return unwrap_dollar_value(*args, **kwargs)
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def to_int(*args, **kwargs):
|
|
171
|
+
return to_int(*args, **kwargs)
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def to_float(*args, **kwargs):
|
|
175
|
+
return to_float(*args, **kwargs)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# biatoolkit/validation.py
|
|
2
|
+
|
|
3
|
+
from typing import Any, Iterable
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_int_list(
|
|
8
|
+
value: Any,
|
|
9
|
+
*,
|
|
10
|
+
dedupe: bool = True,
|
|
11
|
+
keep_order: bool = True,
|
|
12
|
+
) -> list[int]:
|
|
13
|
+
"""
|
|
14
|
+
Normaliza um valor arbitrário em uma lista de inteiros.
|
|
15
|
+
|
|
16
|
+
Casos suportados:
|
|
17
|
+
- None -> []
|
|
18
|
+
- int -> [int]
|
|
19
|
+
- str -> extrai números (ex: "100, 200" / "SKU=300231 x2")
|
|
20
|
+
- list/tuple -> processa cada item recursivamente
|
|
21
|
+
|
|
22
|
+
Comportamento:
|
|
23
|
+
- Ignora valores inválidos
|
|
24
|
+
- Deduplica por padrão
|
|
25
|
+
- Mantém a ordem de aparição por padrão
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
value: Valor de entrada (None, int, str, list, etc.)
|
|
29
|
+
dedupe: Remove valores duplicados.
|
|
30
|
+
keep_order: Mantém a ordem original dos valores.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
list[int]: Lista normalizada de inteiros.
|
|
34
|
+
"""
|
|
35
|
+
if value is None:
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
# Normaliza para iterável
|
|
39
|
+
if isinstance(value, (list, tuple, set)):
|
|
40
|
+
raw: Iterable[Any] = value
|
|
41
|
+
else:
|
|
42
|
+
raw = [value]
|
|
43
|
+
|
|
44
|
+
numbers: list[int] = []
|
|
45
|
+
|
|
46
|
+
for item in raw:
|
|
47
|
+
if item is None:
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
# Inteiro direto
|
|
51
|
+
if isinstance(item, int):
|
|
52
|
+
numbers.append(item)
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# String ou outros tipos
|
|
56
|
+
text = str(item)
|
|
57
|
+
matches = re.findall(r"\d+", text)
|
|
58
|
+
for m in matches:
|
|
59
|
+
try:
|
|
60
|
+
numbers.append(int(m))
|
|
61
|
+
except ValueError:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
if not dedupe:
|
|
65
|
+
return numbers
|
|
66
|
+
|
|
67
|
+
if keep_order:
|
|
68
|
+
seen = set()
|
|
69
|
+
ordered: list[int] = []
|
|
70
|
+
for n in numbers:
|
|
71
|
+
if n not in seen:
|
|
72
|
+
seen.add(n)
|
|
73
|
+
ordered.append(n)
|
|
74
|
+
return ordered
|
|
75
|
+
|
|
76
|
+
return list(set(numbers))
|
|
77
|
+
|
|
78
|
+
def sanitize_like(
|
|
79
|
+
value: str,
|
|
80
|
+
*,
|
|
81
|
+
max_len: int = 80,
|
|
82
|
+
upper: bool = True,
|
|
83
|
+
) -> str:
|
|
84
|
+
"""
|
|
85
|
+
Sanitiza um texto para uso seguro em filtros do tipo LIKE/search.
|
|
86
|
+
|
|
87
|
+
Regras aplicadas:
|
|
88
|
+
- Remove caracteres fora de uma allowlist básica
|
|
89
|
+
- Limita o tamanho do texto
|
|
90
|
+
- Escapa aspas simples
|
|
91
|
+
- Escapa curingas comuns (% e _)
|
|
92
|
+
- Converte para UPPER por padrão
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
value: Texto de entrada.
|
|
96
|
+
max_len: Tamanho máximo permitido.
|
|
97
|
+
upper: Converte o texto final para maiúsculas.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
str: Texto sanitizado.
|
|
101
|
+
"""
|
|
102
|
+
if not value:
|
|
103
|
+
return ""
|
|
104
|
+
|
|
105
|
+
# Normaliza e corta tamanho
|
|
106
|
+
text = str(value).strip()[:max_len]
|
|
107
|
+
|
|
108
|
+
# Allowlist simples (letras, números, acentos, espaço e alguns símbolos, incluindo ', %, _)
|
|
109
|
+
# Mantém ', %, _ para escapá-los depois
|
|
110
|
+
text = re.sub(r"[^0-9A-Za-zÀ-ÿ\s\-\(\)\./'_%]", " ", text)
|
|
111
|
+
|
|
112
|
+
# Escapes básicos
|
|
113
|
+
text = text.replace("'", "''")
|
|
114
|
+
text = text.replace("%", r"\%" ).replace("_", r"\_")
|
|
115
|
+
|
|
116
|
+
# Normaliza espaços
|
|
117
|
+
text = re.sub(r"\s+", " ", text).strip()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
return text.upper() if upper else text
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# Classe fachada para validação
|
|
124
|
+
class BiaValidation:
|
|
125
|
+
"""
|
|
126
|
+
Fachada estática para funções de validação do Bia Toolkit.
|
|
127
|
+
Permite referenciar e utilizar as utilidades de validação de forma padronizada.
|
|
128
|
+
"""
|
|
129
|
+
@staticmethod
|
|
130
|
+
def parse_int_list(*args, **kwargs):
|
|
131
|
+
return parse_int_list(*args, **kwargs)
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def sanitize_like(*args, **kwargs):
|
|
135
|
+
return sanitize_like(*args, **kwargs)
|
|
@@ -11,4 +11,7 @@ biatoolkit.egg-info/SOURCES.txt
|
|
|
11
11
|
biatoolkit.egg-info/dependency_links.txt
|
|
12
12
|
biatoolkit.egg-info/top_level.txt
|
|
13
13
|
biatoolkit/schema/__init__.py
|
|
14
|
-
biatoolkit/schema/header.py
|
|
14
|
+
biatoolkit/schema/header.py
|
|
15
|
+
biatoolkit/validation/__init__.py
|
|
16
|
+
biatoolkit/validation/coercion.py
|
|
17
|
+
biatoolkit/validation/validation.py
|
|
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
|
|
File without changes
|
|
File without changes
|