Code Style Guide
This document outlines the coding standards and conventions used in the BrushsetMaker project. Following these guidelines ensures consistency and maintainability across the codebase.
Table of Contents
- Python Version
- Code Formatting
- Naming Conventions
- Documentation
- Import Organization
- Type Hints
- Error Handling
- Async/Await Patterns
- UI Components
- Project Structure
Python Version
- Target Python Version: 3.12+
- All code must be compatible with Python 3.12 or higher
- Use modern Python features when appropriate
Code Formatting
Indentation and Spacing
- Use 4 spaces for indentation (never tabs)
- Maximum line length: 100 characters (soft limit)
- Use blank lines to separate logical sections within functions
- Add two blank lines between top-level classes and functions
String Formatting
- Use double quotes (
") for strings by default - Use single quotes (
') for string keys in dictionaries when appropriate - Prefer f-strings for string interpolation:
# Good
message = f"Processing {idx} of {total} folders..."
# Avoid
message = "Processing %d of %d folders..." % (idx, total)
message = "Processing {} of {} folders...".format(idx, total)
Whitespace
- One space around operators:
x = y + 5 - No spaces inside parentheses:
function(arg1, arg2) - No trailing whitespace at end of lines
Naming Conventions
Variables and Functions
- Use snake_case for variables and functions:
selected_folder = None
progress_window = None
def create_progress_window(app, total):
pass
Classes
- Use PascalCase for class names:
class BrushsetMaker(toga.App):
pass
class UIBuilder:
pass
class BrushsetHandlers:
pass
Constants
- Use UPPER_SNAKE_CASE for constants:
MAX_ERRORS_TO_DISPLAY = 5
DEFAULT_PADDING = 30
Private Methods
- Prefix private methods with a single underscore:
def _build_single_section(app):
"""Build the single brushset section."""
pass
async def _handle_create_single(self, widget):
"""Wrapper for create single brushset handler."""
pass
Module Names
- Use lowercase with underscores:
handlers.py,builder.py - Keep module names short and descriptive
Documentation
Module Docstrings
- Every module must have a docstring at the top:
"""BrushsetMaker - macOS utility to compile brushsets for Procreate."""
Class Docstrings
- Use simple, descriptive docstrings for classes:
class BrushsetMaker(toga.App):
"""Main application class for BrushsetMaker."""
Function Docstrings
- Use descriptive docstrings for all functions and methods
- Keep docstrings concise and action-oriented:
def startup(self):
"""Construct and show the Toga application."""
# implementation
@staticmethod
async def create_single_brushset(app, widget):
"""Create a single brushset from a selected folder."""
# implementation
- For complex functions, add parameter descriptions if needed:
def create_progress_window(app, total):
"""
Create a progress window.
Args:
app: The application instance
total: Total number of items to process
"""
Comments
- Use inline comments sparingly and only when necessary
- Comments should explain why, not what
- Keep comments up-to-date with code changes
- Use descriptive variable names instead of comments when possible
Import Organization
Organize imports in the following order, with a blank line between each group:
- Standard library imports
- Third-party library imports
- Local application imports
Example:
"""Event handlers for BrushsetMaker application."""
import zipfile
from pathlib import Path
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW
from .handlers import BrushsetHandlers
Import Guidelines
- Use absolute imports for packages
- Use relative imports within the same package
- Avoid wildcard imports (
from module import *) - Import only what you need
__all__ Declarations
- Use
__all__in__init__.pyfiles to define public API:
"""Core application logic."""
from .handlers import BrushsetHandlers
__all__ = ['BrushsetHandlers']
Type Hints
While the current codebase does not use type hints extensively, contributors are encouraged to add them when appropriate, especially for:
- Function parameters and return values
- Public API methods
- Complex data structures
Example:
from pathlib import Path
from typing import Optional, List
def get_subdirectories(root_path: Path) -> List[Path]:
"""Get all subdirectories in the given path."""
return [d for d in root_path.iterdir() if d.is_dir()]
async def create_single_brushset(
app: toga.App,
widget: toga.Widget
) -> None:
"""Create a single brushset from a selected folder."""
pass
Error Handling
Exception Handling
- Always handle exceptions gracefully in user-facing code
- Use specific exception types when possible
- Show user-friendly error messages through dialogs:
try:
# Operation that might fail
result = perform_operation()
except SpecificException as e:
await app.main_window.error_dialog("Error", f"Operation failed: {e}")
except Exception as e:
await app.main_window.error_dialog("Error", f"Unexpected error: {e}")
Error Messages
- Error messages should be clear and actionable
- Include relevant context (file names, paths, etc.)
- Use proper punctuation and capitalization
- Example:
"Error creating brushset: {e}"
Early Returns
- Use early returns to avoid deep nesting:
async def create_single_brushset(app, widget):
"""Create a single brushset from a selected folder."""
folder_path = await app.main_window.select_folder_dialog(
title="Select Folder to Package"
)
if not folder_path:
return # Early return for cancellation
# Continue with main logic
Async/Await Patterns
When to Use Async
- Use
async/awaitfor all UI event handlers - Use
asyncfor file dialogs and I/O operations - Mark handler wrappers as async:
async def _handle_create_single(self, widget):
"""Wrapper for create single brushset handler."""
await BrushsetHandlers.create_single_brushset(self, widget)
Static Methods as Handlers
- Handler logic is implemented as static methods in handler classes
- Wrapper methods in the app class bridge UI callbacks to handlers:
# In app.py
async def _handle_select_bulk(self, widget):
"""Wrapper for select bulk folder handler."""
await BrushsetHandlers.select_bulk_folder(self, widget)
# In handlers.py
@staticmethod
async def select_bulk_folder(app, widget):
"""Handle bulk folder selection."""
# Implementation
UI Components
Toga Style Conventions
- Use
Packfor all styling - Define styles inline when creating widgets:
label = toga.Label(
"BrushsetMaker",
style=Pack(padding=(0, 0, 5, 0), font_size=24, font_weight="bold")
)
Layout Structure
- Use
Boxcontainers for layout organization - Separate major UI sections into builder methods:
@staticmethod
def build_main_window(app):
"""Build and return the main window content."""
main_box = toga.Box(style=Pack(direction=COLUMN, padding=30))
# Build sub-sections
single_box = UIBuilder._build_single_section(app)
bulk_box = UIBuilder._build_bulk_section(app)
main_box.add(single_box)
main_box.add(bulk_box)
return main_box
UI Naming
- Store UI elements as app instance attributes when they need to be accessed later:
app.folder_label = toga.Label("No folder selected")
app.process_button = toga.Button("Process All Subfolders", enabled=False)
Padding and Spacing
- Use consistent padding values throughout the UI
- Main containers:
padding=30 - Section boxes:
padding=20 - Widget spacing:
padding=(0, 0, 10, 0)(top, right, bottom, left) - Add visual separators using
toga.Divider()
Emoji Usage
- Use emojis sparingly for visual enhancement in UI labels:
single_label = toga.Label(
"📦 Package Single Brushset",
style=Pack(padding=(0, 0, 10, 0), font_size=16, font_weight="bold")
)
Project Structure
Package Organization
src/
brushsetmaker/
__init__.py # Package version and metadata
__main__.py # Entry point for module execution
app.py # Main application class
core/ # Business logic
__init__.py
handlers.py # Event handlers
ui/ # User interface
__init__.py
builder.py # UI component builders
Module Responsibilities
app.py: Main application class, window setup, handler wrapperscore/handlers.py: All business logic and event handlingui/builder.py: UI construction and layout__init__.py: Package initialization and public API definition
Separation of Concerns
- Keep UI building separate from business logic
- Handler classes should be stateless static method containers
- Application state lives in the main app class
- UI builders receive the app instance and attach widgets as needed
Testing
When writing tests:
- Place tests in a
tests/directory at the project root - Mirror the source structure in test files
- Use descriptive test names:
test_create_brushset_with_empty_folder - Use pytest as the testing framework
- Mock file system operations when appropriate
Version Control
Commit Messages
- Use clear, descriptive commit messages
- Start with a verb: “Add”, “Fix”, “Update”, “Remove”
- Keep the first line under 72 characters
- Add details in subsequent lines if needed
Example:
Add bulk processing progress window
- Create progress window with status label and progress bar
- Update progress as folders are processed
- Show completion summary with error details
Dependencies
- Use
pyproject.tomlfor dependency management - Pin major versions, allow minor updates:
toga>=0.4.0 - Document any macOS-specific dependencies in the appropriate section
Code Quality Tools
Ruff
The project uses Ruff for linting and code formatting. The configuration is defined in pyproject.toml under [tool.ruff].
Check code style:
ruff check .
Auto-fix issues:
ruff check --fix .
Format code:
ruff format .
The ruff configuration enforces:
- 100 character line length (soft limit)
- Double quotes for strings
- Proper import organization (stdlib → third-party → local)
- Python 3.12+ syntax
- Google-style docstrings
- Pathlib usage over os.path
Pre-commit checking is recommended before committing code.
Summary
The BrushsetMaker codebase follows clean, readable Python conventions with a focus on:
- Clarity: Descriptive names and straightforward logic
- Consistency: Uniform style throughout the project
- Maintainability: Well-organized structure and proper documentation
- User Experience: Graceful error handling and clear feedback
When in doubt, look at existing code for guidance and maintain consistency with the established patterns.