I've been using Flask-Classy for my server, and it works great. However, I've come across a use case that I haven't seen written about, but it's a pretty common use case, so I'd be shocked if it's not possible.
I have two APIs which I want to nest, and by that I mean I have:
class UsersView(FlaskView):
decorators = [jwt_required()]
route_prefix = '/api/v1/'
def index(self):
...
which is located at http://example.com/api/v1/users and I can access user 1 via http://example.com/api/v1/users/1
Now, how would I write the FlaskView which would let me do something like this?
http://example.com/api/v1/users/1/devices/3
When I try embedding the resource id in the route_prefix, I get a keyword argument error:
class DevicesView(FlaskView):
decorators = [jwt_required()]
route_prefix = '/api/v1/users/<user_id>/'
def index(self):
...
TypeError: index() got an unexpected keyword argument 'user_id'
One last point is that I can, naturally, use kwargs:
route_prefix = '/api/v1/users/<user_id>/'
def test(self, **kwargs):
print kwargs['user_id']
http://example.com/api/v1/users/103/devices will spit out '103', however, using kwargs feels kinda hokey. Is there a better way?
I put the top-level placeholder in the route_base in the register call.
For example:
class UsersView(FlaskView):
#route('/', methods=['GET'])
def index(self):
pass
class DevicesView(FlaskView):
#route('/', methods=['GET'])
def index(self, user_id):
pass
UsersView.register(app, route_base='/users', trailing_slash=False)
DevicesView.register(app, route_base='/users/<user_id>/devices', trailing_slash=False)
Now the user_id comes in as the first parameter on every method in DevicesView.
In case you haven't found a solution as yet...The answer is pretty simple here you need to have the index defined as:
def index(self, user_id):
you have to do this since you would want to know the base resource through which to access the required resource. In your example devices index would give me a list of all devices belonging to the user. In order to get this information you will first need to know which user's devices are being asked for
Related
first I created some user management functions I want to use everywhere, and bound them to cherrypy, thinking I could import cherrypy elsewhere and they would be there. Other functions seem to import fine this way, when not used as decorators.
from user import validuser
cherrypy.validuser = validuser
del validuser
that didn't work, so next I tried passing the function into the class that is a section of my cherrypy site (/analyze) from the top level class of pages:
class Root:
analyze = Analyze(cherrypy.validuser) #maps to /analyze
And in the Analyze class, I referred to them. This works for normal functions but not for decorators. why not?
class Analyze:
def __init__(self, validuser):
self.validuser = validuser
#cherrypy.expose
#self.validuser(['uid'])
def index(self, **kw):
return analysis_panel.pick_data_sets(user_id=kw['uid'])
I'm stuck. How can I pass functions in and use them as decorators. I'd rather not wrap my functions like this:
return self.validuser(analysis_panel.pick_data_sets(user_id=kw['uid']),['uid'])
thanks.
ADDED/EDITED: here's what the decorator is doing, because as a separate issue, I don't think it is properly adding user_id into the kwargs
def validuser(old_function, fetch=['uid']):
def new_function(*args, **kw):
"... do stuff. decide is USER is logged in. return USER id or -1 ..."
if USER != -1 and 'uid' in fetch:
kw['uid'] = user_data['fc_uid']
return old_function(*args, **kw)
return new_function
only the kwargs that were passed in appear in the kwargs for the new_function. Anything I try to add isn't there. (what I'm doing appears to work here How can I pass a variable in a decorator to function's argument in a decorated function?)
The proper way in CherryPy to handle a situation like this is to have a tool and to enable that tool on the parts of your site that require authentication. Consider first creating this user-auth tool:
#cherrypy.tools.register('before_handler')
def validate_user():
if USER == -1:
return
cherrypy.request.uid = user_data['fc_uid']
Note that the 'register' decorator was added in CherryPy 5.5.0.
Then, wherever you wish to validate the user, either decorate the handler with the tool:
class Analyze:
#cherrypy.expose
#cherrypy.tools.validate_user()
def index(self):
return analysis_panel.pick_data_sets(user_id=cherrypy.request.uid)
Or in your cherrypy config, enable that tool:
config = {
'/analyze': {
'tools.validate_user.on': True,
},
}
The function/method is defined in the class, it doesn't make sense to decorate it with an instance variable because it won't be the same decorator for each instance.
You may consider using a property to create the decorated method when it is accessed:
#property
def index(self):
#cherrypy.expose
#self.validuser(['uid'])
def wrapped_index(**kw):
return analysis_panel.pick_data_sets(user_id=kw['uid'])
return wrapped_index
You may also consider trying to apply lru_cache to save the method for each instance but I'm not sure how to apply that with the property.
I have below code
#ns.route('/blah/<query>', methods=['GET'])
#api.doc(params={'query': 'Search ID'})
class myClass(Resource):
#api.doc('blah', responses={ 200: 'OK' })
def get(self, query):
"""Returns list of blah."""
which giving me below result:
question is: how can i make Get parameter value not mandatory?
I'm not positive that this is still the case and I'll be the first to admit that I'm new to this particular framework, but based on the answers to this bug from 2013, it does not appear that the framework allows for this kind of behavior.
The recommended workaround is to create two Resources, one with the parameter and one without. That's the workaround I've used; usually pairing it with a simple base class with a method and an optional parameter that both call.
An example from the GitHub Issue:
class Users(Resource):
def get(self):
return users
def post(self):
#...
class User(Resource):
def get(self, id=None):
return find_user_by_id(id)
def post(self):
#...
api.add_resource(Users, '/users')
api.add_resource(User, '/users/<id>')
I want to have a RESTful api which looks like this:
example.com/teams/
example.com/teams/<team_id>
example.com/teams/<team_id>/players
example.com/teams/<team_id>/players/<player_id>
...
example.com/teams/<team_id>/players/<player_id>/seasons/<season_id>/etc
Where each URI can appropriately handle GETs and possibly POSTs.
I would like to be able to do something like:
class Team(Resource):
def post(self):
#Handler for /teams/
def post(self, team_id):
#Handler for /teams/team_id
def post(self, team_id, player_id):
#Handler for /teams/team_id/players/player_id
and using:
api.add_resource(Team, '/teams/', 'teams/<team_id>/players/<player_id>')
Which won't work because the subsequent POST handlers overwrite the previous.
What is the right way with Flask-RESTful to handle an API where there may be a variable number of variables (variable depth of hierarchy) in the URL?
Python does not support method overloading in that particular way. In your code you are not overloading the post() function, you are redefining it
Basically the last definition of post() is what counts, which takes 3 parameters as you can see:
class Team(Resource):
def post(self, team_id, player_id):
# This is the final definition of post()
# The definitions above this one do not take effect
Otherwise it's pretty easy to get the behavior with a single method that has default values for the parameters:
class Team(Resource):
def post(self, team_id=None, player_id=None):
if team_id is None and player_id is None:
# first version
if team_id is not None and player_id is None:
# second version
if team_id is not None and player_id is not None:
# third version
For your URL, Flask will pass in None for the parameter that isn't defined in the URL.
I'm using tastypie and I want to create a Resource for a "singleton" non-model object.
For the purposes of this question, let's assume what I want the URL to represent is some system settings that exist in an ini file.
What this means is that...:
The fields I return for this URL will be custom created for this Resource - there is no model that contains this information.
I want a single URL that will return the data, e.g. a GET request on /api/v1/settings.
The returned data should return in a format that is similar to a details URL - i.e., it should not have meta and objects parts. It should just contain the fields from the settings.
It should not be possible to GET a list of such object nor is it possible to perform POST, DELETE or PUT (this part I know how to do, but I'm adding this here for completeness).
Optional: it should play well with tastypie-swagger for API exploration purposes.
I got this to work, but I think my method is kind of ass-backwards, so I want to know what is the common wisdom here. What I tried so far is to override dehydrate and do all the work there. This requires me to override obj_get but leave it empty (which is kind of ugly) and also to remove the need for id in the details url by overriding override_urls.
Is there a better way of doing this?
You should be able to achieve this with the following. Note I haven't actually tested this, so some tweaking may be required. A more rich example can be found in the Tastypie Docs
class SettingsResource(Resource):
value = fields.CharField(attribute='value', help_text='setting value')
class Meta:
resource_name = 'setting'
fields = ['value']
allowed_methods = ['get']
def detail_uri_kwargs(self, bundle_or_obj):
kwargs = {}
return kwargs
def get_object_list(self, request):
return [self.obj_get()]
def obj_get_list(self, request=None, **kwargs):
return [self.obj_get()]
def obj_get(self, request=None, key=None, **kwargs):
setting = SettingObject()
setting.value = 'whatever value'
return setting
The SettingObject must support the getattr and setattr methods. You can use this as a template:
class SettingObject(object):
def __init__(self, initial=None):
self.__dict__['_data'] = {}
if initial:
self.update(initial)
def __getattr__(self, name):
return self._data.get(name, None)
def __setattr__(self, name, value):
self.__dict__['_data'][name] = value
def update(self, other):
for k in other:
self.__setattr__(k, other[k])
def to_dict(self):
return self._data
This sounds like something completely outside of TastyPie's wheelhouse. Why not have a single view somewhere decorated with #require_GET, if you want to control headers, and return an HttpResponse object with the desired payload as application/json?
The fact that your object is a singleton and all other RESTful interactions with it are prohibited suggests that a REST library is the wrong tool for this job.
I simply want this to work, but it doesn't:
class Test12:
def __init__(self, request):
self.request = request
#view_config(route_name='test1')
def test1(self):
return Response('I am from test 1')
#view_config(route_name='test2')
def test2(self):
return Response('Hi there from test2')
config.add_route('test1', '/test1')
config.add_route('test2', '/test2')
For both URLs /test1 and /test2 -- the response returned by the test2() method is returned. How should I get this to work correctly? (Or am I missing something here?)
The most likely issue is a flaw in your original (and not pasted) code wherein you accidentally named the two methods with the same name.