如何自己编写的ORM

本文介绍了如何使用ORM(对象关系映射)技术编写简单的Python类来映射数据库中的表,以实现数据的增删改查操作。通过定义类、字段和元类,实现了基本的表创建和对象操作。

起步:编写简单的ORM对象

写一个类映射某个数据表,下面是写一个User类,对应数据库中的user表:

1
2
3
4
class User(Model):
     
    id = StringField(primary_key=True,ddl='varchar(50)')
    name = StringField(ddl='varchar(50)')

 

这个表中只有两个字段,id和name,为了简便,这两个字段都为StringField类型。其中StringField类型也为定义的类,对应数据库中的varchar类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Field(object):
    _count = 0
    def __init__(self,**kw):
        self.name = kw.get('name',None)
        self._default = kw.get('default',None)
        self.nullable = kw.get('nullable',False)
        self.primary_key = kw.get('primary_key',False)
        self._order = Field._count
        Field._count = Field._count + 1
        print 'xxx'
        self.ddl = kw.get('ddl','')
 
    @property
    def default(self):
        d = self._default
        return d() if callable(d) else d
    def __str__(self):
        s = ['<%s:%s,%s,default(%s),' % (self.__class__.__name__,self.name,self.ddl,self._default)]
        self.nullable and s.append('N')
        s.append('>')
        return ''.join(s)
class StringField(Field):
    def __init__(self,**kw):
        if not 'default' in kw:
            kw['default'] = ''
        if not 'ddl' in kw:
            kw['ddl'] = 'varchar(250)'
        super(StringField,self).__init__(**kw)

 其中Field为基类,可以将数据库中的类型扩充完。然后在写一个Model类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
        print '%s: %s' % self,value

 Model类父类为dict,其中__metaclass__为元类。这个是整个代码的基础。元类创建一个ModelMetaclass类的实例,注意__new__(cls,name,bases,attrs)中的四个参数表示的意思就很好理解了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ModelMetaclass(type):
    def __new__(cls,name,bases,attrs):
        if name == 'Model':
            return type.__new__(cls,name,bases,attrs)
        print ('Found model: %s' % name)
        mappings = dict()
        primary_key = None
        li = []
        for k,v in attrs.iteritems():
            if isinstance(v,Field):
                if not v.name:
                    v.name = k
                if v.primary_key:
                    if primary_key:
                        raise TypeError('Cannot define more than one primary key in class: %s' % name)
                    if v.nullable:
                        v.nullable = False
                    primary_key = v
                mappings[k] = v
        if not primary_key:
            raise TypeError('Primary key not defined in class: %s' % name)
        for k in mappings.iterkeys():
            attrs.pop(k)
        attrs['__table__'] = name.lower()
        attrs['__mappings__'] = mappings
        attrs['__primary_key__'] = primary_key
        attrs['__sql__'] =  _gen_sql(attrs['__table__'],mappings)
        print str(attrs['__sql__'])
        tables.append(attrs['__sql__'])
        for trigger in _triggers:
            if not trigger in attrs:
                attrs[trigger] = None
        return type.__new__(cls,name,bases,attrs)

 最后是完整的代码,不过只实现了创建表的功能,这只是万里长征的第一步,慢慢来吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# orm.py
tables = []
# Field类
class Field(object):
    _count = 0
    def __init__(self,**kw):
        print 'aaa'
        self.name = kw.get('name',None)
        self._default = kw.get('default',None)
        self.nullable = kw.get('nullable',False)
        self.primary_key = kw.get('primary_key',False)
        self._order = Field._count
        Field._count = Field._count + 1
        print 'xxx'
        self.ddl = kw.get('ddl','')
 
    @property
    def default(self):
        d = self._default
        return d() if callable(d) else d
    def __str__(self):
        s = ['<%s:%s,%s,default(%s),' % (self.__class__.__name__,self.name,self.ddl,self._default)]
        self.nullable and s.append('N')
        s.append('>')
        return ''.join(s)
class StringField(Field):
    def __init__(self,**kw):
        if not 'default' in kw:
            kw['default'] = ''
        if not 'ddl' in kw:
            kw['ddl'] = 'varchar(250)'
        super(StringField,self).__init__(**kw)
_triggers = frozenset(['pre_insert','pre_update','pre_insert'])
# 生成sql语句
def _gen_sql(table_name,mappings):
    pk = None
    sql = ['create table `%s` (' % table_name]
    for f in sorted(mappings.values(),lambda x,y:cmp(x._order, y._order)):
        if not hasattr(f,'ddl'):
            raise StandardError('No ddl in field `%s`' % n)
        ddl = f.ddl
        nullable = f.nullable
        if f.primary_key:
            pk = f.name
        sql.append(nullable and ' `%s` %s' % (f.name,ddl) or ' `%s` %s not null' % (f.name,ddl))
        if pk:
            sql.append(' primary key,')
        pk = None
    sql.append(');')
    return '\n'.join(sql)
         
class ModelMetaclass(type):
    def __new__(cls,name,bases,attrs):
        if name == 'Model':
            return type.__new__(cls,name,bases,attrs)
        print ('Found model: %s' % name)
        mappings = dict()
        primary_key = None
        li = []
        for k,v in attrs.iteritems():
            if isinstance(v,Field):
                if not v.name:
                    v.name = k
                if v.primary_key:
                    if primary_key:
                        raise TypeError('Cannot define more than one primary key in class: %s' % name)
                    if v.nullable:
                        v.nullable = False
                    primary_key = v
                mappings[k] = v
        if not primary_key:
            raise TypeError('Primary key not defined in class: %s' % name)
        for k in mappings.iterkeys():
            attrs.pop(k)
        attrs['__table__'] = name.lower()
        attrs['__mappings__'] = mappings
        attrs['__primary_key__'] = primary_key
        attrs['__sql__'] =  _gen_sql(attrs['__table__'],mappings)
        print str(attrs['__sql__'])
        tables.append(attrs['__sql__'])
        for trigger in _triggers:
            if not trigger in attrs:
                attrs[trigger] = None
        return type.__new__(cls,name,bases,attrs)
 
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
        print '%s: %s' % self,value
  # 该方法可以注释掉
    def save(self):
        fields = []
        params = []
        args = []
        for k,v in self.__mappings__.iteritems():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self,k,None))
        sql = 'insert into %s (%s) values(%s)' % (self.__table__,','.join(fields),','.join(params))
        print sql
        print ('ARGS: %s' % str(args))
 
class User(Model):
     
    id = StringField(primary_key=True,ddl='varchar(50)')
    name = StringField(ddl='varchar(50)')
 
sql = ''.join(tables)
sql = sql.replace('`','')
print sql
import mysql.connector
config = {'user':'root','password':'z5201314','host':'127.0.0.1','port':3306,'database':'test'}
cxn = mysql.connector.connect(**config)
cursor = cxn.cursor()
cursor.execute(sql)

 运行结果:

查看数据库:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值