Skip to content

Commit dab2d2d

Browse files
PabloLIONclaude
andcommitted
fix: Complete final TypedDict migration and resolve type errors
- Update reader.py UsageEntryMapper.map to use RawJSONData parameter - Update data_processors.py flatten_nested_dict to use RawJSONData with proper casting - Update test fixtures in test_display_controller.py to use proper BlockDict structure - Update test fixtures in conftest.py sample_session_data to use RawJSONData - Keep appropriate generic types for invalid test data and raw API structures - Add proper type casting in recursive flatten_nested_dict calls - All mypy type checking now passes without errors 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c04a033 commit dab2d2d

File tree

4 files changed

+69
-19
lines changed

4 files changed

+69
-19
lines changed

src/claude_monitor/core/data_processors.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
ExtractedTokens,
1313
FlattenedData,
1414
JSONSerializable,
15+
RawJSONData,
1516
TokenSource,
1617
)
1718
from claude_monitor.utils.time_utils import TimezoneHandler
@@ -232,7 +233,7 @@ class DataConverter:
232233

233234
@staticmethod
234235
def flatten_nested_dict(
235-
data: dict[str, JSONSerializable], prefix: str = ""
236+
data: RawJSONData, prefix: str = ""
236237
) -> FlattenedData:
237238
"""Flatten nested dictionary structure.
238239
@@ -249,7 +250,7 @@ def flatten_nested_dict(
249250
new_key = f"{prefix}.{key}" if prefix else key
250251

251252
if isinstance(value, dict):
252-
result.update(DataConverter.flatten_nested_dict(value, new_key))
253+
result.update(DataConverter.flatten_nested_dict(cast(RawJSONData, value), new_key))
253254
else:
254255
# Use type: ignore for dynamic key assignment in TypedDict
255256
result[new_key] = value # type: ignore[literal-required]

src/claude_monitor/data/reader.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
ClaudeJSONEntry,
2424
EntryData,
2525
ExtractedMetadata,
26-
JSONSerializable,
2726
RawJSONData,
2827
SystemEntry,
2928
UserEntry,
@@ -409,13 +408,11 @@ def __init__(
409408
self.timezone_handler = timezone_handler
410409

411410
def map(
412-
self, data: dict[str, JSONSerializable], mode: CostMode
411+
self, data: RawJSONData, mode: CostMode
413412
) -> UsageEntry | None:
414413
"""Map raw data to UsageEntry - compatibility interface."""
415-
# Cast to RawJSONData since this is test compatibility interface
416-
from typing import cast
417414
return _map_to_usage_entry(
418-
cast(RawJSONData, data), mode, self.timezone_handler, self.pricing_calculator
415+
data, mode, self.timezone_handler, self.pricing_calculator
419416
)
420417

421418
def _has_valid_tokens(self, tokens: dict[str, int]) -> bool:

src/tests/conftest.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
"""Shared pytest fixtures for Claude Monitor tests."""
22

3-
from datetime import datetime, timezone
3+
from datetime import datetime
4+
from datetime import timezone
45
from unittest.mock import Mock
56

67
import pytest
78

8-
from claude_monitor.core.models import CostMode, UsageEntry
9-
from claude_monitor.types import AnalysisResult, JSONSerializable, RawJSONData
9+
from claude_monitor.core.models import CostMode
10+
from claude_monitor.core.models import UsageEntry
11+
from claude_monitor.types import AnalysisResult
12+
from claude_monitor.types import JSONSerializable
13+
from claude_monitor.types import RawJSONData
1014

1115

1216
@pytest.fixture
@@ -24,7 +28,9 @@ def mock_timezone_handler() -> Mock:
2428
mock.parse_timestamp.return_value = datetime(
2529
2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc
2630
)
27-
mock.ensure_utc.return_value = datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc)
31+
mock.ensure_utc.return_value = datetime(
32+
2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc
33+
)
2834
return mock
2935

3036

@@ -323,7 +329,7 @@ def sample_monitoring_data() -> AnalysisResult:
323329

324330

325331
@pytest.fixture
326-
def sample_session_data() -> dict[str, JSONSerializable]:
332+
def sample_session_data() -> RawJSONData:
327333
"""Sample session data for testing."""
328334
return {
329335
"id": "session_1",

src/tests/test_display_controller.py

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import pytest
77

8-
from claude_monitor.types import JSONSerializable
8+
from claude_monitor.types import BlockDict
99
from claude_monitor.ui.display_controller import (
1010
DisplayController,
1111
LiveDisplayManager,
@@ -23,23 +23,69 @@ def controller(self) -> DisplayController:
2323
return DisplayController()
2424

2525
@pytest.fixture
26-
def sample_active_block(self) -> dict[str, JSONSerializable]:
26+
def sample_active_block(self) -> BlockDict:
2727
"""Sample active block data."""
2828
return {
29+
"id": "test-block-1",
2930
"isActive": True,
31+
"isGap": False,
3032
"totalTokens": 15000,
3133
"costUSD": 0.45,
3234
"sentMessagesCount": 12,
35+
"models": ["claude-3-opus", "claude-3-5-sonnet"],
36+
"durationMinutes": 120.0,
37+
"entries_count": 2,
38+
"tokenCounts": {
39+
"inputTokens": 9000,
40+
"outputTokens": 6000,
41+
"cacheCreationInputTokens": 0,
42+
"cacheReadInputTokens": 0,
43+
},
3344
"perModelStats": {
34-
"claude-3-opus": {"inputTokens": 5000, "outputTokens": 3000},
35-
"claude-3-5-sonnet": {"inputTokens": 4000, "outputTokens": 3000},
45+
"claude-3-opus": {
46+
"input_tokens": 5000,
47+
"output_tokens": 3000,
48+
"cache_creation_tokens": 0,
49+
"cache_read_tokens": 0,
50+
"cost_usd": 0.25,
51+
"entries_count": 1,
52+
},
53+
"claude-3-5-sonnet": {
54+
"input_tokens": 4000,
55+
"output_tokens": 3000,
56+
"cache_creation_tokens": 0,
57+
"cache_read_tokens": 0,
58+
"cost_usd": 0.20,
59+
"entries_count": 1,
60+
},
3661
},
3762
"entries": [
38-
{"timestamp": "2024-01-01T12:00:00Z", "tokens": 5000},
39-
{"timestamp": "2024-01-01T12:30:00Z", "tokens": 10000},
63+
{
64+
"timestamp": "2024-01-01T12:00:00Z",
65+
"inputTokens": 5000,
66+
"outputTokens": 3000,
67+
"cacheCreationTokens": 0,
68+
"cacheReadInputTokens": 0,
69+
"costUSD": 0.25,
70+
"model": "claude-3-opus",
71+
"messageId": "msg-1",
72+
"requestId": "req-1",
73+
},
74+
{
75+
"timestamp": "2024-01-01T12:30:00Z",
76+
"inputTokens": 4000,
77+
"outputTokens": 3000,
78+
"cacheCreationTokens": 0,
79+
"cacheReadInputTokens": 0,
80+
"costUSD": 0.20,
81+
"model": "claude-3-5-sonnet",
82+
"messageId": "msg-2",
83+
"requestId": "req-2",
84+
},
4085
],
4186
"startTime": "2024-01-01T11:00:00Z",
4287
"endTime": "2024-01-01T13:00:00Z",
88+
"actualEndTime": "2024-01-01T12:45:00Z",
4389
}
4490

4591
@pytest.fixture
@@ -64,7 +110,7 @@ def test_init(self, controller: DisplayController) -> None:
64110
def test_extract_session_data(
65111
self,
66112
controller: DisplayController,
67-
sample_active_block: dict[str, JSONSerializable],
113+
sample_active_block: BlockDict,
68114
) -> None:
69115
"""Test session data extraction."""
70116
result = controller._extract_session_data(sample_active_block) # type: ignore[misc]

0 commit comments

Comments
 (0)