bb_api 0.5.3__tar.gz → 0.5.5__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.
- {bb_api-0.5.3 → bb_api-0.5.5}/PKG-INFO +1 -1
- {bb_api-0.5.3 → bb_api-0.5.5}/bb_api/__init__.py +3 -1
- {bb_api-0.5.3 → bb_api-0.5.5}/bb_api/accountability.py +85 -53
- {bb_api-0.5.3 → bb_api-0.5.5}/bb_api/common.py +19 -1
- {bb_api-0.5.3 → bb_api-0.5.5}/bb_api.egg-info/PKG-INFO +1 -1
- {bb_api-0.5.3 → bb_api-0.5.5}/pyproject.toml +1 -1
- {bb_api-0.5.3 → bb_api-0.5.5}/LICENSE +0 -0
- {bb_api-0.5.3 → bb_api-0.5.5}/README.md +0 -0
- {bb_api-0.5.3 → bb_api-0.5.5}/bb_api/gestao_agil.py +0 -0
- {bb_api-0.5.3 → bb_api-0.5.5}/bb_api/sia.py +0 -0
- {bb_api-0.5.3 → bb_api-0.5.5}/bb_api.egg-info/SOURCES.txt +0 -0
- {bb_api-0.5.3 → bb_api-0.5.5}/bb_api.egg-info/dependency_links.txt +0 -0
- {bb_api-0.5.3 → bb_api-0.5.5}/bb_api.egg-info/requires.txt +0 -0
- {bb_api-0.5.3 → bb_api-0.5.5}/bb_api.egg-info/top_level.txt +0 -0
- {bb_api-0.5.3 → bb_api-0.5.5}/setup.cfg +0 -0
|
@@ -4,7 +4,7 @@ from importlib.metadata import PackageNotFoundError, version
|
|
|
4
4
|
try:
|
|
5
5
|
__version__ = version("bb_api")
|
|
6
6
|
except PackageNotFoundError:
|
|
7
|
-
__version__ = "0.5.
|
|
7
|
+
__version__ = "0.5.5"
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
from .common import Ambiente
|
|
@@ -14,6 +14,7 @@ from .accountability import (
|
|
|
14
14
|
AccountabilityV3ControleAPI,
|
|
15
15
|
ExtratoError,
|
|
16
16
|
IntervaloLongoDemaisError,
|
|
17
|
+
RespostaError,
|
|
17
18
|
SemLancamentosError,
|
|
18
19
|
)
|
|
19
20
|
from .sia import (
|
|
@@ -36,6 +37,7 @@ __all__ = [
|
|
|
36
37
|
"CredenciaisNecessariasError",
|
|
37
38
|
"ExtratoError",
|
|
38
39
|
"IntervaloLongoDemaisError",
|
|
40
|
+
"RespostaError",
|
|
39
41
|
"SemLancamentosError",
|
|
40
42
|
"FalhaDownload",
|
|
41
43
|
"ResultadoDownloads",
|
|
@@ -22,22 +22,73 @@ _ERRO_INTERVALO_LONGO = "maior do que 31 dias"
|
|
|
22
22
|
_STATUS_SEM_MOVIMENTO = 404
|
|
23
23
|
_SEM_MOVIMENTO_PADRAO = "não há movimento no período consultado"
|
|
24
24
|
|
|
25
|
+
_LIMITE_DO_CORPO = 500
|
|
25
26
|
|
|
26
|
-
class ExtratoError(Exception):
|
|
27
|
-
"""Erro devolvido pela API ao consultar o extrato do órgão repassador.
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
class RespostaError(Exception):
|
|
29
|
+
"""Resposta de erro devolvida pela API do Banco do Brasil.
|
|
30
|
+
|
|
31
|
+
Carrega o status HTTP e a mensagem originais junto da ação que falhou. Sem
|
|
32
|
+
eles, um ``404`` de "não há o que devolver", um ``403`` de escopo que falta
|
|
33
|
+
e um ``400`` de parâmetro inválido chegam ao chamador iguais.
|
|
34
|
+
|
|
35
|
+
Quando recebe a ``resposta``, guarda também o corpo cru e o
|
|
36
|
+
``x-request-id``: o corpo traz o ``codigo`` e a ``ocorrencia`` do BB, que a
|
|
37
|
+
mensagem sozinha perde, e é com eles que o suporte do banco rastreia a falha.
|
|
31
38
|
"""
|
|
32
39
|
|
|
33
40
|
status_code: int
|
|
34
41
|
mensagem: str
|
|
42
|
+
corpo: str
|
|
43
|
+
request_id: str | None
|
|
35
44
|
|
|
36
|
-
def __init__(
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
acao: str,
|
|
48
|
+
status_code: int,
|
|
49
|
+
mensagem: str,
|
|
50
|
+
*,
|
|
51
|
+
resposta: requests.Response | None = None,
|
|
52
|
+
) -> None:
|
|
39
53
|
self.status_code = status_code
|
|
40
54
|
self.mensagem = mensagem
|
|
55
|
+
self.corpo = ""
|
|
56
|
+
self.request_id = None
|
|
57
|
+
if resposta is not None:
|
|
58
|
+
self.corpo = resposta.text.strip()
|
|
59
|
+
self.request_id = resposta.headers.get("x-request-id")
|
|
60
|
+
super().__init__(_descreve(acao, status_code, mensagem, self.corpo, self.request_id))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _descreve(
|
|
64
|
+
acao: str, status_code: int, mensagem: str, corpo: str, request_id: str | None
|
|
65
|
+
) -> str:
|
|
66
|
+
partes = [f"Não foi possível {acao} (HTTP {status_code}): {mensagem}"]
|
|
67
|
+
if corpo and corpo != mensagem:
|
|
68
|
+
encurtado = corpo[:_LIMITE_DO_CORPO] + ("…" if len(corpo) > _LIMITE_DO_CORPO else "")
|
|
69
|
+
partes.append(f"resposta do BB: {encurtado}")
|
|
70
|
+
if request_id:
|
|
71
|
+
partes.append(f"x-request-id: {request_id}")
|
|
72
|
+
return " | ".join(partes)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ExtratoError(RespostaError):
|
|
76
|
+
"""Erro devolvido pela API ao consultar o extrato do órgão repassador.
|
|
77
|
+
|
|
78
|
+
Carrega o status HTTP e a mensagem originais do Banco do Brasil, porque a
|
|
79
|
+
API usa ``400`` tanto para falha de verdade quanto para situações normais.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
status_code: int,
|
|
85
|
+
mensagem: str,
|
|
86
|
+
*,
|
|
87
|
+
resposta: requests.Response | None = None,
|
|
88
|
+
) -> None:
|
|
89
|
+
super().__init__(
|
|
90
|
+
"reaver o extrato do órgão repassador", status_code, mensagem, resposta=resposta
|
|
91
|
+
)
|
|
41
92
|
|
|
42
93
|
|
|
43
94
|
class SemLancamentosError(ExtratoError):
|
|
@@ -99,14 +150,19 @@ def _mensagem_da_lista_de_erros(campos: dict[str, object]) -> str:
|
|
|
99
150
|
return ""
|
|
100
151
|
|
|
101
152
|
|
|
153
|
+
def _erro(acao: str, res: requests.Response) -> RespostaError:
|
|
154
|
+
"""Erro de uma resposta não-200, com a ação que falhou e o que o BB disse."""
|
|
155
|
+
return RespostaError(acao, res.status_code, _mensagem_de_erro(res), resposta=res)
|
|
156
|
+
|
|
157
|
+
|
|
102
158
|
def _erro_extrato(res: requests.Response) -> ExtratoError:
|
|
103
159
|
mensagem = _mensagem_de_erro(res)
|
|
104
160
|
normalizada = mensagem.casefold()
|
|
105
161
|
if _ERRO_SEM_LANCAMENTOS in normalizada:
|
|
106
|
-
return SemLancamentosError(res.status_code, mensagem)
|
|
162
|
+
return SemLancamentosError(res.status_code, mensagem, resposta=res)
|
|
107
163
|
if _ERRO_INTERVALO_LONGO in normalizada:
|
|
108
|
-
return IntervaloLongoDemaisError(res.status_code, mensagem)
|
|
109
|
-
return ExtratoError(res.status_code, mensagem)
|
|
164
|
+
return IntervaloLongoDemaisError(res.status_code, mensagem, resposta=res)
|
|
165
|
+
return ExtratoError(res.status_code, mensagem, resposta=res)
|
|
110
166
|
|
|
111
167
|
|
|
112
168
|
def _erro_extrato_aplicacao(res: requests.Response) -> ExtratoError:
|
|
@@ -118,7 +174,7 @@ def _erro_extrato_aplicacao(res: requests.Response) -> ExtratoError:
|
|
|
118
174
|
"""
|
|
119
175
|
if res.status_code == _STATUS_SEM_MOVIMENTO:
|
|
120
176
|
mensagem = _mensagem_de_erro(res) or _SEM_MOVIMENTO_PADRAO
|
|
121
|
-
return SemLancamentosError(res.status_code, mensagem)
|
|
177
|
+
return SemLancamentosError(res.status_code, mensagem, resposta=res)
|
|
122
178
|
return _erro_extrato(res)
|
|
123
179
|
|
|
124
180
|
|
|
@@ -235,7 +291,7 @@ class _AccountabilityV3BaseAPI:
|
|
|
235
291
|
)
|
|
236
292
|
|
|
237
293
|
if res.status_code != 200:
|
|
238
|
-
raise
|
|
294
|
+
raise _erro("adquirir as novas credenciais de acesso", res)
|
|
239
295
|
|
|
240
296
|
data = common.parse_json_object(res)
|
|
241
297
|
self._access_token = cast("str", data["access_token"])
|
|
@@ -266,9 +322,7 @@ class _AccountabilityV3BaseAPI:
|
|
|
266
322
|
)
|
|
267
323
|
|
|
268
324
|
if res.status_code != 200:
|
|
269
|
-
raise
|
|
270
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
271
|
-
)
|
|
325
|
+
raise _erro("listar as agências próximas", res)
|
|
272
326
|
|
|
273
327
|
res = common.parse_json_object(res)
|
|
274
328
|
return common.handle_results(
|
|
@@ -411,7 +465,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
411
465
|
)
|
|
412
466
|
|
|
413
467
|
if res.status_code != 200:
|
|
414
|
-
raise
|
|
468
|
+
raise _erro("reaver o documento de despesa do programa de governo", res)
|
|
415
469
|
|
|
416
470
|
res = common.parse_json_object(res)
|
|
417
471
|
|
|
@@ -542,7 +596,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
542
596
|
)
|
|
543
597
|
|
|
544
598
|
if res.status_code != 200:
|
|
545
|
-
raise
|
|
599
|
+
raise _erro("reaver o documento de despesa da prestação de contas", res)
|
|
546
600
|
|
|
547
601
|
res = common.parse_json_object(res)
|
|
548
602
|
|
|
@@ -674,7 +728,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
674
728
|
)
|
|
675
729
|
|
|
676
730
|
if res.status_code != 200:
|
|
677
|
-
raise
|
|
731
|
+
raise _erro("reaver o extrato de subtransações do programa de governo", res)
|
|
678
732
|
|
|
679
733
|
res = common.parse_json_object(res)
|
|
680
734
|
|
|
@@ -974,9 +1028,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
974
1028
|
)
|
|
975
1029
|
|
|
976
1030
|
if res.status_code != 200:
|
|
977
|
-
raise
|
|
978
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
979
|
-
)
|
|
1031
|
+
raise _erro("listar os lançamentos atualizados do programa de governo", res)
|
|
980
1032
|
|
|
981
1033
|
res = common.parse_json_object(res)
|
|
982
1034
|
return common.handle_results(
|
|
@@ -1017,9 +1069,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1017
1069
|
)
|
|
1018
1070
|
|
|
1019
1071
|
if res.status_code != 200:
|
|
1020
|
-
raise
|
|
1021
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1022
|
-
)
|
|
1072
|
+
raise _erro("listar os sublançamentos atualizados do programa de governo", res)
|
|
1023
1073
|
|
|
1024
1074
|
res = common.parse_json_object(res)
|
|
1025
1075
|
return common.handle_results(
|
|
@@ -1053,9 +1103,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1053
1103
|
)
|
|
1054
1104
|
|
|
1055
1105
|
if res.status_code != 200:
|
|
1056
|
-
raise
|
|
1057
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1058
|
-
)
|
|
1106
|
+
raise _erro("listar as categorias do programa de governo", res)
|
|
1059
1107
|
|
|
1060
1108
|
res = common.parse_json_object(res)
|
|
1061
1109
|
return common.handle_results(
|
|
@@ -1086,9 +1134,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1086
1134
|
)
|
|
1087
1135
|
|
|
1088
1136
|
if res.status_code != 200:
|
|
1089
|
-
raise
|
|
1090
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1091
|
-
)
|
|
1137
|
+
raise _erro("reaver o saldo das aplicações financeiras", res)
|
|
1092
1138
|
|
|
1093
1139
|
res = common.parse_json_object(res)
|
|
1094
1140
|
return common.handle_results(
|
|
@@ -1125,9 +1171,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1125
1171
|
)
|
|
1126
1172
|
|
|
1127
1173
|
if res.status_code != 200:
|
|
1128
|
-
raise
|
|
1129
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1130
|
-
)
|
|
1174
|
+
raise _erro("reaver o saldo da conta corrente", res)
|
|
1131
1175
|
|
|
1132
1176
|
res = common.parse_json_object(res)
|
|
1133
1177
|
|
|
@@ -1173,9 +1217,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1173
1217
|
)
|
|
1174
1218
|
|
|
1175
1219
|
if res.status_code not in [200, 201]:
|
|
1176
|
-
raise
|
|
1177
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1178
|
-
)
|
|
1220
|
+
raise _erro("categorizar a despesa do lançamento a crédito", res)
|
|
1179
1221
|
|
|
1180
1222
|
res = common.parse_json_object(res)
|
|
1181
1223
|
return common.handle_results(
|
|
@@ -1222,9 +1264,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1222
1264
|
)
|
|
1223
1265
|
|
|
1224
1266
|
if res.status_code not in [200, 201]:
|
|
1225
|
-
raise
|
|
1226
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1227
|
-
)
|
|
1267
|
+
raise _erro("identificar o lançamento a crédito", res)
|
|
1228
1268
|
|
|
1229
1269
|
res = common.parse_json_object(res)
|
|
1230
1270
|
return common.handle_results(
|
|
@@ -1255,9 +1295,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1255
1295
|
)
|
|
1256
1296
|
|
|
1257
1297
|
if res.status_code not in [200, 201]:
|
|
1258
|
-
raise
|
|
1259
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1260
|
-
)
|
|
1298
|
+
raise _erro("excluir a identificação do lançamento a crédito", res)
|
|
1261
1299
|
|
|
1262
1300
|
res = common.parse_json_object(res)
|
|
1263
1301
|
return common.handle_results(
|
|
@@ -1286,9 +1324,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1286
1324
|
)
|
|
1287
1325
|
|
|
1288
1326
|
if res.status_code != 200:
|
|
1289
|
-
raise
|
|
1290
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1291
|
-
)
|
|
1327
|
+
raise _erro("listar as identificações dos lançamentos a débito", res)
|
|
1292
1328
|
|
|
1293
1329
|
res = common.parse_json_object(res)
|
|
1294
1330
|
return common.handle_results(
|
|
@@ -1431,7 +1467,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1431
1467
|
)
|
|
1432
1468
|
|
|
1433
1469
|
if res.status_code != 200:
|
|
1434
|
-
raise
|
|
1470
|
+
raise _erro("reaver o documento de despesa do programa de governo", res)
|
|
1435
1471
|
|
|
1436
1472
|
res = common.parse_json_object(res)
|
|
1437
1473
|
|
|
@@ -1562,7 +1598,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1562
1598
|
)
|
|
1563
1599
|
|
|
1564
1600
|
if res.status_code != 200:
|
|
1565
|
-
raise
|
|
1601
|
+
raise _erro("reaver o documento de despesa da prestação de contas", res)
|
|
1566
1602
|
|
|
1567
1603
|
res = common.parse_json_object(res)
|
|
1568
1604
|
|
|
@@ -1688,9 +1724,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1688
1724
|
)
|
|
1689
1725
|
|
|
1690
1726
|
if res.status_code != 200:
|
|
1691
|
-
raise
|
|
1692
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1693
|
-
)
|
|
1727
|
+
raise _erro("reaver o extrato de subtransações do programa de governo", res)
|
|
1694
1728
|
|
|
1695
1729
|
res = common.parse_json_object(res)
|
|
1696
1730
|
return common.handle_results(
|
|
@@ -1978,9 +2012,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1978
2012
|
)
|
|
1979
2013
|
|
|
1980
2014
|
if res.status_code != 200:
|
|
1981
|
-
raise
|
|
1982
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1983
|
-
)
|
|
2015
|
+
raise _erro("listar as contas correntes do órgão de controle", res)
|
|
1984
2016
|
|
|
1985
2017
|
res = common.parse_json_object(res)
|
|
1986
2018
|
return common.handle_results(
|
|
@@ -60,9 +60,27 @@ def handle_numeric_string_with_symbols(v: str) -> str:
|
|
|
60
60
|
return re.sub(r"\D", "", v)
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
def _data_de_texto(texto: str) -> datetime:
|
|
64
|
+
"""Interpreta uma data em ISO ou em dd/mm/aaaa.
|
|
65
|
+
|
|
66
|
+
A API devolve os dois formatos, e o valor de uma resposta costuma virar
|
|
67
|
+
parâmetro da consulta seguinte: o ``bookingDate`` do extrato chega em
|
|
68
|
+
dd/mm/aaaa e é o que identifica o documento de despesa daquela transação.
|
|
69
|
+
"""
|
|
70
|
+
formatos = ("%Y-%m-%d", "%d/%m/%Y")
|
|
71
|
+
for formato in formatos:
|
|
72
|
+
try:
|
|
73
|
+
return datetime.strptime(texto, formato)
|
|
74
|
+
except ValueError:
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
aceitos = " ou ".join(formatos)
|
|
78
|
+
raise ValueError(f"A data '{texto}' não está em {aceitos}.")
|
|
79
|
+
|
|
80
|
+
|
|
63
81
|
def handle_dates(v: DateLike) -> str:
|
|
64
82
|
if isinstance(v, str):
|
|
65
|
-
dt =
|
|
83
|
+
dt = _data_de_texto(v)
|
|
66
84
|
elif isinstance(v, datetime):
|
|
67
85
|
dt = v
|
|
68
86
|
else:
|
|
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
|