attime-star-api-python-sdk 1.4.0__tar.gz → 1.8.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.
- {attime_star_api_python_sdk-1.4.0/src/attime_star_api_python_sdk.egg-info → attime_star_api_python_sdk-1.8.0}/PKG-INFO +1 -1
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/pyproject.toml +1 -1
- attime_star_api_python_sdk-1.8.0/src/attime/star/apis/attime001.py +293 -0
- attime_star_api_python_sdk-1.8.0/src/attime/star/dtos/attime001.py +762 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/src/attime/star/dtos/bvix.py +10 -5
- attime_star_api_python_sdk-1.8.0/src/attime/star/dtos/ecommerce.py +684 -0
- attime_star_api_python_sdk-1.8.0/src/attime/star/dtos/enums.py +90 -0
- attime_star_api_python_sdk-1.8.0/src/attime/star/dtos/serde.py +56 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0/src/attime_star_api_python_sdk.egg-info}/PKG-INFO +1 -1
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/src/attime_star_api_python_sdk.egg-info/SOURCES.txt +8 -1
- attime_star_api_python_sdk-1.8.0/tests/test_attime001_atualizacoes.py +125 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/tests/test_attime001_cancelamento.py +56 -1
- attime_star_api_python_sdk-1.8.0/tests/test_attime001_importar_ecommerce.py +158 -0
- attime_star_api_python_sdk-1.8.0/tests/test_attime001_insere_item.py +145 -0
- attime_star_api_python_sdk-1.8.0/tests/test_attime001_relacao.py +111 -0
- attime_star_api_python_sdk-1.4.0/src/attime/star/apis/attime001.py +0 -76
- attime_star_api_python_sdk-1.4.0/src/attime/star/dtos/attime001.py +0 -92
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/LICENSE +0 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/README.md +0 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/setup.cfg +0 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/src/attime/star/api.py +0 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/src/attime/star/apis/__init__.py +0 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/src/attime/star/apis/bvix.py +0 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/src/attime/star/apis/infocar.py +0 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/src/attime/star/dtos/__init__.py +0 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/src/attime/star/dtos/infocar.py +0 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/src/attime_star_api_python_sdk.egg-info/dependency_links.txt +0 -0
- {attime_star_api_python_sdk-1.4.0 → attime_star_api_python_sdk-1.8.0}/src/attime_star_api_python_sdk.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Cliente HTTP ApiAttime001 (ABTP0004)."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Callable, TypeVar
|
|
7
|
+
|
|
8
|
+
import jsonpickle
|
|
9
|
+
from fmconsult.utils.url import UrlUtil
|
|
10
|
+
from http import HTTPMethod
|
|
11
|
+
|
|
12
|
+
from attime.star.api import ATTimeStarApi
|
|
13
|
+
from attime.star.dtos.attime001 import (
|
|
14
|
+
ApiAttime001AtualizacoesDTO,
|
|
15
|
+
ApiAttime001CancelamentoDTO,
|
|
16
|
+
ApiAttime001EmitirItemDTO,
|
|
17
|
+
ApiAttime001RelacaoContratoCpfItemDTO,
|
|
18
|
+
ApiAttime001RelacaoItemDTO,
|
|
19
|
+
EscopoOperacao,
|
|
20
|
+
)
|
|
21
|
+
from attime.star.dtos.ecommerce import ApiAttime001ImportarEcommerceDTO
|
|
22
|
+
|
|
23
|
+
T = TypeVar('T')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _require_escopo_e_contrato(payload: dict) -> None:
|
|
27
|
+
if payload.get('escopoOperacao') in (None, ''):
|
|
28
|
+
raise ValueError('escopoOperacao é obrigatório')
|
|
29
|
+
if payload.get('numeroContrato') in (None, ''):
|
|
30
|
+
raise ValueError('numeroContrato é obrigatório')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _escopo_is(value: object, expected: EscopoOperacao) -> bool:
|
|
34
|
+
if value is None:
|
|
35
|
+
return False
|
|
36
|
+
return str(value).casefold() == expected.value.casefold()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _parse_attime001_list_response(
|
|
40
|
+
res: object,
|
|
41
|
+
*,
|
|
42
|
+
from_dict: Callable[[object], T | None],
|
|
43
|
+
endpoint_label: str,
|
|
44
|
+
) -> list[T]:
|
|
45
|
+
"""Normaliza lista / {dados} / {codigo} no padrão STAR BTC."""
|
|
46
|
+
if isinstance(res, list):
|
|
47
|
+
return [row for row in (from_dict(r) for r in res) if row is not None]
|
|
48
|
+
|
|
49
|
+
if isinstance(res, dict):
|
|
50
|
+
if 'dados' in res:
|
|
51
|
+
dados = res['dados']
|
|
52
|
+
if isinstance(dados, list):
|
|
53
|
+
return [
|
|
54
|
+
row for row in (from_dict(r) for r in dados) if row is not None
|
|
55
|
+
]
|
|
56
|
+
if isinstance(dados, dict):
|
|
57
|
+
parsed = from_dict(dados)
|
|
58
|
+
return [parsed] if parsed is not None else []
|
|
59
|
+
raise Exception(res.get('mensagem', 'Resposta inesperada em dados'))
|
|
60
|
+
|
|
61
|
+
if 'codigo' in res:
|
|
62
|
+
raise Exception(res.get('mensagem', 'Erro desconhecido'))
|
|
63
|
+
|
|
64
|
+
parsed = from_dict(res)
|
|
65
|
+
return [parsed] if parsed is not None else []
|
|
66
|
+
|
|
67
|
+
raise Exception(f'Resposta inesperada da API Attime001 {endpoint_label}')
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _parse_attime001_object_response(
|
|
71
|
+
res: object,
|
|
72
|
+
*,
|
|
73
|
+
from_dict: Callable[[object], T | None],
|
|
74
|
+
endpoint_label: str,
|
|
75
|
+
) -> T:
|
|
76
|
+
"""Como a lista, mas exige exatamente um objeto (ex.: InsereItem)."""
|
|
77
|
+
rows = _parse_attime001_list_response(
|
|
78
|
+
res, from_dict=from_dict, endpoint_label=endpoint_label
|
|
79
|
+
)
|
|
80
|
+
if not rows:
|
|
81
|
+
raise Exception(
|
|
82
|
+
f'Resposta vazia da API Attime001 {endpoint_label}'
|
|
83
|
+
)
|
|
84
|
+
return rows[0]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _require_emitir_item_keys(payload: dict) -> None:
|
|
88
|
+
for key in ('codigoProduto', 'codigoSucursal', 'sequencialContrato'):
|
|
89
|
+
if payload.get(key) in (None, ''):
|
|
90
|
+
raise ValueError(f'{key} é obrigatório')
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Attime001Api(ATTimeStarApi):
|
|
94
|
+
"""Rotinas ApiAttime001 — emissão, manutenção e consultas Relacao."""
|
|
95
|
+
|
|
96
|
+
def cancelamento(
|
|
97
|
+
self, item: ApiAttime001CancelamentoDTO | dict
|
|
98
|
+
) -> list[ApiAttime001CancelamentoDTO]:
|
|
99
|
+
"""POST /btc/ABTP0004/ApiAttime001/Cancelamento."""
|
|
100
|
+
logging.info('cancelling via ApiAttime001 Cancelamento...')
|
|
101
|
+
if isinstance(item, ApiAttime001CancelamentoDTO):
|
|
102
|
+
payload = item.to_dict()
|
|
103
|
+
else:
|
|
104
|
+
payload = dict(item)
|
|
105
|
+
|
|
106
|
+
_require_escopo_e_contrato(payload)
|
|
107
|
+
if _escopo_is(payload.get('escopoOperacao'), EscopoOperacao.DECLINIO) and not payload.get(
|
|
108
|
+
'declinio'
|
|
109
|
+
):
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f'declinio é obrigatório quando escopoOperacao={EscopoOperacao.DECLINIO}'
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
url = UrlUtil().make_url(
|
|
115
|
+
self.base_url,
|
|
116
|
+
['btc', 'ABTP0004', 'ApiAttime001', 'Cancelamento'],
|
|
117
|
+
)
|
|
118
|
+
res = self.call_request(
|
|
119
|
+
http_method=HTTPMethod.POST,
|
|
120
|
+
request_url=url,
|
|
121
|
+
payload=payload,
|
|
122
|
+
)
|
|
123
|
+
res = jsonpickle.decode(res)
|
|
124
|
+
return _parse_attime001_list_response(
|
|
125
|
+
res,
|
|
126
|
+
from_dict=ApiAttime001CancelamentoDTO.from_dict,
|
|
127
|
+
endpoint_label='Cancelamento',
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def atualizacoes(
|
|
131
|
+
self, item: ApiAttime001AtualizacoesDTO | dict
|
|
132
|
+
) -> list[ApiAttime001AtualizacoesDTO]:
|
|
133
|
+
"""POST /btc/ABTP0004/ApiAttime001/Atualizacoes."""
|
|
134
|
+
logging.info('updating via ApiAttime001 Atualizacoes...')
|
|
135
|
+
if isinstance(item, ApiAttime001AtualizacoesDTO):
|
|
136
|
+
payload = item.to_dict()
|
|
137
|
+
else:
|
|
138
|
+
payload = dict(item)
|
|
139
|
+
|
|
140
|
+
_require_escopo_e_contrato(payload)
|
|
141
|
+
|
|
142
|
+
url = UrlUtil().make_url(
|
|
143
|
+
self.base_url,
|
|
144
|
+
['btc', 'ABTP0004', 'ApiAttime001', 'Atualizacoes'],
|
|
145
|
+
)
|
|
146
|
+
res = self.call_request(
|
|
147
|
+
http_method=HTTPMethod.POST,
|
|
148
|
+
request_url=url,
|
|
149
|
+
payload=payload,
|
|
150
|
+
)
|
|
151
|
+
res = jsonpickle.decode(res)
|
|
152
|
+
return _parse_attime001_list_response(
|
|
153
|
+
res,
|
|
154
|
+
from_dict=ApiAttime001AtualizacoesDTO.from_dict,
|
|
155
|
+
endpoint_label='Atualizacoes',
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def insere_item(
|
|
159
|
+
self, item: ApiAttime001EmitirItemDTO | dict
|
|
160
|
+
) -> ApiAttime001EmitirItemDTO:
|
|
161
|
+
"""POST /btc/ABTP0004/ApiAttime001/InsereItem.
|
|
162
|
+
|
|
163
|
+
Disponível em homologação; produção ainda em liberação (STAR).
|
|
164
|
+
"""
|
|
165
|
+
logging.info('inserting item via ApiAttime001 InsereItem...')
|
|
166
|
+
if isinstance(item, ApiAttime001EmitirItemDTO):
|
|
167
|
+
payload = item.to_dict()
|
|
168
|
+
else:
|
|
169
|
+
payload = dict(item)
|
|
170
|
+
|
|
171
|
+
_require_emitir_item_keys(payload)
|
|
172
|
+
|
|
173
|
+
url = UrlUtil().make_url(
|
|
174
|
+
self.base_url,
|
|
175
|
+
['btc', 'ABTP0004', 'ApiAttime001', 'InsereItem'],
|
|
176
|
+
)
|
|
177
|
+
res = self.call_request(
|
|
178
|
+
http_method=HTTPMethod.POST,
|
|
179
|
+
request_url=url,
|
|
180
|
+
payload=payload,
|
|
181
|
+
)
|
|
182
|
+
res = jsonpickle.decode(res)
|
|
183
|
+
return _parse_attime001_object_response(
|
|
184
|
+
res,
|
|
185
|
+
from_dict=ApiAttime001EmitirItemDTO.from_dict,
|
|
186
|
+
endpoint_label='InsereItem',
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def importar_ecommerce(
|
|
190
|
+
self, item: ApiAttime001ImportarEcommerceDTO | dict
|
|
191
|
+
) -> ApiAttime001ImportarEcommerceDTO:
|
|
192
|
+
"""POST /btc/ABTP0004/ApiAttime001/ImportarEcommerce."""
|
|
193
|
+
logging.info('importing ecommerce via ApiAttime001 ImportarEcommerce...')
|
|
194
|
+
if isinstance(item, ApiAttime001ImportarEcommerceDTO):
|
|
195
|
+
payload = item.to_dict()
|
|
196
|
+
else:
|
|
197
|
+
payload = dict(item)
|
|
198
|
+
|
|
199
|
+
if not payload.get('emissao'):
|
|
200
|
+
raise ValueError('emissao é obrigatório')
|
|
201
|
+
emissao = payload['emissao']
|
|
202
|
+
if isinstance(emissao, dict):
|
|
203
|
+
if not emissao.get('clientes'):
|
|
204
|
+
raise ValueError('emissao.clientes é obrigatório')
|
|
205
|
+
if not emissao.get('contratos'):
|
|
206
|
+
raise ValueError('emissao.contratos é obrigatório')
|
|
207
|
+
|
|
208
|
+
url = UrlUtil().make_url(
|
|
209
|
+
self.base_url,
|
|
210
|
+
['btc', 'ABTP0004', 'ApiAttime001', 'ImportarEcommerce'],
|
|
211
|
+
)
|
|
212
|
+
res = self.call_request(
|
|
213
|
+
http_method=HTTPMethod.POST,
|
|
214
|
+
request_url=url,
|
|
215
|
+
payload=payload,
|
|
216
|
+
)
|
|
217
|
+
res = jsonpickle.decode(res)
|
|
218
|
+
return _parse_attime001_object_response(
|
|
219
|
+
res,
|
|
220
|
+
from_dict=ApiAttime001ImportarEcommerceDTO.from_dict,
|
|
221
|
+
endpoint_label='ImportarEcommerce',
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def relacao_contrato(
|
|
225
|
+
self,
|
|
226
|
+
*,
|
|
227
|
+
cpf: str,
|
|
228
|
+
data_inicio_vigencia: str | None = None,
|
|
229
|
+
ramo: str | None = None,
|
|
230
|
+
) -> list[ApiAttime001RelacaoContratoCpfItemDTO]:
|
|
231
|
+
"""GET /btc/ABTP0004/ApiAttime001/RelacaoContrato.
|
|
232
|
+
|
|
233
|
+
Consulta contratos vinculados a um CPF (conferência pós-emissão).
|
|
234
|
+
"""
|
|
235
|
+
logging.info('querying RelacaoContrato via ApiAttime001...')
|
|
236
|
+
if cpf in (None, ''):
|
|
237
|
+
raise ValueError('cpf é obrigatório')
|
|
238
|
+
|
|
239
|
+
params = {'Cpf': cpf}
|
|
240
|
+
if data_inicio_vigencia not in (None, ''):
|
|
241
|
+
params['DataInicioVigencia'] = data_inicio_vigencia
|
|
242
|
+
if ramo not in (None, ''):
|
|
243
|
+
params['Ramo'] = ramo
|
|
244
|
+
|
|
245
|
+
url = UrlUtil().make_url(
|
|
246
|
+
self.base_url,
|
|
247
|
+
['btc', 'ABTP0004', 'ApiAttime001', 'RelacaoContrato'],
|
|
248
|
+
)
|
|
249
|
+
res = self.call_request(
|
|
250
|
+
http_method=HTTPMethod.GET,
|
|
251
|
+
request_url=url,
|
|
252
|
+
params=params,
|
|
253
|
+
)
|
|
254
|
+
res = jsonpickle.decode(res)
|
|
255
|
+
return _parse_attime001_list_response(
|
|
256
|
+
res,
|
|
257
|
+
from_dict=ApiAttime001RelacaoContratoCpfItemDTO.from_dict,
|
|
258
|
+
endpoint_label='RelacaoContrato',
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def relacao_itens(
|
|
262
|
+
self,
|
|
263
|
+
*,
|
|
264
|
+
numero_contrato: str,
|
|
265
|
+
data_ref: str | None = None,
|
|
266
|
+
) -> list[ApiAttime001RelacaoItemDTO]:
|
|
267
|
+
"""GET /btc/ABTP0004/ApiAttime001/RelacaoItens.
|
|
268
|
+
|
|
269
|
+
Consulta itens de um contrato (conferência pós-emissão).
|
|
270
|
+
"""
|
|
271
|
+
logging.info('querying RelacaoItens via ApiAttime001...')
|
|
272
|
+
if numero_contrato in (None, ''):
|
|
273
|
+
raise ValueError('numero_contrato é obrigatório')
|
|
274
|
+
|
|
275
|
+
params = {'NumeroContrato': numero_contrato}
|
|
276
|
+
if data_ref not in (None, ''):
|
|
277
|
+
params['DataRef'] = data_ref
|
|
278
|
+
|
|
279
|
+
url = UrlUtil().make_url(
|
|
280
|
+
self.base_url,
|
|
281
|
+
['btc', 'ABTP0004', 'ApiAttime001', 'RelacaoItens'],
|
|
282
|
+
)
|
|
283
|
+
res = self.call_request(
|
|
284
|
+
http_method=HTTPMethod.GET,
|
|
285
|
+
request_url=url,
|
|
286
|
+
params=params,
|
|
287
|
+
)
|
|
288
|
+
res = jsonpickle.decode(res)
|
|
289
|
+
return _parse_attime001_list_response(
|
|
290
|
+
res,
|
|
291
|
+
from_dict=ApiAttime001RelacaoItemDTO.from_dict,
|
|
292
|
+
endpoint_label='RelacaoItens',
|
|
293
|
+
)
|