Macaron Style Guide

Macaron makes use of different linters. These linters are managed using pre-commit hooks (see the .pre-commit-config.yaml file). Most styling issues should be caught by pre-commit hooks. However, there are some additional styling rules that we follow.

Python Modules

We ban the use of __all__ in __init__.py files due to known issues with the sphinx-apidoc docstring generator.

Naming

We avoid using the same name for two or more classes in all cases (including cases where the two classes are in different modules), due to known issues with the sphinx-apidoc docstring generator.

Docstrings

We use sphinx-apidoc to generate API Reference automatically from Python docstrings written in the code.

We follow the numpydoc style for Python docstrings (see example) with some exceptions.

Docstrings for classes

Each class should have a docstring written in a triple-quoted block describing the purpose of the class.

For variables of a class: we do not use the Attribute section as per the numpydoc style for class docstring, due to some known issues with Sphinx. Instead, to document class variables, we follow the following convention:

  • For simple Python classes having an __init__ constructor: We document the __init__ constructor like any other regular method, i.e. all parameters should be documented. We do not document instance variables and consider them to be private to the class. To expose an instance variable of a class in the documentation, we use Python property and document the property methods (getter, setter, and deleter methods) instead. Example:

    class Point2D:
        """A point in the 2D coordinate system."""
    
        def __init__(self, coordinate: tuple[float, float]) -> None:
            """Construct a point in the 2D coordinate system.
    
            Parameters
            ----------
            coordinate : tuple[float, float]
                A pair of x and y coordinates.
            """
            self._x = tuple[0]
            self._y = tuple[1]
    
        @property
        def x(self) -> float:
            """Get the x coordinate of the point."""
            return self._x
    
  • For Python classes that declare instance variables in class attributes (e.g. typing.NamedTuple, dataclasses.dataclass(), or SQLAlchemy ORM Mapped classes): We document each instance variable with a comment above the variable prefixed with #: (if the comment spans more than one line, all lines must be prefixed with #:). Example:

    from typing import NamedTuple
    
    
    class Point2D(NamedTuple):
        """A point in the 2D coordinate system."""
    
        #: The x coordinate of the point.
        x: float
        #: The y coordinate of the point.
        y: float
    

Logging and Console Output

Macaron uses the rich Python library to provide enhanced and well-formatted logging output in the terminal. All logging should be performed using the macaron.console module.

Why use rich for logging?

  • Provides visually appealing, color-coded, and structured terminal UI.

  • Supports custom components like table, progress bar, and panel for complex output.

  • Makes it easier to spot errors, progress, failed checks, etc.

How to use the logging handler

Import the access_handler from macaron.console and use it to set/get the custom rich handler. For simple logging, you can use the handler’s methods for info, debug, and error messages.

from macaron.console import access_handler

# Get the RichConsoleHandler
rich_handler = access_handler.get_handler()

# Log an info message
console.info("<info message>")

# Log an debug message
console.debug("<debug message>")

# Log an error message
console.error("<error message>")

To modify the console UI, create a function call which takes necessary information and converts them into Rich RenderableType. Also modify the make_layout() function to use the newly created rich component.

def update_find_source_table(self, key: str, value: str | Status) -> None:
    self.find_source_content[key] = value
    find_source_table = Table(show_header=False, box=None)
    find_source_table.add_column("Details", justify="left")
    find_source_table.add_column("Value", justify="left")
    for field, content in self.find_source_content.items():
        find_source_table.add_row(field, content)
    self.find_source_table = find_source_table


def make_layout(self) -> Group:
    layout: list[RenderableType] = []
    if self.command == "find-source":
        if self.find_source_table.row_count > 0:
            layout = layout + [self.find_source_table]
    return Group(*layout)


rich_handler.update_find_source_table("<Detail>", "<Value>")

For more advance formatting, refer to the rich documentation: https://rich.readthedocs.io/en/stable/