prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Mutli-label text classification export issues: same classes but in different orders
  How to reproduce the behaviour
  ---------
  <!-- Before submitting an issue, make sure to check the docs and closed issues and FAQ to see if any of the solutions work for you. https://github.com/doccano/doccano/wiki/Frequently-Asked-Questions -->
  We are two annotators on a multi-label classification project. When I export the annotations, for some examples, me and my co-annotator have put the same labels, but on the exported CSV, they do not appear in the same order:
  
  Annotator 1:
  
  | text | labels |
  | example 1 | label1#label2#label3 |
  
  Annotator 2:
  
  | text | labels |
  | example 1 | label2#label3#label1 |
  
  As I try to use these CSVs for comparing our annotations, this brings more difficulty.
  
  <!-- Include a code example or the steps that led to the problem. Please try to be as specific as possible. -->
  
  Your Environment
  ---------
  <!-- Include details of your environment.-->
  *   Operating System: Debian
  *   Python Version Used: Don't know, I pulled the latest version from Docker Hub
  *   When you install doccano: 3 days ago
  *   How did you install doccano (Heroku button etc): Docker
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `backend/api/views/download/writer.py`
  Content:
  ```
  1 import abc
  2 import csv
  3 import itertools
  4 import json
  5 import os
  6 import uuid
  7 import zipfile
  8 from collections import defaultdict
  9 from typing import Dict, Iterable, Iterator, List
  10 
  11 from .data import Record
  12 
  13 
  14 class BaseWriter:
  15 
  16     def __init__(self, tmpdir: str):
  17         self.tmpdir = tmpdir
  18 
  19     @abc.abstractmethod
  20     def write(self, records: Iterator[Record]) -> str:
  21         raise NotImplementedError()
  22 
  23     def write_zip(self, filenames: Iterable):
  24         save_file = '{}.zip'.format(os.path.join(self.tmpdir, str(uuid.uuid4())))
  25         with zipfile.ZipFile(save_file, 'w', compression=zipfile.ZIP_DEFLATED) as zf:
  26             for file in filenames:
  27                 zf.write(filename=file, arcname=os.path.basename(file))
  28         return save_file
  29 
  30 
  31 class LineWriter(BaseWriter):
  32     extension = 'txt'
  33 
  34     def write(self, records: Iterator[Record]) -> str:
  35         files = {}
  36         for record in records:
  37             filename = os.path.join(self.tmpdir, f'{record.user}.{self.extension}')
  38             if filename not in files:
  39                 f = open(filename, mode='a')
  40                 files[filename] = f
  41             f = files[filename]
  42             line = self.create_line(record)
  43             f.write(f'{line}\n')
  44         for f in files.values():
  45             f.close()
  46         save_file = self.write_zip(files)
  47         for file in files:
  48             os.remove(file)
  49         return save_file
  50 
  51     @abc.abstractmethod
  52     def create_line(self, record) -> str:
  53         raise NotImplementedError()
  54 
  55 
  56 class CsvWriter(BaseWriter):
  57     extension = 'csv'
  58 
  59     def write(self, records: Iterator[Record]) -> str:
  60         writers = {}
  61         file_handlers = set()
  62         records = list(records)
  63         header = self.create_header(records)
  64         for record in records:
  65             filename = os.path.join(self.tmpdir, f'{record.user}.{self.extension}')
  66             if filename not in writers:
  67                 f = open(filename, mode='a', encoding='utf-8')
  68                 writer = csv.DictWriter(f, header)
  69                 writer.writeheader()
  70                 writers[filename] = writer
  71                 file_handlers.add(f)
  72             writer = writers[filename]
  73             line = self.create_line(record)
  74             writer.writerow(line)
  75 
  76         for f in file_handlers:
  77             f.close()
  78         save_file = self.write_zip(writers)
  79         for file in writers:
  80             os.remove(file)
  81         return save_file
  82 
  83     def create_line(self, record) -> Dict:
  84         return {
  85             'id': record.id,
  86             'data': record.data,
  87             'label': '#'.join(record.label),
  88             **record.metadata
  89         }
  90 
  91     def create_header(self, records: List[Record]) -> Iterable[str]:
  92         header = ['id', 'data', 'label']
  93         header += sorted(set(itertools.chain(*[r.metadata.keys() for r in records])))
  94         return header
  95 
  96 
  97 class JSONWriter(BaseWriter):
  98     extension = 'json'
  99 
  100     def write(self, records: Iterator[Record]) -> str:
  101         writers = {}
  102         contents = defaultdict(list)
  103         for record in records:
  104             filename = os.path.join(self.tmpdir, f'{record.user}.{self.extension}')
  105             if filename not in writers:
  106                 f = open(filename, mode='a', encoding='utf-8')
  107                 writers[filename] = f
  108             line = self.create_line(record)
  109             contents[filename].append(line)
  110 
  111         for filename, f in writers.items():
  112             content = contents[filename]
  113             json.dump(content, f, ensure_ascii=False)
  114             f.close()
  115 
  116         save_file = self.write_zip(writers)
  117         for file in writers:
  118             os.remove(file)
  119         return save_file
  120 
  121     def create_line(self, record) -> Dict:
  122         return {
  123             'id': record.id,
  124             'data': record.data,
  125             'label': record.label,
  126             **record.metadata
  127         }
  128 
  129 
  130 class JSONLWriter(LineWriter):
  131     extension = 'jsonl'
  132 
  133     def create_line(self, record):
  134         return json.dumps({
  135             'id': record.id,
  136             'data': record.data,
  137             'label': record.label,
  138             **record.metadata
  139         }, ensure_ascii=False)
  140 
  141 
  142 class FastTextWriter(LineWriter):
  143     extension = 'txt'
  144 
  145     def create_line(self, record):
  146         line = [f'__label__{label}' for label in record.label]
  147         line.append(record.data)
  148         line = ' '.join(line)
  149         return line
  150
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/backend/api/views/download/writer.py b/backend/api/views/download/writer.py
  --- a/backend/api/views/download/writer.py
  +++ b/backend/api/views/download/writer.py
  @@ -84,7 +84,7 @@
           return {
               'id': record.id,
               'data': record.data,
  -            'label': '#'.join(record.label),
  +            'label': '#'.join(sorted(record.label)),
               **record.metadata
           }
   
  @@ -144,6 +144,7 @@
   
       def create_line(self, record):
           line = [f'__label__{label}' for label in record.label]
  +        line.sort()
           line.append(record.data)
           line = ' '.join(line)
           return line
  
verification_info: {"golden_diff": "diff --git a/backend/api/views/download/writer.py b/backend/api/views/download/writer.py\n--- a/backend/api/views/download/writer.py\n+++ b/backend/api/views/download/writer.py\n@@ -84,7 +84,7 @@\n         return {\n             'id': record.id,\n             'data': record.data,\n-            'label': '#'.join(record.label),\n+            'label': '#'.join(sorted(record.label)),\n             **record.metadata\n         }\n \n@@ -144,6 +144,7 @@\n \n     def create_line(self, record):\n         line = [f'__label__{label}' for label in record.label]\n+        line.sort()\n         line.append(record.data)\n         line = ' '.join(line)\n         return line\n", "issue": "Mutli-label text classification export issues: same classes but in different orders\nHow to reproduce the behaviour\r\n---------\r\n<!-- Before submitting an issue, make sure to check the docs and closed issues and FAQ to see if any of the solutions work for you. https://github.com/doccano/doccano/wiki/Frequently-Asked-Questions -->\r\nWe are two annotators on a multi-label classification project. When I export the annotations, for some examples, me and my co-annotator have put the same labels, but on the exported CSV, they do not appear in the same order:\r\n\r\nAnnotator 1:\r\n\r\n| text | labels |\r\n| example 1 | label1#label2#label3 |\r\n\r\nAnnotator 2:\r\n\r\n| text | labels |\r\n| example 1 | label2#label3#label1 |\r\n\r\nAs I try to use these CSVs for comparing our annotations, this brings more difficulty.\r\n\r\n<!-- Include a code example or the steps that led to the problem. Please try to be as specific as possible. -->\r\n\r\nYour Environment\r\n---------\r\n<!-- Include details of your environment.-->\r\n*   Operating System: Debian\r\n*   Python Version Used: Don't know, I pulled the latest version from Docker Hub\r\n*   When you install doccano: 3 days ago\r\n*   How did you install doccano (Heroku button etc): Docker\r\n\n", "before_files": [{"content": "import abc\nimport csv\nimport itertools\nimport json\nimport os\nimport uuid\nimport zipfile\nfrom collections import defaultdict\nfrom typing import Dict, Iterable, Iterator, List\n\nfrom .data import Record\n\n\nclass BaseWriter:\n\n    def __init__(self, tmpdir: str):\n        self.tmpdir = tmpdir\n\n    @abc.abstractmethod\n    def write(self, records: Iterator[Record]) -> str:\n        raise NotImplementedError()\n\n    def write_zip(self, filenames: Iterable):\n        save_file = '{}.zip'.format(os.path.join(self.tmpdir, str(uuid.uuid4())))\n        with zipfile.ZipFile(save_file, 'w', compression=zipfile.ZIP_DEFLATED) as zf:\n            for file in filenames:\n                zf.write(filename=file, arcname=os.path.basename(file))\n        return save_file\n\n\nclass LineWriter(BaseWriter):\n    extension = 'txt'\n\n    def write(self, records: Iterator[Record]) -> str:\n        files = {}\n        for record in records:\n            filename = os.path.join(self.tmpdir, f'{record.user}.{self.extension}')\n            if filename not in files:\n                f = open(filename, mode='a')\n                files[filename] = f\n            f = files[filename]\n            line = self.create_line(record)\n            f.write(f'{line}\\n')\n        for f in files.values():\n            f.close()\n        save_file = self.write_zip(files)\n        for file in files:\n            os.remove(file)\n        return save_file\n\n    @abc.abstractmethod\n    def create_line(self, record) -> str:\n        raise NotImplementedError()\n\n\nclass CsvWriter(BaseWriter):\n    extension = 'csv'\n\n    def write(self, records: Iterator[Record]) -> str:\n        writers = {}\n        file_handlers = set()\n        records = list(records)\n        header = self.create_header(records)\n        for record in records:\n            filename = os.path.join(self.tmpdir, f'{record.user}.{self.extension}')\n            if filename not in writers:\n                f = open(filename, mode='a', encoding='utf-8')\n                writer = csv.DictWriter(f, header)\n                writer.writeheader()\n                writers[filename] = writer\n                file_handlers.add(f)\n            writer = writers[filename]\n            line = self.create_line(record)\n            writer.writerow(line)\n\n        for f in file_handlers:\n            f.close()\n        save_file = self.write_zip(writers)\n        for file in writers:\n            os.remove(file)\n        return save_file\n\n    def create_line(self, record) -> Dict:\n        return {\n            'id': record.id,\n            'data': record.data,\n            'label': '#'.join(record.label),\n            **record.metadata\n        }\n\n    def create_header(self, records: List[Record]) -> Iterable[str]:\n        header = ['id', 'data', 'label']\n        header += sorted(set(itertools.chain(*[r.metadata.keys() for r in records])))\n        return header\n\n\nclass JSONWriter(BaseWriter):\n    extension = 'json'\n\n    def write(self, records: Iterator[Record]) -> str:\n        writers = {}\n        contents = defaultdict(list)\n        for record in records:\n            filename = os.path.join(self.tmpdir, f'{record.user}.{self.extension}')\n            if filename not in writers:\n                f = open(filename, mode='a', encoding='utf-8')\n                writers[filename] = f\n            line = self.create_line(record)\n            contents[filename].append(line)\n\n        for filename, f in writers.items():\n            content = contents[filename]\n            json.dump(content, f, ensure_ascii=False)\n            f.close()\n\n        save_file = self.write_zip(writers)\n        for file in writers:\n            os.remove(file)\n        return save_file\n\n    def create_line(self, record) -> Dict:\n        return {\n            'id': record.id,\n            'data': record.data,\n            'label': record.label,\n            **record.metadata\n        }\n\n\nclass JSONLWriter(LineWriter):\n    extension = 'jsonl'\n\n    def create_line(self, record):\n        return json.dumps({\n            'id': record.id,\n            'data': record.data,\n            'label': record.label,\n            **record.metadata\n        }, ensure_ascii=False)\n\n\nclass FastTextWriter(LineWriter):\n    extension = 'txt'\n\n    def create_line(self, record):\n        line = [f'__label__{label}' for label in record.label]\n        line.append(record.data)\n        line = ' '.join(line)\n        return line\n", "path": "backend/api/views/download/writer.py"}], "after_files": [{"content": "import abc\nimport csv\nimport itertools\nimport json\nimport os\nimport uuid\nimport zipfile\nfrom collections import defaultdict\nfrom typing import Dict, Iterable, Iterator, List\n\nfrom .data import Record\n\n\nclass BaseWriter:\n\n    def __init__(self, tmpdir: str):\n        self.tmpdir = tmpdir\n\n    @abc.abstractmethod\n    def write(self, records: Iterator[Record]) -> str:\n        raise NotImplementedError()\n\n    def write_zip(self, filenames: Iterable):\n        save_file = '{}.zip'.format(os.path.join(self.tmpdir, str(uuid.uuid4())))\n        with zipfile.ZipFile(save_file, 'w', compression=zipfile.ZIP_DEFLATED) as zf:\n            for file in filenames:\n                zf.write(filename=file, arcname=os.path.basename(file))\n        return save_file\n\n\nclass LineWriter(BaseWriter):\n    extension = 'txt'\n\n    def write(self, records: Iterator[Record]) -> str:\n        files = {}\n        for record in records:\n            filename = os.path.join(self.tmpdir, f'{record.user}.{self.extension}')\n            if filename not in files:\n                f = open(filename, mode='a')\n                files[filename] = f\n            f = files[filename]\n            line = self.create_line(record)\n            f.write(f'{line}\\n')\n        for f in files.values():\n            f.close()\n        save_file = self.write_zip(files)\n        for file in files:\n            os.remove(file)\n        return save_file\n\n    @abc.abstractmethod\n    def create_line(self, record) -> str:\n        raise NotImplementedError()\n\n\nclass CsvWriter(BaseWriter):\n    extension = 'csv'\n\n    def write(self, records: Iterator[Record]) -> str:\n        writers = {}\n        file_handlers = set()\n        records = list(records)\n        header = self.create_header(records)\n        for record in records:\n            filename = os.path.join(self.tmpdir, f'{record.user}.{self.extension}')\n            if filename not in writers:\n                f = open(filename, mode='a', encoding='utf-8')\n                writer = csv.DictWriter(f, header)\n                writer.writeheader()\n                writers[filename] = writer\n                file_handlers.add(f)\n            writer = writers[filename]\n            line = self.create_line(record)\n            writer.writerow(line)\n\n        for f in file_handlers:\n            f.close()\n        save_file = self.write_zip(writers)\n        for file in writers:\n            os.remove(file)\n        return save_file\n\n    def create_line(self, record) -> Dict:\n        return {\n            'id': record.id,\n            'data': record.data,\n            'label': '#'.join(sorted(record.label)),\n            **record.metadata\n        }\n\n    def create_header(self, records: List[Record]) -> Iterable[str]:\n        header = ['id', 'data', 'label']\n        header += sorted(set(itertools.chain(*[r.metadata.keys() for r in records])))\n        return header\n\n\nclass JSONWriter(BaseWriter):\n    extension = 'json'\n\n    def write(self, records: Iterator[Record]) -> str:\n        writers = {}\n        contents = defaultdict(list)\n        for record in records:\n            filename = os.path.join(self.tmpdir, f'{record.user}.{self.extension}')\n            if filename not in writers:\n                f = open(filename, mode='a', encoding='utf-8')\n                writers[filename] = f\n            line = self.create_line(record)\n            contents[filename].append(line)\n\n        for filename, f in writers.items():\n            content = contents[filename]\n            json.dump(content, f, ensure_ascii=False)\n            f.close()\n\n        save_file = self.write_zip(writers)\n        for file in writers:\n            os.remove(file)\n        return save_file\n\n    def create_line(self, record) -> Dict:\n        return {\n            'id': record.id,\n            'data': record.data,\n            'label': record.label,\n            **record.metadata\n        }\n\n\nclass JSONLWriter(LineWriter):\n    extension = 'jsonl'\n\n    def create_line(self, record):\n        return json.dumps({\n            'id': record.id,\n            'data': record.data,\n            'label': record.label,\n            **record.metadata\n        }, ensure_ascii=False)\n\n\nclass FastTextWriter(LineWriter):\n    extension = 'txt'\n\n    def create_line(self, record):\n        line = [f'__label__{label}' for label in record.label]\n        line.sort()\n        line.append(record.data)\n        line = ' '.join(line)\n        return line\n", "path": "backend/api/views/download/writer.py"}]}