This guide will help you get started with the Template Desktop Application in under 5 minutes.
Requirements: Python 3.14 on Linux
# Clone the repository
git clone <your-repo-url>
cd Template-Desktop-Application
# Run setup script (checks Python 3.14, creates venv, installs dependencies)
./setup.sh
# Activate virtual environment
source venv/bin/activatepython src/main.pyYou should see a window with:
- "Load Data" button
- "Clear" button
- Item list
- Details view
- Status bar
Try clicking "Load Data" to see the example in action!
# Run all tests
pytest tests/ -v
# Run with coverage
pytest --cov=src --cov-report=html tests/
# Open coverage report
xdg-open htmlcov/index.html-
Model - Pure Python business logic:
cat src/models/example_model.py
-
ViewModel - Presentation logic with Qt signals:
cat src/viewmodels/main_viewmodel.py
-
View - PySide6 UI components:
cat src/views/main_window.py
-
Service - External operations:
cat src/services/example_service.py
# Model tests
cat tests/unit/test_example_model.py
# ViewModel tests
cat tests/unit/test_main_viewmodel.py
# Integration tests
cat tests/integration/test_application.py-
Create the Model (
src/models/counter.py):from dataclasses import dataclass @dataclass class Counter: value: int = 0 def increment(self) -> int: self.value += 1 return self.value
-
Create the ViewModel (
src/viewmodels/counter_viewmodel.py):from PySide6.QtCore import QObject, Signal, Slot from models.counter import Counter class CounterViewModel(QObject): count_changed = Signal(int) def __init__(self): super().__init__() self._counter = Counter() @Slot() def increment(self): new_value = self._counter.increment() self.count_changed.emit(new_value)
-
Add to View (modify
src/views/main_window.py):# In _setup_ui(): self._count_button = QPushButton("Count") self._count_label = QLabel("Count: 0") # In _connect_signals(): self._count_button.clicked.connect(counter_vm.increment) counter_vm.count_changed.connect( lambda v: self._count_label.setText(f"Count: {v}") )
-
Write Tests (
tests/unit/test_counter.py):from src.models.counter import Counter def test_counter_increments(): counter = Counter() result = counter.increment() assert result == 1 assert counter.value == 1
-
Run Tests:
pytest tests/unit/test_counter.py -v
User Action (View) → Signal → ViewModel Method → Service/Model → Signal → View Update
Example from the template:
- User clicks "Load Data" button
- View emits signal to ViewModel.load_data()
- ViewModel calls Service.fetch_data()
- Service returns Models
- ViewModel processes Models
- ViewModel emits data_loaded signal
- View receives signal and updates UI
See src/main.py for the DI pattern:
# Create dependencies (bottom-up)
service = ExampleService() # Infrastructure
viewmodel = MainViewModel(service) # Presentation
view = MainWindow(viewmodel) # UIAGENTS.md- Quick reference and patterns.github/copilot-instructions.md- Comprehensive guidelinesCONTRIBUTING.md- Contribution guidelines
pyproject.toml- Python project configurationrequirements.txt- Dependencies.github/workflows/ci.yml- CI/CD pipeline
-
Read the docs:
README.md- Project overviewAGENTS.md- Development patterns.github/copilot-instructions.md- Detailed guidelines
-
Explore the code:
- Run the application and click around
- Read the example implementations
- Look at the tests to understand patterns
-
Make it yours:
- Remove example files
- Add your own features
- Follow the MVVM pattern
- Write tests for everything
-
Get help:
- Check
AGENTS.mdfor common patterns - Review tests for examples
- Open an issue if stuck
- Check
# Development
python src/main.py # Run application
pytest tests/ -v # Run tests
pytest --cov=src tests/ # Run with coverage
# Code Quality
black src/ tests/ # Format code
isort src/ tests/ # Sort imports
mypy src/ # Type check
pylint src/ # Lint code
# Git
git checkout -b feature/my-feature # Create branch
git commit -m "feat: add feature" # Commit
git push origin feature/my-feature # Push- Always write tests first - TDD helps design better APIs
- Keep MVVM layers separated - No Qt in Models, no business logic in Views
- Use dependency injection - Pass dependencies via constructors
- Use signals for communication - Don't call methods across layers directly
- Type hint everything - Makes code self-documenting
- Run tests frequently - Catch issues early
Add qapp fixture to your test:
def test_something(qapp):
# Your testMake sure virtual environment is activated:
source venv/bin/activateRun with verbose output:
pytest tests/ -v -sYou now have:
- ✅ A working PySide6 application
- ✅ MVVM architecture in place
- ✅ Tests passing
- ✅ CI/CD configured
- ✅ Comprehensive documentation
Start building your application! 🚀