Agent skill
Python Style Standards
Python coding standards including line length (80 chars), naming conventions (snake_case, PascalCase), type hints, docstrings, exception handling, and logging patterns. Run python-code-quality script for automated checks (ruff + pyright + security). Use when writing new Python code or reviewing code quality.
Install this agent skill to your Project
npx add-skill https://github.com/randalmurphal/claude-config/tree/main/skills/python-style
SKILL.md
Python Style Standards
Practical Python coding standards focused on readability, maintainability, and quality.
Line Length & Formatting
80 characters maximum (enforced)
- PEP 8 compliant
- Use
ruff formatfor auto-formatting - Check with
ruff checkandpylint
Quality Checks:
# Primary tool: unified quality + security checks
python-code-quality --fix <path>
# See python-linting skill for tool-specific usage
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Functions | snake_case |
process_scan_results() |
| Variables | snake_case |
asset_count |
| Modules | snake_case |
asset_processor.py |
| Classes | PascalCase |
AssetProcessor |
| Constants | UPPER_CASE |
DEFAULT_BATCH_SIZE |
Descriptive names over abbreviations:
# GOOD - obvious meaning
def get_asset_by_uuid(uuid: str) -> dict | None:
return cache.lookup_and_parse('assets', 'uuid', uuid)
# BAD - cryptic abbreviations
def get_a(u: str) -> dict | None:
return cache.lookup_and_parse('assets', 'uuid', u)
Type Hints
Required for all new code
- Existing code can remain untyped unless refactoring
- Use pyright for type checking (2025 standard)
- Modern Python 3.10+ union syntax preferred
Examples:
# Function signatures
def process_scan(scan_id: int) -> dict[str, Any]:
"""Process a scan and return statistics."""
return {'processed': scan_id}
# Optional values (use | None, not Optional)
def get_asset_id(dv: dict) -> str | None:
assets = dv.get('related', {}).get('assets', [])
return str(assets[0]) if assets else None
# Complex types
def batch_process(
records: list[dict[str, Any]]
) -> tuple[int, dict[str, int]]:
"""Process records and return count and stats."""
return len(records), {'processed': len(records)}
Docstrings
Required for:
- Classes (purpose, key attributes)
- Modules (overview, main components)
- Complex functions (non-obvious logic)
Skip for:
- Simple getters/setters
- Obvious utility functions
- Functions with clear type hints
Style: PEP 8 conventions, explain WHY not WHAT
# GOOD - explains non-obvious WHY
def skip_compliance_insert(self, severity: str) -> bool:
"""Skip inserting Pass/Info compliance records.
WHY: Pass = compliant, no vulnerability exists.
Creating DVs for passing checks pollutes the database.
"""
return severity in ['0', 'Pass', 'Info']
# BAD - verbose restating of obvious code
def get_asset_id(self, dv: dict) -> str | None:
"""Retrieves the asset ID from a detected vulnerability document.
Args:
dv: The detected vulnerability document
Returns:
The asset ID as a string, or None if not found
"""
assets = dv.get('related', {}).get('assets', [])
return str(assets[0]) if assets else None
# GOOD - type hints make it obvious, no docstring needed
def get_asset_id(self, dv: dict) -> str | None:
assets = dv.get('related', {}).get('assets', [])
return str(assets[0]) if assets else None
Exception Handling
ONLY use try/except for connection errors
# BAD - unnecessary try/except (dict.get never raises)
def get_severity(record: dict) -> str:
try:
return record.get('severity', '0')
except Exception:
return '0'
# GOOD - direct access (safe operations don't need wrapping)
def get_severity(record: dict) -> str:
return record.get('severity', '0')
# GOOD - try/except for connection errors only
def fetch_from_api(url: str) -> dict:
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
return response.json()
except requests.Timeout:
LOG.warning(f"Timeout fetching {url}, retrying...")
return fetch_from_api(url) # Retry on timeout
except requests.HTTPError as e:
LOG.error(f"HTTP error {e.response.status_code}: {url}")
raise
except requests.ConnectionError as e:
LOG.error(f"Connection error: {url}")
raise
When to use try/except (ONLY connection errors):
- Network calls (requests, httpx, aiohttp)
- Database connections (pymongo, psycopg2, sqlalchemy)
- External API calls (any network I/O)
- Cache connections (redis, memcached)
When NOT to use try/except:
- dict.get() operations (returns None safely)
- list/dict access with defaults
- File I/O (let it fail with clear error)
- JSON parsing (let it fail with clear error)
- Type conversions (validate first, let it fail if invalid)
- Safe standard library operations
Specific exceptions only (never bare except):
# GOOD - specific connection exceptions
try:
result = api_call()
except requests.Timeout:
handle_timeout()
except requests.HTTPError:
handle_http_error()
except requests.ConnectionError:
handle_connection_error()
# BAD - bare except masks bugs
try:
result = api_call()
except:
pass
Logging Standards
Setup (once per class/module):
import logging
# At module level (preferred)
LOG = logging.getLogger(__name__)
# In class (if needed)
class AssetProcessor:
def __init__(self, config, sub_id):
self.log = logging.getLogger(__name__)
Logging Levels:
LOG.debug()- Detailed/verbose information (IDs, intermediate values)LOG.info()- Operation status and progress (milestones, counts)LOG.warning()- Recoverable issues (missing optional data)LOG.error()- Failures requiring attention (critical errors)
Best Practices:
# Include context: IDs, counts, operation names
self.log.debug(f"Processing record {record_id}")
self.log.info(f"Processed batch of {count} records in {elapsed:.2f}s")
# Progress indicators for long operations
self.log.info(f"Processing {i}/{total} ({i/total*100:.1f}%)")
# Include "Started" messages for operations
self.log.info("Started asset cascade matching")
Function Design
Don't create pointless wrappers:
# BAD - wrapper adds no value
def get_severity(self, record: dict) -> str:
return record.get('severity')
# GOOD - just use directly
severity = record.get('severity')
# GOOD - wrapper adds value (validation, transformation)
def get_normalized_severity(self, record: dict) -> int:
"""Convert severity string to integer with validation."""
raw_severity = record.get('severity', '0')
return int(raw_severity) if raw_severity.isdigit() else 0
Function should add value:
- Validation (check inputs, enforce constraints)
- Transformation (convert formats, normalize data)
- Abstraction (hide complexity, provide clear interface)
- Combination (coordinate multiple operations)
No single-line functions - readability over brevity
Single responsibility principle - each function does one thing well
Constants
Organization:
# Large projects: constants.py for shared values
DEFAULT_BATCH_SIZE = 5000
MAX_WORKERS = 10
TIMEOUT_SECONDS = 30
# Single-file usage: class-level constants
class AssetProcessor:
BATCH_SIZE = 5000
MAX_RETRIES = 3
Naming with WHY comments:
# GOOD - explains the reason for the value
FLOWS_BATCH_SIZE = 5000 # Balance memory usage with DB efficiency
DAA_THRESHOLD = 20000 # Parallel creation at scale
Import Organization
All imports at top of file (except circular import fixes)
Grouped in order:
- Standard library
- Third-party packages
- Local/project imports
Use absolute imports from project root:
# Standard library
import logging
import os
from typing import Any
# Third-party
import pymongo
import redis
from bson import ObjectId
# Local (absolute from project root)
from fisio.common.log import setup_logger
from fisio.common.mongo_helpers import retry_run
from fisio.imports.tenable_sc_refactor.processors import AssetProcessor
Code Organization
File structure:
# 1. Module docstring (if needed)
"""Asset processing for Tenable SC imports."""
# 2. Imports (grouped)
import logging
from typing import Any
# 3. Constants (module-level)
DEFAULT_BATCH_SIZE = 5000
# 4. Helper functions (above their callers)
def validate_asset_data(data: dict) -> bool:
return 'uuid' in data
# 5. Classes
class AssetProcessor:
pass
# 6. Main function (at bottom)
def run(config: dict, sub_id: str) -> dict:
pass
Helper functions above their callers - top-down reading flow
Refactoring Complex Functions
When to refactor:
- Function exceeds 50 statements or 12 branches
- Multiple distinct responsibilities mixed together
- Deep nesting (>3 levels) makes logic hard to follow
- Ruff/pylint complexity warnings
How to refactor:
- Extract focused methods with single responsibilities
- Main function becomes clear orchestration
- Each helper method handles one concern
- Use guard clauses to reduce nesting
# BEFORE - 100+ lines with deep nesting
def process_record(self, record: dict) -> dict | None:
if record.get('type') == 'asset':
if record.get('uuid'):
if self._validate_uuid(record['uuid']):
asset = self._create_asset(record)
if asset:
self._stage_relationships(asset, record)
return asset
return None
# AFTER - clear orchestration + focused helpers
def process_record(self, record: dict) -> dict | None:
"""Process a single record and return created asset."""
if not self._should_process_record(record):
return None
asset = self._create_asset_from_record(record)
if asset:
self._stage_relationships(asset, record)
return asset
def _should_process_record(self, record: dict) -> bool:
"""Check if record should be processed."""
if record.get('type') != 'asset':
return False
if not record.get('uuid'):
return False
return self._validate_uuid(record['uuid'])
Common Anti-Patterns to Avoid
Avoid:
- Bare
except:clauses (use specific exceptions) - Try/except for operations that never raise
- Single-character variable names (except i, j in short loops)
- Functions that just call another function unchanged
- Magic numbers without explanation
- Commented-out code (delete it, use git history)
- Global mutable state
- Deep nesting (use guard clauses)
Quick Reference Card
# Names
function_name() # snake_case
ClassName # PascalCase
CONSTANT_VALUE # UPPER_CASE
# Type hints (required for new code)
def process(data: dict[str, Any]) -> int | None:
pass
# Docstrings (when non-obvious)
def complex_logic() -> bool:
"""Explain WHY, not WHAT."""
pass
# Logging (include context)
LOG.debug(f"Processing record {record_id}")
LOG.info(f"Completed batch of {count} records")
# Exception handling (only when necessary)
try:
api_call() # Operations that can actually fail
except SpecificError:
handle_error() # Never bare except
# Imports (grouped: stdlib → third-party → local)
import os
import pymongo
from fisio.common import helpers
Quality Tools
Primary tool (use this):
# Unified quality + security checks
python-code-quality --fix <path>
Individual tools (see python-linting skill):
ruff- Linting and formattingpyright- Type checkingbandit- Security scanningsemgrep- Security patterns
Remember
Priorities:
- Clarity - Code should be obvious to read
- Maintainability - Easy to modify without breaking
- Quality - Pass linting and tests before claiming done
- Simplicity - Simple solutions over clever ones
When in doubt:
- Check existing code patterns in the project
- Ask for clarification rather than guess
- Err on the side of being more descriptive
- Let type hints and clear names replace documentation
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-prompting
Write effective prompts for Task tool sub-agents, slash commands, and system prompts. Covers Claude 4.x prompting patterns, context engineering, output format specification, and parallel delegation. Use when spawning sub-agents, creating slash commands, or writing system prompts.
test-driven-development
Use when implementing any feature, bug fix, or behavior change - before writing implementation code. Enforces strict RED-GREEN-REFACTOR cycle where tests are written and seen failing before any production code exists.
spec-formats
Templates for brainstorm artifacts and manifest.json. Load when using /spec.
review-initiative
Use after /breakdown-work or manual initiative creation, before running tasks. Use when initiative has 5+ tasks, multiple dependencies, or references a design document.
MCP Integration
Set up and use MCP (Model Context Protocol) servers to extend Claude Code capabilities including PRISM semantic memory, filesystem access, and database integration. Use when setting up MCP servers, debugging MCP connections, or understanding MCP tool usage.
systematic-debugging
Use when debugging any failure, bug, or unexpected behavior - especially before attempting any fix. Enforces root cause investigation before implementation to prevent fix-thrashing and symptom-masking.
Didn't find tool you were looking for?