ZhuLinsen/daily_stock_analysis · error · ValueError

{field_name} must be an integer

Error message

{field_name} must be an integer

What it means

_optional_positive_int converts an optional numeric field with int(value); if value is neither None/'' nor int-convertible (e.g. a non-numeric string, a dict, a list), TypeError/ValueError is caught and re-raised as ValueError '<field> must be an integer'. It validates API query/path params like signal_id before they reach the repository.

Source

Thrown at src/services/decision_signal_outcome_service.py:631

        if horizon:
            return [horizon]
        return list(SUPPORTED_OUTCOME_HORIZONS.keys())

    def _require_existing_signal(self, signal_id: int) -> DecisionSignalRecord:
        signal_id_norm = self._optional_positive_int(signal_id, "signal_id")
        row = self.signal_repo.get(signal_id_norm)
        if row is None:
            raise DecisionSignalNotFoundError(f"Decision signal not found: {signal_id_norm}")
        return row

    @staticmethod
    def _optional_positive_int(value: Any, field_name: str) -> Optional[int]:
        if value in (None, ""):
            return None
        try:
            number = int(value)
        except (TypeError, ValueError) as exc:
            raise ValueError(f"{field_name} must be an integer") from exc
        if number <= 0:
            raise ValueError(f"{field_name} must be positive")
        return number

    @staticmethod
    def _normalize_enum(value: Any, allowed: Iterable[str], field_name: str) -> str:
        text = str(value or "").strip()
        allowed_set = set(allowed)
        if text not in allowed_set:
            allowed_text = ", ".join(sorted(allowed_set))
            raise ValueError(f"{field_name} must be one of {allowed_text}")
        return text

    @classmethod
    def _normalize_optional_enum(cls, value: Any, allowed: Iterable[str], field_name: str) -> Optional[str]:
        if value in (None, ""):
            return None
        return cls._normalize_enum(value, allowed, field_name)

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Send the field as a plain integer (or integer string like "42").
  2. Coerce and validate on the client before the call: use int() and confirm it is whole.
  3. For float strings, convert via int(float(value)) first if fractional input is legitimate.

Example fix

# before
result = service._optional_positive_int("12.5", "signal_id")

# after
result = service._optional_positive_int(int(float("12.5")), "signal_id")  # 12
Defensive patterns

Strategy: type-guard

Validate before calling

def toOptionalInt(value) -> int | None:
    if value in (None, ""):
        return None
    try:
        return int(str(value).strip())
    except (TypeError, ValueError):
        raise HTTPException(400, f"{value!r} is not an integer")

signal_id = toOptionalInt(raw_id)  # before calling the service

Type guard

def isIntLike(value: object) -> bool:
    if value in (None, ""):
        return True
    try:
        int(value)
        return True
    except (TypeError, ValueError):
        return False

Try / catch

try:
    result = service.evaluate_outcomes(signal_id=raw_id)
except ValueError as exc:
    if "must be an integer" in str(exc):
        return JSONResponse(status_code=400, content={"error": "invalid_params", "message": str(exc)})
    raise

Prevention

When it happens

Trigger: Calling decision-signal endpoints/services with signal_id="abc", signal_id=[1], or a float string like "1.5" — int("1.5") raises ValueError; passing an object with no __int__ raises TypeError.

Common situations: String IDs from URL paths or query strings not validated upstream; JSON payloads where the field is typed as string; frontend sending "1.0".

Related errors


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