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

Add type safety and type hinting #317

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open

Conversation

ashleyzhang01
Copy link
Contributor

@ashleyzhang01 ashleyzhang01 commented Nov 5, 2024

Type safety is important for catching bugs early, facilitating refactoring, and improving overall maintainability.
We should use type hints makes the code more understandable to both developers and static type checkers, so our code can be more reliable and predictable

We should:

  • Use type hints
  • Write code in a more functional style
  • Add comments for things like magic values, unintuitive behaviors, gotchas, external context, etc.
  • Types should have capitalized names
  • Keep things pythonic (e.g. use list comprehensions, avoid explicit for-loops, use built-in functions and libraries)
  • Use type aliases to give semantic meaning to primitive types (e.g., ModelPath = str)
  • Functions should typically return a singular type rather than unions
  • Functions not used outside their module should be prefixed with _
  • Use context managers (with statements) for resource management
  • Consider using Protocol classes for structural typing
  • Use assert to help type checking when function logic limits return types
  • Use typing.cast as a no-op to inform the type checker of a type when needed
  • Use schemas instead of args and kwargs 😭 (and to replace Anys)

Copy link

codecov bot commented Nov 5, 2024

Codecov Report

Attention: Patch coverage is 94.57112% with 50 lines in your changes missing coverage. Please review.

Project coverage is 90.91%. Comparing base (44965e9) to head (5232436).

Files with missing lines Patch % Lines
backend/sublet/views.py 86.90% 11 Missing ⚠️
backend/pennmobile/admin.py 50.00% 7 Missing ⚠️
backend/portal/permissions.py 84.21% 6 Missing ⚠️
backend/sublet/serializers.py 90.90% 5 Missing ⚠️
...nndata/management/commands/get_fitness_snapshot.py 63.63% 4 Missing ⚠️
backend/gsr_booking/admin.py 70.00% 3 Missing ⚠️
backend/gsr_booking/models.py 94.91% 3 Missing ⚠️
backend/gsr_booking/api_wrapper.py 96.55% 2 Missing ⚠️
backend/utils/email.py 81.81% 2 Missing ⚠️
backend/dining/api_wrapper.py 90.90% 1 Missing ⚠️
... and 6 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #317      +/-   ##
==========================================
+ Coverage   90.83%   90.91%   +0.08%     
==========================================
  Files          63       64       +1     
  Lines        2629     2851     +222     
==========================================
+ Hits         2388     2592     +204     
- Misses        241      259      +18     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ashleyzhang01 ashleyzhang01 marked this pull request as ready for review November 7, 2024 20:12
@Clue88
Copy link

Clue88 commented Nov 7, 2024

Screenshot 2024-11-07 at 3 33 19 PM

Copy link
Contributor

@vcai122 vcai122 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 doing this!!! I have not worked with type systems in python before so a lot of it is just questions and my initial thoughts. PLEASE feel free to tell me I'm completely wrong since I assume you've been working with this stuff a lot :).

I am pretty opinionated about code though and I think its really important that we have a really clear story of how we want to use types, so here's my thoughts so far

  1. Obviously types are an overall plus! Especially I really liked the parts that I saw where we had mostly pure python: api_wrappers, management commands, helper functions (types on get_usage help a lot)
  2. I am scared of the Anys. I think ideally code with Anys should be rewritten/thought about so we can clean these up, but if we just ship it with Anys, we remove an incentive to fix it. BUT
    • a lot are on dicts where its a Django dict which seems better (and we can fix it if we use some sort Django type library???)
    • Tuneer suggested we can just have a rule where moving forward ppl are not allowed Anys and any code changes touching Any code must be fixed.
    • A lot of it is on pretty bad code that needs to be rewritten anyway
  3. I am concerned about being too "ad-hoc" about this. I'm talking about the Django part of the code here. I think if we want types to actually be helpful, and not just extra developer work without the benefits, we should be deliberate about the way we are adding types . Because Django comes with so much pre-built stuff and its so opinionated, we should consult what other people have done. To that end I've done some initial research:

Let me know what you think

self.expiration = timezone.localtime() + datetime.timedelta(seconds=response["expires_in"])
self.token = response["access_token"]

def request(self, *args, **kwargs):
def request(self, *args: Any, **kwargs: Any) -> requests.Response:
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we would want schemas here? Ideally there are built in ones since these are methods we just override?

from gsr_booking.serializers import GSRBookingSerializer, GSRSerializer
from utils.errors import APIError


User = get_user_model()
if TYPE_CHECKING:
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not very familiar with how type checking works, but why do we have these weird versions of User?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm going to guess its because Django makes us do get_user_model()?

I feel like its clunky to have to put this at the top of all the files.. Maybe we can put this in a file and just import it everywhere? I'm starting to wonder how other Django projects do this cleanly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's cause get_user_model() is a function, so it gets annoyed when we try to declare its type. but yes put it in another file under utils/types

backend/gsr_booking/views.py Outdated Show resolved Hide resolved
backend/gsr_booking/views.py Outdated Show resolved Hide resolved
@@ -10,6 +12,9 @@
)


ValidatedData: TypeAlias = dict[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.

I feel like we don't want this type alias here (also I don't think you did above). Instead, if we are going to have this alias, we should have it in some common shared type file (same with the weird User type thing we are doing).

But I think again, I'm wondering if we should be doing this "ad-hoc"-ly ourselves, and what existing large production type-safe Django projects do?

ValidatedData: TypeAlias = dict[str, Any]
CalendarEventList: TypeAlias = QuerySet[CalendarEvent, Manager[CalendarEvent]]
EventList: TypeAlias = QuerySet[Event, Manager[Event]]
HomePageOrderList: TypeAlias = QuerySet[HomePageOrder, Manager[HomePageOrder]]
Copy link
Contributor

Choose a reason for hiding this comment

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

Same with these. I know they are for the sake of clarity, but I feel like its a bit overkill so just adding clutter here. And also in Django world, seeing that it is a queryset is actually probably more helpful.

def add_post_poll_message(request, model):
ModelType: TypeAlias = Type[Model]
AdminContext: TypeAlias = Dict[str, Any]
MessageText: TypeAlias = str
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, ifl ModelType should be in some common library (or better, installed library someone else has already put together). AdminContext would also benefit from coming from a common library (maybe more generically as Context as this is a common Django idea, or again better from some outside library). MessageText seems overkill.

@@ -162,13 +169,19 @@ class Meta:
"images",
]

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any chance serializers are not returning dictionaries?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if this is about the to_representation thing, it seems to be built into rest_framework to convert

queryset = self.get_queryset()
filter = {"user": self.request.user.id, "sublet": int(self.kwargs["sublet_id"])}
obj = get_object_or_404(queryset, **filter)
obj: Offer = get_object_or_404(queryset, **filter)
Copy link
Contributor

Choose a reason for hiding this comment

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

What dictates if you explicitly annotate the type or not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the type of a variable cant be easily inferred (like x=5 is obv an int) or is ambiguous (None or type can change), it might require an explicit annotation. Or some packages we use may not use type annotations, so when we call those functions, we may need to specify. Or, sometimes types may be ambiguous, like ints and floats are often convertible or strs and datetimes if in right format, so you might want to declare type to increase guardrailing

ashleyzhang01 and others added 4 commits November 13, 2024 16:16
* 🌷 add mypy

* 🌌 fix mypy errors in portal

* 👥user and sublet typing

* 🍽️ fix dining typing

* 🏘️ fix subletting tests typing

* 📊 penndata typing errors

* 📚 gsr booking type fixes

* 🧎‍♀️OMFG NO WAY NO MORE TYPE ERRORS

* 🧹 clean up types files

* 💯 fix tests

* 🌉 extend pre-commit rules
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants