使用Python Dependency Injector构建CLI电影查询应用教程
前言
在现代软件开发中,依赖注入(Dependency Injection, DI)是一种重要的设计模式,它可以帮助我们编写更可测试、更松耦合的代码。本教程将展示如何使用Python Dependency Injector库构建一个命令行界面(CLI)的电影查询应用,通过实践理解依赖注入的核心概念和应用方式。
项目概述
我们将构建一个名为Movie Lister的CLI应用,它具有以下功能:
- 从数据库中查询电影信息
- 支持两种数据库格式:CSV和SQLite
- 能够按导演姓名和发行年份筛选电影
- 采用清晰的架构设计,便于扩展新的数据库格式
环境准备
首先需要创建项目环境:
mkdir movie-lister-tutorial
cd movie-lister-tutorial
python3 -m venv venv
source venv/bin/activate
安装所需依赖包:
pip install dependency-injector pyyaml pytest pytest-cov
项目结构
创建以下项目结构:
./
├── data/
│ └── fixtures.py
├── movies/
│ ├── __init__.py
│ ├── __main__.py
│ └── containers.py
├── config.yml
└── requirements.txt
数据准备
在data/fixtures.py
中创建示例数据:
import csv
import sqlite3
import pathlib
SAMPLE_DATA = [
("The Hunger Games: Mockingjay - Part 2", 2015, "Francis Lawrence"),
("Rogue One: A Star Wars Story", 2016, "Gareth Edwards"),
("The Jungle Book", 2016, "Jon Favreau"),
]
def create_csv(movies_data, path):
# CSV创建逻辑
def create_sqlite(movies_data, path):
# SQLite创建逻辑
def main():
create_csv(SAMPLE_DATA, "data/movies.csv")
create_sqlite(SAMPLE_DATA, "data/movies.db")
运行后会生成CSV和SQLite格式的数据库文件。
核心组件实现
1. 电影实体类
在movies/entities.py
中定义Movie类:
class Movie:
def __init__(self, title: str, year: int, director: str):
self.title = str(title)
self.year = int(year)
self.director = str(director)
2. 电影查找器接口
在movies/finders.py
中定义基础查找器:
from typing import Callable, List
class MovieFinder:
def __init__(self, movie_factory: Callable[..., Movie]):
self._movie_factory = movie_factory
def find_all(self) -> List[Movie]:
raise NotImplementedError()
3. CSV查找器实现
import csv
class CsvMovieFinder(MovieFinder):
def __init__(self, movie_factory, path, delimiter):
self._csv_file_path = path
self._delimiter = delimiter
super().__init__(movie_factory)
def find_all(self):
with open(self._csv_file_path) as csv_file:
return [self._movie_factory(*row) for row in csv.reader(csv_file)]
4. 电影列表器
在movies/listers.py
中实现查询逻辑:
class MovieLister:
def __init__(self, movie_finder):
self._movie_finder = movie_finder
def movies_directed_by(self, director):
return [m for m in self._movie_finder.find_all() if m.director == director]
def movies_released_in(self, year):
return [m for m in self._movie_finder.find_all() if m.year == year]
依赖注入容器
在movies/containers.py
中配置依赖关系:
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
config = providers.Configuration(yaml_files=["config.yml"])
movie = providers.Factory(entities.Movie)
csv_finder = providers.Singleton(
finders.CsvMovieFinder,
movie_factory=movie.provider,
path=config.finder.csv.path,
delimiter=config.finder.csv.delimiter,
)
lister = providers.Factory(
listers.MovieLister,
movie_finder=csv_finder,
)
主程序入口
在__main__.py
中实现应用入口:
from dependency_injector.wiring import Provide, inject
@inject
def main(lister: MovieLister = Provide[Container.lister]):
print("Francis Lawrence movies:")
for movie in lister.movies_directed_by("Francis Lawrence"):
print("\t-", movie)
if __name__ == "__main__":
container = Container()
container.wire(modules=[__name__])
main()
扩展SQLite支持
添加SQLite查找器实现:
class SqliteMovieFinder(MovieFinder):
def __init__(self, movie_factory, path):
self._database = sqlite3.connect(path)
super().__init__(movie_factory)
def find_all(self):
with self._database as db:
return [self._movie_factory(*row) for row in db.execute("SELECT * FROM movies")]
更新容器配置:
sqlite_finder = providers.Singleton(
finders.SqliteMovieFinder,
movie_factory=movie.provider,
path=config.finder.sqlite.path,
)
lister = providers.Factory(
listers.MovieLister,
movie_finder=sqlite_finder, # 切换为SQLite查找器
)
总结
通过本教程,我们实现了一个完整的CLI电影查询应用,并展示了:
- 如何使用Python Dependency Injector管理依赖
- 如何通过接口隔离不同实现
- 如何通过配置灵活切换组件
- 依赖注入如何提高代码的可测试性和可维护性
这种架构设计使得添加新的数据库格式变得非常简单,只需实现新的Finder类并在容器中配置即可,无需修改其他业务逻辑代码。这正是依赖注入模式的优势所在。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考