第十二章:项目实战
项目结构
my_project/
├── src/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py
│ ├── services/
│ │ ├── __init__.py
│ │ └── user_service.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
├── tests/
│ ├── __init__.py
│ └── test_user.py
├── requirements.txt
├── setup.py
├── README.md
└── .gitignore
配置管理
# config.py
import os
from dataclasses import dataclass
@dataclass
class Config:
DEBUG: bool = os.getenv('DEBUG', 'false').lower() == 'true'
DATABASE_URL: str = os.getenv('DATABASE_URL', 'sqlite:///app.db')
SECRET_KEY: str = os.getenv('SECRET_KEY', 'dev-secret-key')
config = Config()
日志配置
# logging_config.py
import logging
def setup_logging():
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log'),
logging.StreamHandler()
]
)
return logging.getLogger(__name__)
logger = setup_logging()
命令行参数
# cli.py
import argparse
def parse_args():
parser = argparse.ArgumentParser(description='My Application')
parser.add_argument('--host', default='0.0.0.0')
parser.add_argument('--port', type=int, default=8000)
parser.add_argument('--debug', action='store_true')
return parser.parse_args()
if __name__ == '__main__':
args = parse_args()
print(f"Running on {args.host}:{args.port}")
单元测试
# tests/test_user.py
import pytest
from src.models.user import User
class TestUser:
def test_create_user(self):
user = User(name="Alice", email="alice@example.com")
assert user.name == "Alice"
assert user.email == "alice@example.com"
def test_invalid_email(self):
with pytest.raises(ValueError):
User(name="Alice", email="invalid-email")
# 运行测试
# pytest tests/ -v
打包发布
# setup.py
from setuptools import setup, find_packages
setup(
name='my_package',
version='1.0.0',
packages=find_packages(),
install_requires=[
'requests>=2.28.0',
'pydantic>=2.0.0',
],
entry_points={
'console_scripts': [
'myapp=src.main:main',
],
},
)
# 构建
# python -m build
# pip install dist/my_package-1.0.0.tar.gz
完整示例:CLI 待办事项应用
# main.py
import json
from pathlib import Path
from dataclasses import dataclass, asdict
from typing import List
from datetime import datetime
@dataclass
class Todo:
id: int
title: str
done: bool = False
created_at: str = ""
def __post_init__(self):
if not self.created_at:
self.created_at = datetime.now().isoformat()
class TodoApp:
def __init__(self, data_file: str = "todos.json"):
self.data_file = Path(data_file)
self.todos: List[Todo] = self._load()
def _load(self) -> List[Todo]:
if self.data_file.exists():
with open(self.data_file) as f:
return [Todo(**t) for t in json.load(f)]
return []
def _save(self):
with open(self.data_file, 'w') as f:
json.dump([asdict(t) for t in self.todos], f, indent=2)
def add(self, title: str):
todo = Todo(id=len(self.todos) + 1, title=title)
self.todos.append(todo)
self._save()
print(f"Added: {title}")
def list(self):
for todo in self.todos:
status = "✓" if todo.done else " "
print(f"[{status}] {todo.id}. {todo.title}")
def done(self, todo_id: int):
for todo in self.todos:
if todo.id == todo_id:
todo.done = True
self._save()
print(f"Completed: {todo.title}")
return
print(f"Todo {todo_id} not found")
if __name__ == '__main__':
import sys
app = TodoApp()
if len(sys.argv) < 2:
app.list()
elif sys.argv[1] == 'add':
app.add(' '.join(sys.argv[2:]))
elif sys.argv[1] == 'done':
app.done(int(sys.argv[2]))