prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  signals docs are confusing
  It seems it is not explained how to connect a callback to a singnal anywhere in Scrapy docs.
  
  http://doc.scrapy.org/en/latest/topics/signals.html tells:
  
  > You can connect to signals (or send your own) through the [Signals API](http://doc.scrapy.org/en/latest/topics/api.html#topics-api-signals).
  
  But if you follow this link you get docs for scrapy.signalmanager.SignalManager - that's fine, but it is not explained where to get a SignalManager instance from.
  
  There is an example in Extension docs (http://doc.scrapy.org/en/latest/topics/extensions.html#sample-extension), but
  
  a) this is just an example;
  b) it is not explained that crawler.signals is a SignalManager instance;
  c) this example is neither in Signals docs nor in SignalManager docs.
  
  There is also a bit of information here: http://doc.scrapy.org/en/latest/topics/api.html#scrapy.crawler.Crawler.signals, but
  
  a) it is not linked to neither from Signal docs nor from SignalManager, so you can't find it if you don't know about it already;
  b) it is not explained that crawler.signals is the only way to access signals.
  
  So in the end users may get some luck connecting signals if they start from Crawler docs, but almost no luck if they start from Signals docs.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `scrapy/utils/misc.py`
  Content:
  ```
  1 """Helper functions which doesn't fit anywhere else"""
  2 import re
  3 import hashlib
  4 from importlib import import_module
  5 from pkgutil import iter_modules
  6 
  7 import six
  8 from w3lib.html import replace_entities
  9 
  10 from scrapy.utils.python import flatten, to_unicode
  11 from scrapy.item import BaseItem
  12 
  13 
  14 _ITERABLE_SINGLE_VALUES = dict, BaseItem, six.text_type, bytes
  15 
  16 
  17 def arg_to_iter(arg):
  18     """Convert an argument to an iterable. The argument can be a None, single
  19     value, or an iterable.
  20 
  21     Exception: if arg is a dict, [arg] will be returned
  22     """
  23     if arg is None:
  24         return []
  25     elif not isinstance(arg, _ITERABLE_SINGLE_VALUES) and hasattr(arg, '__iter__'):
  26         return arg
  27     else:
  28         return [arg]
  29 
  30 
  31 def load_object(path):
  32     """Load an object given its absolute object path, and return it.
  33 
  34     object can be a class, function, variable or an instance.
  35     path ie: 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware'
  36     """
  37 
  38     try:
  39         dot = path.rindex('.')
  40     except ValueError:
  41         raise ValueError("Error loading object '%s': not a full path" % path)
  42 
  43     module, name = path[:dot], path[dot+1:]
  44     mod = import_module(module)
  45 
  46     try:
  47         obj = getattr(mod, name)
  48     except AttributeError:
  49         raise NameError("Module '%s' doesn't define any object named '%s'" % (module, name))
  50 
  51     return obj
  52 
  53 
  54 def walk_modules(path):
  55     """Loads a module and all its submodules from the given module path and
  56     returns them. If *any* module throws an exception while importing, that
  57     exception is thrown back.
  58 
  59     For example: walk_modules('scrapy.utils')
  60     """
  61 
  62     mods = []
  63     mod = import_module(path)
  64     mods.append(mod)
  65     if hasattr(mod, '__path__'):
  66         for _, subpath, ispkg in iter_modules(mod.__path__):
  67             fullpath = path + '.' + subpath
  68             if ispkg:
  69                 mods += walk_modules(fullpath)
  70             else:
  71                 submod = import_module(fullpath)
  72                 mods.append(submod)
  73     return mods
  74 
  75 
  76 def extract_regex(regex, text, encoding='utf-8'):
  77     """Extract a list of unicode strings from the given text/encoding using the following policies:
  78 
  79     * if the regex contains a named group called "extract" that will be returned
  80     * if the regex contains multiple numbered groups, all those will be returned (flattened)
  81     * if the regex doesn't contain any group the entire regex matching is returned
  82     """
  83 
  84     if isinstance(regex, six.string_types):
  85         regex = re.compile(regex, re.UNICODE)
  86 
  87     try:
  88         strings = [regex.search(text).group('extract')]   # named group
  89     except:
  90         strings = regex.findall(text)    # full regex or numbered groups
  91     strings = flatten(strings)
  92 
  93     if isinstance(text, six.text_type):
  94         return [replace_entities(s, keep=['lt', 'amp']) for s in strings]
  95     else:
  96         return [replace_entities(to_unicode(s, encoding), keep=['lt', 'amp'])
  97                 for s in strings]
  98 
  99 
  100 def md5sum(file):
  101     """Calculate the md5 checksum of a file-like object without reading its
  102     whole content in memory.
  103 
  104     >>> from io import BytesIO
  105     >>> md5sum(BytesIO(b'file content to hash'))
  106     '784406af91dd5a54fbb9c84c2236595a'
  107     """
  108     m = hashlib.md5()
  109     while True:
  110         d = file.read(8096)
  111         if not d:
  112             break
  113         m.update(d)
  114     return m.hexdigest()
  115 
  116 def rel_has_nofollow(rel):
  117     """Return True if link rel attribute has nofollow type"""
  118     return True if rel is not None and 'nofollow' in rel.split() else False
  119     
  120
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/scrapy/utils/misc.py b/scrapy/utils/misc.py
  --- a/scrapy/utils/misc.py
  +++ b/scrapy/utils/misc.py
  @@ -1,4 +1,4 @@
  -"""Helper functions which doesn't fit anywhere else"""
  +"""Helper functions which don't fit anywhere else"""
   import re
   import hashlib
   from importlib import import_module
  
verification_info: {"golden_diff": "diff --git a/scrapy/utils/misc.py b/scrapy/utils/misc.py\n--- a/scrapy/utils/misc.py\n+++ b/scrapy/utils/misc.py\n@@ -1,4 +1,4 @@\n-\"\"\"Helper functions which doesn't fit anywhere else\"\"\"\n+\"\"\"Helper functions which don't fit anywhere else\"\"\"\n import re\n import hashlib\n from importlib import import_module\n", "issue": "signals docs are confusing\nIt seems it is not explained how to connect a callback to a singnal anywhere in Scrapy docs.\n\nhttp://doc.scrapy.org/en/latest/topics/signals.html tells:\n\n> You can connect to signals (or send your own) through the [Signals API](http://doc.scrapy.org/en/latest/topics/api.html#topics-api-signals).\n\nBut if you follow this link you get docs for scrapy.signalmanager.SignalManager - that's fine, but it is not explained where to get a SignalManager instance from.\n\nThere is an example in Extension docs (http://doc.scrapy.org/en/latest/topics/extensions.html#sample-extension), but\n\na) this is just an example;\nb) it is not explained that crawler.signals is a SignalManager instance;\nc) this example is neither in Signals docs nor in SignalManager docs.\n\nThere is also a bit of information here: http://doc.scrapy.org/en/latest/topics/api.html#scrapy.crawler.Crawler.signals, but\n\na) it is not linked to neither from Signal docs nor from SignalManager, so you can't find it if you don't know about it already;\nb) it is not explained that crawler.signals is the only way to access signals.\n\nSo in the end users may get some luck connecting signals if they start from Crawler docs, but almost no luck if they start from Signals docs.\n\n", "before_files": [{"content": "\"\"\"Helper functions which doesn't fit anywhere else\"\"\"\nimport re\nimport hashlib\nfrom importlib import import_module\nfrom pkgutil import iter_modules\n\nimport six\nfrom w3lib.html import replace_entities\n\nfrom scrapy.utils.python import flatten, to_unicode\nfrom scrapy.item import BaseItem\n\n\n_ITERABLE_SINGLE_VALUES = dict, BaseItem, six.text_type, bytes\n\n\ndef arg_to_iter(arg):\n    \"\"\"Convert an argument to an iterable. The argument can be a None, single\n    value, or an iterable.\n\n    Exception: if arg is a dict, [arg] will be returned\n    \"\"\"\n    if arg is None:\n        return []\n    elif not isinstance(arg, _ITERABLE_SINGLE_VALUES) and hasattr(arg, '__iter__'):\n        return arg\n    else:\n        return [arg]\n\n\ndef load_object(path):\n    \"\"\"Load an object given its absolute object path, and return it.\n\n    object can be a class, function, variable or an instance.\n    path ie: 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware'\n    \"\"\"\n\n    try:\n        dot = path.rindex('.')\n    except ValueError:\n        raise ValueError(\"Error loading object '%s': not a full path\" % path)\n\n    module, name = path[:dot], path[dot+1:]\n    mod = import_module(module)\n\n    try:\n        obj = getattr(mod, name)\n    except AttributeError:\n        raise NameError(\"Module '%s' doesn't define any object named '%s'\" % (module, name))\n\n    return obj\n\n\ndef walk_modules(path):\n    \"\"\"Loads a module and all its submodules from the given module path and\n    returns them. If *any* module throws an exception while importing, that\n    exception is thrown back.\n\n    For example: walk_modules('scrapy.utils')\n    \"\"\"\n\n    mods = []\n    mod = import_module(path)\n    mods.append(mod)\n    if hasattr(mod, '__path__'):\n        for _, subpath, ispkg in iter_modules(mod.__path__):\n            fullpath = path + '.' + subpath\n            if ispkg:\n                mods += walk_modules(fullpath)\n            else:\n                submod = import_module(fullpath)\n                mods.append(submod)\n    return mods\n\n\ndef extract_regex(regex, text, encoding='utf-8'):\n    \"\"\"Extract a list of unicode strings from the given text/encoding using the following policies:\n\n    * if the regex contains a named group called \"extract\" that will be returned\n    * if the regex contains multiple numbered groups, all those will be returned (flattened)\n    * if the regex doesn't contain any group the entire regex matching is returned\n    \"\"\"\n\n    if isinstance(regex, six.string_types):\n        regex = re.compile(regex, re.UNICODE)\n\n    try:\n        strings = [regex.search(text).group('extract')]   # named group\n    except:\n        strings = regex.findall(text)    # full regex or numbered groups\n    strings = flatten(strings)\n\n    if isinstance(text, six.text_type):\n        return [replace_entities(s, keep=['lt', 'amp']) for s in strings]\n    else:\n        return [replace_entities(to_unicode(s, encoding), keep=['lt', 'amp'])\n                for s in strings]\n\n\ndef md5sum(file):\n    \"\"\"Calculate the md5 checksum of a file-like object without reading its\n    whole content in memory.\n\n    >>> from io import BytesIO\n    >>> md5sum(BytesIO(b'file content to hash'))\n    '784406af91dd5a54fbb9c84c2236595a'\n    \"\"\"\n    m = hashlib.md5()\n    while True:\n        d = file.read(8096)\n        if not d:\n            break\n        m.update(d)\n    return m.hexdigest()\n\ndef rel_has_nofollow(rel):\n    \"\"\"Return True if link rel attribute has nofollow type\"\"\"\n    return True if rel is not None and 'nofollow' in rel.split() else False\n    \n", "path": "scrapy/utils/misc.py"}], "after_files": [{"content": "\"\"\"Helper functions which don't fit anywhere else\"\"\"\nimport re\nimport hashlib\nfrom importlib import import_module\nfrom pkgutil import iter_modules\n\nimport six\nfrom w3lib.html import replace_entities\n\nfrom scrapy.utils.python import flatten, to_unicode\nfrom scrapy.item import BaseItem\n\n\n_ITERABLE_SINGLE_VALUES = dict, BaseItem, six.text_type, bytes\n\n\ndef arg_to_iter(arg):\n    \"\"\"Convert an argument to an iterable. The argument can be a None, single\n    value, or an iterable.\n\n    Exception: if arg is a dict, [arg] will be returned\n    \"\"\"\n    if arg is None:\n        return []\n    elif not isinstance(arg, _ITERABLE_SINGLE_VALUES) and hasattr(arg, '__iter__'):\n        return arg\n    else:\n        return [arg]\n\n\ndef load_object(path):\n    \"\"\"Load an object given its absolute object path, and return it.\n\n    object can be a class, function, variable o instance.\n    path ie: 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware'\n    \"\"\"\n\n    try:\n        dot = path.rindex('.')\n    except ValueError:\n        raise ValueError(\"Error loading object '%s': not a full path\" % path)\n\n    module, name = path[:dot], path[dot+1:]\n    mod = import_module(module)\n\n    try:\n        obj = getattr(mod, name)\n    except AttributeError:\n        raise NameError(\"Module '%s' doesn't define any object named '%s'\" % (module, name))\n\n    return obj\n\n\ndef walk_modules(path):\n    \"\"\"Loads a module and all its submodules from a the given module path and\n    returns them. If *any* module throws an exception while importing, that\n    exception is thrown back.\n\n    For example: walk_modules('scrapy.utils')\n    \"\"\"\n\n    mods = []\n    mod = import_module(path)\n    mods.append(mod)\n    if hasattr(mod, '__path__'):\n        for _, subpath, ispkg in iter_modules(mod.__path__):\n            fullpath = path + '.' + subpath\n            if ispkg:\n                mods += walk_modules(fullpath)\n            else:\n                submod = import_module(fullpath)\n                mods.append(submod)\n    return mods\n\n\ndef extract_regex(regex, text, encoding='utf-8'):\n    \"\"\"Extract a list of unicode strings from the given text/encoding using the following policies:\n\n    * if the regex contains a named group called \"extract\" that will be returned\n    * if the regex contains multiple numbered groups, all those will be returned (flattened)\n    * if the regex doesn't contain any group the entire regex matching is returned\n    \"\"\"\n\n    if isinstance(regex, six.string_types):\n        regex = re.compile(regex, re.UNICODE)\n\n    try:\n        strings = [regex.search(text).group('extract')]   # named group\n    except:\n        strings = regex.findall(text)    # full regex or numbered groups\n    strings = flatten(strings)\n\n    if isinstance(text, six.text_type):\n        return [replace_entities(s, keep=['lt', 'amp']) for s in strings]\n    else:\n        return [replace_entities(to_unicode(s, encoding), keep=['lt', 'amp'])\n                for s in strings]\n\n\ndef md5sum(file):\n    \"\"\"Calculate the md5 checksum of a file-like object without reading its\n    whole content in memory.\n\n    >>> from io import BytesIO\n    >>> md5sum(BytesIO(b'file content to hash'))\n    '784406af91dd5a54fbb9c84c2236595a'\n    \"\"\"\n    m = hashlib.md5()\n    while True:\n        d = file.read(8096)\n        if not d:\n            break\n        m.update(d)\n    return m.hexdigest()\n\ndef rel_has_nofollow(rel):\n    \"\"\"Return True if link rel attribute has nofollow type\"\"\"\n    return True if rel is not None and 'nofollow' in rel.split() else False\n    \n", "path": "scrapy/utils/misc.py"}]}