diff -Nru transitions-0.6.8/Changelog.md transitions-0.6.9/Changelog.md --- transitions-0.6.8/Changelog.md 2018-05-25 13:44:53.000000000 +0000 +++ transitions-0.6.9/Changelog.md 2018-10-26 17:12:13.000000000 +0000 @@ -1,5 +1,12 @@ # Changelog +## 0.6.9 (October 2018) + +Release 0.6.9 is a minor release and contains two bugfixes: + +- Bugfix #314: Do not override already defined model functions with convenience functions (thanks @Arkanayan) +- Bugfix #316: `state.Error` did not call parent's `enter` method (thanks @potens1) + ## 0.6.8 (May, 2018) Release 0.6.8 is a minor release and contains a critical bugfix: diff -Nru transitions-0.6.8/debian/changelog transitions-0.6.9/debian/changelog --- transitions-0.6.8/debian/changelog 2018-08-29 17:42:54.000000000 +0000 +++ transitions-0.6.9/debian/changelog 2018-11-08 09:37:48.000000000 +0000 @@ -1,3 +1,10 @@ +transitions (0.6.9-1) bionic; urgency=medium + + [ Gijs Molenaar ] + * New upstream version 0.6.9 + + -- KERN packaging Thu, 08 Nov 2018 10:37:48 +0100 + transitions (0.6.8-1) bionic; urgency=medium [ Gijs Molenaar ] diff -Nru transitions-0.6.8/README.md transitions-0.6.9/README.md --- transitions-0.6.8/README.md 2018-05-25 13:44:53.000000000 +0000 +++ transitions-0.6.9/README.md 2018-10-26 17:12:13.000000000 +0000 @@ -1,10 +1,10 @@ # transitions -[![Version](https://img.shields.io/badge/version-v0.6.8-orange.svg)](https://github.com/pytransitions/transitions) +[![Version](https://img.shields.io/badge/version-v0.6.9-orange.svg)](https://github.com/pytransitions/transitions) [![Build Status](https://travis-ci.org/pytransitions/transitions.svg?branch=master)](https://travis-ci.org/pytransitions/transitions) [![Coverage Status](https://coveralls.io/repos/pytransitions/transitions/badge.svg?branch=master&service=github)](https://coveralls.io/github/pytransitions/transitions?branch=master) [![Pylint](https://img.shields.io/badge/pylint-9.71%2F10-green.svg)](https://github.com/pytransitions/transitions) [![PyPi](https://img.shields.io/pypi/v/transitions.svg)](https://pypi.org/project/transitions) -[![GitHub commits](https://img.shields.io/github/commits-since/pytransitions/transitions/0.6.6.svg)](https://github.com/pytransitions/transitions/compare/0.6.6...master) +[![GitHub commits](https://img.shields.io/github/commits-since/pytransitions/transitions/0.6.8.svg)](https://github.com/pytransitions/transitions/compare/0.6.9...master) [![License](https://img.shields.io/github/license/pytransitions/transitions.svg)](LICENSE) @@ -510,7 +510,7 @@ print("I am in state C now!") states = ['A', 'B', 'C'] -machine = Machine(states=states) +machine = Machine(states=states, initial='A') # we want a message when state transition to B has been completed machine.add_transition('advance', 'A', 'B', after=after_advance) @@ -532,7 +532,7 @@ If queued processing is enabled, a transition will be finished before the next transition is triggered: ```python -machine = Machine(states=states, queued=True) +machine = Machine(states=states, queued=True, initial='A') ... machine.advance() >>> 'I am in state B now!' @@ -733,6 +733,8 @@ | `'machine.after_state_change'` | `destination` | default callbacks declared on model | | `'machine.finalize_event'` | `source/destination` | callbacks will be executed even if no transition took place or an exception has been raised | +If any callback raises an exception, the processing of callbacks is not continued. This means that when an error occurs before the transition (in `state.on_exit` or earlier), it is halted. In case there is a raise after the transition has been conducted (in `state.on_enter` or later), the state change persists and no rollback is happening. Callbacks specified in `machine.finalize_event` will always be executed unless the exception is raised by a finalizing callback itself. + ### Passing data Sometimes you need to pass the callback functions registered at machine initialization some data that reflects the model's current state. Transitions allows you to do this in two different ways. @@ -1253,6 +1255,7 @@ - keyword: `timeout` (int, optional) -- if passed, an entered state will timeout after `timeout` seconds - keyword: `on_timeout` (string/callable, optional) -- will be called when timeout time has been reached - will raise an `AttributeError` when `timeout` is set but `on_timeout` is not + - Note: A timeout is triggered in a thread. This implies several limitations (e.g. catching Exceptions raised in timeouts). Consider an event queue for more sophisticated applications. * **Tags** -- adds tags to states - keyword: `tags` (list, optional) -- assigns tags to a state @@ -1262,6 +1265,7 @@ - inherits from `Tags` (if you use `Error` do not use `Tags`) - keyword: `accepted` (bool, optional) -- marks a state as accepted - alternatively the keyword `tags` can be passed, containing 'accepted' + - Note: Errors will only be raised if `auto_transitions` has been set to `False`. Otherwise every state can be exited with `to_` methods. * **Volatile** -- initialises an object every time a state is entered - keyword: `volatile` (class, optional) -- every time the state is entered an object of type class will be assigned to the model. The attribute name is defined by `hook`. If omitted, an empty VolatileObject will be created instead diff -Nru transitions-0.6.8/tests/test_states.py transitions-0.6.9/tests/test_states.py --- transitions-0.6.8/tests/test_states.py 2018-05-25 13:44:53.000000000 +0000 +++ transitions-0.6.9/tests/test_states.py 2018-10-26 17:12:13.000000000 +0000 @@ -51,6 +51,23 @@ with self.assertRaises(MachineError): m.fail() + def test_error_callback(self): + @add_state_features(Error) + class CustomMachine(Machine): + pass + + mock_callback = MagicMock() + + states = ['A', {"name": "B", "on_enter": mock_callback}, 'C'] + transitions = [ + ["to_B", "A", "B"], + ["to_C", "B", "C"], + ] + m = CustomMachine(states=states, transitions=transitions, auto_transitions=False, initial='A') + m.to_B() + self.assertEqual(m.state, "B") + self.assertTrue(mock_callback.called) + def test_timeout(self): mock = MagicMock() diff -Nru transitions-0.6.8/tox.ini transitions-0.6.9/tox.ini --- transitions-0.6.8/tox.ini 2018-05-25 13:44:53.000000000 +0000 +++ transitions-0.6.9/tox.ini 2018-10-26 17:12:13.000000000 +0000 @@ -1,5 +1,5 @@ [tox] -envlist = py2, py3, py34, py35, py36, codestyle, check-manifest +envlist = py2, py3, py35, py36, py37, codestyle, check-manifest skip_missing_interpreters = True [testenv] diff -Nru transitions-0.6.8/transitions/core.py transitions-0.6.9/transitions/core.py --- transitions-0.6.8/transitions/core.py 2018-05-25 13:44:53.000000000 +0000 +++ transitions-0.6.9/transitions/core.py 2018-10-26 17:12:13.000000000 +0000 @@ -582,11 +582,7 @@ for mod in models: mod = self if mod == 'self' else mod if mod not in self.models: - if hasattr(mod, 'trigger'): - _LOGGER.warning("%sModel already contains an attribute 'trigger'. Skip method binding ", - self.name) - else: - mod.trigger = partial(_get_trigger, mod) + self._checked_assignment(mod, 'trigger', partial(_get_trigger, mod)) for trigger, _ in self.events.items(): self._add_trigger_to_model(trigger, mod) @@ -771,8 +767,7 @@ self.add_transition('to_%s' % state, self.wildcard_all, state) def _add_model_to_state(self, state, model): - setattr(model, 'is_%s' % state.name, - partial(self.is_state, state.name, model)) + self._checked_assignment(model, 'is_%s' % state.name, partial(self.is_state, state.name, model)) # Add dynamic method callbacks (enter/exit) if there are existing bound methods in the model # except if they are already mentioned in 'on_enter/exit' of the defined state @@ -782,9 +777,14 @@ method not in getattr(state, callback): state.add_callback(callback[3:], method) + def _checked_assignment(self, model, name, func): + if hasattr(model, name): + _LOGGER.warning("%sModel already contains an attribute '%s'. Skip binding.", self.name, name) + else: + setattr(model, name, func) + def _add_trigger_to_model(self, trigger, model): - trig_func = partial(self.events[trigger].trigger, model) - setattr(model, trigger, trig_func) + self._checked_assignment(model, trigger, partial(self.events[trigger].trigger, model)) def get_triggers(self, *args): """ Collects all triggers FROM certain states. @@ -1006,9 +1006,10 @@ @staticmethod def resolve_callable(func, event_data): - """ Converts path to a callable into callable + """ Converts a model's method name or a path to a callable into a callable. + If func is not a string it will be returned unaltered. Args: - func (string, callable): Path to a callable + func (string, callable): Method name or a path to a callable event_data (EventData): Currently processed event Returns: callable function resolved from string or func @@ -1025,7 +1026,7 @@ func = getattr(m, name) except (ImportError, AttributeError, ValueError): raise AttributeError("Callable with name '%s' could neither be retrieved from the passed " - "model nor imported from a module.") + "model nor imported from a module." % func) return func def _has_state(self, state): diff -Nru transitions-0.6.8/transitions/extensions/states.py transitions-0.6.9/transitions/extensions/states.py --- transitions-0.6.8/transitions/extensions/states.py 2018-05-25 13:44:53.000000000 +0000 +++ transitions-0.6.9/transitions/extensions/states.py 2018-10-26 17:12:13.000000000 +0000 @@ -59,6 +59,7 @@ """ if not event_data.machine.get_triggers(self.name) and not self.is_accepted: raise MachineError("Error state '{0}' reached!".format(self.name)) + super(Error, self).enter(event_data) class Timeout(State): diff -Nru transitions-0.6.8/transitions/version.py transitions-0.6.9/transitions/version.py --- transitions-0.6.8/transitions/version.py 2018-05-25 13:44:53.000000000 +0000 +++ transitions-0.6.9/transitions/version.py 2018-10-26 17:12:13.000000000 +0000 @@ -2,4 +2,4 @@ to determine transitions's version during runtime. """ -__version__ = '0.6.8' +__version__ = '0.6.9' diff -Nru transitions-0.6.8/.travis.yml transitions-0.6.9/.travis.yml --- transitions-0.6.8/.travis.yml 2018-05-25 13:44:53.000000000 +0000 +++ transitions-0.6.9/.travis.yml 2018-10-26 17:12:13.000000000 +0000 @@ -2,7 +2,6 @@ python: - "3.6" - "3.5" - - "3.4" - "2.7" install: - pip install python-coveralls coverage