I have created a backend api with Django-rest-framework. This is my views.py -
from django.shortcuts import render
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import generics
from .models import table1, table2
from .serializers import table_1_Serializer, table_2_Serializer
from rest_framework.permissions import IsAuthenticated #new token
class table_1_View(viewsets.ModelViewSet):
queryset = table1.objects.all()
serializer_class = table_1_Serializer
http_method_names = ['get']
class table_2_View(viewsets.ModelViewSet):
queryset = table2.objects.all()
serializer_class = table_2_Serializer
http_method_names = ['get']
This is my serialisers.py
from rest_framework import serializers
from .models import table1, table2
class table_1_Serializer(serializers.ModelSerializer):
class Meta:
model = TasteCluster
fields = ('id', 'name', 'dept_id')
class table_2_Serializer(serializers.ModelSerializer):
class Meta:
model = TasteCluster
fields = ('dept_id', 'dept_name', 'dept_strength')
This is my urls.py
from django.urls import path, include
from . import views
from rest_framework import routers
router = routers.DefaultRouter()
router.register('table_1', views.table_1_View)
router.register('table_2', views.table_2_View, basename='table_2')
urlpatterns = [
path('', include(router.urls))
]
This works fine for me. Now I am trying to integrate Django-Rest-Swagger in my project.
I have installed Django-rest-swagger in the virtual environment.
I have added it to the installed apps. This is my settings.py now -
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'my_app',
'rest_framework',
'rest_framework_swagger',
#'rest_framework.authtoken',
]
This is my new urls.py -
from django.urls import path, include
from . import views
from rest_framework import routers
from rest_framework_swagger.views import get_swagger_view
from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer
schema_view = get_swagger_view(title='Test API Documentation')
router = routers.DefaultRouter(
schema_title='Pastebin API',
schema_renderers=[OpenAPIRenderer, SwaggerUIRenderer]
)
router.register('table_1', views.table_1_View)
router.register('table_2', views.table_2_View, basename='table_2')
urlpatterns = [
path('', include(router.urls)),
path('api_documentation', include('rest_framework.urls', namespace='rest_framework'))
]
But I get error when I do this.
When I try to run
python3 manage.py runserver
I get this error -
Exception in thread django-main-thread:
Traceback (most recent call last):
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 926, in _bootstrap_inner
self.run()
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "/usr/local/lib/python3.7/site-packages/django/utils/autoreload.py", line 54, in wrapper
fn(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/django/core/management/commands/runserver.py", line 117, in inner_run
self.check(display_num_errors=True)
File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 390, in check
include_deployment_checks=include_deployment_checks,
File "/usr/local/lib/python3.7/site-packages/django/core/management/base.py", line 377, in _run_checks
return checks.run_checks(**kwargs)
File "/usr/local/lib/python3.7/site-packages/django/core/checks/registry.py", line 72, in run_checks
new_errors = check(app_configs=app_configs)
File "/usr/local/lib/python3.7/site-packages/django/core/checks/urls.py", line 13, in check_url_config
return check_resolver(resolver)
File "/usr/local/lib/python3.7/site-packages/django/core/checks/urls.py", line 23, in check_resolver
return check_method()
File "/usr/local/lib/python3.7/site-packages/django/urls/resolvers.py", line 398, in check
for pattern in self.url_patterns:
File "/usr/local/lib/python3.7/site-packages/django/utils/functional.py", line 80, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "/usr/local/lib/python3.7/site-packages/django/urls/resolvers.py", line 579, in url_patterns
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
File "/usr/local/lib/python3.7/site-packages/django/utils/functional.py", line 80, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "/usr/local/lib/python3.7/site-packages/django/urls/resolvers.py", line 572, in urlconf_module
return import_module(self.urlconf_name)
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
File "<frozen importlib._bootstrap>", line 983, in _find_and_load
File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 728, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/Users/rahman/Desktop/django_exercise/04project/testapi/testapi/urls.py", line 22, in <module>
path('', include('getapi.urls')),
File "/usr/local/lib/python3.7/site-packages/django/urls/conf.py", line 34, in include
urlconf_module = import_module(urlconf_module)
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/importlib/__init__.py", line 127, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
File "<frozen importlib._bootstrap>", line 983, in _find_and_load
File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 728, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/Users/rahman/Desktop/django_exercise/04project/testapi/getapi/urls.py", line 14, in <module>
schema_renderers=[OpenAPIRenderer, SwaggerUIRenderer]
File "/usr/local/lib/python3.7/site-packages/rest_framework/routers.py", line 341, in __init__
super().__init__(*args, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'schema_title'
How can I integrate swagger with my existing Django-rest-framework app?
change you urls.py for rest framework swagger:-
from rest_framework_swagger.views import get_swagger_view
schema_view = get_swagger_view(title='API')
urlpatterns = [
path('docs/', schema_view),
]
Then in settings.py file, add the below lines:-
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
}
Related
I have an issues with the error return 1048, “Column ‘user_id’ cannot be null” and checked the code but can't find any mistake in it, does somebody else have an idea?
…/store/Serializers.py
class Meta:
model = Customer
fields = ['id', 'user_id', 'phone', 'birth_date', 'membership']
…/store/views.py
class CustomerViewSet(CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
queryset = Customer.objects.all()
serializer_class = CustomerSerializer
#action(detail=False, methods=['GET', 'PUT'])
def me(self, request):
(customer, created) = Customer.objects.get_or_create(user_id=request.user.id)
if request.method == 'GET':
serializer = CustomerSerializer(customer)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = CustomerSerializer(customer, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
…/store/models.py
class Customer(models.Model):
MEMBERSHIP_BRONZE = ‘B’
MEMBERSHIP_SILVER = ‘S’
MEMBERSHIP_GOLD = ‘G’
MEMBERSHIP_CHOICES = [
(MEMBERSHIP_BRONZE, 'Bronze'),
(MEMBERSHIP_SILVER, 'Silver'),
(MEMBERSHIP_GOLD, 'Gold'),
]
phone = models.CharField(max_length=255)
birth_date = models.DateField(null=True, blank=True)
membership = models.CharField(
max_length=1, choices=MEMBERSHIP_CHOICES, default=MEMBERSHIP_BRONZE)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def str(self):
return f'{self.user.first_name} {self.user.last_name}'
#admin.display(ordering='user__first_name')
def first_name(self):
return self.user.first_name
#admin.display(ordering='user__last_name')
def last_name(self):
return self.user.last_name
class Meta:
ordering = ['user__first_name', 'user__last_name']
…/core/serializers.py
class UserCreateSerializer(BaseUserCreateSerializer):
class Meta(BaseUserCreateSerializer.Meta):
fields = [‘id’, ‘username’, ‘password’, ‘email’, ‘first_name’, ‘last_name’]
class UserSerializer(BaseUserSerializer):
class Meta(BaseUserSerializer.Meta):
fields = [‘id’, ‘username’, ‘email’, ‘first_name’, ‘last_name’]
ERROR MESSAGE!!!
Traceback (most recent call last):
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/core/handlers/exception.py”, line 55, in inner
response = get_response(request)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/core/handlers/base.py”, line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/views/decorators/csrf.py”, line 54, in wrapped_view
return view_func(*args, **kwargs)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/rest_framework/viewsets.py”, line 125, in view
return self.dispatch(request, *args, **kwargs)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/rest_framework/views.py”, line 509, in dispatch
response = self.handle_exception(exc)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/rest_framework/views.py”, line 469, in handle_exception
self.raise_uncaught_exception(exc)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/rest_framework/views.py”, line 480, in raise_uncaught_exception
raise exc
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/rest_framework/views.py”, line 506, in dispatch
response = handler(request, *args, **kwargs)
File “/Users/rogerlooser/Documents/Privat_Roger/Schule/Python/storefront/store/views.py”, line 91, in me
(customer, created) = Customer.objects.get_or_create(user_id=request.user.id)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/models/manager.py”, line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/models/query.py”, line 935, in get_or_create
return self.create(**params), True
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/models/query.py”, line 671, in create
obj.save(force_insert=True, using=self.db)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/models/base.py”, line 812, in save
self.save_base(
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/models/base.py”, line 863, in save_base
updated = self._save_table(
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/models/base.py”, line 1006, in _save_table
results = self._do_insert(
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/models/base.py”, line 1047, in _do_insert
return manager._insert(
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/models/manager.py”, line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/models/query.py”, line 1790, in _insert
return query.get_compiler(using=using).execute_sql(returning_fields)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/models/sql/compiler.py”, line 1660, in execute_sql
cursor.execute(sql, params)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/src/django-debug-toolbar/debug_toolbar/panels/sql/tracking.py”, line 230, in execute
return self._record(self.cursor.execute, sql, params)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/src/django-debug-toolbar/debug_toolbar/panels/sql/tracking.py”, line 154, in _record
return method(sql, params)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/backends/utils.py”, line 103, in execute
return super().execute(sql, params)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/backends/utils.py”, line 67, in execute
return self._execute_with_wrappers(
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/backends/utils.py”, line 80, in _execute_with_wrappers
return executor(sql, params, many, context)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/backends/utils.py”, line 89, in _execute
return self.cursor.execute(sql, params)
File “/Users/rogerlooser/.local/share/virtualenvs/storefront-JGOY8xqq/lib/python3.10/site-packages/django/db/backends/mysql/base.py”, line 80, in execute
raise IntegrityError(*tuple(e.args))
django.db.utils.IntegrityError: (1048, “Column ‘user_id’ cannot be null”)
[26/Dec/2022 15:16:38] “GET /store/customers/me/ HTTP/1.1” 500 206743
/Users/rogerlooser/Documents/Privat_Roger/Schule/Python/storefront/store/views.py changed, reloading.
Watching for file changes with StatReloader
Performing system checks…
Maybe somebody of you guys find the issue
I'm stuck when trying to raise a validation error with FastAPI + GraphQL (graphene).
I have a resolver code:
class Query(graphene.ObjectType):
list_categories = graphene.List(CategoryGrapheneModel)
get_category = graphene.Field(CategoryGrapheneModel, id=graphene.Argument(graphene.Int, required=True))
#staticmethod
def resolve_list_categories(parent, info):
return Category.all()
#staticmethod
def resolve_get_category(parent, info, id):
try:
category = Category.find_or_fail(id)
return category
except ModelNotFound as ex:
raise Exception('Category not found')
But instead of getting 400 HTTP response with the message I got 500 Internal Server Error with traceback:
Traceback (most recent call last):
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/uvicorn/protocols/http/h11_impl.py", line 394, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/fastapi/applications.py", line 199, in __call__
await super().__call__(scope, receive, send)
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/applications.py", line 111, in __call__
await self.middleware_stack(scope, receive, send)
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
raise exc from None
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/exceptions.py", line 71, in __call__
await self.app(scope, receive, sender)
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/routing.py", line 566, in __call__
await route.handle(scope, receive, send)
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/routing.py", line 227, in handle
await self.app(scope, receive, send)
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/graphql.py", line 52, in __call__
response = await self.handle_graphql(request)
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/graphql.py", line 105, in handle_graphql
[format_graphql_error(err) for err in result.errors]
File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/graphql.py", line 105, in <listcomp>
[format_graphql_error(err) for err in result.errors]
TypeError: 'NoneType' object is not callable
Googling is not gives me a solution. So please help.
Got handled with it.
The problem was in GraphQLApp, there was calling format_graphql_errors that was undefined (None).
To solve a problem I've created a custom child class from GraphQLApp and changed format_graphql_errors to format_error from graphql.error.graphql_error package.
import json
import typing
from starlette.graphql import GraphQLApp
from starlette import status
from starlette.background import BackgroundTasks
from starlette.concurrency import run_in_threadpool
from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse, Response
from starlette.types import Receive, Scope, Send
from graphql.error.graphql_error import format_error
class CustomGraphQLApp(GraphQLApp):
async def handle_graphql(self, request: Request) -> Response:
if request.method in ("GET", "HEAD"):
if "text/html" in request.headers.get("Accept", ""):
if not self.graphiql:
return PlainTextResponse(
"Not Found", status_code=status.HTTP_404_NOT_FOUND
)
return await self.handle_graphiql(request)
data = request.query_params # type: typing.Mapping[str, typing.Any]
elif request.method == "POST":
content_type = request.headers.get("Content-Type", "")
if "application/json" in content_type:
data = await request.json()
elif "application/graphql" in content_type:
body = await request.body()
text = body.decode()
data = {"query": text}
elif "query" in request.query_params:
data = request.query_params
else:
return PlainTextResponse(
"Unsupported Media Type",
status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
)
else:
return PlainTextResponse(
"Method Not Allowed", status_code=status.HTTP_405_METHOD_NOT_ALLOWED
)
try:
query = data["query"]
variables = data.get("variables")
operation_name = data.get("operationName")
except KeyError:
return PlainTextResponse(
"No GraphQL query found in the request",
status_code=status.HTTP_400_BAD_REQUEST,
)
background = BackgroundTasks()
context = {"request": request, "background": background}
result = await self.execute(
query, variables=variables, context=context, operation_name=operation_name
)
error_data = (
[format_error(err) for err in result.errors]
if result.errors
else None
)
response_data = {"data": result.data}
if error_data:
response_data["errors"] = error_data
status_code = (
status.HTTP_400_BAD_REQUEST if result.errors else status.HTTP_200_OK
)
return JSONResponse(
response_data, status_code=status_code, background=background
)
I hope this will help others.
Based on the great answer from #Vitaly Radchik, I created more universal and compressed code by redefining the existing handle_graphql after format_graphql_error is defined.
import json, typing, inspect
from starlette import status
from starlette.background import BackgroundTasks
from starlette.requests import Request
from starlette.responses import JSONResponse, PlainTextResponse, Response
from graphql.error.graphql_error import format_error as format_graphql_error
# Get source of `handle_graphql` function
code = inspect.getsource(GraphQLApp.handle_graphql).lstrip()
class CustomGraphQLApp(GraphQLApp):
# Redefine handle_graphql function
exec(code)
I am building a tartiflette app with FastApi using tartiflette-asgi and I can't find a way of making regular FastApi authentication or dependency injection work.
The problem lies in how the tartiflette app is built and mounted. When doing
app = FastApi()
gql_app = TartifletteApp(..)
app.mount("/graphql", gql_app)
I have no way of specifying dependencies to execute my headers validation. I've tried using FastApi include_router but it simply doesn't work with TartifletteApp. I have also tried a small hack like
gql_app = TartifletteApp(..)
app.include_router(
gql_app.router,
prefix="/graphql",
# dependencies=[Depends(get_current_user)], # here I would add a token and get a user
)
I get the error
File "/usr/local/lib/python3.6/site-packages/uvicorn/protocols/http/h11_impl.py", line 389, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/usr/local/lib/python3.6/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/usr/local/lib/python3.6/site-packages/fastapi/applications.py", line 181, in __call__
await super().__call__(scope, receive, send) # pragma: no cover
File "/usr/local/lib/python3.6/site-packages/starlette/applications.py", line 111, in __call__
await self.middleware_stack(scope, receive, send)
File "/usr/local/lib/python3.6/site-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/usr/local/lib/python3.6/site-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.6/site-packages/starlette/exceptions.py", line 82, in __call__
raise exc from None
File "/usr/local/lib/python3.6/site-packages/starlette/exceptions.py", line 71, in __call__
await self.app(scope, receive, sender)
File "/usr/local/lib/python3.6/site-packages/starlette/routing.py", line 566, in __call__
await route.handle(scope, receive, send)
File "/usr/local/lib/python3.6/site-packages/starlette/routing.py", line 227, in handle
await self.app(scope, receive, send)
File "/usr/local/lib/python3.6/site-packages/tartiflette_asgi/_endpoints.py", line 84, in dispatch
graphiql = get_graphql_config(request).graphiql
File "/usr/local/lib/python3.6/site-packages/tartiflette_asgi/_middleware.py", line 18, in get_graphql_config
config = conn["graphql"]
File "/usr/local/lib/python3.6/site-packages/starlette/requests.py", line 68, in __getitem__
return self.scope[key]
KeyError: 'graphql'
I could implement the headers validation as a graphql middleware but I was hoping I could do it at theFastApi level so it applies to every endpoint.
Any suggestions on how to solve this?
Create a basic auth first, then add it to Dependencies instead of getting the current user this will add a Basic authentication to your endpoint
from fastapi.security import HTTPBasic, HTTPBasicCredentials
security = HTTPBasic()
app.include_router(
gql_app.router,
prefix="/graphql",
dependencies=[Depends(security)],
)
I have managed to solve this without tartiflette-asgi. The solution is posted here
It looks like:
import os
import json
import typing
from starlette.background import BackgroundTasks
from starlette.datastructures import QueryParams
from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse, Response
from tartiflette import Engine
class GraphQLApp:
def __init__(self, app, modules, schema_path=None, error_coercer=None):
self.engine = Engine(
sdl=schema_path or os.path.join(os.path.dirname(__file__), "schema"),
modules=modules,
error_coercer=error_coercer,
)
app.on_event("startup")(self._cook)
async def _cook(self):
await self.engine.cook()
def _build_context(self, **kwargs):
return kwargs or {} # add custom logic when needed here
async def _get_response(self, request: Request, data: QueryParams, context: dict) -> Response:
try:
query = data["query"]
except KeyError:
return PlainTextResponse("No GraphQL query found in the request", 400)
def _format_error(error: typing.Any) -> dict:
import ast
try:
return ast.literal_eval(str(error))
except ValueError:
return {"message": "Internal Server Error"}
background = BackgroundTasks()
context = {"req": request, "background": background, **self._build_context(**context)}
result: dict = await self.engine.execute(
query,
context=context,
variables=data.get("variables"),
operation_name=data.get("operationName"),
)
content = {"data": result["data"]}
has_errors = "errors" in result
if has_errors:
content["errors"] = [_format_error(error) for error in result["errors"]]
status = 400 if has_errors else 200
return JSONResponse(content=content, status_code=status, background=background)
async def process_request(self, request: Request, context: dict = None) -> Response:
content_type = request.headers.get("Content-Type", "")
if "application/json" in content_type:
try:
data = await request.json()
except json.JSONDecodeError:
return JSONResponse({"error": "Invalid JSON."}, 400)
elif "application/graphql" in content_type:
body = await request.body()
data = {"query": body.decode()}
elif "query" in request.query_params:
data = request.query_params
else:
return PlainTextResponse("Unsupported Media Type", 415)
return await self._get_response(request, data=data, context=context or {})
So I can just do
app = FastApi()
gql_app = GraphQLApp(app)
#app.post("/graphql")
async def graphql_ninja(request: Request):
return await gql_app.process_request(request)
The Django Rest Framework exception handler doesn't seem to be working for me. ValidationErrors are getting turned into 500 responses.
When a ValidationError is raised, it doesn't get converted into a 400.
Traceback (most recent call last):
File "/example/.local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/example/.local/lib/python3.6/site-packages/django/core/handlers/base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/example/.local/lib/python3.6/site-packages/django/core/handlers/base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/example/.local/lib/python3.6/site-packages/django/views/generic/base.py", line 71, in view
return self.dispatch(request, *args, **kwargs)
File "/example/.local/lib/python3.6/site-packages/django/views/generic/base.py", line 97, in dispatch
return handler(request, *args, **kwargs)
File "/example/app/views.py", line 25, in post
serializer.is_valid(raise_exception=True)
File "/example/.local/lib/python3.6/site-packages/rest_framework/serializers.py", line 242, in is_valid
raise ValidationError(self.errors)
rest_framework.exceptions.ValidationError: {'email': [ErrorDetail(string='Enter a valid email address.', code='invalid')]}
[26/Feb/2020 20:44:54] "POST /login/ HTTP/1.1" 500 84465
In settings.py I have
INSTALLED_APPS = [
# ...
'rest_framework',
]
But I get the same behavior whether I have rest_framework in my INSTALLED_APPS or not.
Adding this to settings.py has no effect either:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'bla',
}
Am I missing something obvious?
It turns out it depends on the view you're sub-classing while throwing the exception.
I was subclassing View, which did not work:
from rest_framework import views
from rest_framework.exceptions import ValidationError
class LoginView(views.View):
def post(self, request):
raise ValidationError()
Switching to using GenericAPIView fixed my issue:
from rest_framework import generics
from rest_framework.exceptions import ValidationError
class LoginView(generics.GenericAPIView):
def post(self, request):
raise ValidationError()
Consider the code below (in urls.py):
router = DefaultRouter()
router.register('my-endpoint', MyViewSet, basename='mybasename')
urlpatterns = [
path('api/v1/', include(router.urls)),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
This code is valid but the 2 endpoints (TokenObtainPairView and TokenRefreshView) not appear in the Root API (DRF)
Then, I tried to register both endpoints of django-rest-frakmework-simplejwt on the router:
router = DefaultRouter()
router.register('my-endpoint', MyViewSet, basename='mybasename'),
router.register('api/token/', TokenObtainPairView.as_view(), basename='token_obtain_pair')
router.register('api/token/refresh/', TokenRefreshView.as_view(), basename='token_refresh')
# Other way:
# router.register('api/token/', TokenObtainPairView, basename='token_obtain_pair')
# router.register('api/token/refresh/', TokenRefreshView, basename='token_refresh')
urlpatterns = [
path('api/v1/', include(router.urls))
]
And then when I run, the following error occurs:
File "/home/sidon/dev/boticario-teste/boticashback/boticashback/urls.py", line 32, in <module>
path('api/', include(router.urls)),
File "/home/sidon/miniconda3/envs/botcash/lib/python3.8/site-packages/rest_framework/routers.py", line 78, in urls
self._urls = self.get_urls()
File "/home/sidon/miniconda3/envs/botcash/lib/python3.8/site-packages/rest_framework/routers.py", line 339, in get_urls
urls = super().get_urls()
File "/home/sidon/miniconda3/envs/botcash/lib/python3.8/site-packages/rest_framework/routers.py", line 237, in get_urls
routes = self.get_routes(viewset)
File "/home/sidon/miniconda3/envs/botcash/lib/python3.8/site-packages/rest_framework/routers.py", line 153, in get_routes
extra_actions = viewset.get_extra_actions()
AttributeError: type object 'TokenObtainPairView' has no attribute 'get_extra_actions'
Do not use router for token paths. Use them as separate url paths, and it will work. Use your view set in the router path, and put the token paths in urlpatterns.