4.3 KiB
4.3 KiB
Linter
- Always follow
.ruff.toml. - Run
uv run ruff check --fix --unsafe-fixes. - Keep each line under 100 characters (including spaces).
Code Style
snake_casefor variables and functions.PascalCasefor classes.UPPER_CASEfor constants.
Rules
- Use Pydantic v2 standard.
- Use
uvfor package management. - Do not override dunder methods like
__init__,__iadd__, etc. - Never launch services (
uv run app.py,flask run, etc.); running tests undertests/is allowed. - Prefer simple functions over classes for lightweight helpers.
- Keep files below 800 lines; split when necessary.
- Keep code readable—no clever hacks.
- Never use
print; log withlogger = logging.getLogger(__name__).
Guiding Principles
- Mirror the project’s layered architecture: controller → service → core/domain.
- Reuse existing helpers in
core/,services/, andlibs/before creating new abstractions. - Optimise for observability: deterministic control flow, clear logging, actionable errors.
SQLAlchemy Patterns
-
Models inherit from
models.base.Base; never create ad-hoc metadata or engines. -
Open sessions with context managers:
from sqlalchemy.orm import Session with Session(db.engine, expire_on_commit=False) as session: stmt = select(Workflow).where( Workflow.id == workflow_id, Workflow.tenant_id == tenant_id, ) workflow = session.execute(stmt).scalar_one_or_none() -
Use SQLAlchemy expressions; avoid raw SQL unless necessary.
-
Introduce repository abstractions only for very large tables (e.g., workflow executions) to support alternative storage strategies.
-
Always scope queries by
tenant_idand protect write paths with safeguards (FOR UPDATE, row counts, etc.).
Storage & External IO
- Access storage via
extensions.ext_storage.storage. - Use
core.helper.ssrf_proxyfor outbound HTTP fetches. - Background tasks that touch storage must be idempotent and log the relevant object identifiers.
Pydantic Usage
-
Define DTOs with Pydantic v2 models and forbid extras by default.
-
Use
@field_validator/@model_validatorfor domain rules. -
Example:
from pydantic import BaseModel, ConfigDict, HttpUrl, field_validator class TriggerConfig(BaseModel): endpoint: HttpUrl secret: str model_config = ConfigDict(extra="forbid") @field_validator("secret") def ensure_secret_prefix(cls, value: str) -> str: if not value.startswith("dify_"): raise ValueError("secret must start with dify_") return value
Generics & Protocols
- Use
typing.Protocolto define behavioural contracts (e.g., cache interfaces). - Apply generics (
TypeVar,Generic) for reusable utilities like caches or providers. - Validate dynamic inputs at runtime when generics cannot enforce safety alone.
Error Handling & Logging
- Raise domain-specific exceptions (
services/errors,core/errors) and translate to HTTP responses in controllers. - Declare
logger = logging.getLogger(__name__)at module top. - Include tenant/app/workflow identifiers in log context.
- Log retryable events at
warning, terminal failures aterror.
Tooling & Checks
- Format/lint:
uv run --project api --dev ruff format ./apianduv run --project api --dev ruff check --fix --unsafe-fixes ./api. - Type checks:
uv run --directory api --dev basedpyright. - Tests:
uv run --project api --dev dev/pytest/pytest_unit_tests.sh. - Run all of the above before submitting your work.
Controllers & Services
- Controllers: parse input via Pydantic, invoke services, return serialised responses; no business logic.
- Services: coordinate repositories, providers, background tasks; keep side effects explicit.
- Avoid repositories unless necessary; direct SQLAlchemy usage is preferred for typical tables.
- Document non-obvious behaviour with concise comments.
Miscellaneous
- Use
configs.dify_configfor configuration—never read environment variables directly. - Maintain tenant awareness end-to-end;
tenant_idmust flow through every layer touching shared resources. - Queue async work through
services/async_workflow_service; implement tasks undertasks/with explicit queue selection. - Keep experimental scripts under
dev/; do not ship them in production builds.