Facing issue while POST when it has no payload (python code) - python

The code called by my script is (this is code in api.py)
def post(url, data=None, **kwargs):
"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
"""
return request('post', url, data=data, **kwargs)
Now I need to POST a request which has no payload, as the info which I need to post is in url itself. I have tried following combinations but failed:
1) requests.post(url, auth, data=None)
Fails saying:
result = requests.post(api, auth, data=None)
TypeError: post() got multiple values for keyword argument 'data'
2) requests.post(api, auth, data=payload) where payload is empty json.
Please suggest..

Passed auth param is accepted by function as data one. Then you passed data again, as keyword argument.
result = requests.post(api, auth, data=None)
TypeError: post() got multiple values for keyword argument 'data'
Try this:
result = requests.post(api, auth=auth)

The issue is the incompatibility between the method signature and how you call it.
def post(url, data=None, **kwargs):
result = requests.post(api, auth, data=None)
First of all, based on the error, its save to assume requests is a library you've written (and not the Python requests module), and not a class because you'd get a completely different error otherwise.
Your post method has 2 arguments, url and data.
Your call has three arguments, which python has to unpack: the variables api and auth and a named argument of data=None.
Python assigns api to the url variable in the methods scope, and auth to the data variable. This leaves an extra named data variable in the methods scope, which now is attempting to be assigned again.
Hence the error:
post() got multiple values for keyword argument 'data'

Related

What is the proper way of using python requests, `requests.request("GET",...)` or`requests.get`?

I'm learning Python requests through a book I purchased. From the book and from the websites I researched, it states this is the proper way to perform a GET request.
requests.get(url, params={key: value}, args)
In PyCharm it also shows the same thing.
However, when I use Postman to test and look at the sample code it shows:
requests.request("GET", params={key: value}, args
I'm not sure if I should use request.get or requests.request("GET",`
and why choose one over the other.
They are both correct and will work the same.
The best way to clear up this confusion is to look at the requests source code.
Here is the code for request.get (as of 2.25.1):
def get(url, params=None, **kwargs):
r"""Sends a GET request.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary, list of tuples or bytes to send
in the query string for the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('get', url, params=params, **kwargs)
...which shows that requests.get just calls requests.request with a hardcoded 'get' for the 1st argument. All the other parameters (url, params, **kwargs) are all just passed through.
Basically, it is just a convenience method or a shorthand or a shortcut so you don't have to manually remember which string to pass for the method parameter. It's easier especially when using an IDE because your IDE's IntelliSense can help you select .get or .post or .delete, etc. but not the raw strings "GET", "POST", or "DELETE", etc.
The requests docs can also offer some clarity.
requests.request(method, url, **kwargs)
It says "Constructs and sends a Request.". So this one is for ANY type of request, and you need to pass in 2 required arguments: the method and the URL. All the possible kwargs are listed in the doc, including the params from your example.
requests.get(url, params=None, **kwargs)
It says "Sends a GET request.". So this one is more specific in that it is only for a GET request. It only has 1 required argument, the URL. No need to pass "GET". And then kwargs is "Optional arguments that request takes." which just points back to the main requests.request method.
I would say it's a matter of opinion and coding style which one to use. A use-case where it makes sense to prefer requests.request is when wrapping different API calls and providing a Python interface for them.
For example, I have these APIs:
GET /api/v1/user/[user-id]
PATCH /api/v1/user/[user-id]
DELETE /api/v1/user/[user-id]
When implementing get_user and update_user and delete_user, I could just call the .get, .post, .delete, etc. methods. But if calling these APIs required passing in many and/or complicated kwargs (headers, auth, timeout, etc.), then that would lead to a lot of duplicated code.
Instead, you can do it all in one method then use requests.request:
def get_user(user_id):
return call_api("GET", user_id)
def update_user(user_id, user):
return call_api("PATCH", user_id, user=user)
def delete_user(user_id):
return call_api("DELETE", user_id)
def call_api(method, user_id, user=None):
# Lots of preparation code to make a request
headers = {
"header1": "value1",
"header2": "value2",
# There can be lots of headers...
"headerN": "valueN",
}
timeout = (1, 30)
payload = user.json() if user else {}
url = f"/api/v1/user/{user_id}"
return requests.request(
method,
url,
headers=headers,
timeout=timeout,
json=payload,
)
There are probably other ways to refactor the code above to reduce duplication. That's just an example, where if you called .get, .patch, .delete directly, then you might end up repeating listing all those headers every time, setting up the URL, doing validations, etc.

Unit testing Python Azure Function: How do I construct a mock test request message with a JSON payload?

I want to unit test my Python Azure function. I'm following the Microsoft documentation.
The documentation mocks the call to the function as follows
req = func.HttpRequest(
method='GET',
body=None,
url='/api/HttpTrigger',
params={'name': 'Test'})
I would like to do this but with the parameters passed as a JSON object so that I can follow the req_body = req.get_json() branch of the function code. I guessed I would be able to do this with a function call like
req = func.HttpRequest(
method='GET',
body=json.dumps({'name': 'Test'}),
url='/api/HttpTrigger',
params=None)
If I construct the call like this, req.get_json() fails with the error message AttributeError: 'str' object has no attribute 'decode'.
How do I construct the request with JSON input parameters? It should be trivial but I'm clearly missing something obvious.
If I construct my mock call as follows:
import json
req = func.HttpRequest(
method='POST',
body=json.dumps({'name': 'Test'}).encode('utf8'),
url='/api/HttpTrigger',
params=None)
Then I am able to make a successful call to req.get_json(). Thanks to #MrBeanBremen and #JoeyCai for pointing me in the correct direction i.e. don't call GET and make the message a byte string.
Any HTTP request message is allowed to contain a message body, and thus must parse messages with that in mind. Server semantics for GET, however, are restricted such that a body, if any, has no semantic meaning to the request. The requirements on parsing are separate from the requirements on method semantics.
For your http request, it is a GET method. You can send a request body with GET but it should not have any meaning.
So use the below code to construct a mock HTTP request with a json payload. For more details, you could refer to this article.
req = func.HttpRequest(
method='GET',
body=None,
url='/api/HttpTrigger',
params={'name': 'Test'})
Update:
For Post request, you could send json payload with body=json.dumps({'name': 'Test'}).encode('utf8') while body expects a byte string:
req = func.HttpRequest(
method='POST',
body=json.dumps({'name': 'Test'}).encode('utf8'),
url='/api/HttpTrigger',
params=None)

How to access token based API using requests module in python?

I want to call one API from another API. So external API is having jwt token based authentication .
import requests
response = requests.get('http://host:port/api/users', auth= ("username","password"))
I am getting error :
{"error":"authentication failed: jwt parse error: token contains an invalid number of segments","code":16,"message":"authentication failed: jwt parse error: token contains an invalid number of segments","details":[]}
Or , First i need to call login API ,get the token and while calling another API apply that token in header. If it so then why 'auth' param is there in requests.get(URL, auth=(username,password))?
Here is (from a high-level point of view) the mechanism behind requests:
When the request is constructed request(method, url, **kwargs) only the method and url arguments are mandatory the rest are optional:
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
Afterwards from the methods perspective:
def get(url, params=None, **kwargs):
r"""Sends a GET request.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary, list of tuples or bytes to send
in the query string for the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('get', url, params=params, **kwargs)
def post(url, data=None, json=None, **kwargs):
r"""Sends a POST request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json data to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request('post', url, data=data, json=json, **kwargs)
For get and post mandatory argument is the url and the others are default or optional.
Many web services may require authentication like HTTP Basic Auth. This is the simplest kind, and Requests supports it straight out of the box.
from requests.auth import HTTPBasicAuth
requests.get('http://host:port/api/users', auth=('user', 'pass'))
Which is the same with
from requests.auth import HTTPBasicAuth
requests.get('http://host:port/api/user', auth=HTTPBasicAuth('user', 'pass'))
So basically it's very important how the API was implemented from the authentication point of view (HTTPBasicAuth, HTTPDigest Authentication, OAuth1). Based on this you can use the appropriate module(within requests) in order to authenticate.
Hope this helps

python 3.x urllib.request.HTTPSHandler override METHOD GET POST

context = ssl.create_default_context()
context.load_cert_chain(certificate, pkey)
opener = urllib.request.build_opener(urllib.request.HTTPSHandler(context=context))
response = opener.open(url, data=None)
print(response.read())
Executing the above code with data=None automatically sets the METHOD to GET, while setting data to anything else automatically sets the METHOD to POST.
Is there a way to override this behavior?
According to the docs you can use Request.method, but I'm not understanding how to reference that from 'opener'. https://docs.python.org/3/library/urllib.request.html
Request.method
The HTTP request method to use. By default its value is None, which means that get_method() will do its normal computation of the method to be used. Its value can be set (thus overriding the default computation in get_method()) either by providing a default value by setting it at the class level in a Request subclass, or by passing a value in to the Request constructor via the method argument.
New in version 3.3.
Changed in version 3.4: A default value can now be set in subclasses; >previously it could only be set via the constructor argument.
"method should be a string that indicates the HTTP request method that will be used (e.g. 'HEAD'). If provided, its value is stored in the method attribute and is used by get_method(). The default is 'GET' if data is None or 'POST' otherwise. Subclasses may indicate a different default method by setting the method attribute in the class itself."
If you use the urllib.request.Request you can use the method argument to set the specific method you want for this request:
class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
However - when you use the opener you can't provide the method:
OpenerDirector.open(url, data=None[, timeout])
There is no method argument for the open method.
What you can do - is create a Request object and use the opener to send that request:
req = urllib.request.Request(url, method='POST')
res = opener.open(req)
print(res.read())

Dealing with variable number of keyword arguments to send requests

Related to Python 2.7
How would one go about building a request through a variable number of kwargs when using requests.
I am using the requests module to directly interact with a REST API which requires a variable number of keyword arguments in order to be successful.
Rather than re-writing the same GET/POST request code, I would like to maintain it within a single api class. However handling the variable number of arguments seems to boil down to a series of if-else statements which isn't particularly readable.
For example:
def request(self):
try:
if self.data:
request = requests.post(url=self.url, headers=self.headers,
data=self.data, timeout=self.timeout, verify=False)
else:
request = requests.get(url=self.url, headers=self.headers,
timeout=self.timeout, verify=False)
...
...
Preferably the request properties are build over time and them passed through a single GET or POST request (granted, the above code would still be require but that is minor).
If you make the attributes default to the same values as arguments to requests.post (basically, None), than you can safely pass all of them as keyword arguments:
def request(self):
try:
request = requests.post(url=self.url, headers=self.headers,
data=self.data, timeout=self.timeout,
verify=False)
...

Categories

Resources