FastApi(二) -- 集成peewee操作数据库

FastApi(二) – 集成peewee操作数据库

peewee是什么

Peewee 是一个简单而强大的 Python ORM(对象关系映射)库,它提供了轻量级、简单易用的数据库操作功能。它的设计目标是提供一个简单而直观的 API,同时保持高性能和可扩展性,使得开发者能够轻松地在 Python 应用中进行数据库操作。

  1. 简单易用: Peewee 的 API 设计简洁清晰,易于理解和使用。它提供了类似于 Python 数据结构的对象模型,使得数据库操作变得非常直观和自然。
  2. 轻量级: Peewee 是一个轻量级的 ORM 库,它的代码库相对较小,没有过多的依赖。这使得它非常适合于小型项目或需要高性能的应用。
  3. 支持多种数据库后端: Peewee 支持多种流行的数据库后端,包括 SQLite、MySQL、PostgreSQL 等。这使得开发者可以根据项目需求选择最适合的数据库。
  4. 灵活性: Peewee 提供了丰富的查询构建器和条件表达式,使得开发者能够灵活地构建复杂的查询语句。同时,它还支持原生 SQL 查询,满足了更高级的需求。
  5. 性能优化: 尽管 Peewee 是一个轻量级的 ORM 库,但它仍然具有优秀的性能。它通过一些优化技巧和缓存机制来提高查询效率,同时保持了稳定性和可靠性。
  6. Peewee 拥有一个活跃的开发者社区,提供了丰富的文档和教程,以及持续的更新和维护。这使得开发者能够快速解决问题,并获取帮助和支持。

FastApi如何集成Peewee

  1. 准备工作

    1. 安装FastAPI与Peewee相关包

      pip install fastapi uvicron peewee
      
  2. 搭建FastApi项目并集成Peewee

    1. 创建一个新的 Python 文件,比如 app.py,并编写以下代码来创建一个基本的 FastAPI 应用:

      from fastapi import fastapi
      
      app = fastapi()
      
      if __name__ = "__main__":
          uvicoorn.run(app="main:app", host="localhost", port=8000)
      
    2. 引入peewee包,并连接数据库(以mysql数据库为例)

      from peewee import MySQLDatabase
      
      mysqlDb = MySQLDatabase(
          database='book_libs',
          user='root',
          password='passwrod',
          host='127.0.0.1',
          port=3306)
      
      # 开启服务时连接数据库
      app.on_event('startup')
      def startup():
          mysqlDb.connect()
      
      # 关闭服务时断开连接
      @app.on_event('shutdown')
      def shutdown():
          mysqlDb.close()
      
    3. 创建数据表的映

      -- book_libs.`user` definition
      
      CREATE TABLE `user` (
        `id` bigint unsigned NOT NULL COMMENT '用户id',
        `user_name` varchar(100) NOT NULL COMMENT '用户名称',
        `user_code` varchar(100) NOT NULL COMMENT '用户编码',
        `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
        `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '编辑时间',
        `create_by` bigint unsigned NOT NULL COMMENT '创建人',
        `gender` int NOT NULL DEFAULT '1' COMMENT '性别(1 男 0 女)',
        `avatar` varchar(100) DEFAULT NULL COMMENT '头像',
        `age` int DEFAULT NULL COMMENT '年龄',
        `update_by` bigint unsigned DEFAULT NULL COMMENT '编辑人',
        `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
        PRIMARY KEY (`id`),
        KEY `idx_user_name` (`user_name`) USING BTREE,
        KEY `idx_update_time` (`update_time`) USING BTREE
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户信息';
      

      上面创建了一张user表,下面根据peewee提供的model类来进行表字段的映射:

      from peewee import Model, CharField, IntegerField, BigIntegerField, DateTimeField, BooleanField
      from utils.SnowFlakeUtil import snowflake
      from config.DatabaseConfig import mysqlDb
      from datetime import datetime
      
      class User(Model):
      	id = IntegerField(primary_key=True, default=snowflake.generate)
          user_name = CharField()
          user_code = CharField()
          age = IntegerField()
          gender = IntegerField()
          avatar = CharField()
          deleted = BooleanField()
          create_by = BigIntegerField()
          update_by = BigIntegerField()
          create_time = DateTimeField(default=datetime.now)
          update_time = DateTimeField(default=datetime.now)
          
          class Meta:
              database = mysqlDb
      

      上面就是user表python代码映射试下,在代码中看到有一个 ‘’‘class Meta’‘’ 的代码块,这个是peewee官方规定在集成时需要用这个与数据库建立联系

    通过上面的例子我们已经成功的集成了peewee去进行mysql数据库的操作。那么下面我们就看下具体的一些操作接口吧

Peewee常用的方法

peewee的官方文档中给去了他的接口文档,我们可以跟着接口文档去尝试每一个接口,去进行数据库的操作。接下来我给大家讲解下常用的一些api

  1. 插入数据

    1. model.save()

      def create_user(user: User):
          count: int = user.save()
          
      # 该方法会在数据库中新增一条数据,同时会返回一个数字代表这次操作数据库影响会有多少行
      
    2. Model.create()

      # 方法传递一个实例
      def create_user(user: User):
          insert_user: User = User.create(**user.__dict__)
      
      # create()方法也会在数据库中插入一条数据,在插入数据的同时还会返回一个数据库模型的实例User
      
      # 方法传递一个字典
      def create_user(user_dict: Dict):
          insert_user: user = User.create(**user_dict)
         
      

      通过create()方法进行创建时,create()方法中的参数是通过关键词参数进行传参,因此不能够直接通过传递实体类。

      同时我们看create()方法的源码发现,create()方法的底层调用的是save()方法。在create()方法中先实例化一个实体,然后再调用的实体的save()方法

      class Model(with_metaclass(ModelBase, Node)):
          def __init__(self, *args, **kwargs):
              if kwargs.pop('__no_default__', None):
                  self.__data__ = {}
              else:
                  self.__data__ = self._meta.get_default_dict()
              self._dirty = set(self.__data__)
              self.__rel__ = {}
      
              for k in kwargs:
                  setattr(self, k, kwargs[k])
      
          def __str__(self):
              return str(self._pk) if self._meta.primary_key is not False else 'n/a'
      
      
          @classmethod
          def insert(cls, __data=None, **insert):
              return ModelInsert(cls, cls._normalize_data(__data, insert))
      
          @classmethod
          def create(cls, **query):
              inst = cls(**query)
              inst.save(force_insert=True)
              return inst
      
    3. Model.insert()

      如果不想要返回数据的话我们可以使用insert()方法进行插入数据

      class Model(with_metaclass(ModelBase, Node)):
          @classmethod
          def insert(cls, __data=None, **insert):
              return ModelInsert(cls, cls._normalize_data(__data, insert))
      

      看源码知道,insert方法中有三个参数:

      • cls 类本身
      • __data 数据要插入的字典对象。这个参数通常用于插入单条记录。如果提供了 __data 参数,则其余的关键字参数会被忽略。
      • **insert: 插入数据的关键字参数。这些参数可以是字段名和对应的值,也可以是字段名和函数调用、子查询等。这个参数通常用于插入多条记录或者插入特殊格式的数据。如果提供了 __data 参数,则 **insert 参数会被忽略。

      下面通过两种方式来进行说明:

      1. User.insert(__data={'user_name': 'John', 'age': 30}).execute()
        # 在这个示例中,我们通过 `__data` 参数传递了要插入的数据,其中键是字段名,值是对应的值。
        
      2. data = [
            {'user_name': 'John', 'age': 30},
            {'user_name': 'Alice', 'age': 25}
        ]
        User.insert_many(data).execute()
        # 在这个示例中,我们使用 insert_many 方法插入多条记录,将数据以列表的形式传递给 insert_many 方法。
        
  2. 编辑数据

    编辑数据有两种方式:

    1. 通过model.save()方法进行编辑,该方法如何主键存在,就回根据主键去匹配已有的数据,并对已有的数据进行覆盖

    2. Model.update() 方法可以进行编辑操作

      def update_user(user: User):
          try:
              User.update(__data={
                  'user_name': user.user_name,
                  'user_code': user.user_code,
              }).where(User.id == user.id).execute()
          except Exception as e:
              logger.error("编辑用户{}信息失败, {}".format(user.id, e.__cause__))
              raise CustomException(status_code=400, detail='编辑用户信息失败')
      
  3. 删除数据

    1. Model.delete_instance() 该方法是根据现有的实例来进行删除我们可以先查询到该条数据,然后根据这条数据删除

      user = User.get(User.id == 1)
      user.delete_instance()
      
    2. Model.delete() 该条语句可以配合条件进行删除

      q = User.delete().where(User.active == False)
      q.execute()  # Remove the rows, return number of rows removed.
      
  4. 查询数据

    1. Model.get() 该方法用于获取单条数据的查询

      User.get(User.id == 1)
      
    2. Model.get_by_id() 该方法是根据主键id来进行查询

      User.get_by_id(1)  # Same as above.
      
    3. Model.select() 该方法可以查询多条,也可以配合着条件语句进行查询

      query = User.select().where(User.active == True).order_by(User.username)
      

上面是一些常用且较为简单的api,具体的更多的我们可以去官网上进行插叙学习

使用中的一些坑

  1. 自定义save()函数进行保存时,数据一直保存不了,但是有没有报错?

    通过save()函数的源码我们发现,在进行保存操作时,函数回去判断是否有主键,且主键是否有值。

    class Model(with_metaclass(ModelBase, Node)):
            def save(self, force_insert=False, only=None):
            field_dict = self.__data__.copy()
            if self._meta.primary_key is not False:
                pk_field = self._meta.primary_key
                pk_value = self._pk
            else:
                pk_field = pk_value = None
            if only is not None:
                field_dict = self._prune_fields(field_dict, only)
            elif self._meta.only_save_dirty and not force_insert:
                field_dict = self._prune_fields(field_dict, self.dirty_fields)
                if not field_dict:
                    self._dirty.clear()
                    return False
    
            self._populate_unsaved_relations(field_dict)
            rows = 1
    
            if self._meta.auto_increment and pk_value is None:
                field_dict.pop(pk_field.name, None)
    
            if pk_value is not None and not force_insert:
                if self._meta.composite_key:
                    for pk_part_name in pk_field.field_names:
                        field_dict.pop(pk_part_name, None)
                else:
                    field_dict.pop(pk_field.name, None)
                if not field_dict:
                    raise ValueError('no data to save!')
                rows = self.update(**field_dict).where(self._pk_expr()).execute()
            elif pk_field is not None:
                pk = self.insert(**field_dict).execute()
                if pk is not None and (self._meta.auto_increment or
                                       pk_value is None):
                    self._pk = pk
                    # Although we set the primary-key, do not mark it as dirty.
                    self._dirty.discard(pk_field.name)
            else:
                self.insert(**field_dict).execute()
    
            self._dirty -= set(field_dict)  # Remove any fields we saved.
            return rows
    

    下面是我自定义的save()方法:

        def save(self, *args, **kwargs):
            self.update_time = datetime.now()
            if self.id is None:
                self.id = snowflake.generate()
                self.create_time = datetime.now()
            try:
                count = super().save(*args, **kwargs, force_insert=True)
                print("Save count:", count)  # 调试输出保存的记录数
                return count
            except Exception as e:
                print("Error saving:", e)  # 打印保存过程中的错误信息
                raise e
    

    在方法中我们看见,判断了主键id是否存在,如果不存在我们就新加入一个值,这个时候在去调用父类的save()方法时,就回一直执行编辑操作。但是编辑的时候,通过id去匹配数据我们又没有匹配到具体的数据,因此这儿对数据的操作影响行数一直未0,也并未报错;

    解决方法:

    ​ 可以根据save()函数中的强制插入参数,让save()函数一直只做插入操作

  2. 使用create()方法进行插入操作时,报错save()方法的强制确认参数重复?

    由于上面重写save()函数,将强制确认参数设置为true。而create方法底层调用的是save()方法并传参了强制确认参数造成。

    解决方法:

    1. 不重写save()方法,peewee会自动在新增时加入id;
    2. 两种方法不同时用
  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用 FastAPI 中 Tortoise-ORM、SQLAlchemy 和 peewee 进行 ORM 查询的示例。 ## Tortoise-ORM ```python from fastapi import FastAPI from tortoise import fields from tortoise.contrib.fastapi import register_tortoise, HTTPNotFoundError from tortoise.models import Model from tortoise import Tortoise class User(Model): id = fields.IntField(pk=True) name = fields.CharField(50) email = fields.CharField(50) class Meta: table = "users" app = FastAPI() @app.on_event("startup") async def startup(): await Tortoise.init( db_url="sqlite://db.sqlite3", modules={"models": ["main"]} ) await Tortoise.generate_schemas() @app.on_event("shutdown") async def shutdown(): await Tortoise.close_connections() @app.get("/users") async def get_users(): users = await User.all() return users @app.get("/users/{user_id}") async def get_user(user_id: int): user = await User.get_or_none(id=user_id) if user is None: raise HTTPNotFoundError return user register_tortoise( app, db_url="sqlite://db.sqlite3", modules={"models": ["main"]}, generate_schemas=True, add_exception_handlers=True ) ``` ## SQLAlchemy ```python from fastapi import FastAPI from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) name = Column(String(50)) email = Column(String(50)) engine = create_engine("sqlite:///db.sqlite3") SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) app = FastAPI() @app.get("/users") async def get_users(): db = SessionLocal() users = db.query(User).all() db.close() return users @app.get("/users/{user_id}") async def get_user(user_id: int): db = SessionLocal() user = db.query(User).filter(User.id == user_id).first() db.close() if user is None: raise HTTPNotFoundError return user ``` ## peewee ```python from fastapi import FastAPI from peewee import SqliteDatabase, Model, CharField, IntegerField from playhouse.shortcuts import model_to_dict db = SqliteDatabase("db.sqlite3") class User(Model): id = IntegerField(primary_key=True) name = CharField() email = CharField() class Meta: database = db table_name = "users" app = FastAPI() @app.on_event("startup") def startup(): db.connect() db.create_tables([User]) @app.on_event("shutdown") def shutdown(): db.close() @app.get("/users") async def get_users(): users = [model_to_dict(user) for user in User.select()] return users @app.get("/users/{user_id}") async def get_user(user_id: int): user = User.get_or_none(User.id == user_id) if user is None: raise HTTPNotFoundError return model_to_dict(user) ``` 注意:以上示例中的代码仅用于演示 ORM 查询的基本用法,并且没有进行错误处理。在实际应用中,你应该根据需要添加适当的错误处理和安全性检查。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值