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

Introduce typeguard in tests #2073

Merged
merged 9 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions evap/evaluation/tests/test_models_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from model_bakery import baker

from evap.evaluation.models import Contribution, Course, Evaluation, Questionnaire, UserProfile
from evap.evaluation.models_logging import FieldAction
from evap.evaluation.models_logging import FieldAction, InstanceActionType


class TestLoggedModel(TestCase):
Expand Down Expand Up @@ -52,7 +52,10 @@ def test_data_attribute_is_correctly_parsed_to_fieldactions(self):
)

def test_deletion_data(self):
self.assertEqual(self.evaluation._get_change_data(action_type="delete")["course"]["delete"][0], self.course.id)
self.assertEqual(
self.evaluation._get_change_data(action_type=InstanceActionType.DELETE)["course"]["delete"][0],
self.course.id,
)
self.evaluation.delete()
self.assertEqual(self.evaluation.related_logentries().count(), 0)

Expand Down
17 changes: 13 additions & 4 deletions evap/evaluation/tools.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import typing
from abc import ABC, abstractmethod
from collections import defaultdict
from collections.abc import Iterable, Mapping
Expand All @@ -12,13 +13,17 @@
from django.forms.formsets import BaseFormSet
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.datastructures import MultiValueDict
from django.utils.translation import get_language
from django.views.generic import FormView
from django_stubs_ext import StrOrPromise

M = TypeVar("M", bound=Model)
T = TypeVar("T")
Key = TypeVar("Key")
Value = TypeVar("Value")
CellValue = str | int | float | None
CV = TypeVar("CV", bound=CellValue)


def unordered_groupby(key_value_pairs: Iterable[tuple[Key, Value]]) -> dict[Key, list[Value]]:
Expand All @@ -35,7 +40,9 @@ def unordered_groupby(key_value_pairs: Iterable[tuple[Key, Value]]) -> dict[Key,
return dict(result)


def get_object_from_dict_pk_entry_or_logged_40x(model_cls: type[M], dict_obj: Mapping[str, Any], key: str) -> M:
def get_object_from_dict_pk_entry_or_logged_40x(
model_cls: type[M], dict_obj: MultiValueDict[str, Any] | Mapping[str, Any], key: str
) -> M:
try:
return get_object_or_404(model_cls, pk=dict_obj[key])
# ValidationError happens for UUID id fields when passing invalid arguments
Expand Down Expand Up @@ -130,7 +137,8 @@ def clean_email(email: EmailT) -> EmailT:
return email


def capitalize_first(string: str) -> str:
def capitalize_first(string: StrOrPromise) -> str:
richardebeling marked this conversation as resolved.
Show resolved Hide resolved
"""Realize lazy promise objects and capitalize first letter."""
return string[0].upper() + string[1:]


Expand Down Expand Up @@ -174,6 +182,7 @@ def formset_valid(self, formset) -> HttpResponse:
return super().form_valid(formset)


@typing.runtime_checkable
class HasFormValid(Protocol):
def form_valid(self, form):
pass
Expand Down Expand Up @@ -262,7 +271,7 @@ def __init__(self) -> None:
else:
self.cur_sheet = None

def write_cell(self, label: str | None = "", style: str = "default") -> None:
def write_cell(self, label: CellValue = "", style: str = "default") -> None:
"""Write a single cell and move to the next column."""
self.cur_sheet.write(
self.cur_row,
Expand All @@ -276,7 +285,7 @@ def next_row(self) -> None:
self.cur_col = 0
self.cur_row += 1

def write_row(self, vals: Iterable[str], style: str = "default") -> None:
def write_row(self, vals: Iterable[CV], style: str | typing.Callable[[CV], str] = "default") -> None:
"""
Write a cell for every value and go to the next row.
Styling can be chosen
Expand Down
6 changes: 5 additions & 1 deletion evap/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,8 +418,12 @@ def CHARACTER_ALLOWED_IN_NAME(character): # pylint: disable=invalid-name

TESTING = "test" in sys.argv or "pytest" in sys.modules

# speed up tests
# speed up tests and activate typeguard introspection
if TESTING:
from typeguard import install_import_hook

install_import_hook(("evap", "tools"))

# do not use ManifestStaticFilesStorage as it requires running collectstatic beforehand
STORAGES["staticfiles"]["BACKEND"] = "django.contrib.staticfiles.storage.StaticFilesStorage"

Expand Down
9 changes: 8 additions & 1 deletion evap/staff/importers/enrollment.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,14 @@
return {field.name for field in fields(self) if getattr(self, field.name) != getattr(other, field.name)}


class ValidCourseData(CourseData):
class ValidCourseDataMeta(type):
def __instancecheck__(cls, instance: object) -> TypeGuard["ValidCourseData"]:
if not isinstance(instance, CourseData):
return False

Check warning on line 79 in evap/staff/importers/enrollment.py

View check run for this annotation

Codecov / codecov/patch

evap/staff/importers/enrollment.py#L79

Added line #L79 was not covered by tests
return all_fields_valid(instance)


class ValidCourseData(CourseData, metaclass=ValidCourseDataMeta):
"""Typing: CourseData instance where no element is invalid_value"""

degrees: set[Degree]
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ pylint-django~=2.5.4
pylint~=3.0.1
tblib~=3.0.0
xlrd~=2.0.1
typeguard~=4.1.5