Python requests call not handling authentication as expected - python

I have a list in which I am putting the url and authentication parameters for a requests get call. If I try the call this way I get authentication error (401) but if I break out the auth by itself explicitly on the call things work. Why can't I include auth in this manner and expect it to properly "explode" in the call?
parms = []
parms.append(url)
parms.append('auth=(''UID'', ''PWD'')')
response = requests.get(*parms)
This results in 404 because it is not recognizing the authentication. BUT, if I do this it works. To me these seem the same. The single quotes are only different to get the string to append in the list properly. I thought the first way would result in 2 parameters - url and auth
parms = []
parms.append(url)
response = requests.get(*parms, auth=('UID', 'PWD'))

The first one is equivalent to the following:
requests.get(url, "auth=('UID', 'PWD')")
When you really want:
requests.get(url, auth=('UID', 'PWD'))
You must use this instead:
args = []
kwargs = {}
args.append(url)
kwargs['auth'] = ('UID', 'PWD')
requests.get(*args, **kwargs)
The rule is:
when you have function(foo, bar) you are using positional parameters, they go into *args.
when you have function(foo=1, bar=2) you are using named parameters, they go into **kwargs.
you can mix both, in Python 2 positional parameters must be placed before named parameters.

Related

Zeep got an unexpected keyword argument in operation

I'm trying to send a request to a WSDL WebServices. According to documentation, the requests must be formatted in XML.
I'm using Python and Zeep module. The operation a want to use is called 'Consulta' and parameters are called 'Comercio' and 'NumPedido' (first one is numeric type and second is alphanumeric).
The code I've tried is the following:
from zeep import Client, Settings
settings = Settings(strict=False, xml_huge_tree=True)
client = Client('https://testws.punto-web.com/wcfadproc/srvproceso.svc?wsdl', settings=settings)
request_data = { 'Comercio': '8004749', 'NumPedido': '7030510'}
client.service.Consulta_Marca(**request_data)
But this gives me error TypeError: {http://tempuri.org/}Consulta_Marca() got an unexpected keyword argument 'NumPedido'. Signature: `xml: xsd:string`
Can anyone give me some advice about fixing this? Because seems that the operation is correct, but do not why the parameters are bad.
EDIT:
I've tried this little new code
from zeep import Client, xsd, Settings
settings = Settings(strict=False, xml_huge_tree=True)
client = Client('https://testws.punto-web.com/wcfadproc/srvproceso.svc?wsdl', settings = settings)
parameters = xsd.Element(
'Consulta',
xsd.ComplexType([
xsd.Element(
'Comercio', xsd.Integer()
),
xsd.Element(
'NumPedido', xsd.String()
)
])
)
parameters_values = parameters( Comercio=8004749, NumPedido='7030510')
operation = client.service.Consulta_Marca(parameters_values)
operation
This gives me another error
The formatter threw an exception while trying to deserialize the message: Error in deserializing body of request message for operation 'Consulta_Marca'. End element 'xml' from namespace 'http://tempuri.org/' expected. Found element 'Comercio' from namespace ''. Line 2, position 465
But think I'm closer because if I execute
client.service.Consulta_Marca(parameters), I get a valid response, but with null values because no parameters values were passed.
Please some help!

how can add extra key,value to response of request in python

I need to add some extra key, value to response in requests lib of python3 without changing response structure (because the response will be sent into another service for the process). for example, I received this response:
>>import requests
>>r = requests.post(url=input_kong_address, data=data)
>>print(r.json())
{
"foo":"bar",
"key1":"val1"
}
I need to add "extra_key":"extra_value" to response:
{
"foo":"bar",
"key1":"val1",
"extra_key":"extra_value"
}
and now I want to add some extra key to response and sent it to the next service without changing in structure (class, type and etc):
>>import requests
>>import json
>>r = requests.post(url=input_kong_address, data=data)
>>response_data = r.json()
>>response_data['extra_key']='extra_value' # trying to add extra key and value to response
>>r.json = json.loads(json.dumps(response_data)) # trying to attach new dict to response
>>r.json() # check is worked?
{TypeError}'dict' object is not callable
thank you.
response.json() is a method, so rebinding it to a dict can only lead to this behaviour indeed. Now if you read the source of the response class, you'll find out that this method actually operates on the ._content attribute (accessed via either the .content and/or .text properties). IOW, you just have to assign your serialized json string to response._content:
>>> import requests
>>> import json
>>> r = requests.get("https://www.google.com")
>>> r._content = json.dumps({"foo": "bar"})
>>> r.json()
{u'foo': u'bar'}
>>>
This being said:
the response will be sent into another service for the process
you may want to think twice about your design then. It's of course impossible to tell without knowing much more about your concrete use case, but ask yourself whether this "other service" really needs the whole response object.

List of query params with Flask request.args

I am trying to pass comma separated query parameters to a Flask endpoint.
An example URI would be:
localhost:3031/someresource#?status=1001,1002,1003
Looking at the return of request.args or request.args.getlist('status') I see that I only get a string.
ipdb> pp request.args
ImmutableMultiDict([('status', '1001,1002,1003')])
ipdb> request.args.getlist('status')
['1001,1002,1003']
I know I can split the string by comma but that feels hacky. Is there a more idiomatic way to handle this in Flask? Or are my query params wrong format?
Solution
Since Flask does not directly support comma separated query params, I put this in in my base controller to support comma-separated or duplicate query params on all endpoints.
request_data = {}
params = request.args.getlist('status') or request.form.getlist('status')
if len(params) == 1 and ',' in params[0]:
request_data['status'] = comma_separated_params_to_list(params[0])})
else:
request_data['status'] = params
def comma_separated_params_to_list(param):
result = []
for val in param.split(','):
if val:
result.append(val)
return result
The flask variant getlist expects multiple keys:
from flask import Flask, request
app = Flask(__name__)
#app.route('/')
def status():
first_status = request.args.get("status")
statuses = request.args.getlist("status")
return "First Status: '{}'\nAll Statuses: '{}'".format(first_status, statuses)
❯ curl "http://localhost:5000?status=5&status=7"
First Status: '5'
All Statuses: '['5', '7']'
There's no standard for this, how multiple GET args are parsed/passed depends on which language/framework you're using; flask is built on werkzeug so it allows this style, but you'll have to look it up if you switch away from flask.
As an aside, Its not uncommon REST API design to have commas to pass multiple values for the same key - makes it easier for the user. You're parsing GET args anyway, parsing a resulting string is not that much more hacky. You can choose to raise a 400 HTTP error if their string with commas isn't well formatted.
Some other languages (notably PHP) support 'array' syntax, so that is used sometimes:
/request?status[]=1000&status[]=1001&status[]=1002
This is what you might want here:
request.args.to_dict(flat=False)
flat is True by default, so by setting it to False, you allow it to return a dict with values inside a list when there's more than one.
According to to_dict documentation:
to_dict(flat=True)
Return the contents as regular dict. If flat is True
the returned dict will only have the first item present, if flat is False
all values will be returned as lists.

How do I access a resource with multiple endpoints with Flask-Restful?

Here's a simple Flask-Restful resource:
class ListStuff(Resource):
def get(self):
stuff = SomeFunctionToFetchStuff()
if re.match('/api/', request.path):
return {'stuff': stuff}
return make_response("{}".format(stuff), 200, {'Content-Type': 'text/html'})
api.add_resource(ListStuff, '/list', '/api/list', endpoint='list')
My idea is to allow users to call both /list and /api/list. If they use the first URL, they will get back an HTML representation of the data. If they use the second URL, they will get a JSON representation.
My trouble is when I want to access the URL of this endpoint elsewhere in the program. I can't just use url_for('list'), because that will always return /list , no matter whether the user accessed http://host.example.com/list or http://host.example.com/api/list
So how can I build the URL for /api/list ?
Looks like Hassan was on the right track - I can add a new resource for the same class but give it a different endpoint.
api.add_resource(ListStuff, '/list', endpoint='list')
api.add_resource(ListStuff, '/api/list', endpoint='api-list')
>>> print('URL for "list" is "{}"'.format(url_for('list'))
>>> print('URL for "api-list" is "{}"'.format(url_for('api-list'))
URL for "list" is "/list"
URL for "api-list" is "/api/list"

What is the best way to force a keyword while using **kwargs?

I'm not sure if I have used the correct terminology in the question.
Currently, I am trying to make a wrapper/interface around Google's Blogger API (Blog service).
[I know it has been done already, but I am using this as a project to learn OOP/python.]
I have made a method that gets a set of 25 posts from a blog:
def get_posts(self, **kwargs):
""" Makes an API request. Returns list of posts. """
api_url = '/blogs/{id}/posts'.format(id=self.id)
return self._send_request(api_url, kwargs)
def _send_request(self, url, parameters={}):
""" Sends an HTTP GET request to the Blogger API.
Returns JSON decoded response as a dict. """
url = '{base}{url}?'.format(base=self.base, url=url)
# Requests formats the parameters into the URL for me
try:
r = requests.get(url, params=parameters)
except:
print "** Could not reach url:\n", url
return
api_response = r.text
return self._jload(api_response)
The problem is, I have to specify the API key every time I call the get_posts function:
someblog = BloggerClient(url='http://someblog.blogger.com', key='0123')
someblog.get_posts(key=self.key)
Every API call requires that the key be sent as a parameter on the URL.
Then, what is the best way to do that?
I'm thinking a possible way (but probably not the best way?), is to add the key to the kwargs dictionary in the _send_request():
def _send_request(self, url, parameters={}):
""" Sends an HTTP get request to Blogger API.
Returns JSON decoded response. """
# Format the full API URL:
url = '{base}{url}?'.format(base=self.base, url=url)
# The api key will be always be added:
parameters['key']= self.key
try:
r = requests.get(url, params=parameters)
except:
print "** Could not reach url:\n", url
return
api_response = r.text
return self._jload(api_response)
I can't really get my head around what is the best way (or most pythonic way) to do it.
You could store it in a named constant.
If this code doesn't need to be secure, simply
API_KEY = '1ih3f2ihf2f'
If it's going to be hosted on a server somewhere or needs to be more secure, you could store the value in an environment variable
In your terminal:
export API_KEY='1ih3f2ihf2f'
then in your python script:
import os
API_KEY = os.environ.get('API_KEY')
The problem is, I have to specify the API key every time I call the get_posts function:
If it really is just this one method, the obvious idea is to write a wrapper:
def get_posts(blog, *args, **kwargs):
returns blog.get_posts(*args, key=key, **kwargs)
Or, better, wrap up the class to do it for you:
class KeyRememberingBloggerClient(BloggerClient):
def __init__(self, *args, **kwargs):
self.key = kwargs.pop('key')
super(KeyRememberingBloggerClient, self).__init__(*args, **kwargs)
def get_posts(self, *args, **kwargs):
return super(KeyRememberingBloggerClient, self).get_posts(
*args, key=self.key, **kwargs)
So now:
someblog = KeyRememberingBloggerClient(url='http://someblog.blogger.com', key='0123')
someblog.get_posts()
Yes, you can override or monkeypatch the _send_request method that all of the other methods use, but if there's only 1 or 2 methods that need to be fixed, why delve into the undocumented internals of the class, and fork the body of one of those methods just so you can change it in a way you clearly weren't expected to, instead of doing it cleanly?
Of course if there are 90 different methods scattered across 4 different classes, you might want to consider building these wrappers programmatically (and/or monkeypatching the classes)… or just patching the one private method, as you're doing. That seems reasonable.

Categories

Resources