☰
SQLAlchemy实战:构建命令行待办事项应用
2026/11/5 2:56:10 网站建设 项目流程

1. 从零构建命令行待办事项应用:SQLAlchemy实战指南

作为一个长期与Python打交道的开发者,我始终认为待办事项应用是检验ORM掌握程度的绝佳练手项目。这次我将分享如何用SQLAlchemy构建一个功能完整的命令行待办事项管理器,其中包含许多我在实际项目中积累的数据库操作经验。

这个应用将实现任务增删改查、分类标记、状态变更等核心功能,并支持数据持久化存储。选择SQLite作为默认数据库,因其无需额外服务即可运行,特别适合小型应用开发。下面让我们从环境搭建开始,逐步深入SQLAlchemy的实战应用。

2. 项目环境准备与初始化

2.1 基础环境配置

首先确保你的系统已安装Python 3.7+版本。我推荐使用虚拟环境隔离项目依赖:

python -m venv todoenv source todoenv/bin/activate # Linux/macOS # 或者 todoenv\Scripts\activate (Windows)

安装SQLAlchemy核心库(当前最新版本为2.0+):

pip install sqlalchemy

注意:虽然SQLite包含在Python标准库中,但如果你计划迁移到其他数据库,可以预先安装对应驱动:

  • PostgreSQL: psycopg2-binary
  • MySQL: mysql-connector-python

2.2 项目结构规划

合理的项目结构能显著提升代码可维护性。建议采用以下布局:

todo_cli/ ├── models/ # 数据模型定义 │ ├── __init__.py │ └── tasks.py ├── db/ # 数据库操作 │ ├── __init__.py │ └── session.py ├── commands/ # CLI命令实现 │ ├── __init__.py │ ├── add.py │ └── ... └── main.py # 入口文件

这种模块化设计使得后续功能扩展更加清晰,也便于团队协作开发。

3. 数据模型设计与实现

3.1 核心模型定义

在models/tasks.py中,我们定义两个主要模型:Task(任务)和Category(分类)。这是典型的"多对多"关系场景:

from sqlalchemy import Column, Integer, String, DateTime, Boolean, Enum from sqlalchemy.orm import relationship, declarative_base from datetime import datetime import enum Base = declarative_base() class TaskStatus(enum.Enum): PENDING = 1 IN_PROGRESS = 2 COMPLETED = 3 class Task(Base): __tablename__ = 'tasks' id = Column(Integer, primary_key=True) title = Column(String(100), nullable=False) description = Column(String(500)) due_date = Column(DateTime) status = Column(Enum(TaskStatus), default=TaskStatus.PENDING) created_at = Column(DateTime, default=datetime.utcnow) categories = relationship("Category", secondary="task_categories", back_populates="tasks") class Category(Base): __tablename__ = 'categories' id = Column(Integer, primary_key=True) name = Column(String(50), unique=True, nullable=False) color = Column(String(20)) # 用于命令行显示的颜色代码 tasks = relationship("Task", secondary="task_categories", back_populates="categories") # 关联表 class TaskCategory(Base): __tablename__ = 'task_categories' task_id = Column(Integer, ForeignKey('tasks.id'), primary_key=True) category_id = Column(Integer, ForeignKey('categories.id'), primary_key=True)

3.2 模型设计要点解析

  1. 状态管理:使用Python的enum模块定义任务状态,比纯字符串更规范且易于维护
  2. 时间处理:created_at自动记录创建时间,due_date支持设置截止日期
  3. 关系设计:通过task_categories关联表实现多对多关系,允许一个任务属于多个分类
  4. 字段约束:title和category.name设为非空,category.name保持唯一

实战经验:对于简单的状态字段,Enum比自由文本更可靠。我曾在一个项目中使用字符串存储状态,结果因为大小写不一致导致查询失败,改用Enum后问题彻底解决。

4. 数据库会话管理与CRUD操作

4.1 会话工厂配置

在db/session.py中创建可复用的会话管理工具:

from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, scoped_session # 使用SQLite内存数据库(开发环境) # engine = create_engine('sqlite:///:memory:', echo=True) # 使用文件数据库(生产环境) engine = create_engine('sqlite:///todo.db', echo=False) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) # 线程安全的会话工厂 session_factory = scoped_session(SessionLocal) def get_db(): """获取数据库会话的生成器函数""" db = session_factory() try: yield db finally: db.close()

4.2 核心CRUD实现

在db/crud.py中封装常用操作:

from datetime import datetime from sqlalchemy.orm import Session from models.tasks import Task, Category, TaskStatus def create_task(db: Session, title: str, description: str = None, due_date: datetime = None, categories: list = None): """创建新任务""" task = Task( title=title, description=description, due_date=due_date ) if categories: for cat_name in categories: category = get_or_create_category(db, cat_name) task.categories.append(category) db.add(task) db.commit() db.refresh(task) return task def get_or_create_category(db: Session, name: str): """获取或创建分类""" category = db.query(Category).filter(Category.name == name).first() if not category: category = Category(name=name) db.add(category) db.commit() return category def update_task_status(db: Session, task_id: int, status: TaskStatus): """更新任务状态""" task = db.query(Task).get(task_id) if task: task.status = status db.commit() return True return False def delete_task(db: Session, task_id: int): """删除任务""" task = db.query(Task).get(task_id) if task: db.delete(task) db.commit() return True return False

4.3 查询功能实现

def get_tasks(db: Session, status: TaskStatus = None, category: str = None, overdue: bool = False): """获取任务列表(支持多种过滤条件)""" query = db.query(Task) if status: query = query.filter(Task.status == status) if category: query = query.join(Task.categories).filter(Category.name == category) if overdue: query = query.filter( Task.due_date < datetime.utcnow(), Task.status != TaskStatus.COMPLETED ) return query.order_by(Task.due_date.asc()).all() def get_task_details(db: Session, task_id: int): """获取任务详情(包含分类信息)""" return db.query(Task).options( joinedload(Task.categories) ).filter(Task.id == task_id).first()

性能提示:使用joinedload进行关联加载可以避免N+1查询问题。我曾在一个项目中因为没有使用预加载,导致简单的列表查询产生了上百条SQL语句,性能急剧下降。

5. 命令行界面实现

5.1 使用argparse构建CLI

在main.py中创建命令行入口:

import argparse from datetime import datetime from db.session import get_db from db.crud import ( create_task, get_tasks, update_task_status, delete_task ) def init_parser(): parser = argparse.ArgumentParser(description="命令行待办事项管理器") subparsers = parser.add_subparsers(dest="command") # 添加任务 add_parser = subparsers.add_parser("add", help="添加新任务") add_parser.add_argument("title", help="任务标题") add_parser.add_argument("-d", "--description", help="任务描述") add_parser.add_argument("--due-date", help="截止日期(YYYY-MM-DD)") add_parser.add_argument("-c", "--categories", nargs="+", help="任务分类") # 列表查询 list_parser = subparsers.add_parser("list", help="列出任务") list_parser.add_argument("-s", "--status", choices=["pending", "progress", "completed"], help="按状态过滤") list_parser.add_argument("-c", "--category", help="按分类过滤") list_parser.add_argument("--overdue", action="store_true", help="只显示逾期任务") # 更新状态 status_parser = subparsers.add_parser("status", help="更新任务状态") status_parser.add_argument("task_id", type=int, help="任务ID") status_parser.add_argument("status", choices=["pending", "progress", "completed"], help="新状态") # 删除任务 delete_parser = subparsers.add_parser("delete", help="删除任务") delete_parser.add_argument("task_id", type=int, help="任务ID") return parser def main(): parser = init_parser() args = parser.parse_args() db = next(get_db()) if args.command == "add": due_date = datetime.strptime(args.due_date, "%Y-%m-%d") if args.due_date else None task = create_task( db, title=args.title, description=args.description, due_date=due_date, categories=args.categories ) print(f"创建任务成功!ID: {task.id}") elif args.command == "list": status_map = { "pending": TaskStatus.PENDING, "progress": TaskStatus.IN_PROGRESS, "completed": TaskStatus.COMPLETED } status = status_map.get(args.status) tasks = get_tasks( db, status=status, category=args.category, overdue=args.overdue ) for task in tasks: print(f"{task.id}. [{task.status.name}] {task.title}") if task.due_date: print(f" 截止: {task.due_date.strftime('%Y-%m-%d')}") if task.categories: print(f" 分类: {', '.join(c.name for c in task.categories)}") elif args.command == "status": status_map = { "pending": TaskStatus.PENDING, "progress": TaskStatus.IN_PROGRESS, "completed": TaskStatus.COMPLETED } if update_task_status(db, args.task_id, status_map[args.status]): print("状态更新成功!") else: print("任务不存在!") elif args.command == "delete": if delete_task(db, args.task_id): print("任务删除成功!") else: print("任务不存在!") if __name__ == "__main__": main()

5.2 使用示例

  1. 添加任务:
python main.py add "完成项目报告" -d "需要包含性能测试部分" --due-date 2023-12-31 -c work urgent
  1. 列出所有进行中的工作分类任务:
python main.py list -s progress -c work
  1. 更新任务状态:
python main.py status 2 completed
  1. 删除任务:
python main.py delete 3

6. 高级功能扩展

6.1 数据导出功能

添加导出JSON或CSV格式的功能:

import csv import json from typing import List def export_to_json(db: Session, filepath: str): tasks = db.query(Task).all() data = [ { "id": t.id, "title": t.title, "status": t.status.name, "due_date": t.due_date.isoformat() if t.due_date else None, "categories": [c.name for c in t.categories] } for t in tasks ] with open(filepath, "w") as f: json.dump(data, f, indent=2) def export_to_csv(db: Session, filepath: str): tasks = db.query(Task).all() with open(filepath, "w", newline="") as f: writer = csv.writer(f) writer.writerow(["ID", "Title", "Status", "Due Date", "Categories"]) for t in tasks: writer.writerow([ t.id, t.title, t.status.name, t.due_date.strftime("%Y-%m-%d") if t.due_date else "", ";".join(c.name for c in t.categories) ])

6.2 数据库迁移支持

使用Alembic添加数据库迁移功能:

  1. 安装Alembic:
pip install alembic
  1. 初始化迁移环境:
alembic init migrations
  1. 配置alembic.ini中的数据库连接:
sqlalchemy.url = sqlite:///todo.db
  1. 修改migrations/env.py引入模型:
from models.tasks import Base target_metadata = Base.metadata
  1. 创建首次迁移:
alembic revision --autogenerate -m "initial migration" alembic upgrade head

7. 性能优化与生产准备

7.1 连接池配置

对于生产环境,调整连接池参数提升性能:

from sqlalchemy.pool import QueuePool engine = create_engine( 'sqlite:///todo.db', poolclass=QueuePool, pool_size=5, max_overflow=10, pool_timeout=30, pool_recycle=3600 )

7.2 索引优化

为常用查询字段添加索引:

class Task(Base): # ... 其他字段 ... status = Column(Enum(TaskStatus), default=TaskStatus.PENDING, index=True) due_date = Column(DateTime, index=True) class Category(Base): # ... 其他字段 ... name = Column(String(50), unique=True, nullable=False, index=True)

7.3 事务隔离级别

根据应用需求设置合适的事务隔离级别(以PostgreSQL为例):

engine = create_engine( 'postgresql://user:pass@localhost/dbname', isolation_level="REPEATABLE READ" )

8. 常见问题排查

8.1 会话管理问题

症状:出现"此会话已过期"或"对象已分离"错误

解决方案:

  1. 确保每个请求使用独立会话
  2. 及时commit或rollback
  3. 使用session.refresh()重新加载对象

8.2 性能问题

症状:简单查询响应缓慢

排查步骤:

  1. 启用SQL日志:create_engine(..., echo=True)
  2. 检查是否出现N+1查询
  3. 使用joinedload或selectinload优化关联加载

8.3 数据一致性问题

症状:部分更新未生效或出现脏读

解决方案:

  1. 合理设置事务隔离级别
  2. 对关键操作添加事务锁:
    from sqlalchemy import select, update, func from sqlalchemy.orm import with_for_update stmt = select(Task).where(Task.id==1).with_for_update() task = session.execute(stmt).scalar_one()

9. 项目部署与打包

9.1 使用PyInstaller打包为可执行文件

pip install pyinstaller pyinstaller --onefile --name todo-cli main.py

9.2 制作安装包

创建setup.py:

from setuptools import setup, find_packages setup( name="todo-cli", version="0.1", packages=find_packages(), install_requires=[ 'sqlalchemy>=2.0', ], entry_points={ 'console_scripts': [ 'todo=todo_cli.main:main', ], }, )

安装到系统:

pip install -e .

现在可以直接使用todo命令调用应用。

10. 项目扩展方向

  1. 添加用户系统:扩展User模型,实现多用户支持
  2. 云端同步:通过REST API连接远程数据库
  3. 可视化界面:使用Tkinter或PyQt添加GUI
  4. 提醒功能:集成邮件或系统通知
  5. 数据分析:生成任务完成统计报表

这个项目虽然基础,但涵盖了SQLAlchemy的大部分核心功能。在实际开发中,我发现ORM的真正价值在于它能让你用Python思维操作数据库,而不是纠结于SQL语法。特别是在快速迭代的项目中,良好的模型设计可以显著减少后期维护成本。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询