@dataclass decorator applied two times when declaring intents
Closed this issue · 0 comments
Steps to reproduce
@dataclass
class a_class(Intent):
foo: str = "ciao"
bar: List[str] = field(default_factory=lambda: [])
Expected result
An intent with optional fields foo
and bar
Actual Result
TypeError: non-default argument 'bar' follows default argument
Solution
The reason for the error is that dataclass
is already applied by _IntentMetaclass.__new__()
, which cannot know whether a_class
will be decorated by @dataclass
or not. Internally, dataclass deletes bar
from dict, so that it can be handled in its generated __init__()
. So, the second time it's applied it will find an annotation for "bar", but no "bar" member, causing the TypeError above.
Since @dataclass
is applied programmatically, an explicit @dataclass
decorator is not needed for the framework to work. However, without it IDEs will not apply type hinting and autocompletion on Intent subclasses, which is not acceptable. Possible solutions include:
-
Don't apply
dataclass
programmatically within_IntentMetaclass.__new__()
; chatbot developer must explicitly decorate with@dataclass
. Pro: problem is solved without hacks nor breaking changes. Cons: dataclass can't be enforced in__new__()
, ill Intent subclasses may exist at some point in the flow; also, parameters can't be checked at class definition time, becauseIntent.parameter_schema
(like all other Intent internals) assumes class is a dataclass. Some workarounds:- (for parameter checking) Check parameters on
Agent.register()
instead of class definition time - (for parameter checking) Make a temporary dataclass within
__new__()
and call parameter check on that one. - (for enforcing dataclass) Check is_dataclass 1) in
__new__()
, for parent class when subclassing 2) in Agent.register()
- (for parameter checking) Check parameters on
-
Wrap
@dataclass
in a custom@Intent.dataclass
decorator, which is aware of Intents and doesn't apply itself twice. Pro: dataclass can be enforced in__new__()
without many hacks. Cons: not all IDEs will acknowledge the decorator. While VSCode infers that@Intent.dataclass
returnsdataclasses.dataclass
, and behaves accordingly, PyCharm is much stricter and won't hint the decorated class as a dataclass (https://youtrack.jetbrains.com/issue/PY-40584) -
Overwrite built-in
@dataclass
decorator with a custom one that behaves differently only when applied to Intents.Intents.__init__
has to 1) overwrite standarddataclasses.dataclass
2) detect ifdataclass
is already inglobals()
, and overwrite that one as well. Pro: problem is solved without breaking changes, dataclass can be enforced at class definition time. Cons: much hack