Vibe Python Style
Conventions for writing Python in the Vibe codebase. Apply whenever writing, reviewing, or refactoring Python code.
Style
- Prefer
match/caseover longif/elifchains. - Use the walrus operator
:=only when it shortens code and improves clarity. - Be a never-nester: early returns and guard clauses over nested blocks.
- Modern type hints only: built-in generics (
list,dict) and|unions. Never importOptional,Union,Dict,Listfromtyping. - Use
pathlib.Path(andanyio.Pathin async paths) instead ofos.path. - Use f-strings, comprehensions, and context managers; follow PEP 8.
- Enums:
StrEnum/IntEnumwithauto()and UPPERCASE members. For type-mixing, the mix-in type comes beforeEnumin the bases. Add methods or@propertyrather than parallel lookup tables. - Write declarative, minimalist code: express intent, drop boilerplate.
- Never call a private method from outside of its class in production code. Accessing private methods in tests is acceptable.
- Avoid comments and docstrings, except for when there's a hard to spot corner case
Typing & imports
- Pyright is strict and gates CI; fix types at the source.
- No relative imports —
ban-relative-imports = "all". Alwaysfrom vibe.core.x import …. - No inline
# type: ignoreor# noqa. Fix with refined signatures (TypeVar, Protocol),isinstanceguards,typing.castwhen control flow guarantees the type, or a small typed wrapper at the boundary.
TYPE_CHECKING and lazy imports
Moving imports under if TYPE_CHECKING: or into function bodies cuts startup time but risks runtime NameError. Before merging any import-deferral change, run:
- Ruff
TC004(pre-commit hook) — per-file: flagsTYPE_CHECKING-only names referenced at runtime. uv run python scripts/check_import_contracts.py— runtime cross-file: imports everyfrom <mod> import <name>acrossvibe/andtests/to verify it resolves; also rebuilds Pydantic models to catch lazily-failing field types. Catches cross-file re-exportsTC004misses. Missing non-vibe deps are non-blocking warnings.uv run scripts/suggest_lazy_imports.py— informational: reports deferral candidates (TC001–TC003+ single-function heuristic). Not gated.
Pydantic
- Parse external data via
model_validate,field_validator, ormodel_validator(mode="before")— never ad-hocgetattr/hasattrwalks or customfrom_sdkconstructors. - Set
ConfigDict(extra=…)explicitly. Usevalidation_alias(or field aliases) for kebab-case TOML keys. - Discriminated unions (e.g. MCP
transport): use sibling final classes plus a shared base/mixin, and compose withAnnotated[Union[...], Field(discriminator=...)]. Never narrow the discriminator field in a subclass — it violates LSP and pyright will reject it. - Document
Raises:only for exceptions the function actually raises (or that propagate from public API calls). Don't list speculative built-ins.
Logging & errors
- Use
from vibe.observability.logging import logger— stdlibloggingwithStructuredLogFormatter, notstructlog. - Configure via env:
LOG_LEVEL(defaultWARNING),LOG_MAX_BYTES. Logs land in~/.vibe/logs/vibe.log. - Pass variables as
%spositional args, not f-string interpolation: preferlogger.error("Failed to fetch url=%s", url)overlogger.error(f"Failed to fetch {url}"). This defers formatting to the logging framework (only formats if the message is emitted) and keeps messages grep-friendly. - Define module-local exception hierarchies. Always chain with
raise NewError(...) from e. Rich exceptions expose a_fmt()helper for human-readable output.
File I/O
- Prefer
vibe.core.utils.io.read_safe/read_safe_async/decode_safeover rawPath.read_text(),Path.read_bytes().decode(), oropen(). - They return
ReadSafeResult(text, encoding)and try UTF-8, then BOM detection, then locale, thencharset_normalizerlazily. - Pass
raise_on_error=Trueonly when callers must distinguish corrupt files from valid ones; the default replaces undecodable bytes with U+FFFD.