Skip to main content
Aller au contenu principal

Code Style Guide

Coding standards and best practices for FilterMate development.

Python Standards

PEP 8 Compliance

Follow PEP 8 conventions:

  • Line length: Maximum 120 characters
  • Indentation: 4 spaces (no tabs)
  • Blank lines: 2 between top-level definitions
  • Encoding: UTF-8

Naming Conventions

Classes

# PascalCase
class FilterMateApp:
pass

class FilterEngineTask:
pass

Functions and Methods

# snake_case
def manage_task(task_type, parameters):
pass

def get_datasource_connexion_from_layer(layer):
pass

Constants

# UPPER_SNAKE_CASE
POSTGRESQL_AVAILABLE = True
MAX_RETRIES = 5
DEFAULT_BUFFER_DISTANCE = 100

Private Methods

# Prefix with underscore
def _internal_method(self):
pass

def _validate_input(self, data):
pass

Import Organization

# 1. Standard library
import os
import sys
from typing import Optional, List, Dict

# 2. Third-party (QGIS, PyQt)
from qgis.core import QgsVectorLayer, QgsProject
from qgis.PyQt.QtCore import Qt, pyqtSignal

# 3. Local application
from .config.config import ENV_VARS
from .modules.appUtils import get_datasource_connexion_from_layer

Critical Patterns

1. PostgreSQL Availability Check

ALWAYS check before using PostgreSQL:

from modules.appUtils import POSTGRESQL_AVAILABLE

if POSTGRESQL_AVAILABLE and provider_type == 'postgresql':
# Safe to use psycopg2
connexion = psycopg2.connect(...)
else:
# Fallback to Spatialite or OGR
pass

2. Provider Type Detection

if layer.providerType() == 'postgres':
layer_provider_type = 'postgresql'
elif layer.providerType() == 'spatialite':
layer_provider_type = 'spatialite'
elif layer.providerType() == 'ogr':
layer_provider_type = 'ogr'
else:
layer_provider_type = 'unknown'

3. Signal Blocking

from modules.signal_utils import SignalBlocker

with SignalBlocker(widget):
# Signals blocked here
widget.setValue(new_value)
# Signals automatically restored

4. Resource Management

conn = None
try:
conn = sqlite3.connect(db_path)
# operations
conn.commit()
finally:
if conn:
conn.close()

Documentation

Docstrings

def my_function(param1, param2):
"""
Brief description of function.

Longer description if needed. Explain the purpose,
any important details, or caveats.

Args:
param1 (type): Description
param2 (type): Description

Returns:
type: Description of return value

Raises:
ExceptionType: When this happens
"""
pass

Comments

# Explain WHY, not WHAT
if layer.featureCount() > 50000:
# Large dataset: use PostgreSQL for better performance
backend = PostgreSQLBackend(layer)

# TODO comments for future work
# TODO Phase 3: Implement result caching

# FIXME for known issues
# FIXME: Handle edge case with NULL geometries

Error Handling

User-Facing Errors

from qgis.utils import iface

# Success (duration is positional argument)
iface.messageBar().pushSuccess(
"FilterMate",
"Filter applied successfully",
3
)

# Warning
iface.messageBar().pushWarning(
"FilterMate",
f"Large dataset ({count} features). Consider PostgreSQL.",
10
)

# Error
iface.messageBar().pushCritical(
"FilterMate",
f"Error: {str(error)}",
5
)

Exception Handling

try:
result = backend.execute_filter(...)
except DatabaseConnectionError as e:
show_error(f"Cannot connect: {e}")
except GeometryError as e:
# Attempt automatic repair
repaired = repair_geometry(geom)
result = backend.execute_filter(...)
except Exception as e:
# Catch-all for unexpected errors
logger.error(f"Unexpected error: {e}", exc_info=True)
show_error(f"Operation failed: {e}")

Performance

Large Dataset Warnings

if layer.featureCount() > 50000 and not POSTGRESQL_AVAILABLE:
iface.messageBar().pushWarning(
"FilterMate",
f"Large dataset ({layer.featureCount()} features) "
"without PostgreSQL. Performance may be reduced. "
"Consider installing psycopg2.",
duration=10
)

Lazy Evaluation

# Good: Only load when needed
if need_features:
features = list(layer.getFeatures())

# Avoid: Loading everything upfront
all_features = list(layer.getFeatures())
if need_features:
# use all_features

Testing

Unit Test Structure

import unittest
from unittest.mock import Mock, patch

class TestMyFeature(unittest.TestCase):
def setUp(self):
"""Setup before each test."""
self.layer = Mock(spec=QgsVectorLayer)

def tearDown(self):
"""Cleanup after each test."""
pass

def test_something(self):
"""Test description."""
# Arrange
expected = True

# Act
result = my_function()

# Assert
self.assertEqual(result, expected)

Git Commit Messages

Follow Conventional Commits:

feat: Add Spatialite backend support
fix: Correct provider type detection for OGR layers
docs: Update README with installation instructions
test: Add unit tests for Phase 1
refactor: Extract common DB connection logic
perf: Optimize spatial index creation
style: Format code with black
chore: Update dependencies

Common Anti-Patterns

❌ DON'T

# Don't import psycopg2 directly
import psycopg2 # Will crash if not installed

# Don't assume PostgreSQL is available
conn = psycopg2.connect(...) # May fail

# Don't use blocking operations in main thread
for i in range(1000000):
process(i) # UI freezes

# Don't forget to close connections
conn = sqlite3.connect(db)
# operations
# No cleanup!

✅ DO

# Check availability
from modules.appUtils import POSTGRESQL_AVAILABLE
if POSTGRESQL_AVAILABLE:
import psycopg2

# Provide fallback
if POSTGRESQL_AVAILABLE and provider == 'postgres':
use_postgresql_backend()
else:
use_spatialite_backend()

# Use QgsTask for heavy operations
task = FilterEngineTask(...)
QgsApplication.taskManager().addTask(task)

# Always cleanup
try:
conn = sqlite3.connect(db)
# operations
finally:
if conn:
conn.close()

Code Review Checklist

Before submitting:

  • Code follows PEP 8 style
  • PostgreSQL availability checked where needed
  • Error handling implemented
  • Resources properly cleaned up
  • Tests added/updated
  • Documentation updated
  • Commit messages follow convention
  • No debugging code left (print statements, etc.)

Further Reading