python ORM 模块peewee

python ORM 模块peewee(一): 建立数据库对象

1. 单连接模式

peewee通过Database类及其子类来建立数据库对象,目前peewee支持的数据库有Postgres, MySQL,MySQL,SQLite 以及BerkeleyDB。这里我们仅通过MySQL来进行了解,如果对其他数据库类型的相关内容感兴趣可以参见官方文档
mysql通过MySQLDatabase类来初始化数据库对象,这是Database的一个子类,继承了父类的构造函数:

class Database(database[, threadlocals=True[, autocommit=True[, fields=None[, ops=None[, autorollback=False[, use_speedups=True[, **connect_kwargs]]]]]]])

这里
database指数据库的名称, 字符串格式
threadlocals指 是否用本地线程保存连接
autocommit 自动提交
fields 添加orm中的数据类型到数据库中数据类型的映射
ops 定义操作符映射
autorollback 执行数据库请求时出错则自动回滚
use_speedups 用Cpython speedup模块来加速某些queries
connect_kwargs 数据库驱动中初始化所用的key
另外, MySQLDatabase类还添加了以下选项:
commit_select = True
compound_operations = ['UNION']
for_update = True
subquery_delete_same_table = False
peewee中的mysql驱动有两个pymysqlMySQLdb,但是从源码中可以看到peewee是更倾向于使用MySQLdb的,只有当import MySQLdb as mysql报错后才会开始引用import pymysql as mysql。这里举例介绍一些常用的connect_kwargs,如果需要详细了解,可以参见源程序中的connections.connection类,里面的注释已经写得非常清晰了。例如:

#! /usr/bin/env python
# coding: utf-8

from peewee import *

db = MySQLDatabase(
    database = 'test',# string
    passwd = 'test', # string
    user = 'test', # string
    host = 'localhost', # string
    port = 3306, # int, 可不写
)

官方文档中建议使用数据库前先调用其db.connect()函数,这个函数功能上并不是必须的,但是可以帮助定位错误。
实例化本地数据库还有另外一种方法: mysql://user:passwd@ip:port/my_db这在Connecting using a Database URL中也有介绍

2 实时数据库实例化

Database类中的database字段可以先设置为None, 数据库的实例化可以在随后得到数据可靠名字之后,具体操作如下:

db = MySQLDatabase(None)

# 此时若是调用db.connect()则会报错

# 数据库的名字
db_name = 'mydb'
db.init(
    database = 'mydb',
    host = 'test',
    user = 'test',
    passwd = 'test'
)
3 动态实例化

peewee甚至允许数据库的动态实例化,这里用到了代理模式:

# 创建数据库的代理
db_proxy = Proxy()  

# 使用代理数据库创建表
class BaseModel(Model):
    class Meta:
        database = db_proxy  

class User(BaseModel):
    username = CharField()

# 根据配置条件来选择不同的数据库
if app.config['DEBUG']:
    db= SqliteDatabase('local.db')
elif app.config['TESTING']:
    db= SqliteDatabase(':memory:')
else:
    db= PostgresqlDatabase('mega_production_db')

# 通过代理来根据配置条件将选取的数据库初始化
database_proxy.initialize(db)
4 连接池

peewee为通过一个'PooledDatabase'基类Mysql, sqlite 和Postgres 提供了连接池的功能。这个基类的定义如下:
class PooledDatabase(database[, max_connections=20[, stale_timeout=None[, **kwargs]]])
其中:
max_connections 定义了最大连接数
stale_timeout 定义了每个连接最大可用时间
具体它的MySQL子类为:class PooledMySQLDatabase

 

python ORM 模块peewee(二): 数据库使用的基本流程

#! /usr/bin/env python
# coding: utf-8

# 1. 导入peewee的模块
from peewee import *
from datetime import datetime

# 2. 建立数据库实例
db = MySQLDatabase(
        database = 'test',
        host = 'localhost',
        port = 3306, 
        user = 'test',
        passwd = 'test',
        charset = 'utf8'
        )   
#######################################################################
# 3. 建立数据表的模型
# 4. 先建立基本模型,具体的模型在此基础上继承而来
class BaseModel(Model):
    class Meta:
        # 指定表所在的数据库
        database = db

class User(BaseModel):
    username = CharField(unique=True)


class Tweet(BaseModel):
    user = ForeignKeyField(User, related_name='tweets')
    message = TextField()
    created_date = DateTimeField(default=datetime.now)
    is_published = BooleanField(default=True)

#########################################################################
if __name__ == '__main__':
    try:
        # connect方法不是必须的,但是如果出错可以判断是否是在连接时出的错
        db.connect()
        

        # 5. 创建表,这里有了safe=True选项,会在创建表之前先检查数据库里表是否已经存在,由于创建表的语句往往只需要使用一次,一般建议写入类或者方法中通过具体命令来调用
        # 注意:peewee里面创建表有两个方法, create_tables是`Database`中的方法,创建表时会建立表之间的关系和表的索引,使用`somedb.create_tables([Models], safe=False)`来调用
        # create_table是`Model`中的方法,仅仅创建表本身,而不包含索引和表之间的关系,使用`somemodel.create_table(safe=False)`来调用
        db.create_tables([User, Tweet], safe=True)
        

        # 6. 断开连接 
        db.close()
    except Exception, e:
        print e

python ORM 模块peewee(三): Model的建立

首先,需要理清一下python数据与数据库数据是怎么对应的:

  • Model类:表
  • Field类:表上的列的类型
  • Model实例:表上的一行数据
1. Model Class主要描述的是数据表以及其继承的顺序

这里主要是需要对Model类中的Meta类的定义规范进行小结:

  1. 当Meta类建立后,不应该通过mymodel.Meta来进行访问,而应该使用mymodel._meta, 这是一个ModelOption类的实例, 可以通过它获取model和Meta的一些属性
  2. Meta的可用属性有:
    865265-20160314175143740-1832622103.png
    除了db_tabletable_alias之外,其他的字段均可以被继承
    可以看到,peewee的主键,索引和其他约束均可以在meta中定义。
  3. 主键的定义
class mymodel1(Model):
    name = CharField(max_length=200, primary_key=True) #将该字段设为主键

class mymodel2(Model):
    name = CharField(max_length=200)
    Meta:
        # 如果要不使用主键
        primary_key = False

class mymodel3(Model):
    blog = ForeignKeyField(Blog)
    tag = ForeignKeyField(Tag)

    class Meta:
        primary_key = CompositeKey('blog', 'tag') # primary_key实际上是CompositeKey的一个实例

非整型的主键
当主键为非auto_increment时,插入数据调用save()会出错,应该调用save(force_insert=True)

  1. 添加索引
    单列索引
    多列索引
2. Field Class主要描述python数据类型与数据库数据类型的对应关系和数据表之间的关系(外键,多对多)

Filed在peewee.py中有基本数据类型的定义

# 初始化函数,各个字段的默认值
def __init__(self, null=False, index=False, unique=False,
                 verbose_name=None, help_text=None, db_column=None,
                 default=None, choices=None, primary_key=False, sequence=None,
                 constraints=None, schema=None)

865265-20160314131238271-1167447631.png

在playhouse.fields中定义的额外字段

包括

    PasswordField
    ManyToManyField
    CompressedField
    PickledField
    AESEncryptedField

然而peewee中并不推荐使用这些Field, 例如ManyToManyField,完全可以用一个含两个外键的关系表来维护,ManyToMany关系在ORM中实际上也正是这样来实现的,并且使用这种方式可以描述更加复杂的关系。

自定义field字段

# 1. 定义MyField类
class MyField(Field):
    # Field的标签
    db_field = 'my'
    
    # 返回数据库类型的值
    def db_value(value):
        pass

    # 返回python类型的值
    def python_value(value):
        pass

# 2. 将自定义Field子类与数据表中的列关联,有两种方法:
#     1. 创建数据库时,给fields字段赋值
db = MySQLDatabase('my_db', fields={'my': 'my'})
#     2. 调用register_fields函数
db.register_fields({'my':'my'})

python ORM 模块peewee(四): Model类源码初探

Model的初始化方法主要继承自BaseModel类的__new__方法。 下面粗略的通过源码来看看:

class BaseModel(type):
    # 定义可被继承的属性列表(全局)
    inheritable = set([
        'constraints', 'database', 'db_table_func', 'indexes', 'order_by',
        'primary_key', 'schema', 'validate_backrefs', 'only_save_dirty'])

    def __new__(cls, name, bases, attrs):
        # 如果没有父类,则之间创建BaseModel类
        if not bases:
            return super(BaseModel, cls).__new__(cls, name, bases, attrs)

        # Meta类的属性通过meta_options存储在Model类中
        meta_options = {}
        
        # 将Meta从属性中移除,将Meta中的非私有属性加入meta_options中
        meta = attrs.pop('Meta', None)
        if meta:
            for k, v in meta.__dict__.items():
                if not k.startswith('_'):
                    meta_options[k] = v

        # 从meta中获取主键信息
        model_pk = getattr(meta, 'primary_key', None)
        parent_pk = None

        ############################################################
        # 开始考虑从父类中继承的情况
        #############################################################
        for b in bases:
            if not hasattr(b, '_meta'):
                continue

            base_meta = getattr(b, '_meta')
            if parent_pk is None:
                parent_pk = deepcopy(base_meta.primary_key)
            all_inheritable = cls.inheritable | base_meta._additional_keys

            # 获取父类中的Meta内部类字段,只考虑all_inheritable中的字段
            for (k, v) in base_meta.__dict__.items():
                if k in all_inheritable and k not in meta_options:
                    meta_options[k] = v
                     
            # 获取父类中的Fields, 即表的字段
            for (k, v) in b.__dict__.items():
                if k in attrs:
                    continue
                if isinstance(v, FieldDescriptor):
                    if not v.field.primary_key:
                        attrs[k] = deepcopy(v.field)

        # initialize the new class and set the magic attributes
        cls = super(BaseModel, cls).__new__(cls, name, bases, attrs)
        ModelOptionsBase = meta_options.get('model_options_base', ModelOptions)
        cls._meta = ModelOptionsBase(cls, **meta_options)
        cls._data = None
        cls._meta.indexes = list(cls._meta.indexes)

        # 默认表名的设定,Model名的小写,然后将非数字和英文字符换成'_'
        if not cls._meta.db_table:
            cls._meta.db_table = re.sub('[^\w]+', '_', cls.__name__.lower())

        # replace fields with field descriptors, calling the add_to_class hook
        # 这里筛选attr中的Field类型字段,设置Model中的数据类型
        # 也许可以测试一下类里面的函数是怎么继承的
        fields = []
        for name, attr in cls.__dict__.items():
            if isinstance(attr, Field):
                if attr.primary_key and model_pk:
                    raise ValueError('primary key is overdetermined.')
                elif attr.primary_key:
                    model_pk, pk_name = attr, name
                else:
                    fields.append((attr, name))
        
        composite_key = False
        # 默认主键的设置,如果无法从父类继承,,则使用'id'为key, 也就是行号
        if model_pk is None:
            if parent_pk:
                model_pk, pk_name = parent_pk, parent_pk.name
            else:
                model_pk, pk_name = PrimaryKeyField(primary_key=True), 'id'
        elif isinstance(model_pk, CompositeKey):
            pk_name = '_composite_key'
            composite_key = True

        # 如果model本身有主键的情况
        if model_pk is not False:
            model_pk.add_to_class(cls, pk_name)
            cls._meta.primary_key = model_pk
            cls._meta.auto_increment = (
                isinstance(model_pk, PrimaryKeyField) or
                bool(model_pk.sequence))
            cls._meta.composite_key = composite_key

        # 设置Fields
        for field, name in fields:
            field.add_to_class(cls, name)

        # create a repr and error class before finalizing
        # __unicode__的设置
        if hasattr(cls, '__unicode__'):
            setattr(cls, '__repr__', lambda self: '<%s: %r>' % (
                cls.__name__, self.__unicode__()))

        # 错误信息
        exc_name = '%sDoesNotExist' % cls.__name__
        exc_attrs = {'__module__': cls.__module__}
        exception_class = type(exc_name, (DoesNotExist,), exc_attrs)
        cls.DoesNotExist = exception_class
        cls._meta.prepared()

        if hasattr(cls, 'validate_model'):
            cls.validate_model()

        return cls

转载于:https://my.oschina.net/mickelfeng/blog/872260

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值