Skip to content

Commit 17b3759

Browse files
authored
fix: Regression on 2.3.2 that broke Enum support (#458)
* fix: Regression on 2.3.2 that broke Enum support
1 parent dc8e773 commit 17b3759

3 files changed

Lines changed: 42 additions & 23 deletions

File tree

statemachine/states.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ def __eq__(self, other):
5454
return list(self) == list(other)
5555

5656
def __getattr__(self, name: str):
57-
name = name.lower()
5857
if name in self._states:
5958
return self._states[name]
6059
raise AttributeError(f"{name} not found in {self.__class__.__name__}")
@@ -81,7 +80,7 @@ def items(self):
8180
return self._states.items()
8281

8382
@classmethod
84-
def from_enum(cls, enum_type: EnumType, initial, final=None):
83+
def from_enum(cls, enum_type: EnumType, initial, final=None, use_enum_instance: bool = False):
8584
"""
8685
Creates a new instance of the ``States`` class from an enumeration.
8786
@@ -125,20 +124,37 @@ def from_enum(cls, enum_type: EnumType, initial, final=None):
125124
True
126125
127126
>>> sm.current_state_value
127+
2
128+
129+
If you need to use the enum instance as the state value, you can set the
130+
``use_enum_instance=True``:
131+
132+
>>> states = States.from_enum(Status, initial=Status.pending, use_enum_instance=True)
133+
>>> states.completed.value
128134
<Status.completed: 2>
129135
136+
.. deprecated:: 2.3.3
137+
138+
On the next major release, the ``use_enum_instance=True`` will be the default.
139+
130140
Args:
131141
enum_type: An enumeration containing the states of the machine.
132142
initial: The initial state of the machine.
133143
final: A set of final states of the machine.
144+
use_enum_instance: If ``True``, the value of the state will be the enum item instance,
145+
otherwise the enum item value.
134146
135147
Returns:
136148
A new instance of the :ref:`States (class)`.
137149
"""
138150
final_set = set(ensure_iterable(final))
139151
return cls(
140152
{
141-
e.name.lower(): State(value=e, initial=e is initial, final=e in final_set)
153+
e.name: State(
154+
value=(e if use_enum_instance else e.value),
155+
initial=e is initial,
156+
final=e in final_set,
157+
)
142158
for e in enum_type
143159
}
144160
)

tests/django_project/workflow/statemachines.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
class WorfklowStateMachine(StateMachine):
88
_ = States.from_enum(WorkflowSteps, initial=WorkflowSteps.DRAFT, final=WorkflowSteps.PUBLISHED)
99

10-
publish = _.draft.to(_.published, cond="is_active")
11-
notify_user = _.draft.to.itself(internal=True, cond="has_user")
10+
publish = _.DRAFT.to(_.PUBLISHED, cond="is_active")
11+
notify_user = _.DRAFT.to.itself(internal=True, cond="has_user")
1212

1313
def has_user(self):
1414
return bool(self.model.user)

tests/examples/enum_campaign_machine.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,37 @@
1414

1515

1616
class CampaignStatus(Enum):
17-
draft = 1
18-
producing = 2
19-
closed = 3
17+
DRAFT = 1
18+
PRODUCING = 2
19+
CLOSED = 3
2020

2121

2222
class CampaignMachine(StateMachine):
2323
"A workflow machine"
2424

2525
states = States.from_enum(
26-
CampaignStatus, initial=CampaignStatus.draft, final=CampaignStatus.closed
26+
CampaignStatus,
27+
initial=CampaignStatus.DRAFT,
28+
final=CampaignStatus.CLOSED,
29+
use_enum_instance=True,
2730
)
2831

29-
add_job = states.draft.to(states.draft) | states.producing.to(states.producing)
30-
produce = states.draft.to(states.producing)
31-
deliver = states.producing.to(states.closed)
32+
add_job = states.DRAFT.to(states.DRAFT) | states.PRODUCING.to(states.PRODUCING)
33+
produce = states.DRAFT.to(states.PRODUCING)
34+
deliver = states.PRODUCING.to(states.CLOSED)
3235

3336

3437
# %%
3538
# Asserting campaign machine declaration
3639

37-
assert CampaignMachine.draft.initial
38-
assert not CampaignMachine.draft.final
40+
assert CampaignMachine.DRAFT.initial
41+
assert not CampaignMachine.DRAFT.final
3942

40-
assert not CampaignMachine.producing.initial
41-
assert not CampaignMachine.producing.final
43+
assert not CampaignMachine.PRODUCING.initial
44+
assert not CampaignMachine.PRODUCING.final
4245

43-
assert not CampaignMachine.closed.initial
44-
assert CampaignMachine.closed.final
46+
assert not CampaignMachine.CLOSED.initial
47+
assert CampaignMachine.CLOSED.final
4548

4649

4750
# %%
@@ -50,8 +53,8 @@ class CampaignMachine(StateMachine):
5053
sm = CampaignMachine()
5154
res = sm.send("produce")
5255

53-
assert sm.draft.is_active is False
54-
assert sm.producing.is_active is True
55-
assert sm.closed.is_active is False
56-
assert sm.current_state == sm.producing
57-
assert sm.current_state_value == CampaignStatus.producing
56+
assert sm.DRAFT.is_active is False
57+
assert sm.PRODUCING.is_active is True
58+
assert sm.CLOSED.is_active is False
59+
assert sm.current_state == sm.PRODUCING
60+
assert sm.current_state_value == CampaignStatus.PRODUCING

0 commit comments

Comments
 (0)