|
| 1 | +.. _async: |
| 2 | + |
| 3 | +APIs Assíncronas |
| 4 | +================ |
| 5 | + |
| 6 | +O **python-bcb** oferece suporte completo a programação assíncrona através de métodos ``async_get()`` e ``async_collect()`` em todos os módulos. |
| 7 | +Isso permite buscar dados de forma não-bloqueante e executar múltiplas requisições concorrentemente usando ``asyncio``. |
| 8 | + |
| 9 | +Quando Usar APIs Assíncronas |
| 10 | +---------------------------- |
| 11 | + |
| 12 | +Use as APIs assíncronas quando você precisa: |
| 13 | + |
| 14 | +* **Buscar múltiplas séries** — Reduz o tempo total de execução |
| 15 | +* **Integrar com código assíncrono** — Evita bloquear a event loop |
| 16 | +* **Processamento em larga escala** — Milhares de requisições sem I/O bloqueante |
| 17 | +* **Aplicações web/CLI** — Melhor responsividade e throughput |
| 18 | + |
| 19 | +Exemplo Básico: Múltiplas Séries SGS |
| 20 | +------------------------------------ |
| 21 | + |
| 22 | +Buscar vários indicadores SGS concorrentemente é muito mais rápido que fazer requisições sequenciais: |
| 23 | + |
| 24 | +.. code-block:: python |
| 25 | +
|
| 26 | + import asyncio |
| 27 | + from bcb import sgs |
| 28 | +
|
| 29 | + async def fetch_economic_indicators(): |
| 30 | + """Buscar SELIC, CDI e IPCA concorrentemente.""" |
| 31 | + # Criar tasks para requisições concorrentes |
| 32 | + results = await asyncio.gather( |
| 33 | + sgs.async_get(1, start='2024-01-01'), # SELIC |
| 34 | + sgs.async_get(12, start='2024-01-01'), # CDI |
| 35 | + sgs.async_get(433, start='2024-01-01'), # IPCA |
| 36 | + ) |
| 37 | +
|
| 38 | + selic_df, cdi_df, ipca_df = results |
| 39 | + return selic_df, cdi_df, ipca_df |
| 40 | +
|
| 41 | + # Executar |
| 42 | + selic, cdi, ipca = asyncio.run(fetch_economic_indicators()) |
| 43 | + print(selic.head()) |
| 44 | +
|
| 45 | +Módulo SGS: async_get() |
| 46 | +----------------------- |
| 47 | + |
| 48 | +A função :py:func:`bcb.sgs.async_get` é a versão assíncrona de :py:func:`bcb.sgs.get`. |
| 49 | +Busca dados de múltiplos códigos SGS concorrentemente com a mesma interface que a versão síncrona. |
| 50 | + |
| 51 | +**Exemplo com múltiplos códigos:** |
| 52 | + |
| 53 | +.. code-block:: python |
| 54 | +
|
| 55 | + import asyncio |
| 56 | + from bcb import sgs |
| 57 | +
|
| 58 | + async def main(): |
| 59 | + # Buscar vários indicadores de uma vez |
| 60 | + df = await sgs.async_get( |
| 61 | + [1, 11, 12, 433], # SELIC, Taxa Over, CDI, IPCA |
| 62 | + start='2023-01-01', |
| 63 | + end='2024-12-31' |
| 64 | + ) |
| 65 | + print(df.head()) |
| 66 | +
|
| 67 | + asyncio.run(main()) |
| 68 | +
|
| 69 | +Módulo Currency: async_get() |
| 70 | +----------------------------- |
| 71 | + |
| 72 | +A função :py:func:`bcb.currency.async_get` é a versão assíncrona de :py:func:`bcb.currency.get`. |
| 73 | +Busca taxas de câmbio de forma assíncrona com a mesma interface que a versão síncrona. |
| 74 | + |
| 75 | +**Exemplo:** |
| 76 | + |
| 77 | +.. code-block:: python |
| 78 | +
|
| 79 | + import asyncio |
| 80 | + from bcb import currency |
| 81 | +
|
| 82 | + async def main(): |
| 83 | + # Buscar taxas de câmbio |
| 84 | + usd = await currency.async_get('USD', start='2024-01-01', end='2024-12-31') |
| 85 | + print(usd.head()) |
| 86 | +
|
| 87 | + asyncio.run(main()) |
| 88 | +
|
| 89 | +OData: async_collect() |
| 90 | +---------------------- |
| 91 | + |
| 92 | +Todas as queries OData suportam métodos assíncronos para execução não-bloqueante: |
| 93 | + |
| 94 | +* :py:meth:`ODataQuery.async_collect` — versão assíncrona de :py:meth:`ODataQuery.collect` |
| 95 | +* :py:meth:`Endpoint.async_get` — versão assíncrona de :py:meth:`Endpoint.get` |
| 96 | +* :py:meth:`Endpoint.async_query` — retorna uma query pronta para execução assíncrona |
| 97 | + |
| 98 | +**Exemplo com Expectativas:** |
| 99 | + |
| 100 | +.. code-block:: python |
| 101 | +
|
| 102 | + import asyncio |
| 103 | + from bcb import Expectativas |
| 104 | +
|
| 105 | + async def main(): |
| 106 | + api = Expectativas() |
| 107 | + endpoint = api.get_endpoint('ExpectativasMercadoAnuais') |
| 108 | +
|
| 109 | + # Construir query e executar de forma assíncrona |
| 110 | + df = await ( |
| 111 | + endpoint.query() |
| 112 | + .filter(endpoint.Indicador == 'IPCA') |
| 113 | + .limit(100) |
| 114 | + .async_collect() |
| 115 | + ) |
| 116 | + print(df) |
| 117 | +
|
| 118 | + asyncio.run(main()) |
| 119 | +
|
| 120 | +Padrão asyncio.gather() — Operações Paralelas |
| 121 | +---------------------------------------------- |
| 122 | + |
| 123 | +Use ``asyncio.gather()`` para executar várias operações em paralelo: |
| 124 | + |
| 125 | +.. code-block:: python |
| 126 | +
|
| 127 | + import asyncio |
| 128 | + from bcb import sgs, currency |
| 129 | + from bcb import Expectativas |
| 130 | +
|
| 131 | + async def main(): |
| 132 | + # Executar 3 operações em paralelo |
| 133 | + selic_task = sgs.async_get(1, start='2024-01-01') |
| 134 | + usd_task = currency.async_get('USD', start='2024-01-01') |
| 135 | +
|
| 136 | + api = Expectativas() |
| 137 | + endpoint = api.get_endpoint('ExpectativasMercadoAnuais') |
| 138 | + expectations_task = endpoint.query().filter( |
| 139 | + endpoint.Indicador == 'IPCA' |
| 140 | + ).limit(10).async_collect() |
| 141 | +
|
| 142 | + # Aguardar todas as operações |
| 143 | + selic, usd, expectations = await asyncio.gather( |
| 144 | + selic_task, |
| 145 | + usd_task, |
| 146 | + expectations_task |
| 147 | + ) |
| 148 | +
|
| 149 | + return selic, usd, expectations |
| 150 | +
|
| 151 | + selic, usd, expectations = asyncio.run(main()) |
| 152 | +
|
| 153 | +Tratamento de Erros |
| 154 | +------------------- |
| 155 | + |
| 156 | +As APIs assíncronas lançam as mesmas exceções que as síncronas: |
| 157 | + |
| 158 | +.. code-block:: python |
| 159 | +
|
| 160 | + import asyncio |
| 161 | + from bcb import sgs |
| 162 | + from bcb.exceptions import SGSError, BCBRateLimitError |
| 163 | +
|
| 164 | + async def main(): |
| 165 | + try: |
| 166 | + df = await sgs.async_get(99999) # Código inválido |
| 167 | + except SGSError as e: |
| 168 | + print(f"Erro de dados SGS: {e}") |
| 169 | + except BCBRateLimitError: |
| 170 | + print("Limite de requisições excedido - tente novamente mais tarde") |
| 171 | +
|
| 172 | + asyncio.run(main()) |
| 173 | +
|
| 174 | +Performance: Síncrono vs Assíncrono |
| 175 | +----------------------------------- |
| 176 | + |
| 177 | +**Requisições Sequenciais (bloqueante):** |
| 178 | + |
| 179 | +.. code-block:: python |
| 180 | +
|
| 181 | + # Leva ~5 segundos (1s + 1s + 1s + cada requisição é bloqueante) |
| 182 | + df1 = sgs.get(1, start='2024-01-01') |
| 183 | + df2 = sgs.get(11, start='2024-01-01') |
| 184 | + df3 = sgs.get(12, start='2024-01-01') |
| 185 | +
|
| 186 | +**Requisições Concorrentes (assíncrono):** |
| 187 | + |
| 188 | +.. code-block:: python |
| 189 | +
|
| 190 | + # Leva ~1 segundo (todas 3 são executadas em paralelo) |
| 191 | + import asyncio |
| 192 | +
|
| 193 | + async def main(): |
| 194 | + df1, df2, df3 = await asyncio.gather( |
| 195 | + sgs.async_get(1, start='2024-01-01'), |
| 196 | + sgs.async_get(11, start='2024-01-01'), |
| 197 | + sgs.async_get(12, start='2024-01-01'), |
| 198 | + ) |
| 199 | +
|
| 200 | + asyncio.run(main()) |
| 201 | +
|
| 202 | +Limpeza de Recursos |
| 203 | +------------------- |
| 204 | + |
| 205 | +Para aplicações de longa duração, feche o cliente assíncrono quando terminar: |
| 206 | + |
| 207 | +.. code-block:: python |
| 208 | +
|
| 209 | + import asyncio |
| 210 | + from bcb import http |
| 211 | +
|
| 212 | + async def main(): |
| 213 | + # ... suas operações assíncronas ... |
| 214 | + pass |
| 215 | +
|
| 216 | + asyncio.run(main()) |
| 217 | +
|
| 218 | + # Fechar cliente assíncrono |
| 219 | + asyncio.run(http.close_async_client()) |
| 220 | +
|
| 221 | +Limitações |
| 222 | +---------- |
| 223 | + |
| 224 | +* **Concorrência sem limite** — Use um ``asyncio.Semaphore()`` para limitar requisições simultâneas |
| 225 | +* **Rate limiting** — APIs BCB podem ter limites; implemente backoff exponencial se necessário |
| 226 | +* **Ciclos de evento** — APIs assíncronas exigem uma event loop ativa (use ``asyncio.run()``) |
| 227 | + |
| 228 | +**Exemplo com Semaphore:** |
| 229 | + |
| 230 | +.. code-block:: python |
| 231 | +
|
| 232 | + import asyncio |
| 233 | + from bcb import sgs |
| 234 | +
|
| 235 | + async def main(): |
| 236 | + # Limitar a 3 requisições simultâneas |
| 237 | + semaphore = asyncio.Semaphore(3) |
| 238 | +
|
| 239 | + async def fetch_with_limit(code): |
| 240 | + async with semaphore: |
| 241 | + return await sgs.async_get(code, start='2024-01-01') |
| 242 | +
|
| 243 | + codes = [1, 11, 12, 433, 189] |
| 244 | + results = await asyncio.gather(*[fetch_with_limit(c) for c in codes]) |
| 245 | +
|
| 246 | + return results |
| 247 | +
|
| 248 | + asyncio.run(main()) |
| 249 | +
|
| 250 | +Veja Também |
| 251 | +----------- |
| 252 | + |
| 253 | +* :ref:`SGS` — Documentação completa do módulo SGS |
| 254 | +* :ref:`Conversor de Moedas` — Documentação do módulo currency |
| 255 | +* :ref:`OData` — Documentação do cliente OData |
| 256 | +* `asyncio — asyncpython <https://docs.python.org/3/library/asyncio.html>`_ |
0 commit comments