Modify authorization URL format using requests-oauthlib - python

I am using requests-oauthlib to authenticate with the ETrade API. It requires the authorization URL to have the following format:
https://us.etrade.com/e/t/etws/authorize?key={oauth_consumer_key}&token={oauth_token}
However, when I call authorization_url(), it uses oauth_token instead of token for that parameter. Currently I am using format() to format the URL myself, but now I have both token and oauth_token parameters. This works, but is completely inelegant. Is there some way to modify the behavior of authorization_url() to allow the URL format I require?
For completeness, here is my code:
oauth_session = requests_oauthlib.OAuth1Session(config.oauth_consumer_key, config.consumer_secret, callback_uri='oob')
def get_request_token():
path = 'oauth/request_token'
url = base_url + path
return oauth_session.fetch_request_token(url)
def get_authorization_url(request_token):
url_format = 'https://us.etrade.com/e/t/etws/authorize?key={oauth_consumer_key}&token={oauth_token}'
url = url_format.format(oauth_consumer_key=config.oauth_consumer_key, oauth_token=request_token['oauth_token'])
return oauth_session.authorization_url(url)
request_token = get_request_token()
print(get_authorization_url(request_token))

The authorization_url() function is a convenience function which calls a generic function to add query parameters to a url (OAuthLib's common.add_params_to_uri() which in turn uses urlparse.urlunparse()). There is no way to get authorization_url() to leave out the oauth_token parameter.
The returned type is a str (in Python 3), so as long as you are certain that the url and tokens will only contain valid characters, you can obtain the same result by using a plain string format call as you have done in your function.
However if the additional oauth_token parameter causes no problems, I would suggest using the authorization_url() function for the extra safety provided.
Additionally, it is then unnecessary to do the extra str.format() call in your function - the authorization_url() takes kwargs which can be used to specify those parameters:
def get_authorization_url(request_token):
url = 'https://us.etrade.com/e/t/etws/authorize'
return oauth_session.authorization_url(url,
key=config.oauth_consumer_key,
token=request_token['oauth_token'])

Related

Python http delete requests with URL path variable

I have an Http endpoint exposed as http://localhost:8080/test/api/v1/qc/{id} for delete, while making this API delete call I have to replace with the proper id
I tried below way using the requests module of python
param = {
"id" : 1
}
requests.delete(url = http://localhost:8080/test/api/v1/qc/{id}, params=param)
This API call is breaking with the error
ValueError: No JSON object could be decoded.
How can I do this?
Your code can't run as-is. You need to quote your url string:
url = "http://localhost:8080/test/api/v1/qc/{id}"
Reading the docs for requests, the params only sends the dictionary param as the query string, so it'll only tack on ?id=1 to the end of the URL.
What you want is the {id} to get the value from the dictionary. You can look at this answer for various ways: How do I format a string using a dictionary in python-3.x?
You want something like
requests.delete(url = "http://localhost:8080/test/api/v1/qc/{id}".format(**param))

Redirecting with updated requests parameters

I write grid filters and some functionality is not implemented yet, so I want to redirect people to default grid when they try to use the unimplemented features. In my controller I have access to a request object. Can I update its parameters and use my request object, to redirect the user?
I tried to update request.params directly but it's a read-only object. Tried to use httpfound but it doesn't accept params.
try:
self._parse_filters(filters)
except NotImplementedError:
url = self.request.route_
self.reset_filters = True
self.error = 'Not Implemented Functionality. Default filters loaded'
self._parse_filters(self.default_filters)
You need to build a new URL (possibly based on the parameters in your current request) and use HTTPFound to generate a redirect. To generate the query part of your new url you can use _query parameter of request.route_url method or simply use urllib.urlencode.
new_url = request.route_url(
'not_implemented_view',
blah='boo',
_query={'sort':'asc'} # you can give it request.GET if you want all the same url parameters
)
return HttpFound(new_url)

How to return plain text from flask endpoint? Needed by Prometheus

I need to setup a /metrics endpoint so that Prometheus can consume statistics about an endpoint. How do I go about doing this?
I have this in my Flask app:
#app.route('/metrics')
def metrics():
def generateMetrics():
metrics = ""
... some string builder logic
return metrics
response = make_response(generateMetrics(), 200)
response.mimetype = "text/plain"
return response
Is this the best way? What is the difference between returning a String (just returning metrics here) and returning plain text? Why do I need the mimetype?
Is this the best way?
There are several ways to set the MIME type, better explained and discussed in this StackOverflow question. Your way works fine and gets the job done.
What is the difference between returning a String and returning plain text?
If you return a string Flask will automatically handle some of the Response logic for you. This means using some default values. If you set up two different endpoints you'll see that the difference turns out to be that your code returns the following header:
Content-Type:"text/plain; charset=utf-8"
While just returning a string (and default MIME type) would return the following header:
Content-Type:"text/html; charset=utf-8"
Why do I need the mimetype?
You might say that it is technically more correct, given that your response is simply plain text, and not HTML. However, a more forcing reason for needing it would be that a third party system you are using (Prometheus) relies on or cares about the contents of the "Content-Type" header. If they do, then you must set it for them to accept it.
Example code
For the Content-Type header demonstration I used the following example Python code:
from flask import Flask, make_response
app = Flask(__name__)
def generateMetrics():
return "hello world"
#app.route('/metrics')
def metrics():
response = make_response(generateMetrics(), 200)
response.mimetype = "text/plain"
return response
#app.route('/metrics2')
def metrics2():
return generateMetrics()
I then viewed the returned body and headers using Postman.

Flask Restful: change representation based on URL parameter

I am building an API using Flask and Flask-Restful. The API might be accessed by different sort of tools (web apps, automated tools, etc.) and one of the requirement is to provide different representations (let's say json and csv for the sake of the example)
As explained in the restful doc, it's easy to change the serialization based on the content type, so for my CSV serialization I've added this:
#api.representation('text/csv')
def output_csv(data, code, headers=None):
#some CSV serialized data
data = 'some,csv,fields'
resp = app.make_response(data)
return resp
And it's working when using curl and passing the correct -H "Accept: text/csv" parameter.
The issue is that since some browsers might be routed to a url directly to download a csv file, I would like to be able to force my serialization via a url parameter for example http://my.domain.net/api/resource?format=csv where the format=csvwould have the same effect as -H "Accept: text/csv".
I've gone through both Flask and Flask-Restful documentation and I don't see how to correctly handle this.
Simply create a sub-class of Api and override the mediatypes method:
from werkzeug.exceptions import NotAcceptable
class CustomApi(Api):
FORMAT_MIMETYPE_MAP = {
"csv": "text/csv",
"json": "application/json"
# Add other mimetypes as desired here
}
def mediatypes(self):
"""Allow all resources to have their representation
overriden by the `format` URL argument"""
preferred_response_type = []
format = request.args.get("format")
if format:
mimetype = FORMAT_MIMETYPE_MAP.get(format)
preferred_response_type.append(mimetype)
if not mimetype:
raise NotAcceptable()
return preferred_response_type + super(CustomApi, self).mediatypes()
Basically you want to retrieve parameters from GET method. Please refer to:
How do I get the url parameter in a Flask view

Flask Get the url variables in a before request?

In Flask I have url rules with variables. For example:
my_blueprint.add_url_rule('/<user_token>/bills/',view_func=BillsView.as_view('bills'))
This is going to pass the user_token variable to the BillsView's get and post methods.
I am trying to intercept that user_token variable in the before_request of my blueprint.
Here is my blueprint before_request:
def before_req():
...
...
my_blueprint.before_request(before_req)
The closest I have come is to use request.url_rule. But that does not give me the content of the variable. Just the rule that matches.
Register a URL processor using #app.url_value_preprocessor, which takes the endpoint and values matched from the URL. The values dict can be modified, such as popping a value that won't be used as a view function argument, and instead storing it in the g namespace.
from flask import g
#app.url_value_preprocessor
def store_user_token(endpoint, values):
g.user_token = values.pop('user_token', None)
The docs include a detailed example of using this for extracting an internationalization language code from the URL.
Apart from the URL preprocessors as described above, another approach to get args passed to the URL explicitly will be to use this.
#app.before_request
def get_request_args():
"Provides all request args"
request_args = {**request.view_args, **request.args} if request.view_args else {**request.args}
print('All Request args ',request_args)
More info in the documentation of request.args and request.view_args

Categories