ZhuLinsen/daily_stock_analysis · warning · AlertServiceError

validation_error

validation_error

Error message

No fields provided for update

What it means

AlertServiceError (code 'validation_error') raised in AlertService.update_rule (src/services/alert_service.py:125) when the update payload dict is empty ({}). The service refuses no-op updates rather than returning the row unchanged, forcing callers to express at least one field. Checked after the existence pre-check but before _validate_rule_update_payload.

Source

Thrown at src/services/alert_service.py:125

        self.db = db_manager or DatabaseManager.get_instance()
        self.repo = AlertRepository(self.db)

    def create_rule(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        fields = self._normalize_rule_payload(payload)
        return self._serialize_rule(self.repo.create_rule(fields))

    def get_rule(self, rule_id: int) -> Dict[str, Any]:
        row = self.repo.get_rule(rule_id)
        if row is None:
            raise AlertNotFoundError(f"Alert rule not found: {rule_id}")
        return self._serialize_rule(row)

    def update_rule(self, rule_id: int, payload: Dict[str, Any]) -> Dict[str, Any]:
        row = self.repo.get_rule(rule_id)
        if row is None:
            raise AlertNotFoundError(f"Alert rule not found: {rule_id}")
        if not payload:
            raise AlertServiceError("No fields provided for update")
        self._validate_rule_update_payload(payload)

        merged = self._serialize_rule_base(row)
        merged.update(payload)
        fields = self._normalize_rule_payload(merged, source=merged.get("source") or "api")
        updated = self.repo.update_rule(rule_id, fields)
        if updated is None:
            raise AlertNotFoundError(f"Alert rule not found: {rule_id}")
        return self._serialize_rule(updated)

    def delete_rule(self, rule_id: int) -> bool:
        return self.repo.delete_rule(rule_id)

    def enable_rule(self, rule_id: int, enabled: bool) -> Dict[str, Any]:
        updated = self.repo.update_rule(rule_id, {"enabled": enabled})
        if updated is None:
            raise AlertNotFoundError(f"Alert rule not found: {rule_id}")
        return self._serialize_rule(updated)

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Skip the API call entirely when the diff is empty (client-side check: if not changed: return).
  2. Or include at least one explicit field, e.g. {'enabled': current_enabled} if a touch is really intended.
  3. Treat the error as benign: catch AlertServiceError with this message and show 'no changes'.

Example fix

# before
service.update_rule(rule_id, {k: v for k, v in form.items() if v != original[k]})  # {} when nothing changed

# after
changes = {k: v for k, v in form.items() if v != original[k]}
if changes:
    service.update_rule(rule_id, changes)
else:
    logger.info('no changes for rule %s', rule_id)
Defensive patterns

Strategy: validation

Validate before calling

changes = {k: v for k, v in payload.items() if k in EDITABLE_FIELDS and v != original.get(k)}
if not changes:
    logger.info('no changes for rule %s; skipping update', rule_id)
    return original

Type guard

def has_updatable_fields(payload: dict, editable: set) -> bool:
    return any(k in editable for k in payload)

Try / catch

try:
    service.update_rule(rule_id, payload)
except AlertServiceError as e:
    if 'No fields provided' in str(e):
        return ok('nothing to update')
    raise

Prevention

When it happens

Trigger: PUT with an empty JSON body {}; a diff-based client that computes changed fields and submits none when nothing changed; a PATCH builder that aggregates zero fields because all values matched the current rule.

Common situations: 'Save' buttons that always fire a request even with no edits; client-side diffing that skips sending unchanged fields and ends up sending nothing; test helpers posting empty dicts.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/53d4bf2f78242a9d. Report an issue: GitHub.