Graphene API response is `None` when requesting custom Field - python-3.x

I'm trying to learn graphene-python, and have a very simple api set up to GET "hello" or "goodbye" Message graphene.Fields to all users or a specified user. I want each Message to have a field which is another Metadata, another graphene.Field. But when I make API calls which request Metadata, I get a None response from my API. The more general question is, how can I have a custom graphene.Field, which itself has variables that are graphene.Fields, and how do I resolve these variables?
Here is my sample API code:
import graphene
from uuid import uuid4
import time
users = ['A', 'B',' C']
# describes message metadata
class Metadata(graphene.ObjectType):
time_sent = graphene.Int()
uuid = graphene.String()
def resolve_timeSent(parent, info):
return int(time.time())
def resolve_uuid(parent, info):
return uuid4().hex
# basic message model
# contains message content and metadata
class Message(graphene.ObjectType):
content = graphene.String()
metadata = graphene.Field(Metadata)
def resolve_metadata(parent, info):
return Metadata()
# query schema
class Query(graphene.ObjectType):
# Return a list of message objects
helloTo = graphene.Field(Message, user=graphene.String(required=True))
helloAll = graphene.List(Message)
goodbyeTo = graphene.Field(Message, user=graphene.String(required=True))
goodbyeAll = graphene.List(Message)
###
# resolvers
###
def resolve_helloTo(self, info, user):
data = Message(f"hello, {user}", int(time.time()))
return data
def resolve_helloAll(self, info):
data = [Message(f"hello, {u}!", int(time.time())) for u in users]
return data
def resolve_goodbyeTo(self, info, user):
data = Message(f"goodbye, {user}", int(time.time()))
return data
def resolve_goodbyeAll(self, info):
data = [Message(f"goodbye, {u}!", int(time.time())) for u in users]
return data
schema = graphene.Schema(query=Query)
The query
query {
helloTo(user: "STRANGER") {
content
}
}
Returns a proper response of
{
"helloTo": {
"content": "hello, STRANGER"
}
}
But the queries
query {
helloTo(user: "jack wolf") {
content
metadata
}
}
and
query {
helloTo(user: "jack wolf") {
content
metadata {
time_sent
uuid
}
}
}
Both return None.
How can I fix this? Thank you!

Related

Best practice to call function inside route methode

I'm new to flask and in order to refactor an existing route method on a Flask API, i'm looking for the best practice to reduce it and call method inside the route method.
Acutally the route is designed like that :
#qman.route('/add_report/', methods=['POST'])
def create_report():
"""
Check if data send throught http POST request, is correct based on the report
schema and not already recorded in the table report of the DB.
:param: data from POST request
:return: Ok, valide and imported -> 201, Correct but AlreadyKnown -> 208,
InvalideScheme -> 422
"""
jsonData = request.get_json()
reportSchema = ReportSchema()
try:
data = reportSchema.load(jsonData)
except ValidationError as validation_err:
return(validation_err.messages), 422
nameReportCheck = data["report_name"]
report = Report.query.filter_by(report_name=nameReportCheck).first()
if report is None:
# Create new report
report = Report(
report_name=nameReportCheck,
hostname=data["hostname"],
status=data["status"],
date=data["date"],
nb_analysis_erreur=data["nb_analysis_erreur"]
)
db.session.add(report)
db.session.commit()
NewResult = reportSchema.dump(Report.query.get(report.reportID))
return{"message" : "Created new report" , "report" : NewResult}, 201
else :
reportAlreadyKnown = reportSchema.dump(Report.query.get(report.reportID))
return{"message" : "This report is already in the DB", "report" : reportAlreadyKnown}, 208
In the facts i would like to call a function named valid_schema(_schema, _jsondata) to check if the data send throught POST request match with my schema of model Report().
This function return a Response() object with serialized data and a 200 code if it's serialization is possible or an error that i cath inside try/except with 400 error code.
def valid_schema(_schema, _jsondata):
schema = _schema()
try:
data = schema.load(_jsondata)
except ValidationError as validation_err:
response = Response(validation_err.messages, 422)
return response
response = Response(data, 200, mimetype="application/json")
return response
Then the route method call an other function named create_report(report_data) if valid_schema(_schema, _jsondata) return report_data and 200 code in response object.
With his args, this method check if the records is not already in the DB and if is not, he create a Report() object from report_data arg and insert this one as a new record into the DB.
In fact I guess I can easily call this method inside the route function but it seem weird and there is probably an other way that I can't find, maybe decorator ?
One possibility for refactoring is the use of webargs, Flask-Marshmallow and marshmallow-sqlalchemy.
With Flask-Marshmallow you can check the input by specifying fields and validators. Webargs offers you the option of validating the defined scheme in a decorator and passing it on to the route as an argument. Using marshmallow-sqlalchemy in combination, this is immediately converted into a database model.
The following example is based on your information and gives you a brief overview of the usage. By defining your own error handler, the error messages can also be sent as JSON. Use in blueprints, views or the like is possible.
from flask import Flask
from flask import jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow.validate import Length, OneOf
from webargs.flaskparser import use_args
app = Flask(__name__)
db = SQLAlchemy(app)
ma = Marshmallow(app)
class Report(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True)
hostname = db.Column(db.String)
status = db.Column(db.String)
date = db.Column(db.DateTime)
nb_analysis_error = db.Column(db.String)
class ReportSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Report
load_instance = True
sqla_session = db.session
name = ma.Str(required=True, validate=Length(min=3))
hostname = ma.Str(required=True)
date = ma.DateTime(required=True)
status = ma.Str(required=True, validate=OneOf(['online', 'offline']))
nb_analysis_error = ma.Str(missing='Unknown Error')
#app.route('/add_report', methods=['POST'])
#use_args(ReportSchema(), location='json')
def add_report(report):
report_schema = ReportSchema()
_report = Report.query.filter_by(name=report.name).first()
if _report:
report_data = report_schema.dump(_report)
return jsonify(message='Already Reported', report=report_data), 208
else:
db.session.add(report)
db.session.commit()
report_data = report_schema.dump(report)
return jsonify(message='Created', report=report_data), 201
with app.app_context():
db.drop_all()
db.create_all()

How to filter Django serializer data?

I'm trying to filter data based on userName in JWT. This is how I've been trying to do it:
views.py:
class TestView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request):
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
request.user = user
person = Person.objects.filter(userName=request.user)
except ValidationError as v:
print("validation error", v)
return Response(person[0])
This works as I can get the Person data with print("Person: ", person[0]). The return Response(person[0]) however returns an error: TypeError: Object of type Person is not JSON serializable. I guess I could use a serializer class to return a valid JSON, am I right? I have this in my serializers.py:
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
I just don't know how to use this with my view. If I use serializer instead of person = Person.objects.filter(userName=request.user), how is the filtering supposed to be done?
Please correct me if I'm not on right track at all.
You can add serializer to view, provide query result to serializer and get serialized data:
class TestView(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = TestSerializer # add serializer
def get(self, request):
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
request.user = user
person = Person.objects.filter(userName=request.user).last()
data = self.serializer_class(instance=person).data # serialize query result
except ValidationError as v:
print("validation error", v)
raise v
return Response(data) # return serialized response
Here you can find some examples of using serializer with class based views.
Also note that your Person model might have security-sensitive fields like password, etc, so it's better to specify exact fields you need in serializer rather then use fields = '__all__'.
Are you using Django-Rest-Framework (based on your use of ModelSerializer)?
In pure Django, from the documentation on serializers, you can do something like:
from django.core import serializers
json_response = serializers.serialize("json", person[0])
return Response(json_response)
If you are using Django-Rest-Framework:
return Response(TestSerializer(person[0]).data)

How t return a json response in GET request?

Looking for some help as i am new to python and django ,I am trying to return a response in JSON of an API GET request as the data is to be returned is dictionary made from different instances of different model. but few things are not getting converted in JSON like datetime amount in decimal throwing an error saying not serializable.Also how can i get all the transfer for that perticular order in a list and each instance in a dictionary like[{},{}]
views.py
#api_view(['GET'])
def details(request,id):
if request.method=='GET':
order = get_object_or_404(Orders,id=id,applications=application)
collection = get_object_or_404(Payments,orders=id,direction='COLLECTION',is_active=True)
transfer = get_object_or_404(Payments,orders=order,direction='TRANSEFER',is_active=True)
content = {
'orders': {
"id":id,
"purpose_code":order.purpose_code,
"amount":order.amount,
'collection_payments':{
"id":collection_payments.id,
"amount":collection_payments.amount,
"datetime":collection_payments.datetime,
'transfer': [
{
"id":transfer.id,
"amount":transfer.amount,
"datetime":transfer.datetime,
}
]
}
}
}
return Response(content, status=status.HTTP_200_OK)
models.py
class Orders(models.Model):
id= models.AutoField(primary_key=True)
applications = models.ForeignKey(Applications, on_delete=models.CASCADE)
amount = models.DecimalField(max_digits=19, decimal_places=4)
class Payments(models.Model):
id = models.AutoField(primary_key=True)
orders = models.ForeignKey(Orders, on_delete=models.CASCADE)
direction = models.CharField(max_length=20,choices=[('COLLECTION','COLLECTION'),
('TRANSFER','TRANSFER')])
amount = models.DecimalField(max_digits=19, decimal_places=4, verbose_name='Price in INR')
datetime = models.DateTimeField(auto_now=False,auto_now_add=False)
is_active = models.BooleanField(default=True)

DRF use field-value instead of default (pk) in POST request

Trying send valid POST request.
It's works when i send like this:
{
"name":"but",
"sklad":1
}
Response is something like this, all is valid:
{
"name": "but",
"getsklad": "fence"
}
But when I'm trying send this, with 'fence':
{
"name":"but",
"sklad":"fence"
}
Got error:
Invalid type. The primary key value was expected, str was received.
Have this piece of code in models.py:
class Sklad(models.Model):
storages = (
('fence', 'Забор'),
('sett', 'Тротуарка'),
)
storage = models.CharField(
max_length=10,
choices=storages,
help_text='Name of storage',
default='sett'
)
class Zabor(models.Model):
sklad = models.ForeignKey(Sklad, on_delete=models.CASCADE)
name = models.CharField(max_length=10)
...
This in serializers.py:
class ZaborPostSerializer(serializers.ModelSerializer):
class Meta:
model = Zabor
fields = (
...
'name',
'sklad',
)
And Views.py part:
class ZaborView(APIView):
def post(self, request):
serializer = ZaborPostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'status': 'Success'})
else:
return Response({'status': 'Failed'})
I know what when i write '1' in request, this means 'pk'. But how I can send name rather than the id/pk?
I am beginner. So don't kick me hard for stupid questions)
Since you are providing the Sklad FOREIGN_KEY, which is unique you can filter by that value only with one type: integer, string, etc.
You can set the storage field of Sklad to the primary key:
class Sklad(models.Model):
storages = (
('fence', 'Забор'),
('sett', 'Тротуарка'),
)
storage = models.CharField(
max_length=10,
choices=storages,
help_text='Name of storage',
default='sett',
primary_key=True
)
Now you can directly filter with your string value.
Another option is to filter in the view:
from django.shortcuts import get_object_or_404
from django.http import Http404
class ZaborView(APIView):
def post(self, request):
dataDict = dict(request.data)
try:
sklad = get_object_or_404(Sklad.objects, storage=dataDict['sklad'][0])
serializer = ZaborPostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'status': 'Success'})
else:
return Response({'status': 'Failed'})
except Http404:
return Response({'status': 'Failed'})
Note that it will only work if you have only one Sklad with the given storage field.
If you have multiple ones you can pick the first one, since you do not supply a primary key and you have not other fields to filter:
from django.shortcuts import get_list_or_404
.....
sklad = list(get_list_or_404(Sklad.objects, storage=dataDict['sklad'][0]))[0]
.....
Hope it helps !

How to create dynamic rest ModelSerializer?

I'm currently working on a big code base and i need to send emails from any potential module, that conduct in Circular Dependencies issues in python
so i tried to use apps.get_model() from django.apps but when serializers are declared the models are not ready.
So i'm trying to create a factory function who build the class at runtime instead of launch time
from rest_framework.serializers import ModelSerializer
def make_serializer(model: str, fields: tuple, options = None, **nested_fields) -> ModelSerializer:
"""Generate a new serializer "On the fly", so the model does not have to be imported at launch time.
"""
model_object = apps.get_model(model)
input_fields = fields
if options is None:
options = {}
class Serializer(ModelSerializer):
class Meta:
model = model_object
fields = input_fields
def create(self, validated_data):
# we won't permit to create data from thoses serializers.
raise NotImplementedError
# configure nested serializers.
for nested_field in nested_fields.values():
for key, nested_serializer_class in nested_field.items():
serializer_instance = nested_serializer_class(**options.get(key, {}))
print(model, key, serializer_instance)
setattr(Serializer, key, serializer_instance)
return Serializer
my tests models looks like
class Band(Model):
name = Charfield(max_length=255)
class Influencer(Model):
entity = Charfield(max_length=255)
class Submission(Model):
influencer = ForeignKey(Influencer, ...)
class Campaign(Model):
band = ForeignKey('band.Band', ...)
submissions = ManyToMany(Submission)
and my testing function is:
def test():
serializer = make_serializer(
model='submission.Campaign',
fields=['pk', 'submissions', 'band'],
options={'submissions': {'many': True}},
nested_fields={
'submissions': make_serializer(
model='submission.Submission',
fields=('influencer',),
nested_fields={
'influencer': make_serializer('influencer.Influencer', ('entity',))
},
),
'band': make_serializer('band.Band', ('name',))
}
)
return serializer
instead of having my fields correly with test()(Campaign.objects.last()).data i only got "pks" and my serialiser looks like:
Serializer():
pk = IntegerField(label='ID', read_only=True)
submissions = PrimaryKeyRelatedField(many=True, queryset=Submission.objects.all())
band = PrimaryKeyRelatedField(allow_null=True, queryset=Band.objects.all(), required=False)
i except and output like:
{
"pk": 1,
"band": {
"name": "BobMarley",
},
"submissions": [
{
"influencer": {"entity": "The influencer's name"}
}
]
}
but i got a ReturnDict containing:
{
"pk": 1,
"band": 523,
"submissions": [6, 7, 8]
}
thanks for your time
well after many headcaches i've found out that i CAN'T setattr on a class after it's declaration, so i use a trick based on a dict
def make_serializer(model: str, fields: tuple, options = None, **nested_fields) -> ModelSerializer:
"""Generate a new serializer "On the fly", so the model does not have to be imported at launch time.
"""
name = f'Serializer_{model}'
model_object = apps.get_model(model)
input_fields = fields
if options is None:
options = {}
def create(self, validated_data):
# we won't permit to create data from thoses serializers.
raise NotImplementedError
class Meta:
model = model_object
fields = input_fields
attrs = {"Meta": Meta}
# configure nested serializers.
for key, nested_serializer_class in nested_fields.items():
attrs[key] = nested_serializer_class(**options.get(key, {}))
attrs['create'] = create
return type(ModelDictSerializer)(name, (ModelDictSerializer,), attrs)
the syntax is something like:
campaign_serializer = make_serializer(
model='submission.Campaign',
fields=['pk', 'submissions', 'band'],
options={'submissions': {'many': True}},
submissions=make_serializer(
model='submission.Submission',
fields=('influencer',),
influencer=make_serializer('influencer.Influencer', ('entity',))
),
band=make_serializer('band.Band', ('name',))
)
and it's working like a charm:
Serializer_submission.Campaign(<Campaign: Campaign object (9665)>):
pk = IntegerField(label='ID', read_only=True)
submissions = Serializer_submission.Submission(many=True):
influencer = Serializer_influencer.Influencer():
entity = CharField(allow_blank=True, max_length=255, required=False)
band = Serializer_band.Band():
name = CharField(max_length=255)
i hope this will help someone else

Resources