docs(CONTRIBUTING): improve clarity and formatting in contribution guidelines

Refactor the contribution guidelines to enhance readability and consistency in formatting. Adjusted code blocks and list formatting for better visual structure.

Impact: Contributors will find it easier to follow the guidelines when contributing to the project.

---

docs(README): update automation examples for better readability

Reformatted YAML automation examples in the README to improve clarity and consistency. Indentation and structure were adjusted for better understanding.

Impact: Users will have clearer examples for setting up automations with the integration.

---

chore(manifest): streamline manifest file formatting

Consolidated formatting in the manifest file for consistency. Adjusted the codeowners and requirements sections for a cleaner look.

User-Impact: none

---

chore(pyproject): enhance project configuration for better linting and testing

Updated the pyproject.toml file to improve linting configurations and testing options. Added specific rules for ruff and pytest to align with project standards.

User-Impact: none

---

chore(manifest_schema): simplify JSON schema for integration manifest

Refined the manifest schema by consolidating enum definitions for better readability and maintenance.

User-Impact: none

---

chore(prettier): add Prettier configuration for consistent code formatting

Introduced a Prettier configuration file to standardize code formatting across the project, ensuring consistency in style.

User-Impact: none
This commit is contained in:
Julian Pawlowski 2026-04-14 15:35:16 +00:00
parent 9efa7809d0
commit 2e7ccc36c5
22 changed files with 894 additions and 594 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "jpawlowski/hass.tibber_prices", "name": "jpawlowski/hass.tibber_prices",
"image": "mcr.microsoft.com/devcontainers/python:3.14", "image": "mcr.microsoft.com/devcontainers/base:debian",
"postCreateCommand": "bash .devcontainer/setup-git.sh && scripts/setup/setup", "postCreateCommand": "bash .devcontainer/setup-git.sh && scripts/setup/setup",
"postStartCommand": "scripts/motd", "postStartCommand": "scripts/motd",
"containerEnv": { "containerEnv": {
@ -63,9 +63,7 @@
"**/node_modules/**" "**/node_modules/**"
], ],
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
"python.analysis.extraPaths": [ "python.analysis.extraPaths": ["${workspaceFolder}/.venv/lib/python3.14/site-packages"],
"${workspaceFolder}/.venv/lib/python3.14/site-packages"
],
"python.terminal.activateEnvironment": true, "python.terminal.activateEnvironment": true,
"python.terminal.activateEnvInCurrentTerminal": true, "python.terminal.activateEnvInCurrentTerminal": true,
"python.testing.pytestArgs": ["--no-cov"], "python.testing.pytestArgs": ["--no-cov"],
@ -142,6 +140,8 @@
}, },
"ghcr.io/devcontainers-extra/features/apt-packages:1": { "ghcr.io/devcontainers-extra/features/apt-packages:1": {
"packages": [ "packages": [
"autoconf",
"automake",
"bat", "bat",
"eza", "eza",
"fd-find", "fd-find",
@ -154,9 +154,12 @@
"jo", "jo",
"jq", "jq",
"libpcap-dev", "libpcap-dev",
"libssl-dev",
"libtool",
"libturbojpeg0", "libturbojpeg0",
"miller", "miller",
"moreutils", "moreutils",
"pipx",
"ripgrep", "ripgrep",
"shellcheck", "shellcheck",
"shfmt", "shfmt",

View file

@ -1,6 +1,7 @@
# top-most EditorConfig file # top-most EditorConfig file
root = true root = true
# Default settings - AI-friendly baseline
[*] [*]
charset = utf-8 charset = utf-8
end_of_line = lf end_of_line = lf
@ -9,12 +10,71 @@ trim_trailing_whitespace = true
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
# Python - Home Assistant & Ruff defaults (120 chars)
[*.py] [*.py]
# Python style aligns with Black
indent_size = 4 indent_size = 4
max_line_length = 120
# YAML - Home Assistant configs, GitHub workflows
[*.{yaml,yml}]
indent_size = 2
# JSON - manifest.json, translations, etc.
[*.json] [*.json]
indent_size = 2 indent_size = 2
# Markdown - READMEs, docs (preserve AI formatting)
[*.md] [*.md]
indent_size = 2
trim_trailing_whitespace = false trim_trailing_whitespace = false
max_line_length = off
# TOML - pyproject.toml, Python packaging
[*.toml]
indent_size = 4
# Shell scripts - setup scripts, CI/CD
[*.{sh,bash}]
indent_size = 4
end_of_line = lf
# JavaScript/TypeScript - Frontend panel development
[*.{js,ts,jsx,tsx,mjs,cjs}]
indent_size = 2
# CSS/SCSS - Frontend styling
[*.{css,scss,sass}]
indent_size = 2
# HTML - Lovelace cards, frontend templates
[*.html]
indent_size = 2
# XML - Android Auto integration, etc.
[*.xml]
indent_size = 2
# Jinja2 templates - Home Assistant templates
[*.jinja,*.jinja2,*.j2]
indent_size = 2
# Makefiles require tabs
[Makefile]
indent_style = tab
# GitHub-specific files
[.github/workflows/*.{yml,yaml}]
indent_size = 2
[.github/dependabot.{yml,yaml}]
indent_size = 2
# Docker files
[Dockerfile*]
indent_size = 2
[*.dockerignore]
indent_size = 2
[docker-compose*.{yml,yaml}]
indent_size = 2

2
.github/FUNDING.yml vendored
View file

@ -1,4 +1,4 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [ jpawlowski ] github: [jpawlowski]
buy_me_a_coffee: jpawlowski buy_me_a_coffee: jpawlowski

View file

@ -3,30 +3,30 @@ name: "Bug report"
description: "Report a bug with the custom integration" description: "Report a bug with the custom integration"
labels: ["bug"] labels: ["bug"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: Before you open a new issue, search through the existing issues to see if others have had the same problem. value: Before you open a new issue, search through the existing issues to see if others have had the same problem.
- type: input - type: input
attributes: attributes:
label: "Home Assistant version" label: "Home Assistant version"
description: "The version of Home Assistant you are using" description: "The version of Home Assistant you are using"
placeholder: "2025.1.0" placeholder: "2025.1.0"
validations: validations:
required: true required: true
- type: input - type: input
attributes: attributes:
label: "Integration version" label: "Integration version"
description: "The version of this custom integration you are using" description: "The version of this custom integration you are using"
placeholder: "1.0.0" placeholder: "1.0.0"
validations: validations:
required: false required: false
- type: textarea - type: textarea
attributes: attributes:
label: "System Health details" label: "System Health details"
description: "Paste the data from the System Health card in Home Assistant (https://www.home-assistant.io/more-info/system-health#github-issues)" description: "Paste the data from the System Health card in Home Assistant (https://www.home-assistant.io/more-info/system-health#github-issues)"
validations: validations:
required: false required: false
- type: checkboxes - type: checkboxes
attributes: attributes:
label: Checklist label: Checklist
options: options:
@ -38,13 +38,13 @@ body:
required: true required: true
- label: This issue is not a duplicate issue of any [previous issues](https://github.com/jpawlowski/hass.tibber_prices/issues?q=is%3Aissue+label%3A%22Bug%22+).. - label: This issue is not a duplicate issue of any [previous issues](https://github.com/jpawlowski/hass.tibber_prices/issues?q=is%3Aissue+label%3A%22Bug%22+)..
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Describe the issue" label: "Describe the issue"
description: "A clear and concise description of what the issue is." description: "A clear and concise description of what the issue is."
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Reproduction steps label: Reproduction steps
description: "Without steps to reproduce, it will be hard to fix. It is very important that you fill out this part. Issues without it will be closed." description: "Without steps to reproduce, it will be hard to fix. It is very important that you fill out this part. Issues without it will be closed."
@ -55,7 +55,7 @@ body:
... ...
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Debug logs" label: "Debug logs"
description: "To enable debug logs check this https://www.home-assistant.io/integrations/logger/, this **needs** to include _everything_ from startup of Home Assistant to the point where you encounter the issue." description: "To enable debug logs check this https://www.home-assistant.io/integrations/logger/, this **needs** to include _everything_ from startup of Home Assistant to the point where you encounter the issue."
@ -63,7 +63,7 @@ body:
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Diagnostics dump" label: "Diagnostics dump"
description: "Drag the diagnostics dump file here. (see https://www.home-assistant.io/integrations/diagnostics/ for info)" description: "Drag the diagnostics dump file here. (see https://www.home-assistant.io/integrations/diagnostics/ for info)"

View file

@ -3,10 +3,10 @@ name: "Feature request"
description: "Suggest an idea for this custom integration" description: "Suggest an idea for this custom integration"
labels: ["Feature request"] labels: ["Feature request"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: Before you open a new feature request, search through the existing feature requests to see if others have had the same idea. value: Before you open a new feature request, search through the existing feature requests to see if others have had the same idea.
- type: checkboxes - type: checkboxes
attributes: attributes:
label: Checklist label: Checklist
options: options:
@ -17,7 +17,7 @@ body:
- label: This issue is not a duplicate feature request of [previous feature requests](https://github.com/jpawlowski/hass.tibber_prices/issues?q=is%3Aissue+label%3A%22Feature+Request%22+). - label: This issue is not a duplicate feature request of [previous feature requests](https://github.com/jpawlowski/hass.tibber_prices/issues?q=is%3Aissue+label%3A%22Feature+Request%22+).
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Is your feature request related to a problem? Please describe." label: "Is your feature request related to a problem? Please describe."
description: "A clear and concise description of what the problem is." description: "A clear and concise description of what the problem is."
@ -25,21 +25,21 @@ body:
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Describe the solution you'd like" label: "Describe the solution you'd like"
description: "A clear and concise description of what you want to happen." description: "A clear and concise description of what you want to happen."
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Describe alternatives you've considered" label: "Describe alternatives you've considered"
description: "A clear and concise description of any alternative solutions or features you've considered." description: "A clear and concise description of any alternative solutions or features you've considered."
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: "Additional context" label: "Additional context"
description: "Add any other context or screenshots about the feature request here." description: "Add any other context or screenshots about the feature request here."

View file

@ -44,3 +44,6 @@ updates:
ignore: ignore:
# Dependabot should not update Home Assistant as that should match the homeassistant key in hacs.json # Dependabot should not update Home Assistant as that should match the homeassistant key in hacs.json
- dependency-name: "homeassistant" - dependency-name: "homeassistant"
# pytest version is dictated by pytest-homeassistant-custom-component (pins an exact version)
# Bumping pytest independently breaks the dependency resolution
- dependency-name: "pytest"

View file

@ -5,7 +5,7 @@ on:
branches: branches:
- main - main
paths: paths:
- 'custom_components/tibber_prices/manifest.json' - "custom_components/tibber_prices/manifest.json"
permissions: permissions:
contents: write contents: write

View file

@ -4,10 +4,10 @@ on:
push: push:
branches: [main] branches: [main]
paths: paths:
- 'docs/**' - "docs/**"
- '.github/workflows/docusaurus.yml' - ".github/workflows/docusaurus.yml"
tags: tags:
- 'v*.*.*' - "v*.*.*"
workflow_dispatch: workflow_dispatch:
# Concurrency control: cancel in-progress deployments # Concurrency control: cancel in-progress deployments
@ -47,7 +47,7 @@ jobs:
- uses: actions/setup-node@v6 - uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 24
cache: 'npm' cache: "npm"
cache-dependency-path: | cache-dependency-path: |
docs/user/package-lock.json docs/user/package-lock.json
docs/developer/package-lock.json docs/developer/package-lock.json
@ -56,7 +56,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
python-version: '3.14' python-version: "3.14"
- name: Verify sensor reference is up-to-date - name: Verify sensor reference is up-to-date
run: python3 scripts/docs/generate-sensor-reference --check run: python3 scripts/docs/generate-sensor-reference --check

View file

@ -5,14 +5,14 @@ on:
branches: branches:
- "main" - "main"
paths-ignore: paths-ignore:
- 'docs/**' - "docs/**"
- '.github/workflows/docusaurus.yml' - ".github/workflows/docusaurus.yml"
pull_request: pull_request:
branches: branches:
- "main" - "main"
paths-ignore: paths-ignore:
- 'docs/**' - "docs/**"
- '.github/workflows/docusaurus.yml' - ".github/workflows/docusaurus.yml"
permissions: {} permissions: {}

View file

@ -3,11 +3,11 @@ name: Generate Release Notes
on: on:
push: push:
tags: tags:
- 'v*.*.*' # Triggers on version tags like v1.0.0, v2.1.3, etc. - "v*.*.*" # Triggers on version tags like v1.0.0, v2.1.3, etc.
workflow_dispatch: workflow_dispatch:
inputs: inputs:
tag: tag:
description: 'Tag version to release (e.g., v0.3.0)' description: "Tag version to release (e.g., v0.3.0)"
required: true required: true
type: string type: string

View file

@ -8,14 +8,14 @@ on:
branches: branches:
- main - main
paths-ignore: paths-ignore:
- 'docs/**' - "docs/**"
- '.github/workflows/docusaurus.yml' - ".github/workflows/docusaurus.yml"
pull_request: pull_request:
branches: branches:
- main - main
paths-ignore: paths-ignore:
- 'docs/**' - "docs/**"
- '.github/workflows/docusaurus.yml' - ".github/workflows/docusaurus.yml"
permissions: {} permissions: {}

View file

@ -1,9 +1,20 @@
{ {
"default": true, "default": true,
"MD004": false,
"MD013": false, "MD013": false,
"MD033": false, "MD036": false,
"MD041": false, "MD041": false,
"no-inline-html": false, "no-trailing-punctuation": false,
"line-length": false, "no-inline-html": {
"first-line-heading": false "allowed_elements": ["br", "details", "summary", "img", "a", "kbd"]
},
"code-block-style": {
"style": "fenced"
},
"emphasis-style": {
"style": "underscore"
},
"strong-style": {
"style": "asterisk"
}
} }

View file

@ -11,6 +11,12 @@ __pycache__/
env/ env/
venv/ venv/
# Ignore compiled YAML or generated docs # Ignore local HA dev instance config (not production code)
*.yaml config/
*.yml
# Ignore YAML schemas (structural files with specific formatting conventions)
schemas/yaml/
# Ignore Docusaurus documentation sites they have their own toolchain
# and Prettier reformats <details> blocks inside lists in a way that breaks MDX
docs/

37
.prettierrc.yaml Normal file
View file

@ -0,0 +1,37 @@
# Prettier configuration for Home Assistant Custom Component Development
# Aligned with .editorconfig and .markdownlint.json
printWidth: 120
tabWidth: 2
useTabs: false
semi: true
singleQuote: false
quoteProps: "as-needed"
trailingComma: "es5"
bracketSpacing: true
arrowParens: "always"
proseWrap: "preserve"
endOfLine: "lf"
# File-specific overrides
overrides:
# Markdown - preserve formatting, avoid conflicts with markdownlint
- files: "*.md"
options:
proseWrap: "preserve"
printWidth: 120
trailingComma: "none"
# JSON - Home Assistant manifest, translations
- files: "*.json"
options:
tabWidth: 2
trailingComma: "none"
# JSONC - VS Code settings, devcontainer config
- files: "*.jsonc"
options:
tabWidth: 2
trailingComma: "none"
# YAML would go here, but it's in .prettierignore (handled by redhat.vscode-yaml)

View file

@ -1,15 +1,11 @@
{ {
"domain": "tibber_prices", "domain": "tibber_prices",
"name": "Tibber Price Information & Ratings", "name": "Tibber Price Information & Ratings",
"codeowners": [ "codeowners": ["@jpawlowski"],
"@jpawlowski"
],
"config_flow": true, "config_flow": true,
"documentation": "https://github.com/jpawlowski/hass.tibber_prices", "documentation": "https://github.com/jpawlowski/hass.tibber_prices",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"issue_tracker": "https://github.com/jpawlowski/hass.tibber_prices/issues", "issue_tracker": "https://github.com/jpawlowski/hass.tibber_prices/issues",
"requirements": [ "requirements": ["aiofiles>=23.2.1"],
"aiofiles>=23.2.1"
],
"version": "0.31.0b1" "version": "0.31.0b1"
} }

View file

@ -1,6 +1,9 @@
[build-system] # Custom Component pyproject.toml based on Home Assistant Core
requires = ["setuptools==82.0.1"] # https://github.com/home-assistant/core/blob/dev/pyproject.toml
build-backend = "setuptools.build_meta" #
# Sections not included (HA Core specific):
# - [build-system] - Not published to PyPI (installed via HACS)
# - [tool.pylint.*] - Optional additional linting (Ruff is sufficient)
[project] [project]
name = "tibber_prices" name = "tibber_prices"
@ -13,73 +16,276 @@ packages = ["custom_components.tibber_prices"]
[tool.pyright] [tool.pyright]
include = ["custom_components/tibber_prices"] include = ["custom_components/tibber_prices"]
exclude = [ exclude = [
"**/node_modules", "**/.*",
"**/__pycache__", "**/__pycache__",
"**/.git", "**/config",
"**/.github",
"**/docs", "**/docs",
"**/node_modules",
"**/venv", "**/venv",
"**/.venv",
] ]
venvPath = "." venvPath = "."
venv = ".venv" venv = ".venv"
typeCheckingMode = "basic" typeCheckingMode = "basic"
reportUnusedImport = "none"
[tool.ruff] reportUnusedVariable = "none"
# Based on https://github.com/home-assistant/core/blob/dev/pyproject.toml reportUnusedCoroutine = "none"
target-version = "py314" reportMissingTypeStubs = "none"
line-length = 120
[tool.ruff.lint]
select = ["ALL"]
ignore = [
# "ANN101", # Missing type annotation for `self` in method
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed
"D203", # no-blank-line-before-class (incompatible with formatter)
"D212", # multi-line-summary-first-line (incompatible with formatter)
"COM812", # incompatible with formatter
"ISC001", # incompatible with formatter
"UP037", # quoted annotations; needed for TYPE_CHECKING forward references
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = [
"S101", # assert is fine in tests
"PLR2004", # Magic values are fine in tests
]
"scripts/*" = [
"T201", # print() is the correct output method for CLI scripts
"INP001", # scripts/ is not a Python package (no __init__.py)
]
[tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false
[tool.ruff.lint.pyupgrade]
keep-runtime-typing = true
[tool.ruff.lint.mccabe]
max-complexity = 25
[tool.ruff.lint.isort]
force-single-line = false
known-first-party = ["custom_components", "homeassistant"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
testpaths = ["tests"] testpaths = ["tests"]
python_files = ["test_*.py"] norecursedirs = [".git", "testing_config"]
python_classes = ["Test*"] log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s"
python_functions = ["test_*"] log_date_format = "%Y-%m-%d %H:%M:%S"
asyncio_debug = true
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function" asyncio_default_fixture_loop_scope = "function"
addopts = "-ra -q --strict-markers" addopts = "-ra -q --strict-markers"
markers = [ markers = [
"unit: Unit tests (fast, no external dependencies)", "unit: Unit tests (fast, no external dependencies)",
"integration: Integration tests (may use coordinator/time service)", "integration: Integration tests (may use coordinator/time service)",
] ]
filterwarnings = [
# Treat warnings as errors to catch issues early
"error",
# Ignore specific warnings from third-party libraries as needed
# "ignore:.*custom_components.* is using deprecated.*:DeprecationWarning",
]
[tool.coverage.run]
source = ["custom_components/tibber_prices"]
omit = ["tests/*"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if TYPE_CHECKING:",
"@overload",
]
[tool.ruff]
# Based on https://github.com/home-assistant/core/blob/dev/pyproject.toml
required-version = ">=0.15.1"
target-version = "py314"
line-length = 120
[tool.ruff.lint]
select = [
"A001", # Variable {name} is shadowing a Python builtin
"ASYNC", # flake8-async
"B002", # Python does not support the unary prefix increment
"B005", # Using .strip() with multi-character strings is misleading
"B007", # Loop control variable {name} not used within loop body
"B009", # Do not call getattr with a constant attribute value. It is not any safer than normal property access.
"B014", # Exception handler with duplicate exception
"B015", # Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it.
"B017", # pytest.raises(BaseException) should be considered evil
"B018", # Found useless attribute access. Either assign it to a variable or remove it.
"B023", # Function definition does not bind loop variable {name}
"B024", # `{name}` is an abstract base class, but it has no abstract methods or properties
"B025", # try-except* block with duplicate exception {name}
"B026", # Star-arg unpacking after a keyword argument is strongly discouraged
"B032", # Possible unintentional type annotation (using :). Did you mean to assign (using =)?
"B035", # Dictionary comprehension uses static key
"B904", # Use raise from to specify exception cause
"B905", # zip() without an explicit strict= parameter
"BLE",
"C", # complexity
"COM818", # Trailing comma on bare tuple prohibited
"D", # docstrings
"DTZ003", # Use datetime.now(tz=) instead of datetime.utcnow()
"DTZ004", # Use datetime.fromtimestamp(ts, tz=) instead of datetime.utcfromtimestamp(ts)
"E", # pycodestyle
"F", # pyflakes/autoflake
"F541", # f-string without any placeholders
"FLY", # flynt
"FURB", # refurb
"G", # flake8-logging-format
"I", # isort
"INP", # flake8-no-pep420
"ISC", # flake8-implicit-str-concat
"ICN001", # import concentions; {name} should be imported as {asname}
"LOG", # flake8-logging
"N804", # First argument of a class method should be named cls
"N805", # First argument of a method should be named self
"N815", # Variable {name} in class scope should not be mixedCase
"PERF", # Perflint
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # pylint
"PT", # flake8-pytest-style
"PTH", # flake8-pathlib
"PYI", # flake8-pyi
"RET", # flake8-return
"RSE", # flake8-raise
"RUF005", # Consider iterable unpacking instead of concatenation
"RUF006", # Store a reference to the return value of asyncio.create_task
"RUF007", # Prefer itertools.pairwise() over zip() when iterating over successive pairs
"RUF008", # Do not use mutable default values for dataclass attributes
"RUF010", # Use explicit conversion flag
"RUF013", # PEP 484 prohibits implicit Optional
"RUF016", # Slice in indexed access to type {value_type} uses type {index_type} instead of an integer
"RUF017", # Avoid quadratic list summation
"RUF018", # Avoid assignment expressions in assert statements
"RUF019", # Unnecessary key check before dictionary access
"RUF020", # {never_like} | T is equivalent to T
"RUF021", # Parenthesize a and b expressions when chaining and and or together, to make the precedence clear
"RUF022", # Sort __all__
"RUF023", # Sort __slots__
"RUF024", # Do not pass mutable objects as values to dict.fromkeys
"RUF026", # default_factory is a positional-only argument to defaultdict
"RUF030", # print() call in assert statement is likely unintentional
"RUF032", # Decimal() called with float literal argument
"RUF033", # __post_init__ method with argument defaults
"RUF034", # Useless if-else condition
"RUF059", # unused-unpacked-variable
"RUF100", # Unused `noqa` directive
"RUF101", # noqa directives that use redirected rule codes
"RUF200", # Failed to parse pyproject.toml: {message}
"S102", # Use of exec detected
"S103", # bad-file-permissions
"S108", # hardcoded-temp-file
"S306", # suspicious-mktemp-usage
"S307", # suspicious-eval-usage
"S313", # suspicious-xmlc-element-tree-usage
"S314", # suspicious-xml-element-tree-usage
"S315", # suspicious-xml-expat-reader-usage
"S316", # suspicious-xml-expat-builder-usage
"S317", # suspicious-xml-sax-usage
"S318", # suspicious-xml-mini-dom-usage
"S319", # suspicious-xml-pull-dom-usage
"S601", # paramiko-call
"S602", # subprocess-popen-with-shell-equals-true
"S604", # call-with-shell-equals-true
"S608", # hardcoded-sql-expression
"S609", # unix-command-wildcard-injection
"SIM", # flake8-simplify
"SLF", # flake8-self
"SLOT", # flake8-slots
"T100", # Trace found: {name} used
"T20", # flake8-print
"TC", # flake8-type-checking
"TID", # Tidy imports
"TRY", # tryceratops
"UP", # pyupgrade
"UP031", # Use format specifiers instead of percent format
"UP032", # Use f-string instead of `format` call
"W", # pycodestyle
]
ignore = [
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed
"ASYNC109", # Async function definition with a `timeout` parameter Use `asyncio.timeout` instead
"ASYNC110", # Use `asyncio.Event` instead of awaiting `asyncio.sleep` in a `while` loop
"ASYNC240", # Use an async function for entering the file system
"D202", # No blank lines allowed after function docstring
"D203", # 1 blank line required before class docstring
"D213", # Multi-line docstring summary should start at the second line
"D406", # Section name should end with a newline
"D407", # Section name underlining
"D417", # Missing argument descriptions in docstring - to allow documenting only non-obvious parameters
"E501", # line too long
"PLC1901", # {existing} can be simplified to {replacement} as an empty string is falsey; too many false positives
"PLR0911", # Too many return statements ({returns} > {max_returns})
"PLR0912", # Too many branches ({branches} > {max_branches})
"PLR0913", # Too many arguments to function call ({c_args} > {max_args})
"PLR0915", # Too many statements ({statements} > {max_statements})
"PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
"PLW0108", # Unnecessary lambda wrapping a function call; can often be replaced by the function itself
"PLW1641", # __eq__ without __hash__
"PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target
"PT011", # pytest.raises({exception}) is too broad, set the `match` parameter or use a more specific exception
"PT018", # Assertion should be broken down into multiple parts
"RUF001", # String contains ambiguous unicode character.
"RUF002", # Docstring contains ambiguous unicode character.
"RUF003", # Comment contains ambiguous unicode character.
"RUF015", # Prefer next(...) over single element slice
"SIM102", # Use a single if statement instead of nested if statements
"SIM103", # Return the condition {condition} directly
"SIM108", # Use ternary operator {contents} instead of if-else-block
"SIM115", # Use context handler for opening files
# Moving imports into type-checking blocks can mess with pytest.patch()
"TC001", # Move application import {} into a type-checking block
"TC002", # Move third-party import {} into a type-checking block
"TC003", # Move standard library import {} into a type-checking block
# Quotes for typing.cast generally not necessary, only for performance critical paths
"TC006", # Add quotes to type expression in typing.cast()
"TRY003", # Avoid specifying long messages outside the exception class
"TRY400", # Use `logging.exception` instead of `logging.error`
"UP046", # Non PEP 695 generic class
"UP047", # Non PEP 696 generic function
"UP049", # Avoid private type parameter names
# May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
"W191",
"E111",
"E114",
"E117",
"D203",
"D206",
"D212",
"D300",
"Q",
"COM812",
"COM819",
"ISC001",
# Disabled because ruff does not understand type of __all__ generated by a function
"PLE0605",
"FURB116",
]
[tool.ruff.lint.flake8-import-conventions.extend-aliases]
# Commonly used Home Assistant imports
voluptuous = "vol"
"homeassistant.helpers.config_validation" = "cv"
"homeassistant.helpers.device_registry" = "dr"
"homeassistant.helpers.entity_registry" = "er"
"homeassistant.util.dt" = "dt_util"
[tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false
[tool.ruff.lint.flake8-tidy-imports.banned-api]
"async_timeout".msg = "use asyncio.timeout instead"
"pytz".msg = "use zoneinfo instead"
"tests".msg = "You should not import tests"
[tool.ruff.lint.isort]
force-sort-within-sections = true
known-first-party = ["custom_components", "homeassistant"]
combine-as-imports = true
split-on-trailing-comma = false
[tool.ruff.lint.per-file-ignores]
"script/*" = [
"T20", # print() allowed in scripts
"INP001", # Implicit namespace package (scripts are not a package)
]
"tests/*" = [
"S101", # assert is fine in tests
"PLR2004", # Magic values are fine in tests
"D", # Docstrings not required in tests
"PTH", # Use pathlib - temporary exemption for tests
]
[tool.ruff.lint.mccabe]
max-complexity = 25
[tool.ruff.lint.pydocstyle]
convention = "google"
property-decorators = ["propcache.api.cached_property"]
[tool.ruff.lint.pyupgrade]
keep-runtime-typing = true
[project.optional-dependencies] [project.optional-dependencies]
test = [ test = ["pytest-homeassistant-custom-component>=0.13.323"]
"pytest>=9.0.3",
"pytest-asyncio>=1.3.0",
"pytest-homeassistant-custom-component>=0.13.323",
]

View file

@ -84,15 +84,7 @@
"description": "The integration type.\nhttps://developers.home-assistant.io/docs/creating_integration_manifest/#integration-type", "description": "The integration type.\nhttps://developers.home-assistant.io/docs/creating_integration_manifest/#integration-type",
"type": "string", "type": "string",
"default": "hub", "default": "hub",
"enum": [ "enum": ["device", "entity", "hardware", "helper", "hub", "service", "system"]
"device",
"entity",
"hardware",
"helper",
"hub",
"service",
"system"
]
}, },
"config_flow": { "config_flow": {
"description": "Whether the integration is configurable from the UI.\nhttps://developers.home-assistant.io/docs/creating_integration_manifest/#config-flow", "description": "Whether the integration is configurable from the UI.\nhttps://developers.home-assistant.io/docs/creating_integration_manifest/#config-flow",
@ -375,14 +367,7 @@
"iot_class": { "iot_class": {
"description": "The IoT class of the integration, describing how the integration connects to the device or service.\nhttps://developers.home-assistant.io/docs/creating_integration_manifest/#iot-class", "description": "The IoT class of the integration, describing how the integration connects to the device or service.\nhttps://developers.home-assistant.io/docs/creating_integration_manifest/#iot-class",
"type": "string", "type": "string",
"enum": [ "enum": ["assumed_state", "cloud_polling", "cloud_push", "local_polling", "local_push", "calculated"]
"assumed_state",
"cloud_polling",
"cloud_push",
"local_polling",
"local_push",
"calculated"
]
}, },
"single_config_entry": { "single_config_entry": {
"description": "Whether the integration only supports a single config entry.\nhttps://developers.home-assistant.io/docs/creating_integration_manifest/#single-config-entry-only", "description": "Whether the integration only supports a single config entry.\nhttps://developers.home-assistant.io/docs/creating_integration_manifest/#single-config-entry-only",
@ -395,14 +380,7 @@
} }
}, },
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": ["domain", "name", "codeowners", "documentation", "issue_tracker", "version"],
"domain",
"name",
"codeowners",
"documentation",
"issue_tracker",
"version"
],
"dependencies": { "dependencies": {
"mqtt": { "mqtt": {
"anyOf": [ "anyOf": [