mirror of
https://github.com/thp/pyotherside.git
synced 2025-01-28 23:52:55 +08:00
QML API 1.3: Add support for qrc:/ import paths. Fixes #2
This commit is contained in:
parent
ea0bc3433c
commit
5c8d9cfe57
@ -28,7 +28,7 @@ This section describes the QML API exposed by the *PyOtherSide* QML Plugin.
|
||||
Import Versions
|
||||
---------------
|
||||
|
||||
The current QML API version of PyOtherSide is 1.2. When new features are
|
||||
The current QML API version of PyOtherSide is 1.3. When new features are
|
||||
introduced, or behavior is changed, the API version will be bumped and
|
||||
documented here.
|
||||
|
||||
@ -48,6 +48,13 @@ io.thp.pyotherside 1.2
|
||||
:func:`importModule` or :func:`call`, the signal :func:`error` is emitted
|
||||
with the exception information (filename, line, message) as ``traceback``.
|
||||
|
||||
io.thp.pyotherside 1.3
|
||||
``````````````````````
|
||||
|
||||
* :func:`addImportPath` now also accepts ``qrc:/`` URLs. This is useful if
|
||||
your Python files are embedded as Qt Resources, relative to your QML files
|
||||
(use :func:`Qt.resolvedUrl` from the QML file).
|
||||
|
||||
QML ``Python`` Element
|
||||
----------------------
|
||||
|
||||
@ -60,7 +67,7 @@ To use the ``Python`` element in a QML file, you have to import the plugin using
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
import io.thp.pyotherside 1.2
|
||||
import io.thp.pyotherside 1.3
|
||||
|
||||
Signals
|
||||
```````
|
||||
@ -89,13 +96,19 @@ path and then importing the module asynchronously:
|
||||
|
||||
.. function:: addImportPath(string path)
|
||||
|
||||
Add a local filesystem path to Python's ``sys.path``.
|
||||
Add a path to Python's ``sys.path``.
|
||||
|
||||
.. versionchanged:: 1.1.0
|
||||
:func:`addImportPath` will automatically strip a leading
|
||||
``file://`` from the path, so you can use :func:`Qt.resolvedUrl()`
|
||||
without having to manually strip the leading ``file://`` in QML.
|
||||
|
||||
.. versionchanged:: 1.3.0
|
||||
Starting with QML API version 1.3 (``import io.thp.pyotherside 1.3``),
|
||||
:func:`addImportPath` now also accepts ``qrc:/`` URLs. The first time
|
||||
a ``qrc:/`` path is added, a new import handler will be installed,
|
||||
which will enable Python to transparently import modules from it.
|
||||
|
||||
.. function:: importModule(string name, function callback(success) {})
|
||||
|
||||
Import a Python module.
|
||||
@ -104,7 +117,7 @@ path and then importing the module asynchronously:
|
||||
Previously, this function didn't work correctly for importing
|
||||
modules with dots in their name. Starting with the API version 1.2
|
||||
(``import io.thp.pyotherside 1.2``), this behavior is now fixed,
|
||||
and ``importModule('x.y.z, ...)`` behaves like ``import x.y.z``.
|
||||
and ``importModule('x.y.z', ...)`` behaves like ``import x.y.z``.
|
||||
|
||||
.. versionchanged:: 1.2.0
|
||||
If a JavaScript exception occurs in the callback, the :func:`error`
|
||||
@ -152,7 +165,7 @@ plugin and Python interpreter.
|
||||
.. note::
|
||||
This is not necessarily the same as the QML API version currently in use.
|
||||
The QML API version is decided by the QML import statement, so even if
|
||||
:func:`pluginVersion`` returns 1.2.0, if the plugin has been imported as
|
||||
:func:`pluginVersion` returns 1.2.0, if the plugin has been imported as
|
||||
``import io.thp.pyotherside 1.0``, the API version used would be 1.0.
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
@ -406,6 +419,12 @@ walking the whole resource tree, printing out directory names and file sizes:
|
||||
walk('/')
|
||||
|
||||
|
||||
Importing Python modules from Qt Resources also works starting with QML API 1.3
|
||||
using :func:`Qt.resolvedUrl` from within a QML file in Qt Resources. As an
|
||||
alternative, ``addImportPath('qrc:/')`` will add the root directory of the Qt
|
||||
Resources to Python's module search path.
|
||||
|
||||
|
||||
Cookbook
|
||||
========
|
||||
|
||||
@ -597,7 +616,7 @@ Using this function from QML is straightforward:
|
||||
.. code-block:: javascript
|
||||
|
||||
import QtQuick 2.0
|
||||
import io.thp.pyotherside 1.2
|
||||
import io.thp.pyotherside 1.3
|
||||
|
||||
Rectangle {
|
||||
color: 'black'
|
||||
@ -693,7 +712,7 @@ This module can now be imported in QML and used as ``source`` in the QML
|
||||
.. code-block:: javascript
|
||||
|
||||
import QtQuick 2.0
|
||||
import io.thp.pyotherside 1.2
|
||||
import io.thp.pyotherside 1.3
|
||||
|
||||
Image {
|
||||
id: image
|
||||
@ -784,6 +803,7 @@ Version 1.3.0 (UNRELEASED)
|
||||
--------------------------
|
||||
|
||||
* Access to the `Qt Resource System`_ from Python (see `Qt Resource Access`_).
|
||||
* QML API 1.3: Import from Qt Resources (:func:`addImportPath` with ``qrc:/``).
|
||||
|
||||
Version 1.2.0 (2014-02-16)
|
||||
--------------------------
|
||||
|
6
examples/qrc/data/below/qrc_example_below.py
Normal file
6
examples/qrc/data/below/qrc_example_below.py
Normal file
@ -0,0 +1,6 @@
|
||||
import sys
|
||||
import pyotherside
|
||||
|
||||
print('Hello from below!')
|
||||
print('sys.path =', sys.path)
|
||||
print('pyotherside =', pyotherside)
|
@ -1,5 +1,5 @@
|
||||
import QtQuick 2.0
|
||||
import io.thp.pyotherside 1.2
|
||||
import io.thp.pyotherside 1.3
|
||||
|
||||
Rectangle {
|
||||
width: 100
|
||||
@ -10,6 +10,10 @@ Rectangle {
|
||||
addImportPath(Qt.resolvedUrl('.'));
|
||||
importModule('qrc_example', function (success) {
|
||||
console.log('module imported: ' + success);
|
||||
addImportPath(Qt.resolvedUrl('below'));
|
||||
importModule('qrc_example_below', function (success) {
|
||||
console.log('also imported: ' + success);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,6 @@
|
||||
<qresource>
|
||||
<file>qrc_example.qml</file>
|
||||
<file>qrc_example.py</file>
|
||||
<file>below/qrc_example_below.py</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@ -60,4 +60,5 @@ PyOtherSideExtensionPlugin::registerTypes(const char *uri)
|
||||
qmlRegisterType<QPython10>(uri, 1, 0, PYOTHERSIDE_QPYTHON_NAME);
|
||||
// There is no PyOtherSide 1.1 import, as it's the same as 1.0
|
||||
qmlRegisterType<QPython12>(uri, 1, 2, PYOTHERSIDE_QPYTHON_NAME);
|
||||
qmlRegisterType<QPython13>(uri, 1, 3, PYOTHERSIDE_QPYTHON_NAME);
|
||||
}
|
||||
|
@ -83,6 +83,15 @@ QPython::addImportPath(QString path)
|
||||
path = path.mid(7);
|
||||
}
|
||||
|
||||
if (SINCE_API_VERSION(1, 3) && path.startsWith("qrc:")) {
|
||||
const char *module = "pyotherside.qrc_importer";
|
||||
QString filename = "/io/thp/pyotherside/qrc_importer.py";
|
||||
QString errorMessage = priv->importFromQRC(module, filename);
|
||||
if (!errorMessage.isNull()) {
|
||||
emit error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray utf8bytes = path.toUtf8();
|
||||
|
||||
PyObject *sys_path = PySys_GetObject((char*)"path");
|
||||
|
@ -317,4 +317,13 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class QPython13 : public QPython {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QPython13(QObject *parent=0)
|
||||
: QPython(parent, 1, 3)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* PYOTHERSIDE_QPYTHON_H */
|
||||
|
@ -379,3 +379,49 @@ QPythonPriv::instance()
|
||||
{
|
||||
return priv;
|
||||
}
|
||||
|
||||
QString
|
||||
QPythonPriv::importFromQRC(const char *module, const QString &filename)
|
||||
{
|
||||
PyObject *sys_modules = PySys_GetObject((char *)"modules");
|
||||
if (!PyMapping_Check(sys_modules)) {
|
||||
return QString("sys.modules is not a mapping object");
|
||||
}
|
||||
|
||||
PyObject *qrc_importer = PyMapping_GetItemString(sys_modules,
|
||||
(char *)module);
|
||||
|
||||
if (qrc_importer == NULL) {
|
||||
PyErr_Clear();
|
||||
|
||||
QFile qrc_importer_code(":" + filename);
|
||||
if (!qrc_importer_code.open(QIODevice::ReadOnly)) {
|
||||
return QString("Cannot load qrc importer source");
|
||||
}
|
||||
|
||||
QByteArray ba = qrc_importer_code.readAll();
|
||||
QByteArray fn = QString("qrc:/" + filename).toUtf8();
|
||||
|
||||
PyObject *co = Py_CompileString(ba.constData(), fn.constData(),
|
||||
Py_file_input);
|
||||
if (co == NULL) {
|
||||
QString result = QString("Cannot compile qrc importer: %1")
|
||||
.arg(formatExc());
|
||||
PyErr_Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
qrc_importer = PyImport_ExecCodeModule((char *)module, co);
|
||||
if (qrc_importer == NULL) {
|
||||
QString result = QString("Cannot exec qrc importer: %1")
|
||||
.arg(formatExc());
|
||||
PyErr_Clear();
|
||||
return result;
|
||||
}
|
||||
Py_XDECREF(co);
|
||||
}
|
||||
|
||||
Py_XDECREF(qrc_importer);
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ class QPythonPriv : public QObject {
|
||||
void enter();
|
||||
void leave();
|
||||
|
||||
QString importFromQRC(const char *module, const QString &filename);
|
||||
|
||||
void receiveObject(PyObject *o);
|
||||
static void closing();
|
||||
static QPythonPriv *instance();
|
||||
|
47
src/qrc_importer.py
Normal file
47
src/qrc_importer.py
Normal file
@ -0,0 +1,47 @@
|
||||
#
|
||||
# PyOtherSide: Asynchronous Python 3 Bindings for Qt 5
|
||||
# Copyright (c) 2014, Thomas Perl <m@thp.io>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
|
||||
import sys
|
||||
import pyotherside
|
||||
|
||||
from importlib import abc
|
||||
|
||||
class PyOtherSideQtRCImporter(abc.MetaPathFinder, abc.SourceLoader):
|
||||
def find_module(self, fullname, path):
|
||||
if path is None or all(x.startswith('qrc:') for x in path):
|
||||
if self.get_filename(fullname):
|
||||
return self
|
||||
|
||||
def get_filename(self, fullname):
|
||||
basename = fullname.replace('.', '/')
|
||||
|
||||
for import_path in sys.path:
|
||||
if not import_path.startswith('qrc:'):
|
||||
continue
|
||||
|
||||
for candidate in ('{}/{}.py', '{}/{}/__init__.py'):
|
||||
filename = candidate.format(import_path, basename)
|
||||
if pyotherside.qrc_is_file(filename[len('qrc:'):]):
|
||||
return filename
|
||||
|
||||
def get_data(self, path):
|
||||
return pyotherside.qrc_get_file_contents(path[len('qrc:'):])
|
||||
|
||||
def module_repr(self, m):
|
||||
return "<module '{}' from '{}'>".format(m.__name__, m.__file__)
|
||||
|
||||
sys.meta_path.append(PyOtherSideQtRCImporter())
|
6
src/qrc_importer.qrc
Normal file
6
src/qrc_importer.qrc
Normal file
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource prefix="/io/thp/pyotherside/">
|
||||
<file>qrc_importer.py</file>
|
||||
</qresource>
|
||||
</RCC>
|
@ -28,6 +28,9 @@ HEADERS += pyotherside_plugin.h
|
||||
SOURCES += qpython_imageprovider.cpp
|
||||
HEADERS += qpython_imageprovider.h
|
||||
|
||||
# Importer from Qt Resources
|
||||
RESOURCES += qrc_importer.qrc
|
||||
|
||||
# Python QML Object
|
||||
SOURCES += qpython.cpp
|
||||
HEADERS += qpython.h
|
||||
|
@ -153,4 +153,8 @@ TestPyOtherSide::testEvaluate()
|
||||
// PyOtherSide API 1.2
|
||||
QPython12 py12;
|
||||
testEvaluateWith(&py12);
|
||||
|
||||
// PyOtherSide API 1.3
|
||||
QPython13 py13;
|
||||
testEvaluateWith(&py13);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user