FastAPI - return Single element in from Database - list

I have 2 api endpoints. This one returns a list of all my users.
#app.get("/entity/", response_model=List[schemas.Userentity])
def read_entities(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_entities(db, skip=skip, limit=limit)
return users
def get_entities(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Userentity).offset(skip).limit(limit).all()
This works fine. See the data:
[
{
"id": "89f8a844-a30e-41b2-947d-6eb0fcadb1bf",
"email": "xxx#hotmail.com",
"first_name": "xxx",
"last_name": "xxx",
"username": "schoedeld",
"created_timestamp": "1633711745164"
},
{
"id": "abd6bb6b-ad80-431c-9f64-d9e651638f4c",
"email": "xxxsd#hotmail.com",
"first_name": "xxx",
"last_name": "xxx",
"username": "xxx",
"created_timestamp": "1633711733338"
},
{
"id": "ad0bd5ca-5aed-4d7f-a6ac-2620f2133685",
"email": "xxxx#hotmail.com",
"first_name": "fsdfs",
"last_name": "fsdfs",
"username": "testadmin",
"created_timestamp": "1633710812666"
}
]
Here I have an endpoint to return a single user:
I could return a List with a single element, but I would like to return a single value, so I updated my endpoint to this:
#app.get("/entity/{id}", response_model=schemas.Userentity)
def read_entity(id: str, db: Session = Depends(get_db)):
user = crud.get_entity(db, id=id)
return user
def get_entity(db: Session, id: str):
return db.query(models.Userentity).filter_by(id=id)
This results in the following error:
pydantic.error_wrappers.ValidationError: 6 validation errors for Userentity
response -> id
field required (type=value_error.missing)
response -> email
field required (type=value_error.missing)
response -> first_name
field required (type=value_error.missing)
response -> last_name
field required (type=value_error.missing)
response -> username
field required (type=value_error.missing)
response -> created_timestamp
field required (type=value_error.missing)
I am pretty new to FastAPI and do not understand why this error happens, can anyone help me and explain what I am doing wrong here?

You're never actually running your SQLAlchemy query, so you're returning the query itself:
return db.query(models.Userentity).filter_by(id=id)
Instead you should make SQLAlchemy run your query by either:
# raise an exception if not exactly one row is returned (so more than one or zero)
return db.query(models.Userentity).filter_by(id=id).one()
# Either one or zero rows - return `None` if user wasn't found
return db.query(models.Userentity).filter_by(id=id).one_or_none()
# Return None if user isn't found, otherwise return the first row
# (multiple rows isn't an error)
return db.query(models.Userentity).filter_by(id=id).first()
Next time attach set debugger breakpoint at the return call from your view function (or at least print the value), and you can see exactly what you're trying to return to FastAPI for serialization.

Related

Return custom payload from default response

In Django 2.0 I am using the rest_auth and currently it returns a response like
{
"token": "foo_token",
"user":{
"pk": 1,
"username": "admin",
"email": "test#test.com",
"first_name": "John",
"last_name": "Doe"
}
}
I would like to change this to return something besides the default response django provides. Something like...
{
"token": "foo_token",
"pk":1,
"username": "admin",
"somefield": "Foo Funk"
}
My urls.py look like this currently
url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
url(r'^refresh-token/', refresh_jwt_token),
url(r'^api/userlist', users.user_list),
The only place I can find to possibly change the response is in library files which I am sure is not wise to change. Any help would be great.
rest_auth allows you to change the responses by specifying your own serializer implementation in your settings.py.
For example if you wanted to customize the response for JWT authentication, then you might create:
# myapp/serializers.py
class MyCustomJWTSerializer(serializers.Serializer):
token = serializers.CharField()
pk = serializers.IntegerField()
username = serializers.CharField()
...
which you can then configure in your settings.py as:
REST_AUTH_SERIALIZERS = {
'JWT_SERIALIZER': 'myapp.serializers.MyCustomJWTSerializer'
}
More info here: https://django-rest-auth.readthedocs.io/en/latest/configuration.html

django test - how to get response data for future use

I'm running a login test like so:
def test_login_user(self):
client = APIClient()
url = reverse('rest_login')
data = {
'username': 'test',
'password': 'Welcome2'
}
response = self.client.post(url, data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
client.logout()
If I login to the app normally I see a json return like this:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6ImV2YW4iLCJleHAiOjE1MTQ2NzYzNTYsImVtYWlsIjoiZXZhbkAyOGJlYXR0eS5jb20iLCJvcmlnX2lhdCI6MTUxNDY3Mjc1Nn0.8CfhfgtMLkNjEaWBfNXbUWXQMZG4_LIru_y4pdLlmeI",
"user": {
"pk": 2,
"username": "test",
"email": "test#test.com",
"first_name": "",
"last_name": ""
}
}
I want to be able to grab that token value for future use however the response does not seem to have a data value to grab.
What I'm looking for is response.content per the official documentation
https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testing-responses
You can use response.json()['token'] to get data from the token field.
Usage as below:
token = response.json()['token'];
show error response:
response.context["form"].errors

Using views to change DRF response

I would like to alter the response from an API.
However, it does not alter the result properly. I get a KeyError: 'game'.
I am not sure why, as my API response (via URL) seems to have the value game in it. I may be getting confused with the JSON response, and the python object.
I have a sample of the API response below
results from API
{
"pk": 995,
"game": [
{
"name": "Finance",
"gamelevel": 3
},
{
"name": "Data",
"gamelevel": 1
}
]
},
views.py
class TagList(viewsets.ModelViewSet):
queryset = Task.objects.filter(game__isnull=False).all()
serializer_class = TagSortSerializer
def get_queryset(self):
test = self.queryset.values('title', 'game__name')
result = defaultdict(set)
for item in queryset:
parent = {'name': 'NoLevel_1'}
children = []
for game in item['game']:
if game['gamelevel'] == 1:
parent = game
else:
children.append((game['gamelevel'], game['name']))
result[parent['name']].update(children)
result = [
{
'name': parent,
'game_child': [
{'name': name, 'gamelevel': gamelevel} for gamelevel, name in games
],
'gamelevel': 1,
} for parent, games in result.items()
]
return result
You're using the values queryset method to get only a selection of fields from the model, and the only fields you've specified are title and tag__name. So you won't get game or any of the other keys you've used.
You certainly don't want to use values here in the first place; just do a normal query and access fields via dot lookup rather than dictionary.

How to integrate Haystack with Django Rest Framework for making GET REST API for searching?

model.py
class Item(models.Model):
name=models.CharField(max_length=50)
company=models.CharField(max_length=100)
search_indexes.py
class ItemIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
name=indexes.CharField(model_attr='name')
company=indexes.CharField(model_attr='company')
def get_model(self):
return Item
def index_queryset(self, using=None):
return self.get_model().objects.all()
serializer.py
class ItemSearchSerializer(serializers.Serializer):
text = serializers.CharField()
name=serializers.CharField()
company=serializers.CharField()
views.py
class ItemSearchViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = ItemSearchSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get_queryset(self):
request = self.request
queryset = EmptySearchQuerySet()
if request.GET.get('q', ''):
query = request.GET.get('q', '')
queryset =SearchQuerySet().filter(content=query);
return queryset
And in url.py I added :
router.register(r'searchquery', views.ItemSearchViewSet, base_name='searchquery')
Now on making GET request from postman like :
http://127.0.0.1:8000/searchquery/?q=app, I am getting the response as desired as show below.
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"text": "apple\ndjflkj",
"id": 14,
"name": "apple",
"mrp": "45.000000",
"company": "djflkj",
"imageid": "jfhi",
"edible": false,
"discount": "0.000000",
"deliverable": true,
"seller_uid": "ljhkh",
"category": "ldjhgjfdk"
},
{
"text": "app\nhuhiu",
"id": 16,
"name": "app",
"mrp": "78.000000",
"company": "huhiu",
"imageid": "iyiugiy",
"edible": false,
"discount": "45.000000",
"deliverable": true,
"seller_uid": "hjh",
"category": "hhl"
}
]
}
But the reponse time is very slow it takes around 2700 ms everytime ,
and I want to make it fast. As response of elastic search is much fast
but I don't know what I am doing wrong. Not sure but may be due to
these reasons I am getting this delay : 1) Haystack is made for
django, so on integrating it with django rest framework , it may be
getting slow. 2) I am using free Bonsai Elastic search heroku add on
and it has just 125 mb memory.
This is how I am connecting to Bonsai elastic search (setting.py)
ES_URL = urlparse('https://******#pine-1455731.us-east1.bonsaisearch.net')
print ES_URL
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': ES_URL.scheme + '://' + ES_URL.hostname + ':443',
'INDEX_NAME': 'haystack',
},
}
if ES_URL.username:
HAYSTACK_CONNECTIONS['default']['KWARGS'] = {"http_auth": ES_URL.username + ':' + ES_URL.password}
Any help will be appreciated. I am new to elastic search. I want to do elastic search to search products by name for my android application.
I even don't know whether this is the correct approach to do searching. I thought I would enter name of product I want to search and then i will send a GET request and get all the products which are related.
I did Python Profile please look it here: gist
If any one could suggest me any other way of achieving this I will appreciate your help.
The search response is slow because of this code:
def index_queryset(self, using=None):
return self.get_model().objects.all()
index_queryset is supposed to return query set, you are actually returning all model objects. This method is called for every item which is returned in search.

Show Filters and Ordering in Django Rest Framework Options Request

I'm using the Django Rest Framework I noticed on the web browseable part of the API there is a button called 'options' when clicked it shows the following...
HTTP 200 OK Vary: Accept Content-Type: text/html Allow: HEAD, GET, OPTIONS
{
"parses": [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
],
"renders": [
"application/json",
"text/html"
],
"name": "Products",
"description": "API endpoint."
}
my question is, is there anyway I could list out here all the filter options an other stuff for this url?
You can make OPTIONS return whatever you want, by overriding the .metadata() method on the view.
See here: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/views.py#L340
Update as of 2015: We now have a customizable metadata API that makes this easier: http://www.django-rest-framework.org/api-guide/metadata/
You can totally do this. Here's a custom metadata class that I've been keeping up to date here on StackOverflow. This simply lists all the available filters, their types, and their choices. It also lists the ordering fields that are available on a class:
class SimpleMetadataWithFilters(SimpleMetadata):
def determine_metadata(self, request, view):
metadata = super(SimpleMetadataWithFilters, self).determine_metadata(request, view)
filters = OrderedDict()
if not hasattr(view, 'filter_class'):
# This is the API Root, which is not filtered.
return metadata
for filter_name, filter_type in view.filter_class.base_filters.items():
filter_parts = filter_name.split('__')
filter_name = filter_parts[0]
attrs = OrderedDict()
# Type
attrs['type'] = filter_type.__class__.__name__
# Lookup fields
if len(filter_parts) > 1:
# Has a lookup type (__gt, __lt, etc.)
lookup_type = filter_parts[1]
if filters.get(filter_name) is not None:
# We've done a filter with this name previously, just
# append the value.
attrs['lookup_types'] = filters[filter_name]['lookup_types']
attrs['lookup_types'].append(lookup_type)
else:
attrs['lookup_types'] = [lookup_type]
else:
# Exact match or RelatedFilter
if isinstance(filter_type, RelatedFilter):
model_name = (filter_type.filterset.Meta.model.
_meta.verbose_name_plural.title())
attrs['lookup_types'] = "See available filters for '%s'" % \
model_name
else:
attrs['lookup_types'] = ['exact']
# Do choices
choices = filter_type.extra.get('choices', False)
if choices:
attrs['choices'] = [
{
'value': choice_value,
'display_name': force_text(choice_name, strings_only=True)
}
for choice_value, choice_name in choices
]
# Wrap up.
filters[filter_name] = attrs
metadata['filters'] = filters
if hasattr(view, 'ordering_fields'):
metadata['ordering'] = view.ordering_fields
return metadata
Put that somewhere in your project, then set your DEFAULT_METADATA_CLASS, and you should be all set, with a new key on your OPTIONS requests like so:
"filters": {
"sub_opinions": {
"type": "RelatedFilter"
},
"source": {
"type": "MultipleChoiceFilter",
"choices": [
{
"display_name": "court website",
"value": "C"
},
]
}
...more...
}
This will also display choices, mirroring the way it's handled elsewhere in DRF.