Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Unscoped enums access for PyQt6 and other missing PyQt6 compatibility changes #271

Merged
merged 9 commits into from
Nov 22, 2021

Conversation

dalthviz
Copy link
Member

@dalthviz dalthviz commented Nov 1, 2021

Part of #233

@ccordoba12
Copy link
Member

@dalthviz, I was thinking that in order to avoid the penalty hit involved in loading all those symbols at startup, we could use lazy loading instead. In other words, if the symbol is requested then we load it automatically. If not, it won't be loaded at all.

These are some resources that could help you with that:

I think the third one is the most promising. Unfortunately, that requires Python 3.7, but we could make Qtpy depend on that version given that 3.6 is about to reach end of life.

@CAM-Gerlach
Copy link
Member

Given we've supported 3.6 up to this point with the QtPy 2.0 release, 3.6 still sees some significant use (e.g. its still the default Python on many CIs, distros, etc), and the bindings we support all still support it (particularly the Qt6 ones, which would be impossible to use with any version of QtPy on Python 3.6 otherwise, and in fact Qt6 + Py36 would actually see QtPy downgraded to a mutually incompatible version), if we did go ahead with this approach, it would make sense to guard the lazy imports behind a version check, or try-except, IMO.

@dalthviz
Copy link
Member Author

dalthviz commented Nov 2, 2021

I was thinking that in order to avoid the penalty hit involved in loading all those symbols at startup, we could use lazy loading instead.

Then we will need to precalculate a map of possible import calls without the use of enumeration classes? I'm not totally sure to understand here the use of lazy loading.

The penalty involved with what is here is more related to the automatic detection of the enums and setting the values at parent class level than the import it self I think (that's why I was experimenting with QtCore.Qt enums by having before hand the enum name classes).

@CAM-Gerlach
Copy link
Member

@dalthviz Just in case you aren't already aware and it would be helpful, python -X importtime -c "import qtpy" (or more specifically, -c "import qtpy.modulename") in Python 3.7+ profiles the time taken to import qtpy and each dep, arranged as a reverse tree.

@dalthviz
Copy link
Member Author

dalthviz commented Nov 3, 2021

Just in case doing some benchmarking tests using the timeit module and @CAM-Gerlach suggestion:

timeit testing

from qtpy import QtCore, QtWidgets, QtGui

  • With promote_enums being call:
(pyqt6) C:\Users\dalth>python -m timeit "from qtpy import QtCore, QtWidgets, QtGui"
1 loop, best of 5: 1.2 usec per loop
:0: UserWarning: The test results are likely unreliable. The worst time (7.1 usec) was more than four times slower than the best time (1.2 usec).
  • Without promote_enums being call:
(pyqt6) C:\Users\dalth\Desktop>python -m timeit "from qtpy import QtCore, QtWidgets, QtGui"
200000 loops, best of 5: 949 nsec per loop

import qtpy.QtCore

  • With promote_enums being call:
python -m timeit "import qtpy.QtCore"
1000000 loops, best of 5: 297 nsec per loop
  • Without promote_enums being call:
(pyqt6) C:\Users\dalth\Desktop>python -m timeit "import qtpy.QtCore"
1000000 loops, best of 5: 292 nsec per loop

import qtpy.QtWidgets

  • With promote_enums being call:
(pyqt6) C:\Users\dalth\Desktop>python -m timeit "import qtpy.QtWidgets"
1000000 loops, best of 5: 295 nsec per loop

  • Without promote_enums being call:
(pyqt6) C:\Users\dalth\Desktop>python -m timeit "import qtpy.QtWidgets"
1000000 loops, best of 5: 290 nsec per loop

import qtpy.QtGui

  • With promote_enums being call:
(pyqt6) C:\Users\dalth\Desktop>python -m timeit "import qtpy.QtGui"
1000000 loops, best of 5: 295 nsec per loop
  • Without promote_enums being call:
(pyqt6) C:\Users\dalth\Desktop>python -m timeit "import qtpy.QtGui"
1000000 loops, best of 5: 292 nsec per loop

importtime testing

from qtpy import QtCore, QtWidgets, QtGui

  • With promote_enums being call:
import time: self [us] | cumulative | imported package
import time:       321 |        321 |   _io
import time:       143 |        143 |   marshal
import time:       259 |        259 |   nt
import time:       126 |        126 |   winreg
import time:      2399 |       3247 | _frozen_importlib_external
import time:       108 |        108 |   time
import time:       396 |        503 | zipimport
import time:       185 |        185 |     _codecs
import time:      1228 |       1413 |   codecs
import time:      1204 |       1204 |   encodings.aliases
import time:      2846 |       5462 | encodings
import time:       780 |        780 | encodings.utf_8
import time:       757 |        757 | encodings.cp1252
import time:        70 |         70 | _signal
import time:        66 |         66 |     _abc
import time:       885 |        951 |   abc
import time:      1204 |       2154 | io
import time:       102 |        102 |       _stat
import time:       907 |       1008 |     stat
import time:      1771 |       1771 |     _collections_abc
import time:       903 |        903 |       genericpath
import time:      1384 |       2287 |     ntpath
import time:      1810 |       6874 |   os
import time:       832 |        832 |   _sitebuiltins
import time:      1186 |       1186 |     pywin32_system32
import time:      2693 |       3878 |   pywin32_bootstrap
import time:       606 |        606 |   sitecustomize
import time:       643 |        643 |   usercustomize
import time:      5051 |      17881 | site
import time:       927 |        927 |       packaging.__about__
import time:      1275 |       2202 |     packaging
import time:       216 |        216 |       itertools
import time:       808 |        808 |       keyword
import time:       108 |        108 |         _operator
import time:      1052 |       1159 |       operator
import time:       899 |        899 |       reprlib
import time:       112 |        112 |       _collections
import time:      3178 |       6369 |     collections
import time:       862 |        862 |         types
import time:      1473 |       2335 |       enum
import time:       107 |        107 |         _sre
import time:      1041 |       1041 |           sre_constants
import time:      1448 |       2488 |         sre_parse
import time:      1893 |       4487 |       sre_compile
import time:       109 |        109 |         _functools
import time:      1725 |       1833 |       functools
import time:        96 |         96 |       _locale
import time:       904 |        904 |       copyreg
import time:      2448 |      12101 |     re
import time:       999 |        999 |     warnings
import time:       816 |        816 |       collections.abc
import time:      1234 |       1234 |       contextlib
import time:      3025 |       5074 |     typing
import time:       938 |        938 |     packaging._structures
import time:      7548 |      35229 |   packaging.version
import time:       103 |        103 |       errno
import time:      1206 |       1206 |       signal
import time:       851 |        851 |         _weakrefset
import time:      1567 |       2417 |       threading
import time:       733 |        733 |       fcntl
import time:        76 |         76 |       msvcrt
import time:       124 |        124 |       _winapi
import time:      2357 |       7014 |     subprocess
import time:      4001 |      11015 |   platform
import time:      1088 |       1088 |   qtpy._version
import time:       681 |        681 |     PyQt5
import time:       152 |        832 |   PyQt5.QtCore
import time:      1122 |       1122 |         importlib
import time:       743 |        743 |           importlib._abc
import time:      1224 |       1967 |         importlib.util
import time:       690 |        690 |         importlib.machinery
import time:      1083 |       1083 |         weakref
import time:      1799 |       6660 |       pkgutil
import time:      3046 |       9705 |     PyQt6
import time:        81 |         81 |       atexit
import time:      1477 |       1558 |     PyQt6.sip
import time:     15103 |      26365 |   PyQt6.QtCore
import time:       139 |        139 |     PyQt6.QtDataVisualization
import time:       906 |       1045 |   qtpy.QtDataVisualization
import time:      3472 |      79043 | qtpy
import time:       572 |        572 |     qtpy.sip
import time:       886 |       1458 |   qtpy.enums_compat
import time:     78344 |      79801 | qtpy.QtCore
import time:      4753 |       4753 |     PyQt6.QtGui
import time:      8579 |      13331 |   PyQt6.QtWidgets
import time:      2436 |       2436 |     PyQt6.QtOpenGL
import time:      4527 |       6963 |   PyQt6.QtOpenGLWidgets
import time:     57861 |      78154 | qtpy.QtWidgets
import time:     38523 |      38523 | qtpy.QtGui

  • Without promote_enums being call:
import time: self [us] | cumulative | imported package
import time:       207 |        207 |   _io
import time:        64 |         64 |   marshal
import time:       216 |        216 |   nt
import time:       115 |        115 |   winreg
import time:      1124 |       1724 | _frozen_importlib_external
import time:       111 |        111 |   time
import time:      2148 |       2258 | zipimport
import time:       187 |        187 |     _codecs
import time:      1169 |       1356 |   codecs
import time:      1175 |       1175 |   encodings.aliases
import time:      2388 |       4918 | encodings
import time:       889 |        889 | encodings.utf_8
import time:       797 |        797 | encodings.cp1252
import time:        73 |         73 | _signal
import time:        88 |         88 |     _abc
import time:      1497 |       1585 |   abc
import time:      1288 |       2872 | io
import time:        95 |         95 |       _stat
import time:       805 |        899 |     stat
import time:      1789 |       1789 |     _collections_abc
import time:       784 |        784 |       genericpath
import time:      1544 |       2328 |     ntpath
import time:      1850 |       6864 |   os
import time:       844 |        844 |   _sitebuiltins
import time:      1211 |       1211 |     pywin32_system32
import time:      2933 |       4143 |   pywin32_bootstrap
import time:       577 |        577 |   sitecustomize
import time:       620 |        620 |   usercustomize
import time:      5054 |      18100 | site
import time:       941 |        941 |       packaging.__about__
import time:      1315 |       2256 |     packaging
import time:       220 |        220 |       itertools
import time:       797 |        797 |       keyword
import time:       106 |        106 |         _operator
import time:      1110 |       1216 |       operator
import time:       881 |        881 |       reprlib
import time:       109 |        109 |       _collections
import time:      3261 |       6482 |     collections
import time:       892 |        892 |         types
import time:      1556 |       2448 |       enum
import time:       118 |        118 |         _sre
import time:      1020 |       1020 |           sre_constants
import time:      1681 |       2701 |         sre_parse
import time:      2915 |       5733 |       sre_compile
import time:       103 |        103 |         _functools
import time:      1681 |       1784 |       functools
import time:       100 |        100 |       _locale
import time:       879 |        879 |       copyreg
import time:      2603 |      13545 |     re
import time:       984 |        984 |     warnings
import time:      1125 |       1125 |       collections.abc
import time:      1344 |       1344 |       contextlib
import time:      3231 |       5699 |     typing
import time:       909 |        909 |     packaging._structures
import time:      7377 |      37249 |   packaging.version
import time:       111 |        111 |       errno
import time:      1212 |       1212 |       signal
import time:       855 |        855 |         _weakrefset
import time:      1656 |       2511 |       threading
import time:       762 |        762 |       fcntl
import time:        81 |         81 |       msvcrt
import time:       131 |        131 |       _winapi
import time:      2511 |       7317 |     subprocess
import time:      3841 |      11157 |   platform
import time:      1020 |       1020 |   qtpy._version
import time:       649 |        649 |     PyQt5
import time:       119 |        767 |   PyQt5.QtCore
import time:       981 |        981 |         importlib
import time:       763 |        763 |           importlib._abc
import time:      1235 |       1998 |         importlib.util
import time:       699 |        699 |         importlib.machinery
import time:      1060 |       1060 |         weakref
import time:      1924 |       6660 |       pkgutil
import time:      3203 |       9863 |     PyQt6
import time:        79 |         79 |       atexit
import time:      1756 |       1834 |     PyQt6.sip
import time:     15151 |      26847 |   PyQt6.QtCore
import time:       137 |        137 |     PyQt6.QtDataVisualization
import time:       972 |       1109 |   qtpy.QtDataVisualization
import time:      2751 |      80897 | qtpy
import time:       615 |        615 |     qtpy.sip
import time:       901 |       1515 |   qtpy.enums_compat
import time:      6006 |       7521 | qtpy.QtCore
import time:      4728 |       4728 |     PyQt6.QtGui
import time:      9097 |      13824 |   PyQt6.QtWidgets
import time:      2488 |       2488 |     PyQt6.QtOpenGL
import time:      4104 |       6591 |   PyQt6.QtOpenGLWidgets
import time:    382741 |     403156 | qtpy.QtWidgets
import time:      9334 |       9334 | qtpy.QtGui

@ccordoba12
Copy link
Member

Just in case doing some benchmarking tests using the timeit module and @CAM-Gerlach suggestion

This is really cool! I can't see any problem using the promote_enums approach, so please continue in that direction and disregard my comments about considering lazy loading for this.

@CAM-Gerlach
Copy link
Member

Thanks for the detailed testing and writeup, @dalthviz !

Important to note, using timeit as here to test import times gives grossly misleading results without -n 1 -r 1, as sys.modules is globally cached, so all but the first imports in a test run is virtually instant (low hundreds of ns, exactly as you saw) and essentially constant-time regardless of content, so only the first repetition of the first loop actually means something (you can see this in your testing). Case in point, on current QtPy master with PyQt5:

$ python -m timeit "from qtpy import QtCore, QtWidgets, QtGui"
1 loop, best of 5: 3.42 usec per loop
$ python -m timeit -n 1 -r 1 "from qtpy import QtCore, QtWidgets, QtGui"
1 loop, best of 1: 163 msec per loop

That's 5 orders of magnitude off the true value without -n 1 -r 1 (which was very consistent, at least on my machine number PyQt5).

The -X importtime results paint an interesting picture, though it seems the results for QtWidgets is an outlier in your non-promoted enums run as its an OoM too large, both relative to the promoted enums case and my own testing. FWIW, here were my results of python -X importtime -c "from qtpy import QtCore, QtWidgets, QtGui" on master with PyQt5:

$ python -X importtime -c "from qtpy import QtCore, QtWidgets, QtGui"
import time: self [us] | cumulative | imported package
import time:       571 |        571 |   _io
import time:       149 |        149 |   marshal
import time:       510 |        510 |   nt
import time:       243 |        243 |   winreg
import time:      2425 |       3896 | _frozen_importlib_external
import time:      1017 |       1017 |   time
import time:       925 |       1941 | zipimport
import time:       253 |        253 |     _codecs
import time:      2873 |       3126 |   codecs
import time:      2706 |       2706 |   encodings.aliases
import time:      5643 |      11474 | encodings
import time:      1949 |       1949 | encodings.utf_8
import time:      2240 |       2240 | encodings.cp1252
import time:       245 |        245 | _signal
import time:      2194 |       2194 | encodings.latin_1
import time:       217 |        217 |     _abc
import time:      2360 |       2577 |   abc
import time:      2806 |       5382 | io
import time:       307 |        307 |       _stat
import time:      2577 |       2883 |     stat
import time:      4242 |       4242 |     _collections_abc
import time:      2031 |       2031 |       genericpath
import time:      3166 |       5196 |     ntpath
import time:      4105 |      16425 |   os
import time:      1794 |       1794 |   _sitebuiltins
import time:       302 |        302 |     _locale
import time:      2445 |       2747 |   _bootlocale
import time:      4131 |       4131 |   sitecustomize
import time:      1162 |       1162 |   usercustomize
import time:      8243 |      34500 | site
import time:      2288 |       2288 |       packaging.__about__
import time:      3062 |       5349 |     packaging
import time:       262 |        262 |         _heapq
import time:      2760 |       3021 |       heapq
import time:       357 |        357 |       itertools
import time:      2622 |       2622 |       keyword
import time:       340 |        340 |         _operator
import time:      3576 |       3916 |       operator
import time:      3095 |       3095 |       reprlib
import time:       554 |        554 |       _collections
import time:      8744 |      22307 |     collections
import time:      2739 |       2739 |         types
import time:      4868 |       7606 |       enum
import time:       349 |        349 |         _sre
import time:      3719 |       3719 |           sre_constants
import time:      3498 |       7216 |         sre_parse
import time:      3263 |      10828 |       sre_compile
import time:       278 |        278 |         _functools
import time:      4314 |       4591 |       functools
import time:      3128 |       3128 |       copyreg
import time:      5216 |      31368 |     re
import time:      2881 |       2881 |     warnings
import time:      2697 |       2697 |       collections.abc
import time:      3952 |       3952 |       contextlib
import time:      8687 |      15335 |     typing
import time:      2314 |       2314 |     packaging._structures
import time:     20888 |     100438 |   packaging.version
import time:       343 |        343 |       errno
import time:      4131 |       4131 |       signal
import time:      2506 |       2506 |         _weakrefset
import time:      4213 |       6719 |       threading
import time:      1703 |       1703 |       pwd
import time:      1549 |       1549 |       grp
import time:       241 |        241 |       msvcrt
import time:       326 |        326 |       _winapi
import time:      5331 |      20339 |     subprocess
import time:     11115 |      31454 |   platform
import time:      2965 |       2965 |   qtpy._version
import time:      4067 |       4067 |     PyQt5
import time:       172 |        172 |       atexit
import time:      3532 |       3703 |     PyQt5.sip
import time:     68116 |      75885 |   PyQt5.QtCore
import time:       383 |        383 |     PyQt5.QtDataVisualization
import time:      1714 |       2097 |   qtpy.QtDataVisualization
import time:      5372 |     218209 | qtpy
import time:      1336 |       1336 | qtpy.QtCore
import time:      1107 |       1107 |     qtpy._patch
import time:      1435 |       2542 |   qtpy._patch.qheaderview
import time:     13984 |      13984 |     PyQt5.QtGui
import time:     30251 |      44235 |   PyQt5.QtWidgets
import time:      1571 |      48346 | qtpy.QtWidgets
import time:      1844 |       1844 | qtpy.QtGui

Neglecting that, it looks like promotion has no effect on base qtpy package import time, as expected, while qtpy.QtCore import time increases 10x/1 OoM, from 7.5 ms to 80 ms, qtpy.QtGui import time increased 4x from 9 ms to 40 ms, and an unknown effect on qtpy.QtWidgets, but perhaps somewhere between 2-8x from 10-40 ms to 80 ms. Overall, relative to the base import time, users could see import times increase by around a few times if they use a modest number of widgets, perhaps around a few hundred ms additional (especially on slower machines). That isn't trivial, but it also is an OoM below the three seconds reported previously.

@CAM-Gerlach
Copy link
Member

CAM-Gerlach commented Nov 3, 2021

I pulled this PR and conducted the same tests as @dalthviz under Python 3.9.7 and PyQt6 on my machine (Windows x64, somewhat older) with and without this PR, with the identified issues fixed. Keep in mind that the timeit imports all include the (fixed-ish) time of importing qtpy itself, which was around 100 ms on my machine and unaffected by promotion, so subtract that from each time to get the true per-module import time.

TL;DR: Total time to import qtpy and all three modules increased about 4x (150 ms to 600 ms), while time to import one specific modules and qtpy increased 2-3x (100-150 ms to 300-600 ms); the net increase for the specific module (running timeit with --setup "import qtpy", not shown for brevity) could be much higher, on the order of 100x for QtCore (2 ms to 200 ms), 5x for QtGui (20 ms to 100 ms) and 4x for QtWidgets (50 ms to 200 ms), though potentially less realistic. Similarly, importing qtpy itself showed no change with -X importtime, while time to import QtCore increased 40x (5 ms to 200 ms), QtGui 30x (3 ms to 100 ms), and QtWidgets only 2.6x (75 ms to 200 ms), with a total net import time increase of qtpy plus all three of 2.3x (300 ms to 700 ms). So again, not great, not terrible. On the bright side, the test suite ran no slower, or in fact slighly faster.

Full Results

conda list

$ conda list qt
# packages in environment at C:\Miniconda3\envs\qt6-env:
#
# Name                    Version                   Build  Channel
pyqt6                     6.2.1                    pypi_0    pypi
pyqt6-qt6                 6.2.1                    pypi_0    pypi
pyqt6-sip                 13.1.0                   pypi_0    pypi
pyqt6-webengine           6.2.1                    pypi_0    pypi
pyqt6-webengine-qt6       6.2.1                    pypi_0    pypi
qtpy                      2.0.0.dev0                dev_0    <develop>

timeit testing

from qtpy import QtCore, QtWidgets, QtGui

  • With this PR:
$ python -m timeit -n 1 -r 1 "from qtpy import QtCore, QtWidgets, QtGui"
1 loop, best of 1: 591 msec per loop
  • With master:
$ python -m timeit -n 1 -r 1 "import qtpy.QtCore; import qtpy.QtWidgets; import qtpy.QtGui"
1 loop, best of 1: 161 msec per loop

import qtpy.QtCore

  • With this PR:
$ python -m timeit -n 1 -r 1 "import qtpy.QtCore"
1 loop, best of 1: 324 msec per loop
  • With master:
$ python -m timeit -n 1 -r 1 "import qtpy.QtCore"
1 loop, best of 1: 95.8 msec per loop

import qtpy.QtWidgets

  • With this PR:
$ python -m timeit -n 1 -r 1 "import qtpy.QtWidgets"
1 loop, best of 1: 298 msec per loop
  • With master:
$ python -m timeit -n 1 -r 1 "import qtpy.QtWidgets"
1 loop, best of 1: 150 msec per loop

import qtpy.QtGui

  • With this PR:
$ python -m timeit -n 1 -r 1 "import qtpy.QtGui"
1 loop, best of 1: 217 msec per 
  • With master:
$ python -m timeit -n 1 -r 1 "import qtpy.QtGui"
1 loop, best of 1: 119 msec per loop

importtime testing

from qtpy import QtCore, QtWidgets, QtGui

  • With this PR:
$ python -X importtime -c "from qtpy import QtCore, QtWidgets, QtGui"
import time: self [us] | cumulative | imported package
import time:       561 |        561 |   _io
import time:       128 |        128 |   marshal
import time:       491 |        491 |   nt
import time:       226 |        226 |   winreg
import time:      2017 |       3420 | _frozen_importlib_external
import time:       907 |        907 |   time
import time:       747 |       1653 | zipimport
import time:       178 |        178 |     _codecs
import time:      2475 |       2652 |   codecs
import time:      2428 |       2428 |   encodings.aliases
import time:      4113 |       9192 | encodings
import time:      1674 |       1674 | encodings.utf_8
import time:      1685 |       1685 | encodings.cp1252
import time:       192 |        192 | _signal
import time:      1413 |       1413 | encodings.latin_1
import time:       150 |        150 |     _abc
import time:      1782 |       1932 |   abc
import time:      2243 |       4174 | io
import time:       393 |        393 |       _stat
import time:      2342 |       2735 |     stat
import time:      3908 |       3908 |     _collections_abc
import time:      1512 |       1512 |       genericpath
import time:      2355 |       3866 |     ntpath
import time:      3400 |      13908 |   os
import time:      1760 |       1760 |   _sitebuiltins
import time:       293 |        293 |     _locale
import time:      2144 |       2437 |   _bootlocale
import time:      2433 |       2433 |   sitecustomize
import time:       998 |        998 |   usercustomize
import time:      6982 |      28515 | site
import time:      1809 |       1809 |       packaging.__about__
import time:      2420 |       4228 |     packaging
import time:       165 |        165 |         _heapq
import time:      1952 |       2116 |       heapq
import time:       273 |        273 |       itertools
import time:      1552 |       1552 |       keyword
import time:       210 |        210 |         _operator
import time:      2418 |       2628 |       operator
import time:      1750 |       1750 |       reprlib
import time:       207 |        207 |       _collections
import time:      6039 |      14563 |     collections
import time:      1886 |       1886 |         types
import time:      3909 |       5795 |       enum
import time:       194 |        194 |         _sre
import time:      2448 |       2448 |           sre_constants
import time:      2392 |       4840 |         sre_parse
import time:      2377 |       7410 |       sre_compile
import time:       183 |        183 |         _functools
import time:      2963 |       3146 |       functools
import time:      1841 |       1841 |       copyreg
import time:      4125 |      22315 |     re
import time:      2187 |       2187 |     warnings
import time:      1586 |       1586 |       collections.abc
import time:      2803 |       2803 |       contextlib
import time:      6542 |      10931 |     typing
import time:      1753 |       1753 |     packaging._structures
import time:     15728 |      71701 |   packaging.version
import time:       248 |        248 |       errno
import time:      2666 |       2666 |       signal
import time:      1973 |       1973 |         _weakrefset
import time:      3314 |       5286 |       threading
import time:      1188 |       1188 |       pwd
import time:      1096 |       1096 |       grp
import time:       183 |        183 |       msvcrt
import time:       248 |        248 |       _winapi
import time:      4449 |      15358 |     subprocess
import time:      8653 |      24011 |   platform
import time:      2187 |       2187 |   qtpy._version
import time:      1059 |       1059 |     PyQt5
import time:       229 |       1288 |   PyQt5.QtCore
import time:      2086 |       2086 |         importlib
import time:      1844 |       1844 |             importlib.machinery
import time:      2910 |       4754 |           importlib.abc
import time:      3354 |       8108 |         importlib.util
import time:      2942 |       2942 |         weakref
import time:      5015 |      18149 |       pkgutil
import time:      6361 |      24510 |     PyQt6
import time:       181 |        181 |       atexit
import time:      3556 |       3737 |     PyQt6.sip
import time:     53635 |      81880 |   PyQt6.QtCore
import time:       307 |        307 |     PyQt6.QtDataVisualization
import time:      1843 |       2149 |   qtpy.QtDataVisualization
import time:      4174 |     187387 | qtpy
import time:      1407 |       1407 |     qtpy.sip
import time:      2265 |       3671 |   qtpy.enums_compat
import time:    204817 |     208488 | qtpy.QtCore
import time:     10215 |      10215 |     PyQt6.QtGui
import time:     27608 |      37822 |   PyQt6.QtWidgets
import time:      5994 |       5994 |     PyQt6.QtOpenGL
import time:      9922 |      15916 |   PyQt6.QtOpenGLWidgets
import time:    148769 |     202506 | qtpy.QtWidgets
import time:    113668 |     113668 | qtpy.QtGui
  • With master:
 python -X importtime -c "from qtpy import QtCore, QtWidgets, QtGui"
import time: self [us] | cumulative | imported package
import time:       632 |        632 |   _io
import time:       178 |        178 |   marshal
import time:       526 |        526 |   nt
import time:       219 |        219 |   winreg
import time:      2400 |       3952 | _frozen_importlib_external
import time:       896 |        896 |   time
import time:       822 |       1718 | zipimport
import time:       202 |        202 |     _codecs
import time:      2642 |       2843 |   codecs
import time:      2273 |       2273 |   encodings.aliases
import time:      3875 |       8990 | encodings
import time:      1424 |       1424 | encodings.utf_8
import time:      2024 |       2024 | encodings.cp1252
import time:       300 |        300 | _signal
import time:      1558 |       1558 | encodings.latin_1
import time:       159 |        159 |     _abc
import time:      1834 |       1993 |   abc
import time:      1881 |       3874 | io
import time:       237 |        237 |       _stat
import time:      2320 |       2556 |     stat
import time:      3676 |       3676 |     _collections_abc
import time:      1625 |       1625 |       genericpath
import time:      2537 |       4161 |     ntpath
import time:      4655 |      15046 |   os
import time:      2163 |       2163 |   _sitebuiltins
import time:       207 |        207 |     _locale
import time:      1631 |       1837 |   _bootlocale
import time:      2207 |       2207 |   sitecustomize
import time:      1384 |       1384 |   usercustomize
import time:      6543 |      29178 | site
import time:      2586 |       2586 |       packaging.__about__
import time:      2383 |       4968 |     packaging
import time:       254 |        254 |         _heapq
import time:      2466 |       2719 |       heapq
import time:       329 |        329 |       itertools
import time:      1696 |       1696 |       keyword
import time:       239 |        239 |         _operator
import time:      2671 |       2909 |       operator
import time:      2074 |       2074 |       reprlib
import time:       261 |        261 |       _collections
import time:      7102 |      17087 |     collections
import time:      1903 |       1903 |         types
import time:      3609 |       5512 |       enum
import time:       218 |        218 |         _sre
import time:      2552 |       2552 |           sre_constants
import time:      3240 |       5792 |         sre_parse
import time:      2274 |       8283 |       sre_compile
import time:       214 |        214 |         _functools
import time:      2930 |       3143 |       functools
import time:      1874 |       1874 |       copyreg
import time:      4848 |      23658 |     re
import time:      2087 |       2087 |     warnings
import time:      1878 |       1878 |       collections.abc
import time:      2587 |       2587 |       contextlib
import time:      8350 |      12814 |     typing
import time:      1634 |       1634 |     packaging._structures
import time:     19088 |      81332 |   packaging.version
import time:       223 |        223 |       errno
import time:      3363 |       3363 |       signal
import time:      2036 |       2036 |         _weakrefset
import time:      3547 |       5582 |       threading
import time:      1617 |       1617 |       pwd
import time:      1171 |       1171 |       grp
import time:       324 |        324 |       msvcrt
import time:       307 |        307 |       _winapi
import time:      4440 |      17023 |     subprocess
import time:      9249 |      26271 |   platform
import time:      2249 |       2249 |   qtpy._version
import time:      1162 |       1162 |     PyQt5
import time:       299 |       1460 |   PyQt5.QtCore
import time:      2199 |       2199 |         importlib
import time:      1370 |       1370 |             importlib.machinery
import time:      2788 |       4158 |           importlib.abc
import time:      2834 |       6991 |         importlib.util
import time:      3806 |       3806 |         weakref
import time:      5092 |      18086 |       pkgutil
import time:      6144 |      24230 |     PyQt6
import time:       198 |        198 |       atexit
import time:      3894 |       4092 |     PyQt6.sip
import time:     55747 |      84069 |   PyQt6.QtCore
import time:       446 |        446 |     PyQt6.QtDataVisualization
import time:      1990 |       2435 |   qtpy.QtDataVisualization
import time:      4865 |     202679 | qtpy
import time:      4718 |       4718 | qtpy.QtCore
import time:      2156 |       2156 |     qtpy._patch
import time:      3053 |       5209 |   qtpy._patch.qheaderview
import time:     13059 |      13059 |     PyQt6.QtGui
import time:     32334 |      45392 |   PyQt6.QtWidgets
import time:      5847 |       5847 |     PyQt6.QtOpenGL
import time:     10009 |      15856 |   PyQt6.QtOpenGLWidgets
import time:      9482 |      75937 | qtpy.QtWidgets
import time:      3146 |       3146 | qtpy.QtGui

@dalthviz dalthviz changed the title [WIP] PR: Unscoped enums access for PyQt6 PR: Unscoped enums access for PyQt6 Nov 4, 2021
@dalthviz dalthviz self-assigned this Nov 4, 2021
@dalthviz dalthviz added this to the v2.0.0 milestone Nov 4, 2021
@dalthviz dalthviz changed the title PR: Unscoped enums access for PyQt6 PR: Unscoped enums access for PyQt6 and other missing PyQt6 compatibility changes Nov 4, 2021
Copy link
Member

@CAM-Gerlach CAM-Gerlach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good to me @dalthviz , mostly just some trivial nitpicks and a few more substantive comments/suggestions, though I of course defer to you and @ccordoba12 's on matters of specific subject-matter expertise.

qtpy/enums_compat.py Outdated Show resolved Hide resolved
qtpy/enums_compat.py Outdated Show resolved Hide resolved
qtpy/enums_compat.py Outdated Show resolved Hide resolved
qtpy/enums_compat.py Outdated Show resolved Hide resolved
qtpy/sip.py Outdated Show resolved Hide resolved
qtpy/tests/test_qtgui.py Outdated Show resolved Hide resolved
qtpy/tests/test_qtgui.py Outdated Show resolved Hide resolved
qtpy/tests/test_qtwidgets.py Outdated Show resolved Hide resolved
qtpy/tests/test_qtwidgets.py Outdated Show resolved Hide resolved
qtpy/QtCore.py Outdated Show resolved Hide resolved
Carreau added a commit to Carreau/napari that referenced this pull request Nov 4, 2021
Carreau added a commit to Carreau/napari that referenced this pull request Nov 4, 2021
Copy link
Member

@CAM-Gerlach CAM-Gerlach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @dalthviz ; at least from the aspects I am qualified to evaluate, this this LGTM so far; the rest I leave to @ccordoba12 .

My one concern is the modest performance hit, but its not too bad and only affects PyQt5. I thought about suggesting ameliorating this by adding an env variable (unset by default) to disable it, so that applications only using scoped enums, or not using them at all, could avoid the import-time cost, but as that would introduce side effects for other packages using QtPy, on second though I don't think that's a great idea.

@CAM-Gerlach
Copy link
Member

Looks like this also fixes everything requested by @tlambert in #274 and added in PR #275 , aside from aliasing QDateTime.toPython to QDateTime.toPyDateTime. Assuming @dalthviz agrees, it might be simplest and avoid any merge conflicts to just add that line here (with a Co-authored-by: credit to @tlambert ), since this should be almost ready to go.

@CAM-Gerlach
Copy link
Member

@tlambert03 Looks like @dalthviz incorporated your change, thanks! It also allowed us to unskip a test.

Carreau added a commit to Carreau/napari that referenced this pull request Nov 9, 2021
tlambert03 pushed a commit to napari/napari that referenced this pull request Nov 9, 2021
@CAM-Gerlach
Copy link
Member

Hey @ccordoba12 , this should be ready to go unless you have any last concerns.

Copy link
Contributor

@tlambert03 tlambert03 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw, I've been testing this at superqt, and it works for all of our Qt6 tests. looking forward to having this released :)

Copy link
Member

@ccordoba12 ccordoba12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @dalthviz for this! I left some small comments and suggestions, otherwise looks good to me.

qtpy/QtCore.py Show resolved Hide resolved
qtpy/QtGui.py Show resolved Hide resolved
qtpy/QtWidgets.py Show resolved Hide resolved
qtpy/enums_compat.py Outdated Show resolved Hide resolved
qtpy/enums_compat.py Outdated Show resolved Hide resolved
qtpy/enums_compat.py Show resolved Hide resolved
qtpy/sip.py Outdated Show resolved Hide resolved
@ccordoba12
Copy link
Member

fwiw, I've been testing this at superqt, and it works for all of our Qt6 tests. looking forward to having this released :)

Thanks for the feedback @tlambert03! We're pretty close to release Qtpy 2.0

@tlambert03
Copy link
Contributor

One late thought here... haven't looked to closely at the code, but it would be nice if there were a way to disable unscoped enum access somehow (via env var or otherwise). For instance for internal testing (as opposed to having to lint for it)

@CAM-Gerlach
Copy link
Member

@tlambert03 I'd considered suggesting adding an env variable for this, at the time for performance reasons, but reasoned the extra global state, complexity and interaction with other packages importing QtPy ultimately dissuaded me from that idea. The use case here does sound relatively compelling, so long as the env var was treated as a dev-focused option so as to not break other packages, but the one concern here is that if other packages that yours depends on/interacts with also use QtPy, and don't expect unscoped enums, this will break them too (and probably before yours does, so you likely won't get to see the errors you're looking for if you're trying to test your application). So if we offered this option, we'd want to document this concern and make clear it should be used for local development only (which I can do as part of #61 / #85 that I'm working on for 2.0).

@tlambert03
Copy link
Contributor

tlambert03 commented Nov 21, 2021

The use case here does sound relatively compelling, so long as the env var was treated as a dev-focused option so as to not break other packages

yeah, this is certainly the concern. I get that it's kind of asking for bug reports on your end 😂 ... so i definitely understand if you want to guard that option carefully.

the one concern here is that if other packages that yours depends on/interacts with also use QtPy, and don't expect unscoped enums, this will break them too (and probably before yours does

not sure I followed that. are you saying, if my package did use the "disallow unscoped enums" env var in production? Or just generally? And if generally, isn't that only for Qt < 5.12?
Basically, similarly to @The-Compiler is doing with qutebrowser I'm changing all enums to their scoped variants in my packages. (I don't intend to support <5.12). So I'd like a way to ensure that contributors to my packages are doing that too, even if we use qtpy.

As an alternative, I suppose I could just monkeypatch qtpy in all of my test suites... or do some AST inspection...

So if we offered this option, we'd want to document this concern and make clear it should be used for local development only

yep, that would be the idea. Could even put that in the env var name: "DISABLE_SCOPED_ENUMS_FOR_DEV_ONLY" :)

@CAM-Gerlach
Copy link
Member

not sure I followed that. are you saying, if my package did use the "disallow unscoped enums" env var in production? Or just generally? And if generally, isn't that only for Qt < 5.12?

Hey, sorry for any ambiguity. Basically, if your library/script/application depended upon/imported/invoked etc. another library/script/application that also used QtPy, and that library/script/application expected unscoped enums, it would trigger a crash (regardless of whether your own code used them, and before it was fully exercised to determine that it didn't). If you have relatively tight control over your dep stack or you're interested in patching any upstream issues as well (as it sounds like you are), then this is fine, but there isn't a way to turn it off for just your own code that you control (like a warning filter). If that's fine and expected in your use case, then there's no issue, and as a dev-only best-effort option its an acceptable tradeoff, but it is something we'd want to make sure developers using it are aware of (unless I misunderstand something; my Qt-fu is almost certainly much less than yours).

@tlambert03
Copy link
Contributor

Basically, if your library/script/application depended upon/imported/invoked etc. another library/script/application that also used QtPy, and that library/script/application expected unscoped enums, it would trigger a crash

if the proposed "disallowed unscoped access" env var was set globally and the user had PyQt6 right? That part makes sense to me ... and why I fully agree that it should be a hidden switch, never used in production.

If you're worried about it at all (even just having that possibility around), I don't mind if you'd prefer not to add it.

@CAM-Gerlach
Copy link
Member

Okay, thanks! It seems to make sense to me, with appropriately dire warnings in the docs (and before that, it'll be completely undocumented), but its ultimately up to @ccordoba12 's and @dalthviz 's better judgement.

@ccordoba12
Copy link
Member

but its ultimately up to @ccordoba12 's and @dalthviz 's better judgement

I'm fine with adding it if you think it's going to be useful and it's well documented. But let's leave that for another PR if @tlambert03 is willing to give us a hand with it (since he's the one requesting for it).

Copy link
Member

@ccordoba12 ccordoba12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me now, great work here @dalthviz!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SignalInstance, Slot, and Property for Qt6 QtCore
4 participants