Flask and handeling a Zeep response with datetime - python

I'm calling a SOAP WebService using Zeep, and it returns a JSON-like response with a datetime object. I want to write a micro-service using Flask and return proper JSON response. However, Flask complains that:
TypeError: Object of type datetime is not JSON serializable
from flask import Flask
from flask_restful import Resource, Api
import datetime
app = Flask(__name__)
api = Api(app)
class foo(Resource):
def get(self, x):
zeepResponse = {
'Response': {
'Number': x,
'DateTime': datetime.datetime(2020, 1, 1, 0, 0, 0),
'Other': None
}
}
return zeepResponse
api.add_resource(foo, '/post/<int:x>')
if __name__ == '__main__':
app.run(debug=True)
To test from the command line, simply run:
% curl http://localhost:5000/post/911
Would you guide me how to convert zeepResponse (and the datetime specifically) to a proper JSON serializable structure?

Calling json.dumps(zeepResponse, default=str) seems to fix my problem. From Stack Overflow 11875770
from flask import Flask
from flask_restful import Resource, Api
import datetime
import json
app = Flask(__name__)
api = Api(app)
class foo(Resource):
def get(self, x):
zeepResponse = {
'Response': {
'Number': x,
'DateTime': datetime.datetime(2020, 1, 1, 0, 0, 0),
'Other': None
}
}
return json.loads(json.dumps(zeepResponse, default=str))
api.add_resource(foo, '/post/<int:x>')
if __name__ == '__main__':
app.run(debug=True)

Related

Pass a dataframe in an API call with FastAPI

I have a file called main.py as follows:
from typing import List, Dict
from fastapi import Body, FastAPI
import pandas as pd
#app.post("/")
async def read_df(data: List[Dict], id_process: int = Body(...)):
return {"id_process": id_process, "data": data}
Now, if I run the code for the test, saved in the file test_main.py, that is:
from fastapi.testclient import TestClient
from main import app
import pandas as pd
import json
df = pd.DataFrame([[0.2, 0.8], [0.9, 0.1]], columns=["c1", "c2"])
json_df = json.loads(df.to_json(orient="records"))
client = TestClient(app)
def test_read_df():
response = client.post(
"/",
json={
"id_process": 1,
"data": json_df # (1)
}
)
return response.json()
print(test_read_df())
I get, as excepted:
{'id_process': 1, 'data': [{'c1': 0.2, 'c2': 0.8}, {'c1': 0.9, 'c2': 0.1}]}
Is this the standard way to pass a dataframe in an API call? Or is there a way to directly pass the dataframe df in # (1)? My goal is always to return the dictionary above.
The Process seems to be better, There are some modules like "beneath". Yet this method has the less execution time.
I suggest to continue with this.

How to post a list of pydantic objects that contain datetime property by using aiohttp to fastapi endpoint?

I have a small web server:
# app.py
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
id: int
name: str
#app.post("/items")
async def items_list(items: List[Item]):
return items
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
and a python file that posts to the endpoint:
# req.py
import asyncio
from typing import List
import datetime as dt
import aiohttp
from app import Item
async def main():
data = [
Item(id=1, name='A').dict(),
Item(id=2, name='B').dict()
]
async with aiohttp.ClientSession() as session:
async with session.post(
'http://localhost:8000/items',
json=data
) as response:
print(f'response: {await response.json()}')
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
It works, I can get an output like:
response: [{'id': 1, 'name': 'A'}, {'id': 2, 'name': 'B'}]
If I set data as (no .dict()):
data = [
Item(id=1, name='A'),
Item(id=2, name='B')
]
it doesn't work because Item is not json serializable.
My goal is to post list of items to the fastapi endpoint.
This working example doesn't work if I extend Item as this:
class Item(BaseModel):
id: int
name: str
created_at: dt.datetime
created_at is a datetime and even I em using Items(..).dict() it is not json serializable.
Funny thing is that if I create an Item as:
Item(id=1, name='A',created_at=dt.datetime.utcnow()).json()
its json is perfect:
{"id": 1, "name": "A", "created_at": "2021-12-15T21:10:36.077435"}
but as aiohttp session.post(json...) uses non pydantic json encoder, Item is not json serializable.
I tried to create a new pydantic object:
class ItemsList(BaseModel):
data: List[Item]
end set it as:
data = [
Item(id=1, name='A', created_at=dt.datetime.utcnow()),
Item(id=2, name='B', created_at=dt.datetime.utcnow())
]
data_list = ItemsList(data=data)
Again, pydantic is clever enough to produce proper json:
data_list.json()
{"data": [{"id": 1, "name": "A", "created_at": "2021-12-15T21:17:34.368555"}, {"id": 2, "name": "B", "created_at": "2021-12-15T21:17:34.368555"}]}
but I am not sure how to send such json using aiohttp post.
My question is: How to by using aiohttp post a list of pydantic objects that contain datetime property to fastapi endpoint?
I would be satisfied with sending/receiving list of itmes ([Item, Item, ... Item])
One way to do this is simply pass your Pydantic JSON string as the raw request body:
# Using the "data_list" Pydantic object in one of your examples.
async with session.post(
'http://localhost:8000/items',
# Pass the JSON string as `data`.
data=data_list.json(),
# Manually set content-type.
content_type="application/json"
) as response:
print(f'response: {await response.json()}')
That way you bypass the automatic serialization that isn't compatible with Pydantic.

Runtime.MarshalError in python

I am Getting this error. I am executing code of aws lambda function using python 3.7 to know quicksight dashboard version. Thanks in advance!
errorMessage: "Unable to marshal response: Object of type datetime is not JSON serializable",
errorType : "Runtime.MarshalError"
Code-
import boto3
import time
import sys
client = boto3.client('quicksight')
def lambda_handler(event, context):
response = client.list_dashboard_versions(AwsAccountId='11111', DashboardId='2222',MaxResults=10)
return response
I quick fix could be:
import boto3
import time
import sys
import json
client = boto3.client('quicksight')
def lambda_handler(event, context):
response = client.list_dashboard_versions(AwsAccountId='11111', DashboardId='2222',MaxResults=10)
return json.dumps(response, default=str)
Looking at https://boto3.amazonaws.com/v1/documentation/api/1.14.8/reference/services/quicksight.html#QuickSight.Client.list_dashboard_versions the return looks like this -
{
'DashboardVersionSummaryList': [
{
'Arn': 'string',
'CreatedTime': datetime(2015, 1, 1),
'VersionNumber': 123,
'Status': 'CREATION_IN_PROGRESS'|'CREATION_SUCCESSFUL'|'CREATION_FAILED'|'UPDATE_IN_PROGRESS'|'UPDATE_SUCCESSFUL'|'UPDATE_FAILED',
'SourceEntityArn': 'string',
'Description': 'string'
},
],
'NextToken': 'string',
'Status': 123,
'RequestId': 'string'
}
As you can see, CreatedTime is returned as datetime. If you want to return this as a JSON, you should transform this value.
I was struggling with this today with a method that also returns a datetime.
In my example 'JoinedTimestamp': datetime(2015, 1, 1) resulting in the same Unable to marshal response.
If you don't need the CreatedTime value you might as well remove it from the response as:
for account in list_accounts_response["Accounts"]:
if "JoinedTimestamp" in account:
del account["JoinedTimestamp"]
To follow up on Joseph Lane's answer, transforming this value could be something along the lines of:
for account in list_accounts_response["Accounts"]:
if "JoinedTimestamp" in account:
account["JoinedTimestamp"] = str(account["JoinedTimestamp"])

Response class in Flask-RESTplus

What is the proper way to handle response classes in Flask-RESTplus?
I am experimenting with a simple GET request seen below:
i_throughput = api.model('Throughput', {
'date': fields.String,
'value': fields.String
})
i_server = api.model('Server', {
'sessionId': fields.String,
'throughput': fields.Nested(i_throughput)
})
#api.route('/servers')
class Server(Resource):
#api.marshal_with(i_server)
def get(self):
servers = mongo.db.servers.find()
data = []
for x in servers:
data.append(x)
return data
I want to return my data in as part of a response object that looks like this:
{
status: // some boolean value
message: // some custom response message
error: // if there is an error store it here
trace: // if there is some stack trace dump throw it in here
data: // what was retrieved from DB
}
I am new to Python in general and new to Flask/Flask-RESTplus. There is a lot of tutorials out there and information. One of my biggest problems is that I'm not sure what to exactly search for to get the information I need. Also how does this work with marshalling? If anyone can post good documentation or examples of excellent API's, it would be greatly appreciated.
https://blog.miguelgrinberg.com/post/customizing-the-flask-response-class
from flask import Flask, Response, jsonify
app = Flask(__name__)
class CustomResponse(Response):
#classmethod
def force_type(cls, rv, environ=None):
if isinstance(rv, dict):
rv = jsonify(rv)
return super(MyResponse, cls).force_type(rv, environ)
app.response_class = CustomResponse
#app.route('/hello', methods=['GET', 'POST'])
def hello():
return {'status': 200, 'message': 'custom_message',
'error': 'error_message', 'trace': 'trace_message',
'data': 'input_data'}
result
import requests
response = requests.get('http://localhost:5000/hello')
print(response.text)
{
"data": "input_data",
"error": "error_message",
"message": "custom_message",
"status": 200,
"trace": "trace_message"
}

Convert test client data to JSON

I'm building an app and I want to make some tests. I need to convert the response data from the test client to JSON.
The app:
tasks = [
{
'id': 1,
'title': u'Buy groceries',
'description': u'Milk, Cheese, Pizza, Fruit, Tylenol',
'done': False
},
{
'id': 2,
'title': u'Learn Python',
'description': u'Need to find a good Python tutorial on the web',
'done': False
}
]
app = Flask(__name__, static_url_path="")
#app.route('/myapp/api/v1.0/tasks', methods=['GET'])
def get_tasks():
return jsonify({'tasks': [task for task in tasks]})
if __name__ == '__main__':
app.run(debug=True)
The tests:
class MyTestCase(unittest.TestCase):
def setUp(self):
myapp.app.config['TESTING'] = True
self.app = myapp.app.test_client()
def test_empty_url(self):
response = self.app.get('/myapp/api/v1.0/tasks')
resp = json.loads(response.data)
print(resp)
if __name__ == '__main__':
unittest.main()
When I try to convert response.data to JSON, I get the following error:
TypeError: the JSON object must be str, not 'bytes'
How can I fix this error and get the JSON data?
Flask 1.0 adds the get_json method to the response object, similar to the request object. It handles parsing the response data as JSON, or raises an error if it can't.
data = response.get_json()
Prior to that, and prior to Python 3.6, json.loads expects text, but data is bytes. The response object provides the method get_data, with the as_text parameter to control this.
data = json.loads(response.get_data(as_text=True))

Categories

Resources