fgmacedo / python-statemachine

Python Finite State Machines made easy.
MIT License
910 stars 85 forks source link

Feature request logic_OR logic_NOT wrappers for conditions (and unless condition) #483

Closed miko1ann closed 2 weeks ago

miko1ann commented 1 month ago

That is best python state machine libratry! Thanks!

Description

Im my case i build logic with many states (> 12), also a have many conditions. My way is simple conditions and build transitions logic with combinations of simple transitions. I hepls me keep code clear.

I catch a problem with clearing code. If transition conditions all true or all negative - its ok. But if condition is any of i can't easy add it to class_declaration. For solve this i need make class method which wil wraps my base conditions with logic or.

    async def is_aggregater_logic_or_condition(self, event_data):
            return self.condition1(event_data) or (await self.simple_condition_2(event_data))

What I want

I would really like to use some wrappers. Need _or_conditionwrapper and _not_condwrapper to wraps many conditions. This will allow you to configure the state machine more flexibly and write cleaner code.

For example:

...
  |_.initialized.to(_.waiting_create_by_order_conditions,  # init to wait_create_by_order_conditions
                                       cond=[ ],
                                       unless=[
                                                              State_machine.or_wrapper( 'is_any_trade_buy_presented',
                                                                        'is_can_create_order_buy',
                                                                        'is_buy_price_close_to_market_price',
                                                                        'is_free_deposit_far_to_limit',
                                                                        'is_enough_free_money',) _# means that we went to_waiting_create_by_order_conditions if any one of conditions is negative
                                                                ],
                                       validators=[ 'val_separator','val_update_dep_data']
                                       ) \

Thanks is advance

fgmacedo commented 1 month ago

Hey @miko1ann,

Thank you so much for the kind words! I'm thrilled to hear that you're finding the state machine library helpful for your projects.

I really appreciate your suggestion, and I can see how having or_condition_wrapper and not_cond_wrapper could simplify the logic in transitions, especially when dealing with multiple conditions. It would indeed allow for a cleaner, more flexible way to configure your state machine!

I'll explore adding these wrappers to the library, as this seems like a useful enhancement. A potential path could be creating something similar to a grammar parser or a condition builder. This might simplify how conditions are combined, making the process more natural while reducing the need for multiple wrappers.

Something like accepting expressions, like this (allowing regular boolean algebra, using and, or, not, ( ):

  |_.initialized.to(_.waiting_create_by_order_conditions,  # init to wait_create_by_order_conditions
        unless=["is_any_trade_buy_presented or is_can_create_order_buy or is_buy_price_close_to_market_price or is_free_deposit_far_to_limit or is_enough_free_money"],
        validators=['val_separator', 'val_update_dep_data']
  )

I'll keep you updated as I look into this further! Thanks again for your awesome input!

Update: While a wrapper might be easier to implement, do you think creating a parser would add more value by handling the logic directly? This way, users wouldn’t need to wrap conditions manually, making it more intuitive and streamlined for complex combinations. Would that be a better solution for your use case?

miko1ann commented 1 month ago

Thanks for the quick reply. Personally, I prefer the explicit way. Better explicit than implicit. You rightly said that 3 Boolean logic operations are enough. The parser is a good thing, it will suit me perfectly. My arguments for the explicit way (wrapper) are that it simplifies debugging, often works faster and eliminates errors when renaming methods. It can probably be implemented faster, which will add value to the project faster. And also, if you add a parser to the project, the working code can be reused.

miko1ann commented 1 month ago

Here's what I'd like to note, I liked your implementation of the state machine, because it's minimalistic, simple and at the same time complete. And also excellent documentation.

Minimalistic and well thought out - nothing superfluous and a streamlined system of entities (states, events, actions, listeners, etc.).

Easy to learn - I threw together a prototype to understand whether it suits me or not very quickly. I fiddled around a bit to change the internal state of the model before the transitions, but then I realized that a validator is suitable for this.

Complete - a necessary and sufficient set of functionality.

Good work!

fgmacedo commented 1 month ago

Thanks for the quick reply. Personally, I prefer the explicit way. Better explicit than implicit. You rightly said that 3 Boolean logic operations are enough. The parser is a good thing, it will suit me perfectly. My arguments for the explicit way (wrapper) are that it simplifies debugging, often works faster and eliminates errors when renaming methods. It can probably be implemented faster, which will add value to the project faster. And also, if you add a parser to the project, the working code can be reused.

Good arguments. Makes sense to me.

Is this blocking your usage? Seems that you've figured out a temp workaround by adding a wrapping method, right? If it's not blocking you, I'll take some time to think about the public API for this feature.

miko1ann commented 1 month ago

It doesn't block. I got out of it by repeating the transition and combining the conditions.

 |_.initialized.to(_.waiting_create_by_order_conditions,  # init to wait_create_by_order_conditions
                                       cond=['is_departure_data_ready_to_buy', ],
                                       unless=[
                                           'is_buy_price_close_to_market_price',
                                           'is_any_trade_buy_presented',
                                            ],
                                       ) \
                     | _.initialized.to(_.waiting_create_by_order_conditions,  # init to wait_create_by_order_conditions
                                        cond = ['is_deposit_reached_max_trading_limit',
                                                       'is_departure_data_ready_to_buy',],
                                        unless = ['is_any_trade_buy_presented',]
                                        ) \
                     | _.initialized.to(_.waiting_create_by_order_conditions,  # init to wait_create_by_order_conditions
                                        cond=['is_departure_data_ready_to_buy',],
                                        unless=[
                                            'is_any_trade_buy_presented',
                                        ],
                                        validators=['val_separator', 'val_update_dep_data']
                                        ) \
fgmacedo commented 2 weeks ago

@miko1ann can you please test your use case against the branch macedo/cond-with-boolean-algebra ( https://github.com/fgmacedo/python-statemachine/pull/487 ) ?

pip install  git+https://github.com/fgmacedo/python-statemachine.git@macedo/macedo/cond-with-boolean-algebra

I ended up implementing the parser but used the built-in ast module that parses Python code, so I got syntax error reporting for free :)

Only need to update the docs.