Code style
Style in ApexOS core is almost entirely machine-enforced: Ruff formats and lints, mypy and pylint type-check, and the prek hooks run all of it on commit. This page highlights the conventions you'll interact with most, all sourced from pyproject.toml, mypy.ini, and .strict-typing in the core repository.
Formatting and linting: Ruff
Ruff (minimum version pinned via required-version in pyproject.toml) is both the formatter (ruff-format) and the primary linter (ruff-check --fix). The lint selection is broad — highlights:
ASYNC(flake8-async),B(bugbear),PERF,PL(pylint rules),SIM(simplify),UP(pyupgrade),TRY(tryceratops),RUF(Ruff-specific).D— docstrings are linted (see below).I— import sorting (isort rules, see below).PT— pytest style: fixtures and marks are written without parentheses (@pytest.fixture, not@pytest.fixture()).T20— noprint()in library code; onlyapexos/__main__.py,apexos/scripts/, andscript/may write to stdout.DTZ003/DTZ004/DTZ011— timezone-aware datetime APIs only (datetime.now(tz=...), neverutcnow()).- A curated
S(security) subset — noexec/eval, no unsafe pickle/XML/subprocess-with-shell patterns, no hardcoded SQL.
Deliberately ignored rules worth knowing: E501 (line length is left to the formatter), B008 (function calls in argument defaults are idiomatic for validators), the PLR09xx "too many ..." complexity counts, and quote-related rules that would fight the formatter. Complexity is still bounded: max-complexity = 25 (mccabe).
Typing
Core is strictly typed and mypy runs in CI as a required check. From mypy.ini (generated by apexfest — regenerate with python3 -m script.apexfest -p mypy_config, don't edit by hand):
python_version = 3.14,disallow_untyped_defs,disallow_incomplete_defs,disallow_untyped_calls,disallow_untyped_decorators,warn_return_any,warn_unreachable,strict_equality,no_implicit_optional— new code must be fully annotated.- Extra error codes are enabled:
deprecated,explicit-override,ignore-without-code(every# type: ignoreneeds a code),redundant-self,truthy-iterable. - Strict typing for core files is on by default. Integrations opt in by adding themselves to
.strict-typing; fully annotated components should be listed there to enable the strict checks (includingdisallow_any_genericsfor core files). - Because the platform requires Python 3.14+,
from __future__ import annotationsis banned (enforced as a banned import in the Ruff tidy-imports config). async_timeoutandpytzare likewise banned imports — useasyncio.timeoutandzoneinfo.- Runtime typing is avoided (
runtime-typing = falsein the pylint typing plugin config); modern syntax (X | None, built-in generics) is expected — pyupgrade rules enforce it.
Docstrings
The pydocstyle convention is Google style (convention = "google" in the Ruff config). In practice, throughout the codebase:
- Every module, class, and public function has a docstring (
Drules are enforced; a handful likeD202,D203,D213,D417are relaxed). - The first line is a short imperative summary ending with a period:
"""Handle the POST request for registration.""","""Create a test instance of ApexOS.""". D417is ignored, so you document only the non-obvious parameters rather than exhaustively listing all of them.propcache.api.cached_propertyis registered as a property decorator, so methods decorated with it are documented like properties.
Import conventions
Import order is enforced by Ruff's isort rules with repo-specific settings:
force-sort-within-sections = true,combine-as-imports = true,split-on-trailing-comma = false, andknown-first-party = ["apexos"].- Relative imports are only allowed within an integration or auth provider package (
apexos/components/<domain>/...); everything else imports absolutely. - Test code may never be imported from production code (
testsis a banned import).
A large table of enforced import aliases (flake8-import-conventions) keeps helper imports uniform across the tree. The ones you'll use constantly:
import voluptuous as vol
from apexos.helpers import config_validation as cv
from apexos.helpers import device_registry as dr
from apexos.helpers import entity_registry as er
from apexos.helpers import area_registry as ar
from apexos.helpers import issue_registry as ir
from apexos.util import dt as dt_util
from apexos.util import color as color_util
Similarly, each platform's PLATFORM_SCHEMA must be imported under a domain-prefixed alias (e.g. SENSOR_PLATFORM_SCHEMA, LIGHT_PLATFORM_SCHEMA), and apexos.core.DOMAIN as APEXOS_DOMAIN.
pylint extras
pylint runs alongside Ruff (rules Ruff already covers are disabled to avoid double-reporting) with custom plugins from pylint/plugins, including repo-specific checkers (pylint_apexos) and per-file ignores for tests (fixture redefinition is expected in test signatures). Line endings are LF, and informational messages are treated as failures (fail-on = ["I"]).
The practical takeaway: run prek run --all-files (or just commit — the hooks fire automatically) and let the tooling shape the code. Style debates are settled by configuration, not review comments.