|
| 1 | +# Python Development Guide |
| 2 | + |
| 3 | +This guide outlines the Python coding standards for GhostBSD projects. Following these standards ensures code consistency, maintainability, and compatibility with our development tools. |
| 4 | + |
| 5 | +## Code Style Standards |
| 6 | + |
| 7 | +### PEP 8 Compliance |
| 8 | +All Python code must follow PEP 8 standards with these specific configurations: |
| 9 | + |
| 10 | +- **Line length**: Maximum 120 characters |
| 11 | +- **Indentation**: 4 spaces (no tabs) |
| 12 | +- **Imports**: Organize in three groups separated by blank lines: |
| 13 | + 1. Standard library imports |
| 14 | + 2. Related third-party imports |
| 15 | + 3. Local application/library imports |
| 16 | + |
| 17 | +### Naming Conventions |
| 18 | +- **Variables**: `snake_case` (e.g., `network_name`, `retry_count`) |
| 19 | +- **Functions**: `snake_case` (e.g., `connect_to_network()`, `get_available_networks()`) |
| 20 | +- **Methods**: `snake_case` (e.g., `self.setup_ui()`, `self._validate_input()`) |
| 21 | +- **Classes**: `PascalCase` (e.g., `NetworkManager`, `ConfigDialog`) |
| 22 | +- **Constants**: `UPPER_SNAKE_CASE` (e.g., `MAX_RETRY_ATTEMPTS`, `DEFAULT_TIMEOUT`) |
| 23 | +- **Private attributes**: Single leading underscore `_private_var` |
| 24 | +- **Protected attributes**: Double leading underscore `__protected_var` |
| 25 | + |
| 26 | +### Code Formatting |
| 27 | +- **String quotes**: Use double quotes `"` for user-facing strings, single quotes `'` for internal strings |
| 28 | +- **Trailing commas**: Always use trailing commas in multi-line lists, tuples, and dictionaries |
| 29 | +- **Blank lines**: Two blank lines between top-level functions and classes, one blank line between methods |
| 30 | + |
| 31 | +## Pylint Configuration |
| 32 | + |
| 33 | +Configure Pylint to match our standards by creating a `.pylintrc` file in your project root: |
| 34 | + |
| 35 | +```ini |
| 36 | +[MASTER] |
| 37 | +extension-pkg-whitelist=gi |
| 38 | + |
| 39 | +[FORMAT] |
| 40 | +max-line-length=120 |
| 41 | +indent-string=' ' |
| 42 | + |
| 43 | +[MESSAGES CONTROL] |
| 44 | +disable=missing-docstring, |
| 45 | + too-few-public-methods, |
| 46 | + import-error, |
| 47 | + no-member |
| 48 | + |
| 49 | +[DESIGN] |
| 50 | +max-args=8 |
| 51 | +max-locals=20 |
| 52 | +max-branches=15 |
| 53 | +``` |
| 54 | + |
| 55 | +## Internationalization with Gettext |
| 56 | + |
| 57 | +All user-visible text must be internationalized using gettext. This ensures GhostBSD applications can be translated into multiple languages. |
| 58 | + |
| 59 | +### Setting up Gettext |
| 60 | + |
| 61 | +Import and configure gettext at the top of your Python files following the GhostBSD pattern: |
| 62 | + |
| 63 | +```python |
| 64 | +import gettext |
| 65 | + |
| 66 | +# Set up translation (replace 'your-app-name' with your application name) |
| 67 | +gettext.bindtextdomain('your-app-name', '/usr/local/share/locale') |
| 68 | +gettext.textdomain('your-app-name') |
| 69 | +_ = gettext.gettext |
| 70 | +``` |
| 71 | + |
| 72 | +### Marking Translatable Strings |
| 73 | + |
| 74 | +Wrap all user-visible strings with the `_()` function: |
| 75 | + |
| 76 | +```python |
| 77 | +# Good - translatable |
| 78 | +error_message = _("Unable to connect to network") |
| 79 | +dialog.set_title(_("Network Settings")) |
| 80 | +button.set_label(_("Apply Changes")) |
| 81 | + |
| 82 | +# Bad - not translatable |
| 83 | +error_message = "Unable to connect to network" |
| 84 | +dialog.set_title("Network Settings") |
| 85 | +``` |
| 86 | + |
| 87 | +### String Formatting with Gettext |
| 88 | + |
| 89 | +Use format strings properly with translations: |
| 90 | + |
| 91 | +```python |
| 92 | +# Good - proper formatting |
| 93 | +message = _("Connected to {network_name}").format(network_name=ssid) |
| 94 | +count_text = _("Found {count} networks").format(count=len(networks)) |
| 95 | + |
| 96 | +# Bad - formatting outside translation |
| 97 | +message = _("Connected to") + f" {ssid}" |
| 98 | +``` |
| 99 | + |
| 100 | +### Pluralization |
| 101 | + |
| 102 | +Handle plural forms correctly: |
| 103 | + |
| 104 | +```python |
| 105 | +import ngettext |
| 106 | + |
| 107 | +# Proper pluralization |
| 108 | +count_message = ngettext( |
| 109 | + "Found {count} network", |
| 110 | + "Found {count} networks", |
| 111 | + network_count |
| 112 | +).format(count=network_count) |
| 113 | +``` |
| 114 | + |
| 115 | +## Documentation Standards |
| 116 | + |
| 117 | +### Docstrings |
| 118 | +Use Google-style docstrings for all public functions, classes, and modules: |
| 119 | + |
| 120 | +```python |
| 121 | +def connect_to_network(ssid, password=None): |
| 122 | + """Connect to a wireless network. |
| 123 | + |
| 124 | + Args: |
| 125 | + ssid (str): The network SSID to connect to |
| 126 | + password (str, optional): Network password. Defaults to None. |
| 127 | + |
| 128 | + Returns: |
| 129 | + bool: True if connection successful, False otherwise |
| 130 | + |
| 131 | + Raises: |
| 132 | + NetworkError: If the network interface is not available |
| 133 | + """ |
| 134 | + pass |
| 135 | +``` |
| 136 | + |
| 137 | +### Type Hints |
| 138 | +Use type hints for function parameters and return values: |
| 139 | + |
| 140 | +```python |
| 141 | +def get_available_networks() -> list[dict[str, str]]: |
| 142 | + """Get list of available wireless networks. |
| 143 | + |
| 144 | + Returns: |
| 145 | + List of dictionaries containing network information |
| 146 | + """ |
| 147 | + pass |
| 148 | + |
| 149 | +def connect_to_network(ssid: str, password: str | None = None) -> bool: |
| 150 | + """Connect to specified network.""" |
| 151 | + pass |
| 152 | +``` |
| 153 | + |
| 154 | +## Type Annotations |
| 155 | + |
| 156 | +Type annotations are required for all new code. Python 3.11+ provides modern syntax without importing from `typing`. |
| 157 | + |
| 158 | +### Basic Types |
| 159 | +Use built-in types directly: |
| 160 | + |
| 161 | +```python |
| 162 | +def process_config(name: str, port: int, enabled: bool) -> None: |
| 163 | + """Process configuration with basic types.""" |
| 164 | + pass |
| 165 | + |
| 166 | +def get_timeout() -> float: |
| 167 | + """Return timeout value in seconds.""" |
| 168 | + return 30.5 |
| 169 | +``` |
| 170 | + |
| 171 | +### Collection Types |
| 172 | +Use built-in collection types directly: |
| 173 | + |
| 174 | +```python |
| 175 | +def get_network_list() -> list[str]: |
| 176 | + """Return list of network names.""" |
| 177 | + return ["WiFi-1", "WiFi-2"] |
| 178 | + |
| 179 | +def get_network_config() -> dict[str, str]: |
| 180 | + """Return network configuration mapping.""" |
| 181 | + return {"ssid": "MyNetwork", "security": "WPA2"} |
| 182 | + |
| 183 | +def get_coordinates() -> tuple[int, int]: |
| 184 | + """Return x, y coordinates.""" |
| 185 | + return (100, 200) |
| 186 | + |
| 187 | +def get_unique_names() -> set[str]: |
| 188 | + """Return set of unique network names.""" |
| 189 | + return {"WiFi-1", "WiFi-2"} |
| 190 | +``` |
| 191 | + |
| 192 | +### Optional and Union Types |
| 193 | +Use modern union syntax with `|`: |
| 194 | + |
| 195 | +```python |
| 196 | +def connect_with_password(password: str | None = None) -> bool: |
| 197 | + """Connect to network with optional password.""" |
| 198 | + if password is None: |
| 199 | + return connect_without_auth() |
| 200 | + return connect_with_auth(password) |
| 201 | + |
| 202 | +def parse_value(data: str | int) -> str: |
| 203 | + """Parse value that can be string or integer.""" |
| 204 | + return str(data) |
| 205 | + |
| 206 | +def get_config_value(key: str) -> str | int | bool: |
| 207 | + """Get configuration value of various types.""" |
| 208 | + pass |
| 209 | +``` |
| 210 | + |
| 211 | +### Class Annotations |
| 212 | +Annotate class attributes and methods: |
| 213 | + |
| 214 | +```python |
| 215 | +class NetworkManager: |
| 216 | + """Network management class with type annotations.""" |
| 217 | + |
| 218 | + MAX_RETRIES: int = 3 # Class variable |
| 219 | + |
| 220 | + def __init__(self, interface: str) -> None: |
| 221 | + # Instance variables |
| 222 | + self.interface: str = interface |
| 223 | + self.connected: bool = False |
| 224 | + self.current_network: str | None = None |
| 225 | + self._retry_count: int = 0 |
| 226 | + |
| 227 | + def get_networks(self) -> list[dict[str, str]]: |
| 228 | + """Get available networks.""" |
| 229 | + return [] |
| 230 | + |
| 231 | + def connect(self, ssid: str, password: str | None = None) -> bool: |
| 232 | + """Connect to specified network.""" |
| 233 | + self.current_network = ssid |
| 234 | + return True |
| 235 | +``` |
| 236 | + |
| 237 | +### Complex Collections |
| 238 | +Handle nested collections properly: |
| 239 | + |
| 240 | +```python |
| 241 | +def get_network_details() -> dict[str, list[str]]: |
| 242 | + """Get network names mapped to their properties.""" |
| 243 | + return { |
| 244 | + "WiFi-1": ["WPA2", "5GHz", "Strong"], |
| 245 | + "WiFi-2": ["WPA3", "2.4GHz", "Weak"] |
| 246 | + } |
| 247 | + |
| 248 | +def process_connections( |
| 249 | + connections: list[tuple[str, bool]] |
| 250 | +) -> dict[str, bool]: |
| 251 | + """Process list of connection tuples.""" |
| 252 | + return dict(connections) |
| 253 | +``` |
| 254 | + |
| 255 | +## Error Handling |
| 256 | + |
| 257 | +### Exception Handling |
| 258 | +Use specific exception types and provide translatable error messages: |
| 259 | + |
| 260 | +```python |
| 261 | +try: |
| 262 | + result = network_operation() |
| 263 | +except NetworkNotFoundError: |
| 264 | + error_dialog(_("Network not found. Please check the network name.")) |
| 265 | +except PermissionError: |
| 266 | + error_dialog(_("Permission denied. Please run as administrator.")) |
| 267 | +except Exception as e: |
| 268 | + logger.error("Unexpected error: %s", str(e)) |
| 269 | + error_dialog(_("An unexpected error occurred. Please try again.")) |
| 270 | +``` |
| 271 | + |
| 272 | +### Logging |
| 273 | +Use the standard logging module with appropriate levels: |
| 274 | + |
| 275 | +```python |
| 276 | +import logging |
| 277 | + |
| 278 | +logger = logging.getLogger(__name__) |
| 279 | + |
| 280 | +def process_network_config(): |
| 281 | + logger.info(_("Starting network configuration")) |
| 282 | + try: |
| 283 | + # Process configuration |
| 284 | + logger.debug("Configuration data: %s", config_data) |
| 285 | + except ConfigError as e: |
| 286 | + logger.error(_("Configuration error: %s"), str(e)) |
| 287 | + raise |
| 288 | +``` |
| 289 | + |
| 290 | +## GTK3 Integration |
| 291 | + |
| 292 | +### Widget Creation |
| 293 | +Follow consistent patterns when creating GTK widgets: |
| 294 | + |
| 295 | +```python |
| 296 | +import gi |
| 297 | +gi.require_version('Gtk', '3.0') |
| 298 | +from gi.repository import Gtk |
| 299 | + |
| 300 | +class NetworkDialog(Gtk.Dialog): |
| 301 | + def __init__(self, parent=None): |
| 302 | + super().__init__( |
| 303 | + title=_("Network Settings"), |
| 304 | + parent=parent, |
| 305 | + modal=True |
| 306 | + ) |
| 307 | + |
| 308 | + # Add buttons with translatable labels |
| 309 | + self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL) |
| 310 | + self.add_button(_("Apply"), Gtk.ResponseType.OK) |
| 311 | + |
| 312 | + self._setup_ui() |
| 313 | + |
| 314 | + def _setup_ui(self): |
| 315 | + """Set up the dialog user interface.""" |
| 316 | + pass |
| 317 | +``` |
| 318 | + |
| 319 | +### Signal Connections |
| 320 | +Use descriptive callback names and handle errors appropriately: |
| 321 | + |
| 322 | +```python |
| 323 | +def _on_connect_button_clicked(self, button): |
| 324 | + """Handle connect button click event.""" |
| 325 | + try: |
| 326 | + ssid = self.ssid_entry.get_text() |
| 327 | + if not ssid: |
| 328 | + self._show_error(_("Please enter a network name")) |
| 329 | + return |
| 330 | + |
| 331 | + self._connect_to_network(ssid) |
| 332 | + except Exception as e: |
| 333 | + logger.error("Connection failed: %s", str(e)) |
| 334 | + self._show_error(_("Connection failed. Please try again.")) |
| 335 | +``` |
| 336 | + |
| 337 | +## Tools Integration |
| 338 | + |
| 339 | +### IDE Recommendation |
| 340 | +PyCharm Community Edition is recommended for GhostBSD Python development. It provides excellent built-in code analysis, debugging capabilities, and seamless integration with the coding standards outlined above. |
| 341 | + |
| 342 | +Install PyCharm on GhostBSD: |
| 343 | +```bash |
| 344 | +sudo pkg install pycharm-ce |
| 345 | +``` |
| 346 | + |
| 347 | +PyCharm's default settings align well with these standards, providing automatic code formatting, linting, and error detection that matches our requirements. |
| 348 | + |
| 349 | +### Pre-commit Hooks |
| 350 | +Consider setting up pre-commit hooks to automatically check code style: |
| 351 | + |
| 352 | +```bash |
| 353 | +# Install pre-commit |
| 354 | +pip install pre-commit |
| 355 | + |
| 356 | +# Create .pre-commit-config.yaml |
| 357 | +pre-commit install |
| 358 | +``` |
| 359 | + |
| 360 | +Following these standards helps maintain high code quality and ensures your contributions integrate smoothly with the existing GhostBSD codebase. |
0 commit comments