This guide outlines coding practices for developing Python code in the GEEST project, including adherence to Python naming conventions, formatting styles, type declarations, and logging mechanisms.
- Consistency: Ensure consistent naming conventions, formatting, and structure throughout the codebase.
- Readability: Code should be clear and easy to read, with well-defined logical flows and separation of concerns.
- Robustness: Implement error handling to gracefully manage unexpected situations.
Follow the standard Python naming conventions as defined in PEP 8:
- Variable and Function Names: Use
snake_case
.def create_study_area_directory(working_dir: str) -> str: ...
- Class Names: Use
PascalCase
.class StudyAreaProcessor: ...
- Constants: Use
UPPER_SNAKE_CASE
.DEFAULT_EPSG_CODE = 4326
- Private Variables and Methods: Use a leading underscore.
def _calculate_utm_zone(self, bbox: QgsRectangle) -> int: ...
Follow the standard conventions for PyQt widgets and properties, even when they do not adhere to typical Python naming conventions:
- Signals and Slots: Use
camelCase
. - PyQt Widget Properties and Methods: Use the default
camelCase
as provided by PyQt.
Example:
self.layer_combo.layerChanged.connect(self.field_combo.setLayer)
- Use Black: All Python code should be formatted with black, the opinionated code formatter.
- Configuration: Use the default line length of 88 characters. This ensures uniform formatting across all files and makes the codebase easier to read and maintain.
To format code with Black:
black .
- Use 4 spaces per indentation level.
- Leave 1 blank line between functions and class definitions.
- Leave 2 blank lines before class definitions.
- Always declare types for variables, parameters, and return values to enhance code clarity and type safety.
Examples:
def create_study_area_directory(working_dir: str) -> str:
study_area_dir: str = os.path.join(working_dir, "study_area")
...
layer: QgsVectorLayer = self.layer_combo.currentLayer()
- Use type hints for all function parameters and return values.
- If a function does not return any value, use
-> None
.
Example:
def process_study_area(self) -> None:
...
- Import types from
typing
where necessary:Optional
: To indicate optional parameters.List
,Dict
,Tuple
: For more complex types.
Example:
from typing import List, Optional
def save_to_geopackage(features: List[QgsFeature], layer_name: str) -> None:
...
- Do not use
print()
statements for debugging or outputting messages. - Use
QgsMessageLog
for all logging to ensure messages are appropriately directed to QGIS's logging system.
- Tag all messages with
'Geest'
to allow filtering in the QGIS log. - Use appropriate log levels:
Qgis.Info
: For informational messages.Qgis.Warning
: For warnings that do not interrupt the workflow.Qgis.Critical
: For errors that need immediate attention.
Examples:
QgsMessageLog.logMessage("Created study area grid.", tag="Geest", level=Qgis.Info)
QgsMessageLog.logMessage("Warning: Invalid geometry found.", tag="Geest", level=Qgis.Warning)
QgsMessageLog.logMessage("Error transforming geometry.", tag="Geest", level=Qgis.Critical)
- Graceful Error Handling: Always catch exceptions and provide meaningful error messages through
QgsMessageLog
. - Use
try
/except
Blocks: Wrap code that may raise exceptions intry
/except
blocks and log the error.
Example:
try:
processor.process_study_area()
except Exception as e:
QgsMessageLog.logMessage(f"Error processing study area: {e}", tag="Geest", level=Qgis.Critical)
- Follow the conventions required by PyQt5, using
camelCase
where necessary for properties and methods. - Use descriptive variable names for widgets:
layer_combo
forQgsMapLayerComboBox
field_combo
forQgsFieldComboBox
continue_button
forQPushButton
- Maintain a consistent naming pattern throughout the user interface code.
- Class Methods Order:
__init__
method- Public methods in the order of their usage
- Private (helper) methods prefixed with
_
- Add docstrings to all functions, classes, and modules using the
"""triple quotes"""
format. - Include a brief description, parameters, and return values where applicable.
Example:
def select_directory(self) -> None:
"""
Opens a file dialog to select the working directory and saves it using QSettings.
"""
...
- Use inline comments sparingly and only when necessary to clarify complex logic.
- Use the
#
symbol with a space to start the comment.
Example:
# Transform geometry to the correct CRS once at the start
geom.transform(transform)
- Naming: Use
snake_case
,PascalCase
, orcamelCase
as appropriate. - Formatting: Use
black
for consistent code formatting. - Type Declarations: Declare types for all variables and function signatures.
- Logging: Use
QgsMessageLog
with the tag'Geest'
. - Error Handling: Catch and log exceptions appropriately.
- PyQt5: Follow PyQt5's conventions for widget naming and handling.
- Docstrings and Comments: Use meaningful docstrings and comments to explain the code.
Following these guidelines ensures that code within the GEEST project is clear, consistent, and maintainable.