Repository: napari/napari

Issue:
SettingsManager._load not working with Field(default_factory=...)
## 🐛 Bug
Ran into a [test failure](https://github.com/napari/napari/pull/2695/checks?check_run_id=2925027700#step:7:250) on #2695 that looks to be a bug in the Settings Manager.

The preferred way to add a default mutable container to a pydantic model is to use `default_factor`, but currently, if you do this, you'll get an error:

```python
class PluginsSettings(BaseNapariSettings):
    # this works, and is used in a couple places in the settings
    # but is not how an empty mutable object should be declared
    some_setting: Dict[str, str] = Field(dict())

    # this is the preferred way to create an empty mutable
    # but it doesn't work:
    some_other_setting: Dict[str, str] = Field(default_factory=dict)
```

traceback:

```pytb
napari/utils/settings/_manager.py:262: in get_settings
    SETTINGS = SettingsManager(config_path=config_path)
napari/utils/settings/_manager.py:90: in __init__
    self._load()
napari/utils/settings/_manager.py:186: in _load
    self._defaults[section] = setting(**_section_defaults)
pydantic/env_settings.py:36: in pydantic.env_settings.BaseSettings.__init__
    ???
napari/utils/events/evented_model.py:129: in __init__
    super().__init__(**kwargs)
pydantic/main.py:406: in pydantic.main.BaseModel.__init__
    ???
E   pydantic.error_wrappers.ValidationError: 2 validation errors for PluginsSettings
E   extension2reader
E     none is not an allowed value (type=type_error.none.not_allowed)
E   extension2writer
E     none is not an allowed value (type=type_error.none.not_allowed)

```


Discussion:

Resolving patch:
diff --git a/napari/utils/settings/_manager.py b/napari/utils/settings/_manager.py
--- a/napari/utils/settings/_manager.py
+++ b/napari/utils/settings/_manager.py
@@ -179,11 +179,9 @@ def _load(self):
                     )
                 )
 
-            _section_defaults = {}
-            for option, option_data in setting.schema()["properties"].items():
-                _section_defaults[option] = option_data.get("default", None)
-
-            self._defaults[section] = setting(**_section_defaults)
+            self._defaults[section] = setting(
+                **{k: v.get_default() for k, v in setting.__fields__.items()}
+            )
             model = setting()
             model.events.connect(lambda x: self._save())
             self._settings[section] = model


Test patch:
diff --git a/napari/utils/settings/_tests/test_settings.py b/napari/utils/settings/_tests/test_settings.py
--- a/napari/utils/settings/_tests/test_settings.py
+++ b/napari/utils/settings/_tests/test_settings.py
@@ -3,6 +3,7 @@
 
 import pydantic
 import pytest
+from pydantic.fields import Field
 from yaml import safe_dump, safe_load
 
 import napari.utils.settings._manager as _manager
@@ -234,3 +235,18 @@ def test_get_settings_fails(monkeypatch, tmp_path):
     _manager.get_settings(tmp_path)
     with pytest.raises(Exception):
         _manager.get_settings(tmp_path)
+
+
+def test_default_factory(monkeypatch):
+    from napari.utils.settings._defaults import BaseNapariSettings
+
+    class MockSettings(BaseNapariSettings):
+        test_field: list = Field(default_factory=list)
+
+        class Config:
+            schema_extra = {"section": "test_section"}
+
+    monkeypatch.setattr(_manager, "CORE_SETTINGS", (MockSettings,))
+    mng = _manager.SettingsManager()
+    assert mng.test_section.test_field == []
+    assert mng._defaults['test_section'].test_field == []