thp / pyotherside

Python Bindings for Qt 5 and Qt 6. Allows you to access a CPython 3 interpreter directly from your Qt QML user interface code.
https://thp.io/2011/pyotherside/
Other
364 stars 49 forks source link

Adding import path and importing declaratively #62

Closed estan closed 8 years ago

estan commented 8 years ago

This request is for a feature which I'm not sure is possible, but here it goes.

Consider the following test case:

test.qml

import QtQuick 2.0
import io.thp.pyotherside 1.4

Rectangle {
    color: "red"
    width: 100
    height: 100

    OtherItem {
        color: "blue"
        width: 50
        height: 50
    }

    Python {
        id: py

        Component.onCompleted: {
            addImportPath(Qt.resolvedUrl('.'));

            importModule("test", function() {
                console.log("imported test");
            });
        }
    }
}

OtherItem.qml

import QtQuick 2.0

Rectangle {
    state: "one"

    states: [
        State {
            name: "one"
        }
    ]

    onStateChanged: {
        console.log("state changed, calling some_func");

        /*
         * The below won't work, unless I do the addImportPath and importModule
         * here again:
         *
         * py.addImportPath(Qt.resolvedUrl('.'));
         *
         * py.importModule("test", function() {
         *     py.call("test.some_func", function () {
         *        console.log("some_func returned");
         *     });
         * });
         */
        py.call("test.some_func", function () {
            console.log("some_func returned");
        });
    }
}

test.py:

def some_func():
    print("hey")

which gives the following when calling qmlscene test.qml:

qml: state changed, calling some_func
"PyOtherSide error: Traceback (most recent call last):

  File "<string>", line 1, in <module>

NameError: name 'test' is not defined
"
Unhandled PyOtherSide error: Function not found: 'test.some_func' (Traceback (most recent call last):

  File "<string>", line 1, in <module>

NameError: name 'test' is not defined
)
qml: imported test

I guess I need the addImportPath / importModule in the onStateChanged since that handler is called before the Component.onCompleted on the Python component is called, due to the order in which the components are loaded. If I'd do the same in say a key press handler, it would be no problem, since the Python component would be fully loaded by the time the key is pressed.

It would be very convenient if I could do something like

Python {
    id: py
    importPaths:  [Qt.resolvedUrl('.')]
    imports: ['test']
    ...
}

and from then on be able to rely on that the 'test' module has been imported, no matter where in the visual tree I make use of the interpreter (py).

I'm not sure this is possible though?

estan commented 8 years ago

Seems some of the initial text in the issue description fell off. Added it back in.

estan commented 8 years ago

@thp: I'm assuming you've been busy, but have you had a chance to look at this? Maybe it's something I'm missing, but do I really have to addImportPath and importModule at each place where I make use of the interpreter, even if I'm using a single Python instance like this and referencing it by it's id? It makes it quite tedious when I'm using the interpreter from various parts throughout the code.

thp commented 8 years ago

The "test" module will only be available /after/ the import callback has been called, so use e.g. Qt.createComponent() to create the "OtherItem" instance and do that inside the callback (where you have console.log("imported test");) - only then can you be sure that the module has been imported and is ready to use.

estan commented 8 years ago

Alright. Yes, that makes sense. I guess I was just looking for some way around that, some way of having the Python interpreter guaranteed ready / imports done before everything else. But I realize now it probably doesn't make sense (would making the Python component a singleton work though?).

But yes, I think I'll create my top-level component dynamically like you suggest (since I have various places in its child components where I want to make use of the interpreter).

estan commented 8 years ago

Closing this issue now. I'm using something like:

    Component.onCompleted: {
        addImportPath(Qt.resolvedUrl('.'));

        importModule('orexplore.hmi.machine', function() {
            // Handle events from the Python side by emitting signals.
            setHandler('scan-progress', vidarScanProgress);
            setHandler('door', vidarDoorStatus);
            setHandler('error', vidarError);

            // Create the main window.
            var component = Qt.createComponent('MainWindow.qml');
            window = component.createObject(null);

            if (window === null) {
                console.log('Failed to create main window');
            }
        })
    }

for the Component.onCompleted of my Python component.

Thanks a lot for the help (and for pyotherside).