mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-07-25 15:36:08 +01:00
5b587251c1
* Drop obsolete eager_start type ignore in CoreSys.create_task The asyncio.AbstractEventLoop.create_task signature only gained the eager_start keyword in the 3.14 typeshed. Now that Supervisor targets 3.14, the inline # type: ignore is redundant; mypy --warn-unused-ignores flags it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop redundant int | None casts in OS slot status parsing SlotStatusDataType already declares size, installed.count, and activated.count as NotRequired[int], so .get(...) returns int | None directly. The cast(int | None, ...) wrappers were no-ops; mypy --warn-redundant-casts flags them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop unreachable scheduler else branch _Task.interval is annotated float | time; the float|int isinstance arm narrows to time on the else branch, making the prior elif and the trailing _LOGGER.critical fallback dead code that mypy --warn-unreachable flags. Collapse to a plain else for the time arm. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Initialize credentials before pull_image try block If _get_credentials raises before assigning, the except DockerError handler at line 380-382 references an unbound credentials local. Mypy --enable-error-code=possibly-undefined flags this path. Initialize to an empty dict beforehand so the unauthorized branch reads as "no credentials known", matching the auth=credentials or None semantics on the success path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Use for/else to bind repository_file before .exists() check If FILE_SUFFIX_CONFIGURATION is ever empty the loop body never runs and repository_file is referenced unbound. Mypy --enable-error-code=possibly-undefined flags it. The duplicate .exists() check after the loop was also redundant: every loop body either breaks on a hit or falls through to a final miss. Restructure as for/else so the "no candidate matched" case is handled directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Make match statements exhaustive with explicit case _ mypy --enable-error-code=exhaustive-match flags match blocks that fall through to a trailing return/raise without a wildcard arm. The behaviour is unchanged; this just hoists the default into the match so the compiler can verify all cases are handled and future enum additions are caught. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Tighten mypy: warn-unreachable, redundant casts, unused ignores Add a [tool.mypy] section so the existing mypy --ignore-missing-imports supervisor invocation in CI now also enforces: warn_unreachable — flags dead code, e.g. else branches the type system has already eliminated. warn_redundant_casts — flags cast() calls whose target type already matches the source. warn_unused_ignores — flags # type: ignore comments that no longer suppress anything (typeshed updates make these accumulate silently). exhaustive-match — flags match statements without a default arm, so future enum additions surface as type errors instead of silent fallthroughs. possibly-undefined — flags references to names whose binding depends on a path that may not run (e.g. for-loop variables when the iterable is empty). ignore_missing_imports moves into the config so future invocations don't need the CLI flag, but the workflow command is left unchanged to keep the cache key stable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Disable import error codes instead of ignoring missing imports Match the pattern used in Home Assistant Core's mypy config: use disable_error_code = ["import-not-found", "import-untyped"] rather than ignore_missing_imports. This drops the (now redundant) --ignore-missing-imports flag from the CI invocation as well. The semantics are equivalent for the five untyped third-party deps (pyudev, log_rate_limit, pulsectl, cpe, atomicwrites), but expressed as suppressing two specific error codes rather than disabling all import resolution checks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Use match/case for scheduler interval dispatch Express the float-vs-time dispatch as a match statement rather than isinstance chain. Mypy treats case int() | float() and case time() as exhaustive over the float | time annotation, so the new exhaustive-match check is satisfied without a wildcard arm — and any future broadening of the type will surface here as a static error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Hoist _get_credentials above pull_image try/finally Per review feedback: _get_credentials only does dict reads against internal config state and cannot raise DockerRegistryRateLimitExceeded or aiodocker.DockerError, so it does not need to live inside the try block. Lift it out, and place it before the bus listener registration so a future raise site can't leak a stale listener through the finally cycle. This supersedes the earlier credentials: dict = {} initializer; the local is now bound on every path that reaches the except handler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Enumerate MountUsage members in container_where match Replace the case _: catch-all with case MountUsage.BACKUP | None:. The exhaustive-match check is designed to fire when the matched type widens, so adding a new MountUsage member without deciding whether it exposes a container path now produces a build error pointing at this match. The wildcard form swallowed that question silently. Behaviour unchanged: BACKUP and None still return None. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
390 lines
15 KiB
TOML
390 lines
15 KiB
TOML
[build-system]
|
|
requires = ["setuptools~=82.0.0"]
|
|
build-backend = "setuptools.build_meta"
|
|
|
|
[project]
|
|
name = "Supervisor"
|
|
dynamic = ["version", "dependencies"]
|
|
license = { text = "Apache-2.0" }
|
|
description = "Open-source private cloud os for Home-Assistant based on HassOS"
|
|
readme = "README.md"
|
|
authors = [
|
|
{ name = "The Home Assistant Authors", email = "hello@home-assistant.io" },
|
|
]
|
|
keywords = ["docker", "home-assistant", "api"]
|
|
requires-python = ">=3.14.0"
|
|
|
|
[project.urls]
|
|
"Homepage" = "https://www.home-assistant.io/"
|
|
"Source Code" = "https://github.com/home-assistant/supervisor"
|
|
"Bug Reports" = "https://github.com/home-assistant/supervisor/issues"
|
|
"Docs: Dev" = "https://developers.home-assistant.io/"
|
|
"Discord" = "https://www.home-assistant.io/join-chat/"
|
|
"Forum" = "https://community.home-assistant.io/"
|
|
|
|
[tool.setuptools]
|
|
platforms = ["any"]
|
|
zip-safe = false
|
|
include-package-data = true
|
|
|
|
[tool.setuptools.packages.find]
|
|
include = ["supervisor*"]
|
|
|
|
[tool.pylint.MAIN]
|
|
py-version = "3.14"
|
|
# Use a conservative default here; 2 should speed up most setups and not hurt
|
|
# any too bad. Override on command line as appropriate.
|
|
jobs = 2
|
|
persistent = false
|
|
extension-pkg-allow-list = ["ciso8601"]
|
|
|
|
[tool.pylint.BASIC]
|
|
class-const-naming-style = "any"
|
|
good-names = ["id", "i", "j", "k", "ex", "Run", "_", "fp", "T", "os"]
|
|
|
|
[tool.pylint."MESSAGES CONTROL"]
|
|
# Reasons disabled:
|
|
# format - handled by ruff
|
|
# abstract-method - with intro of async there are always methods missing
|
|
# cyclic-import - doesn't test if both import on load
|
|
# duplicate-code - unavoidable
|
|
# locally-disabled - it spams too much
|
|
# too-many-ancestors - it's too strict.
|
|
# too-many-* - are not enforced for the sake of readability
|
|
# too-few-* - same as too-many-*
|
|
# unused-argument - generic callbacks and setup methods create a lot of warnings
|
|
disable = [
|
|
"format",
|
|
"abstract-method",
|
|
"cyclic-import",
|
|
"duplicate-code",
|
|
"locally-disabled",
|
|
"no-else-return",
|
|
"not-context-manager",
|
|
"too-few-public-methods",
|
|
"too-many-ancestors",
|
|
"too-many-arguments",
|
|
"too-many-branches",
|
|
"too-many-instance-attributes",
|
|
"too-many-lines",
|
|
"too-many-locals",
|
|
"too-many-public-methods",
|
|
"too-many-return-statements",
|
|
"too-many-statements",
|
|
"unused-argument",
|
|
"consider-using-with",
|
|
|
|
# Handled by ruff
|
|
# Ref: <https://github.com/astral-sh/ruff/issues/970>
|
|
"await-outside-async", # PLE1142
|
|
"bad-str-strip-call", # PLE1310
|
|
"bad-string-format-type", # PLE1307
|
|
"bidirectional-unicode", # PLE2502
|
|
"continue-in-finally", # PLE0116
|
|
"duplicate-bases", # PLE0241
|
|
"format-needs-mapping", # F502
|
|
"function-redefined", # F811
|
|
# Needed because ruff does not understand type of __all__ generated by a function
|
|
# "invalid-all-format", # PLE0605
|
|
"invalid-all-object", # PLE0604
|
|
"invalid-character-backspace", # PLE2510
|
|
"invalid-character-esc", # PLE2513
|
|
"invalid-character-nul", # PLE2514
|
|
"invalid-character-sub", # PLE2512
|
|
"invalid-character-zero-width-space", # PLE2515
|
|
"logging-too-few-args", # PLE1206
|
|
"logging-too-many-args", # PLE1205
|
|
"missing-format-string-key", # F524
|
|
"mixed-format-string", # F506
|
|
"no-method-argument", # N805
|
|
"no-self-argument", # N805
|
|
"nonexistent-operator", # B002
|
|
"nonlocal-without-binding", # PLE0117
|
|
"not-in-loop", # F701, F702
|
|
"notimplemented-raised", # F901
|
|
"return-in-init", # PLE0101
|
|
"return-outside-function", # F706
|
|
"syntax-error", # E999
|
|
"too-few-format-args", # F524
|
|
"too-many-format-args", # F522
|
|
"too-many-star-expressions", # F622
|
|
"truncated-format-string", # F501
|
|
"undefined-all-variable", # F822
|
|
"undefined-variable", # F821
|
|
"used-prior-global-declaration", # PLE0118
|
|
"yield-inside-async-function", # PLE1700
|
|
"yield-outside-function", # F704
|
|
"anomalous-backslash-in-string", # W605
|
|
"assert-on-string-literal", # PLW0129
|
|
"assert-on-tuple", # F631
|
|
"bad-format-string", # W1302, F
|
|
"bad-format-string-key", # W1300, F
|
|
"bare-except", # E722
|
|
"binary-op-exception", # PLW0711
|
|
"cell-var-from-loop", # B023
|
|
# "dangerous-default-value", # B006, ruff catches new occurrences, needs more work
|
|
"duplicate-except", # B014
|
|
"duplicate-key", # F601
|
|
"duplicate-string-formatting-argument", # F
|
|
"duplicate-value", # F
|
|
"eval-used", # PGH001
|
|
"exec-used", # S102
|
|
# "expression-not-assigned", # B018, ruff catches new occurrences, needs more work
|
|
"f-string-without-interpolation", # F541
|
|
"forgotten-debug-statement", # T100
|
|
"format-string-without-interpolation", # F
|
|
# "global-statement", # PLW0603, ruff catches new occurrences, needs more work
|
|
"global-variable-not-assigned", # PLW0602
|
|
"implicit-str-concat", # ISC001
|
|
"import-self", # PLW0406
|
|
"inconsistent-quotes", # Q000
|
|
"invalid-envvar-default", # PLW1508
|
|
"keyword-arg-before-vararg", # B026
|
|
"logging-format-interpolation", # G
|
|
"logging-fstring-interpolation", # G
|
|
"logging-not-lazy", # G
|
|
"misplaced-future", # F404
|
|
"named-expr-without-context", # PLW0131
|
|
"nested-min-max", # PLW3301
|
|
# "pointless-statement", # B018, ruff catches new occurrences, needs more work
|
|
"raise-missing-from", # TRY200
|
|
# "redefined-builtin", # A001, ruff is way more stricter, needs work
|
|
"try-except-raise", # TRY203
|
|
"unused-argument", # ARG001, we don't use it
|
|
"unused-format-string-argument", #F507
|
|
"unused-format-string-key", # F504
|
|
"unused-import", # F401
|
|
"unused-variable", # F841
|
|
"useless-else-on-loop", # PLW0120
|
|
"wildcard-import", # F403
|
|
"bad-classmethod-argument", # N804
|
|
"consider-iterating-dictionary", # SIM118
|
|
"empty-docstring", # D419
|
|
"invalid-name", # N815
|
|
"line-too-long", # E501, disabled globally
|
|
"missing-class-docstring", # D101
|
|
"missing-final-newline", # W292
|
|
"missing-function-docstring", # D103
|
|
"missing-module-docstring", # D100
|
|
"multiple-imports", #E401
|
|
"singleton-comparison", # E711, E712
|
|
"subprocess-run-check", # PLW1510
|
|
"superfluous-parens", # UP034
|
|
"ungrouped-imports", # I001
|
|
"unidiomatic-typecheck", # E721
|
|
"unnecessary-direct-lambda-call", # PLC3002
|
|
"unnecessary-lambda-assignment", # PLC3001
|
|
"unneeded-not", # SIM208
|
|
"useless-import-alias", # PLC0414
|
|
"wrong-import-order", # I001
|
|
"wrong-import-position", # E402
|
|
"comparison-of-constants", # PLR0133
|
|
"comparison-with-itself", # PLR0124
|
|
# "consider-alternative-union-syntax", # UP007, typing extension
|
|
"consider-merging-isinstance", # PLR1701
|
|
# "consider-using-alias", # UP006, typing extension
|
|
"consider-using-dict-comprehension", # C402
|
|
"consider-using-generator", # C417
|
|
"consider-using-get", # SIM401
|
|
"consider-using-set-comprehension", # C401
|
|
"consider-using-sys-exit", # PLR1722
|
|
"consider-using-ternary", # SIM108
|
|
"literal-comparison", # F632
|
|
"property-with-parameters", # PLR0206
|
|
"super-with-arguments", # UP008
|
|
"too-many-branches", # PLR0912
|
|
"too-many-return-statements", # PLR0911
|
|
"too-many-statements", # PLR0915
|
|
"trailing-comma-tuple", # COM818
|
|
"unnecessary-comprehension", # C416
|
|
"use-a-generator", # C417
|
|
"use-dict-literal", # C406
|
|
"use-list-literal", # C405
|
|
"useless-object-inheritance", # UP004
|
|
"useless-return", # PLR1711
|
|
# "no-self-use", # PLR6301 # Optional plugin, not enabled
|
|
]
|
|
|
|
[tool.pylint.REPORTS]
|
|
score = false
|
|
|
|
[tool.pylint.TYPECHECK]
|
|
ignored-modules = ["distutils"]
|
|
# re.Pattern methods are C extension methods; pylint cannot detect them when
|
|
# re.Pattern is used as a dataclass field type annotation (false positive).
|
|
generated-members = ["re.Pattern.*"]
|
|
|
|
[tool.pylint.FORMAT]
|
|
expected-line-ending-format = "LF"
|
|
|
|
[tool.pylint.EXCEPTIONS]
|
|
overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"]
|
|
|
|
[tool.pylint.DESIGN]
|
|
max-positional-arguments = 10
|
|
|
|
[tool.mypy]
|
|
warn_unreachable = true
|
|
warn_redundant_casts = true
|
|
warn_unused_ignores = true
|
|
enable_error_code = ["exhaustive-match", "possibly-undefined"]
|
|
disable_error_code = ["import-not-found", "import-untyped"]
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
norecursedirs = [".git"]
|
|
log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s"
|
|
log_date_format = "%Y-%m-%d %H:%M:%S"
|
|
asyncio_default_fixture_loop_scope = "function"
|
|
asyncio_mode = "auto"
|
|
# 10s default timeout per test; per-test overrides via
|
|
# @pytest.mark.timeout(N).
|
|
timeout = 10
|
|
filterwarnings = [
|
|
"error",
|
|
"ignore:pkg_resources is deprecated as an API:DeprecationWarning:dirhash",
|
|
"ignore::pytest.PytestUnraisableExceptionWarning",
|
|
]
|
|
markers = [
|
|
"no_mock_init_websession: disable the autouse mock of init_websession for this test",
|
|
]
|
|
|
|
[tool.ruff]
|
|
lint.select = [
|
|
"B002", # Python does not support the unary prefix increment
|
|
"B007", # Loop control variable {name} not used within loop body
|
|
"B014", # Exception handler with duplicate exception
|
|
"B023", # Function definition does not bind loop variable {name}
|
|
"B026", # Star-arg unpacking after a keyword argument is strongly discouraged
|
|
"B904", # Use raise from to specify exception cause
|
|
"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
|
|
"G", # flake8-logging-format
|
|
"I", # isort
|
|
"ICN001", # import concentions; {name} should be imported as {asname}
|
|
"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
|
|
"PGH004", # Use specific rule codes when using noqa
|
|
"PLC0414", # Useless import alias. Import alias does not rename original package.
|
|
"PLC", # pylint
|
|
"PLE", # pylint
|
|
"PLR", # pylint
|
|
"PLW", # pylint
|
|
"Q000", # Double quotes found but single quotes preferred
|
|
"RUF006", # Store a reference to the return value of asyncio.create_task
|
|
"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
|
|
"SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
|
|
"SIM117", # Merge with-statements that use the same scope
|
|
"SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
|
|
"SIM201", # Use {left} != {right} instead of not {left} == {right}
|
|
"SIM208", # Use {expr} instead of not (not {expr})
|
|
"SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
|
|
"SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
|
|
"SIM401", # Use get from dict with default instead of an if block
|
|
"T100", # Trace found: {name} used
|
|
"T20", # flake8-print
|
|
"TID251", # Banned imports
|
|
"TRY004", # Prefer TypeError exception for invalid type
|
|
"TRY203", # Remove exception handler; error is immediately re-raised
|
|
"UP", # pyupgrade
|
|
"W", # pycodestyle
|
|
]
|
|
|
|
lint.ignore = [
|
|
"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
|
|
"E501", # line too long
|
|
"E731", # do not assign a lambda expression, use a def
|
|
|
|
# Ignore ignored, as the rule is now back in preview/nursery, which cannot
|
|
# be ignored anymore without warnings.
|
|
# https://github.com/astral-sh/ruff/issues/7491
|
|
# "PLC1901", # Lots of false positives
|
|
|
|
# False positives https://github.com/astral-sh/ruff/issues/5386
|
|
"PLC0208", # Use a sequence type instead of a `set` when iterating over values
|
|
"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
|
|
"PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target
|
|
"UP006", # keep type annotation style as is
|
|
"UP007", # keep type annotation style as is
|
|
|
|
# May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
|
|
"W191",
|
|
"E111",
|
|
"E114",
|
|
"E117",
|
|
"D206",
|
|
"D300",
|
|
"Q000",
|
|
"Q001",
|
|
"Q002",
|
|
"Q003",
|
|
"COM812",
|
|
"COM819",
|
|
"ISC001",
|
|
"ISC002",
|
|
|
|
# Disabled because ruff does not understand type of __all__ generated by a function
|
|
"PLE0605",
|
|
]
|
|
|
|
[tool.ruff.lint.flake8-import-conventions.extend-aliases]
|
|
voluptuous = "vol"
|
|
|
|
[tool.ruff.lint.flake8-pytest-style]
|
|
fixture-parentheses = false
|
|
|
|
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
|
"pytz".msg = "use zoneinfo instead"
|
|
|
|
[tool.ruff.lint.isort]
|
|
force-sort-within-sections = true
|
|
section-order = [
|
|
"future",
|
|
"standard-library",
|
|
"third-party",
|
|
"first-party",
|
|
"local-folder",
|
|
]
|
|
forced-separate = ["tests"]
|
|
known-first-party = ["supervisor", "tests"]
|
|
combine-as-imports = true
|
|
split-on-trailing-comma = false
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
|
|
# DBus Service Mocks must use typing and names understood by dbus-fast
|
|
"tests/dbus_service_mocks/*.py" = ["F722", "F821", "N815", "UP037"]
|
|
|
|
[tool.ruff.lint.mccabe]
|
|
max-complexity = 25
|