-
Notifications
You must be signed in to change notification settings - Fork 2
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
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
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. |
There was a problem hiding this 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
- 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) - I am scared of the
Any
s. I think ideally code withAny
s should be rewritten/thought about so we can clean these up, but if we just ship it withAny
s, 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
Any
s and any code changes touchingAny
code must be fixed. - A lot of it is on pretty bad code that needs to be rewritten anyway
- 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:
- https://github.com/typeddjango/djangorestframework-stubs/tree/master/rest_framework-stubs seems promising but idk how to use it
- I think we need some examples of big production Django codebases that uses type safety we can model ours off of
- If we have to write our own types, we should be deliberate and put it in its own library
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: |
There was a problem hiding this comment.
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?
backend/gsr_booking/api_wrapper.py
Outdated
from gsr_booking.serializers import GSRBookingSerializer, GSRSerializer | ||
from utils.errors import APIError | ||
|
||
|
||
User = get_user_model() | ||
if TYPE_CHECKING: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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/penndata/serializers.py
Outdated
@@ -10,6 +12,9 @@ | |||
) | |||
|
|||
|
|||
ValidatedData: TypeAlias = dict[str, Any] |
There was a problem hiding this comment.
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?
backend/penndata/views.py
Outdated
ValidatedData: TypeAlias = dict[str, Any] | ||
CalendarEventList: TypeAlias = QuerySet[CalendarEvent, Manager[CalendarEvent]] | ||
EventList: TypeAlias = QuerySet[Event, Manager[Event]] | ||
HomePageOrderList: TypeAlias = QuerySet[HomePageOrder, Manager[HomePageOrder]] |
There was a problem hiding this comment.
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.
backend/pennmobile/admin.py
Outdated
def add_post_poll_message(request, model): | ||
ModelType: TypeAlias = Type[Model] | ||
AdminContext: TypeAlias = Dict[str, Any] | ||
MessageText: TypeAlias = str |
There was a problem hiding this comment.
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", | |||
] | |||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
* 🌷 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
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:
ModelPath = str
)_
with
statements) for resource management