Naftuli Kay
Naftuli Kay

Reputation: 91790

Recursive Types in dataclass-wizard YAML

I'm attempting to build a simple data-class example for YAML parsing which consists of recursive types.

The YAML in question looks like this:

---
folders:
  - name: a
    children:
      - name: b
  - name: c
    children: []

The way I am defining my types is like so:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from __future__ import annotations  # I'm on Python 3.9
from dataclass_wizard import YAMLWizard, LoadMeta
from dataclasses import dataclass


@dataclass
class DemoManifest(YAMLWizard):
    folders: List[DemoFolder]


@dataclass
class DemoFolder(YAMLWizard):
    name: str
    children: List[DemoFolder]


def main():
    LoadMeta(recursive=True).bind_to(DemoFolder)
    manifest = DemoManifest.from_yaml_file("recurse.yml")
    print(manifest)

It's a pretty simple example. I have one outer type which defines a list of DemoFolder objects, which potentially have a list of children of DemoFolder types as well.

When I run this, I get a RecursionError, maximum depth exceeded. Clearly somehow recursion is breaking the parsing. I thought that I solved the issue using the meta above, but it is definitely not working.

Is it possible to do self-referential deserialization in YAML for dataclass-wizard?


EDIT: This is not an issue of Python compiling. The code compiles and runs, it seems to be an issue with dataclass-wizard, which is where the recursion occurs.

Upvotes: 3

Views: 512

Answers (1)

Wizard.Ritvik
Wizard.Ritvik

Reputation: 11670

It's Q4 2024 and recursive dataclass support has recently been added to dataclass-wizard as of v0.27.0, and I thought I'd update to add an answer here.

If one were to theoretically run the code provided in the question as is, e.g. with:

def main():
    from textwrap import dedent

    yaml_string = dedent("""
    ---
    folders:
      - name: a
        children:
          - name: b
      - name: c
        children: []
    """)

    manifest = DemoManifest.from_yaml(yaml_string)
    print(manifest)


if __name__ == '__main__':
    main()

They would see the following error printed to the console:

    ...
    raise RecursiveClassError(cls) from None
dataclass_wizard.errors.RecursiveClassError: Failure parsing class `DemoFolder`. Consider updating the Meta config to enable the `recursive_classes` flag.

Example with `dataclass_wizard.LoadMeta`:
 >>> LoadMeta(recursive_classes=True).bind_to(DemoFolder)

For more info, please see:
  https://github.com/rnag/dataclass-wizard/issues/62

This is very helpful and points to the root cause as well as the potential solution that can be applied.

By the way, using the recursive flag instead:

LoadMeta(recursive=True).bind_to(DemoFolder)

That only controls whether Meta config for the outer class is applied recursively to inner dataclasses, so that is a red herring and not what we need to apply in this case.

Adding:

LoadMeta(recursive_classes=True).bind_to(DemoManifest)

Now seems to resolve the issue:

DemoManifest(folders=[DemoFolder(name='a', children=[DemoFolder(name='b', children=[])]), DemoFolder(name='c', children=[])])

Full code:

from dataclass_wizard import YAMLWizard, LoadMeta
from dataclasses import dataclass


@dataclass
class DemoManifest(YAMLWizard):
    folders: list['DemoFolder']


@dataclass
class DemoFolder(YAMLWizard):
    name: str
    children: list['DemoFolder']


def main():
    from textwrap import dedent

    yaml_string = dedent("""
    ---
    folders:
      - name: a
        children:
          - name: b
            children: []
      - name: c
        children: []
    """)

    LoadMeta(recursive_classes=True).bind_to(DemoManifest)

    manifest = DemoManifest.from_yaml(yaml_string)
    print(manifest)


if __name__ == '__main__':
    main()

Upvotes: 1

Related Questions