I'm trying to marshal a simple data structure in Flask, as below:
{
"globalNum": 1.23,
"perResultData": [
{
"string1": "test string",
"num1": 1.25
},
{
"string1": "test",
"num1": 1.22
}
]
}
I'm modelling that structure like this:
testmodel = api.model('Model', {
'globalNum': fields.Float,
'perResultData': fields.List(fields.Nested({
"string1": fields.String,
"num1": fields.Float
}))
})
When I try this setup (as per minimum failing code below), if I browse to localhost I get a warning 'No API definition provided.' and the Flask console shows:
File "/home/mikea/.local/lib/python3.6/site-packages/flask_restplus/swagger.py", line 574, in register_model
if name not in self.api.models:
TypeError: unhashable type: 'dict'
Flask works perfectly when I comment out the '#api.marshal_with(testmodel)' line.
Can someone shed some light on what I'm doing wrong, please? Thanks very much
Full code:
from flask_restplus import Resource, Api,fields
app = Flask(__name__)
api = Api(app)
testmodel = api.model('Model', {
'globalNum': fields.Float,
'perResultData': fields.List(fields.Nested({
"string1": fields.String,
"num1": fields.Float
}))
})
#api.route('/')
class incomingRequest(Resource):
#api.marshal_with(testmodel)
def post(self):
return {"globalNum":3.2,
"perResultData":[
{
"string1": "test string",
"num1": 1.25
},
{
"string1": "test",
"num1": 1.22
}
]}
if __name__ == '__main__':
app.run(debug=True)
The answer was here - https://github.com/noirbizarre/flask-restplus/issues/292
When nesting models, you have to wrap them in the model class, like the below:
testmodel = api.model('Model', {
'globalNum': fields.Float,
'perResultData': fields.List(fields.Nested(api.model({
"string1": fields.String,
"num1": fields.Float
})))
})
Related
I have the following endpoint in Flask:
from flask import Flask
from marshmallow import EXCLUDE, Schema, fields
from webargs.flaskparser import use_kwargs
app = Flask(
__name__, static_url_path="", static_folder="static", template_folder="templates"
)
class Measure(Schema):
type = fields.Int(required=True)
value = fields.Float(required=True)
unit = fields.Int(required=True)
date = fields.Int(required=True)
class RequestSchema(Schema):
measures = fields.List(fields.Nested(Measure))
#app.route("/save", methods=["POST"])
#use_kwargs(
RequestSchema,
location="json_or_form",
unknown=EXCLUDE,
)
def save(**measures):
return {"foo": measures}, 200
With pytest, I test the following:
import pytest
#pytest.fixture(scope="module")
def app():
test_app = AppClientPublic()
return test_app.app
def test_save(app):
measures = [
{
"type": 1,
"value": 2.2,
"unit": 3,
"date": 4,
},
{
"type": 5,
"value": 6.6,
"unit": 7,
"date": 8,
},
]
r = app.post(
"/save",
data={"measures": measures},
)
assert r.status_code == 200
I get the following error:
Error 422 Unprocessable Entity: The request was well-formed but was unable to be followed due to semantic errors. - {'messages': {'json_or_form': {'measures': {0: {'_schema': ['Invalid input type.']}, 1: {'_schema': ['Invalid input type.']}}}}, 'schema': <RequestSchema(many=False)>, 'headers': None}
However, if I validate the same dictionary directly:
d = {
"measures": [
{
"type": 1,
"value": 2.2,
"unit": 3,
"date": 4,
},
{
"type": 5,
"value": 6.6,
"unit": 7,
"date": 8,
},
]
}
req_schema = RequestSchema()
errors = req_schema.validate(d)
print(errors) # {} -> no errors
I get no validation error.
I also have absolutely no problem validating simple objects like just a depth-1 dictionary, but I am unable to validate a list of Measure on the Flask endpoint.
What am I doing wrong?
PS:
I am aware of How should I add a field containing a list of dictionaries in Marshmallow Python?. It works fine. My problem is more focused on why it does not work when using it in use_kwargs on the Flask endpoint.
The different versions:
Python 3.8
Flask 2.1.3
marshmallow 3.17.0
webargs 8.2.0
The problem was on the pytest side, not the Flask side.
I changed the post call to use json instead of data:
r = app.post("/save", json={"measures": measures})
# instead of
r = app.post("/save", data={"measures": measures})
I have AWS WAF CDK that is working with rules, and now I'm trying to add a rule in WAF with multiple statements, but I'm getting this error:
Resource handler returned message: "Error reason: You have used none or multiple values for a field that requires exactly one value., field: STATEMENT, parameter: Statement (Service: Wafv2, Status Code: 400, Request ID: 6a36bfe2-543c-458a-9571-e929142f5df1, Extended Request ID: null)" (RequestToken: b751ae12-bb60-bb75-86c0-346926687ea4, HandlerErrorCode: InvalidRequest)
My Code:
{
'name': 'ruleName',
'priority': 3,
'statement': {
'orStatement': {
'statements': [
{
'iPSetReferenceStatement': {
'arn': 'arn:myARN'
}
},
{
'iPSetReferenceStatement': {
'arn': 'arn:myARN'
}
}
]
}
},
'action': {
'allow': {}
},
'visibilityConfig': {
'sampledRequestsEnabled': True,
'cloudWatchMetricsEnabled': True,
'metricName': 'ruleName'
}
},
There are two things going on there:
Firstly, your capitalization is off. iPSetReferenceStatement cannot be parsed and creates an empty statement reference. The correct key is ipSetReferenceStatement.
However, as mentioned here, there is a jsii implementation bug causing some issues with the IPSetReferenceStatementProperty. This causes it not to be parsed properly resulting in a jsii error when synthesizing.
You can fix it by using the workaround mentioned in the post.
Add to your file containing the construct:
import jsii
from aws_cdk import aws_wafv2 as wafv2 # just for clarity, you might already have this imported
#jsii.implements(wafv2.CfnRuleGroup.IPSetReferenceStatementProperty)
class IPSetReferenceStatement:
#property
def arn(self):
return self._arn
#arn.setter
def arn(self, value):
self._arn = value
Then define your ip reference statement as follows:
ip_set_ref_stmnt = IPSetReferenceStatement()
ip_set_ref_stmnt.arn = "arn:aws:..."
ip_set_ref_stmnt_2 = IPSetReferenceStatement()
ip_set_ref_stmnt_2.arn = "arn:aws:..."
Then in the rules section of the webacl, you can use it as follows:
...
rules=[
{
'name': 'ruleName',
'priority': 3,
'statement': {
'orStatement': {
'statements': [
wafv2.CfnWebACL.StatementProperty(
ip_set_reference_statement=ip_set_ref_stmnt
),
wafv2.CfnWebACL.StatementProperty(
ip_set_reference_statement=ip_set_ref_stmnt_2
),
]
}
},
'action': {
'allow': {}
},
'visibilityConfig': {
'sampledRequestsEnabled': True,
'cloudWatchMetricsEnabled': True,
'metricName': 'ruleName'
}
}
]
...
This should synthesize your stack as expected.
I am in the middle of creating todo app integreted with Slack. I need to use dialog.open property of slack.
I managed to go through slack api tutorial however can not finally understand how dialogs work in integration with external systems. I created code which runs after slash command in slack. It should open dialog and show it to user, however it doesn't. I printed some parts of code to see what happens inside - looks like whole code works and server returns 200.
#app.route('/helpdesk', methods=['POST'])
def helpdesk():
print(request.form)
api_url = 'https://slack.com/api/dialog.open'
user_id = request.form['user_id']
trigger_id = request.form['trigger_id']
dialog = {
"token": "J1llSAeQAxNyw8yc37xuEsad",
"trigger_id": trigger_id,
"dialog": {
"callback_id": "ryde-46e2b0",
"title": "Request a Ride",
"submit_label": "Request",
"notify_on_cancel": True,
"state": "Limo",
"elements": [
{
"type": "text",
"label": "Pickup Location",
"name": "loc_origin"
},
{
"type": "text",
"label": "Dropoff Location",
"name": "loc_destination"
}
]
}
}
print(dialog)
requests.post(api_url, data=dialog)
return make_response()
I expect to see dialog window after writing slash command in slack.
What I see in prints:
ImmutableMultiDict([('token', 'J1llSAeQAxNyw8yc37xuEsad'), ('team_id', 'TKWQ5QP7Y'), ('team_domain', 'team-learningslack'), ('channel_id', 'CKH7RSZPC'), ('channel_name', 'slackflask'), ('user_id', 'UKN9KU7JM'), ('user_name', 'konrad.marzec1991'), ('command', '/musi'), ('text', ''), ('response_url', 'https://hooks.slack.com/commands/TKWQ5QP7Y/664885241506/ABjpMYmTWrnXpSBoGMpaJtOV'), ('trigger_id', '669947662833.676821839270.6c4bddd1418d3d4f2c8626f7c9accdf7')])
{'token': 'J1llSAeQAxNyw8yc37xuEsad', 'trigger_id': '669947662833.676821839270.6c4bddd1418d3d4f2c8626f7c9accdf7', 'dialog': {'callback_id': 'ryde-46e2b0', 'title': 'Request a Ride', 'submit_label': 'Request', 'notify_on_cancel': True, 'state': 'Limo', 'elements': [{'type': 'text', 'label': 'Pickup Location', 'name': 'loc_origin'}, {'type': 'text', 'label': 'Dropoff Location', 'name': 'loc_destination'}]}}
127.0.0.1 - - [26/Jun/2019 00:15:35] "POST /helpdesk HTTP/1.1" 200 -
You had 2 issues in your code:
you need to use an access token, not a verification token in the call
to dialog.open
you need to send the dialog definition as JSON, not as as form array
I made these additional changes
- Added code for using a slack token defined as environment variable
- Use the get() method to access form parameters in from the request
- Added code to show the API response from dialog.open
Here is a corrected version of your code:
import os
import requests
from flask import Flask, json, request
app = Flask(__name__) #create the Flask app
#app.route('/helpdesk', methods=['POST'])
def helpdesk():
api_url = 'https://slack.com/api/dialog.open'
trigger_id = request.form.get('trigger_id')
dialog = {
"callback_id": "ryde-46e2b0",
"title": "Request a Ride",
"submit_label": "Request",
"notify_on_cancel": True,
"state": "Limo",
"elements": [
{
"type": "text",
"label": "Pickup Location",
"name": "loc_origin"
},
{
"type": "text",
"label": "Dropoff Location",
"name": "loc_destination"
}
]
}
api_data = {
"token": os.environ['SLACK_TOKEN'],
"trigger_id": trigger_id,
"dialog": json.dumps(dialog)
}
res = requests.post(api_url, data=api_data)
print(res.content)
return make_response()
if __name__ == '__main__':
app.run(debug=True, port=8000) #run app in debug mode on port 8000
I'm trying to make a google home assistant that just parrots back whatever a user says to it. Basically I need to capture what the user is saying, and then feed it back into a response.
I have some puzzle pieces figured out.
One is initializing the API to do queries:
api = ApiAi(os.environ['DEV_ACCESS_TOKEN'], os.environ['CLIENT_ACCESS_TOKEN'])
The other is a fallback intent that is intended to just capture whatever the user says and repeat it back:
#assist.action('fallback', is_fallback=True)
def say_response():
""" Setting the fallback to act as a looper """
speech = "test this" # <-- this should be whatever the user just said
return ask(speech)
Another is the JSON response on the API.AI site looks like this:
{
"id": "XXXX",
"timestamp": "2017-07-20T14:10:06.149Z",
"lang": "en",
"result": {
"source": "agent",
"resolvedQuery": "ok then",
"action": "say_response",
"actionIncomplete": false,
"parameters": {},
"contexts": [],
"metadata": {
"intentId": "a452b371-f583-46c6-8efd-16ad9cde24e4",
"webhookUsed": "true",
"webhookForSlotFillingUsed": "true",
"webhookResponseTime": 112,
"intentName": "fallback"
},
"fulfillment": {
"speech": "test this",
"source": "webhook",
"messages": [
{
"speech": "test this",
"type": 0
}
],
"data": {
"google": {
"expect_user_response": true,
"is_ssml": true
}
}
},
"score": 1
},
"status": {
"code": 200,
"errorType": "success"
},
"sessionId": "XXXX"
}
The module I'm intializing from looks like this: https://github.com/treethought/flask-assistant/blob/master/api_ai/api.py
Full program looks like this:
import os
from flask import Flask, current_app, jsonify
from flask_assistant import Assistant, ask, tell, event, context_manager, request
from flask_assistant import ApiAi
app = Flask(__name__)
assist = Assistant(app, '/')
api = ApiAi(os.environ['DEV_ACCESS_TOKEN'], os.environ['CLIENT_ACCESS_TOKEN'])
# api.post_query(query, None)
#assist.action('fallback', is_fallback=True)
def say_response():
""" Setting the fallback to act as a looper """
speech = "test this" # <-- this should be whatever the user just said
return ask(speech)
#assist.action('help')
def help():
speech = "I just parrot things back!"
## a timeout and event trigger would be nice here?
return ask(speech)
#assist.action('quit')
def quit():
speech = "Leaving program"
return tell(speech)
if __name__ == '__main__':
app.run(debug=False, use_reloader=False)
How do I go about getting the "resolvedQuery" out of the JSON to be fedback as "speech" for the response?
Thanks.
The flask_assistant library does a good job of parsing the request into a dict object.
You can get the resolvedQuery by writing:
speech = request['result']['resolvedQuery']
Just create a new intent (doesn’t matter the name) and a template with sys.any; after that go on your server and use something similar to the following code
userInput = req.get(‘result’).get(‘parameters’).get(‘YOUR_SYS_ANY_PARAMETER_NAME’)
Then send userInput back as the speech response.
Something like this is how you get the initial JSON data:
#app.route(’/google_webhook’, methods=[‘POST’])
def google_webhook():
# Get JSON request
jsonRequest = request.get_json(silent=True, force=True, cache=False)
print("Google Request:")
print(json.dumps(jsonRequest, indent=4))
# Get result
appResult = google_process_request(jsonRequest)
appResult = json.dumps(appResult, indent=4)
print("Google Request finished")
# Make a JSON response
jsonResponse = make_response(appResult)
jsonResponse.headers['Content-Type'] = 'application/json'
return jsonResponse
I am currently developing a site which uses Angular JS for the front-end framework and Flask for the RESTful API. I am using vagrant to host the LAMP server where my app resides. The API was tested using curl, and it works exactly as I want it to.
I get the following error when I run the app:
`*GET http://localhost:5000/choreographer/api/v1.0/choreographers net::ERR_CONNECTION_REFUSED
(anonymous function) # angular.js:10722
sendReq # angular.js:10515
serverRequest # angular.js:10222
processQueue # angular.js:14745
(anonymous function) # angular.js:14761
Scope.$eval # angular.js:15989
Scope.$digest # angular.js:15800
Scope.$apply # angular.js:16097
(anonymous function) # angular.js:12226
eventHandler # angular.js:3298*`
I originally thought this might be a CORS issue, so I tried using $http.jsonp(). This produced the same error. I know that there is something which is hindering my ability to access the API, but I do not know what or how to fix it.
My AngularJS module is as follows:
angular.module('myApp.choreographer', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/choreographer', {
templateUrl: 'choreographer/choreographer.html',
controller: 'choreographerCtrl'
});
}])
.factory('choreographerService', function($resource) {
return $resource('http://localhost:5000/choreographer/api/v1.0/choreographers');
})
.controller('choreographerCtrl', function($scope, choreographerService) {
var choreographers = choreographerService.query(function() {
console.log(choreographers);
}); //query() returns all the entries
});
The flask API is as follows:
\#!env/bin/python
from flask import Flask, jsonify
from flask import abort
from flask import request
app = Flask(__name__)
#app.route('/')
def index():
return "Hello World!"
choreographers = [
{
"id":1,
"firstName": "Danny",
"lastName": "Boy",
"email": "email#email.edu",
"bio": "Biography for Danny."
},
{
"id":2,
"firstName": "Jessica",
"lastName": "Martin",
"email": "helloworld#smu.edu",
"bio": "Biography text for Jessica"
},
{
"id":3,
"firstName": "John",
"lastName": "Doe",
"email": "test#pleasework.com",
"bio": "Biography text for John"
}
]
#app.route('/choreographer/api/v1.0/choreographers', methods=['GET'])
def get_choreographers():
return jsonify({'choreographers': choreographers})
#app.route('/choreographer/api/v1.0/choreographer/<int:choreographer_id>', methods=['GET'])
def get_choreographer(choreographer_id):
choreographer = [choreographer for choreographer in choreographers if choreographer['id'] == choreographer_id]
if len(choreographer) == 0:
abort(404)
return jsonify({'choreographer':choreographer[0]})
#app.route('/choreographer/api/v1.0/choreographer', methods=['POST'])
def add_choreographer():
if not request.json or not 'firstName' in request.json:
abort(400)
choreographer = {
'id': choreographers[-1]['id'] + 1,
'firstName': request.json['firstName'],
'lastName': request.json.get('lastName',""),
'email': request.json['email'],
'bio': request.json['bio'],
}
choreographers.append(choreographer)
return jsonify({'choreographers': choreographer}),201
#app.route('/choreographer/api/v1.0/choreographers/<int:choreographer_id>', methods=['PUT'])
def update_choreographer(choreographer_id):
choreographer = [choreographer for choreographer in choreographers if choreographer['id'] == choreographer_id]
if len(choreographer) == 0:
abort(404)
if not request.json:
abort(400)
if 'firstName' in request.json and type(request.json['firstName']) != unicode:
abort(400)
if 'lastName' in request.json and type(request.json['lastName']) is not unicode:
abort(400)
choreographer[0]['firstName'] = request.json.get('firstName', choreographer[0]['firstName'])
choreographer[0]['lastName'] = request.json.get('lastName', choreographer[0]['lastName'])
choreographer[0]['email'] = request.json.get('email', choreographer[0]['email'])
choreographer[0]['bio'] = request.json.get('bio', choreographer[0]['bio'])
return jsonify({"choreographer": choreographer[0]})
#app.route('/choreographer/api/v1.0/choreographers/<int:choreographer_id>', methods=['DELETE'])
def delete_choreographer(choreographer_id):
choreographer = [choreographer for choreographer in choreographers if choreographer['id'] == choreographer_id]
if len(choreographer) == 0:
abort(404)
choreographers.remove(choreographer[0])
return jsonify({'result':True})
if __name__ == '__main__':
app.run(debug=True)
My Angular tests the first GET method in the Flask API