Skip to content

Commit

Permalink
fix: Regression on 2.3.2 that broke Enum support (#458)
Browse files Browse the repository at this point in the history
* fix: Regression on 2.3.2 that broke Enum support
  • Loading branch information
fgmacedo authored Jul 3, 2024
1 parent dc8e773 commit 17b3759
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 23 deletions.
22 changes: 19 additions & 3 deletions statemachine/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ def __eq__(self, other):
return list(self) == list(other)

def __getattr__(self, name: str):
name = name.lower()
if name in self._states:
return self._states[name]
raise AttributeError(f"{name} not found in {self.__class__.__name__}")
Expand All @@ -81,7 +80,7 @@ def items(self):
return self._states.items()

@classmethod
def from_enum(cls, enum_type: EnumType, initial, final=None):
def from_enum(cls, enum_type: EnumType, initial, final=None, use_enum_instance: bool = False):
"""
Creates a new instance of the ``States`` class from an enumeration.
Expand Down Expand Up @@ -125,20 +124,37 @@ def from_enum(cls, enum_type: EnumType, initial, final=None):
True
>>> sm.current_state_value
2
If you need to use the enum instance as the state value, you can set the
``use_enum_instance=True``:
>>> states = States.from_enum(Status, initial=Status.pending, use_enum_instance=True)
>>> states.completed.value
<Status.completed: 2>
.. deprecated:: 2.3.3
On the next major release, the ``use_enum_instance=True`` will be the default.
Args:
enum_type: An enumeration containing the states of the machine.
initial: The initial state of the machine.
final: A set of final states of the machine.
use_enum_instance: If ``True``, the value of the state will be the enum item instance,
otherwise the enum item value.
Returns:
A new instance of the :ref:`States (class)`.
"""
final_set = set(ensure_iterable(final))
return cls(
{
e.name.lower(): State(value=e, initial=e is initial, final=e in final_set)
e.name: State(
value=(e if use_enum_instance else e.value),
initial=e is initial,
final=e in final_set,
)
for e in enum_type
}
)
4 changes: 2 additions & 2 deletions tests/django_project/workflow/statemachines.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
class WorfklowStateMachine(StateMachine):
_ = States.from_enum(WorkflowSteps, initial=WorkflowSteps.DRAFT, final=WorkflowSteps.PUBLISHED)

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

def has_user(self):
return bool(self.model.user)
39 changes: 21 additions & 18 deletions tests/examples/enum_campaign_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,37 @@


class CampaignStatus(Enum):
draft = 1
producing = 2
closed = 3
DRAFT = 1
PRODUCING = 2
CLOSED = 3


class CampaignMachine(StateMachine):
"A workflow machine"

states = States.from_enum(
CampaignStatus, initial=CampaignStatus.draft, final=CampaignStatus.closed
CampaignStatus,
initial=CampaignStatus.DRAFT,
final=CampaignStatus.CLOSED,
use_enum_instance=True,
)

add_job = states.draft.to(states.draft) | states.producing.to(states.producing)
produce = states.draft.to(states.producing)
deliver = states.producing.to(states.closed)
add_job = states.DRAFT.to(states.DRAFT) | states.PRODUCING.to(states.PRODUCING)
produce = states.DRAFT.to(states.PRODUCING)
deliver = states.PRODUCING.to(states.CLOSED)


# %%
# Asserting campaign machine declaration

assert CampaignMachine.draft.initial
assert not CampaignMachine.draft.final
assert CampaignMachine.DRAFT.initial
assert not CampaignMachine.DRAFT.final

assert not CampaignMachine.producing.initial
assert not CampaignMachine.producing.final
assert not CampaignMachine.PRODUCING.initial
assert not CampaignMachine.PRODUCING.final

assert not CampaignMachine.closed.initial
assert CampaignMachine.closed.final
assert not CampaignMachine.CLOSED.initial
assert CampaignMachine.CLOSED.final


# %%
Expand All @@ -50,8 +53,8 @@ class CampaignMachine(StateMachine):
sm = CampaignMachine()
res = sm.send("produce")

assert sm.draft.is_active is False
assert sm.producing.is_active is True
assert sm.closed.is_active is False
assert sm.current_state == sm.producing
assert sm.current_state_value == CampaignStatus.producing
assert sm.DRAFT.is_active is False
assert sm.PRODUCING.is_active is True
assert sm.CLOSED.is_active is False
assert sm.current_state == sm.PRODUCING
assert sm.current_state_value == CampaignStatus.PRODUCING

0 comments on commit 17b3759

Please sign in to comment.