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

refactor(chore) Add mypy static type checking #321

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

cashall-0
Copy link

Because

  • We want to enhance the project to use static typing

This commit

  • Fixes all objects and methods to be type-checked
  • Update test files to be type-checked
  • Add a command to the Makefile to run the static type checking
  • Add a test to the github actions workflow to run the type checker on pull requests.

Fixes #314

Signed-off-by: siyaka promise <siyakapromise@gmail.com>
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
@cashall-0
Copy link
Author

Hi @b4handjr , the CI type checking is failing because it seems not to be installing mypy. I added mypy to the group dev dependencies, is this appropriate?

Signed-off-by: siyaka promise <siyakapromise@gmail.com>
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
Makefile Outdated
Comment on lines 22 to 23
typecheck:
poetry run mypy .
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like you forgot to add install_dependencies here. Look at the lines above for a clue.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks, it now installs mypy by adding install_dependencies, I was missing that.

Signed-off-by: siyaka promise <siyakapromise@gmail.com>
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
@cashall-0
Copy link
Author

Hi @b4handjr, please can you look into my pr to recommend necessary changes

@b4handjr
Copy link
Contributor

Looking at the build on github actions, it seems there is a failing type check in the docs/ directory, can you please set the type checker to ignore that directory? I also see some failures within tests/webserver.py.

Signed-off-by: siyaka promise <siyakapromise@gmail.com>
@cashall-0
Copy link
Author

Looking at the build on github actions, it seems there is a failing type check in the docs/ directory, can you please set the type checker to ignore that directory? I also see some failures within tests/webserver.py.

I have set mypy to ignore the doc folder, and also cleared the webserver error by removing the deprecated import.

@b4handjr b4handjr self-requested a review October 24, 2024 18:45
@b4handjr
Copy link
Contributor

Looking at the build on github actions, it seems there is a failing type check in the docs/ directory, can you please set the type checker to ignore that directory? I also see some failures within tests/webserver.py.

I have set mypy to ignore the doc folder, and also cleared the webserver error by removing the deprecated import.

Great thanks, I will take a look

@b4handjr
Copy link
Contributor

Hello @cashall-0, please pull main and update accordingly, I merge the fix for the failing tests. Thanks!

@cashall-0
Copy link
Author

Hello @cashall-0, please pull main and update accordingly, I merge the fix for the failing tests. Thanks!

Alright, I will get to it.

Signed-off-by: siyaka promise <siyakapromise@gmail.com>
@cashall-0
Copy link
Author

Hello @cashall-0, please pull main and update accordingly, I merge the fix for the failing tests. Thanks!

I have pulled and updated accordingly, do let me know whatever needs to change as you review.

@b4handjr
Copy link
Contributor

Hello @cashall-0, please pull main and update accordingly, I merge the fix for the failing tests. Thanks!

I have pulled and updated accordingly, do let me know whatever needs to change as you review.

Thanks! I'll give it a look today

Copy link
Contributor

@b4handjr b4handjr left a comment

Choose a reason for hiding this comment

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

Thanks for working on this. I left some comments, I also noticed some of your new files don't have new lines at the end of them.

Comment on lines +32 to +33
if TYPE_CHECKING:
from foxpuppet.windows import BrowserWindow # Import for static typing
Copy link
Contributor

Choose a reason for hiding this comment

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

What's this for?

Copy link
Author

Choose a reason for hiding this comment

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

It keeps showing errors of cyclic dependency, like two classes importing from each other. so I imported for just the typechecking.
The current import is done inside the method.
example:
ImportError: cannot import name 'BrowserWindow' from partially initialized module 'foxpuppet.windows' (most likely due to a circular import) (/root/outreachy/moxilla/FoxPuppet/foxpuppet/windows/init.py)

Copy link
Contributor

Choose a reason for hiding this comment

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

I did some research on this and this pattern seems to be the accepted way

tests/test_notifications.py Outdated Show resolved Hide resolved
@@ -26,7 +29,7 @@ def __init__(self, selenium, handle):
self.wait = WebDriverWait(self.selenium, timeout=10)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this have a type?

Copy link
Author

Choose a reason for hiding this comment

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

it can,I assigned type WebDriverWait

@@ -21,7 +22,7 @@ class AddOnInstallConfirmation(BaseNotification):
"""Add-on install confirmation notification."""

@property
def addon_name(self):
def addon_name(self) -> str | Any:
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this also Any?

Copy link
Author

Choose a reason for hiding this comment

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

done, changed to str

Comment on lines 27 to 28
from foxpuppet.windows import BrowserWindow

Copy link
Contributor

Choose a reason for hiding this comment

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

Could this import not go at the top?

Copy link
Author

Choose a reason for hiding this comment

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

yes, Done

"""First Firefox browser window opened."""
return foxpuppet.browser


@pytest.fixture
def firefox_options(firefox_options):
def firefox_options(firefox_options: Any) -> Any:
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use this, from selenium.webdriver.firefox.options import Options as FirefoxOptions

Copy link
Author

@cashall-0 cashall-0 Nov 4, 2024

Choose a reason for hiding this comment

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

Done, but I had problems with these after using the type FireFoxOptions
if os.getenv("MOZREGRESSION_BINARY"): firefox_options.binary = os.getenv("MOZREGRESSION_BINARY") # type: ignore firefox_options.log.level = "trace" # type: ignore

that's why I used type ignore. Can you please look at this. In line 54 - 56.

Comment on lines 95 to 97
def test_open_close_notification(
browser: BrowserWindow, blocked_notification: AddOnInstallBlocked
) -> BaseNotification:
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a test, it shouldn't return anything

@@ -32,7 +33,7 @@ def __init__(self, host="", port=8000):
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)

@property
def host(self):
def host(self) -> Any | str:
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this really Any or string? Why not just a string or int?

Copy link
Author

Choose a reason for hiding this comment

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

it has the types "str | bytes | bytearray" leaving it at "str" or "str | int" would show an error. But on tracing the variables and method calls it shows type assigned as "_RetAddress" which was used as a type alias for "Any".

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah okay gotcha, thanks for that explanation.

Signed-off-by: siyaka promise <siyakapromise@gmail.com>
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
Copy link
Contributor

@b4handjr b4handjr left a comment

Choose a reason for hiding this comment

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

Left some of my thoughts. There are also some files that are missing a new line

Comment on lines 54 to 55
if os.getenv("MOZREGRESSION_BINARY"):
firefox_options.binary = os.getenv("MOZREGRESSION_BINARY")
firefox_options.log.level = "trace"
firefox_options.binary = os.getenv("MOZREGRESSION_BINARY") # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets get rid of this if statement, we shouldn't need this MOZREGRESSION_BINARY stuff

Copy link
Author

Choose a reason for hiding this comment

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

Okay, I have removed the check and binary assignment.

firefox_options.binary = os.getenv("MOZREGRESSION_BINARY")
firefox_options.log.level = "trace"
firefox_options.binary = os.getenv("MOZREGRESSION_BINARY") # type: ignore
firefox_options.log.level = "trace" # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be str?

Copy link
Author

Choose a reason for hiding this comment

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

log.level is actually of the type str, but the default value assigned to the log.level in options.py is None, so this somehow is interfering with type check making it ask for a type None.

Comment on lines 52 to 54
def blocked_notification(
addon: Any, browser: BrowserWindow, webserver: WebServer, selenium: WebDriver
) -> BaseNotification:
Copy link
Contributor

Choose a reason for hiding this comment

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

addon should have a type.

Copy link
Author

Choose a reason for hiding this comment

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

done, unbundled the class AddOn which was previously encapsulated in a method.

Comment on lines 152 to 154
def test_confirm_addon_install(
addon, browser: BrowserWindow, confirmation_notification: AddOnInstallConfirmation
) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

addon should have a type.

Copy link
Author

Choose a reason for hiding this comment

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

done

Comment on lines 161 to 163
def test_addon_install_complete(
addon, browser: BrowserWindow, complete_notification: AddOnInstallComplete
) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

addon should have a type.

Copy link
Author

Choose a reason for hiding this comment

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

done

tests/conftest.py Outdated Show resolved Hide resolved
@cashall-0
Copy link
Author

Thanks for working on this. I left some comments, I also noticed some of your new files don't have new lines at the end of them.

I might be confused about what new files you are referring to, can you specify one of such?

Signed-off-by: siyaka promise <siyakapromise@gmail.com>
@cashall-0
Copy link
Author

@b4handjr I'm trying to update my branch and push but there is a conflict in poetry.lock, the hash generated is different due to changes in project.toml. Which of these should I accept? my hash or the hash from main ? or is there a better way to resolve this?

@b4handjr
Copy link
Contributor

b4handjr commented Nov 5, 2024

@b4handjr I'm trying to update my branch and push but there is a conflict in poetry.lock, the hash generated is different due to changes in project.toml. Which of these should I accept? my hash or the hash from main ? or is there a better way to resolve this?

You could delete this file and merge in the new pyproject.toml file and just generate a new poetry.lock file, that should resolve the conflicts.

Let me know when you get that resolved and I'll take another look. It's looking good so far, thank you!

.github/workflows/linux_tests.yml Outdated Show resolved Hide resolved
Makefile Outdated Show resolved Hide resolved
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
@cashall-0
Copy link
Author

@b4handjr I'm trying to update my branch and push but there is a conflict in poetry.lock, the hash generated is different due to changes in project.toml. Which of these should I accept? my hash or the hash from main ? or is there a better way to resolve this?

You could delete this file and merge in the new pyproject.toml file and just generate a new poetry.lock file, that should resolve the conflicts.

Let me know when you get that resolved and I'll take another look. It's looking good so far, thank you!

While trying to resolve this, I noticed poetry version changed from 1.8.4 to 1.8.3 on the main. Is there a particular reason for the downgrade?

@cashall-0
Copy link
Author

@b4handjr , I have tried to resolve the conflict and fixed from your recommendations. you can now take another look , thanks.

@b4handjr
Copy link
Contributor

b4handjr commented Nov 6, 2024

@b4handjr , I have tried to resolve the conflict and fixed from your recommendations. you can now take another look , thanks.

Thanks, I'll take a look but I think this is looking good

Copy link
Contributor

@b4handjr b4handjr left a comment

Choose a reason for hiding this comment

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

A few spots in the main modules that are missing types. I don't think we need to be too picky in the tests. Thanks for the work so far, this is just about done!

foxpuppet/windows/base.py Outdated Show resolved Hide resolved
foxpuppet/windows/base.py Outdated Show resolved Hide resolved
foxpuppet/windows/browser/notifications/addons.py Outdated Show resolved Hide resolved
@@ -31,15 +37,15 @@ def create(window, root):
:py:class:`BaseNotification`: Firefox notification.

"""
notifications = {}
notifications: dict[Any, Any] = {}
_id = root.get_property("id")
Copy link
Contributor

Choose a reason for hiding this comment

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

This could use a type

Copy link
Author

Choose a reason for hiding this comment

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

Done, it is of the type str but mypy wouldn't let my assign only that so I used suggested str | bool | WebElement | dict

tests/webserver.py Outdated Show resolved Hide resolved
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
@b4handjr b4handjr self-requested a review November 12, 2024 15:15
Copy link
Contributor

@b4handjr b4handjr left a comment

Choose a reason for hiding this comment

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

Just a few final comments. I'd like to not use Any as much as possible, but there will be some places where we need it.

tests/test_notifications.py Outdated Show resolved Hide resolved
tests/test_notifications.py Outdated Show resolved Hide resolved
"""Retrieve the notification description."""
if self.window.firefox_version >= 67:
return self.root.find_element(By.CLASS_NAME, "popup-notification-description")
return self.root.find_anonymous_element_by_attribute(
"class", "popup-notification-description"
)

def find_close_button(self):
def find_close_button(self) -> WebElement | Any:
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems this could just a return a WebElement no?

Copy link
Author

Choose a reason for hiding this comment

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

Yes , but for this method and four others written this way
def find_close_button(self) -> WebElement | Any: """Retrieve the close button.""" if self.window.firefox_version >= 67: return self.root.find_element(By.CLASS_NAME, "popup-notification-closebutton") return self.root.find_anonymous_element_by_attribute("anonid", "closebutton")

for the second return statement here, I can't seem to find the implementation of the method find_anonymous_element_by_attribute() hence, the any for whatever the outcome.

foxpuppet/windows/base.py Outdated Show resolved Hide resolved
foxpuppet/windows/browser/notifications/addons.py Outdated Show resolved Hide resolved
Signed-off-by: siyaka promise <siyakapromise@gmail.com>
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.

Add mypy static type checking
2 participants