Search code examples
pythonpyqt5fbs

Access fbs resource files in non-GUI code


Using fbs to package a PyQt5 app, what could be the cleanest way to access resource files from non-GUI code?

In the example below, I made a simple PyQt5 app with one window containing one button. The button triggers a function that will read a .json resource file and print its attribute message in the console. The function print_data() defined in logic.py should also work without the GUI app, for example to be used by other python scripts. Therefore, it cannot be move as a method of the MainWindow class.

It is easy and handy to use the application context's get_resource() method to get the path to mainwindow.ui. Unfortunately, it cannot be used by print_data() to get the path to static_data.json as the function has no access to the application context.

What would be the best solution?

  • Use fbs's resources system only for gui-related resource files and keep static_data.json with the python files?
  • Try to access the application context outside of the MainWindow class?
  • Use another python resources library such as pkg_resources to handle non gui-related resource files?
  • Use another tool such as cx_freeze to package the app?

Directory structure:

|-- src
|  |-- main
|  |  |-- python
|  |  |  |-- main.py
|  |  |  |-- gui.py
|  |  |  |-- logic.py
|  |  |-- resources
|  |  |  |-- base
|  |  |  |  |-- mainwindow.ui
|  |  |  |  |-- static_data.json

Content of main.py:

from fbs_runtime.application_context.PyQt5 import ApplicationContext
import sys
from gui import MainWindow

if __name__ == '__main__':
    appctxt = ApplicationContext()
    window = MainWindow(appctxt)
    window.show()
    exit_code = appctxt.app.exec_()
    sys.exit(exit_code)

Content of gui.py:

from PyQt5.QtWidgets import QMainWindow
from PyQt5 import uic

from logic import print_data


class MainWindow(QMainWindow):
    def __init__(self, context):
        super().__init__()

        # Loading the .ui file from the resources
        self.ui = uic.loadUi(context.get_resource("mainwindow.ui"), self)

        # Binding the button to the print_data function defined in logic.py
        self.main_button.clicked.connect(print_data)

Content of logic.py:

import json


def print_data():

    # Getting the resource data filepath
    filepath = "../resources/base/static_data.json"

    # Processing the resource file
    with open(filepath) as file:
        data = json.load(file)
        print(data["message"])

Solution

  • python
    ├── base.py
    ├── gui.py
    ├── logic.py
    └── main.py
    

    One possible solution is to create the context in a file where everyone can access that variable:

    base.py

    from fbs_runtime.application_context.PyQt5 import ApplicationContext
    
    context = ApplicationContext()
    

    Then it is used in the other classes:

    gui.py

    from PyQt5.QtWidgets import QMainWindow
    from PyQt5 import uic
    
    from base import context
    
    from logic import print_data
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super().__init__()
            # Loading the .ui file from the resources
            self.ui = uic.loadUi(context.get_resource("mainwindow.ui"), self)
    
            # Binding the button to the print_data function defined in logic.py
            self.main_button.clicked.connect(print_data)
    

    logic.py

    import json
    
    from base import context
    
    
    def print_data():
        # Getting the resource data filepath
        filepath = context.get_resource("static_data.json")
    
        # Processing the resource file
        with open(filepath) as file:
            data = json.load(file)
            print(data["message"])
    

    main.py

    import sys
    
    from base import context
    from gui import MainWindow
    
    if __name__ == "__main__":
        window = MainWindow()
        window.resize(250, 150)
        window.show()
        exit_code = context.app.exec_()
        sys.exit(exit_code)