mailcore-sdk 0.1.0a1__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.
mailcore/__init__.py ADDED
@@ -0,0 +1,47 @@
1
+ """Cliente Python da API Mailcore para aplicações de servidor."""
2
+
3
+ from .assinatura import verificar_assinatura
4
+ from .cliente import Cliente, ClienteAsync
5
+ from .erros import Erro, ErroApi, ErroAssinatura, ErroConfiguracao, ErroResposta, ErroTransporte
6
+ from .tipos import (
7
+ AnexoCompostoEmail,
8
+ AnexoEmail,
9
+ ConfiguracaoCliente,
10
+ EmailComModelo,
11
+ EmailHtml,
12
+ EmailTexto,
13
+ EventoRecebido,
14
+ OpcoesRequisicao,
15
+ PedidoEmail,
16
+ PedidoEmailLote,
17
+ Resposta,
18
+ RespostaEnvio,
19
+ RespostaLote,
20
+ )
21
+ from .versao import VERSAO
22
+
23
+ __all__ = [
24
+ "Cliente",
25
+ "ClienteAsync",
26
+ "Erro",
27
+ "ErroApi",
28
+ "ErroAssinatura",
29
+ "ErroConfiguracao",
30
+ "ErroResposta",
31
+ "ErroTransporte",
32
+ "Resposta",
33
+ "PedidoEmail",
34
+ "PedidoEmailLote",
35
+ "RespostaEnvio",
36
+ "RespostaLote",
37
+ "AnexoEmail",
38
+ "AnexoCompostoEmail",
39
+ "EmailHtml",
40
+ "EmailTexto",
41
+ "EmailComModelo",
42
+ "ConfiguracaoCliente",
43
+ "OpcoesRequisicao",
44
+ "EventoRecebido",
45
+ "verificar_assinatura",
46
+ "VERSAO",
47
+ ]
mailcore/assinatura.py ADDED
@@ -0,0 +1,105 @@
1
+ """Verificação local interoperável de webhooks HMAC-SHA256."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import binascii
7
+ import hashlib
8
+ import hmac
9
+ import json
10
+ import math
11
+ import re
12
+ import time
13
+ from collections.abc import Mapping
14
+ from typing import cast
15
+
16
+ from .erros import ErroAssinatura
17
+ from .tipos import EventoRecebido
18
+
19
+
20
+ def _constante_invalida(_valor: str) -> None:
21
+ raise ValueError
22
+
23
+
24
+ def verificar_assinatura(
25
+ corpo_bruto: bytes | str,
26
+ cabecalhos: Mapping[str, str],
27
+ segredo: str,
28
+ *,
29
+ agora: float | None = None,
30
+ tolerancia: int = 300,
31
+ ) -> EventoRecebido:
32
+ """Verifique o corpo recebido antes de interpretar JSON ou executar ações."""
33
+ try:
34
+ if not isinstance(corpo_bruto, (str, bytes)):
35
+ raise ValueError
36
+ corpo = corpo_bruto.encode("utf-8") if isinstance(corpo_bruto, str) else corpo_bruto
37
+ if (
38
+ len(corpo) > 8 * 1024 * 1024
39
+ or type(tolerancia) is not int
40
+ or not 1 <= tolerancia <= 300
41
+ ):
42
+ raise ValueError
43
+ if not isinstance(segredo, str) or not segredo.startswith("whsec_") or len(segredo) > 1024:
44
+ raise ValueError
45
+ normalizados: dict[str, str] = {}
46
+ for nome, valor in cabecalhos.items():
47
+ nome = nome.lower().replace("_", "-").removeprefix("http-")
48
+ if nome not in {"svix-id", "svix-timestamp", "svix-signature"}:
49
+ continue
50
+ if nome in normalizados or not isinstance(valor, str):
51
+ raise ValueError
52
+ normalizados[nome] = valor
53
+ identificador = normalizados["svix-id"]
54
+ instante = normalizados["svix-timestamp"]
55
+ assinaturas = normalizados["svix-signature"]
56
+ if not re.fullmatch(r"[\x21-\x7e]{1,256}", identificador) or not re.fullmatch(
57
+ r"[1-9][0-9]{0,15}", instante
58
+ ):
59
+ raise ValueError
60
+ momento = time.time() if agora is None else agora
61
+ if (
62
+ isinstance(momento, bool)
63
+ or not math.isfinite(momento)
64
+ or len(assinaturas) > 4096
65
+ or abs(momento - int(instante)) > tolerancia
66
+ ):
67
+ raise ValueError
68
+ chave = base64.b64decode(segredo[6:], validate=True)
69
+ if len(chave) < 16:
70
+ raise ValueError
71
+ esperado = hmac.digest(
72
+ chave, f"{identificador}.{instante}.".encode("ascii") + corpo, hashlib.sha256
73
+ )
74
+ valido = False
75
+ for assinatura in assinaturas.split()[:32]:
76
+ versao, separador, valor = assinatura.partition(",")
77
+ if versao != "v1" or not separador:
78
+ continue
79
+ try:
80
+ valido |= hmac.compare_digest(esperado, base64.b64decode(valor, validate=True))
81
+ except (ValueError, binascii.Error):
82
+ continue
83
+ if not valido:
84
+ raise ValueError
85
+ evento = json.loads(corpo.decode("utf-8"), parse_constant=_constante_invalida)
86
+ if (
87
+ not isinstance(evento, dict)
88
+ or not isinstance(evento.get("type"), str)
89
+ or not isinstance(evento.get("created_at"), str)
90
+ or "data" not in evento
91
+ ):
92
+ raise ValueError
93
+ return cast(EventoRecebido, evento)
94
+ except (
95
+ ValueError,
96
+ TypeError,
97
+ KeyError,
98
+ AttributeError,
99
+ UnicodeError,
100
+ OverflowError,
101
+ RecursionError,
102
+ ):
103
+ raise ErroAssinatura(
104
+ "Não foi possível verificar a autenticidade do webhook.", codigo="assinatura_invalida"
105
+ ) from None
mailcore/cliente.py ADDED
@@ -0,0 +1,105 @@
1
+ """Clientes por instância: reutilize durante a vida da aplicação ou tarefa."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from types import TracebackType
6
+ from typing import Unpack
7
+
8
+ import httpx
9
+
10
+ from . import recursos
11
+ from .tipos import ConfiguracaoCliente
12
+ from .transporte import ChaveApi, Transporte, TransporteAsync
13
+
14
+
15
+ class Cliente:
16
+ def __init__(
17
+ self,
18
+ chave_api: ChaveApi = None,
19
+ *,
20
+ transporte_http: httpx.BaseTransport | None = None,
21
+ **configuracao: Unpack[ConfiguracaoCliente],
22
+ ) -> None:
23
+ self._transporte = Transporte(chave_api, transporte_http=transporte_http, **configuracao)
24
+ self.emails = recursos.Emails(self._transporte)
25
+ self.lotes = recursos.Lotes(self._transporte)
26
+ self.dominios = recursos.Dominios(self._transporte)
27
+ self.chaves_api = recursos.ChavesApi(self._transporte)
28
+ self.contatos = recursos.Contatos(self._transporte)
29
+ self.segmentos = recursos.Segmentos(self._transporte)
30
+ self.topicos = recursos.Topicos(self._transporte)
31
+ self.propriedades_contatos = recursos.PropriedadesContatos(self._transporte)
32
+ self.modelos = recursos.Modelos(self._transporte)
33
+ self.webhooks = recursos.Webhooks(self._transporte)
34
+ self.campanhas = recursos.Campanhas(self._transporte)
35
+ self.automacoes = recursos.Automacoes(self._transporte)
36
+ self.eventos = recursos.Eventos(self._transporte)
37
+ self.recebimento = recursos.Recebimento(self._transporte)
38
+ self.caixas = recursos.Caixas(self._transporte)
39
+ self.cli = recursos.CliMailcore(self._transporte)
40
+
41
+ def __enter__(self) -> Cliente:
42
+ return self
43
+
44
+ def __exit__(
45
+ self,
46
+ tipo: type[BaseException] | None,
47
+ valor: BaseException | None,
48
+ rastreamento: TracebackType | None,
49
+ ) -> None:
50
+ self.fechar()
51
+
52
+ def fechar(self) -> None:
53
+ self._transporte.fechar()
54
+
55
+ def __repr__(self) -> str:
56
+ return "<Cliente credenciais=protegidas>"
57
+
58
+
59
+ class ClienteAsync:
60
+ def __init__(
61
+ self,
62
+ chave_api: ChaveApi = None,
63
+ *,
64
+ transporte_http: httpx.AsyncBaseTransport | None = None,
65
+ **configuracao: Unpack[ConfiguracaoCliente],
66
+ ) -> None:
67
+ self._transporte = TransporteAsync(
68
+ chave_api, transporte_http=transporte_http, **configuracao
69
+ )
70
+ self.emails = recursos.EmailsAsync(self._transporte)
71
+ self.lotes = recursos.LotesAsync(self._transporte)
72
+ self.dominios = recursos.DominiosAsync(self._transporte)
73
+ self.chaves_api = recursos.ChavesApiAsync(self._transporte)
74
+ self.contatos = recursos.ContatosAsync(self._transporte)
75
+ self.segmentos = recursos.SegmentosAsync(self._transporte)
76
+ self.topicos = recursos.TopicosAsync(self._transporte)
77
+ self.propriedades_contatos = recursos.PropriedadesContatosAsync(self._transporte)
78
+ self.modelos = recursos.ModelosAsync(self._transporte)
79
+ self.webhooks = recursos.WebhooksAsync(self._transporte)
80
+ self.campanhas = recursos.CampanhasAsync(self._transporte)
81
+ self.automacoes = recursos.AutomacoesAsync(self._transporte)
82
+ self.eventos = recursos.EventosAsync(self._transporte)
83
+ self.recebimento = recursos.RecebimentoAsync(self._transporte)
84
+ self.caixas = recursos.CaixasAsync(self._transporte)
85
+ self.cli = recursos.CliMailcoreAsync(self._transporte)
86
+
87
+ async def __aenter__(self) -> ClienteAsync:
88
+ return self
89
+
90
+ async def __aexit__(
91
+ self,
92
+ tipo: type[BaseException] | None,
93
+ valor: BaseException | None,
94
+ rastreamento: TracebackType | None,
95
+ ) -> None:
96
+ await self.fechar()
97
+
98
+ async def fechar(self) -> None:
99
+ await self._transporte.fechar()
100
+
101
+ async def fechar_async(self) -> None:
102
+ await self.fechar()
103
+
104
+ def __repr__(self) -> str:
105
+ return "<ClienteAsync credenciais=protegidas>"
mailcore/erros.py ADDED
@@ -0,0 +1,42 @@
1
+ """Erros públicos com metadados e sem reprodução de corpos de transporte."""
2
+
3
+ from collections.abc import Mapping
4
+ from types import MappingProxyType
5
+
6
+
7
+ class Erro(Exception):
8
+ def __init__(
9
+ self,
10
+ mensagem: str = "Não foi possível concluir a operação.",
11
+ *,
12
+ codigo: str = "erro_mailcore",
13
+ status: int | None = None,
14
+ cabecalhos: Mapping[str, str] | None = None,
15
+ ) -> None:
16
+ super().__init__(mensagem)
17
+ self.codigo = codigo
18
+ self.status = status
19
+ self.cabecalhos: Mapping[str, str] = MappingProxyType(dict(cabecalhos or {}))
20
+
21
+ def __repr__(self) -> str:
22
+ return f"{type(self).__name__}(codigo={self.codigo!r}, status={self.status!r})"
23
+
24
+
25
+ class ErroApi(Erro):
26
+ """Rejeição HTTP; código/mensagem públicos são preservados quando válidos."""
27
+
28
+
29
+ class ErroConfiguracao(ValueError):
30
+ """Opções, identificadores ou JSON de entrada inválidos."""
31
+
32
+
33
+ class ErroTransporte(Erro):
34
+ """Conexão, protocolo incompleto ou prazo de atividade excedido."""
35
+
36
+
37
+ class ErroResposta(Erro):
38
+ """JSON inválido, codificação inesperada ou limite de resposta excedido."""
39
+
40
+
41
+ class ErroAssinatura(Erro):
42
+ """Webhook não autenticado; nenhum detalhe do segredo é exposto."""
mailcore/py.typed ADDED
File without changes
@@ -0,0 +1,93 @@
1
+ """Recursos síncronos e assíncronos do cliente Mailcore."""
2
+
3
+ from .automacoes import (
4
+ Automacoes,
5
+ AutomacoesAsync,
6
+ )
7
+ from .caixas import (
8
+ Caixas,
9
+ CaixasAsync,
10
+ )
11
+ from .campanhas import (
12
+ Campanhas,
13
+ CampanhasAsync,
14
+ )
15
+ from .chaves_api import (
16
+ ChavesApi,
17
+ ChavesApiAsync,
18
+ )
19
+ from .cli import (
20
+ CliMailcore,
21
+ CliMailcoreAsync,
22
+ )
23
+ from .contatos import (
24
+ Contatos,
25
+ ContatosAsync,
26
+ PropriedadesContatos,
27
+ PropriedadesContatosAsync,
28
+ Segmentos,
29
+ SegmentosAsync,
30
+ Topicos,
31
+ TopicosAsync,
32
+ )
33
+ from .dominios import (
34
+ Dominios,
35
+ DominiosAsync,
36
+ )
37
+ from .emails import (
38
+ Emails,
39
+ EmailsAsync,
40
+ Lotes,
41
+ LotesAsync,
42
+ )
43
+ from .eventos import (
44
+ Eventos,
45
+ EventosAsync,
46
+ )
47
+ from .modelos import (
48
+ Modelos,
49
+ ModelosAsync,
50
+ )
51
+ from .recebimento import (
52
+ Recebimento,
53
+ RecebimentoAsync,
54
+ )
55
+ from .webhooks import (
56
+ Webhooks,
57
+ WebhooksAsync,
58
+ )
59
+
60
+ __all__ = [
61
+ "Emails",
62
+ "EmailsAsync",
63
+ "Lotes",
64
+ "LotesAsync",
65
+ "Dominios",
66
+ "DominiosAsync",
67
+ "ChavesApi",
68
+ "ChavesApiAsync",
69
+ "Contatos",
70
+ "ContatosAsync",
71
+ "Segmentos",
72
+ "SegmentosAsync",
73
+ "Topicos",
74
+ "TopicosAsync",
75
+ "PropriedadesContatos",
76
+ "PropriedadesContatosAsync",
77
+ "Modelos",
78
+ "ModelosAsync",
79
+ "Webhooks",
80
+ "WebhooksAsync",
81
+ "Campanhas",
82
+ "CampanhasAsync",
83
+ "Automacoes",
84
+ "AutomacoesAsync",
85
+ "Eventos",
86
+ "EventosAsync",
87
+ "Recebimento",
88
+ "RecebimentoAsync",
89
+ "Caixas",
90
+ "CaixasAsync",
91
+ "CliMailcore",
92
+ "CliMailcoreAsync",
93
+ ]
@@ -0,0 +1,252 @@
1
+ """Operações de automacoes do contrato público Mailcore."""
2
+
3
+ from __future__ import annotations
4
+
5
+ # Fonte estática; manutenção em scripts/gerar_recursos.py.
6
+ from collections.abc import Mapping
7
+ from typing import TYPE_CHECKING, Any, Unpack
8
+
9
+ from ..tipos import (
10
+ OpcoesRequisicao,
11
+ Resposta,
12
+ )
13
+ from .base import RecursoBase, RecursoBaseAsync, segmento
14
+
15
+ if TYPE_CHECKING:
16
+ from ..transporte import Transporte, TransporteAsync
17
+
18
+
19
+ class ExecucoesAutomacoes(RecursoBase):
20
+ def listar(
21
+ self,
22
+ automacao_id: str,
23
+ filtros: Mapping[str, Any] | None = None,
24
+ **opcoes: Unpack[OpcoesRequisicao],
25
+ ) -> Resposta[Any]:
26
+ return self._executar(
27
+ "GET",
28
+ f"/automations/{segmento(automacao_id)}/runs",
29
+ consulta=filtros,
30
+ **opcoes,
31
+ )
32
+
33
+ def obter(
34
+ self,
35
+ automacao_id: str,
36
+ execucao_id: str,
37
+ **opcoes: Unpack[OpcoesRequisicao],
38
+ ) -> Resposta[Any]:
39
+ return self._executar(
40
+ "GET",
41
+ f"/automations/{segmento(automacao_id)}/runs/{segmento(execucao_id)}",
42
+ **opcoes,
43
+ )
44
+
45
+
46
+ class Automacoes(RecursoBase):
47
+ def __init__(self, transporte: Transporte) -> None:
48
+ super().__init__(transporte)
49
+ self.execucoes: ExecucoesAutomacoes = ExecucoesAutomacoes(transporte)
50
+
51
+ def criar(
52
+ self,
53
+ pedido: Mapping[str, Any],
54
+ **opcoes: Unpack[OpcoesRequisicao],
55
+ ) -> Resposta[Any]:
56
+ return self._executar(
57
+ "POST",
58
+ "/automations",
59
+ corpo=pedido,
60
+ **opcoes,
61
+ )
62
+
63
+ def listar(
64
+ self,
65
+ filtros: Mapping[str, Any] | None = None,
66
+ **opcoes: Unpack[OpcoesRequisicao],
67
+ ) -> Resposta[Any]:
68
+ return self._executar(
69
+ "GET",
70
+ "/automations",
71
+ consulta=filtros,
72
+ **opcoes,
73
+ )
74
+
75
+ def obter(
76
+ self,
77
+ id: str,
78
+ **opcoes: Unpack[OpcoesRequisicao],
79
+ ) -> Resposta[Any]:
80
+ return self._executar(
81
+ "GET",
82
+ f"/automations/{segmento(id)}",
83
+ **opcoes,
84
+ )
85
+
86
+ def atualizar(
87
+ self,
88
+ id: str,
89
+ pedido: Mapping[str, Any],
90
+ **opcoes: Unpack[OpcoesRequisicao],
91
+ ) -> Resposta[Any]:
92
+ return self._executar(
93
+ "PATCH",
94
+ f"/automations/{segmento(id)}",
95
+ corpo=pedido,
96
+ **opcoes,
97
+ )
98
+
99
+ def remover(
100
+ self,
101
+ id: str,
102
+ **opcoes: Unpack[OpcoesRequisicao],
103
+ ) -> Resposta[Any]:
104
+ return self._executar(
105
+ "DELETE",
106
+ f"/automations/{segmento(id)}",
107
+ **opcoes,
108
+ )
109
+
110
+ def duplicar(
111
+ self,
112
+ id: str,
113
+ pedido: Mapping[str, Any] | None = None,
114
+ **opcoes: Unpack[OpcoesRequisicao],
115
+ ) -> Resposta[Any]:
116
+ return self._executar(
117
+ "POST",
118
+ f"/automations/{segmento(id)}/duplicate",
119
+ corpo={} if pedido is None else pedido,
120
+ **opcoes,
121
+ )
122
+
123
+ def parar(
124
+ self,
125
+ id: str,
126
+ pedido: Mapping[str, Any],
127
+ **opcoes: Unpack[OpcoesRequisicao],
128
+ ) -> Resposta[Any]:
129
+ return self._executar(
130
+ "POST",
131
+ f"/automations/{segmento(id)}/stop",
132
+ corpo=pedido,
133
+ **opcoes,
134
+ )
135
+
136
+
137
+ class ExecucoesAutomacoesAsync(RecursoBaseAsync):
138
+ async def listar(
139
+ self,
140
+ automacao_id: str,
141
+ filtros: Mapping[str, Any] | None = None,
142
+ **opcoes: Unpack[OpcoesRequisicao],
143
+ ) -> Resposta[Any]:
144
+ return await self._executar(
145
+ "GET",
146
+ f"/automations/{segmento(automacao_id)}/runs",
147
+ consulta=filtros,
148
+ **opcoes,
149
+ )
150
+
151
+ async def obter(
152
+ self,
153
+ automacao_id: str,
154
+ execucao_id: str,
155
+ **opcoes: Unpack[OpcoesRequisicao],
156
+ ) -> Resposta[Any]:
157
+ return await self._executar(
158
+ "GET",
159
+ f"/automations/{segmento(automacao_id)}/runs/{segmento(execucao_id)}",
160
+ **opcoes,
161
+ )
162
+
163
+
164
+ class AutomacoesAsync(RecursoBaseAsync):
165
+ def __init__(self, transporte: TransporteAsync) -> None:
166
+ super().__init__(transporte)
167
+ self.execucoes: ExecucoesAutomacoesAsync = ExecucoesAutomacoesAsync(transporte)
168
+
169
+ async def criar(
170
+ self,
171
+ pedido: Mapping[str, Any],
172
+ **opcoes: Unpack[OpcoesRequisicao],
173
+ ) -> Resposta[Any]:
174
+ return await self._executar(
175
+ "POST",
176
+ "/automations",
177
+ corpo=pedido,
178
+ **opcoes,
179
+ )
180
+
181
+ async def listar(
182
+ self,
183
+ filtros: Mapping[str, Any] | None = None,
184
+ **opcoes: Unpack[OpcoesRequisicao],
185
+ ) -> Resposta[Any]:
186
+ return await self._executar(
187
+ "GET",
188
+ "/automations",
189
+ consulta=filtros,
190
+ **opcoes,
191
+ )
192
+
193
+ async def obter(
194
+ self,
195
+ id: str,
196
+ **opcoes: Unpack[OpcoesRequisicao],
197
+ ) -> Resposta[Any]:
198
+ return await self._executar(
199
+ "GET",
200
+ f"/automations/{segmento(id)}",
201
+ **opcoes,
202
+ )
203
+
204
+ async def atualizar(
205
+ self,
206
+ id: str,
207
+ pedido: Mapping[str, Any],
208
+ **opcoes: Unpack[OpcoesRequisicao],
209
+ ) -> Resposta[Any]:
210
+ return await self._executar(
211
+ "PATCH",
212
+ f"/automations/{segmento(id)}",
213
+ corpo=pedido,
214
+ **opcoes,
215
+ )
216
+
217
+ async def remover(
218
+ self,
219
+ id: str,
220
+ **opcoes: Unpack[OpcoesRequisicao],
221
+ ) -> Resposta[Any]:
222
+ return await self._executar(
223
+ "DELETE",
224
+ f"/automations/{segmento(id)}",
225
+ **opcoes,
226
+ )
227
+
228
+ async def duplicar(
229
+ self,
230
+ id: str,
231
+ pedido: Mapping[str, Any] | None = None,
232
+ **opcoes: Unpack[OpcoesRequisicao],
233
+ ) -> Resposta[Any]:
234
+ return await self._executar(
235
+ "POST",
236
+ f"/automations/{segmento(id)}/duplicate",
237
+ corpo={} if pedido is None else pedido,
238
+ **opcoes,
239
+ )
240
+
241
+ async def parar(
242
+ self,
243
+ id: str,
244
+ pedido: Mapping[str, Any],
245
+ **opcoes: Unpack[OpcoesRequisicao],
246
+ ) -> Resposta[Any]:
247
+ return await self._executar(
248
+ "POST",
249
+ f"/automations/{segmento(id)}/stop",
250
+ corpo=pedido,
251
+ **opcoes,
252
+ )
@@ -0,0 +1,51 @@
1
+ """Base fina: um método do recurso corresponde a uma operação HTTP."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import TYPE_CHECKING, Any, Unpack
7
+ from urllib.parse import quote
8
+
9
+ from ..erros import ErroConfiguracao
10
+ from ..tipos import GestorFluxo, GestorFluxoAsync, OpcoesRequisicao, Resposta
11
+
12
+ if TYPE_CHECKING:
13
+ from ..transporte import Transporte, TransporteAsync
14
+
15
+
16
+ def segmento(valor: str) -> str:
17
+ if (
18
+ not isinstance(valor, str)
19
+ or not valor
20
+ or valor in {".", ".."}
21
+ or re.search(r"[\x00-\x1f\x7f]", valor)
22
+ ):
23
+ raise ErroConfiguracao("Informe um identificador não vazio e sem controles.")
24
+ try:
25
+ return quote(valor, safe="-._~")
26
+ except UnicodeError:
27
+ raise ErroConfiguracao("O identificador deve ser UTF-8 válido.") from None
28
+
29
+
30
+ class RecursoBase:
31
+ def __init__(self, transporte: Transporte) -> None:
32
+ self._transporte = transporte
33
+
34
+ def _executar(self, metodo: str, caminho: str, **opcoes: Any) -> Resposta[Any]:
35
+ return self._transporte.executar(metodo, caminho, **opcoes)
36
+
37
+ def _fluxo(self, metodo: str, caminho: str, **opcoes: Unpack[OpcoesRequisicao]) -> GestorFluxo:
38
+ return self._transporte.fluxo(metodo, caminho, formato="fluxo", **opcoes)
39
+
40
+
41
+ class RecursoBaseAsync:
42
+ def __init__(self, transporte: TransporteAsync) -> None:
43
+ self._transporte = transporte
44
+
45
+ async def _executar(self, metodo: str, caminho: str, **opcoes: Any) -> Resposta[Any]:
46
+ return await self._transporte.executar(metodo, caminho, **opcoes)
47
+
48
+ def _fluxo(
49
+ self, metodo: str, caminho: str, **opcoes: Unpack[OpcoesRequisicao]
50
+ ) -> GestorFluxoAsync:
51
+ return self._transporte.fluxo(metodo, caminho, formato="fluxo", **opcoes)