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

Upgrade python syntax to 3.9 and newer #6376

Merged
merged 1 commit into from Oct 19, 2022

Conversation

jamesobutler
Copy link
Contributor

@jamesobutler jamesobutler commented May 14, 2022

This continually enforces changes made in 4483cc0 and 1a85eba.

Changes were applied automatically by running:
pre_commit run --all-files


EDIT

Similarly to 1a85eba (introduced through PR #6132) and 4483cc0 (introduced through PR #5625), this PR uses pyupgrade to also automatically upgrade python syntax.

Using pyupgrade

  • Install: PythonSlicer -m pip install pyupgrade
  • Running:
    • On 1 file: PythonSlicer -m pyupgrade --py39-plus MyPythonFile.py
    • On multiple files, there are few options:
      • on unix system:
        PythonSlicer -m pyupgrade --py39-plus $(find . -type f -name "*.py")
        
      • on any system, use @jamesobutler pyupgrade-script.py written to automate running pyupgrade across all python files in the Slicer repo. It was run by PythonSlicer pyupgrade-script.py.
        # pyupgrade-script.py
        import os
        import subprocess
        
        search_directory = "C:/Users/JamesButler/Documents/GitHub/Slicer"
        for root, _, files in os.walk(search_directory):
            for file_item in files:
                file_path = os.path.join(root, file_item)
                if os.path.isfile(file_path) and file_path.endswith(".py"):
                    subprocess.call(["PythonSlicer", "-m", "pyupgrade", "--py39-plus", file_path])

@jamesobutler
Copy link
Contributor Author

It will be easier to resolve merge conflicts in this PR if #6375 is merged first.

@jamesobutler jamesobutler force-pushed the pyupgrade-pre-commit branch 2 times, most recently from 5a24763 to a737f53 Compare June 20, 2022 02:29
@jamesobutler jamesobutler force-pushed the pyupgrade-pre-commit branch 3 times, most recently from 524e91f to d8ad3aa Compare June 23, 2022 14:01
@jamesobutler
Copy link
Contributor Author

I've rebased this PR to fix merge conflicts from the recent whitespace updates.

@jamesobutler
Copy link
Contributor Author

I'm following up on some of my old PRs. @jcfr Do you think we can go ahead and proceed with this one? Let me know if ENH is an appropriate commit prefix for some of this CI/utility related work - re ENH: Update GitHub Actions lint workflow to include pyupgrade

@jamesobutler
Copy link
Contributor Author

Following up on this old PR here.

Any objections to proceeding with this one @jcfr, @lassoan, @pieper ?

Copy link
Contributor

@lassoan lassoan left a comment

Choose a reason for hiding this comment

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

Thank you, looks all good, just added two small comments.

@@ -2707,7 +2707,7 @@ def _messageDisplay(logLevel, text, testingReturnValue, mainWindowNeeded=False,
if not windowTitle:
windowTitle = slicer.app.applicationName + " " + logLevelString
if slicer.app.testingEnabled():
logging.info("Testing mode is enabled: Returning %s and skipping message box [%s]." % (testingReturnValue, windowTitle))
logging.info(f"Testing mode is enabled: Returning {testingReturnValue} and skipping message box [{windowTitle}].")
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer not to introduce usage of f-strings in logging functions, because using f-strings results in performing expensive string manipulations even if the message does not end up being printed due to the current log level.

Has pyupgrade recommended this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes pyupgrade changes it to .format() and then re-running changes it again to an f-string. The maintainer points to asottile/pyupgrade#503 (comment) in that f-string usage here is not a performance drop.

Copy link
Member

Choose a reason for hiding this comment

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

If there's a performance concern fstrings and format statements should be avoided in logging calls since the arguments will be evaluated before the logging method is invoked, so the computation will always happen regardless of log level.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not going to argue with the maintainer of pyupgrade on this one, so since there are no exceptions to keep % formatting only for logging statements with pyupgrade that means I would need to remove pyupgrade from the pre-commit-config.yaml in this PR and we would have to apply all the changes in a manual review basis and every so often update the Slicer code base to get back into the compliance that we believe in.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the link @jamesobutler, it led to some very interesting discussions. There seems to be a very strong disagreement between various Python developers on this topic and probably the correct answer depends on the use case. The insignificant performance drop was a measurement error, in the same issue further along the discussion, the actual performance drop was shown to be about 20%. So, this is a real problem. There are additional potential issues with exceptions during string conversion.

However, in our specific these cases here, there is no performance or exception-safety problems, so it should be fine to use f-strings.

My only concern is that using f-strings in logging would make this the default choice everywhere, even where performance matters. But maybe keeping the classic formatting in our log messages is not a very effective way of educating/reminding developers anyway, and if the cost is not to use pyupgrade at all or spend a lot of time with fighting with various lint tools, then it may just not worth it.

So, I'm OK with keeping the suggested changes.

Copy link
Member

@jcfr jcfr Oct 18, 2022

Choose a reason for hiding this comment

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

Looking at https://pylint.pycqa.org/en/latest/user_guide/messages/warning/logging-format-interpolation.html, the following is indicated:

Another reasonable option is to use f-string. If you want to do that, you need to enable logging-format-interpolation and disable logging-fstring-interpolation.

Given the fact there is no significant performance impact 1, enforcing f-string everywhere is reasonable.

That said, I think we should explicitly configure pylint with:

  • logging-fstring-interpolation -> disabled
  • logging-format-interpolation -> enabled

Footnotes

  1. https://github.com/Slicer/Slicer/pull/6376/files#r984071600

@@ -2730,7 +2730,7 @@ def messageBox(text, parent=None, **kwargs):
import logging, qt, slicer
if slicer.app.testingEnabled():
testingReturnValue = qt.QMessageBox.Ok
logging.info("Testing mode is enabled: Returning %s (qt.QMessageBox.Ok) and displaying an auto-closing message box [%s]." % (testingReturnValue, text))
logging.info(f"Testing mode is enabled: Returning {testingReturnValue} (qt.QMessageBox.Ok) and displaying an auto-closing message box [{text}].")
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above, I would not use f-strings in logging.

@jcfr
Copy link
Member

jcfr commented Oct 11, 2022

Following the integration of #6573, this should be rebased against the current main

@jamesobutler
Copy link
Contributor Author

I've rebased to fix the merge conflict.

@jamesobutler
Copy link
Contributor Author

I've rebased to update to the latest version of pyupgrade and re-run to be in compliance.

@@ -18,7 +18,7 @@ def writeFile(path, content):
pass

# Write file
with open(path, "wt") as f:
with open(path, "w") as f:
Copy link
Member

Choose a reason for hiding this comment

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

Since text mode (t) is the default 1, this makes sense ✔️

Footnotes

  1. https://stackoverflow.com/questions/23051062/open-files-in-rt-and-wt-modes

@@ -2707,7 +2707,7 @@ def _messageDisplay(logLevel, text, testingReturnValue, mainWindowNeeded=False,
if not windowTitle:
windowTitle = slicer.app.applicationName + " " + logLevelString
if slicer.app.testingEnabled():
logging.info("Testing mode is enabled: Returning %s and skipping message box [%s]." % (testingReturnValue, windowTitle))
logging.info(f"Testing mode is enabled: Returning {testingReturnValue} and skipping message box [{windowTitle}].")
Copy link
Member

@jcfr jcfr Oct 18, 2022

Choose a reason for hiding this comment

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

Looking at https://pylint.pycqa.org/en/latest/user_guide/messages/warning/logging-format-interpolation.html, the following is indicated:

Another reasonable option is to use f-string. If you want to do that, you need to enable logging-format-interpolation and disable logging-fstring-interpolation.

Given the fact there is no significant performance impact 1, enforcing f-string everywhere is reasonable.

That said, I think we should explicitly configure pylint with:

  • logging-fstring-interpolation -> disabled
  • logging-format-interpolation -> enabled

Footnotes

  1. https://github.com/Slicer/Slicer/pull/6376/files#r984071600

@jamesobutler
Copy link
Contributor Author

@jcfr I've included W1203 in the ignore of .flake8 configuration which I believe utilizes the pylint codes.
https://pylint.pycqa.org/en/latest/user_guide/messages/warning/logging-fstring-interpolation.html

@jcfr
Copy link
Member

jcfr commented Oct 19, 2022

After looking at this in more details, I realized that pyupgrade does not make use of pylint, so updating the setting has no effect. I will then remove the last commit.

  • Remove commit ENH: Ignore pylint W1203 logging-fstring-interpolation

Strictly speaking, running pyupgrade in the GitHub workflow does not really make sense because (1) we do not yet push back the changes to the open pull-request (this could be done enabling and (2) rewriting details are not reported back in the output.

Using pre-commit to run against all files may indeed be convenient and may avoid the following:

PythonSlicer -m pip install pyupgrade
PythonSlicer -m pyupgrade --py39-plus $(find . -type f -name "*.py")

and only require the following

PythonSlicer -m pip install pre-commit
PythonSlicer -m pre_commit run --all-files

Source: https://discourse.slicer.org/t/locally-execute-pre-commit-linting-locally/22837

This means that the update of the workflow without enabling service like pre-commit.ci 1 or using action like git-auto-commit-action2 does not really fulfill:

continually enforces changes made in 4483cc0 and 1a85eba.

For these reasons, I will:

  • update the pull-request to only integrate the changes without updating the workflow.
  • create an issue for adding auto-update of pull-request

Footnotes

  1. https://pre-commit.ci/

  2. https://github.com/stefanzweifel/git-auto-commit-action

@jcfr jcfr changed the title Enforce latest python syntax using pyupgrade with pre-commit framework Enforce latest python syntax using pyupgrade Oct 19, 2022
@jcfr
Copy link
Member

jcfr commented Oct 19, 2022

@jamesobutler Comparing with the version I obtained with pyupgrade with the one you originally introduced in https://github.com/Slicer/Slicer/tree/0baa88d681a9a4a0bdc8a42a1b6bd97cf1f9265e, I noticed that the following different. Did you explicitly update CLISerializationTest.py ?

image

@jcfr jcfr changed the title Enforce latest python syntax using pyupgrade Upgrade python syntax to 3.9 and newer Oct 19, 2022
@jamesobutler
Copy link
Contributor Author

jamesobutler commented Oct 19, 2022

There was indeed some re-runs of pyupgrade as when you run it once and then immediately run it a second time, it will change more things the second time around. Yes, I might’ve additionally changed some things as well but I don’t remember.

Adding pyupgrade to the pre-commit run enforces the updated syntax for new PRs, but I wasn’t planning for it to be auto fixed by a bot. It would be up to the developer to see the reported issues just like if they had incorrect tab counts reported by pylint which they would manually fix and push. I would treat this as a developer responsibility similar to when someone has a commit that doesn’t pass the commit message check in a multi commit PR and it requires fixing up to pass required status checks.

Strictly speaking, running pyupgrade in the GitHub workflow does not really make sense because (1) we do not yet push back the changes to the open pull-request (this could be done enabling and (2) rewriting details are not reported back in the output.

Re (1) I suggest not to auto-fix PRs with pre-commit.ci to push changes back as mentioned in
#6573 (comment) because when working in a multi commit PR the changes would be in a single commit and not squashed down cleanly into the commit that introduced the compliance issue. Auto-fix can be helpful but will reduce the quality of the commit history with fix-me-up type commits.

Re (2) rewriting details are indeed reported back in the GitHub action workflow results (or in pre-commit.ci results) and also locally as you’ve shown in your screenshot above. This provides developers clear instructions about what something should be changed or that they can run pyupgrade locally to apply the changes and commit and squash into their appropriate commit.

@jcfr
Copy link
Member

jcfr commented Oct 19, 2022

Thanks for the detailed answer, this is very helpful 🙏

I will integrate the change and publish pull-requests respectively adding (1) execution of pyupgrade (2) autoupdate 1 through the GitHub workflow.

not planning for it to be auto fixed by a bot [...] it would be up to the developer to see the reported issues

👍

rewriting details [...] reported back in the GitHub action workflow results

The screenshot I provided was obtained after running the tool locally, I will investigate further to see how rewritten changed are displayed within the GitHub workflow as I couldn't find an example (yet)

Footnotes

  1. https://github.com/Slicer/Slicer/pull/6573#issuecomment-1274839133

Changes were applied automatically by running:

  PythonSlicer -m pip install pyupgrade

followed by the following command executed twice

  PythonSlicer -m pyupgrade --py39-plus MyPythonFile.py

for every python file in the Slicer repository.

Notes:

* Running the command twice is needed to ensure that string associated
  with `logging` package are updated to use f-string.

* Changes have been manually reviewed to revisit some of the changes
  automatically proposed:
  - removed remaining parenthesis after class definition in WebServer
  - removed remaining occurences of `socker.error` from WebServer
  - updated CLISerializationTest to use f-string

Co-authored-by: Jean-Christophe Fillion-Robin <jchris.fillionr@kitware.com>
@jcfr jcfr merged commit a4ea799 into Slicer:main Oct 19, 2022
@jamesobutler jamesobutler deleted the pyupgrade-pre-commit branch October 19, 2022 21:04
@jamesobutler
Copy link
Contributor Author

Here' a screenshot from a private repo with an example of pyupgrade indicating changes:
image

@jcfr
Copy link
Member

jcfr commented Oct 19, 2022

Thanks for sharing the example, I will follow up with a PR including your original commit updating the pre-commit configuration. 🚀

@jcfr
Copy link
Member

jcfr commented Oct 20, 2022

I will follow up with a PR including your original commit updating the pre-commit configuration.

See #6605

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

Successfully merging this pull request may close these issues.

None yet

4 participants