Uploaded image for project: 'Qt for Python'
  1. Qt for Python
  2. PYSIDE-2381

QSettings list of strings does not roundtrip

    XMLWordPrintable

Details

    • Bug
    • Resolution: Unresolved
    • P4: Low
    • None
    • 5.15.2, 6.5.1.1
    • PySide
    • None
    • Linux/X11
    • 88075cc35 (dev), 3dfc872e3 (dev), 7fd802a64 (6.5), c27c7a78a (6.5)

    Description

      When storing list of strings, in an INI file, it's not always possible to get back the original value.

      qsettings-string-list.py
      from PySide6.QtCore import QSettings
      
      s = QSettings("test_py.ini", QSettings.IniFormat)
      s.setValue("empty_list", [])
      s.setValue("str", "foo")
      s.setValue("one", ["foo"])
      s.setValue("empty_string", [""])
      s.setValue("many", ["foo", "bar"])
      s.setValue("many_empty_strings", ["", ""])
      s.sync()
      
      shutil.copyfile("test_py.ini", "test_py_readonly.ini")
      s2 = QSettings("test_py_readonly.ini", QSettings.IniFormat)
      
      print("{:>18} {:>14} {:<14}".format("KEY", "FROM MEMORY", "FROM DISK"))
      
      for key in (
          "missing",
          "empty_list",
          "str",
          "one",
          "empty_string",
          "many",
          "many_empty_strings",
      ):
          print("{:>18} {!r:>14} {!r:<14}".format(key, s.value(key), s2.value(key)))
          print("{:>18} {!r:>14} {!r:<14} type=list".format(key, s.value(key, type=list), s2.value(key, type=list)))
          print("{:>18} {!r:>14} {!r:<14} default=[]".format(key, s.value(key, []), s2.value(key, [])))
          print("{:>18} {!r:>14} {!r:<14} type=list default=[]".format(key, s.value(key, [], type=list), s2.value(key, [], type=list)))
      
      $ python qsettings-string-list.py
                     KEY    FROM MEMORY FROM DISK     
                 missing           None None          
                 missing          ['0'] ['0']          type=list
                 missing             [] []             default=[]
                 missing             [] []             type=list default=[]
              empty_list             [] None          
              empty_list             [] []             type=list
              empty_list             [] None           default=[]
              empty_list             [] []             type=list default=[]
                     str          'foo' 'foo'         
                     str        ['foo'] ['foo']        type=list
                     str          'foo' 'foo'          default=[]
                     str        ['foo'] ['foo']        type=list default=[]
                     one        ['foo'] 'foo'         
                     one             [] ['foo']        type=list
                     one        ['foo'] 'foo'          default=[]
                     one             [] ['foo']        type=list default=[]
            empty_string           [''] ''            
            empty_string             [] []             type=list
            empty_string           [''] ''             default=[]
            empty_string             [] []             type=list default=[]
                    many ['foo', 'bar'] ['foo', 'bar']
                    many             [] []             type=list
                    many ['foo', 'bar'] ['foo', 'bar'] default=[]
                    many             [] []             type=list default=[]
      many_empty_strings       ['', ''] ['', '']      
      many_empty_strings             [] []             type=list
      many_empty_strings       ['', ''] ['', '']       default=[]
      many_empty_strings             [] []             type=list default=[]
      
      test_py.ini
      [General]
      empty_list=@Invalid()
      empty_string=
      many=foo, bar
      many_empty_strings=, 
      one=foo
      str=foo
      

      Observations:

      • Reading the "missing" key with just `type=list` leads to a surprising results: `['0']`.
      • Specifying `type=list` almost always returns an empty list (except for "str", the "missing"case above and "one" when read from disk).
      • The generated file is the same as the C++ below, setValue(key, list) serialization looks ok.
      • Reading an empty or single-element "FROM MEMORY" (the QVariant is cached internally?) vs "FROM DISK".
         

      I tested the same in C++ and it work as I would expect:

      qsettings-string-list.cpp
      #include <QSettings>
      #include <QStringList>
      
      int main()
      {
        QSettings settings("test.ini", QSettings::IniFormat);
      
        settings.setValue("empty_list", QStringList());
        settings.setValue("one", QStringList({ "foo" }));
        settings.setValue("empty_string", QStringList({ "" }));
        settings.setValue("many", QStringList({ "foo", "bar" }));
        settings.setValue("many_empty_strings", QStringList({ "", "" }));
      
        qDebug() << settings.value("missing").toStringList();
        qDebug() << settings.value("empty_list").toStringList();
        qDebug() << settings.value("empty_string").toStringList();
        qDebug() << settings.value("one").toStringList();
        qDebug() << settings.value("many").toStringList();
        qDebug() << settings.value("many_empty_strings").toStringList();
      
        return 0;
      }
      
      $ ./qsettings-string-list
      QList()
      QList()
      QList("")
      QList("foo")
      QList("foo", "bar")
      QList("", "")
      
      test.ini
      [General]
      empty_list=@Invalid()
      empty_string=
      many=foo, bar
      many_empty_strings=, 
      one=foo
      

      There is a unit test for `type=list` in the repo but I suspect the test passes because it does not check the content of the returned value, just the type:

      sources/pyside6/tests/QtCore/qsettings_test.ini
      [General]
      var1=a, b, c
      var2=a
      
      sources/pyside6/tests/QtCore/qsettings_test.py
      class TestQSettings(unittest.TestCase):
          def testConversions(self):
              ...
              r = settings.value('var2', type=list)
              self.assertEqual(type(r), list)
      

      Source https://github.com/qtproject/pyside-pyside-setup/blob/499406a68b9c3ebbbeca5ab526999960b46a4869/sources/pyside6/tests/QtCore/qsettings_test.py#L32-L33

      Attachments

        1. pyside2381_diag.diff
          1 kB
        2. pyside2381_log.txt
          18 kB
        3. pyside2381.py
          1 kB
        4. qsettings-string-list.py
          1 kB
        5. test_qsettings_stringlist_roundtrip.py
          2 kB

        Issue Links

          For Gerrit Dashboard: PYSIDE-2381
          # Subject Branch Project Status CR V

          Activity

            People

              Unassigned Unassigned
              sarcasm Guillaume Papin
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:

                Gerrit Reviews

                  There are no open Gerrit changes