mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
chore(lint): name MappingProxyType in the mutable-collection fix messages
LIT001/LIT002 and the typing.Dict ban all steered dict-shaped values to frozen dataclasses or suppression even though the checker already accepts MappingProxyType as a freezing wrapper; the messages now name it so the dict-shaped freeze path is actually discoverable at fix time.
This commit is contained in:
parent
b66d4e6965
commit
0c1a4b127d
4 changed files with 21 additions and 9 deletions
|
|
@ -45,7 +45,7 @@ When you fix violations gated by `ruff-strict-budget.json`, `type-discipline-bud
|
|||
|
||||
If you're trying to create a new function that relies on untyped stuff, instead of adding more Any's and pushing `reportAny` / `reportExplicitAny` closer to their basedpyright ceilings, just validate it in the caller with Pydantic (a model or `TypeAdapter` that returns the typed thing or raises will do) and then pass the now typed variable in
|
||||
|
||||
If you get an LIT001 or LIT002 fail, refactor the code to follow functional programming best practices rather than introducing mutable data structures. For example, build values in one shot with comprehensions or generators wrapped in `tuple()` / `frozenset()` instead of seeding an empty `list`/`dict`/`set` and mutating it over time. Ideally, `# mutable-ok` is never used; reach for it only as a genuine last resort when an immutable rewrite is truly impossible, and always pair it with a real reason
|
||||
If you get an LIT001 or LIT002 fail, refactor the code to follow functional programming best practices rather than introducing mutable data structures. For example, build values in one shot with comprehensions or generators wrapped in `tuple()` / `frozenset()`, and freeze dict-shaped values with `types.MappingProxyType({...})` (annotated as `Mapping[...]`), instead of seeding an empty `list`/`dict`/`set` and mutating it over time. Ideally, `# mutable-ok` is never used; reach for it only as a genuine last resort when an immutable rewrite is truly impossible, and always pair it with a real reason
|
||||
|
||||
Every lint or type suppression must name the exact rule inside brackets and carry a reason comment, e.g. `# pyright: ignore[reportArgumentType] # stubs lack async overload` or `# noqa: TID251 # <reason>`. `# type: ignore` is banned (LIT009): pyrightconfig.json sets `enableTypeIgnoreComments` to false, so it silently does nothing
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ Follow these coding conventions for new/updated code (a three-line fix in a lega
|
|||
- Composition over inheritance
|
||||
- Never-nester: early returns over deep nesting
|
||||
- Don't throw; model failures as values (One function (e.g., raise_public) maps error union to existing public exception contracts via exhaustive match + assert_never)
|
||||
- No mutation; don't reassign variables, global or local. Instead of mutable lists and dicts, prefer tuples, frozen dataclasses (with slots=True), etc.
|
||||
- No mutation; don't reassign variables, global or local. Instead of mutable lists and dicts, prefer tuples, frozen dataclasses (with slots=True), `types.MappingProxyType`, etc.
|
||||
- Annotate every variable with `: Final` (LIT010). Unpacking and walrus targets cannot carry the annotation, so they are implicitly final. Don't rebind them. Never rebind or mutate function parameters (LIT011); `self`/`cls` attribute stores are the exception. If rebinding or in-place mutation is truly unavoidable, suppress with `# rebind-ok: <reason>` explaining why
|
||||
- Use dependency injection
|
||||
- Fully typed; no `Any` or coarse types like `dict[str, Any]` or just `dict`. Every function parameter must be strongly typed
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ max-args = 5
|
|||
"typing.Any".msg = "Use a concrete type. Frozen slots=True dataclass (preferred) / NamedTuple / ReadOnly TypedDict for payloads."
|
||||
"typing_extensions.Any".msg = "Same as typing.Any."
|
||||
"typing.List".msg = "tuple[X, ...] for state, Sequence[X] for params."
|
||||
"typing.Dict".msg = "Frozen dataclass / NamedTuple / ReadOnly TypedDict; create a Mapping alias with concrete value types if truly dynamic."
|
||||
"typing.Dict".msg = "Frozen dataclass / NamedTuple / ReadOnly TypedDict; if truly dynamic, a Mapping alias with concrete value types, frozen at runtime with types.MappingProxyType({...})."
|
||||
"typing.Set".msg = "frozenset[X] or AbstractSet[X]."
|
||||
"typing.MutableSequence".msg = "Sequence[X]."
|
||||
"typing.MutableMapping".msg = "See typing.Dict."
|
||||
|
|
|
|||
|
|
@ -11,14 +11,16 @@ LIT001 Mutable collection in a type annotation, anywhere it appears: function
|
|||
collection lets whoever holds it grow or rewrite it after the fact; annotate
|
||||
a read-only view instead (Mapping/Sequence/AbstractSet/tuple[X, ...]/
|
||||
frozenset[X], or a frozen dataclass / NamedTuple / ReadOnly TypedDict) and
|
||||
build it functionally (comprehension / map, not append-in-a-loop).
|
||||
build it functionally (comprehension / map / MappingProxyType({...}), not
|
||||
append-in-a-loop).
|
||||
Suppress with `# mutable-ok: <reason>` on the offending line.
|
||||
LIT002 Mutable-collection *construction*: a list/dict/set literal or comprehension, or
|
||||
a call to a mutable constructor (list/dict/set/deque/defaultdict/Counter/...).
|
||||
Catches the unannotated seed-then-mutate pattern LIT001 cannot see (`acc = []`).
|
||||
Build the value in one shot and freeze it: a `tuple`/`frozenset` wrapping a
|
||||
generator (`tuple(f(x) for x in xs)`), a tuple literal, or a frozen dataclass /
|
||||
NamedTuple / ReadOnly TypedDict. Generator expressions and `tuple`/`frozenset`
|
||||
generator (`tuple(f(x) for x in xs)`), a tuple literal, `MappingProxyType({...})`
|
||||
for a dict-shaped value, or a frozen dataclass / NamedTuple / ReadOnly TypedDict.
|
||||
Generator expressions and `tuple`/`frozenset`
|
||||
calls are not construction and pass. Annotation-internal lists (`Callable[[int],
|
||||
str]`) are exempt, as is a value passed directly to a freezing wrapper
|
||||
(`tuple(...)`, `frozenset(...)`, `MappingProxyType(...)`): it is frozen before
|
||||
|
|
@ -279,7 +281,8 @@ def _mutable_ann(path: Path, line: int, name: str, where: str) -> Violation:
|
|||
f"mutable `{name}` in {where}: a mutable collection can be grown or rewritten "
|
||||
f"by whoever holds it. Annotate a read-only view -- Mapping[...], Sequence[...], "
|
||||
f"AbstractSet[...], tuple[X, ...], frozenset[X], or a frozen dataclass / "
|
||||
f"NamedTuple / ReadOnly TypedDict -- and build it functionally, not by "
|
||||
f"NamedTuple / ReadOnly TypedDict -- and build it functionally "
|
||||
f"(comprehension / map / `MappingProxyType({{...}})`), not by "
|
||||
f"append-in-a-loop (suppress: `# mutable-ok: <reason>`)",
|
||||
)
|
||||
|
||||
|
|
@ -488,8 +491,9 @@ def iter_construction_violations(path: Path, tree: ast.AST, comments: Comments)
|
|||
path, node.lineno, "LIT002",
|
||||
f"mutable {kind}: this builds a collection that can be grown or rewritten. "
|
||||
f"Build it in one shot and freeze it -- a tuple/frozenset wrapping a generator "
|
||||
f"(`tuple(f(x) for x in xs)`), a tuple literal, or a frozen dataclass / NamedTuple "
|
||||
f"/ ReadOnly TypedDict (suppress: `# mutable-ok: <reason>`)",
|
||||
f"(`tuple(f(x) for x in xs)`), a tuple literal, `MappingProxyType({{...}})` for a "
|
||||
f"dict-shaped value, or a frozen dataclass / NamedTuple / ReadOnly TypedDict "
|
||||
f"(suppress: `# mutable-ok: <reason>`)",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -175,6 +175,14 @@ def test_unfrozen_literal_still_counts(tmp_path):
|
|||
assert "LIT002" in _codes(tmp_path, "from types import MappingProxyType\nd = {'a': 1}\nm = MappingProxyType(d)\n")
|
||||
|
||||
|
||||
def test_fix_messages_name_mappingproxytype(tmp_path):
|
||||
f = tmp_path / "snippet.py"
|
||||
f.write_text("x: dict[str, int] = {}\n", encoding="utf-8")
|
||||
messages = {v.code: v.message for v in checker.check_file(f)}
|
||||
assert "MappingProxyType" in messages["LIT001"]
|
||||
assert "MappingProxyType" in messages["LIT002"]
|
||||
|
||||
|
||||
def test_mutable_ok_with_reason_suppresses_both_rules(tmp_path):
|
||||
codes = _codes(tmp_path, "x: dict[str, int] = {} # mutable-ok: in-place buffer mutated hot path\n")
|
||||
assert "LIT001" not in codes
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue