首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >to转换、超时、内存中、持久化到数据库

to转换、超时、内存中、持久化到数据库
EN

Stack Overflow用户
提问于 2021-04-07 21:07:27
回答 1查看 75关注 0票数 1

关于pyTransitions包的问题,我目前在我的一个项目中使用它。

在评估不同的包时,我在一开始就进行了测试,除了其他功能之外,还有l知道我将来会需要的超时功能。

然后,我一点一点地选择使用sqlalchemy将我所有的有限状态机(实际上只是一个id和state)持久化到磁盘上,并且只在需要触发转换时才重新加载它们-这工作得很好。

不幸的是,现在再次需要超时处理,甚至在尝试将其集成到我的代码中之前,我非常确定这对我来说不起作用。我想我是在说一个显而易见的事情:为了在一组(可能更大的) fsms上正确处理超时,它们必须作为活动对象存在内存中,而不是仅仅从数据库加载。

这是您已经在用例中遇到的东西吗?有没有办法也访问这个超时计数器,以便在任何时候通过适当的修复来持久化和重新加载它,以便允许超时机制倒退,即使在实际超时期间对象在RAM中不是活动的?

如果没有简单的内置替代方案,我想我会在RAM中创建一个驻留对象池,周期性地保存它们,如果我的应用程序宕机了,我会重新加载它们吗?(我的特定场景使用sqlalchemy,但我猜同样的情况也适用于酸菜)

为任何想法或建议提前干杯并感谢乔尔

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-04-09 17:04:27

到目前为止,还没有恢复超时的内置功能。但是,有一些方法可以将计算机从(字典)配置转换为(字典)配置。该扩展名为MarkupMachine,并在FAQ notebook中提到。

我们需要的是一个超时状态类,它存储关于何时应该触发超时的信息,并且可以根据这些信息恢复超时。我们还需要MarkupMachine来处理这些自定义状态信息。MarkupMachine._convert_models将模型及其当前状态转换为字典,MarkupMachine._add_markup_model将获取字典以再次实例化模型。因此,我们需要扩展这两种方法。

为了保持代码简短并专注于概念,我将走捷径。然而,没有任何假设是强制性的。我将假设: a)您可以在某种意义上处理配置,即您可以调整它们以存储和检索您的数据库。此外,我将假设b)您的机器是您的有状态模型,c)您使用默认的状态‘model_attribute’,d)您不使用嵌套/分层机器,e)当您触发事件时,您不传递重要的自定义信息。最后,f)你不介意恢复的状态被进入,潜在的on_enter_<callbacks>将被触发,g)你不需要毫秒(分数)维度的精度。这听起来好像很多。但同样,这并不是破坏交易的因素,而只是需要更复杂的案例处理。

代码语言:javascript
复制
from transitions.extensions.markup import MarkupMachine
from transitions.extensions.states import Timeout
from transitions.core import EventData, Event
import time
from datetime import datetime


class MarkupTimeout(Timeout):

    def __init__(self, *args, **kwargs):
        # Timeout expects a number but MarkupMachine passes values to states as strings right now
        kwargs['timeout'] = int(kwargs.get('timeout', 0))
        super(MarkupTimeout, self).__init__(*args, **kwargs)
        # we store trigger times in a dictionary with the model id as keys
        self.timeout_at = {}
        self.timeout = int(self.timeout)

    def resume(self, timeout_at, event_data):
        # since we want to give our MarkupMachine some time to instantiate we postulate that
        # the earliest possible trigger time is in a second.
        trigger_time = time.time() + 1
        timeout_at = trigger_time if timeout_at < trigger_time else timeout_at
        # we store the default timeout time ...
        tmp = self.timeout
        # ... and temporary override it with the delta of the intended trigger time and the current time
        self.timeout = timeout_at - time.time()
        # ... enter the state and trigger the creation of the timer
        self.enter(event_data)
        # restore the timeout for any future enter event
        self.timeout = tmp

    def enter(self, event_data):
        # a timeout will only be initiated if the timeout value is greater than 0
        if self.timeout > 0:
            # calculate the time when the timeout will trigger (approximately) ...
            timeout_time = time.time() + self.timeout
            # and store it in the previously created dictionary
            self.timeout_at[id(event_data.model)] = timeout_time
            print(f"I should timeout at: {datetime.utcfromtimestamp(timeout_time)}")
        super(MarkupTimeout, self).enter(event_data)

    def exit(self, event_data):
        super(MarkupTimeout, self).exit(event_data)
        # remove the timeout time when the state is exited
        self.timeout_at[id(event_data.model)] = None


class DBMachine(MarkupMachine):

    # DBMachine will use this class when states are created
    state_cls = MarkupTimeout

    # we customize our model definition and add 'timeout_at' to it
    # usually MarkupMachine would iterate over all models but since we assume the model is just
    # the machine itself, we can skip that part
    def _convert_models(self):
        state = self.get_state(self.state)
        timeout_at = state.timeout_at.get(id(self), None)
        model_def = {'state': state.name,
                     'name': 'DBMachine',
                     'class-name': 'self',
                     'timeout_at': str(timeout_at) if timeout_at is not None else ''}
        return [model_def]

    def _add_markup_model(self, markup):
        initial = markup.get('state', None)
        timeout_at = markup.get('timeout_at', '')
        self.add_model(self, initial)
        if timeout_at:
            state = self.get_state(self.state)
            # as mentioned above, every configuration value is a string right now
            ms = float(timeout_at)
            # since we did not store event data, we need to create a temporary event with a minimal EventData object
            # that can be passed to state callbacks
            state.resume(ms, EventData(state=state,
                                       event=Event(name="resume", machine=self),
                                       machine=self,
                                       model=self,
                                       args=[],
                                       kwargs={}))


# we pass a timeout only for 'pending'
states = ['init', dict(name='pending', timeout=5, on_timeout='cancel'), 'done', 'cancelled']
transitions = [
    dict(trigger='request', source='init', dest='pending'),
    dict(trigger='cancel', source='pending', dest='cancelled'),
    dict(trigger='result', source='pending', dest='done')
]

m = DBMachine(states=states, transitions=transitions, initial='init')
# transition to 'pending' and initiate timer
m.request()
assert m.is_pending()
config = m.markup  # [1]
# remove old machine
del m
# create new machine from configuration
m2 = DBMachine(markup=config)
assert m2.is_pending()
time.sleep(10)
assert m2.is_cancelled()

配置1将如下所示:

代码语言:javascript
复制
{ 'after_state_change': [],
  'auto_transitions': True,
  'before_state_change': [],
  'finalize_event': [],
  'ignore_invalid_triggers': None,
  'initial': 'init',
  'models': [ { 'class-name': 'self',
                'name': 'DBMachine',
                'state': 'pending',
                'timeout_at': '1617958918.6320097'}],
  'prepare_event': [],
  'queued': False,
  'send_event': False,
  'states': [ {'name': 'init'},
              {'name': 'pending', 'on_timeout': ['cancel'], 'timeout': '5'},
              {'name': 'done'},
              {'name': 'cancelled'}],
  'transitions': [ {'dest': 'pending', 'source': 'init', 'trigger': 'request'},
                   { 'dest': 'cancelled',
                     'source': 'pending',
                     'trigger': 'cancel'},
                   {'dest': 'done', 'source': 'pending', 'trigger': 'result'}]}

我假设可以对此配置进行重新组织,使SQL查询能够过滤即将到来的超时,并在必要时实例化机器。timeout_at也可以存储为datetime字符串,而不是unix时间戳,如果这会使查询更容易的话。您也可以只存储models部件,而不是从配置创建DBMachine,而是以“通用”的方式创建它:

代码语言:javascript
复制
# reuse the states and transitions and only create the model from configuration
# 'model=None' prevents the machine from adding itself as a model too early
m2 = DBMachine(model=None, states=states, transitions=transitions, initial='init')
m2._add_markup_model(config['models'][0])
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66986729

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档