Coding Style Guide - CApy-RPI/app GitHub Wiki
Coding Style Guide for Python Discord Bot with MongoDB
1. Code Structure and Organization
1.1 Folder Structure
Maintain a clear, modular folder layout:
capy/
├── docs/
├── src/
│ ├── capy_app/
│ │ ├── backend/
│ │ │ ├── db/ # Database models and connection utilities
│ │ │ ├── mods/ # Additional modules for extended functionalities
│ │ │ ├── res/ # Static resources like images, JSON files, etc.
│ │ │ ├── utils/ # Utility/helper functions
│ │ ├── frontend/
│ │ │ ├── cogs/ # Separate modules for bot commands/features
│ │ │ ├── bot.py
│ │ ├── config.py # Configuration constants
│ │ └── main.py # Entry point of the bot
├── tests/ # Unit and integration tests
│ ├── capy_app/
│ │ ├── backend/
│ │ │ ├── db/
│ │ │ ├── mods/
│ │ │ ├── res/
│ │ │ ├── utils/
│ │ ├── frontend/
│ │ │ ├── cogs/
│ │ │ ├── bot.py
│ │ ├── config.py
│ │ └── main.py
├── .env # Environment variables and secrets
├── requirements.txt # Python dependencies
├── requirements_dev.txt # Development dependencies
├── style.md # Coding style guide
├── test.md # Testing script
└── updater.py # Script for updating host
1.2 Code Structure
- Use a class-based design for modularity:
- Define a
Cog
for each bot feature (e.g., moderation, profiles). - Separate utility methods by category into individual files for clarity.
- Define a
2. Python Conventions
2.1 Variables and Functions
-
Use snake_case for variables and functions:
-
snake_case: Naming convention where words are written in lowercase and separated by underscores (_).
# Variable
user_profile = {}
# Function
def get_user_profile(user_id: str) -> dict:
...
2.2 Classes
-
Use PascalCase for class names:
-
PascalCase: Naming convention where all words start with an uppercase letter and no spaces or underscores are used - all words are joined together.
class UserProfile:
...
class GetUserProfile:
...
class PascalCaseExample:
...
2.3 Files
- Use lowercase_with_underscores for file names (e.g.,
user_profiles.py
).
3. Comments and Documentation
3.1 Inline Comments
- Provide concise, meaningful inline comments above complex code:
# Fetch the user profile from the database
user_profile = db.get_user(user_id)
3.2 Function and Class Docstrings
- Write docstrings for all public functions, methods, and classes. Follow the Google style for docstrings:
def fetch_user_data(user_id: str) -> dict:
"""
Fetch user data from the database.
Args:
user_id (str): The unique ID of the user.
Returns:
dict: The user profile data.
"""
3.3 Module Docstrings
- Include a brief description at the top of each file:
"""
This module handles MongoDB connection and operations
for user profiles.
"""
4. Semantic Guidelines
4.1 Code Readability
- Prioritize clarity over brevity:
# Good
for user in user_list:
if user.is_active():
active_users.append(user)
# Bad
active_users = [u for u in user_list if u.is_active()]
4.2 MongoDB Semantics
- Use descriptive collection and field names:
- Collection names: snake_case plural (e.g.,
user_profiles
). - Field names: camelCase (to match MongoDB standards).
- Collection names: snake_case plural (e.g.,
{
"_id": "123456",
"userName": "johndoe",
"profileSettings": {
"theme": "dark",
"notificationsEnabled": true
}
}
4.3 Logging
- Use Python's
logging
module:- Log at appropriate levels (
DEBUG
,INFO
,WARNING
,ERROR
,CRITICAL
). - Include timestamps and context:
- Log at appropriate levels (
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
4.4 Guard Clauses
- Use guard clauses to simplify complex conditional logic:
def process_user(user):
if not user.is_active():
return
# Process active user
...
- As compared to:
def process_user(user):
if user.is_active():
# Process active user
...
4.5 Helper Methods
- Use helper methods to break down complex algorithms into smaller, manageable pieces, especially for methods with many tab scopes:
def complex_algorithm(data):
result = step_one(data)
result = step_two(result)
return step_three(result)
def step_one(data):
# Perform first step
...
def step_two(data):
# Perform second step
...
def step_three(data):
# Perform third step
...
4.6 Error Handling
- Catch specific exceptions, log meaningful messages, and use
raise
to propagate errors:
try:
db.connect()
except ConnectionError as e:
logging.error(f"Database connection failed: {e}")
raise
- Example:
def fetch_user_data(user_id: str) -> dict:
try:
user_data = db.get_user(user_id)
if not user_data:
raise ValueError(f"No user found with ID: {user_id}")
return user_data
except Exception as e:
logging.error(f"Error fetching user data: {e}")
raise
5. Imports
5.1 Minimize Imports
- Minimize the use of
from library import a, b, c, d
. Instead, import the entire module and use it with the module name to avoid namespace conflicts:
# Good
import os
import sys
# Bad
from os import path, mkdir, remove
from sys import argv, exit
5.2 Import Order
- Follow the standard import order: standard library imports, third-party imports, and local imports. Separate each group with a blank line:
# Standard library imports
import os
import sys
# Third-party imports
import requests
# Local imports
from .utils import helper_function
6. Module Initialization
__init__.py
Files
6.1 Creating - When adding new scripts to a module, ensure that the module's directory contains an
__init__.py
file. This file can be empty or used to initialize the module.