{"record":{"id":"64eb4110ba6f969b","repo":"datawhalechina/hello-agents","slug":"q","errorCode":null,"errorMessage":"搜索关键词 q 不能为空","messagePattern":"搜索关键词 q 不能为空","errorType":"http","errorClass":"MovieServiceError","httpStatus":400,"severity":"warning","filePath":"Co-creation-projects/aatanxiao12-beep-YingQian/backend/app/services/movie_service.py","lineNumber":313,"sourceCode":"        if movie_id <= 0:\n            raise MovieServiceError(\"movie_id 必须为正整数\", status_code=400)\n\n        data = self._get(\n            f\"/movie/{movie_id}\",\n            {\"append_to_response\": \"credits\"},\n        )\n        return self._map_detail(data)\n\n    def search(\n        self,\n        q: str,\n        year: Optional[int] = None,\n        page: int = 1,\n    ) -> List[CandidateMovie]:\n        \"\"\"按关键词搜索电影（TMDB GET /search/movie）。\"\"\"\n        query = (q or \"\").strip()\n        if not query:\n            raise MovieServiceError(\"搜索关键词 q 不能为空\", status_code=400)\n\n        data = self._get(\n            \"/search/movie\",\n            {\n                \"query\": query,\n                \"year\": year,\n                \"page\": page,\n            },\n        )\n        return [self._map_result(item) for item in data.get(\"results\") or []]\n\n    def discover(\n        self,\n        with_genres: Optional[str] = None,\n        year: Optional[int] = None,\n        year_gte: Optional[int] = None,\n        year_lte: Optional[int] = None,\n        max_runtime: Optional[int] = None,","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/aatanxiao12-beep-YingQian/backend/app/services/movie_service.py#L295-L331","documentation":"MovieServiceError with status_code=400 raised by search when the query string is empty after stripping. The service refuses to call TMDB's /search/movie with an empty query because TMDB itself would reject it; this is a fail-fast input guard. Note that q=None is also coerced to empty via (q or '').strip(), so passing None triggers the same error rather than a TypeError.","triggerScenarios":"search('') or search('   '); q=None from an omitted request field; whitespace-only input from a form; a string that became empty after normalization; LLM tool call with query=''.","commonSituations":"Frontend submitting an untouched search box; agent deciding to search with no keyword; whitespace from copy-paste; API consumers treating q as optional.","solutions":["Validate non-empty q at the API schema level (min_length=1) so FastAPI returns 422 with a field-specific message","Strip input in the caller and skip the search call (or prompt the user) when empty instead of catching the error","For agent tool schemas, mark query as required with a description forbidding empty strings"],"exampleFix":"# before\n@app.get(\"/movies/search\")\nasync def search_movies(q: str | None = None):\n    return service.search(q)  # raises 400 on empty\n\n# after\nfrom pydantic import field_validator\n\nclass SearchQuery(BaseModel):\n    q: str\n    @field_validator(\"q\")\n    @classmethod\n    def q_not_blank(cls, v: str) -> str:\n        v = v.strip()\n        if not v:\n            raise ValueError(\"q 不能为空\")\n        return v","handlingStrategy":"validation","validationCode":"def searchable(q: str | None) -> bool:\n    return isinstance(q, str) and q.strip() != ''","typeGuard":"def is_non_blank_str(v) -> bool:\n    return isinstance(v, str) and len(v.strip()) > 0","tryCatchPattern":null,"preventionTips":["Model q as a required pydantic field with strip_whitespace=True and min_length=1","Skip the search call (or prompt the user) when the query is blank instead of catching 400s","In agent tool schemas, mark query required and describe it as a non-empty keyword"],"tags":["tmdb","validation","http-400","search","python"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}