We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[config] FilenameAttribute throws exceptions when configuring
https://github.com/sopel-irc/sopel/blob/master/sopel/config/types.py#L314-L340

Serialize and parse require extra arguments that they are not passed during `sopel --configure-modules`


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sopel/config/types.py`
Content:
```
1 # coding=utf8
2 """Types for creating section definitions.
3 
4 A section definition consists of a subclass of ``StaticSection``, on which any
5 number of subclasses of ``BaseValidated`` (a few common ones of which are
6 available in this module) are assigned as attributes. These descriptors define
7 how to read values from, and write values to, the config file.
8 
9 As an example, if one wanted to define the ``[spam]`` section as having an
10 ``eggs`` option, which contains a list of values, they could do this:
11 
12     >>> class SpamSection(StaticSection):
13     ...     eggs = ListAttribute('eggs')
14     ...
15     >>> SpamSection(config, 'spam')
16     >>> print(config.spam.eggs)
17     []
18     >>> config.spam.eggs = ['goose', 'turkey', 'duck', 'chicken', 'quail']
19     >>> print(config.spam.eggs)
20     ['goose', 'turkey', 'duck', 'chicken', 'quail']
21     >>> config.spam.eggs = 'herring'
22     Traceback (most recent call last):
23         ...
24     ValueError: ListAttribute value must be a list.
25 """
26 
27 from __future__ import unicode_literals
28 import os.path
29 import sys
30 from sopel.tools import get_input
31 
32 try:
33     import configparser
34 except ImportError:
35     import ConfigParser as configparser
36 
37 if sys.version_info.major >= 3:
38     unicode = str
39     basestring = (str, bytes)
40 
41 
42 class NO_DEFAULT(object):
43     """A special value to indicate that there should be no default."""
44 
45 
46 class StaticSection(object):
47     """A configuration section with parsed and validated settings.
48 
49     This class is intended to be subclassed with added ``ValidatedAttribute``s.
50     """
51     def __init__(self, config, section_name, validate=True):
52         if not config.parser.has_section(section_name):
53             config.parser.add_section(section_name)
54         self._parent = config
55         self._parser = config.parser
56         self._section_name = section_name
57         for value in dir(self):
58             try:
59                 getattr(self, value)
60             except ValueError as e:
61                 raise ValueError(
62                     'Invalid value for {}.{}: {}'.format(section_name, value,
63                                                          e.message)
64                 )
65             except AttributeError:
66                 if validate:
67                     raise ValueError(
68                         'Missing required value for {}.{}'.format(section_name,
69                                                                   value)
70                     )
71 
72     def configure_setting(self, name, prompt, default=NO_DEFAULT):
73         """Return a validated value for this attribute from the terminal.
74 
75         ``prompt`` will be the docstring of the attribute if not given.
76 
77         If ``default`` is passed, it will be used if no value is given by the
78         user. If it is not passed, the current value of the setting, or the
79         default value if it's unset, will be used. Note that if ``default`` is
80         passed, the current value of the setting will be ignored, even if it is
81         not the attribute's default.
82         """
83         clazz = getattr(self.__class__, name)
84         if default is NO_DEFAULT:
85             try:
86                 default = getattr(self, name)
87             except AttributeError:
88                 pass
89             except ValueError:
90                 print('The configured value for this option was invalid.')
91                 if clazz.default is not NO_DEFAULT:
92                     default = clazz.default
93         while True:
94             try:
95                 value = clazz.configure(prompt, default)
96             except ValueError as exc:
97                 print(exc)
98             else:
99                 break
100         setattr(self, name, value)
101 
102 
103 class BaseValidated(object):
104     """The base type for a descriptor in a ``StaticSection``."""
105     def __init__(self, name, default=None):
106         """
107         ``name`` is the name of the setting in the section.
108         ``default`` is the value to be returned if the setting is not set. If
109         not given, AttributeError will be raised instead.
110         """
111         self.name = name
112         self.default = default
113 
114     def configure(self, prompt, default):
115         """With the prompt and default, parse and return a value from terminal.
116         """
117         if default is not NO_DEFAULT and default is not None:
118             prompt = '{} [{}]'.format(prompt, default)
119         value = get_input(prompt + ' ')
120         if not value and default is NO_DEFAULT:
121             raise ValueError("You must provide a value for this option.")
122         value = value or default
123         return self.parse(value)
124 
125     def serialize(self, value):
126         """Take some object, and return the string to be saved to the file.
127 
128         Must be implemented in subclasses.
129         """
130         raise NotImplemented("Serialize method must be implemented in subclass")
131 
132     def parse(self, value):
133         """Take a string from the file, and return the appropriate object.
134 
135         Must be implemented in subclasses."""
136         raise NotImplemented("Parse method must be implemented in subclass")
137 
138     def __get__(self, instance, owner=None):
139         if instance is None:
140             # If instance is None, we're getting from a section class, not an
141             # instance of a session class. It makes the wizard code simpler
142             # (and is really just more intuitive) to return the descriptor
143             # instance here.
144             return self
145 
146         try:
147             value = instance._parser.get(instance._section_name, self.name)
148         except configparser.NoOptionError:
149             if self.default is not NO_DEFAULT:
150                 return self.default
151             raise AttributeError(
152                 "Missing required value for {}.{}".format(
153                     instance._section_name, self.name
154                 )
155             )
156         return self.parse(value)
157 
158     def __set__(self, instance, value):
159         if value is None:
160             instance._parser.remove_option(instance._section_name, self.name)
161             return
162         value = self.serialize(value)
163         instance._parser.set(instance._section_name, self.name, value)
164 
165     def __delete__(self, instance):
166         instance._parser.remove_option(instance._section_name, self.name)
167 
168 
169 def _parse_boolean(value):
170     if value is True or value == 1:
171         return value
172     if isinstance(value, basestring):
173         return value.lower() in ['1', 'yes', 'y', 'true', 'on']
174     return bool(value)
175 
176 
177 def _serialize_boolean(value):
178     return 'true' if _parse_boolean(value) else 'false'
179 
180 
181 class ValidatedAttribute(BaseValidated):
182     def __init__(self, name, parse=None, serialize=None, default=None):
183         """A descriptor for settings in a ``StaticSection``
184 
185         ``parse`` is the function to be used to read the string and create the
186         appropriate object. If not given, return the string as-is.
187         ``serialize`` takes an object, and returns the value to be written to
188         the file. If not given, defaults to ``unicode``.
189         """
190         self.name = name
191         if parse == bool:
192             parse = _parse_boolean
193             if not serialize or serialize == bool:
194                 serialize = _serialize_boolean
195         self.parse = parse or self.parse
196         self.serialize = serialize or self.serialize
197         self.default = default
198 
199     def serialize(self, value):
200         return unicode(value)
201 
202     def parse(self, value):
203         return value
204 
205     def configure(self, prompt, default):
206         if self.parse == _parse_boolean:
207             prompt += ' (y/n)'
208             default = 'y' if default else 'n'
209         return super(ValidatedAttribute, self).configure(prompt, default)
210 
211 
212 class ListAttribute(BaseValidated):
213     """A config attribute containing a list of string values.
214 
215     Values are saved to the file as a comma-separated list. It does not
216     currently support commas within items in the list. By default, the spaces
217     before and after each item are stripped; you can override this by passing
218     ``strip=False``."""
219     def __init__(self, name, strip=True, default=None):
220         default = default or []
221         super(ListAttribute, self).__init__(name, default=default)
222         self.strip = strip
223 
224     def parse(self, value):
225         value = value.split(',')
226         if self.strip:
227             return [v.strip() for v in value]
228         else:
229             return value
230 
231     def serialize(self, value):
232         if not isinstance(value, (list, set)):
233             raise ValueError('ListAttribute value must be a list.')
234         return ','.join(value)
235 
236     def configure(self, prompt, default):
237         each_prompt = '?'
238         if isinstance(prompt, tuple):
239             each_prompt = prompt[1]
240             prompt = prompt[0]
241 
242         if default is not NO_DEFAULT:
243             default = ','.join(default)
244             prompt = '{} [{}]'.format(prompt, default)
245         else:
246             default = ''
247         print(prompt)
248         values = []
249         value = get_input(each_prompt + ' ') or default
250         while value:
251             values.append(value)
252             value = get_input(each_prompt + ' ')
253         return self.parse(','.join(values))
254 
255 
256 class ChoiceAttribute(BaseValidated):
257     """A config attribute which must be one of a set group of options.
258 
259     Currently, the choices can only be strings."""
260     def __init__(self, name, choices, default=None):
261         super(ChoiceAttribute, self).__init__(name, default=default)
262         self.choices = choices
263 
264     def parse(self, value):
265         if value in self.choices:
266             return value
267         else:
268             raise ValueError('Value must be in {}'.format(self.choices))
269 
270     def serialize(self, value):
271         if value in self.choices:
272             return value
273         else:
274             raise ValueError('Value must be in {}'.format(self.choices))
275 
276 
277 class FilenameAttribute(BaseValidated):
278     """A config attribute which must be a file or directory."""
279     def __init__(self, name, relative=True, directory=False, default=None):
280         """
281         ``relative`` is whether the path should be relative to the location
282         of the config file (absolute paths will still be absolute). If
283         ``directory`` is True, the path must indicate a directory, rather than
284         a file.
285         """
286         super(FilenameAttribute, self).__init__(name, default=default)
287         self.relative = relative
288         self.directory = directory
289 
290     def __get__(self, instance, owner=None):
291         if instance is None:
292             return self
293         try:
294             value = instance._parser.get(instance._section_name, self.name)
295         except configparser.NoOptionError:
296             if self.default is not NO_DEFAULT:
297                 value = self.default
298             else:
299                 raise AttributeError(
300                     "Missing required value for {}.{}".format(
301                         instance._section_name, self.name
302                     )
303                 )
304         main_config = instance._parent
305         this_section = getattr(main_config, instance._section_name)
306         return self.parse(main_config, this_section, value)
307 
308     def __set__(self, instance, value):
309         main_config = instance._parent
310         this_section = getattr(main_config, instance._section_name)
311         value = self.serialize(main_config, this_section, value)
312         instance._parser.set(instance._section_name, self.name, value)
313 
314     def parse(self, main_config, this_section, value):
315         if value is None:
316             return
317 
318         value = os.path.expanduser(value)
319 
320         if not os.path.isabs(value):
321             if not self.relative:
322                 raise ValueError("Value must be an absolute path.")
323             value = os.path.join(main_config.homedir, value)
324 
325         if self.directory and not os.path.isdir(value):
326             try:
327                 os.makedirs(value)
328             except OSError:
329                 raise ValueError(
330                     "Value must be an existing or creatable directory.")
331         if not self.directory and not os.path.isfile(value):
332             try:
333                 open(value, 'w').close()
334             except OSError:
335                 raise ValueError("Value must be an existant or creatable file.")
336         return value
337 
338     def serialize(self, main_config, this_section, value):
339         self.parse(main_config, this_section, value)
340         return value  # So that it's still relative
341
```
--- 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/sopel/config/types.py b/sopel/config/types.py
--- a/sopel/config/types.py
+++ b/sopel/config/types.py
@@ -92,7 +92,7 @@
                     default = clazz.default
         while True:
             try:
-                value = clazz.configure(prompt, default)
+                value = clazz.configure(prompt, default, self._parent, self._section_name)
             except ValueError as exc:
                 print(exc)
             else:
@@ -111,7 +111,7 @@
         self.name = name
         self.default = default
 
-    def configure(self, prompt, default):
+    def configure(self, prompt, default, parent, section_name):
         """With the prompt and default, parse and return a value from terminal.
         """
         if default is not NO_DEFAULT and default is not None:
@@ -202,7 +202,7 @@
     def parse(self, value):
         return value
 
-    def configure(self, prompt, default):
+    def configure(self, prompt, default, parent, section_name):
         if self.parse == _parse_boolean:
             prompt += ' (y/n)'
             default = 'y' if default else 'n'
@@ -233,7 +233,7 @@
             raise ValueError('ListAttribute value must be a list.')
         return ','.join(value)
 
-    def configure(self, prompt, default):
+    def configure(self, prompt, default, parent, section_name):
         each_prompt = '?'
         if isinstance(prompt, tuple):
             each_prompt = prompt[1]
@@ -311,6 +311,17 @@
         value = self.serialize(main_config, this_section, value)
         instance._parser.set(instance._section_name, self.name, value)
 
+    def configure(self, prompt, default, parent, section_name):
+        """With the prompt and default, parse and return a value from terminal.
+        """
+        if default is not NO_DEFAULT and default is not None:
+            prompt = '{} [{}]'.format(prompt, default)
+        value = get_input(prompt + ' ')
+        if not value and default is NO_DEFAULT:
+            raise ValueError("You must provide a value for this option.")
+        value = value or default
+        return self.parse(parent, section_name, value)
+
     def parse(self, main_config, this_section, value):
         if value is None:
             return

{"golden_diff": "diff --git a/sopel/config/types.py b/sopel/config/types.py\n--- a/sopel/config/types.py\n+++ b/sopel/config/types.py\n@@ -92,7 +92,7 @@\n                     default = clazz.default\n         while True:\n             try:\n-                value = clazz.configure(prompt, default)\n+                value = clazz.configure(prompt, default, self._parent, self._section_name)\n             except ValueError as exc:\n                 print(exc)\n             else:\n@@ -111,7 +111,7 @@\n         self.name = name\n         self.default = default\n \n-    def configure(self, prompt, default):\n+    def configure(self, prompt, default, parent, section_name):\n         \"\"\"With the prompt and default, parse and return a value from terminal.\n         \"\"\"\n         if default is not NO_DEFAULT and default is not None:\n@@ -202,7 +202,7 @@\n     def parse(self, value):\n         return value\n \n-    def configure(self, prompt, default):\n+    def configure(self, prompt, default, parent, section_name):\n         if self.parse == _parse_boolean:\n             prompt += ' (y/n)'\n             default = 'y' if default else 'n'\n@@ -233,7 +233,7 @@\n             raise ValueError('ListAttribute value must be a list.')\n         return ','.join(value)\n \n-    def configure(self, prompt, default):\n+    def configure(self, prompt, default, parent, section_name):\n         each_prompt = '?'\n         if isinstance(prompt, tuple):\n             each_prompt = prompt[1]\n@@ -311,6 +311,17 @@\n         value = self.serialize(main_config, this_section, value)\n         instance._parser.set(instance._section_name, self.name, value)\n \n+    def configure(self, prompt, default, parent, section_name):\n+        \"\"\"With the prompt and default, parse and return a value from terminal.\n+        \"\"\"\n+        if default is not NO_DEFAULT and default is not None:\n+            prompt = '{} [{}]'.format(prompt, default)\n+        value = get_input(prompt + ' ')\n+        if not value and default is NO_DEFAULT:\n+            raise ValueError(\"You must provide a value for this option.\")\n+        value = value or default\n+        return self.parse(parent, section_name, value)\n+\n     def parse(self, main_config, this_section, value):\n         if value is None:\n             return\n", "issue": "[config] FilenameAttribute throws exceptions when configuring\nhttps://github.com/sopel-irc/sopel/blob/master/sopel/config/types.py#L314-L340\n\nSerialize and parse require extra arguments that they are not passed during `sopel --configure-modules`\n\n", "before_files": [{"content": "# coding=utf8\n\"\"\"Types for creating section definitions.\n\nA section definition consists of a subclass of ``StaticSection``, on which any\nnumber of subclasses of ``BaseValidated`` (a few common ones of which are\navailable in this module) are assigned as attributes. These descriptors define\nhow to read values from, and write values to, the config file.\n\nAs an example, if one wanted to define the ``[spam]`` section as having an\n``eggs`` option, which contains a list of values, they could do this:\n\n    >>> class SpamSection(StaticSection):\n    ...     eggs = ListAttribute('eggs')\n    ...\n    >>> SpamSection(config, 'spam')\n    >>> print(config.spam.eggs)\n    []\n    >>> config.spam.eggs = ['goose', 'turkey', 'duck', 'chicken', 'quail']\n    >>> print(config.spam.eggs)\n    ['goose', 'turkey', 'duck', 'chicken', 'quail']\n    >>> config.spam.eggs = 'herring'\n    Traceback (most recent call last):\n        ...\n    ValueError: ListAttribute value must be a list.\n\"\"\"\n\nfrom __future__ import unicode_literals\nimport os.path\nimport sys\nfrom sopel.tools import get_input\n\ntry:\n    import configparser\nexcept ImportError:\n    import ConfigParser as configparser\n\nif sys.version_info.major >= 3:\n    unicode = str\n    basestring = (str, bytes)\n\n\nclass NO_DEFAULT(object):\n    \"\"\"A special value to indicate that there should be no default.\"\"\"\n\n\nclass StaticSection(object):\n    \"\"\"A configuration section with parsed and validated settings.\n\n    This class is intended to be subclassed with added ``ValidatedAttribute``s.\n    \"\"\"\n    def __init__(self, config, section_name, validate=True):\n        if not config.parser.has_section(section_name):\n            config.parser.add_section(section_name)\n        self._parent = config\n        self._parser = config.parser\n        self._section_name = section_name\n        for value in dir(self):\n            try:\n                getattr(self, value)\n            except ValueError as e:\n                raise ValueError(\n                    'Invalid value for {}.{}: {}'.format(section_name, value,\n                                                         e.message)\n                )\n            except AttributeError:\n                if validate:\n                    raise ValueError(\n                        'Missing required value for {}.{}'.format(section_name,\n                                                                  value)\n                    )\n\n    def configure_setting(self, name, prompt, default=NO_DEFAULT):\n        \"\"\"Return a validated value for this attribute from the terminal.\n\n        ``prompt`` will be the docstring of the attribute if not given.\n\n        If ``default`` is passed, it will be used if no value is given by the\n        user. If it is not passed, the current value of the setting, or the\n        default value if it's unset, will be used. Note that if ``default`` is\n        passed, the current value of the setting will be ignored, even if it is\n        not the attribute's default.\n        \"\"\"\n        clazz = getattr(self.__class__, name)\n        if default is NO_DEFAULT:\n            try:\n                default = getattr(self, name)\n            except AttributeError:\n                pass\n            except ValueError:\n                print('The configured value for this option was invalid.')\n                if clazz.default is not NO_DEFAULT:\n                    default = clazz.default\n        while True:\n            try:\n                value = clazz.configure(prompt, default)\n            except ValueError as exc:\n                print(exc)\n            else:\n                break\n        setattr(self, name, value)\n\n\nclass BaseValidated(object):\n    \"\"\"The base type for a descriptor in a ``StaticSection``.\"\"\"\n    def __init__(self, name, default=None):\n        \"\"\"\n        ``name`` is the name of the setting in the section.\n        ``default`` is the value to be returned if the setting is not set. If\n        not given, AttributeError will be raised instead.\n        \"\"\"\n        self.name = name\n        self.default = default\n\n    def configure(self, prompt, default):\n        \"\"\"With the prompt and default, parse and return a value from terminal.\n        \"\"\"\n        if default is not NO_DEFAULT and default is not None:\n            prompt = '{} [{}]'.format(prompt, default)\n        value = get_input(prompt + ' ')\n        if not value and default is NO_DEFAULT:\n            raise ValueError(\"You must provide a value for this option.\")\n        value = value or default\n        return self.parse(value)\n\n    def serialize(self, value):\n        \"\"\"Take some object, and return the string to be saved to the file.\n\n        Must be implemented in subclasses.\n        \"\"\"\n        raise NotImplemented(\"Serialize method must be implemented in subclass\")\n\n    def parse(self, value):\n        \"\"\"Take a string from the file, and return the appropriate object.\n\n        Must be implemented in subclasses.\"\"\"\n        raise NotImplemented(\"Parse method must be implemented in subclass\")\n\n    def __get__(self, instance, owner=None):\n        if instance is None:\n            # If instance is None, we're getting from a section class, not an\n            # instance of a session class. It makes the wizard code simpler\n            # (and is really just more intuitive) to return the descriptor\n            # instance here.\n            return self\n\n        try:\n            value = instance._parser.get(instance._section_name, self.name)\n        except configparser.NoOptionError:\n            if self.default is not NO_DEFAULT:\n                return self.default\n            raise AttributeError(\n                \"Missing required value for {}.{}\".format(\n                    instance._section_name, self.name\n                )\n            )\n        return self.parse(value)\n\n    def __set__(self, instance, value):\n        if value is None:\n            instance._parser.remove_option(instance._section_name, self.name)\n            return\n        value = self.serialize(value)\n        instance._parser.set(instance._section_name, self.name, value)\n\n    def __delete__(self, instance):\n        instance._parser.remove_option(instance._section_name, self.name)\n\n\ndef _parse_boolean(value):\n    if value is True or value == 1:\n        return value\n    if isinstance(value, basestring):\n        return value.lower() in ['1', 'yes', 'y', 'true', 'on']\n    return bool(value)\n\n\ndef _serialize_boolean(value):\n    return 'true' if _parse_boolean(value) else 'false'\n\n\nclass ValidatedAttribute(BaseValidated):\n    def __init__(self, name, parse=None, serialize=None, default=None):\n        \"\"\"A descriptor for settings in a ``StaticSection``\n\n        ``parse`` is the function to be used to read the string and create the\n        appropriate object. If not given, return the string as-is.\n        ``serialize`` takes an object, and returns the value to be written to\n        the file. If not given, defaults to ``unicode``.\n        \"\"\"\n        self.name = name\n        if parse == bool:\n            parse = _parse_boolean\n            if not serialize or serialize == bool:\n                serialize = _serialize_boolean\n        self.parse = parse or self.parse\n        self.serialize = serialize or self.serialize\n        self.default = default\n\n    def serialize(self, value):\n        return unicode(value)\n\n    def parse(self, value):\n        return value\n\n    def configure(self, prompt, default):\n        if self.parse == _parse_boolean:\n            prompt += ' (y/n)'\n            default = 'y' if default else 'n'\n        return super(ValidatedAttribute, self).configure(prompt, default)\n\n\nclass ListAttribute(BaseValidated):\n    \"\"\"A config attribute containing a list of string values.\n\n    Values are saved to the file as a comma-separated list. It does not\n    currently support commas within items in the list. By default, the spaces\n    before and after each item are stripped; you can override this by passing\n    ``strip=False``.\"\"\"\n    def __init__(self, name, strip=True, default=None):\n        default = default or []\n        super(ListAttribute, self).__init__(name, default=default)\n        self.strip = strip\n\n    def parse(self, value):\n        value = value.split(',')\n        if self.strip:\n            return [v.strip() for v in value]\n        else:\n            return value\n\n    def serialize(self, value):\n        if not isinstance(value, (list, set)):\n            raise ValueError('ListAttribute value must be a list.')\n        return ','.join(value)\n\n    def configure(self, prompt, default):\n        each_prompt = '?'\n        if isinstance(prompt, tuple):\n            each_prompt = prompt[1]\n            prompt = prompt[0]\n\n        if default is not NO_DEFAULT:\n            default = ','.join(default)\n            prompt = '{} [{}]'.format(prompt, default)\n        else:\n            default = ''\n        print(prompt)\n        values = []\n        value = get_input(each_prompt + ' ') or default\n        while value:\n            values.append(value)\n            value = get_input(each_prompt + ' ')\n        return self.parse(','.join(values))\n\n\nclass ChoiceAttribute(BaseValidated):\n    \"\"\"A config attribute which must be one of a set group of options.\n\n    Currently, the choices can only be strings.\"\"\"\n    def __init__(self, name, choices, default=None):\n        super(ChoiceAttribute, self).__init__(name, default=default)\n        self.choices = choices\n\n    def parse(self, value):\n        if value in self.choices:\n            return value\n        else:\n            raise ValueError('Value must be in {}'.format(self.choices))\n\n    def serialize(self, value):\n        if value in self.choices:\n            return value\n        else:\n            raise ValueError('Value must be in {}'.format(self.choices))\n\n\nclass FilenameAttribute(BaseValidated):\n    \"\"\"A config attribute which must be a file or directory.\"\"\"\n    def __init__(self, name, relative=True, directory=False, default=None):\n        \"\"\"\n        ``relative`` is whether the path should be relative to the location\n        of the config file (absolute paths will still be absolute). If\n        ``directory`` is True, the path must indicate a directory, rather than\n        a file.\n        \"\"\"\n        super(FilenameAttribute, self).__init__(name, default=default)\n        self.relative = relative\n        self.directory = directory\n\n    def __get__(self, instance, owner=None):\n        if instance is None:\n            return self\n        try:\n            value = instance._parser.get(instance._section_name, self.name)\n        except configparser.NoOptionError:\n            if self.default is not NO_DEFAULT:\n                value = self.default\n            else:\n                raise AttributeError(\n                    \"Missing required value for {}.{}\".format(\n                        instance._section_name, self.name\n                    )\n                )\n        main_config = instance._parent\n        this_section = getattr(main_config, instance._section_name)\n        return self.parse(main_config, this_section, value)\n\n    def __set__(self, instance, value):\n        main_config = instance._parent\n        this_section = getattr(main_config, instance._section_name)\n        value = self.serialize(main_config, this_section, value)\n        instance._parser.set(instance._section_name, self.name, value)\n\n    def parse(self, main_config, this_section, value):\n        if value is None:\n            return\n\n        value = os.path.expanduser(value)\n\n        if not os.path.isabs(value):\n            if not self.relative:\n                raise ValueError(\"Value must be an absolute path.\")\n            value = os.path.join(main_config.homedir, value)\n\n        if self.directory and not os.path.isdir(value):\n            try:\n                os.makedirs(value)\n            except OSError:\n                raise ValueError(\n                    \"Value must be an existing or creatable directory.\")\n        if not self.directory and not os.path.isfile(value):\n            try:\n                open(value, 'w').close()\n            except OSError:\n                raise ValueError(\"Value must be an existant or creatable file.\")\n        return value\n\n    def serialize(self, main_config, this_section, value):\n        self.parse(main_config, this_section, value)\n        return value  # So that it's still relative\n", "path": "sopel/config/types.py"}], "after_files": [{"content": "# coding=utf8\n\"\"\"Types for creating section definitions.\n\nA section definition consists of a subclass of ``StaticSection``, on which any\nnumber of subclasses of ``BaseValidated`` (a few common ones of which are\navailable in this module) are assigned as attributes. These descriptors define\nhow to read values from, and write values to, the config file.\n\nAs an example, if one wanted to define the ``[spam]`` section as having an\n``eggs`` option, which contains a list of values, they could do this:\n\n    >>> class SpamSection(StaticSection):\n    ...     eggs = ListAttribute('eggs')\n    ...\n    >>> SpamSection(config, 'spam')\n    >>> print(config.spam.eggs)\n    []\n    >>> config.spam.eggs = ['goose', 'turkey', 'duck', 'chicken', 'quail']\n    >>> print(config.spam.eggs)\n    ['goose', 'turkey', 'duck', 'chicken', 'quail']\n    >>> config.spam.eggs = 'herring'\n    Traceback (most recent call last):\n        ...\n    ValueError: ListAttribute value must be a list.\n\"\"\"\n\nfrom __future__ import unicode_literals\nimport os.path\nimport sys\nfrom sopel.tools import get_input\n\ntry:\n    import configparser\nexcept ImportError:\n    import ConfigParser as configparser\n\nif sys.version_info.major >= 3:\n    unicode = str\n    basestring = (str, bytes)\n\n\nclass NO_DEFAULT(object):\n    \"\"\"A special value to indicate that there should be no default.\"\"\"\n\n\nclass StaticSection(object):\n    \"\"\"A configuration section with parsed and validated settings.\n\n    This class is intended to be subclassed with added ``ValidatedAttribute``s.\n    \"\"\"\n    def __init__(self, config, section_name, validate=True):\n        if not config.parser.has_section(section_name):\n            config.parser.add_section(section_name)\n        self._parent = config\n        self._parser = config.parser\n        self._section_name = section_name\n        for value in dir(self):\n            try:\n                getattr(self, value)\n            except ValueError as e:\n                raise ValueError(\n                    'Invalid value for {}.{}: {}'.format(section_name, value,\n                                                         e.message)\n                )\n            except AttributeError:\n                if validate:\n                    raise ValueError(\n                        'Missing required value for {}.{}'.format(section_name,\n                                                                  value)\n                    )\n\n    def configure_setting(self, name, prompt, default=NO_DEFAULT):\n        \"\"\"Return a validated value for this attribute from the terminal.\n\n        ``prompt`` will be the docstring of the attribute if not given.\n\n        If ``default`` is passed, it will be used if no value is given by the\n        user. If it is not passed, the current value of the setting, or the\n        default value if it's unset, will be used. Note that if ``default`` is\n        passed, the current value of the setting will be ignored, even if it is\n        not the attribute's default.\n        \"\"\"\n        clazz = getattr(self.__class__, name)\n        if default is NO_DEFAULT:\n            try:\n                default = getattr(self, name)\n            except AttributeError:\n                pass\n            except ValueError:\n                print('The configured value for this option was invalid.')\n                if clazz.default is not NO_DEFAULT:\n                    default = clazz.default\n        while True:\n            try:\n                value = clazz.configure(prompt, default, self._parent, self._section_name)\n            except ValueError as exc:\n                print(exc)\n            else:\n                break\n        setattr(self, name, value)\n\n\nclass BaseValidated(object):\n    \"\"\"The base type for a descriptor in a ``StaticSection``.\"\"\"\n    def __init__(self, name, default=None):\n        \"\"\"\n        ``name`` is the name of the setting in the section.\n        ``default`` is the value to be returned if the setting is not set. If\n        not given, AttributeError will be raised instead.\n        \"\"\"\n        self.name = name\n        self.default = default\n\n    def configure(self, prompt, default, parent, section_name):\n        \"\"\"With the prompt and default, parse and return a value from terminal.\n        \"\"\"\n        if default is not NO_DEFAULT and default is not None:\n            prompt = '{} [{}]'.format(prompt, default)\n        value = get_input(prompt + ' ')\n        if not value and default is NO_DEFAULT:\n            raise ValueError(\"You must provide a value for this option.\")\n        value = value or default\n        return self.parse(value)\n\n    def serialize(self, value):\n        \"\"\"Take some object, and return the string to be saved to the file.\n\n        Must be implemented in subclasses.\n        \"\"\"\n        raise NotImplemented(\"Serialize method must be implemented in subclass\")\n\n    def parse(self, value):\n        \"\"\"Take a string from the file, and return the appropriate object.\n\n        Must be implemented in subclasses.\"\"\"\n        raise NotImplemented(\"Parse method must be implemented in subclass\")\n\n    def __get__(self, instance, owner=None):\n        if instance is None:\n            # If instance is None, we're getting from a section class, not an\n            # instance of a session class. It makes the wizard code simpler\n            # (and is really just more intuitive) to return the descriptor\n            # instance here.\n            return self\n\n        try:\n            value = instance._parser.get(instance._section_name, self.name)\n        except configparser.NoOptionError:\n            if self.default is not NO_DEFAULT:\n                return self.default\n            raise AttributeError(\n                \"Missing required value for {}.{}\".format(\n                    instance._section_name, self.name\n                )\n            )\n        return self.parse(value)\n\n    def __set__(self, instance, value):\n        if value is None:\n            instance._parser.remove_option(instance._section_name, self.name)\n            return\n        value = self.serialize(value)\n        instance._parser.set(instance._section_name, self.name, value)\n\n    def __delete__(self, instance):\n        instance._parser.remove_option(instance._section_name, self.name)\n\n\ndef _parse_boolean(value):\n    if value is True or value == 1:\n        return value\n    if isinstance(value, basestring):\n        return value.lower() in ['1', 'yes', 'y', 'true', 'on']\n    return bool(value)\n\n\ndef _serialize_boolean(value):\n    return 'true' if _parse_boolean(value) else 'false'\n\n\nclass ValidatedAttribute(BaseValidated):\n    def __init__(self, name, parse=None, serialize=None, default=None):\n        \"\"\"A descriptor for settings in a ``StaticSection``\n\n        ``parse`` is the function to be used to read the string and create the\n        appropriate object. If not given, return the string as-is.\n        ``serialize`` takes an object, and returns the value to be written to\n        the file. If not given, defaults to ``unicode``.\n        \"\"\"\n        self.name = name\n        if parse == bool:\n            parse = _parse_boolean\n            if not serialize or serialize == bool:\n                serialize = _serialize_boolean\n        self.parse = parse or self.parse\n        self.serialize = serialize or self.serialize\n        self.default = default\n\n    def serialize(self, value):\n        return unicode(value)\n\n    def parse(self, value):\n        return value\n\n    def configure(self, prompt, default, parent, section_name):\n        if self.parse == _parse_boolean:\n            prompt += ' (y/n)'\n            default = 'y' if default else 'n'\n        return super(ValidatedAttribute, self).configure(prompt, default)\n\n\nclass ListAttribute(BaseValidated):\n    \"\"\"A config attribute containing a list of string values.\n\n    Values are saved to the file as a comma-separated list. It does not\n    currently support commas within items in the list. By default, the spaces\n    before and after each item are stripped; you can override this by passing\n    ``strip=False``.\"\"\"\n    def __init__(self, name, strip=True, default=None):\n        default = default or []\n        super(ListAttribute, self).__init__(name, default=default)\n        self.strip = strip\n\n    def parse(self, value):\n        value = value.split(',')\n        if self.strip:\n            return [v.strip() for v in value]\n        else:\n            return value\n\n    def serialize(self, value):\n        if not isinstance(value, (list, set)):\n            raise ValueError('ListAttribute value must be a list.')\n        return ','.join(value)\n\n    def configure(self, prompt, default, parent, section_name):\n        each_prompt = '?'\n        if isinstance(prompt, tuple):\n            each_prompt = prompt[1]\n            prompt = prompt[0]\n\n        if default is not NO_DEFAULT:\n            default = ','.join(default)\n            prompt = '{} [{}]'.format(prompt, default)\n        else:\n            default = ''\n        print(prompt)\n        values = []\n        value = get_input(each_prompt + ' ') or default\n        while value:\n            values.append(value)\n            value = get_input(each_prompt + ' ')\n        return self.parse(','.join(values))\n\n\nclass ChoiceAttribute(BaseValidated):\n    \"\"\"A config attribute which must be one of a set group of options.\n\n    Currently, the choices can only be strings.\"\"\"\n    def __init__(self, name, choices, default=None):\n        super(ChoiceAttribute, self).__init__(name, default=default)\n        self.choices = choices\n\n    def parse(self, value):\n        if value in self.choices:\n            return value\n        else:\n            raise ValueError('Value must be in {}'.format(self.choices))\n\n    def serialize(self, value):\n        if value in self.choices:\n            return value\n        else:\n            raise ValueError('Value must be in {}'.format(self.choices))\n\n\nclass FilenameAttribute(BaseValidated):\n    \"\"\"A config attribute which must be a file or directory.\"\"\"\n    def __init__(self, name, relative=True, directory=False, default=None):\n        \"\"\"\n        ``relative`` is whether the path should be relative to the location\n        of the config file (absolute paths will still be absolute). If\n        ``directory`` is True, the path must indicate a directory, rather than\n        a file.\n        \"\"\"\n        super(FilenameAttribute, self).__init__(name, default=default)\n        self.relative = relative\n        self.directory = directory\n\n    def __get__(self, instance, owner=None):\n        if instance is None:\n            return self\n        try:\n            value = instance._parser.get(instance._section_name, self.name)\n        except configparser.NoOptionError:\n            if self.default is not NO_DEFAULT:\n                value = self.default\n            else:\n                raise AttributeError(\n                    \"Missing required value for {}.{}\".format(\n                        instance._section_name, self.name\n                    )\n                )\n        main_config = instance._parent\n        this_section = getattr(main_config, instance._section_name)\n        return self.parse(main_config, this_section, value)\n\n    def __set__(self, instance, value):\n        main_config = instance._parent\n        this_section = getattr(main_config, instance._section_name)\n        value = self.serialize(main_config, this_section, value)\n        instance._parser.set(instance._section_name, self.name, value)\n\n    def configure(self, prompt, default, parent, section_name):\n        \"\"\"With the prompt and default, parse and return a value from terminal.\n        \"\"\"\n        if default is not NO_DEFAULT and default is not None:\n            prompt = '{} [{}]'.format(prompt, default)\n        value = get_input(prompt + ' ')\n        if not value and default is NO_DEFAULT:\n            raise ValueError(\"You must provide a value for this option.\")\n        value = value or default\n        return self.parse(parent, section_name, value)\n\n    def parse(self, main_config, this_section, value):\n        if value is None:\n            return\n\n        value = os.path.expanduser(value)\n\n        if not os.path.isabs(value):\n            if not self.relative:\n                raise ValueError(\"Value must be an absolute path.\")\n            value = os.path.join(main_config.homedir, value)\n\n        if self.directory and not os.path.isdir(value):\n            try:\n                os.makedirs(value)\n            except OSError:\n                raise ValueError(\n                    \"Value must be an existing or creatable directory.\")\n        if not self.directory and not os.path.isfile(value):\n            try:\n                open(value, 'w').close()\n            except OSError:\n                raise ValueError(\"Value must be an existant or creatable file.\")\n        return value\n\n    def serialize(self, main_config, this_section, value):\n        self.parse(main_config, this_section, value)\n        return value  # So that it's still relative\n", "path": "sopel/config/types.py"}]}