|
4 | 4 | import math |
5 | 5 | from dataclasses import dataclass, field |
6 | 6 | from functools import cached_property |
7 | | -from typing import TYPE_CHECKING, Any |
| 7 | +from typing import TYPE_CHECKING, TypedDict |
8 | 8 |
|
9 | 9 | if TYPE_CHECKING: |
10 | 10 | from microbenchmark.scenario import Scenario |
11 | 11 |
|
12 | 12 |
|
| 13 | +class _ScenarioMeta(TypedDict): |
| 14 | + name: str |
| 15 | + doc: str |
| 16 | + number: int |
| 17 | + |
| 18 | + |
| 19 | +class _ResultJson(TypedDict): |
| 20 | + durations: list[float] |
| 21 | + is_primary: bool |
| 22 | + scenario: _ScenarioMeta | None |
| 23 | + |
| 24 | + |
13 | 25 | @dataclass |
14 | 26 | class BenchmarkResult: |
15 | 27 | scenario: Scenario | None |
@@ -45,28 +57,37 @@ def p99(self) -> BenchmarkResult: |
45 | 57 | return self.percentile(99) |
46 | 58 |
|
47 | 59 | def to_json(self) -> str: |
48 | | - scenario_data: dict[str, Any] | None |
| 60 | + scenario_meta: _ScenarioMeta | None |
49 | 61 | if self.scenario is not None: |
50 | | - scenario_data = { |
51 | | - 'name': self.scenario.name, |
52 | | - 'doc': self.scenario.doc, |
53 | | - 'number': self.scenario.number, |
54 | | - } |
| 62 | + scenario_meta = _ScenarioMeta( |
| 63 | + name=self.scenario.name, |
| 64 | + doc=self.scenario.doc, |
| 65 | + number=self.scenario.number, |
| 66 | + ) |
55 | 67 | else: |
56 | | - scenario_data = None |
57 | | - return json.dumps({ |
58 | | - 'durations': list(self.durations), |
59 | | - 'is_primary': self.is_primary, |
60 | | - 'scenario': scenario_data, |
61 | | - }) |
| 68 | + scenario_meta = None |
| 69 | + data: _ResultJson = _ResultJson( |
| 70 | + durations=list(self.durations), |
| 71 | + is_primary=self.is_primary, |
| 72 | + scenario=scenario_meta, |
| 73 | + ) |
| 74 | + return json.dumps(data) |
62 | 75 |
|
63 | 76 | @classmethod |
64 | 77 | def from_json(cls, data: str) -> BenchmarkResult: |
65 | | - parsed = json.loads(data) |
66 | | - if 'durations' not in parsed or 'is_primary' not in parsed: |
| 78 | + raw: object = json.loads(data) |
| 79 | + if not isinstance(raw, dict): |
| 80 | + raise ValueError('JSON must be an object') |
| 81 | + if 'durations' not in raw or 'is_primary' not in raw: |
67 | 82 | raise ValueError('JSON is missing required fields: durations, is_primary') |
| 83 | + raw_durations = raw['durations'] |
| 84 | + raw_is_primary = raw['is_primary'] |
| 85 | + if not isinstance(raw_durations, list): |
| 86 | + raise ValueError('durations must be a list') |
| 87 | + if not isinstance(raw_is_primary, bool): |
| 88 | + raise ValueError('is_primary must be a bool') |
68 | 89 | return cls( |
69 | 90 | scenario=None, |
70 | | - durations=tuple(float(d) for d in parsed['durations']), |
71 | | - is_primary=bool(parsed['is_primary']), |
| 91 | + durations=tuple(float(d) for d in raw_durations), |
| 92 | + is_primary=raw_is_primary, |
72 | 93 | ) |
0 commit comments