Skip to content

Commit c200694

Browse files
committed
chore: refactor using plain old classes
1 parent d7abe10 commit c200694

3 files changed

Lines changed: 101 additions & 94 deletions

File tree

juju/model/__init__.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3300,24 +3300,24 @@ async def new_wait_for_idle(
33003300

33013301
started = time.monotonic()
33023302
deadline = None if timeout is None else started + timeout
3303+
loop = _idle.Loop(
3304+
apps=apps,
3305+
wait_for_exact_units=wait_for_exact_units,
3306+
wait_for_units=wait_for_units,
3307+
idle_period=idle_period,
3308+
)
33033309

3304-
async def status_on_demand():
3305-
while True:
3306-
yield _idle.check(
3310+
while True:
3311+
done = loop.next(
3312+
_idle.check(
33073313
await self.get_status(),
33083314
apps=apps,
33093315
raise_on_error=raise_on_error,
33103316
raise_on_blocked=raise_on_blocked,
33113317
status=status,
33123318
)
3319+
)
33133320

3314-
async for done in _idle.loop(
3315-
status_on_demand(),
3316-
apps=apps,
3317-
wait_for_exact_units=wait_for_exact_units,
3318-
wait_for_units=wait_for_units,
3319-
idle_period=idle_period,
3320-
):
33213321
logger.info(f"wait_for_idle start{time.monotonic() - started:+.1f} {done=}")
33223322
if done:
33233323
break

juju/model/_idle.py

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import logging
88
import time
99
from dataclasses import dataclass
10-
from typing import AbstractSet, AsyncIterable
10+
from typing import AbstractSet
1111

1212
from ..client._definitions import (
1313
ApplicationStatus,
@@ -32,70 +32,72 @@ class CheckStatus:
3232
"""Units with stable agent status (FIXME details)."""
3333

3434

35-
async def loop(
36-
foo: AsyncIterable[CheckStatus | None],
37-
*,
38-
apps: AbstractSet[str],
39-
wait_for_exact_units: int | None = None,
40-
wait_for_units: int,
41-
idle_period: float,
42-
) -> AsyncIterable[bool]:
43-
"""The outer, time-dependents logic of a wait_for_idle loop."""
44-
idle_since: dict[str, float] = {}
45-
46-
async for status in foo:
35+
class Loop:
36+
def __init__(
37+
self,
38+
*,
39+
apps: AbstractSet[str],
40+
wait_for_exact_units: int | None = None,
41+
wait_for_units: int,
42+
idle_period: float,
43+
):
44+
self.apps = apps
45+
self.wait_for_exact_units = wait_for_exact_units
46+
self.wait_for_units = wait_for_units
47+
self.idle_period = idle_period
48+
self.idle_since: dict[str, float] = {}
49+
50+
def next(self, status: CheckStatus | None) -> bool:
4751
logger.info("wait_for_idle iteration %s", status)
4852
now = time.monotonic()
4953

5054
if not status:
51-
yield False
52-
continue
55+
return False
5356

54-
expected_idle_since = now - idle_period
57+
expected_idle_since = now - self.idle_period
5558

5659
# FIXME there's some confusion about what a "busy" unit is
5760
# are we ready when over the last idle_period, every time sampled:
5861
# a. >=N units were ready (possibly different each time), or
5962
# b. >=N units were ready each time
6063
for name in status.units:
6164
if name in status.idle_units:
62-
idle_since[name] = min(now, idle_since.get(name, float("inf")))
65+
self.idle_since[name] = min(
66+
now, self.idle_since.get(name, float("inf"))
67+
)
6368
else:
64-
idle_since[name] = float("inf")
69+
self.idle_since[name] = float("inf")
6570

66-
if busy := {n for n, t in idle_since.items() if t > expected_idle_since}:
71+
if busy := {n for n, t in self.idle_since.items() if t > expected_idle_since}:
6772
logger.info("Waiting for units to be idle enough: %s", busy)
68-
yield False
69-
continue
73+
return False
7074

71-
for app_name in apps:
75+
for app_name in self.apps:
7276
ready_units = [
7377
n for n in status.ready_units if n.startswith(f"{app_name}/")
7478
]
75-
if len(ready_units) < wait_for_units:
79+
if len(ready_units) < self.wait_for_units:
7680
logger.info(
7781
"Waiting for app %r units %s >= %s",
7882
app_name,
7983
len(status.ready_units),
80-
wait_for_units,
84+
self.wait_for_units,
8185
)
82-
yield False
83-
break
86+
return False
8487

8588
if (
86-
wait_for_exact_units is not None
87-
and len(ready_units) != wait_for_exact_units
89+
self.wait_for_exact_units is not None
90+
and len(ready_units) != self.wait_for_exact_units
8891
):
8992
logger.info(
9093
"Waiting for app %r units %s == %s",
9194
app_name,
9295
len(ready_units),
93-
wait_for_exact_units,
96+
self.wait_for_exact_units,
9497
)
95-
yield False
96-
break
97-
else:
98-
yield True
98+
return False
99+
100+
return True
99101

100102

101103
def check(

tests/unit/test_idle_loop.py

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,61 @@
22
# Licensed under the Apache V2, see LICENCE file for details.
33
from __future__ import annotations
44

5-
from freezegun import freeze_time
6-
7-
from juju.model._idle import CheckStatus, loop
5+
from typing import AbstractSet, Iterable
86

7+
from freezegun import freeze_time
98

10-
async def alist(agen):
11-
return [v async for v in agen]
9+
from juju.model._idle import CheckStatus, Loop
10+
11+
12+
def unroll(
13+
statuses: Iterable[CheckStatus | None],
14+
*,
15+
apps: AbstractSet[str],
16+
wait_for_exact_units: int | None = None,
17+
wait_for_units: int,
18+
idle_period: float,
19+
) -> list[bool]:
20+
loop = Loop(
21+
apps=apps,
22+
wait_for_exact_units=wait_for_exact_units,
23+
wait_for_units=wait_for_units,
24+
idle_period=idle_period,
25+
)
26+
return [loop.next(s) for s in statuses]
1227

1328

14-
async def test_wait_for_apps():
15-
async def checks():
29+
def test_wait_for_apps():
30+
def checks():
1631
yield None
1732
yield None
1833

19-
assert await alist(
20-
loop(
21-
checks(),
22-
apps={"a"},
23-
wait_for_units=0,
24-
idle_period=0,
25-
)
34+
assert unroll(
35+
checks(),
36+
apps={"a"},
37+
wait_for_units=0,
38+
idle_period=0,
2639
) == [False, False]
2740

2841

29-
async def test_at_least_units():
30-
async def checks():
42+
def test_at_least_units():
43+
def checks():
3144
yield CheckStatus({"u/0", "u/1", "u/2"}, {"u/0"}, {"u/0", "u/1", "u/2"})
3245
yield CheckStatus({"u/0", "u/1", "u/2"}, {"u/0", "u/1"}, {"u/0", "u/1", "u/2"})
3346
yield CheckStatus(
3447
{"u/0", "u/1", "u/2"}, {"u/0", "u/1", "u/2"}, {"u/0", "u/1", "u/2"}
3548
)
3649

3750
with freeze_time():
38-
assert await alist(
39-
loop(
40-
checks(),
41-
apps={"u"},
42-
wait_for_units=2,
43-
idle_period=0,
44-
)
51+
assert unroll(
52+
checks(),
53+
apps={"u"},
54+
wait_for_units=2,
55+
idle_period=0,
4556
) == [False, True, True]
4657

4758

48-
async def test_for_exact_units():
59+
def test_for_exact_units():
4960
good = CheckStatus(
5061
{"u/0", "u/1", "u/2"},
5162
{"u/1", "u/2"},
@@ -62,55 +73,49 @@ async def test_for_exact_units():
6273
{"u/0", "u/1", "u/2"},
6374
)
6475

65-
async def checks():
76+
def checks():
6677
yield too_few
6778
yield good
6879
yield too_many
6980
yield good
7081

71-
assert await alist(
72-
loop(
73-
checks(),
74-
apps={"u"},
75-
wait_for_units=1,
76-
wait_for_exact_units=2,
77-
idle_period=0,
78-
)
82+
assert unroll(
83+
checks(),
84+
apps={"u"},
85+
wait_for_units=1,
86+
wait_for_exact_units=2,
87+
idle_period=0,
7988
) == [False, True, False, True]
8089

8190

82-
async def test_idle_ping_pong():
91+
def test_idle_ping_pong():
8392
good = CheckStatus({"hexanator/0"}, {"hexanator/0"}, {"hexanator/0"})
8493
bad = CheckStatus({"hexanator/0"}, {"hexanator/0"}, set())
8594

86-
async def checks():
95+
def checks():
8796
with freeze_time() as clock:
8897
for status in [good, bad, good, bad]:
8998
yield status
9099
clock.tick(10)
91100

92-
assert await alist(
93-
loop(
94-
checks(),
95-
apps={"hexanator"},
96-
wait_for_units=1,
97-
idle_period=15,
98-
)
101+
assert unroll(
102+
checks(),
103+
apps={"hexanator"},
104+
wait_for_units=1,
105+
idle_period=15,
99106
) == [False, False, False, False]
100107

101108

102-
async def test_idle_period():
103-
async def checks():
109+
def test_idle_period():
110+
def checks():
104111
with freeze_time() as clock:
105112
for _ in range(4):
106113
yield CheckStatus({"hexanator/0"}, {"hexanator/0"}, {"hexanator/0"})
107114
clock.tick(10)
108115

109-
assert await alist(
110-
loop(
111-
checks(),
112-
apps={"hexanator"},
113-
wait_for_units=1,
114-
idle_period=15,
115-
)
116+
assert unroll(
117+
checks(),
118+
apps={"hexanator"},
119+
wait_for_units=1,
120+
idle_period=15,
116121
) == [False, False, True, True]

0 commit comments

Comments
 (0)