bb_api 0.5.2__tar.gz → 0.5.4__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.2 → bb_api-0.5.4}/PKG-INFO +1 -1
- {bb_api-0.5.2 → bb_api-0.5.4}/bb_api/__init__.py +3 -1
- {bb_api-0.5.2 → bb_api-0.5.4}/bb_api/accountability.py +45 -49
- {bb_api-0.5.2 → bb_api-0.5.4}/bb_api/common.py +34 -2
- {bb_api-0.5.2 → bb_api-0.5.4}/bb_api.egg-info/PKG-INFO +1 -1
- {bb_api-0.5.2 → bb_api-0.5.4}/pyproject.toml +1 -1
- {bb_api-0.5.2 → bb_api-0.5.4}/LICENSE +0 -0
- {bb_api-0.5.2 → bb_api-0.5.4}/README.md +0 -0
- {bb_api-0.5.2 → bb_api-0.5.4}/bb_api/gestao_agil.py +0 -0
- {bb_api-0.5.2 → bb_api-0.5.4}/bb_api/sia.py +0 -0
- {bb_api-0.5.2 → bb_api-0.5.4}/bb_api.egg-info/SOURCES.txt +0 -0
- {bb_api-0.5.2 → bb_api-0.5.4}/bb_api.egg-info/dependency_links.txt +0 -0
- {bb_api-0.5.2 → bb_api-0.5.4}/bb_api.egg-info/requires.txt +0 -0
- {bb_api-0.5.2 → bb_api-0.5.4}/bb_api.egg-info/top_level.txt +0 -0
- {bb_api-0.5.2 → bb_api-0.5.4}/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.4"
|
|
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",
|
|
@@ -23,23 +23,34 @@ _STATUS_SEM_MOVIMENTO = 404
|
|
|
23
23
|
_SEM_MOVIMENTO_PADRAO = "não há movimento no período consultado"
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
class
|
|
27
|
-
"""
|
|
26
|
+
class RespostaError(Exception):
|
|
27
|
+
"""Resposta de erro devolvida pela API do Banco do Brasil.
|
|
28
28
|
|
|
29
|
-
Carrega o status HTTP e a mensagem originais
|
|
30
|
-
|
|
29
|
+
Carrega o status HTTP e a mensagem originais junto da ação que falhou. Sem
|
|
30
|
+
eles, um ``404`` de "não há o que devolver", um ``403`` de escopo que falta
|
|
31
|
+
e um ``400`` de parâmetro inválido chegam ao chamador iguais.
|
|
31
32
|
"""
|
|
32
33
|
|
|
33
34
|
status_code: int
|
|
34
35
|
mensagem: str
|
|
35
36
|
|
|
36
|
-
def __init__(self, status_code: int, mensagem: str) -> None:
|
|
37
|
-
|
|
38
|
-
super().__init__(f"Não foi possível reaver o extrato do órgão repassador {detalhe}")
|
|
37
|
+
def __init__(self, acao: str, status_code: int, mensagem: str) -> None:
|
|
38
|
+
super().__init__(f"Não foi possível {acao} (HTTP {status_code}): {mensagem}")
|
|
39
39
|
self.status_code = status_code
|
|
40
40
|
self.mensagem = mensagem
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
class ExtratoError(RespostaError):
|
|
44
|
+
"""Erro devolvido pela API ao consultar o extrato do órgão repassador.
|
|
45
|
+
|
|
46
|
+
Carrega o status HTTP e a mensagem originais do Banco do Brasil, porque a
|
|
47
|
+
API usa ``400`` tanto para falha de verdade quanto para situações normais.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, status_code: int, mensagem: str) -> None:
|
|
51
|
+
super().__init__("reaver o extrato do órgão repassador", status_code, mensagem)
|
|
52
|
+
|
|
53
|
+
|
|
43
54
|
class SemLancamentosError(ExtratoError):
|
|
44
55
|
"""A conta não tem lançamento nenhum no período consultado.
|
|
45
56
|
|
|
@@ -99,6 +110,11 @@ def _mensagem_da_lista_de_erros(campos: dict[str, object]) -> str:
|
|
|
99
110
|
return ""
|
|
100
111
|
|
|
101
112
|
|
|
113
|
+
def _erro(acao: str, res: requests.Response) -> RespostaError:
|
|
114
|
+
"""Erro de uma resposta não-200, com a ação que falhou e o que o BB disse."""
|
|
115
|
+
return RespostaError(acao, res.status_code, _mensagem_de_erro(res))
|
|
116
|
+
|
|
117
|
+
|
|
102
118
|
def _erro_extrato(res: requests.Response) -> ExtratoError:
|
|
103
119
|
mensagem = _mensagem_de_erro(res)
|
|
104
120
|
normalizada = mensagem.casefold()
|
|
@@ -235,7 +251,7 @@ class _AccountabilityV3BaseAPI:
|
|
|
235
251
|
)
|
|
236
252
|
|
|
237
253
|
if res.status_code != 200:
|
|
238
|
-
raise
|
|
254
|
+
raise _erro("adquirir as novas credenciais de acesso", res)
|
|
239
255
|
|
|
240
256
|
data = common.parse_json_object(res)
|
|
241
257
|
self._access_token = cast("str", data["access_token"])
|
|
@@ -266,9 +282,7 @@ class _AccountabilityV3BaseAPI:
|
|
|
266
282
|
)
|
|
267
283
|
|
|
268
284
|
if res.status_code != 200:
|
|
269
|
-
raise
|
|
270
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
271
|
-
)
|
|
285
|
+
raise _erro("listar as agências próximas", res)
|
|
272
286
|
|
|
273
287
|
res = common.parse_json_object(res)
|
|
274
288
|
return common.handle_results(
|
|
@@ -411,7 +425,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
411
425
|
)
|
|
412
426
|
|
|
413
427
|
if res.status_code != 200:
|
|
414
|
-
raise
|
|
428
|
+
raise _erro("reaver o documento de despesa do programa de governo", res)
|
|
415
429
|
|
|
416
430
|
res = common.parse_json_object(res)
|
|
417
431
|
|
|
@@ -542,7 +556,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
542
556
|
)
|
|
543
557
|
|
|
544
558
|
if res.status_code != 200:
|
|
545
|
-
raise
|
|
559
|
+
raise _erro("reaver o documento de despesa da prestação de contas", res)
|
|
546
560
|
|
|
547
561
|
res = common.parse_json_object(res)
|
|
548
562
|
|
|
@@ -674,7 +688,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
674
688
|
)
|
|
675
689
|
|
|
676
690
|
if res.status_code != 200:
|
|
677
|
-
raise
|
|
691
|
+
raise _erro("reaver o extrato de subtransações do programa de governo", res)
|
|
678
692
|
|
|
679
693
|
res = common.parse_json_object(res)
|
|
680
694
|
|
|
@@ -761,6 +775,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
761
775
|
df = common.handle_results(
|
|
762
776
|
extrato,
|
|
763
777
|
main_list="listaLancamentosExtrato",
|
|
778
|
+
keep_header_when_empty=True,
|
|
764
779
|
insertables=[
|
|
765
780
|
"numeroAgenciaRecebedora",
|
|
766
781
|
"digitoVerificadoraContaRecebedora",
|
|
@@ -913,6 +928,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
913
928
|
return common.handle_results(
|
|
914
929
|
res,
|
|
915
930
|
main_list="listaLancamentos",
|
|
931
|
+
keep_header_when_empty=True,
|
|
916
932
|
insertables=[
|
|
917
933
|
"codigoProgramaGoverno",
|
|
918
934
|
"nomeProgramaGoverno",
|
|
@@ -972,9 +988,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
972
988
|
)
|
|
973
989
|
|
|
974
990
|
if res.status_code != 200:
|
|
975
|
-
raise
|
|
976
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
977
|
-
)
|
|
991
|
+
raise _erro("listar os lançamentos atualizados do programa de governo", res)
|
|
978
992
|
|
|
979
993
|
res = common.parse_json_object(res)
|
|
980
994
|
return common.handle_results(
|
|
@@ -1015,9 +1029,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1015
1029
|
)
|
|
1016
1030
|
|
|
1017
1031
|
if res.status_code != 200:
|
|
1018
|
-
raise
|
|
1019
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1020
|
-
)
|
|
1032
|
+
raise _erro("listar os sublançamentos atualizados do programa de governo", res)
|
|
1021
1033
|
|
|
1022
1034
|
res = common.parse_json_object(res)
|
|
1023
1035
|
return common.handle_results(
|
|
@@ -1051,9 +1063,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1051
1063
|
)
|
|
1052
1064
|
|
|
1053
1065
|
if res.status_code != 200:
|
|
1054
|
-
raise
|
|
1055
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1056
|
-
)
|
|
1066
|
+
raise _erro("listar as categorias do programa de governo", res)
|
|
1057
1067
|
|
|
1058
1068
|
res = common.parse_json_object(res)
|
|
1059
1069
|
return common.handle_results(
|
|
@@ -1084,9 +1094,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1084
1094
|
)
|
|
1085
1095
|
|
|
1086
1096
|
if res.status_code != 200:
|
|
1087
|
-
raise
|
|
1088
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1089
|
-
)
|
|
1097
|
+
raise _erro("reaver o saldo das aplicações financeiras", res)
|
|
1090
1098
|
|
|
1091
1099
|
res = common.parse_json_object(res)
|
|
1092
1100
|
return common.handle_results(
|
|
@@ -1123,9 +1131,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1123
1131
|
)
|
|
1124
1132
|
|
|
1125
1133
|
if res.status_code != 200:
|
|
1126
|
-
raise
|
|
1127
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1128
|
-
)
|
|
1134
|
+
raise _erro("reaver o saldo da conta corrente", res)
|
|
1129
1135
|
|
|
1130
1136
|
res = common.parse_json_object(res)
|
|
1131
1137
|
|
|
@@ -1171,9 +1177,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1171
1177
|
)
|
|
1172
1178
|
|
|
1173
1179
|
if res.status_code not in [200, 201]:
|
|
1174
|
-
raise
|
|
1175
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1176
|
-
)
|
|
1180
|
+
raise _erro("categorizar a despesa do lançamento a crédito", res)
|
|
1177
1181
|
|
|
1178
1182
|
res = common.parse_json_object(res)
|
|
1179
1183
|
return common.handle_results(
|
|
@@ -1220,9 +1224,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1220
1224
|
)
|
|
1221
1225
|
|
|
1222
1226
|
if res.status_code not in [200, 201]:
|
|
1223
|
-
raise
|
|
1224
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1225
|
-
)
|
|
1227
|
+
raise _erro("identificar o lançamento a crédito", res)
|
|
1226
1228
|
|
|
1227
1229
|
res = common.parse_json_object(res)
|
|
1228
1230
|
return common.handle_results(
|
|
@@ -1253,9 +1255,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1253
1255
|
)
|
|
1254
1256
|
|
|
1255
1257
|
if res.status_code not in [200, 201]:
|
|
1256
|
-
raise
|
|
1257
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1258
|
-
)
|
|
1258
|
+
raise _erro("excluir a identificação do lançamento a crédito", res)
|
|
1259
1259
|
|
|
1260
1260
|
res = common.parse_json_object(res)
|
|
1261
1261
|
return common.handle_results(
|
|
@@ -1284,9 +1284,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
1284
1284
|
)
|
|
1285
1285
|
|
|
1286
1286
|
if res.status_code != 200:
|
|
1287
|
-
raise
|
|
1288
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1289
|
-
)
|
|
1287
|
+
raise _erro("listar as identificações dos lançamentos a débito", res)
|
|
1290
1288
|
|
|
1291
1289
|
res = common.parse_json_object(res)
|
|
1292
1290
|
return common.handle_results(
|
|
@@ -1429,7 +1427,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1429
1427
|
)
|
|
1430
1428
|
|
|
1431
1429
|
if res.status_code != 200:
|
|
1432
|
-
raise
|
|
1430
|
+
raise _erro("reaver o documento de despesa do programa de governo", res)
|
|
1433
1431
|
|
|
1434
1432
|
res = common.parse_json_object(res)
|
|
1435
1433
|
|
|
@@ -1560,7 +1558,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1560
1558
|
)
|
|
1561
1559
|
|
|
1562
1560
|
if res.status_code != 200:
|
|
1563
|
-
raise
|
|
1561
|
+
raise _erro("reaver o documento de despesa da prestação de contas", res)
|
|
1564
1562
|
|
|
1565
1563
|
res = common.parse_json_object(res)
|
|
1566
1564
|
|
|
@@ -1686,9 +1684,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1686
1684
|
)
|
|
1687
1685
|
|
|
1688
1686
|
if res.status_code != 200:
|
|
1689
|
-
raise
|
|
1690
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1691
|
-
)
|
|
1687
|
+
raise _erro("reaver o extrato de subtransações do programa de governo", res)
|
|
1692
1688
|
|
|
1693
1689
|
res = common.parse_json_object(res)
|
|
1694
1690
|
return common.handle_results(
|
|
@@ -1773,6 +1769,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1773
1769
|
df = common.handle_results(
|
|
1774
1770
|
extrato,
|
|
1775
1771
|
main_list="listaLancamentosExtrato",
|
|
1772
|
+
keep_header_when_empty=True,
|
|
1776
1773
|
insertables=[
|
|
1777
1774
|
"numeroAgenciaRecebedora",
|
|
1778
1775
|
"digitoVerificadoraContaRecebedora",
|
|
@@ -1922,6 +1919,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1922
1919
|
return common.handle_results(
|
|
1923
1920
|
res,
|
|
1924
1921
|
main_list="listaLancamentos",
|
|
1922
|
+
keep_header_when_empty=True,
|
|
1925
1923
|
insertables=[
|
|
1926
1924
|
"codigoProgramaGoverno",
|
|
1927
1925
|
"nomeProgramaGoverno",
|
|
@@ -1974,9 +1972,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1974
1972
|
)
|
|
1975
1973
|
|
|
1976
1974
|
if res.status_code != 200:
|
|
1977
|
-
raise
|
|
1978
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1979
|
-
)
|
|
1975
|
+
raise _erro("listar as contas correntes do órgão de controle", res)
|
|
1980
1976
|
|
|
1981
1977
|
res = common.parse_json_object(res)
|
|
1982
1978
|
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:
|
|
@@ -81,11 +99,25 @@ def handle_results(
|
|
|
81
99
|
insertables: Sequence[str] | None = None,
|
|
82
100
|
explodeables: Sequence[str] | None = None,
|
|
83
101
|
rename_dict: Mapping[str, str] | None = None,
|
|
102
|
+
*,
|
|
103
|
+
keep_header_when_empty: bool = False,
|
|
84
104
|
) -> pd.DataFrame:
|
|
105
|
+
"""Monta o DataFrame da resposta: uma linha por item de ``main_list``.
|
|
106
|
+
|
|
107
|
+
Os campos de ``insertables`` ficam no nível de cima da resposta, fora da
|
|
108
|
+
lista, e são repetidos em todas as linhas.
|
|
109
|
+
|
|
110
|
+
Com ``keep_header_when_empty``, uma lista vazia devolve uma linha só com
|
|
111
|
+
esses campos, em vez de um DataFrame sem linha nenhuma. É o que preserva o
|
|
112
|
+
cabeçalho das respostas em que ele carrega o dado principal, como o
|
|
113
|
+
rendimento do mês nos extratos de aplicação.
|
|
114
|
+
"""
|
|
85
115
|
record = cast("Mapping[str, object]", data)
|
|
86
116
|
|
|
87
117
|
if main_list is not None:
|
|
88
|
-
|
|
118
|
+
itens = cast("list[dict[Hashable, object]]", record.get(main_list) or [])
|
|
119
|
+
so_cabecalho = not itens and keep_header_when_empty
|
|
120
|
+
df = pd.DataFrame(index=[0]) if so_cabecalho else pd.DataFrame(itens)
|
|
89
121
|
else:
|
|
90
122
|
df = pd.DataFrame([dict(record)])
|
|
91
123
|
|
|
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
|