Skip to content

Commit d446d99

Browse files
authored
fix: Regression that broke sync callbacks interacting with Django ORM. Closes #446 (#452)
1 parent 2669b92 commit d446d99

5 files changed

Lines changed: 34 additions & 4 deletions

File tree

docs/releases/2.3.2.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ See {ref}`listeners` for more details.
4343

4444
## Bugfixes in 2.3.2
4545

46+
- Fixes [#446](https://github.com/fgmacedo/python-statemachine/issues/446): Regression that broke sync callbacks
47+
interacting with Django ORM due to the added async support and
48+
[Django's async safety guards](https://docs.djangoproject.com/en/5.1/topics/async/#async-safety).
4649
- Fixes [#449](https://github.com/fgmacedo/python-statemachine/issues/449): Regression that did not trigger events
4750
in nested calls within an already running transition.
4851

statemachine/signature.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
from types import MethodType
88
from typing import Any
99

10+
try:
11+
# Compatibility with Django ORM
12+
# https://docs.djangoproject.com/en/5.1/topics/async/#async-safety
13+
from asgiref.sync import sync_to_async
14+
except ImportError: # pragma: no cover
15+
sync_to_async = None # type: ignore[assignment]
16+
1017

1118
def _make_key(method):
1219
method = method.func if isinstance(method, partial) else method
@@ -52,7 +59,12 @@ def wrap(cls, method):
5259

5360
metadata_to_copy = method.func if isinstance(method, partial) else method
5461

55-
if iscoroutinefunction(method):
62+
is_coroutine = iscoroutinefunction(method)
63+
if not is_coroutine and sync_to_async is not None:
64+
method = sync_to_async(method)
65+
is_coroutine = True
66+
67+
if is_coroutine:
5668

5769
async def method_wrapper(*args: Any, **kwargs: Any) -> Any:
5870
ba = sig_bind_expected(*args, **kwargs)

tests/conftest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sys
22
from datetime import datetime
33
from typing import List
4+
from unittest.mock import patch
45

56
import pytest
67

@@ -197,3 +198,9 @@ def retry(self):
197198
return self.model
198199

199200
return ApprovalMachine
201+
202+
203+
@pytest.fixture(autouse=True, scope="module")
204+
def _mock_sync_to_async():
205+
with patch("statemachine.signature.sync_to_async", None):
206+
yield

tests/django_project/core/settings.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
DATABASES = {
1919
"default": {
2020
"ENGINE": "django.db.backends.sqlite3",
21-
"NAME": ":memory:",
21+
"NAME": BASE_DIR / "db.sqlite3",
22+
"TEST": {
23+
"NAME": "testdb.sqlite3",
24+
},
2225
}
2326
}
2427

tests/django_project/workflow/tests.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44
from workflow.statemachines import WorfklowStateMachine
55

66
pytestmark = [
7-
pytest.mark.django_db,
7+
pytest.mark.django_db(transaction=True),
88
]
99

1010

11+
@pytest.fixture(autouse=True, scope="module")
12+
def _mock_sync_to_async():
13+
"""Override the mock so the sync callbacks are decorated with sync_to_async"""
14+
15+
1116
@pytest.fixture()
1217
def Workflow():
1318
from workflow.models import Workflow
@@ -39,7 +44,6 @@ def test_two(self, one):
3944
with pytest.raises(TransitionNotAllowed):
4045
wf.send("publish")
4146

42-
@pytest.mark.xfail(reason="This test is a regression on 2.3.0+ due to asyncio support.")
4347
def test_async_with_db_operation(self, one, User, Workflow):
4448
"""Regression test for https://github.com/fgmacedo/python-statemachine/issues/446"""
4549

@@ -52,5 +56,6 @@ def test_async_with_db_operation(self, one, User, Workflow):
5256

5357
# And clear model cache, casing user to be loaded later on
5458
one = Workflow.objects.get(pk=one.pk)
59+
5560
wf = WorfklowStateMachine(one)
5661
wf.send("notify_user")

0 commit comments

Comments
 (0)