在_cp_dispatch / popargs

时间:2015-06-15 16:35:24

标签: python sqlalchemy cherrypy

我正在使用带有sqlalchemy的cherrypy来构建一个无头(只有CLI客户端)的restful服务器。

我使用以下方法将sqlalchemy绑定到cherrypy引擎:https://bitbucket.org/Lawouach/cherrypy-recipes/src/c8290261eefb/web/database/sql_alchemy/ 如果数据库不存在,则会略微修改recipe以构建数据库。

服务器公开了几个uri,例如客户端,文章,商店......

  • 获取 / clients获取客户列表
  • 获取 / clients / 1获取客户端ID#1
  • GET / clients / foo获取名为foo的客户端
  • PUT / clients / 1更新客户端#1
  • DELETE / client / foo删除名为foo的clien ...

我打算将装饰器 popargs _cp_dispatch 一起使用,以便在处理之前将我的资源名称转换为其ID。我使用 cherrypy.dispatch.MethodDispatcher 作为调度程序(所以我可以编写GET / POST / PUT / DELETE方法)

我可以从任何GET / POST / PUT / DELETE方法访问该插件,但我无法从_cp_dispatch访问它。

在进入GET / POST / PUT / DELETE方法之前,我知道如何将资源名称转换为他们的id?

这是我的问题的复制品

$ tree
.
├── __init__.py
├── models.py
├── my.db
├── root.py
├── saplugin.py
├── saplugin.py.original
└── satool.py

我的sqlalchemy模型(我有几个,只有一个是重现问题所必需的)

$ cat models.py
# -*- coding: utf-8 -*-
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
Base = declarative_base()

class User(Base):

    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)

这是服务器的主要脚本

$ cat root.py
# -*- coding: utf-8 -*-

import cherrypy
from models import User
from saplugin import SAEnginePlugin
from satool import SATool

def name_to_id(param, table):
    if not param.isdigit():
        param = cherrypy.request.db.query(table.id).\
                filter(table.name == param).one()
    return param

class Root(object):

    exposed = True

    def __init__(self):
        self.users = Users()

    def GET(self):
        # Get the SQLAlchemy session associated
        # with this request.
        # It'll be released once the request
        # processing terminates
        return "Hello World"


class Users(object):

    exposed = True

    @cherrypy.popargs('user_id')
    def GET(self, user_id=None, **kwargs):
        user_id = name_to_id(user_id, User)
        return "I'm resource %s" % user_id

if __name__ == '__main__':
    # Register the SQLAlchemy plugin
    SAEnginePlugin(cherrypy.engine).subscribe()

    # Register the SQLAlchemy tool
    cherrypy.tools.db = SATool()

    cherrypy.quickstart(Root(), '', {'/': {'tools.db.on': True,
                                        "request.dispatch": cherrypy.dispatch.MethodDispatcher()}})

以下是应用于sqlalchemy插件receipe的修改

$ diff -up saplugin.py.original saplugin.py
--- saplugin.py.original    2015-06-15 18:14:45.469706863 +0200
+++ saplugin.py 2015-06-15 18:14:37.042741785 +0200
@@ -3,6 +3,7 @@ import cherrypy
from cherrypy.process import wspbus, plugins
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
+from models import User, Base

__all__ = ['SAEnginePlugin']

@@ -26,7 +27,16 @@ class SAEnginePlugin(plugins.SimplePlugi
        self.sa_engine = create_engine('sqlite:///my.db', echo=False)
        self.bus.subscribe("bind-session", self.bind)
        self.bus.subscribe("commit-session", self.commit)
- 
+        # Creating the database
+        self.bus.log('Creating database')
+        self.bus.subscribe("create-all", self.create_all)
+        try:
+            self.create_all()
+        except Exception as err:
+            logging.error("Can't start")
+            logging.error(err.message)
+            sys.exit(1)
+
    def stop(self):
        self.bus.log('Stopping down DB access')
        self.bus.unsubscribe("bind-session", self.bind)
@@ -59,4 +69,8 @@ class SAEnginePlugin(plugins.SimplePlugi
            raise
        finally:
            self.session.remove()
-    
+
+    def create_all(self):
+        """ create database structure """
+        self.bus.log('Creating database')
+        Base.metadata.create_all(self.sa_engine)

当我在GET方法的开头转换名称时,这是服务器的日志。

$ python root.py 
[15/Jun/2015:18:16:23] ENGINE Listening for SIGHUP.
[15/Jun/2015:18:16:23] ENGINE Listening for SIGTERM.
[15/Jun/2015:18:16:23] ENGINE Listening for SIGUSR1.
[15/Jun/2015:18:16:23] ENGINE Bus STARTING
[15/Jun/2015:18:16:23] ENGINE Starting up DB access
[15/Jun/2015:18:16:23] ENGINE Creating database
[15/Jun/2015:18:16:23] ENGINE Creating database
[15/Jun/2015:18:16:23] ENGINE Started monitor thread 'Autoreloader'.
[15/Jun/2015:18:16:23] ENGINE Started monitor thread '_TimeoutMonitor'.
[15/Jun/2015:18:16:23] ENGINE Serving on 127.0.0.1:8080
[15/Jun/2015:18:16:23] ENGINE Bus STARTED
127.0.0.1 - - [15/Jun/2015:18:16:26] "GET /users/1 HTTP/1.1" 200 14 "" "curl/7.29.0"
127.0.0.1 - - [15/Jun/2015:18:16:28] "GET /users/foo HTTP/1.1" 200 14 "" "curl/7.29.0"

以下是以前日志的查询

$ curl 127.0.0.1:8080/users/1; echo
I'm resource 1
$ curl 127.0.0.1:8080/users/foo; echo
I'm resource 1

以下是数据库的内容

$ sqlite3 my.db 
sqlite> .schema
CREATE TABLE users (
    id INTEGER NOT NULL, 
    name VARCHAR, 
    PRIMARY KEY (id)
);
sqlite> select * from users;
1|foo
2|bar

我不能同时使用装饰器popargs和_cp_dispatch方法。 我还没有找到如何使用装饰器处理我的url段。 当我尝试仅使用_cp_dispatch方法时,我最终得到以下错误:

File "/usr/lib/python2.7/site-packages/cherrypy/_cprequest.py", line 628, in respond
    self.get_resource(path_info)
File "/usr/lib/python2.7/site-packages/cherrypy/_cprequest.py", line 744, in get_resource
    dispatch(path)
File "/usr/lib/python2.7/site-packages/cherrypy/_cpdispatch.py", line 423, in __call__
    resource, vpath = self.find_handler(path_info)
File "/usr/lib/python2.7/site-packages/cherrypy/_cpdispatch.py", line 311, in find_handler
    subnode = dispatch(vpath=iternames)
File "root.py", line 49, in _cp_dispatch
    vpath[0] = name_to_id(vpath[0], Users)
File "root.py", line 10, in name_to_id
    param = cherrypy.request.db.query(table.id).\
File "/usr/lib/python2.7/site-packages/cherrypy/__init__.py", line 208, in __getattr__
    return getattr(child, name)
AttributeError: 'Request' object has no attribute 'db'

以下是应用于Users类的修改:

class Users(object):

    # [....]

    def _cp_dispatch(self, vpath):
        if len(vpath) > 1:
            raise
        vpath[0] = name_to_id(vpath[0], Users)
        return vpath

我正在使用以下版本(我正在使用Centos 7环境,并且不能使用来自pip的那些更改): $ rpm -qa | egrep“cherrypy | sqlalchemy” 蟒蛇-CherryPy的-3.2.2-4.el7.noarch 蟒-SQLAlchemy的-0.9.7-3.el7.x86_64

非常感谢您的帮助!!!!

1 个答案:

答案 0 :(得分:0)

documentation中描述了这个问题的解决方案。

我已将以下工具添加到根脚本:

class UserManager(cherrypy.Tool):
    def __init__(self):
        cherrypy.Tool.__init__(self, 'before_handler',
                            self.load, priority=10)

    def load(self, **kwargs):
        req = cherrypy.request

        # let's assume we have a db session
        # attached to the request somehow 
        db = req.db

        # retrieve the user id and remove it
        # from the request parameters
        if 'user' in req.params:
            user = req.params.pop('user')
            if not user.isdigit():
                req.params['user'] = db.query(User).\
                        filter(User.name == user).one()
            else:
                req.params['user'] = db.query(User).\
                        filter(User.id == user).one()

cherrypy.tools.user = UserManager()

并重写Users类,如下所示:

@cherrypy.popargs("user")
class Users(object):

    exposed = True

    @cherrypy.tools.user()
    def GET(self, user=None, **kwargs):
        if user:
            return "I'm resource %s" % user.id
        else:
            return "All users"

装饰者 @ cherrypy.popargs("用户")告诉我们在网址中抓取第一段并将其存储到密钥"用户&#34 ;。 装饰器 @ cherrypy.tools.user()告诉我们在输入 GET 方法之前调用工具用户。 在 UserManager 类的 load 方法中,我将用户的id或名称转换为sqlalchemy模型。