zoukankan      html  css  js  c++  java
  • 浅谈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__初始化。

  • 相关阅读:
    进程与线程
    闭包
    form表单提交
    让图片在div盒子中水平垂直居中
    第一个shell脚本——修改配置文件
    Linux系统find命令的常用方法
    Linux使echo命令输出结果带颜色
    Linux面试题
    无人值守批量安装
    Linux系统PXE高效批量网络装机的配置
  • 原文地址:https://www.cnblogs.com/alexkn/p/4827767.html
Copyright © 2011-2022 走看看