Example #1
0
    def _transition() -> bool:
        with app.session.begin_nested():
            lock_label(app, label)
            current_state = get_current_state(app, label, state_machine)

            if isinstance(current_state, Action):
                return process_action(
                    app=app,
                    state=current_state,
                    state_machine=state_machine,
                    label=label,
                )

            elif isinstance(current_state, Gate):  # pragma: no branch
                if not current_state.trigger_on_entry:
                    return False

                return process_gate(
                    app=app,
                    state=current_state,
                    state_machine=state_machine,
                    label=label,
                )

            else:
                raise RuntimeError(  # pragma: no cover
                    "Unsupported state type {0}".format(current_state), )
Example #2
0
def process_cron(
    process: LabelStateProcessor,
    get_labels: Callable[[StateMachine, State], Iterable[str]],
    app: App,
    state_machine: StateMachine,
    state: State,
):
    """
    Cron event entrypoint.
    """
    with app.new_session():
        relevant_labels = get_labels(state_machine, state)

    for label_name in relevant_labels:
        with suppress_exceptions(app.logger):
            label = LabelRef(name=label_name, state_machine=state_machine.name)
            could_progress = False

            with app.new_session():
                lock_label(app, label)
                current_state = get_current_state(app, label, state_machine)

                if current_state != state:
                    continue

                could_progress = process(
                    app=app,
                    state=state,
                    state_machine=state_machine,
                    label=label,
                )

            with app.new_session():
                if could_progress:
                    process_transitions(app, label)
Example #3
0
def delete_label(app: App, label: LabelRef) -> None:
    """
    Deletes the metadata for a label and marks the label as deleted.

    The history for the label is not changed (in order to allow post-hoc
    analysis of the path the label took through the state machine).
    """
    state_machine = get_state_machine(app, label)  # Raises UnknownStateMachine

    try:
        row = lock_label(app, label)
    except UnknownLabel:
        return

    if row is None or row.deleted:
        return

    # Record the label as having been deleted and remove its metadata
    row.metadata = {}
    row.deleted = True

    # Add a history entry for the deletion
    current_state = get_current_state(app, label, state_machine)

    if current_state is None:
        raise AssertionError(f"Active label {label} has no current state!")

    row.history.append(History(
        old_state=current_state.name,
        new_state=None,
    ))
Example #4
0
def _process_transitions_for_metadata_update(
    app: App,
    label: LabelRef,
    state_machine: StateMachine,
    state_pending_update: State,
):
    with app.session.begin_nested():
        lock_label(app, label)
        current_state = get_current_state(app, label, state_machine)

        if state_pending_update != current_state:
            # We have raced with another update, and are no longer in
            # the state for which we needed an update, so we should
            # stop.
            return

        if not isinstance(current_state, Gate):  # pragma: no branch
            # Cannot be hit because of the semantics of
            # `needs_gate_evaluation_for_metadata_change`. Here to
            # appease mypy.
            raise RuntimeError(  # pragma: no cover
                "Label not in a gate", )

        could_progress = process_gate(
            app=app,
            state=current_state,
            state_machine=state_machine,
            label=label,
        )

    if could_progress:
        process_transitions(app, label)
def test_get_current_state_for_label_in_invalid_state(custom_app,
                                                      create_label):
    state_to_be_removed = Gate(
        name='start',
        triggers=[],
        next_states=ConstantNextState('end'),
        exit_condition=ExitConditionProgram('false'),
    )
    end_state = Gate(
        name='end',
        triggers=[],
        next_states=NoNextStates(),
        exit_condition=ExitConditionProgram('false'),
    )

    app = custom_app(
        state_machines={
            'test_machine':
            StateMachine(
                name='test_machine',
                states=[state_to_be_removed, end_state],
                feeds=[],
                webhooks=[],
            ),
        })

    label = create_label('foo', 'test_machine', {})
    state_machine = app.config.state_machines['test_machine']

    # Remove the state which we expect the label to be in from the state
    # machine; this is logically equivalent to loading a new config which does
    # not have the state
    del state_machine.states[0]

    with app.new_session():
        with pytest.raises(Exception):
            utils.get_current_state(app, label, state_machine)
def test_does_not_need_gate_evaluation_for_metadata_change_with_action(
        app, create_label, mock_webhook):
    state_machine = app.config.state_machines['test_machine']

    # Force the action to fail here so that the label is left in the action
    # state.
    with mock_webhook(WebhookResult.FAIL):
        label = create_label('foo', 'test_machine', {'should_progress': True})

    with app.new_session():
        current_state = utils.get_current_state(
            app,
            label,
            state_machine,
        )
        assert current_state.name == 'perform_action'

        assert utils.needs_gate_evaluation_for_metadata_change(
            app,
            state_machine,
            label,
            {},
        ) == (False, current_state)
def test_needs_gate_evaluation_for_metadata_change(app, create_label):
    label = create_label('foo', 'test_machine', {})
    state_machine = app.config.state_machines['test_machine']

    with app.new_session():
        current_state = utils.get_current_state(
            app,
            label,
            state_machine,
        )

        assert utils.needs_gate_evaluation_for_metadata_change(
            app,
            state_machine,
            label,
            {'foo': 'bar'},
        ) == (False, current_state)

        assert utils.needs_gate_evaluation_for_metadata_change(
            app,
            state_machine,
            label,
            {'should_progress': 'bar'},
        ) == (True, current_state)
def test_get_current_state_for_deleted_label(app, create_deleted_label):
    label = create_deleted_label('foo', 'test_machine')
    state_machine = app.config.state_machines['test_machine']
    with app.new_session():
        assert utils.get_current_state(app, label, state_machine) is None
def test_get_current_state(app, create_label):
    label = create_label('foo', 'test_machine', {})
    state_machine = app.config.state_machines['test_machine']
    state = state_machine.states[0]
    with app.new_session():
        assert utils.get_current_state(app, label, state_machine) == state
Example #10
0
def get_label_state(app: App, label: LabelRef) -> Optional[State]:
    """Finds the current state of a label."""
    state_machine = get_state_machine(app, label)
    return get_current_state(app, label, state_machine)