Please consider to check Django Ninja
π« - [email protected]
π¨ Fast, Async-ready, Openapi, type hints based framework for building APIs
Home Page: https://django-ninja.dev
License: MIT License
Please consider to check Django Ninja
π« - [email protected]
Hi, @vitalik. Is it a bug?
When i send request with empty body, i getting this error
"detail": [
{
"loc": [
"body",
"data"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
It happens because when we get None from cls.get_request_data(request, path_params)
this code always crash
if data is None:
return cls()
Hi,
This is not an issue, but rather a proposal.
I came by Django-Ninja a few days ago while surfing the Internet. This is beyond great, I would love to contribute.
An essential feature IMHO is to develop a REST implementation of the Django authentication system with Ninja instead of DRF. I have used Djoser before, which exactly that but implemented with DRF.
Is that in your plans? What can I do to help?
the docs state that the response schema gets documented but im unable to see it.
am i doing something wrong?
Hello,
I am a beginner in Django Ninja, I would like to know if there are tutorials on how to do a simple CRUD in Django Ninja?
greetings,
I am trying to return list of objects but it is giving me error please tell me how to do that
Thanks for this amazing tool. Quick question: how do you set up an API route that allows users to upload images?
In this moment ninja use decorator param to find response schema.
@api.post('/login', response={200: Token, 401: Message, 402: Message})
@api.get("/tasks", response=List[TaskSchema])
@api.post("/users/", response=UserOut)
I think we should use python annotation like as request schemas
def login(request, payload: Auth) -> {200: Token, 401: Message, 402: Message}):
def tasks(request) -> List[TaskSchema]:
def create_user(request, data: UserIn) -> UserOut:
We can take this schema funcion.__annotations__.get('return')
@vitalik what you think about this enhancement? I want take on this task!
from asgiref.sync import sync_to_async
from ninja import NinjaAPI, Schema
from ninja.security import APIKeyQuery
from .models import Client
api = NinjaAPI()
class ApiKey(APIKeyQuery):
param_name = "key"
async def authenticate(self, request, key):
try:
return await sync_to_async(Client.objects.get)(key=key)
except Client.DoesNotExist:
pass
class Test(Schema):
msg: str
@api.get("", response=Test, auth=ApiKey())
async def test(request):
return 200, {"msg": "hello!"}
The API is called with a non-existing API key.
What should happen: Ninja should return a 401 response.
What actually happens: Returns 200.
Implement _run_checks and _run_authentication as async methods in AsyncOperation.
class AsyncOperation(Operation):
def __init__(self, *args, **kwargs):
if django.VERSION < (3, 1): # pragma: no cover
raise Exception("Async operations are supported only with Django 3.1+")
super().__init__(*args, **kwargs)
self.is_async = True
async def _run_checks(self, request):
"Runs security checks for each operation"
# auth:
if self.auth_callbacks:
error = await self._run_authentication(request)
if error:
return error
# csrf:
if self.api.csrf:
error = check_csrf(request, self.view_func)
if error:
return error
async def _run_authentication(self, request):
for callback in self.auth_callbacks:
result = await callback(request)
if result is not None:
request.auth = result
return
return Response({"detail": "Unauthorized"}, status=401)
async def run(self, request, **kw):
error = await self._run_checks(request)
if error:
return error
values, errors = self._get_values(request, kw)
if errors:
return Response({"detail": errors}, status=422)
result = await self.view_func(request, **values)
return self._create_response(result)
Above example will then return 401 on an invalid key as expected.
Hi, thank you for awesome framework. We really miss there tags=[] ability for operations. It will be fine having similar like in fastapi. Other-way all requests grouped under 'default' in /api/docs.
( https://swagger.io/docs/specification/grouping-operations-with-tags/ )
Thanks
I was trying to add authentication on routers but it's not working. Below is the code snippet how I am implement the authentication.
api = NinjaAPI(version="1.0.0", auth=TokenAuthentication())
api.add_router('/', account_router)
from ninja import Router
router = Router()
@router.post("/login")
def login(request):
pass
@router.post("/register")
def register(request):
pass
I can see Ninja does quite good job in casting requests into Schemas together with type casting.
Can I also return some object that will then be casted onto some other defined Schema ie to filter off some sensitive attributes?
Something like response_model functionality from Fastapi:
https://fastapi.tiangolo.com/tutorial/response-model/
f```
rom ninja import Schema
class UserIn(Schema):
username: str
password: str
class UserOut(Schema):
id: int
username: str
@api.post("/users/", response=UserOut)
def create_user(request, data: UserIn):
user = User(username=data.username)
user.set_password(data.password)
user.save()
return user
This Above example is taken from the Docs, as I am stuck in a Case in which there is no chance of always passing the Instance to the return , As If we pass @api.post("/users/", response=UserOut) Userout then it will always waiting for the user instance to return , But in my case its may be or may not be.
Here Below is my Use Case which I want to achieve
class UserLogin(Schema):
email: str
password: str
class UserOut(Schema):
id: int
username: str
email: str
phone_number : int
is_active: bool
@authapi.post("auth/login", response=UserOut)
def login(request, user_login: UserLogin):
if "@" in user_login.email:
user = User.objects.get(email__iexact=user_login.email)
if hasattr(user, 'teacher'):
if request.META.get('HTTP_CLIENT_NAME') == 'teacher':
if not user.is_active:
try:
message = "An email containing verification instructions was sent"
return {"message": message, "code": 401, "data": {'is_active': False}, "error_message": ""}
except:
pass
if user.teacher.is_verified == False:
message = "Admin verification is required, please wait."
return {"message": message, "code": 400, "data": {}, "error_message": message}
return user
as In my Case there will be a chance of Coming error message also, But if we pass userout then Complusorily they want only User instance to be return , Showing me the error when we are using Userout and the string message comes:-
ydantic.error_wrappers.ValidationError: 5 validation errors for Response
response -> id
field required (type=value_error.missing)
response -> username
field required (type=value_error.missing)
response -> email
field required (type=value_error.missing)
response -> phone_number
field required (type=value_error.missing)
response -> is_active
field required (type=value_error.missing)
Please help me out
Hi, i have some humble ideas.
Now we have routes like @router.get("/check_model", response={200: UserModel, 202: UserModel})
It's case from tests.
And i'm really frustrated that we must duplicate Schema (UserModel) for many cases.
I suggest allow functions as key to multiple response.
we can do it cause functions is immutable
Examples of enhancement usage:
@router.get("/check_model", response={lambda x: x >= 200 and x < 300: UserModel})
or
def response_200(n):
return n >= 200 and n < 300
from helpers import response_200
@router.get("/check_model", response={response_200: UserModel})
Also i want to set default response schema, for example for errors
We can use functions for this:
@router.get("/check_model", response={response_200: UserModel, lambda x: True: ErrorModel})
It will work because we check status_codes sequentially from left to right.
or we can add special status like 0
or 'default'
I really enjoy using Schemas as you've defined and configured them for this project. However, I'm running into some issues when using a Schema with a list of strings in my query parameters. I've documented an example below of this issue.
I've got my filters as such...
class Filters(Schema):
tags: List[str] = []
def __init__(self, **data):
print(data)
super().__init__(**data)
I've used the init method for debugging, which I'll address in a bit.
My view function as...
@api.get("")
def search_tagged_objects(request, filters: Filters = Query(...)):
pass
The Swagger documentation is generated correctly, yay! But unfortunately, when I actually pass in a tag (or multiple tags) to the filter (either through the Swagger documentation or a request URL), I'm met with the error displayed below.
When I print out the data passed to the Filters object, I'm met with the output...
{'tags': 'Tag A'}
... which is not the list of strings that I wanted to pass in.
The temporary fix that I've gotten working thus far is to NOT use a Schema model, and instead write the function definition as such...
def search_tagged_objects(request, tags: List[str] = Query(...)):
And while this totally works, it seems a bit messy. I've got 7 filters that I'd like to incorporate into a single function, and I've got pagination parameters (basically limit and offset) that I'd like to use for this function, as well as other functions. The issue seems to be solely with using a list inside an object in the query parameters.
I checked the tests and it doesn't look like this use case is covered (but correct me if I'm wrong). Let me know if this is something you'd like me to look at more, I'm happy to write some tests and submit a PR. I really do like how the documentation generated is correct (for both using the Filters and function-inline specifications), I'd like to add some small quality of life changes to this though.
I have found all the examples in the document are function-based view. As long as I am more accustomed to write code with class-based view rather than function-based view, how can I use class-based view with Django-Ninja?
Thank you!
i used a nested schema to get data of the many to many field but it does not seem to work.
Dear @vitalik , this comment also not an issue, but more look like a question or proposal. But I really happy with using django-ninja, and faced to a problem, when it's required to add some key values to response header. Instead I've added middleware app to do that, but it breaks some logic for me.
Shortcuts are useful to follow the DRY philosophy. For that, I think we must create a shortcuts file to overwrite the commonly-used functions in Django as get_object_or_404.
Here I let my contribution :)
from django.http import Http404
from django.shortcuts import get_object_or_404 as dj_get_object_or_404
def get_object_or_404(klass, *args, **kwargs):
try:
return dj_get_object_or_404(klass, *args, **kwargs)
except Http404:
return Response({}, status=404)
Great app!
What is the development status?
Hi, @vitalik, can u please add the "hacktoberfest" topic to django-ninja repo to participate in hacktoberfest?
Is there a preferred workflow of writing endpoint tests with this library?
first off, thanks for this great tool. I'm new into creating APis so please bear with me.
I'm building an API A from off another API B. I have to clone this API A for an academic activity.
so when API A is called :
/api/v1/login?email={email}&password={password}
it results in proper JSON but when you call it with something like:
/api/v1/login?email={email}
it results in specific JSON response that reads:
{"response":{"status":202,"message":"Invalid email & credentials"}}
so far, I have setup API calls, and it is working fine for when I make correct call. However, when I make call with missing parameter like this:
http://127.0.0.1:8000/api/v1/login?email`
it always returns the default JSON response :
{"detail": [{"loc": ["query", "email"], "msg": "field required", "type": "value_error.missing"}]}
so how do I change the above response to match with the error response of API A whenever it is called with a parameter missing?
thank you so much for your time.
BTW here is my code so far:
from django.contrib import admin
from django.urls import path
from ninja import NinjaAPI
from ninja import Schema
import requests
api = NinjaAPI()
version = "v1"
@api.get("/login")
def login(request, email:str, password:str):
response = requests.get(f'some_host_address/api/v1/login?email={email}&password={password}')
mydata = response.json()
return {"result": mydata}
urlpatterns = [
path('admin/', admin.site.urls),
path(f"api/{version}/", api.urls),
]
I am trying to Validate the CSRF-Token on every Request Please Guide me how to do that in this API framework
Can you explain why one should use django-ninja and what cases of using it?
From the docs, it's unclear what benefits django-ninja
offer. For example:
Read full proposal here - http://django-ninja.rest-framework.com/proposals/cbv/
To allow incapsulate common logic into class methods
from ninja import Router
router = Router()
@router.path('/project/{project_id}/tasks')
class Tasks:
def __init__(self, request, project_id=int):
user_projects = request.user.project_set
self.project = get_object_or_404(user_projects, id=project_id))
self.tasks = self.project.task_set.all()
@router.get('/', response=List[TaskOut])
def task_list(self, request):
return self.tasks
@router.get('/{task_id}/', response=TaskOut)
def details(self, request, task_id: int):
return get_object_or_404(self.tasks, id=task_id)
Read full proposal here - http://django-ninja.rest-framework.com/proposals/models/
To make it possible to convert django models to ninja Schemas:
from ninja import ModelSchema
class UserOut(ModelSchema):
class Meta:
model = User
fields = ['email', 'first_name', 'last_name']
This feature will definitely land soon to production code
But there is just one open question:
configuration class - should it be Meta
(django way) or Config
(pydantic way) ?
Hey, I just found this project and wanted to say that it's exciting to have such a fast way to create an API in Django!
I'm trying it out on a small side project and ran into an issue with the types/casting for a Django ImageFieldFile. Calling str
on the field gives the correct result (ie, it implements __str__
), but setting the type for the field to str
produces a ValidationError
(response -> 0 -> photo str type expected (type=type_error.str)
).
I'm not sure if this is something which hasn't been implemented or is a shortcoming in my understanding of pydantic or python type hinting (which are new to me), but I wasn't able to find anything relevant in the docs and my other attempts to understand the issue.
Hi,
I'm pretty new to API programming so please bear with me.
I am trying to define login endpoint which will take username and password as form parameters, like this:
@router.post("/token")
def get_token(request, username: str = Form(...), password: str = Form(...)):
return {username: password}
Generated API docs will display endpoint and form parameters correctly, however when I try using the endpoint through the docs it will generate curl like this:
curl -X POST "http://localhost:8000/api/token" -H "accept: */*" -d ""
and the error I get is code 422 Undocumented | Error: Unprocessable Entity
with response:
{
"detail": [
{
"loc": [
"form",
"username"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"form",
"password"
],
"msg": "field required",
"type": "value_error.missing"
}
]
Looks like the parameters and their values are not passed to the generated API call.
If I do a manual curl call like this:
curl -X POST -F 'username=myusername' -F 'password=mypassword' http://localhost:8000/api/token
everything works as expected.
Thanks in advance!
I'd like to be able to do something like:
@router.post('/foo/', name='bar')
Which would allow me to do reverse('api-1.0.0:bar')
in order to dynamically get the URL without needing to hard code it. It looks like if this line is updated to pass in an optional name, then I think it could work.
If I'm reading the code correctly, all the METHOD
s (get
/post
/etc) would need to be updated to take an optional name
(or url_name
or something) argument. Additionally, api_operation
and add_api_operation
would need to be updated to take the same optional argument. Lastly, I think something like how self.operations
is being updated in add_api_operation
would need to be added to tell urls_paths
what the names are.
I don't know how off base I am here. Let me know what you think.
Super slick library by the way. Big fan.
Edit:
Alternatively, some kind of auto-generated name based on the app the end point is in + the path would also work.
If we have @router.post('foo/bar/')
in an app called baz
, an auto-name of something like baz_foo_bar
so we could reverse('api-1.0.0:baz_foo_bar')
could be pretty slick. This solution has the by product of not needing to update nearly as much code.
I'm currently using DRF and a third-party package called djangorestframework-camel-case (DRFCC). DRFCC provides "Camel case JSON support for Django REST framework", which simply outputs snake case to camel case, and vice versa for input. This is really helpful for my counterpart on the front-end/React side of our application, so he doesn't need to camel case fields himself. DRFCC has 348 stars at this point, so I believe this is a fairly popular use case.
An implementation of this seems fairly straightforward using Pydantic's alias generators, and I see that you've already configured some specific Pydantic Config options for your Schema class.
Is this something you'd be interested in having included as an option to your Schema class? I'll be implementing this myself regardless, but I'm happy to discuss this in greater detail and submit a PR, I can also document this addition to the PR.
Hi,
Great work, Pydantic with its standard lib type annotations is surely way forward for APIs!
Do you think class based views would be something easily implemented to Ninja?
I've seen couple of gh issues on this for Fastapi, however it hasn't arrived to the package yet.
But there's fastapi-utils extension that does the job:
https://fastapi-utils.davidmontague.xyz/user-guide/class-based-views/
Do you think something similar could be done here?
Hi! I need to hide {"detail": errors} on prod env. It's error after _get_values(request, kw)
I think, we should add some param to NinjaAPI, like hide_errors, to hide detail in error.
@vitalik do you approve this solution?
Read full proposal here - http://django-ninja.rest-framework.com/proposals/responses/
Allow to define multiple response schemas for API operation
See also #4 discussion
C:\thirds\Python37\Lib\site-packages\ninja\templates\ninja\swagger.html
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js"></script>I've glanced over various pages of documentation, and I've noticed a few spelling/grammar mistakes. Would you like me to make a PR for fixes?
This project is amazing work that you did, but do you have any plan for an Athentication system for generating tokens and resetting the user's password and etc?, I mean for now should I do all of it manually right?
Hello @vitalik, I'm having lots of fun playing around with Django Ninja, congratulations it's great!
One thing I was wondering: is it currently impossible to accommodate file uploads within the framework?
There seems to me, to be no obvious way to define a file object in a request body's schema.
Apologies in advance, if I'm missing something obvious, and if anyone has a way of doing so, please let me know! π
Hi vitalik,
This project is magical, I cannot imagine myself developing APIs using anything else from now on, I'm actually in the process of migrating two projects to Ninja already! π
I have an enhancement proposal for the router, I repetitively configure each route for auth, tags, response, etc. if it is possible to configure these generally in the router.
Examples:
instead of
staff_router = Router()
@staff.get('/orders',
tags=['staff'],
auth=IsAuthorized(staff_auth),
response={200: List[InventoryOrderSchemaOut]}
)
@staff.get ...
we do
staff = Router(
tags=['staff'],
auth=IsAuthorized('STAFF'),
)
@staff.get('/orders',
response={200: List[InventoryOrderSchemaOut]}
)
I'm a new user to your library β just discovered on Reddit a few days ago. Apologies if this was answered elsewhere.
OpenAPI supports a parameter called operationId
, which provides an API-unique discriminator for an endpoint. There is annoyingly no hotlink to the relevant section; OpenAPI's docs could use improving:
https://swagger.io/docs/specification/paths-and-operations/
The ID isn't particularly useful for humans (it doesn't appear at all in the docs), but for a programmable client it is particularly useful as it avoids all the clutter of needing to deal with URL templates, method, and headers β you can just provide the operationId along with any arguments the endpoint requres.
If it's not too bold to point out, I use these today in FastAPI and would love to see β or bring β this support in/to django-ninja, if there's enough interest (and maybe a little help; I'm not a fantastic coder).
Any plans to add custom exception handlers for authentication and return a custom response? Here's a reference: https://indominusbyte.github.io/fastapi-jwt-auth/usage/optional/
@app.exception_handler(AuthJWTException)
def authjwt_exception_handler(request: Request, exc: AuthJWTException):
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.message}
)
Hi @vitalik ,
first of all congrats for your work in this project! I really like it. I've a simple question, sorry if this is a trivial one! ;)
In Django Rest Framework we can integrate JWT authentication quite easily using this https://github.com/SimpleJWT/django-rest-framework-simplejwt.
Is there something similar for django-ninja? I saw that in another issue (#9) you mentioned JWT, but I don't understand how it could be done in practice.
Thank you very much for your help and keep up the good work!
I can't tell for sure from the docs whether this is as I think it is but here goes:
I love this project for the pydantic integration and not forcing me to choose between that and django.
For our new project at my company I'd prefer me and my team to write our new service with sync style code. gunicorn with gevent workers seems ideal for this. It monkey patches the most commonly used libraries for I/O such that even though we're not writing explicit async code, we have non-blocking I/O in the most important places. Sure, this is not the style to get every last drop of performance. It doesn't lend itself to sending a bunch of requests in parallel for instance. But for this project that's not a concern so I'd prefer not to add the overhead of forcing people think async and not risk a PR with a missing await
that causes our event loop to block.
Have I described the above correctly? If so, is there a neat monkey patching solution for such that I can call a database in a sync method without worrying?
I'm new to python so I might be skipping over something that's obvious to anyone who knows its newer async abilities.
Hi! Thanks a lot for building this project. It looks really interesting.
I have seen that your docs says that
Type hints and automatic docs let's you focus only on business logic
But, there are some things to improve in this field:
py.typed
file in the root, example: https://github.com/dry-python/returns/blob/master/returns/py.typed Without this file mypy
won't be able to know that sources are typedmypy
support! This is very important for end users of typed libs. I was not able to find any mypy
references in the CIdjango-stubs
support. I have seen that you have troubles typing HttpRequest
and HttpResponse
. That's what django-stubs
can solve! https://github.com/typeddjango/django-stubsCheers! π
This one also not issue, but a proposal.
It will be very useful for response schema validation in jsonapi standards.
Is there a way to emit the underlying column value (the primary key) for a ForeignKey reference, instead of a nested class?
isAuthenticated token generation is by done by user manually by creating tables? is there any predefined method to get auth token or it is to be done manually
Type a message
quoting from docs :
from someapp.models import Client
"and find a Client in database that corresponds to this key. The Client instance will be set to request.auth attribu"
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.