-
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathpyproject.toml
More file actions
290 lines (275 loc) · 12.4 KB
/
pyproject.toml
File metadata and controls
290 lines (275 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
[project]
authors = [
{name = "The ESPHome Authors", email = "esphome@openhomefoundation.org"},
]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Home Automation",
]
dependencies = [
"esphome-device-builder-frontend==0.1.83",
"aiohttp>=3.9.0",
# Provides an ``aiohttp.AsyncResolver`` that resolves ``.local``
# hostnames through an injected ``AsyncZeroconf`` (the same
# instance the device-state monitor already owns), so the
# outbound peer-link client doesn't depend on the host OS
# having a working mDNS responder wired into ``getaddrinfo``.
"aiohttp-asyncmdnsresolver>=0.1.1",
"colorlog>=6.8.0",
"cryptography>=42.0.2",
# Cheap non-cryptographic hash for the editor's
# validate-result cache (content → cached subprocess result).
"fnv-hash-fast>=1.0.0",
"ifaddr>=0.2.0",
"mashumaro>=3.13",
"orjson>=3.9.0",
"pyyaml>=6.0",
# ESPHome already ships ruamel.yaml as a transitive dependency,
# but the automations editor's parse / upsert path imports it
# directly. Pin explicitly so the backend's lean install path
# (no esphome optional dep) still carries the round-trip parser
# the automation editor needs.
"ruamel.yaml>=0.18.0",
# ESPHome already drags voluptuous in transitively, but the
# remote-build storage models (``models/remote_build.py``) use it
# directly via project-local validators. Pinning it explicitly so
# lean envs (the sync-boards pre-commit hook, any future
# esphome-less install path) don't drop it.
"voluptuous>=0.13.1",
]
description = "ESPHome Device Builder"
license = "Apache-2.0"
maintainers = [
{name = "Open Home Foundation", email = "esphome@openhomefoundation.org"},
]
name = "esphome-device-builder"
readme = "README.md"
requires-python = ">=3.12"
version = "0.0.0" # Set by GH action on release
[project.optional-dependencies]
esphome = [
"esphome>=2024.1.0",
]
test = [
"blockbuster>=1.5.5,<1.6",
"codespell==2.4.2",
"jsonschema>=4.20.0",
"mypy==2.1.0",
"pre-commit==4.6.0",
"pre-commit-hooks==6.0.0",
"pytest==9.0.3",
"pytest-aiohttp==1.1.0",
"pytest-codspeed==5.0.1",
"pytest-cov==7.1.0",
"pytest-timeout==2.4.0",
"pytest-xdist==3.8.0",
"ruff==0.15.13",
"types-PyYAML",
]
[project.urls]
Documentation = "https://esphome.io"
Homepage = "https://esphome.io"
Issues = "https://github.com/esphome/device-builder/issues"
Repository = "https://github.com/esphome/device-builder"
[project.scripts]
esphome-device-builder = "esphome_device_builder.__main__:main"
esphome-device-builder-discover = "esphome_device_builder.discover:main"
[tool.setuptools]
include-package-data = true
packages = {find = {include = ["esphome_device_builder*"]}}
platforms = ["any"]
zip-safe = false
[tool.setuptools.package-data]
esphome_device_builder = [
"py.typed",
"definitions/boards/*/images/*",
"definitions/boards.json",
"definitions/components.json",
"definitions/automations.json",
]
[tool.ruff]
fix = true
line-length = 100
show-fixes = true
target-version = "py312"
[tool.ruff.format]
line-ending = "lf"
[tool.ruff.lint]
# PLR0904 (too-many-public-methods) is still in ruff's preview set;
# enabling preview + explicit-preview-rules opts that single rule in
# without pulling every other unstable rule along with it. The intent
# is to catch *new* god-classes before they grow — the four pre-existing
# offenders are grandfathered with inline ``# noqa: PLR0904`` so this
# rule doesn't block existing work while they're refactored down.
preview = true
explicit-preview-rules = true
ignore = [
"D101", # missing docstring in public class (too noisy for now)
"D102", # missing docstring in public method (too noisy for now)
"D103", # missing docstring in public function (too noisy for now)
"D107", # missing docstring in __init__ (too noisy for now)
"D203", # conflicts with D211
"D213", # conflicts with D212
"D417", # false positives
"PLR0913", # too many arguments — not a useful constraint here
"PLR2004", # magic value comparison — too noisy for status codes / structural checks
# RUF001 / RUF002 / RUF003 flag U+00B5 (MICRO SIGN) as "ambiguous" against
# U+03BC (GREEK SMALL LETTER MU). U+00B5 is the canonical SI micro-prefix
# character (e.g. µs, µA, µHz) and is what ESPHome itself emits for
# unit_of_measurement; component-catalog code, unit-extraction tests,
# and the explainer comment in script/sync_components.py all need to
# handle that codepoint literally. False-positive for this codebase.
"RUF001", # ambiguous-unicode-character-string
"RUF002", # ambiguous-unicode-character-docstring
"RUF003", # ambiguous-unicode-character-comment
"S101", # assert used for type checking
"S104", # binding to all interfaces
# ASYNC109 wants async fns to use ``asyncio.timeout()`` ctx manager instead
# of accepting a ``timeout: float`` parameter, but our subprocess /
# config-bundle helpers (``_run_esptool``, ``run_subprocess_capture``,
# etc.) deliberately surface ``timeout`` as part of their API — callers
# pass per-call values, not constants. Forcing every caller to wrap in
# ``async with asyncio.timeout():`` would be more boilerplate, not less.
"ASYNC109",
# ASYNC240 flags ``pathlib.Path`` methods inside async functions and
# recommends ``trio.Path`` / ``anyio.path``. We use plain asyncio (no
# trio / anyio), and asyncio has no async filesystem API — sync
# ``Path`` methods are the correct shape, hopped through
# ``run_in_executor`` where they actually block. False positive for
# this codebase.
"ASYNC240",
# TRY003 wants every ``raise ExceptionType("msg")`` replaced with a
# custom exception class. We deliberately use the ``CommandError(code,
# msg)`` API + ``EsphomeError("msg")`` from upstream for ad-hoc
# errors; the custom-class-per-raise discipline would explode the
# exception-class surface for no callable benefit.
"TRY003",
]
select = [
"A", # flake8-builtins
"ASYNC", # flake8-async — catch blocking calls / busy-wait in async funcs
"B", # flake8-bugbear
"BLE", # flake8-blind-except — flag ``except Exception:`` so the catch scope is deliberate
"C4", # flake8-comprehensions
"D", # pydocstyle
"DTZ", # flake8-datetimez — flag naive datetime construction; force explicit tz
"E", # pycodestyle errors
"F", # pyflakes
"FBT", # flake8-boolean-trap — flag positional ``bool`` params / calls
"FLY", # flynt: convert string formatting to f-strings
"FURB", # refurb
"G", # flake8-logging-format — flag % / .format / f-string in logger calls
"I", # isort
"ICN", # flake8-import-conventions — pin canonical import aliases
"INP", # flake8-no-pep420 — flag implicit namespace packages (missing __init__.py)
"ISC", # flake8-implicit-str-concat — flag accidental ``"a" "b"`` adjacent-string concat
"LOG", # flake8-logging — catch ``logging.warn`` / wrong exception handling on the logger
"N", # pep8-naming
"PERF", # performance
"PL", # pylint
"PTH", # flake8-use-pathlib — prefer pathlib over os.path/os primitives
"PLR0904", # too-many-public-methods (preview) — cap new god-classes at 20
"RET", # flake8-return
"RUF", # ruff-specific
"S", # flake8-bandit
"SIM", # flake8-simplify
"SLF", # flake8-self — flag cross-class private member access so the State
# dataclass boundary stays load-bearing; sibling-module access within a
# controller package is exempted via per-file-ignores below.
"T20", # flake8-print
"TRY", # tryceratops — exception-handling antipatterns (TRY003 globally ignored above)
"UP", # pyupgrade
"W", # pycodestyle warnings
]
[tool.ruff.lint.per-file-ignores]
# CLI scripts: noisy print is fine, late imports gate optional esphome introspection
# behind try/except ImportError, and several subprocess invocations are inherent.
# SLF001 is also waived here so smoke-test scripts (``check_catalog.py``) can
# inspect catalog internals (``_by_id``, ``_components``) without per-line noqa.
# BLE001 is waived because sync scripts iterate over hundreds of catalog
# entries and want "log + continue" for any failure shape; per-entry noqa
# would be noise.
"script/*" = ["T20", "S108", "S310", "S110", "S603", "S607", "E501", "PLC0415", "SLF001", "BLE001", "FBT001", "FBT002", "TRY400"]
# Discovery CLI: prints are the entire UX (mirrors aioesphomeapi-discover).
"esphome_device_builder/discover.py" = ["T20"]
# Tests need free access to controller privates for setup / assertion;
# enforcing SLF001 here would mean a noqa on nearly every fixture and
# would obscure the cases where the protection actually matters.
# ASYNC110 (``while: await asyncio.sleep``) is also waived — tests
# legitimately poll for "until condition" without the production-side
# ``asyncio.Event`` plumbing the rule suggests; in production this rule
# stays load-bearing.
"tests/*" = ["S105", "S106", "SLF001", "ASYNC110", "BLE001", "FBT001", "FBT002", "FBT003", "TRY301"] # hardcoded test credentials
# Manual scripts: print is the UX, late imports gate optional setup,
# subprocess + tempdir paths are part of the CLI contract.
"tests/manual/*" = ["T20", "S108", "PLC0415"]
# Controller-package siblings (``controllers/X/runner.py`` etc.) are
# delegate modules that share state with ``controllers/X/controller.py``.
# CLAUDE.md's State convention scopes cross-module *data* through
# ``controller.state.X`` but leaves base infrastructure (``_db``,
# ``_listeners``, ``_tasks``) and private helper methods underscored —
# siblings reach those by design. The protection SLF001 buys us lives
# at the cross-*package* boundary (and at the top-level files outside
# this glob), which this exemption preserves.
"esphome_device_builder/controllers/**/*.py" = ["SLF001"]
# GitHub Action helpers: prints are CI-log diagnostics, lazy imports keep the
# module importable for unit tests without PyGithub installed, and the temp
# file path is a runner-controlled location not user input. SLF001 is also
# waived so action unit tests can call into helper-private methods directly.
# INP001 (implicit namespace package) is waived because action directories
# are standalone scripts, not part of the Python package tree. PTH123
# (use Path.open) is waived for the same reason — action scripts open
# files via plain ``open()`` for legibility, no pathlib churn warranted.
".github/actions/**" = ["T20", "S108", "PLC0415", "BLE001", "SLF001", "INP001", "PTH123"]
[tool.codespell]
skip = "*.json,*/definitions/*"
ignore-words-list = "commitish,hass"
[tool.ruff.lint.pydocstyle]
convention = "pep257"
[tool.ruff.lint.isort]
known-first-party = ["esphome_device_builder"]
[tool.mypy]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_defs = true
follow_imports = "silent"
ignore_missing_imports = true
python_version = "3.12"
warn_return_any = true
warn_unused_configs = true
[tool.pytest.ini_options]
asyncio_mode = "auto"
# ``loadgroup`` is xdist's default ``load`` scheduler (each test goes
# to whichever worker is free) plus an extra rule: tests tagged
# ``@pytest.mark.xdist_group("<name>")`` are pinned to a single
# worker. The catalog-heavy modules use that to share one
# ``ComponentCatalog.load`` (~2s on Linux CI) per run instead of
# paying it on every xdist worker.
addopts = "--dist=loadgroup --ignore=tests/real_compile"
# ``tests/real_compile/`` spawns full ``esphome compile``
# subprocesses and runs minutes per test; excluded from the
# default ``pytest`` invocation so a contributor running
# ``pytest`` locally doesn't get surprised by a multi-minute
# build. Runs in its own CI job (see
# ``.github/workflows/real-compile-tests.yml``); contributors
# can opt in locally with ``pytest tests/real_compile``.
# Default per-test timeout (pytest-timeout plugin) so a local
# ``pytest tests/...`` invocation doesn't wedge forever when an
# event loop deadlocks, a subprocess hangs, or an asyncio.wait_for
# is forgotten. Slowest tests in the suite today land under 3s
# (catalog setup + a couple of peer-link reconnect probes), so 10s
# gives ~3x headroom against any single test legitimately running
# slow. CI passes ``--timeout=120`` explicitly (see
# ``.github/workflows/test.yml``) for the looser cap that covers
# noisy runners; the CLI flag wins over this default. Override
# locally with ``--timeout=30`` or ``--timeout=0`` when iterating
# on a known-slow test.
timeout = 10
[build-system]
build-backend = "setuptools.build_meta"
requires = ["setuptools>=75.0"]