qgis / QGIS

QGIS is a free, open source, cross platform (lin/win/mac) geographical information system (GIS)
https://qgis.org
GNU General Public License v2.0
10.61k stars 3.01k forks source link

pyqt5_to_pyqt6.py fails on infinite recursion #58659

Closed kannes closed 1 month ago

kannes commented 2 months ago

What is the bug or the crash?

The script for conversion of Qt5 Python code to Qt6 always fails for me with

$ python /path/QGIS/scripts/pyqt5_to_pyqt6/pyqt5_to_pyqt6.py foo
Traceback (most recent call last):
  File "/path/QGIS/scripts/pyqt5_to_pyqt6/pyqt5_to_pyqt6.py", line 678, in <module>
    raise SystemExit(main())
                     ^^^^^^
  File "/path/QGIS/scripts/pyqt5_to_pyqt6/pyqt5_to_pyqt6.py", line 663, in main
    get_class_enums(value)
  File "/path/QGIS/scripts/pyqt5_to_pyqt6/pyqt5_to_pyqt6.py", line 650, in get_class_enums
    get_class_enums(value, depth)
  File "/path/QGIS/scripts/pyqt5_to_pyqt6/pyqt5_to_pyqt6.py", line 650, in get_class_enums
    get_class_enums(value, depth)
  File "/path/QGIS/scripts/pyqt5_to_pyqt6/pyqt5_to_pyqt6.py", line 650, in get_class_enums
    get_class_enums(value, depth)
  [Previous line repeated 993 more times]
  File "/path/QGIS/scripts/pyqt5_to_pyqt6/pyqt5_to_pyqt6.py", line 617, in get_class_enums
    matched_classes = {item}.union(all_subclasses(item))
                                   ^^^^^^^^^^^^^^^^^^^^
  File "/path/QGIS/scripts/pyqt5_to_pyqt6/pyqt5_to_pyqt6.py", line 615, in all_subclasses
    return {cls}.union(
                      ^
RecursionError: maximum recursion depth exceeded

Steps to reproduce the issue

  1. Create a new directory foo
  2. Run python pyqt5_to_pyqt6.py foo
  3. See the crash

This happens the same, if a proper directory full of Python files is specified or an empty directory.

Versions

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">

QGIS version | 3.38.1-Grenoble | QGIS code branch | Release 3.38 -- | -- | -- | -- Qt version | 5.15.14 Compiled against Python | 3.12.4 | Running against Python | 3.12.5 GDAL/OGR version | 3.9.1 PROJ version | 9.4.1 EPSG Registry database version | v11.006 (2024-03-13) GEOS version | 3.12.2-CAPI-1.18.2 Compiled against SQLite | 3.46.0 | Running against SQLite | 3.46.1 PDAL version | 2.7.2 PostgreSQL client version | 16.3 SpatiaLite version | 5.1.0 QWT version | 6.3.0 QScintilla2 version | 2.14.1 OS version | Manjaro Linux   |   |   |   Active Python plugins plugin_reloader | 0.11 spatial_filter_qt6 | 1.6 MetaSearch | 0.3.6 db_manager | 0.1.20 grassprovider | 2.12.99 processing | 2.12.99 QGIS version 3.38.1-Grenoble QGIS code branch [Release 3.38](https://github.com/qgis/QGIS/tree/release-3_38) Qt version 5.15.14 Compiled against Python 3.12.4 Running against Python 3.12.5 GDAL/OGR version 3.9.1 PROJ version 9.4.1 EPSG Registry database version v11.006 (2024-03-13) GEOS version 3.12.2-CAPI-1.18.2 Compiled against SQLite 3.46.0 Running against SQLite 3.46.1 PDAL version 2.7.2 PostgreSQL client version 16.3 SpatiaLite version 5.1.0 QWT version 6.3.0 QScintilla2 version 2.14.1 OS version Manjaro Linux Active Python plugins plugin_reloader 0.11 spatial_filter_qt6 1.6 MetaSearch 0.3.6 db_manager 0.1.20 grassprovider 2.12.99 processing 2.12.99 ### Supported QGIS version - [X] I'm running a supported QGIS version according to [the roadmap](https://www.qgis.org/en/site/getinvolved/development/roadmap.html#release-schedule). ### New profile - [X] I tried with a new [QGIS profile](https://docs.qgis.org/latest/en/docs/user_manual/introduction/qgis_configuration.html#working-with-user-profiles) ### Additional context I made the recursive call to `get_class_enums()` halt if it went to a depth of more than 2-3, and collect the items on which it went this far, I get: ``` qgis_analysis.QgsGeometryCheck.Change qgis_analysis.QgsGeometryCheck.ChangeType qgis_analysis.QgsGeometryCheck.ChangeWhat qgis_analysis.QgsGeometryCheck.CheckType qgis_analysis.QgsGeometryCheck.Flag qgis_analysis.QgsGeometryCheck.Flags qgis_analysis.QgsGeometryCheck.LayerFeatureIds qgis_core.Qgis qgis_core.Qgis.AnnotationItemFlags qgis_core.Qgis.AnnotationItemGuiFlags qgis_core.Qgis.BabelCommandFlags qgis_core.Qgis.BabelFormatCapabilities qgis_core.Qgis.BrowserItemCapabilities qgis_core.Qgis.CoordinateTransformationFlags qgis_core.Qgis.DataItemProviderCapabilities qgis_core.Qgis.DataProviderFlags qgis_core.Qgis.DatabaseProviderConnectionCapabilities2 qgis_core.Qgis.DateTimeStatistics qgis_core.Qgis.FeatureRequestFlags qgis_core.Qgis.FieldConfigurationFlags qgis_core.Qgis.FileOperationFlags qgis_core.Qgis.GeometryValidityFlags qgis_core.Qgis.GpsInformationComponents qgis_core.Qgis.HistoryProviderBackends qgis_core.Qgis.LabelLinePlacementFlags qgis_core.Qgis.LabelPolygonPlacementFlags qgis_core.Qgis.LabelingFlags qgis_core.Qgis.LayerFilters qgis_core.Qgis.LayerTreeFilterFlags qgis_core.Qgis.LegendJsonRenderFlags qgis_core.Qgis.LoadStyleFlags qgis_core.Qgis.MapLayerActionFlags qgis_core.Qgis.MapLayerActionTargets qgis_core.Qgis.MapLayerProperties qgis_core.Qgis.MapLayerRendererFlags qgis_core.Qgis.MapSettingsFlags qgis_core.Qgis.MarkerLinePlacements qgis_core.Qgis.MessageLevel qgis_core.Qgis.PlotToolFlags qgis_core.Qgis.ProcessingAlgorithmFlags qgis_core.Qgis.ProcessingFeatureSourceDefinitionFlags qgis_core.Qgis.ProcessingFeatureSourceFlags qgis_core.Qgis.ProcessingParameterFlags qgis_core.Qgis.ProcessingParameterTypeFlags qgis_core.Qgis.ProcessingProviderFlags qgis_core.Qgis.ProfileGeneratorFlags qgis_core.Qgis.ProjectCapabilities qgis_core.Qgis.ProjectFlags qgis_core.Qgis.ProjectReadFlags qgis_core.Qgis.ProviderStyleStorageCapabilities qgis_core.Qgis.RasterBandStatistics qgis_core.Qgis.RasterInterfaceCapabilities qgis_core.Qgis.RasterProviderCapabilities qgis_core.Qgis.RasterRendererCapabilities qgis_core.Qgis.RasterRendererFlags qgis_core.Qgis.RasterTemporalCapabilityFlags qgis_core.Qgis.RelationshipCapabilities qgis_core.Qgis.RenderContextFlags qgis_core.Qgis.ScriptLanguageCapabilities qgis_core.Qgis.SelectionFlags qgis_core.Qgis.SettingsOptions qgis_core.Qgis.SettingsTreeNodeOptions qgis_core.Qgis.SldExportOptions qgis_core.Qgis.SnappingTypes qgis_core.Qgis.SqlLayerDefinitionCapabilities qgis_core.Qgis.Statistics qgis_core.Qgis.StringStatistics qgis_core.Qgis.SublayerFlags qgis_core.Qgis.SublayerQueryFlags qgis_core.Qgis.SymbolFlags qgis_core.Qgis.SymbolLayerFlags qgis_core.Qgis.SymbolLayerUserFlags qgis_core.Qgis.SymbolPreviewFlags qgis_core.Qgis.SymbolRenderHints qgis_core.Qgis.TextRendererFlags qgis_core.Qgis.TiledSceneProviderCapabilities qgis_core.Qgis.TiledSceneRendererFlags qgis_core.Qgis.TiledSceneRequestFlags qgis_core.Qgis.VectorDataProviderAttributeEditCapabilities qgis_core.Qgis.VectorFileWriterCapabilities qgis_core.Qgis.VectorLayerTypeFlags qgis_core.Qgis.VectorRenderingSimplificationFlags qgis_core.Qgis.VectorTileProviderCapabilities qgis_core.Qgis.VectorTileProviderFlags qgis_core.Qgis.ViewSyncModeFlags qgis_core.Qgis.ZonalStatistics qgis_core.QgsAbstractDatabaseProviderConnection.Capabilities qgis_core.QgsAbstractDatabaseProviderConnection.Capability qgis_core.QgsAbstractDatabaseProviderConnection.GeometryColumnCapabilities qgis_core.QgsAbstractDatabaseProviderConnection.GeometryColumnCapability qgis_core.QgsAbstractDatabaseProviderConnection.QueryResult qgis_core.QgsAbstractDatabaseProviderConnection.SpatialIndexOptions qgis_core.QgsAbstractDatabaseProviderConnection.SqlVectorLayerOptions qgis_core.QgsAbstractDatabaseProviderConnection.TableFlags qgis_core.QgsAbstractDatabaseProviderConnection.TableProperty qgis_core.QgsAbstractGeometry.AxisOrder qgis_core.QgsAbstractGeometry.SegmentationToleranceType qgis_core.QgsAbstractGeometry.WkbFlag qgis_core.QgsAbstractGeometry.WkbFlags qgis_core.QgsArcGisRestUtils.FeatureToJsonFlags qgis_core.QgsAttributeEditorRelation.Button qgis_core.QgsAttributeEditorRelation.Buttons qgis_core.QgsAuthManager.MessageLevel qgis_core.QgsDataSourceUri.SslMode qgis_core.QgsFieldProxyModel.Filter qgis_core.QgsFieldProxyModel.Filters qgis_core.QgsLayoutManagerProxyModel.Filter qgis_core.QgsLayoutManagerProxyModel.Filters qgis_core.QgsLocatorFilter.Flag qgis_core.QgsLocatorFilter.Flags qgis_core.QgsLocatorFilter.Priority qgis_core.QgsMapLayer.LayerFlag qgis_core.QgsMapLayer.LayerFlags qgis_core.QgsMapLayer.PropertyType qgis_core.QgsMapLayer.ReadFlag qgis_core.QgsMapLayer.ReadFlags qgis_core.QgsMapLayer.StyleCategories qgis_core.QgsMapLayer.StyleCategory qgis_core.QgsMeshDriverMetadata.MeshDriverCapabilities qgis_core.QgsMeshDriverMetadata.MeshDriverCapability qgis_core.QgsPointCloudAttributeProxyModel.Filter qgis_core.QgsPointCloudAttributeProxyModel.Filters qgis_core.QgsProcessing.LayerOptionsFlags qgis_core.QgsProjectStyleDatabaseProxyModel.Filters qgis_core.QgsRasterProjector.Precision qgis_core.QgsRenderChecker.Flags qgis_core.QgsServerWmsDimensionProperties.PredefinedWmsDimensionName qgis_core.QgsServerWmsDimensionProperties.WmsDimensionInfo qgis_core.QgsSnappingConfig.IndividualLayerSettings qgis_core.QgsSnappingConfig.ScaleDependencyMode qgis_core.QgsSnappingConfig.SnappingType qgis_core.QgsTask.Flag qgis_core.QgsTask.Flags qgis_core.QgsTask.SubTaskDependency qgis_core.QgsTask.TaskStatus qgis_gui.QgsAdvancedDigitizingDockWidget.CadCapacities qgis_gui.QgsAdvancedDigitizingDockWidget.CadCapacity qgis_gui.QgsAdvancedDigitizingDockWidget.CadConstraint qgis_gui.QgsAdvancedDigitizingDockWidget.WidgetSetMode qgis_gui.QgsAttributeEditorContext.FormMode qgis_gui.QgsAttributeEditorContext.Mode qgis_gui.QgsAttributeEditorContext.RelationMode qgis_gui.QgsAttributeTableFilterModel.ColumnType qgis_gui.QgsAttributeTableFilterModel.FilterMode qgis_gui.QgsAuthSettingsWidget.WarningType qgis_gui.QgsCodeEditor.Flags qgis_gui.QgsColorButton.Behavior qgis_gui.QgsColorRampLegendNodeWidget.Capabilities qgis_gui.QgsColorTextWidget.ColorTextFormat qgis_gui.QgsCoordinateReferenceSystemProxyModel.Filter qgis_gui.QgsCoordinateReferenceSystemProxyModel.Filters qgis_gui.QgsDualView.FeatureListBrowsingAction qgis_gui.QgsDualView.ViewMode qgis_gui.QgsExpressionBuilderWidget.Flag qgis_gui.QgsExpressionBuilderWidget.Flags qgis_gui.QgsFileWidget.RelativeStorage qgis_gui.QgsFileWidget.StorageMode qgis_gui.QgsFilterLineEdit.ClearMode qgis_gui.QgsFloatingWidget.AnchorPoint qgis_gui.QgsFontButton.Mode qgis_gui.QgsGui.HigFlag qgis_gui.QgsGui.HigFlags qgis_gui.QgsGui.ProjectCrsBehavior qgis_gui.QgsMapToolIdentify.IdentifyMode qgis_gui.QgsMapToolIdentify.IdentifyResult qgis_gui.QgsMapToolIdentify.LayerType qgis_gui.QgsMapToolIdentify.Type qgis_gui.QgsMediaWidget.Mode qgis_gui.QgsProcessingToolboxProxyModel.Filters qgis_gui.QgsRelationEditorWidget.Button qgis_gui.QgsRelationEditorWidget.Buttons qgis_gui.QgsSourceSelectProvider.Capabilities qgis_gui.QgsSourceSelectProvider.Ordering qgis_gui.QgsSublayersDialog.LayerDefinition qgis_gui.QgsSublayersDialog.PromptMode qgis_gui.QgsSublayersDialog.ProviderType qgis_gui.QgsVectorLayerSaveAsDialog.Options ```
nicogodet commented 2 months ago

Same here:

PS D:\_QGIS\PLUGINS\isl_box> python .\scripts\pyqt5_to_pyqt6.py .\isl_box\core\
Could not find platform independent libraries <prefix>
Traceback (most recent call last):
  File "D:\_QGIS\PLUGINS\isl_box\scripts\pyqt5_to_pyqt6.py", line 710, in get_class_enums
    get_class_enums(value)
  File "D:\_QGIS\PLUGINS\isl_box\scripts\pyqt5_to_pyqt6.py", line 710, in get_class_enums
    get_class_enums(value)
  File "D:\_QGIS\PLUGINS\isl_box\scripts\pyqt5_to_pyqt6.py", line 710, in get_class_enums
    get_class_enums(value)
  [Previous line repeated 995 more times]
  File "D:\_QGIS\PLUGINS\isl_box\scripts\pyqt5_to_pyqt6.py", line 674, in get_class_enums
    matched_classes = {item}.union(all_subclasses(item))
                                   ^^^^^^^^^^^^^^^^^^^^
  File "D:\_QGIS\PLUGINS\isl_box\scripts\pyqt5_to_pyqt6.py", line 672, in all_subclasses
    return {cls}.union(s for c in cls.__subclasses__() for s in all_subclasses(c))
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded

(my editor might have fucked up the format so lines differs)

I added a print juste before the recursive call and here is the output:

python .\scripts\pyqt5_to_pyqt6.py .\isl_box\ ```console Get enums for qgis.PyQt.QtCore Get enums for *************************************************************************** QtCore.py --------------------- Date : November 2015 Copyright : (C) 2015 by Matthias Kuhn Email : matthias at opengis dot ch *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * *************************************************************************** Get enums for qgis.PyQt Get enums for <_frozen_importlib_external.SourceFileLoader object at 0x000001FA216EA870> Get enums for ModuleSpec(name='qgis.PyQt.QtCore', loader=<_frozen_importlib_external.SourceFileLoader object at 0x000001FA216EA870>, origin='C:\\OSGeo4W\\apps\\qgis\\python\\qgis\\PyQt\\QtCore.py') Get enums for C:\OSGeo4W\apps\qgis\python\qgis\PyQt\QtCore.py Get enums for C:\OSGeo4W\apps\qgis\python\qgis\PyQt\__pycache__\QtCore.cpython-312.pyc Get enums for {'__name__': 'builtins', '__doc__': "Built-in functions, types, exceptions, and other objects.\n\nThis module provides direct access to all 'built-in'\nidentifiers of Python; for example, builtins.len is\nthe full name for the built-in function len().\n\nThis module is not normally accessed explicitly by most\napplications, but can be useful in modules that provide\nobjects with the same name as a built-in value, but in\nwhich the built-in of that name is also needed.", '__package__': '', '__loader__': , '__spec__': ModuleSpec(name='builtins', loader=, origin='built-in'), '__build_class__': , '__import__': , 'abs': , 'all': , 'any': , 'ascii': , 'bin': , 'breakpoint': , 'callable': , 'chr': , 'compile': , 'delattr': , 'dir': , 'divmod': , 'eval': , 'exec': , 'format': , 'getattr': , 'globals': , 'hasattr': , 'hash': , 'hex': , 'id': , 'input': , 'isinstance': , 'issubclass': , 'iter': , 'aiter': , 'len': , 'locals': , 'max': , 'min': , 'next': , 'anext': , 'oct': , 'ord': , 'pow': , 'print': , 'repr': , 'round': , 'setattr': , 'sorted': , 'sum': , 'vars': , 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': , 'memoryview': , 'bytearray': , 'bytes': , 'classmethod': , 'complex': , 'dict': , 'enumerate': , 'filter': , 'float': , 'frozenset': , 'property': , 'int': , 'list': , 'map': , 'object': , 'range': , 'reversed': , 'set': , 'slice': , 'staticmethod': , 'str': , 'super': , 'tuple': , 'type': , 'zip': , '__debug__': True, 'BaseException': , 'BaseExceptionGroup': , 'Exception': , 'GeneratorExit': , 'KeyboardInterrupt': , 'SystemExit': , 'ArithmeticError': , 'AssertionError': , 'AttributeError': , 'BufferError': , 'EOFError': , 'ImportError': , 'LookupError': , 'MemoryError': , 'NameError': , 'OSError': , 'ReferenceError': , 'RuntimeError': , 'StopAsyncIteration': , 'StopIteration': , 'SyntaxError': , 'SystemError': , 'TypeError': , 'ValueError': , 'Warning': , 'FloatingPointError': , 'OverflowError': , 'ZeroDivisionError': , 'BytesWarning': , 'DeprecationWarning': , 'EncodingWarning': , 'FutureWarning': , 'ImportWarning': , 'PendingDeprecationWarning': , 'ResourceWarning': , 'RuntimeWarning': , 'SyntaxWarning': , 'UnicodeWarning': , 'UserWarning': , 'BlockingIOError': , 'ChildProcessError': , 'ConnectionError': , 'FileExistsError': , 'FileNotFoundError': , 'InterruptedError': , 'IsADirectoryError': , 'NotADirectoryError': , 'PermissionError': , 'ProcessLookupError': , 'TimeoutError': , 'IndentationError': , 'IndexError': , 'KeyError': , 'ModuleNotFoundError': , 'NotImplementedError': , 'RecursionError': , 'UnboundLocalError': , 'UnicodeError': , 'BrokenPipeError': , 'ConnectionAbortedError': , 'ConnectionRefusedError': , 'ConnectionResetError': , 'TabError': , 'UnicodeDecodeError': , 'UnicodeEncodeError': , 'UnicodeTranslateError': , 'ExceptionGroup': , 'EnvironmentError': , 'IOError': , 'WindowsError': , 'open': , 'quit': Use quit() or Ctrl-Z plus Return to exit, 'exit': Use exit() or Ctrl-Z plus Return to exit, 'copyright': Copyright (c) 2001-2023 Python Software Foundation. All Rights Reserved. Copyright (c) 2000 BeOpen.com. All Rights Reserved. Copyright (c) 1995-2001 Corporation for National Research Initiatives. All Rights Reserved. Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam. All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands for supporting Python development. See www.python.org for more information., 'license': See https://www.python.org/psf/license/, 'help': Type help() for interactive help, or help(object) for help about object.} Get enums for Matthias Kuhn Get enums for November 2015 Get enums for (C) 2015, Matthias Kuhn Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for 2 Get enums for 0 Get enums for 3 Get enums for 4 Get enums for 2 Get enums for 1 Get enums for 5.15.10 Get enums for 5.15.13 Get enums for 331530 Get enums for 331533 Get enums for Get enums for Get enums for Get enums for {'sip_flags': '-n PyQt5.sip -t Qt_5_15_13 -t WS_WIN'} Get enums for Get enums for Get enums for Get enums for Get enums for None Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for NULL Get enums for PyQt6.QtGui Get enums for None Get enums for PyQt6 Get enums for <_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD0CC20> Get enums for ModuleSpec(name='PyQt6.QtGui', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD0CC20>, origin='D:\\_QGIS\\PLUGINS\\isl_box\\.venv\\Lib\\site-packages\\PyQt6\\QtGui.pyd') Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for D:\_QGIS\PLUGINS\isl_box\.venv\Lib\site-packages\PyQt6\QtGui.pyd Get enums for PyQt6.QtWidgets Get enums for None Get enums for PyQt6 Get enums for <_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD0F230> Get enums for ModuleSpec(name='PyQt6.QtWidgets', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD0F230>, origin='D:\\_QGIS\\PLUGINS\\isl_box\\.venv\\Lib\\site-packages\\PyQt6\\QtWidgets.pyd') Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for 16777215 Get enums for D:\_QGIS\PLUGINS\isl_box\.venv\Lib\site-packages\PyQt6\QtWidgets.pyd Get enums for PyQt6.QtTest Get enums for None Get enums for PyQt6 Get enums for <_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD57590> Get enums for ModuleSpec(name='PyQt6.QtTest', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD57590>, origin='D:\\_QGIS\\PLUGINS\\isl_box\\.venv\\Lib\\site-packages\\PyQt6\\QtTest.pyd') Get enums for Get enums for Get enums for Get enums for D:\_QGIS\PLUGINS\isl_box\.venv\Lib\site-packages\PyQt6\QtTest.pyd Get enums for PyQt6.QtSql Get enums for None Get enums for PyQt6 Get enums for <_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD57050> Get enums for ModuleSpec(name='PyQt6.QtSql', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD57050>, origin='D:\\_QGIS\\PLUGINS\\isl_box\\.venv\\Lib\\site-packages\\PyQt6\\QtSql.pyd') Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for D:\_QGIS\PLUGINS\isl_box\.venv\Lib\site-packages\PyQt6\QtSql.pyd Get enums for PyQt6.QtSvg Get enums for None Get enums for PyQt6 Get enums for <_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD573E0> Get enums for ModuleSpec(name='PyQt6.QtSvg', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD573E0>, origin='D:\\_QGIS\\PLUGINS\\isl_box\\.venv\\Lib\\site-packages\\PyQt6\\QtSvg.pyd') Get enums for Get enums for Get enums for Get enums for D:\_QGIS\PLUGINS\isl_box\.venv\Lib\site-packages\PyQt6\QtSvg.pyd Get enums for PyQt6.QtXml Get enums for None Get enums for PyQt6 Get enums for <_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD576B0> Get enums for ModuleSpec(name='PyQt6.QtXml', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD576B0>, origin='D:\\_QGIS\\PLUGINS\\isl_box\\.venv\\Lib\\site-packages\\PyQt6\\QtXml.pyd') Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for D:\_QGIS\PLUGINS\isl_box\.venv\Lib\site-packages\PyQt6\QtXml.pyd Get enums for PyQt6.QtNetwork Get enums for None Get enums for PyQt6 Get enums for <_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD561B0> Get enums for ModuleSpec(name='PyQt6.QtNetwork', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD561B0>, origin='D:\\_QGIS\\PLUGINS\\isl_box\\.venv\\Lib\\site-packages\\PyQt6\\QtNetwork.pyd') Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for D:\_QGIS\PLUGINS\isl_box\.venv\Lib\site-packages\PyQt6\QtNetwork.pyd Get enums for PyQt6.QtPrintSupport Get enums for None Get enums for PyQt6 Get enums for <_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD55610> Get enums for ModuleSpec(name='PyQt6.QtPrintSupport', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FD55610>, origin='D:\\_QGIS\\PLUGINS\\isl_box\\.venv\\Lib\\site-packages\\PyQt6\\QtPrintSupport.pyd') Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for D:\_QGIS\PLUGINS\isl_box\.venv\Lib\site-packages\PyQt6\QtPrintSupport.pyd Get enums for PyQt6.Qsci Get enums for None Get enums for PyQt6 Get enums for <_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FCE10D0> Get enums for ModuleSpec(name='PyQt6.Qsci', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x000001FA1FCE10D0>, origin='D:\\_QGIS\\PLUGINS\\isl_box\\.venv\\Lib\\site-packages\\PyQt6\\Qsci.pyd') Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for 134657 Get enums for 2.14.1 Get enums for D:\_QGIS\PLUGINS\isl_box\.venv\Lib\site-packages\PyQt6\Qsci.pyd Get enums for qgis.core Get enums for *************************************************************************** __init__.py --------------------- Date : May 2014 Copyright : (C) 2014 by Nathan Woodrow Email : woodrow dot nathan at gmail dot com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * *************************************************************************** Get enums for qgis.core Get enums for <_frozen_importlib_external.SourceFileLoader object at 0x000001FA24771D30> Get enums for ModuleSpec(name='qgis.core', loader=<_frozen_importlib_external.SourceFileLoader object at 0x000001FA24771D30>, origin='C:\\OSGeo4W\\apps\\qgis\\python\\qgis\\core\\__init__.py', submodule_search_locations=['C:\\OSGeo4W\\apps\\qgis\\python\\qgis\\core']) Get enums for ['C:\\OSGeo4W\\apps\\qgis\\python\\qgis\\core'] Get enums for C:\OSGeo4W\apps\qgis\python\qgis\core\__init__.py Get enums for C:\OSGeo4W\apps\qgis\python\qgis\core\__pycache__\__init__.cpython-312.pyc Get enums for {'__name__': 'builtins', '__doc__': "Built-in functions, types, exceptions, and other objects.\n\nThis module provides direct access to all 'built-in'\nidentifiers of Python; for example, builtins.len is\nthe full name for the built-in function len().\n\nThis module is not normally accessed explicitly by most\napplications, but can be useful in modules that provide\nobjects with the same name as a built-in value, but in\nwhich the built-in of that name is also needed.", '__package__': '', '__loader__': , '__spec__': ModuleSpec(name='builtins', loader=, origin='built-in'), '__build_class__': , '__import__': , 'abs': , 'all': , 'any': , 'ascii': , 'bin': , 'breakpoint': , 'callable': , 'chr': , 'compile': , 'delattr': , 'dir': , 'divmod': , 'eval': , 'exec': , 'format': , 'getattr': , 'globals': , 'hasattr': , 'hash': , 'hex': , 'id': , 'input': , 'isinstance': , 'issubclass': , 'iter': , 'aiter': , 'len': , 'locals': , 'max': , 'min': , 'next': , 'anext': , 'oct': , 'ord': , 'pow': , 'print': , 'repr': , 'round': , 'setattr': , 'sorted': , 'sum': , 'vars': , 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': , 'memoryview': , 'bytearray': , 'bytes': , 'classmethod': , 'complex': , 'dict': , 'enumerate': , 'filter': , 'float': , 'frozenset': , 'property': , 'int': , 'list': , 'map': , 'object': , 'range': , 'reversed': , 'set': , 'slice': , 'staticmethod': , 'str': , 'super': , 'tuple': , 'type': , 'zip': , '__debug__': True, 'BaseException': , 'BaseExceptionGroup': , 'Exception': , 'GeneratorExit': , 'KeyboardInterrupt': , 'SystemExit': , 'ArithmeticError': , 'AssertionError': , 'AttributeError': , 'BufferError': , 'EOFError': , 'ImportError': , 'LookupError': , 'MemoryError': , 'NameError': , 'OSError': , 'ReferenceError': , 'RuntimeError': , 'StopAsyncIteration': , 'StopIteration': , 'SyntaxError': , 'SystemError': , 'TypeError': , 'ValueError': , 'Warning': , 'FloatingPointError': , 'OverflowError': , 'ZeroDivisionError': , 'BytesWarning': , 'DeprecationWarning': , 'EncodingWarning': , 'FutureWarning': , 'ImportWarning': , 'PendingDeprecationWarning': , 'ResourceWarning': , 'RuntimeWarning': , 'SyntaxWarning': , 'UnicodeWarning': , 'UserWarning': , 'BlockingIOError': , 'ChildProcessError': , 'ConnectionError': , 'FileExistsError': , 'FileNotFoundError': , 'InterruptedError': , 'IsADirectoryError': , 'NotADirectoryError': , 'PermissionError': , 'ProcessLookupError': , 'TimeoutError': , 'IndentationError': , 'IndexError': , 'KeyError': , 'ModuleNotFoundError': , 'NotImplementedError': , 'RecursionError': , 'UnboundLocalError': , 'UnicodeError': , 'BrokenPipeError': , 'ConnectionAbortedError': , 'ConnectionRefusedError': , 'ConnectionResetError': , 'TabError': , 'UnicodeDecodeError': , 'UnicodeEncodeError': , 'UnicodeTranslateError': , 'ExceptionGroup': , 'EnvironmentError': , 'IOError': , 'WindowsError': , 'open': , 'quit': Use quit() or Ctrl-Z plus Return to exit, 'exit': Use exit() or Ctrl-Z plus Return to exit, 'copyright': Copyright (c) 2001-2023 Python Software Foundation. All Rights Reserved. Copyright (c) 2000 BeOpen.com. All Rights Reserved. Copyright (c) 1995-2001 Corporation for National Research Initiatives. All Rights Reserved. Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam. All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands for supporting Python development. See www.python.org for more information., 'license': See https://www.python.org/psf/license/, 'help': Type help() for interactive help, or help(object) for help about object.} Get enums for Nathan Woodrow Get enums for May 2014 Get enums for (C) 2014, Nathan Woodrow Get enums for NULL Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for Get enums for ```
troopa81 commented 1 month ago

I fail to reproduce the issue on master (but script hasn't change since 3.38.1).

@nicogodet @kannes Did you modifiy the python script because it looks like code and traceback line number have changed.

How did you installed QGIS ? with packages? Which distribution ?

nicogodet commented 1 month ago

@troopa81 my VSCode has a format on save by default thats why and I also add some print and depth counter to avoid python error (look for comment with your GH name)

my pyqt5_to_pyqt6.py script ```python #!/usr/bin/env python3 """ *************************************************************************** 3to4.py --------------------- Date : 2023 December Copyright : (C) 2023 by Julien Cabieces Email : julien dot cabieces at oslandia dot com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * *************************************************************************** """ """ Migrate a folder containing python files from QGIS3/Qt5 to QGIS4/Qt6 Highly inspired from this video https://www.youtube.com/watch?v=G1omxo5pphw needed tools: pip install astpretty tokenize-rt Few useful commands: - display python file ast astpretty --no-show-offsets myfile.py - display file tokens tokenize-rt myfile.py """ __author__ = "Julien Cabieces" __date__ = "2023 December" __copyright__ = "(C) 2023, Julien Cabieces" import argparse import ast import glob import inspect import os import sys from collections import defaultdict from enum import Enum from typing import Sequence from PyQt6 import ( Qsci, QtCore, QtGui, QtNetwork, QtPrintSupport, QtSql, QtSvg, QtTest, QtWidgets, QtXml, ) from PyQt6.Qsci import * # noqa: F403 from PyQt6.QtCore import * # noqa: F403 from PyQt6.QtGui import * # noqa: F403 from PyQt6.QtNetwork import * # noqa: F403 from PyQt6.QtPrintSupport import * # noqa: F403 from PyQt6.QtSql import * # noqa: F403 from PyQt6.QtTest import * # noqa: F403 from PyQt6.QtWidgets import * # noqa: F403 from PyQt6.QtXml import * # noqa: F403 from tokenize_rt import Offset, reversed_enumerate, src_to_tokens, tokens_to_src try: import qgis._3d as qgis_3d # noqa: F403 import qgis.analysis as qgis_analysis # noqa: F403 import qgis.core as qgis_core # noqa: F403 import qgis.gui as qgis_gui # noqa: F403 from qgis._3d import * # noqa: F403 from qgis.analysis import * # noqa: F403 from qgis.core import * # noqa: F403 from qgis.gui import * # noqa: F403 except ImportError: qgis_core = None qgis_gui = None qgis_analysis = None qgis_3d = None print("QGIS classes not available for introspection, only a partial upgrade will be performed") sys.setrecursionlimit(1500) target_modules = [ QtCore, QtGui, QtWidgets, QtTest, QtSql, QtSvg, QtXml, QtNetwork, QtPrintSupport, Qsci, ] if qgis_core is not None: target_modules.extend([qgis_core, qgis_gui, qgis_analysis, qgis_3d]) # qmetatype which have been renamed qmetatype_mapping = { "Invalid": "UnknownType", "BitArray": "QBitArray", "Bitmap": "QBitmap", "Brush": "QBrush", "ByteArray": "QByteArray", "Char": "QChar", "Color": "QColor", "Cursor": "QCursor", "Date": "QDate", "DateTime": "QDateTime", "EasingCurve": "QEasingCurve", "Uuid": "QUuid", "ModelIndex": "QModelIndex", "PersistentModelIndex": "QPersistentModelIndex", "Font": "QFont", "Hash": "QVariantHash", "Icon": "QIcon", "Image": "QImage", "KeySequence": "QKeySequence", "Line": "QLine", "LineF": "QLineF", "List": "QVariantList", "Locale": "QLocale", "Map": "QVariantMap", "Transform": "QTransform", "Matrix4x4": "QMatrix4x4", "Palette": "QPalette", "Pen": "QPen", "Pixmap": "QPixmap", "Point": "QPoint", "PointF": "QPointF", "Polygon": "QPolygon", "PolygonF": "QPolygonF", "Quaternion": "QQuaternion", "Rect": "QRect", "RectF": "QRectF", "RegularExpression": "QRegularExpression", "Region": "QRegion", "Size": "QSize", "SizeF": "QSizeF", "SizePolicy": "QSizePolicy", "String": "QString", "StringList": "QStringList", "TextFormat": "QTextFormat", "TextLength": "QTextLength", "Time": "QTime", "Url": "QUrl", "Vector2D": "QVector2D", "Vector3D": "QVector3D", "Vector4D": "QVector4D", "UserType": "User", } deprecated_renamed_enums = { ("Qt", "MidButton"): ("MouseButton", "MiddleButton"), ("Qt", "TextColorRole"): ("ItemDataRole", "ForegroundRole"), ("Qt", "BackgroundColorRole"): ("ItemDataRole", "BackgroundRole"), ("QPainter", "HighQualityAntialiasing"): ("RenderHint", "Antialiasing"), } rename_function_attributes = {"exec_": "exec"} rename_function_definitions = {"exec_": "exec"} import_warnings = { "QRegExp": "QRegExp is removed in Qt6, please use QRegularExpression for Qt5/Qt6 compatibility" } # { (class, enum_value) : enum_name } qt_enums = {} ambiguous_enums = defaultdict(set) def fix_file(filename: str, qgis3_compat: bool) -> int: with open(filename, encoding="UTF-8") as f: contents = f.read() fix_qvariant_type = [] # QVariant.Int, QVariant.Double ... fix_pyqt_import = [] # from PyQt5.QtXXX fix_qt_enums = {} # Unscoping of enums member_renames = {} token_renames = {} function_def_renames = {} rename_qt_enums = [] # Renaming deprecated removed enums custom_updates = {} imported_modules = set() extra_imports = defaultdict(set) removed_imports = defaultdict(set) import_offsets = {} object_types = {} def visit_assign(_node: ast.Assign, _parent): if ( isinstance(_node.value, ast.Call) and isinstance(_node.value.func, ast.Name) and _node.value.func.id in ("QFontMetrics", "QFontMetricsF") ): object_types[_node.targets[0].id] = _node.value.func.id def visit_call(_node: ast.Call, _parent): if isinstance(_node.func, ast.Attribute): if _node.func.attr in rename_function_attributes: attr_node = _node.func member_renames[ Offset(_node.func.lineno, attr_node.end_col_offset - len(_node.func.attr) - 1) ] = rename_function_attributes[_node.func.attr] if _node.func.attr == "addAction": if len(_node.args) >= 4: sys.stderr.write( f"{filename}:{_node.lineno}:{_node.col_offset} WARNING: fragile call to addAction. Use my_action = QAction(...), obj.addAction(my_action) instead.\n" ) if _node.func.attr == "desktop": if len(_node.args) == 0: sys.stderr.write( f"{filename}:{_node.lineno}:{_node.col_offset} WARNING: QDesktopWidget is deprecated and removed in Qt6. Replace with alternative approach instead.\n" ) if isinstance(_node.func, ast.Name) and _node.func.id == "QVariant": if not _node.args: extra_imports["qgis.core"].update({"NULL"}) def _invalid_qvariant_to_null(start_index: int, tokens): assert tokens[start_index].src == "QVariant" assert tokens[start_index + 1].src == "(" assert tokens[start_index + 2].src == ")" tokens[start_index] = tokens[start_index]._replace(src="NULL") for i in range(start_index + 1, start_index + 3): tokens[i] = tokens[i]._replace(src="") custom_updates[Offset(_node.lineno, _node.col_offset)] = _invalid_qvariant_to_null elif ( len(_node.args) == 1 and isinstance(_node.args[0], ast.Attribute) and isinstance(_node.args[0].value, ast.Name) and _node.args[0].value.id == "QVariant" ): extra_imports["qgis.core"].update({"NULL"}) def _fix_null_qvariant(start_index: int, tokens): assert tokens[start_index].src == "QVariant" assert tokens[start_index + 1].src == "(" assert tokens[start_index + 2].src == "QVariant" assert tokens[start_index + 3].src == "." assert tokens[start_index + 5].src == ")" tokens[start_index] = tokens[start_index]._replace(src="NULL") for i in range(start_index + 1, start_index + 6): tokens[i] = tokens[i]._replace(src="") custom_updates[Offset(_node.lineno, _node.col_offset)] = _fix_null_qvariant elif isinstance(_node.func, ast.Name) and _node.func.id == "QDateTime": if len(_node.args) == 8: # QDateTime(yyyy, mm, dd, hh, MM, ss, ms, ts) doesn't work anymore, # so port to more reliable QDateTime(QDate, QTime, ts) form extra_imports["qgis.PyQt.QtCore"].update({"QDate", "QTime"}) def _fix_qdatetime_construct(start_index: int, tokens): i = start_index + 1 assert tokens[i].src == "(" tokens[i] = tokens[i]._replace(src="(QDate(") while tokens[i].offset < Offset(_node.args[2].lineno, _node.args[2].col_offset): i += 1 assert tokens[i + 1].src == "," i += 1 tokens[i] = tokens[i]._replace(src="), QTime(") i += 1 while not tokens[i].src.strip(): tokens[i] = tokens[i]._replace(src="") i += 1 while tokens[i].offset < Offset(_node.args[6].lineno, _node.args[6].col_offset): i += 1 i += 1 assert tokens[i].src == "," tokens[i] = tokens[i]._replace(src="),") custom_updates[Offset(_node.lineno, _node.col_offset)] = _fix_qdatetime_construct elif ( len(_node.args) == 1 and isinstance(_node.args[0], ast.Call) and _node.args[0].func.id == "QDate" ): # QDateTime(QDate(..)) doesn't work anymore, # so port to more reliable QDateTime(QDate(...), QTime(0,0,0)) form extra_imports["qgis.PyQt.QtCore"].update({"QTime"}) def _fix_qdatetime_construct(start_index: int, tokens): assert tokens[start_index].src == "QDateTime" assert tokens[start_index + 1].src == "(" assert tokens[start_index + 2].src == "QDate" assert tokens[start_index + 3].src == "(" i = start_index + 4 while tokens[i].offset < Offset( _node.args[0].end_lineno, _node.args[0].end_col_offset ): i += 1 assert tokens[i - 1].src == ")" tokens[i - 1] = tokens[i - 1]._replace(src="), QTime(0, 0, 0)") custom_updates[Offset(_node.lineno, _node.col_offset)] = _fix_qdatetime_construct def visit_attribute(_node: ast.Attribute, _parent): if isinstance(_node.value, ast.Name): if _node.value.id == "qApp": token_renames[Offset(_node.value.lineno, _node.value.col_offset)] = ( "QApplication.instance()" ) extra_imports["qgis.PyQt.QtWidgets"].update({"QApplication"}) removed_imports["qgis.PyQt.QtWidgets"].update({"qApp"}) if _node.value.id == "QVariant" and _node.attr == "Type": def _replace_qvariant_type(start_index: int, tokens): # QVariant.Type.XXX doesn't exist, it should be QVariant.XXX assert tokens[start_index].src == "QVariant" assert tokens[start_index + 1].src == "." assert tokens[start_index + 2].src == "Type" assert tokens[start_index + 3].src == "." tokens[start_index + 2] = tokens[start_index + 2]._replace(src="") tokens[start_index + 3] = tokens[start_index + 3]._replace(src="") custom_updates[Offset(node.lineno, node.col_offset)] = _replace_qvariant_type if object_types.get(_node.value.id) in ("QFontMetrics", "QFontMetricsF"): if _node.attr == "width": sys.stderr.write( f"{filename}:{_node.lineno}:{_node.col_offset} WARNING: QFontMetrics.width() " "has been removed in Qt6. Use QFontMetrics.horizontalAdvance() if plugin can " "safely require Qt >= 5.11, or QFontMetrics.boundingRect().width() otherwise.\n" ) elif isinstance(_node.value, ast.Call): if _node.attr == "width" and ( ( isinstance(_node.value.func, ast.Attribute) and _node.value.func.attr == "fontMetrics" ) or ( isinstance(_node.value.func, ast.Name) and _node.value.func.id == "QFontMetrics" ) ): sys.stderr.write( f"{filename}:{_node.lineno}:{_node.col_offset} WARNING: QFontMetrics.width() " "has been removed in Qt6. Use QFontMetrics.horizontalAdvance() if plugin can " "safely require Qt >= 5.11, or QFontMetrics.boundingRect().width() otherwise.\n" ) def visit_subscript(_node: ast.Subscript, _parent): if isinstance(_node.value, ast.Attribute): if ( _node.value.attr == "activated" and isinstance(_node.slice, ast.Name) and _node.slice.id == "str" ): sys.stderr.write( f"{filename}:{_node.lineno}:{_node.col_offset} WARNING: activated[str] " "has been removed in Qt6. Consider using QComboBox.activated instead if the string is not required, " "or QComboBox.textActivated if the plugin can " "safely require Qt >= 5.14. Otherwise conditional Qt version code will need to be introduced.\n" ) def visit_import(_node: ast.ImportFrom, _parent): import_offsets[Offset(node.lineno, node.col_offset)] = ( node.module, set(name.name for name in node.names), node.end_lineno, node.end_col_offset, ) imported_modules.add(node.module) for name in node.names: if name.name in import_warnings: print(f"{filename}: {import_warnings[name.name]}") if name.name == "resources_rc": sys.stderr.write( f"{filename}:{_node.lineno}:{_node.col_offset} WARNING: support for compiled resources " "is removed in Qt6. Directly load icon resources by file path and load UI fields using " "uic.loadUiType by file path instead.\n" ) if _node.module == "qgis.PyQt.Qt": extra_imports["qgis.PyQt.QtCore"].update({"Qt"}) removed_imports["qgis.PyQt.Qt"].update({"Qt"}) tree = ast.parse(contents, filename=filename) for parent in ast.walk(tree): for node in ast.iter_child_nodes(parent): if isinstance(node, ast.ImportFrom): visit_import(node, parent) if ( not qgis3_compat and isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name) and node.value.id == "QVariant" ): fix_qvariant_type.append(Offset(node.lineno, node.col_offset)) if isinstance(node, ast.Call): visit_call(node, parent) elif isinstance(node, ast.Attribute): visit_attribute(node, parent) elif isinstance(node, ast.Subscript): visit_subscript(node, parent) elif isinstance(node, ast.Assign): visit_assign(node, parent) if isinstance(node, ast.FunctionDef) and node.name in rename_function_definitions: function_def_renames[Offset(node.lineno, node.col_offset)] = ( rename_function_definitions[node.name] ) if ( isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name) and (node.value.id, node.attr) in ambiguous_enums ): disambiguated = False try: actual = eval(f"{node.value.id}.{node.attr}") obj = globals()[node.value.id] if isinstance(obj, type): for attr_name in dir(obj): attr = getattr(obj, attr_name) if attr is actual.__class__: # print(f'Found alias {node.value.id}.{attr_name}') disambiguated = True fix_qt_enums[Offset(node.lineno, node.col_offset)] = ( node.value.id, attr_name, node.attr, ) break except AttributeError: pass if not disambiguated: possible_values = [ f"{node.value.id}.{e}.{node.attr}" for e in ambiguous_enums[(node.value.id, node.attr)] ] sys.stderr.write( f'{filename}:{node.lineno}:{node.col_offset} WARNING: ambiguous enum, cannot fix: {node.value.id}.{node.attr}. Could be: {", ".join(possible_values)}\n' ) elif ( isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name) and not isinstance(parent, ast.Attribute) and (node.value.id, node.attr) in qt_enums ): fix_qt_enums[Offset(node.lineno, node.col_offset)] = ( node.value.id, qt_enums[(node.value.id, node.attr)], node.attr, ) if ( isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name) and (node.value.id, node.attr) in deprecated_renamed_enums ): rename_qt_enums.append(Offset(node.lineno, node.col_offset)) elif ( isinstance(node, ast.ImportFrom) and node.module and node.module.startswith("PyQt5.") ): fix_pyqt_import.append(Offset(node.lineno, node.col_offset)) for module, classes in extra_imports.items(): if module not in imported_modules: class_import = ", ".join(classes) import_statement = f"from {module} import {class_import}" print(f"{filename}: Missing import, manually add \n\t{import_statement}") if not any( [ fix_qvariant_type, fix_pyqt_import, fix_qt_enums, rename_qt_enums, member_renames, function_def_renames, custom_updates, extra_imports, removed_imports, token_renames, ] ): return 0 tokens = src_to_tokens(contents) for i, token in reversed_enumerate(tokens): if token.offset in import_offsets: end_import_offset = Offset(*import_offsets[token.offset][-2:]) del import_offsets[token.offset] assert tokens[i].src == "from" token_index = i + 1 while not tokens[token_index].src.strip(): token_index += 1 module = "" while tokens[token_index].src.strip(): module += tokens[token_index].src token_index += 1 if extra_imports.get(module) or removed_imports.get(module): current_imports = set() while True: token_index += 1 if tokens[token_index].offset == end_import_offset: break if tokens[token_index].src.strip() in ("", ",", "import", "(", ")"): continue import_ = tokens[token_index].src if import_ in removed_imports.get(module, set()): tokens[token_index] = tokens[token_index]._replace(src="") prev_token_index = token_index - 1 while True: if tokens[prev_token_index].src.strip() in ("", ","): tokens[prev_token_index] = tokens[prev_token_index]._replace(src="") prev_token_index -= 1 else: break none_forward = True current_index = prev_token_index + 1 while True: if tokens[current_index].src in ("\n", ")"): break elif tokens[current_index].src.strip(): none_forward = False break current_index += 1 none_backward = True current_index = prev_token_index while True: if tokens[current_index].src in ("import",): break elif tokens[current_index].src.strip(): none_backward = False break current_index -= 1 if none_backward and none_forward: # no more imports from this module, remove whole import while True: if tokens[current_index].src in ("from",): break current_index -= 1 while True: if tokens[current_index].src in ("\n",): tokens[current_index] = tokens[current_index]._replace(src="") break tokens[current_index] = tokens[current_index]._replace(src="") current_index += 1 else: current_imports.add(import_) imports_to_add = extra_imports.get(module, set()) - current_imports if imports_to_add: additional_import_string = ", ".join(imports_to_add) if tokens[token_index - 1].src == ")": token_index -= 1 while tokens[token_index].src.strip() in ("", ",", ")"): tokens[token_index] = tokens[token_index]._replace(src="") token_index -= 1 tokens[token_index + 1] = tokens[token_index + 1]._replace( src=f", {additional_import_string})" ) else: tokens[token_index] = tokens[token_index]._replace( src=f", {additional_import_string}{tokens[token_index].src}" ) if token.offset in fix_qvariant_type: assert tokens[i].src == "QVariant" assert tokens[i + 1].src == "." tokens[i] = tokens[i]._replace(src="QMetaType.Type") attr = tokens[i + 2].src if attr in qmetatype_mapping: tokens[i + 2] = tokens[i + 2]._replace(src=qmetatype_mapping[attr]) if token.offset in custom_updates: custom_updates[token.offset](i, tokens) if token.offset in fix_pyqt_import: assert tokens[i + 2].src == "PyQt5" tokens[i + 2] = tokens[i + 2]._replace(src="qgis.PyQt") if token.offset in function_def_renames and tokens[i].src == "def": tokens[i + 2] = tokens[i + 2]._replace(src=function_def_renames[token.offset]) if token.offset in token_renames: tokens[i] = tokens[i]._replace(src=token_renames[token.offset]) if token.offset in member_renames: counter = i while tokens[counter].src != ".": counter += 1 tokens[counter + 1] = tokens[counter + 1]._replace(src=member_renames[token.offset]) if token.offset in fix_qt_enums: assert tokens[i + 1].src == "." _class, enum_name, value = fix_qt_enums[token.offset] # make sure we CAN import enum! try: eval(f"{_class}.{enum_name}.{value}") tokens[i + 2] = tokens[i + 2]._replace(src=f"{enum_name}.{tokens[i + 2].src}") except AttributeError: # let's see if we can find what the replacement should be automatically... # print(f'Trying to find {_class}.{value}.') actual = eval(f"{_class}.{value}") # print(f'Trying to find aliases for {actual.__class__}.') obj = globals()[_class] recovered = False if isinstance(obj, type): for attr_name in dir(obj): try: attr = getattr(obj, attr_name) if attr is actual.__class__: # print(f'Found alias {_class}.{attr_name}') recovered = True tokens[i + 2] = tokens[i + 2]._replace( src=f"{attr_name}.{tokens[i + 2].src}" ) except AttributeError: continue if not recovered: sys.stderr.write( f"{filename}:{token.line}:{token.utf8_byte_offset} ERROR: wanted to replace with {_class}.{enum_name}.{value}, but does not exist\n" ) continue if token.offset in rename_qt_enums: assert tokens[i + 1].src == "." enum_name = deprecated_renamed_enums[(tokens[i].src, tokens[i + 2].src)] assert enum_name tokens[i + 2] = tokens[i + 2]._replace(src=f"{enum_name[0]}.{enum_name[1]}") new_contents = tokens_to_src(tokens) with open(filename, "w") as f: f.write(new_contents) return new_contents != contents def get_class_enums(item, depth): # @troopa81: add depth arg if not inspect.isclass(item): return if depth >= 5: # @troopa81: return to avoid python error return # enums might be referenced using a subclass instead of their # parent class, so we need to loop through all those too... def all_subclasses(cls): if cls is object: return set() return {cls}.union(s for c in cls.__subclasses__() for s in all_subclasses(c)) matched_classes = {item}.union(all_subclasses(item)) for key, value in item.__dict__.items(): if inspect.isclass(value) and type(value).__name__ == "EnumType": for ekey, evalue in value.__dict__.items(): for matched_class in matched_classes: if isinstance(evalue, value): try: test_value = getattr(item, str(ekey)) if not issubclass(type(test_value), Enum): # There's a naming clash between an enum value (Eg QgsAggregateMappingModel.ColumnDataIndex.Aggregate) # and a class (QgsAggregateMappingModel.Aggregate) # So don't do any upgrades for these values, as current code will always be referring # to the CLASS continue except AttributeError: pass if (matched_class.__name__, ekey) in ambiguous_enums: if ( value.__name__ not in ambiguous_enums[(matched_class.__name__, ekey)] ): ambiguous_enums[(matched_class.__name__, ekey)].add(value.__name__) continue existing_entry = qt_enums.get((matched_class.__name__, ekey)) if existing_entry != value.__name__ and existing_entry: ambiguous_enums[(matched_class.__name__, ekey)].add(existing_entry) ambiguous_enums[(matched_class.__name__, ekey)].add(value.__name__) del qt_enums[(matched_class.__name__, ekey)] else: qt_enums[(matched_class.__name__, ekey)] = f"{value.__name__}" elif inspect.isclass(value): # @troopa81: depth counter depth += 1 get_class_enums(value, depth) def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument("directory") parser.add_argument( "--qgis3-incompatible-changes", action="store_true", help="Apply modifications that would break behavior on QGIS 3, hence code may not work on QGIS 3", ) args = parser.parse_args(argv) # get all scope for all qt enum for module in target_modules: for key, value in module.__dict__.items(): # @troopa81: depth counter init + print value depth = 0 print(f"Get enums for {value}") get_class_enums(value, depth) ret = 0 for filename in glob.glob(os.path.join(args.directory, "**/*.py"), recursive=True): print(f"Processing {filename}") if "auto_additions" in filename: continue ret |= fix_file(filename, not args.qgis3_incompatible_changes) return ret if __name__ == "__main__": raise SystemExit(main()) ```

Script executed from VSCode on Windows in a powershell terminal and venv:

C:\OSGeo4W\bin\python-qgis.bat -m venv --system-site-packages .venv
C:\OSGeo4W\bin\python-qgis.bat -c "import pathlib;import qgis;print(str((pathlib.Path(qgis.__file__)/'../..').resolve()))" > .venv\qgis.pth

.venv\Lib\site-packages\sitecustomize.py

import os

os.add_dll_directory("C:/OSGeo4W/bin")
os.add_dll_directory("C:/OSGeo4W/apps/qgis/bin")
os.add_dll_directory("C:/OSGeo4W/apps/Qt5/bin")

.venv\pyvenv.cfg

home = C:\OSGeo4W\apps\Python312
include-system-site-packages = true
version = 3.12.6
executable = C:\OSGeo4W\apps\Python312\python.exe
command = C:\OSGeo4W\apps\Python312\python.exe -m venv --system-site-packages D:\_QGIS\PLUGINS\isl_box\.venv

image

nicogodet commented 1 month ago

@troopa81 I found something I added 2 prints image

When it reaches module qgis.core:

Get enums for <class 'qgis._core.Qgis'>
dict_items([('__module__', 'qgis._core'), [...], ('AnnotationItemFlags', <class 'qgis._core.Qgis.AnnotationItemFlags'>), [...]}

qgis._core.Qgis has an item which is a class so if inspect.isclass(value) and type(value).__name__ == "EnumType": fails and recursive call starts here as it tries to get enums for this class.

dict_items([('__module__', 'qgis._core'), ('__hash__', <slot wrapper '__hash__' of 'AnnotationItemFlags' objects>), ('__lt__', <slot wrapper '__lt__' of 'AnnotationItemFlags' objects>), ('__le__', <slot wrapper '__le__' of 'AnnotationItemFlags' objects>), ('__eq__', <slot wrapper '__eq__' of 'AnnotationItemFlags' objects>), ('__ne__', <slot wrapper '__ne__' of 'AnnotationItemFlags' objects>), ('__gt__', <slot wrapper '__gt__' of 'AnnotationItemFlags' objects>), ('__ge__', <slot wrapper '__ge__' of 'AnnotationItemFlags' objects>), ('__bool__', <slot wrapper '__bool__' of 'AnnotationItemFlags' objects>), ('__invert__', <slot wrapper '__invert__' of 'AnnotationItemFlags' objects>), ('__and__', <slot wrapper '__and__' of 'AnnotationItemFlags' objects>), ('__rand__', <slot wrapper '__rand__' of 'AnnotationItemFlags' objects>), ('__xor__', <slot wrapper '__xor__' of 'AnnotationItemFlags' objects>), ('__rxor__', <slot wrapper '__rxor__' of 'AnnotationItemFlags' objects>), ('__or__', <slot wrapper '__or__' of 'AnnotationItemFlags' objects>), ('__ror__', <slot wrapper '__ror__' of 'AnnotationItemFlags' objects>), ('__int__', <slot wrapper '__int__' of 'AnnotationItemFlags' objects>), ('__iand__', <slot wrapper '__iand__' of 'AnnotationItemFlags' objects>), ('__ixor__', <slot wrapper '__ixor__' of 'AnnotationItemFlags' objects>), ('__ior__', <slot wrapper '__ior__' of 'AnnotationItemFlags' objects>), ('__index__', <slot wrapper '__index__' of 'AnnotationItemFlags' objects>), ('__weakref__', <attribute '__weakref__' of 'AnnotationItemFlags' objects>), ('__doc__', 'Qgis.AnnotationItemFlags()\nQgis.AnnotationItemFlags(f: Union[Qgis.AnnotationItemFlags, Qgis.AnnotationItemFlag])\nQgis.AnnotationItemFlags(a0: Qgis.AnnotationItemFlags)'), ('baseClass', <class 'qgis._core.Qgis'>)])

Removing useless items

dict_items([('__module__', 'qgis._core'),[...], ('baseClass', <class 'qgis._core.Qgis'>)])

Found a class ! Try to get enums for this class qgis._core.Qgis items of qgis._core.Qgis contains a class which is not Enum, try to get enums for this class, ............

I added a condition in the loop

if key == "baseClass":
    continue

And no more recursive error

troopa81 commented 1 month ago

@nicogodet Your proposition was the good one, we add baseClass item in sipify script mainly because we need it later so we need to avoid looping on it.