bb_api 0.5.1__tar.gz → 0.5.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bb_api
3
- Version: 0.5.1
3
+ Version: 0.5.2
4
4
  Summary: Wrapper da API do Banco do Brasil.
5
5
  Requires-Python: >=3.13
6
6
  Description-Content-Type: text/markdown
@@ -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.1"
7
+ __version__ = "0.5.2"
8
8
 
9
9
 
10
10
  from .common import Ambiente
@@ -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 Exception(
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"])
@@ -711,7 +763,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
711
763
  main_list="listaLancamentosExtrato",
712
764
  insertables=[
713
765
  "numeroAgenciaRecebedora",
714
- "digitoVerificadorContaRecebedora",
766
+ "digitoVerificadoraContaRecebedora",
715
767
  "numeroContaCorrenteRecebedora",
716
768
  "numeroDigitoVerificadorContaCorrenteRecebedora",
717
769
  "nomeClienteRecebedor",
@@ -719,7 +771,6 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
719
771
  "CNPJFundoInvestimento",
720
772
  "valorCotaExtrato",
721
773
  "dataAfericaoValorCota",
722
- "ultimaCotacaoCota",
723
774
  "dataUltimaCotacaoCota",
724
775
  "sinalRentabilidadeMes",
725
776
  "valorRentabilidadeMes",
@@ -758,7 +809,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
758
809
  ],
759
810
  rename_dict={
760
811
  "numeroAgenciaRecebedora": "Número Agência Recebedora",
761
- "digitoVerificadorContaRecebedora": "Dígito Verificador Conta Recebedora",
812
+ "digitoVerificadoraContaRecebedora": "Dígito Verificador Conta Recebedora",
762
813
  "numeroContaCorrenteRecebedora": "Número Conta Corrente Recebedora",
763
814
  "numeroDigitoVerificadorContaCorrenteRecebedora": "Número Dígito Verificador Conta Corrente Recebedora",
764
815
  "nomeClienteRecebedor": "Nome Cliente Recebedor",
@@ -766,7 +817,6 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
766
817
  "CNPJFundoInvestimento": " CNPJ Fundo Investimento",
767
818
  "valorCotaExtrato": "Valor Cota Extrato",
768
819
  "dataAfericaoValorCota": "Data Afericão Valor Cota",
769
- "ultimaCotacaoCota": "Última Cotação Cota",
770
820
  "dataUltimaCotacaoCota": "Data Última Cotação Cota",
771
821
  "sinalRentabilidadeMes": "Sinal Rentabilidade Mês",
772
822
  "valorRentabilidadeMes": "Valor Rentabilidade Mês",
@@ -837,6 +887,12 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
837
887
  mes: int,
838
888
  ano: int,
839
889
  ) -> pd.DataFrame:
890
+ """Reaver o extrato mensal de uma variação de poupança da conta.
891
+
892
+ Levanta :class:`SemLancamentosError` quando não há o que devolver e
893
+ :class:`ExtratoError` quando a variação não existe — nesse caso a API
894
+ responde ``400`` dizendo que o número da variação não é válido.
895
+ """
840
896
  access_token = self._get_access_token()
841
897
 
842
898
  res = requests.request(
@@ -851,9 +907,7 @@ class AccountabilityV3RepasseAPI(_AccountabilityV3BaseAPI):
851
907
  )
852
908
 
853
909
  if res.status_code != 200:
854
- raise Exception(
855
- "Não foi possível listar as categorias do programa de governo."
856
- )
910
+ raise _erro_extrato_aplicacao(res)
857
911
 
858
912
  res = common.parse_json_object(res)
859
913
  return common.handle_results(
@@ -1689,6 +1743,14 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
1689
1743
  mes: int,
1690
1744
  ano: int,
1691
1745
  ) -> pd.DataFrame:
1746
+ """Reaver o extrato mensal de um fundo de investimento da conta.
1747
+
1748
+ ``fundo_investimento_id`` é o ``grupoAtivo`` da operação no saldo de
1749
+ aplicações financeiras, não o ``codigo`` nem a ``modalidade``.
1750
+
1751
+ Levanta :class:`SemLancamentosError` quando o mês não tem movimento ou
1752
+ a conta não tem esse fundo.
1753
+ """
1692
1754
  access_token = self._get_access_token()
1693
1755
 
1694
1756
  res = requests.request(
@@ -1703,9 +1765,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
1703
1765
  )
1704
1766
 
1705
1767
  if res.status_code != 200:
1706
- raise Exception(
1707
- "Não foi possível listar as categorias do programa de governo."
1708
- )
1768
+ raise _erro_extrato_aplicacao(res)
1709
1769
 
1710
1770
  res = common.parse_json_object(res)
1711
1771
  extrato = cast("dict[str, object]", res["extrato"])
@@ -1715,7 +1775,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
1715
1775
  main_list="listaLancamentosExtrato",
1716
1776
  insertables=[
1717
1777
  "numeroAgenciaRecebedora",
1718
- "digitoVerificadorContaRecebedora",
1778
+ "digitoVerificadoraContaRecebedora",
1719
1779
  "numeroContaCorrenteRecebedora",
1720
1780
  "numeroDigitoVerificadorContaCorrenteRecebedora",
1721
1781
  "nomeClienteRecebedor",
@@ -1723,7 +1783,6 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
1723
1783
  "CNPJFundoInvestimento",
1724
1784
  "valorCotaExtrato",
1725
1785
  "dataAfericaoValorCota",
1726
- "ultimaCotacaoCota",
1727
1786
  "dataUltimaCotacaoCota",
1728
1787
  "sinalRentabilidadeMes",
1729
1788
  "valorRentabilidadeMes",
@@ -1762,7 +1821,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
1762
1821
  ],
1763
1822
  rename_dict={
1764
1823
  "numeroAgenciaRecebedora": "Número Agência Recebedora",
1765
- "digitoVerificadorContaRecebedora": "Dígito Verificador Conta Recebedora",
1824
+ "digitoVerificadoraContaRecebedora": "Dígito Verificador Conta Recebedora",
1766
1825
  "numeroContaCorrenteRecebedora": "Número Conta Corrente Recebedora",
1767
1826
  "numeroDigitoVerificadorContaCorrenteRecebedora": "Número Dígito Verificador Conta Corrente Recebedora",
1768
1827
  "nomeClienteRecebedor": "Nome Cliente Recebedor",
@@ -1770,7 +1829,6 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
1770
1829
  "CNPJFundoInvestimento": " CNPJ Fundo Investimento",
1771
1830
  "valorCotaExtrato": "Valor Cota Extrato",
1772
1831
  "dataAfericaoValorCota": "Data Afericão Valor Cota",
1773
- "ultimaCotacaoCota": "Última Cotação Cota",
1774
1832
  "dataUltimaCotacaoCota": "Data Última Cotação Cota",
1775
1833
  "sinalRentabilidadeMes": "Sinal Rentabilidade Mês",
1776
1834
  "valorRentabilidadeMes": "Valor Rentabilidade Mês",
@@ -1840,6 +1898,11 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
1840
1898
  variacao_poupanca: str,
1841
1899
  codigo_variacao: int,
1842
1900
  ) -> pd.DataFrame:
1901
+ """Reaver o extrato de uma variação de poupança da conta.
1902
+
1903
+ Levanta :class:`SemLancamentosError` quando não há o que devolver e
1904
+ :class:`ExtratoError` quando a variação não existe.
1905
+ """
1843
1906
  access_token = self._get_access_token()
1844
1907
 
1845
1908
  res = requests.request(
@@ -1853,9 +1916,7 @@ class AccountabilityV3ControleAPI(_AccountabilityV3BaseAPI):
1853
1916
  )
1854
1917
 
1855
1918
  if res.status_code != 200:
1856
- raise Exception(
1857
- "Não foi possível listar as categorias do programa de governo."
1858
- )
1919
+ raise _erro_extrato_aplicacao(res)
1859
1920
 
1860
1921
  res = common.parse_json_object(res)
1861
1922
  return common.handle_results(
@@ -91,7 +91,7 @@ def handle_results(
91
91
 
92
92
  if insertables is not None:
93
93
  for insertable in insertables:
94
- df[insertable] = cast("Scalar", record[insertable])
94
+ df[insertable] = cast("Scalar", record.get(insertable))
95
95
 
96
96
  if explodeables is not None:
97
97
  for explodeable in explodeables:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bb_api
3
- Version: 0.5.1
3
+ Version: 0.5.2
4
4
  Summary: Wrapper da API do Banco do Brasil.
5
5
  Requires-Python: >=3.13
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bb_api"
3
- version = "0.5.1"
3
+ version = "0.5.2"
4
4
  description = "Wrapper da API do Banco do Brasil."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
File without changes
File without changes
File without changes
File without changes
File without changes