How to trigger a REST API based on a condition in Flask? - python

I have a Flask application where I let users access third party applications and fetch data from them and perform some visualizations.Now the user has to provide the application name and it's credentials in order to fetch the data.Now I want to avoid putting the application name in the url and rather all of the data should be sent as a POST request where I will parse the POST data, connect to the required app with the given credentials and perform some visualizations.This is what the user will send as a POST data
{
"application_name": "appdynamics",
"account_id": "sdf632sef",
"username": "kuhku86tg",
"password": "oihsd832"
}
Now I want to trigger my particular REST API class based on the application name provided by the user.
The way I planned was to create a seperate file that involves getting the POST data using request parser and then calling it in the main application where I will trigger my REST API class with a if condition based on the application name.Below is the file parse.py
from flask_restful import reqparse
# create a parser object
parser = reqparse.RequestParser()
# add agruments to the parser object
parser.add_argument('account_id', type=str, required=False, help="Please define 'account_id'")
parser.add_argument('username', type=str, required=False, help="Please define 'username'")
parser.add_argument('password', type=str, required=False, help="Please define 'password'")
parser.add_argument('application_name', type=str, required=False, help="Please define 'application name'")
data = parser.parse_args()
Now I call it in the main application app.py
from parser import data
from flask import Flask
from flask_restful import Api
app = Flask(__name__)
# create an API for the Flask app
api = Api(app)
# if the user demands info for appdynamics, trigger the Appdynamics API class
if data['application_name'] == "appdynamics":
api.add_resource(AppdynamicsAPI, "/<string:name>") # the string will contain the metric requirement
if __name__ == "__main__":
app.run(port=5000, debug=True)
Below is the section where the logic for the REST API is written
from parser import data
from flask_restful import Resource, reqparse
from fetch_data.appdynamics import fetch_all_apps, fetch_avg_resp_time, calls_per_min
from models.user import *
class AppdynamicsAPI(Resource):
# authenticate users
def post(self, name):
first_data = data
# if the user passes the credentials, insert it into the database otherwise use the last known credentials
# ensure you only insert valid credentials
if all([first_data['account_id'], first_data['password'], first_data['username']]):
users.update(first_data, {i: j for i, j in first_data.items()}, upsert=True)
print({i: j for i, j in first_data.items()})
credentials = users.find_one({})
print("Credentials", credentials)
account_id = credentials['account_id']
username = credentials['username']
password = credentials['password']
t_duration = first_data['t_duration']
if name == "allapps":
status_code, result = fetch_all_apps(account_id, username, password)
if status_code == 200:
return {"information": result}, status_code
return {"message": "Please enter correct credentials"}, status_code
However I receive the below error
Traceback (most recent call last):
File "/home/souvik/PycharmProjects/ServiceHandler/app.py", line 3, in <module>
from resource.appdynamics_resource import AppdynamicsAPI
File "/home/souvik/PycharmProjects/ServiceHandler/resource/appdynamics_resource.py", line 4, in <module>
from authentication.parser import data
File "/home/souvik/PycharmProjects/ServiceHandler/authentication/parser.py", line 14, in <module>
data = parser.parse_args()
File "/home/souvik/utorapp/lib/python3.5/site-packages/flask_restful/reqparse.py", line 302, in parse_args
req.unparsed_arguments = dict(self.argument_class('').source(req)) if strict else {}
File "/home/souvik/utorapp/lib/python3.5/site-packages/werkzeug/local.py", line 364, in <lambda>
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
File "/home/souvik/utorapp/lib/python3.5/site-packages/werkzeug/local.py", line 306, in _get_current_object
return self.__local()
File "/home/souvik/utorapp/lib/python3.5/site-packages/flask/globals.py", line 37, in _lookup_req_object
raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.

You currently call data = parser.parse_args() in top-level code of a module. This runs at import time, but there is nothing to parse while your module gets imported, since this happens during startup and not while handling a request.
Call this from your view function (ie the code that runs while handling a request) instead. You will also need to restructure your code - calling api.add_resource() is something you do during startup/initialization time, not while handling a request.
The important thing to understand is that this is not PHP where all your code runs when a request is received. Instead, the Python modules are imported when you start your application (flask run, app.run(), or running it in a WSGI container). When a request is received only the code related to handling that request runs.

Related

Using flask to run a python script

I have a script called output.py
This script takes in 2 inputs, fileA and file B.
I can run it on my terminal by using the command output.py -fileA -fileB. The script will create a new JSON file and save it to the directory.
I want to run this script using Flask. I've defined a bare bones App here but I'm not sure how I'd run this using Flask
from flask import Flask
import output
import scripting
app = Flask(__name__)
#app.route('/')
def script():
return output
if __name__ == '__main__':
app.run()
Can someone help me out here, thanks!
It appears you are new to Flask. Get some basic tutorials (there are many on the web).
There are a couple of options:
Send contents of file A and File b as json payload. Pull the A and B content off the json body and do the processing you need to and return the body.
Send the contents of the file as multipart/form-data (you can send multiple files).
Note: This is not working code - just for illustration.
from flask import Flask, request, make_response
app = Flask(__name__)
def build_response(status=False, error="", data={}, total=0, headers=[], contentType="application/json", expose_headers=["X-Total-Count"], retcode=400, additional_data=None):
resp = {"success": status, "error": error, "data": data}
resp = make_response(json.dumps(resp))
for item in headers:
resp.headers[item] = headers[item]
resp.headers['Content-Type'] = contentType
resp.headers.add('Access-Control-Expose-Headers', ','.join(expose_headers))
resp.status_code = retcode
return resp
#app.route('/run-script', methods=['POST'])
def run_script():
# check if the post request has the file part
try:
# Note: THis code is just to illustrate the concept.
# Option-1 (content type must be application/json)
json_dict = request.get_json()
fileA = json_dict["fileA"]
fileB = json_dict["fileB"]
# Option-2 (Note: fileA/fileB are objects, put a pdb and check it out)
fileA = request.files['fileA']
fileB = request.files['fileA']
resp = process(fileA, fileB)
return build_response(status=True, data=resp, retcode=200)
except Exception as e:
msg = f"Error - {str(ec)}"
return build_response(status=False, error=msg, retcode=400)

Pythonanywhere: Twitter API authenticates from bash, but fails on scheduled task

I'm using the python-twitter (not tweepy) module in my application.
I have a script set up (maketweet.py) that, when I run it from bash / console, works successfully (i.e. it makes a tweet).
The problem I'm having is that when I run the same script as a scheduled task, I get an error:
Traceback (most recent call last):
File "/home/dir1/dir2/proj/maketweet.py", line 21, in <module>
api = create_api()
File "/home/dir1/dir2/proj/config.py", line 31, in create_api
if api.VerifyCredentials():
File "/home/dir1/.local/lib/python3.8/site-packages/twitter/api.py", line 4699, in VerifyCredentials
resp = self._RequestUrl(url, 'GET', data)
File "/home/dir1/.local/lib/python3.8/site-packages/twitter/api.py", line 4959, in _RequestUrl
raise TwitterError("The twitter.Api instance must be authenticated.")
twitter.error.TwitterError: The twitter.Api instance must be authenticated.
The other problem I'm having is that I can't conceive why it would make a difference that I'm using a scheduled task, rather than running the file directly from bash. Here are the contents of config.py:
#!/usr/bin/python3.8
import twitter
import os
import logging
from dotenv import load_dotenv
logger = logging.getLogger()
# project folder is one level up from file location
project_folder = pathlib.Path(__file__).parent.absolute()
load_dotenv(os.path.join(project_folder, '.env'))
# Authenticate to Twitter and create API object
TWITTER_CONSUMER_API_KEY = os.getenv("TWITTER_CONSUMER_API_KEY")
TWITTER_CONSUMER_API_SECRET = os.getenv("TWITTER_CONSUMER_API_SECRET")
TWITTER_ACCESS_TOKEN = os.getenv("TWITTER_ACCESS_TOKEN")
TWITTER_ACCESS_SECRET = os.getenv("TWITTER_ACCESS_SECRET")
def create_api():
# Create API object
api = twitter.Api(
access_token_key = TWITTER_ACCESS_TOKEN,
access_token_secret = TWITTER_ACCESS_SECRET,
consumer_key = TWITTER_CONSUMER_API_KEY,
consumer_secret = TWITTER_CONSUMER_API_SECRET,
sleep_on_rate_limit=True)
# test API object
if api.VerifyCredentials():
pass
else:
logger.error("Error creating API", exc_info=True)
raise Exception("Twitter user authentication error")
logger.info("API created")
return api
Obviously there's an error in creating the API. I imagine this has something to do with the environment variables and how they are accessed through scheduled tasks vs. bash. Just really not sure how to figure this one out...

Validate JWT access_token from AAD Azure Fails

I need to validate a azure jwt access_token from the service I'm working on. We are currently making a request to https://graph.microsoft.com/beta/me. If the request succeeded, the token is valid. Unfortunately we will not be able to keep doing that.
I have tried a variety of ideas for this. None of them with success. Even jwt.io does not recognize the signature, even though jwt kid and the kid from one of the available signatures in jwk_uri matches.
Based on this blog post I have created a following solution (also available on github) .
My implementation is very similar to the blog post with a few changes:
#!/usr/bin/env python2
import jwt
import requests
import sys
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.backends import default_backend
def get_public_key(access_token):
""" Retrieve public key for access token """
token_header = jwt.get_unverified_header(access_token)
res = requests.get('https://login.microsoftonline.com/common/.well-known/openid-configuration')
jwk_uri = res.json()['jwks_uri']
res = requests.get(jwk_uri)
jwk_keys = res.json()
x5c = None
# Iterate JWK keys and extract matching x5c chain
for key in jwk_keys['keys']:
if key['kid'] == token_header['kid']:
x5c = key['x5c']
break
else:
raise Exception('Certificate not found in {}'.format(jwk_uri))
cert = ''.join([
'-----BEGIN CERTIFICATE-----\n',
x5c[0],
'\n-----END CERTIFICATE-----\n',
])
try:
public_key = load_pem_x509_certificate(cert.encode(), default_backend()).public_key()
except Exception as error:
raise Exception('Failed to load public key:', error)
return public_key, key['kid']
def main():
print '\n'
if len(sys.argv) < 2 or '-h' in sys.argv:
print 'Run it again passing acces token:\n\tpython jwt_validation.py <access_token>'
sys.exit(1)
access_token = sys.argv[1]
audience = 'https://graph.microsoft.com'
public_key, kid = get_public_key(access_token)
try:
jwt.decode(
access_token,
public_key,
algorithms='RS256',
audience=audience,
)
except Exception as error:
print 'key {} did not worked, error:'.format(kid), error
sys.exit(1)
print('Key worked!')
if __name__ == '__main__':
main()
This solution returns Invalid Signature for a valid access_token with the following traceback:
Traceback (most recent call last):
File "jwt_validation/jwt_validation.py", line 63, in <module>
main()
File "jwt_validation/jwt_validation.py", line 57, in main
audience=audience,
File "~/.pyenv/versions/jwt-validation-tool/lib/python2.7/site-packages/jwt/api_jwt.py", line 93, in decode
jwt, key=key, algorithms=algorithms, options=options, **kwargs
File "~/.pyenv/versions/jwt-validation-tool/lib/python2.7/site-packages/jwt/api_jws.py", line 157, in decode
key, algorithms)
File "~/.pyenv/versions/jwt-validation-tool/lib/python2.7/site-packages/jwt/api_jws.py", line 224, in _verify_signature
raise InvalidSignatureError('Signature verification failed')
jwt.exceptions.InvalidSignatureError: Signature verification failed
Any ideas of what I could be wrong would be helpful.
I ran into simillar issue, after couple of days investigating I've found in my case that I am requesting token with built in scopes (openid and profile) and without any custom scope which results in issuing tokens with different audiance (MS Graph) and hence tokens signed with with different public key (because I think that issued access_token is just a forward of the delegated MS Graph scope).
I resolved the issue by adding a custom scope in my app registration (Expose API section) and now my access_tokens are issued with valid audiance and I am able to check signature with my app public key.
Usually the audience claim is used to point to the client_id of the client application you have registered in Azure/Microsoft. Looks to me that the error is occurring due to a mismatch of claims in jwt(probably audience). Check whether you have set the correct client_id for the audience variable.

Python restplus API to upload and dowload files

With python flask_restplus what is correct way to have a post and get methods to get and push a file e.g. xlsx to the server ?
Does the marshaling need to be used for this ?
reference: https://philsturgeon.uk/api/2016/01/04/http-rest-api-file-uploads/
This answer give general info but not in the python>flask>restplus context: REST API File Upload
First you need to configure a parser
# parsers.py
import werkzeug
from flask_restplus import reqparse
file_upload = reqparse.RequestParser()
file_upload.add_argument('xls_file',
type=werkzeug.datastructures.FileStorage,
location='files',
required=True,
help='XLS file')
Then add a new resource to your api namespace
# api.py
import …
import parsers
#api.route('/upload/')
class my_file_upload(Resource):
#api.expect(parsers.file_upload)
def post(self):
args = parsers.file_upload.parse_args()
if args['xls_file'].mimetype == 'application/xls':
destination = os.path.join(current_app.config.get('DATA_FOLDER'), 'medias/')
if not os.path.exists(destination):
os.makedirs(destination)
xls_file = '%s%s' % (destination, 'custom_file_name.xls')
args['xls_file'].save(xls_file)
else:
abort(404)
return {'status': 'Done'}
I hope this helps.

Python restFUL web service - routing to a specific function in a file

I am implementing a simple API in python using werkzeug. I have created a simple application 'localhost'. I want to execute a function after a GET request. I am confused with URL routing. I have gone through this tutorial and implemented routing but still can't figure out how to send a request to another file. Here is my code:
url_map = Map([
Rule('/spell', endpoint='spell_checker.py'),
Rule('/he', endpoint='hello/test')
])
#Request.application
def application(request):
urls = url_map.bind_to_environ(environ)
try:
endpoint, args = urls.match()
print(endpoint + " " + args)
#urls.dispatch('test')
except httplib.HTTPException, e:
return e(environ, start_response)
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['Rul1e points to %r with arguments %r' % (endpoint, args)]
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('127.0.0.1', 4000, application)
I have another another in a file named hello.py in the same directory
def index():
print 'This is the test'
I want to call index function from URL like localhost:4000/hello which will call the index function of the file hello.py
Kindly assist me.
If you want to use a function from an external library first of all you have to import the external library
import foo #your library
And then for calling a function "foo_function" you have to call this function using:
foo.foo_function(args) #where args are declared in the hello.py file
The endpoint is typically a string and can be used to uniquely
identify the URL
So it doesn't bind a function to a url, you have to do it yourself.
After this line
endpoint, args = urls.match()
you can put some kind of control statements to run specific functions, in your case:
if endpoint == "hello/test":
hello.index(request, **args)
assuming you have imported hello module when you point your browser to \he the program will call hello.index function.
http://werkzeug.pocoo.org/docs/0.11/tutorial/#step-4-the-routing

Categories

Resources