How do I log swagger / connexion request body? - python-logging

I'm using connexion and Swagger to create an API in Python. I'd like to log all of the incoming calls to a file so I can see what's being requested. I've been able to log the path of the calls I've received but I can't figure out how to log the body.
Here's where I've configured my logging:
if __name__ == '__main__':
import logging
logging.basicConfig(format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s', filename='golden_record.log',level=logging.DEBUG)
When I look into the log, I see the path, as in the following for a POST call...
2019-03-23 12:47:16,182 - werkzeug - INFO - 127.0.0.1 - - [23/Mar/2019
12:47:16] "POST /golden_record/account HTTP/1.1" 400 -
but I don't see the body of the call (i.e. the data I've sent). Is there a way to automatically record this for every call that comes in? Doing so would help debug calls that aren't functioning as desired. Thanks!

Just add a Flask before_request.
Connexion instance stores the Flask instance as the app attribute, so you can access using app.app.
Add the code and you will log the body:
#app.app.before_request
def log_request_info():
print('Body: %s', request.get_data())
Change the print method to your logger:
#app.app.before_request
def log_request_info():
logger.info('Body: %s', request.get_data())
The request import is :
from flask import request
And the app is your Connexion instance:
app = connexion.App(__name__, specification_dir="./swagger/")
I created a Gist with the code.
https://gist.github.com/kevinmmartins/f832c21215bb51cea73b8fdd28f6d88d

Related

Request to Steam API working with curl, does not work in browser, insomnia nor flask

I am trying to do a simple GET request to the Steam API:
If I do in the terminal:
curl http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/\?key\=XXXXXX\&steamids\=76561197960435530
It works:
{"response":{"players":[{"steamid":"76561197960435530","communityvisibilitystate":3,"profilestate":1,"personaname":"Robin","profileurl":"https://steamcommunity.com/id/robinwalker/","avatar":"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/f1/f1dd60a188883caf82d0cbfccfe6aba0af1732d4.jpg","avatarmedium":"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/f1/f1dd60a188883caf82d0cbfccfe6aba0af1732d4_medium.jpg","avatarfull":"https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/f1/f1dd60a188883caf82d0cbfccfe6aba0af1732d4_full.jpg","avatarhash":"f1dd60a188883caf82d0cbfccfe6aba0af1732d4","personastate":0,"realname":"Robin Walker","primaryclanid":"103582791429521412","timecreated":1063407589,"personastateflags":0,"loccountrycode":"US","locstatecode":"WA","loccityid":3961}]}}
However, when I type in my local browser the url, I get 404 Bad Request: "Required parameter 'key' is missing".
I though it might be related with CORS, so I tried to sent the response from a local flask application:
import requests
from flask import Flask
app = Flask(__name__)
#app.route('/')
#cross_origin()
def get_data():
return(requests.get('http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/\?key\=XXXXXXX\&steamids\=76561197960435530').content)
But I also get 404 Not Found error
When you're making the request via Flask or typing it in your browser, do not escape the url yourself (if you look at the url, you will see stuff like \? and \=
Use the full url i.e.
http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=XXXXXXX&steamids=76561197960435530

How to check Hubot log output in test script

I'm writing a test for my Hubot (which acts as a Slack bot). Triggered by certain Slack messages, the bot sends an HTTP POST request to a separate Rails app. How can I check in my test script that the HTTP request has been sent? My guess was that I should check the contents of the robot.logger (please let me know if there's a better way) - but if so, how can I access the log in the test?
Hubot script (basically, it informs the Rails app about a user who is leaving the office to take a break):
module.exports = (robot) ->
robot.respond /off to lunch/i, (res) ->
res.reply('Later alligator')
robot.logger.info "This user is going on lunch break: #{res.message.user.id}"
data = JSON.stringify({
slack_user_id: res.message.user.id
})
robot.http(process.env.RAILS_APP_URL + '/break')
.header('Content-Type', 'application/json')
.post(data) (err, resp, body) ->
if err
robot.logger.info "Encountered an error. #{err}"
res.reply('Sorry, there was an error recording your break time')
else
robot.logger.info 'Successfully sent HTTP POST request to Rails app'
Log output when I execute this script:
INFO This user is going on lunch break: [SLACK_USER_ID]
INFO Successfully sent HTTP POST request to Rails app
As I mentioned above, I'd like to check in my test script that the HTTP request was sent, by asserting that the log is going to include the message 'Successfully sent HTTP POST request to Rails app'. However, I don't know how to access the Hubot's log in my test. I thought it would have something to do with process.stdout because the bot logs to stdout, but I couldn't get it to work.
Test script:
Helper = require('hubot-test-helper')
helper = new Helper('../scripts/break-start.coffee')
request = require('request')
expect = require('chai').expect
nock = require('nock')
describe 'bot responds to user message and sends ', ->
beforeEach ->
# Set up the room before running the test.
#room = helper.createRoom()
# Set up a request interceptor.
nock(process.env.RAILS_APP_URL)
.post('/break', { slack_user_id: 'bob' })
.reply(200)
afterEach ->
# Tear down the room after the test to free up the listener.
#room.destroy()
context 'user sends lunch message', ->
beforeEach ->
#room.user.say('bob', '#hubot Off to lunch')
it 'responds to users who are off to lunch', ->
expect(#room.messages).to.eql [
['bob', '#hubot Off to lunch']
['hubot', '#bob Later alligator']
# I want to do something like this:
# expect(robot.log).include('Successfully sent HTTP POST request to Rails app')
Of course, I can see in the console log when I run the test that the HTTP request is being sent, but I'd also like to assert it so that the test fails if the request is not sent.
Log output when test is executed:
INFO This user is going on lunch break: bob
✓ responds to users who are off to lunch
INFO Successfully sent HTTP POST request to Rails app
Thank you for any help in advance.
I wouldn't advise writing tests depending on logs. The log is a side-effect of the program. If you change the log output, the tests will fail, even though the functionality is still correct.
Instead you should use a library to mock out and check if the http request was performed. Actually making the request would be a side-effect, and again shouldn't be done in your tests (what if you cause excessive load on an external service due to tests running?
You are already using the nock library to catch the request. It can also be used to check if the request was made (see the expectations docs from the nock repo).
Here is an example using the requestScope.done() from nock in your test.
it 'sends the break request to the rails server', ->
# capture the request to the rails app
railsRequest = nock(process.env.RAILS_APP_URL)
.post('/break', { slack_user_id: 'bob' })
.reply(200)
# make the request and wait for it to be completed
await #room.user.say('bob', '#hubot Off to lunch')
# check that the request was made
railsRequest.done()
I'm using await to ensure the function call which should make the request is completed before testing for it. If you're not familiar with await, you can move the check (railsRequest.done()) into a .then() promise handler on the #room.user.say(...) call instead.
Promise version:
Regarding your comment, here is the promisified version. You need to pass .then a function. If you pass it .then request.done() then the request.done() expectation will be executed immediately and its result will be passed as the promise callback. Either wrap it in another function (see below) or remove the parenthesis (.then request.done). But be careful with the second option. If your promise returns a value, this will be passed to the callback. As it's from a library, this may cause unexpected behaviour - that's why I would suggest the first option:
it 'sends the break request to the rails server', ->
# capture the request to the rails app
railsRequest = nock(process.env.RAILS_APP_URL)
.post('/break', { slack_user_id: 'bob' })
.reply(200)
# make the request and wait for it to be completed
#room.user.say('bob', '#hubot Off to lunch').then ->
# check that the request was made
railsRequest.done()

How can you build a custom endpoint on another website?

I am trying to get used to flask but I want to basically create a custom endpoint.
For example:
given a website is called: abc.com
I basically want to create an endpoint: abc.com/me
which would then print something like "HI"
if I understand you correctyly, this peace of code is what you want to do.
When you run the code servers up and run on the localhost:5000 in default port, if you route localhost:5000/me page shows hi. also read documantation
from flask import Flask
app = Flask(__name__)
#app.route('/me')
def me():
return 'Hi'
if __name__ == '__main__':
app.run()

Twitter API working fron development server but not from GAE (Google App Engine)

I am trying to access Twitter API from GAE (Google App Engine), however it works from development environment but not in production environment. Code is this:
This is the code:
import requests,sys
sys.path.insert(0,'requests_oauthlib')
from requests_oauthlib import OAuth1
from urlparse import parse_qs
REQUEST_TOKEN_URL = "https://api.twitter.com/oauth/request_token"
CONSUMER_KEY = "xxxx"
CONSUMER_SECRET = "xxxx"
OAUTH_TOKEN = "xxxx"
OAUTH_TOKEN_SECRET = "xxxx"
oauth = OAuth1(CONSUMER_KEY, client_secret=CONSUMER_SECRET)
r = requests.post(url=REQUEST_TOKEN_URL, auth=oauth)
print r
After running the code in GAE I receive:
<Response [403]>
But after running the same code in development environment I receive:
<Response [200]>
What is the difference?
Why in GAE twitter response is 403, but from my dev env I receive 200?
(For a better reference my settings in twitter does not include a callback URL.)
Response 403 is the response from twitter when the request is made from GAE. (And the response is correct 200 when is sent from my development server).
As far as I understand response 403 is:
"403 Forbidden
The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead."
(This was taken from this link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
In the other hand Response 200:
"200 OK
The request has succeeded."
The point is, when production server (GAE PAAS Platform) makes the request, Why response from twitter is 403 (Forbiden), but when is made from dev server response is correct (200)?
Why twitter API is doing some kind of difference depending on who makes the request?.
In twitter API settings I authorized the application in GAE.
I wonder what is the difference.

Cannot get service account authorization to work on GCS script using Python client lib APIs

In attempting to write a python script to access GCS using service-based authorization, I have come up with the following. Note that 'key' is the contents of my p12 file.
I am attempting to just read the list of buckets on my account. I have successfully created one bucket using the web interface to GCS, and can see that with gsutil.
When I execute the code below I get a 403 error. At first I thought I was not authorized correctly, but I tried from this very useful web page (which uses web-based authorization), and it works correctly. https://developers.google.com/apis-explorer/#p/storage/v1beta1/storage.buckets.list?projectId=&_h=2&
When I look at the headers and query string and compare them to the keaders and query of the website-generated request I see that there is no authorization header, and that there is no key= tag in the query string. I suppose I thought that the credential authorization would have taken care of this for me.
What am I doing wrong?
code:
credentials = SignedJwtAssertionCredentials(
'xxx-my-long-email-from-the-console#developer.gserviceaccount.com',
key,
scope='https://www.googleapis.com/auth/devstorage.full_control')
http = httplib2.Http()
http = credentials.authorize(http)
service = build("storage", "v1beta1", http=http)
# Build the request
request = service.buckets().list(projectId="159910083329")
# Diagnostic
pprint.pprint(request.headers)
pprint.pprint(request.to_json())
# Do it!
response = request.execute()
When I try to execute I get the 403.
I got this working, however, the code I used is not fundamentally different from the snippet you posted. Just in case you'd like to diff my version with yours, attached below is a complete copy of a Python program that worked for me. I initially got a 403, just like you, which was due to inheriting your project id :). After updating that value to use my project ID, I got a correct bucket listing. Two things to check:
Make sure the project id you are using is correct and has the "Google Cloud Storage JSON API" enabled on the Google Developer Console "Services" tab (it's a different service from the other Google Cloud Storage API).
Make sure you are loading the service accounts private key exactly as it came from the developer's console. I would recommend reading it into memory from the file you downloaded, as I've done here, rather than trying to copy it into a string literal in your code.
#!/usr/bin/env python
import pprint
import oauth2client
from oauth2client.client import SignedJwtAssertionCredentials
import httplib2
from apiclient.discovery import build
f = open('key.p12', 'r')
key = f.read()
f.close()
credentials = SignedJwtAssertionCredentials(
'REDACTED',
key,
scope='https://www.googleapis.com/auth/devstorage.full_control')
http = httplib2.Http()
http = credentials.authorize(http)
service = build("storage", "v1beta1", http=http)
# Build the request
request = service.buckets().list(projectId="REDACTED")
# Diagnostic
pprint.pprint(request.headers)
pprint.pprint(request.to_json())
# Do it!
response = request.execute()
pprint.pprint(response)