mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-15 08:17:08 +08:00
create monkeypatching function for adding get_item dunder (#526)
* run black * create monkeypatching function for adding get_item dunder * whoops i forgot the test * change the name in INSTALLED_APPS to make test pass * turn the whole thing into a proper package * move django_stubs_ext to requirements.txt * also install requirements.txt * attempt to fix pre-commit * numerous small code review fixes * fix dependency issues * small dependency fixes * configure proper license file location * add the rest of the monkeypatching * use strict mypy * update contributing with a note monkeypatching generics * copy release script from parent package
This commit is contained in:
49
django_stubs_ext/README.md
Normal file
49
django_stubs_ext/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Extensions and monkey-patching for django-stubs
|
||||
|
||||
[](https://travis-ci.com/typeddjango/django-stubs)
|
||||
[](http://mypy-lang.org/)
|
||||
[](https://gitter.im/mypy-django/Lobby)
|
||||
|
||||
|
||||
This package contains extensions and monkey-patching functions for the [django-stubs](https://github.com/typeddjango/django-stubs) package. Certain features of django-stubs (i.e. generic django classes that don't define the `__class_getitem__` method) require runtime monkey-patching, which can't be done with type stubs. These extensions were split into a separate package so library consumers don't need `mypy` as a runtime dependency ([#526](https://github.com/typeddjango/django-stubs/pull/526#pullrequestreview-525798031)).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install django-stubs-ext
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
In your Django application, use the following code:
|
||||
|
||||
```py
|
||||
import django_stubs_ext
|
||||
|
||||
django_stubs_ext.monkeypath()
|
||||
```
|
||||
|
||||
This only needs to be called once, so the call to `monkeypatch` should be placed in your top-level urlconf.
|
||||
|
||||
## Version compatibility
|
||||
|
||||
Since django-stubs supports multiple Django versions, this package takes care to only monkey-patch the features needed by your django version, and decides which features to patch at runtime. This is completely safe, as (currently) we only add a `__class_getitem__` method that does nothing:
|
||||
|
||||
```py
|
||||
@classmethod
|
||||
def __class_getitem__(cls, *args, **kwargs):
|
||||
return cls
|
||||
```
|
||||
|
||||
## To get help
|
||||
|
||||
For help with django-stubs, please view the main repository at <https://github.com/typeddjango/django-stubs>
|
||||
|
||||
We have a Gitter chat here: <https://gitter.im/mypy-django/Lobby>
|
||||
If you think you have a more generic typing issue, please refer to <https://github.com/python/mypy> and their Gitter.
|
||||
|
||||
## Contributing
|
||||
|
||||
The django-stubs-ext package is part of the [django-stubs](https://github.com/typeddjango/django-stubs) monorepo. If you would like to contribute, please view the django-stubs [contribution guide](https://github.com/typeddjango/django-stubs/blob/master/CONTRIBUTING.md).
|
||||
|
||||
You can always also reach out in gitter to discuss your contributions!
|
||||
3
django_stubs_ext/django_stubs_ext/__init__.py
Normal file
3
django_stubs_ext/django_stubs_ext/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .monkeypatch import monkeypatch
|
||||
|
||||
__all__ = ["monkeypatch"]
|
||||
47
django_stubs_ext/django_stubs_ext/monkeypatch.py
Normal file
47
django_stubs_ext/django_stubs_ext/monkeypatch.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from typing import Any, Generic, List, Optional, Type, TypeVar
|
||||
|
||||
import django
|
||||
from django.contrib.admin import ModelAdmin
|
||||
from django.contrib.admin.options import BaseModelAdmin
|
||||
from django.views.generic.edit import FormMixin
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class MPGeneric(Generic[_T]):
|
||||
"""Create a data class to hold metadata about the gneric classes needing monkeypatching.
|
||||
|
||||
The `version` param is optional, and a value of `None` means that the monkeypatch is
|
||||
version-independent.
|
||||
|
||||
This is slightly overkill for our purposes, but useful for future-proofing against any
|
||||
possible issues we may run into with this method.
|
||||
"""
|
||||
|
||||
version: Optional[int]
|
||||
cls: Type[_T]
|
||||
|
||||
def __init__(self, cls: Type[_T], version: Optional[int] = None):
|
||||
"""Set the data fields, basic constructor."""
|
||||
self.version = version
|
||||
self.cls = cls
|
||||
|
||||
|
||||
# certain django classes need to be generic, but lack the __class_getitem__ dunder needed to
|
||||
# annotate them: https://github.com/typeddjango/django-stubs/issues/507
|
||||
# this list stores them so `monkeypatch` can fix them when called
|
||||
_need_generic: List[MPGeneric[Any]] = [
|
||||
MPGeneric(ModelAdmin),
|
||||
MPGeneric(FormMixin),
|
||||
MPGeneric(BaseModelAdmin),
|
||||
]
|
||||
|
||||
|
||||
# currently just adds the __class_getitem__ dunder. if more monkeypatching is needed, add it here
|
||||
def monkeypatch() -> None:
|
||||
"""Monkey patch django as necessary to work properly with mypy."""
|
||||
for el in filter(lambda x: django.VERSION[0] == x.version or x.version is None, _need_generic):
|
||||
el.cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls)
|
||||
|
||||
|
||||
__all__ = ["monkeypatch"]
|
||||
14
django_stubs_ext/mypy.ini
Normal file
14
django_stubs_ext/mypy.ini
Normal file
@@ -0,0 +1,14 @@
|
||||
[mypy]
|
||||
strict_optional = True
|
||||
ignore_missing_imports = True
|
||||
check_untyped_defs = True
|
||||
warn_no_return = False
|
||||
show_traceback = True
|
||||
allow_redefinition = True
|
||||
incremental = True
|
||||
|
||||
plugins =
|
||||
mypy_django_plugin.main
|
||||
|
||||
[mypy.plugins.django-stubs]
|
||||
django_settings_module = scripts.django_tests_settings
|
||||
8
django_stubs_ext/pyproject.toml
Normal file
8
django_stubs_ext/pyproject.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = '\.pyi?$'
|
||||
|
||||
[tool.isort]
|
||||
line_length = 120
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = true
|
||||
8
django_stubs_ext/pytest.ini
Normal file
8
django_stubs_ext/pytest.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
[pytest]
|
||||
testpaths =
|
||||
./tests
|
||||
addopts =
|
||||
--tb=native
|
||||
-s
|
||||
-v
|
||||
--cache-clear
|
||||
17
django_stubs_ext/release.sh
Normal file
17
django_stubs_ext/release.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
|
||||
if [[ -z $(git status -s) ]]
|
||||
then
|
||||
if [[ "$VIRTUAL_ENV" != "" ]]
|
||||
then
|
||||
pip install --upgrade setuptools wheel twine
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload dist/*
|
||||
rm -rf dist/ build/
|
||||
else
|
||||
echo "this script must be executed inside an active virtual env, aborting"
|
||||
fi
|
||||
else
|
||||
echo "git working tree is not clean, aborting"
|
||||
fi
|
||||
7
django_stubs_ext/setup.cfg
Normal file
7
django_stubs_ext/setup.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[flake8]
|
||||
exclude = .*/
|
||||
select = F401, Y
|
||||
max_line_length = 120
|
||||
|
||||
[metadata]
|
||||
license_file = ../LICENSE.txt
|
||||
42
django_stubs_ext/setup.py
Normal file
42
django_stubs_ext/setup.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from distutils.core import setup
|
||||
|
||||
from setuptools import find_packages
|
||||
|
||||
with open("README.md") as f:
|
||||
readme = f.read()
|
||||
|
||||
dependencies = [
|
||||
"django",
|
||||
]
|
||||
|
||||
setup(
|
||||
name="django-stubs-ext",
|
||||
version="0.1.0",
|
||||
description="Monkey-patching and extensions for django-stubs",
|
||||
long_description=readme,
|
||||
long_description_content_type="text/markdown",
|
||||
license="MIT",
|
||||
url="https://github.com/typeddjango/django-stubs",
|
||||
author="Simula Proxy",
|
||||
author_email="3nki.nam.shub@gmail.com",
|
||||
py_modules=[],
|
||||
python_requires=">=3.6",
|
||||
install_requires=dependencies,
|
||||
packages=["django_stubs_ext", *find_packages(exclude=["scripts"])],
|
||||
classifiers=[
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Typing :: Typed",
|
||||
"Framework :: Django",
|
||||
"Framework :: Django :: 2.2",
|
||||
"Framework :: Django :: 3.0",
|
||||
"Framework :: Django :: 3.1",
|
||||
],
|
||||
project_urls={
|
||||
"Release notes": "https://github.com/typeddjango/django-stubs/releases",
|
||||
},
|
||||
)
|
||||
11
django_stubs_ext/tests/test_monkeypatching.py
Normal file
11
django_stubs_ext/tests/test_monkeypatching.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import django_stubs_ext
|
||||
from django_stubs_ext.monkeypatch import _need_generic
|
||||
|
||||
django_stubs_ext.monkeypatch()
|
||||
|
||||
|
||||
def test_patched_generics() -> None:
|
||||
"""Test that the generics actually get patched."""
|
||||
for el in _need_generic:
|
||||
# This only throws an exception if the monkeypatch failed
|
||||
assert el.cls[type] == el.cls # `type` is arbitrary
|
||||
Reference in New Issue
Block a user