Files
home-information/docs/dev/shared/coding-standards.md
2026-05-27 19:15:01 -05:00

10 KiB

Coding Standards

Code Conventions Checklist

Checklists for writing and reviewing code.

General:

  • Code does not use hard-coded "magic" strings.
  • Code does no use any hard-coded "magic" numbers.
  • All comments add value and follow our commenting guidelines.
  • No urls appear as hard-coded path and all use Django url names and reverse().
  • There no .flake8 linting violations.
  • The file ends with a newline
  • All new files follow the project structure's name and location conventions.
  • There are no Emoji's in any code, templates or documentation.

Imports:

  • All module imports are at the top of the file.
  • Imports are grouped logically: system/pip, django, project, apps then module local.
  • Imports have one line space between groups.
  • Within a group, imports are sorted alphabetically
  • No module relies on indirect (hidden) imports
  • There are no unused imports

Method Declarations:

  • All methods uses type hints for their parameters and return values.
  • All method definition with more than two arguments use one line per argunment.
  • All multi-line method signatures have all their types and default values aligned.
  • No methods return position-dependent tuples.

Class Declarations:

  • All dataclass definitions have all their types and default values aligned.
  • All enum definitions have all their types and default values aligned.
  • Are enums subclass LabeledEnum

Method Calling:

  • All method calls use named parameters.
  • All metghod calls with more than two argumants use one line per argument.
  • All multi-line method calls use a comma after last item.
  • Spaces surrounding all equals ("=") signs when passing parameters to methods.

Expressions:

  • All boolean assignments to conditional clauses are wrapped in bool().
  • All loops end with an explicit continue or a return.
  • All methods end with an explicit return or a raise?
  • All multi-line arrays, sets, dictionaries use a comma after last item.
  • Compound/complex conditional statements use explicit delimiting parentheses.
  • Single quote are used for all strings in Python code.
  • All multi-line arrays, dictionaries and sets use a comma after last item.

Views:

  • All url paths components follow our standard ordering conventions.
  • All url Django names follow our standard naming conventions
  • All view names match url names (except for casing and underlining).
  • ALl views raise exceptions for common error conditions. (Let middleware handle it.)

Templates:

  • Template names referenced in views closely match the view names that use them.
  • No templates have in-line Javascript.
  • No templates have in-line CSS.
  • Templates appear in a subdirectory matching their purpose: modals, panes, pages.
  • Template tags load statments near the top of the file.
  • Django template comments use {# #} (single line) or {% comment %}...{% endcomment %} (multi-line), never <!-- --> (HTML comments ship to the browser).

Code Conventions Details

No "magic" strings

We do not use "magic" or hard-coded strings when needing multiple references. Any string that need to be used in two or more places is a risk of them being mismatched. This includes, but is not limited to:

All DOM ids and class strings that are shared between client and server must adhere to our DIVID pattern. See "Client-Server Namespace Sharing" in Front End Guidelines.

Type Hints

  • We add type hints to dataclass fields, method parameters and method return values.
  • We do not add type hints to locally declared method variables.
  • Some allowed, but not required exceptions:
    • The request parameter when appearing in a Django view class.
    • Single parameter methods where the method name or parameter name makes its type unambiguous.

Method Parameter Formatting

For readability, besides adding type hints to method parameters, we adhere to the following formatting conventions:

  • For methods with a single parameter, or parameters of native types, they can appear in one line with the method name.
  • If more than one parameter and app-defined types, then use a multiple line declaration.
  • For methods with three or more parameters, we use one line per parameter and align the type names.

Good Examples:

    def set_entity( self, entity_id : int ) -> EntityPath:

    def set_entity_order( self, entity_id : int, rank : int ) -> EntityPath:

    def set_entity_path( self,
                         entity_id     : int,
                         location      : Location,
                         svg_path_str  : str        ) -> EntityPath:

Bad Examples:

    def set_entity_type( self, entity_id : int, entity_type : EntityType ) -> EntityPath:

    def set_entity_path( self,
                         entity_id : int,
                         location : Location,
                         svg_path_str: str ) -> EntityPath:

    def set_entity_path( self, entity_id : int,
                         location : Location, svg_path_str: str ) -> EntityPath:

Variable Assignment vs Inlining

We prefer explicit variable assignment over inlining function calls. This is not about minimizing lines of code - it's about readability and debuggability.

Good - Named intermediate values

table_name = self.queryset.model._meta.db_table
logger.debug( f"Processing table: {table_name}" )

cutoff_date = datetimeproxy.now() - timedelta( days=30 )
old_records = queryset.filter( created__lt=cutoff_date )

Bad - Inlined function calls

logger.debug( f"Processing table: {self.queryset.model._meta.db_table}" )

old_records = queryset.filter(
    created__lt=datetimeproxy.now() - timedelta( days=30 )
)

Benefits of variable assignment:

  • Provides semantic naming that clarifies intent
  • Easier to debug (can inspect intermediate values)
  • Improves readability by breaking complex expressions
  • Allows reuse without recalculation

Explicit Booleans

We prefer to wrap all expression that evaluate to a boolean in bool() to make it explicit what type we are expecting:

Good

   my_variable = bool( len(my_list) > 4 )

Bad*

   my_variable = len(my_list) == 4

Complex Boolean Expressions

  • For boolean clauses and conditionals where there are multiple clauses, we prefer to explicitly enclose each clause with parentheses in order to make the intention clear.
  • We do not rely on the user having a deep understanding of the compiler's ordeer of precedence.
  • We use one line per clause unless the combined clauses are very short and obvious.
  • Single boolean typed variables or methods that return a boolean do not need paretheses.

Good:

    if is_editing and location_view:
        pass
                
    if (( hass_state.domain == HassApi.SWITCH_DOMAIN )
          and ( HassApi.LIGHT_DOMAIN in prefixes_seen )):
        pass
                
    if ( HassApi.BINARY_SENSOR_DOMAIN in domain_set
         and device_class_set.intersection( HassApi.OPEN_CLOSE_DEVICE_CLASS_SET )):
        pass

   

Bad:

    if hass_state.domain == HassApi.SWITCH_DOMAIN and HassApi.LIGHT_DOMAIN == 'foo':
        pass

Control Flow Statements

  • Always include explicit continue statements in loops
  • Always include explicit return statements in functions
  • This improves code readability and makes control flow intentions explicit

Example:

def process_items(items):
    results = []
    for item in items:
        if not item.valid:
            continue  # Explicit continue for invalid items
        
        if item.needs_processing:
            result = process(item)
            results.append(result)
            continue  # Explicit continue after processing
        
        # Handle non-processing case
        results.append(item.default_value)
        continue  # Explicit continue at end of loop
    
    return results  # Explicit return at end of function

Operator Spacing

  • Use spaces around assignment operators and most other operators in expressions
  • Examples: x = y + z, result += value, if count == 0
  • Exception: Don't add spaces in function keyword arguments (func(x=y)) or type annotations

Parentheses Spacing (Deliberate PEP8 Deviation)

  • We prefer spaces inside parentheses for enhanced readability
  • This is a deliberate deviation from PEP8 standards (E201, E202)
  • Examples:
    • Good: if ( condition ):
    • Good: my_function( param1, param2 )
    • Good: result = calculate( x + y )
  • This applies to all parentheses: function calls, conditionals, expressions
  • Rationale: Extra spacing improves readability by visually separating content from delimiters

Boolean Expressions

When assigning or returning boolean values, wrap expressions in bool() to make intent explicit:

# Good - explicit boolean conversion
is_active = bool(user.last_login)
in_modal_context = bool(request.POST.get('context') == 'modal')

# Avoid - implicit boolean conversion
is_active = user.last_login
in_modal_context = request.POST.get('context') == 'modal'

Linting: Flake8 Configurations

The project uses two different flake8 configurations:

  • Development Configuration (src/.flake8) : Our preferred style for daily development work, with specific whitespace deviations from PEP8 for enhanced readability:
    • E201, E202: We use spaces inside parentheses for better visual separation
    • E221: We align operators and values in multi-line declarations
    • E251: We use spaces around keyword parameters for consistency
    • Note: These are deliberate choices for improved code readability, not oversights
  • CI Configuration (src/.flake8-ci): GitHub Actions enforces these standards and blocks PR merging if violations exist.

Commenting

The content and semantics of comments — what to keep, rewrite, or remove — are covered in Commenting Guidelines. The checklist above covers the syntactic surface only.

Special Cases

TRACE Pattern (Accepted)

  • TRACE = False # for debugging is an accepted pattern
  • Addresses Python logging limitation (lacks TRACE level below DEBUG)