Giter VIP home page Giter VIP logo

mypy-django's Introduction

mypy-django

Type stubs to use the mypy static type-checker with your Django projects

This project includes the PEP-484 compatible "type stubs" for Django APIs. Using a compliant checking tool (typically, mypy), it allows you to document and verify more of your code. Your annotated code will look like:

def vote(request: HttpRequest, question_id: str) -> HttpResponse:
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {'question': question})
    else:
        selected_choice.votes += 1
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

If you use incorrect annotations, like in the following example

class ResultsView(generic.DetailView):
    model = Question

    def get_template_names(self) -> str:
        if some_condition():
            return "template_a.html"
        else:
            return "template_b.html"

Running mypy will report the problem:

$ mypy --strict-optional -p polls
...
polls/views.py: note: In class "ResultsView":
polls/views.py:41: error: Return type of "get_template_names" incompatible with supertype "SingleObjectTemplateResponseMixin"
polls/views.py:41: error: Return type of "get_template_names" incompatible with supertype "TemplateResponseMixin"

Mypy Pony

Installation and usage

You'll need to install mypy (other PEP-484 checkers might work, but I haven't tested them). pip install mypy should do the trick. There are no other requirements.

This is not a python package (no actual executable code), so this is not installed with pip or available in PyPI. You can just git clone the latest version from https://github.com/machinalis/mypy-django.git or download and unzip https://github.com/machinalis/mypy-django/archive/master.zip

Once you have your copy, set your MYPYPATH environment variable to point to the files. For example (in Linux/bash):

$ export MYPYPATH=/home/dmoisset/mypy-django/
$ ls $MYPYPATH
django
$ ls $MYPYPATH/django
conf  core  http  __init__.pyi  urls  utils  views

If you don't see the above (the second line might have a few more items in your computer), check that the path exists, and that it points to the correct level in the directory tree.

Motivation

We are building this as a tool at Machinalis to improve the quality of the Django projects we build for our clients. Feel free to contact me if you want to hear more about how we use it or how it can be applied. I can be found at [email protected] or at @dmoisset via Twitter.

In a more general perspective, it makes sense to use static typing for Django given the following:

  1. Much of the user application code for Django projects consists in operating on objects defined by the framework. Unlike other APIs where you mostly pass around standard python data structures, this means that you don't get much benefit from PEP-484 static typing because everything gets annotated as Any (i.e. unchecked)
  2. A large part of the framework follows a very structured, almost declarative approach where you just fill-out a structure (for example, defining models, admin options, generic views, url routers, forms, settings)
  3. Django already has a policy of checking types before starting serving. Many of the system checks performed by manage.py check are actually type checks. So this fits very well with the framework philosophy

Full example

I reimplemented most of the standard Django tutorial with annotations, so you can see how it looks. The code (and a README with some details of problems and solutions found when annotating) are available at https://github.com/machinalis/mypy-django-example

Known issues

  • The current version is mainly focused on supporting Django 1.10 under python 3.x. Given that the APIs I cover are the core components and haven't changed much, you probably can work with older versions of Django and it might work. Python 2.x will not be supported (The code uses str to describe arguments/return values that can be text strings, i.e. unicode).
  • Many django modules that you might import are not supported yet. So you might need to silence with # type: ignore some messages like:
polls/views.py:1: error: No library stub file for module 'django.db.models.query'
  • It's recommended that you run mypy with the --strict-optional option; many of the stubs assume that you do, and you might get some warnings inside the stub files if you don't use it.

Roadmap

v0.1 - Initial release - October 2016

  • Request and Response objects
    • Including supporting classes like QueryDict and file objects
  • Generic views
  • URL resolver
  • Other miscellaneous components required by the above (timezones, cookies, ...)

v0.2 - In development

  • Admin support
  • django.shortcuts
  • Paginators

Probably never

  • Querysets may have some partial support, but complex arguments (like the ones for filter and get queries) or Q and F objects are beyond the expressive possibilities of mypy as it is now.
  • The template language is a separate language and can not be covered by mypy, so any type errors inside the template can not be detected by it.

License

BSD. See LICENSE file for details

mypy-django's People

Contributors

abarto avatar dmoisset avatar vinitkumar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mypy-django's Issues

State of the project

Hi, @dmoisset!

I really like the initiative taken by you and your collegues over at Machinalis. Static type checking for Django on a general basis would be great!

For the moment it is really difficult to investigate the state of static type checking in the Django community. When searching around I have found this repository and several Google usergroup threads heavily involving you, but more recent information is hard to come by.

Is it possible to adress this in the README in this repository? Questions I have are:

  • Are there any plans for pulling these stubs into the official python/typeshed repository?
  • Or is it still the plan to pursue pulling this into upstream Django?
  • What has been your general impression of the Django maintainers interest in doing so?
  • Is the plan to wait for Django 2.0, where Python version 3 can be assumed?

Thanks,
Jakob

Add integration tests to the project with Travis CI

In order to develop any project, automated testing is essential. I reckon the type stubs can be tested by creating a directory with example files, and by testing the output of mypy after running mypy on those files. We can then ensure that certain symbols are stubbed, that certain types are inferred, etc.

django.core.exceptions.ImproperlyConfigured: django-configurations settings importer wasn't correctly installed.

Bug Report

when trying to run "mypy" on my company's existing codebase I'm getting an error as follows:

root@826a40544ab8:/app/wwau/wwau# mypy --config-file=./setup.cfg ./wwau/settings.py 
Error constructing plugin instance of NewSemanalDjangoPlugin

Traceback (most recent call last):
  File "/usr/local/bin/mypy", line 8, in <module>
    sys.exit(console_entry())
  File "/usr/local/lib/python3.6/site-packages/mypy/__main__.py", line 8, in console_entry
    main(None, sys.stdout, sys.stderr)
  File "mypy/main.py", line 90, in main
  File "mypy/build.py", line 180, in build
  File "mypy/build.py", line 229, in _build
  File "mypy/build.py", line 472, in load_plugins
  File "mypy/build.py", line 450, in load_plugins_from_config
  File "/usr/local/lib/python3.6/site-packages/mypy_django_plugin/main.py", line 100, in __init__
    self.django_context = DjangoContext(django_settings_module)
  File "/usr/local/lib/python3.6/site-packages/mypy_django_plugin/django/context.py", line 88, in __init__
    apps, settings = initialize_django(self.django_settings_module)
  File "/usr/local/lib/python3.6/site-packages/mypy_django_plugin/django/context.py", line 70, in initialize_django
    settings._setup()
  File "/usr/local/lib/python3.6/site-packages/django/conf/__init__.py", line 66, in _setup
    self._wrapped = Settings(settings_module)
  File "/usr/local/lib/python3.6/site-packages/django/conf/__init__.py", line 157, in __init__
    mod = importlib.import_module(self.SETTINGS_MODULE)
  File "/usr/local/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/app/wwau/wwau/wwau/settings.py", line 5, in <module>
    from common_settings import CommonBase, CommonLocal, CommonCI, CommonServer, VerboseLoggingMixin
  File "/app/shared/common_settings.py", line 17, in <module>
    class CommonBase(Configuration):
  File "/usr/local/lib/python3.6/site-packages/configurations/base.py", line 28, in __new__
    raise ImproperlyConfigured(install_failure)
django.core.exceptions.ImproperlyConfigured: django-configurations settings importer wasn't correctly installed. Please use one of the starter functions to install it as mentioned in the docs: https://django-configurations.readthedocs.io/

To Reproduce

Unable to reproduce in a similar django project sadly.

Expected Behavior

Just like in the other project - I'd like to see the mypy type errors in the console.

Actual Behavior

as above

Your Environment

  • Mypy version used: mypy 0.790
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files):
    [mypy]
    files=wwau,../../shared
    ignore_missing_imports=true
    plugins =
    mypy_django_plugin.main

[mypy.plugins.django-stubs]
django_settings_module = "wwau.settings"

  • Python version used: Python 3.6.10
  • Operating system and version:
    Linux 826a40544ab8 5.8.16-2-MANJARO #1 SMP PREEMPT Mon Oct 19 11:33:03 UTC 2020 x86_64 GNU/Linux

Support QuerySet with Generic types

I have some modified stubs for QuerySet and Manager so they accept type parameters. This makes the following possible.

for foo in Foo.objects.all(): # The type of foo is inferred as Foo

Using it requires stubbing models like so.

# foo.py
from django.db import models


class Foo(models.Model):
    bar = models.CharField(max_length=255)
# foo.pyi
from django.db import models


class Foo(models.Model):
    objects = ... # type: models.Manager['Foo']
    bar = ... # type: str

Would you be interested in adding this to the project if I create a pull request?

class Meta of Models

I ran into a problem with mypy on the Meta classes of Django models. The mypy issue tracker redirected my to this place.

Do you have an answer for this question? python/mypy#3855

add Django versioning?

Thanks for your work on these stubs!

We are adding type annotations to our Django-based server codebase at Instagram, and we'd like to use (and contribute back to) this project. One issue is that we are on Django 1.8, and will likely need to modify some stubs accordingly. Would you be open to switching to a versioned layout for this project, so we could contribute back the 1.8-specific stubs for others who might find them useful?

We could use a typeshed-style model, with a "common" directory for cross-compatible stubs and then "1.8" and "1.10" directories with version-specific override stub files. And then a script to build a single combined directory for a given Django version. I'd be happy to implement this, if you are open to the approach.

Two-line file, both ignored, leads to errors within mypy-django. Errors disappear if one line removed, get worse if the other.

Encountered against the latest checkouts, with mypy==0.521

Two lines:

from django.views import View # type: ignore
from django.db import models  # type: ignore

Errors:

$ MYPYPATH=mypy-django mypy --strict-optional --disallow-untyped-calls test_mypy.py 
mypy-django/django/views/generic/list.pyi:3: error: No library stub file for module 'django.db.models'
mypy-django/django/views/generic/list.pyi:3: note: (Stub files are from https://github.com/python/typeshed)
mypy-django/django/views/generic/list.pyi:4: error: No library stub file for module 'django.db.models.query'

One line:

from django.views import View # type: ignore

Errors:

$ MYPYPATH=mypy-django mypy --strict-optional --disallow-untyped-calls test_mypy.py 
mypy-django/django/views/generic/dates.pyi:4: error: No library stub file for module 'django.db'
mypy-django/django/views/generic/dates.pyi:4: note: (Stub files are from https://github.com/python/typeshed)
mypy-django/django/views/generic/detail.pyi:3: error: No library stub file for module 'django.db'
mypy-django/django/views/generic/edit.pyi:3: error: No library stub file for module 'django.db'
mypy-django/django/views/generic/list.pyi:3: error: No library stub file for module 'django.db.models'
mypy-django/django/views/generic/list.pyi:4: error: No library stub file for module 'django.db.models.query'

This is stripped down from a more reasonably-sized file that actually did things, but it seems to be the imports themselves that triggered the issues.

I think this must be some kind of import-order issue.

(I forget why I was ignoring both lines, but it doesn't seem to really have a tangible effect on the output.)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.