Skip to content

Commit a9a69e0

Browse files
committed
ruff and pytest fixes
1 parent 058a9d8 commit a9a69e0

File tree

2 files changed

+128
-36
lines changed

2 files changed

+128
-36
lines changed

metrics/interfaces/management/commands/seed_random.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from collections.abc import Callable, Iterable
44
from datetime import date, timedelta
55
from decimal import Decimal
6+
from operator import itemgetter
67
from typing import override
78

89
from django.core.management import CommandParser, call_command
@@ -120,7 +121,7 @@ def _seed_metrics_data(
120121
scale_config: dict[str, int],
121122
truncate_first: bool,
122123
progress_callback: Callable[[str], None] | None = None,
123-
) -> dict[str, int]:
124+
) -> dict[str, int]: # noqa: PLR0914
124125
"""Seed supporting metric models and time series rows for the selected scale."""
125126
if progress_callback is not None:
126127
progress_callback("Preparing metric taxonomy and geography records...")
@@ -251,7 +252,7 @@ def _seed_time_series_rows(
251252
age: Age,
252253
days: int,
253254
progress_callback: Callable[[str], None] | None = None,
254-
) -> tuple[int, int]:
255+
) -> tuple[int, int]: # noqa: PLR0914
255256
frequency = TimePeriod.Weekly.value
256257
today = date.today()
257258
start_date = today - timedelta(days=days - 1)
@@ -399,13 +400,15 @@ def _build_theme_hierarchy_records(
399400

400401
parent_theme_name = child_to_parent[sub_theme_name]
401402
sub_theme_pairs.add((sub_theme_name, parent_theme_name))
402-
for topic_value in topic_group.return_list():
403-
topic_rows.append((topic_value, sub_theme_name, parent_theme_name))
403+
topic_rows.extend(
404+
(topic_value, sub_theme_name, parent_theme_name)
405+
for topic_value in topic_group.return_list()
406+
)
404407

405408
theme_names = sorted({parent_name for _, parent_name in sub_theme_pairs})
406409
sub_theme_rows = sorted(
407410
sub_theme_pairs,
408-
key=lambda value: (value[1], value[0]),
411+
key=itemgetter(1, 0),
409412
)
410413
return theme_names, sub_theme_rows, topic_rows
411414

tests/unit/metrics/interfaces/management/test_seed_random.py

Lines changed: 120 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
from unittest import mock
55

66
import pytest
7-
from django.core.management.base import CommandError
87
from django.core.management import CommandParser
8+
from django.core.management.base import CommandError
99

10-
from metrics.interfaces.management.commands.seed_random import Command, SCALE_CONFIGS
10+
from metrics.interfaces.management.commands.seed_random import SCALE_CONFIGS, Command
1111

1212
MODULE_PATH = "metrics.interfaces.management.commands.seed_random"
13+
FULL_BATCH_DAYS = 5000
14+
SMALL_GEO_COUNT = 3
15+
LARGE_GEO_COUNT = 7
1316

1417

1518
def _fake_metric_hierarchy() -> SimpleNamespace:
@@ -70,6 +73,7 @@ def test_handle_metrics_dataset(
7073
spy_seed_metrics_data.assert_called_once_with(
7174
scale_config=SCALE_CONFIGS["small"],
7275
truncate_first=True,
76+
progress_callback=mock.ANY,
7377
)
7478
spy_call_command.assert_not_called()
7579
spy_print_summary.assert_called_once_with(
@@ -121,81 +125,129 @@ def test_handle_cms_dataset_uses_time_seed_and_builds_cms(
121125

122126
@mock.patch.object(Command, "_truncate_metrics_data")
123127
@mock.patch.object(Command, "_seed_time_series_rows")
128+
@mock.patch.object(Command, "_build_geography_seed_values")
129+
@mock.patch.object(Command, "_build_theme_hierarchy_records")
124130
@mock.patch.object(Command, "_bulk_create")
125131
@mock.patch(f"{MODULE_PATH}.Geography")
126132
@mock.patch(f"{MODULE_PATH}.Metric")
127133
@mock.patch(f"{MODULE_PATH}.Topic")
128134
@mock.patch(f"{MODULE_PATH}.SubTheme")
129135
@mock.patch(f"{MODULE_PATH}.Theme")
136+
@mock.patch(f"{MODULE_PATH}.GeographyType")
130137
@mock.patch(f"{MODULE_PATH}.transaction.atomic")
131-
@mock.patch(f"{MODULE_PATH}.GeographyType.objects.create")
132138
@mock.patch(f"{MODULE_PATH}.Stratum.objects.create")
133139
@mock.patch(f"{MODULE_PATH}.Age.objects.create")
134140
def test_seed_metrics_data_builds_expected_counts_and_calls(
135141
self,
136142
spy_age_create: mock.MagicMock,
137143
spy_stratum_create: mock.MagicMock,
138-
spy_geography_type_create: mock.MagicMock,
139144
spy_atomic: mock.MagicMock,
145+
spy_geography_type: mock.MagicMock,
140146
spy_theme: mock.MagicMock,
141147
spy_sub_theme: mock.MagicMock,
142148
spy_topic: mock.MagicMock,
143149
spy_metric: mock.MagicMock,
144150
spy_geography: mock.MagicMock,
145151
spy_bulk_create: mock.MagicMock,
152+
spy_build_theme_hierarchy_records: mock.MagicMock,
153+
spy_build_geography_seed_values: mock.MagicMock,
146154
spy_seed_time_series_rows: mock.MagicMock,
147155
spy_truncate: mock.MagicMock,
148156
):
157+
spy_progress_callback = mock.MagicMock()
149158
spy_atomic.return_value = nullcontext()
150-
spy_theme.side_effect = lambda **kwargs: SimpleNamespace(**kwargs)
151-
spy_sub_theme.side_effect = lambda **kwargs: SimpleNamespace(**kwargs)
152-
spy_topic.side_effect = lambda **kwargs: SimpleNamespace(**kwargs)
153-
spy_metric.side_effect = lambda **kwargs: SimpleNamespace(**kwargs)
154-
spy_geography.side_effect = lambda **kwargs: SimpleNamespace(**kwargs)
155-
spy_geography_type_create.return_value = SimpleNamespace(name="Nation")
159+
spy_geography_type.side_effect = SimpleNamespace
160+
spy_theme.side_effect = SimpleNamespace
161+
spy_sub_theme.side_effect = SimpleNamespace
162+
spy_topic.side_effect = SimpleNamespace
163+
spy_metric.side_effect = SimpleNamespace
164+
spy_geography.side_effect = SimpleNamespace
156165
spy_stratum_create.return_value = SimpleNamespace(name="All")
157166
spy_age_create.return_value = SimpleNamespace(name="All ages")
158167
spy_seed_time_series_rows.return_value = (77, 88)
168+
spy_build_theme_hierarchy_records.return_value = (
169+
["infectious_disease", "climate_and_environment"],
170+
[
171+
("respiratory", "infectious_disease"),
172+
("vectors", "climate_and_environment"),
173+
],
174+
[
175+
("COVID-19", "respiratory", "infectious_disease"),
176+
("ticks", "vectors", "climate_and_environment"),
177+
],
178+
)
179+
spy_build_geography_seed_values.return_value = [
180+
{
181+
"name": "England",
182+
"geography_code": "E92000001",
183+
"geography_type": "Nation",
184+
},
185+
{
186+
"name": "Area 2",
187+
"geography_code": "E09000002",
188+
"geography_type": "Lower Tier Local Authority",
189+
},
190+
]
159191

160-
themes = [SimpleNamespace(name=f"Theme {index + 1}") for index in range(3)]
192+
themes = [
193+
SimpleNamespace(name="infectious_disease"),
194+
SimpleNamespace(name="climate_and_environment"),
195+
]
161196
sub_themes = [
162-
SimpleNamespace(
163-
name=f"SubTheme {index + 1}", theme=themes[index % len(themes)]
164-
)
165-
for index in range(6)
197+
SimpleNamespace(name="respiratory", theme=themes[0]),
198+
SimpleNamespace(name="vectors", theme=themes[1]),
166199
]
167200
topics = [
168201
SimpleNamespace(
169-
name=f"Topic {index + 1}",
170-
sub_theme=sub_themes[index % len(sub_themes)],
171-
)
172-
for index in range(12)
202+
name="COVID-19",
203+
sub_theme=sub_themes[0],
204+
),
205+
SimpleNamespace(
206+
name="ticks",
207+
sub_theme=sub_themes[1],
208+
),
173209
]
174210
metrics = [
175211
SimpleNamespace(
176212
name=f"Metric {index + 1}", topic=topics[index % len(topics)]
177213
)
178214
for index in range(4)
179215
]
216+
geography_types = [
217+
SimpleNamespace(name="Nation"),
218+
SimpleNamespace(name="Lower Tier Local Authority"),
219+
]
180220
geographies = [
181221
SimpleNamespace(
182-
name=f"Area {index + 1}",
183-
geography_code=f"RND{index + 1:04d}",
184-
geography_type=spy_geography_type_create.return_value,
185-
)
186-
for index in range(2)
222+
name="England",
223+
geography_code="E92000001",
224+
geography_type=geography_types[0],
225+
),
226+
SimpleNamespace(
227+
name="Area 2",
228+
geography_code="E09000002",
229+
geography_type=geography_types[1],
230+
),
231+
]
232+
spy_bulk_create.side_effect = [
233+
themes,
234+
sub_themes,
235+
topics,
236+
metrics,
237+
geography_types,
238+
geographies,
187239
]
188-
spy_bulk_create.side_effect = [themes, sub_themes, topics, metrics, geographies]
189240

190241
result = Command._seed_metrics_data(
191242
scale_config={"geographies": 2, "metrics": 4, "days": 9},
192243
truncate_first=True,
244+
progress_callback=spy_progress_callback,
193245
)
194246

195247
assert result == {
196-
"Theme": 3,
197-
"SubTheme": 6,
198-
"Topic": 12,
248+
"Theme": 2,
249+
"SubTheme": 2,
250+
"Topic": 2,
199251
"Metric": 4,
200252
"Geography": 2,
201253
"CoreTimeSeries": 77,
@@ -208,7 +260,12 @@ def test_seed_metrics_data_builds_expected_counts_and_calls(
208260
stratum=spy_stratum_create.return_value,
209261
age=spy_age_create.return_value,
210262
days=9,
263+
progress_callback=spy_progress_callback,
211264
)
265+
spy_progress_callback.assert_any_call(
266+
"Preparing metric taxonomy and geography records..."
267+
)
268+
spy_progress_callback.assert_any_call("Generating Core/API time series rows...")
212269

213270
def test_truncate_metrics_data_deletes_from_all_models(self):
214271
model_names = [
@@ -248,19 +305,24 @@ def test_seed_time_series_rows_flushes_remainder(
248305
):
249306
spy_core_time_series.side_effect = lambda **kwargs: kwargs
250307
spy_api_time_series.side_effect = lambda **kwargs: kwargs
308+
spy_progress_callback = mock.MagicMock()
251309

252310
core_count, api_count = Command._seed_time_series_rows(
253311
metrics=[_fake_metric_hierarchy()],
254312
geographies=[_fake_geography()],
255313
stratum=SimpleNamespace(name="All"),
256314
age=SimpleNamespace(name="All ages"),
257315
days=1,
316+
progress_callback=spy_progress_callback,
258317
)
259318

260319
assert core_count == 1
261320
assert api_count == 1
262321
spy_core_time_series.objects.bulk_create.assert_called_once()
263322
spy_api_time_series.objects.bulk_create.assert_called_once()
323+
progress_messages = [call.args[0] for call in spy_progress_callback.call_args_list]
324+
assert any(message.startswith("Processed 1/1 metrics") for message in progress_messages)
325+
assert any(message.startswith("Inserted ") for message in progress_messages)
264326

265327
@mock.patch(f"{MODULE_PATH}.APITimeSeries")
266328
@mock.patch(f"{MODULE_PATH}.CoreTimeSeries")
@@ -277,11 +339,11 @@ def test_seed_time_series_rows_flushes_at_batch_size(
277339
geographies=[_fake_geography()],
278340
stratum=SimpleNamespace(name="All"),
279341
age=SimpleNamespace(name="All ages"),
280-
days=5000,
342+
days=FULL_BATCH_DAYS,
281343
)
282344

283-
assert core_count == 5000
284-
assert api_count == 5000
345+
assert core_count == FULL_BATCH_DAYS
346+
assert api_count == FULL_BATCH_DAYS
285347
spy_core_time_series.objects.bulk_create.assert_called_once()
286348
spy_api_time_series.objects.bulk_create.assert_called_once()
287349

@@ -346,3 +408,30 @@ def test_add_arguments_rejects_invalid_dataset_value():
346408

347409
with pytest.raises(CommandError):
348410
parser.parse_args(["--dataset", "invalid"])
411+
412+
413+
def test_build_theme_hierarchy_records_contains_expected_real_values():
414+
theme_names, sub_theme_rows, topic_rows = Command._build_theme_hierarchy_records()
415+
416+
assert "infectious_disease" in theme_names
417+
assert any(sub_theme == "respiratory" for sub_theme, _ in sub_theme_rows)
418+
assert any(topic == "COVID-19" and sub_theme == "respiratory" for topic, sub_theme, _ in topic_rows)
419+
420+
421+
def test_build_geography_seed_values_returns_required_count():
422+
small_geographies = Command._build_geography_seed_values(count=SMALL_GEO_COUNT)
423+
larger_geographies = Command._build_geography_seed_values(count=LARGE_GEO_COUNT)
424+
425+
assert len(small_geographies) == SMALL_GEO_COUNT
426+
assert len(larger_geographies) == LARGE_GEO_COUNT
427+
assert small_geographies[0]["name"] == "United Kingdom"
428+
assert larger_geographies[-1]["geography_type"] in {
429+
"Nation",
430+
"Lower Tier Local Authority",
431+
}
432+
433+
434+
def test_format_enum_name_replaces_underscores_and_title_cases():
435+
assert Command._format_enum_name("LOWER_TIER_LOCAL_AUTHORITY") == (
436+
"Lower Tier Local Authority"
437+
)

0 commit comments

Comments
 (0)