| name | python-code-style |
| description | Follow Python code organization conventions including method ordering, datetime handling, circular import avoidance, and type annotations. Use when organizing service classes, handling dates, or structuring modules. |
| user-invocable | true |
| argument-hint | [topic] |
Python Code Style
Python code organization conventions for clean, maintainable codebases covering method ordering, datetime handling, circular import avoidance, and modern type annotations.
When to Use This Skill
Use this skill when:
- Organizing methods in service classes or modules
- Working with datetime objects and timestamps
- Resolving circular import issues
- Adding type annotations to code
- Structuring function-based modules
Quick Reference
Method Organization Hierarchy
class MyService:
def __init__(self):
pass
@classmethod
def from_config(cls, config: dict):
pass
@staticmethod
def parse_id(value: str):
pass
def process_request(self):
pass
def validate_input(self):
pass
def _internal_helper(self):
pass
Datetime Usage
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
timestamp = datetime(2025, 1, 15, 10, 30, 0, tzinfo=timezone.utc)
now = datetime.now()
Principle: Method Organization
Public Before Private
Public methods (part of the API) appear before private/internal methods (prefixed with _). This allows developers to understand the public API of each service at a glance.
Anti-Pattern (ā Avoid)
class UserService:
def __init__(self):
pass
def _validate_email(self, email: str):
pass
def create_user(self, email: str, name: str):
return self._validate_email(email)
def _hash_password(self, password: str):
pass
def authenticate(self, email: str, password: str):
pass
Problems:
- Readers must scan through implementation details to find the public API
- Harder to understand what the service does at a glance
- Inconsistent organization makes navigation difficult
Recommended Pattern (ā
Use This)
class UserService:
def __init__(self, db: Database):
self.db = db
def create_user(self, email: str, name: str) -> User:
"""Create a new user account."""
self._validate_email(email)
return User(email=email, name=name)
def authenticate(self, email: str, password: str) -> bool:
"""Authenticate user credentials."""
user = self.db.find_user(email)
return self._verify_password(user, password)
def _validate_email(self, email: str):
"""Validate email format."""
if "@" not in email:
raise ValueError("Invalid email")
def _verify_password(self, user: User, password: str) -> bool:
"""Verify password hash."""
return self._hash_password(password) == user.password_hash
def _hash_password(self, password: str) -> str:
"""Hash password for storage."""
return hashlib.sha256(password.encode()).hexdigest()
Standard Method Order
Methods follow this ordering:
- Special methods (
__init__, __str__, __repr__, etc.)
- Class methods (
@classmethod)
- Static methods (
@staticmethod)
- Instance methods (public, ordered by abstraction level)
- Private methods (prefixed with
_)
class ReportService:
def __init__(self, repo: str):
self.repo = repo
def __str__(self):
return f"ReportService({self.repo})"
@classmethod
def from_config(cls, config: dict):
return cls(config["repo"])
@staticmethod
def validate_format(fmt: str):
return fmt in ["json", "csv", "html"]
def generate_report(self, data: list) -> str:
validated = self._validate_data(data)
return self._format_output(validated)
def export_report(self, report: str, path: str):
self._write_file(path, report)
def _validate_data(self, data: list):
return [item for item in data if item]
def _format_output(self, data: list) -> str:
return "\n".join(str(item) for item in data)
def _write_file(self, path: str, content: str):
with open(path, "w") as f:
f.write(content)
Benefits
ā
Easier onboarding: New developers quickly understand what a service does
ā
Better maintainability: Clear separation between public contracts and implementation
ā
Intuitive navigation: Consistent structure helps developers find methods quickly
ā
Clearer API boundaries: Public vs. private methods are visually distinct
Principle: Section Headers for Complex Services
When to Use Section Headers
For services with many methods (10+), use descriptive section headers with separators to group related functionality.
Simple Section Comments
class SimpleService:
def __init__(self):
pass
def operation_one(self):
pass
def operation_two(self):
pass
@staticmethod
def utility_function():
pass
def _internal_helper(self):
pass
Descriptive Headers for Complex Services
class ComplexService:
def __init__(self):
pass
def create_resource(self, data: dict):
pass
def get_resource(self, id: str):
pass
def update_resource(self, id: str, data: dict):
pass
def delete_resource(self, id: str):
pass
def find_resources(self, filter: dict):
pass
def count_resources(self, filter: dict):
pass
@staticmethod
def parse_identifier(text: str):
pass
@staticmethod
def validate_data(data: dict):
pass
Benefits
ā
Visual organization: Separators make different sections obvious
ā
Faster navigation: Developers quickly jump to relevant sections
ā
Logical grouping: Related methods are clearly grouped together
Principle: Module-Level Code Organization
Function-Based Modules
For modules with functions rather than classes, use this order:
- Dataclasses and models (public before private)
- Public API functions (high-level to low-level)
- Module utilities (helper functions used by the public API)
- Private helper functions (prefixed with
_)
Recommended Pattern (ā
Use This)
from dataclasses import dataclass
from typing import List
@dataclass
class ProjectArtifact:
"""Public dataclass for artifact data."""
name: str
size: int
created_at: str
@dataclass
class ArtifactMetadata:
"""Metadata for artifact collections."""
total_count: int
total_size: int
def find_artifacts(project: str) -> List[ProjectArtifact]:
"""Highest-level public function - main entry point."""
raw_data = _fetch_from_api(project)
return [_parse_artifact(item) for item in raw_data]
def get_artifact_details(artifact: ProjectArtifact) -> dict:
"""Mid-level public function - supporting operation."""
return {
"name": artifact.name,
"size_mb": artifact.size / 1024 / 1024,
"age_days": calculate_age(artifact.created_at)
}
def parse_artifact_name(name: str) -> tuple[str, str]:
"""Utility function used by multiple operations."""
parts = name.split("-")
return parts[0], "-".join(parts[1:])
def calculate_age(timestamp: str) -> int:
"""Calculate age in days from timestamp."""
pass
def _fetch_from_api(project: str) -> list:
"""Private implementation detail - API interaction."""
pass
def _parse_artifact(data: dict) -> ProjectArtifact:
"""Private implementation detail - data parsing."""
return ProjectArtifact(
name=data["name"],
size=data["size"],
created_at=data["created_at"]
)
Benefits
ā
Clear structure: Module organization is consistent and predictable
ā
Easy to find: Related code is grouped logically
ā
Public API first: Readers see the module's purpose immediately
Principle: Always Use Timezone-Aware Datetimes
All datetime objects must be timezone-aware. Naive datetimes (without timezone information) are not allowed and will cause validation errors.
Anti-Pattern (ā Avoid)
from datetime import datetime
now = datetime.now()
timestamp = datetime(2025, 1, 15, 10, 30, 0)
utc_now = datetime.utcnow()
Recommended Pattern (ā
Use This)
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
timestamp = datetime(2025, 1, 15, 10, 30, 0, tzinfo=timezone.utc)
created_at = datetime.now(timezone.utc)
last_updated = datetime.now(timezone.utc)
timestamp_str = now.isoformat()
Parsing ISO 8601 Timestamps
Use a helper function for parsing ISO 8601 timestamps:
from datetime import datetime, timezone
def parse_iso_timestamp(timestamp_str: str) -> datetime:
"""Parse ISO 8601 timestamp to timezone-aware datetime.
Handles both "Z" and "+00:00" timezone formats.
"""
if timestamp_str.endswith("Z"):
timestamp_str = timestamp_str[:-1] + "+00:00"
dt = datetime.fromisoformat(timestamp_str)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt
parsed_dt = parse_iso_timestamp("2025-01-15T10:30:00Z")
parsed_dt2 = parse_iso_timestamp("2025-01-15T10:30:00+00:00")
Domain Model Validation
Validate timezone-aware datetimes in domain models:
from dataclasses import dataclass
from datetime import datetime, timezone
@dataclass
class Event:
name: str
created_at: datetime
def __post_init__(self):
"""Validate that datetime fields are timezone-aware."""
if self.created_at.tzinfo is None:
raise ValueError(
f"created_at must be timezone-aware. "
f"Use datetime.now(timezone.utc) or parse_iso_timestamp()"
)
event = Event("deployment", datetime.now(timezone.utc))
event = Event("deployment", datetime.now())
Why UTC?
ā
Unambiguous: UTC has no daylight saving time
ā
Standard: Industry best practice for system timestamps
ā
Interoperable: Works across all timezones
ā
Comparable: All timestamps in same timezone can be directly compared
Common Pitfalls
| ā Avoid | ā
Use Instead |
|---|
datetime.now() | datetime.now(timezone.utc) |
datetime.utcnow() (deprecated) | datetime.now(timezone.utc) |
datetime(2025, 1, 15, 10, 30) | datetime(2025, 1, 15, 10, 30, tzinfo=timezone.utc) |
datetime.fromisoformat(iso_str) | parse_iso_timestamp(iso_str) |
Benefits
ā
Prevents comparison errors: No "can't compare offset-naive and offset-aware datetimes"
ā
Unambiguous data: Timestamps clearly indicate their timezone
ā
ISO 8601 compliant: Standard format works everywhere
ā
Validated: Domain models prevent naive datetimes at construction time
Principle: Avoid Circular Imports
Circular imports indicate architectural problems. Fix the dependency structure rather than working around it.
Anti-Pattern (ā Avoid)
from typing import TYPE_CHECKING, List
if TYPE_CHECKING:
from module_b import SomeClass
def process_items(items: List["SomeClass"]):
pass
from typing import Any, List
def process_items(items: List[Any]):
pass
Problems:
TYPE_CHECKING means your code works at runtime only because the import is skipped
Any throws away type safety entirely
- Both hide the architectural issue rather than solving it
Recommended Pattern (ā
Use This)
Fix the dependency structure by establishing one-way dependencies:
- Identify which module is more foundational (lower-level)
- Ensure the lower-level module never imports from higher-level ones
- Verify with grep:
grep "from higher_module" lower_module.py should return nothing
from dataclasses import dataclass
@dataclass
class GitHubPullRequest:
number: int
title: str
state: str
from typing import List
from lower_level import GitHubPullRequest
@dataclass
class ProjectStats:
project_name: str
open_prs: List[GitHubPullRequest]
When You Have a Genuine Cycle
If you have a genuine cycle, either:
- Move shared code to a third module that both can depend on
- Refactor so the lower-level module doesn't need higher-level types
@dataclass
class SharedType:
pass
from shared import SharedType
from shared import SharedType
Benefits
ā
Clean architecture: One-way dependency graphs are easier to understand
ā
No runtime surprises: All imports work at runtime, not just type checking
ā
Better modularity: Lower-level modules are more reusable
ā
Full type safety: No need for Any or string annotations
Principle: Modern Type Annotations
Use Self for Factory Methods
When a classmethod or factory returns an instance of its own class, use Self instead of quoted string annotations:
class User:
@classmethod
def from_string(cls, value: str) -> "User":
return cls()
from typing import Self
class User:
@classmethod
def from_string(cls, value: str) -> Self:
return cls()
@classmethod
def from_dict(cls, data: dict) -> Self:
return cls()
Benefits:
- Cleaner syntax
- Works correctly with subclasses
- Avoids forward reference strings
Use from __future__ import annotations for Self-Referential Types
For classes that reference themselves in type hints (tree structures, containers with nested elements):
from __future__ import annotations
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class Section:
name: str
elements: List[Section]
parent: Optional[Section] = None
def add(self, element: Section) -> Section:
self.elements.append(element)
element.parent = self
return self
Benefits
ā
Cleaner code: No quoted string annotations
ā
Works with subclasses: Self adapts to the actual class
ā
Better readability: Self-referential types look natural
ā
Forward compatible: Prepares for future Python versions
Related Patterns
This skill relates to several software design patterns:
- Template Method Pattern: Method organization makes it easier to see which methods to override
- Strategy Pattern: Static methods are good candidates for strategy implementations
- Factory Pattern: Class methods serve as alternative constructors
- Value Object Pattern: Timezone-aware datetimes make better value objects
Related Skills
- creating-services: Apply method organization principles when creating service classes
- testing-services: Use consistent organization for test code readability
- domain-modeling: Organize domain model methods following these conventions
- dependency-injection: Constructor organization relates to dependency management
Further Reading