👷 Add pre-commit (#1336)

* 🔧 Add pre-commit config

Similar to graphene and graphene-sqlalchemy

* ⬆ Bump black

* 👷 Lint on CI

* ⬆ Bump flake8-black

* 🔧 Keep excluding migrations

* ⬆ Bump flake8

* 🔧 Remove black and flake8 from tox config

* ⬆ Update pre-commit versions

* Upgrade syntax to python 3.7+

* Format with pre-commit

dedent docs/schema.py to allow formatting

* Fix tests on python 3.7
This commit is contained in:
Nikolai Røed Kristiansen 2022-10-19 16:10:30 +02:00 committed by GitHub
parent f24cbd5148
commit 4517e32224
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 779 additions and 244 deletions

View File

@ -16,7 +16,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install tox
- name: Run lint 💅
- name: Run pre-commit 💅
run: tox
env:
TOXENV: flake8
TOXENV: pre-commit

30
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,30 @@
default_language_version:
python: python3.9
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: check-merge-conflict
- id: check-json
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
exclude: ^docs/.*$
- id: pretty-format-json
args:
- --autofix
- id: trailing-whitespace
exclude: README.md
- repo: https://github.com/asottile/pyupgrade
rev: v2.37.3
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/psf/black
rev: 22.6.0
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8

View File

@ -13,7 +13,7 @@ tests:
.PHONY: format ## Format code
format:
black --exclude "/migrations/" graphene_django examples setup.py
black graphene_django examples setup.py
.PHONY: lint ## Lint code
lint:

View File

@ -8,25 +8,22 @@
class CategoryType(DjangoObjectType):
class Meta:
model = Category
fields = '__all__'
fields = "__all__"
class IngredientType(DjangoObjectType):
class Meta:
model = Ingredient
fields = '__all__'
fields = "__all__"
class Query(object):
category = graphene.Field(CategoryType,
id=graphene.Int(),
name=graphene.String())
class Query:
category = graphene.Field(CategoryType, id=graphene.Int(), name=graphene.String())
all_categories = graphene.List(CategoryType)
ingredient = graphene.Field(IngredientType,
id=graphene.Int(),
name=graphene.String())
ingredient = graphene.Field(
IngredientType, id=graphene.Int(), name=graphene.String()
)
all_ingredients = graphene.List(IngredientType)
def resolve_all_categories(self, info, **kwargs):
@ -36,8 +33,8 @@
return Ingredient.objects.all()
def resolve_category(self, info, **kwargs):
id = kwargs.get('id')
name = kwargs.get('name')
id = kwargs.get("id")
name = kwargs.get("name")
if id is not None:
return Category.objects.get(pk=id)
@ -48,8 +45,8 @@
return None
def resolve_ingredient(self, info, **kwargs):
id = kwargs.get('id')
name = kwargs.get('name')
id = kwargs.get("id")
name = kwargs.get("name")
if id is not None:
return Ingredient.objects.get(pk=id)

View File

@ -1 +1,52 @@
[{"model": "ingredients.category", "pk": 1, "fields": {"name": "Dairy"}}, {"model": "ingredients.category", "pk": 2, "fields": {"name": "Meat"}}, {"model": "ingredients.ingredient", "pk": 1, "fields": {"name": "Eggs", "notes": "Good old eggs", "category": 1}}, {"model": "ingredients.ingredient", "pk": 2, "fields": {"name": "Milk", "notes": "Comes from a cow", "category": 1}}, {"model": "ingredients.ingredient", "pk": 3, "fields": {"name": "Beef", "notes": "Much like milk, this comes from a cow", "category": 2}}, {"model": "ingredients.ingredient", "pk": 4, "fields": {"name": "Chicken", "notes": "Definitely doesn't come from a cow", "category": 2}}]
[
{
"fields": {
"name": "Dairy"
},
"model": "ingredients.category",
"pk": 1
},
{
"fields": {
"name": "Meat"
},
"model": "ingredients.category",
"pk": 2
},
{
"fields": {
"category": 1,
"name": "Eggs",
"notes": "Good old eggs"
},
"model": "ingredients.ingredient",
"pk": 1
},
{
"fields": {
"category": 1,
"name": "Milk",
"notes": "Comes from a cow"
},
"model": "ingredients.ingredient",
"pk": 2
},
{
"fields": {
"category": 2,
"name": "Beef",
"notes": "Much like milk, this comes from a cow"
},
"model": "ingredients.ingredient",
"pk": 3
},
{
"fields": {
"category": 2,
"name": "Chicken",
"notes": "Definitely doesn't come from a cow"
},
"model": "ingredients.ingredient",
"pk": 4
}
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2015-12-04 18:15
from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models
@ -10,24 +8,46 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Category',
name="Category",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name='Ingredient',
name="Ingredient",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('notes', models.TextField()),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ingredients', to='ingredients.Category')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("notes", models.TextField()),
(
"category",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="ingredients",
to="ingredients.Category",
),
),
],
),
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-11-04 00:50
from __future__ import unicode_literals
from django.db import migrations, models
@ -8,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ingredients', '0001_initial'),
("ingredients", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name='ingredient',
name='notes',
model_name="ingredient",
name="notes",
field=models.TextField(blank=True, null=True),
),
]

View File

@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ingredients', '0002_auto_20161104_0050'),
("ingredients", "0002_auto_20161104_0050"),
]
operations = [
migrations.AlterModelOptions(
name='category',
options={'verbose_name_plural': 'Categories'},
name="category",
options={"verbose_name_plural": "Categories"},
),
]

View File

@ -16,7 +16,7 @@ class IngredientType(DjangoObjectType):
fields = "__all__"
class Query(object):
class Query:
category = graphene.Field(CategoryType, id=graphene.Int(), name=graphene.String())
all_categories = graphene.List(CategoryType)

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2015-12-04 18:20
from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models
@ -11,26 +9,62 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('ingredients', '0001_initial'),
("ingredients", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='Recipe',
name="Recipe",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('instructions', models.TextField()),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=100)),
("instructions", models.TextField()),
],
),
migrations.CreateModel(
name='RecipeIngredient',
name="RecipeIngredient",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.FloatField()),
('unit', models.CharField(choices=[('kg', 'Kilograms'), ('l', 'Litres'), ('', 'Units')], max_length=20)),
('ingredient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='used_by', to='ingredients.Ingredient')),
('recipes', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='amounts', to='recipes.Recipe')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("amount", models.FloatField()),
(
"unit",
models.CharField(
choices=[("kg", "Kilograms"), ("l", "Litres"), ("", "Units")],
max_length=20,
),
),
(
"ingredient",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="used_by",
to="ingredients.Ingredient",
),
),
(
"recipes",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="amounts",
to="recipes.Recipe",
),
),
],
),
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-11-04 01:06
from __future__ import unicode_literals
from django.db import migrations, models
@ -8,18 +6,26 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recipes', '0001_initial'),
("recipes", "0001_initial"),
]
operations = [
migrations.RenameField(
model_name='recipeingredient',
old_name='recipes',
new_name='recipe',
model_name="recipeingredient",
old_name="recipes",
new_name="recipe",
),
migrations.AlterField(
model_name='recipeingredient',
name='unit',
field=models.CharField(choices=[(b'unit', b'Units'), (b'kg', b'Kilograms'), (b'l', b'Litres'), (b'st', b'Shots')], max_length=20),
model_name="recipeingredient",
name="unit",
field=models.CharField(
choices=[
(b"unit", b"Units"),
(b"kg", b"Kilograms"),
(b"l", b"Litres"),
(b"st", b"Shots"),
],
max_length=20,
),
),
]

View File

@ -6,13 +6,21 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recipes', '0002_auto_20161104_0106'),
("recipes", "0002_auto_20161104_0106"),
]
operations = [
migrations.AlterField(
model_name='recipeingredient',
name='unit',
field=models.CharField(choices=[('unit', 'Units'), ('kg', 'Kilograms'), ('l', 'Litres'), ('st', 'Shots')], max_length=20),
model_name="recipeingredient",
name="unit",
field=models.CharField(
choices=[
("unit", "Units"),
("kg", "Kilograms"),
("l", "Litres"),
("st", "Shots"),
],
max_length=20,
),
),
]

View File

@ -16,7 +16,7 @@ class RecipeIngredientType(DjangoObjectType):
fields = "__all__"
class Query(object):
class Query:
recipe = graphene.Field(RecipeType, id=graphene.Int(), title=graphene.String())
all_recipes = graphene.List(RecipeType)

View File

@ -1 +1,52 @@
[{"model": "ingredients.category", "pk": 1, "fields": {"name": "Dairy"}}, {"model": "ingredients.category", "pk": 2, "fields": {"name": "Meat"}}, {"model": "ingredients.ingredient", "pk": 1, "fields": {"name": "Eggs", "notes": "Good old eggs", "category": 1}}, {"model": "ingredients.ingredient", "pk": 2, "fields": {"name": "Milk", "notes": "Comes from a cow", "category": 1}}, {"model": "ingredients.ingredient", "pk": 3, "fields": {"name": "Beef", "notes": "Much like milk, this comes from a cow", "category": 2}}, {"model": "ingredients.ingredient", "pk": 4, "fields": {"name": "Chicken", "notes": "Definitely doesn't come from a cow", "category": 2}}]
[
{
"fields": {
"name": "Dairy"
},
"model": "ingredients.category",
"pk": 1
},
{
"fields": {
"name": "Meat"
},
"model": "ingredients.category",
"pk": 2
},
{
"fields": {
"category": 1,
"name": "Eggs",
"notes": "Good old eggs"
},
"model": "ingredients.ingredient",
"pk": 1
},
{
"fields": {
"category": 1,
"name": "Milk",
"notes": "Comes from a cow"
},
"model": "ingredients.ingredient",
"pk": 2
},
{
"fields": {
"category": 2,
"name": "Beef",
"notes": "Much like milk, this comes from a cow"
},
"model": "ingredients.ingredient",
"pk": 3
},
{
"fields": {
"category": 2,
"name": "Chicken",
"notes": "Definitely doesn't come from a cow"
},
"model": "ingredients.ingredient",
"pk": 4
}
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2015-12-04 18:15
from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models
@ -10,24 +8,46 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Category',
name="Category",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name='Ingredient',
name="Ingredient",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('notes', models.TextField()),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ingredients', to='ingredients.Category')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
("notes", models.TextField()),
(
"category",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="ingredients",
to="ingredients.Category",
),
),
],
),
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-11-04 00:50
from __future__ import unicode_literals
from django.db import migrations, models
@ -8,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ingredients', '0001_initial'),
("ingredients", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name='ingredient',
name='notes',
model_name="ingredient",
name="notes",
field=models.TextField(blank=True, null=True),
),
]

View File

@ -28,7 +28,7 @@ class IngredientNode(DjangoObjectType):
}
class Query(object):
class Query:
category = Node.Field(CategoryNode)
all_categories = DjangoFilterConnectionField(CategoryNode)

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2015-12-04 18:20
from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models
@ -11,26 +9,62 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('ingredients', '0001_initial'),
("ingredients", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='Recipe',
name="Recipe",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('instructions', models.TextField()),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=100)),
("instructions", models.TextField()),
],
),
migrations.CreateModel(
name='RecipeIngredient',
name="RecipeIngredient",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.FloatField()),
('unit', models.CharField(choices=[('kg', 'Kilograms'), ('l', 'Litres'), ('', 'Units')], max_length=20)),
('ingredient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='used_by', to='ingredients.Ingredient')),
('recipes', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='amounts', to='recipes.Recipe')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("amount", models.FloatField()),
(
"unit",
models.CharField(
choices=[("kg", "Kilograms"), ("l", "Litres"), ("", "Units")],
max_length=20,
),
),
(
"ingredient",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="used_by",
to="ingredients.Ingredient",
),
),
(
"recipes",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="amounts",
to="recipes.Recipe",
),
),
],
),
]

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2016-11-04 01:06
from __future__ import unicode_literals
from django.db import migrations, models
@ -8,18 +6,26 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recipes', '0001_initial'),
("recipes", "0001_initial"),
]
operations = [
migrations.RenameField(
model_name='recipeingredient',
old_name='recipes',
new_name='recipe',
model_name="recipeingredient",
old_name="recipes",
new_name="recipe",
),
migrations.AlterField(
model_name='recipeingredient',
name='unit',
field=models.CharField(choices=[(b'unit', b'Units'), (b'kg', b'Kilograms'), (b'l', b'Litres'), (b'st', b'Shots')], max_length=20),
model_name="recipeingredient",
name="unit",
field=models.CharField(
choices=[
(b"unit", b"Units"),
(b"kg", b"Kilograms"),
(b"l", b"Litres"),
(b"st", b"Shots"),
],
max_length=20,
),
),
]

View File

@ -25,7 +25,7 @@ class RecipeIngredientNode(DjangoObjectType):
}
class Query(object):
class Query:
recipe = Node.Field(RecipeNode)
all_recipes = DjangoFilterConnectionField(RecipeNode)

View File

@ -1 +1,302 @@
[{"model": "auth.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$24000$0SgBlSlnbv5c$ijVQipm2aNDlcrTL8Qi3SVNHphTm4HIsDfUi4kn9tog=", "last_login": "2016-11-04T00:46:58Z", "is_superuser": true, "username": "admin", "first_name": "", "last_name": "", "email": "asdf@example.com", "is_staff": true, "is_active": true, "date_joined": "2016-11-03T18:24:40Z", "groups": [], "user_permissions": []}}, {"model": "recipes.recipe", "pk": 1, "fields": {"title": "Cheerios With a Shot of Vermouth", "instructions": "https://xkcd.com/720/"}}, {"model": "recipes.recipe", "pk": 2, "fields": {"title": "Quail Eggs in Whipped Cream and MSG", "instructions": "https://xkcd.com/720/"}}, {"model": "recipes.recipe", "pk": 3, "fields": {"title": "Deep Fried Skittles", "instructions": "https://xkcd.com/720/"}}, {"model": "recipes.recipe", "pk": 4, "fields": {"title": "Newt ala Doritos", "instructions": "https://xkcd.com/720/"}}, {"model": "recipes.recipe", "pk": 5, "fields": {"title": "Fruit Salad", "instructions": "Chop up and add together"}}, {"model": "recipes.recipeingredient", "pk": 1, "fields": {"recipes": 5, "ingredient": 9, "amount": 1.0, "unit": "unit"}}, {"model": "recipes.recipeingredient", "pk": 2, "fields": {"recipes": 5, "ingredient": 10, "amount": 2.0, "unit": "unit"}}, {"model": "recipes.recipeingredient", "pk": 3, "fields": {"recipes": 5, "ingredient": 7, "amount": 3.0, "unit": "unit"}}, {"model": "recipes.recipeingredient", "pk": 4, "fields": {"recipes": 5, "ingredient": 8, "amount": 4.0, "unit": "unit"}}, {"model": "recipes.recipeingredient", "pk": 5, "fields": {"recipes": 4, "ingredient": 5, "amount": 1.0, "unit": "kg"}}, {"model": "recipes.recipeingredient", "pk": 6, "fields": {"recipes": 4, "ingredient": 6, "amount": 2.0, "unit": "l"}}, {"model": "recipes.recipeingredient", "pk": 7, "fields": {"recipes": 3, "ingredient": 4, "amount": 1.0, "unit": "unit"}}, {"model": "recipes.recipeingredient", "pk": 8, "fields": {"recipes": 2, "ingredient": 2, "amount": 1.0, "unit": "kg"}}, {"model": "recipes.recipeingredient", "pk": 9, "fields": {"recipes": 2, "ingredient": 11, "amount": 2.0, "unit": "l"}}, {"model": "recipes.recipeingredient", "pk": 10, "fields": {"recipes": 2, "ingredient": 12, "amount": 3.0, "unit": "st"}}, {"model": "recipes.recipeingredient", "pk": 11, "fields": {"recipes": 1, "ingredient": 1, "amount": 1.0, "unit": "kg"}}, {"model": "recipes.recipeingredient", "pk": 12, "fields": {"recipes": 1, "ingredient": 3, "amount": 1.0, "unit": "st"}}, {"model": "ingredients.category", "pk": 1, "fields": {"name": "fruit"}}, {"model": "ingredients.category", "pk": 3, "fields": {"name": "xkcd"}}, {"model": "ingredients.ingredient", "pk": 1, "fields": {"name": "Cheerios", "notes": "this is a note", "category": 3}}, {"model": "ingredients.ingredient", "pk": 2, "fields": {"name": "Quail Eggs", "notes": "has more notes", "category": 3}}, {"model": "ingredients.ingredient", "pk": 3, "fields": {"name": "Vermouth", "notes": "", "category": 3}}, {"model": "ingredients.ingredient", "pk": 4, "fields": {"name": "Skittles", "notes": "", "category": 3}}, {"model": "ingredients.ingredient", "pk": 5, "fields": {"name": "Newt", "notes": "Braised and Confuesd", "category": 3}}, {"model": "ingredients.ingredient", "pk": 6, "fields": {"name": "Doritos", "notes": "Crushed", "category": 3}}, {"model": "ingredients.ingredient", "pk": 7, "fields": {"name": "Apple", "notes": "", "category": 1}}, {"model": "ingredients.ingredient", "pk": 8, "fields": {"name": "Orange", "notes": "", "category": 1}}, {"model": "ingredients.ingredient", "pk": 9, "fields": {"name": "Banana", "notes": "", "category": 1}}, {"model": "ingredients.ingredient", "pk": 10, "fields": {"name": "Grapes", "notes": "", "category": 1}}, {"model": "ingredients.ingredient", "pk": 11, "fields": {"name": "Whipped Cream", "notes": "", "category": 3}}, {"model": "ingredients.ingredient", "pk": 12, "fields": {"name": "MSG", "notes": "", "category": 3}}]
[
{
"fields": {
"date_joined": "2016-11-03T18:24:40Z",
"email": "asdf@example.com",
"first_name": "",
"groups": [],
"is_active": true,
"is_staff": true,
"is_superuser": true,
"last_login": "2016-11-04T00:46:58Z",
"last_name": "",
"password": "pbkdf2_sha256$24000$0SgBlSlnbv5c$ijVQipm2aNDlcrTL8Qi3SVNHphTm4HIsDfUi4kn9tog=",
"user_permissions": [],
"username": "admin"
},
"model": "auth.user",
"pk": 1
},
{
"fields": {
"instructions": "https://xkcd.com/720/",
"title": "Cheerios With a Shot of Vermouth"
},
"model": "recipes.recipe",
"pk": 1
},
{
"fields": {
"instructions": "https://xkcd.com/720/",
"title": "Quail Eggs in Whipped Cream and MSG"
},
"model": "recipes.recipe",
"pk": 2
},
{
"fields": {
"instructions": "https://xkcd.com/720/",
"title": "Deep Fried Skittles"
},
"model": "recipes.recipe",
"pk": 3
},
{
"fields": {
"instructions": "https://xkcd.com/720/",
"title": "Newt ala Doritos"
},
"model": "recipes.recipe",
"pk": 4
},
{
"fields": {
"instructions": "Chop up and add together",
"title": "Fruit Salad"
},
"model": "recipes.recipe",
"pk": 5
},
{
"fields": {
"amount": 1.0,
"ingredient": 9,
"recipes": 5,
"unit": "unit"
},
"model": "recipes.recipeingredient",
"pk": 1
},
{
"fields": {
"amount": 2.0,
"ingredient": 10,
"recipes": 5,
"unit": "unit"
},
"model": "recipes.recipeingredient",
"pk": 2
},
{
"fields": {
"amount": 3.0,
"ingredient": 7,
"recipes": 5,
"unit": "unit"
},
"model": "recipes.recipeingredient",
"pk": 3
},
{
"fields": {
"amount": 4.0,
"ingredient": 8,
"recipes": 5,
"unit": "unit"
},
"model": "recipes.recipeingredient",
"pk": 4
},
{
"fields": {
"amount": 1.0,
"ingredient": 5,
"recipes": 4,
"unit": "kg"
},
"model": "recipes.recipeingredient",
"pk": 5
},
{
"fields": {
"amount": 2.0,
"ingredient": 6,
"recipes": 4,
"unit": "l"
},
"model": "recipes.recipeingredient",
"pk": 6
},
{
"fields": {
"amount": 1.0,
"ingredient": 4,
"recipes": 3,
"unit": "unit"
},
"model": "recipes.recipeingredient",
"pk": 7
},
{
"fields": {
"amount": 1.0,
"ingredient": 2,
"recipes": 2,
"unit": "kg"
},
"model": "recipes.recipeingredient",
"pk": 8
},
{
"fields": {
"amount": 2.0,
"ingredient": 11,
"recipes": 2,
"unit": "l"
},
"model": "recipes.recipeingredient",
"pk": 9
},
{
"fields": {
"amount": 3.0,
"ingredient": 12,
"recipes": 2,
"unit": "st"
},
"model": "recipes.recipeingredient",
"pk": 10
},
{
"fields": {
"amount": 1.0,
"ingredient": 1,
"recipes": 1,
"unit": "kg"
},
"model": "recipes.recipeingredient",
"pk": 11
},
{
"fields": {
"amount": 1.0,
"ingredient": 3,
"recipes": 1,
"unit": "st"
},
"model": "recipes.recipeingredient",
"pk": 12
},
{
"fields": {
"name": "fruit"
},
"model": "ingredients.category",
"pk": 1
},
{
"fields": {
"name": "xkcd"
},
"model": "ingredients.category",
"pk": 3
},
{
"fields": {
"category": 3,
"name": "Cheerios",
"notes": "this is a note"
},
"model": "ingredients.ingredient",
"pk": 1
},
{
"fields": {
"category": 3,
"name": "Quail Eggs",
"notes": "has more notes"
},
"model": "ingredients.ingredient",
"pk": 2
},
{
"fields": {
"category": 3,
"name": "Vermouth",
"notes": ""
},
"model": "ingredients.ingredient",
"pk": 3
},
{
"fields": {
"category": 3,
"name": "Skittles",
"notes": ""
},
"model": "ingredients.ingredient",
"pk": 4
},
{
"fields": {
"category": 3,
"name": "Newt",
"notes": "Braised and Confuesd"
},
"model": "ingredients.ingredient",
"pk": 5
},
{
"fields": {
"category": 3,
"name": "Doritos",
"notes": "Crushed"
},
"model": "ingredients.ingredient",
"pk": 6
},
{
"fields": {
"category": 1,
"name": "Apple",
"notes": ""
},
"model": "ingredients.ingredient",
"pk": 7
},
{
"fields": {
"category": 1,
"name": "Orange",
"notes": ""
},
"model": "ingredients.ingredient",
"pk": 8
},
{
"fields": {
"category": 1,
"name": "Banana",
"notes": ""
},
"model": "ingredients.ingredient",
"pk": 9
},
{
"fields": {
"category": 1,
"name": "Grapes",
"notes": ""
},
"model": "ingredients.ingredient",
"pk": 10
},
{
"fields": {
"category": 3,
"name": "Whipped Cream",
"notes": ""
},
"model": "ingredients.ingredient",
"pk": 11
},
{
"fields": {
"category": 3,
"name": "MSG",
"notes": ""
},
"model": "ingredients.ingredient",
"pk": 12
}
]

View File

@ -1,5 +1,3 @@
from __future__ import absolute_import
from django.db import models

View File

@ -1,4 +1,4 @@
class MissingType(object):
class MissingType:
def __init__(self, *args, **kwargs):
pass

View File

@ -74,8 +74,7 @@ def get_choices(choices):
choices = choices.items()
for value, help_text in choices:
if isinstance(help_text, (tuple, list)):
for choice in get_choices(help_text):
yield choice
yield from get_choices(help_text)
else:
name = convert_choice_name(value)
while name in converted_names:
@ -92,7 +91,7 @@ def convert_choices_to_named_enum_with_descriptions(name, choices):
named_choices = [(c[0], c[1]) for c in choices]
named_choices_descriptions = {c[0]: c[2] for c in choices}
class EnumWithDescriptionsType(object):
class EnumWithDescriptionsType:
@property
def description(self):
return str(named_choices_descriptions[self.name])
@ -109,7 +108,7 @@ def generate_enum_name(django_model_meta, field):
)
name = custom_func(field)
elif graphene_settings.DJANGO_CHOICE_FIELD_ENUM_V2_NAMING is True:
name = to_camel_case("{}_{}".format(django_model_meta.object_name, field.name))
name = to_camel_case(f"{django_model_meta.object_name}_{field.name}")
else:
name = "{app_label}{object_name}{field_name}Choices".format(
app_label=to_camel_case(django_model_meta.app_label.title()),
@ -155,7 +154,9 @@ def get_django_field_description(field):
@singledispatch
def convert_django_field(field, registry=None):
raise Exception(
"Don't know how to convert the Django field %s (%s)" % (field, field.__class__)
"Don't know how to convert the Django field {} ({})".format(
field, field.__class__
)
)

View File

@ -7,7 +7,7 @@ from .exception.formating import wrap_exception
from .types import DjangoDebug
class DjangoDebugContext(object):
class DjangoDebugContext:
def __init__(self):
self.debug_promise = None
self.promises = []
@ -46,7 +46,7 @@ class DjangoDebugContext(object):
unwrap_cursor(connection)
class DjangoDebugMiddleware(object):
class DjangoDebugMiddleware:
def resolve(self, next, root, info, **args):
context = info.context
django_debug = getattr(context, "django_debug", None)

View File

@ -1,5 +1,4 @@
# Code obtained from django-debug-toolbar sql panel tracking
from __future__ import absolute_import, unicode_literals
import json
from threading import local
@ -50,7 +49,7 @@ def unwrap_cursor(connection):
del connection._graphene_cursor
class ExceptionCursorWrapper(object):
class ExceptionCursorWrapper:
"""
Wraps a cursor and raises an exception on any operation.
Used in Templates panel.
@ -63,7 +62,7 @@ class ExceptionCursorWrapper(object):
raise SQLQueryTriggered()
class NormalCursorWrapper(object):
class NormalCursorWrapper:
"""
Wraps a cursor and logs queries.
"""
@ -85,7 +84,7 @@ class NormalCursorWrapper(object):
if not params:
return params
if isinstance(params, dict):
return dict((key, self._quote_expr(value)) for key, value in params.items())
return {key: self._quote_expr(value) for key, value in params.items()}
return list(map(self._quote_expr, params))
def _decode(self, param):

View File

@ -8,7 +8,7 @@ from ..middleware import DjangoDebugMiddleware
from ..types import DjangoDebug
class context(object):
class context:
pass

View File

@ -28,7 +28,7 @@ class DjangoListField(Field):
_type = _type.of_type
# Django would never return a Set of None vvvvvvv
super(DjangoListField, self).__init__(List(NonNull(_type)), *args, **kwargs)
super().__init__(List(NonNull(_type)), *args, **kwargs)
assert issubclass(
self._underlying_type, DjangoObjectType
@ -63,7 +63,7 @@ class DjangoListField(Field):
return queryset
def wrap_resolve(self, parent_resolver):
resolver = super(DjangoListField, self).wrap_resolve(parent_resolver)
resolver = super().wrap_resolve(parent_resolver)
_type = self.type
if isinstance(_type, NonNull):
_type = _type.of_type
@ -87,7 +87,7 @@ class DjangoConnectionField(ConnectionField):
graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST,
)
kwargs.setdefault("offset", Int())
super(DjangoConnectionField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
@property
def type(self):

View File

@ -44,7 +44,7 @@ class DjangoFilterConnectionField(DjangoConnectionField):
self._filtering_args = None
self._extra_filter_meta = extra_filter_meta
self._base_args = None
super(DjangoFilterConnectionField, self).__init__(type_, *args, **kwargs)
super().__init__(type_, *args, **kwargs)
@property
def args(self):
@ -90,9 +90,7 @@ class DjangoFilterConnectionField(DjangoConnectionField):
kwargs[k] = convert_enum(v)
return kwargs
qs = super(DjangoFilterConnectionField, cls).resolve_queryset(
connection, iterable, info, args
)
qs = super().resolve_queryset(connection, iterable, info, args)
filterset = filterset_class(
data=filter_kwargs(), queryset=qs, request=info.context

View File

@ -22,6 +22,6 @@ class ArrayFilter(TypedFilter):
return qs
if self.distinct:
qs = qs.distinct()
lookup = "%s__%s" % (self.field_name, self.lookup_expr)
lookup = f"{self.field_name}__{self.lookup_expr}"
qs = self.get_method(qs)(**{lookup: value})
return qs

View File

@ -17,7 +17,7 @@ class GlobalIDFilter(Filter):
_id = None
if value is not None:
_, _id = from_global_id(value)
return super(GlobalIDFilter, self).filter(qs, _id)
return super().filter(qs, _id)
class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter):
@ -25,4 +25,4 @@ class GlobalIDMultipleChoiceFilter(MultipleChoiceFilter):
def filter(self, qs, value):
gids = [from_global_id(v)[1] for v in value]
return super(GlobalIDMultipleChoiceFilter, self).filter(qs, gids)
return super().filter(qs, gids)

View File

@ -23,4 +23,4 @@ class ListFilter(TypedFilter):
else:
return qs.none()
else:
return super(ListFilter, self).filter(qs, value)
return super().filter(qs, value)

View File

@ -12,7 +12,7 @@ class TypedFilter(Filter):
def __init__(self, input_type=None, *args, **kwargs):
self._input_type = input_type
super(TypedFilter, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
@property
def input_type(self):

View File

@ -31,7 +31,7 @@ class GrapheneFilterSetMixin(BaseFilterSet):
def setup_filterset(filterset_class):
"""Wrap a provided filterset in Graphene-specific functionality"""
return type(
"Graphene{}".format(filterset_class.__name__),
f"Graphene{filterset_class.__name__}",
(filterset_class, GrapheneFilterSetMixin),
{},
)
@ -40,7 +40,7 @@ def setup_filterset(filterset_class):
def custom_filterset_factory(model, filterset_base_class=FilterSet, **meta):
"""Create a filterset for the given model using the provided meta data"""
meta.update({"model": model})
meta_class = type(str("Meta"), (object,), meta)
meta_class = type("Meta", (object,), meta)
filterset = type(
str("%sFilterSet" % model._meta.object_name),
(filterset_base_class, GrapheneFilterSetMixin),

View File

@ -1,4 +1,4 @@
from mock import MagicMock
from unittest.mock import MagicMock
import pytest
from django.db import models

View File

@ -67,7 +67,7 @@ def assert_arguments(field, *arguments):
actual = [name for name in args if name not in ignore and not name.startswith("_")]
assert set(arguments) == set(
actual
), "Expected arguments ({}) did not match actual ({})".format(arguments, actual)
), f"Expected arguments ({arguments}) did not match actual ({actual})"
def assert_orderable(field):
@ -141,7 +141,7 @@ def test_filter_shortcut_filterset_context():
@property
def qs(self):
qs = super(ArticleContextFilter, self).qs
qs = super().qs
return qs.filter(reporter=self.request.reporter)
class Query(ObjectType):
@ -166,7 +166,7 @@ def test_filter_shortcut_filterset_context():
editor=r2,
)
class context(object):
class context:
reporter = r2
query = """

View File

@ -349,19 +349,19 @@ def test_fk_id_in_filter(query):
schema = Schema(query=query)
query = """
query {
articles (reporter_In: [%s, %s]) {
edges {
node {
query {{
articles (reporter_In: [{}, {}]) {{
edges {{
node {{
headline
reporter {
reporter {{
lastName
}
}
}
}
}
""" % (
}}
}}
}}
}}
}}
""".format(
john_doe.id,
jean_bon.id,
)

View File

@ -98,7 +98,7 @@ def test_typed_filter_schema(schema):
)
for filter_field, gql_type in filters.items():
assert "{}: {}".format(filter_field, gql_type) in all_articles_filters
assert f"{filter_field}: {gql_type}" in all_articles_filters
def test_typed_filters_work(schema):

View File

@ -95,7 +95,7 @@ class DjangoFormMutation(BaseDjangoFormMutation):
_meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
super(DjangoFormMutation, cls).__init_subclass_with_meta__(
super().__init_subclass_with_meta__(
_meta=_meta, input_fields=input_fields, **options
)
@ -127,7 +127,7 @@ class DjangoModelFormMutation(BaseDjangoFormMutation):
return_field_name=None,
only_fields=(),
exclude_fields=(),
**options
**options,
):
if not form_class:
@ -147,7 +147,7 @@ class DjangoModelFormMutation(BaseDjangoFormMutation):
registry = get_global_registry()
model_type = registry.get_type_for_model(model)
if not model_type:
raise Exception("No type registered for model: {}".format(model.__name__))
raise Exception(f"No type registered for model: {model.__name__}")
if not return_field_name:
model_name = model.__name__
@ -163,7 +163,7 @@ class DjangoModelFormMutation(BaseDjangoFormMutation):
_meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
super(DjangoModelFormMutation, cls).__init_subclass_with_meta__(
super().__init_subclass_with_meta__(
_meta=_meta, input_fields=input_fields, **options
)

View File

@ -73,16 +73,12 @@ class Command(CommandArguments):
elif file_extension == ".json":
self.save_json_file(out, schema_dict, indent)
else:
raise CommandError(
'Unrecognised file format "{}"'.format(file_extension)
)
raise CommandError(f'Unrecognised file format "{file_extension}"')
style = getattr(self, "style", None)
success = getattr(style, "SUCCESS", lambda x: x)
self.stdout.write(
success("Successfully dumped GraphQL schema to {}".format(out))
)
self.stdout.write(success(f"Successfully dumped GraphQL schema to {out}"))
def handle(self, *args, **options):
options_schema = options.get("schema")

View File

@ -1,4 +1,4 @@
class Registry(object):
class Registry:
def __init__(self):
self._registry = {}
self._field_registry = {}

View File

@ -114,7 +114,7 @@ class SerializerMutation(ClientIDMutation):
_meta.fields = yank_fields_from_attrs(output_fields, _as=Field)
input_fields = yank_fields_from_attrs(input_fields, _as=InputField)
super(SerializerMutation, cls).__init_subclass_with_meta__(
super().__init_subclass_with_meta__(
_meta=_meta, input_fields=input_fields, **options
)

View File

@ -72,7 +72,7 @@ def convert_serializer_to_input_type(serializer_class):
for name, field in serializer.fields.items()
}
ret_type = type(
"{}Input".format(serializer.__class__.__name__),
f"{serializer.__class__.__name__}Input",
(graphene.InputObjectType,),
items,
)

View File

@ -11,7 +11,6 @@ This module provides the `graphene_settings` object, that is used to access
Graphene settings, checking for user settings first, then falling
back to the defaults.
"""
from __future__ import unicode_literals
from django.conf import settings
from django.test.signals import setting_changed
@ -78,7 +77,7 @@ def import_from_string(val, setting_name):
module = importlib.import_module(module_path)
return getattr(module, class_name)
except (ImportError, AttributeError) as e:
msg = "Could not import '%s' for Graphene setting '%s'. %s: %s." % (
msg = "Could not import '{}' for Graphene setting '{}'. {}: {}.".format(
val,
setting_name,
e.__class__.__name__,
@ -87,7 +86,7 @@ def import_from_string(val, setting_name):
raise ImportError(msg)
class GrapheneSettings(object):
class GrapheneSettings:
"""
A settings object, that allows API settings to be accessed as properties.
For example:

View File

@ -1,5 +1,3 @@
from __future__ import absolute_import
from django.db import models
from django.utils.translation import gettext_lazy as _
@ -37,7 +35,7 @@ class Film(models.Model):
class DoeReporterManager(models.Manager):
def get_queryset(self):
return super(DoeReporterManager, self).get_queryset().filter(last_name="Doe")
return super().get_queryset().filter(last_name="Doe")
class Reporter(models.Model):
@ -57,7 +55,7 @@ class Reporter(models.Model):
)
def __str__(self): # __unicode__ on Python 2
return "%s %s" % (self.first_name, self.last_name)
return f"{self.first_name} {self.last_name}"
def __init__(self, *args, **kwargs):
"""
@ -67,7 +65,7 @@ class Reporter(models.Model):
when a CNNReporter is pulled from the database, it is still
of type Reporter. This was added to test proxy model support.
"""
super(Reporter, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
if self.reporter_type == 2: # quick and dirty way without enums
self.__class__ = CNNReporter
@ -77,7 +75,7 @@ class Reporter(models.Model):
class CNNReporterManager(models.Manager):
def get_queryset(self):
return super(CNNReporterManager, self).get_queryset().filter(reporter_type=2)
return super().get_queryset().filter(reporter_type=2)
class CNNReporter(Reporter):

View File

@ -2,7 +2,7 @@ from textwrap import dedent
from django.core import management
from io import StringIO
from mock import mock_open, patch
from unittest.mock import mock_open, patch
from graphene import ObjectType, Schema, String

View File

@ -1151,9 +1151,9 @@ def test_connection_should_limit_after_to_list_length():
REPORTERS = [
dict(
first_name="First {}".format(i),
last_name="Last {}".format(i),
email="johndoe+{}@example.com".format(i),
first_name=f"First {i}",
last_name=f"Last {i}",
email=f"johndoe+{i}@example.com",
a_choice=1,
)
for i in range(6)

View File

@ -3,7 +3,7 @@ from textwrap import dedent
import pytest
from django.db import models
from mock import patch
from unittest.mock import patch
from graphene import Connection, Field, Interface, ObjectType, Schema, String
from graphene.relay import Node
@ -104,7 +104,7 @@ def test_django_objecttype_with_custom_meta():
@classmethod
def __init_subclass_with_meta__(cls, **options):
options.setdefault("_meta", ArticleTypeOptions(cls))
super(ArticleType, cls).__init_subclass_with_meta__(**options)
super().__init_subclass_with_meta__(**options)
class Article(ArticleType):
class Meta:
@ -484,7 +484,7 @@ def test_django_objecttype_neither_fields_nor_exclude():
def custom_enum_name(field):
return "CustomEnum{}".format(field.name.title())
return f"CustomEnum{field.name.title()}"
class TestDjangoObjectType:

View File

@ -2,7 +2,7 @@ import json
import pytest
from django.utils.translation import gettext_lazy
from mock import patch
from unittest.mock import patch
from ..utils import camelize, get_model_fields, GraphQLTestCase
from .models import Film, Reporter
@ -11,11 +11,11 @@ from ..utils.testing import graphql_query
def test_get_model_fields_no_duplication():
reporter_fields = get_model_fields(Reporter)
reporter_name_set = set([field[0] for field in reporter_fields])
reporter_name_set = {field[0] for field in reporter_fields}
assert len(reporter_fields) == len(reporter_name_set)
film_fields = get_model_fields(Film)
film_name_set = set([field[0] for field in film_fields])
film_name_set = {field[0] for field in film_fields}
assert len(film_fields) == len(film_name_set)
@ -54,7 +54,7 @@ def test_graphql_test_case_operation_name(post_mock):
tc._pre_setup()
tc.setUpClass()
tc.query("query { }", operation_name="QueryName")
body = json.loads(post_mock.call_args.args[1])
body = json.loads(post_mock.call_args[0][1])
# `operationName` field from https://graphql.org/learn/serving-over-http/#post-request
assert (
"operationName",
@ -66,7 +66,7 @@ def test_graphql_test_case_operation_name(post_mock):
@patch("graphene_django.utils.testing.Client.post")
def test_graphql_query_case_operation_name(post_mock):
graphql_query("query { }", operation_name="QueryName")
body = json.loads(post_mock.call_args.args[1])
body = json.loads(post_mock.call_args[0][1])
# `operationName` field from https://graphql.org/learn/serving-over-http/#post-request
assert (
"operationName",

View File

@ -2,7 +2,7 @@ import json
import pytest
from mock import patch
from unittest.mock import patch
from django.db import connection
@ -507,7 +507,7 @@ def test_handles_invalid_json_bodies(client):
def test_handles_django_request_error(client, monkeypatch):
def mocked_read(*args):
raise IOError("foo-bar")
raise OSError("foo-bar")
monkeypatch.setattr("django.http.request.HttpRequest.read", mocked_read)

View File

@ -168,11 +168,9 @@ class DjangoObjectType(ObjectType):
if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class):
raise Exception(
(
"Can only set filter_fields or filterset_class if "
"Django-Filter is installed"
)
)
assert not (fields and exclude), (
"Cannot set both 'fields' and 'exclude' options on "
@ -228,7 +226,7 @@ class DjangoObjectType(ObjectType):
if use_connection is None and interfaces:
use_connection = any(
(issubclass(interface, Node) for interface in interfaces)
issubclass(interface, Node) for interface in interfaces
)
if use_connection and not connection:
@ -255,7 +253,7 @@ class DjangoObjectType(ObjectType):
_meta.fields = django_fields
_meta.connection = connection
super(DjangoObjectType, cls).__init_subclass_with_meta__(
super().__init_subclass_with_meta__(
_meta=_meta, interfaces=interfaces, **options
)

View File

@ -65,7 +65,7 @@ def graphql_query(
return resp
class GraphQLTestMixin(object):
class GraphQLTestMixin:
"""
Based on: https://www.sam.today/blog/testing-graphql-with-graphene-django/
"""

View File

@ -26,7 +26,7 @@ class HttpError(Exception):
def __init__(self, response, message=None, *args, **kwargs):
self.response = response
self.message = message = message or response.content.decode()
super(HttpError, self).__init__(message, *args, **kwargs)
super().__init__(message, *args, **kwargs)
def get_accepted_content_types(request):

View File

@ -5,7 +5,7 @@ test=pytest
universal=1
[flake8]
exclude = docs,graphene_django/debug/sql/*,migrations
exclude = docs,graphene_django/debug/sql/*
max-line-length = 120
select =
# Dictionary key repeated

16
tox.ini
View File

@ -2,7 +2,7 @@
envlist =
py{37,38,39,310}-django32,
py{38,39,310}-django{40,41,main},
black,flake8
pre-commit
[gh-actions]
python =
@ -32,14 +32,8 @@ deps =
djangomain: https://github.com/django/django/archive/main.zip
commands = {posargs:py.test --cov=graphene_django graphene_django examples}
[testenv:black]
basepython = python3.9
deps = -e.[dev]
[testenv:pre-commit]
skip_install = true
deps = pre-commit
commands =
black --exclude "/migrations/" graphene_django examples setup.py --check
[testenv:flake8]
basepython = python3.9
deps = -e.[dev]
commands =
flake8 graphene_django examples setup.py
pre-commit run --all-files --show-diff-on-failure