Skip to content

Commit d19a0a4

Browse files
committed
chore: eliminate ~1900 test warnings by upgrading deps and fixing deprecated usage
- Bump pytest-asyncio >=0.25.0 and pytest-mock >=3.14.0 to fix ~1800 asyncio DeprecationWarnings on Python 3.14 - Remove deprecated `path` parameter from pytest_ignore_collect (PytestRemovedIn9Warning) - Add filterwarnings for PytestBenchmarkWarning (expected with xdist) - Migrate test_weighted_transitions.py from deprecated `current_state` to `configuration` - Convert release notes 2.3/2.4/2.5 doctests using `current_state` to plain python blocks
1 parent 8d012fe commit d19a0a4

8 files changed

Lines changed: 154 additions & 206 deletions

File tree

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ timeout 120 uv run pytest -n 4
127127

128128
Testes normally run under 60s (~40s on average), so take a closer look if they take longer, it can be a regression.
129129

130+
When analyzing warnings or extensive output, run the tests **once** saving the output to a file
131+
(`> /tmp/pytest-output.txt 2>&1`), then analyze the file — instead of running the suite
132+
repeatedly with different greps.
133+
130134
Coverage is enabled by default (`--cov` is in `pyproject.toml`'s `addopts`). To generate a
131135
coverage report to a file, pass `--cov-report` **in addition to** `--cov`:
132136

conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ def __init__(self):
2929
doctest_namespace["asyncio"] = ContribAsyncio()
3030

3131

32-
def pytest_ignore_collect(collection_path, path, config):
32+
def pytest_ignore_collect(collection_path, config):
3333
if sys.version_info >= (3, 10): # noqa: UP036
3434
return None
3535

36-
if "django_project" in str(path):
36+
if "django_project" in str(collection_path):
3737
return True
3838

3939

docs/releases/2.3.0.md

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,22 @@ async code with a state machine.
2626
```
2727

2828

29-
```py
30-
>>> class AsyncStateMachine(StateMachine):
31-
... initial = State('Initial', initial=True)
32-
... final = State('Final', final=True)
33-
...
34-
... advance = initial.to(final)
35-
...
36-
... async def on_advance(self):
37-
... return 42
29+
```python
30+
class AsyncStateMachine(StateMachine):
31+
initial = State('Initial', initial=True)
32+
final = State('Final', final=True)
3833

34+
advance = initial.to(final)
3935

36+
async def on_advance(self):
37+
return 42
4038

41-
>>> async def run_sm():
42-
... sm = AsyncStateMachine()
43-
... res = await sm.advance()
44-
... return (42, sm.current_state.name)
4539

46-
>>> asyncio.run(run_sm())
47-
(42, 'Final')
40+
async def run_sm():
41+
sm = AsyncStateMachine()
42+
res = await sm.advance()
43+
return (42, sm.current_state.name)
4844

45+
asyncio.run(run_sm())
46+
# (42, 'Final')
4947
```

docs/releases/2.4.0.md

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,29 @@ This release introduces support for conditionals with Boolean algebra. You can n
1616

1717
Example (with a spoiler of the next highlight):
1818

19-
```py
20-
>>> from statemachine import StateMachine, State, Event
19+
```python
20+
from statemachine import StateMachine, State, Event
2121

22-
>>> class AnyConditionSM(StateMachine):
23-
... start = State(initial=True)
24-
... end = State(final=True)
25-
...
26-
... submit = Event(
27-
... start.to(end, cond="used_money or used_credit"),
28-
... name="finish order",
29-
... )
30-
...
31-
... used_money: bool = False
32-
... used_credit: bool = False
22+
class AnyConditionSM(StateMachine):
23+
start = State(initial=True)
24+
end = State(final=True)
25+
26+
submit = Event(
27+
start.to(end, cond="used_money or used_credit"),
28+
name="finish order",
29+
)
3330

34-
>>> sm = AnyConditionSM()
35-
>>> sm.submit()
36-
Traceback (most recent call last):
37-
TransitionNotAllowed: Can't finish order when in Start.
31+
used_money: bool = False
32+
used_credit: bool = False
3833

39-
>>> sm.used_credit = True
40-
>>> sm.submit()
41-
>>> sm.current_state.id
42-
'end'
34+
sm = AnyConditionSM()
35+
sm.submit()
36+
# TransitionNotAllowed: Can't finish order when in Start.
4337

38+
sm.used_credit = True
39+
sm.submit()
40+
sm.current_state.id
41+
# 'end'
4442
```
4543

4644
```{seealso}

docs/releases/2.5.0.md

Lines changed: 35 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -64,46 +64,27 @@ You can think of the event as an implementation of the **command** design patter
6464
On this example, we iterate until the state machine reaches a final state,
6565
listing the current state allowed events and executing the simulated user choice:
6666

67-
```
68-
>>> import random
69-
>>> random.seed("15")
67+
```python
68+
import random
69+
random.seed("15")
7070

71-
>>> sm = AccountStateMachine()
71+
sm = AccountStateMachine()
7272

73-
>>> while not sm.current_state.final:
74-
... allowed_events = sm.allowed_events
75-
... print("Choose an action: ")
76-
... for idx, event in enumerate(allowed_events):
77-
... print(f"{idx} - {event.name}")
78-
...
79-
... user_input = random.randint(0, len(allowed_events)-1)
80-
... print(f"User input: {user_input}")
81-
...
82-
... event = allowed_events[user_input]
83-
... print(f"Running the option {user_input} - {event.name}")
84-
... event()
85-
Choose an action:
86-
0 - Suspend
87-
1 - Overdraft
88-
2 - Close account
89-
User input: 0
90-
Running the option 0 - Suspend
91-
Choose an action:
92-
0 - Activate
93-
1 - Close account
94-
User input: 0
95-
Running the option 0 - Activate
96-
Choose an action:
97-
0 - Suspend
98-
1 - Overdraft
99-
2 - Close account
100-
User input: 2
101-
Running the option 2 - Close account
102-
Account has been closed.
73+
while not sm.current_state.final:
74+
allowed_events = sm.allowed_events
75+
print("Choose an action: ")
76+
for idx, event in enumerate(allowed_events):
77+
print(f"{idx} - {event.name}")
78+
79+
user_input = random.randint(0, len(allowed_events)-1)
80+
print(f"User input: {user_input}")
10381

104-
>>> print(f"SM is in {sm.current_state.name} state.")
105-
SM is in Closed state.
82+
event = allowed_events[user_input]
83+
print(f"Running the option {user_input} - {event.name}")
84+
event()
10685

86+
print(f"SM is in {sm.current_state.name} state.")
87+
# SM is in Closed state.
10788
```
10889

10990
### Conditions expressions in 2.5.0
@@ -120,30 +101,28 @@ The following comparison operators are supported:
120101

121102
Example:
122103

123-
```py
124-
>>> from statemachine import StateMachine, State, Event
104+
```python
105+
from statemachine import StateMachine, State, Event
125106

126-
>>> class AnyConditionSM(StateMachine):
127-
... start = State(initial=True)
128-
... end = State(final=True)
129-
...
130-
... submit = Event(
131-
... start.to(end, cond="order_value > 100"),
132-
... name="finish order",
133-
... )
134-
...
135-
... order_value: float = 0
107+
class AnyConditionSM(StateMachine):
108+
start = State(initial=True)
109+
end = State(final=True)
110+
111+
submit = Event(
112+
start.to(end, cond="order_value > 100"),
113+
name="finish order",
114+
)
136115

137-
>>> sm = AnyConditionSM()
138-
>>> sm.submit()
139-
Traceback (most recent call last):
140-
TransitionNotAllowed: Can't finish order when in Start.
116+
order_value: float = 0
141117

142-
>>> sm.order_value = 135.0
143-
>>> sm.submit()
144-
>>> sm.current_state.id
145-
'end'
118+
sm = AnyConditionSM()
119+
sm.submit()
120+
# TransitionNotAllowed: Can't finish order when in Start.
146121

122+
sm.order_value = 135.0
123+
sm.submit()
124+
sm.current_state.id
125+
# 'end'
147126
```
148127

149128
```{seealso}

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ dev = [
3939
"pytest-cov >=6.0.0; python_version >='3.9'",
4040
"pytest-cov; python_version <'3.9'",
4141
"pytest-sugar >=1.0.0",
42-
"pytest-mock >=3.10.0",
42+
"pytest-mock >=3.14.0",
4343
"pytest-benchmark >=4.0.0",
44-
"pytest-asyncio",
44+
"pytest-asyncio >=0.25.0",
4545
"pydot",
4646
"django >=5.2.11; python_version >='3.10'",
4747
"pytest-django >=4.8.0; python_version >'3.8'",
@@ -93,6 +93,9 @@ log_cli_level = "DEBUG"
9393
log_cli_format = "%(relativeCreated)6.0fms %(threadName)-18s %(name)-35s %(message)s"
9494
log_cli_date_format = "%H:%M:%S"
9595
asyncio_default_fixture_loop_scope = "module"
96+
filterwarnings = [
97+
"ignore::pytest_benchmark.logger.PytestBenchmarkWarning",
98+
]
9699

97100
[tool.coverage.run]
98101
branch = true

tests/test_weighted_transitions.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@ class TestWeightedTransitionsBasic:
2222
def test_deterministic_with_seed(self, WeightedIdleSC):
2323
sm = WeightedIdleSC()
2424
sm.send("idle")
25-
first_state = sm.current_state
25+
first_config = sm.configuration
2626

2727
sm.send("finish")
2828
sm.send("idle")
29-
second_state = sm.current_state
29+
second_config = sm.configuration
3030

3131
# With seed=42, results are deterministic
3232
# Create a fresh instance to verify same seed produces same sequence
3333
sm2 = WeightedIdleSC()
3434
sm2.send("idle")
35-
assert sm2.current_state == first_state
35+
assert sm2.configuration == first_config
3636
sm2.send("finish")
3737
sm2.send("idle")
38-
assert sm2.current_state == second_state
38+
assert sm2.configuration == second_config
3939

4040
def test_statistical_distribution(self, WeightedIdleSC):
4141
"""Over many iterations, the distribution should approximate the weights."""
@@ -45,7 +45,7 @@ def test_statistical_distribution(self, WeightedIdleSC):
4545

4646
for _ in range(iterations):
4747
sm.send("idle")
48-
counts[sm.current_state.id] += 1
48+
counts[next(iter(sm.configuration)).id] += 1
4949
sm.send("finish")
5050

5151
# With 70/20/10 weights, check roughly correct distribution (within 5%)
@@ -63,7 +63,7 @@ class SingleWeighted(StateChart):
6363

6464
sm = SingleWeighted()
6565
sm.send("go")
66-
assert sm.current_state == SingleWeighted.s2
66+
assert sm.configuration == {SingleWeighted.s2}
6767

6868
def test_equal_weights(self):
6969
class EqualWeights(StateChart):
@@ -80,7 +80,7 @@ class EqualWeights(StateChart):
8080

8181
for _ in range(iterations):
8282
sm.send("go")
83-
counts[sm.current_state.id] += 1
83+
counts[next(iter(sm.configuration)).id] += 1
8484
sm.send("back")
8585

8686
# Should be roughly 50/50 within 5%
@@ -102,7 +102,7 @@ class FloatWeights(StateChart):
102102

103103
for _ in range(iterations):
104104
sm.send("go")
105-
counts[sm.current_state.id] += 1
105+
counts[next(iter(sm.configuration)).id] += 1
106106
sm.send("back")
107107

108108
assert abs(counts["s2"] / iterations - 0.70) < 0.05
@@ -119,7 +119,7 @@ class MixedWeights(StateChart):
119119

120120
sm = MixedWeights()
121121
sm.send("go")
122-
assert sm.current_state in (MixedWeights.s2, MixedWeights.s3)
122+
assert sm.configuration & {MixedWeights.s2, MixedWeights.s3}
123123

124124

125125
class TestWeightedTransitionsWithGuards:
@@ -147,7 +147,7 @@ def is_allowed(self):
147147
counts = Counter()
148148
for _ in range(1000):
149149
sm.send("go")
150-
counts[sm.current_state.id] += 1
150+
counts[next(iter(sm.configuration)).id] += 1
151151
sm.send("back")
152152

153153
assert counts["s2"] > 0
@@ -175,7 +175,7 @@ def is_blocked(self):
175175

176176
# When not blocked, s2 can fire
177177
sm.send("go")
178-
first_state = sm.current_state
178+
first_state = next(iter(sm.configuration))
179179
sm.send("back")
180180

181181
# When blocked, s2 cond fails even if weight selects it
@@ -184,7 +184,7 @@ def is_blocked(self):
184184
for _ in range(100):
185185
try:
186186
sm.send("go")
187-
results[sm.current_state.id] += 1
187+
results[next(iter(sm.configuration)).id] += 1
188188
sm.send("back")
189189
except Exception:
190190
results["failed"] += 1
@@ -427,12 +427,12 @@ class MultiGroup(StateChart):
427427
sm = MultiGroup()
428428

429429
sm.send("go_a")
430-
state_a = sm.current_state
430+
state_a = next(iter(sm.configuration))
431431
assert state_a in (MultiGroup.s2, MultiGroup.s3)
432432
sm.send("back")
433433

434434
sm.send("go_b")
435-
state_b = sm.current_state
435+
state_b = next(iter(sm.configuration))
436436
assert state_b in (MultiGroup.s4, MultiGroup.s5)
437437

438438

0 commit comments

Comments
 (0)