I have a falcon app with a parameterized route for getting resources. The user does not know the uuid of the resource because it is temporary, so a redirect is needed.
The user will make a GET /transaction request, and a redirect to the returned path of 302 found response.
How can I parse the uuid from the request path?
The app would look like this:
api = falcon.API()
api.add_route('/transaction', Transaction)
api.add_route('/transaction/{id}', TransactionItem))
And the recources something like this:
class Transaction(object):
def on_get(self, req, resp):
id = get_current_id()
resp.status = falcon.HTTPFound('/TransactionItem/{}'.format(id))
class TransactionItem(object):
def on_get(self, req, resp):
// Parse id from path?
transaction = get_transaction(id)
// ...
// include info in the response, etc
resp.status = falcon.HTTP_200
Ok so.
Flacon passes the matched route fields as a keywords arguments. That means thats in Your TransactionItem class Your on_get must have one of the (You can chose one which is more clear for You) given definitions :
# 1st way
def on_get(self, req, resp, id=None):
...
# 2nd way (**kwargs catches all keywords args)
def on_get(self, req, resp, **kwargs):
id = kwargs.get('id')
The passed field will be dafault passed as str if You want to have it converted by falcon You can use the builtin in Falcon UUIDConverter
Here the docs for converter : https://falcon.readthedocs.io/en/stable/api/routing.html#falcon.routing.UUIDConverter
Related
I'm trying to create a website with optional url sub-paths:
/user - Returns general information on users
/user/edit - Edits the user
I've tried setting:
config.add_route('user', '/user/{action}')
#view_defaults(route_name="user")
class UserViews():
# not sure what (if anything) to put in #view_config here...
def user_general(self):
return Response("General User Info"
#view_config(match_param="action=edit")
def edit(self):
return Response("Editing user")
However while this works for /user/edit, it returns a 404 for /user
It also fails in the same way if I set 2 explicit routes with a shared path - e.g.:
config.add_route('login', '/user')
config.add_route('edit_user', '/user/edit')
I've tried things like setting match_params="action=" but can't get it to work.
Any ideas on how this can be achieved?
user_general inherits the default route configuration of the class, which requires an {action} match param. When you do not supply that in the request, the route for that view will never match, returning a 404 not found response.
You need to add a decorator with the route_name argument to user_general to override the default route for the view.
#view_config(
route_name="user"
)
def user_general(self):
The following works for me as a complete example with some minor explicit naming conventions.
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config, view_defaults
#view_defaults(route_name="user_action")
class UserViews():
def __init__(self, context, request):
self.request = request
self.context = context
#view_config(
route_name="user_get",
request_method="GET"
)
def get_user(request):
return Response("I got you, Babe!")
#view_config(
match_param="action=edit"
)
def edit(self):
return Response("Don't ever change, Babe!")
if __name__ == "__main__":
with Configurator() as config:
config.add_route("user_get", "/user")
config.add_route('user_action', '/user/{action}')
config.scan()
app = config.make_wsgi_app()
server = make_server("0.0.0.0", 6543, app)
server.serve_forever()
I want to pass a value through the Headers of a get request.
Im trying the below but it doesn't work,
class ListCategoriesView(generics.ListAPIView):
"""
Provides a get method handler.
"""
serializer_class = CategorySerializer
def get(self, request, *args, **kwargs):
token = request.data.get("token", "")
if not token:
"""
do some action here
"""
if not UserAccess.objects.filter(accessToken=token).exists():
"""
do some action here
"""
else:
"""
do some action here
"""
I want to pass the token in the headers like that :
can anyone help me with this issue,
thanks a lot in advance.
You said it yourself, you're passing it in the headers, so you need to get it from there. DRF does not do anything special to access the headers, so it proxies to the underlying Django HttpRequest object, which makes them available via the META attribute, converted to uppercase and prefixed by HTTP_:
token = request.META.get("HTTP_TOKEN", "")
I'm new to Falcon, and I was wondering if there was a Flask-like "url_for" solution for the framework. I've scoured the docs and I can't seem to find anything relevant with a google/stack search.
To clarify for users of Falcon who haven't used Flask, I would like to dynamically fetch a defined resource's URL. I'm specifically trying to achieve resource expansion, by including a link to my resources within my payload so the frontend doesn't have to construct any URLs.
Code:
class PostResource(object):
def on_get(self, req, resp, post_id):
"""Fetch single post resource."""
resp.status = falcon.HTTP_200
post_dto = post_to_dto(get_post(post_id))
# TODO: find url_to alternative for falcon: specify post resource location
post_dto.href = ''
resp.body = to_json(PostDtoSerializer, post_dto)
class PostCollectionResource(object):
def on_get(self, req, resp):
"""
Fetch grid view for all post resources.
Note: This endpoint support pagination, pagination arguments must be provided via query args.
"""
resp.status = falcon.HTTP_200
# TODO: add hrefs for each post for end ui
post_collection_dto = PostCollectionDto(
posts=[post_to_dto(post, comments=False) for post in get_posts(
start=req.params.get('start', None), count=req.params.get('count', None)
)])
resp.body = to_json(PostCollectionDtoSerializer, post_collection_dto)
def on_post(self, req, resp):
"""Create a new post resource."""
resp.status = falcon.HTTP_201
payload = req.stream.read()
user = req.context.get('user')
create_post(user._id, from_json(PostFormDtoSerializer, payload))
# TODO: find url_to alternative for falcon: redirect to on_get
resp.set_header('Location', '')
Post collection example:
[
{
"href": ".../post/000000/",
"links": [
"rel": "like",
"href": ".../post/000000/like"
],
"title": "Foobar",
...
}
]
I would like to be able to generate a link to the PostResource.
For the sake of closing this thread, I'm now using the methodology detailed here https://github.com/neetjn/py-blog/issues/16.
Falcon does not support this as confirmed by the maintainers, my work around was to create a base resource with a static route and child method to construct a link to the given resource using the information from the request's req argument.
Example:
class BaseResource(object):
route = ''
#classmethod
def url_to(cls, host, **kwargs) -> str:
return f'{host}{cls.route.format(**kwargs)}'
...
class PostResource(BaseResource):
route = '/v1/post/{post_id}'
def on_get(self, req, res):
pass
class PostCollectionResource(BaseResource):
route = '/v1/posts/'
def on_get(self, req, res):
posts = get_posts()
for post in posts:
post.href = PostResource.url_to(req.netloc, post_id=post.id)
I have 2 Flask apps (different projects) that work together . One implements some API which uses tokens for auth. The second one consumes the API and makes a web interface for it. Now I have a login function that sends the username and password to the API, and if correct, gets the auth token in return. Once I have the token, I save it to the session of the user and the user should now be considered as logged in/ autheticated. How can I implement the login_required decorator for such a case.
Here is my login function -
def login(self):
response = make_request(BASE_URL + 'login/', clean_data(self.data))
if response.status_code == 200:
session['auth_token'] = response.json().get('auth_token')
return True
return False
How can I make the login_required decorator?
Also I am using Redis to store sessions if that matters.
Have a look at the official flask docs regarding decorators:
https://flask.palletsprojects.com/en/1.1.x/patterns/viewdecorators/ or the python docs https://www.python.org/dev/peps/pep-0318/ as well.
Your decorator should look something like:
from functools import wraps
from flask import abort
import jwt
def authorize(f):
#wraps(f)
def decorated_function(*args, **kws):
if not 'Authorization' in request.headers:
abort(401)
user = None
data = request.headers['Authorization'].encode('ascii','ignore')
token = str.replace(str(data), 'Bearer ','')
try:
user = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])['sub']
except:
abort(401)
return f(user, *args, **kws)
return decorated_function
... and then in your app.py you may have:
#app.route('/api/game', methods=['POST'])
#authorize
def create(user):
data = json.loads(request.data)
....
In this particular case I have used JWT as token and your token can be different respectively the decoding of the token can be your custom implementation, but the basic mechanisms are pretty much as on the example above.
I would place the following decorator function in somewhere common
def validate_api_token(validation_func):
def decorator(f):
#wraps(f)
def decorated_function(*args, **kws):
api_token = request.headers.get('Authorization')
is_valid_api_token = validation_func(api_token)
if is_valid_api_token:
return f(*args, **kws)
return 'Invalid API Token', 401
return decorated_function
return decorator
For small POC flask apps, if you're ok with storing the tokens in a non-versioned file, the following can work:
# tokens are read from a non-versioned `.tokens` file and loaded into a set
api_tokens = load_api_tokens()
def simple_api_token_validation(api_token):
return api_token in api_tokens
#app.route("/v1/my/secret/function", methods=['POST'])
#validate_api_token(simple_api_token_validation)
def my_secret_function():
body = request.get_json()
# ...
Another simple option is to query against a database (e.g. redis):
redis_session = Redis(host=REDIS_HOST, password=REDIS_PASSWORD)
def redis_api_token_validation(api_token):
if not api_token:
return False
api_token_hash = hashlib.sha256(api_token.encode()).hexdigest()
return redis_session.exists(f'api:tokens:{api_token_hash}')
#app.route("/v1/my/secret/function", methods=['POST'])
#validate_api_token(redis_api_token_validation)
def my_secret_function():
body = request.get_json()
# ...
Best IMO as #Velin answered is to use jwt to validate the token
Given that each subsequent request will contain the API token, the decorator should do the following
Accept a generic request. You can use *args and **kargs for that
Extract the token from the header and compare it with the token stored in db (not Redis, but wherever the token generated is stored in the backend)
If authenticated, the *args and **kargs should be passed on to the decorated function
The output of the decorated function should then be returned as is
If the authentication failed, an error message should be returned.
For explanation on decorators, check out this link:
http://thecodeship.com/patterns/guide-to-python-function-decorators/
I'm building a REST API with Flask which add a photo to a database. The database is abstracted in PhotoModel Class. The API receives a JSON formated HTTP POST which contain the picture in a bin string an the name, all the other parameters are optional.
How to construct "photo" object if some param aren't present in the JSON posted?
On the database Model (PhotoModel) I have specify only two compulsory items, so the logic to only take into account params present in the JSON should be in the function bellow.
def add_photo():
"""Add photo to database"""
if request.method == 'POST' and request.headers['Content-Type'] == 'application/json':
photo = PhotoModel(
name = request.json['name'],
device_version = request.json['device_version'],
date = request.json['date'],
picture = request.json['picture'],
comment = request.json['comment']
)
try:
photo.put()
return "200"
except CapabilityDisabledError:
return "500 DB read-only"
else:
return "415 Unsupported Media Type"
I can't figure out how to do it, any pointer would help
Take a look at peewee it comes with a RESTful API in JSON. It's also an light ORM engine.
I've discovered JSON Schema and it works fantastic to validate JSON requests.
Create a decorator which you can use for all views:
from functools import update_wrapper
from jsonschema import validate as jsonschema_validate
def validate(schema):
def decorator(f):
def wrapped_function(*args, **kwargs):
# Validate request Content Type
if request.json is None:
raise ValidationError("Content Type must be JSON")
# Validate document
jsonschema_validate(request.json, schema)
return f(*args, **kwargs)
return update_wrapper(wrapped_function, f)
return decorator
Use decorator for your views:
#app.route('/', methods=['POST'])
#validate(schema)
def insert_document():
# now your request.json object is validated against the specified schema
data = request.get_json() #You can use any method here.
#Below the required parameters outside the try
email=data['email']
role=data['role']
try:
#Here are the optional json parameters inside a try
firstname = data['firstname']
lastname = data['lastname']
except KeyError:
#Here handle the exception, maybe parse some default values.
pass