🎯

python-project

🎯Skill

from majiayu000/claude-arsenal

VibeIndex|
What it does

Guides creating modern Python projects with type hints, uv, Ruff, async patterns, and LiteLLM integration for 2025.

πŸ“¦

Part of

majiayu000/claude-arsenal(24 items)

python-project

Installation

Install ScriptRun install script
curl -LsSf https://astral.sh/uv/install.sh | sh
πŸ“– Extracted from docs: majiayu000/claude-arsenal
6Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

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

Overview

# 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.

```python

# ❌ 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.

```python

# 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

```bash

# 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.

```toml

[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

```python

# 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

```python

# 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

```python

# 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

```python

# 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

```python

# 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

```python

# 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

```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

```python

# 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

```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

```markdown

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](reference/architecture.md) β€” Project structure patterns
  • [reference/tech-stack.md](reference/tech-stack.md) β€” Tool comparisons
  • [reference/patterns.md](reference/patterns.md) β€” Python design patterns