Repository: pyqtgraph/pyqtgraph

Issue:
import pyqtgraph.canvas fails.
<!-- In the following, please describe your issue in detail! -->
<!-- If some sections do not apply, just remove them. -->

### Short description
Importing pyqtgraph.canvas causes the program to abort and exit with an error code..

### Code to reproduce
<!-- Please provide a minimal working example that reproduces the issue in the code block below.
     Ideally, this should be a full example someone else could run without additional setup. -->

```python
print("Mark 0", flush=True)
import time
print("Mark 1", flush=True)
import pyqtgraph.canvas
print("Mark 2", flush=True)
time.sleep(10)
print("Mark 3", flush=True)

```

### Expected behavior
1. The program should print out all the markers four ,0 to 3, with a 10 seconds delay between marker 2 and 3.
2. The program should exit with status code 0 (ok)


### Real behavior
1. The program prints only markers 0 and 1.
2. The program exist with status code 127 (error)

### An error occurred?
No error messages were noticed. The program just exits when it tries to import pyqtgraph.canvas.

### Tested environment(s)

 * PyQtGraph version: <!-- output of pyqtgraph.__version__ --> 0.13.3
 * Qt Python binding: <!-- output of pyqtgraph.Qt.VERSION_INFO -->  PyQt6 6.5.2 Qt 6.5.2
 * Python version:  3.12.0
 * NumPy version: <!-- output of numpy.__version__ --> 1.26.0
 * Operating system:  Windows 10, Ryzen
 * Installation method: <!-- e.g. pip, conda, system packages, ... --> pip

### Additional context

The issue is discussed here in the pyinstaller repository https://github.com/pyinstaller/pyinstaller/issues/7991#issuecomment-1752032919

Swapping the two lines of CanvasManager as described the link above seems to fix the problem.



Discussion:
Hello, zapta. I read your post about pyqtgraph.canvas fail.
I create a PyQt6 app using PlotWidget from pyqtgraph.
Versions of my modules:
PyQt6==6.6.1
PyQt6-Qt6==6.6.1
PyQt6-sip==13.6.0
pyqtgraph==0.13.3
pip version 23.3.2
Python version 3.12.0

But when I try to create an exe-file using auto-py-to-exe program I see the next situation: auto-py-to-exe shows "36311 INFO: Loading module hook 'hook-pyqtgraph.py' from 'C:\\EXE_file\\graphic_exe\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\hooks\\stdhooks'..." message in command prompt and after that it returns the next error: "PyInstaller.isolated._parent.SubprocessDiedError: Child process died calling _collect_submodules() with args=('pyqtgraph.canvas', 'warn once') and kwargs={}. Its exit code was 3221225725."

I tried to repeat rokm's recommendation to swapping that two lines in CanvasManager.py file:
        CanvasManager.SINGLETON = self
        QtCore.QObject.__init__(self)
, but, unfortunately, it didn't fix my problem.
This is all lines from auto-py-to-exe command prompt:

Running auto-py-to-exe v2.42.0
Building directory: C:\Users\shema\AppData\Local\Temp\tmp3sqdzyor
Provided command: pyinstaller --noconfirm --onedir --console  "C:/EXE_file/graphic1.py"
Recursion Limit is set to 5000
Executing: pyinstaller --noconfirm --onedir --console C:/EXE_file/graphic1.py --distpath C:\Users\shema\AppData\Local\Temp\tmp3sqdzyor\application --workpath C:\Users\shema\AppData\Local\Temp\tmp3sqdzyor\build --specpath C:\Users\shema\AppData\Local\Temp\tmp3sqdzyor

24035 INFO: PyInstaller: 6.3.0
24039 INFO: Python: 3.12.0
24103 INFO: Platform: Windows-10-10.0.19045-SP0
24107 INFO: wrote C:\Users\shema\AppData\Local\Temp\tmp3sqdzyor\graphic1.spec
24118 INFO: Extending PYTHONPATH with paths
['C:\\EXE_file']
24488 INFO: checking Analysis
24506 INFO: Building Analysis because Analysis-00.toc is non existent
24509 INFO: Initializing module dependency graph...
24524 INFO: Caching module graph hooks...
24579 INFO: Analyzing base_library.zip ...
27066 INFO: Loading module hook 'hook-encodings.py' from 'C:\\EXE_file\\graphic_exe\\Lib\\site-packages\\PyInstaller\\hooks'...
31428 INFO: Loading module hook 'hook-pickle.py' from 'C:\\EXE_file\\graphic_exe\\Lib\\site-packages\\PyInstaller\\hooks'...
34152 INFO: Loading module hook 'hook-heapq.py' from 'C:\\EXE_file\\graphic_exe\\Lib\\site-packages\\PyInstaller\\hooks'...
35712 INFO: Caching module dependency graph...
36095 INFO: Running Analysis Analysis-00.toc
36106 INFO: Looking for Python shared library...
36127 INFO: Using Python shared library: C:\Users\shema\AppData\Local\Programs\Python\Python312\python312.dll
36131 INFO: Analyzing C:\EXE_file\graphic1.py
36174 INFO: Loading module hook 'hook-PyQt6.py' from 'C:\\EXE_file\\graphic_exe\\Lib\\site-packages\\PyInstaller\\hooks'...
36311 INFO: Loading module hook 'hook-pyqtgraph.py' from 'C:\\EXE_file\\graphic_exe\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\hooks\\stdhooks'...
An error occurred while packaging
Traceback (most recent call last):
  File "C:\EXE_file\graphic_exe\Lib\site-packages\auto_py_to_exe\packaging.py", line 132, in package
    run_pyinstaller()
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\__main__.py", line 198, in run
    run_build(pyi_config, spec_file, **vars(args))
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\__main__.py", line 69, in run_build
    PyInstaller.building.build_main.main(pyi_config, spec_file, **kwargs)
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\building\build_main.py", line 1071, in main
    build(specfile, distpath, workpath, clean_build)
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\building\build_main.py", line 1011, in build
    exec(code, spec_namespace)
  File "C:\Users\shema\AppData\Local\Temp\tmp3sqdzyor\graphic1.spec", line 4, in <module>
    a = Analysis(
        ^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\building\build_main.py", line 470, in __init__
    self.__postinit__()
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\building\datastruct.py", line 184, in __postinit__
    self.assemble()
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\building\build_main.py", line 608, in assemble
    priority_scripts.append(self.graph.add_script(script))
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\depend\analysis.py", line 268, in add_script
    self._top_script_node = super().add_script(pathname)
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\lib\modulegraph\modulegraph.py", line 1160, in add_script
    self._process_imports(n)
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\lib\modulegraph\modulegraph.py", line 2591, in _process_imports
    target_modules = self._safe_import_hook(*import_info, **kwargs)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\depend\analysis.py", line 437, in _safe_import_hook
    return super()._safe_import_hook(target_module_partname, source_module, target_attr_names, level, edge_attr)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\lib\modulegraph\modulegraph.py", line 2048, in _safe_import_hook
    target_modules = self.import_hook(
                     ^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\lib\modulegraph\modulegraph.py", line 1232, in import_hook
    target_package, target_module_partname = self._find_head_package(
                                             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\lib\modulegraph\modulegraph.py", line 1411, in _find_head_package
    target_package = self._safe_import_module(
                     ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\depend\analysis.py", line 484, in _safe_import_module
    return super()._safe_import_module(module_basename, module_name, parent_package)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\lib\modulegraph\modulegraph.py", line 1792, in _safe_import_module
    self._process_imports(n)
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\lib\modulegraph\modulegraph.py", line 2591, in _process_imports
    target_modules = self._safe_import_hook(*import_info, **kwargs)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\depend\analysis.py", line 368, in _safe_import_hook
    excluded_imports = self._find_all_excluded_imports(source_module.identifier)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\depend\analysis.py", line 356, in _find_all_excluded_imports
    excluded_imports.update(module_hook.excludedimports)
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\depend\imphook.py", line 316, in __getattr__
    self._load_hook_module()
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\depend\imphook.py", line 383, in _load_hook_module
    self._hook_module = importlib_load_source(self.hook_module_name, self.hook_filename)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\compat.py", line 581, in importlib_load_source
    mod_loader.exec_module(mod)
  File "<frozen importlib._bootstrap_external>", line 994, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "C:\EXE_file\graphic_exe\Lib\site-packages\_pyinstaller_hooks_contrib\hooks\stdhooks\hook-pyqtgraph.py", line 37, in <module>
    all_imports = collect_submodules("pyqtgraph", filter=lambda name: name != "pyqtgraph.examples")
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\utils\hooks\__init__.py", line 622, in collect_submodules
    modules, subpackages, on_error = isolated_python.call(_collect_submodules, name, on_error)
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\EXE_file\graphic_exe\Lib\site-packages\PyInstaller\isolated\_parent.py", line 307, in call
    raise SubprocessDiedError(
PyInstaller.isolated._parent.SubprocessDiedError: Child process died calling _collect_submodules() with args=('pyqtgraph.canvas', 'warn once') and kwargs={}. Its exit code was 3221225725.

Project output will not be moved to output folder
Complete.

I can send my PyQt6 app in .rar archive.
If you find something new, tell me and others.

PS:
I'm so sorry. I apologize to you, zapta, and others. I want to tell all others to check where you rewrite CanvasManager.py file.
I swapped that two line: _"CanvasManager.SINGLETON = self"_, _"QtCore.QObject.__init__(self)"_ and **use auto-py-to-exe not from virtual environment**. It is very important moment for other beginner- and middle-level programmers.
To repair that problem in venv, open your venv folder -> Lib -> pyqtgraph -> canvas -> "CanvasManager.py".

I join to your request, zapta, about PyQtGraph error not only like "pyqtgraph.canvas" fail, like the common module (At my example: "PyQtGraph breaks PyInstaller").

Resolving patch:
diff --git a/pyqtgraph/canvas/CanvasManager.py b/pyqtgraph/canvas/CanvasManager.py
--- a/pyqtgraph/canvas/CanvasManager.py
+++ b/pyqtgraph/canvas/CanvasManager.py
@@ -13,8 +13,9 @@ class CanvasManager(QtCore.QObject):
     def __init__(self):
         if CanvasManager.SINGLETON is not None:
             raise Exception("Can only create one canvas manager.")
-        CanvasManager.SINGLETON = self
+        # It is important to save SINGLETON *after* the call to QObject.__init__, see #2838.
         QtCore.QObject.__init__(self)
+        CanvasManager.SINGLETON = self
         self.canvases = weakref.WeakValueDictionary()
 
     @classmethod


Test patch:
diff --git a/tests/test_canvas_manager_crash.py b/tests/test_canvas_manager_crash.py
new file mode 100644
--- /dev/null
+++ b/tests/test_canvas_manager_crash.py
@@ -0,0 +1,8 @@
+import subprocess
+import sys
+
+
+def test_canvas_manager_singleton_crash() -> None:
+    """Regression test for #2838."""
+    proc = subprocess.run([sys.executable, "-c", "import pyqtgraph.canvas"])
+    assert proc.returncode == 0