Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CODESTYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ This is the style guide for any **Python project(s)** in this repo.
**Two profiles.** A repo's Python is one of two shapes, and the rest of this section (uv project, `uv.lock`, `uv run`, `src` layout, pytest coverage) describes the **project** profile. The two differ by whether the Python has **third-party runtime dependencies**, which shows up structurally in `pyproject.toml`, so the audit detects the profile there (`python.profile.detect`):

- **Project** - the Python has third-party runtime dependencies, or is the repo's deliverable. It is a PEP 621 uv project: `[project]` with `dependencies` (dev tools in `[project.optional-dependencies]` or `[dependency-groups]`), a `[build-system]`, and a committed `uv.lock` (pinned LF - see [Line Endings][line-endings]); CI runs `uv sync --frozen` + `uv run <tool>`, so the lockfile pins tool versions.
- **Scripts** - stdlib-only utility scripts embedded in a **non-Python** repo (e.g. a Python tooling subtree of a `csharp` app). Run the tools with **`uvx`** (no project install, no lockfile): the `pyproject.toml` carries **only** `[tool.ruff]` / `[tool.mypy]` config - no `[project]`, no `[build-system]`, no `uv.lock` (that metadata would misrepresent it as a shippable package). **mypy** is the type checker (there is no first-party package for pyright strict to anchor on). Because there is no lockfile to pin versions, **CI pins the exact tool versions in the `uvx` command** (`uvx ruff@<ver>`, `uvx mypy@<ver>`, bumpable there) while the VS Code tasks and README run the unpinned latest - a deliberate CI-vs-local gap so local tooling never silently falls behind. `.py` files follow the repo's line-ending default (CRLF in a CRLF-default repo; a shebang-executed script is LF-pinned by path - see [Line Endings][line-endings]). There is no pytest suite, so the coverage expectation is N/A; a co-present `csharp` type still carries `codecov.yml` for its own tests.
- **Scripts** - stdlib-only utility scripts embedded in a **non-Python** repo (e.g. a Python tooling subtree of a `csharp` app). Run the tools with **`uvx`** (no project install, no lockfile): the `pyproject.toml` carries **only** tool config (`[tool.ruff]`, `[tool.mypy]`, and an optional `[tool.pyright]` editor block) - no `[project]`, no `[build-system]`, no `uv.lock` (that metadata would misrepresent it as a shippable package). **mypy** is the type-check gate (there is no first-party package for pyright strict to anchor on), and a `[tool.pyright]` block in **standard** mode keeps Pylance quiet in the editor - the same mypy-gate/pyright-editor split the Project profile uses. There is no lockfile, and a `uvx <tool>@<ver>` pin in a `run:` step is not something Dependabot tracks, so **CI runs `uvx ruff@latest` / `uvx mypy@latest`** rather than a manual pin that would silently go stale. The fleet rule is to pin only what Dependabot auto-updates (SHA-pinned actions, package deps) and otherwise run latest, so the VS Code tasks, README, and CI all run the unpinned latest here. `.py` files follow the repo's line-ending default (CRLF in a CRLF-default repo, and a shebang-executed script is LF-pinned by path - see [Line Endings][line-endings]). There is no pytest suite, so the coverage expectation is N/A. A co-present `csharp` type still carries `codecov.yml` for its own tests.
Comment thread
ptr727 marked this conversation as resolved.

### Toolchain

Expand Down
18 changes: 18 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Config only (no [project]/[build-system]/uv.lock) - the Scripts profile, CODESTYLE.md "Two profiles".

[tool.ruff]
target-version = "py313"
line-length = 100

[tool.ruff.lint]
extend-select = ["I"] # isort import ordering, on top of the default rules

[tool.mypy]
python_version = "3.13"
files = ["spec", "host-setup"]

[tool.pyright]
pythonVersion = "3.13"
typeCheckingMode = "standard"
include = ["spec", "host-setup"]
exclude = ["**/__pycache__"]
11 changes: 6 additions & 5 deletions spec/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import subprocess
import sys
from datetime import datetime, timezone
from typing import Any

ROOT = pathlib.Path(__file__).resolve().parent.parent

Expand Down Expand Up @@ -59,8 +60,8 @@ def hub_name():
HUB_NAME, HUB_NAME_FROM_REMOTE = hub_name()


def gh(path, ok404=False):
"""GET a REST path via gh, returning parsed JSON or None on 404 when ok404.
def gh(path, ok404=False) -> Any:
"""GET a REST path via gh, returning parsed JSON, or None on a 404 (when ok404) or an empty response body.

No --paginate: on object endpoints it concatenates page documents into unparseable JSON. Every
list read here fits one page; callers pass per_page=100 where a default page could truncate.
Expand Down Expand Up @@ -276,7 +277,7 @@ def classify_verbatim(down_text, canon_text, past_texts):
return "modified"


_HISTORY_CACHE = {} # rel_path -> [past revision content], reused as a canonical is compared against every audited repo
_HISTORY_CACHE: dict[str, list[str]] = {} # rel_path -> past revision contents, cached because one canonical is compared against every audited repo


def git_file_history(rel_path):
Expand Down Expand Up @@ -506,7 +507,7 @@ def audit_repo(entry, spec):
# to ""), whereas a too-large or non-inline payload returns encoding "none" (text stays None -> flagged).
text = base64.b64decode(content["content"]).decode("utf-8", "replace") if content.get("encoding") == "base64" else None
# Interface conformance (name + wiring) plus any verbatim job regions the contract pins.
if fid == "interface":
if item is not None and fid == "interface":
if text is None:
findings.append(("DRIFT", f"interface: could not read {path} content on {ground} to verify its contract (no inline content returned); verify by hand"))
else:
Comment thread
ptr727 marked this conversation as resolved.
Expand All @@ -517,7 +518,7 @@ def audit_repo(entry, spec):
findings.extend(check_verbatim(f"{path} job '{job}'", text, canonical_rel,
extract=lambda t, j=job: split_jobs(t).get(j)))
# Whole-file verbatim: byte-identical to the hub's canonical after EOL normalization.
elif fid == "verbatim":
elif item is not None and fid == "verbatim":
if text is None:
findings.append(("DRIFT", f"verbatim: could not read {path} content on {ground} to compare (no inline content returned); verify by hand"))
else:
Expand Down
2 changes: 1 addition & 1 deletion spec/project-types.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
{ "id": "python.mypy.allowed", "verdict": "intent", "assert": "mypy is permitted as an additional type checker (not banned); required for a Home Assistant integration (platinum strict-typing) and is the SCRIPTS profile's type checker. When used it runs in CI and the editor.", "intentRef": "CODESTYLE.md" },
{ "id": "python.coverage.codecov", "verdict": "letter", "assert": "The test job collects coverage (pytest --cov-report=xml) and uploads it to Codecov via codecov/codecov-action, best-effort (continue-on-error and fail_ci_if_error: false); CODECOV_TOKEN is stored in the repo actions secrets. Required for every Python repo with tests. N/A for the SCRIPTS profile (lint/type-checked only, no pytest); in a mixed repo the codecov.yml file-presence is still required by any co-present type that has tests, e.g. csharp.", "intentRef": "WORKFLOW.md" },
{ "id": "python.uvlock.pinned", "verdict": "letter", "assert": "PROJECT profile: the committed uv.lock is pinned to LF in both .editorconfig ([uv.lock]) and .gitattributes (uv.lock text eol=lf); uv regenerates it LF on every platform, so a CRLF-default repo otherwise reds editorconfig-checker on every uv lock/sync. N/A for a non-uv Python repo (a Home Assistant integration on pip/requirements) and for the SCRIPTS profile (no uv.lock by definition).", "intentRef": "AGENTS.md#line-endings" },
{ "id": "python.scripts.uvx", "verdict": "letter", "assert": "SCRIPTS profile only: the tools run via uvx (no project install, no lockfile). CI pins exact tool versions in the uvx command (e.g. uvx ruff@X, uvx mypy@Y), bumpable there; the VS Code tasks and README run the unpinned latest, a deliberate CI-vs-local gap so local never silently falls behind CI. N/A for the PROJECT profile (which pins tool versions via uv.lock + uv sync --frozen instead).", "intentRef": "CODESTYLE.md" }
{ "id": "python.scripts.uvx", "verdict": "letter", "assert": "SCRIPTS profile only: the tools run via uvx (no project install, no lockfile). A uvx <tool>@<ver> pin in a run: step is not Dependabot-trackable, so CI runs uvx ruff@latest / uvx mypy@latest - the fleet rule pins only what Dependabot auto-updates and otherwise runs latest, never a manual pin that goes stale. VS Code tasks, README, and CI all run the unpinned latest. N/A for the PROJECT profile (which pins tool versions via uv.lock + uv sync --frozen instead).", "intentRef": "CODESTYLE.md" }
]
},
"console": {
Expand Down