biatoolkit 1.2.0__py3-none-any.whl
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/__init__.py +4 -0
- biatoolkit/basic_client.py +141 -0
- biatoolkit/sankhya_call.py +433 -0
- biatoolkit/schema/__init__.py +1 -0
- biatoolkit/schema/header.py +19 -0
- biatoolkit/settings.py +106 -0
- biatoolkit/test_sankhya.py +39 -0
- biatoolkit/util.py +228 -0
- biatoolkit-1.2.0.dist-info/METADATA +492 -0
- biatoolkit-1.2.0.dist-info/RECORD +12 -0
- biatoolkit-1.2.0.dist-info/WHEEL +5 -0
- biatoolkit-1.2.0.dist-info/top_level.txt +1 -0
biatoolkit/__init__.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
biatoolkit.basic_client
|
|
3
|
+
|
|
4
|
+
Este módulo contém o BiaClient, um cliente HTTP assíncrono para comunicação
|
|
5
|
+
com servidores MCP (Model Context Protocol).
|
|
6
|
+
|
|
7
|
+
Responsabilidades:
|
|
8
|
+
- Abrir e gerenciar conexões HTTP streamable com servidores MCP.
|
|
9
|
+
- Inicializar sessões MCP.
|
|
10
|
+
- Encapsular chamadas comuns (listar tools, executar tool).
|
|
11
|
+
|
|
12
|
+
O objetivo é esconder os detalhes de sessão, streams e inicialização,
|
|
13
|
+
expondo uma API simples para quem consome a biblioteca.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any, Awaitable, Callable, Optional, Dict
|
|
19
|
+
|
|
20
|
+
from mcp import ClientSession
|
|
21
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
22
|
+
|
|
23
|
+
from .settings import BiaToolkitSettings
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BiaClient:
|
|
27
|
+
"""
|
|
28
|
+
Cliente básico para interação com servidores MCP.
|
|
29
|
+
|
|
30
|
+
Esta classe encapsula toda a complexidade envolvida em:
|
|
31
|
+
- Abrir conexões HTTP streamable
|
|
32
|
+
- Criar e inicializar ClientSession
|
|
33
|
+
- Executar chamadas MCP
|
|
34
|
+
|
|
35
|
+
Exemplos de uso:
|
|
36
|
+
client = BiaClient("http://localhost:8000")
|
|
37
|
+
tools = await client.list_tools()
|
|
38
|
+
result = await client.call_tool("minha_tool", {"x": 1})
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
url: str = "http://0.0.0.0:8000/mcp",
|
|
44
|
+
headers: Optional[Dict[str, str]] = None,
|
|
45
|
+
settings: Optional[BiaToolkitSettings] = None,
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Inicializa o cliente MCP.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
url:
|
|
52
|
+
URL base do servidor MCP.
|
|
53
|
+
- Se não terminar com '/mcp', o sufixo será adicionado automaticamente.
|
|
54
|
+
headers:
|
|
55
|
+
Headers HTTP opcionais enviados em todas as requisições.
|
|
56
|
+
Normalmente usados para enviar contexto (runtime, autenticação, etc.).
|
|
57
|
+
settings:
|
|
58
|
+
Configurações opcionais da biblioteca (timeout, etc.).
|
|
59
|
+
Se None, carrega defaults e overrides via variáveis de ambiente.
|
|
60
|
+
"""
|
|
61
|
+
# Carrega configurações globais (timeout, etc.)
|
|
62
|
+
self.settings = settings or BiaToolkitSettings.from_env()
|
|
63
|
+
|
|
64
|
+
# Garante que a URL termine com '/mcp'
|
|
65
|
+
suffix = "/mcp"
|
|
66
|
+
self.url = url if url.endswith(suffix) else f"{url}{suffix}"
|
|
67
|
+
|
|
68
|
+
# Headers HTTP que serão enviados para o servidor MCP
|
|
69
|
+
self.headers = headers
|
|
70
|
+
|
|
71
|
+
async def _with_session(self, fn: Callable[[ClientSession], Awaitable[Any]]) -> Any:
|
|
72
|
+
"""
|
|
73
|
+
Executa uma função dentro de uma sessão MCP já inicializada.
|
|
74
|
+
|
|
75
|
+
Este método centraliza todo o boilerplate necessário para:
|
|
76
|
+
- Abrir a conexão HTTP streamable
|
|
77
|
+
- Criar a ClientSession
|
|
78
|
+
- Chamar session.initialize()
|
|
79
|
+
- Garantir fechamento correto dos recursos
|
|
80
|
+
|
|
81
|
+
Ele recebe uma função (callback) que recebe a ClientSession e
|
|
82
|
+
executa a lógica específica (listar tools, chamar tool, etc.).
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
fn: Função assíncrona que recebe uma ClientSession.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
O valor retornado pela função fn.
|
|
89
|
+
"""
|
|
90
|
+
async with streamablehttp_client(
|
|
91
|
+
self.url,
|
|
92
|
+
self.headers,
|
|
93
|
+
# Timeout configurável via settings
|
|
94
|
+
timeout=self.settings.client_timeout_seconds,
|
|
95
|
+
# Mantém o servidor ativo mesmo após fechar streams
|
|
96
|
+
terminate_on_close=False,
|
|
97
|
+
) as (read_stream, write_stream, _):
|
|
98
|
+
|
|
99
|
+
# Cria a sessão MCP usando os streams
|
|
100
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
101
|
+
# Inicialização obrigatória do protocolo MCP
|
|
102
|
+
await session.initialize()
|
|
103
|
+
|
|
104
|
+
# Executa a lógica específica passada pelo caller
|
|
105
|
+
return await fn(session)
|
|
106
|
+
|
|
107
|
+
async def list_tools(self) -> dict:
|
|
108
|
+
"""
|
|
109
|
+
Lista todas as ferramentas (tools) disponíveis no servidor MCP.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
dict: Estrutura contendo as tools expostas pelo servidor.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
async def _call(session: ClientSession) -> Any:
|
|
116
|
+
return await session.list_tools()
|
|
117
|
+
|
|
118
|
+
return await self._with_session(_call)
|
|
119
|
+
|
|
120
|
+
async def call_tool(self, tool_name: str, params: dict = None) -> dict:
|
|
121
|
+
"""
|
|
122
|
+
Executa uma ferramenta específica disponível no servidor MCP.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
tool_name:
|
|
126
|
+
Nome da tool a ser executada (exatamente como exposta pelo servidor).
|
|
127
|
+
params:
|
|
128
|
+
Parâmetros da tool, enviados como dicionário.
|
|
129
|
+
Pode ser None se a tool não exigir parâmetros.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
dict: Resultado da execução da tool.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
async def _call(session: ClientSession) -> Any:
|
|
136
|
+
return await session.call_tool(tool_name, params)
|
|
137
|
+
|
|
138
|
+
return await self._with_session(_call)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
__all__ = ["BiaClient"]
|
|
@@ -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 @@
|
|
|
1
|
+
# biatoolkit/schema/__init__.py
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
class Header:
|
|
3
|
+
def __init__(self,
|
|
4
|
+
current_host: str = None,
|
|
5
|
+
user_email: str = None,
|
|
6
|
+
jwt_token: str = None,
|
|
7
|
+
jsessionid: str = None,
|
|
8
|
+
organization_id: int = None,
|
|
9
|
+
codparc: int = None,
|
|
10
|
+
iam_user_id: int = None,
|
|
11
|
+
gateway_token: str = None):
|
|
12
|
+
self.current_host = current_host
|
|
13
|
+
self.user_email = user_email
|
|
14
|
+
self.jwt_token = jwt_token
|
|
15
|
+
self.jsessionid = jsessionid
|
|
16
|
+
self.organization_id = organization_id
|
|
17
|
+
self.codparc = codparc
|
|
18
|
+
self.iam_user_id = iam_user_id
|
|
19
|
+
self.gateway_token = gateway_token
|