Hello anda_skoa,

Thank you for the reply. I am not sure whether Windows applies such file locking automatically. I've been working to move my application's data store from binary files (which could just as well be JSON or XML) to SQLite mostly because of this concern. I can launch multiple instances of the application, open the same file in both, and edit the file in both. Windows by itself provides no warnings about the file's content being modified in another process, and does not prevent either instance from writing to the binary data store. The file's data simply reflects the state of the data in whichever instance of the app was last saved.

I believe that I have solved this, though, thanks not to Qt's or SQLite's documentation, but Python's on its sqlite3 module, which reads:

When a database is accessed by multiple connections, and one of the processes modifies the database, the SQLite database is locked until that transaction is committed. The timeout parameter specifies how long the connection should wait for the lock to go away until raising an exception. The default for the timeout parameter is 5.0 (five seconds).
I was unaware that the transaction had to be committed to free the db lock. In the test app, the one query that writes to the database is the INSERT query. So, I passed in the db variable to the model, made it an instance variable of the model, and in the insertRow() method I called on the db object the transaction() and then commit() methods (in that order, as the Qt and PySide documentation dictates) following the call to exec_() on the query object. The problem still persisted, and I realized that it's perhaps because the query is an instance variable (prepared in the __init__ method of the model) and therefore remains indefinitely uncommitted. Once I moved the QSqlQuery() object instantiation to the insertRow() method, the problem was fully solved.

The only caveat is that, because the dataChanged() signal is only emitted when rows are inserted, changes to the data store are not reflected across the multiple open instances of the app until I actually insert another row in that particular instance. So, I'm just thinking how I could best solve this. Right now all that comes to mind is calling reset() on the model with some sort of polling implementation, but I would hate to be resetting the model on such a constant basis.

Below is my modified code: (Note that db.isOpen() still does not return what I would expect it to.)

Qt Code:
  1. from PySide.QtCore import *
  2. from PySide.QtGui import *
  3. from PySide.QtSql import *
  4. import sys
  5.  
  6. class MyDlg(QDialog):
  7.  
  8. def __init__(self):
  9. super(MyDlg, self).__init__()
  10. db = QSqlDatabase.addDatabase("QSQLITE")
  11. filename = "C:/temp/testfile.db"
  12. db.setDatabaseName(filename)
  13. if db.isOpen():
  14. print "Database is already open by another process!"
  15. db.open()
  16.  
  17. self.model = ListModel(db)
  18. view = QListView()
  19. view.setModel(self.model)
  20. add_button = QPushButton("Insert row")
  21. layout = QVBoxLayout()
  22. layout.addWidget(view)
  23. layout.addWidget(add_button)
  24. self.setLayout(layout)
  25.  
  26. add_button.clicked.connect(self.addRow)
  27.  
  28. def addRow(self):
  29. text, o = QInputDialog.getText(self, "Enter a row to add", "Text")
  30. self.model.insertRow(self.model.rowCount()+1, QModelIndex(), text)
  31.  
  32. class ListModel(QAbstractListModel):
  33.  
  34. def __init__(self, db):
  35. super(ListModel, self).__init__()
  36. self.db = db
  37.  
  38. def rowCount(self, parent=QModelIndex()):
  39. count_query = QSqlQuery()
  40. count_query.prepare("SELECT COUNT(1) from names")
  41. count_query.exec_()
  42. count_query.next()
  43. return count_query.value(0)
  44.  
  45. def data(self, index, role=Qt.DisplayRole):
  46. if not index.isValid():
  47. return None
  48. if role == Qt.DisplayRole:
  49. data_query = QSqlQuery()
  50. data_query.prepare("SELECT name FROM names WHERE rowid=?")
  51. data_query.addBindValue(index.row() + 1)
  52. data_query.exec_()
  53. data_query.next()
  54. return data_query.value(0)
  55.  
  56. def insertRow(self, row, parent=QModelIndex(), text="Default"):
  57. insert_query = QSqlQuery()
  58. insert_query.prepare("INSERT INTO names VALUES (?)")
  59. insert_query.addBindValue(text)
  60. if insert_query.exec_():
  61. self.db.transaction()
  62. self.db.commit()
  63. self.dataChanged.emit(self.index(row, 0), self.index(row, 0))
  64. return True
  65. else:
  66. print insert_query.lastError().databaseText()
  67. return False
  68.  
  69. def main():
  70. app = QApplication(sys.argv)
  71. dlg = MyDlg()
  72. dlg.show()
  73. app.exec_()
  74.  
  75. main()
To copy to clipboard, switch view to plain text mode