How to utilise payload instead of params in Taskqueue? - python

Within my unitest when I attempt the following POST; I get the params within the request.base_url. I would like to have it within request.form. How do I achieve that?
self.taskqueue_stub = apiproxy_stub_map.apiproxy.GetStub('taskqueue')
tasks = self.taskqueue_stub.GetTasks("postios")
self.assertEqual(len(tasks), 1)
task = tasks[0]
params = base64.b64decode(task["body"])
headers['Content-Type'] = 'application/json'
response = self.client.post(task["url"], params, headers=headers)
I found a way to pass it as data: request.data, but thats not good enough.
response = self.client.post(task["url"], data=params, headers=headers)
The reason I have to do this, is the way I add the task in my code.
taskqueue.Task(url='/worker',
params={"json_records": jsonified_task_records,
"user": user.key.urlsafe()}
).add(queue_name='postios')
Hence within the /worker view I expect to find the params in request.form.

I ended up using payload instead of params.
params = {"json_records": jsonified_task_records,
"user": user.key.urlsafe()}
taskqueue.Task(url='/worker',
payload=json.dumps(params)
).add(queue_name='postios')
Now I always have it as request.data within my /worker view so that both unit tests and production code can expect to find it there.
Beware that payloads is unicode in this case and nested json structure needs to be converted once again via json.loads.
e.g. /worker view:
jsons = json.loads(request.data)
user_hash = jsons['user']
json_records = json.loads(jsons['json_records']) # jsons['json_records'] is unicode

Related

How to mock a url path returning response in Django / Python?

I have a function like this:
def get_some_data(api_url, **kwargs)
# some logic on generating headers
# some more logic
response = requests.get(api_url, headers, params)
return response
I need to create a fake/mock "api_url", which, when made request to, would generate a valid response.
I understand how to mock the response:
def mock_response(data):
response = requests.Response()
response.status_code = 200
response._content = json.dumps(data)
return response
But i need to make the test call like this:
def test_get_some_data(api_url: some_magic_url_path_that_will_return_mock_response):
Any ideas on how to create an url path returning a response within the scope of the test (only standard Django, Python, pytest, unittest) would be very much appreciated
The documentation is very well written and more than clear on how to mock whatever you want. But, let say you have a service that makes the 3rd party API call:
def foo(url, params):
# some logic on generating headers
# some more logic
response = requests.get(url, headers, params)
return response
In your test you want to mock the return value of this service.
#patch("path_to_service.foo")
def test_api_call_response(self, mock_response):
mock_response.return_value = # Whatever the return value you want it to be
# Here you call the service as usual
response = foo(..., ...)
# Assert your response

API Query Doesn't return results when "params" is encoded into a get request but API call works when URL isn't encoded

So when I access the API target via Postman with the URL below, it works fine without any issues
base_url = https://api.cats.net/orgs/CatEmpire/audit-log?phrase=action:stuck_on_tree+date_of_event:2022-01-11
However, when I append the below parameters into my request, the URL comes out differently and I'm no longer able to get results
parameters = {
'action:': 'stuck_on_tree',
'date_of_event:': '2022-01-11'
}
PAT = asdasdhdhdhhd123123
response = requests.get(base_url, headers={"Accept": "application/vnd.github.v3+json", "Authorization": f"Bearer {PAT}"}, params=parameters)
print(response.request.url)
#This returns https://api.cats.net/orgs/CatEmpire/audit-log?phrase=&action%3A=stuck_on_tree&date_of_event%3A=2022-01-11
I have tried to use:
paramters_string = urllib.parse.urlencode(parameters, safe='')
And then I updated my response variable below, but the results are still exaxtly the same. I have tried to do some digging but I can't seem to figure out if this an issue because I'm using a dictionary to pass the params, or if there's something else that I'm not able to understand. I'm fairly new to Python.
`response = requests.get(base_url, headers={"Accept": "application/vnd.github.v3+json", "Authorization": f"Bearer {PAT}"}, params=parameters)`
Your base URL should not include part of your query string (?phrase=).
Use this for your base URL:
https://api.cats.net/orgs/CatEmpire/audit-log
For your parameters, use this:
parameters = {
'phrase': 'action:stuck_on_tree+date_of_event:2022-01-11'
}
Update
Since you can't URL encode your parameters due to API constraints, you'll have to pass them as a string like so:
parameters = 'phrase=action:stuck_on_tree+date_of_event:2022-01-11'
have you tried ?
PAT = "asdasdhdhdhhd123123"
data = parameters
this is just an example of authorization request:
import requests
endpoint = ".../api/ip"
data = {"ip": "1.1.2.3"}
headers = {"Authorization": "Bearer MYREALLYLONGTOKENIGOT"}
response = requests.post(endpoint, data=data, headers=headers)
So I figured it out. I needed to create a separate key for each parameter like below:
params = {
'phrase': 'action:stuck_on_tree',
'date_of_event:': '2022-01-11'
}
I'm not sure why I couldn't fit everything into the 'phrase' key, but this works and is also what I was trying to do to begin with because I wanted to be able to pass things dynamically into the params dictionary, so having all of my parameters in one key was going to be an issue. I didn't need to encode or unencode anything. Basically the semi colon was my issue lol. Thanks for all your help #dimz & #john glenn!

What is the best method to return smmry api request with block of text in json instead of url?

I am trying to write a function in python that returns the json from a request to the smmry API. I was able to get it working with the SM_URL request like this:
def summry():
API_ENDPOINT = "https://api.smmry.com"
API_KEY = "B..."
params = {
"SM_API_KEY":API_KEY,
"SM_URL":"https:..."
}
r = requests.get(url=API_ENDPOINT, params=params)
return r.json()
However, I am not sure how you would do this for passing in a block of text instead of a URL. I have tried making the request with sm_api_input=my_input but that returned an error of insufficient variables. I have also tried it with a POST request and got the same error.
If anyone is curious, this is how I solved the problem. Turns out I needed an Expect: 100-continue header and the sm_api_input is a separate post field instead of a get query.
def summry(text):
API_KEY = "B..."
API_ENDPOINT = "https://api.smmry.com"
data = {
"sm_api_input":text
}
params = {
"SM_API_KEY":API_KEY
}
header_params = {"Expect":"100-continue"}
r = requests.post(url=API_ENDPOINT, params=params, data=data, headers=header_params)
return r.json()

Python GET request with a nested parameters

I'm trying to write API client for Jira with Python requests lib according reference:
https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/
Request to be generated:
http://localhost:8080/rest/api/2/search?jql=assignee=charlie&startAt=2&maxResults=2
As I know, parameters to GET request should be passed as dictionary like:
params = {'assignee':'charlie', 'startAt':'2'}
But all main parameters are nested in jql parameter, so I assume there is should be a nested dict like:
params = {'jql': {'assignee': 'charlie'}}
But that's doesn't work - as a result I've got request to
/rest/api/2/search?jql=assignee
As expect /rest/api/2/search?jql=assignee=charlie
using
r = requests.get(url, params=params)
How to manage such request?
UPD:
To be more clear, I'd like to wrap request in a method with kwargs, like:
search_query(assignee='charlie', startAt=1, etc...)
And then generate a query using this params, but maybe there are any other ideas.
You are missing couple of key parameters, mainly if you are pushing data via requests, the data go into the data argument. Also the moment you push JSON data, you need to set the headers correctly as well. The last thing is authentication. Have you tried to post it in this manner?
import json
requests.post(url=url, headers={"Content-Type": "application/json"},
auth=('username', 'password'), # your username and password
data=json.dumps(params)
)
Also by the JIRA documentation you've provided (https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/) if you want to push query as data, the url you want is /rest/api/2/search.

Using Python requests, can I add "data" to a prepared request?

The code below sets up _request if the HTTP method is GET, then it has an ifstatement for handling PUT POST and PATCH.
I'm trying to have one single request setup statement for all method types.
Is this possible? It appears to me that there is no way to add data to a prepared request, and if this is true then perhaps I'm stuck with needing two different ways of setting up a request, one way for GET and one way for PUT, PATCH and POST.
def fetch_from_api(self):
s = Session()
headers = { "Authorization" : REST_API_AUTHORIZATION_HEADER}
_request = Request(self.method, self.url_for_api, headers=headers)
if self.method in ['POST', 'PATCH', 'PUT']:
headers['content-type'] = 'application/x-www-form-urlencoded'
_request = Request(self.method, self.url_for_api, headers=headers, data=self.postdata)
prepped = _request.prepare()
self.api_response = s.send(prepped)
The question is a little old and hopefully #DukeDougal already has a solution. Maybe this will help others, though.
The first thing I notice in the example is that a Request object is created near the beginning of the method. Then, if the method is "POST", "PATCH", or "PUT", the Request constructor is called again to get another object. In that case, the first object is gone. It was created unnecessarily.
When a data= argument isn't given to the Request constructor, it's the same as specifying data=None. Take advantage of that and call the constructor only once, then a data value won't need to be added to an existing Request (or PreparedRequest) object:
def fetch_from_api(self):
s = Session()
headers = {'Authorization': REST_API_AUTHORIZATION_HEADER}
data = None # Assume no data until method is checked
if self.method in ['POST', 'PATCH', 'PUT']:
headers['content-type'] = 'application/x-www-form-urlencoded'
data = self.postdata # Add the data
# Now headers and data are ready, get a Request object
_request = Request(self.method, self.url_for_api, headers=headers, data=data)
prepped = _request.prepare()
self.api_response = s.send(prepped)
If you look at the requests.Request model, it looks like you can set the data attribute if needed:
some_request = Request(method, url, headers=headers)
if # ...we decide we need to add data:
some_request.data = data
Looking at the model, it appears that this would work, because when you prepare the request later on, it looks at the instance's data attribute.
EDIT:
But reading your question a bit more closely, it looks like you want to add data to a prepared_request. I guess you could create your own prepared_request and pass the data in specifically when you call the prepare method, but I don't see how that helps? It seems like you want to just branch and maybe add data or maybe not?
Anyway, the above seems it could potentially simplify your code slightly to the following:
def fetch_from_api(self):
s = Session()
headers = { "Authorization" : REST_API_AUTHORIZATION_HEADER}
_request = Request(self.method, self.url_for_api, headers=headers)
if self.method in ['POST', 'PATCH', 'PUT']:
headers['content-type'] = 'application/x-www-form-urlencoded'
_request.data = self.postdata
prepped = _request.prepare()
self.api_response = s.send(prepped)
(But that doesn't look much simpler to me. What are we trying to achieve? Also, it seems weird to have a method called fetch_from_api that could also be POSTing or PUTing data. As a dev, I would not be expecting that to be the case from the name.)
In the past, I've done stuff like this as a result of having to sign requests: I have to create them in one place and then hand them off to a class that knows how to create signatures, which then hands them back. In other words, you can certainly edit requests before preparing and sending them on their way.
Anyway, I haven't tried any of this, but it's similar to some things I've done in the past with requests, so it looks legit, but I would be concerned about what you are attempting to achieve and whether or not things are being crammed together which maybe should not be.
I am using HTTPforHumans, requests module.
import requests
def pv_request(url, methods, data=None, headers=None, type=None):
try:
if 'POST' in methods:
return requests.post(url=url, headers=headers, data=data).json()
elif 'GET' in methods:
return requests.get(url=url, headers=headers, data=data).json()
elif 'PUT' in methods:
if type == 'music':
return requests.put(url=url, headers=headers, data=data).json()
elif type == 'image':
return requests.put(url=url, headers=headers, data=open(data, 'rb')).json()
except requests.exceptions.ConnectionError:
return None
Might not be in the lines of what you are looking for, but here is my all-in-on purpose request handler.

Categories