Creating an Inclusive Django Community with Kenya Phelps
Published July 15, 2026
This video features Kyle Bebak at DjangoCon US 2022 in San Diego, California, USA.
By now most of us have heard of, or used, Python type hints. Since being added to Python in 3.5, they've spread to every corner of the Python ecosystem.
These days many libraries are built from the ground up with type hints. But type hints don't have to live inline with the code, or even in the same repo. Anyone can write standalone type hints (also called stubs) for a framework like Django, and eventually people did.
In this talk I'll show you how to use a great set of type stubs for Django called django-types, how to check your type hints with pyright, some of the challenges with adding type hints to Django code, and how to make type hints and type checking a real productivity boost.
This talk was presented at: https://2022.djangocon.us/talks/type-checking-your-django-code-with-and/
LINKS:
Follow Kyle Bebak 👇
Follow DjangCon US 👇
https://twitter.com/djangocon
Follow DEFNA 👇
https://twitter.com/defnado
https://www.defna.org/
Kyle Bebak explains how Python type annotations and static type checkers can improve Django projects by catching mistakes early, documenting code, and enabling editor features such as navigation, autocomplete, and safer refactoring. He recommends Pyright and django-types, a type-checker-agnostic fork of django-stubs, and shows how to configure Pyright with a Poetry-managed virtual environment. Using a small Django REST Framework application, he demonstrates annotating Django’s implicit foreign-key IDs, reverse managers, many-to-many relations, nullable fields, constrained values, and authenticated requests, then uses those annotations to check model and view code. He argues that the upfront effort is especially worthwhile as projects and teams grow, while suggesting that Django would benefit from shipping official type hints.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Hi Welcome to DjangoCon 2022. And welcome to this presentation. I'm going to talk about type checking Django code with Django types and PyWrite. I like, you know, when there's working code for presentations, so anyone that wants to can, you know, get the code and slides at this. uh GitHub repo here. And I think the uh links to the GitHub repo and the slides are also in the description for this talk Um yeah, so let's get started. Uh Python type pens.
Uh yeah, before we talk about PyPents for Django, let's just talk about uh PyPints for any Python code. So they were added to Python in version 3. 5. And since then they've kind of spread throughout the Python ecosystem. They're sort of everywhere. A lot of newer libraries. these days are just built from the ground up with with type annotations in line with the code. For older libraries, like Django types are often added as stubs. Um stubs are files that are separate from the code. They can be bundled in the same repo as a library, but
don't have to be. In the case of Django, they're not, um, there are uh third party libraries with with Type annotations for Django. So stubs are nice because you don't have to touch library source code if you don't want to. Which means, you know, they can be maintained by the community. And you don't necessarily need to get uh buy-in from project maintainers to add type annotations to their code. Yeah, there's sort of a network effect with uh type annotations. Um The standard library now has like very accurate type annotations for just about everything and more and more third-party libraries have
have type hints and uh the more code that you depend on that has type hints um Sort of the more benefit you get uh from using type hints and a type checker yourself. Uh so maybe when type hints were first introduced in 3. 5, there wasn't uh a really like strong reason to adopt them, but I I think that has changed um as the type ins have kind of spread throughout. all of Python. Um of course, as with everything in engineering, type hints, uh, you know, using them is is a trade-off, adding them to a project is a trade-off. There's you know, more work up front.
Um but uh you know this work uh of adding type hints helps with correctness um in in my opinion, and I hope that'll be apparent when we get to coding with type hints later in the presentation. Helps with documentation. Uh I I think code with type hints tends to document itself. more effectively than code without. And hopefully that will also be apparent in the presentation. It helps with IDE style features like you know navigation, um, you know, refactoring code, uh changing code and having confidence that you're not breaking things.
Um And yeah, uh type hints uh if they're you know configured uh so that your text editor can take advantage of them, uh can kind of give your text editor capabilities that used to be exclusive to um IDEs. So that's in my opinion a plus and and we'll have a look at that as well So now that we've talked about Python type ins, let's have a look at type hints in Django. Yeah, as I mentioned earlier, Django doesn't have type hints in line with the source code. And there are a number of reasons for that
that are sort of like you know, documented by the Django core devs um online. Um one is that uh type hints when uh they were you know sort of first proposed to be included in Django were a bit immature and there was uncertainty around like which type checker uh was, you know, perhaps the best one to use, um, and which type hint library kind of made the most sense. Um And another reason is that, you know, as I mentioned earlier, adding type hints to a code base is a trade-off. It definitely raises the barrier for
new contributors. could be seen to raise the barrier for maintainers as well, especially if they're not used to to working with type hints. Another reason that's kind of not in this slide is that Django does things that are like difficult to type in some cases. And we'll see that later in this presentation where we have to kind of add some type annotations manually. I think the extra effort there is like well worth the the reward, but Yeah, having type hints that sort of accurately describe all of the things that Django can do, especially in the ORM, is difficult. Um
Yeah. So what this means is that there are separate libraries with type stubs that are maintained by the community. And the biggest one. The sort of most important one and the best maintained one is Django Stubs here. And then there's Django Types, which is a fork of Django stubs, and we'll we'll talk about those. And as far as type checkers, in other words, like just a CLI tool that you can run against your code to You know, on your dev machine or in a CI server to see if there are type errors and where they are. There are a bunch of kind of mature and serious options. The
uh oldest and most canonical one is my pi uh that is sort of maintained by by Dropbox. Um There's PyRite, which is maintained by Microsoft. There's Pyre, which is maintained by Facebook. There's PyType, which is maintained by Google. So a lot of big tech companies here have their own Python type checkers. And this can seem, you know, maybe a little overwhelming. There are a lot of choices. If you're using a language like TypeScript, you know, you use the TypeScript compiler to check your types. There is no ambiguity there. With uh Python, it's not as straightforward. Um I'd like to talk a little bit about these different choices and explain why I think uh some are better than others.
Um So yeah, starting not with the type pint libraries for Django, but with the with the type checkers. I would go with PyRite. That's the one that I prefer to use for a couple of reasons. One, PyRite is not only a type checker, it's also a language server. as part of the uh language server protocol, which is kind of a spec that's uh authored and maintained by by Microsoft. Um Uh for those of you that haven't used uh LSP plugins in your text editors before. This is the part of things that can give like a sort of bare bones
text editor like, you know, Vim or Emacs or Sublime Text or whatever, the capabilities of an ID without all of the cruft that an IDE might have. Yeah. The other point in favor of Uh Pyright is that the uh author of the library is just a fantastic programmer. He's, you know, even the author of a few pipe uh type checking peps, uh, some of which have been accepted. Uh you know, for inclusion into Python, um, and the quality of the code base and the tool, I think, are just kind of unmatched. Um And yeah, if we grant that
uh, you know, PyRite or Pyre or PyType, any of those three are um like a better option or an option that we'd like to be able to choose. Instead of just choosing myPy, well that uh sort of forces us to use Django types instead of Django Stubs um because Django Stubbs is only compatible with MyPy. Why is that? Well Djang Django Stubs is yeah the you know best maintained and kind of oldest set of type stubs for for Django. Um when it got started uh
MyPy was pretty much the only game in town for Python type checking. You know, the other type checkers existed, but MyPy was by far the most mature. And that has changed. They've, you know, uh sort of all caught up to each other to some extent. And uh yeah, Django Subs kind of bet heavily on my pi and they didn't just create Python uh type stubs, they created a MyPy plugin, uh, which means there's syntax in there that's like not compatible with standard type annotations and other type checkers simply cannot use. Django stubs. So Django Types is just a fork of Django Subs with the uh
um myPy stuff kind of taken out so that the type stubs are compatible with my PyPyRipePy Pyre PyType any of the Python type checkers. So that's what we'll use in the presentation today. So now that we have like an idea of what we want to use to add type hints to our uh you know simple Django code base here. Uh let's talk about requirements. The requirements are pretty simple. We need to install type stubs as project dependencies. So yeah, we'll use Django types and Django Rest framework types. Django Rest Framework Types is analogous to Django types.
It's a fork of Django Rest Framework stubs that is compatible with any type checker and not just with my pi. And yeah, we'll install PyRite and enable it in our text editor, for example, Sublime Text, which is what I'll be using today. And then we'll configure a pyrigonfig. json file to basically tell Pyrite where our dependencies are. We'll install the project dependencies into a virtual end and uh we'll tell PyRite where those dependencies are so it can help us with code navigation, with checking correctness of things and so on.
Yeah, one other thing to mention before we just head to the command line and uh look at some of this stuff uh is that If you can be on Python 3. 8 end up , that's sort of more desirable for using type ins. They're they they work like um without any hitches in previous versions, but the syntax is definitely friendlier if you're on Python 3. 8 and up. You can just Do this thing here from future import annotations and then uh syntax changes that you know, shipped in Python 3. 9 or 3. 10, uh or will ship in 3.
11 are all available in Python 3. 8. And yeah, this just makes things more ergonomic. You can use like, you know, the built-in list syntax instead of having instead of having to import, you know, uh capital list here from the typing module. You can use this pipe for unions instead of the instead of importing union from the typing module and you know just other niceties So yeah, installing dependencies. If I head to the command line here, I've got this project. And Yeah, I've already installed uh
Pyrite. And um I've installed Poetry. Poetry is what I'm going to be using to install dependencies, and I've already run Poetry Install to install those dependencies. Yeah. If I run poetry show-v, uh Poetry will show me which dependencies I've installed for this project and where it's put them on disk in this virtual end. Yeah, and this is sort of like a minimal set of dependencies. We just want Django and uh Django Rest framework and be typestebs for Django and for Django res framework. If we look at Pyproject. toml
here, this is where these dependencies are specified. Yeah, so to tell pyRite where uh these dependencies are, we need to create a pyright config. json file. I've got this pyRightconfig-ci. json file. This one is checked into version control, whereas pyright config. json is not. And that's because if you're working on a project with multiple devs, it's nice that everyone can create their own highwriteconfig. json. Uh and specify their own values for uh VM path.
And uh the end here. Um we can take those values from what was printed to The console by Poetry Show Dash B. Uh This here is the VM path, and this is the VM. Um and uh yeah, once we do this, if we uh run the PyWrite from the command line here. Uh We'll see that we have some errors. We'll get to the code in a little bit, but none of these errors are import errors.
PyRite is now able to resolve all of these dependencies if we hadn't done this or if we I don't know get rid of this code and run PyRite again we're gonna have a whole lot more errors because pyRite can't find our dependencies. Yeah, there are a bunch of like configuration options in this JSON file here. Those are described in the PyRite repo. And they just give you sort of uh knobs to play with and make your sort of type checking experience sort of more or less uh strict. Um yeah, I tend to think this
picture is better. Okay, so yeah, if I run PyRite again, looks like you know we're we're sort of Done installing our dependencies here. Yeah, so we're going to add types to an existing project. We've got uh just three models in this project, the post uh thread and user. We're gonna build something like the world's jankiest Reddit. Um And yeah, we've got these posts up here that uh you know belong to a user that's what this sort of line with the fork on the end of it means um you know a user has many posts and every post belongs to a user and we can see that we've got a foreign key from
uh posts to a user over here. Every post also belongs to a thread, kind of like a channel. And uh Well I guess if we're using the the jankiest Reddit lingo, a a subreddit. So we've got like a thread ID pointing to thread. That's a foreign key. Yeah, and then a user can subscribe to threads, so we'll have this many-to-many relationship between uh user and thread uh that you know basically says like a thread can have many users subscribe to it and a user can be subscribed to many threads. And then we've got this nullable foreign key here.
That's what this like dotted line here indicates. We've got this nullable foreign key. from thread to user, the thread can have a single moderator that points to some user. And yeah, what we'll do is kind of add type hints to the models, which is a good place to start. We'll get to that in a moment. And then after we've added type hints to the models, we'll kind of flesh out some view code. uh that takes advantage of the type ends. So yeah, you know where to focus. We'll start with the models. We have to uh add type annotations
uh for every like foreign key field that ends with underscore id. These fields aren't explicitly declared on the model class. They're kind of like added to the model by Django. And this is what I was talking about earlier where I mentioned that Django has some patterns that make like it difficult for a type checker to uh Or you know, impossible for a type checker to um infer what's what's going on with with every class. Um so some things we have to annotate manually. Um Yeah, so on the user model, actually on the post model, we would you know
have like a user ID field that we annotate and just you know mention that that's an integer. And on the thread, we'd have you know a moderator ID field, and because this is nullable, we'd sort of tell the type checker that this is an integer or none. Um Yeah, another thing that we have to annotate by hand is reverse foreign key managers. If uh you look at, you know Maybe post here and every post belongs to a user is authored by a user. That means like a user has many posts. And if we have a user instance in the ORM, we can do, you know, user.
post. all, for example, to get all the posts that uh belong to the user. Um, we have to annotate the those posts by hand on the user class and just say that post points to a manager of of post instances. Many to many fields. Sort of the same logic there as with the reverse foreign key manager. This is something that the type checker can't infer and we have to annotate manually. And then there are some special cases. that aren't required, but uh like we can sort of get more mileage out of the type checker if we give it a bit more information about, I don't know, where we have a a car field that, you know, is restricted to just a few choices instead of having the type checker
think that this Carfield is just a uh you know string every time we can say, well, no, it has to be, you know, one of these literal choices. And we'll we'll see that in a moment when we start coding. For views, there's not as much to worry about. We'll get a lot of mileage just out of adding the type annotations to the models. But one thing that's nice to do is create our own. request class that inherits from request. We're going to be using DRF here, so it'll inherit from a DRF request. And we're just going to change the user attribute like on a request instance so that it points to one of like our users instead of you know an abstract base user or one of Django's built-in user classes.
And If there are places where we're kind of fighting with the type checker and we know that some code is okay and the type checker doesn't think it is because it doesn't have enough information to know that, we can work around these kinds of errors with uh cast or this type ignore directive in a comment. We should try to do that sort of as infrequently as possible if you. or ignoring your type checker , it is giving you less value. But there are some places where like there's no other good way to work around things. So yeah, let's just go over to the text editor now and start adding type pints to the models and then write some of these views.
Yeah, so we're gonna start with our models. Um we've got this base model here that uh you know, our other models inherit from and uh it ensures that you know every model uh has like some extra record keeping fields here, some timestamps. Um And then we'll move down to our user model. User doesn't have any foreign keys to other models, but it does have some sort of reverse object managers. Every post belongs to a user, so a user has posts. And we'll just annotate this and say that posts here on the user points to a manager of post instances. We'll do the same thing for
for threads. Remember every uh you know user can subscribe to multiple threads, so uh we'll have threads here uh and this will be a models dot many two many field um pointing to thread through the user thread table, which is down here. This is the through table between Thread and user. Um Yeah, a user can also moderate a thread and a single user could moderate uh you know many threads. So we'll have I guess a
um Yeah, a reverse object manager called moderated threads. That's also a manager of thread here. Um and yeah, regarding role, if we look at the type of role. PyRat thinks right now that this is a car field that just has a string inside of it. We can do a bit better than that. We know that, you know, the role for any user is either member, moderator, or admin. So We can instead say that this is a car field that points to a literal of
Member Moderator or admin. Um And if we do this, uh Pyrite is going to complain at us. Um It's still convinced that this is just a car field with a string inside. It doesn't think it's okay that we uh Sort of restrict this value to being one of these three literal values. So here we're gonna work around the uh type checker and we're just gonna tell it not to freak out about this. Um and now we have a car field uh like
whose value has to be one of member, moderator, or admin, which is kind of nice. And if we think we might use this literal type in other places in the code base, well we can, I don't know, pull it out of here. We can, you know, call this, I don't know, user role type. And then we can create a user role type That's equal to this. This is just a type alias now. Um yeah, so that We'll take care of things for user. If we look at some of the other fields, you'll see that in email field the type checker knows there's a string inside there that
you know that seems correct. And yeah, we can move down to posts. So post has foreign keys to user and thread. We'll add a user ID in here because this is sort of some Django magic and can't be inferred by the type checker We know that this points to an integer. Same for the thread ID. If we look at the type of user, And thread, the type of user looks good. So if one key pointing to user, the type of thread does not. That's because we have this kind of syntax syntax here. And the reason we have this syntax is because thread is defined later in the module. So we'll just tell the type checker
that this is a models. foreign key with a thread pointing to a thread. And now this type here is sort of more explicit and more correct. If we look at text, yeah, this is you know going to sort of result to a string when we access, you know, uh dot text on a post instance and is deleted is going to be a Boolean and that looks good to me. Let's move on to thread. Uh we've got this moderator here. If we look at the type of moderator, it's a foreign key that points to user and none. This is really cool to type. Checker knows because this argument is true here,
that this moderator doesn't always point to a user, sometimes it points to none. We'll add a moderator ID. Uh that also points to a an integer or none. Um Yeah, we've got this many to many field pointing to users, so we're gonna want. to add that type annotation as well. Users point to a models dot many to many field Uh user through user thread. That looks good
Yeah, and that about takes care of things. We're not going to be like explicitly using the through table model much here, but we could add like a couple of annotations anyway. We could have a user ID which is an integer and a uh thread ID which is an integer an integer and uh we'll sort of leave it at that I guess you know worth looking at a date time field. The type checker knows that like this subscribe that will resolve to a date time, which is nice. And yeah, before we get to writing, you know, view code here, we we've annotated our models. And before we get to writing view code, let's just write a function that, you know, sends some email notifications to users about threads that
uh they're subscribed to that have had you know posts published to them in the last week. So we'll we'll send uh in this function we'll send an email notification to each user with you know the text from all the posts in you know, all the threads that each user subscribed to in the last, you know, seven days for posts that were published in the last seven days. So yeah, we'll we'll start with these threads here And maybe this is just going to be, you know, thread dot objects. all dot prefetch related. We want to prefetch the related users that are subscribed to the thread and the related posts. Um yeah.
Uh so this is now a base manager of thread. We can iterate over these threads and uh the type checker will know what's you know in this sort of iterable of threads which is cool for thread and threads um Let's get some posts. Yeah, posts could be the post sort of created in the last week. Equals thread. Yeah, TypeChecker knows this has a type of thread and that's cool. It knows that it has posts. Oh no, it doesn't. Oh, and that's because we haven't added every post belongs to a third. We haven't added a
reverse object manager for posts up here. So uh this would be a manager of posts. And now we should be good. Cool. So thread dot post. Maybe filter. created at is greater than uh the current timestamp minus uh Seven days. That looks good. And yeah, we want to kind of get the concatenated text of all these posts so we can stick that into an email and send it to uh The users that are subscribed to this thread, uh, so maybe we'll do that.
We'll just say that, you know, text here is equal to uh You know, we'll do something very simple here where we take each post and separate uh it from the next post by a couple of new lines. And uh we'll pass, you know, post dot text or post in posts here. And now we've got a string. Um, you know, this is a post This text is a string. Yeah, the type checker sort of knows what's going on. Yeah. And now for all of the users uh
for user and I guess thread. users. all. It's going to be all of the users that are subscribed to the thread. Uh we will call uh send email with uh the user's email and uh the text And now the type checker is happy and you know the code looks okay. Yeah, maybe we'll do a better job up here with send email. We'll annotate this function as well and we'll say that you know email has to be a string and text has to be a string. And then if we pass something like user. id into here, the type checker would tell us like this is not okay because int is not compatible with string. It's not assignable to string.
But email is so so we're good. So that about covers things for our models module here and let's head over to the views Yeah, we're just going to write some view code that takes advantage of the type hints we've added to the models. The first thing I want to do is just create this user request type. If we look at self dot requests in here. Because we've installed type stubs for DRF, TypeChecker knows that this is a request instance. And if we look at the user,
This is an abstract base user or an anonymous user, but because you know the only users that can hit this view have to be authenticated, we kind of know that it's uh, you know, once we're in here, like this user is one of our users. So Let's just uh improve things slightly by uh creating a user request class. That inherits from a quest and is identical to it in every way except that you know the user attribute doesn't point to an anonymous user or an abstract base user, points to one of our users. So yeah, that's our user request. And now we can annotate this view class here so that
You know, uh self. request always refers to a user user request and not um a regular DRF request. And now this guy is a user and that means he 'll have attributes like email created at uh and so on this is kind of nice um so yeah let's start with this first view here user post uh list create we're gonna like uh list all of a user's posts or create a post uh for this user. Um And yeah, we're going to assume we have like a real serial class, a serializer class lying around here somewhere, but we're not going to do that in this presentation.
So We just want to list a user's posts. I guess we can you know return here post dot uh objects dot uh filter um maybe the user that created the post is equal to self dot request dot user. That looks good Yeah. And uh if we want like some extra safety, this is not necessary, but if we want some extra safety, we can tell the type checker that it Should always expect git query set here to return a query set of post instances.
And then if we do something else like, I don't know, return user. objects. all, type checker will. Tell us that's not okay. Um, yeah, manager of user is not, you know, assignable to three set of posts, whereas a manager of posts is assignable to a query set of posts. So we're good here. And yeah, when we you know perform create here. Yeah. We oh it looks like this here needs to be a list create API view and not just a list API view. And yeah, I'm gonna just jump into the source code.
This is another nice thing about having uh high right uh know where our dependencies are, it can help us like you know, look up code definitions and navigate the code more easily. So I'm just going to jump over to uh this list create API view class here. And if we You know, look at this class. Um We can see that it's in our virtual end up here. So yeah, PyRite is resolving the dependencies in the virtual end. Yeah, we have this list create API view. It has this create model mix, and this has perform create. And it looks like perform create just calls serializer. save, so that's kind of all we need to do in perform
create. But we're going to kind of set the user to uh being self. request dot user when we call serializer. save. And that's just going to ensure that this post is created by uh the user that just hit this endpoint. Um yeah and it looks like this endpoint is sort of finished now and we can Uh move on to this one here, post retrieve update. Um so Yeah, in uh perform update here we're just going to uh call uh you know the superclass to perform update on
and pass the serializer in uh but we want to make sure that you know a member can only update their own posts a moderate a moderator can update their own posts or uh posts in any thread they moderate, um and an admin can update any posts at all. So Let's you know get the post from this view. We'll have this post which has a type of post and uh this We'll just be equal to self dot get object. So we've got a post now. And if And maybe maybe for you know ergonomics in the view, we'll just uh sign self. request. user to a user variable.
So we've got user equals self. request dotus, and this thing has a type of Oh no, it has a type of abstract base user and anonymous user, and the reason it has that type is because we haven't specified that the request in this view is a user request then we'll go ahead and just do this for our other view classes right now as well All right, so now if we look at this user that has a type of user, that's good. And you know, if user dot roll is equal to uh normal. Oh, and what did I do here? Yeah, um, this is the type checker
helping us out again. Uh because uh the Role has to be one of member, moderator, or admin, it'll tell us like that this comparison here is not valid. It's not going to raise an exception at runtime, but it's certainly a mistake. on the ty on the part of the programmer because you know this is always going to evaluate to false. You may as well just write, you know, if false here. Um so uh if the user has a role of member, uh you know, if uh I guess the post dot user is not equal to this user, then we'll raise an HTTP 404 before we ever call perform update on the serializer and uh you know
Um this user just won't get to do this. If we look at the type of post dot user, it's user if we, you know. Uh tried this kind of comparison. The type checker would tell us like there's a problem here. Like this also is always going to evaluate the false. These guys don't have even the same like uh class. So uh it's got our back there as well. Um that takes care of The case when user. rolle is uh is just member and if user. Um And I guess a moderator can
uh update their own. posts and they can also update the posts in any uh thread that they moderate. So if it's not their post and it's not a thread that they moderate. So I guess we would post. thread. moderator is not equal to uh user, then we've got a problem and we'll do the same thing. We'll raise an HTTP 404 Um and yeah, if the user role is an admin, um Whoops. If the user role is an admin here, then we're good. We don't have to do any checks. They can just update any post they want to.
Yeah, and then we'll add views here for uh it looks like we haven't imported this. Uh so that's kind of nice that TypeChecker will help us auto-import things um and even sort of order them alphabetically to the best of its ability. Not as well as iSorp, but it does a pretty good job. So let's say we want to have this view where a user can subscribe to a thread. We just want to basically sort of add a through table record and user thread to establish you know this many-to-many relationship between user and thread. So we'll just uh Yeah, maybe we'll get this thread here
that has a type of thread and this could be equal to self. getobject And then we'll add the user to the thread. If we tried to add like 10 here or something, the type checker knows what we can add to a thread. It's kind of cool. You know, 10 isn't compatible with with user. Um it knows that we've got to add a user in here. Um And to unsubscribe from a thread, we just need to remove a user from a thread. So looks like this code is going to be almost identical. Yeah, and that should take care of that.
Yeah, so that that about covers it. Now we've you know kind of written some very basic views here and seen how the type checker can help us. you know, catch mistakes, uh autocomplete code for us, uh look up definitions, you know, in the libraries we depend on, navigate through the code, and so on. So yeah, let's head back to the slides here. That brings this presentation sort of to a close here, and I want to just recap some things. uh starting with and in in my opinion at least typeens are are great. And the bigger the project, the better they they they shine as projects get bigger and have more developers working on them. because they you know help the code document itself and
help you catch you know bugs um at sort of like compile time really type check time uh instead of like having to catch those bugs in your tests or maybe like catching them in production. Yeah. It would be, you know, really great if the canonical type subs for uh Django were uh in this Django subs library because uh the community there is kind of bigger than in the Django types library, although the maintainer of Django types is just fantastic. Yeah, I don't know if that'll happen, but that that would be nice. And whether or not that happens
, it would be great if type hints could eventually be added to Django proper. Um, they don't, you know, have to be in line with the code to a developer uh that's just using Django as a dependency and not like you know developing Django themselves. It makes no difference whether the type hints are in line with the code or whether they're in like a stubs file that, you know, kind of lives in the same repo. Yeah, it would just be sort of one less dependency. You wouldn't have to like choose between a library like Django Stubbs or Django Types or anything else. If you, you know, depended on Django, your type checker would automatically resolve the type hints for the library.
And I understand like why the core devs decided not to do this in the earlier days of Django Type hints, but I think some of the uh sort of obstacles have become smaller and uh the situation has improved, you know, there's sort of less uncertainty in the uh Yeah, Python type annotations ecosystem, the syntax is kind of mature and not nearly as clunky as it used to be. And uh because of that, you know, it may even prove to be the case. It certainly proves to be the case uh in code that I write that like adding type hints uh you know to the code even though it imposes this upfront cost makes development easier.
I, you know kind of add type ins to any code that I'm writing these days, uh, unless it's like a really small one-off script that I'm gonna run in iPython to to check something out. Uh because It just make things, you know, it makes things faster. Um and yeah, the burden wouldn't be huge because, you know. uh the community with libraries like Django Stubs and Django Types uh has done a lot of you know the the heavy lifting already. Um yeah. So That will really bring this to a close and uh I just want to thank everyone for uh tuning in and watching the presentation. Um Yeah, a little blurb about me and where I work.
I work at Elementary ML. We do um machine vision and machine learning on uh IoT connected devices. Um And we use a lot of Python. And if you uh you know check out our site and you think it looks cool and you're a fan of Python, you know, it would be cool if you got in touch. Um thanks again for watching and you know enjoy the rest of Django Con. Bye bye.
Type hints improve correctness, documentation, editor support, navigation, and refactoring confidence. Their benefits increase as more of the project and its dependencies are typed, although adding them has an upfront cost.
Discussed at 3:30The speaker recommends Pyright because it is both a type checker and a language server, giving editors IDE-like navigation and assistance. He also praises its implementation quality and the expertise of its author.
Discussed at 8:13Use django-types with Pyright. Django-stubs includes a MyPy-specific plugin and is therefore not compatible with Pyright, while django-types is a fork with that MyPy-specific functionality removed so it works with multiple type checkers.
Discussed at 9:46Install Django types and Django REST framework types as project dependencies, install Pyright, enable its language-server integration in the editor, and configure a pyrightconfig.json with the virtual environment path and environment name so dependencies can be resolved.
Discussed at 12:04Manually annotate Django-generated foreign-key ID fields, reverse foreign-key managers, and many-to-many fields because the type checker cannot reliably infer them. Nullable foreign keys should be typed as the value or None, and constrained choices can use Literal types for extra precision.
Discussed at 19:50You can define a request subclass whose user attribute is your project’s User model, then annotate views with that request type. Pyright can consequently catch invalid role comparisons, wrong model types, and incorrect arguments, while also providing autocomplete and navigation through dependencies.
Discussed at 34:49Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 14, 2026