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

[Feature][Python Console] Add Reformat code action #51733

Merged
merged 15 commits into from
Mar 17, 2023

Conversation

YoannQDQ
Copy link
Contributor

@YoannQDQ YoannQDQ commented Feb 5, 2023

Description

Add a format code action in the Python Console Editor:

format

Settings

  • Format on save: if enabled, formatting is applied just before saving the script
  • Sort imports: Sort import statements using isort
  • Max line length: Control how the formatter will wrap the lines, and controls the editor ruler
  • Formatter: autopep8 or black
    • Level (autopep8 only) See Autopep8 aggressiveness level
    • Normalize quotes (black only): Replace all single quotes with double quotes if possible
black autopep8
black autopep8

Sorting example

isort will sort import statement in three different groups:

  • standard library imports (re, os, sys, json, ...)
  • third-party modules (PyQt5, pandas, dateutil, ...)
  • first-party modules (qgis, processing, ...)

sort_imports

@github-actions github-actions bot added the Python Console Python Console label Feb 5, 2023
@github-actions github-actions bot added this to the 3.30.0 milestone Feb 5, 2023
@nyalldawson nyalldawson added the Frozen Feature freeze - Do not merge! label Feb 6, 2023
@nyalldawson
Copy link
Collaborator

Great addition @YoannQDQ ! Since it's a new feature it'll need to wait till after 3.30 is released before it's eligible for merge.

@Gustry
Copy link
Contributor

Gustry commented Feb 6, 2023

Nice addition indeed !

Are we sure that autopep8 is installed, even on windows from the binaries ?

@YoannQDQ
Copy link
Contributor Author

YoannQDQ commented Feb 6, 2023

Nice addition indeed !

Are we sure that autopep8 is installed, even on windows from the binaries ?

Hum... I'm not sure. Besides, I just added black as well, and I know for sure it is NOT installed (neither on Linux nor on Windows...).

Do you happen to know how we can ensure black and autopep8 are installed?

@Gustry
Copy link
Contributor

Gustry commented Feb 6, 2023

Yes, for black it's sure it's not by default.
You can for instance :

try:
    import autopep8
    AUTO_PEP8_INSTALLED=True
except ImportError:
    AUTO_PEP8_INSTALLED=False

And then enable buttons carefully according to this variable ?

@YoannQDQ
Copy link
Contributor Author

YoannQDQ commented Feb 6, 2023

Yes, for black it's sure it's not by default. You can for instance :

try:
    import autopep8
    AUTO_PEP8_INSTALLED=True
except ImportError:
    AUTO_PEP8_INSTALLED=False

And then enable buttons carefully according to this variable ?

Thanks, but I meant "How to force black and autopep8 to be packaged with QGIS?".
If it is not desirable to install them by default, I could indeed check whether they're installed or not to enable/disable the feature accordingly, with a side note explaining how the formatters can be installed. (@nyalldawson any thoughts?)

@uclaros
Copy link
Contributor

uclaros commented Feb 9, 2023

Nice!
I believe Re-Format code would be better wording.
Are the applied changes undo-able?

@YoannQDQ
Copy link
Contributor Author

YoannQDQ commented Feb 9, 2023

Nice! I believe Re-Format code would be better wording. Are the applied changes undo-able?

You're probably right. I'll change it. And yes, they are.

@3nids
Copy link
Member

3nids commented Feb 10, 2023

Once #51783 is merged, you'll be able to use the new structure for the settings in Python.

  1. in QgsPythonUtils
    a. Create a tree node: static inline QgsSettingsTreeNode *sTreeConsole = QgsSettingsTree::treeRoot()->createChildNode( QStringLiteral( "console" ) );
    b. Declare the settings using QgsSettingsEntryString or any other implementation of QgsSettingsEntryBase
  2. From Python, you can access the settings using QgsSettingsTree.node("console").childSetting("my_setting")

@nicogodet
Copy link
Member

IMO, it definitely needs a try/except on import as proposed in #51733 (comment)

Une erreur est survenue lors de l'exécution du code suivant:
import console


Traceback (most recent call last):
  File "", line 1, in 
  File "D:\Downloads\QGIS-Portable\lib\python3.11\site-packages\qgis\utils.py", line 888, in _import
    mod = _builtin_import(name, globals, locals, fromlist, level)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Downloads/QGIS-Portable/share/qgis/python\console\__init__.py", line 24, in 
    from .console import show_console  # NOQA
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Downloads\QGIS-Portable\lib\python3.11\site-packages\qgis\utils.py", line 888, in _import
    mod = _builtin_import(name, globals, locals, fromlist, level)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Downloads/QGIS-Portable/share/qgis/python\console\console.py", line 33, in 
    from .console_editor import EditorTabWidget
  File "D:\Downloads\QGIS-Portable\lib\python3.11\site-packages\qgis\utils.py", line 888, in _import
    mod = _builtin_import(name, globals, locals, fromlist, level)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\Downloads/QGIS-Portable/share/qgis/python\console\console_editor.py", line 33, in 
    import autopep8
  File "D:\Downloads\QGIS-Portable\lib\python3.11\site-packages\qgis\utils.py", line 888, in _import
    mod = _builtin_import(name, globals, locals, fromlist, level)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ModuleNotFoundError: No module named 'autopep8'

Python error on startup

@YoannQDQ YoannQDQ marked this pull request as draft February 17, 2023 12:38
@YoannQDQ
Copy link
Contributor Author

YoannQDQ commented Feb 17, 2023

IMO, it definitely needs a try/except on import as proposed in #51733 (comment)

Yes my bad, I should have explicitly converted this PR to a draft before. I'm well aware that it does not work as it is right now, but I'm reluctant to just wrap the imports in try...except and call it a day. This feature will almost never be used if one has to manually install black, autopep8 and isort (Most QGIS users are not familiar with how to do it).

So, in my earlier comment, I asked how to properly ensure the relevant python modules are installed along QGIS, for every supported platform (at least Windows, MacOS, Ubuntu, Debian).
With that said, I may still have to resort to use try...except block.

Another possibility would be to use the following function to install modules as needed.

import importlib
import subprocess

def install(module):
    try:
        return importlib.import_module(module)
    except ModuleNotFoundError:
        pass
    try:
        import pip
    except ModuleNotFoundError:
        print(f"Pip not found. Try installing python3-pip with your package manager")
        return None
    
    result = subprocess.run(f"python -m pip install {module}", shell=True, capture_output=True)
    if result.returncode != 0:
        print(f"Pip error: {result.stderr.decode('utf8')}")
        return None

    try:
        return importlib.import_module(module)
    except ModuleNotFoundError:
        print("Module not found")
        return None

When the user triggers the reformat action, he would be prompted to confirm the installation of the required python modules (which could fail depending on the platform, and in offline situations).

def check_install(required_modules):
    missing = set()
    for module in set(required_modules):
        try:
            importlib.import_module(module)
        except ModuleNotFoundError:
            missing.add(module)
            
    if not missing:
        return True

    modules = "\n - ".join(sorted(missing))
    res = QMessageBox.question(
        iface.mainWindow(), 
        "Info", 
        "Some required python modules are not installed:\n - {0}\n Do you want to try and install them now?".format(modules)
    )
    if res != QMessageBox.Yes:
        return False
        
    for module in sorted():
        if install(missing):
            missing.remove(module)

    if not missing:
        QMessageBox.information(
            iface.mainWindow(), 
            "Info", 
            "Modules were successfuly installed!"
        )
        return True
        
    QMessageBox.warning(
        iface.mainWindow(), 
        "Warning", 
        "Some modules could not be installed"
    )
    return False

@YoannQDQ YoannQDQ force-pushed the python-console-format branch 2 times, most recently from 39ea4ca to f526ab4 Compare February 21, 2023 14:50
@YoannQDQ YoannQDQ changed the title [Feature][Python Console] Format code with autopep8 [Feature][Python Console] Add Reformat code action Feb 24, 2023
@kannes
Copy link
Contributor

kannes commented Feb 26, 2023

This feature will almost never be used if one has to manually install black, autopep8 and isort (Most QGIS users are not familiar with how to do it).

I wouldn't worry about that. This feature is quite advanced even for many people who write Python scripts in QGIS. I would bet that those who care about code formatting are very likely to also be capable of installing packages to their QGIS environment.

PS: This is a super cool feature, thank you! I'll use it a lot :)

@Gustry
Copy link
Contributor

Gustry commented Feb 28, 2023

+1 with @kannes

I guess it also depends how visible are these buttons if the underneath library is not installed.

  • Is the button only disabled with a nice tooltip explaining why ?
  • or is the button enabled but the popup is coming on click ?

I guess the second one is better to "invite" people installing them. But from a UX point of view, the first one is better. I would go for 2 I think.

@YoannQDQ YoannQDQ marked this pull request as ready for review March 3, 2023 21:01
@YoannQDQ YoannQDQ force-pushed the python-console-format branch 2 times, most recently from f1c1e61 to db9d0fc Compare March 3, 2023 21:45
@nyalldawson nyalldawson modified the milestones: 3.30.0, 3.32 (feature) Mar 6, 2023
@nyalldawson nyalldawson removed the Frozen Feature freeze - Do not merge! label Mar 6, 2023
@YoannQDQ YoannQDQ force-pushed the python-console-format branch 2 times, most recently from c58cf16 to 5669f16 Compare March 12, 2023 13:27
@nyalldawson
Copy link
Collaborator

@YoannQDQ would you mind splitting out the package installation parts of this PR for submission as a follow up pr? Those are slightly controversial and I'd like to merge just the actual reformatting part of this work before we tackle the harder questions...

@YoannQDQ
Copy link
Contributor Author

@YoannQDQ would you mind splitting out the package installation parts of this PR for submission as a follow up pr? Those are slightly controversial and I'd like to merge just the actual reformatting part of this work before we tackle the harder questions...

Done.

python/console/console_editor.py Outdated Show resolved Hide resolved
python/console/console_editor.py Outdated Show resolved Hide resolved
@YoannQDQ YoannQDQ closed this Mar 15, 2023
@YoannQDQ YoannQDQ reopened this Mar 15, 2023
@nyalldawson
Copy link
Collaborator

Thanks @YoannQDQ !

@nyalldawson nyalldawson merged commit 4c74bd9 into qgis:master Mar 17, 2023
@nyalldawson nyalldawson added Needs Documentation When merging a labeled PR, an issue will be created in the Doc repo. Changelog Items that are queued to appear in the visual changelog - remove after harvesting labels Mar 17, 2023
@github-actions
Copy link

@YoannQDQ
This pull request has been tagged as requiring documentation.

A documentation ticket will be opened at https://github.com/qgis/QGIS-Documentation when this PR is merged.

Please update the description (not the comments) with helpful description and screenshot to help the work from documentors.
Also, any commit having [needs-doc] or [Needs Documentation] in will see its message pushed to the issue, so please be as verbose as you can.

Thank you!

@github-actions
Copy link

@YoannQDQ
A documentation ticket has been opened at qgis/QGIS-Documentation#8105
It is your responsibility to visit this ticket and add as much detail as possible for the documentation team to correctly document this change.
Thank you!

@3nids
Copy link
Member

3nids commented Mar 17, 2023

This PR introduced settings without correctly declaring them as stated here #51733 (comment)

@YoannQDQ YoannQDQ deleted the python-console-format branch March 17, 2023 20:06
@zacharlie zacharlie added ChangelogHarvested This PR description has been harvested in the Changelog already. and removed Changelog Items that are queued to appear in the visual changelog - remove after harvesting labels Jun 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ChangelogHarvested This PR description has been harvested in the Changelog already. Feature Needs Documentation When merging a labeled PR, an issue will be created in the Doc repo. Python Console Python Console
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants