2121 import httpx
2222
2323from datetime import datetime
24+ import asyncio
2425
26+ # Cache global para os dados
27+ _cached_data = None
28+ _last_update = None
29+ _is_fetching = False
2530
2631# Função assíncrona para buscar os dados da API
27- async def fetch_usd_brl_data ():
28- url = "https://economia.awesomeapi.com.br/json/daily/USD-BRL/15"
29- if IS_PYODIDE :
30- # Usar pyfetch no ambiente web
31- try :
32- response = await pyfetch (url , method = "GET" )
33- if response .status == 200 :
34- return await response .json ()
35- else :
36- print (f"Erro: status code { response .status } " )
37- return []
38- except Exception as e :
39- print (f"Erro ao buscar dados: { e } " )
40- return []
41- else :
42- # Usar httpx no ambiente desktop/servidor
43- async with httpx .AsyncClient () as client :
32+ async def fetch_usd_brl_data (force_refresh = False ):
33+ global _cached_data , _last_update , _is_fetching
34+
35+ # Se já estiver buscando dados, aguarda
36+ while _is_fetching :
37+ await asyncio .sleep (0.1 )
38+
39+ # Verifica se tem cache válido (menos de 5 minutos)
40+ now = datetime .now ()
41+ if not force_refresh and _cached_data and _last_update :
42+ delta = now - _last_update
43+ if delta .total_seconds () < 300 : # 5 minutos
44+ return _cached_data
45+
46+ _is_fetching = True
47+ try :
48+ url = "https://economia.awesomeapi.com.br/json/daily/USD-BRL/15"
49+ max_retries = 3
50+ retry_count = 0
51+
52+ while retry_count < max_retries :
4453 try :
45- response = await client .get (url )
46- if response .status_code == 200 :
47- return response .json ()
54+ if IS_PYODIDE :
55+ response = await pyfetch (url , method = "GET" )
56+ if response .status == 200 :
57+ data = await response .json ()
58+ _cached_data = data
59+ _last_update = now
60+ return data
4861 else :
49- print (f"Erro: status code { response .status_code } " )
50- return []
62+ async with httpx .AsyncClient () as client :
63+ response = await client .get (url )
64+ if response .status_code == 200 :
65+ data = response .json ()
66+ _cached_data = data
67+ _last_update = now
68+ return data
69+
70+ retry_count += 1
71+ if retry_count < max_retries :
72+ await asyncio .sleep (1 )
5173 except Exception as e :
52- print (f"Erro ao buscar dados: { e } " )
53- return []
74+ print (f"Tentativa { retry_count + 1 } falhou: { str (e )} " )
75+ retry_count += 1
76+ if retry_count < max_retries :
77+ await asyncio .sleep (1 )
78+
79+ # Se chegou aqui, todas as tentativas falharam
80+ # Retorna cache antigo se disponível, mesmo que expirado
81+ return _cached_data if _cached_data else []
82+ finally :
83+ _is_fetching = False
5484
85+ # Iniciar o pré-carregamento dos dados
86+ async def preload_data ():
87+ await fetch_usd_brl_data ()
5588
5689# Função para criar o gráfico com Pyecharts
5790def create_chart (data ):
91+ if not data :
92+ return None
93+
5894 dates = [
5995 datetime .fromtimestamp (int (entry ["timestamp" ])).strftime ("%d/%m" )
6096 for entry in data [::- 1 ]
@@ -65,7 +101,13 @@ def create_chart(data):
65101
66102 bar = (
67103 Bar (
68- init_opts = opts .InitOpts (width = "100%" , height = "400px" , theme = ThemeType .LIGHT )
104+ init_opts = opts .InitOpts (
105+ width = "100%" ,
106+ height = "400px" ,
107+ theme = ThemeType .LIGHT ,
108+ animation_opts = opts .AnimationOpts (animation = False ), # Desativa animações
109+ js_host = "https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/" , # Usa CDN em vez de embutir
110+ )
69111 )
70112 .add_xaxis (dates )
71113 .add_yaxis (
@@ -117,54 +159,115 @@ def create_chart(data):
117159 html = bar .render_embed ()
118160 return base64 .b64encode (html .encode ("utf-8" )).decode ("utf-8" )
119161
120-
121162# Função assíncrona para carregar o gráfico
122163async def load_chart (page , chart_container ):
123- # Exibir mensagem de carregamento
124- progress_ring = ft .ProgressRing (
125- width = 32 , height = 32 , stroke_width = 4 , color = COLORS ["primary" ]
126- )
127- chart_container .content = ft .Column (
128- [
129- ft .Text ("Carregando dados..." , color = COLORS ["text_secondary" ], font_family = "Roboto" ),
130- progress_ring ,
131- ],
132- alignment = "center" ,
133- horizontal_alignment = "center" ,
134- )
135- page .update ()
136-
137- # Buscar os dados
138- data = await fetch_usd_brl_data ()
139- if not data :
140- chart_container .content = ft .Text (
141- "Erro ao carregar dados" , color = COLORS ["error" ], font_family = "Roboto"
164+ try :
165+ # Exibir mensagem de carregamento
166+ progress_ring = ft .ProgressRing (
167+ width = 32 , height = 32 , stroke_width = 4 , color = COLORS ["primary" ]
168+ )
169+ chart_container .content = ft .Column (
170+ [
171+ ft .Text ("Carregando dados..." , color = COLORS ["text_secondary" ], font_family = "Roboto" ),
172+ progress_ring ,
173+ ],
174+ alignment = "center" ,
175+ horizontal_alignment = "center" ,
142176 )
143177 page .update ()
144- return
145178
146- # Atualizar para renderização
147- chart_container .content = ft .Column (
148- [
149- ft .Text ("Renderizando gráfico..." , color = COLORS ["text_secondary" ], font_family = "Roboto" ),
150- progress_ring ,
151- ],
152- alignment = "center" ,
153- horizontal_alignment = "center" ,
154- )
155- page .update ()
179+ # Buscar os dados (usa cache se disponível)
180+ data = await fetch_usd_brl_data ()
181+ if not data :
182+ chart_container .content = ft .Column (
183+ [
184+ ft .Text (
185+ "Erro ao carregar dados" ,
186+ color = COLORS ["error" ],
187+ size = 16 ,
188+ font_family = "Roboto" ,
189+ text_align = "center" ,
190+ ),
191+ ft .ElevatedButton (
192+ "Tentar Novamente" ,
193+ on_click = lambda _ : page .run_task (lambda : load_chart (page , chart_container )),
194+ bgcolor = COLORS ["primary" ],
195+ color = ft .Colors .WHITE ,
196+ ),
197+ ],
198+ alignment = "center" ,
199+ horizontal_alignment = "center" ,
200+ spacing = 20 ,
201+ )
202+ page .update ()
203+ return
156204
157- # Criar e exibir o gráfico
158- encoded_html = create_chart ( data )
159- data_url = f"data:text/html;base64, { encoded_html } "
160- chart_webview = ft .WebView (
161- url = data_url ,
162- expand = True ,
163- bgcolor = COLORS [ "surface" ] ,
164- )
165- chart_container . content = chart_webview
166- page .update ()
205+ # Atualizar para renderização
206+ chart_container . content = ft . Column (
207+ [
208+ ft .Text ( "Renderizando gráfico..." , color = COLORS [ "text_secondary" ], font_family = "Roboto" ),
209+ progress_ring ,
210+ ] ,
211+ alignment = "center" ,
212+ horizontal_alignment = "center" ,
213+ )
214+ page .update ()
167215
216+ # Criar e exibir o gráfico
217+ encoded_html = create_chart (data )
218+ if encoded_html :
219+ data_url = f"data:text/html;base64,{ encoded_html } "
220+ chart_webview = ft .WebView (
221+ url = data_url ,
222+ expand = True ,
223+ bgcolor = COLORS ["surface" ],
224+ )
225+ chart_container .content = chart_webview
226+ else :
227+ chart_container .content = ft .Column (
228+ [
229+ ft .Text (
230+ "Erro ao renderizar gráfico" ,
231+ color = COLORS ["error" ],
232+ size = 16 ,
233+ font_family = "Roboto" ,
234+ text_align = "center" ,
235+ ),
236+ ft .ElevatedButton (
237+ "Tentar Novamente" ,
238+ on_click = lambda _ : page .run_task (lambda : load_chart (page , chart_container )),
239+ bgcolor = COLORS ["primary" ],
240+ color = ft .Colors .WHITE ,
241+ ),
242+ ],
243+ alignment = "center" ,
244+ horizontal_alignment = "center" ,
245+ spacing = 20 ,
246+ )
247+ page .update ()
248+
249+ except Exception as e :
250+ chart_container .content = ft .Column (
251+ [
252+ ft .Text (
253+ f"Erro inesperado: { str (e )} " ,
254+ color = COLORS ["error" ],
255+ size = 16 ,
256+ font_family = "Roboto" ,
257+ text_align = "center" ,
258+ ),
259+ ft .ElevatedButton (
260+ "Tentar Novamente" ,
261+ on_click = lambda _ : page .run_task (lambda : load_chart (page , chart_container )),
262+ bgcolor = COLORS ["primary" ],
263+ color = ft .Colors .WHITE ,
264+ ),
265+ ],
266+ alignment = "center" ,
267+ horizontal_alignment = "center" ,
268+ spacing = 20 ,
269+ )
270+ page .update ()
168271
169272# Função principal do conteúdo da página
170273def currency_chart_content (page : ft .Page ):
@@ -177,11 +280,24 @@ def currency_chart_content(page: ft.Page):
177280 height = 400 ,
178281 )
179282
283+ # Inicializar com mensagem de carregamento
284+ progress_ring = ft .ProgressRing (
285+ width = 32 , height = 32 , stroke_width = 4 , color = COLORS ["primary" ]
286+ )
287+ chart_container .content = ft .Column (
288+ [
289+ ft .Text ("Carregando dados..." , color = COLORS ["text_secondary" ], font_family = "Roboto" ),
290+ progress_ring ,
291+ ],
292+ alignment = "center" ,
293+ horizontal_alignment = "center" ,
294+ )
295+
180296 async def init_chart ():
181297 await load_chart (page , chart_container )
182298
183299 # Carregar o gráfico de forma assíncrona
184- page .add_async_callback (init_chart )
300+ page .run_task (init_chart )
185301
186302 return ft .Container (
187303 content = ft .Column (
0 commit comments