bb_api 0.5.1__tar.gz → 0.5.3__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.1 → bb_api-0.5.3}/PKG-INFO +1 -1
- {bb_api-0.5.1 → bb_api-0.5.3}/bb_api/__init__.py +1 -1
- {bb_api-0.5.1 → bb_api-0.5.3}/bb_api/accountability.py +85 -20
- {bb_api-0.5.1 → bb_api-0.5.3}/bb_api/common.py +16 -2
- {bb_api-0.5.1 → bb_api-0.5.3}/bb_api.egg-info/PKG-INFO +1 -1
- {bb_api-0.5.1 → bb_api-0.5.3}/pyproject.toml +1 -1
- {bb_api-0.5.1 → bb_api-0.5.3}/LICENSE +0 -0
- {bb_api-0.5.1 → bb_api-0.5.3}/README.md +0 -0
- {bb_api-0.5.1 → bb_api-0.5.3}/bb_api/gestao_agil.py +0 -0
- {bb_api-0.5.1 → bb_api-0.5.3}/bb_api/sia.py +0 -0
- {bb_api-0.5.1 → bb_api-0.5.3}/bb_api.egg-info/SOURCES.txt +0 -0
- {bb_api-0.5.1 → bb_api-0.5.3}/bb_api.egg-info/dependency_links.txt +0 -0
- {bb_api-0.5.1 → bb_api-0.5.3}/bb_api.egg-info/requires.txt +0 -0
- {bb_api-0.5.1 → bb_api-0.5.3}/bb_api.egg-info/top_level.txt +0 -0
- {bb_api-0.5.1 → bb_api-0.5.3}/setup.cfg +0 -0
|
@@ -19,6 +19,9 @@ MAX_DIAS_EXTRATO = 31
|
|
|
19
19
|
_ERRO_SEM_LANCAMENTOS = "não existem lançamentos"
|
|
20
20
|
_ERRO_INTERVALO_LONGO = "maior do que 31 dias"
|
|
21
21
|
|
|
22
|
+
_STATUS_SEM_MOVIMENTO = 404
|
|
23
|
+
_SEM_MOVIMENTO_PADRAO = "não há movimento no período consultado"
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
class ExtratoError(Exception):
|
|
24
27
|
"""Erro devolvido pela API ao consultar o extrato do órgão repassador.
|
|
@@ -43,6 +46,10 @@ class SemLancamentosError(ExtratoError):
|
|
|
43
46
|
A API responde ``400`` nesse caso, e não ``200`` com uma lista vazia, então
|
|
44
47
|
esse resultado normal chega como erro e precisa ser separado de uma falha —
|
|
45
48
|
senão uma conta parada vira indistinguível de uma consulta quebrada.
|
|
49
|
+
|
|
50
|
+
Nos extratos de aplicação (fundos de investimento e poupança) a mesma
|
|
51
|
+
situação chega como ``404`` de corpo vazio, e cobre tanto o mês sem
|
|
52
|
+
movimento quanto a aplicação que a conta não possui.
|
|
46
53
|
"""
|
|
47
54
|
|
|
48
55
|
|
|
@@ -64,9 +71,34 @@ def _mensagem_de_erro(res: requests.Response) -> str:
|
|
|
64
71
|
valor = campos.get(chave)
|
|
65
72
|
if isinstance(valor, str) and valor:
|
|
66
73
|
return valor
|
|
74
|
+
|
|
75
|
+
detalhada = _mensagem_da_lista_de_erros(campos)
|
|
76
|
+
if detalhada:
|
|
77
|
+
return detalhada
|
|
67
78
|
return res.text.strip()
|
|
68
79
|
|
|
69
80
|
|
|
81
|
+
def _mensagem_da_lista_de_erros(campos: dict[str, object]) -> str:
|
|
82
|
+
"""Extrai a mensagem de um corpo no formato ``{"erros": [{...}]}``.
|
|
83
|
+
|
|
84
|
+
``menssagem``, com dois esses, é como o Banco do Brasil escreve a chave
|
|
85
|
+
nesse formato de erro.
|
|
86
|
+
"""
|
|
87
|
+
for chave in ("erros", "errors"):
|
|
88
|
+
itens = campos.get(chave)
|
|
89
|
+
if not isinstance(itens, list) or not itens:
|
|
90
|
+
continue
|
|
91
|
+
primeiro = cast("list[object]", itens)[0]
|
|
92
|
+
if not isinstance(primeiro, dict):
|
|
93
|
+
continue
|
|
94
|
+
item = cast("dict[str, object]", primeiro)
|
|
95
|
+
for interna in ("menssagem", "mensagem", "message", "descricao"):
|
|
96
|
+
valor = item.get(interna)
|
|
97
|
+
if isinstance(valor, str) and valor:
|
|
98
|
+
return valor
|
|
99
|
+
return ""
|
|
100
|
+
|
|
101
|
+
|
|
70
102
|
def _erro_extrato(res: requests.Response) -> ExtratoError:
|
|
71
103
|
mensagem = _mensagem_de_erro(res)
|
|
72
104
|
normalizada = mensagem.casefold()
|
|
@@ -77,6 +109,19 @@ def _erro_extrato(res: requests.Response) -> ExtratoError:
|
|
|
77
109
|
return ExtratoError(res.status_code, mensagem)
|
|
78
110
|
|
|
79
111
|
|
|
112
|
+
def _erro_extrato_aplicacao(res: requests.Response) -> ExtratoError:
|
|
113
|
+
"""Classifica o erro dos extratos de fundo de investimento e de poupança.
|
|
114
|
+
|
|
115
|
+
Esses extratos usam ``404`` de corpo vazio para "não há o que devolver" —
|
|
116
|
+
mês sem movimento ou aplicação que a conta não tem —, então esse status
|
|
117
|
+
vira :class:`SemLancamentosError` em vez de falha.
|
|
118
|
+
"""
|
|
119
|
+
if res.status_code == _STATUS_SEM_MOVIMENTO:
|
|
120
|
+
mensagem = _mensagem_de_erro(res) or _SEM_MOVIMENTO_PADRAO
|
|
121
|
+
return SemLancamentosError(res.status_code, mensagem)
|
|
122
|
+
return _erro_extrato(res)
|
|
123
|
+
|
|
124
|
+
|
|
80
125
|
class _AccountabilityV3BaseAPI:
|
|
81
126
|
_app_key: str
|
|
82
127
|
_client_id: str
|
|
@@ -685,6 +730,15 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
685
730
|
mes: int,
|
|
686
731
|
ano: int,
|
|
687
732
|
) -> pd.DataFrame:
|
|
733
|
+
"""Reaver o extrato mensal de um fundo de investimento da conta.
|
|
734
|
+
|
|
735
|
+
``fundo_investimento_id`` é o ``grupoAtivo`` que
|
|
736
|
+
:meth:`get_saldo_aplicacoes_financeiras` devolve em cada operação — não
|
|
737
|
+
o ``codigo`` nem a ``modalidade``, que respondem ``404``.
|
|
738
|
+
|
|
739
|
+
Levanta :class:`SemLancamentosError` quando o mês não tem movimento ou
|
|
740
|
+
a conta não tem esse fundo.
|
|
741
|
+
"""
|
|
688
742
|
access_token = self._get_access_token()
|
|
689
743
|
|
|
690
744
|
res = requests.request(
|
|
@@ -699,9 +753,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
699
753
|
)
|
|
700
754
|
|
|
701
755
|
if res.status_code != 200:
|
|
702
|
-
raise
|
|
703
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
704
|
-
)
|
|
756
|
+
raise _erro_extrato_aplicacao(res)
|
|
705
757
|
|
|
706
758
|
res = common.parse_json_object(res)
|
|
707
759
|
extrato = cast("dict[str, object]", res["extrato"])
|
|
@@ -709,9 +761,10 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
709
761
|
df = common.handle_results(
|
|
710
762
|
extrato,
|
|
711
763
|
main_list="listaLancamentosExtrato",
|
|
764
|
+
keep_header_when_empty=True,
|
|
712
765
|
insertables=[
|
|
713
766
|
"numeroAgenciaRecebedora",
|
|
714
|
-
"
|
|
767
|
+
"digitoVerificadoraContaRecebedora",
|
|
715
768
|
"numeroContaCorrenteRecebedora",
|
|
716
769
|
"numeroDigitoVerificadorContaCorrenteRecebedora",
|
|
717
770
|
"nomeClienteRecebedor",
|
|
@@ -719,7 +772,6 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
719
772
|
"CNPJFundoInvestimento",
|
|
720
773
|
"valorCotaExtrato",
|
|
721
774
|
"dataAfericaoValorCota",
|
|
722
|
-
"ultimaCotacaoCota",
|
|
723
775
|
"dataUltimaCotacaoCota",
|
|
724
776
|
"sinalRentabilidadeMes",
|
|
725
777
|
"valorRentabilidadeMes",
|
|
@@ -758,7 +810,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
758
810
|
],
|
|
759
811
|
rename_dict={
|
|
760
812
|
"numeroAgenciaRecebedora": "Número Agência Recebedora",
|
|
761
|
-
"
|
|
813
|
+
"digitoVerificadoraContaRecebedora": "Dígito Verificador Conta Recebedora",
|
|
762
814
|
"numeroContaCorrenteRecebedora": "Número Conta Corrente Recebedora",
|
|
763
815
|
"numeroDigitoVerificadorContaCorrenteRecebedora": "Número Dígito Verificador Conta Corrente Recebedora",
|
|
764
816
|
"nomeClienteRecebedor": "Nome Cliente Recebedor",
|
|
@@ -766,7 +818,6 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
766
818
|
"CNPJFundoInvestimento": " CNPJ Fundo Investimento",
|
|
767
819
|
"valorCotaExtrato": "Valor Cota Extrato",
|
|
768
820
|
"dataAfericaoValorCota": "Data Afericão Valor Cota",
|
|
769
|
-
"ultimaCotacaoCota": "Última Cotação Cota",
|
|
770
821
|
"dataUltimaCotacaoCota": "Data Última Cotação Cota",
|
|
771
822
|
"sinalRentabilidadeMes": "Sinal Rentabilidade Mês",
|
|
772
823
|
"valorRentabilidadeMes": "Valor Rentabilidade Mês",
|
|
@@ -837,6 +888,12 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
837
888
|
mes: int,
|
|
838
889
|
ano: int,
|
|
839
890
|
) -> pd.DataFrame:
|
|
891
|
+
"""Reaver o extrato mensal de uma variação de poupança da conta.
|
|
892
|
+
|
|
893
|
+
Levanta :class:`SemLancamentosError` quando não há o que devolver e
|
|
894
|
+
:class:`ExtratoError` quando a variação não existe — nesse caso a API
|
|
895
|
+
responde ``400`` dizendo que o número da variação não é válido.
|
|
896
|
+
"""
|
|
840
897
|
access_token = self._get_access_token()
|
|
841
898
|
|
|
842
899
|
res = requests.request(
|
|
@@ -851,14 +908,13 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
|
|
|
851
908
|
)
|
|
852
909
|
|
|
853
910
|
if res.status_code != 200:
|
|
854
|
-
raise
|
|
855
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
856
|
-
)
|
|
911
|
+
raise _erro_extrato_aplicacao(res)
|
|
857
912
|
|
|
858
913
|
res = common.parse_json_object(res)
|
|
859
914
|
return common.handle_results(
|
|
860
915
|
res,
|
|
861
916
|
main_list="listaLancamentos",
|
|
917
|
+
keep_header_when_empty=True,
|
|
862
918
|
insertables=[
|
|
863
919
|
"codigoProgramaGoverno",
|
|
864
920
|
"nomeProgramaGoverno",
|
|
@@ -1689,6 +1745,14 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1689
1745
|
mes: int,
|
|
1690
1746
|
ano: int,
|
|
1691
1747
|
) -> pd.DataFrame:
|
|
1748
|
+
"""Reaver o extrato mensal de um fundo de investimento da conta.
|
|
1749
|
+
|
|
1750
|
+
``fundo_investimento_id`` é o ``grupoAtivo`` da operação no saldo de
|
|
1751
|
+
aplicações financeiras, não o ``codigo`` nem a ``modalidade``.
|
|
1752
|
+
|
|
1753
|
+
Levanta :class:`SemLancamentosError` quando o mês não tem movimento ou
|
|
1754
|
+
a conta não tem esse fundo.
|
|
1755
|
+
"""
|
|
1692
1756
|
access_token = self._get_access_token()
|
|
1693
1757
|
|
|
1694
1758
|
res = requests.request(
|
|
@@ -1703,9 +1767,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1703
1767
|
)
|
|
1704
1768
|
|
|
1705
1769
|
if res.status_code != 200:
|
|
1706
|
-
raise
|
|
1707
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1708
|
-
)
|
|
1770
|
+
raise _erro_extrato_aplicacao(res)
|
|
1709
1771
|
|
|
1710
1772
|
res = common.parse_json_object(res)
|
|
1711
1773
|
extrato = cast("dict[str, object]", res["extrato"])
|
|
@@ -1713,9 +1775,10 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1713
1775
|
df = common.handle_results(
|
|
1714
1776
|
extrato,
|
|
1715
1777
|
main_list="listaLancamentosExtrato",
|
|
1778
|
+
keep_header_when_empty=True,
|
|
1716
1779
|
insertables=[
|
|
1717
1780
|
"numeroAgenciaRecebedora",
|
|
1718
|
-
"
|
|
1781
|
+
"digitoVerificadoraContaRecebedora",
|
|
1719
1782
|
"numeroContaCorrenteRecebedora",
|
|
1720
1783
|
"numeroDigitoVerificadorContaCorrenteRecebedora",
|
|
1721
1784
|
"nomeClienteRecebedor",
|
|
@@ -1723,7 +1786,6 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1723
1786
|
"CNPJFundoInvestimento",
|
|
1724
1787
|
"valorCotaExtrato",
|
|
1725
1788
|
"dataAfericaoValorCota",
|
|
1726
|
-
"ultimaCotacaoCota",
|
|
1727
1789
|
"dataUltimaCotacaoCota",
|
|
1728
1790
|
"sinalRentabilidadeMes",
|
|
1729
1791
|
"valorRentabilidadeMes",
|
|
@@ -1762,7 +1824,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1762
1824
|
],
|
|
1763
1825
|
rename_dict={
|
|
1764
1826
|
"numeroAgenciaRecebedora": "Número Agência Recebedora",
|
|
1765
|
-
"
|
|
1827
|
+
"digitoVerificadoraContaRecebedora": "Dígito Verificador Conta Recebedora",
|
|
1766
1828
|
"numeroContaCorrenteRecebedora": "Número Conta Corrente Recebedora",
|
|
1767
1829
|
"numeroDigitoVerificadorContaCorrenteRecebedora": "Número Dígito Verificador Conta Corrente Recebedora",
|
|
1768
1830
|
"nomeClienteRecebedor": "Nome Cliente Recebedor",
|
|
@@ -1770,7 +1832,6 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1770
1832
|
"CNPJFundoInvestimento": " CNPJ Fundo Investimento",
|
|
1771
1833
|
"valorCotaExtrato": "Valor Cota Extrato",
|
|
1772
1834
|
"dataAfericaoValorCota": "Data Afericão Valor Cota",
|
|
1773
|
-
"ultimaCotacaoCota": "Última Cotação Cota",
|
|
1774
1835
|
"dataUltimaCotacaoCota": "Data Última Cotação Cota",
|
|
1775
1836
|
"sinalRentabilidadeMes": "Sinal Rentabilidade Mês",
|
|
1776
1837
|
"valorRentabilidadeMes": "Valor Rentabilidade Mês",
|
|
@@ -1840,6 +1901,11 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1840
1901
|
variacao_poupanca: str,
|
|
1841
1902
|
codigo_variacao: int,
|
|
1842
1903
|
) -> pd.DataFrame:
|
|
1904
|
+
"""Reaver o extrato de uma variação de poupança da conta.
|
|
1905
|
+
|
|
1906
|
+
Levanta :class:`SemLancamentosError` quando não há o que devolver e
|
|
1907
|
+
:class:`ExtratoError` quando a variação não existe.
|
|
1908
|
+
"""
|
|
1843
1909
|
access_token = self._get_access_token()
|
|
1844
1910
|
|
|
1845
1911
|
res = requests.request(
|
|
@@ -1853,14 +1919,13 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
|
|
|
1853
1919
|
)
|
|
1854
1920
|
|
|
1855
1921
|
if res.status_code != 200:
|
|
1856
|
-
raise
|
|
1857
|
-
"Não foi possível listar as categorias do programa de governo."
|
|
1858
|
-
)
|
|
1922
|
+
raise _erro_extrato_aplicacao(res)
|
|
1859
1923
|
|
|
1860
1924
|
res = common.parse_json_object(res)
|
|
1861
1925
|
return common.handle_results(
|
|
1862
1926
|
res,
|
|
1863
1927
|
main_list="listaLancamentos",
|
|
1928
|
+
keep_header_when_empty=True,
|
|
1864
1929
|
insertables=[
|
|
1865
1930
|
"codigoProgramaGoverno",
|
|
1866
1931
|
"nomeProgramaGoverno",
|
|
@@ -81,17 +81,31 @@ def handle_results(
|
|
|
81
81
|
insertables: Sequence[str] | None = None,
|
|
82
82
|
explodeables: Sequence[str] | None = None,
|
|
83
83
|
rename_dict: Mapping[str, str] | None = None,
|
|
84
|
+
*,
|
|
85
|
+
keep_header_when_empty: bool = False,
|
|
84
86
|
) -> pd.DataFrame:
|
|
87
|
+
"""Monta o DataFrame da resposta: uma linha por item de ``main_list``.
|
|
88
|
+
|
|
89
|
+
Os campos de ``insertables`` ficam no nível de cima da resposta, fora da
|
|
90
|
+
lista, e são repetidos em todas as linhas.
|
|
91
|
+
|
|
92
|
+
Com ``keep_header_when_empty``, uma lista vazia devolve uma linha só com
|
|
93
|
+
esses campos, em vez de um DataFrame sem linha nenhuma. É o que preserva o
|
|
94
|
+
cabeçalho das respostas em que ele carrega o dado principal, como o
|
|
95
|
+
rendimento do mês nos extratos de aplicação.
|
|
96
|
+
"""
|
|
85
97
|
record = cast("Mapping[str, object]", data)
|
|
86
98
|
|
|
87
99
|
if main_list is not None:
|
|
88
|
-
|
|
100
|
+
itens = cast("list[dict[Hashable, object]]", record.get(main_list) or [])
|
|
101
|
+
so_cabecalho = not itens and keep_header_when_empty
|
|
102
|
+
df = pd.DataFrame(index=[0]) if so_cabecalho else pd.DataFrame(itens)
|
|
89
103
|
else:
|
|
90
104
|
df = pd.DataFrame([dict(record)])
|
|
91
105
|
|
|
92
106
|
if insertables is not None:
|
|
93
107
|
for insertable in insertables:
|
|
94
|
-
df[insertable] = cast("Scalar", record
|
|
108
|
+
df[insertable] = cast("Scalar", record.get(insertable))
|
|
95
109
|
|
|
96
110
|
if explodeables is not None:
|
|
97
111
|
for explodeable in explodeables:
|
|
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
|