We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
get_func_args() from scrapy.utils.python crashes on partial functions
I use Python 2.7 and used a partial function (`functools.partial`) as part of an input_processor for the ItemLoader. For instance,

``` python
price = Field(input_processor=Compose(some_partial_func, some_other_func))
```

During execution, `get_func_args() from scrapy.util.python` goes into an infinite loop until

``` python
/usr/local/lib/python2.7/dist-packages/scrapy/utils/python.pyc in get_func_args(func, stripself)
    161             return []
    162         else:
--> 163             return get_func_args(func.__call__, True)
    164     else:
    165         raise TypeError('%s is not callable' % type(func))

/usr/local/lib/python2.7/dist-packages/scrapy/utils/python.pyc in get_func_args(func, stripself)
    161             return []
    162         else:
--> 163             return get_func_args(func.__call__, True)
    164     else:
    165         raise TypeError('%s is not callable' % type(func))

/usr/local/lib/python2.7/dist-packages/scrapy/utils/python.pyc in get_func_args(func, stripself)
    151     if inspect.isfunction(func):
    152         func_args, _, _, _ = inspect.getargspec(func)
--> 153     elif inspect.isclass(func):
    154         return get_func_args(func.__init__, True)
    155     elif inspect.ismethod(func):

/usr/lib/python2.7/inspect.pyc in isclass(object)
     63         __doc__         documentation string
     64         __module__      name of module in which this class was defined"""
---> 65     return isinstance(object, (type, types.ClassType))
     66 
     67 def ismethod(object):

RuntimeError: maximum recursion depth exceeded while calling a Python object
```

happens. Looks like `get_func_args` needs to handle partial functions as a separate case.


--- 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/python.py`
Content:
```
1 """
2 This module contains essential stuff that should've come with Python itself ;)
3 
4 It also contains functions (or functionality) which is in Python versions
5 higher than 2.5 which used to be the lowest version supported by Scrapy.
6 
7 """
8 import os
9 import re
10 import inspect
11 import weakref
12 import errno
13 from functools import wraps
14 from sgmllib import SGMLParser
15 
16 
17 class FixedSGMLParser(SGMLParser):
18     """The SGMLParser that comes with Python has a bug in the convert_charref()
19     method. This is the same class with the bug fixed"""
20 
21     def convert_charref(self, name):
22         """This method fixes a bug in Python's SGMLParser."""
23         try:
24             n = int(name)
25         except ValueError:
26             return
27         if not 0 <= n <= 127 : # ASCII ends at 127, not 255
28             return
29         return self.convert_codepoint(n)
30 
31 
32 def flatten(x):
33     """flatten(sequence) -> list
34 
35     Returns a single, flat list which contains all elements retrieved
36     from the sequence and all recursively contained sub-sequences
37     (iterables).
38 
39     Examples:
40     >>> [1, 2, [3,4], (5,6)]
41     [1, 2, [3, 4], (5, 6)]
42     >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, (8,9,10)])
43     [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10]"""
44 
45     result = []
46     for el in x:
47         if hasattr(el, "__iter__"):
48             result.extend(flatten(el))
49         else:
50             result.append(el)
51     return result
52 
53 
54 def unique(list_, key=lambda x: x):
55     """efficient function to uniquify a list preserving item order"""
56     seen = set()
57     result = []
58     for item in list_:
59         seenkey = key(item)
60         if seenkey in seen:
61             continue
62         seen.add(seenkey)
63         result.append(item)
64     return result
65 
66 
67 def str_to_unicode(text, encoding=None, errors='strict'):
68     """Return the unicode representation of text in the given encoding. Unlike
69     .encode(encoding) this function can be applied directly to a unicode
70     object without the risk of double-decoding problems (which can happen if
71     you don't use the default 'ascii' encoding)
72     """
73 
74     if encoding is None:
75         encoding = 'utf-8'
76     if isinstance(text, str):
77         return text.decode(encoding, errors)
78     elif isinstance(text, unicode):
79         return text
80     else:
81         raise TypeError('str_to_unicode must receive a str or unicode object, got %s' % type(text).__name__)
82 
83 def unicode_to_str(text, encoding=None, errors='strict'):
84     """Return the str representation of text in the given encoding. Unlike
85     .encode(encoding) this function can be applied directly to a str
86     object without the risk of double-decoding problems (which can happen if
87     you don't use the default 'ascii' encoding)
88     """
89 
90     if encoding is None:
91         encoding = 'utf-8'
92     if isinstance(text, unicode):
93         return text.encode(encoding, errors)
94     elif isinstance(text, str):
95         return text
96     else:
97         raise TypeError('unicode_to_str must receive a unicode or str object, got %s' % type(text).__name__)
98 
99 def re_rsearch(pattern, text, chunk_size=1024):
100     """
101     This function does a reverse search in a text using a regular expression
102     given in the attribute 'pattern'.
103     Since the re module does not provide this functionality, we have to find for
104     the expression into chunks of text extracted from the end (for the sake of efficiency).
105     At first, a chunk of 'chunk_size' kilobytes is extracted from the end, and searched for
106     the pattern. If the pattern is not found, another chunk is extracted, and another
107     search is performed.
108     This process continues until a match is found, or until the whole file is read.
109     In case the pattern wasn't found, None is returned, otherwise it returns a tuple containing
110     the start position of the match, and the ending (regarding the entire text).
111     """
112     def _chunk_iter():
113         offset = len(text)
114         while True:
115             offset -= (chunk_size * 1024)
116             if offset <= 0:
117                 break
118             yield (text[offset:], offset)
119         yield (text, 0)
120 
121     pattern = re.compile(pattern) if isinstance(pattern, basestring) else pattern
122     for chunk, offset in _chunk_iter():
123         matches = [match for match in pattern.finditer(chunk)]
124         if matches:
125             return (offset + matches[-1].span()[0], offset + matches[-1].span()[1])
126     return None
127 
128 def memoizemethod_noargs(method):
129     """Decorator to cache the result of a method (without arguments) using a
130     weak reference to its object
131     """
132     cache = weakref.WeakKeyDictionary()
133     @wraps(method)
134     def new_method(self, *args, **kwargs):
135         if self not in cache:
136             cache[self] = method(self, *args, **kwargs)
137         return cache[self]
138     return new_method
139 
140 _BINARYCHARS = set(map(chr, range(32))) - set(["\0", "\t", "\n", "\r"])
141 
142 def isbinarytext(text):
143     """Return True if the given text is considered binary, or false
144     otherwise, by looking for binary bytes at their chars
145     """
146     assert isinstance(text, str), "text must be str, got '%s'" % type(text).__name__
147     return any(c in _BINARYCHARS for c in text)
148 
149 def get_func_args(func, stripself=False):
150     """Return the argument name list of a callable"""
151     if inspect.isfunction(func):
152         func_args, _, _, _ = inspect.getargspec(func)
153     elif inspect.isclass(func):
154         return get_func_args(func.__init__, True)
155     elif inspect.ismethod(func):
156         return get_func_args(func.__func__, True)
157     elif inspect.ismethoddescriptor(func):
158         return []
159     elif hasattr(func, '__call__'):
160         if inspect.isroutine(func):
161             return []
162         else:
163             return get_func_args(func.__call__, True)
164     else:
165         raise TypeError('%s is not callable' % type(func))
166     if stripself:
167         func_args.pop(0)
168     return func_args
169 
170 def get_spec(func):
171     """Returns (args, kwargs) tuple for a function
172     >>> import re
173     >>> get_spec(re.match)
174     (['pattern', 'string'], {'flags': 0})
175 
176     >>> class Test(object):
177     ...     def __call__(self, val):
178     ...         pass
179     ...     def method(self, val, flags=0):
180     ...         pass
181 
182     >>> get_spec(Test)
183     (['self', 'val'], {})
184 
185     >>> get_spec(Test.method)
186     (['self', 'val'], {'flags': 0})
187 
188     >>> get_spec(Test().method)
189     (['self', 'val'], {'flags': 0})
190     """
191 
192     if inspect.isfunction(func) or inspect.ismethod(func):
193         spec = inspect.getargspec(func)
194     elif hasattr(func, '__call__'):
195         spec = inspect.getargspec(func.__call__)
196     else:
197         raise TypeError('%s is not callable' % type(func))
198 
199     defaults = spec.defaults or []
200 
201     firstdefault = len(spec.args) - len(defaults)
202     args = spec.args[:firstdefault]
203     kwargs = dict(zip(spec.args[firstdefault:], defaults))
204     return args, kwargs
205 
206 def equal_attributes(obj1, obj2, attributes):
207     """Compare two objects attributes"""
208     # not attributes given return False by default
209     if not attributes:
210         return False
211 
212     for attr in attributes:
213         # support callables like itemgetter
214         if callable(attr):
215             if not attr(obj1) == attr(obj2):
216                 return False
217         else:
218             # check that objects has attribute
219             if not hasattr(obj1, attr):
220                 return False
221             if not hasattr(obj2, attr):
222                 return False
223             # compare object attributes
224             if not getattr(obj1, attr) == getattr(obj2, attr):
225                 return False
226     # all attributes equal
227     return True
228 
229 
230 class WeakKeyCache(object):
231 
232     def __init__(self, default_factory):
233         self.default_factory = default_factory
234         self._weakdict = weakref.WeakKeyDictionary()
235 
236     def __getitem__(self, key):
237         if key not in self._weakdict:
238             self._weakdict[key] = self.default_factory(key)
239         return self._weakdict[key]
240 
241 
242 def stringify_dict(dct_or_tuples, encoding='utf-8', keys_only=True):
243     """Return a (new) dict with the unicode keys (and values if, keys_only is
244     False) of the given dict converted to strings. `dct_or_tuples` can be a
245     dict or a list of tuples, like any dict constructor supports.
246     """
247     d = {}
248     for k, v in dict(dct_or_tuples).iteritems():
249         k = k.encode(encoding) if isinstance(k, unicode) else k
250         if not keys_only:
251             v = v.encode(encoding) if isinstance(v, unicode) else v
252         d[k] = v
253     return d
254 
255 def is_writable(path):
256     """Return True if the given path can be written (if it exists) or created
257     (if it doesn't exist)
258     """
259     if os.path.exists(path):
260         return os.access(path, os.W_OK)
261     else:
262         return os.access(os.path.dirname(path), os.W_OK)
263 
264 def setattr_default(obj, name, value):
265     """Set attribute value, but only if it's not already set. Similar to
266     setdefault() for dicts.
267     """
268     if not hasattr(obj, name):
269         setattr(obj, name, value)
270 
271 
272 def retry_on_eintr(function, *args, **kw):
273     """Run a function and retry it while getting EINTR errors"""
274     while True:
275         try:
276             return function(*args, **kw)
277         except IOError as e:
278             if e.errno != errno.EINTR:
279                 raise
280
```
--- 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()

```

diff --git a/scrapy/utils/python.py b/scrapy/utils/python.py
--- a/scrapy/utils/python.py
+++ b/scrapy/utils/python.py
@@ -10,7 +10,7 @@
 import inspect
 import weakref
 import errno
-from functools import wraps
+from functools import partial, wraps
 from sgmllib import SGMLParser
 
 
@@ -156,6 +156,8 @@
         return get_func_args(func.__func__, True)
     elif inspect.ismethoddescriptor(func):
         return []
+    elif isinstance(func, partial):
+        return get_func_args(func.func)
     elif hasattr(func, '__call__'):
         if inspect.isroutine(func):
             return []

{"golden_diff": "diff --git a/scrapy/utils/python.py b/scrapy/utils/python.py\n--- a/scrapy/utils/python.py\n+++ b/scrapy/utils/python.py\n@@ -10,7 +10,7 @@\n import inspect\n import weakref\n import errno\n-from functools import wraps\n+from functools import partial, wraps\n from sgmllib import SGMLParser\n \n \n@@ -156,6 +156,8 @@\n         return get_func_args(func.__func__, True)\n     elif inspect.ismethoddescriptor(func):\n         return []\n+    elif isinstance(func, partial):\n+        return get_func_args(func.func)\n     elif hasattr(func, '__call__'):\n         if inspect.isroutine(func):\n             return []\n", "issue": "get_func_args() from scrapy.utils.python crashes on partial functions\nI use Python 2.7 and used a partial function (`functools.partial`) as part of an input_processor for the ItemLoader. For instance,\n\n``` python\nprice = Field(input_processor=Compose(some_partial_func, some_other_func))\n```\n\nDuring execution, `get_func_args() from scrapy.util.python` goes into an infinite loop until\n\n``` python\n/usr/local/lib/python2.7/dist-packages/scrapy/utils/python.pyc in get_func_args(func, stripself)\n    161             return []\n    162         else:\n--> 163             return get_func_args(func.__call__, True)\n    164     else:\n    165         raise TypeError('%s is not callable' % type(func))\n\n/usr/local/lib/python2.7/dist-packages/scrapy/utils/python.pyc in get_func_args(func, stripself)\n    161             return []\n    162         else:\n--> 163             return get_func_args(func.__call__, True)\n    164     else:\n    165         raise TypeError('%s is not callable' % type(func))\n\n/usr/local/lib/python2.7/dist-packages/scrapy/utils/python.pyc in get_func_args(func, stripself)\n    151     if inspect.isfunction(func):\n    152         func_args, _, _, _ = inspect.getargspec(func)\n--> 153     elif inspect.isclass(func):\n    154         return get_func_args(func.__init__, True)\n    155     elif inspect.ismethod(func):\n\n/usr/lib/python2.7/inspect.pyc in isclass(object)\n     63         __doc__         documentation string\n     64         __module__      name of module in which this class was defined\"\"\"\n---> 65     return isinstance(object, (type, types.ClassType))\n     66 \n     67 def ismethod(object):\n\nRuntimeError: maximum recursion depth exceeded while calling a Python object\n```\n\nhappens. Looks like `get_func_args` needs to handle partial functions as a separate case.\n\n", "before_files": [{"content": "\"\"\"\nThis module contains essential stuff that should've come with Python itself ;)\n\nIt also contains functions (or functionality) which is in Python versions\nhigher than 2.5 which used to be the lowest version supported by Scrapy.\n\n\"\"\"\nimport os\nimport re\nimport inspect\nimport weakref\nimport errno\nfrom functools import wraps\nfrom sgmllib import SGMLParser\n\n\nclass FixedSGMLParser(SGMLParser):\n    \"\"\"The SGMLParser that comes with Python has a bug in the convert_charref()\n    method. This is the same class with the bug fixed\"\"\"\n\n    def convert_charref(self, name):\n        \"\"\"This method fixes a bug in Python's SGMLParser.\"\"\"\n        try:\n            n = int(name)\n        except ValueError:\n            return\n        if not 0 <= n <= 127 : # ASCII ends at 127, not 255\n            return\n        return self.convert_codepoint(n)\n\n\ndef flatten(x):\n    \"\"\"flatten(sequence) -> list\n\n    Returns a single, flat list which contains all elements retrieved\n    from the sequence and all recursively contained sub-sequences\n    (iterables).\n\n    Examples:\n    >>> [1, 2, [3,4], (5,6)]\n    [1, 2, [3, 4], (5, 6)]\n    >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, (8,9,10)])\n    [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10]\"\"\"\n\n    result = []\n    for el in x:\n        if hasattr(el, \"__iter__\"):\n            result.extend(flatten(el))\n        else:\n            result.append(el)\n    return result\n\n\ndef unique(list_, key=lambda x: x):\n    \"\"\"efficient function to uniquify a list preserving item order\"\"\"\n    seen = set()\n    result = []\n    for item in list_:\n        seenkey = key(item)\n        if seenkey in seen:\n            continue\n        seen.add(seenkey)\n        result.append(item)\n    return result\n\n\ndef str_to_unicode(text, encoding=None, errors='strict'):\n    \"\"\"Return the unicode representation of text in the given encoding. Unlike\n    .encode(encoding) this function can be applied directly to a unicode\n    object without the risk of double-decoding problems (which can happen if\n    you don't use the default 'ascii' encoding)\n    \"\"\"\n\n    if encoding is None:\n        encoding = 'utf-8'\n    if isinstance(text, str):\n        return text.decode(encoding, errors)\n    elif isinstance(text, unicode):\n        return text\n    else:\n        raise TypeError('str_to_unicode must receive a str or unicode object, got %s' % type(text).__name__)\n\ndef unicode_to_str(text, encoding=None, errors='strict'):\n    \"\"\"Return the str representation of text in the given encoding. Unlike\n    .encode(encoding) this function can be applied directly to a str\n    object without the risk of double-decoding problems (which can happen if\n    you don't use the default 'ascii' encoding)\n    \"\"\"\n\n    if encoding is None:\n        encoding = 'utf-8'\n    if isinstance(text, unicode):\n        return text.encode(encoding, errors)\n    elif isinstance(text, str):\n        return text\n    else:\n        raise TypeError('unicode_to_str must receive a unicode or str object, got %s' % type(text).__name__)\n\ndef re_rsearch(pattern, text, chunk_size=1024):\n    \"\"\"\n    This function does a reverse search in a text using a regular expression\n    given in the attribute 'pattern'.\n    Since the re module does not provide this functionality, we have to find for\n    the expression into chunks of text extracted from the end (for the sake of efficiency).\n    At first, a chunk of 'chunk_size' kilobytes is extracted from the end, and searched for\n    the pattern. If the pattern is not found, another chunk is extracted, and another\n    search is performed.\n    This process continues until a match is found, or until the whole file is read.\n    In case the pattern wasn't found, None is returned, otherwise it returns a tuple containing\n    the start position of the match, and the ending (regarding the entire text).\n    \"\"\"\n    def _chunk_iter():\n        offset = len(text)\n        while True:\n            offset -= (chunk_size * 1024)\n            if offset <= 0:\n                break\n            yield (text[offset:], offset)\n        yield (text, 0)\n\n    pattern = re.compile(pattern) if isinstance(pattern, basestring) else pattern\n    for chunk, offset in _chunk_iter():\n        matches = [match for match in pattern.finditer(chunk)]\n        if matches:\n            return (offset + matches[-1].span()[0], offset + matches[-1].span()[1])\n    return None\n\ndef memoizemethod_noargs(method):\n    \"\"\"Decorator to cache the result of a method (without arguments) using a\n    weak reference to its object\n    \"\"\"\n    cache = weakref.WeakKeyDictionary()\n    @wraps(method)\n    def new_method(self, *args, **kwargs):\n        if self not in cache:\n            cache[self] = method(self, *args, **kwargs)\n        return cache[self]\n    return new_method\n\n_BINARYCHARS = set(map(chr, range(32))) - set([\"\\0\", \"\\t\", \"\\n\", \"\\r\"])\n\ndef isbinarytext(text):\n    \"\"\"Return True if the given text is considered binary, or false\n    otherwise, by looking for binary bytes at their chars\n    \"\"\"\n    assert isinstance(text, str), \"text must be str, got '%s'\" % type(text).__name__\n    return any(c in _BINARYCHARS for c in text)\n\ndef get_func_args(func, stripself=False):\n    \"\"\"Return the argument name list of a callable\"\"\"\n    if inspect.isfunction(func):\n        func_args, _, _, _ = inspect.getargspec(func)\n    elif inspect.isclass(func):\n        return get_func_args(func.__init__, True)\n    elif inspect.ismethod(func):\n        return get_func_args(func.__func__, True)\n    elif inspect.ismethoddescriptor(func):\n        return []\n    elif hasattr(func, '__call__'):\n        if inspect.isroutine(func):\n            return []\n        else:\n            return get_func_args(func.__call__, True)\n    else:\n        raise TypeError('%s is not callable' % type(func))\n    if stripself:\n        func_args.pop(0)\n    return func_args\n\ndef get_spec(func):\n    \"\"\"Returns (args, kwargs) tuple for a function\n    >>> import re\n    >>> get_spec(re.match)\n    (['pattern', 'string'], {'flags': 0})\n\n    >>> class Test(object):\n    ...     def __call__(self, val):\n    ...         pass\n    ...     def method(self, val, flags=0):\n    ...         pass\n\n    >>> get_spec(Test)\n    (['self', 'val'], {})\n\n    >>> get_spec(Test.method)\n    (['self', 'val'], {'flags': 0})\n\n    >>> get_spec(Test().method)\n    (['self', 'val'], {'flags': 0})\n    \"\"\"\n\n    if inspect.isfunction(func) or inspect.ismethod(func):\n        spec = inspect.getargspec(func)\n    elif hasattr(func, '__call__'):\n        spec = inspect.getargspec(func.__call__)\n    else:\n        raise TypeError('%s is not callable' % type(func))\n\n    defaults = spec.defaults or []\n\n    firstdefault = len(spec.args) - len(defaults)\n    args = spec.args[:firstdefault]\n    kwargs = dict(zip(spec.args[firstdefault:], defaults))\n    return args, kwargs\n\ndef equal_attributes(obj1, obj2, attributes):\n    \"\"\"Compare two objects attributes\"\"\"\n    # not attributes given return False by default\n    if not attributes:\n        return False\n\n    for attr in attributes:\n        # support callables like itemgetter\n        if callable(attr):\n            if not attr(obj1) == attr(obj2):\n                return False\n        else:\n            # check that objects has attribute\n            if not hasattr(obj1, attr):\n                return False\n            if not hasattr(obj2, attr):\n                return False\n            # compare object attributes\n            if not getattr(obj1, attr) == getattr(obj2, attr):\n                return False\n    # all attributes equal\n    return True\n\n\nclass WeakKeyCache(object):\n\n    def __init__(self, default_factory):\n        self.default_factory = default_factory\n        self._weakdict = weakref.WeakKeyDictionary()\n\n    def __getitem__(self, key):\n        if key not in self._weakdict:\n            self._weakdict[key] = self.default_factory(key)\n        return self._weakdict[key]\n\n\ndef stringify_dict(dct_or_tuples, encoding='utf-8', keys_only=True):\n    \"\"\"Return a (new) dict with the unicode keys (and values if, keys_only is\n    False) of the given dict converted to strings. `dct_or_tuples` can be a\n    dict or a list of tuples, like any dict constructor supports.\n    \"\"\"\n    d = {}\n    for k, v in dict(dct_or_tuples).iteritems():\n        k = k.encode(encoding) if isinstance(k, unicode) else k\n        if not keys_only:\n            v = v.encode(encoding) if isinstance(v, unicode) else v\n        d[k] = v\n    return d\n\ndef is_writable(path):\n    \"\"\"Return True if the given path can be written (if it exists) or created\n    (if it doesn't exist)\n    \"\"\"\n    if os.path.exists(path):\n        return os.access(path, os.W_OK)\n    else:\n        return os.access(os.path.dirname(path), os.W_OK)\n\ndef setattr_default(obj, name, value):\n    \"\"\"Set attribute value, but only if it's not already set. Similar to\n    setdefault() for dicts.\n    \"\"\"\n    if not hasattr(obj, name):\n        setattr(obj, name, value)\n\n\ndef retry_on_eintr(function, *args, **kw):\n    \"\"\"Run a function and retry it while getting EINTR errors\"\"\"\n    while True:\n        try:\n            return function(*args, **kw)\n        except IOError as e:\n            if e.errno != errno.EINTR:\n                raise\n", "path": "scrapy/utils/python.py"}], "after_files": [{"content": "\"\"\"\nThis module contains essential stuff that should've come with Python itself ;)\n\nIt also contains functions (or functionality) which is in Python versions\nhigher than 2.5 which used to be the lowest version supported by Scrapy.\n\n\"\"\"\nimport os\nimport re\nimport inspect\nimport weakref\nimport errno\nfrom functools import partial, wraps\nfrom sgmllib import SGMLParser\n\n\nclass FixedSGMLParser(SGMLParser):\n    \"\"\"The SGMLParser that comes with Python has a bug in the convert_charref()\n    method. This is the same class with the bug fixed\"\"\"\n\n    def convert_charref(self, name):\n        \"\"\"This method fixes a bug in Python's SGMLParser.\"\"\"\n        try:\n            n = int(name)\n        except ValueError:\n            return\n        if not 0 <= n <= 127 : # ASCII ends at 127, not 255\n            return\n        return self.convert_codepoint(n)\n\n\ndef flatten(x):\n    \"\"\"flatten(sequence) -> list\n\n    Returns a single, flat list which contains all elements retrieved\n    from the sequence and all recursively contained sub-sequences\n    (iterables).\n\n    Examples:\n    >>> [1, 2, [3,4], (5,6)]\n    [1, 2, [3, 4], (5, 6)]\n    >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, (8,9,10)])\n    [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10]\"\"\"\n\n    result = []\n    for el in x:\n        if hasattr(el, \"__iter__\"):\n            result.extend(flatten(el))\n        else:\n            result.append(el)\n    return result\n\n\ndef unique(list_, key=lambda x: x):\n    \"\"\"efficient function to uniquify a list preserving item order\"\"\"\n    seen = set()\n    result = []\n    for item in list_:\n        seenkey = key(item)\n        if seenkey in seen:\n            continue\n        seen.add(seenkey)\n        result.append(item)\n    return result\n\n\ndef str_to_unicode(text, encoding=None, errors='strict'):\n    \"\"\"Return the unicode representation of text in the given encoding. Unlike\n    .encode(encoding) this function can be applied directly to a unicode\n    object without the risk of double-decoding problems (which can happen if\n    you don't use the default 'ascii' encoding)\n    \"\"\"\n\n    if encoding is None:\n        encoding = 'utf-8'\n    if isinstance(text, str):\n        return text.decode(encoding, errors)\n    elif isinstance(text, unicode):\n        return text\n    else:\n        raise TypeError('str_to_unicode must receive a str or unicode object, got %s' % type(text).__name__)\n\ndef unicode_to_str(text, encoding=None, errors='strict'):\n    \"\"\"Return the str representation of text in the given encoding. Unlike\n    .encode(encoding) this function can be applied directly to a str\n    object without the risk of double-decoding problems (which can happen if\n    you don't use the default 'ascii' encoding)\n    \"\"\"\n\n    if encoding is None:\n        encoding = 'utf-8'\n    if isinstance(text, unicode):\n        return text.encode(encoding, errors)\n    elif isinstance(text, str):\n        return text\n    else:\n        raise TypeError('unicode_to_str must receive a unicode or str object, got %s' % type(text).__name__)\n\ndef re_rsearch(pattern, text, chunk_size=1024):\n    \"\"\"\n    This function does a reverse search in a text using a regular expression\n    given in the attribute 'pattern'.\n    Since the re module does not provide this functionality, we have to find for\n    the expression into chunks of text extracted from the end (for the sake of efficiency).\n    At first, a chunk of 'chunk_size' kilobytes is extracted from the end, and searched for\n    the pattern. If the pattern is not found, another chunk is extracted, and another\n    search is performed.\n    This process continues until a match is found, or until the whole file is read.\n    In case the pattern wasn't found, None is returned, otherwise it returns a tuple containing\n    the start position of the match, and the ending (regarding the entire text).\n    \"\"\"\n    def _chunk_iter():\n        offset = len(text)\n        while True:\n            offset -= (chunk_size * 1024)\n            if offset <= 0:\n                break\n            yield (text[offset:], offset)\n        yield (text, 0)\n\n    pattern = re.compile(pattern) if isinstance(pattern, basestring) else pattern\n    for chunk, offset in _chunk_iter():\n        matches = [match for match in pattern.finditer(chunk)]\n        if matches:\n            return (offset + matches[-1].span()[0], offset + matches[-1].span()[1])\n    return None\n\ndef memoizemethod_noargs(method):\n    \"\"\"Decorator to cache the result of a method (without arguments) using a\n    weak reference to its object\n    \"\"\"\n    cache = weakref.WeakKeyDictionary()\n    @wraps(method)\n    def new_method(self, *args, **kwargs):\n        if self not in cache:\n            cache[self] = method(self, *args, **kwargs)\n        return cache[self]\n    return new_method\n\n_BINARYCHARS = set(map(chr, range(32))) - set([\"\\0\", \"\\t\", \"\\n\", \"\\r\"])\n\ndef isbinarytext(text):\n    \"\"\"Return True if the given text is considered binary, or false\n    otherwise, by looking for binary bytes at their chars\n    \"\"\"\n    assert isinstance(text, str), \"text must be str, got '%s'\" % type(text).__name__\n    return any(c in _BINARYCHARS for c in text)\n\ndef get_func_args(func, stripself=False):\n    \"\"\"Return the argument name list of a callable\"\"\"\n    if inspect.isfunction(func):\n        func_args, _, _, _ = inspect.getargspec(func)\n    elif inspect.isclass(func):\n        return get_func_args(func.__init__, True)\n    elif inspect.ismethod(func):\n        return get_func_args(func.__func__, True)\n    elif inspect.ismethoddescriptor(func):\n        return []\n    elif isinstance(func, partial):\n        return get_func_args(func.func)\n    elif hasattr(func, '__call__'):\n        if inspect.isroutine(func):\n            return []\n        else:\n            return get_func_args(func.__call__, True)\n    else:\n        raise TypeError('%s is not callable' % type(func))\n    if stripself:\n        func_args.pop(0)\n    return func_args\n\ndef get_spec(func):\n    \"\"\"Returns (args, kwargs) tuple for a function\n    >>> import re\n    >>> get_spec(re.match)\n    (['pattern', 'string'], {'flags': 0})\n\n    >>> class Test(object):\n    ...     def __call__(self, val):\n    ...         pass\n    ...     def method(self, val, flags=0):\n    ...         pass\n\n    >>> get_spec(Test)\n    (['self', 'val'], {})\n\n    >>> get_spec(Test.method)\n    (['self', 'val'], {'flags': 0})\n\n    >>> get_spec(Test().method)\n    (['self', 'val'], {'flags': 0})\n    \"\"\"\n\n    if inspect.isfunction(func) or inspect.ismethod(func):\n        spec = inspect.getargspec(func)\n    elif hasattr(func, '__call__'):\n        spec = inspect.getargspec(func.__call__)\n    else:\n        raise TypeError('%s is not callable' % type(func))\n\n    defaults = spec.defaults or []\n\n    firstdefault = len(spec.args) - len(defaults)\n    args = spec.args[:firstdefault]\n    kwargs = dict(zip(spec.args[firstdefault:], defaults))\n    return args, kwargs\n\ndef equal_attributes(obj1, obj2, attributes):\n    \"\"\"Compare two objects attributes\"\"\"\n    # not attributes given return False by default\n    if not attributes:\n        return False\n\n    for attr in attributes:\n        # support callables like itemgetter\n        if callable(attr):\n            if not attr(obj1) == attr(obj2):\n                return False\n        else:\n            # check that objects has attribute\n            if not hasattr(obj1, attr):\n                return False\n            if not hasattr(obj2, attr):\n                return False\n            # compare object attributes\n            if not getattr(obj1, attr) == getattr(obj2, attr):\n                return False\n    # all attributes equal\n    return True\n\n\nclass WeakKeyCache(object):\n\n    def __init__(self, default_factory):\n        self.default_factory = default_factory\n        self._weakdict = weakref.WeakKeyDictionary()\n\n    def __getitem__(self, key):\n        if key not in self._weakdict:\n            self._weakdict[key] = self.default_factory(key)\n        return self._weakdict[key]\n\n\ndef stringify_dict(dct_or_tuples, encoding='utf-8', keys_only=True):\n    \"\"\"Return a (new) dict with the unicode keys (and values if, keys_only is\n    False) of the given dict converted to strings. `dct_or_tuples` can be a\n    dict or a list of tuples, like any dict constructor supports.\n    \"\"\"\n    d = {}\n    for k, v in dict(dct_or_tuples).iteritems():\n        k = k.encode(encoding) if isinstance(k, unicode) else k\n        if not keys_only:\n            v = v.encode(encoding) if isinstance(v, unicode) else v\n        d[k] = v\n    return d\n\ndef is_writable(path):\n    \"\"\"Return True if the given path can be written (if it exists) or created\n    (if it doesn't exist)\n    \"\"\"\n    if os.path.exists(path):\n        return os.access(path, os.W_OK)\n    else:\n        return os.access(os.path.dirname(path), os.W_OK)\n\ndef setattr_default(obj, name, value):\n    \"\"\"Set attribute value, but only if it's not already set. Similar to\n    setdefault() for dicts.\n    \"\"\"\n    if not hasattr(obj, name):\n        setattr(obj, name, value)\n\n\ndef retry_on_eintr(function, *args, **kw):\n    \"\"\"Run a function and retry it while getting EINTR errors\"\"\"\n    while True:\n        try:\n            return function(*args, **kw)\n        except IOError as e:\n            if e.errno != errno.EINTR:\n                raise\n", "path": "scrapy/utils/python.py"}]}