美文网首页python学习pythonpython
Pony ORM - 有着优美的查询语法的Python ORM

Pony ORM - 有着优美的查询语法的Python ORM

作者: 偷闲一天打个盹 | 来源:发表于2019-03-19 16:24 被阅读0次

    Pony ORM - 有着优美的查询语法的Python ORM

    Pony是一个先进的Python ORM,支持使用类似于python中列表推导式的语法来进行查询:

    select(c for c in Customer if sum(c.orders.total_price) > 1000)
    
    MySQL:
    SELECT `c`.`id`
    FROM `customer` `c`
      LEFT JOIN `order` `order-1`
        ON `c`.`id` = `order-1`.`customer`
    GROUP BY `c`.`id`
    HAVING coalesce(SUM(`order-1`.`total_price`), 0) > 1000
    

    除此之外,你还可以使用lambda表达式来进行类似的查询:

    Customer.select(lambda c: sum(c.orders.total_price) > 1000)
    

    甚至,你也粗暴的使用原生SQL进行查询:

    x = 1000
    y = 500
    Product.select_by_sql("SELECT * FROM Product WHERE price > $x OR price = $(y * 2)")
    
    或者
    Product.select_by_sql(
        "SELECT * FROM Product WHERE price > $x OR price = $(y * 2)",
        globals={'x': 100}, locals={'y': 200}
    )
    

    Pony会查找$符号后定义的变量或者表达式,并自动计算其值然后用作参数传入Query对象,而不是简单的字符串替换,从而使SQL注入变为不可能。

    与Django和SQLAlchemy等ORM相比,它有以下更多的优势:

    • 非常优美的、pythonic的语法
    • 支持python2 & 3
    • 支持PostgreSQL、MySQL、Oracle以及sqlite
    • 自动优化查询

    与Django相比,Pony还提供:

    • IdentityMap模式
    • 自动事务管理
    • 自动缓存查询和对象
    • 完全支持复合键
    • 使用LEFT JOIN,HAVING和SQL的其他功能轻松编写查询的能力

    除此之外,Pony的社区非常的活跃,比如官方在Stack Overflow上有非常迅速的响应速度。从版本0.7开始,Pony ORM在Apache License 2.0版下发布。

    安装pony

    如果只使用sqlite数据库,Pony的安装只需要1条命令:

    pip install pony
    

    如果使用其他数据库,需要安装额外的驱动:

    • PostgreSQL: psycopg or psycopg2cffi
    • MySQL: MySQL-python or PyMySQL
    • Oracle: cx_Oracle

    开始使用

    • 数据库与表定义
    • 记录增删改
    • 查询
    • 事务

    数据库与表定义

    首先你需要创建一个db的对象:

    from pony.orm import *
    db = Database()
    
    # 或者(不推荐)
    from pony import orm
    db = orm.Database()
    

    然后定义表的实体,它继承于db的Entity属性:

    class User(db.Entity):
        name = Required(str)
        uid = Required(int, unique=True, nullable=True)
        face = Required(str)
        info = Required(str)
        gift_rec = Set('GiftRec')
    
    class GiftRec(db.Entity):
        key = Required(str, unique=True)
        room_id = Required(int)
        gift_id = Required(int)
        gift_name = Required(str)
        gift_type = Required(str)
        sender_id = Required(User)
        sender_type = Required(int, nullable=True)
        created_time = Required(datetime.datetime, default=datetime.datetime.now)
        status = Required(int)
    

    接下来绑定到数据库:

    db.bind(provider='mysql', user='Jax', password='123456', host='129.204.43.2', port=44444, database='bilibili')
    # 或者
    db.bind(provider='sqlite', filename=':memory:')
    db.bind(provider='sqlite', filename='filename', create_db=True)
    db.bind(provider='oracle', user='', password='', dsn='')
    

    然后创建映射:

    db.generate_mapping(check_tables=True, create_tables=False)
    

    设置为debug模式:

    set_sql_debug(True)
    

    除此之外,可以使用db.on_connect的装饰器,在db连接的时候执行一些操作:

    db = Database()
    
    # entities declaration
    
    @db.on_connect(provider='sqlite')
    def sqlite_case_sensitivity(db, connection):
        cursor = connection.cursor()
        cursor.execute('PRAGMA case_sensitive_like = OFF')
    
    db.bind(**options)
    db.generate_mapping(create_tables=True)
    

    表的详细定义

    字段

    使用attr_name = kind(type, *options)来定义表的字段,kind一共有四种情况:

    • Required
    • Optional

    Required和Optional是最常用的两种属性,它们的区别就是前者所定义的实体,必须存在一个值,而后者则没有要求。

    • PrimaryKey

    映射到数据库中就是primary key,每个实体必须包含一个。如果你没有显示的定义,那么pony就会自动指定一个,类似于:

    id = PrimaryKey(int, auto=True)
    
    • Set

    Set代表1个集合,也叫做关系。这里实现to-many的关系,目前(0.7)pony不支持primitive types。

    type支持多种类型:

    • str
    • unicode
    • int
    • float
    • Decimal
    • datetime
    • date
    • time
    • timedelta
    • bool
    • buffer - used for binary data in Python 2 and 3
    • bytes - used for binary data in Python 3
    • LongStr - used for large strings
    • LongUnicode - used for large strings
    • UUID
    • Json - used for mapping to native database JSON type
    • IntArray - array of integers
    • StrArray - array of strings
    • FloatArray - array of floats

    options相关的说明:
    <a href="https://docs.ponyorm.org/api_reference.html#attribute-options" target="_blank">https://docs.ponyorm.org/api_reference.html#attribute-options</a>

    复合键

    class Example(db.Entity):
        a = Required(int)
        b = Required(str)
        c = Required(str)
        d = Required(str)
        PrimaryKey(a, b)
        composite_key(c, d)
        # composite_key(a, b) will be represented as the UNIQUE ("a", "b") constraint.
    

    复合索引

    class Example(db.Entity):
        a = Required(str)
        b = Optional(int)
        composite_index(a, b)
        # or composite_index(a, 'b')
    

    实体关联

    一对多:

    class Order(db.Entity):
        items = Set("OrderItem")
    
    class OrderItem(db.Entity):
        order = Required(Order)
    

    在上面定义的OrderItem不能脱离Order而存在。如果你想允许OrderItem在分配给确切的Order之前就存在,可以将order 属性定义为Optional:

    class Order(db.Entity):
        items = Set("OrderItem")
    
    class OrderItem(db.Entity):
        order = Optional(Order)
    

    多对多(Pony 会自动生成中间表):

    class Product(db.Entity):
        tags = Set("Tag")
    
    class Tag(db.Entity):
        products = Set(Product)
    

    一对一:

    必须定义为Optional-Required 或者Optional-Optional:

    class Person(db.Entity):
        passport = Optional("Passport")
    
    class Passport(db.Entity):
        person = Required("Person")
    

    自我关联:

    实体可以使用自引用关系与自身关联。这种关系可以有两种类型:对称和非对称。非对称关系由属于同一实体的两个属性定义。

    对称关系的具体特性是,实体只指定了一个关系属性,而该属性定义了关系的两边。这种关系可以是一对一,也可以是多对多。以下是自我参考关系的示例:

    class Person(db.Entity):
        name = Required(str)
        spouse = Optional("Person", reverse="spouse") # symmetric one-to-one
        friends = Set("Person", reverse="friends")    # symmetric many-to-many
        manager = Optional("Person", reverse="employees") # one side of non-symmetric
        employees = Set("Person", reverse="manager") # another side of non-symmetric
    

    两实体间的多重关系:

    当两个实体之间有多个关系时,pony需要指定反向属性。这是为了让Pony知道哪一对属性相互关联。

    class User(db.Entity):
        tweets = Set("Tweet", reverse="author")
        favorites = Set("Tweet", reverse="favorited")
    
    class Tweet(db.Entity):
        author = Required(User, reverse="tweets")
        favorited = Set(User, reverse="favorites")
    

    记录增删改

    插入

    customer1 = Customer(login="John", password="***", name="John", email="john@google.com")
    

    创建对象时,所有的参数必须指定为kw参数,如果有默认值则可省略之。

    所有创建的实例都属于当前的db_session()。在某些ORM中,需要调用对象的save()方法才能保存它。这是非常愚蠢的,因为程序员必须跟踪创建或更新了哪些对象,并且不能忘记对每个对象调用save()方法。

    Pony自动跟踪创建或更新的对象,并在当前db_session()结束时自动将其保存到数据库中。如果需要在离开db_session()作用域之前保存新创建的对象,可以使用flush()commit()函数来保存。

    class Customer(db.Entity):
        id = PrimaryKey(int, auto=True)
        email = Required(str)
    
    @db_session
    def handler(email):
        c = Customer(email=email)
    
    def handler(email):
        with db_session:
            john = TeamMember(name='John')
    

    更新:

    product = Product.select(...
    product.quantity += 10
    

    更新多个字段:

    order = Order[123]
    order.state = "Shipped"
    order.date_shipped = datetime.now()
    
    order = Order[123]
    order.set(state="Shipped", date_shipped=datetime.now())
    

    Pony总是在执行以下方法之前自动保存db_session()缓存中累积的更改:select()get()exists()execute()commit()

    * 遗憾的是,在当前(0.7.9)版本,Pony不支持bulk update。

    删除:

    order.delete()
    # 或者:
    delete(p for p in Product if p.category.name == 'SD Card')
    # 或:
    Product.select(lambda p: p.category.name == 'SD Card').delete(bulk=True)
    

    查询

    通过pk获取:

    user = User[1024]
    

    同样的语法也适用于具有复合键的对象,只需要按照实体类描述中定义属性的相同顺序列出复合主键的元素,用逗号分隔即可。当数据库记录不存在时,就抛出ObjectNotFound异常。
    同时,也可以:

    product1 = Product.get(name='Product1')
    

    当多个值返回的时候,会抛出MultipleObjectsFoundError异常。

    通过生成器表达式,获取多个对象:

    good_customer = select(c for c in Customer if sum(o.total_price for o in c.orders) > 1000)
    # 或者通过属性来过滤:
    good_customer = select(c for c in Customer if sum(c.orders.total_price) > 1000)
    
    # 可以继续在query对象上使用filter:
    good_adult_customer = good_customer.filter(lambda c: c.age >= 18)
    
    # 还能基于query生成一个新的query:
    good_adult_chinese_customer = select(c.name for c in good_adult_customer if c.country == 'CN')
    

    更多的查询实例:<a href="https://docs.ponyorm.org/queries.html#pony-query-examples" target="_blank">https://docs.ponyorm.org/queries.html#pony-query-examples</a>

    聚合

    以下五个聚合函数用于声明性查询:sum()count()min()max()avg()group_concat()。让我们来看一些使用这些函数的简单查询示例。

    # Total GPA of students from group 101:
    sum(s.gpa for s in Student if s.group.number == 101)
    
    #Number of students with a GPA above three:
    count(s for s in Student if s.gpa > 3)
    
    # First name of a student, who studies philosophy, sorted alphabetically:
    min(s.name for s in Student if "Philosophy" in s.courses.name)
    
    # Birth date of the youngest student in group 101:
    max(s.dob for s in Student if s.group.number == 101)
    
    # Average GPA in department 44:
    avg(s.gpa for s in Student if s.group.dept.number == 44)
    
    # Names of students of group 101 joined by comma:
    group_concat(s.name for s in Student if s.group.number == 101)
    

    需要注意的是,上述的sum、count等函数并非是python的标准函数,而是属于pony.orm模块中,pony实现的函数。pony使用了与python标准函数相同的名称来构造这些函数,如果在程序中直接使用它,不会影响原有的标准函数的行为。但如果忘了从pony.orm中导入这些函数,使用python标准函数sum、count……时会出现一个错误。

    聚合函数也可以在查询中使用:

    # 例如,不仅需要查找组中最年轻的学生的出生日期,还需要查找学生本人,则可以编写以下查询:
    select(s for s in Student if s.group.number == 101 and s.dob == max(s.dob for s in Student if s.group.number == 101))
    
    # to get all groups with an average GPA above 4.5:
    select(g for g in Group if avg(s.gpa for s in g.students) > 4.5)
    

    条件计数:

    select((g, count(s for s in g.students if s.gpa <= 3),
               count(s for s in g.students if s.gpa > 3 and s.gpa <= 4),
               count(s for s in g.students if s.gpa > 4)) for g in Group)
    

    更复杂的聚合

    借助Pony可以实现复杂的分组:

    # group by an attribute part:
    select((s.dob.year, avg(s.gpa)) for s in Student)
    # 此时birth year不会被加`distinct`条件,因为它现在是dob的一个属性
    
    # You can have expressions inside the aggregate functions:
    select((item.order, sum(item.price * item.quantity))
            for item in OrderItem if item.order.id == 123)
    
    # Here is another way of making the same query:
    select((order, sum(order.items.price * order.items.quantity))
            for order in Order if order.id == 123)
    

    事务

    数据库事务是一个逻辑工作单元,可以由一个或多个操作组成。事务是原子的,这意味着当事务对数据库进行更改时,在提交事务时所有更改都会成功,或者在回滚事务时所有更改都会撤消。

    借助Pony的db session,你可以实现自动事务管理。

    db_session

    与数据库交互的代码必须放在数据库会话中。与数据库一起工作的每个应用程序线程都建立一个单独的数据库session,并使用一个单独的标识映射实例。当通过对象的主键或唯一键访问对象,并且该对象已存储在标识映射中时,此标识映射可作为缓存使用,有助于避免数据库查询。为了使用数据库会话处理数据库,可以使用@db_session()修饰器或db_session()上下文管理器。当会话结束时,它执行以下操作:

    • 如果数据已更改且未发生异常,则提交事务,否则将回滚事务
    • 释放db的connection到连接池
    • 清除标识映射缓存

    如果忘记在必要时指定db_session(),pony将在处理数据库时引发异常。

    # Example of using the @db_session() decorator:
    @db_session
    def check_user(username):
        return User.exists(username=username)
    
    # Example of using the db_session() context manager:
    def process_request():
        ...
        with db_session:
            u = User.get(username=username)
            ...
    

    嵌套的db_session

    如果您递归地进入db_session()作用域,例如从另一个用@db_session()修饰的函数调用一个用@db_session()修饰的函数,那么pony将不会创建新的会话,而是为两个函数共享同一个会话。数据库会话在离开最外层的db_session()修饰器或上下文管理器的作用域时结束。

    db_session的cache

    Pony在几个阶段缓存数据以提高性能。它缓存:

    • 生成器表达式转换的结果。如果在程序中多次使用同一个生成器表达式查询,则只会将其转换为SQL一次。这个缓存对于整个程序是全局的,不仅仅是对于单个数据库会话
    • 从数据库创建或加载的对象。离开db_session()作用域或事务回滚时清除此缓存。
    • 查询结果。如果用相同的参数再次调用相同的查询,pony将从缓存返回查询结果。一旦任何实体实例发生更改,就会清除此缓存。离开db_session()作用域或事务回滚时清除此缓存

    多数据库混合使用

    如果一个session中,使用到了多个数据库,那么在离开session时Pony会针对每个db执行commit()rollback()。如果需要在退出函数之前提交到一个数据库,可以使用db1.commit()db2.commit()方法。

    乐观的并发控制

    默认情况下,pony使用乐观并发控制概念来提高性能。Pony不会获取数据库的行锁,取而代之的是,它会验证是否有其他事务修改了它已读取或正在尝试修改的数据。如果检查到冲突的修改,提交事务时就会抛出一个异常:

    OptimisticCheckError, 'Object XYZ was updated outside of current transaction'
    

    关于乐观锁和悲观锁的分析:
    <a href="https://docs.ponyorm.org/transactions.html#optimistic-concurrency-control" target="_blank">https://docs.ponyorm.org/transactions.html#optimistic-concurrency-control</a>

    相关文章

      网友评论

        本文标题:Pony ORM - 有着优美的查询语法的Python ORM

        本文链接:https://www.haomeiwen.com/subject/wdknmqtx.html