在尊重访问控制的同时为REST API序列化SQLAlchemy模型?

Mah*_*der 7 python aop serialization

目前,我们以及大多数Web框架的序列化工作方式是某种类型的方法调用,它将模型转换为某种类型的格式.在我们的例子中,我们to_dict()在每个模型上都有一个方法,它构造并返回一个键值字典,其中键是字段名,值是实例变量.

在我们的代码中,我们都有以下代码片段:json.dumps(**some_model_object.to_dict())将序列化为some_model_objectjson.最近,我们决定向用户公开一些内部资源,但是如果请求用户不是超级用户,则其中一些资源具有我们不希望在序列化期间传回的特定私有实例值.

我试图想出一个简洁的设计,允许更容易的序列化,以及允许我们序列化为json以外的格式.我认为这是面向方面设计/编程的一个非常好的用例,其中方面尊重请求访问控制并基于请求用户的持久性来序列化对象.

这是类似于我现在的东西:

from framework import current_request


class User(SQLAlchemyDeclarativeModel):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    first_name = Column(Unicode(255))
    last_name = Column(Unicode(255))
    private_token = Column(Unicode(4096))

    def to_dict(self):
        serialized = dict((column_name, getattr(self, column_name))
                          for column_name in self.__table__.c.keys())

        # current request might not be bound yet, could be in a unit test etc.
        if current_request and not current_request.user.is_superuser():
            # we explicitly define the allowed items because if we accidentally add
            # a private variable to the User table, then it might be exposed.
            allowed = ['id', 'first_name', 'last_name']
            serialized = dict((k, v) for k, v in serialized.iteritems() if k in allowed)

        return serialized
Run Code Online (Sandbox Code Playgroud)

可以看出,这不太理想,因为现在我必须将数据库模型与当前请求结合起来.虽然这是非常明确的,但请求耦合是一种代码味道,我试图看看如何干净地完成这项工作.

我考虑过这样做的一种方法是在模型上注册一些字段,如下所示:

class User(SQLAlchemyDeclarativeModel):
    __tablename__ = 'users'
    __public__ = ['id', 'first_name', 'last_name']
    __internal__ = User.__exposed__ + ['private_token']

    id = Column(Integer, primary_key=True)
    first_name = Column(Unicode(255))
    last_name = Column(Unicode(255))
    private_token = Column(Unicode(4096))
Run Code Online (Sandbox Code Playgroud)

然后,我将有一个序列化器类,它与每个WSGI调用上的当前请求绑定,该调用将采用所需的序列化器.例如:

import simplejson

from framework import JSONSerializer  # json serialization strategy
from framework import serializer

# assume response format was requested as json
serializer.register_serializer(JSONSerializer(simplejson.dumps))
serializer.bind(current_request)
Run Code Online (Sandbox Code Playgroud)

然后在我的视野中,我会这样做:

from framework import Response

user = session.query(User).first()
return Response(code=200, serializer.serialize(user))
Run Code Online (Sandbox Code Playgroud)

serialize 将实施如下:

def serialize(self, db_model_obj):
    attributes = '__public__'
    if self.current_request.user.is_superuser():
        attributes = '__private__'

    payload = dict((c, getattr(db_model_obj, c)) 
                   for c in getattr(db_model_obj, attributes))

    return self.serialization_strategy.execute(payload)
Run Code Online (Sandbox Code Playgroud)

关于这种方法的可读性和清晰度的想法?这是解决这个问题的pythonic方法吗?

提前致谢.

zzz*_*eek 7

通过mixin建立"序列化"合同:

class Serializer(object):
    __public__ = None
    "Must be implemented by implementors"

    __internal__ = None
    "Must be implemented by implementors"

    def to_serializable_dict(self):
        # do stuff with __public__, __internal__
        # ...
Run Code Online (Sandbox Code Playgroud)

通过WSGI集成保持简单."注册",JSONSerializer作为一个对象,所有这些都是某种Java/Spring的东西,不需要那么大张旗鼓.下面是我的pylons 1.0风格的解决方案,我还没有金字塔:

def my_controller(self):
   # ...

   return to_response(request, response, myobject)


# elsewhere
def to_response(req, resp, obj):
    # this would be more robust, look in
    # req, resp, catch key errors, whatever.
    # xxx_serialize are just functions.  don't need state
    serializer = {
       'application/json':json_serialize,
       'application/xml':xml_serialize,
       # ...
    }[req.headers['content-type']]

    return serializer(obj)
Run Code Online (Sandbox Code Playgroud)