I have a GET and PUT request built below:
from flask import Flask
from flask_restful import Api, Resource, reqparse
app = Flask(__name__)
api = Api(app)
userStorage =[
{
"id": "1234",
"currentBot": "BestBot"
}
]
class User(Resource):
def get(self, id):
for user in userStorage:
if(id == user["id"]):
return user, 200
return "User not found", 404
def put(self, id):
parser = reqparse.RequestParser()
parser.add_argument("currentBot")
args = parser.parse_args()
for user in userStorage:
if(id == user["id"]):
user["currentBot"] = args["currentBot"]
return user, 200
user = {
"id": id,
"currentBot": args["currentBot"]
}
userStorage.append(user)
return user, 201
def delete(self, id):
global userStorage
userStorage = [user for user in userStorage if user["id"] != id]
return "{} is deleted.".format(id), 200
api.add_resource(User, "/user/<string:id>")
app.run(debug = True, port = 4000)
Postman can properly get a response 200 when I do a simple get request but when I try to do a request through my own program it returns a 404
import requests
payload2Storage = {
"currentBot": "BestBot"
}
headers = {"Content-Type": "application/json"}
params = {
"id": "1234"
}
#response = requests.request("PUT", "http://127.0.0.1:4000/user/", data=payload2Storage, params=params, headers=headers)
response2 = requests.request("GET", "http://127.0.0.1:4000/user/", params=params, headers=headers)
Is there something wrong with my request to get the info from userStorage?
In the client code, changing from 127.0.0.1 to localhost worked for me. Try this:
response2 = requests.request("GET", "http://localhost:4000/user/", params=params, headers=headers)
OR in the server code, bind the server to 127.0.0.1 explicitly via host argument like this:
app.run(debug = True, port = 4000, host='127.0.0.1')
Other error in the code is user["id"] returns a str while id is a int, change the code as below:
def get(self, id):
for user in userStorage:
if(id == int(user["id"])):
return user, 200
return "User not found", 404
Related
So I have this flask application and I'm trying to test it with pytest
app.py:
import os
from flask import Flask, jsonify
from flask_smorest import Api
from flask_jwt_extended import JWTManager
from flask_migrate import Migrate
from db import db
from resources.item import blp as ItemBlueprint
from resources.store import blp as StoreBlueprint
from resources.tag import blp as TagBlueprint
from resources.user import blp as UserBlueprint
from blocklist import BLOCKLIST
def create_app(db_url=None):
app = Flask(__name__)
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["API_TITLE"] = "Stores REST API"
app.config["API_VERSION"] = "v1"
app.config["OPENAPI_VERSION"] = "3.0.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
app.config["SQLALCHEMY_DATABASE_URI"] = db_url or os.getenv("DATABASE_URL", "sqlite:///data.db")
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
db.init_app(app)
api = Api(app)
migrate = Migrate(app, db)
app.config['JWT_SECRET_KEY'] = '69490938337699758397870296439802775085'
jwt = JWTManager(app)
#jwt.needs_fresh_token_loader
def token_not_fresh_callback(jwt_header, jwt_payload):
return (
jsonify(
{
"description": "The token is not fresh.",
"error": "fresh_token_required",
}
),
401,
)
#jwt.token_in_blocklist_loader
def check_if_token_in_blocklist(jwt_header, jwt_payload):
return jwt_payload['jti'] in BLOCKLIST
#jwt.revoked_token_loader
def revoked_token_callback(jwt_header, jwt_payload):
return (
jsonify(
{
"description": "The token has been revoked.",
"error": "token_revoked"
}
)
)
#jwt.additional_claims_loader
def add_claims_to_jwt(identity):
if identity == 1:
return {"is_admin": True}
return {"is_admin": False}
#jwt.expired_token_loader
def expired_token_callback(jwt_header, jwt_payload):
return (
jsonify(
{
"message": "The token has expired.",
"error": "token_expired"
}
),
401,
)
#jwt.invalid_token_loader
def invalid_token_callback(error):
return (
jsonify(
{
"message": "Signature verification failed.",
"error": "invalid_token"
}
), 401,
)
#jwt.unauthorized_loader
def missing_token_callback(error):
return (
jsonify(
{
"description": "Request does not contain an access token.",
"error": "authorization_required",
}
), 401,
)
# #app.before_first_request
# def create_tables():
# db.create_all()
api.register_blueprint(ItemBlueprint)
api.register_blueprint(StoreBlueprint)
api.register_blueprint(TagBlueprint)
api.register_blueprint(UserBlueprint)
return app
And I am trying to test some endpoints like create a store, so I'm doing this in the tests/conftest.py
from app import create_app
#pytest.fixture(scope='module')
def client():
flask_app = create_app()
flask_app.testing = True
flask_app.testing = True
with flask_app.test_client() as testing_client:
with flask_app.app_context():
yield testing_client
And there is the test for creation of a store
from flask_jwt_extended import create_access_token
def test_store_creation(client):
"""
GIVEN a Store model
WHEN a new Store is created
THEN check the name field is defined correctly
"""
access_token = create_access_token('admin')
headers = {
'Authorization': 'Bearer {}'.format(access_token)
}
response = client.post('/store', data={"name": "test_store"}, headers=headers)
print(response.get_json())
assert response.status_code == 201
The Store schema
class PlainItemSchema(Schema):
id = fields.Str(dump_only=True)
name = fields.Str(required=True)
price = fields.Float(required=True)
class PlainTagSchema(Schema):
id = fields.Int(dump_only=True)
name = fields.Str()
class PlainStoreSchema(Schema):
id = fields.Str(dump_only=True)
name = fields.Str(required=True)
class StoreSchema(PlainStoreSchema):
items = fields.List(fields.Nested(PlainItemSchema()), dump_only=True)
tags = fields.List(fields.Nested(PlainTagSchema()), dump_only=True)
My Store Blueprint
from flask.views import MethodView
from flask_smorest import abort, Blueprint
from sqlalchemy.exc import SQLAlchemyError, IntegrityError
from flask_jwt_extended import jwt_required, get_jwt
from schemas import StoreSchema
from db import db
from models import StoreModel
blp = Blueprint('stores', __name__, description='Operations on stores')
#blp.route('/store/<int:store_id>')
class Store(MethodView):
#blp.response(200, StoreSchema)
def get(self, store_id):
store = StoreModel.query.get_or_404(store_id)
return store
#jwt_required(fresh=True)
def delete(self, store_id):
"""Only admins can delete stores"""
jwt = get_jwt()
if not jwt.get('is_admin'):
abort(400, message='Admin privilege required.')
store = StoreModel.query.get_or_404(store_id)
db.session.delete(store)
db.session.commit()
return {"message": "Store deleted"}
#blp.route("/store")
class StoreList(MethodView):
#blp.response(200, StoreSchema(many=True))
def get(self):
return StoreModel.query.all()
#jwt_required()
#blp.arguments(StoreSchema)
#blp.response(201, StoreSchema)
def post(self, store_data):
store = StoreModel(**store_data)
try:
db.session.add(store)
db.session.commit()
except IntegrityError:
abort(
400,
message="A store with that name already exists.",
)
except SQLAlchemyError:
abort(500, message="An error occurred creating the store.")
return store
The error I'm getting is this
============================= test session starts ==============================
collecting ... collected 1 item
test_stores.py::test_store_creation FAILED [100%]{'code': 422, 'errors': {'json': {'name': ['Missing data for required field.']}}, 'status': 'Unprocessable Entity'}
test_stores.py:4 (test_store_creation)
422 != 201
Expected :201
Actual :422
<Click to see difference>
client = <FlaskClient <Flask 'app'>>
def test_store_creation(client):
"""
GIVEN a Store model
WHEN a new Store is created
THEN check the name field is defined correctly
"""
data = {
"name": "test"
}
access_token = create_access_token('admin')
headers = {
'Authorization': 'Bearer {}'.format(access_token)
}
response = client.post('/store', headers=headers, data=data)
print(response.get_json())
> assert response.status_code == 201
E assert 422 == 201
E + where 422 = <WrapperTestResponse 109 bytes [422 UNPROCESSABLE ENTITY]>.status_code
test_stores.py:22: AssertionError
It's like I'm not sending any data in the cliet.post() method, In fact if I remove the 'data={'name': 'test'}' I get the same result.
I tried to put data dict outside the method and convert it to json with json.dumps(data) still getting the result
flask-smorest is expecting you to post json data as default, so automatically send it as json OR adjust your header manually to reflect the data your are sending.
Fix your code as:
response = client.post("/store", json={"name": "test_store"}, headers=headers)
I'm trying to implement flask_jwt_extended to my flask app.
My Use Case is, I want to set Authorization headers to every request. So that when a #jwt_required decorator is decorated to a flask route it can be access if an access token is present on the Authorization headers.
I've tried some solutions like the one below:
I use #app.after_request to attach headers to every request but still it gives me this response
{
"msg": "Missing Authorization Header"
}
Here is my code:
#app.after_request
def add_headers(resp):
access_token = session.get("access_token", None)
if access_token is not None:
resp.headers["Authorization"] = f"Bearer {access_token}"
return resp
return resp
my login route:
#app.route('/', methods=["GET", "POST"])
def login():
if request.method == "POST":
form = request.form
_username = form["username"]
_password = form["password"]
for username in Users:
if username.get(_username):
if safe_str_cmp(username[_username]["password"],_password):
access_token = create_access_token(identity=username[_username]["user_id"], fresh = True)
session["access_token"] = access_token
res = make_response(redirect(url_for("home")))
res.headers["Authorization"] = f"Bearer {access_token}"
return res
else:
return "Incorrect password"
return f"Hello{_username} doesn't exist"
return render_template("login.html")
Here is my protected route:
#app.route('/home')
#jwt_required
def home():
res = Response(render_template("base.html"))
return res
I've also tried adding my headers in the route,But it still the headers I specify are not recognize and still give me the same response message. Here's how do it
#app.route('/home')
#jwt_required
def home():
access_token = session.get("access_token", None)
print(access_token)
if access_token is not None:
res = Response(render_template("base.html"), headers={"Authorization" : f"Bearer {access_token}"})
return res
res = Response(render_template("base.html"))
return res
Writing this for those who will come across this and are trying to implement an OAuth flow
Don't use decorators use middleware instead
I believe you should handle this in a middleware. In the middleware, you can set the authorization property of the 2nd parameter of the call function which contains the current wsgi app environment variables you have passed in your init function. Look at code below:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
cookie = Request(environ).cookies.get('access_token')
if cookie is not None:
environ['HTTP_AUTHORIZATION']='Bearer '+cookie
return self.app(environ, start_response)
I am writing a Flask application script and have encountered a function that accepts arguments and functions outside of the flask app in a standalone script but will not accept the the 'TFEVT_node_id'argument I pass to it inside of the Flask app. Here is the Flask code:
#################################################
# Flask Setup
#################################################
#app = Flask(__name__, static_url_path='')
app = Flask(__name__)
CORS(app)
#################################################
#global variables
TFEVT_node_id = ''
token = ''
#################################################
# CityIQ API calls
#################################################
def get_token():
print("Get Token")
url = 'https://auth.aa.cityiq.io/oauth/token'
querystring = {"grant_type":"client_credentials"}
response = requests.get(url, auth=HTTPBasicAuth(client,secret), params=querystring).json()
token = response['access_token']
print("Token Received")
return token
#given a specific subasset this function returns the CityIQ traffic events for a given period of time in minutes
def get_traffic(TFEVT_node_id, lookback_minutes):
url_string = '/events'
url = "https://sandiego.cityiq.io/api/v2/event/assets/"+TFEVT_node_id+url_string
ts = datetime.datetime.now().timestamp()
CityIQ_Current_TS = int(ts*1000)
CityIQ_TS_Calc_TS = datetime.datetime.now() - timedelta(minutes=lookback_minutes)
CityIQ_Starttime_TS = int((ts-(lookback_minutes*60))*1000)
querystring = {"eventType":"TFEVT","startTime":CityIQ_Starttime_TS,"endTime":CityIQ_Current_TS,"pageSize":"100"}
payload = ""
headers = {
'Authorization': "Bearer {}".format(token),
'Predix-Zone-Id': "SD-IE-TRAFFIC",
'cache-control': "no-cache",
}
response = requests.request("GET", url, headers=headers, params=querystring).json()
return(response)
#give it an CityIQ node ID and it will return the asset TFEVT child .
def get_asset_TFEVT(node_id):
url = "https://sandiego.cityiq.io/api/v2/metadata/assets/"+node_id+"/subAssets"
payload = ""
headers = {
'Authorization': "Bearer {}".format(token),
'Predix-Zone-Id': "SD-IE-ENVIRONMENTAL",
'cache-control': "no-cache",
}
response = requests.request("GET", url, data=payload, headers=headers).json()
for i in response['assets']:
if any('TFEVT' in i for i in [i][0]['eventTypes']):
global TFEVT_node_id
TFEVT_node_id = ([i][0]['assetUid'])
return(TFEVT_node_id)
#test def that should be removed in production
def test(TFEVT_node_id, lookback_minutes):
found_cars = get_traffic(TFEVT_node_id, lookback_minutes)
print(found_cars)
return(found_cars)
#################################################
# Flask Routes
#################################################
#app.route('/')
def hello_world():
global token
token = get_token()
return 'documentation comming soon!'
#app.route('/test/<string:node_id>')
def go(node_id):
global token
token = get_token()
global TFEVT_node_id
TFEVT_node_id = get_asset_TFEVT(node_id)
cars_list = []
lookback_minutes = 15
env_output = {}
ccars = test(TFEVT_node_id, lookback_minutes)
cars_list.append(ccars)
env_output.update({'Cars' : cars_list})
if __name__ == '__main__':
app.run()
Again, I am getting the desired result when these functions are run outside of a Flask app. Inside of the Flask app (running the code above) returns a TypeError: The view function did not return a valid response. I have traced that back to the 404 response in the gettraffic function where the lookback minutes variable is getting through but the TFEVT_node_id isn't.
I am new to Python.
Your go() function does not have a return. A response is expected. See: About responses.
This is my github repository for this live demo I have going on at the moment.
//index.js is where the POST request is being sent from
class Index extends React.Component {
state = {
loading: false,
};
handleClickLoading = () => {
this.setState(state => ({
loading: true,
}));
var jqXHR = $.ajax({
type: "POST",
url: "http://localhost:5000/login",
async: true,
data: { mydata: [Cookies.get('links'), Cookies.get('numSentences')] },
success: function(){
const data = JSON.parse(jqXHR.responseText);
console.log(data);
}
});
Cookies.set('final', jqXHR.responseText);
console.log(jqXHR.responseText)
};
render() {
const {loading} = this.state;
return (
<div>
<AppBar/>
<Block/>
<Card>
<CardActions>
<Button size="large"
fullWidth={true}
onClick={this.handleClickLoading}>Submit</Button>
</CardActions>
</Card>
<Fade
in={loading}
unmountOnExit
>
<BottomBar/>
</Fade>
</div>
)
}
}
export default Index;
and its trying to get data ran through python scripts through a Flask server:
...
def crossdomain(origin=None, methods=None, headers=None,
max_age=21600, attach_to_all=True,
automatic_options=True):
if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods))
if headers is not None and not isinstance(headers, list):
headers = ', '.join(x.upper() for x in headers)
if not isinstance(origin, list):
origin = ', '.join(origin)
if isinstance(max_age, timedelta):
max_age = max_age.total_seconds()
def get_methods():
if methods is not None:
return methods
options_resp = current_app.make_default_options_response()
return options_resp.headers['allow']
def decorator(f):
def wrapped_function(*args, **kwargs):
if automatic_options and request.method == 'OPTIONS':
resp = current_app.make_default_options_response()
else:
resp = make_response(f(*args, **kwargs))
if not attach_to_all and request.method != 'OPTIONS':
return resp
h = resp.headers
h['Access-Control-Allow-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age)
if headers is not None:
h['Access-Control-Allow-Headers'] = headers
return resp
f.provide_automatic_options = False
return update_wrapper(wrapped_function, f)
return decorator
#app.route("/")
def home():
return "hi"
#app.route("/index")
#app.route('/login', methods=['GET', 'POST', 'OPTIONS'])
#crossdomain(origin='*')
def login():
message = None
if request.method == 'POST':
datafromjs = request.form['mydata']
result = "test"
resp = make_response('{"response": '+result+'}')
resp.headers['Content-Type'] = "application/json"
resp.headers.add('Access-Control-Allow-Origin', '*')
resp.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
resp.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
return resp
return render_template('login.html', message='')
if __name__ == "__main__":
app.run(debug = True)
And this is the console output onClick from the browser:
and Flask:
The javascript is trying to fetch the output of a few python functions given a few parameters.
I'm just at an impasse here and it's the last cog in my project. I have no prior experience with this kind of stuff but any help is appreciated!
I believe this is a CORS issue, like JJ Hakala points out. Even if you are going to the same domain (localhost) but on a different port (8080 -> 5000) then you need an authorisation header to be present (server side).
If you can ensure that the header access-control-allow-origin:* is present (a wildcard domain approach) in server responses, then you should have no more ajax issues.
https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
I am sending GET request from localhost:8000 to flask :
$(document).ready(function() {
$('#test').click(function() {
$.getJSON("http://localhost:5000/test/", function() {
}).done(function() {
document.location = "http://localhost:5000";
}).fail(function() {
console.log("server not ready.");
});
});
});
and in "server.py" I am handling GET like:
import app
#server.route('/test/',methods = ['GET'])
def test():
print "in test status check"
return jsonify({'status':'OK'})
However I am getting this error:
XMLHttpRequest cannot load http://127.0.0.1:**5000**/test/. Origin http://127.0.0.1:**8000** is not allowed by Access-Control-Allow-Origin.
In flask you can create custom decorator to control Access Origin Policy. This article may help you: http://flask.pocoo.org/snippets/56/
Code from article:
from datetime import timedelta
from flask import make_response, request, current_app
from functools import update_wrapper
def crossdomain(origin=None, methods=None, headers=None,
max_age=21600, attach_to_all=True,
automatic_options=True):
if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods))
if headers is not None and not isinstance(headers, basestring):
headers = ', '.join(x.upper() for x in headers)
if not isinstance(origin, basestring):
origin = ', '.join(origin)
if isinstance(max_age, timedelta):
max_age = max_age.total_seconds()
def get_methods():
if methods is not None:
return methods
options_resp = current_app.make_default_options_response()
return options_resp.headers['allow']
def decorator(f):
def wrapped_function(*args, **kwargs):
if automatic_options and request.method == 'OPTIONS':
resp = current_app.make_default_options_response()
else:
resp = make_response(f(*args, **kwargs))
if not attach_to_all and request.method != 'OPTIONS':
return resp
h = resp.headers
h['Access-Control-Allow-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age)
if headers is not None:
h['Access-Control-Allow-Headers'] = headers
return resp
f.provide_automatic_options = False
return update_wrapper(wrapped_function, f)
return decorator
And here is how you can use it:
#app.route('/my_service')
#crossdomain(origin='*')
def my_service():
return jsonify(foo='cross domain ftw')