prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Handle it gracefully when start_url is used instead of start_urls
  Over the last year I’ve seen a few cases ([recent example](https://stackoverflow.com/q/58664004/939364)) of this, people missing the `s` at the end of the `start_urls`.
  
  It may be nice to find a way to gracefully let the developer know where the issue is, why there is no crawling happening.
  
  </issue>
  <code>
  [start of scrapy/spiders/__init__.py]
  1 """
  2 Base class for Scrapy spiders
  3 
  4 See documentation in docs/topics/spiders.rst
  5 """
  6 import logging
  7 import warnings
  8 
  9 from scrapy import signals
  10 from scrapy.http import Request
  11 from scrapy.utils.trackref import object_ref
  12 from scrapy.utils.url import url_is_from_spider
  13 from scrapy.exceptions import ScrapyDeprecationWarning
  14 from scrapy.utils.deprecate import method_is_overridden
  15 
  16 
  17 class Spider(object_ref):
  18     """Base class for scrapy spiders. All spiders must inherit from this
  19     class.
  20     """
  21 
  22     name = None
  23     custom_settings = None
  24 
  25     def __init__(self, name=None, **kwargs):
  26         if name is not None:
  27             self.name = name
  28         elif not getattr(self, 'name', None):
  29             raise ValueError("%s must have a name" % type(self).__name__)
  30         self.__dict__.update(kwargs)
  31         if not hasattr(self, 'start_urls'):
  32             self.start_urls = []
  33 
  34     @property
  35     def logger(self):
  36         logger = logging.getLogger(self.name)
  37         return logging.LoggerAdapter(logger, {'spider': self})
  38 
  39     def log(self, message, level=logging.DEBUG, **kw):
  40         """Log the given message at the given log level
  41 
  42         This helper wraps a log call to the logger within the spider, but you
  43         can use it directly (e.g. Spider.logger.info('msg')) or use any other
  44         Python logger too.
  45         """
  46         self.logger.log(level, message, **kw)
  47 
  48     @classmethod
  49     def from_crawler(cls, crawler, *args, **kwargs):
  50         spider = cls(*args, **kwargs)
  51         spider._set_crawler(crawler)
  52         return spider
  53 
  54     def _set_crawler(self, crawler):
  55         self.crawler = crawler
  56         self.settings = crawler.settings
  57         crawler.signals.connect(self.close, signals.spider_closed)
  58 
  59     def start_requests(self):
  60         cls = self.__class__
  61         if method_is_overridden(cls, Spider, 'make_requests_from_url'):
  62             warnings.warn(
  63                 "Spider.make_requests_from_url method is deprecated; it "
  64                 "won't be called in future Scrapy releases. Please "
  65                 "override Spider.start_requests method instead (see %s.%s)." % (
  66                     cls.__module__, cls.__name__
  67                 ),
  68             )
  69             for url in self.start_urls:
  70                 yield self.make_requests_from_url(url)
  71         else:
  72             for url in self.start_urls:
  73                 yield Request(url, dont_filter=True)
  74 
  75     def make_requests_from_url(self, url):
  76         """ This method is deprecated. """
  77         return Request(url, dont_filter=True)
  78 
  79     def parse(self, response):
  80         raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))
  81 
  82     @classmethod
  83     def update_settings(cls, settings):
  84         settings.setdict(cls.custom_settings or {}, priority='spider')
  85 
  86     @classmethod
  87     def handles_request(cls, request):
  88         return url_is_from_spider(request.url, cls)
  89 
  90     @staticmethod
  91     def close(spider, reason):
  92         closed = getattr(spider, 'closed', None)
  93         if callable(closed):
  94             return closed(reason)
  95 
  96     def __str__(self):
  97         return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))
  98 
  99     __repr__ = __str__
  100 
  101 
  102 # Top-level imports
  103 from scrapy.spiders.crawl import CrawlSpider, Rule
  104 from scrapy.spiders.feed import XMLFeedSpider, CSVFeedSpider
  105 from scrapy.spiders.sitemap import SitemapSpider
  106
  [end of scrapy/spiders/__init__.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/scrapy/spiders/__init__.py b/scrapy/spiders/__init__.py
  --- a/scrapy/spiders/__init__.py
  +++ b/scrapy/spiders/__init__.py
  @@ -58,6 +58,11 @@
   
       def start_requests(self):
           cls = self.__class__
  +        if not self.start_urls and hasattr(self, 'start_url'):
  +            raise AttributeError(
  +                "Crawling could not start: 'start_urls' not found "
  +                "or empty (but found 'start_url' attribute instead, "
  +                "did you miss an 's'?)")
           if method_is_overridden(cls, Spider, 'make_requests_from_url'):
               warnings.warn(
                   "Spider.make_requests_from_url method is deprecated; it "
  
verification_info: {"golden_diff": "diff --git a/scrapy/spiders/__init__.py b/scrapy/spiders/__init__.py\n--- a/scrapy/spiders/__init__.py\n+++ b/scrapy/spiders/__init__.py\n@@ -58,6 +58,11 @@\n \n     def start_requests(self):\n         cls = self.__class__\n+        if not self.start_urls and hasattr(self, 'start_url'):\n+            raise AttributeError(\n+                \"Crawling could not start: 'start_urls' not found \"\n+                \"or empty (but found 'start_url' attribute instead, \"\n+                \"did you miss an 's'?)\")\n         if method_is_overridden(cls, Spider, 'make_requests_from_url'):\n             warnings.warn(\n                 \"Spider.make_requests_from_url method is deprecated; it \"\n", "issue": "Handle it gracefully when start_url is used instead of start_urls\nOver the last year I\u2019ve seen a few cases ([recent example](https://stackoverflow.com/q/58664004/939364)) of this, people missing the `s` at the end of the `start_urls`.\r\n\r\nIt may be nice to find a way to gracefully let the developer know where the issue is, why there is no crawling happening.\n", "before_files": [{"content": "\"\"\"\nBase class for Scrapy spiders\n\nSee documentation in docs/topics/spiders.rst\n\"\"\"\nimport logging\nimport warnings\n\nfrom scrapy import signals\nfrom scrapy.http import Request\nfrom scrapy.utils.trackref import object_ref\nfrom scrapy.utils.url import url_is_from_spider\nfrom scrapy.exceptions import ScrapyDeprecationWarning\nfrom scrapy.utils.deprecate import method_is_overridden\n\n\nclass Spider(object_ref):\n    \"\"\"Base class for scrapy spiders. All spiders must inherit from this\n    class.\n    \"\"\"\n\n    name = None\n    custom_settings = None\n\n    def __init__(self, name=None, **kwargs):\n        if name is not None:\n            self.name = name\n        elif not getattr(self, 'name', None):\n            raise ValueError(\"%s must have a name\" % type(self).__name__)\n        self.__dict__.update(kwargs)\n        if not hasattr(self, 'start_urls'):\n            self.start_urls = []\n\n    @property\n    def logger(self):\n        logger = logging.getLogger(self.name)\n        return logging.LoggerAdapter(logger, {'spider': self})\n\n    def log(self, message, level=logging.DEBUG, **kw):\n        \"\"\"Log the given message at the given log level\n\n        This helper wraps a log call to the logger within the spider, but you\n        can use it directly (e.g. Spider.logger.info('msg')) or use any other\n        Python logger too.\n        \"\"\"\n        self.logger.log(level, message, **kw)\n\n    @classmethod\n    def from_crawler(cls, crawler, *args, **kwargs):\n        spider = cls(*args, **kwargs)\n        spider._set_crawler(crawler)\n        return spider\n\n    def _set_crawler(self, crawler):\n        self.crawler = crawler\n        self.settings = crawler.settings\n        crawler.signals.connect(self.close, signals.spider_closed)\n\n    def start_requests(self):\n        cls = self.__class__\n        if method_is_overridden(cls, Spider, 'make_requests_from_url'):\n            warnings.warn(\n                \"Spider.make_requests_from_url method is deprecated; it \"\n                \"won't be called in future Scrapy releases. Please \"\n                \"override Spider.start_requests method instead (see %s.%s).\" % (\n                    cls.__module__, cls.__name__\n                ),\n            )\n            for url in self.start_urls:\n                yield self.make_requests_from_url(url)\n        else:\n            for url in self.start_urls:\n                yield Request(url, dont_filter=True)\n\n    def make_requests_from_url(self, url):\n        \"\"\" This method is deprecated. \"\"\"\n        return Request(url, dont_filter=True)\n\n    def parse(self, response):\n        raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))\n\n    @classmethod\n    def update_settings(cls, settings):\n        settings.setdict(cls.custom_settings or {}, priority='spider')\n\n    @classmethod\n    def handles_request(cls, request):\n        return url_is_from_spider(request.url, cls)\n\n    @staticmethod\n    def close(spider, reason):\n        closed = getattr(spider, 'closed', None)\n        if callable(closed):\n            return closed(reason)\n\n    def __str__(self):\n        return \"<%s %r at 0x%0x>\" % (type(self).__name__, self.name, id(self))\n\n    __repr__ = __str__\n\n\n# Top-level imports\nfrom scrapy.spiders.crawl import CrawlSpider, Rule\nfrom scrapy.spiders.feed import XMLFeedSpider, CSVFeedSpider\nfrom scrapy.spiders.sitemap import SitemapSpider\n", "path": "scrapy/spiders/__init__.py"}]}