Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    majiayu000

    python-project

    majiayu000/python-project
    Coding
    6
    1 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Modern Python project architecture guide for 2025. Use when creating Python projects (APIs, CLI, data pipelines). Covers uv, Ruff, Pydantic, FastAPI, and async patterns.

    SKILL.md

    Python Project Architecture

    Core Principles

    • Type hints everywhere — Pydantic for runtime, mypy for static
    • uv for everything — Package management, virtualenv, Python version
    • Ruff only — Replace Flake8 + Black + isort with single tool
    • src layout — All code under src/ directory
    • pyproject.toml only — No setup.py, no requirements.txt
    • Async all the way — Once async, stay async through call chain
    • No backwards compatibility — Delete, don't deprecate. Change directly
    • LiteLLM for LLM APIs — Use LiteLLM proxy for all LLM integrations

    No Backwards Compatibility

    Delete unused code. Change directly. No compatibility layers.

    # ❌ BAD: Deprecated decorator kept around
    import warnings
    
    def old_function():
        warnings.warn("Use new_function instead", DeprecationWarning)
        return new_function()
    
    # ❌ BAD: Alias for renamed functions
    new_name = old_name  # "for backwards compatibility"
    
    # ❌ BAD: Unused parameters with underscore
    def process(_legacy_param, data):
        ...
    
    # ❌ BAD: Version checking for old behavior
    if version < "2.0":
        # old behavior
        ...
    
    # ✅ GOOD: Just delete and update all usages
    def new_function():
        ...
    # Then: Find & replace all old_function → new_function
    
    # ✅ GOOD: Remove unused parameters entirely
    def process(data):
        ...
    

    LiteLLM for LLM APIs

    Use LiteLLM proxy. Don't call provider APIs directly.

    # src/myapp/llm.py
    from openai import AsyncOpenAI
    
    from myapp.config import settings
    
    # Connect to LiteLLM proxy using OpenAI SDK
    client = AsyncOpenAI(
        base_url=settings.litellm_url,  # "http://localhost:4000"
        api_key=settings.litellm_api_key,
    )
    
    
    async def complete(prompt: str, model: str = "gpt-4o") -> str:
        """Call any LLM through LiteLLM proxy."""
        response = await client.chat.completions.create(
            model=model,  # "gpt-4o", "claude-3-opus", "gemini-pro", etc.
            messages=[{"role": "user", "content": prompt}],
        )
        return response.choices[0].message.content or ""
    

    Quick Start

    1. Initialize Project

    # Install uv (if not installed)
    curl -LsSf https://astral.sh/uv/install.sh | sh
    
    # Create new project
    uv init myapp
    cd myapp
    
    # Set Python version
    echo "3.12" > .python-version
    
    # Add dependencies
    uv add fastapi uvicorn pydantic sqlalchemy httpx
    uv add --dev pytest pytest-asyncio ruff mypy
    

    2. Apply Tech Stack

    Layer Recommendation
    Package Manager uv
    Linting + Format Ruff
    Type Checking mypy
    Validation Pydantic v2
    Web Framework FastAPI
    Database SQLAlchemy 2.0 + asyncpg
    HTTP Client httpx
    Testing pytest + pytest-asyncio
    Logging structlog

    Version Strategy

    Always use latest. Never pin in templates.

    [project]
    dependencies = [
        "fastapi",      # uv resolves to latest
        "pydantic",
        "sqlalchemy",
    ]
    
    • uv add fetches latest compatible versions
    • uv.lock ensures reproducible builds
    • uv sync installs exact locked versions

    3. Use Standard Structure (src layout)

    myapp/
    ├── pyproject.toml         # Single config file
    ├── uv.lock                # Lock file (commit this)
    ├── .python-version        # Python version for uv
    ├── src/
    │   └── myapp/
    │       ├── __init__.py
    │       ├── __main__.py    # Entry point
    │       ├── main.py        # FastAPI app
    │       ├── config.py      # Pydantic Settings
    │       ├── models/        # Pydantic models
    │       │   ├── __init__.py
    │       │   └── user.py
    │       ├── services/      # Business logic
    │       │   ├── __init__.py
    │       │   └── user.py
    │       ├── repositories/  # Data access
    │       │   ├── __init__.py
    │       │   └── user.py
    │       ├── api/           # HTTP layer
    │       │   ├── __init__.py
    │       │   ├── deps.py    # Dependencies
    │       │   └── routes/
    │       │       ├── __init__.py
    │       │       └── user.py
    │       └── core/          # Shared utilities
    │           ├── __init__.py
    │           ├── exceptions.py
    │           └── logging.py
    ├── tests/
    │   ├── __init__.py
    │   ├── conftest.py        # Fixtures
    │   └── test_user.py
    └── Makefile
    

    Architecture Layers

    main.py — FastAPI Application

    # src/myapp/main.py
    from contextlib import asynccontextmanager
    
    from fastapi import FastAPI
    
    from myapp.api.routes import router
    from myapp.config import settings
    from myapp.core.logging import setup_logging
    from myapp.db import engine
    
    
    @asynccontextmanager
    async def lifespan(app: FastAPI):
        # Startup
        setup_logging()
        yield
        # Shutdown
        await engine.dispose()
    
    
    app = FastAPI(
        title=settings.app_name,
        lifespan=lifespan,
    )
    
    app.include_router(router, prefix="/api/v1")
    
    
    @app.get("/health")
    async def health():
        return {"status": "ok"}
    

    config.py — Pydantic Settings

    # src/myapp/config.py
    from pydantic_settings import BaseSettings, SettingsConfigDict
    
    
    class Settings(BaseSettings):
        model_config = SettingsConfigDict(
            env_file=".env",
            env_file_encoding="utf-8",
        )
    
        app_name: str = "myapp"
        debug: bool = False
    
        # Database
        database_url: str = "postgresql+asyncpg://localhost/myapp"
    
        # LiteLLM
        litellm_url: str = "http://localhost:4000"
        litellm_api_key: str = ""
    
    
    settings = Settings()
    

    models/ — Pydantic Models

    # src/myapp/models/user.py
    from datetime import datetime
    from uuid import UUID
    
    from pydantic import BaseModel, EmailStr, Field
    
    
    class UserBase(BaseModel):
        email: EmailStr
        name: str = Field(min_length=2, max_length=100)
    
    
    class UserCreate(UserBase):
        pass
    
    
    class UserUpdate(BaseModel):
        email: EmailStr | None = None
        name: str | None = Field(default=None, min_length=2, max_length=100)
    
    
    class User(UserBase):
        id: UUID
        created_at: datetime
        updated_at: datetime
    
        model_config = {"from_attributes": True}
    

    services/ — Business Logic

    # src/myapp/services/user.py
    from uuid import UUID
    
    from myapp.core.exceptions import NotFoundError, ConflictError
    from myapp.models.user import User, UserCreate, UserUpdate
    from myapp.repositories.user import UserRepository
    
    
    class UserService:
        def __init__(self, repo: UserRepository):
            self.repo = repo
    
        async def get(self, id: UUID) -> User:
            user = await self.repo.get(id)
            if not user:
                raise NotFoundError("user", str(id))
            return user
    
        async def create(self, data: UserCreate) -> User:
            existing = await self.repo.get_by_email(data.email)
            if existing:
                raise ConflictError("email already exists")
            return await self.repo.create(data)
    
        async def update(self, id: UUID, data: UserUpdate) -> User:
            user = await self.get(id)
            return await self.repo.update(user, data)
    
        async def delete(self, id: UUID) -> None:
            user = await self.get(id)
            await self.repo.delete(user)
    

    api/routes/ — HTTP Handlers

    # src/myapp/api/routes/user.py
    from uuid import UUID
    
    from fastapi import APIRouter, Depends, status
    
    from myapp.api.deps import get_user_service
    from myapp.models.user import User, UserCreate, UserUpdate
    from myapp.services.user import UserService
    
    router = APIRouter(prefix="/users", tags=["users"])
    
    
    @router.get("/{id}", response_model=User)
    async def get_user(
        id: UUID,
        service: UserService = Depends(get_user_service),
    ):
        return await service.get(id)
    
    
    @router.post("", response_model=User, status_code=status.HTTP_201_CREATED)
    async def create_user(
        data: UserCreate,
        service: UserService = Depends(get_user_service),
    ):
        return await service.create(data)
    
    
    @router.patch("/{id}", response_model=User)
    async def update_user(
        id: UUID,
        data: UserUpdate,
        service: UserService = Depends(get_user_service),
    ):
        return await service.update(id, data)
    
    
    @router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
    async def delete_user(
        id: UUID,
        service: UserService = Depends(get_user_service),
    ):
        await service.delete(id)
    

    core/exceptions.py — Custom Exceptions

    # src/myapp/core/exceptions.py
    from fastapi import HTTPException, status
    
    
    class AppError(Exception):
        """Base application error."""
    
        def __init__(self, message: str, code: str):
            self.message = message
            self.code = code
            super().__init__(message)
    
    
    class NotFoundError(AppError):
        def __init__(self, resource: str, id: str):
            super().__init__(f"{resource} not found: {id}", "NOT_FOUND")
    
    
    class ConflictError(AppError):
        def __init__(self, message: str):
            super().__init__(message, "CONFLICT")
    
    
    class ValidationError(AppError):
        def __init__(self, message: str):
            super().__init__(message, "VALIDATION_ERROR")
    
    
    # FastAPI exception handler
    def app_error_to_http(error: AppError) -> HTTPException:
        status_map = {
            "NOT_FOUND": status.HTTP_404_NOT_FOUND,
            "CONFLICT": status.HTTP_409_CONFLICT,
            "VALIDATION_ERROR": status.HTTP_400_BAD_REQUEST,
        }
        return HTTPException(
            status_code=status_map.get(error.code, status.HTTP_500_INTERNAL_SERVER_ERROR),
            detail={"message": error.message, "code": error.code},
        )
    

    pyproject.toml

    [project]
    name = "myapp"
    version = "0.1.0"
    description = "My application"
    requires-python = ">=3.12"
    dependencies = [
        "fastapi",
        "uvicorn[standard]",
        "pydantic",
        "pydantic-settings",
        "sqlalchemy[asyncio]",
        "asyncpg",
        "httpx",
        "structlog",
    ]
    
    [tool.uv]
    dev-dependencies = [
        "pytest",
        "pytest-asyncio",
        "pytest-cov",
        "ruff",
        "mypy",
    ]
    
    [tool.ruff]
    line-length = 100
    target-version = "py312"
    
    [tool.ruff.lint]
    select = [
        "E",   # pycodestyle errors
        "F",   # pyflakes
        "I",   # isort
        "UP",  # pyupgrade
        "B",   # flake8-bugbear
        "SIM", # flake8-simplify
    ]
    
    [tool.ruff.lint.isort]
    known-first-party = ["myapp"]
    
    [tool.mypy]
    strict = true
    python_version = "3.12"
    
    [tool.pytest.ini_options]
    asyncio_mode = "auto"
    testpaths = ["tests"]
    

    Testing

    # tests/conftest.py
    import pytest
    from httpx import ASGITransport, AsyncClient
    
    from myapp.main import app
    
    
    @pytest.fixture
    async def client():
        async with AsyncClient(
            transport=ASGITransport(app=app),
            base_url="http://test",
        ) as client:
            yield client
    
    
    # tests/test_user.py
    import pytest
    
    
    @pytest.mark.asyncio
    async def test_create_user(client):
        response = await client.post(
            "/api/v1/users",
            json={"email": "test@example.com", "name": "Test User"},
        )
        assert response.status_code == 201
        data = response.json()
        assert data["email"] == "test@example.com"
    
    
    @pytest.mark.asyncio
    async def test_get_user_not_found(client):
        response = await client.get("/api/v1/users/00000000-0000-0000-0000-000000000000")
        assert response.status_code == 404
    

    Makefile

    .PHONY: dev test lint fmt check clean
    
    # Run development server
    dev:
    	uv run uvicorn myapp.main:app --reload
    
    # Run tests
    test:
    	uv run pytest
    
    # Run tests with coverage
    test-cov:
    	uv run pytest --cov=myapp --cov-report=html
    
    # Lint code
    lint:
    	uv run ruff check src tests
    
    # Format code
    fmt:
    	uv run ruff format src tests
    	uv run ruff check --fix src tests
    
    # Type check
    typecheck:
    	uv run mypy src
    
    # Run all checks
    check: fmt lint typecheck test
    	@echo "All checks passed!"
    
    # Clean
    clean:
    	rm -rf .pytest_cache .mypy_cache .ruff_cache htmlcov .coverage
    	find . -type d -name __pycache__ -exec rm -rf {} +
    
    # Sync dependencies
    sync:
    	uv sync
    
    # Upgrade dependencies
    upgrade:
    	uv lock --upgrade
    	uv sync
    

    Checklist

    ## Project Setup
    - [ ] uv initialized with pyproject.toml
    - [ ] .python-version set (3.12+)
    - [ ] src/ layout structure
    - [ ] Ruff configured
    - [ ] mypy strict mode
    
    ## Architecture
    - [ ] Pydantic models for validation
    - [ ] Services for business logic
    - [ ] Repositories for data access
    - [ ] Custom exceptions
    - [ ] Dependency injection
    
    ## Quality
    - [ ] pytest with pytest-asyncio
    - [ ] Type hints everywhere
    - [ ] Structured logging
    - [ ] Error handling middleware
    
    ## CI
    - [ ] ruff check
    - [ ] ruff format --check
    - [ ] mypy
    - [ ] pytest
    

    See Also

    • reference/architecture.md — Project structure patterns
    • reference/tech-stack.md — Tool comparisons
    • reference/patterns.md — Python design patterns
    Recommended Servers
    Codeinterpreter
    Codeinterpreter
    Vercel Grep
    Vercel Grep
    Svelte
    Svelte
    Repository
    majiayu000/claude-arsenal
    Files