Architecture patterns, optimization techniques, and professional development practices
This guide covers the recommended patterns and practices for building production-quality applications with the Bravura SDK. Following these practices ensures maintainable, performant, and professional applications.
---
---
class ProfessionalApp:
"""
Recommended application architecture using Bravura SDK.
Features:
- Clean separation of concerns
- Proper component lifecycle management
- Comprehensive error handling
- Performance optimizations
"""
def __init__(self):
# 1. Initialize core components
self.initialize_core()
# 2. Setup UI framework
self.initialize_ui()
# 3. Create application components
self.initialize_components()
# 4. Setup event handlers
self.initialize_handlers()
def initialize_core(self):
"""Initialize core application components."""
self.config = self.load_configuration()
self.logger = self.setup_logging()
self.worker = self.create_worker_thread()
def initialize_ui(self):
"""Initialize UI framework and theming."""
self.root = tk.Tk()
self.theme_manager = ProfessionalThemeManager(self)
self.apply_initial_theme()
def initialize_components(self):
"""Create UI components."""
self.create_menu_bar()
self.create_main_interface()
self.create_status_components()
def initialize_handlers(self):
"""Setup event handlers and bindings."""
self.setup_keyboard_shortcuts()
self.setup_window_handlers()
self.setup_error_handlers()
class ComponentManager:
"""Manages component creation, updates, and cleanup."""
def __init__(self, app):
self.app = app
self.components = {}
self.active_components = set()
def create_component(self, name, component_class, *args, **kwargs):
"""Create and register a component."""
if name in self.components:
self.destroy_component(name)
component = component_class(*args, **kwargs)
self.components[name] = component
self.active_components.add(name)
return component
def get_component(self, name):
"""Get a registered component."""
return self.components.get(name)
def destroy_component(self, name):
"""Properly destroy a component."""
if name in self.components:
component = self.components[name]
if hasattr(component, 'destroy'):
component.destroy()
elif hasattr(component, 'close'):
component.close()
del self.components[name]
self.active_components.discard(name)
def cleanup_all(self):
"""Cleanup all components on application exit."""
for name in list(self.active_components):
self.destroy_component(name)
---
professional_app/
├── main.py # Application entry point
├── config.py # Configuration management
├── ui/
│ ├── __init__.py
│ ├── main_window.py # Main window class
│ ├── components/ # UI components
│ │ ├── __init__.py
│ │ ├── file_panel.py
│ │ ├── progress_panel.py
│ │ └── status_bar.py
│ └── themes/ # Custom themes
│ └── corporate_theme.py
├── core/
│ ├── __init__.py
│ ├── processor.py # Business logic
│ ├── worker.py # Background processing
│ └── validators.py # Input validation
├── utils/
│ ├── __init__.py
│ ├── logging_utils.py
│ └── file_utils.py
└── tests/
├── __init__.py
├── test_main.py
└── test_processor.py
# main.py
import sys
import traceback
from ui.main_window import MainWindow
from config import Config
def main():
"""Application entry point with comprehensive error handling."""
try:
# Load configuration
config = Config()
# Create and run application
app = MainWindow(config)
app.run()
except KeyboardInterrupt:
print("\nApplication interrupted by user")
sys.exit(0)
except Exception as e:
print(f"Fatal error: {e}")
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
# config.py
import json
import os
from pathlib import Path
from typing import Dict, Any
class Config:
"""Centralized configuration management."""
DEFAULT_CONFIG = {
"theme": {
"default_theme": "wigley_site",
"auto_switch": False
},
"window": {
"width": 1200,
"height": 800,
"remember_position": True,
"stay_on_top": False
},
"performance": {
"gpu_acceleration": True,
"animation_speed": 1.0,
"max_threads": 4
},
"logging": {
"level": "INFO",
"max_file_size": 10 * 1024 * 1024, # 10MB
"backup_count": 5
}
}
def __init__(self, config_file: str = "app_config.json"):
self.config_file = Path(config_file)
self.data = self.load()
def load(self) -> Dict[str, Any]:
"""Load configuration from file."""
if self.config_file.exists():
try:
with open(self.config_file, 'r') as f:
return {**self.DEFAULT_CONFIG, **json.load(f)}
except (json.JSONDecodeError, IOError) as e:
print(f"Warning: Could not load config: {e}")
return self.DEFAULT_CONFIG.copy()
else:
return self.DEFAULT_CONFIG.copy()
def save(self):
"""Save configuration to file."""
try:
self.config_file.parent.mkdir(parents=True, exist_ok=True)
with open(self.config_file, 'w') as f:
json.dump(self.data, f, indent=2)
except IOError as e:
print(f"Warning: Could not save config: {e}")
def get(self, key: str, default=None):
"""Get configuration value."""
keys = key.split('.')
value = self.data
for k in keys:
if isinstance(value, dict) and k in value:
value = value[k]
else:
return default
return value
def set(self, key: str, value: Any):
"""Set configuration value."""
keys = key.split('.')
config = self.data
for k in keys[:-1]:
if k not in config or not isinstance(config[k], dict):
config[k] = {}
config = config[k]
config[keys[-1]] = value
self.save()
---
class FileProcessingPanel(ttk.Frame):
"""Professional file processing panel using Bravura components."""
def __init__(self, parent, theme_manager, **kwargs):
super().__init__(parent, **kwargs)
self.theme_manager = theme_manager
self.worker = None
self.create_ui()
self.setup_bindings()
def create_ui(self):
"""Create the user interface."""
# File selection section
self.create_file_section()
# Processing controls
self.create_control_section()
# Progress section
self.create_progress_section()
# Results section
self.create_results_section()
def create_file_section(self):
"""Create file selection interface."""
file_frame = ttk.LabelFrame(self, text="File Selection", padding=10)
file_frame.pack(fill="x", pady=(0, 10))
# File path entry with browse button
ttk.Label(file_frame, text="Input File:").grid(row=0, column=0, sticky="w")
self.file_path = tk.StringVar()
entry = ttk.Entry(file_frame, textvariable=self.file_path, width=50)
entry.grid(row=0, column=1, sticky="ew", padx=(5, 0))
# Browse button
from bravura.components import CompactPremiumButton
CompactPremiumButton(
file_frame,
text="Browse...",
command=self.browse_file,
width=10
).grid(row=0, column=2, padx=(5, 0))
file_frame.grid_columnconfigure(1, weight=1)
def create_control_section(self):
"""Create processing controls."""
control_frame = ttk.Frame(self)
control_frame.pack(fill="x", pady=(0, 10))
# Primary action button
from bravura.components import PremiumButton
self.process_btn = PremiumButton(
control_frame,
text="🚀 Process File",
command=self.start_processing,
style="primary",
width=18,
theme_tokens=self.theme_manager.get_current_theme_colors()
)
self.process_btn.pack(side="left", padx=(0, 10))
# Cancel button
self.cancel_btn = PremiumButton(
control_frame,
text="Cancel",
command=self.cancel_processing,
style="secondary",
width=12,
theme_tokens=self.theme_manager.get_current_theme_colors()
)
self.cancel_btn.pack(side="left")
def create_progress_section(self):
"""Create progress display."""
progress_frame = ttk.LabelFrame(self, text="Progress", padding=10)
progress_frame.pack(fill="x", pady=(0, 10))
# Progress bar
from bravura.components import GlowingProgressBar
self.progress_bar = GlowingProgressBar(
progress_frame,
label_text="Processing:",
bar_type="rainbow",
width=400
)
self.progress_bar.pack(fill="x")
# Status label
self.status_label = ttk.Label(progress_frame, text="Ready")
self.status_label.pack(anchor="w", pady=(5, 0))
def create_results_section(self):
"""Create results display."""
results_frame = ttk.LabelFrame(self, text="Results", padding=10)
results_frame.pack(fill="both", expand=True)
# Results text area
self.results_text = tk.Text(
results_frame,
height=10,
wrap="word",
font=("Consolas", 10)
)
scrollbar = ttk.Scrollbar(results_frame, command=self.results_text.yview)
self.results_text.configure(yscrollcommand=scrollbar.set)
self.results_text.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Style the text widget
self.apply_text_styling()
def apply_text_styling(self):
"""Apply theme colors to text widget."""
colors = self.theme_manager.get_current_theme_colors()
self.results_text.configure(
bg=colors.get("background_secondary", "#2a2a2a"),
fg=colors.get("text_main", "#ffffff"),
insertbackground=colors.get("text_main", "#ffffff"),
selectbackground=colors.get("accent_color_1", "#007acc")
)
def browse_file(self):
"""Browse for input file."""
from tkinter import filedialog
filename = filedialog.askopenfilename(
title="Select file to process",
filetypes=[("All files", "*.*")]
)
if filename:
self.file_path.set(filename)
def start_processing(self):
"""Start file processing."""
file_path = self.file_path.get()
if not file_path:
messagebox.showwarning("No File", "Please select a file first.")
return
# Disable buttons
self.process_btn.set_loading(True, "Processing...")
self.cancel_btn.set_enabled(False)
# Start processing in background
self.start_background_processing(file_path)
def start_background_processing(self, file_path):
"""Start background processing task."""
from bravura.core.worker import Worker
def processing_task(emit, cancel):
"""Background processing task."""
try:
# Simulate processing steps
total_steps = 100
for step in range(total_steps):
if cancel.is_set():
emit("CANCELLED")
return
# Do processing work here
import time
time.sleep(0.1) # Simulate work
# Update progress
emit("PROGRESS", step=step, total=total_steps,
message=f"Processing step {step + 1}/{total_steps}")
emit("COMPLETED", result="Processing successful!")
except Exception as e:
emit("ERROR", error=str(e))
# Create and start worker
self.worker = Worker()
self.worker.run(processing_task, on_message=self.on_worker_message)
def on_worker_message(self, message_type, **kwargs):
"""Handle messages from background worker."""
if message_type == "PROGRESS":
step = kwargs.get("step", 0)
total = kwargs.get("total", 100)
message = kwargs.get("message", "")
# Update progress bar
progress = (step + 1) / total * 100
self.progress_bar.set_value(progress)
self.status_label.config(text=message)
elif message_type == "COMPLETED":
result = kwargs.get("result", "")
self.processing_completed(result)
elif message_type == "ERROR":
error = kwargs.get("error", "Unknown error")
self.processing_error(error)
elif message_type == "CANCELLED":
self.processing_cancelled()
def processing_completed(self, result):
"""Handle successful processing completion."""
self.reset_ui_state()
self.results_text.insert("end", f"✓ {result}\n")
self.results_text.see("end")
def processing_error(self, error):
"""Handle processing error."""
self.reset_ui_state()
self.results_text.insert("end", f"✗ Error: {error}\n")
self.results_text.see("end")
messagebox.showerror("Processing Error", str(error))
def processing_cancelled(self):
"""Handle processing cancellation."""
self.reset_ui_state()
self.results_text.insert("end", "⚠ Processing cancelled by user\n")
self.results_text.see("end")
def cancel_processing(self):
"""Cancel current processing."""
if self.worker and self.worker.is_running():
self.worker.cancel()
self.cancel_btn.config(text="Cancelling...")
def reset_ui_state(self):
"""Reset UI to ready state."""
self.process_btn.set_loading(False)
self.cancel_btn.set_enabled(True)
self.cancel_btn.config(text="Cancel")
self.progress_bar.set_value(0)
self.status_label.config(text="Ready")
def setup_bindings(self):
"""Setup keyboard shortcuts and event bindings."""
# Ctrl+O to browse file
self.bind("<Control-o>", lambda e: self.browse_file())
# Ctrl+Enter to start processing
self.bind("<Control-Return>", lambda e: self.start_processing())
# Escape to cancel
self.bind("<Escape>", lambda e: self.cancel_processing())
class BackgroundProcessor:
"""Professional background processing with proper error handling."""
def __init__(self, app):
self.app = app
self.worker = None
self.cancel_event = threading.Event()
def process_file(self, file_path, options=None):
"""Process a file in the background."""
if self.worker and self.worker.is_running():
raise RuntimeError("Processing already in progress")
self.cancel_event.clear()
options = options or {}
def processing_task(emit, cancel):
"""Background processing task."""
try:
# Validate inputs
if not os.path.exists(file_path):
emit("ERROR", error=f"File not found: {file_path}")
return
# Start processing
emit("STARTED", message=f"Processing {os.path.basename(file_path)}")
# Process file (replace with actual logic)
result = self._process_file_content(file_path, options, emit, cancel)
if not cancel.is_set():
emit("COMPLETED", result=result)
except Exception as e:
emit("ERROR", error=str(e))
self.app.logger.error(f"Processing failed: {e}", exc_info=True)
self.worker = Worker()
self.worker.run(processing_task, on_message=self.app.on_processing_message)
def _process_file_content(self, file_path, options, emit, cancel):
"""Process file content with progress updates."""
# Get file size for progress calculation
file_size = os.path.getsize(file_path)
# Process file in chunks
processed = 0
chunk_size = options.get("chunk_size", 8192)
with open(file_path, 'rb') as f:
while not cancel.is_set():
chunk = f.read(chunk_size)
if not chunk:
break
# Process chunk (replace with actual processing)
processed_chunk = self._process_chunk(chunk, options)
processed += len(chunk)
# Update progress
progress = (processed / file_size) * 100
emit("PROGRESS",
progress=progress,
message=f"Processed {processed:,} of {file_size:,} bytes")
return {"processed_bytes": processed, "file_size": file_size}
def _process_chunk(self, chunk, options):
"""Process a chunk of data."""
# Replace with actual processing logic
import time
time.sleep(0.001) # Simulate processing time
return chunk
def cancel(self):
"""Cancel current processing."""
if self.worker:
self.worker.cancel()
self.cancel_event.set()
def is_processing(self):
"""Check if processing is in progress."""
return self.worker and self.worker.is_running()
---
class MemoryManager:
"""Memory usage optimization for long-running applications."""
def __init__(self, app):
self.app = app
self.gc_threshold = 1000 # Objects before GC
self.memory_checks = []
def optimize_memory_usage(self):
"""Perform memory optimization."""
import gc
import psutil
# Force garbage collection
collected = gc.collect()
# Get memory usage
process = psutil.Process()
memory_mb = process.memory_info().rss / 1024 / 1024
self.memory_checks.append({
'timestamp': time.time(),
'memory_mb': memory_mb,
'objects_collected': collected
})
# Keep only last 100 checks
if len(self.memory_checks) > 100:
self.memory_checks = self.memory_checks[-100:]
return memory_mb
def get_memory_trend(self):
"""Get memory usage trend."""
if len(self.memory_checks) < 2:
return "insufficient_data"
recent = self.memory_checks[-10:]
avg_recent = sum(m['memory_mb'] for m in recent) / len(recent)
avg_overall = sum(m['memory_mb'] for m in self.memory_checks) / len(self.memory_checks)
if avg_recent > avg_overall * 1.2:
return "increasing"
elif avg_recent < avg_overall * 0.8:
return "decreasing"
else:
return "stable"
The AmbientBackground component demonstrates excellent UI responsiveness practices:
# Ambient backgrounds run at smooth 60 FPS with minimal CPU impact
from bravura.components import AmbientBackground
ambient_bg = AmbientBackground(
root,
theme_tokens=theme_manager.get_current_theme_colors(),
enable_animation=True,
particle_count=30, # Smooth even with 30-40 particles
animation_speed="medium" # Fixed 60 FPS regardless of speed
)
# Performance characteristics:
# - 60 FPS animation (16.67ms frame time)
# - <2% CPU usage on modern systems
# - Harmonic easing for organic movement
# - No UI blocking or stuttering
Key Performance Features:
set_theme_tokens() method updates colors without recreation (10x faster)Best Practice: Use set_theme_tokens() for Theme Changes
class ProfessionalApp:
def __init__(self):
self.root = tk.Tk()
self.theme_manager = ProfessionalThemeManager(self)
self.theme_manager.apply_theme("wigley_site")
# Create ambient background
self.ambient_bg = AmbientBackground(
self.root,
theme_tokens=self.theme_manager.get_current_theme_colors(),
enable_animation=True,
particle_count=30
)
def change_theme(self, theme_name):
"""Change theme with optimized ambient background update."""
# Apply new theme
self.theme_manager.apply_theme(theme_name)
# Update ambient background instantly (NEW in v1.0.0)
if self.ambient_bg:
self.ambient_bg.set_theme_tokens(
self.theme_manager.get_current_theme_colors()
)
# Update other UI components as needed
self.update_ui_colors()
Performance Comparison:
| Method | Time | Visual Quality | Memory |
|---|---|---|---|
| `set_theme_tokens()` | ~10ms | Smooth, no flicker | No allocation |
| Recreation (old way) | ~100ms | Visual "pop" | New allocation |
When to Use Each Method:
✅ Use set_theme_tokens() (Recommended):
⚠️ Use Recreation (Only When Needed):
class UIUpdater:
"""Ensures UI updates don't block the main thread."""
def __init__(self, root):
self.root = root
self.update_queue = queue.Queue()
self.running = True
# Start update processing thread
self.update_thread = threading.Thread(target=self._process_updates, daemon=True)
self.update_thread.start()
def schedule_update(self, callback, *args, **kwargs):
"""Schedule a UI update to run on main thread."""
self.update_queue.put((callback, args, kwargs))
def _process_updates(self):
"""Process UI updates on main thread."""
while self.running:
try:
callback, args, kwargs = self.update_queue.get(timeout=0.1)
# Schedule callback on main thread
self.root.after(0, callback, *args, **kwargs)
except queue.Empty:
continue
except Exception as e:
print(f"UI update error: {e}")
def shutdown(self):
"""Shutdown the UI updater."""
self.running = False
if self.update_thread.is_alive():
self.update_thread.join(timeout=1.0)
class ComponentCache:
"""Cache frequently used UI components."""
def __init__(self):
self.cache = {}
self.access_times = {}
def get_component(self, key, factory_func, *args, **kwargs):
"""Get cached component or create new one."""
if key not in self.cache:
self.cache[key] = factory_func(*args, **kwargs)
self.access_times[key] = time.time()
return self.cache[key]
def cleanup_old_components(self, max_age_seconds=300):
"""Remove components that haven't been accessed recently."""
current_time = time.time()
keys_to_remove = []
for key, access_time in self.access_times.items():
if current_time - access_time > max_age_seconds:
keys_to_remove.append(key)
for key in keys_to_remove:
if key in self.cache:
component = self.cache[key]
if hasattr(component, 'destroy'):
component.destroy()
del self.cache[key]
del self.access_times[key]
def clear_cache(self):
"""Clear all cached components."""
for component in self.cache.values():
if hasattr(component, 'destroy'):
component.destroy()
self.cache.clear()
self.access_times.clear()
---
class ErrorHandler:
"""Centralized error handling for the application."""
def __init__(self, app):
self.app = app
self.error_log = []
self.setup_global_exception_handling()
def setup_global_exception_handling(self):
"""Setup global exception handlers."""
def handle_exception(exc_type, exc_value, exc_traceback):
"""Handle uncaught exceptions."""
error_info = {
'type': exc_type.__name__,
'message': str(exc_value),
'traceback': traceback.format_exception(exc_type, exc_value, exc_traceback),
'timestamp': time.time()
}
self.error_log.append(error_info)
# Log the error
self.app.logger.error(
f"Uncaught exception: {error_info['message']}",
exc_info=(exc_type, exc_value, exc_traceback)
)
# Show user-friendly error dialog
self.show_error_dialog(error_info)
# Set global exception handler
sys.excepthook = handle_exception
# Handle Tkinter errors
def tkinter_error_handler(error):
self.handle_tkinter_error(error)
# Override Tkinter's error reporting
tk.Tk.report_callback_exception = tkinter_error_handler
def handle_error(self, error, context=""):
"""Handle an application error."""
error_info = {
'error': str(error),
'context': context,
'timestamp': time.time(),
'traceback': traceback.format_exc()
}
self.error_log.append(error_info)
# Log error
self.app.logger.error(f"Error in {context}: {error}", exc_info=True)
# Determine error severity
if self._is_critical_error(error):
self.handle_critical_error(error_info)
else:
self.handle_recoverable_error(error_info)
def _is_critical_error(self, error):
"""Determine if an error is critical."""
critical_patterns = [
"out of memory",
"disk full",
"permission denied",
"connection failed"
]
error_str = str(error).lower()
return any(pattern in error_str for pattern in critical_patterns)
def handle_critical_error(self, error_info):
"""Handle critical application errors."""
message = (
"A critical error has occurred. The application may be in an "
"unstable state.\n\n"
f"Error: {error_info['error']}\n\n"
"Please save your work and restart the application."
)
messagebox.showerror("Critical Error", message)
# Offer to save error report
if messagebox.askyesno("Save Error Report",
"Would you like to save an error report?"):
self.save_error_report(error_info)
def handle_recoverable_error(self, error_info):
"""Handle recoverable application errors."""
message = f"An error occurred: {error_info['error']}"
if error_info['context']:
message += f"\n\nContext: {error_info['context']}"
messagebox.showwarning("Error", message)
def handle_tkinter_error(self, error):
"""Handle Tkinter-specific errors."""
# Extract useful information from Tkinter error
error_str = str(error)
# Log Tkinter error
self.app.logger.error(f"Tkinter error: {error_str}")
# Some Tkinter errors can be ignored (like callback errors during shutdown)
if "application has been destroyed" in error_str.lower():
return
# Show error dialog for serious Tkinter errors
messagebox.showerror("Interface Error",
f"A user interface error occurred:\n\n{error_str}")
def show_error_dialog(self, error_info):
"""Show error dialog for uncaught exceptions."""
message = (
"An unexpected error has occurred. The application will continue "
"running, but some features may not work correctly.\n\n"
f"Error: {error_info['message']}\n\n"
"Please report this error if it persists."
)
result = messagebox.askyesno("Unexpected Error", message + "\n\nShow details?")
if result:
# Show detailed error information
details = "\n".join(error_info['traceback'])
self.show_error_details(error_info['message'], details)
def show_error_details(self, message, details):
"""Show detailed error information."""
dialog = tk.Toplevel()
dialog.title("Error Details")
dialog.geometry("600x400")
# Error message
ttk.Label(dialog, text="Error:", font=("Arial", 10, "bold")).pack(anchor="w", padx=10, pady=5)
msg_label = ttk.Label(dialog, text=message, wraplength=580)
msg_label.pack(anchor="w", padx=10)
# Details
ttk.Label(dialog, text="Details:", font=("Arial", 10, "bold")).pack(anchor="w", padx=10, pady=(20, 5))
# Scrollable text area for traceback
text_frame = ttk.Frame(dialog)
text_frame.pack(fill="both", expand=True, padx=10, pady=(0, 10))
text_widget = tk.Text(text_frame, wrap="word", font=("Consolas", 9))
scrollbar = ttk.Scrollbar(text_frame, command=text_widget.yview)
text_widget.configure(yscrollcommand=scrollbar.set)
text_widget.insert("1.0", details)
text_widget.config(state="disabled")
text_widget.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Close button
ttk.Button(dialog, text="Close", command=dialog.destroy).pack(pady=10)
def save_error_report(self, error_info):
"""Save error report to file."""
from datetime import datetime
timestamp = datetime.fromtimestamp(error_info['timestamp']).strftime("%Y%m%d_%H%M%S")
filename = f"error_report_{timestamp}.txt"
try:
with open(filename, 'w') as f:
f.write("Error Report\n")
f.write("=" * 50 + "\n\n")
f.write(f"Timestamp: {datetime.fromtimestamp(error_info['timestamp'])}\n")
f.write(f"Error Type: {error_info.get('type', 'Unknown')}\n")
f.write(f"Message: {error_info['message']}\n\n")
if 'context' in error_info:
f.write(f"Context: {error_info['context']}\n\n")
f.write("Traceback:\n")
f.write("-" * 30 + "\n")
if 'traceback' in error_info:
if isinstance(error_info['traceback'], list):
f.write("".join(error_info['traceback']))
else:
f.write(error_info['traceback'])
messagebox.showinfo("Report Saved",
f"Error report saved as: {filename}")
except Exception as e:
messagebox.showerror("Save Failed",
f"Could not save error report: {e}")
def get_error_summary(self):
"""Get summary of recent errors."""
recent_errors = [e for e in self.error_log
if time.time() - e['timestamp'] < 3600] # Last hour
return {
'total_errors': len(self.error_log),
'recent_errors': len(recent_errors),
'last_error': self.error_log[-1] if self.error_log else None
}
---
# tests/test_main.py
import unittest
import tkinter as tk
from unittest.mock import Mock, patch
from main_window import MainWindow
class TestMainWindow(unittest.TestCase):
"""Test cases for main application window."""
def setUp(self):
"""Set up test fixtures."""
self.root = tk.Tk()
self.app = MainWindow(self.root)
def tearDown(self):
"""Clean up after tests."""
if self.root:
self.root.destroy()
def test_initialization(self):
"""Test that application initializes correctly."""
self.assertIsNotNone(self.app.theme_manager)
self.assertIsNotNone(self.app.worker)
self.assertEqual(self.app.title, "Professional App")
def test_theme_application(self):
"""Test theme application."""
initial_theme = self.app.theme_manager.get_current_theme_name()
self.app.theme_manager.apply_theme("dark")
self.assertEqual(self.app.theme_manager.get_current_theme_name(), "dark")
# Restore original theme
self.app.theme_manager.apply_theme(initial_theme)
@patch('main_window.messagebox.showinfo')
def test_file_processing(self, mock_messagebox):
"""Test file processing functionality."""
# Mock file selection
with patch('tkinter.filedialog.askopenfilename', return_value='/test/file.txt'):
self.app.browse_file()
self.assertEqual(self.app.file_path.get(), '/test/file.txt')
def test_error_handling(self):
"""Test error handling."""
with self.assertRaises(ValueError):
self.app.process_invalid_input()
def test_ui_components(self):
"""Test that UI components are created."""
self.assertIsNotNone(self.app.process_btn)
self.assertIsNotNone(self.app.progress_bar)
self.assertIsNotNone(self.app.status_label)
# tests/test_integration.py
import unittest
import time
from main_window import MainWindow
class TestIntegration(unittest.TestCase):
"""Integration tests for complete workflows."""
def setUp(self):
"""Set up integration test environment."""
self.app = MainWindow()
self.app.root.withdraw() # Hide window during testing
def tearDown(self):
"""Clean up after integration tests."""
if self.app.root:
self.app.root.destroy()
def test_complete_processing_workflow(self):
"""Test complete file processing workflow."""
# Setup test file
test_file = self.create_test_file("test_data.txt", "sample content")
try:
# Set file path
self.app.file_path.set(test_file)
# Start processing
self.app.start_processing()
# Wait for completion (with timeout)
timeout = 30
start_time = time.time()
while self.app.worker.is_running() and (time.time() - start_time) < timeout:
time.sleep(0.1)
self.app.root.update()
# Verify completion
self.assertFalse(self.app.worker.is_running())
self.assertIn("completed", self.app.status_label.cget("text").lower())
finally:
# Cleanup
os.remove(test_file)
def test_error_recovery(self):
"""Test error recovery and user feedback."""
# Set invalid file path
self.app.file_path.set("/nonexistent/file.txt")
# Attempt processing
self.app.start_processing()
# Wait for error handling
time.sleep(1)
self.app.root.update()
# Verify error handling
self.assertIn("error", self.app.status_label.cget("text").lower())
def create_test_file(self, filename, content):
"""Create a temporary test file."""
with open(filename, 'w') as f:
f.write(content)
return filename
# tests/test_performance.py
import unittest
import time
import psutil
from main_window import MainWindow
class TestPerformance(unittest.TestCase):
"""Performance tests for application."""
def setUp(self):
"""Set up performance test."""
self.app = MainWindow()
self.app.root.withdraw()
def tearDown(self):
"""Clean up after performance tests."""
if self.app.root:
self.app.root.destroy()
def test_startup_time(self):
"""Test application startup time."""
start_time = time.time()
# Create new instance (simulating startup)
test_app = MainWindow()
test_app.root.withdraw()
startup_time = time.time() - start_time
# Should start up in under 2 seconds
self.assertLess(startup_time, 2.0,
f"Startup took {startup_time:.2f}s, expected < 2.0s")
test_app.root.destroy()
def test_memory_usage(self):
"""Test memory usage during operation."""
process = psutil.Process()
initial_memory = process.memory_info().rss / 1024 / 1024 # MB
# Perform some operations
self.app.theme_manager.apply_theme("dark")
self.app.theme_manager.apply_theme("light")
self.app.theme_manager.apply_theme("wigley_site")
final_memory = process.memory_info().rss / 1024 / 1024 # MB
memory_increase = final_memory - initial_memory
# Memory increase should be reasonable (< 50MB)
self.assertLess(memory_increase, 50.0,
f"Memory increased by {memory_increase:.1f}MB, expected < 50MB")
def test_ui_responsiveness(self):
"""Test UI responsiveness during operations."""
# Start a background operation
self.app.start_processing()
# Measure UI response time
start_time = time.time()
# Perform UI operations
self.app.root.update()
self.app.status_label.config(text="Test update")
response_time = time.time() - start_time
# UI should respond within 100ms
self.assertLess(response_time, 0.1,
f"UI response took {response_time:.3f}s, expected < 0.1s")
---
# build_config.py
import os
from pathlib import Path
class BuildConfig:
"""Configuration for application builds."""
# Application metadata
APP_NAME = "Professional App"
APP_VERSION = "1.0.0"
AUTHOR = "Your Company"
DESCRIPTION = "Professional desktop application built with Bravura SDK"
# Build settings
BUILD_DIR = Path("build")
DIST_DIR = Path("dist")
# Include files
INCLUDE_FILES = [
"gui_main.py",
"glowing_progress_bar.py",
"components/",
"themes/",
"config.json"
]
# Exclude patterns
EXCLUDE_PATTERNS = [
"*.pyc",
"__pycache__",
"*.log",
".git/",
"tests/",
"docs/"
]
# PyInstaller settings
PYINSTALLER_CONFIG = {
"name": APP_NAME,
"version": APP_VERSION,
"author": AUTHOR,
"description": DESCRIPTION,
"console": False, # Hide console window
"windowed": True, # Create GUI app
"onefile": True, # Single executable
"clean": True, # Clean cache
"noupx": False, # Use UPX compression
}
@classmethod
def get_includes(cls):
"""Get list of files to include in build."""
includes = []
for pattern in cls.INCLUDE_FILES:
if os.path.isfile(pattern):
includes.append(pattern)
elif os.path.isdir(pattern):
for root, dirs, files in os.walk(pattern):
for file in files:
if not any(file.endswith(ext) for ext in ['.pyc', '.pyo']):
includes.append(os.path.join(root, file))
return includes
@classmethod
def validate_build(cls):
"""Validate build configuration."""
errors = []
# Check required files exist
for file in cls.INCLUDE_FILES:
if os.path.exists(file):
continue
elif file.endswith('/'): # Directory
if not os.path.isdir(file.rstrip('/')):
errors.append(f"Required directory not found: {file}")
else:
errors.append(f"Required file not found: {file}")
# Check for conflicting settings
if cls.PYINSTALLER_CONFIG["onefile"] and cls.PYINSTALLER_CONFIG["onedir"]:
errors.append("Cannot use both 'onefile' and 'onedir' PyInstaller options")
return errors
# build.py
#!/usr/bin/env python3
"""
Automated build script for Professional App.
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
from build_config import BuildConfig
def clean_build():
"""Clean previous build artifacts."""
print("🧹 Cleaning previous build...")
dirs_to_clean = [BuildConfig.BUILD_DIR, BuildConfig.DIST_DIR]
for dir_path in dirs_to_clean:
if dir_path.exists():
shutil.rmtree(dir_path)
print(f" Removed {dir_path}")
def create_build_environment():
"""Create build environment."""
print("🏗️ Creating build environment...")
BuildConfig.BUILD_DIR.mkdir(parents=True, exist_ok=True)
BuildConfig.DIST_DIR.mkdir(parents=True, exist_ok=True)
# Validate configuration
errors = BuildConfig.validate_build()
if errors:
print("❌ Build configuration errors:")
for error in errors:
print(f" {error}")
sys.exit(1)
print(" Build environment ready")
def install_dependencies():
"""Install build dependencies."""
print("📦 Installing dependencies...")
try:
subprocess.check_call([
sys.executable, "-m", "pip", "install",
"--upgrade", "pip", "setuptools", "wheel"
])
# Install PyInstaller if not present
subprocess.check_call([
sys.executable, "-m", "pip", "install", "PyInstaller"
])
print(" Dependencies installed")
except subprocess.CalledProcessError as e:
print(f"❌ Failed to install dependencies: {e}")
sys.exit(1)
def build_executable():
"""Build executable using PyInstaller."""
print("🔨 Building executable...")
try:
cmd = [
sys.executable, "-m", "PyInstaller",
"--name", BuildConfig.APP_NAME,
"--version", BuildConfig.APP_VERSION,
"--noconsole", # Hide console
"--onefile", # Single executable
"--clean", # Clean cache
"--distpath", str(BuildConfig.DIST_DIR),
"--workpath", str(BuildConfig.BUILD_DIR),
]
# Add includes
for include in BuildConfig.get_includes():
cmd.extend(["--add-data", f"{include};{include}"])
# Add main script
cmd.append("main.py")
subprocess.check_call(cmd)
print(" Executable built successfully")
except subprocess.CalledProcessError as e:
print(f"❌ Build failed: {e}")
sys.exit(1)
def create_installer():
"""Create installer package."""
print("📦 Creating installer...")
# This would integrate with NSIS, Inno Setup, or similar
# For now, just copy additional files
try:
# Copy documentation
doc_dir = BuildConfig.DIST_DIR / "docs"
if Path("docs").exists():
shutil.copytree("docs", doc_dir)
# Copy examples
example_dir = BuildConfig.DIST_DIR / "examples"
if Path("examples").exists():
shutil.copytree("examples", example_dir)
# Create README for distribution
readme_content = f"""
{BuildConfig.APP_NAME} v{BuildConfig.APP_VERSION}
{BuildConfig.DESCRIPTION}
Created with Bravura SDK - Professional GUI Framework
Installation:
1. Extract all files to a folder
2. Run {BuildConfig.APP_NAME}.exe
For support: contact {BuildConfig.AUTHOR}
"""
readme_path = BuildConfig.DIST_DIR / "README.txt"
with open(readme_path, 'w') as f:
f.write(readme_content)
print(" Installer package created")
except Exception as e:
print(f"❌ Failed to create installer: {e}")
sys.exit(1)
def run_tests():
"""Run test suite before building."""
print("🧪 Running tests...")
try:
result = subprocess.run([
sys.executable, "-m", "pytest", "tests/",
"-v", "--tb=short"
], capture_output=True, text=True)
if result.returncode != 0:
print("❌ Tests failed:")
print(result.stdout)
print(result.stderr)
sys.exit(1)
print(" All tests passed")
except FileNotFoundError:
print("⚠️ pytest not found, skipping tests")
except subprocess.CalledProcessError as e:
print(f"❌ Test execution failed: {e}")
sys.exit(1)
def main():
"""Main build process."""
print(f"🚀 Building {BuildConfig.APP_NAME} v{BuildConfig.APP_VERSION}")
print("=" * 50)
# Run build steps
run_tests()
clean_build()
create_build_environment()
install_dependencies()
build_executable()
create_installer()
# Success message
exe_path = BuildConfig.DIST_DIR / f"{BuildConfig.APP_NAME}.exe"
size_mb = exe_path.stat().st_size / 1024 / 1024
print("\n" + "=" * 50)
print("✅ Build completed successfully!")
print(f"📁 Output: {BuildConfig.DIST_DIR}")
print(".1f")
print(f"🎯 Ready for distribution")
if __name__ == "__main__":
main()
---
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
language_version: python3.8
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/pycqa/flake8
rev: 4.0.1
hooks:
- id: flake8
args: ["--max-line-length=100", "--extend-ignore=E203,W503"]
# docs_maintenance.py
"""
Documentation maintenance utilities.
"""
import os
import re
from pathlib import Path
class DocsMaintainer:
"""Maintain documentation quality and consistency."""
def __init__(self, docs_dir="docs"):
self.docs_dir = Path(docs_dir)
def check_broken_links(self):
"""Check for broken internal links in documentation."""
broken_links = []
for md_file in self.docs_dir.glob("**/*.md"):
content = md_file.read_text()
# Find markdown links
link_pattern = r'\[([^\]]+)\]\(([^)]+)\)'
links = re.findall(link_pattern, content)
for link_text, link_url in links:
if link_url.startswith('./') or link_url.startswith('../'):
# Relative link - check if target exists
target_path = (md_file.parent / link_url).resolve()
if not target_path.exists():
broken_links.append({
'file': str(md_file),
'link_text': link_text,
'link_url': link_url,
'target_path': str(target_path)
})
return broken_links
def check_consistency(self):
"""Check documentation consistency."""
issues = []
# Check version consistency
version_pattern = r'Version:\s*([\d.]+)'
versions = {}
for md_file in self.docs_dir.glob("**/*.md"):
content = md_file.read_text()
matches = re.findall(version_pattern, content, re.IGNORECASE)
if matches:
versions[str(md_file)] = matches
# Check for version inconsistencies
if versions:
all_versions = [v for versions_list in versions.values() for v in versions_list]
unique_versions = set(all_versions)
if len(unique_versions) > 1:
issues.append({
'type': 'version_inconsistency',
'message': f'Multiple versions found: {unique_versions}',
'files': versions
})
return issues
def update_timestamps(self):
"""Update last modified timestamps in documentation."""
import datetime
for md_file in self.docs_dir.glob("**/*.md"):
stat = md_file.stat()
modified_time = datetime.datetime.fromtimestamp(stat.st_mtime)
content = md_file.read_text()
# Update timestamp pattern
timestamp_pattern = r'(\* Last Updated:).*'
new_timestamp = f'* Last Updated: {modified_time.strftime("%B %d, %Y")}'
if re.search(timestamp_pattern, content):
new_content = re.sub(timestamp_pattern, new_timestamp, content)
md_file.write_text(new_content)
print(f"Updated timestamp in {md_file}")
def validate_examples(self):
"""Validate code examples in documentation."""
validation_issues = []
for md_file in self.docs_dir.glob("**/*.md"):
content = md_file.read_text()
# Find Python code blocks
code_block_pattern = r'```python\s*(.*?)\s*```'
code_blocks = re.findall(code_block_pattern, content, re.DOTALL)
for i, code_block in enumerate(code_blocks):
# Basic syntax check
try:
compile(code_block, f'<{md_file.name}:block_{i+1}>', 'exec')
except SyntaxError as e:
validation_issues.append({
'file': str(md_file),
'block': i + 1,
'error': str(e),
'code': code_block[:200] + '...' if len(code_block) > 200 else code_block
})
return validation_issues
def generate_toc(self, md_file):
"""Generate table of contents for a markdown file."""
content = md_file.read_text()
# Find headers
header_pattern = r'^(#{1,6})\s+(.+){{RenderedMarkdown}}#39;
headers = []
for line in content.split('\n'):
match = re.match(header_pattern, line)
if match:
level = len(match.group(1))
title = match.group(2)
headers.append((level, title))
# Generate TOC
toc_lines = ['## Table of Contents', '']
for level, title in headers:
indent = ' ' * (level - 2) if level > 2 else ''
link = re.sub(r'[^\w\s-]', '', title).replace(' ', '-').lower()
toc_lines.append(f'{indent}- [{title}](#{link})')
toc = '\n'.join(toc_lines)
# Insert TOC after title
lines = content.split('\n')
insert_index = 1
# Find end of frontmatter/title
for i, line in enumerate(lines):
if line.startswith('---') or line.strip() == '':
continue
elif line.startswith('#') and i > 0:
insert_index = i
break
# Insert TOC
new_content = '\n'.join(lines[:insert_index]) + '\n\n' + toc + '\n\n' + '\n'.join(lines[insert_index:])
md_file.write_text(new_content)
print(f"Generated TOC for {md_file}")
def main():
"""Run documentation maintenance."""
maintainer = DocsMaintainer()
print("🔍 Checking documentation...")
# Check broken links
broken_links = maintainer.check_broken_links()
if broken_links:
print(f"❌ Found {len(broken_links)} broken links:")
for link in broken_links:
print(f" {link['file']}: {link['link_text']} -> {link['link_url']}")
# Check consistency
consistency_issues = maintainer.check_consistency()
if consistency_issues:
print(f"⚠️ Found {len(consistency_issues)} consistency issues:")
for issue in consistency_issues:
print(f" {issue['type']}: {issue['message']}")
# Validate examples
validation_issues = maintainer.validate_examples()
if validation_issues:
print(f"❌ Found {len(validation_issues)} code validation issues:")
for issue in validation_issues:
print(f" {issue['file']} block {issue['block']}: {issue['error']}")
# Update timestamps
maintainer.update_timestamps()
print("✅ Documentation maintenance complete")
if __name__ == "__main__":
main()
---
This comprehensive best practices guide covers the essential patterns and techniques for building production-quality applications with the Bravura SDK. Following these practices ensures maintainable, performant, and professional applications that scale effectively.