Skip to content

RecursionError with Self-Referential Models in CliApp #781

@Tesla2000

Description

@Tesla2000

When using CliApp.run() with a BaseSettings class that contains a field with a self-referential model type, a RecursionError occurs. The CLI argument parser enters infinite recursion when trying to introspect the self-referential field structure.

Environment:
pydantic==2.12.5
pydantic-settings==2.13.0
pydantic_core==2.41.5
annotated-types==0.7.0
typing_extensions==4.15.0
python-dotenv==1.2.1
typing-inspection==0.4.2

Minimal Reproducible Example (behavior is the same without Optional and default value):

from typing import Optional

from pydantic import BaseModel
from pydantic_settings import BaseSettings, CliApp


class Foo(BaseModel):
    foo: Optional["Foo"] = None


Foo.model_rebuild()


class RecursiveSettings(BaseSettings):
    foo: Foo
    def cli_cmd(self):
        pass

CliApp.run(RecursiveSettings)

Expected behavior:
Works the same as RecursiveSettings().cli_cmd() parses values from specified sources without raising recursion error or raising a more specific error if there is an internal reason for this behavior
Actual behavior:
Raises RecursionError
Top part

Traceback (most recent call last):
  File "/home/filip/PassionProjects/pydantic-bug/main.py", line 19, in <module>
    CliApp.run(RecursiveSettings)
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic_settings/main.py", line 733, in run
    sources, init_kwargs = model_cls._settings_init_sources(**model_init_data)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic_settings/main.py", line 424, in _settings_init_sources
    cli_settings = CliSettingsSource[Any](
                   ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/typing.py", line 1157, in __call__
    result = self.__origin__(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/cli.py", line 415, in __init__
    self._connect_root_parser(
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/cli.py", line 906, in _connect_root_parser
    self._add_parser_args(
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/cli.py", line 1049, in _add_parser_args
    self._add_parser_submodels(

Bottom part

    self._add_parser_args(
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/cli.py", line 1049, in _add_parser_args
    self._add_parser_submodels(
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/cli.py", line 1242, in _add_parser_submodels
    self._add_parser_args(
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/cli.py", line 960, in _add_parser_args
    arg = _CliArg(
          ^^^^^^^^
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/cli.py", line 117, in __init__
    super().__init__(**values)
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py", line 369, in init_private_attributes
    default = private_attr.get_default()
              ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic/fields.py", line 1453, in get_default
    return _utils.smart_deepcopy(self.default) if self.default_factory is None else self.default_factory()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/filip/PassionProjects/pydantic-bug/.venv/lib/python3.12/site-packages/pydantic/_internal/_utils.py", line 354, in smart_deepcopy
    return deepcopy(obj)  # slowest way when we actually might need a deepcopy
           ^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions