kuhl-haus-mdp 0.1.10__tar.gz → 0.1.11__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.
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/PKG-INFO +1 -1
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/pyproject.toml +1 -1
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/analyzers/top_stocks.py +3 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/market_data_cache.py +22 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_cache_ttl.py +0 -1
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/components/test_market_data_cache.py +102 -1
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/LICENSE.txt +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/README.md +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/analyzers/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/analyzers/analyzer.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/analyzers/massive_data_analyzer.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/market_data_scanner.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/widget_data_service.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/helpers/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/helpers/process_manager.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/helpers/queue_name_resolver.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/helpers/utils.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_listener.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_processor.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_queues.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/web_socket_message_serde.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/constants.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_analyzer_result.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_cache_keys.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_pubsub_keys.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_scanner_names.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/massive_data_queue.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/top_stocks_cache_item.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/analyzers/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/analyzers/test_massive_data_analyzer.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/analyzers/test_top_stocks_rehydrate.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/components/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/components/test_market_data_scanner.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/components/test_widget_data_service.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/helpers/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/helpers/test_process_manager.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/helpers/test_queue_name_resolver.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/helpers/test_utils.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/integ/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/integ/test_web_socket_message_serde.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/models/__init__.py +0 -0
- {kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/tests/models/test_top_stocks_cache_item.py +0 -0
|
@@ -136,6 +136,9 @@ class TopStocksAnalyzer(Analyzer):
|
|
|
136
136
|
prev_day_volume = snapshot.prev_day.volume
|
|
137
137
|
prev_day_vwap = snapshot.prev_day.vwap
|
|
138
138
|
break
|
|
139
|
+
except KeyError:
|
|
140
|
+
retry_count += 1
|
|
141
|
+
await self.cache.delete_ticker_snapshot(event.symbol)
|
|
139
142
|
except Exception as e:
|
|
140
143
|
self.logger.error(f"Failed to get snapshot for {event.symbol}: {e}")
|
|
141
144
|
retry_count += 1
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/market_data_cache.py
RENAMED
|
@@ -26,6 +26,18 @@ class MarketDataCache:
|
|
|
26
26
|
self.redis_client = redis_client
|
|
27
27
|
self.http_session = None
|
|
28
28
|
|
|
29
|
+
async def delete_cache(self, cache_key: str):
|
|
30
|
+
"""
|
|
31
|
+
Delete cache entry.
|
|
32
|
+
|
|
33
|
+
:arg cache_key: Cache key to delete
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
await self.redis_client.delete(cache_key)
|
|
37
|
+
self.logger.info(f"Deleted cache entry: {cache_key}")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
self.logger.error(f"Error deleting cache entry: {e}")
|
|
40
|
+
|
|
29
41
|
async def get_cache(self, cache_key: str) -> Optional[dict]:
|
|
30
42
|
"""Fetch current value from Redis cache (for snapshot requests)."""
|
|
31
43
|
value = await self.redis_client.get(cache_key)
|
|
@@ -44,6 +56,16 @@ class MarketDataCache:
|
|
|
44
56
|
await self.redis_client.publish(publish_key, json.dumps(data))
|
|
45
57
|
self.logger.info(f"Published data for {publish_key}")
|
|
46
58
|
|
|
59
|
+
async def delete_ticker_snapshot(self, ticker: str):
|
|
60
|
+
"""
|
|
61
|
+
Delete ticker snapshot from cache.
|
|
62
|
+
|
|
63
|
+
:param ticker: symbol of ticker to delete snapshot for
|
|
64
|
+
:return: None
|
|
65
|
+
"""
|
|
66
|
+
cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
|
|
67
|
+
await self.delete_cache(cache_key=cache_key)
|
|
68
|
+
|
|
47
69
|
async def get_ticker_snapshot(self, ticker: str) -> TickerSnapshot:
|
|
48
70
|
self.logger.info(f"Getting snapshot for {ticker}")
|
|
49
71
|
cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
|
|
@@ -781,4 +781,105 @@ async def test_get_http_session_singleton_behavior(mock_client_session):
|
|
|
781
781
|
# Assert
|
|
782
782
|
mock_client_session.assert_called_once() # Should only be called once
|
|
783
783
|
assert result1 == result2 == result3 == mock_session # All should return same instance
|
|
784
|
-
assert id(result1) == id(result2) == id(result3) # Verify same object in memory
|
|
784
|
+
assert id(result1) == id(result2) == id(result3) # Verify same object in memory
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
@pytest.mark.asyncio
|
|
788
|
+
async def test_delete_ticker_snapshot_with_valid_ticker_expect_cache_deleted():
|
|
789
|
+
# Arrange
|
|
790
|
+
mock_redis_client = AsyncMock()
|
|
791
|
+
mock_rest_client = MagicMock()
|
|
792
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
793
|
+
ticker = "TEST"
|
|
794
|
+
cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
|
|
795
|
+
|
|
796
|
+
# Act
|
|
797
|
+
await sut.delete_ticker_snapshot(ticker)
|
|
798
|
+
|
|
799
|
+
# Assert
|
|
800
|
+
mock_redis_client.delete.assert_awaited_once_with(cache_key)
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
@pytest.mark.asyncio
|
|
804
|
+
async def test_delete_ticker_snapshot_with_empty_ticker_expect_no_side_effect():
|
|
805
|
+
# Arrange
|
|
806
|
+
mock_redis_client = AsyncMock()
|
|
807
|
+
mock_rest_client = MagicMock()
|
|
808
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
809
|
+
ticker = ""
|
|
810
|
+
cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
|
|
811
|
+
|
|
812
|
+
# Act
|
|
813
|
+
await sut.delete_ticker_snapshot(ticker)
|
|
814
|
+
|
|
815
|
+
# Assert
|
|
816
|
+
mock_redis_client.delete.assert_awaited_once_with(cache_key)
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
@pytest.mark.asyncio
|
|
820
|
+
@patch("logging.Logger.error")
|
|
821
|
+
async def test_delete_ticker_snapshot_with_redis_error_expect_logged_error(mock_logger):
|
|
822
|
+
# Arrange
|
|
823
|
+
mock_redis_client = AsyncMock()
|
|
824
|
+
mock_redis_client.delete = AsyncMock(side_effect=Exception("Redis connection error"))
|
|
825
|
+
mock_rest_client = MagicMock()
|
|
826
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
827
|
+
ticker = "TEST"
|
|
828
|
+
cache_key = f"{MarketDataCacheKeys.TICKER_SNAPSHOTS.value}:{ticker}"
|
|
829
|
+
|
|
830
|
+
# Act
|
|
831
|
+
await sut.delete_ticker_snapshot(ticker)
|
|
832
|
+
|
|
833
|
+
# Assert
|
|
834
|
+
mock_redis_client.delete.assert_awaited_once_with(cache_key)
|
|
835
|
+
mock_logger.assert_called_once_with("Error deleting cache entry: Redis connection error")
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
@pytest.mark.asyncio
|
|
839
|
+
async def test_delete_cache_with_existing_key_expect_cache_deleted():
|
|
840
|
+
# Arrange
|
|
841
|
+
mock_redis_client = AsyncMock()
|
|
842
|
+
mock_rest_client = MagicMock()
|
|
843
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
844
|
+
test_cache_key = "test:cache:key"
|
|
845
|
+
|
|
846
|
+
# Act
|
|
847
|
+
await sut.delete_cache(test_cache_key)
|
|
848
|
+
|
|
849
|
+
# Assert
|
|
850
|
+
mock_redis_client.delete.assert_awaited_once_with(test_cache_key)
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
@pytest.mark.asyncio
|
|
854
|
+
@patch("logging.Logger.error")
|
|
855
|
+
async def test_delete_cache_with_redis_error_expect_error_logged(mock_logger):
|
|
856
|
+
# Arrange
|
|
857
|
+
mock_redis_client = AsyncMock()
|
|
858
|
+
mock_redis_client.delete = AsyncMock(side_effect=Exception("Redis connection error"))
|
|
859
|
+
mock_rest_client = MagicMock()
|
|
860
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
861
|
+
test_cache_key = "test:cache:key"
|
|
862
|
+
|
|
863
|
+
# Act
|
|
864
|
+
await sut.delete_cache(test_cache_key)
|
|
865
|
+
|
|
866
|
+
# Assert
|
|
867
|
+
mock_redis_client.delete.assert_awaited_once_with(test_cache_key)
|
|
868
|
+
mock_logger.assert_called_once_with(f"Error deleting cache entry: Redis connection error")
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
@pytest.mark.asyncio
|
|
872
|
+
@patch("logging.Logger.info")
|
|
873
|
+
async def test_delete_cache_with_successful_deletion_expect_info_logged(mock_logger):
|
|
874
|
+
# Arrange
|
|
875
|
+
mock_redis_client = AsyncMock()
|
|
876
|
+
mock_rest_client = MagicMock()
|
|
877
|
+
sut = MarketDataCache(rest_client=mock_rest_client, redis_client=mock_redis_client, massive_api_key="test_key")
|
|
878
|
+
test_cache_key = "test:cache:key"
|
|
879
|
+
|
|
880
|
+
# Act
|
|
881
|
+
await sut.delete_cache(test_cache_key)
|
|
882
|
+
|
|
883
|
+
# Assert
|
|
884
|
+
mock_redis_client.delete.assert_awaited_once_with(test_cache_key)
|
|
885
|
+
mock_logger.assert_called_once_with(f"Deleted cache entry: {test_cache_key}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/analyzers/massive_data_analyzer.py
RENAMED
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/market_data_scanner.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/components/widget_data_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/helpers/queue_name_resolver.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_listener.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_processor.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/massive_data_queues.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/integ/web_socket_message_serde.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_cache_keys.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_pubsub_keys.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/market_data_scanner_names.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/massive_data_queue.py
RENAMED
|
File without changes
|
{kuhl_haus_mdp-0.1.10 → kuhl_haus_mdp-0.1.11}/src/kuhl_haus/mdp/models/top_stocks_cache_item.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|