We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
RepresentationMixin breaks on classes with no default parameters
```
from parsl.utils import RepresentationMixin

class A(RepresentationMixin):

   def __init__(self, q):
     self.q = q

x = A(q=4)
print(x)
```

gives:

```
$ python b.py 
Traceback (most recent call last):
  File "b.py", line 10, in <module>
    print(x)
  File "/home/benc/parsl/src/parsl/parsl/utils.py", line 193, in __repr__
    defaults = dict(zip(reversed(argspec.args), reversed(argspec.defaults)))
TypeError: 'NoneType' object is not reversible
```

Changing `__init__` to:

```
   def __init__(self, q=3):
```

fixes this.

At a guess, argspec.defaults is None rather than an empty sequence in the breaking 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: `parsl/utils.py`
Content:
```
1 import inspect
2 import logging
3 import os
4 import shlex
5 import subprocess
6 import threading
7 import time
8 from contextlib import contextmanager
9 from functools import wraps
10 
11 import parsl
12 from parsl.version import VERSION
13 
14 logger = logging.getLogger(__name__)
15 
16 
17 def get_version():
18     version = parsl.__version__
19     work_tree = os.path.dirname(os.path.dirname(__file__))
20     git_dir = os.path.join(work_tree, '.git')
21     if os.path.exists(git_dir):
22         env = {'GIT_WORK_TREE': work_tree, 'GIT_DIR': git_dir}
23         try:
24             cmd = shlex.split('git rev-parse --short HEAD')
25             head = subprocess.check_output(cmd, env=env).strip().decode('utf-8')
26             diff = subprocess.check_output(shlex.split('git diff HEAD'), env=env)
27             status = 'dirty' if diff else 'clean'
28             version = '{v}-{head}-{status}'.format(v=VERSION, head=head, status=status)
29         except Exception:
30             pass
31 
32     return version
33 
34 
35 def get_all_checkpoints(rundir="runinfo"):
36     """Finds the checkpoints from all last runs.
37 
38     Note that checkpoints are incremental, and this helper will not find
39     previous checkpoints from earlier than the most recent run. It probably
40     should be made to do so.
41 
42     Kwargs:
43        - rundir(str) : Path to the runinfo directory
44 
45     Returns:
46        - a list suitable for the checkpointFiles parameter of DataFlowKernel
47          constructor
48 
49     """
50 
51     if(not os.path.isdir(rundir)):
52         return []
53 
54     dirs = sorted(os.listdir(rundir))
55 
56     checkpoints = []
57 
58     for runid in dirs:
59 
60         checkpoint = os.path.abspath('{}/{}/checkpoint'.format(rundir, runid))
61 
62         if os.path.isdir(checkpoint):
63             checkpoints.append(checkpoint)
64 
65     return checkpoints
66 
67 
68 def get_last_checkpoint(rundir="runinfo"):
69     """Find the checkpoint from the last run, if one exists.
70 
71     Note that checkpoints are incremental, and this helper will not find
72     previous checkpoints from earlier than the most recent run. It probably
73     should be made to do so.
74 
75     Kwargs:
76        - rundir(str) : Path to the runinfo directory
77 
78     Returns:
79      - a list suitable for checkpointFiles parameter of DataFlowKernel
80        constructor, with 0 or 1 elements
81 
82     """
83     if not os.path.isdir(rundir):
84         return []
85 
86     dirs = sorted(os.listdir(rundir))
87 
88     if len(dirs) == 0:
89         return []
90 
91     last_runid = dirs[-1]
92     last_checkpoint = os.path.abspath('{}/{}/checkpoint'.format(rundir, last_runid))
93 
94     if(not(os.path.isdir(last_checkpoint))):
95         return []
96 
97     return [last_checkpoint]
98 
99 
100 def timeout(seconds=None):
101     def decorator(func, *args, **kwargs):
102         @wraps(func)
103         def wrapper(*args, **kwargs):
104             t = threading.Thread(target=func, args=args, kwargs=kwargs, name="Timeout-Decorator")
105             t.start()
106             result = t.join(seconds)
107             if t.is_alive():
108                 raise RuntimeError('timed out in {}'.format(func))
109             return result
110         return wrapper
111     return decorator
112 
113 
114 @contextmanager
115 def wait_for_file(path, seconds=10):
116     for i in range(0, int(seconds * 100)):
117         time.sleep(seconds / 100.)
118         if os.path.exists(path):
119             break
120     yield
121 
122 
123 @contextmanager
124 def time_limited_open(path, mode, seconds=1):
125     with wait_for_file(path, seconds):
126         logger.debug("wait_for_file yielded")
127     f = open(path, mode)
128     yield f
129     f.close()
130 
131 
132 def wtime_to_minutes(time_string):
133     ''' wtime_to_minutes
134 
135     Convert standard wallclock time string to minutes.
136 
137     Args:
138         - Time_string in HH:MM:SS format
139 
140     Returns:
141         (int) minutes
142 
143     '''
144     hours, mins, seconds = time_string.split(':')
145     total_mins = int(hours) * 60 + int(mins)
146     if total_mins < 1:
147         logger.warning("Time string '{}' parsed to {} minutes, less than 1".format(time_string, total_mins))
148     return total_mins
149 
150 
151 class RepresentationMixin(object):
152     """A mixin class for adding a __repr__ method.
153 
154     The __repr__ method will return a string equivalent to the code used to instantiate
155     the child class, with any defaults included explicitly. The __max_width__ class variable
156     controls the maximum width of the representation string. If this width is exceeded,
157     the representation string will be split up, with one argument or keyword argument per line.
158 
159     Any arguments or keyword arguments in the constructor must be defined as attributes, or
160     an AttributeError will be raised.
161 
162     Examples
163     --------
164     >>> from parsl.utils import RepresentationMixin
165     >>> class Foo(RepresentationMixin):
166             def __init__(self, first, second, third='three', fourth='fourth'):
167                 self.first = first
168                 self.second = second
169                 self.third = third
170                 self.fourth = fourth
171     >>> bar = Foo(1, 'two', fourth='baz')
172     >>> bar
173     Foo(1, 'two', third='three', fourth='baz')
174     """
175     __max_width__ = 80
176 
177     def __repr__(self):
178         init = self.__init__
179 
180         # This test looks for a single layer of wrapping performed by
181         # functools.update_wrapper, commonly used in decorators. This will
182         # allow RepresentationMixin to see through a single such decorator
183         # applied to the __init__ method of a class, and find the underlying
184         # arguments. It will not see through multiple layers of such
185         # decorators, or cope with other decorators which do not use
186         # functools.update_wrapper.
187 
188         if hasattr(init, '__wrapped__'):
189             init = init.__wrapped__
190 
191         argspec = inspect.getfullargspec(init)
192         if len(argspec.args) > 1:
193             defaults = dict(zip(reversed(argspec.args), reversed(argspec.defaults)))
194         else:
195             defaults = {}
196 
197         for arg in argspec.args[1:]:
198             if not hasattr(self, arg):
199                 template = 'class {} uses {} in the constructor, but does not define it as an attribute'
200                 raise AttributeError(template.format(self.__class__.__name__, arg))
201 
202         args = [getattr(self, a) for a in argspec.args[1:-len(defaults)]]
203         kwargs = {key: getattr(self, key) for key in defaults}
204 
205         def assemble_multiline(args, kwargs):
206             def indent(text):
207                 lines = text.splitlines()
208                 if len(lines) <= 1:
209                     return text
210                 return "\n".join("    " + l for l in lines).strip()
211             args = ["\n    {},".format(indent(repr(a))) for a in args]
212             kwargs = ["\n    {}={}".format(k, indent(repr(v)))
213                       for k, v in sorted(kwargs.items())]
214 
215             info = "".join(args) + ", ".join(kwargs)
216             return self.__class__.__name__ + "({}\n)".format(info)
217 
218         def assemble_line(args, kwargs):
219             kwargs = ['{}={}'.format(k, repr(v)) for k, v in sorted(kwargs.items())]
220 
221             info = ", ".join([repr(a) for a in args] + kwargs)
222             return self.__class__.__name__ + "({})".format(info)
223 
224         if len(assemble_line(args, kwargs)) <= self.__class__.__max_width__:
225             return assemble_line(args, kwargs)
226         else:
227             return assemble_multiline(args, kwargs)
228
```
--- 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/parsl/utils.py b/parsl/utils.py
--- a/parsl/utils.py
+++ b/parsl/utils.py
@@ -189,7 +189,7 @@
             init = init.__wrapped__
 
         argspec = inspect.getfullargspec(init)
-        if len(argspec.args) > 1:
+        if len(argspec.args) > 1 and argspec.defaults is not None:
             defaults = dict(zip(reversed(argspec.args), reversed(argspec.defaults)))
         else:
             defaults = {}
@@ -199,7 +199,10 @@
                 template = 'class {} uses {} in the constructor, but does not define it as an attribute'
                 raise AttributeError(template.format(self.__class__.__name__, arg))
 
-        args = [getattr(self, a) for a in argspec.args[1:-len(defaults)]]
+        if len(defaults) != 0:
+            args = [getattr(self, a) for a in argspec.args[1:-len(defaults)]]
+        else:
+            args = [getattr(self, a) for a in argspec.args[1:]]
         kwargs = {key: getattr(self, key) for key in defaults}
 
         def assemble_multiline(args, kwargs):

{"golden_diff": "diff --git a/parsl/utils.py b/parsl/utils.py\n--- a/parsl/utils.py\n+++ b/parsl/utils.py\n@@ -189,7 +189,7 @@\n             init = init.__wrapped__\n \n         argspec = inspect.getfullargspec(init)\n-        if len(argspec.args) > 1:\n+        if len(argspec.args) > 1 and argspec.defaults is not None:\n             defaults = dict(zip(reversed(argspec.args), reversed(argspec.defaults)))\n         else:\n             defaults = {}\n@@ -199,7 +199,10 @@\n                 template = 'class {} uses {} in the constructor, but does not define it as an attribute'\n                 raise AttributeError(template.format(self.__class__.__name__, arg))\n \n-        args = [getattr(self, a) for a in argspec.args[1:-len(defaults)]]\n+        if len(defaults) != 0:\n+            args = [getattr(self, a) for a in argspec.args[1:-len(defaults)]]\n+        else:\n+            args = [getattr(self, a) for a in argspec.args[1:]]\n         kwargs = {key: getattr(self, key) for key in defaults}\n \n         def assemble_multiline(args, kwargs):\n", "issue": "RepresentationMixin breaks on classes with no default parameters\n```\r\nfrom parsl.utils import RepresentationMixin\r\n\r\nclass A(RepresentationMixin):\r\n\r\n   def __init__(self, q):\r\n     self.q = q\r\n\r\nx = A(q=4)\r\nprint(x)\r\n```\r\n\r\ngives:\r\n\r\n```\r\n$ python b.py \r\nTraceback (most recent call last):\r\n  File \"b.py\", line 10, in <module>\r\n    print(x)\r\n  File \"/home/benc/parsl/src/parsl/parsl/utils.py\", line 193, in __repr__\r\n    defaults = dict(zip(reversed(argspec.args), reversed(argspec.defaults)))\r\nTypeError: 'NoneType' object is not reversible\r\n```\r\n\r\nChanging `__init__` to:\r\n\r\n```\r\n   def __init__(self, q=3):\r\n```\r\n\r\nfixes this.\r\n\r\nAt a guess, argspec.defaults is None rather than an empty sequence in the breaking case.\n", "before_files": [{"content": "import inspect\nimport logging\nimport os\nimport shlex\nimport subprocess\nimport threading\nimport time\nfrom contextlib import contextmanager\nfrom functools import wraps\n\nimport parsl\nfrom parsl.version import VERSION\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_version():\n    version = parsl.__version__\n    work_tree = os.path.dirname(os.path.dirname(__file__))\n    git_dir = os.path.join(work_tree, '.git')\n    if os.path.exists(git_dir):\n        env = {'GIT_WORK_TREE': work_tree, 'GIT_DIR': git_dir}\n        try:\n            cmd = shlex.split('git rev-parse --short HEAD')\n            head = subprocess.check_output(cmd, env=env).strip().decode('utf-8')\n            diff = subprocess.check_output(shlex.split('git diff HEAD'), env=env)\n            status = 'dirty' if diff else 'clean'\n            version = '{v}-{head}-{status}'.format(v=VERSION, head=head, status=status)\n        except Exception:\n            pass\n\n    return version\n\n\ndef get_all_checkpoints(rundir=\"runinfo\"):\n    \"\"\"Finds the checkpoints from all last runs.\n\n    Note that checkpoints are incremental, and this helper will not find\n    previous checkpoints from earlier than the most recent run. It probably\n    should be made to do so.\n\n    Kwargs:\n       - rundir(str) : Path to the runinfo directory\n\n    Returns:\n       - a list suitable for the checkpointFiles parameter of DataFlowKernel\n         constructor\n\n    \"\"\"\n\n    if(not os.path.isdir(rundir)):\n        return []\n\n    dirs = sorted(os.listdir(rundir))\n\n    checkpoints = []\n\n    for runid in dirs:\n\n        checkpoint = os.path.abspath('{}/{}/checkpoint'.format(rundir, runid))\n\n        if os.path.isdir(checkpoint):\n            checkpoints.append(checkpoint)\n\n    return checkpoints\n\n\ndef get_last_checkpoint(rundir=\"runinfo\"):\n    \"\"\"Find the checkpoint from the last run, if one exists.\n\n    Note that checkpoints are incremental, and this helper will not find\n    previous checkpoints from earlier than the most recent run. It probably\n    should be made to do so.\n\n    Kwargs:\n       - rundir(str) : Path to the runinfo directory\n\n    Returns:\n     - a list suitable for checkpointFiles parameter of DataFlowKernel\n       constructor, with 0 or 1 elements\n\n    \"\"\"\n    if not os.path.isdir(rundir):\n        return []\n\n    dirs = sorted(os.listdir(rundir))\n\n    if len(dirs) == 0:\n        return []\n\n    last_runid = dirs[-1]\n    last_checkpoint = os.path.abspath('{}/{}/checkpoint'.format(rundir, last_runid))\n\n    if(not(os.path.isdir(last_checkpoint))):\n        return []\n\n    return [last_checkpoint]\n\n\ndef timeout(seconds=None):\n    def decorator(func, *args, **kwargs):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            t = threading.Thread(target=func, args=args, kwargs=kwargs, name=\"Timeout-Decorator\")\n            t.start()\n            result = t.join(seconds)\n            if t.is_alive():\n                raise RuntimeError('timed out in {}'.format(func))\n            return result\n        return wrapper\n    return decorator\n\n\n@contextmanager\ndef wait_for_file(path, seconds=10):\n    for i in range(0, int(seconds * 100)):\n        time.sleep(seconds / 100.)\n        if os.path.exists(path):\n            break\n    yield\n\n\n@contextmanager\ndef time_limited_open(path, mode, seconds=1):\n    with wait_for_file(path, seconds):\n        logger.debug(\"wait_for_file yielded\")\n    f = open(path, mode)\n    yield f\n    f.close()\n\n\ndef wtime_to_minutes(time_string):\n    ''' wtime_to_minutes\n\n    Convert standard wallclock time string to minutes.\n\n    Args:\n        - Time_string in HH:MM:SS format\n\n    Returns:\n        (int) minutes\n\n    '''\n    hours, mins, seconds = time_string.split(':')\n    total_mins = int(hours) * 60 + int(mins)\n    if total_mins < 1:\n        logger.warning(\"Time string '{}' parsed to {} minutes, less than 1\".format(time_string, total_mins))\n    return total_mins\n\n\nclass RepresentationMixin(object):\n    \"\"\"A mixin class for adding a __repr__ method.\n\n    The __repr__ method will return a string equivalent to the code used to instantiate\n    the child class, with any defaults included explicitly. The __max_width__ class variable\n    controls the maximum width of the representation string. If this width is exceeded,\n    the representation string will be split up, with one argument or keyword argument per line.\n\n    Any arguments or keyword arguments in the constructor must be defined as attributes, or\n    an AttributeError will be raised.\n\n    Examples\n    --------\n    >>> from parsl.utils import RepresentationMixin\n    >>> class Foo(RepresentationMixin):\n            def __init__(self, first, second, third='three', fourth='fourth'):\n                self.first = first\n                self.second = second\n                self.third = third\n                self.fourth = fourth\n    >>> bar = Foo(1, 'two', fourth='baz')\n    >>> bar\n    Foo(1, 'two', third='three', fourth='baz')\n    \"\"\"\n    __max_width__ = 80\n\n    def __repr__(self):\n        init = self.__init__\n\n        # This test looks for a single layer of wrapping performed by\n        # functools.update_wrapper, commonly used in decorators. This will\n        # allow RepresentationMixin to see through a single such decorator\n        # applied to the __init__ method of a class, and find the underlying\n        # arguments. It will not see through multiple layers of such\n        # decorators, or cope with other decorators which do not use\n        # functools.update_wrapper.\n\n        if hasattr(init, '__wrapped__'):\n            init = init.__wrapped__\n\n        argspec = inspect.getfullargspec(init)\n        if len(argspec.args) > 1:\n            defaults = dict(zip(reversed(argspec.args), reversed(argspec.defaults)))\n        else:\n            defaults = {}\n\n        for arg in argspec.args[1:]:\n            if not hasattr(self, arg):\n                template = 'class {} uses {} in the constructor, but does not define it as an attribute'\n                raise AttributeError(template.format(self.__class__.__name__, arg))\n\n        args = [getattr(self, a) for a in argspec.args[1:-len(defaults)]]\n        kwargs = {key: getattr(self, key) for key in defaults}\n\n        def assemble_multiline(args, kwargs):\n            def indent(text):\n                lines = text.splitlines()\n                if len(lines) <= 1:\n                    return text\n                return \"\\n\".join(\"    \" + l for l in lines).strip()\n            args = [\"\\n    {},\".format(indent(repr(a))) for a in args]\n            kwargs = [\"\\n    {}={}\".format(k, indent(repr(v)))\n                      for k, v in sorted(kwargs.items())]\n\n            info = \"\".join(args) + \", \".join(kwargs)\n            return self.__class__.__name__ + \"({}\\n)\".format(info)\n\n        def assemble_line(args, kwargs):\n            kwargs = ['{}={}'.format(k, repr(v)) for k, v in sorted(kwargs.items())]\n\n            info = \", \".join([repr(a) for a in args] + kwargs)\n            return self.__class__.__name__ + \"({})\".format(info)\n\n        if len(assemble_line(args, kwargs)) <= self.__class__.__max_width__:\n            return assemble_line(args, kwargs)\n        else:\n            return assemble_multiline(args, kwargs)\n", "path": "parsl/utils.py"}], "after_files": [{"content": "import inspect\nimport logging\nimport os\nimport shlex\nimport subprocess\nimport threading\nimport time\nfrom contextlib import contextmanager\nfrom functools import wraps\n\nimport parsl\nfrom parsl.version import VERSION\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_version():\n    version = parsl.__version__\n    work_tree = os.path.dirname(os.path.dirname(__file__))\n    git_dir = os.path.join(work_tree, '.git')\n    if os.path.exists(git_dir):\n        env = {'GIT_WORK_TREE': work_tree, 'GIT_DIR': git_dir}\n        try:\n            cmd = shlex.split('git rev-parse --short HEAD')\n            head = subprocess.check_output(cmd, env=env).strip().decode('utf-8')\n            diff = subprocess.check_output(shlex.split('git diff HEAD'), env=env)\n            status = 'dirty' if diff else 'clean'\n            version = '{v}-{head}-{status}'.format(v=VERSION, head=head, status=status)\n        except Exception:\n            pass\n\n    return version\n\n\ndef get_all_checkpoints(rundir=\"runinfo\"):\n    \"\"\"Finds the checkpoints from all last runs.\n\n    Note that checkpoints are incremental, and this helper will not find\n    previous checkpoints from earlier than the most recent run. It probably\n    should be made to do so.\n\n    Kwargs:\n       - rundir(str) : Path to the runinfo directory\n\n    Returns:\n       - a list suitable for the checkpointFiles parameter of DataFlowKernel\n         constructor\n\n    \"\"\"\n\n    if(not os.path.isdir(rundir)):\n        return []\n\n    dirs = sorted(os.listdir(rundir))\n\n    checkpoints = []\n\n    for runid in dirs:\n\n        checkpoint = os.path.abspath('{}/{}/checkpoint'.format(rundir, runid))\n\n        if os.path.isdir(checkpoint):\n            checkpoints.append(checkpoint)\n\n    return checkpoints\n\n\ndef get_last_checkpoint(rundir=\"runinfo\"):\n    \"\"\"Find the checkpoint from the last run, if one exists.\n\n    Note that checkpoints are incremental, and this helper will not find\n    previous checkpoints from earlier than the most recent run. It probably\n    should be made to do so.\n\n    Kwargs:\n       - rundir(str) : Path to the runinfo directory\n\n    Returns:\n     - a list suitable for checkpointFiles parameter of DataFlowKernel\n       constructor, with 0 or 1 elements\n\n    \"\"\"\n    if not os.path.isdir(rundir):\n        return []\n\n    dirs = sorted(os.listdir(rundir))\n\n    if len(dirs) == 0:\n        return []\n\n    last_runid = dirs[-1]\n    last_checkpoint = os.path.abspath('{}/{}/checkpoint'.format(rundir, last_runid))\n\n    if(not(os.path.isdir(last_checkpoint))):\n        return []\n\n    return [last_checkpoint]\n\n\ndef timeout(seconds=None):\n    def decorator(func, *args, **kwargs):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            t = threading.Thread(target=func, args=args, kwargs=kwargs, name=\"Timeout-Decorator\")\n            t.start()\n            result = t.join(seconds)\n            if t.is_alive():\n                raise RuntimeError('timed out in {}'.format(func))\n            return result\n        return wrapper\n    return decorator\n\n\n@contextmanager\ndef wait_for_file(path, seconds=10):\n    for i in range(0, int(seconds * 100)):\n        time.sleep(seconds / 100.)\n        if os.path.exists(path):\n            break\n    yield\n\n\n@contextmanager\ndef time_limited_open(path, mode, seconds=1):\n    with wait_for_file(path, seconds):\n        logger.debug(\"wait_for_file yielded\")\n    f = open(path, mode)\n    yield f\n    f.close()\n\n\ndef wtime_to_minutes(time_string):\n    ''' wtime_to_minutes\n\n    Convert standard wallclock time string to minutes.\n\n    Args:\n        - Time_string in HH:MM:SS format\n\n    Returns:\n        (int) minutes\n\n    '''\n    hours, mins, seconds = time_string.split(':')\n    total_mins = int(hours) * 60 + int(mins)\n    if total_mins < 1:\n        logger.warning(\"Time string '{}' parsed to {} minutes, less than 1\".format(time_string, total_mins))\n    return total_mins\n\n\nclass RepresentationMixin(object):\n    \"\"\"A mixin class for adding a __repr__ method.\n\n    The __repr__ method will return a string equivalent to the code used to instantiate\n    the child class, with any defaults included explicitly. The __max_width__ class variable\n    controls the maximum width of the representation string. If this width is exceeded,\n    the representation string will be split up, with one argument or keyword argument per line.\n\n    Any arguments or keyword arguments in the constructor must be defined as attributes, or\n    an AttributeError will be raised.\n\n    Examples\n    --------\n    >>> from parsl.utils import RepresentationMixin\n    >>> class Foo(RepresentationMixin):\n            def __init__(self, first, second, third='three', fourth='fourth'):\n                self.first = first\n                self.second = second\n                self.third = third\n                self.fourth = fourth\n    >>> bar = Foo(1, 'two', fourth='baz')\n    >>> bar\n    Foo(1, 'two', third='three', fourth='baz')\n    \"\"\"\n    __max_width__ = 80\n\n    def __repr__(self):\n        init = self.__init__\n\n        # This test looks for a single layer of wrapping performed by\n        # functools.update_wrapper, commonly used in decorators. This will\n        # allow RepresentationMixin to see through a single such decorator\n        # applied to the __init__ method of a class, and find the underlying\n        # arguments. It will not see through multiple layers of such\n        # decorators, or cope with other decorators which do not use\n        # functools.update_wrapper.\n\n        if hasattr(init, '__wrapped__'):\n            init = init.__wrapped__\n\n        argspec = inspect.getfullargspec(init)\n        if len(argspec.args) > 1 and argspec.defaults is not None:\n            defaults = dict(zip(reversed(argspec.args), reversed(argspec.defaults)))\n        else:\n            defaults = {}\n\n        for arg in argspec.args[1:]:\n            if not hasattr(self, arg):\n                template = 'class {} uses {} in the constructor, but does not define it as an attribute'\n                raise AttributeError(template.format(self.__class__.__name__, arg))\n\n        if len(defaults) != 0:\n            args = [getattr(self, a) for a in argspec.args[1:-len(defaults)]]\n        else:\n            args = [getattr(self, a) for a in argspec.args[1:]]\n        kwargs = {key: getattr(self, key) for key in defaults}\n\n        def assemble_multiline(args, kwargs):\n            def indent(text):\n                lines = text.splitlines()\n                if len(lines) <= 1:\n                    return text\n                return \"\\n\".join(\"    \" + l for l in lines).strip()\n            args = [\"\\n    {},\".format(indent(repr(a))) for a in args]\n            kwargs = [\"\\n    {}={}\".format(k, indent(repr(v)))\n                      for k, v in sorted(kwargs.items())]\n\n            info = \"\".join(args) + \", \".join(kwargs)\n            return self.__class__.__name__ + \"({}\\n)\".format(info)\n\n        def assemble_line(args, kwargs):\n            kwargs = ['{}={}'.format(k, repr(v)) for k, v in sorted(kwargs.items())]\n\n            info = \", \".join([repr(a) for a in args] + kwargs)\n            return self.__class__.__name__ + \"({})\".format(info)\n\n        if len(assemble_line(args, kwargs)) <= self.__class__.__max_width__:\n            return assemble_line(args, kwargs)\n        else:\n            return assemble_multiline(args, kwargs)\n", "path": "parsl/utils.py"}]}