Модель уже содержит атрибут «триггер». Пропустить привязку - PullRequest
0 голосов
/ 06 марта 2020

Я хочу реализовать конечный автомат для моей модели Order, и я использую эту прекрасную библиотеку pytransitions . Но я сталкиваюсь с этой странной проблемой.

Это мой order.py с моделью:

from order_state_machine import OrderStateMachine

class Order(BaseModel):
    def __init__(self, order_dict):
        super().__init__(order_dict)
        # next line basically creates Customer object inside Order model
        self.set('customer', Customer(order_dict['customer']))
        self.machine = OrderStateMachine(self)

Это мой order_state_machine.py:

from transitions import Machine

class OrderStateMachine(Machine):
    order_states = ['pending', 'paid', 'shipped', 'delivered', 'canceled']

    order_transitions = [
        {'trigger': 'pay', 'source': 'pending', 'dest': 'paid'},
        {'trigger': 'deliver', 'source': 'shipped', 'dest': 'delivered'},
        {'trigger': 'cancel', 'source': 'shipped', 'dest': 'canceled'},
    ]

    def __init__(self, order):
        super().__init__(
            model=order,
            states=OrderStateMachine.order_states,
            transitions=OrderStateMachine.order_transitions,
            initial='pending'
        )

И когда я do:

from order import Order

new_order = Order(order_dict)

new_order.state  # returns 'pending'
new_order.pay()
new_order.state  # I expect 'paid'

Это, new_order.pay() строка, выдаёт мне TypeError: 'NoneType' object is not callable ошибку. А также предупреждение Model already contains an attribute 'trigger'. Skip binding. и множество подобных предупреждений.

Может ли кто-нибудь помочь мне решить эту проблему, могут быть сопровождающие библиотеки. Спасибо.

1 Ответ

1 голос
/ 09 марта 2020

Я не могу сказать со стопроцентной уверенностью, но я предполагаю, что ваш BaseModel уже содержит атрибуты и методы, которые перекрываются с методами, которыми transitions хочет динамически декорировать вашу модель.

transitions сделает «проверенное» назначение, которое означает, что оно будет назначать триггеры и вспомогательные функции для модели только тогда, когда требуемое имя еще не занято существующими атрибутами или методами. Причиной этого является то, что иногда люди не заботятся о триггерах и вызывают функции исключительно по имени (фактически, используя метод trigger).

Я предполагаю, что ваш BaseModel содержит присвоение атрибута, аналогичное [ 1], как показано ниже.

from transitions import Machine
import logging


class BaseModel:

    def __init__(self, order_dict):
        self.pay = None  # [1] already defined attribute

    def trigger(self):  # [2] already defined method
        pass


class OrderStateMachine(Machine):
    order_states = ['pending', 'paid', 'shipped', 'delivered', 'canceled']

    order_transitions = [
        {'trigger': 'pay', 'source': 'pending', 'dest': 'paid'},
        {'trigger': 'deliver', 'source': 'shipped', 'dest': 'delivered'},
        {'trigger': 'cancel', 'source': 'shipped', 'dest': 'canceled'},
    ]

    def __init__(self, order):
        super().__init__(
            model=order,
            states=OrderStateMachine.order_states,
            transitions=OrderStateMachine.order_transitions,
            initial='pending'
        )


class Order(BaseModel):
    def __init__(self, order_dict):
        super().__init__(order_dict)
        self.machine = OrderStateMachine(self)

logging.basicConfig(level=logging.DEBUG)
# [2] will cause 'Model already contains an attribute 'trigger'. Skip binding.'
new_order = Order({})  
new_order.state  # returns 'pending'
# [1] will cause a TypeError: 'NoneType' object is not callable
new_order.pay()
new_order.state

Чтобы справиться с этим, необходимо убедиться, что имена триггеров и атрибуты модели являются взаимоисключающими, чтобы предотвратить конфликты имен. Если вы действительно определили все эти методы специально - например, для добавления некоторых подсказок завершения кода в вашу IDE - и вы ДЕЙСТВИТЕЛЬНО хотите сохранить их таким образом, вы можете переопределить метод Machine._checked_assignment:

class OveriddingMachine(Machine):

    # assign everything to the model ignoring already existing attributes
    def _checked_assignment(self, model, name, func):
        setattr(model, name, func)

Обратите внимание, что если вы сделаете это, ваша машина может связываться с вашей моделью нежелательным образом.

...