dariowho/intents

@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, because Intent.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()
  • 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 returns dataclasses.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 standard dataclasses.dataclass 2) detect if dataclass is already in globals(), and overwrite that one as well. Pro: problem is solved without breaking changes, dataclass can be enforced at class definition time. Cons: much hack