From fbd97df18f3219bbe369c586745a6cc7abd1f1cd Mon Sep 17 00:00:00 2001 From: Wilson Freitas Date: Sun, 14 Jun 2026 21:36:16 -0300 Subject: [PATCH] Improve OData describe output --- bcb/odata/api.py | 11 ++++++--- bcb/odata/framework.py | 40 ++++++++++++++++++++++---------- docs/currency.rst | 1 + docs/expectativas.rst | 11 +++++---- docs/odata.rst | 4 ++-- docs/taxajuros.rst | 5 ++-- tests/test_odata.py | 52 +++++++++++++++++++++++++++++++++++++++++- 7 files changed, 99 insertions(+), 25 deletions(-) diff --git a/bcb/odata/api.py b/bcb/odata/api.py index 7aa0441..40f3769 100644 --- a/bcb/odata/api.py +++ b/bcb/odata/api.py @@ -372,7 +372,7 @@ def __init__(self, *, timeout: RequestTimeout = None) -> None: self._timeout = timeout self.service = ODataService(self.BASE_URL, timeout=timeout) - def describe(self, endpoint: Optional[str] = None) -> None: + def describe(self, endpoint: Optional[str] = None, *, full: bool = True) -> None: """ Mostra a descrição de uma API ou de um *endpoint* específico. @@ -381,7 +381,12 @@ def describe(self, endpoint: Optional[str] = None) -> None: ---------- endpoint : None (padrão) ou str - nome do *endpoint* + Nome do *endpoint*. Quando informado, mostra apenas a descrição + desse *endpoint*. + full : bool, default True + Quando ``endpoint`` não é informado, mostra os detalhes de todos + os *endpoints*. Use ``False`` para imprimir apenas a listagem curta + com os nomes dos *endpoints*. Returns ------- @@ -392,7 +397,7 @@ def describe(self, endpoint: Optional[str] = None) -> None: if endpoint: self.service[endpoint].describe() else: - self.service.describe() + self.service.describe(full=full) def get_endpoint(self, endpoint: str) -> Endpoint: """ diff --git a/bcb/odata/framework.py b/bcb/odata/framework.py index 069b667..5c2dbc8 100644 --- a/bcb/odata/framework.py +++ b/bcb/odata/framework.py @@ -578,20 +578,36 @@ def function_imports(self) -> dict[str, ODataFunctionImport]: def entity_sets(self) -> dict[str, ODataEntitySet]: return self.metadata.entity_sets - def describe(self) -> None: - es_names = [] - for es in self.entity_sets.keys(): - k = f"{self.metadata.namespace}.{es}" - if k not in self.metadata._used_elements: - es_names.append(es) - if len(es_names): + def _standalone_entity_sets(self) -> list[ODataEntitySet]: + return [ + entity_set + for name, entity_set in self.entity_sets.items() + if f"{self.metadata.namespace}.{name}" not in self.metadata._used_elements + ] + + def describe(self, *, full: bool = False) -> None: + entity_sets = self._standalone_entity_sets() + function_imports = list(self.function_imports.values()) + + if full: + if entity_sets: + print("EntitySets:") + for entity_set in entity_sets: + entity_set.describe() + if function_imports: + print("FunctionImports:") + for function_import in function_imports: + function_import.describe() + return + + if entity_sets: print("EntitySets:") - for es in es_names: - print(" ", es) - if len(self.function_imports): + for entity_set in entity_sets: + print(" ", entity_set.name) + if function_imports: print("FunctionImports:") - for es in self.function_imports.keys(): - print(" ", es) + for function_import in function_imports: + print(" ", function_import.name) def query( self, entity_set: Union[ODataEntitySet, ODataFunctionImport] diff --git a/docs/currency.rst b/docs/currency.rst index 682fa72..39dc068 100644 --- a/docs/currency.rst +++ b/docs/currency.rst @@ -17,6 +17,7 @@ __ documentacao_ A classe :py:class:`bcb.PTAX` retorna cotações de moedas obtidas a partir da `API de Moedas`__ do BCB. Esta implementação é mais estável que a do :ref:`Conversor de Moedas`. +O método ``describe`` mostra os *endpoints*, parâmetros e propriedades disponíveis. .. ipython:: python diff --git a/docs/expectativas.rst b/docs/expectativas.rst index ab1aa34..d244e73 100644 --- a/docs/expectativas.rst +++ b/docs/expectativas.rst @@ -26,7 +26,9 @@ defasagem de 1 ano. Ao instanciar a classe :py:class:`bcb.Expectativas` diversas informações são obtidas e a melhor maneira de interagir com a API é -através do método :py:meth:`bcb.Expectativas.describe`. +através do método :py:meth:`bcb.Expectativas.describe`, que mostra os +*endpoints* e suas propriedades. Para obter apenas a listagem curta dos nomes, +use ``describe(full=False)``. .. ipython:: python @@ -35,10 +37,9 @@ através do método :py:meth:`bcb.Expectativas.describe`. em = Expectativas() em.describe() -O método :py:meth:`bcb.Expectativas.describe` também recebe o nomes dos -*endpoints* e apresenta uma descrição do *endpoint* trazendo o seu tipo -`EntityType` e as propriedades retornadas `Properties` e os seus respectivos -tipos. +O método :py:meth:`bcb.Expectativas.describe` também recebe o nome de um +*endpoint* para restringir a saída, apresentando o seu tipo `EntityType` e as +propriedades retornadas `Properties` com os seus respectivos tipos. .. ipython:: python diff --git a/docs/odata.rst b/docs/odata.rst index b7a6b03..67f73af 100644 --- a/docs/odata.rst +++ b/docs/odata.rst @@ -52,7 +52,7 @@ Segue um exemplo de como acessar a API do PIX. pix = SPI() É necessário importar e criar um objeto da classe que implementa a API, neste caso a classe ``SPI``. -Tendo o objeto, executar o método ``describe`` para visualizar o *endpoints* disponíveis na API. +Tendo o objeto, executar o método ``describe`` para visualizar os *endpoints* disponíveis na API e suas propriedades. Para obter apenas a listagem curta dos nomes, use ``describe(full=False)``. .. ipython:: python @@ -60,7 +60,7 @@ Tendo o objeto, executar o método ``describe`` para visualizar o *endpoints* di pix.describe() Como vemos, a API do PIX tem 4 *endpoints* (``EntitySets``). -Para ver as informações retornadas por cada *endpoint* é só executar o método ``describe`` passando como argumento +Para restringir a saída a um *endpoint*, execute o método ``describe`` passando como argumento o nome do *endpoint*. .. ipython:: python diff --git a/docs/taxajuros.rst b/docs/taxajuros.rst index e305ee1..fcd286e 100644 --- a/docs/taxajuros.rst +++ b/docs/taxajuros.rst @@ -11,7 +11,8 @@ __ documentacao_ Os dados são obtidos a partir da `API de Taxas de Juros`__. -Esta API usa o serviço ``taxaJuros`` versão ``v2`` e tem os ``EntitySets``: +Esta API usa o serviço ``taxaJuros`` versão ``v2``. O método ``describe`` +mostra os ``EntitySets`` e suas propriedades: .. ipython:: python @@ -19,7 +20,7 @@ Esta API usa o serviço ``taxaJuros`` versão ``v2`` e tem os ``EntitySets``: em = TaxaJuros() em.describe() -As características do ``EntitySets`` podem ser visualizadas por: +Para restringir a saída a um ``EntitySet`` específico, informe o seu nome: .. ipython:: python diff --git a/tests/test_odata.py b/tests/test_odata.py index 05ad61d..bcc0e6a 100644 --- a/tests/test_odata.py +++ b/tests/test_odata.py @@ -5,7 +5,7 @@ import pandas as pd import pytest -from bcb.odata.api import Expectativas +from bcb.odata.api import Expectativas, ODataAPI from bcb.odata.framework import ( ODataParameter, ODataProperty, @@ -120,6 +120,56 @@ def test_invalid_endpoint_raises(httpx_mock): api.get_endpoint("DoesNotExist") +def test_api_describe_shows_all_endpoint_details_by_default(httpx_mock, capsys): + add_service_mocks(httpx_mock) + api = Expectativas() + + api.describe() + + output = capsys.readouterr().out + assert "EntitySets:" in output + assert "EntitySet (Endpoint): ExpectativasMercadoAnuais" in output + assert "EntityType: IFBCB_DadosSeries_v2.Expectativa" in output + assert "Properties: Indicador, Data, Mediana" in output + + +def test_api_describe_can_show_summary_only(httpx_mock, capsys): + add_service_mocks(httpx_mock) + api = Expectativas() + + api.describe(full=False) + + output = capsys.readouterr().out + assert "EntitySets:" in output + assert " ExpectativasMercadoAnuais" in output + assert "Properties:" not in output + + +def test_api_describe_specific_endpoint_is_preserved(httpx_mock, capsys): + add_service_mocks(httpx_mock) + api = Expectativas() + + api.describe("ExpectativasMercadoAnuais") + + output = capsys.readouterr().out + assert "EntitySet (Endpoint): ExpectativasMercadoAnuais" in output + assert "Properties: Indicador, Data, Mediana" in output + + +def test_api_describe_shows_function_import_details_by_default(httpx_mock, capsys): + add_function_service_mocks(httpx_mock) + api = ODataAPI(FUNCTION_BASE_URL) + + api.describe() + + output = capsys.readouterr().out + assert "FunctionImports:" in output + assert "Function: CotacaoMoedaPeriodo" in output + assert "Parameters: moeda , dataInicial , limite " in output + assert "EntitySet: CotacoesMoedaPeriodo" in output + assert "Properties: Moeda , Data , CotacaoCompra " in output + + def test_service_root_status_error_raises_odata_error(httpx_mock): httpx_mock.add_response( url=EXPECTATIVAS_BASE_URL,