SKILL.md
Python Development Skill
Modern Python Standards (3.10+)
Type Hints and Annotations
from typing import Protocol, TypeVar, Generic
from collections.abc import Sequence, Mapping
from pathlib import Path
# Modern union syntax (3.10+)
def process_data(data: str | int | None) -> str:
match data:
case str() if data.strip():
return f"String: {data}"
case int() if data > 0:
return f"Positive int: {data}"
case int() if data <= 0:
return f"Non-positive int: {data}"
case None:
return "No data"
case _:
return "Invalid data"
# Protocol for structural typing
class Drawable(Protocol):
def draw(self) -> None: ...
def area(self) -> float: ...
def render_shape(shape: Drawable) -> None:
print(f"Rendering shape with area: {shape.area()}")
shape.draw()
# Generic types
T = TypeVar('T')
class Repository(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
Pylance Integration for Development
# Pylance provides real-time type checking and IntelliSense
# Configure for optimal development experience
# Type-safe configuration patterns
from typing import TypedDict, Literal
class DatabaseConfig(TypedDict):
host: str
port: int
database: str
ssl_mode: Literal["disable", "require", "verify-full"]
def connect_database(config: DatabaseConfig) -> None:
# Pylance ensures all required fields are present
# and validates literal types
pass
# Pylance catches type errors at development time
def process_user_data(user_id: int) -> dict[str, str]:
# Pylance will warn about potential None returns
user = get_user(user_id) # type: User | None
if user is None:
raise ValueError("User not found")
# Now Pylance knows user is not None
return {
"name": user.name,
"email": user.email,
"status": user.status
}
# Advanced type narrowing with Pylance
def handle_response(response: dict[str, any]) -> str:
if "error" in response:
# Pylance understands type narrowing
error_data = response["error"]
if isinstance(error_data, dict) and "message" in error_data:
return f"Error: {error_data['message']}"
return "Unknown error occurred"
if "data" in response:
return str(response["data"])
return "No data in response"
Development Workflow with Static Analysis
# Development setup for optimal type checking
# In pyproject.toml:
[tool.pylsp-mypy]
enabled = true
live_mode = true
strict = true
[tool.ruff]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # Pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
"A", # flake8-builtins
"C4", # flake8-comprehensions
"T20", # flake8-print
]
# Pre-commit hooks for code quality
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: pylance-check
name: Pylance type check
entry: pylance
language: system
args: ["--check"]
files: \.py$
- id: mypy
name: MyPy type check
entry: mypy
language: system
args: ["--strict"]
files: \.py$
- id: ruff
name: Ruff linter
entry: ruff
language: system
args: ["check", "--fix"]
files: \.py$
def add(self, item: T) -> None: self._items.append(item)
def getall(self) -> list[T]: return self.items.copy()
### Modern Python Idioms
**Dataclasses and Pydantic**
from dataclasses import dataclass, field from typing import Optional from datetime import datetime
@dataclass class User: name: str email: str createdat: datetime = field(defaultfactory=datetime.now) active: bool = True tags: list[str] = field(default_factory=list)
def __post_init__(self): if "@" not in self.email: raise ValueError("Invalid email format")
Pydantic for validation (external data)
from pydantic import BaseModel, EmailStr, Field
class UserModel(BaseModel): name: str = Field(..., minlength=1, maxlength=100) email: EmailStr age: int = Field(ge=0, le=150)
class Config: validate_assignment = True
**Context Managers**
from contextlib import contextmanager import sqlite3
@contextmanager def databasetransaction(dbpath: str): conn = sqlite3.connect(db_path) try: yield conn conn.commit() except Exception: conn.rollback() raise finally: conn.close()
Usage
with database_transaction("app.db") as conn: conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",))
**Pathlib Usage**
from pathlib import Path
def processconfigfiles(configdir: str | Path) -> dict[str, str]: configpath = Path(config_dir)
if not configpath.exists(): raise FileNotFoundError(f"Config directory not found: {configpath}")
configs = {} for configfile in configpath.glob("*.yaml"): with configfile.open() as f: configs[configfile.stem] = f.read()
return configs
## Error Handling Patterns
### Custom Exceptions
class AppException(Exception): """Base exception for application-specific errors.""" pass
class ValidationError(AppException): """Raised when data validation fails.""" def init(self, field: str, value: any, message: str): self.field = field self.value = value super().init(f"Validation failed for {field}: {message}")
class DatabaseError(AppException): """Raised when database operations fail.""" pass
def validateuserage(age: int) -> None: if age < 0 or age > 150: raise ValidationError("age", age, "must be between 0 and 150")
### Error Handling Best Practices
import logging from typing import Optional
logger = logging.getLogger(name)
def safe_divide(a: float, b: float) -> Optional[float]: """Safely divide two numbers, returning None if division by zero.""" try: return a / b except ZeroDivisionError: logger.warning(f"Division by zero attempted: {a} / {b}") return None
def processuserdata(data: dict) -> dict: """Process user data with comprehensive error handling.""" try: # Validate required fields if not (name := data.get("name", "").strip()): raise ValidationError("name", data.get("name"), "is required")
if not (email := data.get("email", "").strip()): raise ValidationError("email", data.get("email"), "is required")
# Process data return { "name": name.title(), "email": email.lower(), "created_at": datetime.now().isoformat() }
except ValidationError: logger.error(f"User data validation failed: {data}") raise except Exception as e: logger.exception("Unexpected error processing user data") raise AppException(f"Failed to process user data: {e}") from e
## Testing Patterns
### Pytest Best Practices
import pytest from unittest.mock import Mock, patch from pathlib import Path
Fixtures
@pytest.fixture def user_data(): return { "name": "Alice Smith", "email": "[email protected]", "age": 30 }
@pytest.fixture def tempconfigdir(tmppath): """Create a temporary config directory with test files.""" configdir = tmppath / "config" configdir.mkdir()
(configdir / "app.yaml").writetext("debug: true\nport: 8000\n") (configdir / "db.yaml").writetext("host: localhost\nport: 5432\n")
return config_dir
Parametrized tests
@pytest.mark.parametrize("inputage,expected", [ (25, True), (0, True), (150, True), (-1, False), (151, False), ]) def testagevalidation(inputage, expected): if expected: validateuserage(inputage) # Should not raise else: with pytest.raises(ValidationError): validateuserage(inputage)
Mocking
def testdatabaseoperation(): with patch('app.database.connect') as mockconnect: mockconn = Mock() mockconnect.returnvalue = mock_conn
result = somedatabaseoperation()
mockconnect.assertcalledonce() mockconn.execute.assertcalledwith("SELECT * FROM users")
Async testing
@pytest.mark.asyncio async def testasyncapicall(): with patch('httpx.AsyncClient.get') as mockget: mockresponse = Mock() mockresponse.statuscode = 200 mockresponse.json.returnvalue = {"success": True} mockget.returnvalue = mockresponse
result = await fetchuserdata("123")
assert result["success"] is True mockget.assertcalled_once()
### Test Organization
tests/conftest.py - Shared fixtures
import pytest from app import create_app
@pytest.fixture def app(): return create_app(testing=True)
@pytest.fixture def client(app): return app.test_client()
tests/test_models.py - Model tests
class TestUser: def testusercreation(self, userdata): user = User(**userdata) assert user.name == "Alice Smith" assert user.email == "[email protected]"
def testinvalidemailraiseserror(self): with pytest.raises(ValidationError): User(name="Test", email="invalid-email")
tests/test_services.py - Service tests
class TestUserService: @pytest.fixture(autouse=True) def setup(self): self.service = UserService()
def testcreateusersuccess(self, userdata): user = self.service.createuser(userdata) assert user.id is not None assert user.name == user_data["name"]
## Performance and Optimization
### Profiling and Timing
import time import functools from typing import Callable, Any
def timer(func: Callable) -> Callable: """Decorator to time function execution.""" @functools.wraps(func) def wrapper(*args: Any, **kwargs: Any) -> Any: start = time.perfcounter() result = func(*args, **kwargs) end = time.perfcounter() print(f"{func.name} took {end - start:.4f} seconds") return result return wrapper
@timer def slow_operation(n: int) -> list[int]: return [i**2 for i in range(n)]
Memory profiling with tracemalloc
import tracemalloc
def profilememory(func: Callable) -> Callable: @functools.wraps(func) def wrapper(*args, **kwargs): tracemalloc.start() result = func(*args, **kwargs) current, peak = tracemalloc.gettraced_memory() tracemalloc.stop() print(f"{func.name} - Current: {current / 1024 / 1024:.2f}MB, Peak: {peak / 1024 / 1024:.2f}MB") return result return wrapper
### Async Best Practices
import asyncio import aiohttp from typing import List
async def fetch_url(session: aiohttp.ClientSession, url: str) -> dict: """Fetch a single URL.""" async with session.get(url) as response: return await response.json()
async def fetchmultipleurls(urls: List[str]) -> List[dict]: """Fetch multiple URLs concurrently.""" async with aiohttp.ClientSession() as session: tasks = [fetchurl(session, url) for url in urls] return await asyncio.gather(*tasks, returnexceptions=True)
Semaphore for rate limiting
async def fetchwithlimit(urls: List[str], maxconcurrent: int = 10) -> List[dict]: """Fetch URLs with concurrency limit.""" semaphore = asyncio.Semaphore(maxconcurrent)
async def fetchlimited(url: str) -> dict: async with semaphore: async with aiohttp.ClientSession() as session: return await fetchurl(session, url)
tasks = [fetchlimited(url) for url in urls] return await asyncio.gather(*tasks, returnexceptions=True)
## Configuration and Environment Management
### Configuration Pattern
from dataclasses import dataclass from pathlib import Path import os from typing import Optional
@dataclass class DatabaseConfig: host: str = "localhost" port: int = 5432 username: str = "user" password: str = "" database: str = "app_db"
@property def url(self) -> str: return f"postgresql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}"
@dataclass class AppConfig: debug: bool = False secret_key: str = "" database: DatabaseConfig = DatabaseConfig()
@classmethod def fromenv(cls) -> "AppConfig": return cls( debug=os.getenv("DEBUG", "false").lower() == "true", secretkey=os.getenv("SECRETKEY", "dev-key"), database=DatabaseConfig( host=os.getenv("DBHOST", "localhost"), port=int(os.getenv("DBPORT", "5432")), username=os.getenv("DBUSER", "user"), password=os.getenv("DBPASSWORD", ""), database=os.getenv("DBNAME", "app_db"), ) )
Usage
config = AppConfig.from_env()
### Logging Configuration
import logging.config import sys
LOGGINGCONFIG = { "version": 1, "disableexisting_loggers": False, "formatters": { "standard": { "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s" }, "detailed": { "format": "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s" } }, "handlers": { "console": { "class": "logging.StreamHandler", "level": "INFO", "formatter": "standard", "stream": sys.stdout }, "file": { "class": "logging.FileHandler", "level": "DEBUG", "formatter": "detailed", "filename": "app.log" } }, "loggers": { "": { # Root logger "level": "INFO", "handlers": ["console", "file"] }, "app": { "level": "DEBUG", "handlers": ["console", "file"], "propagate": False } } }
def setuplogging(): logging.config.dictConfig(LOGGINGCONFIG)
Usage in modules
logger = logging.getLogger(name) logger.info("Application started")
These patterns provide a solid foundation for writing maintainable, tested, and performant Python code following modern best practices.