├── .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 |
--------------------------------------------------------------------------------