APIClient headers for testing - python

I'm working with django rest_framework and consuming an API that brings a header value to validate the sender. I have the problem when I use APIClient to test the webhook that I made.
#pytest.mark.django_db
def test_change_status_webhook_ok(webhook_client, status_changes):
fixture_signature = (
"51b93156c361bfce14c527ddcb27cc3791e9ea6ede23bc5a56efa3be28e6a54d"
)
url = reverse("webhook_v1:notification")
response = webhook_client.post(
url,
json.dumps(status_changes),
content_type="application/json",
**{"X-Extserv-Signature": fixture_signature}
)
assert response.status_code == 200
The problem is when I tried to get the X-Extserv-Signature from headers.
I tried with:
ret = request.headers.get("X-Extserv-Signature")
this way only works when I receive a request from postman.. but the below form works when I do the request from APIClient and cannot get the value using the same above code.
ret = request.META["X-Extserv-Signature"]
You know how I can set the X-Extserv-Signature key value to headers in APIClient?

From the docs:
A case insensitive, dict-like object that provides access to all HTTP-prefixed headers
request.headers will only contain headers that are HTTP-prefixed, so:
response = webhook_client.post(
url,
json.dumps(status_changes),
content_type="application/json",
**{"HTTP_X_EXTSERV_SIGNATURE": fixture_signature} # <-- Use this
)

Related

request.Request to delete a gitlab branch does not work but works using curl DELETE

I am trying to delete a git branch from gitlab, using the gitlab API with a personal access token.
If I use curl like this:
curl --request DELETE --header "PRIVATE_TOKEN: somesecrettoken" "deleteurl"
then it works and the branch is deleted.
But if I use requests like this:
token_data = {'private_token': "somesecrettoken"}
requests.Request("DELETE", url, data= token_data)
it doesn't work; the branch is not deleted.
Your requests code is indeed not doing the same thing. You are setting data=token_data, which puts the token in the request body. The curl command-line uses a HTTP header instead, and leaves the body empty.
Do the same in Python:
token_data = {'Private-Token': "somesecrettoken"}
requests.Request("DELETE", url, headers=token_data)
You can also put the token in the URL parameters, via the params argument:
token_data = {'private_token': "somesecrettoken"}
requests.Request("DELETE", url, params=token_data)
This adds ?private_token=somesecrettoken to the URL sent to gitlab.
However, GitLab does accept the private_token value in the request body as well, either as form data or as JSON. Which means that you are using the requests API wrong.
A requests.Request() instance is not going to be sent without additional work. It is normally only needed if you want to access the prepared data before sending.
If you don't need to use this more advanced feature, use the requests.delete() method:
response = requests.delete(url, headers=token_data)
If you do need the feature, use a requests.Session() object, then first prepare the request object, then send it:
with requests.Session() as session:
request = requests.Request("DELETE", url, params=token_data)
prepped = request.prepare()
response = session.send(prepped)
Even without needing to use prepared requests, a session is very helpful when using an API. You can set the token once, on a session:
with requests.Session() as session:
session.headers['Private-Token'] = 'somesecrettoken'
# now all requests via the session will use this header
response = session.get(url1)
response = session.post(url2, json=....)
response = session.delete(url3)
# etc.

hosting an image with the flask and then processing the same using another view function in the same code

so I am hosting an image using flask and then I want to do a post request to an API using the url all in the same code:
#app.route('/host')
def host():
return send_from_directory("C:/images", "image1.png")
#app.route('/post')
def post():
response = requests.post(url, data={'input':'<url for host>', headers=headers)
return jsonify(response.json())
I believe as both these view functions are in the same python file, post() gets blocked.
Is there a workaround this problem ?
PS: if I host images on a different machine, it works, but that's not what I desire.
Thanks!
I think there are some problems with your code.
First, I don't believe there is an #app.post() decorator in Flask. My guess is that you were trying to specify that that route should be POSTed to by your users. The way to do that would be #app.route('/post', methods=['POST']).
Next, it seems like you want the /post endpoint to send a POST request to a user-specified(?) URL when the user sends an HTTP request to this endpoint. The way you would do that for a user-specified / user-POSTed URL is something like this (I haven't run this code to test it):
#app.route('/send_post_request', methods=['POST'])
def send_post_request():
user_posted_data = json.loads(request.data)
user_specified_url = user_posted_data['url']
dict_to_post= { 'input': url_for('hosts') }
headers = {} # Fill these in
response = requests.post(user_specified_url , json=dict_to_post, headers=headers)
return jsonify(response.json())
If the URL to send the POST request to is known by the server, you could have your user simply send a GET request:
#app.route('/send_post_request', methods=['GET'])
def send_post_request():
dict_to_post = { 'input': url_for('hosts') }
headers = {} # Fill these in
server_specified_url = '' # Fill this in
response = requests.post(server_specified_url, json=dict_to_post, headers=headers)
return jsonify(response.json())

Flask Testing a put request with custom headers

Im trying to test a PUT request in my Flask app, using flasks test client.
Everything looks good to me but i keep getting 400 BAD request.
I tried the same request using POSTMAN and I get the response back.
Here is the code
from flask import Flask
app = Flask(__name__)
data = {"filename": "/Users/resources/rovi_source_mock.csv"}
headers = {'content-type': 'application/json'}
api = "http://localhost:5000/ingest"
with app.test_client() as client:
api_response = client.put(api, data=data, headers=headers)
print(api_response)
Output
Response streamed [400 BAD REQUEST]
You do need to actually encode the data to JSON:
import json
with app.test_client() as client:
api_response = client.put(api, data=json.dumps(data), headers=headers)
Setting data to a dictionary treats that as a regular form request, so each key-value pair would be encoded into application/x-www-form-urlencoded or multipart/form-data content, if you had used either content type. As it is, your data is entirely ignored instead.
I think it is simpler to just pass the data using the json parameter instead of the data parameter:
reponse = test_client.put(
api,
json=data,
)
Quoting from here:
Passing the json argument in the test client methods sets the request
data to the JSON-serialized object and sets the content type to
application/json.

Microsoft Dynamics CRM 2016 Web API Put request clears value

I'm trying to use a put request to update Dynamics CRM. When I run the following python code I get a 204 response, but instead of updating a value it clears it. For example firstname gets updated to null instead of "WebApiUpdate".
import adal
import json
import urllib2
import requests
endpoint = "https://login.microsoftonline.com/00000000-0000-0000-0000-000000000002"
username = "example#example.com"
password = "password"
resource_uri = "https://orgname.crm.dynamics.com"
token_response = adal.acquire_token_with_username_password(endpoint,username,password,resource= resource_uri)
print token_response['accessToken']
payload = { "value": "WebApiUpdate"}
url = "http://xxxxx.crm.dynamics.com/api/data/v8.0/contacts(00000000-0000-0000-0000-000000000001)/firstname"
headers = {"Authorization":"Bearer "+token_response['accessToken']}
r = requests.put(url,data=payload,headers=headers)
print r.content
Solution:
set the url to be https:// instead of http://
set data = payload to json = payload in the request. ex:
r = requests.put(url,json=payload,headers=headers)
What was going wrong:
The initial call, which included the payload (regardless of if it was set to data or json), received a response of HTTP/1.0 301 Moved Permanently. In this response was a header with the new location. This location simply included https:// in the url. Then my code sent out a second put request that went to the correct https url, had the correct header parameters, but did not include the payload. This caused my put request to not include the value to update to, so Microsoft Dynamics CRM set the value to null.
Microsoft Dynamics CRM requires the put request payload to be formatted in json. After I updated the link to be https:// and the payload was included, it was formatted as x-www-form-urlencoded.That means the payload was appended to the request as value=WebApiUpdate. This caused a reply of HTTP/1.1 400 Bad Request. Changing the payload to be formatted as json means the payload will be appended to the request as {"value": "WebApiUpdate"}.
How I solved it:
I set my program to output the http put call it was making by adding the below code that I found from this stack overflow question. I then noticed that there were two non identical put calls and went from there.
import logging
try:
import http.client as http_client
except ImportError:
# Python 2
import httplib as http_client
http_client.HTTPConnection.debuglevel = 1
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
I read the documentation on the request method here which I found on this stack overflow question

Return a requests.Response object from Flask

I'm trying to build a simple proxy using Flask and requests. The code is as follows:
#app.route('/es/<string:index>/<string:type>/<string:id>',
methods=['GET', 'POST', 'PUT']):
def es(index, type, id):
elasticsearch = find_out_where_elasticsearch_lives()
# also handle some authentication
url = '%s%s%s%s' % (elasticsearch, index, type, id)
esreq = requests.Request(method=request.method, url=url,
headers=request.headers, data=request.data)
resp = requests.Session().send(esreq.prepare())
return resp.text
This works, except that it loses the status code from Elasticsearch. I tried returning resp (a requests.models.Response) directly, but this fails with
TypeError: 'Response' object is not callable
Is there another, simple, way to return a requests.models.Response from Flask?
Ok, found it:
If a tuple is returned the items in the tuple can provide extra information. Such tuples have to be in the form (response, status, headers). The status value will override the status code and headers can be a list or dictionary of additional header values.
(Flask docs.)
So
return (resp.text, resp.status_code, resp.headers.items())
seems to do the trick.
Using text or content property of the Response object will not work if the server returns encoded data (such as content-encoding: gzip) and you return the headers unchanged. This happens because text and content have been decoded, so there will be a mismatch between the header-reported encoding and the actual encoding.
According to the documentation:
In the rare case that you’d like to get the raw socket response from the server, you can access r.raw. If you want to do this, make sure you set stream=True in your initial request.
and
Response.raw is a raw stream of bytes – it does not transform the response content.
So, the following works for gzipped data too:
esreq = requests.Request(method=request.method, url=url,
headers=request.headers, data=request.data)
resp = requests.Session().send(esreq.prepare(), stream=True)
return resp.raw.read(), resp.status_code, resp.headers.items()
If you use a shortcut method such as get, it's just:
resp = requests.get(url, stream=True)
return resp.raw.read(), resp.status_code, resp.headers.items()
Flask can return an object of type flask.wrappers.Response.
You can create one of these from your requests.models.Response object r like this:
from flask import Response
return Response(
response=r.reason,
status=r.status_code,
headers=dict(r.headers)
)
I ran into the same scenario, except that in my case my requests.models.Response contained an attachment. This is how I got it to work:
return send_file(BytesIO(result.content), mimetype=result.headers['Content-Type'], as_attachment=True)
My use case is to call another API in my own Flask API. I'm just propagating unsuccessful requests.get calls through my Flask response. Here's my successful approach:
headers = {
'Authorization': 'Bearer Muh Token'
}
try:
response = requests.get(
'{domain}/users/{id}'\
.format(domain=USERS_API_URL, id=hit['id']),
headers=headers)
response.raise_for_status()
except HTTPError as err:
logging.error(err)
flask.abort(flask.Response(response=response.content, status=response.status_code, headers=response.headers.items()))

Categories