Skip to content

[py] PEP 484 type hints for common.exceptions and webdriver.support.color #9482

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

Merged
merged 6 commits into from
May 27, 2021

Conversation

hoefling
Copy link
Contributor

@hoefling hoefling commented May 17, 2021

Description

This is the initial PR that adds type hints to Python codebase. Typed modules:

  • selenium.common.exceptions,
  • webdriver.support.color.
    I have selected those two for the initial typing since they do not depend on any other modules and can be typed in isolation.

Motivation and Context

The goal of this PR is to configure and add initial integration for the mypy tool (checks the type hints validity). It can be run via

$ tox -e mypy

or

$ TOXENV=mypy tox

Right now it emits 1248 errors, mainly due to missing type hints; the goal is to gradually reduce it to zero. No errors are emitted in modules typed in this PR.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the contributing document.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@CLAassistant
Copy link

CLAassistant commented May 17, 2021

CLA assistant check
All committers have signed the CLA.

@hoefling
Copy link
Contributor Author

@AutomatedTester gentle ping (no rush though!). Please advise:

  1. Is the PR too big for a review? I will split it if necessary and create smaller PRs in the future.
  2. I would like to add a new job that runs mypy checks, but it will fail until all the codebase is typed and unfortunately GH actions syntax doesn't allow to ignore failing jobs. I can filter out untyped modules in mypy config (so no errors are reported) and loosen the filter gradually with each typed module, like suggested in mypy docs:
    # mypy config
    [mypy]
    ...
    [mypy-selenium.webdriver.*]
    ignore_errors = True
    
    [mypy-selenium.webdriver.support.event_firing_webdriver]
    ignore_errors = True
    
    # etc
    However, maybe you have a better idea for me?

Copy link
Member

@AutomatedTester AutomatedTester left a comment

Choose a reason for hiding this comment

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

@AutomatedTester gentle ping (no rush though!). Please advise:

1. Is the PR too big for a review? I will split it if necessary and create smaller PRs in the future.

No, this is fine. Don't go much bigger than this though.

2. I would like to add a new job that runs `mypy` checks, but it will fail until all the codebase is typed and unfortunately GH actions syntax doesn't allow to ignore failing jobs. I can filter out untyped modules in `mypy` config (so no errors are reported) and loosen the filter gradually with each typed module, like suggested in [mypy docs](https://mypy.readthedocs.io/en/stable/existing_code.html):
   ```ini
   # mypy config
   [mypy]
   ...
   [mypy-selenium.webdriver.*]
   ignore_errors = True
   
   [mypy-selenium.webdriver.support.event_firing_webdriver]
   ignore_errors = True
   
   # etc
   ```

I've not used mypy in anger so I might need your help here. Is there a way that we can do a pass over and have file that holds the current output so we can't regress and then fix the others? A little similar to how Rubocop works for Ruby. If your suggestion above does that then do it and we can then clean it up as we go. Let's try not boil the ocean.


class WebDriverException(Exception):
"""
Base webdriver exception.
"""

def __init__(self, msg=None, screen=None, stacktrace=None):
def __init__(self, msg: Any = None, screen: Any = None, stacktrace: Any = None) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

These are always going to be str or `None

@@ -126,11 +128,11 @@ class UnexpectedAlertPresentException(WebDriverException):
Usually raised when an unexpected modal is blocking the webdriver from executing
commands.
"""
def __init__(self, msg=None, screen=None, stacktrace=None, alert_text=None):
def __init__(self, msg: Any = None, screen: Any = None, stacktrace: Any = None, alert_text: Any = None) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

The types here are str or None.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@AutomatedTester I guess except stacktrace? Looking at this right now:

stacktrace = None
st_value = value.get('stackTrace') or value.get('stacktrace')
if st_value:
if isinstance(st_value, str):
stacktrace = st_value.split('\n')
else:
stacktrace = []
try:
for frame in st_value:
line = self._value_or_default(frame, 'lineNumber', '')
file = self._value_or_default(frame, 'fileName', '<anonymous>')
if line:
file = "%s:%s" % (file, line)
meth = self._value_or_default(frame, 'methodName', '<anonymous>')
if 'className' in frame:
meth = "%s.%s" % (frame['className'], meth)
msg = " at %s (%s)"
msg = msg % (meth, file)
stacktrace.append(msg)
except TypeError:
pass
if exception_class == UnexpectedAlertPresentException:
alert_text = None
if 'data' in value:
alert_text = value['data'].get('text')
elif 'alert' in value:
alert_text = value['alert'].get('text')
raise exception_class(message, screen, stacktrace, alert_text)
raise exception_class(message, screen, stacktrace)

@hoefling
Copy link
Contributor Author

I've not used mypy in anger so I might need your help here. Is there a way that we can do a pass over and have file that holds the current output so we can't regress and then fix the others? A little similar to how Rubocop works for Ruby. If your suggestion above does that then do it and we can then clean it up as we go. Let's try not boil the ocean.

I see what you mean - unfortunately, my suggestion doesn't do that, it's merely "hide modules with errors until those are fixed, and don't forget to uncover them back once fixed". However, mypy can produce JUnit and Cobertura reports. Let me play with them - I think the report file can be cached on main branch and compared against the report in the PR. Maybe we can even integrate that in a service like codecov.io and get an automatic regression reports in PRs. However, I wouldn't bloat this PR up anymore and introduce the CI job in another PR.

@hoefling hoefling requested a review from AutomatedTester May 23, 2021 13:27
hoefling added 5 commits May 23, 2021 15:29
Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>
Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>
Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>
Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>
Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>
@hoefling hoefling force-pushed the pep-484-type-hints branch from 8f331c7 to 6ed6f8a Compare May 23, 2021 13:29
@AutomatedTester AutomatedTester merged commit f75343f into SeleniumHQ:trunk May 27, 2021
@sonarqubecloud
Copy link

Kudos, SonarCloud Quality Gate passed!

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

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

Successfully merging this pull request may close these issues.

3 participants