116 lines
4.3 KiB
Markdown
116 lines
4.3 KiB
Markdown
## Linter
|
||
|
||
- Always follow `.ruff.toml`.
|
||
- Run `uv run ruff check --fix --unsafe-fixes`.
|
||
- Keep each line under 100 characters (including spaces).
|
||
|
||
## Code Style
|
||
|
||
- `snake_case` for variables and functions.
|
||
- `PascalCase` for classes.
|
||
- `UPPER_CASE` for constants.
|
||
|
||
## Rules
|
||
|
||
- Use Pydantic v2 standard.
|
||
- Use `uv` for package management.
|
||
- Do not override dunder methods like `__init__`, `__iadd__`, etc.
|
||
- Never launch services (`uv run app.py`, `flask run`, etc.); running tests under `tests/` 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 with `logger = logging.getLogger(__name__)`.
|
||
|
||
## Guiding Principles
|
||
|
||
- Mirror the project’s layered architecture: controller → service → core/domain.
|
||
- Reuse existing helpers in `core/`, `services/`, and `libs/` 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:
|
||
|
||
```python
|
||
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_id` and protect write paths with safeguards (`FOR UPDATE`, row counts, etc.).
|
||
|
||
## Storage & External IO
|
||
|
||
- Access storage via `extensions.ext_storage.storage`.
|
||
- Use `core.helper.ssrf_proxy` for 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_validator` for domain rules.
|
||
|
||
- Example:
|
||
|
||
```python
|
||
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.Protocol` to 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 at `error`.
|
||
|
||
## Tooling & Checks
|
||
|
||
- Format/lint: `uv run --project api --dev ruff format ./api` and `uv 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_config` for configuration—never read environment variables directly.
|
||
- Maintain tenant awareness end-to-end; `tenant_id` must flow through every layer touching shared resources.
|
||
- Queue async work through `services/async_workflow_service`; implement tasks under `tasks/` with explicit queue selection.
|
||
- Keep experimental scripts under `dev/`; do not ship them in production builds.
|