# django-types [![PyPI](https://img.shields.io/pypi/v/django-types.svg)](https://pypi.org/project/django-types/)
Type stubs for [Django](https://www.djangoproject.com).
> Note: this project was forked from
> <https://github.com/typeddjango/django-stubs> with the goal of removing the
> [`mypy`](https://github.com/python/mypy) plugin dependency so that `mypy`
> can't [crash due to Django
> config](https://github.com/typeddjango/django-stubs/issues/318), and that
> non-`mypy` type checkers like
> [`pyright`](https://github.com/microsoft/pyright) will work better with
> Django.
## install
```bash
pip install django-types
```
You can get a `TypeError: 'type' object is not subscriptable`
when you will try to use `QuerySet[MyModel]`, `Manager[MyModel]` or some other Django-based Generic types.
This happens because these Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method in runtime.
1. You can go with [`django_stubs_ext`](https://github.com/typeddjango/django-stubs/tree/master/django_stubs_ext) helper, that patches all the types we use as Generic in django.
Install it:
```bash
pip install django-stubs-ext # as a production dependency
```
And then place in your top-level settings:
```python
import django_stubs_ext
django_stubs_ext.monkeypatch()
```
You can add extra types to patch with `django_stubs_ext.monkeypatch(extra_classes=[YourDesiredType])`
2. You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for type-checking and as a regular `str` in runtime.
## usage
### ForeignKey ids and related names as properties in ORM models
When defining a Django ORM model with a foreign key, like so:
```python
class User(models.Model):
team = models.ForeignKey(
"Team",
null=True,
on_delete=models.SET_NULL,
)
role = models.ForeignKey(
"Role",
null=True,
on_delete=models.SET_NULL,
related_name="users",
)
```
two properties are created, `team` as expected, and `team_id`. Also, a related
manager called `user_set` is created on `Team` for the reverse access.
In order to properly add typing to the foreign key itself and also for the created ids you can do
something like this:
```python
from typing import TYPE_CHECKING
from someapp.models import Team
if TYPE_CHECKING:
# In this example Role cannot be imported due to circular import issues,
# but doing so inside TYPE_CHECKING will make sure that the typing below
# knows what "Role" means
from anotherapp.models import Role
class User(models.Model):
team_id: Optional[int]
team = models.ForeignKey(
Team,
null=True,
on_delete=models.SET_NULL,
)
role_id: int
role = models.ForeignKey["Role"](
"Role",
null=False,
on_delete=models.SET_NULL,
related_name="users",
)
reveal_type(User().team)
# note: Revealed type is 'Optional[Team]'
reveal_type(User().role)
# note: Revealed type is 'Role'
```
This will make sure that `team_id` and `role_id` can be accessed. Also, `team` and `role`
will be typed to their right objects.
To be able to access the related manager `Team` and `Role` you could do:
```python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
# This doesn't really exists on django so it always need to be imported this way
from django.db.models.manager import RelatedManager
from user.models import User
class Team(models.Model):
if TYPE_CHECKING:
user_set = RelatedManager["User"]()
class Role(models.Model):
if TYPE_CHECKING:
users = RelatedManager["User"]()
reveal_type(Team().user_set)
# note: Revealed type is 'RelatedManager[User]'
reveal_type(Role().users)
# note: Revealed type is 'RelatedManager[User]'
```
An alternative is using annotations:
```python
from __future__ import annotations # or just be in python 3.11
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from django.db.models import Manager
from user.models import User
class Team(models.Model):
user_set: Manager[User]
class Role(models.Model):
users: Manager[User]
reveal_type(Team().user_set)
# note: Revealed type is 'Manager[User]'
reveal_type(Role().users)
# note: Revealed type is 'Manager[User]'
```
### `id Field`
By default Django will create an `AutoField` for you if one doesn't exist.
For type checkers to know about the `id` field you'll need to declare the
field explicitly.
```python
# before
class Post(models.Model):
...
# after
class Post(models.Model):
id = models.AutoField(primary_key=True)
# OR
id: int
```
### `HttpRequest`'s `user` property
The `HttpRequest`'s `user` property has a type of `Union[AbstractBaseUser, AnonymousUser]`,
but for most of your views you'll probably want either an authed user or an
`AnonymousUser`.
So we can define a subclass for each case:
```python
class AuthedHttpRequest(HttpRequest):
user: User # type: ignore [assignment]
```
And then you can use it in your views:
```python
@auth.login_required
def activity(request: AuthedHttpRequest, team_id: str) -> HttpResponse:
...
```
You can also get more strict with your `login_required` decorator so that the
first argument of the function it is decorating is `AuthedHttpRequest`:
```python
from typing import Any, Union, TypeVar, cast
from django.http import HttpRequest, HttpResponse
from typing_extensions import Protocol
from functools import wraps
class RequestHandler1(Protocol):
def __call__(self, request: AuthedHttpRequest) -> HttpResponse:
...
class RequestHandler2(Protocol):
def __call__(self, request: AuthedHttpRequest, __arg1: Any) -> HttpResponse:
...
RequestHandler = Union[RequestHandler1, RequestHandler2]
# Verbose bound arg due to limitations of Python typing.
# see: https://github.com/python/mypy/issues/5876
_F = TypeVar("_F", bound=RequestHandler)
def login_required(view_func: _F) -> _F:
@wraps(view_func)
def wrapped_view(
request: AuthedHttpRequest, *args: object, **kwargs: object
) -> HttpResponse:
if request.user.is_authenticated:
return view_func(request, *args, **kwargs) # type: ignore [call-arg]
raise AuthenticationRequired
return cast(_F, wrapped_view)
```
Then the following will type error:
```python
@auth.login_required
def activity(request: HttpRequest, team_id: str) -> HttpResponse:
...
```
## related
- <https://github.com/sbdchd/djangorestframework-types>
- <https://github.com/sbdchd/celery-types>
- <https://github.com/sbdchd/mongo-types>
- <https://github.com/sbdchd/msgpack-types>
## Releasing a new version
1. Navigate to https://github.com/sbdchd/django-types/actions/workflows/bump_version.yml and click "Run workflow".
2. Select the version level you wish to increase and click "Run workflow" to bump the version and publish to PyPI.
Raw data
{
"_id": null,
"home_page": "https://github.com/sbdchd/django-types",
"name": "django-types",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.8",
"maintainer_email": null,
"keywords": "django, types, mypy, stubs",
"author": "Steve Dignam",
"author_email": "steve@dignam.xyz",
"download_url": "https://files.pythonhosted.org/packages/12/a1/a17e5d2222f963fd1714325c2869adf27fab1299f1430d8913bfecc6e5c0/django_types-0.20.0.tar.gz",
"platform": null,
"description": "# django-types [![PyPI](https://img.shields.io/pypi/v/django-types.svg)](https://pypi.org/project/django-types/)\n\nType stubs for [Django](https://www.djangoproject.com).\n\n> Note: this project was forked from\n> <https://github.com/typeddjango/django-stubs> with the goal of removing the\n> [`mypy`](https://github.com/python/mypy) plugin dependency so that `mypy`\n> can't [crash due to Django\n> config](https://github.com/typeddjango/django-stubs/issues/318), and that\n> non-`mypy` type checkers like\n> [`pyright`](https://github.com/microsoft/pyright) will work better with\n> Django.\n\n## install\n\n```bash\npip install django-types\n```\n\nYou can get a `TypeError: 'type' object is not subscriptable`\nwhen you will try to use `QuerySet[MyModel]`, `Manager[MyModel]` or some other Django-based Generic types.\n\nThis happens because these Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method in runtime.\n\n1. You can go with [`django_stubs_ext`](https://github.com/typeddjango/django-stubs/tree/master/django_stubs_ext) helper, that patches all the types we use as Generic in django.\n\n Install it:\n\n ```bash\n pip install django-stubs-ext # as a production dependency\n ```\n\n And then place in your top-level settings:\n\n ```python\n import django_stubs_ext\n\n django_stubs_ext.monkeypatch()\n ```\n\n You can add extra types to patch with `django_stubs_ext.monkeypatch(extra_classes=[YourDesiredType])`\n\n2. You can use strings instead: `'QuerySet[MyModel]'` and `'Manager[MyModel]'`, this way it will work as a type for type-checking and as a regular `str` in runtime.\n\n\n## usage\n\n### ForeignKey ids and related names as properties in ORM models\n\nWhen defining a Django ORM model with a foreign key, like so:\n\n```python\nclass User(models.Model):\n team = models.ForeignKey(\n \"Team\",\n null=True,\n on_delete=models.SET_NULL,\n )\n role = models.ForeignKey(\n \"Role\",\n null=True,\n on_delete=models.SET_NULL,\n related_name=\"users\",\n )\n```\n\ntwo properties are created, `team` as expected, and `team_id`. Also, a related\nmanager called `user_set` is created on `Team` for the reverse access.\n\nIn order to properly add typing to the foreign key itself and also for the created ids you can do\nsomething like this:\n\n```python\nfrom typing import TYPE_CHECKING\n\nfrom someapp.models import Team\nif TYPE_CHECKING:\n # In this example Role cannot be imported due to circular import issues,\n # but doing so inside TYPE_CHECKING will make sure that the typing below\n # knows what \"Role\" means\n from anotherapp.models import Role\n\n\nclass User(models.Model):\n team_id: Optional[int]\n team = models.ForeignKey(\n Team,\n null=True,\n on_delete=models.SET_NULL,\n )\n role_id: int\n role = models.ForeignKey[\"Role\"](\n \"Role\",\n null=False,\n on_delete=models.SET_NULL,\n related_name=\"users\",\n )\n\n\nreveal_type(User().team)\n# note: Revealed type is 'Optional[Team]'\nreveal_type(User().role)\n# note: Revealed type is 'Role'\n```\n\nThis will make sure that `team_id` and `role_id` can be accessed. Also, `team` and `role`\nwill be typed to their right objects.\n\nTo be able to access the related manager `Team` and `Role` you could do:\n\n```python\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n # This doesn't really exists on django so it always need to be imported this way\n from django.db.models.manager import RelatedManager\n from user.models import User\n\n\nclass Team(models.Model):\n if TYPE_CHECKING:\n user_set = RelatedManager[\"User\"]()\n\n\nclass Role(models.Model):\n if TYPE_CHECKING:\n users = RelatedManager[\"User\"]()\n\nreveal_type(Team().user_set)\n# note: Revealed type is 'RelatedManager[User]'\nreveal_type(Role().users)\n# note: Revealed type is 'RelatedManager[User]'\n```\n\nAn alternative is using annotations:\n\n\n\n```python\nfrom __future__ import annotations # or just be in python 3.11\n\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n from django.db.models import Manager\n from user.models import User\n\n\nclass Team(models.Model):\n user_set: Manager[User]\n\n\nclass Role(models.Model):\n users: Manager[User]\n\nreveal_type(Team().user_set)\n# note: Revealed type is 'Manager[User]'\nreveal_type(Role().users)\n# note: Revealed type is 'Manager[User]'\n```\n\n\n### `id Field`\n\nBy default Django will create an `AutoField` for you if one doesn't exist.\n\nFor type checkers to know about the `id` field you'll need to declare the\nfield explicitly.\n\n```python\n# before\nclass Post(models.Model):\n ...\n\n# after\nclass Post(models.Model):\n id = models.AutoField(primary_key=True)\n # OR\n id: int\n```\n\n### `HttpRequest`'s `user` property\n\nThe `HttpRequest`'s `user` property has a type of `Union[AbstractBaseUser, AnonymousUser]`,\nbut for most of your views you'll probably want either an authed user or an\n`AnonymousUser`.\n\nSo we can define a subclass for each case:\n\n```python\nclass AuthedHttpRequest(HttpRequest):\n user: User # type: ignore [assignment]\n```\n\nAnd then you can use it in your views:\n\n```python\n@auth.login_required\ndef activity(request: AuthedHttpRequest, team_id: str) -> HttpResponse:\n ...\n```\n\nYou can also get more strict with your `login_required` decorator so that the\nfirst argument of the function it is decorating is `AuthedHttpRequest`:\n\n```python\nfrom typing import Any, Union, TypeVar, cast\nfrom django.http import HttpRequest, HttpResponse\nfrom typing_extensions import Protocol\nfrom functools import wraps\n\nclass RequestHandler1(Protocol):\n def __call__(self, request: AuthedHttpRequest) -> HttpResponse:\n ...\n\n\nclass RequestHandler2(Protocol):\n def __call__(self, request: AuthedHttpRequest, __arg1: Any) -> HttpResponse:\n ...\n\n\nRequestHandler = Union[RequestHandler1, RequestHandler2]\n\n\n# Verbose bound arg due to limitations of Python typing.\n# see: https://github.com/python/mypy/issues/5876\n_F = TypeVar(\"_F\", bound=RequestHandler)\n\n\ndef login_required(view_func: _F) -> _F:\n @wraps(view_func)\n def wrapped_view(\n request: AuthedHttpRequest, *args: object, **kwargs: object\n ) -> HttpResponse:\n if request.user.is_authenticated:\n return view_func(request, *args, **kwargs) # type: ignore [call-arg]\n raise AuthenticationRequired\n\n return cast(_F, wrapped_view)\n```\n\nThen the following will type error:\n\n```python\n@auth.login_required\ndef activity(request: HttpRequest, team_id: str) -> HttpResponse:\n ...\n```\n\n## related\n\n- <https://github.com/sbdchd/djangorestframework-types>\n- <https://github.com/sbdchd/celery-types>\n- <https://github.com/sbdchd/mongo-types>\n- <https://github.com/sbdchd/msgpack-types>\n\n## Releasing a new version\n1. Navigate to https://github.com/sbdchd/django-types/actions/workflows/bump_version.yml and click \"Run workflow\".\n2. Select the version level you wish to increase and click \"Run workflow\" to bump the version and publish to PyPI.\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Type stubs for Django",
"version": "0.20.0",
"project_urls": {
"Homepage": "https://github.com/sbdchd/django-types",
"Repository": "https://github.com/sbdchd/django-types"
},
"split_keywords": [
"django",
" types",
" mypy",
" stubs"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "44eed4cd24637f31cb131e629df64133f7960fcdc48a183918a352f02db0cd6f",
"md5": "a13ba4d6c5f35cc84d74138ed6574d51",
"sha256": "a0b5c2c9a1e591684bb21a93b64e50ca6cb2d3eab48f49faff1eac706bd3a9c7"
},
"downloads": -1,
"filename": "django_types-0.20.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a13ba4d6c5f35cc84d74138ed6574d51",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.8",
"size": 374226,
"upload_time": "2024-12-12T00:57:37",
"upload_time_iso_8601": "2024-12-12T00:57:37.702993Z",
"url": "https://files.pythonhosted.org/packages/44/ee/d4cd24637f31cb131e629df64133f7960fcdc48a183918a352f02db0cd6f/django_types-0.20.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "12a1a17e5d2222f963fd1714325c2869adf27fab1299f1430d8913bfecc6e5c0",
"md5": "1788a431f40f2115375cf6178b033c1e",
"sha256": "4e55d2c56155e3d69d75def9eb1d95a891303f2ac19fccf6fe8056da4293fae7"
},
"downloads": -1,
"filename": "django_types-0.20.0.tar.gz",
"has_sig": false,
"md5_digest": "1788a431f40f2115375cf6178b033c1e",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.8",
"size": 178278,
"upload_time": "2024-12-12T00:57:40",
"upload_time_iso_8601": "2024-12-12T00:57:40.897460Z",
"url": "https://files.pythonhosted.org/packages/12/a1/a17e5d2222f963fd1714325c2869adf27fab1299f1430d8913bfecc6e5c0/django_types-0.20.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-12 00:57:40",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "sbdchd",
"github_project": "django-types",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "django-types"
}