Python Rules for Cursor Editor

3 Rules
Programming Language
.mdc Format

About These Rules

These Python coding rules are specifically formatted for the Cursor Editor as .mdc files, focusing on type hints, modern Python patterns, and best practices. Each rule is designed to help you write more maintainable Python code while leveraging Cursor's AI capabilities for enhanced development.

1. Type Hints and Modern Python

Implement comprehensive type hints and modern Python patterns for better code reliability and maintainability. This approach ensures better type checking and takes advantage of Python's type system.

python-type-hints.mdc
from typing import TypeVar, Generic, Optional, List, Dict, Any, Union
from dataclasses import dataclass
from datetime import datetime
from enum import Enum

# Type definitions
class UserRole(str, Enum):
    ADMIN = "admin"
    USER = "user"
    GUEST = "guest"

@dataclass
class UserPreferences:
    theme: str
    notifications: bool

@dataclass
class User:
    id: str
    name: str
    email: str
    role: UserRole
    created_at: datetime
    preferences: Optional[UserPreferences] = None

# Generic type utility
T = TypeVar('T')

class ApiResponse(Generic[T]):
    def __init__(self, data: T, status: int, message: str):
        self.data = data
        self.status = status
        self.message = message

# Type-safe service
class UserService:
    def __init__(self, api_url: str):
        self.api_url = api_url

    async def get_user(self, user_id: str) -> ApiResponse[User]:
        try:
            # Simulated API call
            user_data = {
                "id": user_id,
                "name": "John Doe",
                "email": "[email protected]",
                "role": UserRole.USER,
                "created_at": datetime.now(),
                "preferences": UserPreferences(theme="dark", notifications=True)
            }
            
            user = User(**user_data)
            return ApiResponse(
                data=user,
                status=200,
                message="User retrieved successfully"
            )
        except Exception as e:
            raise ValueError(f"Failed to fetch user: {str(e)}")

    async def update_user(
        self, 
        user_id: str, 
        updates: Dict[str, Any]
    ) -> ApiResponse[User]:
        try:
            # Simulated API call
            user_data = {
                "id": user_id,
                "name": updates.get("name", "John Doe"),
                "email": updates.get("email", "[email protected]"),
                "role": UserRole(updates.get("role", UserRole.USER)),
                "created_at": datetime.now(),
                "preferences": UserPreferences(
                    theme=updates.get("theme", "dark"),
                    notifications=updates.get("notifications", True)
                )
            }
            
            user = User(**user_data)
            return ApiResponse(
                data=user,
                status=200,
                message="User updated successfully"
            )
        except Exception as e:
            raise ValueError(f"Failed to update user: {str(e)}")

# Example usage
async def main():
    user_service = UserService("https://api.example.com")
    
    try:
        # Get user
        response = await user_service.get_user("123")
        print(f"User: {response.data}")
        
        # Update user
        updates = {
            "name": "Jane Doe",
            "role": UserRole.ADMIN
        }
        response = await user_service.update_user("123", updates)
        print(f"Updated user: {response.data}")
    except ValueError as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

Why This Rule Matters

  • Enables better type safety and error prevention
  • Improves code reliability and maintainability
  • Facilitates better IDE support and autocompletion
  • Reduces runtime errors
  • Enables better code documentation

Using with Cursor

Cursor's AI capabilities can help you:

  • Suggest type hints
  • Generate dataclasses
  • Implement generic types
  • Improve type safety
  • Fix type errors

2. Async Programming and Error Handling

Implement modern async programming patterns and robust error handling for better performance and maintainability. This approach helps Cursor's AI better understand async contexts and provide more accurate debugging assistance.

python-async-patterns.mdc
from typing import TypeVar, Generic, Optional, List, Dict, Any
from dataclasses import dataclass
from datetime import datetime
import asyncio
import logging
from functools import wraps
import time

# Custom exceptions
class AppError(Exception):
    def __init__(self, message: str, code: str, details: Optional[Dict[str, Any]] = None):
        super().__init__(message)
        self.code = code
        self.details = details or {}
        self.timestamp = datetime.now()

class ValidationError(AppError):
    def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
        super().__init__(message, "VALIDATION_ERROR", details)

class APIError(AppError):
    def __init__(self, message: str, code: str, details: Optional[Dict[str, Any]] = None):
        super().__init__(message, code, details)

# Async retry decorator
def async_retry(
    max_attempts: int = 3,
    delay: float = 1.0,
    backoff: float = 2.0,
    exceptions: tuple = (Exception,)
):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(max_attempts):
                try:
                    return await func(*args, **kwargs)
                except exceptions as e:
                    last_exception = e
                    if attempt == max_attempts - 1:
                        break
                    await asyncio.sleep(delay * (backoff ** attempt))
            raise last_exception
        return wrapper
    return decorator

# Async context manager
class AsyncResource:
    def __init__(self, name: str):
        self.name = name
        self.logger = logging.getLogger(__name__)

    async def __aenter__(self):
        self.logger.info(f"Acquiring {self.name}")
        await asyncio.sleep(0.1)  # Simulate resource acquisition
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        self.logger.info(f"Releasing {self.name}")
        await asyncio.sleep(0.1)  # Simulate resource release

# Example usage
class UserService:
    def __init__(self, db_url: str):
        self.db_url = db_url
        self.logger = logging.getLogger(__name__)

    @async_retry(max_attempts=3, delay=1.0)
    async def get_user(self, user_id: str) -> Dict[str, Any]:
        async with AsyncResource("database") as db:
            try:
                # Simulated database query
                await asyncio.sleep(0.1)
                if not user_id:
                    raise ValidationError("User ID is required", {"user_id": user_id})
                
                return {
                    "id": user_id,
                    "name": "John Doe",
                    "email": "[email protected]"
                }
            except Exception as e:
                self.logger.error(f"Failed to get user: {str(e)}")
                raise APIError(
                    "Failed to get user",
                    "DATABASE_ERROR",
                    {"user_id": user_id, "error": str(e)}
                )

    @async_retry(max_attempts=3, delay=1.0)
    async def update_user(
        self,
        user_id: str,
        updates: Dict[str, Any]
    ) -> Dict[str, Any]:
        async with AsyncResource("database") as db:
            try:
                # Simulated database update
                await asyncio.sleep(0.1)
                if not user_id:
                    raise ValidationError("User ID is required", {"user_id": user_id})
                
                return {
                    "id": user_id,
                    **updates,
                    "updated_at": datetime.now()
                }
            except Exception as e:
                self.logger.error(f"Failed to update user: {str(e)}")
                raise APIError(
                    "Failed to update user",
                    "DATABASE_ERROR",
                    {"user_id": user_id, "updates": updates, "error": str(e)}
                )

# Example usage
async def main():
    # Configure logging
    logging.basicConfig(level=logging.INFO)
    
    user_service = UserService("postgresql://localhost:5432/mydb")
    
    try:
        # Get user
        user = await user_service.get_user("123")
        print(f"User: {user}")
        
        # Update user
        updates = {"name": "Jane Doe", "email": "[email protected]"}
        updated_user = await user_service.update_user("123", updates)
        print(f"Updated user: {updated_user}")
    except AppError as e:
        print(f"Application error: {e.code} - {str(e)}")
        if e.details:
            print(f"Details: {e.details}")
    except Exception as e:
        print(f"Unexpected error: {str(e)}")

if __name__ == "__main__":
    asyncio.run(main())

Why This Rule Matters

  • Enables better async operation handling
  • Improves error handling and recovery
  • Facilitates better performance
  • Reduces code complexity
  • Enables better debugging

Using with Cursor

Cursor's AI capabilities can help you:

  • Suggest async patterns
  • Generate error handling code
  • Implement retry mechanisms
  • Optimize async operations
  • Debug async issues

3. Module Organization and Testing

Implement proper module organization and testing patterns for better code maintainability and reliability. This approach helps Cursor's AI better understand code structure and provide more accurate suggestions.

python-module-organization.mdc
from typing import TypeVar, Generic, Optional, List, Dict, Any
from dataclasses import dataclass
from datetime import datetime
import asyncio
import logging
from pathlib import Path
import json
import pytest
from unittest.mock import Mock, patch

# Project structure
"""
my_project/
├── src/
│   ├── __init__.py
│   ├── config.py
│   ├── models/
│   │   ├── __init__.py
│   │   └── user.py
│   ├── services/
│   │   ├── __init__.py
│   │   └── user_service.py
│   └── utils/
│       ├── __init__.py
│       └── validation.py
├── tests/
│   ├── __init__.py
│   ├── conftest.py
│   └── test_user_service.py
└── requirements.txt
"""

# config.py
@dataclass
class Config:
    database_url: str
    api_key: str
    environment: str
    log_level: str = "INFO"

    @classmethod
    def from_env(cls) -> "Config":
        return cls(
            database_url="postgresql://localhost:5432/mydb",
            api_key="your-api-key",
            environment="development"
        )

# models/user.py
@dataclass
class User:
    id: str
    name: str
    email: str
    created_at: datetime

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "User":
        return cls(
            id=data["id"],
            name=data["name"],
            email=data["email"],
            created_at=datetime.fromisoformat(data["created_at"])
        )

    def to_dict(self) -> Dict[str, Any]:
        return {
            "id": self.id,
            "name": self.name,
            "email": self.email,
            "created_at": self.created_at.isoformat()
        }

# services/user_service.py
class UserService:
    def __init__(self, config: Config):
        self.config = config
        self.logger = logging.getLogger(__name__)

    async def get_user(self, user_id: str) -> User:
        # Implementation
        pass

    async def update_user(self, user_id: str, updates: Dict[str, Any]) -> User:
        # Implementation
        pass

# utils/validation.py
def validate_email(email: str) -> bool:
    import re
    pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
    return bool(re.match(pattern, email))

def validate_user_data(data: Dict[str, Any]) -> None:
    if not data.get("email"):
        raise ValueError("Email is required")
    if not validate_email(data["email"]):
        raise ValueError("Invalid email format")

# tests/conftest.py
@pytest.fixture
def config():
    return Config.from_env()

@pytest.fixture
def user_service(config):
    return UserService(config)

# tests/test_user_service.py
@pytest.mark.asyncio
async def test_get_user(user_service):
    # Arrange
    user_id = "123"
    expected_user = User(
        id=user_id,
        name="John Doe",
        email="[email protected]",
        created_at=datetime.now()
    )

    # Act
    with patch.object(user_service, "get_user") as mock_get_user:
        mock_get_user.return_value = expected_user
        result = await user_service.get_user(user_id)

    # Assert
    assert result == expected_user
    mock_get_user.assert_called_once_with(user_id)

@pytest.mark.asyncio
async def test_update_user(user_service):
    # Arrange
    user_id = "123"
    updates = {
        "name": "Jane Doe",
        "email": "[email protected]"
    }
    expected_user = User(
        id=user_id,
        name=updates["name"],
        email=updates["email"],
        created_at=datetime.now()
    )

    # Act
    with patch.object(user_service, "update_user") as mock_update_user:
        mock_update_user.return_value = expected_user
        result = await user_service.update_user(user_id, updates)

    # Assert
    assert result == expected_user
    mock_update_user.assert_called_once_with(user_id, updates)

# Example usage
async def main():
    # Configure logging
    logging.basicConfig(level=logging.INFO)
    
    # Load configuration
    config = Config.from_env()
    
    # Initialize service
    user_service = UserService(config)
    
    try:
        # Get user
        user = await user_service.get_user("123")
        print(f"User: {user}")
        
        # Update user
        updates = {"name": "Jane Doe", "email": "[email protected]"}
        updated_user = await user_service.update_user("123", updates)
        print(f"Updated user: {updated_user}")
    except Exception as e:
        print(f"Error: {str(e)}")

if __name__ == "__main__":
    asyncio.run(main())

Why This Rule Matters

  • Improves code organization
  • Enables better testing
  • Facilitates easier maintenance
  • Improves code reusability
  • Enables better dependency management

Using with Cursor

Cursor's AI capabilities can help you:

  • Suggest module organization patterns
  • Generate test cases
  • Optimize module structure
  • Improve code organization
  • Generate module documentation