淺談python web框架中的orm設計


看了一下廖雪峰的那個web框架,其實就是封裝了web.py,請求使用異步並將aiomysql做為MySQL數據庫提供了異步IO的驅動,前端部分則整合了jinja.其中最難的應該是orm部分了。

下面是orm一個簡單的例子。

class User(Model):
    __table__ = 'users'

    id = StringField(primary_key=True, default=next_id, ddl='varchar(50)')
    email = StringField(ddl='varchar(50)')
    passwd = StringField(ddl='varchar(50)')
    admin = BooleanField()
    name = StringField(ddl='varchar(50)')
    image = StringField(ddl='varchar(500)')
    created_at = FloatField(default=time.time)

整體來看這是一個為User的簡單table,集成了Model類。然后字段則是各種類型Field。

1.先來看看字段,也就是Field部分。Field部分比較簡單,繼承了object,然后初始化一些字段,其實沒什么好說的。在字段賦值時,參數以dict格式傳入,傳入到哪里呢,本來type的dict是為none的,但下面這段代碼用__new__改變了原來的type,而元類是創建類的類,因此,Field字段的參數,都會在ModelMetaclass類中做處理,下面看個調試的例子,看看元類的威力。

IPython 4.0.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: from orm import Model

In [2]: class Blog(Model):
   ...:         pass
   ...:

In [3]:

In [3]: a=Blog

In [4]: type(a)
Out[4]: orm.ModelMetaclass

In [5]: dir(a)
Out[5]:
['__class__',
 '__contains__',
 '__delattr__',
 '__delitem__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__fields__',
 '__format__',
 '__ge__',
 '__getattr__',
 '__getattribute__',
 '__getitem__',
 '__gt__',
 '__hash__',
 '__init__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mappings__',
 '__module__',
 '__ne__',
 '__new__',
 '__primary_key__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__setitem__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__table__',
 '__weakref__',
 'clear',
 'copy',
 'find',
 'findAll',
 'findNumber',
 'fromkeys',
 'get',
 'getValue',
 'getValueOrDefault',
 'items',
 'keys',
 'pop',
 'popitem',
 'remove',
 'save',
 'setdefault',
 'update',
 'values']

In [6]:

從上面可以看到,a是繼承了元類修改后的類Blog的一個對象,因此繼承了新增的屬性跟方法。所以元類都能對所有的orm中的表做出約束和修改。

下面這段繼承自type的ModelMetaclass就是元類的代碼。

class ModelMetaclass(type):

    def __new__(cls, name, bases, attrs):
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        tableName = attrs.get('__table__', None) or name
        logging.info('found model: %s (table: %s)' % (name, tableName))
        mappings = dict()
        fields = []
        primaryKey = None
        for k, v in attrs.items():
            if isinstance(v, Field):
                logging.info('  found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
                if v.primary_key:
                    # 找到主鍵:
                    if primaryKey:
                        raise StandardError('Duplicate primary key for field: %s' % k)
                    primaryKey = k
                else:
                    fields.append(k)
        if not primaryKey:
            raise StandardError('Primary key not found.')
        for k in mappings.keys():
            attrs.pop(k)
        escaped_fields = list(map(lambda f: '`%s`' % f, fields))
        attrs['__mappings__'] = mappings # 保存屬性和列的映射關系
        attrs['__table__'] = tableName
        attrs['__primary_key__'] = primaryKey # 主鍵屬性名
        attrs['__fields__'] = fields # 除主鍵外的屬性名
        attrs['__select__'] = 'select `%s`, %s from `%s`' % (primaryKey, ', '.join(escaped_fields), tableName)
        attrs['__insert__'] = 'insert into `%s` (%s, `%s`) values (%s)' % (tableName, ', '.join(escaped_fields), primaryKey, create_args_string(len(escaped_fields) + 1))
        attrs['__update__'] = 'update `%s` set %s where `%s`=?' % (tableName, ', '.join(map(lambda f: '`%s`=?' % (mappings.get(f).name or f), fields)), primaryKey)
        attrs['__delete__'] = 'delete from `%s` where `%s`=?' % (tableName, primaryKey)
        return type.__new__(cls, name, bases, attrs)

而Model類相對簡單,主要是為了初始化了get/set以及幾個getvalue的函數,這一塊根據自己需要看看就好。

class Model(dict, metaclass=ModelMetaclass):

    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def getValue(self, key):
        return getattr(self, key, None)

    def getValueOrDefault(self, key):
        value = getattr(self, key, None)
        if value is None:
            field = self.__mappings__[key]
            if field.default is not None:
                value = field.default() if callable(field.default) else field.default
                logging.debug('using default value for %s: %s' % (key, str(value)))
                setattr(self, key, value)
        return value

    @classmethod
    @asyncio.coroutine
    def findNumber(cls, selectField, where=None, args=None):
        ' find number by select and where. '
        sql = ['select %s _num_ from `%s`' % (selectField, cls.__table__)]
        if where:
            sql.append('where')
            sql.append(where)
        rs = yield from select(' '.join(sql), args, 1)
        if len(rs) == 0:
            return None
        return rs[0]['_num_']

....

@classmethod是為了將方法變成類方法,@asyncio.coroutine則是為了做異步處理。

那每一個orm下面的表是如何進行值傳遞的呢?下面看看另一個調試例子

In [2]: from orm import Model,StringField

In [3]: class Blog(Model):
   ...:         __table__='user'
   ...:         id=StringField(ddl='varchar(50)')
   ...:         age=StringField(name="ss")
   ...:

In [4]: a = Blog

In [5]: dir(a)
Out[5]:
['__class__',
 '__contains__',
 '__delattr__',
 '__delitem__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__fields__',
 '__format__',
 '__ge__',
 '__getattr__',
 '__getattribute__',
 '__getitem__',
 '__gt__',
 '__hash__',
 '__init__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mappings__',
 '__module__',
 '__ne__',
 '__new__',
 '__primary_key__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__setitem__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__table__',
 '__weakref__',
 'age',
 'clear',
 'copy',
 'find',
 'findAll',
 'findNumber',
 'fromkeys',
 'get',
 'getValue',
 'getValueOrDefault',
 'id',
 'items',
 'keys',
 'pop',
 'popitem',
 'remove',
 'save',
 'setdefault',
 'update',
 'values']

In [6]:

In [6]: a.__dict__
Out[6]: mappingproxy({'age': <orm.StringField object at 0x0000000004899A20>, '__
mappings__': {}, '__table__': 'user', '__primary_key__': None, '__module__': '__
main__', '__doc__': None, '__fields__': [], 'id': <orm.StringField object at 0x0
0000000048999E8>})
In [
7]: a.__mappings__ Out[7]: {}

從這里可以看到,是以{'id':object,'age':object}向上傳遞到元類的attr,最后在__new__中處理后,重新傳遞給__init__初始化。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM