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 +47 -0
- mailcore/assinatura.py +105 -0
- mailcore/cliente.py +105 -0
- mailcore/erros.py +42 -0
- mailcore/py.typed +0 -0
- mailcore/recursos/__init__.py +93 -0
- mailcore/recursos/automacoes.py +252 -0
- mailcore/recursos/base.py +51 -0
- mailcore/recursos/caixas.py +784 -0
- mailcore/recursos/campanhas.py +391 -0
- mailcore/recursos/chaves_api.py +109 -0
- mailcore/recursos/cli.py +78 -0
- mailcore/recursos/contatos.py +892 -0
- mailcore/recursos/dominios.py +179 -0
- mailcore/recursos/emails.py +266 -0
- mailcore/recursos/eventos.py +159 -0
- mailcore/recursos/modelos.py +484 -0
- mailcore/recursos/recebimento.py +211 -0
- mailcore/recursos/webhooks.py +252 -0
- mailcore/tipos.py +164 -0
- mailcore/transporte.py +602 -0
- mailcore/versao.py +3 -0
- mailcore_sdk-0.1.0a1.dist-info/METADATA +142 -0
- mailcore_sdk-0.1.0a1.dist-info/RECORD +27 -0
- mailcore_sdk-0.1.0a1.dist-info/WHEEL +5 -0
- mailcore_sdk-0.1.0a1.dist-info/licenses/LICENSE +21 -0
- mailcore_sdk-0.1.0a1.dist-info/top_level.txt +1 -0
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)
|