├── .gitignore ├── LICENSE ├── README.md ├── changelog.md ├── django_hint.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt └── top_level.txt ├── django_hint ├── __init__.py └── typehint.py ├── publish.sh └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE generated 2 | .idea 3 | .vscode 4 | .DS_Store 5 | 6 | # Environment 7 | venv/ 8 | build/ 9 | dist/ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 The Python Packaging Authority 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-hint 2 | 3 | `Django_hint` is a module to help you type hint your django project to work with different IDEs. It has been tested in PyCharm and with pylint in VSCode. 4 | 5 | ``` 6 | Notice: Python3.6 or later is required for this module 7 | ``` 8 |
9 | 10 | ## Installation 11 | You can use the `pip` to install django_hint 12 | 13 | ``` 14 | pip3 install django_hint 15 | ``` 16 | 17 |
18 | 19 | ## Usage 20 | The following use cases can be type hinted using `django_hint` to help your IDE recognize the type of variable. 21 | 1. Database QuerySet 22 | 2. Model Class 23 | 3. WSGIRequest 24 | 4. Django-Rest-Framework Token Authentication 25 | 26 | As a bonus, all of the native python type hints such as `List`, `Union`, `Optional` etc. from `typing` module can be imported from `django_hint` 27 | 28 |
29 | 30 | ## Database QuerySet 31 | It is used to hint that the variable is an `QuerySet` object containing multiple objects whose nature we will determine. This type is used for filter functions that return a iterable query, such as `filter`, `all`, `order_by`, etc.
32 | You need to hint it to `QueryType` and pass the object type inside the `[]`. Example: 33 | ```python 34 | from django_hint import QueryType 35 | 36 | sample_query: QueryType[SampleModel] = SampleModel.objects.filter(name='sample') 37 | ``` 38 | 39 | The `sample_query` variable will be treated as a `QuerySet`. While looping through the objects, each object will be treated as a `SampleModel` 40 | 41 | *Please note that if you extend your model to `StandardModelType` as explained below, `QueryType` will not usually be needed* 42 | 43 |
44 | 45 | ## Model Class 46 | Django adds a few attributes to a `Model` instance which are not available in the `models.Model` and will not be available in your IDE. 47 | The most notable attribute is the `Manager` which is accessible via an attribute called `objects`.
48 | 49 | To include these attributes in your IDE, You have to extend your model to the `StandardModelType` class of `django_hint` as well as `models.Model` and use it just like any other model class.
50 | 51 | `StandardModelType` provides the `objects` property and provides the return type of the `objects`'s functions such as `filter`, `get`, etc. In order to provide the correct return types, you have to pass the model's name as the generic type to `StandardModelType` 52 | 53 | Note that `StandardModeltype` will NOT have any effect on your database and will NOT make new migrations on `makemigrations` command. 54 | 55 | ```python 56 | from django.db import models 57 | from django_hint import StandardModelType 58 | 59 | class SampleModel(models.Model, StandardModelType['SampleModel']): 60 | """Just like any other model""" 61 | name: str = models.CharField(max_length=100) 62 | 63 | 64 | 65 | all_samples = SampleModel.objects.all() 66 | for sample in all_samples: 67 | print(sample.name) 68 | ``` 69 |
70 | 71 | ## WSGIRequest 72 | It is used to hint the nature of the `request` argument of the view (both function and class based). 73 | The `request` will be treated as a `HttpRequest` having the `user` variable attached to it. Example: 74 | ```python 75 | from django_hint import RequestType 76 | 77 | def sample_view(request: RequestType): 78 | if request.user.is_authenticated: 79 | print(request.POST.get('data')) 80 | ``` 81 |
82 | 83 | ## Django-Rest-Framework Token Authentication 84 | If you are using the token authentication of the `Django-Rest-Framework`, the request object will have a `user` variable and an `auth` variable of `rest_framework.authtoken.models.Token` instance. `DRFTokenRequestType` will hint the IDE of those two variables. 85 | 86 | ```python 87 | from django_hint import DRFTokenRequestType 88 | 89 | def sample_view(request: DRFTokenRequestType): 90 | print(request.auth.key) 91 | ``` 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.3.2 (2023-07-07) 4 | - Added `QueryFilter` to the export 5 | 6 | ## 0.3.0 (2023-06-29) 7 | - Added return type to `objects` common functions such as `filter`, `get`, etc. ([#2](https://github.com/Vieolo/django-hint/issues/2)) 8 | 9 | #### Breaking Changes 10 | - `StandardModelType` now requires the name of the model as Generic -------------------------------------------------------------------------------- /django_hint.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: django-hint 3 | Version: 0.3.2 4 | Summary: Type hinting package for django 5 | Home-page: https://github.com/Vieolo/django-hint 6 | Author: Vieolo OÜ 7 | Author-email: info@vieolo.com 8 | Classifier: Programming Language :: Python :: 3.6 9 | Classifier: License :: OSI Approved :: MIT License 10 | Classifier: Operating System :: OS Independent 11 | Description-Content-Type: text/markdown 12 | License-File: LICENSE 13 | 14 | # django-hint 15 | 16 | `Django_hint` is a module to help you type hint your django project to work with different IDEs. It has been tested in PyCharm and with pylint in VSCode. 17 | 18 | ``` 19 | Notice: Python3.6 or later is required for this module 20 | ``` 21 |
22 | 23 | ## Installation 24 | You can use the `pip` to install django_hint 25 | 26 | ``` 27 | pip3 install django_hint 28 | ``` 29 | 30 |
31 | 32 | ## Usage 33 | The following use cases can be type hinted using `django_hint` to help your IDE recognize the type of variable. 34 | 1. Database QuerySet 35 | 2. Model Class 36 | 3. WSGIRequest 37 | 4. Django-Rest-Framework Token Authentication 38 | 39 | As a bonus, all of the native python type hints such as `List`, `Union`, `Optional` etc. from `typing` module can be imported from `django_hint` 40 | 41 |
42 | 43 | ## Database QuerySet 44 | It is used to hint that the variable is an `QuerySet` object containing multiple objects whose nature we will determine. This type is used for filter functions that return a iterable query, such as `filter`, `all`, `order_by`, etc.
45 | You need to hint it to `QueryType` and pass the object type inside the `[]`. Example: 46 | ```python 47 | from django_hint import QueryType 48 | 49 | sample_query: QueryType[SampleModel] = SampleModel.objects.filter(name='sample') 50 | ``` 51 | 52 | The `sample_query` variable will be treated as a `QuerySet`. While looping through the objects, each object will be treated as a `SampleModel` 53 | 54 | *Please note that if you extend your model to `StandardModelType` as explained below, `QueryType` will not usually be needed* 55 | 56 |
57 | 58 | ## Model Class 59 | Django adds a few attributes to a `Model` instance which are not available in the `models.Model` and will not be available in your IDE. 60 | The most notable attribute is the `Manager` which is accessible via an attribute called `objects`.
61 | 62 | To include these attributes in your IDE, You have to extend your model to the `StandardModelType` class of `django_hint` as well as `models.Model` and use it just like any other model class.
63 | 64 | `StandardModelType` provides the `objects` property and provides the return type of the `objects`'s functions such as `filter`, `get`, etc. In order to provide the correct return types, you have to pass the model's name as the generic type to `StandardModelType` 65 | 66 | Note that `StandardModeltype` will NOT have any effect on your database and will NOT make new migrations on `makemigrations` command. 67 | 68 | ```python 69 | from django.db import models 70 | from django_hint import StandardModelType 71 | 72 | class SampleModel(models.Model, StandardModelType['SampleModel']): 73 | """Just like any other model""" 74 | name: str = models.CharField(max_length=100) 75 | 76 | 77 | 78 | all_samples = SampleModel.objects.all() 79 | for sample in all_samples: 80 | print(sample.name) 81 | ``` 82 |
83 | 84 | ## WSGIRequest 85 | It is used to hint the nature of the `request` argument of the view (both function and class based). 86 | The `request` will be treated as a `HttpRequest` having the `user` variable attached to it. Example: 87 | ```python 88 | from django_hint import RequestType 89 | 90 | def sample_view(request: RequestType): 91 | if request.user.is_authenticated: 92 | print(request.POST.get('data')) 93 | ``` 94 |
95 | 96 | ## Django-Rest-Framework Token Authentication 97 | If you are using the token authentication of the `Django-Rest-Framework`, the request object will have a `user` variable and an `auth` variable of `rest_framework.authtoken.models.Token` instance. `DRFTokenRequestType` will hint the IDE of those two variables. 98 | 99 | ```python 100 | from django_hint import DRFTokenRequestType 101 | 102 | def sample_view(request: DRFTokenRequestType): 103 | print(request.auth.key) 104 | ``` 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /django_hint.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | LICENSE 2 | README.md 3 | setup.py 4 | django_hint/__init__.py 5 | django_hint/typehint.py 6 | django_hint.egg-info/PKG-INFO 7 | django_hint.egg-info/SOURCES.txt 8 | django_hint.egg-info/dependency_links.txt 9 | django_hint.egg-info/top_level.txt -------------------------------------------------------------------------------- /django_hint.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /django_hint.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | django_hint 2 | -------------------------------------------------------------------------------- /django_hint/__init__.py: -------------------------------------------------------------------------------- 1 | from .typehint import QueryType, QueryFilter, RequestType, DRFTokenRequestType, StandardModelType, List, Optional, Union, Deque, Dict, DefaultDict, FrozenSet, ChainMap, Counter, Set 2 | from .typehint import Generic, Callable, Tuple, TypeVar, Type, ClassVar 3 | __all__ = [ 4 | "QueryType", 5 | "RequestType", 6 | "DRFTokenRequestType", 7 | "QueryFilter", 8 | "StandardModelType", 9 | "List", 10 | "Optional", 11 | "Union", 12 | "Dict", 13 | "DefaultDict", 14 | "Set", 15 | "FrozenSet", 16 | "Counter", 17 | "Deque", 18 | "ChainMap", 19 | 'Generic', 20 | 'Callable', 21 | 'Tuple', 22 | 'TypeVar', 23 | 'Type', 24 | 'ClassVar' 25 | ] 26 | 27 | 28 | -------------------------------------------------------------------------------- /django_hint/typehint.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime as datetime 2 | 3 | from django.db.models import QuerySet as QuerySet 4 | from django.http.request import HttpRequest as HttpRequest 5 | from django.contrib.auth.models import User as User 6 | from django.core.exceptions import ObjectDoesNotExist 7 | from django.core.exceptions import MultipleObjectsReturned as Mor 8 | 9 | 10 | from typing import Iterator 11 | from typing import TypeVar 12 | from typing import Generic 13 | from typing import List 14 | from typing import Optional 15 | from typing import Union 16 | from typing import Dict 17 | from typing import DefaultDict 18 | from typing import Set 19 | from typing import FrozenSet 20 | from typing import Counter 21 | from typing import Deque 22 | from typing import ChainMap 23 | from typing import Callable 24 | from typing import Tuple 25 | from typing import Type 26 | from typing import ClassVar 27 | 28 | _Z = TypeVar("_Z") 29 | 30 | 31 | class Token: 32 | """ 33 | The default authorization token model. 34 | """ 35 | key: str 36 | user: User 37 | created: datetime 38 | 39 | def save(self, *args, **kwargs): 40 | pass 41 | 42 | def generate_key(self) -> str: 43 | pass 44 | 45 | def __str__(self): 46 | pass 47 | 48 | @classmethod 49 | def from_db(cls, db, field_names, values): 50 | pass 51 | 52 | def __repr__(self): 53 | pass 54 | 55 | def __eq__(self, other): 56 | pass 57 | 58 | def __hash__(self): 59 | pass 60 | 61 | def __reduce__(self): 62 | pass 63 | 64 | def __getstate__(self): 65 | """Hook to allow choosing the attributes to pickle.""" 66 | pass 67 | 68 | def __setstate__(self, state): 69 | pass 70 | 71 | def get_deferred_fields(self): 72 | """ 73 | Return a set containing names of deferred fields for this instance. 74 | """ 75 | pass 76 | 77 | def refresh_from_db(self, using=None, fields=None): 78 | """ 79 | Reload field values from the database. 80 | 81 | By default, the reloading happens from the database this instance was 82 | loaded from, or by the read router if this instance wasn't loaded from 83 | any database. The using parameter will override the default. 84 | 85 | Fields can be used to specify which fields to reload. The fields 86 | should be an iterable of field attnames. If fields is None, then 87 | all non-deferred fields are reloaded. 88 | 89 | When accessing deferred fields of an instance, the deferred loading 90 | of the field will call this method. 91 | """ 92 | pass 93 | 94 | def serializable_value(self, field_name): 95 | """ 96 | Return the value of the field name for this instance. If the field is 97 | a foreign key, return the id value instead of the object. If there's 98 | no Field object with this name on the model, return the model 99 | attribute's value. 100 | 101 | Used to serialize a field's value (in the serializer, or form output, 102 | for example). Normally, you would just access the attribute directly 103 | and not use this method. 104 | """ 105 | pass 106 | 107 | def save_base(self, raw=False, force_insert=False, 108 | force_update=False, using=None, update_fields=None): 109 | """ 110 | Handle the parts of saving which should be done only once per save, 111 | yet need to be done in raw saves, too. This includes some sanity 112 | checks and signal sending. 113 | 114 | The 'raw' argument is telling save_base not to save any parent 115 | models and not to do any changes to the values before save. This 116 | is used by fixture loading. 117 | """ 118 | pass 119 | 120 | def delete(self, using=None, keep_parents=False): 121 | pass 122 | 123 | def prepare_database_save(self, field): 124 | pass 125 | 126 | def clean(self): 127 | """ 128 | Hook for doing any extra model-wide validation after clean() has been 129 | called on every field by self.clean_fields. Any ValidationError raised 130 | by this method will not be associated with a particular field; it will 131 | have a special-case association with the field defined by NON_FIELD_ERRORS. 132 | """ 133 | pass 134 | 135 | def validate_unique(self, exclude=None): 136 | """ 137 | Check unique constraints on the model and raise ValidationError if any 138 | failed. 139 | """ 140 | pass 141 | 142 | def date_error_message(self, lookup_type, field_name, unique_for): 143 | pass 144 | 145 | def unique_error_message(self, model_class, unique_check): 146 | pass 147 | 148 | def full_clean(self, exclude=None, validate_unique=True): 149 | """ 150 | Call clean_fields(), clean(), and validate_unique() on the model. 151 | Raise a ValidationError for any errors that occur. 152 | """ 153 | pass 154 | 155 | def clean_fields(self, exclude=None): 156 | """ 157 | Clean all fields and raise a ValidationError containing a dict 158 | of all validation errors if any occur. 159 | """ 160 | pass 161 | 162 | @classmethod 163 | def check(cls, **kwargs): 164 | pass 165 | 166 | 167 | class RequestType(HttpRequest): 168 | user: User 169 | 170 | 171 | class DRFTokenRequestType(HttpRequest): 172 | user: User 173 | auth: Token 174 | 175 | 176 | 177 | ############################ 178 | # QuerySet and Model # 179 | ############################ 180 | 181 | 182 | class QueryType(Generic[_Z], QuerySet): 183 | def __iter__(self) -> Iterator[_Z]: pass 184 | 185 | 186 | class QuerySetBase(Generic[_Z], QuerySet): 187 | def get(self, *args, **kwargs) -> _Z: 188 | """ 189 | Perform the query and return a single object matching the given 190 | keyword arguments. 191 | """ 192 | pass 193 | 194 | def create(self, **kwargs) -> _Z: 195 | """ 196 | Create a new object with the given kwargs, saving it to the database 197 | and returning the created object. 198 | """ 199 | pass 200 | 201 | def first(self) -> _Z: 202 | """Return the first object of a query or None if no match is found.""" 203 | pass 204 | 205 | def last(self) -> _Z: 206 | """Return the last object of a query or None if no match is found.""" 207 | pass 208 | 209 | def get_or_create(self, defaults=None, **kwargs) -> Tuple[_Z, bool]: 210 | """ 211 | Look up an object with the given kwargs, creating one if necessary. 212 | Return a tuple of (object, created), where created is a boolean 213 | specifying whether an object was created. 214 | """ 215 | pass 216 | 217 | def update_or_create(self, defaults=None, create_defaults=None, **kwargs) -> Tuple[_Z, bool]: 218 | """ 219 | Look up an object with the given kwargs, updating one with defaults 220 | if it exists, otherwise create a new one. Optionally, an object can 221 | be created with different values than defaults by using 222 | create_defaults. 223 | Return a tuple (object, created), where created is a boolean 224 | specifying whether an object was created. 225 | """ 226 | pass 227 | 228 | class QueryFilter(Generic[_Z], QuerySetBase[_Z]): 229 | def __iter__(self) -> Iterator[_Z]: pass 230 | 231 | 232 | class QuerySetType(Generic[_Z], QuerySetBase[_Z]): 233 | def all(self) -> QueryFilter[_Z]: 234 | """ 235 | Return a new QuerySet that is a copy of the current one. This allows a 236 | QuerySet to proxy for a model manager in some cases. 237 | """ 238 | pass 239 | 240 | def filter(self, *args, **kwargs) -> QueryFilter[_Z]: 241 | """ 242 | Return a new QuerySet instance with the args ANDed to the existing 243 | set. 244 | """ 245 | pass 246 | 247 | def exclude(self, *args, **kwargs) -> QueryFilter[_Z]: 248 | """ 249 | Return a new QuerySet instance with NOT (args) ANDed to the existing 250 | set. 251 | """ 252 | pass 253 | 254 | def select_for_update(self, nowait=False, skip_locked=False, of=(), no_key=False) -> QueryFilter[_Z]: 255 | """ 256 | Return a new QuerySet instance that will select objects with a 257 | FOR UPDATE lock. 258 | """ 259 | pass 260 | 261 | def select_related(self, *fields) -> QueryFilter[_Z]: 262 | """ 263 | Return a new QuerySet instance that will select related objects. 264 | 265 | If fields are specified, they must be ForeignKey fields and only those 266 | related objects are included in the selection. 267 | 268 | If select_related(None) is called, clear the list. 269 | """ 270 | pass 271 | 272 | def prefetch_related(self, *lookups) -> QueryFilter[_Z]: 273 | """ 274 | Return a new QuerySet instance that will prefetch the specified 275 | Many-To-One and Many-To-Many related objects when the QuerySet is 276 | evaluated. 277 | 278 | When prefetch_related() is called more than once, append to the list of 279 | prefetch lookups. If prefetch_related(None) is called, clear the list. 280 | """ 281 | pass 282 | 283 | def annotate(self, *args, **kwargs) -> QueryFilter[_Z]: 284 | """ 285 | Return a query set in which the returned objects have been annotated 286 | with extra data or aggregations. 287 | """ 288 | pass 289 | 290 | def alias(self, *args, **kwargs) -> QueryFilter[_Z]: 291 | """ 292 | Return a query set with added aliases for extra data or aggregations. 293 | """ 294 | pass 295 | 296 | def order_by(self, *field_names) -> QueryFilter[_Z]: 297 | """Return a new QuerySet instance with the ordering changed.""" 298 | pass 299 | 300 | def distinct(self, *field_names) -> QueryFilter[_Z]: 301 | """ 302 | Return a new QuerySet instance that will select only distinct results. 303 | """ 304 | pass 305 | def reverse(self) -> QueryFilter[_Z]: 306 | """Reverse the ordering of the QuerySet.""" 307 | pass 308 | 309 | class StandardModelType(Generic[_Z]): 310 | objects: QuerySetType[_Z] 311 | DoesNotExist: Union[ObjectDoesNotExist, Callable] 312 | MultipleObjectsReturned: Union[Mor, Callable] 313 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | echo "Starting to publish (Note that email is not acceptable for username)" 4 | 5 | python3 -m pip install --upgrade setuptools wheel 6 | python3 setup.py sdist bdist_wheel 7 | python3 -m pip install --upgrade twine 8 | python3 -m twine upload dist/* 9 | 10 | echo "$?" -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="django_hint", 8 | version="0.3.2", 9 | author="Vieolo OÜ", 10 | author_email="info@vieolo.com", 11 | description="Type hinting package for django", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/Vieolo/django-hint", 15 | packages=setuptools.find_packages(), 16 | classifiers=[ 17 | "Programming Language :: Python :: 3.6", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: OS Independent", 20 | ], 21 | ) 22 | --------------------------------------------------------------------------------