跳转至

第十二章:项目实战

项目结构

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]))