"use strict";(globalThis.webpackChunkdocs_split_developer=globalThis.webpackChunkdocs_split_developer||[]).push([[3904],{5343:(e,s,n)=>{n.r(s),n.d(s,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>t,metadata:()=>i,toc:()=>d});const i=JSON.parse('{"id":"coding-guidelines","title":"Coding Guidelines","description":"Note: For complete coding standards, see AGENTS.md.","source":"@site/docs/coding-guidelines.md","sourceDirName":".","slug":"/coding-guidelines","permalink":"/hass.tibber_prices/developer/coding-guidelines","draft":false,"unlisted":false,"editUrl":"https://github.com/jpawlowski/hass.tibber_prices/tree/main/docs/developer/docs/coding-guidelines.md","tags":[],"version":"current","lastUpdatedAt":1764985026000,"frontMatter":{"comments":false},"sidebar":"tutorialSidebar","previous":{"title":"Development Setup","permalink":"/hass.tibber_prices/developer/setup"},"next":{"title":"Critical Behavior Patterns - Testing Guide","permalink":"/hass.tibber_prices/developer/critical-patterns"}}');var l=n(4848),r=n(8453);const t={comments:!1},a="Coding Guidelines",c={},d=[{value:"Code Style",id:"code-style",level:2},{value:"Naming Conventions",id:"naming-conventions",level:2},{value:"Class Names",id:"class-names",level:3},{value:"Import Order",id:"import-order",level:2},{value:"Critical Patterns",id:"critical-patterns",level:2},{value:"Time Handling",id:"time-handling",level:3},{value:"Translation Loading",id:"translation-loading",level:3},{value:"Price Data Enrichment",id:"price-data-enrichment",level:3}];function o(e){const s={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(s.header,{children:(0,l.jsx)(s.h1,{id:"coding-guidelines",children:"Coding Guidelines"})}),"\n",(0,l.jsxs)(s.blockquote,{children:["\n",(0,l.jsxs)(s.p,{children:[(0,l.jsx)(s.strong,{children:"Note:"})," For complete coding standards, see ",(0,l.jsx)(s.a,{href:"https://github.com/jpawlowski/hass.tibber_prices/blob/v0.20.0/AGENTS.md",children:(0,l.jsx)(s.code,{children:"AGENTS.md"})}),"."]}),"\n"]}),"\n",(0,l.jsx)(s.h2,{id:"code-style",children:"Code Style"}),"\n",(0,l.jsxs)(s.ul,{children:["\n",(0,l.jsxs)(s.li,{children:[(0,l.jsx)(s.strong,{children:"Formatter/Linter"}),": Ruff (replaces Black, Flake8, isort)"]}),"\n",(0,l.jsxs)(s.li,{children:[(0,l.jsx)(s.strong,{children:"Max line length"}),": 120 characters"]}),"\n",(0,l.jsxs)(s.li,{children:[(0,l.jsx)(s.strong,{children:"Max complexity"}),": 25 (McCabe)"]}),"\n",(0,l.jsxs)(s.li,{children:[(0,l.jsx)(s.strong,{children:"Target"}),": Python 3.13"]}),"\n"]}),"\n",(0,l.jsx)(s.p,{children:"Run before committing:"}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-bash",children:"./scripts/lint # Auto-fix issues\n./scripts/release/hassfest # Validate integration structure\n"})}),"\n",(0,l.jsx)(s.h2,{id:"naming-conventions",children:"Naming Conventions"}),"\n",(0,l.jsx)(s.h3,{id:"class-names",children:"Class Names"}),"\n",(0,l.jsx)(s.p,{children:(0,l.jsx)(s.strong,{children:"All public classes MUST use the integration name as prefix."})}),"\n",(0,l.jsx)(s.p,{children:"This is a Home Assistant standard to avoid naming conflicts between integrations."}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-python",children:"# \u2705 CORRECT\nclass TibberPricesApiClient:\nclass TibberPricesDataUpdateCoordinator:\nclass TibberPricesSensor:\n\n# \u274c WRONG - Missing prefix\nclass ApiClient:\nclass DataFetcher:\nclass TimeService:\n"})}),"\n",(0,l.jsx)(s.p,{children:(0,l.jsx)(s.strong,{children:"When prefix is required:"})}),"\n",(0,l.jsxs)(s.ul,{children:["\n",(0,l.jsx)(s.li,{children:"Public classes used across multiple modules"}),"\n",(0,l.jsx)(s.li,{children:"All exception classes"}),"\n",(0,l.jsx)(s.li,{children:"All coordinator and entity classes"}),"\n",(0,l.jsx)(s.li,{children:"Data classes (dataclasses, NamedTuples) used as public APIs"}),"\n"]}),"\n",(0,l.jsx)(s.p,{children:(0,l.jsx)(s.strong,{children:"When prefix can be omitted:"})}),"\n",(0,l.jsxs)(s.ul,{children:["\n",(0,l.jsxs)(s.li,{children:["Private helper classes within a single module (prefix with ",(0,l.jsx)(s.code,{children:"_"})," underscore)"]}),"\n",(0,l.jsxs)(s.li,{children:["Type aliases and callbacks (e.g., ",(0,l.jsx)(s.code,{children:"TimeServiceCallback"}),")"]}),"\n",(0,l.jsx)(s.li,{children:"Small internal NamedTuples for function returns"}),"\n"]}),"\n",(0,l.jsx)(s.p,{children:(0,l.jsx)(s.strong,{children:"Private Classes:"})}),"\n",(0,l.jsx)(s.p,{children:"If a helper class is ONLY used within a single module file, prefix it with underscore:"}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-python",children:'# \u2705 Private class - used only in this file\nclass _InternalHelper:\n """Helper used only within this module."""\n pass\n\n# \u274c Wrong - no prefix but used across modules\nclass DataFetcher: # Should be TibberPricesDataFetcher\n pass\n'})}),"\n",(0,l.jsxs)(s.p,{children:[(0,l.jsx)(s.strong,{children:"Note:"})," Currently (Nov 2025), this project has ",(0,l.jsx)(s.strong,{children:"NO private classes"})," - all classes are used across module boundaries."]}),"\n",(0,l.jsx)(s.p,{children:(0,l.jsx)(s.strong,{children:"Current Technical Debt:"})}),"\n",(0,l.jsxs)(s.p,{children:["Many existing classes lack the ",(0,l.jsx)(s.code,{children:"TibberPrices"})," prefix. Before refactoring:"]}),"\n",(0,l.jsxs)(s.ol,{children:["\n",(0,l.jsxs)(s.li,{children:["Document the plan in ",(0,l.jsx)(s.code,{children:"/planning/class-naming-refactoring.md"})]}),"\n",(0,l.jsxs)(s.li,{children:["Use ",(0,l.jsx)(s.code,{children:"multi_replace_string_in_file"})," for bulk renames"]}),"\n",(0,l.jsx)(s.li,{children:"Test thoroughly after each module"}),"\n"]}),"\n",(0,l.jsxs)(s.p,{children:["See ",(0,l.jsx)(s.a,{href:"https://github.com/jpawlowski/hass.tibber_prices/blob/v0.20.0/AGENTS.md",children:(0,l.jsx)(s.code,{children:"AGENTS.md"})})," for complete list of classes needing rename."]}),"\n",(0,l.jsx)(s.h2,{id:"import-order",children:"Import Order"}),"\n",(0,l.jsxs)(s.ol,{children:["\n",(0,l.jsx)(s.li,{children:"Python stdlib (specific types only)"}),"\n",(0,l.jsxs)(s.li,{children:["Third-party (",(0,l.jsx)(s.code,{children:"homeassistant.*"}),", ",(0,l.jsx)(s.code,{children:"aiohttp"}),")"]}),"\n",(0,l.jsxs)(s.li,{children:["Local (",(0,l.jsx)(s.code,{children:".api"}),", ",(0,l.jsx)(s.code,{children:".const"}),")"]}),"\n"]}),"\n",(0,l.jsx)(s.h2,{id:"critical-patterns",children:"Critical Patterns"}),"\n",(0,l.jsx)(s.h3,{id:"time-handling",children:"Time Handling"}),"\n",(0,l.jsxs)(s.p,{children:["Always use ",(0,l.jsx)(s.code,{children:"dt_util"})," from ",(0,l.jsx)(s.code,{children:"homeassistant.util"}),":"]}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-python",children:"from homeassistant.util import dt as dt_util\n\nprice_time = dt_util.parse_datetime(starts_at)\nprice_time = dt_util.as_local(price_time) # Convert to HA timezone\nnow = dt_util.now()\n"})}),"\n",(0,l.jsx)(s.h3,{id:"translation-loading",children:"Translation Loading"}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-python",children:'# In __init__.py async_setup_entry:\nawait async_load_translations(hass, "en")\nawait async_load_standard_translations(hass, "en")\n'})}),"\n",(0,l.jsx)(s.h3,{id:"price-data-enrichment",children:"Price Data Enrichment"}),"\n",(0,l.jsx)(s.p,{children:"Always enrich raw API data:"}),"\n",(0,l.jsx)(s.pre,{children:(0,l.jsx)(s.code,{className:"language-python",children:"from .price_utils import enrich_price_info_with_differences\n\nenriched = enrich_price_info_with_differences(\n price_info_data,\n thresholds,\n)\n"})}),"\n",(0,l.jsxs)(s.p,{children:["See ",(0,l.jsx)(s.a,{href:"https://github.com/jpawlowski/hass.tibber_prices/blob/v0.20.0/AGENTS.md",children:(0,l.jsx)(s.code,{children:"AGENTS.md"})})," for complete guidelines."]})]})}function h(e={}){const{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,l.jsx)(s,{...e,children:(0,l.jsx)(o,{...e})}):o(e)}}}]);