Skip to content

Enforce quality checks and type hints, improve quality and typing#73

Merged
bradenmacdonald merged 22 commits intomainfrom
braden/type-hints
Aug 24, 2023
Merged

Enforce quality checks and type hints, improve quality and typing#73
bradenmacdonald merged 22 commits intomainfrom
braden/type-hints

Conversation

@bradenmacdonald
Copy link
Contributor

@bradenmacdonald bradenmacdonald commented Aug 16, 2023

As it turns out, the CI checks for this repo were not running pylint or mypy, and there were a ton of lint errors and typing bugs.

This repo's packages also didn't have any py.typed files, so whatever type hints they declared were being ignored by other code like edx-platform code that uses this.

This PR:

  • configures CI to run quality checks, including using mypy to do static type checking of the code.
  • Adds more type hints, both to make code visible to mypy and to improve its quality
  • fixes all the lint issues found
  • fixes all the type hint issues found

A few of these issues automatically identified are clearly bugs, which are now fixed :)

Private-ref: FAL-3476

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Aug 16, 2023
@openedx-webhooks
Copy link

openedx-webhooks commented Aug 16, 2023

Thanks for the pull request, @bradenmacdonald! Please note that it may take us up to several weeks or months to complete a review and merge your PR.

Feel free to add as much of the following information to the ticket as you can:

  • supporting documentation
  • Open edX discussion forum threads
  • timeline information ("this must be merged by XX date", and why that is)
  • partner information ("this is a course on edx.org")
  • any other information that can help Product understand the context for the PR

All technical communication about the code itself will be done via the GitHub pull request interface. As a reminder, our process documentation is here.

Please let us know once your PR is ready for our review and all tests are green.

message=message,
published_at=published_at,
published_by=published_by,
published_by_id=published_by,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a bug: the function accepted an int but assigned it to a User model attribute.

Check whether a LearningPackage with a particular key exists.
"""
LearningPackage.objects.filter(key=key).exists()
return LearningPackage.objects.filter(key=key).exists()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a bug - missing return statement.

@bradenmacdonald bradenmacdonald changed the title Use mypy to check type hints (WIP) Enforce quality checks and type hints, improve quality and typing Aug 16, 2023
)


def is_displayable_text(mime_type):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function was no longer used, after a prior refactor.

"""
Create a new TextContent instance for the given RawContent.
"""
text = codecs.decode(raw_content.file.open().read(), encoding)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a bug (I assume): the API accepted an encoding parameter but ignored it, and always used "utf-8-sig"

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup, ty!

)
if rc_created or not hasattr(raw_content, "text_content"):
text = codecs.decode(data_bytes, "utf-8-sig")
text = codecs.decode(data_bytes, encoding)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here: encoding parameter was silently ignored.



def create_hash_digest(data_bytes):
def create_hash_digest(data_bytes: bytes) -> str:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

With a type annotation, this argument could be renamed from data_bytes to data: bytes which is much more clear (data_bytes could be a size in bytes, for example). But the name data_bytes is already used by create_raw_content in the public API so I'm not sure if it's OK to change.

Copy link
Contributor

Choose a reason for hiding this comment

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

The only place it could be used is edx-platform, so I think it's okay to change if it's not already referenced there. I also don't think it's that important to do so though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah honestly this is a very minor thing :p

taxonomy = Taxonomy(
name=name,
description=description,
description=description or "",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Per Django convention, NULL/None should not be used for text fields.

def __init__(self, tag: dict, format: str, message: str, **kargs):
self.tag = tag
def __init__(self, tag: dict | None, format: str, message: str, **kargs):
super().__init__(tag)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These exception classes are quite messy and could use a refactor. Once you add type hints, the problems become more obvious. In particular, none of them were calling super().__init__(), and the purpose of the tag field is unclear. Some have hard-coded messages and some let you override the message. For now I just made the minimal changes to get type checks passing.

tags_data = json.load(file)
except json.JSONDecodeError as error:
return None, [
return [], [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was returning (None, list) in a clear violation of the declared return type (list, list)

errors = cls._verify_header(list(header_fields or []))
if errors:
return None, errors
return [], errors
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here, invalid return of None

return task.status
if task is None:
raise ValueError("No import task was created yet.")
return TagImportTaskState(task.status)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a bug: the typing declared that it returned enum instances, but it was returning the enum values which are just plain strings. Also it wasn't handling the case where there was no "last import task".

),
)
description = MultiCollationTextField(
null=True,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Django convention is not not use NULL for text fields, unless there's a very good reason, which doesn't seem to be the case here.

@bradenmacdonald bradenmacdonald force-pushed the braden/type-hints branch 2 times, most recently from 647babd to 0697182 Compare August 23, 2023 20:01
) == expected
# FIXME: something is wrong with this test case. It's setting the string
# "tag_id" as the primary key (integer) of the Tag instance, and it mentions
# "parent validation" but there is nothing to do with parents here.
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 sure what was going on with this test case. It was failing and I don't know what it's even trying to do, so for now I commented it out.

Copy link
Contributor

Choose a reason for hiding this comment

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

@ChrisChV Could you please check this when you have time?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ack!

object_id="id",
tag=tag,
)
self.language_taxonomy.validate_object_tag(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function just returns a bool so this test was having no effect. I commented it out because again I'm not sure what it's supposed to be doing.

Copy link
Contributor

Choose a reason for hiding this comment

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

@ChrisChV Same here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ack!

Copy link
Contributor

@rpenido rpenido left a comment

Choose a reason for hiding this comment

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

Hi @bradenmacdonald! Awesome work here.

I just pointed out some nits, but feel free to push back if you want to!

We are working a lot with this repository, so we should merge this ASAP to avoid a rebase hell.

Question:

  • Is there a chance that we break edx-platform (if the version is not pinned) because we forced the types here, and there is a wrong call on edx-platform side (or another package that imports this package)?

PS: Running make quality I got a warning on the console:

pylint: Command line or configuration file:1: UserWarning: Specifying exception names in the overgeneral-exceptions option without module name is deprecated and support for it will be removed in pylint 3.0. Use fully qualified name (maybe 'builtins.Exception' ?) instead.

Changing the pylintrc as suggested in the warning removed it.

[EXCEPTIONS]
overgeneral-exceptions = builtins.Exception

@bradenmacdonald
Copy link
Contributor Author

@rpenido Thanks for the great review! (and fast!) I agree that we should merge this ASAP because of the large number of possible conflicts.

PS: Running make quality I got a warning on the console... Changing the pylintrc as suggested in the warning removed it.

The pylintrc is managed by edx-lint, so they'll probably fix it "upstream" at some point. We don't need to fix it separately in this repo.

Is there a chance that we break edx-platform (if the version is not pinned) because we forced the types here, and there is a wrong call on edx-platform side (or another package that imports this package)?

Nope. Python ignores the types at runtime, and on the edx-platform side they're not even being checked yet. Actually once this PR is merged, I'm going to modify edx-platform so that they are checked.

Is there something that I can check on the django-rulesPR? I'm not sure how to reproduce what are you experiencing.

Sorry, I didn't mention that. You need to edit openedx_tagging/core/tagging/rules.py to remove the # type: ignore from the line where we import rules, and then if you run mypy you'll see the error I mentioned.

@bradenmacdonald
Copy link
Contributor Author

@ormsbee Since Jill is on leave, would you be able to approve merging this PR?

Copy link
Contributor

@ormsbee ormsbee left a comment

Choose a reason for hiding this comment

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

Looks good to me. I realize there are some follow-up items from this (like the test), but it's probably better to merge this ASAP given it's size and breadth.

Thanks @bradenmacdonald. This is one of those things that I really wanted to see happen but didn't get around to. It warms my heart to see this sort of thing revealing bugs.

@bradenmacdonald bradenmacdonald merged commit 01185f3 into main Aug 24, 2023
@bradenmacdonald bradenmacdonald deleted the braden/type-hints branch August 24, 2023 16:48
@openedx-webhooks
Copy link

@bradenmacdonald 🎉 Your pull request was merged! Please take a moment to answer a two question survey so we can improve your experience in the future.

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

Labels

open-source-contribution PR author is not from Axim or 2U

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants