diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index a90f4e5..8826781 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -33,6 +33,7 @@
"./agents/go-build-resolver.md",
"./agents/go-reviewer.md",
"./agents/planner.md",
+ "./agents/python-reviewer.md",
"./agents/refactor-cleaner.md",
"./agents/security-reviewer.md",
"./agents/tdd-guide.md"
diff --git a/README.md b/README.md
index 2c1970e..8f2b69f 100644
--- a/README.md
+++ b/README.md
@@ -9,10 +9,17 @@


-
- English |
- 简体中文
-
+---
+
+
+
+**🌐 Language / 语言 / 語言**
+
+[**English**](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md)
+
+
+
+---
**The complete collection of Claude Code configs from an Anthropic hackathon winner.**
@@ -54,7 +61,47 @@ This repo is the raw code only. The guides explain everything.
---
-## Cross-Platform Support
+## 🚀 Quick Start
+
+Get up and running in under 2 minutes:
+
+### Step 1: Install the Plugin
+
+```bash
+# Add marketplace
+/plugin marketplace add affaan-m/everything-claude-code
+
+# Install plugin
+/plugin install everything-claude-code@everything-claude-code
+```
+
+### Step 2: Install Rules (Required)
+
+> ⚠️ **Important:** Claude Code plugins cannot distribute `rules` automatically. Install them manually:
+
+```bash
+# Clone the repo first
+git clone https://github.com/affaan-m/everything-claude-code.git
+
+# Copy rules (applies to all projects)
+cp -r everything-claude-code/rules/* ~/.claude/rules/
+```
+
+### Step 3: Start Using
+
+```bash
+# Try a command
+/plan "Add user authentication"
+
+# Check available commands
+/plugin list everything-claude-code@everything-claude-code
+```
+
+✨ **That's it!** You now have access to 15+ agents, 30+ skills, and 20+ commands.
+
+---
+
+## 🌐 Cross-Platform Support
This plugin now fully supports **Windows, macOS, and Linux**. All hooks and scripts have been rewritten in Node.js for maximum compatibility.
@@ -89,7 +136,7 @@ Or use the `/setup-pm` command in Claude Code.
---
-## What's Inside
+## 📦 What's Inside
This repo is a **Claude Code plugin** - install it directly or copy components manually.
@@ -194,7 +241,7 @@ everything-claude-code/
---
-## Ecosystem Tools
+## 🛠️ Ecosystem Tools
### Skill Creator
@@ -229,7 +276,7 @@ Both options create:
- **Instinct collections** - For continuous-learning-v2
- **Pattern extraction** - Learns from your commit history
-### Continuous Learning v2
+### 🧠 Continuous Learning v2
The instinct-based learning system automatically learns your patterns:
@@ -244,7 +291,7 @@ See `skills/continuous-learning-v2/` for full documentation.
---
-## Requirements
+## 📋 Requirements
### Claude Code CLI Version
@@ -271,7 +318,7 @@ Duplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded fil
---
-## Installation
+## 📥 Installation
### Option 1: Install as Plugin (Recommended)
@@ -321,7 +368,7 @@ This gives you instant access to all commands, agents, skills, and hooks.
---
-### Option 2: Manual Installation
+### 🔧 Option 2: Manual Installation
If you prefer manual control over what's installed:
@@ -354,7 +401,7 @@ Copy desired MCP servers from `mcp-configs/mcp-servers.json` to your `~/.claude.
---
-## Key Concepts
+## 🎯 Key Concepts
### Agents
@@ -412,7 +459,7 @@ Rules are always-follow guidelines. Keep them modular:
---
-## Running Tests
+## 🧪 Running Tests
The plugin includes a comprehensive test suite:
@@ -428,7 +475,7 @@ node tests/hooks/hooks.test.js
---
-## Contributing
+## 🤝 Contributing
**Contributions are welcome and encouraged.**
@@ -450,7 +497,7 @@ Please contribute! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
---
-## Background
+## 📖 Background
I've been using Claude Code since the experimental rollout. Won the Anthropic x Forum Ventures hackathon in Sep 2025 building [zenith.chat](https://zenith.chat) with [@DRodriguezFX](https://x.com/DRodriguezFX) - entirely using Claude Code.
@@ -458,7 +505,7 @@ These configs are battle-tested across multiple production applications.
---
-## Important Notes
+## ⚠️ Important Notes
### Context Window Management
@@ -481,13 +528,13 @@ These configs work for my workflow. You should:
---
-## Star History
+## 🌟 Star History
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
---
-## Links
+## 🔗 Links
- **Shorthand Guide (Start Here):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795)
- **Longform Guide (Advanced):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
@@ -496,7 +543,7 @@ These configs work for my workflow. You should:
---
-## License
+## 📄 License
MIT - Use freely, modify as needed, contribute back if you can.
diff --git a/README.zh-CN.md b/README.zh-CN.md
index a0b5ee9..1499d54 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -7,10 +7,17 @@


-
- English |
- 简体中文
-
+---
+
+
+
+**🌐 Language / 语言 / 語言**
+
+[**English**](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md)
+
+
+
+---
**来自 Anthropic 黑客马拉松获胜者的完整 Claude Code 配置集合。**
@@ -52,7 +59,47 @@
---
-## 跨平台支持
+## 🚀 快速开始
+
+在 2 分钟内快速上手:
+
+### 第一步:安装插件
+
+```bash
+# 添加市场
+/plugin marketplace add affaan-m/everything-claude-code
+
+# 安装插件
+/plugin install everything-claude-code@everything-claude-code
+```
+
+### 第二步:安装规则(必需)
+
+> ⚠️ **重要提示:** Claude Code 插件无法自动分发 `rules`,需要手动安装:
+
+```bash
+# 首先克隆仓库
+git clone https://github.com/affaan-m/everything-claude-code.git
+
+# 复制规则(应用于所有项目)
+cp -r everything-claude-code/rules/* ~/.claude/rules/
+```
+
+### 第三步:开始使用
+
+```bash
+# 尝试一个命令
+/plan "添加用户认证"
+
+# 查看可用命令
+/plugin list everything-claude-code@everything-claude-code
+```
+
+✨ **完成!** 你现在可以使用 15+ 代理、30+ 技能和 20+ 命令。
+
+---
+
+## 🌐 跨平台支持
此插件现在完全支持 **Windows、macOS 和 Linux**。所有钩子和脚本都已用 Node.js 重写,以实现最大的兼容性。
@@ -87,7 +134,7 @@ node scripts/setup-package-manager.js --detect
---
-## 里面有什么
+## 📦 里面有什么
这个仓库是一个 **Claude Code 插件** - 直接安装或手动复制组件。
@@ -192,7 +239,7 @@ everything-claude-code/
---
-## 生态系统工具
+## 🛠️ 生态系统工具
### 技能创建器
@@ -227,7 +274,7 @@ everything-claude-code/
- **直觉集合** - 用于 continuous-learning-v2
- **模式提取** - 从你的提交历史中学习
-### 持续学习 v2
+### 🧠 持续学习 v2
基于直觉的学习系统自动学习你的模式:
@@ -242,7 +289,7 @@ everything-claude-code/
---
-## 安装
+## 📥 安装
### 选项 1:作为插件安装(推荐)
@@ -292,7 +339,7 @@ everything-claude-code/
---
-### 选项 2:手动安装
+### 🔧 选项 2:手动安装
如果你希望对安装的内容进行手动控制:
@@ -325,7 +372,7 @@ cp -r everything-claude-code/skills/* ~/.claude/skills/
---
-## 关键概念
+## 🎯 关键概念
### 代理
@@ -383,7 +430,7 @@ model: opus
---
-## 运行测试
+## 🧪 运行测试
插件包含一个全面的测试套件:
@@ -399,7 +446,7 @@ node tests/hooks/hooks.test.js
---
-## 贡献
+## 🤝 贡献
**欢迎并鼓励贡献。**
@@ -421,7 +468,7 @@ node tests/hooks/hooks.test.js
---
-## 背景
+## 📖 背景
自实验性推出以来,我一直在使用 Claude Code。2025 年 9 月,与 [@DRodriguezFX](https://x.com/DRodriguezFX) 一起使用 Claude Code 构建 [zenith.chat](https://zenith.chat),赢得了 Anthropic x Forum Ventures 黑客马拉松。
@@ -429,7 +476,7 @@ node tests/hooks/hooks.test.js
---
-## 重要说明
+## ⚠️ 重要说明
### 上下文窗口管理
@@ -452,13 +499,13 @@ node tests/hooks/hooks.test.js
---
-## Star 历史
+## 🌟 Star 历史
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
---
-## 链接
+## 🔗 链接
- **精简指南(从这里开始):** [The Shorthand Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2012378465664745795)
- **详细指南(高级):** [The Longform Guide to Everything Claude Code](https://x.com/affaanmustafa/status/2014040193557471352)
@@ -467,7 +514,7 @@ node tests/hooks/hooks.test.js
---
-## 许可证
+## 📄 许可证
MIT - 自由使用,根据需要修改,如果可以请回馈。
diff --git a/agents/python-reviewer.md b/agents/python-reviewer.md
new file mode 100644
index 0000000..47e5c58
--- /dev/null
+++ b/agents/python-reviewer.md
@@ -0,0 +1,469 @@
+---
+name: python-reviewer
+description: Expert Python code reviewer specializing in PEP 8 compliance, Pythonic idioms, type hints, security, and performance. Use for all Python code changes. MUST BE USED for Python projects.
+tools: ["Read", "Grep", "Glob", "Bash"]
+model: opus
+---
+
+You are a senior Python code reviewer ensuring high standards of Pythonic code and best practices.
+
+When invoked:
+1. Run `git diff -- '*.py'` to see recent Python file changes
+2. Run static analysis tools if available (ruff, mypy, pylint, black --check)
+3. Focus on modified `.py` files
+4. Begin review immediately
+
+## Security Checks (CRITICAL)
+
+- **SQL Injection**: String concatenation in database queries
+ ```python
+ # Bad
+ cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
+ # Good
+ cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
+ ```
+
+- **Command Injection**: Unvalidated input in subprocess/os.system
+ ```python
+ # Bad
+ os.system(f"curl {url}")
+ # Good
+ subprocess.run(["curl", url], check=True)
+ ```
+
+- **Path Traversal**: User-controlled file paths
+ ```python
+ # Bad
+ open(os.path.join(base_dir, user_path))
+ # Good
+ clean_path = os.path.normpath(user_path)
+ if clean_path.startswith(".."):
+ raise ValueError("Invalid path")
+ safe_path = os.path.join(base_dir, clean_path)
+ ```
+
+- **Eval/Exec Abuse**: Using eval/exec with user input
+- **Pickle Unsafe Deserialization**: Loading untrusted pickle data
+- **Hardcoded Secrets**: API keys, passwords in source
+- **Weak Crypto**: Use of MD5/SHA1 for security purposes
+- **YAML Unsafe Load**: Using yaml.load without Loader
+
+## Error Handling (CRITICAL)
+
+- **Bare Except Clauses**: Catching all exceptions
+ ```python
+ # Bad
+ try:
+ process()
+ except:
+ pass
+
+ # Good
+ try:
+ process()
+ except ValueError as e:
+ logger.error(f"Invalid value: {e}")
+ ```
+
+- **Swallowing Exceptions**: Silent failures
+- **Exception Instead of Flow Control**: Using exceptions for normal control flow
+- **Missing Finally**: Resources not cleaned up
+ ```python
+ # Bad
+ f = open("file.txt")
+ data = f.read()
+ # If exception occurs, file never closes
+
+ # Good
+ with open("file.txt") as f:
+ data = f.read()
+ # or
+ f = open("file.txt")
+ try:
+ data = f.read()
+ finally:
+ f.close()
+ ```
+
+## Type Hints (HIGH)
+
+- **Missing Type Hints**: Public functions without type annotations
+ ```python
+ # Bad
+ def process_user(user_id):
+ return get_user(user_id)
+
+ # Good
+ from typing import Optional
+
+ def process_user(user_id: str) -> Optional[User]:
+ return get_user(user_id)
+ ```
+
+- **Using Any Instead of Specific Types**
+ ```python
+ # Bad
+ from typing import Any
+
+ def process(data: Any) -> Any:
+ return data
+
+ # Good
+ from typing import TypeVar
+
+ T = TypeVar('T')
+
+ def process(data: T) -> T:
+ return data
+ ```
+
+- **Incorrect Return Types**: Mismatched annotations
+- **Optional Not Used**: Nullable parameters not marked as Optional
+
+## Pythonic Code (HIGH)
+
+- **Not Using Context Managers**: Manual resource management
+ ```python
+ # Bad
+ f = open("file.txt")
+ try:
+ content = f.read()
+ finally:
+ f.close()
+
+ # Good
+ with open("file.txt") as f:
+ content = f.read()
+ ```
+
+- **C-Style Looping**: Not using comprehensions or iterators
+ ```python
+ # Bad
+ result = []
+ for item in items:
+ if item.active:
+ result.append(item.name)
+
+ # Good
+ result = [item.name for item in items if item.active]
+ ```
+
+- **Checking Types with isinstance**: Using type() instead
+ ```python
+ # Bad
+ if type(obj) == str:
+ process(obj)
+
+ # Good
+ if isinstance(obj, str):
+ process(obj)
+ ```
+
+- **Not Using Enum/Magic Numbers**
+ ```python
+ # Bad
+ if status == 1:
+ process()
+
+ # Good
+ from enum import Enum
+
+ class Status(Enum):
+ ACTIVE = 1
+ INACTIVE = 2
+
+ if status == Status.ACTIVE:
+ process()
+ ```
+
+- **String Concatenation in Loops**: Using + for building strings
+ ```python
+ # Bad
+ result = ""
+ for item in items:
+ result += str(item)
+
+ # Good
+ result = "".join(str(item) for item in items)
+ ```
+
+- **Mutable Default Arguments**: Classic Python pitfall
+ ```python
+ # Bad
+ def process(items=[]):
+ items.append("new")
+ return items
+
+ # Good
+ def process(items=None):
+ if items is None:
+ items = []
+ items.append("new")
+ return items
+ ```
+
+## Code Quality (HIGH)
+
+- **Too Many Parameters**: Functions with >5 parameters
+ ```python
+ # Bad
+ def process_user(name, email, age, address, phone, status):
+ pass
+
+ # Good
+ from dataclasses import dataclass
+
+ @dataclass
+ class UserData:
+ name: str
+ email: str
+ age: int
+ address: str
+ phone: str
+ status: str
+
+ def process_user(data: UserData):
+ pass
+ ```
+
+- **Long Functions**: Functions over 50 lines
+- **Deep Nesting**: More than 4 levels of indentation
+- **God Classes/Modules**: Too many responsibilities
+- **Duplicate Code**: Repeated patterns
+- **Magic Numbers**: Unnamed constants
+ ```python
+ # Bad
+ if len(data) > 512:
+ compress(data)
+
+ # Good
+ MAX_UNCOMPRESSED_SIZE = 512
+
+ if len(data) > MAX_UNCOMPRESSED_SIZE:
+ compress(data)
+ ```
+
+## Concurrency (HIGH)
+
+- **Missing Lock**: Shared state without synchronization
+ ```python
+ # Bad
+ counter = 0
+
+ def increment():
+ global counter
+ counter += 1 # Race condition!
+
+ # Good
+ import threading
+
+ counter = 0
+ lock = threading.Lock()
+
+ def increment():
+ global counter
+ with lock:
+ counter += 1
+ ```
+
+- **Global Interpreter Lock Assumptions**: Assuming thread safety
+- **Async/Await Misuse**: Mixing sync and async code incorrectly
+
+## Performance (MEDIUM)
+
+- **N+1 Queries**: Database queries in loops
+ ```python
+ # Bad
+ for user in users:
+ orders = get_orders(user.id) # N queries!
+
+ # Good
+ user_ids = [u.id for u in users]
+ orders = get_orders_for_users(user_ids) # 1 query
+ ```
+
+- **Inefficient String Operations**
+ ```python
+ # Bad
+ text = "hello"
+ for i in range(1000):
+ text += " world" # O(n²)
+
+ # Good
+ parts = ["hello"]
+ for i in range(1000):
+ parts.append(" world")
+ text = "".join(parts) # O(n)
+ ```
+
+- **List in Boolean Context**: Using len() instead of truthiness
+ ```python
+ # Bad
+ if len(items) > 0:
+ process(items)
+
+ # Good
+ if items:
+ process(items)
+ ```
+
+- **Unnecessary List Creation**: Using list() when not needed
+ ```python
+ # Bad
+ for item in list(dict.keys()):
+ process(item)
+
+ # Good
+ for item in dict:
+ process(item)
+ ```
+
+## Best Practices (MEDIUM)
+
+- **PEP 8 Compliance**: Code formatting violations
+ - Import order (stdlib, third-party, local)
+ - Line length (default 88 for Black, 79 for PEP 8)
+ - Naming conventions (snake_case for functions/variables, PascalCase for classes)
+ - Spacing around operators
+
+- **Docstrings**: Missing or poorly formatted docstrings
+ ```python
+ # Bad
+ def process(data):
+ return data.strip()
+
+ # Good
+ def process(data: str) -> str:
+ """Remove leading and trailing whitespace from input string.
+
+ Args:
+ data: The input string to process.
+
+ Returns:
+ The processed string with whitespace removed.
+ """
+ return data.strip()
+ ```
+
+- **Logging vs Print**: Using print() for logging
+ ```python
+ # Bad
+ print("Error occurred")
+
+ # Good
+ import logging
+ logger = logging.getLogger(__name__)
+ logger.error("Error occurred")
+ ```
+
+- **Relative Imports**: Using relative imports in scripts
+- **Unused Imports**: Dead code
+- **Missing `if __name__ == "__main__"`**: Script entry point not guarded
+
+## Python-Specific Anti-Patterns
+
+- **`from module import *`**: Namespace pollution
+ ```python
+ # Bad
+ from os.path import *
+
+ # Good
+ from os.path import join, exists
+ ```
+
+- **Not Using `with` Statement**: Resource leaks
+- **Silencing Exceptions**: Bare `except: pass`
+- **Comparing to None with ==**
+ ```python
+ # Bad
+ if value == None:
+ process()
+
+ # Good
+ if value is None:
+ process()
+ ```
+
+- **Not Using `isinstance` for Type Checking**: Using type()
+- **Shadowing Built-ins**: Naming variables `list`, `dict`, `str`, etc.
+ ```python
+ # Bad
+ list = [1, 2, 3] # Shadows built-in list type
+
+ # Good
+ items = [1, 2, 3]
+ ```
+
+## Review Output Format
+
+For each issue:
+```text
+[CRITICAL] SQL Injection vulnerability
+File: app/routes/user.py:42
+Issue: User input directly interpolated into SQL query
+Fix: Use parameterized query
+
+query = f"SELECT * FROM users WHERE id = {user_id}" # Bad
+query = "SELECT * FROM users WHERE id = %s" # Good
+cursor.execute(query, (user_id,))
+```
+
+## Diagnostic Commands
+
+Run these checks:
+```bash
+# Type checking
+mypy .
+
+# Linting
+ruff check .
+pylint app/
+
+# Formatting check
+black --check .
+isort --check-only .
+
+# Security scanning
+bandit -r .
+
+# Dependencies audit
+pip-audit
+safety check
+
+# Testing
+pytest --cov=app --cov-report=term-missing
+```
+
+## Approval Criteria
+
+- **Approve**: No CRITICAL or HIGH issues
+- **Warning**: MEDIUM issues only (can merge with caution)
+- **Block**: CRITICAL or HIGH issues found
+
+## Python Version Considerations
+
+- Check `pyproject.toml` or `setup.py` for Python version requirements
+- Note if code uses features from newer Python versions (type hints | 3.5+, f-strings 3.6+, walrus 3.8+, match 3.10+)
+- Flag deprecated standard library modules
+- Ensure type hints are compatible with minimum Python version
+
+## Framework-Specific Checks
+
+### Django
+- **N+1 Queries**: Use `select_related` and `prefetch_related`
+- **Missing migrations**: Model changes without migrations
+- **Raw SQL**: Using `raw()` or `execute()` when ORM could work
+- **Transaction management**: Missing `atomic()` for multi-step operations
+
+### FastAPI/Flask
+- **CORS misconfiguration**: Overly permissive origins
+- **Dependency injection**: Proper use of Depends/injection
+- **Response models**: Missing or incorrect response models
+- **Validation**: Pydantic models for request validation
+
+### Async (FastAPI/aiohttp)
+- **Blocking calls in async functions**: Using sync libraries in async context
+- **Missing await**: Forgetting to await coroutines
+- **Async generators**: Proper async iteration
+
+Review with the mindset: "Would this code pass review at a top Python shop or open-source project?"
diff --git a/commands/python-review.md b/commands/python-review.md
new file mode 100644
index 0000000..ba594b2
--- /dev/null
+++ b/commands/python-review.md
@@ -0,0 +1,297 @@
+---
+description: Comprehensive Python code review for PEP 8 compliance, type hints, security, and Pythonic idioms. Invokes the python-reviewer agent.
+---
+
+# Python Code Review
+
+This command invokes the **python-reviewer** agent for comprehensive Python-specific code review.
+
+## What This Command Does
+
+1. **Identify Python Changes**: Find modified `.py` files via `git diff`
+2. **Run Static Analysis**: Execute `ruff`, `mypy`, `pylint`, `black --check`
+3. **Security Scan**: Check for SQL injection, command injection, unsafe deserialization
+4. **Type Safety Review**: Analyze type hints and mypy errors
+5. **Pythonic Code Check**: Verify code follows PEP 8 and Python best practices
+6. **Generate Report**: Categorize issues by severity
+
+## When to Use
+
+Use `/python-review` when:
+- After writing or modifying Python code
+- Before committing Python changes
+- Reviewing pull requests with Python code
+- Onboarding to a new Python codebase
+- Learning Pythonic patterns and idioms
+
+## Review Categories
+
+### CRITICAL (Must Fix)
+- SQL/Command injection vulnerabilities
+- Unsafe eval/exec usage
+- Pickle unsafe deserialization
+- Hardcoded credentials
+- YAML unsafe load
+- Bare except clauses hiding errors
+
+### HIGH (Should Fix)
+- Missing type hints on public functions
+- Mutable default arguments
+- Swallowing exceptions silently
+- Not using context managers for resources
+- C-style looping instead of comprehensions
+- Using type() instead of isinstance()
+- Race conditions without locks
+
+### MEDIUM (Consider)
+- PEP 8 formatting violations
+- Missing docstrings on public functions
+- Print statements instead of logging
+- Inefficient string operations
+- Magic numbers without named constants
+- Not using f-strings for formatting
+- Unnecessary list creation
+
+## Automated Checks Run
+
+```bash
+# Type checking
+mypy .
+
+# Linting and formatting
+ruff check .
+black --check .
+isort --check-only .
+
+# Security scanning
+bandit -r .
+
+# Dependency audit
+pip-audit
+safety check
+
+# Testing
+pytest --cov=app --cov-report=term-missing
+```
+
+## Example Usage
+
+```text
+User: /python-review
+
+Agent:
+# Python Code Review Report
+
+## Files Reviewed
+- app/routes/user.py (modified)
+- app/services/auth.py (modified)
+
+## Static Analysis Results
+✓ ruff: No issues
+✓ mypy: No errors
+⚠️ black: 2 files need reformatting
+✓ bandit: No security issues
+
+## Issues Found
+
+[CRITICAL] SQL Injection vulnerability
+File: app/routes/user.py:42
+Issue: User input directly interpolated into SQL query
+```python
+query = f"SELECT * FROM users WHERE id = {user_id}" # Bad
+```
+Fix: Use parameterized query
+```python
+query = "SELECT * FROM users WHERE id = %s" # Good
+cursor.execute(query, (user_id,))
+```
+
+[HIGH] Mutable default argument
+File: app/services/auth.py:18
+Issue: Mutable default argument causes shared state
+```python
+def process_items(items=[]): # Bad
+ items.append("new")
+ return items
+```
+Fix: Use None as default
+```python
+def process_items(items=None): # Good
+ if items is None:
+ items = []
+ items.append("new")
+ return items
+```
+
+[MEDIUM] Missing type hints
+File: app/services/auth.py:25
+Issue: Public function without type annotations
+```python
+def get_user(user_id): # Bad
+ return db.find(user_id)
+```
+Fix: Add type hints
+```python
+def get_user(user_id: str) -> Optional[User]: # Good
+ return db.find(user_id)
+```
+
+[MEDIUM] Not using context manager
+File: app/routes/user.py:55
+Issue: File not closed on exception
+```python
+f = open("config.json") # Bad
+data = f.read()
+f.close()
+```
+Fix: Use context manager
+```python
+with open("config.json") as f: # Good
+ data = f.read()
+```
+
+## Summary
+- CRITICAL: 1
+- HIGH: 1
+- MEDIUM: 2
+
+Recommendation: ❌ Block merge until CRITICAL issue is fixed
+
+## Formatting Required
+Run: `black app/routes/user.py app/services/auth.py`
+```
+
+## Approval Criteria
+
+| Status | Condition |
+|--------|-----------|
+| ✅ Approve | No CRITICAL or HIGH issues |
+| ⚠️ Warning | Only MEDIUM issues (merge with caution) |
+| ❌ Block | CRITICAL or HIGH issues found |
+
+## Integration with Other Commands
+
+- Use `/python-test` first to ensure tests pass
+- Use `/code-review` for non-Python specific concerns
+- Use `/python-review` before committing
+- Use `/build-fix` if static analysis tools fail
+
+## Framework-Specific Reviews
+
+### Django Projects
+The reviewer checks for:
+- N+1 query issues (use `select_related` and `prefetch_related`)
+- Missing migrations for model changes
+- Raw SQL usage when ORM could work
+- Missing `transaction.atomic()` for multi-step operations
+
+### FastAPI Projects
+The reviewer checks for:
+- CORS misconfiguration
+- Pydantic models for request validation
+- Response models correctness
+- Proper async/await usage
+- Dependency injection patterns
+
+### Flask Projects
+The reviewer checks for:
+- Context management (app context, request context)
+- Proper error handling
+- Blueprint organization
+- Configuration management
+
+## Related
+
+- Agent: `agents/python-reviewer.md`
+- Skills: `skills/python-patterns/`, `skills/python-testing/`
+
+## Common Fixes
+
+### Add Type Hints
+```python
+# Before
+def calculate(x, y):
+ return x + y
+
+# After
+from typing import Union
+
+def calculate(x: Union[int, float], y: Union[int, float]) -> Union[int, float]:
+ return x + y
+```
+
+### Use Context Managers
+```python
+# Before
+f = open("file.txt")
+data = f.read()
+f.close()
+
+# After
+with open("file.txt") as f:
+ data = f.read()
+```
+
+### Use List Comprehensions
+```python
+# Before
+result = []
+for item in items:
+ if item.active:
+ result.append(item.name)
+
+# After
+result = [item.name for item in items if item.active]
+```
+
+### Fix Mutable Defaults
+```python
+# Before
+def append(value, items=[]):
+ items.append(value)
+ return items
+
+# After
+def append(value, items=None):
+ if items is None:
+ items = []
+ items.append(value)
+ return items
+```
+
+### Use f-strings (Python 3.6+)
+```python
+# Before
+name = "Alice"
+greeting = "Hello, " + name + "!"
+greeting2 = "Hello, {}".format(name)
+
+# After
+greeting = f"Hello, {name}!"
+```
+
+### Fix String Concatenation in Loops
+```python
+# Before
+result = ""
+for item in items:
+ result += str(item)
+
+# After
+result = "".join(str(item) for item in items)
+```
+
+## Python Version Compatibility
+
+The reviewer notes when code uses features from newer Python versions:
+
+| Feature | Minimum Python |
+|---------|----------------|
+| Type hints | 3.5+ |
+| f-strings | 3.6+ |
+| Walrus operator (`:=`) | 3.8+ |
+| Position-only parameters | 3.8+ |
+| Match statements | 3.10+ |
+| Type unions (`x | None`) | 3.10+ |
+
+Ensure your project's `pyproject.toml` or `setup.py` specifies the correct minimum Python version.
diff --git a/docs/zh-TW/README.md b/docs/zh-TW/README.md
index 1ddf66f..ad0adb6 100644
--- a/docs/zh-TW/README.md
+++ b/docs/zh-TW/README.md
@@ -7,6 +7,18 @@


+---
+
+
+
+**🌐 Language / 语言 / 語言**
+
+[**English**](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](README.md)
+
+
+
+---
+
**來自 Anthropic 黑客松冠軍的完整 Claude Code 設定集合。**
經過 10 個月以上密集日常使用、打造真實產品所淬煉出的生產就緒代理程式、技能、鉤子、指令、規則和 MCP 設定。
@@ -47,7 +59,47 @@
---
-## 跨平台支援
+## 🚀 快速開始
+
+在 2 分鐘內快速上手:
+
+### 第一步:安裝外掛程式
+
+```bash
+# 新增市集
+/plugin marketplace add affaan-m/everything-claude-code
+
+# 安裝外掛程式
+/plugin install everything-claude-code@everything-claude-code
+```
+
+### 第二步:安裝規則(必需)
+
+> ⚠️ **重要提示:** Claude Code 外掛程式無法自動分發 `rules`,需要手動安裝:
+
+```bash
+# 首先複製儲存庫
+git clone https://github.com/affaan-m/everything-claude-code.git
+
+# 複製規則(應用於所有專案)
+cp -r everything-claude-code/rules/* ~/.claude/rules/
+```
+
+### 第三步:開始使用
+
+```bash
+# 嘗試一個指令
+/plan "新增使用者認證"
+
+# 查看可用指令
+/plugin list everything-claude-code@everything-claude-code
+```
+
+✨ **完成!** 您現在使用 15+ 代理程式、30+ 技能和 20+ 指令。
+
+---
+
+## 🌐 跨平台支援
此外掛程式現已完整支援 **Windows、macOS 和 Linux**。所有鉤子和腳本已使用 Node.js 重寫以獲得最佳相容性。
@@ -82,7 +134,7 @@ node scripts/setup-package-manager.js --detect
---
-## 內容概覽
+## 📦 內容概覽
本儲存庫是一個 **Claude Code 外掛程式** - 可直接安裝或手動複製元件。
@@ -182,7 +234,7 @@ everything-claude-code/
---
-## 生態系統工具
+## 🛠️ 生態系統工具
### ecc.tools - 技能建立器
@@ -204,7 +256,7 @@ everything-claude-code/
---
-## 安裝
+## 📥 安裝
### 選項 1:以外掛程式安裝(建議)
@@ -240,7 +292,7 @@ everything-claude-code/
---
-### 選項 2:手動安裝
+### 🔧 選項 2:手動安裝
如果您偏好手動控制安裝內容:
@@ -273,7 +325,7 @@ cp -r everything-claude-code/skills/* ~/.claude/skills/
---
-## 核心概念
+## 🎯 核心概念
### 代理程式(Agents)
@@ -331,7 +383,7 @@ You are a senior code reviewer...
---
-## 執行測試
+## 🧪 執行測試
外掛程式包含完整的測試套件:
@@ -347,7 +399,7 @@ node tests/hooks/hooks.test.js
---
-## 貢獻
+## 🤝 貢獻
**歡迎並鼓勵貢獻。**
@@ -369,7 +421,7 @@ node tests/hooks/hooks.test.js
---
-## 背景
+## 📖 背景
我從實驗性推出就開始使用 Claude Code。2025 年 9 月與 [@DRodriguezFX](https://x.com/DRodriguezFX) 一起使用 Claude Code 打造 [zenith.chat](https://zenith.chat),贏得了 Anthropic x Forum Ventures 黑客松。
@@ -377,7 +429,7 @@ node tests/hooks/hooks.test.js
---
-## 重要注意事項
+## ⚠️ 重要注意事項
### 上下文視窗管理
@@ -400,13 +452,13 @@ node tests/hooks/hooks.test.js
---
-## Star 歷史
+## 🌟 Star 歷史
[](https://star-history.com/#affaan-m/everything-claude-code&Date)
---
-## 連結
+## 🔗 連結
- **簡明指南(從這裡開始):** [Everything Claude Code 簡明指南](https://x.com/affaanmustafa/status/2012378465664745795)
- **完整指南(進階):** [Everything Claude Code 完整指南](https://x.com/affaanmustafa/status/2014040193557471352)
@@ -415,7 +467,7 @@ node tests/hooks/hooks.test.js
---
-## 授權
+## 📄 授權
MIT - 自由使用、依需求修改、如可能請回饋貢獻。
diff --git a/skills/django-patterns/SKILL.md b/skills/django-patterns/SKILL.md
new file mode 100644
index 0000000..2db064f
--- /dev/null
+++ b/skills/django-patterns/SKILL.md
@@ -0,0 +1,733 @@
+---
+name: django-patterns
+description: Django architecture patterns, REST API design with DRF, ORM best practices, caching, signals, middleware, and production-grade Django apps.
+---
+
+# Django Development Patterns
+
+Production-grade Django architecture patterns for scalable, maintainable applications.
+
+## When to Activate
+
+- Building Django web applications
+- Designing Django REST Framework APIs
+- Working with Django ORM and models
+- Setting up Django project structure
+- Implementing caching, signals, middleware
+
+## Project Structure
+
+### Recommended Layout
+
+```
+myproject/
+├── config/
+│ ├── __init__.py
+│ ├── settings/
+│ │ ├── __init__.py
+│ │ ├── base.py # Base settings
+│ │ ├── development.py # Dev settings
+│ │ ├── production.py # Production settings
+│ │ └── test.py # Test settings
+│ ├── urls.py
+│ ├── wsgi.py
+│ └── asgi.py
+├── manage.py
+└── apps/
+ ├── __init__.py
+ ├── users/
+ │ ├── __init__.py
+ │ ├── models.py
+ │ ├── views.py
+ │ ├── serializers.py
+ │ ├── urls.py
+ │ ├── permissions.py
+ │ ├── filters.py
+ │ ├── services.py
+ │ └── tests/
+ └── products/
+ └── ...
+```
+
+### Split Settings Pattern
+
+```python
+# config/settings/base.py
+from pathlib import Path
+
+BASE_DIR = Path(__file__).resolve().parent.parent.parent
+
+SECRET_KEY = env('DJANGO_SECRET_KEY')
+DEBUG = False
+ALLOWED_HOSTS = []
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'rest_framework',
+ 'rest_framework.authtoken',
+ 'corsheaders',
+ # Local apps
+ 'apps.users',
+ 'apps.products',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'whitenoise.middleware.WhiteNoiseMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'corsheaders.middleware.CorsMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'config.urls'
+WSGI_APPLICATION = 'config.wsgi.application'
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.postgresql',
+ 'NAME': env('DB_NAME'),
+ 'USER': env('DB_USER'),
+ 'PASSWORD': env('DB_PASSWORD'),
+ 'HOST': env('DB_HOST'),
+ 'PORT': env('DB_PORT', default='5432'),
+ }
+}
+
+# config/settings/development.py
+from .base import *
+
+DEBUG = True
+ALLOWED_HOSTS = ['localhost', '127.0.0.1']
+
+DATABASES['default']['NAME'] = 'myproject_dev'
+
+INSTALLED_APPS += ['debug_toolbar']
+
+MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
+
+EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+
+# config/settings/production.py
+from .base import *
+
+DEBUG = False
+ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
+SECURE_SSL_REDIRECT = True
+SESSION_COOKIE_SECURE = True
+CSRF_COOKIE_SECURE = True
+SECURE_HSTS_SECONDS = 31536000
+SECURE_HSTS_INCLUDE_SUBDOMAINS = True
+SECURE_HSTS_PRELOAD = True
+
+# Logging
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'handlers': {
+ 'file': {
+ 'level': 'WARNING',
+ 'class': 'logging.FileHandler',
+ 'filename': '/var/log/django/django.log',
+ },
+ },
+ 'loggers': {
+ 'django': {
+ 'handlers': ['file'],
+ 'level': 'WARNING',
+ 'propagate': True,
+ },
+ },
+}
+```
+
+## Model Design Patterns
+
+### Model Best Practices
+
+```python
+from django.db import models
+from django.contrib.auth.models import AbstractUser
+from django.core.validators import MinValueValidator, MaxValueValidator
+
+class User(AbstractUser):
+ """Custom user model extending AbstractUser."""
+ email = models.EmailField(unique=True)
+ phone = models.CharField(max_length=20, blank=True)
+ birth_date = models.DateField(null=True, blank=True)
+
+ USERNAME_FIELD = 'email'
+ REQUIRED_FIELDS = ['username']
+
+ class Meta:
+ db_table = 'users'
+ verbose_name = 'user'
+ verbose_name_plural = 'users'
+ ordering = ['-date_joined']
+
+ def __str__(self):
+ return self.email
+
+ def get_full_name(self):
+ return f"{self.first_name} {self.last_name}".strip()
+
+class Product(models.Model):
+ """Product model with proper field configuration."""
+ name = models.CharField(max_length=200)
+ slug = models.SlugField(unique=True, max_length=250)
+ description = models.TextField(blank=True)
+ price = models.DecimalField(
+ max_digits=10,
+ decimal_places=2,
+ validators=[MinValueValidator(0)]
+ )
+ stock = models.PositiveIntegerField(default=0)
+ is_active = models.BooleanField(default=True)
+ category = models.ForeignKey(
+ 'Category',
+ on_delete=models.CASCADE,
+ related_name='products'
+ )
+ tags = models.ManyToManyField('Tag', blank=True, related_name='products')
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ db_table = 'products'
+ ordering = ['-created_at']
+ indexes = [
+ models.Index(fields=['slug']),
+ models.Index(fields=['-created_at']),
+ models.Index(fields=['category', 'is_active']),
+ ]
+ constraints = [
+ models.CheckConstraint(
+ check=models.Q(price__gte=0),
+ name='price_non_negative'
+ )
+ ]
+
+ def __str__(self):
+ return self.name
+
+ def save(self, *args, **kwargs):
+ if not self.slug:
+ self.slug = slugify(self.name)
+ super().save(*args, **kwargs)
+```
+
+### QuerySet Best Practices
+
+```python
+from django.db import models
+
+class ProductQuerySet(models.QuerySet):
+ """Custom QuerySet for Product model."""
+
+ def active(self):
+ """Return only active products."""
+ return self.filter(is_active=True)
+
+ def with_category(self):
+ """Select related category to avoid N+1 queries."""
+ return self.select_related('category')
+
+ def with_tags(self):
+ """Prefetch tags for many-to-many relationship."""
+ return self.prefetch_related('tags')
+
+ def in_stock(self):
+ """Return products with stock > 0."""
+ return self.filter(stock__gt=0)
+
+ def search(self, query):
+ """Search products by name or description."""
+ return self.filter(
+ models.Q(name__icontains=query) |
+ models.Q(description__icontains=query)
+ )
+
+class Product(models.Model):
+ # ... fields ...
+
+ objects = ProductQuerySet.as_manager() # Use custom QuerySet
+
+# Usage
+Product.objects.active().with_category().in_stock()
+```
+
+### Manager Methods
+
+```python
+class ProductManager(models.Manager):
+ """Custom manager for complex queries."""
+
+ def get_or_none(self, **kwargs):
+ """Return object or None instead of DoesNotExist."""
+ try:
+ return self.get(**kwargs)
+ except self.model.DoesNotExist:
+ return None
+
+ def create_with_tags(self, name, price, tag_names):
+ """Create product with associated tags."""
+ product = self.create(name=name, price=price)
+ tags = [Tag.objects.get_or_create(name=name)[0] for name in tag_names]
+ product.tags.set(tags)
+ return product
+
+ def bulk_update_stock(self, product_ids, quantity):
+ """Bulk update stock for multiple products."""
+ return self.filter(id__in=product_ids).update(stock=quantity)
+
+# In model
+class Product(models.Model):
+ # ... fields ...
+ custom = ProductManager()
+```
+
+## Django REST Framework Patterns
+
+### Serializer Patterns
+
+```python
+from rest_framework import serializers
+from django.contrib.auth.password_validation import validate_password
+from .models import Product, User
+
+class ProductSerializer(serializers.ModelSerializer):
+ """Serializer for Product model."""
+
+ category_name = serializers.CharField(source='category.name', read_only=True)
+ average_rating = serializers.FloatField(read_only=True)
+ discount_price = serializers.SerializerMethodField()
+
+ class Meta:
+ model = Product
+ fields = [
+ 'id', 'name', 'slug', 'description', 'price',
+ 'discount_price', 'stock', 'category_name',
+ 'average_rating', 'created_at'
+ ]
+ read_only_fields = ['id', 'slug', 'created_at']
+
+ def get_discount_price(self, obj):
+ """Calculate discount price if applicable."""
+ if hasattr(obj, 'discount') and obj.discount:
+ return obj.price * (1 - obj.discount.percent / 100)
+ return obj.price
+
+ def validate_price(self, value):
+ """Ensure price is non-negative."""
+ if value < 0:
+ raise serializers.ValidationError("Price cannot be negative.")
+ return value
+
+class ProductCreateSerializer(serializers.ModelSerializer):
+ """Serializer for creating products."""
+
+ class Meta:
+ model = Product
+ fields = ['name', 'description', 'price', 'stock', 'category']
+
+ def validate(self, data):
+ """Custom validation for multiple fields."""
+ if data['price'] > 10000 and data['stock'] > 100:
+ raise serializers.ValidationError(
+ "Cannot have high-value products with large stock."
+ )
+ return data
+
+class UserRegistrationSerializer(serializers.ModelSerializer):
+ """Serializer for user registration."""
+
+ password = serializers.CharField(
+ write_only=True,
+ required=True,
+ validators=[validate_password],
+ style={'input_type': 'password'}
+ )
+ password_confirm = serializers.CharField(write_only=True, style={'input_type': 'password'})
+
+ class Meta:
+ model = User
+ fields = ['email', 'username', 'password', 'password_confirm']
+
+ def validate(self, data):
+ """Validate passwords match."""
+ if data['password'] != data['password_confirm']:
+ raise serializers.ValidationError({
+ "password_confirm": "Password fields didn't match."
+ })
+ return data
+
+ def create(self, validated_data):
+ """Create user with hashed password."""
+ validated_data.pop('password_confirm')
+ password = validated_data.pop('password')
+ user = User.objects.create(**validated_data)
+ user.set_password(password)
+ user.save()
+ return user
+```
+
+### ViewSet Patterns
+
+```python
+from rest_framework import viewsets, status, filters
+from rest_framework.decorators import action
+from rest_framework.response import Response
+from rest_framework.permissions import IsAuthenticated, IsAdminUser
+from django_filters.rest_framework import DjangoFilterBackend
+from .models import Product
+from .serializers import ProductSerializer, ProductCreateSerializer
+from .permissions import IsOwnerOrReadOnly
+from .filters import ProductFilter
+from .services import ProductService
+
+class ProductViewSet(viewsets.ModelViewSet):
+ """ViewSet for Product model."""
+
+ queryset = Product.objects.select_related('category').prefetch_related('tags')
+ permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
+ filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
+ filterset_class = ProductFilter
+ search_fields = ['name', 'description']
+ ordering_fields = ['price', 'created_at', 'name']
+ ordering = ['-created_at']
+
+ def get_serializer_class(self):
+ """Return appropriate serializer based on action."""
+ if self.action == 'create':
+ return ProductCreateSerializer
+ return ProductSerializer
+
+ def perform_create(self, serializer):
+ """Save with user context."""
+ serializer.save(created_by=self.request.user)
+
+ @action(detail=False, methods=['get'])
+ def featured(self, request):
+ """Return featured products."""
+ featured = self.queryset.filter(is_featured=True)[:10]
+ serializer = self.get_serializer(featured, many=True)
+ return Response(serializer.data)
+
+ @action(detail=True, methods=['post'])
+ def purchase(self, request, pk=None):
+ """Purchase a product."""
+ product = self.get_object()
+ service = ProductService()
+ result = service.purchase(product, request.user)
+ return Response(result, status=status.HTTP_201_CREATED)
+
+ @action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])
+ def my_products(self, request):
+ """Return products created by current user."""
+ products = self.queryset.filter(created_by=request.user)
+ page = self.paginate_queryset(products)
+ serializer = self.get_serializer(page, many=True)
+ return self.get_paginated_response(serializer.data)
+```
+
+### Custom Actions
+
+```python
+from rest_framework.decorators import api_view, permission_classes
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+
+@api_view(['POST'])
+@permission_classes([IsAuthenticated])
+def add_to_cart(request):
+ """Add product to user cart."""
+ product_id = request.data.get('product_id')
+ quantity = request.data.get('quantity', 1)
+
+ try:
+ product = Product.objects.get(id=product_id)
+ except Product.DoesNotExist:
+ return Response(
+ {'error': 'Product not found'},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ cart, _ = Cart.objects.get_or_create(user=request.user)
+ CartItem.objects.create(
+ cart=cart,
+ product=product,
+ quantity=quantity
+ )
+
+ return Response({'message': 'Added to cart'}, status=status.HTTP_201_CREATED)
+```
+
+## Service Layer Pattern
+
+```python
+# apps/orders/services.py
+from typing import Optional
+from django.db import transaction
+from .models import Order, OrderItem
+
+class OrderService:
+ """Service layer for order-related business logic."""
+
+ @staticmethod
+ @transaction.atomic
+ def create_order(user, cart: Cart) -> Order:
+ """Create order from cart."""
+ order = Order.objects.create(
+ user=user,
+ total_price=cart.total_price
+ )
+
+ for item in cart.items.all():
+ OrderItem.objects.create(
+ order=order,
+ product=item.product,
+ quantity=item.quantity,
+ price=item.product.price
+ )
+
+ # Clear cart
+ cart.items.all().delete()
+
+ return order
+
+ @staticmethod
+ def process_payment(order: Order, payment_data: dict) -> bool:
+ """Process payment for order."""
+ # Integration with payment gateway
+ payment = PaymentGateway.charge(
+ amount=order.total_price,
+ token=payment_data['token']
+ )
+
+ if payment.success:
+ order.status = Order.Status.PAID
+ order.save()
+ # Send confirmation email
+ OrderService.send_confirmation_email(order)
+ return True
+
+ return False
+
+ @staticmethod
+ def send_confirmation_email(order: Order):
+ """Send order confirmation email."""
+ # Email sending logic
+ pass
+```
+
+## Caching Strategies
+
+### View-Level Caching
+
+```python
+from django.views.decorators.cache import cache_page
+from django.utils.decorators import method_decorator
+
+@method_decorator(cache_page(60 * 15), name='dispatch') # 15 minutes
+class ProductListView(generic.ListView):
+ model = Product
+ template_name = 'products/list.html'
+ context_object_name = 'products'
+```
+
+### Template Fragment Caching
+
+```django
+{% load cache %}
+{% cache 500 sidebar %}
+ ... expensive sidebar content ...
+{% endcache %}
+```
+
+### Low-Level Caching
+
+```python
+from django.core.cache import cache
+
+def get_featured_products():
+ """Get featured products with caching."""
+ cache_key = 'featured_products'
+ products = cache.get(cache_key)
+
+ if products is None:
+ products = list(Product.objects.filter(is_featured=True))
+ cache.set(cache_key, products, timeout=60 * 15) # 15 minutes
+
+ return products
+```
+
+### QuerySet Caching
+
+```python
+from django.core.cache import cache
+
+def get_popular_categories():
+ cache_key = 'popular_categories'
+ categories = cache.get(cache_key)
+
+ if categories is None:
+ categories = list(Category.objects.annotate(
+ product_count=Count('products')
+ ).filter(product_count__gt=10).order_by('-product_count')[:20])
+ cache.set(cache_key, categories, timeout=60 * 60) # 1 hour
+
+ return categories
+```
+
+## Signals
+
+### Signal Patterns
+
+```python
+# apps/users/signals.py
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+from django.contrib.auth import get_user_model
+from .models import Profile
+
+User = get_user_model()
+
+@receiver(post_save, sender=User)
+def create_user_profile(sender, instance, created, **kwargs):
+ """Create profile when user is created."""
+ if created:
+ Profile.objects.create(user=instance)
+
+@receiver(post_save, sender=User)
+def save_user_profile(sender, instance, **kwargs):
+ """Save profile when user is saved."""
+ instance.profile.save()
+
+# apps/users/apps.py
+from django.apps import AppConfig
+
+class UsersConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'apps.users'
+
+ def ready(self):
+ """Import signals when app is ready."""
+ import apps.users.signals
+```
+
+## Middleware
+
+### Custom Middleware
+
+```python
+# middleware/active_user_middleware.py
+import time
+from django.utils.deprecation import MiddlewareMixin
+
+class ActiveUserMiddleware(MiddlewareMixin):
+ """Middleware to track active users."""
+
+ def process_request(self, request):
+ """Process incoming request."""
+ if request.user.is_authenticated:
+ # Update last active time
+ request.user.last_active = timezone.now()
+ request.user.save(update_fields=['last_active'])
+
+class RequestLoggingMiddleware(MiddlewareMixin):
+ """Middleware for logging requests."""
+
+ def process_request(self, request):
+ """Log request start time."""
+ request.start_time = time.time()
+
+ def process_response(self, request, response):
+ """Log request duration."""
+ if hasattr(request, 'start_time'):
+ duration = time.time() - request.start_time
+ logger.info(f'{request.method} {request.path} - {response.status_code} - {duration:.3f}s')
+ return response
+```
+
+## Performance Optimization
+
+### N+1 Query Prevention
+
+```python
+# Bad - N+1 queries
+products = Product.objects.all()
+for product in products:
+ print(product.category.name) # Separate query for each product
+
+# Good - Single query with select_related
+products = Product.objects.select_related('category').all()
+for product in products:
+ print(product.category.name)
+
+# Good - Prefetch for many-to-many
+products = Product.objects.prefetch_related('tags').all()
+for product in products:
+ for tag in product.tags.all():
+ print(tag.name)
+```
+
+### Database Indexing
+
+```python
+class Product(models.Model):
+ name = models.CharField(max_length=200, db_index=True)
+ slug = models.SlugField(unique=True)
+ category = models.ForeignKey('Category', on_delete=models.CASCADE)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ indexes = [
+ models.Index(fields=['name']),
+ models.Index(fields=['-created_at']),
+ models.Index(fields=['category', 'created_at']),
+ ]
+```
+
+### Bulk Operations
+
+```python
+# Bulk create
+Product.objects.bulk_create([
+ Product(name=f'Product {i}', price=10.00)
+ for i in range(1000)
+])
+
+# Bulk update
+products = Product.objects.all()[:100]
+for product in products:
+ product.is_active = True
+Product.objects.bulk_update(products, ['is_active'])
+
+# Bulk delete
+Product.objects.filter(stock=0).delete()
+```
+
+## Quick Reference
+
+| Pattern | Description |
+|---------|-------------|
+| Split settings | Separate dev/prod/test settings |
+| Custom QuerySet | Reusable query methods |
+| Service Layer | Business logic separation |
+| ViewSet | REST API endpoints |
+| Serializer validation | Request/response transformation |
+| select_related | Foreign key optimization |
+| prefetch_related | Many-to-many optimization |
+| Cache first | Cache expensive operations |
+| Signals | Event-driven actions |
+| Middleware | Request/response processing |
+
+Remember: Django provides many shortcuts, but for production applications, structure and organization matter more than concise code. Build for maintainability.
diff --git a/skills/django-security/SKILL.md b/skills/django-security/SKILL.md
new file mode 100644
index 0000000..9d228af
--- /dev/null
+++ b/skills/django-security/SKILL.md
@@ -0,0 +1,592 @@
+---
+name: django-security
+description: Django security best practices, authentication, authorization, CSRF protection, SQL injection prevention, XSS prevention, and secure deployment configurations.
+---
+
+# Django Security Best Practices
+
+Comprehensive security guidelines for Django applications to protect against common vulnerabilities.
+
+## When to Activate
+
+- Setting up Django authentication and authorization
+- Implementing user permissions and roles
+- Configuring production security settings
+- Reviewing Django application for security issues
+- Deploying Django applications to production
+
+## Core Security Settings
+
+### Production Settings Configuration
+
+```python
+# settings/production.py
+import os
+
+DEBUG = False # CRITICAL: Never use True in production
+
+ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
+
+# Security headers
+SECURE_SSL_REDIRECT = True
+SESSION_COOKIE_SECURE = True
+CSRF_COOKIE_SECURE = True
+SECURE_HSTS_SECONDS = 31536000 # 1 year
+SECURE_HSTS_INCLUDE_SUBDOMAINS = True
+SECURE_HSTS_PRELOAD = True
+SECURE_CONTENT_TYPE_NOSNIFF = True
+SECURE_BROWSER_XSS_FILTER = True
+X_FRAME_OPTIONS = 'DENY'
+
+# HTTPS and Cookies
+SESSION_COOKIE_HTTPONLY = True
+CSRF_COOKIE_HTTPONLY = True
+SESSION_COOKIE_SAMESITE = 'Lax'
+CSRF_COOKIE_SAMESITE = 'Lax'
+
+# Secret key (must be set via environment variable)
+SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
+if not SECRET_KEY:
+ raise ImproperlyConfigured('DJANGO_SECRET_KEY environment variable is required')
+
+# Password validation
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ 'OPTIONS': {
+ 'min_length': 12,
+ }
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+```
+
+## Authentication
+
+### Custom User Model
+
+```python
+# apps/users/models.py
+from django.contrib.auth.models import AbstractUser
+from django.db import models
+
+class User(AbstractUser):
+ """Custom user model for better security."""
+
+ email = models.EmailField(unique=True)
+ phone = models.CharField(max_length=20, blank=True)
+
+ USERNAME_FIELD = 'email' # Use email as username
+ REQUIRED_FIELDS = ['username']
+
+ class Meta:
+ db_table = 'users'
+ verbose_name = 'User'
+ verbose_name_plural = 'Users'
+
+ def __str__(self):
+ return self.email
+
+# settings/base.py
+AUTH_USER_MODEL = 'users.User'
+```
+
+### Password Hashing
+
+```python
+# Django uses PBKDF2 by default. For stronger security:
+PASSWORD_HASHERS = [
+ 'django.contrib.auth.hashers.Argon2PasswordHasher',
+ 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
+ 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
+ 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
+]
+```
+
+### Session Management
+
+```python
+# Session configuration
+SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # Or 'db'
+SESSION_CACHE_ALIAS = 'default'
+SESSION_COOKIE_AGE = 3600 * 24 * 7 # 1 week
+SESSION_SAVE_EVERY_REQUEST = False
+SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Better UX, but less secure
+```
+
+## Authorization
+
+### Permissions
+
+```python
+# models.py
+from django.db import models
+from django.contrib.auth.models import Permission
+
+class Post(models.Model):
+ title = models.CharField(max_length=200)
+ content = models.TextField()
+ author = models.ForeignKey(User, on_delete=models.CASCADE)
+
+ class Meta:
+ permissions = [
+ ('can_publish', 'Can publish posts'),
+ ('can_edit_others', 'Can edit posts of others'),
+ ]
+
+ def user_can_edit(self, user):
+ """Check if user can edit this post."""
+ return self.author == user or user.has_perm('app.can_edit_others')
+
+# views.py
+from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
+from django.views.generic import UpdateView
+
+class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
+ model = Post
+ permission_required = 'app.can_edit_others'
+ raise_exception = True # Return 403 instead of redirect
+
+ def get_queryset(self):
+ """Only allow users to edit their own posts."""
+ return Post.objects.filter(author=self.request.user)
+```
+
+### Custom Permissions
+
+```python
+# permissions.py
+from rest_framework import permissions
+
+class IsOwnerOrReadOnly(permissions.BasePermission):
+ """Allow only owners to edit objects."""
+
+ def has_object_permission(self, request, view, obj):
+ # Read permissions allowed for any request
+ if request.method in permissions.SAFE_METHODS:
+ return True
+
+ # Write permissions only for owner
+ return obj.author == request.user
+
+class IsAdminOrReadOnly(permissions.BasePermission):
+ """Allow admins to do anything, others read-only."""
+
+ def has_permission(self, request, view):
+ if request.method in permissions.SAFE_METHODS:
+ return True
+ return request.user and request.user.is_staff
+
+class IsVerifiedUser(permissions.BasePermission):
+ """Allow only verified users."""
+
+ def has_permission(self, request, view):
+ return request.user and request.user.is_authenticated and request.user.is_verified
+```
+
+### Role-Based Access Control (RBAC)
+
+```python
+# models.py
+from django.contrib.auth.models import AbstractUser, Group
+
+class User(AbstractUser):
+ ROLE_CHOICES = [
+ ('admin', 'Administrator'),
+ ('moderator', 'Moderator'),
+ ('user', 'Regular User'),
+ ]
+ role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')
+
+ def is_admin(self):
+ return self.role == 'admin' or self.is_superuser
+
+ def is_moderator(self):
+ return self.role in ['admin', 'moderator']
+
+# Mixins
+class AdminRequiredMixin:
+ """Mixin to require admin role."""
+
+ def dispatch(self, request, *args, **kwargs):
+ if not request.user.is_authenticated or not request.user.is_admin():
+ from django.core.exceptions import PermissionDenied
+ raise PermissionDenied
+ return super().dispatch(request, *args, **kwargs)
+```
+
+## SQL Injection Prevention
+
+### Django ORM Protection
+
+```python
+# GOOD: Django ORM automatically escapes parameters
+def get_user(username):
+ return User.objects.get(username=username) # Safe
+
+# GOOD: Using parameters with raw()
+def search_users(query):
+ return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])
+
+# BAD: Never directly interpolate user input
+def get_user_bad(username):
+ return User.objects.raw(f'SELECT * FROM users WHERE username = {username}') # VULNERABLE!
+
+# GOOD: Using filter with proper escaping
+def get_users_by_email(email):
+ return User.objects.filter(email__iexact=email) # Safe
+
+# GOOD: Using Q objects for complex queries
+from django.db.models import Q
+def search_users_complex(query):
+ return User.objects.filter(
+ Q(username__icontains=query) |
+ Q(email__icontains=query)
+ ) # Safe
+```
+
+### Extra Security with raw()
+
+```python
+# If you must use raw SQL, always use parameters
+User.objects.raw(
+ 'SELECT * FROM users WHERE email = %s AND status = %s',
+ [user_input_email, status]
+)
+```
+
+## XSS Prevention
+
+### Template Escaping
+
+```django
+{# Django auto-escapes variables by default - SAFE #}
+{{ user_input }} {# Escaped HTML #}
+
+{# Explicitly mark safe only for trusted content #}
+{{ trusted_html|safe }} {# Not escaped #}
+
+{# Use template filters for safe HTML #}
+{{ user_input|escape }} {# Same as default #}
+{{ user_input|striptags }} {# Remove all HTML tags #}
+
+{# JavaScript escaping #}
+
+```
+
+### Safe String Handling
+
+```python
+from django.utils.safestring import mark_safe
+from django.utils.html import escape
+
+# BAD: Never mark user input as safe without escaping
+def render_bad(user_input):
+ return mark_safe(user_input) # VULNERABLE!
+
+# GOOD: Escape first, then mark safe
+def render_good(user_input):
+ return mark_safe(escape(user_input))
+
+# GOOD: Use format_html for HTML with variables
+from django.utils.html import format_html
+
+def greet_user(username):
+ return format_html('{}', escape(username))
+```
+
+### HTTP Headers
+
+```python
+# settings.py
+SECURE_CONTENT_TYPE_NOSNIFF = True # Prevent MIME sniffing
+SECURE_BROWSER_XSS_FILTER = True # Enable XSS filter
+X_FRAME_OPTIONS = 'DENY' # Prevent clickjacking
+
+# Custom middleware
+from django.conf import settings
+
+class SecurityHeaderMiddleware:
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ response = self.get_response(request)
+ response['X-Content-Type-Options'] = 'nosniff'
+ response['X-Frame-Options'] = 'DENY'
+ response['X-XSS-Protection'] = '1; mode=block'
+ response['Content-Security-Policy'] = "default-src 'self'"
+ return response
+```
+
+## CSRF Protection
+
+### Default CSRF Protection
+
+```python
+# settings.py - CSRF is enabled by default
+CSRF_COOKIE_SECURE = True # Only send over HTTPS
+CSRF_COOKIE_HTTPONLY = True # Prevent JavaScript access
+CSRF_COOKIE_SAMESITE = 'Lax' # Prevent CSRF in some cases
+CSRF_TRUSTED_ORIGINS = ['https://example.com'] # Trusted domains
+
+# Template usage
+
+
+# AJAX requests
+function getCookie(name) {
+ let cookieValue = null;
+ if (document.cookie && document.cookie !== '') {
+ const cookies = document.cookie.split(';');
+ for (let i = 0; i < cookies.length; i++) {
+ const cookie = cookies[i].trim();
+ if (cookie.substring(0, name.length + 1) === (name + '=')) {
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+ break;
+ }
+ }
+ }
+ return cookieValue;
+}
+
+fetch('/api/endpoint/', {
+ method: 'POST',
+ headers: {
+ 'X-CSRFToken': getCookie('csrftoken'),
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data)
+});
+```
+
+### Exempting Views (Use Carefully)
+
+```python
+from django.views.decorators.csrf import csrf_exempt
+
+@csrf_exempt # Only use when absolutely necessary!
+def webhook_view(request):
+ # Webhook from external service
+ pass
+```
+
+## File Upload Security
+
+### File Validation
+
+```python
+import os
+from django.core.exceptions import ValidationError
+
+def validate_file_extension(value):
+ """Validate file extension."""
+ ext = os.path.splitext(value.name)[1]
+ valid_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf']
+ if not ext.lower() in valid_extensions:
+ raise ValidationError('Unsupported file extension.')
+
+def validate_file_size(value):
+ """Validate file size (max 5MB)."""
+ filesize = value.size
+ if filesize > 5 * 1024 * 1024:
+ raise ValidationError('File too large. Max size is 5MB.')
+
+# models.py
+class Document(models.Model):
+ file = models.FileField(
+ upload_to='documents/',
+ validators=[validate_file_extension, validate_file_size]
+ )
+```
+
+### Secure File Storage
+
+```python
+# settings.py
+MEDIA_ROOT = '/var/www/media/'
+MEDIA_URL = '/media/'
+
+# Use a separate domain for media in production
+MEDIA_DOMAIN = 'https://media.example.com'
+
+# Don't serve user uploads directly
+# Use whitenoise or a CDN for static files
+# Use a separate server or S3 for media files
+```
+
+## API Security
+
+### Rate Limiting
+
+```python
+# settings.py
+REST_FRAMEWORK = {
+ 'DEFAULT_THROTTLE_CLASSES': [
+ 'rest_framework.throttling.AnonRateThrottle',
+ 'rest_framework.throttling.UserRateThrottle'
+ ],
+ 'DEFAULT_THROTTLE_RATES': {
+ 'anon': '100/day',
+ 'user': '1000/day',
+ 'upload': '10/hour',
+ }
+}
+
+# Custom throttle
+from rest_framework.throttling import UserRateThrottle
+
+class BurstRateThrottle(UserRateThrottle):
+ scope = 'burst'
+ rate = '60/min'
+
+class SustainedRateThrottle(UserRateThrottle):
+ scope = 'sustained'
+ rate = '1000/day'
+```
+
+### Authentication for APIs
+
+```python
+# settings.py
+REST_FRAMEWORK = {
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
+ 'rest_framework.authentication.TokenAuthentication',
+ 'rest_framework.authentication.SessionAuthentication',
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
+ ],
+ 'DEFAULT_PERMISSION_CLASSES': [
+ 'rest_framework.permissions.IsAuthenticated',
+ ],
+}
+
+# views.py
+from rest_framework.decorators import api_view, permission_classes
+from rest_framework.permissions import IsAuthenticated
+
+@api_view(['GET', 'POST'])
+@permission_classes([IsAuthenticated])
+def protected_view(request):
+ return Response({'message': 'You are authenticated'})
+```
+
+## Security Headers
+
+### Content Security Policy
+
+```python
+# settings.py
+CSP_DEFAULT_SRC = "'self'"
+CSP_SCRIPT_SRC = "'self' https://cdn.example.com"
+CSP_STYLE_SRC = "'self' 'unsafe-inline'"
+CSP_IMG_SRC = "'self' data: https:"
+CSP_CONNECT_SRC = "'self' https://api.example.com"
+
+# Middleware
+class CSPMiddleware:
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ response = self.get_response(request)
+ response['Content-Security-Policy'] = (
+ f"default-src {CSP_DEFAULT_SRC}; "
+ f"script-src {CSP_SCRIPT_SRC}; "
+ f"style-src {CSP_STYLE_SRC}; "
+ f"img-src {CSP_IMG_SRC}; "
+ f"connect-src {CSP_CONNECT_SRC}"
+ )
+ return response
+```
+
+## Environment Variables
+
+### Managing Secrets
+
+```python
+# Use python-decouple or django-environ
+import environ
+
+env = environ.Env(
+ # set casting, default value
+ DEBUG=(bool, False)
+)
+
+# reading .env file
+environ.Env.read_env()
+
+SECRET_KEY = env('DJANGO_SECRET_KEY')
+DATABASE_URL = env('DATABASE_URL')
+ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
+
+# .env file (never commit this)
+DEBUG=False
+SECRET_KEY=your-secret-key-here
+DATABASE_URL=postgresql://user:password@localhost:5432/dbname
+ALLOWED_HOSTS=example.com,www.example.com
+```
+
+## Logging Security Events
+
+```python
+# settings.py
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'handlers': {
+ 'file': {
+ 'level': 'WARNING',
+ 'class': 'logging.FileHandler',
+ 'filename': '/var/log/django/security.log',
+ },
+ 'console': {
+ 'level': 'INFO',
+ 'class': 'logging.StreamHandler',
+ },
+ },
+ 'loggers': {
+ 'django.security': {
+ 'handlers': ['file', 'console'],
+ 'level': 'WARNING',
+ 'propagate': True,
+ },
+ 'django.request': {
+ 'handlers': ['file'],
+ 'level': 'ERROR',
+ 'propagate': False,
+ },
+ },
+}
+```
+
+## Quick Security Checklist
+
+| Check | Description |
+|-------|-------------|
+| `DEBUG = False` | Never run with DEBUG in production |
+| HTTPS only | Force SSL, secure cookies |
+| Strong secrets | Use environment variables for SECRET_KEY |
+| Password validation | Enable all password validators |
+| CSRF protection | Enabled by default, don't disable |
+| XSS prevention | Django auto-escapes, don't use `|safe` with user input |
+| SQL injection | Use ORM, never concatenate strings in queries |
+| File uploads | Validate file type and size |
+| Rate limiting | Throttle API endpoints |
+| Security headers | CSP, X-Frame-Options, HSTS |
+| Logging | Log security events |
+| Updates | Keep Django and dependencies updated |
+
+Remember: Security is a process, not a product. Regularly review and update your security practices.
diff --git a/skills/django-tdd/SKILL.md b/skills/django-tdd/SKILL.md
new file mode 100644
index 0000000..7b88405
--- /dev/null
+++ b/skills/django-tdd/SKILL.md
@@ -0,0 +1,728 @@
+---
+name: django-tdd
+description: Django testing strategies with pytest-django, TDD methodology, factory_boy, mocking, coverage, and testing Django REST Framework APIs.
+---
+
+# Django Testing with TDD
+
+Test-driven development for Django applications using pytest, factory_boy, and Django REST Framework.
+
+## When to Activate
+
+- Writing new Django applications
+- Implementing Django REST Framework APIs
+- Testing Django models, views, and serializers
+- Setting up testing infrastructure for Django projects
+
+## TDD Workflow for Django
+
+### Red-Green-Refactor Cycle
+
+```python
+# Step 1: RED - Write failing test
+def test_user_creation():
+ user = User.objects.create_user(email='test@example.com', password='testpass123')
+ assert user.email == 'test@example.com'
+ assert user.check_password('testpass123')
+ assert not user.is_staff
+
+# Step 2: GREEN - Make test pass
+# Create User model or factory
+
+# Step 3: REFACTOR - Improve while keeping tests green
+```
+
+## Setup
+
+### pytest Configuration
+
+```ini
+# pytest.ini
+[pytest]
+DJANGO_SETTINGS_MODULE = config.settings.test
+testpaths = tests
+python_files = test_*.py
+python_classes = Test*
+python_functions = test_*
+addopts =
+ --reuse-db
+ --nomigrations
+ --cov=apps
+ --cov-report=html
+ --cov-report=term-missing
+ --strict-markers
+markers =
+ slow: marks tests as slow
+ integration: marks tests as integration tests
+```
+
+### Test Settings
+
+```python
+# config/settings/test.py
+from .base import *
+
+DEBUG = True
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': ':memory:',
+ }
+}
+
+# Disable migrations for speed
+class DisableMigrations:
+ def __contains__(self, item):
+ return True
+
+ def __getitem__(self, item):
+ return None
+
+MIGRATION_MODULES = DisableMigrations()
+
+# Faster password hashing
+PASSWORD_HASHERS = [
+ 'django.contrib.auth.hashers.MD5PasswordHasher',
+]
+
+# Email backend
+EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+
+# Celery always eager
+CELERY_TASK_ALWAYS_EAGER = True
+CELERY_TASK_EAGER_PROPAGATES = True
+```
+
+### conftest.py
+
+```python
+# tests/conftest.py
+import pytest
+from django.utils import timezone
+from django.contrib.auth import get_user_model
+
+User = get_user_model()
+
+@pytest.fixture(autouse=True)
+def timezone_settings(settings):
+ """Ensure consistent timezone."""
+ settings.TIME_ZONE = 'UTC'
+
+@pytest.fixture
+def user(db):
+ """Create a test user."""
+ return User.objects.create_user(
+ email='test@example.com',
+ password='testpass123',
+ username='testuser'
+ )
+
+@pytest.fixture
+def admin_user(db):
+ """Create an admin user."""
+ return User.objects.create_superuser(
+ email='admin@example.com',
+ password='adminpass123',
+ username='admin'
+ )
+
+@pytest.fixture
+def authenticated_client(client, user):
+ """Return authenticated client."""
+ client.force_login(user)
+ return client
+
+@pytest.fixture
+def api_client():
+ """Return DRF API client."""
+ from rest_framework.test import APIClient
+ return APIClient()
+
+@pytest.fixture
+def authenticated_api_client(api_client, user):
+ """Return authenticated API client."""
+ api_client.force_authenticate(user=user)
+ return api_client
+```
+
+## Factory Boy
+
+### Factory Setup
+
+```python
+# tests/factories.py
+import factory
+from factory import fuzzy
+from datetime import datetime, timedelta
+from django.contrib.auth import get_user_model
+from apps.products.models import Product, Category
+
+User = get_user_model()
+
+class UserFactory(factory.django.DjangoModelFactory):
+ """Factory for User model."""
+
+ class Meta:
+ model = User
+
+ email = factory.Sequence(lambda n: f"user{n}@example.com")
+ username = factory.Sequence(lambda n: f"user{n}")
+ password = factory.PostGenerationMethodCall('set_password', 'testpass123')
+ first_name = factory.Faker('first_name')
+ last_name = factory.Faker('last_name')
+ is_active = True
+
+class CategoryFactory(factory.django.DjangoModelFactory):
+ """Factory for Category model."""
+
+ class Meta:
+ model = Category
+
+ name = factory.Faker('word')
+ slug = factory.LazyAttribute(lambda obj: obj.name.lower())
+ description = factory.Faker('text')
+
+class ProductFactory(factory.django.DjangoModelFactory):
+ """Factory for Product model."""
+
+ class Meta:
+ model = Product
+
+ name = factory.Faker('sentence', nb_words=3)
+ slug = factory.LazyAttribute(lambda obj: obj.name.lower().replace(' ', '-'))
+ description = factory.Faker('text')
+ price = fuzzy.FuzzyDecimal(10.00, 1000.00, 2)
+ stock = fuzzy.FuzzyInteger(0, 100)
+ is_active = True
+ category = factory.SubFactory(CategoryFactory)
+ created_by = factory.SubFactory(UserFactory)
+
+ @factory.post_generation
+ def tags(self, create, extracted, **kwargs):
+ """Add tags to product."""
+ if not create:
+ return
+ if extracted:
+ for tag in extracted:
+ self.tags.add(tag)
+```
+
+### Using Factories
+
+```python
+# tests/test_models.py
+import pytest
+from tests.factories import ProductFactory, UserFactory
+
+def test_product_creation():
+ """Test product creation using factory."""
+ product = ProductFactory(price=100.00, stock=50)
+ assert product.price == 100.00
+ assert product.stock == 50
+ assert product.is_active is True
+
+def test_product_with_tags():
+ """Test product with tags."""
+ tags = [TagFactory(name='electronics'), TagFactory(name='new')]
+ product = ProductFactory(tags=tags)
+ assert product.tags.count() == 2
+
+def test_multiple_products():
+ """Test creating multiple products."""
+ products = ProductFactory.create_batch(10)
+ assert len(products) == 10
+```
+
+## Model Testing
+
+### Model Tests
+
+```python
+# tests/test_models.py
+import pytest
+from django.core.exceptions import ValidationError
+from tests.factories import UserFactory, ProductFactory
+
+class TestUserModel:
+ """Test User model."""
+
+ def test_create_user(self, db):
+ """Test creating a regular user."""
+ user = UserFactory(email='test@example.com')
+ assert user.email == 'test@example.com'
+ assert user.check_password('testpass123')
+ assert not user.is_staff
+ assert not user.is_superuser
+
+ def test_create_superuser(self, db):
+ """Test creating a superuser."""
+ user = UserFactory(
+ email='admin@example.com',
+ is_staff=True,
+ is_superuser=True
+ )
+ assert user.is_staff
+ assert user.is_superuser
+
+ def test_user_str(self, db):
+ """Test user string representation."""
+ user = UserFactory(email='test@example.com')
+ assert str(user) == 'test@example.com'
+
+class TestProductModel:
+ """Test Product model."""
+
+ def test_product_creation(self, db):
+ """Test creating a product."""
+ product = ProductFactory()
+ assert product.id is not None
+ assert product.is_active is True
+ assert product.created_at is not None
+
+ def test_product_slug_generation(self, db):
+ """Test automatic slug generation."""
+ product = ProductFactory(name='Test Product')
+ assert product.slug == 'test-product'
+
+ def test_product_price_validation(self, db):
+ """Test price cannot be negative."""
+ product = ProductFactory(price=-10)
+ with pytest.raises(ValidationError):
+ product.full_clean()
+
+ def test_product_manager_active(self, db):
+ """Test active manager method."""
+ ProductFactory.create_batch(5, is_active=True)
+ ProductFactory.create_batch(3, is_active=False)
+
+ active_count = Product.objects.active().count()
+ assert active_count == 5
+
+ def test_product_stock_management(self, db):
+ """Test stock management."""
+ product = ProductFactory(stock=10)
+ product.reduce_stock(5)
+ product.refresh_from_db()
+ assert product.stock == 5
+
+ with pytest.raises(ValueError):
+ product.reduce_stock(10) # Not enough stock
+```
+
+## View Testing
+
+### Django View Testing
+
+```python
+# tests/test_views.py
+import pytest
+from django.urls import reverse
+from tests.factories import ProductFactory, UserFactory
+
+class TestProductViews:
+ """Test product views."""
+
+ def test_product_list(self, client, db):
+ """Test product list view."""
+ ProductFactory.create_batch(10)
+
+ response = client.get(reverse('products:list'))
+
+ assert response.status_code == 200
+ assert len(response.context['products']) == 10
+
+ def test_product_detail(self, client, db):
+ """Test product detail view."""
+ product = ProductFactory()
+
+ response = client.get(reverse('products:detail', kwargs={'slug': product.slug}))
+
+ assert response.status_code == 200
+ assert response.context['product'] == product
+
+ def test_product_create_requires_login(self, client, db):
+ """Test product creation requires authentication."""
+ response = client.get(reverse('products:create'))
+
+ assert response.status_code == 302
+ assert response.url.startswith('/accounts/login/')
+
+ def test_product_create_authenticated(self, authenticated_client, db):
+ """Test product creation as authenticated user."""
+ response = authenticated_client.get(reverse('products:create'))
+
+ assert response.status_code == 200
+
+ def test_product_create_post(self, authenticated_client, db, category):
+ """Test creating a product via POST."""
+ data = {
+ 'name': 'Test Product',
+ 'description': 'A test product',
+ 'price': '99.99',
+ 'stock': 10,
+ 'category': category.id,
+ }
+
+ response = authenticated_client.post(reverse('products:create'), data)
+
+ assert response.status_code == 302
+ assert Product.objects.filter(name='Test Product').exists()
+```
+
+## DRF API Testing
+
+### Serializer Testing
+
+```python
+# tests/test_serializers.py
+import pytest
+from rest_framework.exceptions import ValidationError
+from apps.products.serializers import ProductSerializer
+from tests.factories import ProductFactory
+
+class TestProductSerializer:
+ """Test ProductSerializer."""
+
+ def test_serialize_product(self, db):
+ """Test serializing a product."""
+ product = ProductFactory()
+ serializer = ProductSerializer(product)
+
+ data = serializer.data
+
+ assert data['id'] == product.id
+ assert data['name'] == product.name
+ assert data['price'] == str(product.price)
+
+ def test_deserialize_product(self, db):
+ """Test deserializing product data."""
+ data = {
+ 'name': 'Test Product',
+ 'description': 'Test description',
+ 'price': '99.99',
+ 'stock': 10,
+ 'category': 1,
+ }
+
+ serializer = ProductSerializer(data=data)
+
+ assert serializer.is_valid()
+ product = serializer.save()
+
+ assert product.name == 'Test Product'
+ assert float(product.price) == 99.99
+
+ def test_price_validation(self, db):
+ """Test price validation."""
+ data = {
+ 'name': 'Test Product',
+ 'price': '-10.00',
+ 'stock': 10,
+ }
+
+ serializer = ProductSerializer(data=data)
+
+ assert not serializer.is_valid()
+ assert 'price' in serializer.errors
+
+ def test_stock_validation(self, db):
+ """Test stock cannot be negative."""
+ data = {
+ 'name': 'Test Product',
+ 'price': '99.99',
+ 'stock': -5,
+ }
+
+ serializer = ProductSerializer(data=data)
+
+ assert not serializer.is_valid()
+ assert 'stock' in serializer.errors
+```
+
+### API ViewSet Testing
+
+```python
+# tests/test_api.py
+import pytest
+from rest_framework.test import APIClient
+from rest_framework import status
+from django.urls import reverse
+from tests.factories import ProductFactory, UserFactory
+
+class TestProductAPI:
+ """Test Product API endpoints."""
+
+ @pytest.fixture
+ def api_client(self):
+ """Return API client."""
+ return APIClient()
+
+ def test_list_products(self, api_client, db):
+ """Test listing products."""
+ ProductFactory.create_batch(10)
+
+ url = reverse('api:product-list')
+ response = api_client.get(url)
+
+ assert response.status_code == status.HTTP_200_OK
+ assert response.data['count'] == 10
+
+ def test_retrieve_product(self, api_client, db):
+ """Test retrieving a product."""
+ product = ProductFactory()
+
+ url = reverse('api:product-detail', kwargs={'pk': product.id})
+ response = api_client.get(url)
+
+ assert response.status_code == status.HTTP_200_OK
+ assert response.data['id'] == product.id
+
+ def test_create_product_unauthorized(self, api_client, db):
+ """Test creating product without authentication."""
+ url = reverse('api:product-list')
+ data = {'name': 'Test Product', 'price': '99.99'}
+
+ response = api_client.post(url, data)
+
+ assert response.status_code == status.HTTP_401_UNAUTHORIZED
+
+ def test_create_product_authorized(self, authenticated_api_client, db):
+ """Test creating product as authenticated user."""
+ url = reverse('api:product-list')
+ data = {
+ 'name': 'Test Product',
+ 'description': 'Test',
+ 'price': '99.99',
+ 'stock': 10,
+ }
+
+ response = authenticated_api_client.post(url, data)
+
+ assert response.status_code == status.HTTP_201_CREATED
+ assert response.data['name'] == 'Test Product'
+
+ def test_update_product(self, authenticated_api_client, db):
+ """Test updating a product."""
+ product = ProductFactory(created_by=authenticated_api_client.user)
+
+ url = reverse('api:product-detail', kwargs={'pk': product.id})
+ data = {'name': 'Updated Product'}
+
+ response = authenticated_api_client.patch(url, data)
+
+ assert response.status_code == status.HTTP_200_OK
+ assert response.data['name'] == 'Updated Product'
+
+ def test_delete_product(self, authenticated_api_client, db):
+ """Test deleting a product."""
+ product = ProductFactory(created_by=authenticated_api_client.user)
+
+ url = reverse('api:product-detail', kwargs={'pk': product.id})
+ response = authenticated_api_client.delete(url)
+
+ assert response.status_code == status.HTTP_204_NO_CONTENT
+
+ def test_filter_products_by_price(self, api_client, db):
+ """Test filtering products by price."""
+ ProductFactory(price=50)
+ ProductFactory(price=150)
+
+ url = reverse('api:product-list')
+ response = api_client.get(url, {'price_min': 100})
+
+ assert response.status_code == status.HTTP_200_OK
+ assert response.data['count'] == 1
+
+ def test_search_products(self, api_client, db):
+ """Test searching products."""
+ ProductFactory(name='Apple iPhone')
+ ProductFactory(name='Samsung Galaxy')
+
+ url = reverse('api:product-list')
+ response = api_client.get(url, {'search': 'Apple'})
+
+ assert response.status_code == status.HTTP_200_OK
+ assert response.data['count'] == 1
+```
+
+## Mocking and Patching
+
+### Mocking External Services
+
+```python
+# tests/test_views.py
+from unittest.mock import patch, Mock
+import pytest
+
+class TestPaymentView:
+ """Test payment view with mocked payment gateway."""
+
+ @patch('apps.payments.services.stripe')
+ def test_successful_payment(self, mock_stripe, client, user, product):
+ """Test successful payment with mocked Stripe."""
+ # Configure mock
+ mock_stripe.Charge.create.return_value = {
+ 'id': 'ch_123',
+ 'status': 'succeeded',
+ 'amount': 9999,
+ }
+
+ client.force_login(user)
+ response = client.post(reverse('payments:process'), {
+ 'product_id': product.id,
+ 'token': 'tok_visa',
+ })
+
+ assert response.status_code == 302
+ mock_stripe.Charge.create.assert_called_once()
+
+ @patch('apps.payments.services.stripe')
+ def test_failed_payment(self, mock_stripe, client, user, product):
+ """Test failed payment."""
+ mock_stripe.Charge.create.side_effect = Exception('Card declined')
+
+ client.force_login(user)
+ response = client.post(reverse('payments:process'), {
+ 'product_id': product.id,
+ 'token': 'tok_visa',
+ })
+
+ assert response.status_code == 302
+ assert 'error' in response.url
+```
+
+### Mocking Email Sending
+
+```python
+# tests/test_email.py
+from django.core import mail
+from django.test import override_settings
+
+@override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend')
+def test_order_confirmation_email(db, order):
+ """Test order confirmation email."""
+ order.send_confirmation_email()
+
+ assert len(mail.outbox) == 1
+ assert order.user.email in mail.outbox[0].to
+ assert 'Order Confirmation' in mail.outbox[0].subject
+```
+
+## Integration Testing
+
+### Full Flow Testing
+
+```python
+# tests/test_integration.py
+import pytest
+from django.urls import reverse
+from tests.factories import UserFactory, ProductFactory
+
+class TestCheckoutFlow:
+ """Test complete checkout flow."""
+
+ def test_guest_to_purchase_flow(self, client, db):
+ """Test complete flow from guest to purchase."""
+ # Step 1: Register
+ response = client.post(reverse('users:register'), {
+ 'email': 'test@example.com',
+ 'password': 'testpass123',
+ 'password_confirm': 'testpass123',
+ })
+ assert response.status_code == 302
+
+ # Step 2: Login
+ response = client.post(reverse('users:login'), {
+ 'email': 'test@example.com',
+ 'password': 'testpass123',
+ })
+ assert response.status_code == 302
+
+ # Step 3: Browse products
+ product = ProductFactory(price=100)
+ response = client.get(reverse('products:detail', kwargs={'slug': product.slug}))
+ assert response.status_code == 200
+
+ # Step 4: Add to cart
+ response = client.post(reverse('cart:add'), {
+ 'product_id': product.id,
+ 'quantity': 1,
+ })
+ assert response.status_code == 302
+
+ # Step 5: Checkout
+ response = client.get(reverse('checkout:review'))
+ assert response.status_code == 200
+ assert product.name in response.content.decode()
+
+ # Step 6: Complete purchase
+ with patch('apps.checkout.services.process_payment') as mock_payment:
+ mock_payment.return_value = True
+ response = client.post(reverse('checkout:complete'))
+
+ assert response.status_code == 302
+ assert Order.objects.filter(user__email='test@example.com').exists()
+```
+
+## Testing Best Practices
+
+### DO
+
+- **Use factories**: Instead of manual object creation
+- **One assertion per test**: Keep tests focused
+- **Descriptive test names**: `test_user_cannot_delete_others_post`
+- **Test edge cases**: Empty inputs, None values, boundary conditions
+- **Mock external services**: Don't depend on external APIs
+- **Use fixtures**: Eliminate duplication
+- **Test permissions**: Ensure authorization works
+- **Keep tests fast**: Use `--reuse-db` and `--nomigrations`
+
+### DON'T
+
+- **Don't test Django internals**: Trust Django to work
+- **Don't test third-party code**: Trust libraries to work
+- **Don't ignore failing tests**: All tests must pass
+- **Don't make tests dependent**: Tests should run in any order
+- **Don't over-mock**: Mock only external dependencies
+- **Don't test private methods**: Test public interface
+- **Don't use production database**: Always use test database
+
+## Coverage
+
+### Coverage Configuration
+
+```bash
+# Run tests with coverage
+pytest --cov=apps --cov-report=html --cov-report=term-missing
+
+# Generate HTML report
+open htmlcov/index.html
+```
+
+### Coverage Goals
+
+| Component | Target Coverage |
+|-----------|-----------------|
+| Models | 90%+ |
+| Serializers | 85%+ |
+| Views | 80%+ |
+| Services | 90%+ |
+| Utilities | 80%+ |
+| Overall | 80%+ |
+
+## Quick Reference
+
+| Pattern | Usage |
+|---------|-------|
+| `@pytest.mark.django_db` | Enable database access |
+| `client` | Django test client |
+| `api_client` | DRF API client |
+| `factory.create_batch(n)` | Create multiple objects |
+| `patch('module.function')` | Mock external dependencies |
+| `override_settings` | Temporarily change settings |
+| `force_authenticate()` | Bypass authentication in tests |
+| `assertRedirects` | Check for redirects |
+| `assertTemplateUsed` | Verify template usage |
+| `mail.outbox` | Check sent emails |
+
+Remember: Tests are documentation. Good tests explain how your code should work. Keep them simple, readable, and maintainable.
diff --git a/skills/django-verification/SKILL.md b/skills/django-verification/SKILL.md
new file mode 100644
index 0000000..23438e8
--- /dev/null
+++ b/skills/django-verification/SKILL.md
@@ -0,0 +1,460 @@
+---
+name: django-verification
+description: Verification loop for Django projects: migrations, linting, tests with coverage, security scans, and deployment readiness checks before release or PR.
+---
+
+# Django Verification Loop
+
+Run before PRs, after major changes, and pre-deploy to ensure Django application quality and security.
+
+## Phase 1: Environment Check
+
+```bash
+# Verify Python version
+python --version # Should match project requirements
+
+# Check virtual environment
+which python
+pip list --outdated
+
+# Verify environment variables
+python -c "import os; import environ; print('DJANGO_SECRET_KEY set' if os.environ.get('DJANGO_SECRET_KEY') else 'MISSING: DJANGO_SECRET_KEY')"
+```
+
+If environment is misconfigured, stop and fix.
+
+## Phase 2: Code Quality & Formatting
+
+```bash
+# Type checking
+mypy . --config-file pyproject.toml
+
+# Linting with ruff
+ruff check . --fix
+
+# Formatting with black
+black . --check
+black . # Auto-fix
+
+# Import sorting
+isort . --check-only
+isort . # Auto-fix
+
+# Django-specific checks
+python manage.py check --deploy
+```
+
+Common issues:
+- Missing type hints on public functions
+- PEP 8 formatting violations
+- Unsorted imports
+- Debug settings left in production configuration
+
+## Phase 3: Migrations
+
+```bash
+# Check for unapplied migrations
+python manage.py showmigrations
+
+# Create missing migrations
+python manage.py makemigrations --check
+
+# Dry-run migration application
+python manage.py migrate --plan
+
+# Apply migrations (test environment)
+python manage.py migrate
+
+# Check for migration conflicts
+python manage.py makemigrations --merge # Only if conflicts exist
+```
+
+Report:
+- Number of pending migrations
+- Any migration conflicts
+- Model changes without migrations
+
+## Phase 4: Tests + Coverage
+
+```bash
+# Run all tests with pytest
+pytest --cov=apps --cov-report=html --cov-report=term-missing --reuse-db
+
+# Run specific app tests
+pytest apps/users/tests/
+
+# Run with markers
+pytest -m "not slow" # Skip slow tests
+pytest -m integration # Only integration tests
+
+# Coverage report
+open htmlcov/index.html
+```
+
+Report:
+- Total tests: X passed, Y failed, Z skipped
+- Overall coverage: XX%
+- Per-app coverage breakdown
+
+Coverage targets:
+
+| Component | Target |
+|-----------|--------|
+| Models | 90%+ |
+| Serializers | 85%+ |
+| Views | 80%+ |
+| Services | 90%+ |
+| Overall | 80%+ |
+
+## Phase 5: Security Scan
+
+```bash
+# Dependency vulnerabilities
+pip-audit
+safety check --full-report
+
+# Django security checks
+python manage.py check --deploy
+
+# Bandit security linter
+bandit -r . -f json -o bandit-report.json
+
+# Secret scanning (if gitleaks is installed)
+gitleaks detect --source . --verbose
+
+# Environment variable check
+python -c "from django.core.exceptions import ImproperlyConfigured; from django.conf import settings; settings.DEBUG"
+```
+
+Report:
+- Vulnerable dependencies found
+- Security configuration issues
+- Hardcoded secrets detected
+- DEBUG mode status (should be False in production)
+
+## Phase 6: Django Management Commands
+
+```bash
+# Check for model issues
+python manage.py check
+
+# Collect static files
+python manage.py collectstatic --noinput --clear
+
+# Create superuser (if needed for tests)
+echo "from apps.users.models import User; User.objects.create_superuser('admin@example.com', 'admin')" | python manage.py shell
+
+# Database integrity
+python manage.py check --database default
+
+# Cache verification (if using Redis)
+python -c "from django.core.cache import cache; cache.set('test', 'value', 10); print(cache.get('test'))"
+```
+
+## Phase 7: Performance Checks
+
+```bash
+# Django Debug Toolbar output (check for N+1 queries)
+# Run in dev mode with DEBUG=True and access a page
+# Look for duplicate queries in SQL panel
+
+# Query count analysis
+django-admin debugsqlshell # If django-debug-sqlshell installed
+
+# Check for missing indexes
+python manage.py shell << EOF
+from django.db import connection
+with connection.cursor() as cursor:
+ cursor.execute("SELECT table_name, index_name FROM information_schema.statistics WHERE table_schema = 'public'")
+ print(cursor.fetchall())
+EOF
+```
+
+Report:
+- Number of queries per page (should be < 50 for typical pages)
+- Missing database indexes
+- Duplicate queries detected
+
+## Phase 8: Static Assets
+
+```bash
+# Check for npm dependencies (if using npm)
+npm audit
+npm audit fix
+
+# Build static files (if using webpack/vite)
+npm run build
+
+# Verify static files
+ls -la staticfiles/
+python manage.py findstatic css/style.css
+```
+
+## Phase 9: Configuration Review
+
+```python
+# Run in Python shell to verify settings
+python manage.py shell << EOF
+from django.conf import settings
+import os
+
+# Critical checks
+checks = {
+ 'DEBUG is False': not settings.DEBUG,
+ 'SECRET_KEY set': bool(settings.SECRET_KEY and len(settings.SECRET_KEY) > 30),
+ 'ALLOWED_HOSTS set': len(settings.ALLOWED_HOSTS) > 0,
+ 'HTTPS enabled': getattr(settings, 'SECURE_SSL_REDIRECT', False),
+ 'HSTS enabled': getattr(settings, 'SECURE_HSTS_SECONDS', 0) > 0,
+ 'Database configured': settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3',
+}
+
+for check, result in checks.items():
+ status = '✓' if result else '✗'
+ print(f"{status} {check}")
+EOF
+```
+
+## Phase 10: Logging Configuration
+
+```bash
+# Test logging output
+python manage.py shell << EOF
+import logging
+logger = logging.getLogger('django')
+logger.warning('Test warning message')
+logger.error('Test error message')
+EOF
+
+# Check log files (if configured)
+tail -f /var/log/django/django.log
+```
+
+## Phase 11: API Documentation (if DRF)
+
+```bash
+# Generate schema
+python manage.py generateschema --format openapi-json > schema.json
+
+# Validate schema
+# Check if schema.json is valid JSON
+python -c "import json; json.load(open('schema.json'))"
+
+# Access Swagger UI (if using drf-yasg)
+# Visit http://localhost:8000/swagger/ in browser
+```
+
+## Phase 12: Diff Review
+
+```bash
+# Show diff statistics
+git diff --stat
+
+# Show actual changes
+git diff
+
+# Show changed files
+git diff --name-only
+
+# Check for common issues
+git diff | grep -i "todo\|fixme\|hack\|xxx"
+git diff | grep "print(" # Debug statements
+git diff | grep "DEBUG = True" # Debug mode
+git diff | grep "import pdb" # Debugger
+```
+
+Checklist:
+- No debugging statements (print, pdb, breakpoint())
+- No TODO/FIXME comments in critical code
+- No hardcoded secrets or credentials
+- Database migrations included for model changes
+- Configuration changes documented
+- Error handling present for external calls
+- Transaction management where needed
+
+## Output Template
+
+```
+DJANGO VERIFICATION REPORT
+==========================
+
+Phase 1: Environment Check
+ ✓ Python 3.11.5
+ ✓ Virtual environment active
+ ✓ All environment variables set
+
+Phase 2: Code Quality
+ ✓ mypy: No type errors
+ ✗ ruff: 3 issues found (auto-fixed)
+ ✓ black: No formatting issues
+ ✓ isort: Imports properly sorted
+ ✓ manage.py check: No issues
+
+Phase 3: Migrations
+ ✓ No unapplied migrations
+ ✓ No migration conflicts
+ ✓ All models have migrations
+
+Phase 4: Tests + Coverage
+ Tests: 247 passed, 0 failed, 5 skipped
+ Coverage:
+ Overall: 87%
+ users: 92%
+ products: 89%
+ orders: 85%
+ payments: 91%
+
+Phase 5: Security Scan
+ ✗ pip-audit: 2 vulnerabilities found (fix required)
+ ✓ safety check: No issues
+ ✓ bandit: No security issues
+ ✓ No secrets detected
+ ✓ DEBUG = False
+
+Phase 6: Django Commands
+ ✓ collectstatic completed
+ ✓ Database integrity OK
+ ✓ Cache backend reachable
+
+Phase 7: Performance
+ ✓ No N+1 queries detected
+ ✓ Database indexes configured
+ ✓ Query count acceptable
+
+Phase 8: Static Assets
+ ✓ npm audit: No vulnerabilities
+ ✓ Assets built successfully
+ ✓ Static files collected
+
+Phase 9: Configuration
+ ✓ DEBUG = False
+ ✓ SECRET_KEY configured
+ ✓ ALLOWED_HOSTS set
+ ✓ HTTPS enabled
+ ✓ HSTS enabled
+ ✓ Database configured
+
+Phase 10: Logging
+ ✓ Logging configured
+ ✓ Log files writable
+
+Phase 11: API Documentation
+ ✓ Schema generated
+ ✓ Swagger UI accessible
+
+Phase 12: Diff Review
+ Files changed: 12
+ +450, -120 lines
+ ✓ No debug statements
+ ✓ No hardcoded secrets
+ ✓ Migrations included
+
+RECOMMENDATION: ⚠️ Fix pip-audit vulnerabilities before deploying
+
+NEXT STEPS:
+1. Update vulnerable dependencies
+2. Re-run security scan
+3. Deploy to staging for final testing
+```
+
+## Pre-Deployment Checklist
+
+- [ ] All tests passing
+- [ ] Coverage ≥ 80%
+- [ ] No security vulnerabilities
+- [ ] No unapplied migrations
+- [ ] DEBUG = False in production settings
+- [ ] SECRET_KEY properly configured
+- [ ] ALLOWED_HOSTS set correctly
+- [ ] Database backups enabled
+- [ ] Static files collected and served
+- [ ] Logging configured and working
+- [ ] Error monitoring (Sentry, etc.) configured
+- [ ] CDN configured (if applicable)
+- [ ] Redis/cache backend configured
+- [ ] Celery workers running (if applicable)
+- [ ] HTTPS/SSL configured
+- [ ] Environment variables documented
+
+## Continuous Integration
+
+### GitHub Actions Example
+
+```yaml
+# .github/workflows/django-verification.yml
+name: Django Verification
+
+on: [push, pull_request]
+
+jobs:
+ verify:
+ runs-on: ubuntu-latest
+ services:
+ postgres:
+ image: postgres:14
+ env:
+ POSTGRES_PASSWORD: postgres
+ options: >-
+ --health-cmd pg_isready
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+
+ - name: Cache pip
+ uses: actions/cache@v3
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+
+ - name: Install dependencies
+ run: |
+ pip install -r requirements.txt
+ pip install ruff black mypy pytest pytest-django pytest-cov bandit safety pip-audit
+
+ - name: Code quality checks
+ run: |
+ ruff check .
+ black . --check
+ isort . --check-only
+ mypy .
+
+ - name: Security scan
+ run: |
+ bandit -r . -f json -o bandit-report.json
+ safety check --full-report
+ pip-audit
+
+ - name: Run tests
+ env:
+ DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
+ DJANGO_SECRET_KEY: test-secret-key
+ run: |
+ pytest --cov=apps --cov-report=xml --cov-report=term-missing
+
+ - name: Upload coverage
+ uses: codecov/codecov-action@v3
+```
+
+## Quick Reference
+
+| Check | Command |
+|-------|---------|
+| Environment | `python --version` |
+| Type checking | `mypy .` |
+| Linting | `ruff check .` |
+| Formatting | `black . --check` |
+| Migrations | `python manage.py makemigrations --check` |
+| Tests | `pytest --cov=apps` |
+| Security | `pip-audit && bandit -r .` |
+| Django check | `python manage.py check --deploy` |
+| Collectstatic | `python manage.py collectstatic --noinput` |
+| Diff stats | `git diff --stat` |
+
+Remember: Automated verification catches common issues but doesn't replace manual code review and testing in staging environment.
diff --git a/skills/python-patterns/SKILL.md b/skills/python-patterns/SKILL.md
new file mode 100644
index 0000000..c86e4d4
--- /dev/null
+++ b/skills/python-patterns/SKILL.md
@@ -0,0 +1,749 @@
+---
+name: python-patterns
+description: Pythonic idioms, PEP 8 standards, type hints, and best practices for building robust, efficient, and maintainable Python applications.
+---
+
+# Python Development Patterns
+
+Idiomatic Python patterns and best practices for building robust, efficient, and maintainable applications.
+
+## When to Activate
+
+- Writing new Python code
+- Reviewing Python code
+- Refactoring existing Python code
+- Designing Python packages/modules
+
+## Core Principles
+
+### 1. Readability Counts
+
+Python prioritizes readability. Code should be obvious and easy to understand.
+
+```python
+# Good: Clear and readable
+def get_active_users(users: list[User]) -> list[User]:
+ """Return only active users from the provided list."""
+ return [user for user in users if user.is_active]
+
+
+# Bad: Clever but confusing
+def get_active_users(u):
+ return [x for x in u if x.a]
+```
+
+### 2. Explicit is Better Than Implicit
+
+Avoid magic; be clear about what your code does.
+
+```python
+# Good: Explicit configuration
+import logging
+
+logging.basicConfig(
+ level=logging.INFO,
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+)
+
+# Bad: Hidden side effects
+import some_module
+some_module.setup() # What does this do?
+```
+
+### 3. EAFP - Easier to Ask Forgiveness Than Permission
+
+Python prefers exception handling over checking conditions.
+
+```python
+# Good: EAFP style
+def get_value(dictionary: dict, key: str) -> Any:
+ try:
+ return dictionary[key]
+ except KeyError:
+ return default_value
+
+# Bad: LBYL (Look Before You Leap) style
+def get_value(dictionary: dict, key: str) -> Any:
+ if key in dictionary:
+ return dictionary[key]
+ else:
+ return default_value
+```
+
+## Type Hints
+
+### Basic Type Annotations
+
+```python
+from typing import Optional, List, Dict, Any
+
+def process_user(
+ user_id: str,
+ data: Dict[str, Any],
+ active: bool = True
+) -> Optional[User]:
+ """Process a user and return the updated User or None."""
+ if not active:
+ return None
+ return User(user_id, data)
+```
+
+### Modern Type Hints (Python 3.9+)
+
+```python
+# Python 3.9+ - Use built-in types
+def process_items(items: list[str]) -> dict[str, int]:
+ return {item: len(item) for item in items}
+
+# Python 3.8 and earlier - Use typing module
+from typing import List, Dict
+
+def process_items(items: List[str]) -> Dict[str, int]:
+ return {item: len(item) for item in items}
+```
+
+### Type Aliases and TypeVar
+
+```python
+from typing import TypeVar, Union
+
+# Type alias for complex types
+JSON = Union[dict[str, Any], list[Any], str, int, float, bool, None]
+
+def parse_json(data: str) -> JSON:
+ return json.loads(data)
+
+# Generic types
+T = TypeVar('T')
+
+def first(items: list[T]) -> T | None:
+ """Return the first item or None if list is empty."""
+ return items[0] if items else None
+```
+
+### Protocol-Based Duck Typing
+
+```python
+from typing import Protocol
+
+class Renderable(Protocol):
+ def render(self) -> str:
+ """Render the object to a string."""
+
+def render_all(items: list[Renderable]) -> str:
+ """Render all items that implement the Renderable protocol."""
+ return "\n".join(item.render() for item in items)
+```
+
+## Error Handling Patterns
+
+### Specific Exception Handling
+
+```python
+# Good: Catch specific exceptions
+def load_config(path: str) -> Config:
+ try:
+ with open(path) as f:
+ return Config.from_json(f.read())
+ except FileNotFoundError as e:
+ raise ConfigError(f"Config file not found: {path}") from e
+ except json.JSONDecodeError as e:
+ raise ConfigError(f"Invalid JSON in config: {path}") from e
+
+# Bad: Bare except
+def load_config(path: str) -> Config:
+ try:
+ with open(path) as f:
+ return Config.from_json(f.read())
+ except:
+ return None # Silent failure!
+```
+
+### Exception Chaining
+
+```python
+def process_data(data: str) -> Result:
+ try:
+ parsed = json.loads(data)
+ except json.JSONDecodeError as e:
+ # Chain exceptions to preserve the traceback
+ raise ValueError(f"Failed to parse data: {data}") from e
+```
+
+### Custom Exception Hierarchy
+
+```python
+class AppError(Exception):
+ """Base exception for all application errors."""
+ pass
+
+class ValidationError(AppError):
+ """Raised when input validation fails."""
+ pass
+
+class NotFoundError(AppError):
+ """Raised when a requested resource is not found."""
+ pass
+
+# Usage
+def get_user(user_id: str) -> User:
+ user = db.find_user(user_id)
+ if not user:
+ raise NotFoundError(f"User not found: {user_id}")
+ return user
+```
+
+## Context Managers
+
+### Resource Management
+
+```python
+# Good: Using context managers
+def process_file(path: str) -> str:
+ with open(path, 'r') as f:
+ return f.read()
+
+# Bad: Manual resource management
+def process_file(path: str) -> str:
+ f = open(path, 'r')
+ try:
+ return f.read()
+ finally:
+ f.close()
+```
+
+### Custom Context Managers
+
+```python
+from contextlib import contextmanager
+
+@contextmanager
+def timer(name: str):
+ """Context manager to time a block of code."""
+ start = time.perf_counter()
+ yield
+ elapsed = time.perf_counter() - start
+ print(f"{name} took {elapsed:.4f} seconds")
+
+# Usage
+with timer("data processing"):
+ process_large_dataset()
+```
+
+### Context Manager Classes
+
+```python
+class DatabaseTransaction:
+ def __init__(self, connection):
+ self.connection = connection
+
+ def __enter__(self):
+ self.connection.begin_transaction()
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if exc_type is None:
+ self.connection.commit()
+ else:
+ self.connection.rollback()
+ return False # Don't suppress exceptions
+
+# Usage
+with DatabaseTransaction(conn):
+ user = conn.create_user(user_data)
+ conn.create_profile(user.id, profile_data)
+```
+
+## Comprehensions and Generators
+
+### List Comprehensions
+
+```python
+# Good: List comprehension for simple transformations
+names = [user.name for user in users if user.is_active]
+
+# Bad: Manual loop
+names = []
+for user in users:
+ if user.is_active:
+ names.append(user.name)
+
+# Complex comprehensions should be expanded
+# Bad: Too complex
+result = [x * 2 for x in items if x > 0 if x % 2 == 0]
+
+# Good: Use a generator function
+def filter_and_transform(items: Iterable[int]) -> list[int]:
+ result = []
+ for x in items:
+ if x > 0 and x % 2 == 0:
+ result.append(x * 2)
+ return result
+```
+
+### Generator Expressions
+
+```python
+# Good: Generator for lazy evaluation
+total = sum(x * x for x in range(1_000_000))
+
+# Bad: Creates large intermediate list
+total = sum([x * x for x in range(1_000_000)])
+```
+
+### Generator Functions
+
+```python
+def read_large_file(path: str) -> Iterator[str]:
+ """Read a large file line by line."""
+ with open(path) as f:
+ for line in f:
+ yield line.strip()
+
+# Usage
+for line in read_large_file("huge.txt"):
+ process(line)
+```
+
+## Data Classes and Named Tuples
+
+### Data Classes
+
+```python
+from dataclasses import dataclass, field
+from datetime import datetime
+
+@dataclass
+class User:
+ """User entity with automatic __init__, __repr__, and __eq__."""
+ id: str
+ name: str
+ email: str
+ created_at: datetime = field(default_factory=datetime.now)
+ is_active: bool = True
+
+# Usage
+user = User(
+ id="123",
+ name="Alice",
+ email="alice@example.com"
+)
+```
+
+### Data Classes with Validation
+
+```python
+@dataclass
+class User:
+ email: str
+ age: int
+
+ def __post_init__(self):
+ # Validate email format
+ if "@" not in self.email:
+ raise ValueError(f"Invalid email: {self.email}")
+ # Validate age range
+ if self.age < 0 or self.age > 150:
+ raise ValueError(f"Invalid age: {self.age}")
+```
+
+### Named Tuples
+
+```python
+from typing import NamedTuple
+
+class Point(NamedTuple):
+ """Immutable 2D point."""
+ x: float
+ y: float
+
+ def distance(self, other: 'Point') -> float:
+ return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
+
+# Usage
+p1 = Point(0, 0)
+p2 = Point(3, 4)
+print(p1.distance(p2)) # 5.0
+```
+
+## Decorators
+
+### Function Decorators
+
+```python
+import functools
+import time
+
+def timer(func: Callable) -> Callable:
+ """Decorator to time function execution."""
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ start = time.perf_counter()
+ result = func(*args, **kwargs)
+ elapsed = time.perf_counter() - start
+ print(f"{func.__name__} took {elapsed:.4f}s")
+ return result
+ return wrapper
+
+@timer
+def slow_function():
+ time.sleep(1)
+
+# slow_function() prints: slow_function took 1.0012s
+```
+
+### Parameterized Decorators
+
+```python
+def repeat(times: int):
+ """Decorator to repeat a function multiple times."""
+ def decorator(func: Callable) -> Callable:
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ results = []
+ for _ in range(times):
+ results.append(func(*args, **kwargs))
+ return results
+ return wrapper
+ return decorator
+
+@repeat(times=3)
+def greet(name: str) -> str:
+ return f"Hello, {name}!"
+
+# greet("Alice") returns ["Hello, Alice!", "Hello, Alice!", "Hello, Alice!"]
+```
+
+### Class-Based Decorators
+
+```python
+class CountCalls:
+ """Decorator that counts how many times a function is called."""
+ def __init__(self, func: Callable):
+ functools.update_wrapper(self, func)
+ self.func = func
+ self.count = 0
+
+ def __call__(self, *args, **kwargs):
+ self.count += 1
+ print(f"{self.func.__name__} has been called {self.count} times")
+ return self.func(*args, **kwargs)
+
+@CountCalls
+def process():
+ pass
+
+# Each call to process() prints the call count
+```
+
+## Concurrency Patterns
+
+### Threading for I/O-Bound Tasks
+
+```python
+import concurrent.futures
+import threading
+
+def fetch_url(url: str) -> str:
+ """Fetch a URL (I/O-bound operation)."""
+ import urllib.request
+ with urllib.request.urlopen(url) as response:
+ return response.read().decode()
+
+def fetch_all_urls(urls: list[str]) -> dict[str, str]:
+ """Fetch multiple URLs concurrently using threads."""
+ with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
+ future_to_url = {executor.submit(fetch_url, url): url for url in urls}
+ results = {}
+ for future in concurrent.futures.as_completed(future_to_url):
+ url = future_to_url[future]
+ try:
+ results[url] = future.result()
+ except Exception as e:
+ results[url] = f"Error: {e}"
+ return results
+```
+
+### Multiprocessing for CPU-Bound Tasks
+
+```python
+def process_data(data: list[int]) -> int:
+ """CPU-intensive computation."""
+ return sum(x ** 2 for x in data)
+
+def process_all(datasets: list[list[int]]) -> list[int]:
+ """Process multiple datasets using multiple processes."""
+ with concurrent.futures.ProcessPoolExecutor() as executor:
+ results = list(executor.map(process_data, datasets))
+ return results
+```
+
+### Async/Await for Concurrent I/O
+
+```python
+import asyncio
+
+async def fetch_async(url: str) -> str:
+ """Fetch a URL asynchronously."""
+ import aiohttp
+ async with aiohttp.ClientSession() as session:
+ async with session.get(url) as response:
+ return await response.text()
+
+async def fetch_all(urls: list[str]) -> dict[str, str]:
+ """Fetch multiple URLs concurrently."""
+ tasks = [fetch_async(url) for url in urls]
+ results = await asyncio.gather(*tasks, return_exceptions=True)
+ return dict(zip(urls, results))
+```
+
+## Package Organization
+
+### Standard Project Layout
+
+```
+myproject/
+├── src/
+│ └── mypackage/
+│ ├── __init__.py
+│ ├── main.py
+│ ├── api/
+│ │ ├── __init__.py
+│ │ └── routes.py
+│ ├── models/
+│ │ ├── __init__.py
+│ │ └── user.py
+│ └── utils/
+│ ├── __init__.py
+│ └── helpers.py
+├── tests/
+│ ├── __init__.py
+│ ├── conftest.py
+│ ├── test_api.py
+│ └── test_models.py
+├── pyproject.toml
+├── README.md
+└── .gitignore
+```
+
+### Import Conventions
+
+```python
+# Good: Import order - stdlib, third-party, local
+import os
+import sys
+from pathlib import Path
+
+import requests
+from fastapi import FastAPI
+
+from mypackage.models import User
+from mypackage.utils import format_name
+
+# Good: Use isort for automatic import sorting
+# pip install isort
+```
+
+### __init__.py for Package Exports
+
+```python
+# mypackage/__init__.py
+"""mypackage - A sample Python package."""
+
+__version__ = "1.0.0"
+
+# Export main classes/functions at package level
+from mypackage.models import User, Post
+from mypackage.utils import format_name
+
+__all__ = ["User", "Post", "format_name"]
+```
+
+## Memory and Performance
+
+### Using __slots__ for Memory Efficiency
+
+```python
+# Bad: Regular class uses __dict__ (more memory)
+class Point:
+ def __init__(self, x: float, y: float):
+ self.x = x
+ self.y = y
+
+# Good: __slots__ reduces memory usage
+class Point:
+ __slots__ = ['x', 'y']
+
+ def __init__(self, x: float, y: float):
+ self.x = x
+ self.y = y
+```
+
+### Generator for Large Data
+
+```python
+# Bad: Returns full list in memory
+def read_lines(path: str) -> list[str]:
+ with open(path) as f:
+ return [line.strip() for line in f]
+
+# Good: Yields lines one at a time
+def read_lines(path: str) -> Iterator[str]:
+ with open(path) as f:
+ for line in f:
+ yield line.strip()
+```
+
+### Avoid String Concatenation in Loops
+
+```python
+# Bad: O(n²) due to string immutability
+result = ""
+for item in items:
+ result += str(item)
+
+# Good: O(n) using join
+result = "".join(str(item) for item in items)
+
+# Good: Using StringIO for building
+from io import StringIO
+
+buffer = StringIO()
+for item in items:
+ buffer.write(str(item))
+result = buffer.getvalue()
+```
+
+## Python Tooling Integration
+
+### Essential Commands
+
+```bash
+# Code formatting
+black .
+isort .
+
+# Linting
+ruff check .
+pylint mypackage/
+
+# Type checking
+mypy .
+
+# Testing
+pytest --cov=mypackage --cov-report=html
+
+# Security scanning
+bandit -r .
+
+# Dependency management
+pip-audit
+safety check
+```
+
+### pyproject.toml Configuration
+
+```toml
+[project]
+name = "mypackage"
+version = "1.0.0"
+requires-python = ">=3.9"
+dependencies = [
+ "requests>=2.31.0",
+ "pydantic>=2.0.0",
+]
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=7.4.0",
+ "pytest-cov>=4.1.0",
+ "black>=23.0.0",
+ "ruff>=0.1.0",
+ "mypy>=1.5.0",
+]
+
+[tool.black]
+line-length = 88
+target-version = ['py39']
+
+[tool.ruff]
+line-length = 88
+select = ["E", "F", "I", "N", "W"]
+
+[tool.mypy]
+python_version = "3.9"
+warn_return_any = true
+warn_unused_configs = true
+disallow_untyped_defs = true
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+addopts = "--cov=mypackage --cov-report=term-missing"
+```
+
+## Quick Reference: Python Idioms
+
+| Idiom | Description |
+|-------|-------------|
+| EAFP | Easier to Ask Forgiveness than Permission |
+| Context managers | Use `with` for resource management |
+| List comprehensions | For simple transformations |
+| Generators | For lazy evaluation and large datasets |
+| Type hints | Annotate function signatures |
+| Dataclasses | For data containers with auto-generated methods |
+| `__slots__` | For memory optimization |
+| f-strings | For string formatting (Python 3.6+) |
+| `pathlib.Path` | For path operations (Python 3.4+) |
+| `enumerate` | For index-element pairs in loops |
+
+## Anti-Patterns to Avoid
+
+```python
+# Bad: Mutable default arguments
+def append_to(item, items=[]):
+ items.append(item)
+ return items
+
+# Good: Use None and create new list
+def append_to(item, items=None):
+ if items is None:
+ items = []
+ items.append(item)
+ return items
+
+# Bad: Checking type with type()
+if type(obj) == list:
+ process(obj)
+
+# Good: Use isinstance
+if isinstance(obj, list):
+ process(obj)
+
+# Bad: Comparing to None with ==
+if value == None:
+ process()
+
+# Good: Use is
+if value is None:
+ process()
+
+# Bad: from module import *
+from os.path import *
+
+# Good: Explicit imports
+from os.path import join, exists
+
+# Bad: Bare except
+try:
+ risky_operation()
+except:
+ pass
+
+# Good: Specific exception
+try:
+ risky_operation()
+except SpecificError as e:
+ logger.error(f"Operation failed: {e}")
+```
+
+__Remember__: Python code should be readable, explicit, and follow the principle of least surprise. When in doubt, prioritize clarity over cleverness.
diff --git a/skills/python-testing/SKILL.md b/skills/python-testing/SKILL.md
new file mode 100644
index 0000000..8b10024
--- /dev/null
+++ b/skills/python-testing/SKILL.md
@@ -0,0 +1,815 @@
+---
+name: python-testing
+description: Python testing strategies using pytest, TDD methodology, fixtures, mocking, parametrization, and coverage requirements.
+---
+
+# Python Testing Patterns
+
+Comprehensive testing strategies for Python applications using pytest, TDD methodology, and best practices.
+
+## When to Activate
+
+- Writing new Python code (follow TDD: red, green, refactor)
+- Designing test suites for Python projects
+- Reviewing Python test coverage
+- Setting up testing infrastructure
+
+## Core Testing Philosophy
+
+### Test-Driven Development (TDD)
+
+Always follow the TDD cycle:
+
+1. **RED**: Write a failing test for the desired behavior
+2. **GREEN**: Write minimal code to make the test pass
+3. **REFACTOR**: Improve code while keeping tests green
+
+```python
+# Step 1: Write failing test (RED)
+def test_add_numbers():
+ result = add(2, 3)
+ assert result == 5
+
+# Step 2: Write minimal implementation (GREEN)
+def add(a, b):
+ return a + b
+
+# Step 3: Refactor if needed (REFACTOR)
+```
+
+### Coverage Requirements
+
+- **Target**: 80%+ code coverage
+- **Critical paths**: 100% coverage required
+- Use `pytest --cov` to measure coverage
+
+```bash
+pytest --cov=mypackage --cov-report=term-missing --cov-report=html
+```
+
+## pytest Fundamentals
+
+### Basic Test Structure
+
+```python
+import pytest
+
+def test_addition():
+ """Test basic addition."""
+ assert 2 + 2 == 4
+
+def test_string_uppercase():
+ """Test string uppercasing."""
+ text = "hello"
+ assert text.upper() == "HELLO"
+
+def test_list_append():
+ """Test list append."""
+ items = [1, 2, 3]
+ items.append(4)
+ assert 4 in items
+ assert len(items) == 4
+```
+
+### Assertions
+
+```python
+# Equality
+assert result == expected
+
+# Inequality
+assert result != unexpected
+
+# Truthiness
+assert result # Truthy
+assert not result # Falsy
+assert result is True # Exactly True
+assert result is False # Exactly False
+assert result is None # Exactly None
+
+# Membership
+assert item in collection
+assert item not in collection
+
+# Comparisons
+assert result > 0
+assert 0 <= result <= 100
+
+# Type checking
+assert isinstance(result, str)
+
+# Exception testing (preferred approach)
+with pytest.raises(ValueError):
+ raise ValueError("error message")
+
+# Check exception message
+with pytest.raises(ValueError, match="invalid input"):
+ raise ValueError("invalid input provided")
+
+# Check exception attributes
+with pytest.raises(ValueError) as exc_info:
+ raise ValueError("error message")
+assert str(exc_info.value) == "error message"
+```
+
+## Fixtures
+
+### Basic Fixture Usage
+
+```python
+import pytest
+
+@pytest.fixture
+def sample_data():
+ """Fixture providing sample data."""
+ return {"name": "Alice", "age": 30}
+
+def test_sample_data(sample_data):
+ """Test using the fixture."""
+ assert sample_data["name"] == "Alice"
+ assert sample_data["age"] == 30
+```
+
+### Fixture with Setup/Teardown
+
+```python
+@pytest.fixture
+def database():
+ """Fixture with setup and teardown."""
+ # Setup
+ db = Database(":memory:")
+ db.create_tables()
+ db.insert_test_data()
+
+ yield db # Provide to test
+
+ # Teardown
+ db.close()
+
+def test_database_query(database):
+ """Test database operations."""
+ result = database.query("SELECT * FROM users")
+ assert len(result) > 0
+```
+
+### Fixture Scopes
+
+```python
+# Function scope (default) - runs for each test
+@pytest.fixture
+def temp_file():
+ with open("temp.txt", "w") as f:
+ yield f
+ os.remove("temp.txt")
+
+# Module scope - runs once per module
+@pytest.fixture(scope="module")
+def module_db():
+ db = Database(":memory:")
+ db.create_tables()
+ yield db
+ db.close()
+
+# Session scope - runs once per test session
+@pytest.fixture(scope="session")
+def shared_resource():
+ resource = ExpensiveResource()
+ yield resource
+ resource.cleanup()
+```
+
+### Fixture with Parameters
+
+```python
+@pytest.fixture(params=[1, 2, 3])
+def number(request):
+ """Parameterized fixture."""
+ return request.param
+
+def test_numbers(number):
+ """Test runs 3 times, once for each parameter."""
+ assert number > 0
+```
+
+### Using Multiple Fixtures
+
+```python
+@pytest.fixture
+def user():
+ return User(id=1, name="Alice")
+
+@pytest.fixture
+def admin():
+ return User(id=2, name="Admin", role="admin")
+
+def test_user_admin_interaction(user, admin):
+ """Test using multiple fixtures."""
+ assert admin.can_manage(user)
+```
+
+### Autouse Fixtures
+
+```python
+@pytest.fixture(autouse=True)
+def reset_config():
+ """Automatically runs before every test."""
+ Config.reset()
+ yield
+ Config.cleanup()
+
+def test_without_fixture_call():
+ # reset_config runs automatically
+ assert Config.get_setting("debug") is False
+```
+
+### Conftest.py for Shared Fixtures
+
+```python
+# tests/conftest.py
+import pytest
+
+@pytest.fixture
+def client():
+ """Shared fixture for all tests."""
+ app = create_app(testing=True)
+ with app.test_client() as client:
+ yield client
+
+@pytest.fixture
+def auth_headers(client):
+ """Generate auth headers for API testing."""
+ response = client.post("/api/login", json={
+ "username": "test",
+ "password": "test"
+ })
+ token = response.json["token"]
+ return {"Authorization": f"Bearer {token}"}
+```
+
+## Parametrization
+
+### Basic Parametrization
+
+```python
+@pytest.mark.parametrize("input,expected", [
+ ("hello", "HELLO"),
+ ("world", "WORLD"),
+ ("PyThOn", "PYTHON"),
+])
+def test_uppercase(input, expected):
+ """Test runs 3 times with different inputs."""
+ assert input.upper() == expected
+```
+
+### Multiple Parameters
+
+```python
+@pytest.mark.parametrize("a,b,expected", [
+ (2, 3, 5),
+ (0, 0, 0),
+ (-1, 1, 0),
+ (100, 200, 300),
+])
+def test_add(a, b, expected):
+ """Test addition with multiple inputs."""
+ assert add(a, b) == expected
+```
+
+### Parametrize with IDs
+
+```python
+@pytest.mark.parametrize("input,expected", [
+ ("valid@email.com", True),
+ ("invalid", False),
+ ("@no-domain.com", False),
+], ids=["valid-email", "missing-at", "missing-domain"])
+def test_email_validation(input, expected):
+ """Test email validation with readable test IDs."""
+ assert is_valid_email(input) is expected
+```
+
+### Parametrized Fixtures
+
+```python
+@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
+def db(request):
+ """Test against multiple database backends."""
+ if request.param == "sqlite":
+ return Database(":memory:")
+ elif request.param == "postgresql":
+ return Database("postgresql://localhost/test")
+ elif request.param == "mysql":
+ return Database("mysql://localhost/test")
+
+def test_database_operations(db):
+ """Test runs 3 times, once for each database."""
+ result = db.query("SELECT 1")
+ assert result is not None
+```
+
+## Markers and Test Selection
+
+### Custom Markers
+
+```python
+# Mark slow tests
+@pytest.mark.slow
+def test_slow_operation():
+ time.sleep(5)
+
+# Mark integration tests
+@pytest.mark.integration
+def test_api_integration():
+ response = requests.get("https://api.example.com")
+ assert response.status_code == 200
+
+# Mark unit tests
+@pytest.mark.unit
+def test_unit_logic():
+ assert calculate(2, 3) == 5
+```
+
+### Run Specific Tests
+
+```bash
+# Run only fast tests
+pytest -m "not slow"
+
+# Run only integration tests
+pytest -m integration
+
+# Run integration or slow tests
+pytest -m "integration or slow"
+
+# Run tests marked as unit but not slow
+pytest -m "unit and not slow"
+```
+
+### Configure Markers in pytest.ini
+
+```ini
+[pytest]
+markers =
+ slow: marks tests as slow
+ integration: marks tests as integration tests
+ unit: marks tests as unit tests
+ django: marks tests as requiring Django
+```
+
+## Mocking and Patching
+
+### Mocking Functions
+
+```python
+from unittest.mock import patch, Mock
+
+@patch("mypackage.external_api_call")
+def test_with_mock(api_call_mock):
+ """Test with mocked external API."""
+ api_call_mock.return_value = {"status": "success"}
+
+ result = my_function()
+
+ api_call_mock.assert_called_once()
+ assert result["status"] == "success"
+```
+
+### Mocking Return Values
+
+```python
+@patch("mypackage.Database.connect")
+def test_database_connection(connect_mock):
+ """Test with mocked database connection."""
+ connect_mock.return_value = MockConnection()
+
+ db = Database()
+ db.connect()
+
+ connect_mock.assert_called_once_with("localhost")
+```
+
+### Mocking Exceptions
+
+```python
+@patch("mypackage.api_call")
+def test_api_error_handling(api_call_mock):
+ """Test error handling with mocked exception."""
+ api_call_mock.side_effect = ConnectionError("Network error")
+
+ with pytest.raises(ConnectionError):
+ api_call()
+
+ api_call_mock.assert_called_once()
+```
+
+### Mocking Context Managers
+
+```python
+@patch("builtins.open", new_callable=mock_open)
+def test_file_reading(mock_file):
+ """Test file reading with mocked open."""
+ mock_file.return_value.read.return_value = "file content"
+
+ result = read_file("test.txt")
+
+ mock_file.assert_called_once_with("test.txt", "r")
+ assert result == "file content"
+```
+
+### Using Autospec
+
+```python
+@patch("mypackage.DBConnection", autospec=True)
+def test_autospec(db_mock):
+ """Test with autospec to catch API misuse."""
+ db = db_mock.return_value
+ db.query("SELECT * FROM users")
+
+ # This would fail if DBConnection doesn't have query method
+ db_mock.assert_called_once()
+```
+
+### Mock Class Instances
+
+```python
+class TestUserService:
+ @patch("mypackage.UserRepository")
+ def test_create_user(self, repo_mock):
+ """Test user creation with mocked repository."""
+ repo_mock.return_value.save.return_value = User(id=1, name="Alice")
+
+ service = UserService(repo_mock.return_value)
+ user = service.create_user(name="Alice")
+
+ assert user.name == "Alice"
+ repo_mock.return_value.save.assert_called_once()
+```
+
+### Mock Property
+
+```python
+@pytest.fixture
+def mock_config():
+ """Create a mock with a property."""
+ config = Mock()
+ type(config).debug = PropertyMock(return_value=True)
+ type(config).api_key = PropertyMock(return_value="test-key")
+ return config
+
+def test_with_mock_config(mock_config):
+ """Test with mocked config properties."""
+ assert mock_config.debug is True
+ assert mock_config.api_key == "test-key"
+```
+
+## Testing Async Code
+
+### Async Tests with pytest-asyncio
+
+```python
+import pytest
+
+@pytest.mark.asyncio
+async def test_async_function():
+ """Test async function."""
+ result = await async_add(2, 3)
+ assert result == 5
+
+@pytest.mark.asyncio
+async def test_async_with_fixture(async_client):
+ """Test async with async fixture."""
+ response = await async_client.get("/api/users")
+ assert response.status_code == 200
+```
+
+### Async Fixture
+
+```python
+@pytest.fixture
+async def async_client():
+ """Async fixture providing async test client."""
+ app = create_app()
+ async with app.test_client() as client:
+ yield client
+
+@pytest.mark.asyncio
+async def test_api_endpoint(async_client):
+ """Test using async fixture."""
+ response = await async_client.get("/api/data")
+ assert response.status_code == 200
+```
+
+### Mocking Async Functions
+
+```python
+@pytest.mark.asyncio
+@patch("mypackage.async_api_call")
+async def test_async_mock(api_call_mock):
+ """Test async function with mock."""
+ api_call_mock.return_value = {"status": "ok"}
+
+ result = await my_async_function()
+
+ api_call_mock.assert_awaited_once()
+ assert result["status"] == "ok"
+```
+
+## Testing Exceptions
+
+### Testing Expected Exceptions
+
+```python
+def test_divide_by_zero():
+ """Test that dividing by zero raises ZeroDivisionError."""
+ with pytest.raises(ZeroDivisionError):
+ divide(10, 0)
+
+def test_custom_exception():
+ """Test custom exception with message."""
+ with pytest.raises(ValueError, match="invalid input"):
+ validate_input("invalid")
+```
+
+### Testing Exception Attributes
+
+```python
+def test_exception_with_details():
+ """Test exception with custom attributes."""
+ with pytest.raises(CustomError) as exc_info:
+ raise CustomError("error", code=400)
+
+ assert exc_info.value.code == 400
+ assert "error" in str(exc_info.value)
+```
+
+## Testing Side Effects
+
+### Testing File Operations
+
+```python
+import tempfile
+import os
+
+def test_file_processing():
+ """Test file processing with temp file."""
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
+ f.write("test content")
+ temp_path = f.name
+
+ try:
+ result = process_file(temp_path)
+ assert result == "processed: test content"
+ finally:
+ os.unlink(temp_path)
+```
+
+### Testing with pytest's tmp_path Fixture
+
+```python
+def test_with_tmp_path(tmp_path):
+ """Test using pytest's built-in temp path fixture."""
+ test_file = tmp_path / "test.txt"
+ test_file.write_text("hello world")
+
+ result = process_file(str(test_file))
+ assert result == "hello world"
+ # tmp_path automatically cleaned up
+```
+
+### Testing with tmpdir Fixture
+
+```python
+def test_with_tmpdir(tmpdir):
+ """Test using pytest's tmpdir fixture."""
+ test_file = tmpdir.join("test.txt")
+ test_file.write("data")
+
+ result = process_file(str(test_file))
+ assert result == "data"
+```
+
+## Test Organization
+
+### Directory Structure
+
+```
+tests/
+├── conftest.py # Shared fixtures
+├── __init__.py
+├── unit/ # Unit tests
+│ ├── __init__.py
+│ ├── test_models.py
+│ ├── test_utils.py
+│ └── test_services.py
+├── integration/ # Integration tests
+│ ├── __init__.py
+│ ├── test_api.py
+│ └── test_database.py
+└── e2e/ # End-to-end tests
+ ├── __init__.py
+ └── test_user_flow.py
+```
+
+### Test Classes
+
+```python
+class TestUserService:
+ """Group related tests in a class."""
+
+ @pytest.fixture(autouse=True)
+ def setup(self):
+ """Setup runs before each test in this class."""
+ self.service = UserService()
+
+ def test_create_user(self):
+ """Test user creation."""
+ user = self.service.create_user("Alice")
+ assert user.name == "Alice"
+
+ def test_delete_user(self):
+ """Test user deletion."""
+ user = User(id=1, name="Bob")
+ self.service.delete_user(user)
+ assert not self.service.user_exists(1)
+```
+
+## Best Practices
+
+### DO
+
+- **Follow TDD**: Write tests before code (red-green-refactor)
+- **Test one thing**: Each test should verify a single behavior
+- **Use descriptive names**: `test_user_login_with_invalid_credentials_fails`
+- **Use fixtures**: Eliminate duplication with fixtures
+- **Mock external dependencies**: Don't depend on external services
+- **Test edge cases**: Empty inputs, None values, boundary conditions
+- **Aim for 80%+ coverage**: Focus on critical paths
+- **Keep tests fast**: Use marks to separate slow tests
+
+### DON'T
+
+- **Don't test implementation**: Test behavior, not internals
+- **Don't use complex conditionals in tests**: Keep tests simple
+- **Don't ignore test failures**: All tests must pass
+- **Don't test third-party code**: Trust libraries to work
+- **Don't share state between tests**: Tests should be independent
+- **Don't catch exceptions in tests**: Use `pytest.raises`
+- **Don't use print statements**: Use assertions and pytest output
+- **Don't write tests that are too brittle**: Avoid over-specific mocks
+
+## Common Patterns
+
+### Testing API Endpoints (FastAPI/Flask)
+
+```python
+@pytest.fixture
+def client():
+ app = create_app(testing=True)
+ return app.test_client()
+
+def test_get_user(client):
+ response = client.get("/api/users/1")
+ assert response.status_code == 200
+ assert response.json["id"] == 1
+
+def test_create_user(client):
+ response = client.post("/api/users", json={
+ "name": "Alice",
+ "email": "alice@example.com"
+ })
+ assert response.status_code == 201
+ assert response.json["name"] == "Alice"
+```
+
+### Testing Database Operations
+
+```python
+@pytest.fixture
+def db_session():
+ """Create a test database session."""
+ session = Session(bind=engine)
+ session.begin_nested()
+ yield session
+ session.rollback()
+ session.close()
+
+def test_create_user(db_session):
+ user = User(name="Alice", email="alice@example.com")
+ db_session.add(user)
+ db_session.commit()
+
+ retrieved = db_session.query(User).filter_by(name="Alice").first()
+ assert retrieved.email == "alice@example.com"
+```
+
+### Testing Class Methods
+
+```python
+class TestCalculator:
+ @pytest.fixture
+ def calculator(self):
+ return Calculator()
+
+ def test_add(self, calculator):
+ assert calculator.add(2, 3) == 5
+
+ def test_divide_by_zero(self, calculator):
+ with pytest.raises(ZeroDivisionError):
+ calculator.divide(10, 0)
+```
+
+## pytest Configuration
+
+### pytest.ini
+
+```ini
+[pytest]
+testpaths = tests
+python_files = test_*.py
+python_classes = Test*
+python_functions = test_*
+addopts =
+ --strict-markers
+ --disable-warnings
+ --cov=mypackage
+ --cov-report=term-missing
+ --cov-report=html
+markers =
+ slow: marks tests as slow
+ integration: marks tests as integration tests
+ unit: marks tests as unit tests
+```
+
+### pyproject.toml
+
+```toml
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = ["test_*.py"]
+python_classes = ["Test*"]
+python_functions = ["test_*"]
+addopts = [
+ "--strict-markers",
+ "--cov=mypackage",
+ "--cov-report=term-missing",
+ "--cov-report=html",
+]
+markers = [
+ "slow: marks tests as slow",
+ "integration: marks tests as integration tests",
+ "unit: marks tests as unit tests",
+]
+```
+
+## Running Tests
+
+```bash
+# Run all tests
+pytest
+
+# Run specific file
+pytest tests/test_utils.py
+
+# Run specific test
+pytest tests/test_utils.py::test_function
+
+# Run with verbose output
+pytest -v
+
+# Run with coverage
+pytest --cov=mypackage --cov-report=html
+
+# Run only fast tests
+pytest -m "not slow"
+
+# Run until first failure
+pytest -x
+
+# Run and stop on N failures
+pytest --maxfail=3
+
+# Run last failed tests
+pytest --lf
+
+# Run tests with pattern
+pytest -k "test_user"
+
+# Run with debugger on failure
+pytest --pdb
+```
+
+## Quick Reference
+
+| Pattern | Usage |
+|---------|-------|
+| `pytest.raises()` | Test expected exceptions |
+| `@pytest.fixture()` | Create reusable test fixtures |
+| `@pytest.mark.parametrize()` | Run tests with multiple inputs |
+| `@pytest.mark.slow` | Mark slow tests |
+| `pytest -m "not slow"` | Skip slow tests |
+| `@patch()` | Mock functions and classes |
+| `tmp_path` fixture | Automatic temp directory |
+| `pytest --cov` | Generate coverage report |
+| `assert` | Simple and readable assertions |
+
+**Remember**: Tests are code too. Keep them clean, readable, and maintainable. Good tests catch bugs; great tests prevent them.