# **Python** **Scope**: This document defines the mandatory standards for all Python code to ensure readability, maintainability, security, and performance. ### **1. Code Layout & Formatting** #### **1.1 Indentation** - **Rule**: Use 4 spaces per indentation level. - **Prohibited**: Never use tabs. #### **1.2 Line Length** - **Standard**: Limit all lines to a maximum of 79 characters (PEP 8 standard). - **Modern Exception**: For complex configurations or type hints, 88 characters (Black formatter default) is acceptable if agreed upon by the team. - **Handling**: Break long lines using parentheses `()`, not backslashes `\`. ```Python # Good user_permissions = ( "read_access", "write_access", "delete_access", "admin_access" ) ``` #### **1.3 Imports** - **Order**: - Standard Library (e.g., `os`, `sys`) - Third-Party (e.g., `requests`, `pandas`) - Local Application (e.g., `from myapp.utils import helper`) - **Style**: Use absolute imports. Avoid wildcard imports (`from module import *`). #### **1.4 Whitespace** - **Operators**: Surround binary operators with a single space (`x = 12`). - **Definitions**: One space after the colon in type hints (`age: int`). - **Functions**: Two blank lines before top-level functions; one blank line before methods inside a class. ### **2. Naming Conventions** Follow strict PEP 8 naming to communicate intent instantly. | Entity | Convention | Example | | --------- | --------------------- | ---------------------- | | Modules | `snake_case` | `data_processor.py` | | Classes | `PascalCase` | `UserProfile` | | Functions | `snake_case` | `calculate_total()` | | Variables | `snake_case` | `is_active`, `user_id` | | Constants | `SCREAMING_SNAKE` | `MAX_RETRIES = 3` | | Private | `_leading_underscore` | `_internal_cache` | ### **3. Modern Type Hinting (Python 3.10+)** Type hinting is no longer optional; it is mandatory for maintainability. #### **3.1 Syntax** - **Rule**: Use the modern pipe | operator for Unions instead of importing `Union` or `Optional`. - **Return Types**: Always specify the return type, even if it is `None`. ```Python # Bad (Old Style) from typing import List, Optional def find_user(ids: List[int]) -> Optional[str]: ... # Good (Modern Style) def find_user(ids: list[int]) -> str | None: ... ``` #### **3.2 Collections** - **Rule**: Use standard collections `(list, dict, tuple)` instead of `typing.List` or `typing.Dict`. ### **4. Documentation & Docstrings** #### **4.1 Format** - **Standard**: Use the Google Style docstring format. It is cleaner and more readable than reStructuredText. - **Requirement**: All public modules, classes, and functions must have a docstring. ```Python def connect_to_db(timeout: int = 10) -> bool: """Establishes a connection to the primary database. Args: timeout (int): Max time in seconds to wait for connection. Returns: bool: True if connection successful, False otherwise. Raises: ConnectionError: If the network is down. """ pass ``` #### **4.2 Comments** - **Rule**: Comments must explain "Why", not "What". - **Maintenance**: Delete commented-out code immediately. Do not commit dead code. ### **5. Programming Best Practices** **5.1 Error Handling** - **Prohibited**: Never use bare except:. - **Best Practice**: Catch specific exceptions. - **Chaining**: When re-raising an exception, use `raise ... from e` to preserve the stack trace. ```Python # Good try: process_data() except ValueError as e: raise DataProcessingError("Invalid data format") from e ``` #### **5.2 Conditionals & Truthiness** - Explicit is better: Use `if x is None:` instead `of if x == None:`. - Implicit False: Use implicit boolean checks for lists/strings. - Yes: `if my_list:` (Checks if list is not empty) - No: `if len(my_list) > 0:` #### **5.3 Magic Numbers** - **Rule**: Replace raw numbers/strings with named constants or Enums. ```Python from enum import Enum class Status(Enum): PENDING = "pending" ACTIVE = "active" # Usage if user.status == Status.ACTIVE: ... ``` ### **6. Async & Performance (Modern Standards)** #### **6.1 Async/Await** - **Rule**: Do not use blocking code (e.g., `time.sleep, requests.get`) inside `async` functions. Use `await asyncio.sleep` or asynchronous libraries (e.g., `httpx`). - **Safe Handling**: Use `TaskGroup` (Python 3.11+) for managing concurrent tasks safely. #### **6.2 Loops** - **Rule**: Use `enumerate()` for indexing. - **Rule**: Prefer List Comprehensions for simple transformations, but use `for` loops if the logic is complex. ### **7. Security & Privacy** - **Secrets**: Never commit API keys, passwords, or tokens to Git. Use `.env` files and `pydantic-settings`. - **Inputs**: Always validate user input. Never pass user input directly to `eval(), exec(),` or raw SQL queries (use ORM or parameterized queries). ### **8. Automation & Tooling** Manual enforcement is unreliable. The project can use the following automated tools: - **Ruff**: The modern, high-speed replacement for Flake8 and isort. - **Black**: For uncompromising code formatting. - **Mypy**: For static type checking. - **Pre-commit**: To run these checks before every commit. Configuration (`pyproject.toml`) Copy this configuration to standardise your team's tooling: ```Python [tool.ruff] line-length = 79 select = ["E", "F", "I", "UP", "B"] # Enforce PEP8, Imports, pyupgrade, Bugbear [tool.mypy] strict = true ignore_missing_imports = true ``` **Recommended folder structure for modern Python projects)** ``` project_root/ │ ├── src/ │ └── my_package/ │ ├── __init__.py │ ├── main.py │ └── utils.py ├── tests/ │ ├── __init__.py │ └── test_main.py ├── .env # Secrets (GitIgnored) ├── .gitignore ├── pyproject.toml # Tooling Config └── README.md ```