Skip to content

Commit 027f691

Browse files
style: format code with black and isort
Co-authored-by: merendamattia <[email protected]>
1 parent 1136a0f commit 027f691

File tree

2 files changed

+79
-69
lines changed

2 files changed

+79
-69
lines changed

src/tools/analyze_financial_asset.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
This module provides a single tool that combines price retrieval and return calculation
55
for a comprehensive financial asset analysis.
66
"""
7+
78
import logging
89
import time
910
from datetime import datetime, timedelta
@@ -47,24 +48,27 @@ def _get_cached_analysis(ticker: str, years: int) -> Optional[str]:
4748
Optional[str]: Cached JSON response or None if not found
4849
"""
4950
cache_key = _get_cache_key(ticker, years)
50-
51+
5152
# Try to get from Streamlit session state first (if available)
5253
try:
5354
import streamlit as st
54-
if hasattr(st, 'session_state') and hasattr(st.session_state, 'financial_asset_cache'):
55+
56+
if hasattr(st, "session_state") and hasattr(
57+
st.session_state, "financial_asset_cache"
58+
):
5559
cache = st.session_state.financial_asset_cache
5660
if cache_key in cache:
5761
logger.info("Cache HIT for %s (from session_state)", cache_key)
5862
return cache[cache_key]
5963
except (ImportError, RuntimeError):
6064
# Streamlit not available or not in a session context
6165
pass
62-
66+
6367
# Fall back to module-level cache
6468
if cache_key in _CACHE:
6569
logger.info("Cache HIT for %s (from module cache)", cache_key)
6670
return _CACHE[cache_key]
67-
71+
6872
logger.info("Cache MISS for %s", cache_key)
6973
return None
7074

@@ -79,26 +83,29 @@ def _set_cached_analysis(ticker: str, years: int, result: str) -> None:
7983
result: JSON response to cache
8084
"""
8185
cache_key = _get_cache_key(ticker, years)
82-
86+
8387
# Try to store in Streamlit session state first (if available)
8488
try:
8589
import streamlit as st
86-
if hasattr(st, 'session_state'):
87-
if not hasattr(st.session_state, 'financial_asset_cache'):
90+
91+
if hasattr(st, "session_state"):
92+
if not hasattr(st.session_state, "financial_asset_cache"):
8893
st.session_state.financial_asset_cache = {}
8994
st.session_state.financial_asset_cache[cache_key] = result
9095
logger.debug("Cached result for %s in session_state", cache_key)
9196
except (ImportError, RuntimeError):
9297
# Streamlit not available or not in a session context
9398
pass
94-
99+
95100
# Also store in module-level cache as fallback
96101
_CACHE[cache_key] = result
97102
logger.debug("Cached result for %s in module cache", cache_key)
98103

99104

100105
@tool
101-
def analyze_financial_asset(ticker: str, years: int = 10, use_cache: bool = True) -> str:
106+
def analyze_financial_asset(
107+
ticker: str, years: int = 10, use_cache: bool = True
108+
) -> str:
102109
"""
103110
Comprehensive financial asset analysis tool.
104111
@@ -132,8 +139,13 @@ def analyze_financial_asset(ticker: str, years: int = 10, use_cache: bool = True
132139
- error: Error message if unsuccessful
133140
"""
134141
try:
135-
logger.info("Starting analysis for %s with %d years (use_cache=%s)", ticker, years, use_cache)
136-
142+
logger.info(
143+
"Starting analysis for %s with %d years (use_cache=%s)",
144+
ticker,
145+
years,
146+
use_cache,
147+
)
148+
137149
# Check cache if enabled
138150
if use_cache:
139151
cached_result = _get_cached_analysis(ticker, years)
@@ -226,11 +238,11 @@ def analyze_financial_asset(ticker: str, years: int = 10, use_cache: bool = True
226238

227239
logger.info("Analysis completed successfully")
228240
result_json = response.model_dump_json()
229-
241+
230242
# Cache the successful result
231243
if use_cache:
232244
_set_cached_analysis(ticker, years, result_json)
233-
245+
234246
return result_json
235247

236248
except Exception as e:
@@ -483,9 +495,11 @@ def get_price_at_date(target_date):
483495
logger.debug(
484496
"%d-year return: %.2f%%",
485497
year,
486-
returns_dict[f"{year}_year"]
487-
if isinstance(returns_dict[f"{year}_year"], (int, float))
488-
else None,
498+
(
499+
returns_dict[f"{year}_year"]
500+
if isinstance(returns_dict[f"{year}_year"], (int, float))
501+
else None
502+
),
489503
)
490504
else:
491505
returns_dict[f"{year}_year"] = "N/A"

tests/unit/test_analyze_financial_asset_cache.py

Lines changed: 49 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -60,37 +60,37 @@ def test_cache_set_and_get(self):
6060
"""Test that we can set and retrieve cached values."""
6161
test_data = '{"success": true, "ticker": "SWDA"}'
6262
_set_cached_analysis("SWDA", 10, test_data)
63-
63+
6464
result = _get_cached_analysis("SWDA", 10)
6565
assert result == test_data
6666

6767
def test_cache_different_tickers(self):
6868
"""Test that cache correctly handles different tickers."""
6969
data1 = '{"success": true, "ticker": "SWDA"}'
7070
data2 = '{"success": true, "ticker": "AAPL"}'
71-
71+
7272
_set_cached_analysis("SWDA", 10, data1)
7373
_set_cached_analysis("AAPL", 10, data2)
74-
74+
7575
assert _get_cached_analysis("SWDA", 10) == data1
7676
assert _get_cached_analysis("AAPL", 10) == data2
7777

7878
def test_cache_different_years(self):
7979
"""Test that cache correctly handles different year periods."""
8080
data1 = '{"success": true, "years": 5}'
8181
data2 = '{"success": true, "years": 10}'
82-
82+
8383
_set_cached_analysis("SWDA", 5, data1)
8484
_set_cached_analysis("SWDA", 10, data2)
85-
85+
8686
assert _get_cached_analysis("SWDA", 5) == data1
8787
assert _get_cached_analysis("SWDA", 10) == data2
8888

8989
def test_cache_uppercase_normalization(self):
9090
"""Test that cache normalizes ticker case."""
9191
test_data = '{"success": true, "ticker": "SWDA"}'
9292
_set_cached_analysis("swda", 10, test_data)
93-
93+
9494
# Should be able to retrieve with different case
9595
result = _get_cached_analysis("SWDA", 10)
9696
assert result == test_data
@@ -107,139 +107,133 @@ def teardown_method(self):
107107
"""Clear cache after each test."""
108108
_CACHE.clear()
109109

110-
@patch('src.tools.analyze_financial_asset._search_and_resolve_symbol')
111-
@patch('src.tools.analyze_financial_asset._get_historical_prices_internal')
112-
@patch('src.tools.analyze_financial_asset._calculate_returns_internal')
110+
@patch("src.tools.analyze_financial_asset._search_and_resolve_symbol")
111+
@patch("src.tools.analyze_financial_asset._get_historical_prices_internal")
112+
@patch("src.tools.analyze_financial_asset._calculate_returns_internal")
113113
def test_cache_miss_calls_functions(
114114
self, mock_calc_returns, mock_get_prices, mock_search_symbol
115115
):
116116
"""Test that on cache miss, all internal functions are called."""
117117
# Setup mocks
118118
mock_search_symbol.return_value = MagicMock(
119-
success=True,
120-
found_symbol="SWDA.DE",
121-
company_name="Test ETF"
119+
success=True, found_symbol="SWDA.DE", company_name="Test ETF"
122120
)
123121
mock_get_prices.return_value = {
124122
"success": True,
125123
"prices": [
126124
{"date": "2020-01-01", "close_price": 100.0},
127-
{"date": "2021-01-01", "close_price": 110.0}
125+
{"date": "2021-01-01", "close_price": 110.0},
128126
],
129127
"data_points": 2,
130128
"years_available": 1.0,
131129
"start_date": "2020-01-01",
132-
"end_date": "2021-01-01"
130+
"end_date": "2021-01-01",
133131
}
134132
mock_calc_returns.return_value = {
135133
"success": True,
136134
"returns": {"1_year": 10.0},
137-
"total_return": 10.0
135+
"total_return": 10.0,
138136
}
139137

140138
# First call should miss cache and call functions
141139
result1 = analyze_financial_asset("SWDA", years=10, use_cache=True)
142-
140+
143141
# Verify functions were called
144142
assert mock_search_symbol.called
145143
assert mock_get_prices.called
146144
assert mock_calc_returns.called
147-
145+
148146
# Verify result is valid JSON
149147
data = json.loads(result1)
150148
assert data["success"] is True
151149
assert data["ticker"] == "SWDA"
152150

153-
@patch('src.tools.analyze_financial_asset._search_and_resolve_symbol')
154-
@patch('src.tools.analyze_financial_asset._get_historical_prices_internal')
155-
@patch('src.tools.analyze_financial_asset._calculate_returns_internal')
151+
@patch("src.tools.analyze_financial_asset._search_and_resolve_symbol")
152+
@patch("src.tools.analyze_financial_asset._get_historical_prices_internal")
153+
@patch("src.tools.analyze_financial_asset._calculate_returns_internal")
156154
def test_cache_hit_skips_functions(
157155
self, mock_calc_returns, mock_get_prices, mock_search_symbol
158156
):
159157
"""Test that on cache hit, internal functions are not called."""
160158
# Setup mocks for first call
161159
mock_search_symbol.return_value = MagicMock(
162-
success=True,
163-
found_symbol="SWDA.DE",
164-
company_name="Test ETF"
160+
success=True, found_symbol="SWDA.DE", company_name="Test ETF"
165161
)
166162
mock_get_prices.return_value = {
167163
"success": True,
168164
"prices": [
169165
{"date": "2020-01-01", "close_price": 100.0},
170-
{"date": "2021-01-01", "close_price": 110.0}
166+
{"date": "2021-01-01", "close_price": 110.0},
171167
],
172168
"data_points": 2,
173169
"years_available": 1.0,
174170
"start_date": "2020-01-01",
175-
"end_date": "2021-01-01"
171+
"end_date": "2021-01-01",
176172
}
177173
mock_calc_returns.return_value = {
178174
"success": True,
179175
"returns": {"1_year": 10.0},
180-
"total_return": 10.0
176+
"total_return": 10.0,
181177
}
182178

183179
# First call - cache miss
184180
result1 = analyze_financial_asset("SWDA", years=10, use_cache=True)
185-
181+
186182
# Reset mock call counts
187183
mock_search_symbol.reset_mock()
188184
mock_get_prices.reset_mock()
189185
mock_calc_returns.reset_mock()
190-
186+
191187
# Second call - should hit cache
192188
result2 = analyze_financial_asset("SWDA", years=10, use_cache=True)
193-
189+
194190
# Verify functions were NOT called on second attempt
195191
assert not mock_search_symbol.called
196192
assert not mock_get_prices.called
197193
assert not mock_calc_returns.called
198-
194+
199195
# Results should be identical
200196
assert result1 == result2
201197

202-
@patch('src.tools.analyze_financial_asset._search_and_resolve_symbol')
203-
@patch('src.tools.analyze_financial_asset._get_historical_prices_internal')
204-
@patch('src.tools.analyze_financial_asset._calculate_returns_internal')
198+
@patch("src.tools.analyze_financial_asset._search_and_resolve_symbol")
199+
@patch("src.tools.analyze_financial_asset._get_historical_prices_internal")
200+
@patch("src.tools.analyze_financial_asset._calculate_returns_internal")
205201
def test_use_cache_false_bypasses_cache(
206202
self, mock_calc_returns, mock_get_prices, mock_search_symbol
207203
):
208204
"""Test that use_cache=False bypasses the cache."""
209205
# Setup mocks
210206
mock_search_symbol.return_value = MagicMock(
211-
success=True,
212-
found_symbol="SWDA.DE",
213-
company_name="Test ETF"
207+
success=True, found_symbol="SWDA.DE", company_name="Test ETF"
214208
)
215209
mock_get_prices.return_value = {
216210
"success": True,
217211
"prices": [
218212
{"date": "2020-01-01", "close_price": 100.0},
219-
{"date": "2021-01-01", "close_price": 110.0}
213+
{"date": "2021-01-01", "close_price": 110.0},
220214
],
221215
"data_points": 2,
222216
"years_available": 1.0,
223217
"start_date": "2020-01-01",
224-
"end_date": "2021-01-01"
218+
"end_date": "2021-01-01",
225219
}
226220
mock_calc_returns.return_value = {
227221
"success": True,
228222
"returns": {"1_year": 10.0},
229-
"total_return": 10.0
223+
"total_return": 10.0,
230224
}
231225

232226
# First call with caching
233227
result1 = analyze_financial_asset("SWDA", years=10, use_cache=True)
234-
228+
235229
# Reset mocks
236230
mock_search_symbol.reset_mock()
237231
mock_get_prices.reset_mock()
238232
mock_calc_returns.reset_mock()
239-
233+
240234
# Second call with use_cache=False should call functions again
241235
result2 = analyze_financial_asset("SWDA", years=10, use_cache=False)
242-
236+
243237
# Verify functions WERE called even though data is cached
244238
assert mock_search_symbol.called
245239
assert mock_get_prices.called
@@ -250,7 +244,7 @@ def test_cache_respects_case_insensitive_ticker(self):
250244
# Pre-populate cache with lowercase ticker
251245
test_data = '{"success": true, "ticker": "swda"}'
252246
_set_cached_analysis("swda", 10, test_data)
253-
247+
254248
# Request with uppercase should hit the same cache
255249
result = _get_cached_analysis("SWDA", 10)
256250
assert result == test_data
@@ -273,30 +267,32 @@ def test_streamlit_cache_storage(self):
273267
mock_st_module = MagicMock()
274268
mock_session_state = MagicMock()
275269
mock_st_module.session_state = mock_session_state
276-
270+
277271
# Patch the import
278-
with patch.dict('sys.modules', {'streamlit': mock_st_module}):
272+
with patch.dict("sys.modules", {"streamlit": mock_st_module}):
279273
test_data = '{"success": true, "ticker": "SWDA"}'
280274
_set_cached_analysis("SWDA", 10, test_data)
281-
275+
282276
# Verify that financial_asset_cache was created and data was stored
283277
# The session_state should have been accessed to set the cache
284-
assert mock_session_state.financial_asset_cache.__setitem__.called or \
285-
hasattr(mock_session_state, 'financial_asset_cache')
278+
assert (
279+
mock_session_state.financial_asset_cache.__setitem__.called
280+
or hasattr(mock_session_state, "financial_asset_cache")
281+
)
286282

287283
def test_streamlit_cache_retrieval(self):
288284
"""Test that cache is retrieved from Streamlit session state when available."""
289285
# Create a mock streamlit module with pre-cached data
290286
test_data = '{"success": true, "ticker": "SWDA"}'
291-
287+
292288
mock_st_module = MagicMock()
293289
mock_session_state = MagicMock()
294290
# Configure the mock to have the cache
295291
mock_session_state.financial_asset_cache = {"SWDA_10": test_data}
296292
mock_st_module.session_state = mock_session_state
297-
293+
298294
# Patch the import
299-
with patch.dict('sys.modules', {'streamlit': mock_st_module}):
295+
with patch.dict("sys.modules", {"streamlit": mock_st_module}):
300296
result = _get_cached_analysis("SWDA", 10)
301297
assert result == test_data
302298

@@ -305,11 +301,11 @@ def test_fallback_to_module_cache_when_no_streamlit(self):
305301
# Store data in module cache
306302
test_data = '{"success": true, "ticker": "SWDA"}'
307303
_set_cached_analysis("SWDA", 10, test_data)
308-
304+
309305
# Retrieve should work even without Streamlit
310306
result = _get_cached_analysis("SWDA", 10)
311307
assert result == test_data
312-
308+
313309
# Verify it's in module cache
314310
assert "SWDA_10" in _CACHE
315311
assert _CACHE["SWDA_10"] == test_data

0 commit comments

Comments
 (0)