biatoolkit 1.1.1__tar.gz → 1.2.0__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.1.1 → biatoolkit-1.2.0}/PKG-INFO +143 -2
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/README.md +79 -1
- biatoolkit-1.2.0/biatoolkit/sankhya_call.py +433 -0
- biatoolkit-1.2.0/biatoolkit/test_sankhya.py +39 -0
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/biatoolkit.egg-info/PKG-INFO +143 -2
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/biatoolkit.egg-info/SOURCES.txt +2 -0
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/setup.py +1 -1
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/biatoolkit/__init__.py +0 -0
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/biatoolkit/basic_client.py +0 -0
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/biatoolkit/schema/__init__.py +0 -0
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/biatoolkit/schema/header.py +0 -0
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/biatoolkit/settings.py +0 -0
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/biatoolkit/util.py +0 -0
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/biatoolkit.egg-info/dependency_links.txt +0 -0
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/biatoolkit.egg-info/top_level.txt +0 -0
- {biatoolkit-1.1.1 → biatoolkit-1.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: biatoolkit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Biblioteca para desenvolvedores que utilizam o BiaAgentBuilder
|
|
5
5
|
Author: Bia Platform Team
|
|
6
6
|
Author-email: data.platform@sankhya.com.br
|
|
@@ -348,4 +348,145 @@ O método `get_parameter(parameter_name: str)` busca o parâmetro informado em d
|
|
|
348
348
|
- Um arquivo `.env` para testes locais.
|
|
349
349
|
- No cofre de segredos do Bia Agent Builder para usar em ambiente produtivo.
|
|
350
350
|
|
|
351
|
-
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Validação e Coerção de Dados
|
|
355
|
+
|
|
356
|
+
O Bia Toolkit oferece funções utilitárias para normalização, sanitização e coerção de dados, facilitando o consumo seguro de entradas vindas de APIs, headers, payloads e integrações legadas.
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
### Validação de Dados (`biatoolkit.validation.validation`)
|
|
360
|
+
|
|
361
|
+
Utilize a classe **BiaValidation** para acessar as funções de validação de forma padronizada:
|
|
362
|
+
|
|
363
|
+
- **BiaValidation.parse_int_list(value, dedupe=True, keep_order=True)**
|
|
364
|
+
- Normaliza um valor arbitrário em uma lista de inteiros.
|
|
365
|
+
- Suporta: None, int, str (extrai números), list/tuple (recursivo).
|
|
366
|
+
- Ignora valores inválidos.
|
|
367
|
+
- Deduplica e mantém ordem por padrão.
|
|
368
|
+
- Exemplo:
|
|
369
|
+
```python
|
|
370
|
+
from biatoolkit.validation.validation import BiaValidation
|
|
371
|
+
BiaValidation.parse_int_list("SKU=300231 x2") # [300231, 2]
|
|
372
|
+
BiaValidation.parse_int_list([1, 2, 1, 3], dedupe=False) # [1, 2, 1, 3]
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
- **BiaValidation.sanitize_like(value, max_len=80, upper=True)**
|
|
376
|
+
- Sanitiza texto para uso seguro em filtros LIKE/search SQL.
|
|
377
|
+
- Remove caracteres fora da allowlist, limita tamanho, escapa aspas simples, %, _ e normaliza espaços.
|
|
378
|
+
- Converte para maiúsculas por padrão.
|
|
379
|
+
- Exemplo:
|
|
380
|
+
```python
|
|
381
|
+
from biatoolkit.validation.validation import BiaValidation
|
|
382
|
+
BiaValidation.sanitize_like("O'Reilly") # "O''REILLY"
|
|
383
|
+
BiaValidation.sanitize_like("100%_OK") # "100\\%\\_OK"
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
### Coerção de Dados (`biatoolkit.validation.coercion`)
|
|
388
|
+
|
|
389
|
+
Utilize a classe **BiaCoercion** para acessar as funções de coerção de forma padronizada:
|
|
390
|
+
|
|
391
|
+
- **BiaCoercion.ensure_list(value)**
|
|
392
|
+
- Garante que o valor seja retornado como lista.
|
|
393
|
+
- None → [], list → list, tuple/set → list, dict → [dict], outro → [valor].
|
|
394
|
+
- Exemplo:
|
|
395
|
+
```python
|
|
396
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
397
|
+
BiaCoercion.ensure_list(None) # []
|
|
398
|
+
BiaCoercion.ensure_list({"a": 1}) # [{"a": 1}]
|
|
399
|
+
BiaCoercion.ensure_list(5) # [5]
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
- **BiaCoercion.unwrap_dollar_value(value)**
|
|
403
|
+
- Desembrulha valores no formato {"$": ...} (comum em integrações legadas).
|
|
404
|
+
- Exemplo:
|
|
405
|
+
```python
|
|
406
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
407
|
+
BiaCoercion.unwrap_dollar_value({"$": 123}) # 123
|
|
408
|
+
BiaCoercion.unwrap_dollar_value("abc") # "abc"
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
- **BiaCoercion.to_int(value, default=0)**
|
|
412
|
+
- Converte valor para int de forma segura.
|
|
413
|
+
- None, "", bool, NaN/inf → default.
|
|
414
|
+
- Aceita strings numéricas, floats (trunca), etc.
|
|
415
|
+
- Exemplo:
|
|
416
|
+
```python
|
|
417
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
418
|
+
BiaCoercion.to_int(" 42 ") # 42
|
|
419
|
+
BiaCoercion.to_int(None, default=-1) # -1
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
- **BiaCoercion.to_float(value, default=0.0)**
|
|
423
|
+
- Converte valor para float de forma segura.
|
|
424
|
+
- None, "", bool, NaN/inf → default.
|
|
425
|
+
- Aceita strings com vírgula decimal.
|
|
426
|
+
- Exemplo:
|
|
427
|
+
```python
|
|
428
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
429
|
+
BiaCoercion.to_float("12,34") # 12.34
|
|
430
|
+
BiaCoercion.to_float("abc", default=-1.0) # -1.0
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
Essas funções são úteis para garantir robustez e previsibilidade ao tratar dados vindos de múltiplas fontes, reduzindo erros e if/else espalhados pelo código.
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Integração com Sankhya (`biatoolkit.sankhya_call`)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
O Bia Toolkit oferece integração pronta para consumo de serviços HTTP da plataforma Sankhya (ou gateway), com autenticação via JSESSIONID e configuração de timeouts/retries via variáveis de ambiente.
|
|
442
|
+
|
|
443
|
+
### Objetivo
|
|
444
|
+
|
|
445
|
+
- Facilitar chamadas autenticadas a serviços Sankhya sem que o desenvolvedor precise lidar com autenticação, headers ou gerenciamento de sessão HTTP.
|
|
446
|
+
- Fornecer métodos prontos para chamadas genéricas (`call_json`) e para consultas a views (`load_view`).
|
|
447
|
+
- Permitir uso estático compatível com scaffolds legados via `Sankhya.Call(...)`.
|
|
448
|
+
|
|
449
|
+
### Configuração
|
|
450
|
+
|
|
451
|
+
- O caminho do serviço (`/mge/service.sbr`) é fixo no toolkit.
|
|
452
|
+
- O parâmetro `base_url` é obrigatório e pode ser passado explicitamente ou extraído automaticamente do header do runtime (`current_host` via `BiaUtil`).
|
|
453
|
+
- Não existe mais configuração via variável de ambiente para base_url ou service_path.
|
|
454
|
+
- As variáveis de ambiente opcionais são apenas para timeout, retries e SSL:
|
|
455
|
+
- `SANKHYA_TIMEOUT_CONNECT`: timeout de conexão (default: 3.05)
|
|
456
|
+
- `SANKHYA_TIMEOUT_READ`: timeout de leitura (default: 12)
|
|
457
|
+
- `SANKHYA_RETRIES_TOTAL`: número de tentativas em falha (default: 3)
|
|
458
|
+
- `SANKHYA_RETRY_BACKOFF`: backoff entre tentativas (default: 0.5)
|
|
459
|
+
- `SANKHYA_VERIFY_SSL`: se valida SSL (default: "1")
|
|
460
|
+
|
|
461
|
+
### Principais classes e métodos
|
|
462
|
+
|
|
463
|
+
- **SankhyaSettings**: Dataclass de configuração de timeouts/retries/SSL. Use `SankhyaSettings.from_env()` para obter as configurações do ambiente.
|
|
464
|
+
- **Sankhya**: Classe principal de integração. Permite instanciar com contexto MCP (`FastMCP`) ou usar métodos estáticos.
|
|
465
|
+
- `call_json(...)`: Realiza chamada HTTP autenticada, retorna JSON.
|
|
466
|
+
- `load_view(...)`: Helper para consultas a views Sankhya (`CRUDServiceProvider.loadView`).
|
|
467
|
+
- `Call(...)`: Método estático compatível com scaffolds legados.
|
|
468
|
+
- **SankhyaHTTPError**: Exceção lançada em caso de erro HTTP (status != 200), contendo status_code e response_text.
|
|
469
|
+
|
|
470
|
+
### Exemplo de uso (instanciado)
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
from biatoolkit.sankhya_call import Sankhya
|
|
474
|
+
sk = Sankhya(mcp=mcp)
|
|
475
|
+
result = sk.load_view("BIA_VW_MB_RULES", "CODPROD_A = 123", fields="*")
|
|
476
|
+
# O base_url será extraído automaticamente do header do runtime (current_host)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Exemplo de uso (estático)
|
|
480
|
+
|
|
481
|
+
```python
|
|
482
|
+
from biatoolkit.sankhya_call import Sankhya
|
|
483
|
+
result = Sankhya.Call(jsessionID="...", payload={...}, base_url="https://meu.sankhya.com.br", query="serviceName=...&outputType=json")
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Observações
|
|
487
|
+
|
|
488
|
+
- O JSESSIONID pode ser passado explicitamente ou extraído automaticamente do header do runtime (se rodando em MCP Server).
|
|
489
|
+
- O método `load_view` facilita consultas a views Sankhya, montando o payload e a querystring automaticamente, e resolve o base_url do header se não for passado.
|
|
490
|
+
- O método `call_json` permite chamadas genéricas a qualquer serviço Sankhya, com controle total sobre headers, método HTTP, payload e querystring, mas exige base_url explícito se não estiver em contexto MCP.
|
|
491
|
+
|
|
492
|
+
---
|
|
@@ -381,4 +381,82 @@ fornecido em ambiente produtivo pelo Bia Agent Builder.
|
|
|
381
381
|
- Um arquivo `.env` para testes locais.
|
|
382
382
|
- No cofre de segredos do Bia Agent Builder para usar em ambiente produtivo.
|
|
383
383
|
|
|
384
|
-
|
|
384
|
+
|
|
385
|
+
## **Validação e Coerção de Dados**
|
|
386
|
+
|
|
387
|
+
O Bia Toolkit oferece funções utilitárias para normalização, sanitização e coerção de dados, facilitando o consumo seguro de entradas vindas de APIs, headers, payloads e integrações legadas.
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
### **Validação de Dados** (`biatoolkit.validation.validation`)
|
|
391
|
+
|
|
392
|
+
Utilize a classe **BiaValidation** para acessar as funções de validação de forma padronizada:
|
|
393
|
+
|
|
394
|
+
- **BiaValidation.parse_int_list(value, dedupe=True, keep_order=True)**
|
|
395
|
+
- Normaliza um valor arbitrário em uma lista de inteiros.
|
|
396
|
+
- Suporta: None, int, str (extrai números), list/tuple (recursivo).
|
|
397
|
+
- Ignora valores inválidos.
|
|
398
|
+
- Deduplica e mantém ordem por padrão.
|
|
399
|
+
- Exemplo:
|
|
400
|
+
```python
|
|
401
|
+
from biatoolkit.validation.validation import BiaValidation
|
|
402
|
+
BiaValidation.parse_int_list("SKU=300231 x2") # [300231, 2]
|
|
403
|
+
BiaValidation.parse_int_list([1, 2, 1, 3], dedupe=False) # [1, 2, 1, 3]
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
- **BiaValidation.sanitize_like(value, max_len=80, upper=True)**
|
|
407
|
+
- Sanitiza texto para uso seguro em filtros LIKE/search SQL.
|
|
408
|
+
- Remove caracteres fora da allowlist, limita tamanho, escapa aspas simples, %, _ e normaliza espaços.
|
|
409
|
+
- Converte para maiúsculas por padrão.
|
|
410
|
+
- Exemplo:
|
|
411
|
+
```python
|
|
412
|
+
from biatoolkit.validation.validation import BiaValidation
|
|
413
|
+
BiaValidation.sanitize_like("O'Reilly") # "O''REILLY"
|
|
414
|
+
BiaValidation.sanitize_like("100%_OK") # "100\\%\\_OK"
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### **Coerção de Dados** (`biatoolkit.validation.coercion`)
|
|
418
|
+
|
|
419
|
+
Utilize a classe **BiaCoercion** para acessar as funções de coerção de forma padronizada:
|
|
420
|
+
|
|
421
|
+
- **BiaCoercion.ensure_list(value)**
|
|
422
|
+
- Garante que o valor seja retornado como lista.
|
|
423
|
+
- None → [], list → list, tuple/set → list, dict → [dict], outro → [valor].
|
|
424
|
+
- Exemplo:
|
|
425
|
+
```python
|
|
426
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
427
|
+
BiaCoercion.ensure_list(None) # []
|
|
428
|
+
BiaCoercion.ensure_list({"a": 1}) # [{"a": 1}]
|
|
429
|
+
BiaCoercion.ensure_list(5) # [5]
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
- **BiaCoercion.unwrap_dollar_value(value)**
|
|
433
|
+
- Desembrulha valores no formato {"$": ...} (comum em integrações legadas).
|
|
434
|
+
- Exemplo:
|
|
435
|
+
```python
|
|
436
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
437
|
+
BiaCoercion.unwrap_dollar_value({"$": 123}) # 123
|
|
438
|
+
BiaCoercion.unwrap_dollar_value("abc") # "abc"
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
- **BiaCoercion.to_int(value, default=0)**
|
|
442
|
+
- Converte valor para int de forma segura.
|
|
443
|
+
- None, "", bool, NaN/inf → default.
|
|
444
|
+
- Aceita strings numéricas, floats (trunca), etc.
|
|
445
|
+
- Exemplo:
|
|
446
|
+
```python
|
|
447
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
448
|
+
BiaCoercion.to_int(" 42 ") # 42
|
|
449
|
+
BiaCoercion.to_int(None, default=-1) # -1
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
- **BiaCoercion.to_float(value, default=0.0)**
|
|
453
|
+
- Converte valor para float de forma segura.
|
|
454
|
+
- None, "", bool, NaN/inf → default.
|
|
455
|
+
- Aceita strings com vírgula decimal.
|
|
456
|
+
- Exemplo:
|
|
457
|
+
```python
|
|
458
|
+
to_float("12,34") # 12.34
|
|
459
|
+
to_float("abc", default=-1.0) # -1.0
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Essas funções são úteis para garantir robustez e previsibilidade ao tratar dados vindos de múltiplas fontes, reduzindo erros e if/else espalhados pelo código.
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"""
|
|
2
|
+
biatoolkit.sankhya_call
|
|
3
|
+
|
|
4
|
+
Classe utilitária para chamadas HTTP a serviços Sankhya (ou gateway),
|
|
5
|
+
autenticando via JSESSIONID (cookie), preferencialmente obtido do header
|
|
6
|
+
do runtime (AgentCore) via BiaUtil.
|
|
7
|
+
|
|
8
|
+
Objetivo:
|
|
9
|
+
- O dev do MCP Tool não precisa recriar autenticação, headers e session.
|
|
10
|
+
- Basta chamar Sankhya(...).call_json(...) ou Sankhya.Call(...) (compat).
|
|
11
|
+
|
|
12
|
+
Requisitos:
|
|
13
|
+
- requests
|
|
14
|
+
- urllib3 (vem com requests)
|
|
15
|
+
- boto3 (já usado no biatoolkit.util)
|
|
16
|
+
- mcp.server.fastmcp.FastMCP (se usar no server; opcional para modo local)
|
|
17
|
+
|
|
18
|
+
Config via env (todas opcionais):
|
|
19
|
+
- SANKHYA_TIMEOUT_CONNECT: default 3.05
|
|
20
|
+
- SANKHYA_TIMEOUT_READ: default 12
|
|
21
|
+
- SANKHYA_RETRIES_TOTAL: default 3
|
|
22
|
+
- SANKHYA_RETRY_BACKOFF: default 0.5
|
|
23
|
+
- SANKHYA_VERIFY_SSL: default "1"
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from typing import Any, Dict, Optional, Tuple
|
|
30
|
+
import os
|
|
31
|
+
import json
|
|
32
|
+
import logging
|
|
33
|
+
|
|
34
|
+
import requests
|
|
35
|
+
from requests.adapters import HTTPAdapter
|
|
36
|
+
from urllib3.util.retry import Retry
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
# FastMCP só existe no runtime/servidor MCP. Em testes locais, pode faltar.
|
|
40
|
+
from mcp.server.fastmcp import FastMCP
|
|
41
|
+
except Exception: # pragma: no cover
|
|
42
|
+
FastMCP = Any # type: ignore
|
|
43
|
+
|
|
44
|
+
from .util import BiaUtil
|
|
45
|
+
from .settings import BiaToolkitSettings
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Caminho fixo do serviço Sankhya
|
|
54
|
+
SERVICE_PATH = "/mge/service.sbr"
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class SankhyaSettings:
|
|
58
|
+
"""
|
|
59
|
+
Configurações específicas da integração Sankhya (timeouts, retries, SSL).
|
|
60
|
+
Não armazena base_url nem service_path.
|
|
61
|
+
"""
|
|
62
|
+
timeout_connect: float
|
|
63
|
+
timeout_read: float
|
|
64
|
+
retries_total: int
|
|
65
|
+
retry_backoff: float
|
|
66
|
+
verify_ssl: bool
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def from_env() -> "SankhyaSettings":
|
|
70
|
+
def _f(name: str, default: float) -> float:
|
|
71
|
+
try:
|
|
72
|
+
return float(os.getenv(name, str(default)))
|
|
73
|
+
except Exception:
|
|
74
|
+
return default
|
|
75
|
+
|
|
76
|
+
def _i(name: str, default: int) -> int:
|
|
77
|
+
try:
|
|
78
|
+
return int(os.getenv(name, str(default)))
|
|
79
|
+
except Exception:
|
|
80
|
+
return default
|
|
81
|
+
|
|
82
|
+
def _b(name: str, default: bool) -> bool:
|
|
83
|
+
raw = os.getenv(name, "1" if default else "0").strip().lower()
|
|
84
|
+
return raw in ("1", "true", "yes", "y", "on")
|
|
85
|
+
|
|
86
|
+
return SankhyaSettings(
|
|
87
|
+
timeout_connect=_f("SANKHYA_TIMEOUT_CONNECT", 3.05),
|
|
88
|
+
timeout_read=_f("SANKHYA_TIMEOUT_READ", 12.0),
|
|
89
|
+
retries_total=_i("SANKHYA_RETRIES_TOTAL", 3),
|
|
90
|
+
retry_backoff=_f("SANKHYA_RETRY_BACKOFF", 0.5),
|
|
91
|
+
verify_ssl=_b("SANKHYA_VERIFY_SSL", True),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def build_url(
|
|
95
|
+
*,
|
|
96
|
+
url: Optional[str] = None,
|
|
97
|
+
base_url: Optional[str] = None,
|
|
98
|
+
query: Optional[str] = None,
|
|
99
|
+
) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Resolve URL final para chamada Sankhya.
|
|
102
|
+
- Se url for completa (http/https), usa como está.
|
|
103
|
+
- Se url for relativo, concatena base_url + url.
|
|
104
|
+
- Se não houver url, monta base_url + SERVICE_PATH.
|
|
105
|
+
- Se não houver base_url, lança erro.
|
|
106
|
+
"""
|
|
107
|
+
if url and url.strip():
|
|
108
|
+
u = url.strip()
|
|
109
|
+
if u.startswith("http://") or u.startswith("https://"):
|
|
110
|
+
final = u
|
|
111
|
+
else:
|
|
112
|
+
if not base_url:
|
|
113
|
+
raise ValueError("base_url deve ser informado para url relativo.")
|
|
114
|
+
b = base_url.strip().rstrip("/")
|
|
115
|
+
if not u.startswith("/"):
|
|
116
|
+
u = "/" + u
|
|
117
|
+
final = b + u
|
|
118
|
+
else:
|
|
119
|
+
if not base_url:
|
|
120
|
+
raise ValueError("base_url deve ser informado para montar a URL Sankhya.")
|
|
121
|
+
b = base_url.strip().rstrip("/")
|
|
122
|
+
final = f"{b}{SERVICE_PATH}"
|
|
123
|
+
if query:
|
|
124
|
+
if "?" in final:
|
|
125
|
+
return f"{final}&{query.lstrip('?')}"
|
|
126
|
+
return f"{final}?{query.lstrip('?')}"
|
|
127
|
+
return final
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class SankhyaHTTPError(RuntimeError):
|
|
132
|
+
def __init__(self, message: str, status_code: Optional[int] = None, response_text: str = ""):
|
|
133
|
+
super().__init__(message)
|
|
134
|
+
self.status_code = status_code
|
|
135
|
+
self.response_text = response_text
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class Sankhya:
|
|
139
|
+
"""
|
|
140
|
+
Classe responsável por integrar e chamar serviços da plataforma Sankhya.
|
|
141
|
+
|
|
142
|
+
Uso recomendado (em MCP server):
|
|
143
|
+
sk = Sankhya(mcp)
|
|
144
|
+
out = sk.load_view("BIA_VW_MB_RULES", "CODPROD_A = 123", fields="*")
|
|
145
|
+
|
|
146
|
+
Uso compatível com o scaffold (estático):
|
|
147
|
+
out = Sankhya.Call(jsessionID="...", payload={...})
|
|
148
|
+
# ou sem jsessionID se você passar mcp:
|
|
149
|
+
out = Sankhya.Call(jsessionID=None, mcp=mcp, payload={...})
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
def __init__(
|
|
153
|
+
self,
|
|
154
|
+
mcp: Optional[FastMCP] = None,
|
|
155
|
+
*,
|
|
156
|
+
toolkit_settings: Optional[BiaToolkitSettings] = None,
|
|
157
|
+
sankhya_settings: Optional[SankhyaSettings] = None,
|
|
158
|
+
default_headers: Optional[Dict[str, str]] = None,
|
|
159
|
+
):
|
|
160
|
+
"""
|
|
161
|
+
Inicializa a instância Sankhya.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
mcp: Instância opcional do FastMCP para contexto do runtime.
|
|
165
|
+
toolkit_settings: Configurações globais do toolkit (opcional).
|
|
166
|
+
sankhya_settings: Configurações específicas da integração Sankhya (opcional).
|
|
167
|
+
default_headers: Headers HTTP padrão para todas as requisições (opcional).
|
|
168
|
+
"""
|
|
169
|
+
self.mcp = mcp
|
|
170
|
+
self.toolkit_settings = toolkit_settings or BiaToolkitSettings.from_env()
|
|
171
|
+
self.sankhya_settings = sankhya_settings or SankhyaSettings.from_env()
|
|
172
|
+
self.default_headers = default_headers or {}
|
|
173
|
+
|
|
174
|
+
# Cria uma sessão HTTP com política de retries configurada
|
|
175
|
+
self._session = self._build_session()
|
|
176
|
+
|
|
177
|
+
# -------------------------
|
|
178
|
+
# Sessão HTTP + retries
|
|
179
|
+
# -------------------------
|
|
180
|
+
def _build_session(self) -> requests.Session:
|
|
181
|
+
"""
|
|
182
|
+
Cria uma sessão HTTP configurada com política de retries.
|
|
183
|
+
Utiliza as configurações de timeout e retries do Sankhya.
|
|
184
|
+
"""
|
|
185
|
+
s = requests.Session()
|
|
186
|
+
|
|
187
|
+
# Configura política de retries para falhas temporárias
|
|
188
|
+
retries = Retry(
|
|
189
|
+
total=max(0, int(self.sankhya_settings.retries_total)),
|
|
190
|
+
connect=max(0, int(self.sankhya_settings.retries_total)),
|
|
191
|
+
read=max(0, int(self.sankhya_settings.retries_total)),
|
|
192
|
+
backoff_factor=float(self.sankhya_settings.retry_backoff),
|
|
193
|
+
status_forcelist=(429, 500, 502, 503, 504),
|
|
194
|
+
allowed_methods=("POST", "GET"),
|
|
195
|
+
raise_on_status=False,
|
|
196
|
+
)
|
|
197
|
+
adapter = HTTPAdapter(max_retries=retries)
|
|
198
|
+
s.mount("https://", adapter)
|
|
199
|
+
s.mount("http://", adapter)
|
|
200
|
+
return s
|
|
201
|
+
|
|
202
|
+
# -------------------------
|
|
203
|
+
# JSESSIONID resolve
|
|
204
|
+
# -------------------------
|
|
205
|
+
def _resolve_jsessionid(self, jsessionid: Optional[str] = None) -> str:
|
|
206
|
+
"""
|
|
207
|
+
Resolve o JSESSIONID a ser usado na autenticação.
|
|
208
|
+
Prioriza o valor explícito, depois tenta extrair do header do runtime via BiaUtil.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
jsessionid: Token de sessão explícito (opcional).
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
str: JSESSIONID válido.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
ValueError: Se não for possível obter o JSESSIONID.
|
|
218
|
+
"""
|
|
219
|
+
# 1) parâmetro explícito
|
|
220
|
+
if jsessionid and str(jsessionid).strip():
|
|
221
|
+
return str(jsessionid).strip()
|
|
222
|
+
|
|
223
|
+
# 2) header do runtime via BiaUtil (se tiver mcp)
|
|
224
|
+
if self.mcp is not None:
|
|
225
|
+
try:
|
|
226
|
+
util = BiaUtil(self.mcp, self.toolkit_settings)
|
|
227
|
+
h = util.get_header()
|
|
228
|
+
if h and getattr(h, "jsessionid", None):
|
|
229
|
+
return str(h.jsessionid).strip()
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.debug("Falha ao ler jsessionid do header via BiaUtil: %s", e)
|
|
232
|
+
|
|
233
|
+
raise ValueError(
|
|
234
|
+
"JSESSIONID ausente. Passe jsessionid explicitamente ou forneça 'mcp' para ler do header."
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# -------------------------
|
|
239
|
+
# Métodos públicos
|
|
240
|
+
# -------------------------
|
|
241
|
+
def call_json(
|
|
242
|
+
self,
|
|
243
|
+
*,
|
|
244
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
245
|
+
jsessionid: Optional[str] = None,
|
|
246
|
+
url: Optional[str] = None,
|
|
247
|
+
base_url: Optional[str] = None,
|
|
248
|
+
query: Optional[str] = None,
|
|
249
|
+
method: str = "POST",
|
|
250
|
+
extra_headers: Optional[Dict[str, str]] = None,
|
|
251
|
+
timeout: Optional[Tuple[float, float]] = None,
|
|
252
|
+
raise_for_http_error: bool = True,
|
|
253
|
+
) -> Dict[str, Any]:
|
|
254
|
+
"""
|
|
255
|
+
Faz uma chamada HTTP ao serviço Sankhya e retorna JSON (ou erro detalhado).
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
payload: Corpo JSON (para POST). Pode ser None.
|
|
259
|
+
jsessionid: Se None, tenta resolver do header (mcp) automaticamente.
|
|
260
|
+
url: URL completa (se quiser ignorar base_url/service_path).
|
|
261
|
+
base_url: Base URL alternativa (opcional).
|
|
262
|
+
query: Querystring adicional (ex: "serviceName=...&outputType=json")
|
|
263
|
+
method: "POST" ou "GET"
|
|
264
|
+
extra_headers: Headers adicionais.
|
|
265
|
+
timeout: (connect, read) override
|
|
266
|
+
raise_for_http_error: Se True, lança SankhyaHTTPError em status != 200
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
dict: Resposta JSON (ou {"raw_text": "..."} se não for JSON)
|
|
270
|
+
"""
|
|
271
|
+
# Resolve o token de sessão (JSESSIONID)
|
|
272
|
+
sid = self._resolve_jsessionid(jsessionid)
|
|
273
|
+
|
|
274
|
+
# Monta a querystring, sempre incluindo o mgeSession
|
|
275
|
+
query_parts = []
|
|
276
|
+
if query:
|
|
277
|
+
query_parts.append(query.lstrip("?"))
|
|
278
|
+
query_parts.append(f"mgeSession={sid}")
|
|
279
|
+
final_query = "&".join(query_parts)
|
|
280
|
+
|
|
281
|
+
# Monta a URL final da requisição
|
|
282
|
+
final_url = build_url(url=url, query=final_query, base_url=base_url)
|
|
283
|
+
print("FINAL URL:", final_url)
|
|
284
|
+
|
|
285
|
+
# Monta os headers da requisição
|
|
286
|
+
headers: Dict[str, str] = {
|
|
287
|
+
"Content-Type": "application/json",
|
|
288
|
+
**self.default_headers,
|
|
289
|
+
}
|
|
290
|
+
if extra_headers:
|
|
291
|
+
headers.update(extra_headers)
|
|
292
|
+
|
|
293
|
+
# Define o timeout da requisição
|
|
294
|
+
t = timeout or (self.sankhya_settings.timeout_connect, self.sankhya_settings.timeout_read)
|
|
295
|
+
|
|
296
|
+
# Realiza a chamada HTTP (GET ou POST)
|
|
297
|
+
if method.upper() == "GET":
|
|
298
|
+
resp = self._session.get(final_url, headers=headers, timeout=t, verify=self.sankhya_settings.verify_ssl)
|
|
299
|
+
else:
|
|
300
|
+
print("\n" + "="*40)
|
|
301
|
+
print("Sankhya POST Request:")
|
|
302
|
+
print(f"URL: {final_url}")
|
|
303
|
+
print(f"Headers: {json.dumps(headers, ensure_ascii=False, indent=2)}")
|
|
304
|
+
print(f"Payload: {json.dumps(payload, ensure_ascii=False, indent=2)}")
|
|
305
|
+
print(f"Timeout: {t}")
|
|
306
|
+
print(f"Verify SSL: {self.sankhya_settings.verify_ssl}")
|
|
307
|
+
print("="*40 + "\n")
|
|
308
|
+
resp = self._session.post(final_url, headers=headers, json=payload, timeout=t, verify=self.sankhya_settings.verify_ssl)
|
|
309
|
+
|
|
310
|
+
# Trata erros HTTP
|
|
311
|
+
if resp.status_code != 200:
|
|
312
|
+
msg = f"Falha HTTP {resp.status_code} ao chamar Sankhya/gateway."
|
|
313
|
+
if raise_for_http_error:
|
|
314
|
+
raise SankhyaHTTPError(msg, status_code=resp.status_code, response_text=resp.text)
|
|
315
|
+
return {"error": True, "status_code": resp.status_code, "message": msg, "response_text": resp.text}
|
|
316
|
+
|
|
317
|
+
# Tenta decodificar JSON; se não der, retorna texto puro
|
|
318
|
+
try:
|
|
319
|
+
return resp.json()
|
|
320
|
+
except Exception:
|
|
321
|
+
return {"raw_text": resp.text}
|
|
322
|
+
|
|
323
|
+
def load_view(
|
|
324
|
+
self,
|
|
325
|
+
view_name: str,
|
|
326
|
+
where_sql: str,
|
|
327
|
+
*,
|
|
328
|
+
fields: str = "*",
|
|
329
|
+
jsessionid: Optional[str] = None,
|
|
330
|
+
url: Optional[str] = None,
|
|
331
|
+
base_url: Optional[str] = None,
|
|
332
|
+
output_type: str = "json",
|
|
333
|
+
extra_headers: Optional[Dict[str, str]] = None,
|
|
334
|
+
) -> Dict[str, Any]:
|
|
335
|
+
"""
|
|
336
|
+
Helper para o CRUDServiceProvider.loadView (mge/service.sbr).
|
|
337
|
+
Monta o payload e a querystring automaticamente para facilitar consultas a views Sankhya.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
view_name: Nome da view no Sankhya.
|
|
341
|
+
where_sql: Cláusula WHERE (string).
|
|
342
|
+
fields: Campos a retornar (string).
|
|
343
|
+
jsessionid: Se None, pega do header (mcp).
|
|
344
|
+
url: URL completa opcional (override).
|
|
345
|
+
base_url: Base URL alternativa (opcional).
|
|
346
|
+
output_type: "json" (default).
|
|
347
|
+
extra_headers: Headers adicionais.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
dict: Resposta da consulta à view.
|
|
351
|
+
"""
|
|
352
|
+
# Monta a querystring para o serviço
|
|
353
|
+
query = f"serviceName=CRUDServiceProvider.loadView&outputType={output_type}"
|
|
354
|
+
|
|
355
|
+
# Monta o corpo do payload conforme esperado pelo serviço
|
|
356
|
+
body = {
|
|
357
|
+
"serviceName": "CRUDServiceProvider.loadView",
|
|
358
|
+
"requestBody": {
|
|
359
|
+
"query": {
|
|
360
|
+
"viewName": view_name,
|
|
361
|
+
"where": {"$": where_sql},
|
|
362
|
+
"fields": {"field": {"$": fields}},
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
util = BiaUtil(self.mcp)
|
|
369
|
+
# Se base_url não foi fornecido, tenta obter do util.get_header().current_host
|
|
370
|
+
if not base_url:
|
|
371
|
+
base_url = util.get_header().current_host
|
|
372
|
+
if not base_url:
|
|
373
|
+
raise ValueError("base_url não definida e não foi possível obter current_host do header.")
|
|
374
|
+
|
|
375
|
+
return self.call_json(
|
|
376
|
+
payload=body,
|
|
377
|
+
jsessionid=jsessionid,
|
|
378
|
+
url=url,
|
|
379
|
+
base_url=base_url,
|
|
380
|
+
query=query,
|
|
381
|
+
method="POST",
|
|
382
|
+
extra_headers=extra_headers,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# -------------------------
|
|
386
|
+
# Compat com scaffold
|
|
387
|
+
# -------------------------
|
|
388
|
+
@staticmethod
|
|
389
|
+
def Call(
|
|
390
|
+
jsessionID: Optional[str] = None,
|
|
391
|
+
*,
|
|
392
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
393
|
+
mcp: Optional[FastMCP] = None,
|
|
394
|
+
url: Optional[str] = None,
|
|
395
|
+
base_url: Optional[str] = None,
|
|
396
|
+
query: Optional[str] = None,
|
|
397
|
+
method: str = "POST",
|
|
398
|
+
extra_headers: Optional[Dict[str, str]] = None,
|
|
399
|
+
) -> Optional[dict]:
|
|
400
|
+
"""
|
|
401
|
+
Método estático compatível com o scaffold original.
|
|
402
|
+
Permite chamada rápida ao serviço Sankhya sem instanciar manualmente a classe.
|
|
403
|
+
|
|
404
|
+
Exemplos de uso:
|
|
405
|
+
Sankhya.Call(jsessionID="...", payload={...}, query="serviceName=...&outputType=json")
|
|
406
|
+
Sankhya.Call(jsessionID=None, mcp=mcp, payload={...}, query="...")
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
jsessionID: Token de sessão JSESSIONID (opcional).
|
|
410
|
+
payload: Corpo da requisição (dict).
|
|
411
|
+
mcp: Instância opcional do FastMCP para contexto do runtime.
|
|
412
|
+
url: URL completa (opcional).
|
|
413
|
+
base_url: Base URL alternativa (opcional).
|
|
414
|
+
query: Querystring adicional (opcional).
|
|
415
|
+
method: "POST" ou "GET".
|
|
416
|
+
extra_headers: Headers adicionais.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
dict: Resposta do serviço ou lança erro se HTTP != 200.
|
|
420
|
+
"""
|
|
421
|
+
sk = Sankhya(mcp=mcp)
|
|
422
|
+
return sk.call_json(
|
|
423
|
+
payload=payload,
|
|
424
|
+
jsessionid=jsessionID,
|
|
425
|
+
url=url,
|
|
426
|
+
base_url=base_url,
|
|
427
|
+
query=query,
|
|
428
|
+
method=method,
|
|
429
|
+
extra_headers=extra_headers,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
__all__ = ["Sankhya", "SankhyaSettings", "SankhyaHTTPError"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
#sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/..'))
|
|
4
|
+
from biatoolkit.sankhya_call import Sankhya, SankhyaHTTPError
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
# Opcional: não depende mais disso, mas pode deixar.
|
|
8
|
+
os.environ.setdefault("SANKHYA_SERVICE_PATH", "/mge/service.sbr")
|
|
9
|
+
|
|
10
|
+
# ⚠️ COLE AQUI seu JSESSIONID em UMA LINHA (sem ENTER no meio)
|
|
11
|
+
REAL = "CrK0qgoMYcnfZsURMi7iarpFKWSVqS-qO3HWMiu8.sankhya-w-78598f67f-n7mcp"
|
|
12
|
+
|
|
13
|
+
if not REAL or REAL == "COLE_SEU_JSESSIONID_AQUI":
|
|
14
|
+
raise SystemExit("Cole seu JSESSIONID na variável REAL (uma linha só) e rode: python -m biatoolkit.test_sankhya")
|
|
15
|
+
|
|
16
|
+
# Base URL do ambiente correto (o novo)
|
|
17
|
+
BASE_URL = "https://ecossistema2.sankhyacloud.com.br"
|
|
18
|
+
|
|
19
|
+
sk = Sankhya()
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
out = sk.load_view(
|
|
23
|
+
"BIA_VW_MB_RULES",
|
|
24
|
+
"1=1",
|
|
25
|
+
fields="COMPLDESC_A,COMPLDESC_B,CONFIDENCE_A_B,LIFT_A_B,CNT_AB,DISPONIVEL_B,DTGERACAO",
|
|
26
|
+
jsessionid=REAL,
|
|
27
|
+
base_url=BASE_URL, # <-- NOVO: injeta a base url aqui
|
|
28
|
+
# url="/mge/service.sbr", # opcional: se quiser passar só o path
|
|
29
|
+
# url="https://ecossistema2.sankhyacloud.com.br/mge/service.sbr", # opcional: URL completa
|
|
30
|
+
)
|
|
31
|
+
print("OK! Resposta (top-level keys):", list(out.keys()))
|
|
32
|
+
print(out)
|
|
33
|
+
if isinstance(out, dict):
|
|
34
|
+
print(json.dumps(out, indent=2, ensure_ascii=False))
|
|
35
|
+
|
|
36
|
+
except SankhyaHTTPError as e:
|
|
37
|
+
print("HTTP ERROR:", e.status_code)
|
|
38
|
+
print("BODY (primeiros 500 chars):")
|
|
39
|
+
print((e.response_text or "")[:500])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: biatoolkit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Biblioteca para desenvolvedores que utilizam o BiaAgentBuilder
|
|
5
5
|
Author: Bia Platform Team
|
|
6
6
|
Author-email: data.platform@sankhya.com.br
|
|
@@ -348,4 +348,145 @@ O método `get_parameter(parameter_name: str)` busca o parâmetro informado em d
|
|
|
348
348
|
- Um arquivo `.env` para testes locais.
|
|
349
349
|
- No cofre de segredos do Bia Agent Builder para usar em ambiente produtivo.
|
|
350
350
|
|
|
351
|
-
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Validação e Coerção de Dados
|
|
355
|
+
|
|
356
|
+
O Bia Toolkit oferece funções utilitárias para normalização, sanitização e coerção de dados, facilitando o consumo seguro de entradas vindas de APIs, headers, payloads e integrações legadas.
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
### Validação de Dados (`biatoolkit.validation.validation`)
|
|
360
|
+
|
|
361
|
+
Utilize a classe **BiaValidation** para acessar as funções de validação de forma padronizada:
|
|
362
|
+
|
|
363
|
+
- **BiaValidation.parse_int_list(value, dedupe=True, keep_order=True)**
|
|
364
|
+
- Normaliza um valor arbitrário em uma lista de inteiros.
|
|
365
|
+
- Suporta: None, int, str (extrai números), list/tuple (recursivo).
|
|
366
|
+
- Ignora valores inválidos.
|
|
367
|
+
- Deduplica e mantém ordem por padrão.
|
|
368
|
+
- Exemplo:
|
|
369
|
+
```python
|
|
370
|
+
from biatoolkit.validation.validation import BiaValidation
|
|
371
|
+
BiaValidation.parse_int_list("SKU=300231 x2") # [300231, 2]
|
|
372
|
+
BiaValidation.parse_int_list([1, 2, 1, 3], dedupe=False) # [1, 2, 1, 3]
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
- **BiaValidation.sanitize_like(value, max_len=80, upper=True)**
|
|
376
|
+
- Sanitiza texto para uso seguro em filtros LIKE/search SQL.
|
|
377
|
+
- Remove caracteres fora da allowlist, limita tamanho, escapa aspas simples, %, _ e normaliza espaços.
|
|
378
|
+
- Converte para maiúsculas por padrão.
|
|
379
|
+
- Exemplo:
|
|
380
|
+
```python
|
|
381
|
+
from biatoolkit.validation.validation import BiaValidation
|
|
382
|
+
BiaValidation.sanitize_like("O'Reilly") # "O''REILLY"
|
|
383
|
+
BiaValidation.sanitize_like("100%_OK") # "100\\%\\_OK"
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
### Coerção de Dados (`biatoolkit.validation.coercion`)
|
|
388
|
+
|
|
389
|
+
Utilize a classe **BiaCoercion** para acessar as funções de coerção de forma padronizada:
|
|
390
|
+
|
|
391
|
+
- **BiaCoercion.ensure_list(value)**
|
|
392
|
+
- Garante que o valor seja retornado como lista.
|
|
393
|
+
- None → [], list → list, tuple/set → list, dict → [dict], outro → [valor].
|
|
394
|
+
- Exemplo:
|
|
395
|
+
```python
|
|
396
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
397
|
+
BiaCoercion.ensure_list(None) # []
|
|
398
|
+
BiaCoercion.ensure_list({"a": 1}) # [{"a": 1}]
|
|
399
|
+
BiaCoercion.ensure_list(5) # [5]
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
- **BiaCoercion.unwrap_dollar_value(value)**
|
|
403
|
+
- Desembrulha valores no formato {"$": ...} (comum em integrações legadas).
|
|
404
|
+
- Exemplo:
|
|
405
|
+
```python
|
|
406
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
407
|
+
BiaCoercion.unwrap_dollar_value({"$": 123}) # 123
|
|
408
|
+
BiaCoercion.unwrap_dollar_value("abc") # "abc"
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
- **BiaCoercion.to_int(value, default=0)**
|
|
412
|
+
- Converte valor para int de forma segura.
|
|
413
|
+
- None, "", bool, NaN/inf → default.
|
|
414
|
+
- Aceita strings numéricas, floats (trunca), etc.
|
|
415
|
+
- Exemplo:
|
|
416
|
+
```python
|
|
417
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
418
|
+
BiaCoercion.to_int(" 42 ") # 42
|
|
419
|
+
BiaCoercion.to_int(None, default=-1) # -1
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
- **BiaCoercion.to_float(value, default=0.0)**
|
|
423
|
+
- Converte valor para float de forma segura.
|
|
424
|
+
- None, "", bool, NaN/inf → default.
|
|
425
|
+
- Aceita strings com vírgula decimal.
|
|
426
|
+
- Exemplo:
|
|
427
|
+
```python
|
|
428
|
+
from biatoolkit.validation.coercion import BiaCoercion
|
|
429
|
+
BiaCoercion.to_float("12,34") # 12.34
|
|
430
|
+
BiaCoercion.to_float("abc", default=-1.0) # -1.0
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
Essas funções são úteis para garantir robustez e previsibilidade ao tratar dados vindos de múltiplas fontes, reduzindo erros e if/else espalhados pelo código.
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Integração com Sankhya (`biatoolkit.sankhya_call`)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
O Bia Toolkit oferece integração pronta para consumo de serviços HTTP da plataforma Sankhya (ou gateway), com autenticação via JSESSIONID e configuração de timeouts/retries via variáveis de ambiente.
|
|
442
|
+
|
|
443
|
+
### Objetivo
|
|
444
|
+
|
|
445
|
+
- Facilitar chamadas autenticadas a serviços Sankhya sem que o desenvolvedor precise lidar com autenticação, headers ou gerenciamento de sessão HTTP.
|
|
446
|
+
- Fornecer métodos prontos para chamadas genéricas (`call_json`) e para consultas a views (`load_view`).
|
|
447
|
+
- Permitir uso estático compatível com scaffolds legados via `Sankhya.Call(...)`.
|
|
448
|
+
|
|
449
|
+
### Configuração
|
|
450
|
+
|
|
451
|
+
- O caminho do serviço (`/mge/service.sbr`) é fixo no toolkit.
|
|
452
|
+
- O parâmetro `base_url` é obrigatório e pode ser passado explicitamente ou extraído automaticamente do header do runtime (`current_host` via `BiaUtil`).
|
|
453
|
+
- Não existe mais configuração via variável de ambiente para base_url ou service_path.
|
|
454
|
+
- As variáveis de ambiente opcionais são apenas para timeout, retries e SSL:
|
|
455
|
+
- `SANKHYA_TIMEOUT_CONNECT`: timeout de conexão (default: 3.05)
|
|
456
|
+
- `SANKHYA_TIMEOUT_READ`: timeout de leitura (default: 12)
|
|
457
|
+
- `SANKHYA_RETRIES_TOTAL`: número de tentativas em falha (default: 3)
|
|
458
|
+
- `SANKHYA_RETRY_BACKOFF`: backoff entre tentativas (default: 0.5)
|
|
459
|
+
- `SANKHYA_VERIFY_SSL`: se valida SSL (default: "1")
|
|
460
|
+
|
|
461
|
+
### Principais classes e métodos
|
|
462
|
+
|
|
463
|
+
- **SankhyaSettings**: Dataclass de configuração de timeouts/retries/SSL. Use `SankhyaSettings.from_env()` para obter as configurações do ambiente.
|
|
464
|
+
- **Sankhya**: Classe principal de integração. Permite instanciar com contexto MCP (`FastMCP`) ou usar métodos estáticos.
|
|
465
|
+
- `call_json(...)`: Realiza chamada HTTP autenticada, retorna JSON.
|
|
466
|
+
- `load_view(...)`: Helper para consultas a views Sankhya (`CRUDServiceProvider.loadView`).
|
|
467
|
+
- `Call(...)`: Método estático compatível com scaffolds legados.
|
|
468
|
+
- **SankhyaHTTPError**: Exceção lançada em caso de erro HTTP (status != 200), contendo status_code e response_text.
|
|
469
|
+
|
|
470
|
+
### Exemplo de uso (instanciado)
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
from biatoolkit.sankhya_call import Sankhya
|
|
474
|
+
sk = Sankhya(mcp=mcp)
|
|
475
|
+
result = sk.load_view("BIA_VW_MB_RULES", "CODPROD_A = 123", fields="*")
|
|
476
|
+
# O base_url será extraído automaticamente do header do runtime (current_host)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Exemplo de uso (estático)
|
|
480
|
+
|
|
481
|
+
```python
|
|
482
|
+
from biatoolkit.sankhya_call import Sankhya
|
|
483
|
+
result = Sankhya.Call(jsessionID="...", payload={...}, base_url="https://meu.sankhya.com.br", query="serviceName=...&outputType=json")
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Observações
|
|
487
|
+
|
|
488
|
+
- O JSESSIONID pode ser passado explicitamente ou extraído automaticamente do header do runtime (se rodando em MCP Server).
|
|
489
|
+
- O método `load_view` facilita consultas a views Sankhya, montando o payload e a querystring automaticamente, e resolve o base_url do header se não for passado.
|
|
490
|
+
- O método `call_json` permite chamadas genéricas a qualquer serviço Sankhya, com controle total sobre headers, método HTTP, payload e querystring, mas exige base_url explícito se não estiver em contexto MCP.
|
|
491
|
+
|
|
492
|
+
---
|
|
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
|