diff options
-rw-r--r-- | coin_build_instructions.py | 4 | ||||
-rw-r--r-- | coin_test_instructions.py | 15 | ||||
-rw-r--r-- | examples/sql/books/bookdelegate.py | 134 | ||||
-rw-r--r-- | examples/sql/books/books.pyproject | 5 | ||||
-rw-r--r-- | examples/sql/books/books.qrc | 5 | ||||
-rw-r--r-- | examples/sql/books/bookwindow.py | 140 | ||||
-rw-r--r-- | examples/sql/books/bookwindow.ui | 164 | ||||
-rw-r--r-- | examples/sql/books/createdb.py | 117 | ||||
-rw-r--r-- | examples/sql/books/images/star.png | bin | 0 -> 782 bytes | |||
-rw-r--r-- | examples/sql/books/main.py | 53 | ||||
-rw-r--r-- | examples/sql/books/rc_books.py | 88 | ||||
-rw-r--r-- | examples/sql/books/ui_bookwindow.py | 129 | ||||
-rw-r--r-- | sources/pyside2/cmake/Macros/PySideModules.cmake | 13 | ||||
-rw-r--r-- | sources/pyside2/doc/deployment-fbs.rst | 20 | ||||
-rw-r--r-- | sources/pyside2/doc/deployment-pyinstaller.rst | 49 | ||||
-rw-r--r-- | sources/pyside2/doc/deployment.rst | 10 | ||||
-rw-r--r-- | sources/shiboken2/libshiboken/basewrapper.cpp | 99 |
17 files changed, 1001 insertions, 44 deletions
diff --git a/coin_build_instructions.py b/coin_build_instructions.py index d094d5b2a..9a7d6272c 100644 --- a/coin_build_instructions.py +++ b/coin_build_instructions.py @@ -101,7 +101,7 @@ def call_setup(python_ver): rmtree(_env, True) run_instruction(["virtualenv", "-p", _pExe, _env], "Failed to create virtualenv") - install_pip_dependencies(env_pip, ["numpy", "setuptools", "sphinx", "six"]) + install_pip_dependencies(env_pip, ["pip", "numpy", "setuptools", "sphinx", "six"]) install_pip_wheel_package(env_pip) cmd = [env_python, "-u", "setup.py"] @@ -116,8 +116,6 @@ def call_setup(python_ver): "--verbose-build"] if python_ver == "3": cmd += ["--limited-api=yes"] - else: - cmd += ["--skip-docs"] # 1.4.2019: errors in sphinx_build on openSUSE 4.2 if is_snapshot_build(): cmd += ["--snapshot-build"] diff --git a/coin_test_instructions.py b/coin_test_instructions.py index 6c3e051bd..2a42ec677 100644 --- a/coin_test_instructions.py +++ b/coin_test_instructions.py @@ -38,7 +38,6 @@ ############################################################################# from build_scripts.options import has_option from build_scripts.options import option_value -from build_scripts.utils import get_python_dict from build_scripts.utils import install_pip_dependencies from build_scripts.utils import install_pip_wheel_package from build_scripts.utils import get_qtci_virtualEnv @@ -65,19 +64,11 @@ if _ci_features is not None: CI_RELEASE_CONF = has_option("packaging") -def get_package_version(): - """ Returns the version string for the PySide2 package. """ - pyside_version_py = os.path.join(os.path.dirname(__file__), - "sources", "pyside2", "pyside_version.py") - dict = get_python_dict(pyside_version_py) - return (int(dict['major_version']), int(dict['minor_version']), - int(dict['patch_version'])) - def call_testrunner(python_ver, buildnro): _pExe, _env, env_pip, env_python = get_qtci_virtualEnv(python_ver, CI_HOST_OS, CI_HOST_ARCH, CI_TARGET_ARCH) rmtree(_env, True) run_instruction(["virtualenv", "-p", _pExe, _env], "Failed to create virtualenv") - install_pip_dependencies(env_pip, ["numpy", "PyOpenGL", "setuptools", "six", "pyinstaller"]) + install_pip_dependencies(env_pip, ["pip", "numpy", "PyOpenGL", "setuptools", "six", "pyinstaller"]) install_pip_wheel_package(env_pip) cmd = [env_python, "testrunner.py", "test", "--blacklist", "build_history/blacklist.txt", @@ -87,9 +78,7 @@ def call_testrunner(python_ver, buildnro): qmake_path = get_ci_qmake_path(CI_ENV_INSTALL_DIR, CI_HOST_OS) # Try to install built wheels, and build some buildable examples. - # Fixme: Skip wheel testing for Qt >= 5.14 until - # qt5/09f28e9e1d989a70c876138a4cf24e35c67e0fbb has landed in dev - if CI_RELEASE_CONF and get_package_version()[1] < 14: + if CI_RELEASE_CONF: wheel_tester_path = os.path.join("testing", "wheel_tester.py") cmd = [env_python, wheel_tester_path, qmake_path] run_instruction(cmd, "Error while running wheel_tester.py") diff --git a/examples/sql/books/bookdelegate.py b/examples/sql/books/bookdelegate.py new file mode 100644 index 000000000..a0bd92334 --- /dev/null +++ b/examples/sql/books/bookdelegate.py @@ -0,0 +1,134 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import copy +from PySide2.QtSql import QSqlRelationalDelegate +from PySide2.QtWidgets import (QItemDelegate, QSpinBox, QStyledItemDelegate, + QStyle, QStyleOptionViewItem) +from PySide2.QtGui import QMouseEvent, QPixmap, QPalette +from PySide2.QtCore import QEvent, QSize, Qt + + +class BookDelegate(QSqlRelationalDelegate): + """Books delegate to rate the books""" + + def __init__(self, parent=None): + QSqlRelationalDelegate.__init__(self, parent) + self.star = QPixmap(":/images/star.png") + + def paint(self, painter, option, index): + """ Paint the items in the table. + + If the item referred to by <index> is a StarRating, we + handle the painting ourselves. For the other items, we + let the base class handle the painting as usual. + + In a polished application, we'd use a better check than + the column number to find out if we needed to paint the + stars, but it works for the purposes of this example. + """ + if index.column() != 5: + # Since we draw the grid ourselves: + opt = copy.copy(option) + opt.rect = option.rect.adjusted(0, 0, -1, -1) + QSqlRelationalDelegate.paint(self, painter, opt, index) + else: + model = index.model() + if option.state & QStyle.State_Enabled: + if option.state & QStyle.State_Active: + color_group = QPalette.Normal + else: + color_group = QPalette.Inactive + else: + color_group = QPalette.Disabled + + if option.state & QStyle.State_Selected: + painter.fillRect(option.rect, + option.palette.color(color_group, QPalette.Highlight)) + rating = model.data(index, Qt.DisplayRole) + width = self.star.width() + height = self.star.height() + x = option.rect.x() + y = option.rect.y() + (option.rect.height() / 2) - (height / 2) + for i in range(rating): + painter.drawPixmap(x, y, self.star) + x += width + + # Since we draw the grid ourselves: + self.drawFocus(painter, option, option.rect.adjusted(0, 0, -1, -1)) + + pen = painter.pen() + painter.setPen(option.palette.color(QPalette.Mid)) + painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight()) + painter.drawLine(option.rect.topRight(), option.rect.bottomRight()) + painter.setPen(pen) + + def sizeHint(self, option, index): + """ Returns the size needed to display the item in a QSize object. """ + if index.column() == 5: + size_hint = QSize(5 * self.star.width(), self.star.height()) + QSize(1, 1) + return size_hint + # Since we draw the grid ourselves: + return QSqlRelationalDelegate.sizeHint(self, option, index) + QSize(1, 1) + + def editorEvent(self, event, model, option, index): + if index.column() != 5: + return False + + if event.type() == QEvent.MouseButtonPress: + mouse_pos = event.pos() + new_stars = int(0.7 + (mouse_pos.x() - option.rect.x()) / self.star.width()) + stars = max(0, min(new_stars, 5)) + model.setData(index, stars) + # So that the selection can change + return False + + return True + + def createEditor(self, parent, option, index): + if index.column() != 4: + return QSqlRelationalDelegate.createEditor(self, parent, option, index) + + # For editing the year, return a spinbox with a range from -1000 to 2100. + spinbox = QSpinBox(parent) + spinbox.setFrame(False) + spinbox.setMaximum(2100) + spinbox.setMinimum(-1000) + return spinbox diff --git a/examples/sql/books/books.pyproject b/examples/sql/books/books.pyproject new file mode 100644 index 000000000..44a1ef219 --- /dev/null +++ b/examples/sql/books/books.pyproject @@ -0,0 +1,5 @@ +{ + "files": ["main.py", "bookdelegate.py", "bookwindow.py", + "createdb.py", "books.qrc", "bookwindow.ui", + "images/star.png"] +} diff --git a/examples/sql/books/books.qrc b/examples/sql/books/books.qrc new file mode 100644 index 000000000..d6ad21337 --- /dev/null +++ b/examples/sql/books/books.qrc @@ -0,0 +1,5 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>images/star.png</file> +</qresource> +</RCC> diff --git a/examples/sql/books/bookwindow.py b/examples/sql/books/bookwindow.py new file mode 100644 index 000000000..500acf2ef --- /dev/null +++ b/examples/sql/books/bookwindow.py @@ -0,0 +1,140 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +from __future__ import print_function, absolute_import + +from PySide2.QtWidgets import (QAction, QAbstractItemView, qApp, QDataWidgetMapper, + QHeaderView, QMainWindow, QMessageBox) +from PySide2.QtGui import QKeySequence +from PySide2.QtSql import (QSqlRelation, QSqlRelationalTableModel, QSqlTableModel, + QSqlError) +from PySide2.QtCore import QAbstractItemModel, QObject, QSize, Qt, Slot +import createdb +from ui_bookwindow import Ui_BookWindow +from bookdelegate import BookDelegate + + +class BookWindow(QMainWindow, Ui_BookWindow): + """A window to show the books available""" + + def __init__(self): + QMainWindow.__init__(self) + self.setupUi(self) + + #check for SQL errors + err = createdb.init_db() + if err.type() is not QSqlError.NoError: + showError(err) + return + + model = QSqlRelationalTableModel(self.bookTable) + model.setEditStrategy(QSqlTableModel.OnManualSubmit) + model.setTable("books") + + # Remember the indexes of the columns: + author_idx = model.fieldIndex("author") + genre_idx = model.fieldIndex("genre") + + # Set the relations to the other database tables: + model.setRelation(author_idx, QSqlRelation("authors", "id", "name")) + model.setRelation(genre_idx, QSqlRelation("genres", "id", "name")) + + # Set the localized header captions: + model.setHeaderData(author_idx, Qt.Horizontal, self.tr("Author Name")) + model.setHeaderData(genre_idx, Qt.Horizontal, self.tr("Genre")) + model.setHeaderData(model.fieldIndex("title"), Qt.Horizontal, self.tr("Title")) + model.setHeaderData(model.fieldIndex("year"), Qt.Horizontal, self.tr("Year")) + model.setHeaderData(model.fieldIndex("rating"), Qt.Horizontal, self.tr("Rating")) + + if not model.select(): + print(model.lastError()) + + # Set the model and hide the ID column: + self.bookTable.setModel(model) + self.bookTable.setItemDelegate(BookDelegate(self.bookTable)) + self.bookTable.setColumnHidden(model.fieldIndex("id"), True) + self.bookTable.setSelectionMode(QAbstractItemView.SingleSelection) + + # Initialize the Author combo box: + self.authorEdit.setModel(model.relationModel(author_idx)) + self.authorEdit.setModelColumn(model.relationModel(author_idx).fieldIndex("name")) + + self.genreEdit.setModel(model.relationModel(genre_idx)) + self.genreEdit.setModelColumn(model.relationModel(genre_idx).fieldIndex("name")) + + # Lock and prohibit resizing of the width of the rating column: + self.bookTable.horizontalHeader().setSectionResizeMode(model.fieldIndex("rating"), + QHeaderView.ResizeToContents) + + mapper = QDataWidgetMapper(self) + mapper.setModel(model) + mapper.setItemDelegate(BookDelegate(self)) + mapper.addMapping(self.titleEdit, model.fieldIndex("title")) + mapper.addMapping(self.yearEdit, model.fieldIndex("year")) + mapper.addMapping(self.authorEdit, author_idx) + mapper.addMapping(self.genreEdit, genre_idx) + mapper.addMapping(self.ratingEdit, model.fieldIndex("rating")) + + selection_model = self.bookTable.selectionModel() + selection_model.currentRowChanged.connect(mapper.setCurrentModelIndex) + + self.bookTable.setCurrentIndex(model.index(0, 0)) + self.create_menubar() + + def showError(err): + QMessageBox.critical(self, "Unable to initialize Database", + "Error initializing database: " + err.text()) + + def create_menubar(self): + file_menu = self.menuBar().addMenu(self.tr("&File")) + quit_action = file_menu.addAction(self.tr("&Quit")) + quit_action.triggered.connect(qApp.quit) + + help_menu = self.menuBar().addMenu(self.tr("&Help")) + about_action = help_menu.addAction(self.tr("&About")) + about_action.setShortcut(QKeySequence.HelpContents) + about_action.triggered.connect(self.about) + aboutQt_action = help_menu.addAction("&About Qt") + aboutQt_action.triggered.connect(qApp.aboutQt) + + def about(self): + QMessageBox.about(self, self.tr("About Books"), + self.tr("<p>The <b>Books</b> example shows how to use Qt SQL classes " + "with a model/view framework.")) diff --git a/examples/sql/books/bookwindow.ui b/examples/sql/books/bookwindow.ui new file mode 100644 index 000000000..ce8f9f933 --- /dev/null +++ b/examples/sql/books/bookwindow.ui @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BookWindow</class> + <widget class="QMainWindow" name="BookWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>601</width> + <height>420</height> + </rect> + </property> + <property name="windowTitle"> + <string>Books</string> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>9</number> + </property> + <property name="topMargin"> + <number>9</number> + </property> + <property name="rightMargin"> + <number>9</number> + </property> + <property name="bottomMargin"> + <number>9</number> + </property> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string/> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>9</number> + </property> + <property name="topMargin"> + <number>9</number> + </property> + <property name="rightMargin"> + <number>9</number> + </property> + <property name="bottomMargin"> + <number>9</number> + </property> + <item> + <widget class="QTableView" name="bookTable"> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Details</string> + </property> + <layout class="QFormLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string><b>Title:</b></string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="titleEdit"> + <property name="enabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string><b>Author: </b></string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="authorEdit"> + <property name="enabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string><b>Genre:</b></string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="genreEdit"> + <property name="enabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string><b>Year:</b></string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QSpinBox" name="yearEdit"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="prefix"> + <string/> + </property> + <property name="minimum"> + <number>-1000</number> + </property> + <property name="maximum"> + <number>2100</number> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string><b>Rating:</b></string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QSpinBox" name="ratingEdit"> + <property name="maximum"> + <number>5</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </widget> + <tabstops> + <tabstop>bookTable</tabstop> + <tabstop>titleEdit</tabstop> + <tabstop>authorEdit</tabstop> + <tabstop>genreEdit</tabstop> + <tabstop>yearEdit</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/examples/sql/books/createdb.py b/examples/sql/books/createdb.py new file mode 100644 index 000000000..d662cacd1 --- /dev/null +++ b/examples/sql/books/createdb.py @@ -0,0 +1,117 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +from PySide2.QtSql import QSqlDatabase, QSqlError, QSqlQuery +from datetime import datetime + + +def add_book(q, title, year, authorId, genreId, rating): + q.addBindValue(title) + q.addBindValue(year) + q.addBindValue(authorId) + q.addBindValue(genreId) + q.addBindValue(rating) + q.exec_() + + +def add_genre(q, name): + q.addBindValue(name) + q.exec_() + return q.lastInsertId() + + +def add_author(q, name, birthdate): + q.addBindValue(name) + q.addBindValue(birthdate) + q.exec_() + return q.lastInsertId() + + +def init_db(): + db = QSqlDatabase.addDatabase("QSQLITE") + db.setDatabaseName(":memory:") + + if not db.open(): + return db.lastError() + + tables = db.tables() + for table in tables: + if table == "books" and table == "authors": + return QSqlError() + + q = QSqlQuery() + if not q.exec_("create table books(id integer primary key, title varchar, author integer, " + "genre integer, year integer, rating integer)"): + return q.lastError() + if not q.exec_("create table authors(id integer primary key, name varchar, birthdate date)"): + return q.lastError() + if not q.exec_("create table genres(id integer primary key, name varchar)"): + return q.lastError() + + if not q.prepare("insert into authors(name, birthdate) values(?, ?)"): + return q.lastError() + asimovId = add_author(q, "Isaac Asimov", datetime(1920, 2, 1)) + greeneId = add_author(q, "Graham Greene", datetime(1904, 10, 2)) + pratchettId = add_author(q, "Terry Pratchett", datetime(1948, 4, 28)) + + if not q.prepare("insert into genres(name) values(?)"): + return q.lastError() + sfiction = add_genre(q, "Science Fiction") + fiction = add_genre(q, "Fiction") + fantasy = add_genre(q, "Fantasy") + + if not q.prepare("insert into books(title, year, author, genre, rating) " + "values(?, ?, ?, ?, ?)"): + return q.lastError() + add_book(q, "Foundation", 1951, asimovId, sfiction, 3) + add_book(q, "Foundation and Empire", 1952, asimovId, sfiction, 4) + add_book(q, "Second Foundation", 1953, asimovId, sfiction, 3) + add_book(q, "Foundation's Edge", 1982, asimovId, sfiction, 3) + add_book(q, "Foundation and Earth", 1986, asimovId, sfiction, 4) + add_book(q, "Prelude to Foundation", 1988, asimovId, sfiction, 3) + add_book(q, "Forward the Foundation", 1993, asimovId, sfiction, 3) + add_book(q, "The Power and the Glory", 1940, greeneId, fiction, 4) + add_book(q, "The Third Man", 1950, greeneId, fiction, 5) + add_book(q, "Our Man in Havana", 1958, greeneId, fiction, 4) + add_book(q, "Guards! Guards!", 1989, pratchettId, fantasy, 3) + add_book(q, "Night Watch", 2002, pratchettId, fantasy, 3) + add_book(q, "Going Postal", 2004, pratchettId, fantasy, 3) + + return QSqlError() diff --git a/examples/sql/books/images/star.png b/examples/sql/books/images/star.png Binary files differnew file mode 100644 index 000000000..87f4464bd --- /dev/null +++ b/examples/sql/books/images/star.png diff --git a/examples/sql/books/main.py b/examples/sql/books/main.py new file mode 100644 index 000000000..50d2c0d6b --- /dev/null +++ b/examples/sql/books/main.py @@ -0,0 +1,53 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide2.QtWidgets import QApplication +from bookwindow import BookWindow +import rc_books + +if __name__ == "__main__": + app = QApplication([]) + + window = BookWindow() + window.resize(800, 600) + window.show() + + sys.exit(app.exec_()) diff --git a/examples/sql/books/rc_books.py b/examples/sql/books/rc_books.py new file mode 100644 index 000000000..6f2cbbeb6 --- /dev/null +++ b/examples/sql/books/rc_books.py @@ -0,0 +1,88 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 5.14.0 +# WARNING! All changes made in this file will be lost! + +from PySide2 import QtCore + +qt_resource_data = b"\ +\x00\x00\x03\x0e\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xffa\ +\x00\x00\x00\x09pHYs\x00\x00\x0b\x11\x00\x00\x0b\x11\ +\x01\x7fd_\x91\x00\x00\x00\x07tIME\x07\xd4\x09\ +\x03\x12\x11\x08\x18~\xe5:\x00\x00\x00\x06bKGD\ +\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x02\x9bID\ +AT8\xcbc\x98:c\x1e#:\xe6\xe5d\xcf\x17\ +\x12\x12\x16\xc4&\x87\x8e\x19\xb0\x09v\xc6\x18\xb7x\xea\ +\x8b\xcd\x9c=o\x09i\x06,X4\x8f\xf1\xd2\xa5\x99\ +L\xb9\xa1\x16\xc5\xc7\xbb\xed\xff\x0a\xf2\xb2;M\x9f\xb5\ +\x908\x03\x16,\x9a\xcb\xf8\xe0\xde\x04\x96\xc7\x0f\xdby\ +\xe7MO\xc8\xfbv\xbf\xe5\xff\xb4\x0a\x9b\x9by\x851\ +\xdc\xd3g-\x82k\x983\x7f)\xe3l F1`\ +\xca\xf4y\x8c\xd7\xaeMg\x02i~\xf2\xa8Y\xe1\xd2\ +\xa5\xfa\xdc_\x9f7\xfd\xffx\xbf\xea\x7fE\x96m\x97\ +\x81\x81>'33\x8b\xa5\x9e8gi\xb8\x9e\xc0f\ +&&\xa6D\x14\x03&N\x9d\xc7x\xef\xdeD\x96'\ +\x0f[E\x9f>j\xd6\xbdu\xb3\x22\xef\xd7\xb7=\xff\ +\xbe\x7f\xe8\xfb\x7f~S\xcc\xef\x05\xc5\xea\x9fNOQ\ +\xfb\x7f\xbaM\xed\xbf\x87\x1a\xefn5-\x1dV\x14\x03\ +f\xcf[\xce\xa8\xa4\xa9![W\xed\x9b}\xefJ\xcb\ +\xcew\xaf&\x7f\xfa\xfee\xc9\xff\xef\x1f\xfa\xff\xbf\xbf\ +\x95\xf2\xff\xc9^\x83\xffW\x17\xaa\xfdot\x12{\xc4\ +\xc7\xc7/\x8e\x12\x06Y\xb9\x85\xcc\xb2\x82\x1c\xf3\xa7D\ +\xab\xfe\xfa\xbe%\xe2\xff\x8fgm\xff\x7f|\x9a\x08\xd6\ +\xfc\xf5Y\xcd\xff\xd7\xe7\xfc\xfe\xdf\xde\xa0\xf5\x7fE\x94\ +\xecO\x16\x16V\xebi3\xe7\xa3\x06\xe2\xe4is\x18\ +\xe7/Z\xc1\xc8\xce\xc1i\x10\xe5\xa8\xd2\xbe\xa6\xcd\xe7\ +\xf6\xc3m\x99\xff~^(\xf8\xff\xe1j\xe0\xff\x17G\ +L\xff\xdf\xdf\xae\xf6\xbf\xc2]\xf4\xba\x9a\x9a\x06\x1bF\ +,\x00\x01#2\xe6\xe6\xe6`Q\xd6\x941_\xde\xe4\ +q\xfb\xc3y\xd3\xff\x1b\x8aT\xff\xbf?`\xff\xff\xdc\ +l\xe5\xff\xea\xc2\x1c9\xd3g-\xc0i\x00\x13\x10\x8b\ +\x03\xb1?\x10\xe7\xf5\x16\xd8\xde\xf8p\xc6\xe4\xbf\x9d<\ +\xf7t\x7fC\xe9\x95\xb7\x96\xd9\xff\x9b\x9c,\xfdN@\ +@H\x14\x9b\x01LP,\x06\xc4\x19@|\x22;\xca\ +\xf0\xe7\xe9\xf9\x06\xff\x81\xec\x03@\xbc^\x82\x9f\xf3\xf6\ +\x9e\x1a\xf3_az\x823P\xd2\x01T#3\x10\x0b\ +\x00\xb1\x1e\x10\x17\x03\xf1\xd1\xa8@\xdd\x9f\xad\x09J \ +\x03\xfe\x00\xf17 >\x0f\xb4kf\xb9\xa7\xea\x0d}\ +i>#d\x03\xb4\x808\x08\x88k\x81x\x09\xd4\xc6\ +\x1b\x11a\x06\xdf\xec\x94\xb8\xdf\x03\xd9;\x81x\x1a\x10\ +\xf7\x82\xd4\xb0\xb2\xb1G\xf9\xda\x99:L\x9d9\x9f\x09\ +f\x80\x0e\x10;\x02\xb1\x13\x10[\x00\xb1\x01\x10\x07\x06\ +{h\x9c\x02\xd2k\xa0\x86\x8b\x001\x17\x10\xf3\x80\xb0\ +\x88\x88(\xcb,hFC\xf6\x02\x08\xb3\x001+\x10\ +K122\xe4\x01i7 \x96\x01b6\xa88\x0b\ +T=cW\xef$\xb0\x01\x00\xceo{\xf5UL\xf0\ +\xac\x00\x00\x00\x00IEND\xaeB`\x82\ +" + +qt_resource_name = b"\ +\x00\x06\ +\x07\x03}\xc3\ +\x00i\ +\x00m\x00a\x00g\x00e\x00s\ +\x00\x08\ +\x0a\x85X\x07\ +\x00s\ +\x00t\x00a\x00r\x00.\x00p\x00n\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01j\x965\xd3\xea\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/examples/sql/books/ui_bookwindow.py b/examples/sql/books/ui_bookwindow.py new file mode 100644 index 000000000..5eb412d92 --- /dev/null +++ b/examples/sql/books/ui_bookwindow.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'bookwindow.ui' +## +## Created by: Qt User Interface Compiler version 5.14.0 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide2.QtCore import (QCoreApplication, QMetaObject, QObject, QPoint, + QRect, QSize, QUrl, Qt) +from PySide2.QtGui import (QBrush, QColor, QConicalGradient, QFont, + QFontDatabase, QIcon, QLinearGradient, QPalette, QPainter, QPixmap, + QRadialGradient) +from PySide2.QtWidgets import * + +class Ui_BookWindow(object): + def setupUi(self, BookWindow): + if BookWindow.objectName(): + BookWindow.setObjectName(u"BookWindow") + BookWindow.resize(601, 420) + self.centralWidget = QWidget(BookWindow) + self.centralWidget.setObjectName(u"centralWidget") + self.vboxLayout = QVBoxLayout(self.centralWidget); + self.vboxLayout.setSpacing(6) + self.vboxLayout.setObjectName(u"vboxLayout") + self.vboxLayout.setContentsMargins(9, 9, 9, 9); + self.groupBox = QGroupBox(self.centralWidget) + self.groupBox.setObjectName(u"groupBox") + self.vboxLayout1 = QVBoxLayout(self.groupBox); + self.vboxLayout1.setSpacing(6) + self.vboxLayout1.setObjectName(u"vboxLayout1") + self.vboxLayout1.setContentsMargins(9, 9, 9, 9); + self.bookTable = QTableView(self.groupBox) + self.bookTable.setObjectName(u"bookTable") + self.bookTable.setSelectionBehavior(QAbstractItemView.SelectRows) + + self.vboxLayout1.addWidget(self.bookTable); + + self.groupBox_2 = QGroupBox(self.groupBox) + self.groupBox_2.setObjectName(u"groupBox_2") + self.formLayout = QFormLayout(self.groupBox_2); + self.formLayout.setObjectName(u"formLayout") + self.label_5 = QLabel(self.groupBox_2) + self.label_5.setObjectName(u"label_5") + + self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label_5); + + self.titleEdit = QLineEdit(self.groupBox_2) + self.titleEdit.setObjectName(u"titleEdit") + self.titleEdit.setEnabled(True) + + self.formLayout.setWidget(0, QFormLayout.FieldRole, self.titleEdit); + + self.label_2 = QLabel(self.groupBox_2) + self.label_2.setObjectName(u"label_2") + + self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_2); + + self.authorEdit = QComboBox(self.groupBox_2) + self.authorEdit.setObjectName(u"authorEdit") + self.authorEdit.setEnabled(True) + + self.formLayout.setWidget(1, QFormLayout.FieldRole, self.authorEdit); + + self.label_3 = QLabel(self.groupBox_2) + self.label_3.setObjectName(u"label_3") + + self.formLayout.setWidget(2, QFormLayout.LabelRole, self.label_3); + + self.genreEdit = QComboBox(self.groupBox_2) + self.genreEdit.setObjectName(u"genreEdit") + self.genreEdit.setEnabled(True) + + self.formLayout.setWidget(2, QFormLayout.FieldRole, self.genreEdit); + + self.label_4 = QLabel(self.groupBox_2) + self.label_4.setObjectName(u"label_4") + + self.formLayout.setWidget(3, QFormLayout.LabelRole, self.label_4); + + self.yearEdit = QSpinBox(self.groupBox_2) + self.yearEdit.setObjectName(u"yearEdit") + self.yearEdit.setEnabled(True) + self.yearEdit.setMinimum(-1000) + self.yearEdit.setMaximum(2100) + + self.formLayout.setWidget(3, QFormLayout.FieldRole, self.yearEdit); + + self.label = QLabel(self.groupBox_2) + self.label.setObjectName(u"label") + + self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label); + + self.ratingEdit = QSpinBox(self.groupBox_2) + self.ratingEdit.setObjectName(u"ratingEdit") + self.ratingEdit.setMaximum(5) + + self.formLayout.setWidget(4, QFormLayout.FieldRole, self.ratingEdit); + + + self.vboxLayout1.addWidget(self.groupBox_2); + + + self.vboxLayout.addWidget(self.groupBox); + + BookWindow.setCentralWidget(self.centralWidget) + QWidget.setTabOrder(self.bookTable, self.titleEdit) + QWidget.setTabOrder(self.titleEdit, self.authorEdit) + QWidget.setTabOrder(self.authorEdit, self.genreEdit) + QWidget.setTabOrder(self.genreEdit, self.yearEdit) + + self.retranslateUi(BookWindow) + + QMetaObject.connectSlotsByName(BookWindow) + # setupUi + + def retranslateUi(self, BookWindow): + BookWindow.setWindowTitle(QCoreApplication.translate("BookWindow", u"Books", None)) + self.groupBox.setTitle("") + self.groupBox_2.setTitle(QCoreApplication.translate("BookWindow", u"Details", None)) + self.label_5.setText(QCoreApplication.translate("BookWindow", u"<b>Title:</b>", None)) + self.label_2.setText(QCoreApplication.translate("BookWindow", u"<b>Author: </b>", None)) + self.label_3.setText(QCoreApplication.translate("BookWindow", u"<b>Genre:</b>", None)) + self.label_4.setText(QCoreApplication.translate("BookWindow", u"<b>Year:</b>", None)) + self.yearEdit.setPrefix("") + self.label.setText(QCoreApplication.translate("BookWindow", u"<b>Rating:</b>", None)) + # retranslateUi diff --git a/sources/pyside2/cmake/Macros/PySideModules.cmake b/sources/pyside2/cmake/Macros/PySideModules.cmake index cbf401102..77dc8c8ac 100644 --- a/sources/pyside2/cmake/Macros/PySideModules.cmake +++ b/sources/pyside2/cmake/Macros/PySideModules.cmake @@ -169,6 +169,19 @@ macro(create_pyside_module) set(ld_prefix "LD_LIBRARY_PATH=") endif() set(ld_prefix "${ld_prefix}${pysidebindings_BINARY_DIR}/libpyside${PATH_SEP}${SHIBOKEN_SHARED_LIBRARY_DIR}") + + # On Windows we also need to propagate the whole environment PATH value, because pyside modules + # import Qt, and the Qt modules are found from PATH. + if(WIN32) + # Get the value of PATH with CMake separators. + file(TO_CMAKE_PATH "$ENV{PATH}" path_value) + + # Replace the CMake list separators with "\;"s, to avoid the PATH values being + # interpreted as CMake list elements, we actually want to pass the whole string separated + # by ";" to the command line. + make_path(path_value "${path_value}") + string(APPEND ld_prefix "${PATH_SEP}${path_value}") + endif() set(generate_pyi_options run --skip --sys-path "${pysidebindings_BINARY_DIR}" "${SHIBOKEN_PYTHON_MODULE_DIR}") diff --git a/sources/pyside2/doc/deployment-fbs.rst b/sources/pyside2/doc/deployment-fbs.rst index 94c52a08b..ff489f745 100644 --- a/sources/pyside2/doc/deployment-fbs.rst +++ b/sources/pyside2/doc/deployment-fbs.rst @@ -14,9 +14,9 @@ options. Preparation =========== -Installing `fbs` can be done via **pip**:: +Installing `fbs` (>= 0.7.6) can be done via **pip**:: - pip install fbs pyinstaller==3.4 + pip install fbs If you are using a virtual environment, remember to activate it before installing it. @@ -62,18 +62,12 @@ The main file will be under the `python` directory, and its content by default i import sys - class AppContext(ApplicationContext): # 1. Subclass ApplicationContext - def run(self): # 2. Implement run() - window = QMainWindow() - version = self.build_settings['version'] - window.setWindowTitle("MyApp v" + version) - window.resize(250, 150) - window.show() - return self.app.exec_() # 3. End run() with this line - if __name__ == '__main__': - appctxt = AppContext() # 4. Instantiate the subclass - exit_code = appctxt.run() # 5. Invoke run() + appctxt = ApplicationContext() # 1. Instantiate ApplicationContext + window = QMainWindow() + window.resize(250, 150) + window.show() + exit_code = appctxt.app.exec_() # 2. Invoke appctxt.app.exec_() sys.exit(exit_code) The example will show an empty `QMainWindow`, and you can execute it by running:: diff --git a/sources/pyside2/doc/deployment-pyinstaller.rst b/sources/pyside2/doc/deployment-pyinstaller.rst index 8e6a76052..f361daf4a 100644 --- a/sources/pyside2/doc/deployment-pyinstaller.rst +++ b/sources/pyside2/doc/deployment-pyinstaller.rst @@ -122,3 +122,52 @@ an executable inside the `dist/` directory that you can execute:: cd dist/ ./MyApplication + + +Current Caveats To Be Aware Of +============================== + + +PyInstaller Problem +------------------- + +As already mentioned, `PyInstaller` will pick a system installation of PySide2 or +Shiboken2 instead of your virtualenv version without notice, if it exists. +This may be no problem if those PySide2 or shiboken2 versions are the same. + +If you are working with different versions, this can result in frustrating sessions, +when you think you are testing a new version, but `PyInstaller` +is silently working with a different, older version. + + +Problem with numpy in Python 2.7.16 +----------------------------------- + +A recent problem of PyInstaller is the appearance of Python 2.7.16 . +This Python version creates a problem that is known from Python 3 as a `Tcl/Tk` problem. +This does rarely show up in Python 3 because `Tcl/Tk` is seldom used with `PyInstaller. + +On Python 2.7.16, this problem is very much visible, since many people are using numpy. +For some reason, installing `numpy` creates a dependency of `Tcl/Tk`, which can +be circumvented only by explicitly excluding `Tcl/Tk` related things by adding +this line to the analysis section of the spec-file:: + + excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'], + + +Safety Instructions +------------------- + +o When using `PyInstaller` with `virtualenv`, make sure that there is no system + installation of PySide2 or shiboken2. + +o Before compiling, use `pip -uninstall pyside2 shiboken2 -y` multiple times, until + none of the programs is found anymore. + +o Pip is usually a good tool. But to be 100 % sure, you should directly remove + the PySide2 and shiboken2 folders from site-packages. + +o Be sure to use the right version of pip. The safest way to really run the right + pip, use the Python that you mean: Instead of the pip command, better use:: + + <path/to/your/>python -m pip diff --git a/sources/pyside2/doc/deployment.rst b/sources/pyside2/doc/deployment.rst index 582e38992..a125dc4d7 100644 --- a/sources/pyside2/doc/deployment.rst +++ b/sources/pyside2/doc/deployment.rst @@ -12,6 +12,7 @@ The options for a project are: 3. Freezing the application in a single binary file, or into a directory. For the **third** option, there are many available tools: + * `fbs <https://build-system.fman.io/>`_, * `PyInstaller <https://www.pyinstaller.org/>`_, * `cx_Freeze <https://anthony-tuininga.github.io/cx_Freeze/>`_, * `py2exe <http://www.py2exe.org/>`_, @@ -26,13 +27,14 @@ The following table summarizes the above mentioned tools support: =========== ======= ===== ===== ======= Name License Linux macOS Windows =========== ======= ===== ===== ======= +fbs GPL yes yes yes +PyInstaller GPL yes yes yes +cx_Freeze MIT yes yes yes py2exe MIT no no yes py2app MIT no yes no -cx_Freeze MIT yes yes yes -PyInstaller GPL yes yes yes =========== ======= ===== ===== ======= -From the table we can see that only *cx_Freeze* and *PyInstaller* +From the table we can see that only *fbs*, *cx_Freeze* and *PyInstaller* meet our requirements. All tools are command-line based, and it could become @@ -65,6 +67,6 @@ described tools. :name: mastertoc :maxdepth: 2 + deployment-fbs.rst deployment-pyinstaller.rst deployment-cxfreeze.rst - deployment-fbs.rst diff --git a/sources/shiboken2/libshiboken/basewrapper.cpp b/sources/shiboken2/libshiboken/basewrapper.cpp index 6ea793a79..faaca5e4b 100644 --- a/sources/shiboken2/libshiboken/basewrapper.cpp +++ b/sources/shiboken2/libshiboken/basewrapper.cpp @@ -94,6 +94,92 @@ static PyType_Spec SbkObjectType_Type_spec = { }; +#if PY_VERSION_HEX < 0x03000000 +/***************************************************************************** + * + * PYSIDE-816: Workaround for Python 2.7 + * + * This is an add-on for function typeobject.c:tp_new_wrapper from Python 2.7 . + * Problem: + * In Python 3.X, tp_new_wrapper uses this check: + + while (staticbase && (staticbase->tp_new == slot_tp_new)) + + * In Python 2.7, it uses this, instead: + + while (staticbase && (staticbase->tp_flags & Py_TPFLAGS_HEAPTYPE)) + + * The problem is that heap types have this unwanted dependency. + * But we cannot get at static slot_tp_new, and so we have to use + * the original function and patch Py_TPFLAGS_HEAPTYPE away during the call. + */ + +static PyCFunction old_tp_new_wrapper = nullptr; + +static PyObject * +tp_new_wrapper(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyTypeObject *type = reinterpret_cast<PyTypeObject *>(self); + Py_ssize_t orig_flags = type->tp_flags; + type->tp_flags &= ~Py_TPFLAGS_HEAPTYPE; + PyObject *ret = reinterpret_cast<ternaryfunc>(old_tp_new_wrapper)(self, args, kwds); + type->tp_flags = orig_flags; + return ret; +} + +// This is intentionally the new docstring of Python 3.7 . +static struct PyMethodDef tp_new_methoddef[] = { + {"__new__", (PyCFunction)tp_new_wrapper, METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("__new__($type, *args, **kwargs)\n--\n\n" + "Create and return a new object. " + "See help(type) for accurate signature.")}, + {0} +}; + +static int +get_old_tp_new_wrapper(void) +{ + // We get the old tp_new_wrapper from any initialized type. + PyTypeObject *type = &PyType_Type; + PyObject *dict = type->tp_dict; + PyObject *key, *func = nullptr; + Py_ssize_t pos = 0; + while (PyDict_Next(dict, &pos, &key, &func)) { + char *name = PyString_AsString(key); + if (strcmp(name, "__new__") == 0) { + break; + } + } + if (func == nullptr) + return -1; + PyCFunctionObject *pycf_ob = reinterpret_cast<PyCFunctionObject *>(func); + old_tp_new_wrapper = pycf_ob->m_ml->ml_meth; + return 0; +} + +static int +add_tp_new_wrapper(PyTypeObject *type) +{ + // get the original tp_new_wrapper + if (old_tp_new_wrapper == nullptr && get_old_tp_new_wrapper() < 0) + return -1; + // initialize tp_dict + if (type->tp_dict == nullptr) + type->tp_dict = PyDict_New(); + if (type->tp_dict == nullptr) + return -1; + PyObject *ob_type = reinterpret_cast<PyObject *>(type); + Shiboken::AutoDecRef func(PyCFunction_New(tp_new_methoddef, ob_type)); + if (func.isNull()) + return -1; + if (PyDict_SetItemString(type->tp_dict, "__new__", func)) + return -1; + return 0; +} +/*****************************************************************************/ +#endif // PY_VERSION_HEX < 0x03000000 + + PyTypeObject *SbkObjectType_TypeF(void) { static PyTypeObject *type = nullptr; @@ -102,13 +188,8 @@ PyTypeObject *SbkObjectType_TypeF(void) PepHeapType_SIZE + sizeof(SbkObjectTypePrivate); type = reinterpret_cast<PyTypeObject *>(PyType_FromSpec(&SbkObjectType_Type_spec)); #if PY_VERSION_HEX < 0x03000000 - // PYSIDE-816: Python 2.7 has a bad check for Py_TPFLAGS_HEAPTYPE in - // typeobject.c func tp_new_wrapper. In Python 3 it was updated after - // the transition to the new type API, but not in 2.7 . Fortunately, - // the types did not change much when transitioning to heaptypes. We - // pretend that this type is a normal type in 2.7 and hope that this - // has no bad effect. - type->tp_flags &= ~Py_TPFLAGS_HEAPTYPE; + if (add_tp_new_wrapper(type) < 0) + return nullptr; #endif } return type; @@ -299,10 +380,6 @@ void SbkObjectTypeDealloc(PyObject* pyObj) SbkObjectTypePrivate *sotp = PepType_SOTP(pyObj); PyTypeObject *type = reinterpret_cast<PyTypeObject*>(pyObj); -#if PY_VERSION_HEX < 0x03000000 - // PYSIDE-816: Restore the heap type flag. Better safe than sorry. - type->tp_flags |= Py_TPFLAGS_HEAPTYPE; -#endif PyObject_GC_UnTrack(pyObj); #ifndef Py_LIMITED_API Py_TRASHCAN_SAFE_BEGIN(pyObj); |