{"record":{"id":"ffeb9a6a101a71aa","repo":"D4Vinci/Scrapling","slug":"self-class-name-must-implement-parse-row","errorCode":null,"errorMessage":"{self.__class__.__name__} must implement parse_row() method","messagePattern":"(.+?) must implement parse_row\\(\\) method","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"scrapling/spiders/templates/feed.py","lineNumber":139,"sourceCode":"        \"\"\"Read the feed's rows and dispatch each one to `parse_row`.\"\"\"\n        content_type = response.headers.get(\"content-type\") if response.headers else None\n        try:\n            body = _decompress(response.body, content_type)\n        except OSError as e:\n            self.logger.warning(f\"Failed to decompress feed: {e}\")\n            return\n\n        text = body.decode(response.encoding or \"utf-8\", errors=\"replace\")\n        reader = DictReader(StringIO(text), fieldnames=self.headers, delimiter=self.delimiter, quotechar=self.quotechar)\n        for row in reader:\n            async for result in self.parse_row(response, dict(row)):\n                yield result\n\n    async def parse_row(\n        self, response: \"Response\", row: Dict[str, Any]\n    ) -> AsyncGenerator[Union[Dict[str, Any], Request, None], None]:\n        \"\"\"Override to process one feed row as a `{column: value}` dictionary.\"\"\"\n        raise NotImplementedError(f\"{self.__class__.__name__} must implement parse_row() method\")\n        yield  # Make this a generator for type checkers\n","sourceCodeStart":121,"sourceCodeEnd":141,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/spiders/templates/feed.py#L121-L141","documentation":"CSVFeedSpider-derived spiders must override parse_row(); the base implementation raises NotImplementedError. The engine decodes the CSV body with DictReader using the configured headers/delimiter/quotechar, then calls parse_row(response, row) once per row as a dict.","triggerScenarios":"Subclassing the CSV feed template without implementing parse_row; defining a differently named method (e.g. parse_line) that never gets called.","commonSituations":"Scaffolding CSV spiders and forgetting the hook; porting code from other frameworks with different callback names.","solutions":["Implement `async def parse_row(self, response, row)` yielding items/Requests/None, where row is a {column: value} dict","Check headers/delimiter configuration too, since DictReader needs them to produce correct columns"],"exampleFix":"// before\nclass CSV(CSVFeedSpider):\n    name = \"csv\"\n\n// after\nclass CSV(CSVFeedSpider):\n    name = \"csv\"\n\n    async def parse_row(self, response, row):\n        yield {\"sku\": row[\"sku\"], \"price\": row[\"price\"]}","handlingStrategy":"validation","validationCode":"assert CSV.parse_row is not CSVFeedSpider.parse_row, \"implement parse_row()\"","typeGuard":"def implements_parse_row(cls) -> bool:\n    return cls.parse_row is not CSVFeedSpider.parse_row","tryCatchPattern":null,"preventionTips":["Implement parse_row in every CSV feed spider","Verify headers/delimiter config so rows come out with the expected columns"],"tags":["feed","csv","abstract-method","spider-template"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}