0

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?)

Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
Marc Maxmeister
  • 4,191
  • 4
  • 40
  • 54
  • I also found this example useful - demo of how to make a process to authenticate users on cherrypy. https://stackoverflow.com/questions/6552025/cherrypy-custom-tool-for-user-authentication Note that the docs she and I pulled from (here https://github.com/cherrypy/CherryPy/issues/1311) attach the step in the wrong place. This is a 'before_handler' step. – Marc Maxmeister Dec 18 '17 at 15:38

2 Answers2

1

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,
    },
}
Jason R. Coombs
  • 41,115
  • 10
  • 83
  • 93
  • Thanks. This keeps the interface simple, as I wanted. A decorator reads more pythonic to me. I am running on python 3.6 (production) and version 7, 11, 13 on test versions. so I'll need to upgrade first. – Marc Maxmeister Dec 18 '17 at 15:30
  • I read the docs and you don't need to use the register decorator to make this work on older versions. https://github.com/cherrypy/CherryPy/issues/1311 -- just assign it like this: `cherrypy.tools.validate_user = Tool('on_start_resource', validate_user)` – Marc Maxmeister Dec 18 '17 at 15:32
  • @jason-r-coombs I find this works in site.py main file, but when I use this decorator on a different part of the site in another file, it is in the namespace, but not found in the "Toolbox": @cherrypy.tools.validate_user() AttributeError: 'Toolbox' object has no attribute 'validate_user' – Marc Maxmeister Dec 18 '17 at 17:53
  • If using the decorator method, the tool needs to have been defined/assigned before its used as a decorator... and before means relative to the order in which Python reads the modules. You have two choices: use the config method, which defers the tool use until the server starts or ensure that the tool is installed very early, before any of those modules are loaded. – Jason R. Coombs Dec 19 '17 at 16:26
  • I did find [this module](https://github.com/cherrypy/cherrypy/blob/master/cherrypy/_cpconfig.py), whose docstring illustrates how one could use cherrypy.config as a decorator to configure a single handler. – Jason R. Coombs Dec 19 '17 at 17:50
  • I posted a related cherrypy question here: https://stackoverflow.com/questions/47876084/cant-call-a-decorator-within-the-imported-sub-class-of-a-cherrpy-application-s on just the part of trying to break up my `site.py` into multiple files, and have the user-authentication function work in all of them – Marc Maxmeister Dec 20 '17 at 14:43
0

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.

Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59