Skip to content

Commit b4c90df

Browse files
authored
Merge pull request #95 from ghostbsd/improve-dev-docs
Update contributor documentation for Python and development setup
2 parents be15ad5 + 2a663c3 commit b4c90df

File tree

2 files changed

+361
-1
lines changed

2 files changed

+361
-1
lines changed

contributor/code/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Automation and system configuration:
3131
**[Development Environment Setup](development-setup)****Start here!** Set up your development machine, install tools, and configure your environment for GhostBSD development.
3232

3333
### Development Guides
34-
- [Python Development Guide](python-guide)Contributing to our GTK3 applications
34+
- [Python Development Guide](python-guide)Python coding standards, typing, and GTK3 development
3535
- [C Development Guide](c-guide) – Working with system components
3636
- [Shell Scripting Guide](shell-guide) – Build tools and configuration scripts
3737
- [Testing and QA](testing) – How to test your changes

contributor/code/python-guide.md

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
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

Comments
 (0)