Dedalus style guide quick reference. Key rules for writing code that lasts. Use when writing or reviewing Python code.
# I/O function: io: Io as first parameter
async def fetch_user(io: Io, user_id: str) -> User | None: ...
# Pure function: no io parameter
def compute_discount(user: User, cart: Cart) -> Decimal: ...Any. Period. If you reach for Any, you have not modeled the domain.object as a type annotation. It is Any in disguise.JSONValue, JSONObject, JSONPrimitive, or JSONArray from core.types.json. These are proper recursive RFC 8259 types. There is no excuse for dict[str, object] or dict[str, Any].**kwargs: Any without justification.fetch_user(io=io, user_id=uid)result = ...; return resultFoo(x).bar() hides the instance. Split into foo = Foo(x) then result = foo.bar().assert a; assert b not assert a and bChargeError(org_id, reason)Exception without # noqa: BLE001passBillingError.ChargeError# Bad: silent, undebuggable
model = request.model or config.default_model or "gpt-4"
# Good: explicit precedence with early returns
def get_model(request, config):
if request.model:
return request.model
if config.default_model:
return config.default_model
raise ValueError("model is required")get_user_by_id not get_usrtimeout_ms, latency_ms_p99res, ret, cfg, ctxuser, balance, org_idfor field in fields, not for f in fields. Applies in comprehensions too: {field.name: field for field in fields}.async def fetch_user(io: Io, user_id: str) -> User | None:
"""Fetch user from database by ID.
The returned User is a snapshot; mutations won't persist.
Args:
io: I/O capability handle.
user_id: The unique identifier for the user.
Returns:
The User object if found, None otherwise.
""""""user = get_user(id) # Get the user → delete the comment# --- Label --- (no multi-line box separators)# fmt: off / # fmt: on (see column-aligned-fields skill for detailed rules)__init__.py: bare docstring only. No imports, no re-exports, no __all__.__all__ in implementation files either. Public API is communicated by naming convention (public vs _-prefixed). We never use import *._-prefix true internals callers never need.types.py. Logic in the main module._default_instance patterns.functools.cache for expensive I/O loads, not manual ClassVar[dict] caches. Don't import cross-package deps for simple stdlib needs.100_000 not 1000004096 is fine# --- Tests ---
from inline_tests import test # noqa: E402
@test
async def rejects_empty_id():
import pytest # noqa: PLC0415
with pytest.raises(ValueError):
await fetch_user("")# noqa: PLC0415tests/uv run pytest path/to/file.py --inline-tests -vuv run ruff format <files>uv run ruff check <files> --fixAny without justification__all__ in any implementation file