|
| 1 | +# Python Style Guide |
| 2 | + |
| 3 | +This document outlines the coding style, conventions, and common patterns for the `python-json-logger` project. Adhering to this guide will help maintain code consistency, readability, and quality. |
| 4 | + |
| 5 | +## General Principles |
| 6 | + |
| 7 | +* **Readability Counts:** Write code that is easy for others (and your future self) to understand. |
| 8 | +* **Consistency:** Strive for consistency in naming, formatting, and structure throughout the codebase. |
| 9 | +* **Simplicity:** Prefer simple, straightforward solutions over overly complex ones. |
| 10 | + |
| 11 | +## Formatting and Linting |
| 12 | + |
| 13 | +We use automated tools to enforce a consistent code style and catch potential errors. These include: |
| 14 | + |
| 15 | +* **Black:** For opinionated code formatting. |
| 16 | +* **Pylint:** For static code analysis and error detection. |
| 17 | +* **MyPy:** For static type checking. |
| 18 | + |
| 19 | +Ensure these tools are run before committing code. Configuration for these tools can be found in `pyproject.toml`, `pylintrc`, and `mypy.ini` respectively. This guide does not repeat rules enforced by these tools but focuses on conventions not automatically verifiable. |
| 20 | + |
| 21 | +## Imports |
| 22 | + |
| 23 | +Imports should be structured into the following groups, separated by a blank line: |
| 24 | + |
| 25 | +1. **Future Imports:** e.g., `from __future__ import annotations` |
| 26 | +2. **Standard Library Imports:** e.g., `import sys`, `from datetime import datetime` |
| 27 | +3. **Installed (Third-Party) Library Imports:** e.g., `import pytest` |
| 28 | +4. **Application (Local) Imports:** e.g., `from .core import BaseJsonFormatter` |
| 29 | + |
| 30 | +Within each group, imports should generally be alphabetized. |
| 31 | + |
| 32 | +## Naming Conventions |
| 33 | + |
| 34 | +* **Modules:** `lowercase_with_underscores.py` |
| 35 | +* **Packages:** `lowercase` |
| 36 | +* **Classes:** `CapWords` (e.g., `BaseJsonFormatter`) |
| 37 | +* **Type Aliases:** `CapWords` (e.g., `OptionalCallableOrStr`) |
| 38 | +* **Functions and Methods:** `lowercase_with_underscores()` (e.g., `merge_record_extra()`) |
| 39 | +* **Variables:** `lowercase_with_underscores` |
| 40 | +* **Constants:** `UPPERCASE_WITH_UNDERSCORES` (e.g., `RESERVED_ATTRS`) |
| 41 | + |
| 42 | +## Type Hinting |
| 43 | + |
| 44 | +* All new code **must** include type hints for function arguments, return types, and variables where appropriate. |
| 45 | +* Use standard types from the `typing` module (e.g., `Optional`, `Union`, `List`, `Dict`, `Callable`, `Any`). |
| 46 | +* For Python versions older than 3.10, use `typing_extensions.TypeAlias` for creating type aliases. For Python 3.10+, use `typing.TypeAlias`. |
| 47 | + |
| 48 | +## Docstrings |
| 49 | + |
| 50 | +* All public modules, classes, functions, and methods **must** have docstrings. |
| 51 | +* We use `mkdocstrings` for generating API documentation, which defaults to the **Google Python Style Guide** for docstrings. Please adhere to this style. You can find the guide [here](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings). |
| 52 | +* Docstrings should clearly explain the purpose, arguments, return values, and any exceptions raised. |
| 53 | +* Use the following markers to indicate changes over time: |
| 54 | + * `*New in version_number*`: For features added in a specific version. |
| 55 | + * `*Changed in version_number*`: For changes made in a specific version. |
| 56 | + * `*Deprecated in version_number*`: For features deprecated in a specific version. |
| 57 | + |
| 58 | + Example: |
| 59 | + ```python |
| 60 | + def my_function(param1: str, param2: int) -> bool: |
| 61 | + """Does something interesting. |
| 62 | +
|
| 63 | + Args: |
| 64 | + param1: The first parameter, a string. |
| 65 | + param2: The second parameter, an integer. |
| 66 | +
|
| 67 | + Returns: |
| 68 | + True if successful, False otherwise. |
| 69 | +
|
| 70 | + Raises: |
| 71 | + ValueError: If param2 is negative. |
| 72 | +
|
| 73 | + *New in 3.1* |
| 74 | + """ |
| 75 | + if param2 < 0: |
| 76 | + raise ValueError("param2 cannot be negative") |
| 77 | + # ... function logic ... |
| 78 | + return True |
| 79 | + ``` |
| 80 | + |
| 81 | +## Return Statements |
| 82 | + |
| 83 | +* **All functions and methods must have an explicit `return` statement.** |
| 84 | +* If a function does not logically return a value, it should end with `return None` or simply `return`. This makes the intent clear. |
| 85 | + |
| 86 | + Example: |
| 87 | + ```python |
| 88 | + def process_data(data: dict) -> None: |
| 89 | + """Processes the given data.""" |
| 90 | + # ... processing logic ... |
| 91 | + print(data) |
| 92 | + return # or return None |
| 93 | + ``` |
| 94 | + |
| 95 | +## Class Structure |
| 96 | + |
| 97 | +* Group methods logically within a class. For example: |
| 98 | + * Initialization methods (`__init__`, `__new__`) |
| 99 | + * Public methods |
| 100 | + * Protected/Private methods (by convention, prefixed with `_` or `__`) |
| 101 | + * Special methods (`__str__`, `__repr__`) |
| 102 | +* Within source code comments, use `## Section Name ##` or `### Subsection Name ###` to delineate logical sections if it improves readability, especially in larger classes (e.g., `## Parent Methods ##` as seen in `src/pythonjsonlogger/core.py`). |
| 103 | + |
| 104 | +## Testing |
| 105 | + |
| 106 | +This project uses `pytest` for testing. |
| 107 | + |
| 108 | +* **Test Location:** Tests are located in the `tests/` directory. |
| 109 | +* **Test Naming:** Test files should be named `test_*.py` and test functions/methods should be prefixed with `test_`. |
| 110 | +* **Fixtures:** Utilize `pytest` fixtures (e.g., `@pytest.fixture`) for setting up test preconditions and managing test state. |
| 111 | + * The `LoggingEnvironment` dataclass and `env` fixture in `tests/test_formatters.py` are good examples of reusable test setups for logger testing. Strive to create similar helpers for common testing scenarios. |
| 112 | +* **Parametrization:** Use `@pytest.mark.parametrize` to run the same test function with different inputs and expected outputs. This is highly encouraged to reduce code duplication and cover multiple scenarios efficiently. |
| 113 | +* **Clarity and Focus:** Each test case should ideally test one specific aspect of functionality. Test names should be descriptive of what they are testing. |
| 114 | +* **Assertions:** Use clear and specific `pytest` assertions (e.g., `assert foo == bar`, `assert isinstance(obj, MyClass)`). |
| 115 | +* **Robustness:** Write tests that are not overly brittle. For example, avoid making assertions on exact string matches of complex, auto-generated outputs if only a part of it is relevant. |
| 116 | + |
| 117 | +## Common Code Patterns and Idioms |
| 118 | + |
| 119 | +* **Version-Specific Logic:** When code needs to behave differently based on the Python version, use `sys.version_info`: |
| 120 | + ```python |
| 121 | + if sys.version_info >= (3, 10): |
| 122 | + # Python 3.10+ specific code |
| 123 | + pass |
| 124 | + else: |
| 125 | + # Code for older versions |
| 126 | + pass |
| 127 | + ``` |
| 128 | +* **Type Aliases for Clarity:** Use `TypeAlias` for complex or frequently used type combinations to improve readability. |
| 129 | + ```python |
| 130 | + from typing import List, Tuple, Union |
| 131 | + from typing_extensions import TypeAlias # For Python < 3.10 |
| 132 | + |
| 133 | + Coordinate: TypeAlias = Tuple[int, int] |
| 134 | + PointOrListOfPoints: TypeAlias = Union[Coordinate, List[Coordinate]] |
| 135 | + ``` |
| 136 | +* **Custom Exceptions:** Define custom exception classes for application-specific error conditions to provide more meaningful error information (e.g., `MissingPackageError`). |
| 137 | +* **Helper/Utility Functions:** Encapsulate reusable logic into well-named helper functions, often placed in `utils.py` or similar utility modules. |
| 138 | + |
| 139 | +## Comments |
| 140 | + |
| 141 | +* Use comments to explain non-obvious code, complex logic, or important decisions. |
| 142 | +* Avoid comments that merely restate what the code does. |
| 143 | +* Module-level, class-level, and function/method-level explanations should primarily be in docstrings. |
| 144 | +* For internal code organization within files, especially in longer modules or classes, use comments like `## Section Title ##` or `### Subsection Title ###` to delineate logical blocks of code. This is distinct from Markdown headings used in this document. |
| 145 | + |
| 146 | +By following these guidelines, we can ensure that `python-json-logger` remains a high-quality, maintainable, and developer-friendly library. |
0 commit comments