在遵循访问控制的情况下,为REST API序列化SQLAlchemy模型?
目前,我们以及大多数网页框架的序列化方式是通过某种方法调用,将模型转储成某种格式。在我们的情况下,每个模型都有一个 to_dict()
方法,它会构建并返回一个键值对字典,键是字段名,值是实例变量。
在我们的代码中,有很多类似这样的片段: json.dumps(**some_model_object.to_dict())
,这会将 some_model_object
序列化成json格式。最近,我们决定向用户开放一些内部资源,但这些资源中有些特定的私有实例值,如果请求的用户不是超级用户,我们不想在序列化时传输这些值。
我正在尝试设计一个更简洁的方案,以便更容易进行序列化,同时也能将数据序列化成除了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
可以看出,这样并不理想,因为我必须将数据库模型与当前请求耦合在一起。虽然这样做是 非常 明确的,但请求耦合是一种代码异味,我正在尝试寻找更干净的解决方案。
我考虑过的一种方法是,在模型上注册一些字段,像这样:
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))
然后,我会有一个序列化类,在每次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)
然后在我的视图中,我只需这样做:
from framework import Response
user = session.query(User).first()
return Response(code=200, serializer.serialize(user))
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)
对于这种方法的可读性和清晰性有什么看法?这算是一个符合Python风格的解决方案吗?
提前感谢!
1 个回答
7
通过一个混合类来建立“序列化”的约定:
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__
# ...
保持简单,使用WSGI集成。只需“注册”JSONSerializer作为一个对象,其他的都是一些Java/Spring的东西,不需要那么复杂。下面是我在pylons 1.0中的解决方案,我还没有使用pyramid:
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)