Search code examples
python-3.xnginxamazon-ec2cherrypy

cherrypy - 'module' object has no attribute session when run with uWSGI


I am using cherrypy 3.6.0, nginx 1.6.2 and uWSGI 2.0.9 on amazon linux.

uwsgi.json config:

{
  "uwsgi": {
      "socket": ["127.0.0.1:8080"],
      "master": True,
      "processes": 1,
      "threads": 24,
      "uid": "ec2-user",
      "protocol": "http",
      "vacuum": True
      "chdir": "/usr/local/src/myapp",
      "wsgi-file": "/usr/local/src/myapp/wsgi.py",
      "logto": "/var/log/uwsgi.log"
  }
}

I try enabling sessions:

 cherrypy.config.update({
    'server.socket_host': '0.0.0.0',
    'engine.autoreload.on': False,
    'server.socket_port': 8080,
    'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
    'tools.sessions.on' : True,
    'tools.sessions.storage_type': "file",
    'tools.sessions.storage_path': "/home/ec2-user/.cherrypy_sessions",
    'tools.sessions.timeout': 60
})

to use them here:

import cherrypy
import random
import string
import json
import urllib.parse


class IdentityApi(object):

@cherrypy.expose
def gen_crosssite(self):
    crsf = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(32))
    cherrypy.session['my_state'] = crsf;
    return json.dumps({'crsf': crsf})

I get the following exception:

[25/Feb/2015:15:51:36] HTTP Traceback (most recent call last):
File "/usr/local/lib/python3.4/site-packages/cherrypy/_cprequest.py", line 670, in respond
response.body = self.handler()
File "/usr/local/lib/python3.4/site-packages/cherrypy/lib/encoding.py", line 217, in __call__
self.body = self.oldhandler(*args, **kwargs)
File "/usr/local/lib/python3.4/site-packages/cherrypy/_cpdispatch.py", line 61, in __call__
return self.callable(*self.args, **self.kwargs)
File "./api/IdentityApi.py", line 25, in gen_crsf
  cherrypy.session['my_state'] = crsf;
AttributeError: 'module' object has no attribute 'session'

update: Running it from the commandline 'python3 wsgi.py' and WITHOUT uWSGI works.

Had to comment out:

cherrypy.server.unsubscribe()
cherrypy.engine.start()

updated wsgi.py:

if __name__ == '__main__':
    cherrypy.log('jobtagr log file = %s', cherrypy.log.access_file)
    setup_logging()
    cherrypy.config.update({
        'server.socket_host': '0.0.0.0',
        'engine.autoreload.on': False,
        'server.socket_port': 8080,
        'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
        'tools.sessions.on' : True,
        'tools.sessions.storage_type': "file",
        'tools.sessions.storage_path': "/home/ec2-user/.cherrypy_sessions",
        'tools.sessions.timeout': 60
    })
    #    cherrypy.server.unsubscribe()
    #    cherrypy.engine.start()
    cherrypy.quickstart(application)

How do I get sessions to work under uWSGI?


Solution

  • Observations:

    1. cherrypy.session attribute doesn't exist unless sessions.init is called by the session tool,
    2. Don't mix global (server, engine) and application (tools, dispatch) configs,
    3. Don't mess with routing. You've declared HTTP verb dispatch, but I see non-verb gen_crosssite.

    Update

    I've modelled your case and see the problem now. It's documented behaviour but it is a little subtle anyway. It happens when you switch from cherrypy.quickstart which is more demo thing, to managing real setup with cherrypy.tree.mount without paying attention to what happens with configuration. Here's cherrypy.quickstart, it's small:

    def quickstart(root=None, script_name="", config=None):
        if config:
            _global_conf_alias.update(config)
    
        tree.mount(root, script_name, config)
    
        engine.signals.subscribe()
        engine.start()
        engine.block()
    

    As you see, it sets both, server (global) config and application config in tree.mount. Here's the warning from Basics documentation section about global config:

    cherrypy.config.update() is not meant to be used to configure the application. It is a common mistake. It is used to configure the server and engine.

    And here's Combined Configuration Files section:

    If you are only deploying a single application, you can make a single config file that contains both global and app entries. Just stick the global entries into a config section named [global] (or top level key global), and pass the same file to both config.update and tree.mount. If you’re calling cherrypy.quickstart(app root, script name, config), it will pass the config to both places for you. But as soon as you decide to add another application to the same site, you need to separate the two config files/dicts.

    Here goes your case modelled with two applications:

    #!/usr/bin/env python3
    
    import random
    import string
    
    import cherrypy
    
    
    class Api:
    
      def __init__(self):
        self.string = String()
        # ...
    
    
    class String:
    
      exposed = True
    
    
      def GET(self):
        return cherrypy.session.get('mystring', '')
    
      def POST(self, length=8):
        result = ''.join(random.sample(string.hexdigits, int(length)))
        cherrypy.session['mystring'] = result
        return result
    
      def PUT(self, new):
        cherrypy.session['mystring'] = new
    
      def DELETE(self):
        cherrypy.session.pop('mystring', None)
    
    
    class Token:
    
      @cherrypy.expose
      def csrf(self):
        crsf = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(32))
        cherrypy.session['csrf'] = crsf
        return {'crsf': crsf}
    
    
    if __name__ == '__main__':
      # global server config
      cherrypy.config.update({
        'server.socket_host' : '0.0.0.0',
        'server.socket_port' : 8080,
      })
    
      # applicaton config is provided
      cherrypy.tree.mount(Api(), '/api', {
        '/' : {
          'tools.sessions.on' : True,
          'request.dispatch'  : cherrypy.dispatch.MethodDispatcher(),
        },
        '/string' : {
          'tools.response_headers.on'      : True,
          'tools.response_headers.headers' : [('Content-Type', 'text/plain')]  
        }
      })
    
      # applicaton config is provided
      cherrypy.tree.mount(Token(), '/token', {'/' : {
        'tools.sessions.on' : True,
        'tools.json_out.on' : True,
      }})
    
      cherrypy.engine.signals.subscribe()
      cherrypy.engine.start()
      cherrypy.engine.block()