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

Enable mypy's check_untyped_defs #6444

Merged
merged 2 commits into from
Jul 17, 2024
Merged

Enable mypy's check_untyped_defs #6444

merged 2 commits into from
Jul 17, 2024

Conversation

marcospri
Copy link
Member

Make necessary changes to allow a clean mypy run after the change

@@ -1,3 +1,4 @@
# type: ignore
Copy link
Member Author

Choose a reason for hiding this comment

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

Product/plugin code might need more time consuming refactor to typecheck.

Just ignoring it for now to be able to enable check_untyped_defs

Make necessary changes to allow a clean mypy run after the change
@@ -400,7 +400,7 @@ def course_infos(self):

for row in self._db.execute(query):
# SQLAlchemy returns None instead of [].
authority_provided_ids = row.authority_provided_ids or []
row_authority_provided_ids = row.authority_provided_ids or []
Copy link
Member Author

Choose a reason for hiding this comment

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

This was re-typing a variable from the outer scope.

@@ -50,7 +50,7 @@ def __init__(self, request) -> None:
self.organization_service: OrganizationService = request.find_service(
OrganizationService
)
self.organization_usage_report_service: OrganizationService = (
self.organization_usage_report_service: OrganizationUsageReportService = (
Copy link
Member Author

Choose a reason for hiding this comment

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

This was the wrong annotation.

file_id = course.get_mapped_file_id(
DOCUMENT_URL_REGEX.search(document_url)["file_id"]
)
document_url_match = DOCUMENT_URL_REGEX.search(document_url)
Copy link
Member Author

Choose a reason for hiding this comment

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

There are a few of these, assert that we expect the regex to always be successful.

@@ -266,7 +266,7 @@ def _configure_assignment(self, assignment):
)

def _configure_js_for_file_picker(
self, assignment: Assignment, route: str = "configure_assignment"
self, assignment: Assignment | None, route: str = "configure_assignment"
Copy link
Member Author

Choose a reason for hiding this comment

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

_configure_js_for_file_picker takes an empty assignment when first creating an assignment.

@@ -225,7 +225,7 @@ python_version = 3.11
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true

check_untyped_defs = true
Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

@robertknight robertknight left a comment

Choose a reason for hiding this comment

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

Looks sensible to me. I had a small queries about # type: ignore and lazy="dynamic".

@@ -78,7 +78,7 @@ class Assignment(CreatedUpdatedMixin, Base):
deep_linking_uuid: Mapped[str | None] = mapped_column(sa.Unicode, nullable=True)
"""UUID that identifies the deep linking that created this assignment."""

groupings: Mapped[list[Grouping]] = sa.orm.relationship(
groupings: DynamicMapped[Grouping] = sa.orm.relationship(
secondary="assignment_grouping", viewonly=True, lazy="dynamic"
)
Copy link
Member

Choose a reason for hiding this comment

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

I note in the docs that lazy="dynamic" is a deprecated feature. Is it planned to migrate to the new thing at some point?

Copy link
Member Author

Choose a reason for hiding this comment

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

Assigned this small note to myself, I don't think it's that urgent to migrate away.

We'd keep this in mind to avoid adding new usages of it.

@@ -20,7 +20,7 @@ def is_file_in_course(self, course_id, file_id, type_) -> bool:

def find_matching_file_in_course(
self,
store_new_course_files: Callable[[str], None],
store_new_course_files: Callable[[str], Any] | Callable[[int], Any],
Copy link
Member

Choose a reason for hiding this comment

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

It would be useful if this function had docs to clarify what the "non-obvious" parameters are and what the Any values here represent.

Copy link
Member Author

Choose a reason for hiding this comment

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

Any here is the return value, we don't use it.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm... interesting. In TypeScript we use void as the return type for function arguments where the return value isn't used because any (arg: string) => T can be assigned to (arg: string) => void. This has an advantage over any because the type checker will complain if the function does try to use the argument. It seems that the same approach doesn't work here though if using Callable[[str], None].

This fails to typecheck:

from typing import Callable

def do_something(callback: Callable[[str], None]) -> None:
    callback("foo")

def bar(arg: str) -> str:
    return arg + arg

do_something(bar)

This does work, but requires boilerplate:

from typing import Callable, TypeVar

R = TypeVar("R", covariant=True)

def do_something(callback: Callable[[str], R]) -> None:
    callback("foo")

def bar(arg: str) -> str:
    return arg + arg

do_something(bar)

To be clear, this is just me being curious. I think Any is fine for this PR to get us started.

"""
Get an SQL comparator for matching emails.

This will match the full email if it contains '@' or interpret the text
as a domain if not. This will search over all the provided fields.
"""
return sa.or_(
return sa.or_( # type: ignore
Copy link
Member

Choose a reason for hiding this comment

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

What is the issue here? Also, can you add a comments alongside the type: ignore? In the frontend we have markers like this:

 // @ts-ignore - TS doesn't know about PDFViewerApplication global.

As TS fortunately allows us to add notes for future readers.

Copy link
Member

Choose a reason for hiding this comment

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

One other useful thing TS can do is to indicate that you expect an error:

 // @ts-expect-error - Navigation API is missing from TS

This will cause a warning or error if the suppression subsequently becomes unnecessary. Is there a mechanism in mypy to inform us if a # type: ignore becomes unnecessary?

Copy link
Member Author

Choose a reason for hiding this comment

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

Is there a mechanism in mypy to inform us if a # type: ignore becomes unnecessary?

Yes, we'll get an error for those with the current conf 👍

@robertknight
Copy link
Member

robertknight commented Jul 17, 2024

I'm going to merge this while Marcos is away to make sure that new code added subsequently is compatible with the check_untyped_defs flag.

@robertknight robertknight merged commit 36c04d2 into main Jul 17, 2024
9 checks passed
@robertknight robertknight deleted the mypy-functions-2 branch July 17, 2024 10:49
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.

2 participants