We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
upload command on windows: backslashes on server, invalid dataset
A user created a valid dataset on a windows machine with the `webknossos convert` command, then called `webknossos upload` with a valid token. The upload went through, but the directory structure got lost: the files on the server had backslashes in the paths, like `'color\2-2-1\z0\y7\x1.wkw'`. Instead, when sending files to upload, the client should always replace the client’s path separator by `/`.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `webknossos/webknossos/client/_upload_dataset.py`
Content:
```
1 import os
2 import warnings
3 from functools import lru_cache
4 from pathlib import Path
5 from tempfile import TemporaryDirectory
6 from time import gmtime, strftime
7 from typing import Iterator, List, NamedTuple, Optional, Tuple
8 from uuid import uuid4
9 
10 import httpx
11 
12 from ..dataset import Dataset, Layer, RemoteDataset
13 from ..utils import get_rich_progress
14 from ._resumable import Resumable
15 from .api_client.models import (
16     ApiDatasetUploadInformation,
17     ApiLinkedLayerIdentifier,
18     ApiReserveDatasetUploadInformation,
19 )
20 from .context import _get_context, _WebknossosContext
21 
22 DEFAULT_SIMULTANEOUS_UPLOADS = 5
23 MAXIMUM_RETRY_COUNT = 4
24 
25 
26 class LayerToLink(NamedTuple):
27     dataset_name: str
28     layer_name: str
29     new_layer_name: Optional[str] = None
30     organization_id: Optional[str] = (
31         None  # defaults to the user's organization before uploading
32     )
33 
34     @classmethod
35     def from_remote_layer(
36         cls,
37         layer: Layer,
38         new_layer_name: Optional[str] = None,
39         organization_id: Optional[str] = None,
40     ) -> "LayerToLink":
41         ds = layer.dataset
42         assert isinstance(
43             ds, RemoteDataset
44         ), f"The passed layer must belong to a RemoteDataset, but belongs to {ds}"
45         return cls(ds._dataset_name, layer.name, new_layer_name, organization_id)
46 
47     def as_api_linked_layer_identifier(self) -> ApiLinkedLayerIdentifier:
48         context = _get_context()
49         return ApiLinkedLayerIdentifier(
50             self.organization_id or context.organization_id,
51             self.dataset_name,
52             self.layer_name,
53             self.new_layer_name,
54         )
55 
56 
57 @lru_cache(maxsize=None)
58 def _cached_get_upload_datastore(context: _WebknossosContext) -> str:
59     datastores = context.api_client_with_auth.datastore_list()
60     for datastore in datastores:
61         if datastore.allows_upload:
62             return datastore.url
63     raise ValueError("No datastore found where datasets can be uploaded.")
64 
65 
66 def _walk(
67     path: Path,
68     base_path: Optional[Path] = None,
69 ) -> Iterator[Tuple[Path, Path, int]]:
70     if base_path is None:
71         base_path = path
72     if path.is_dir():
73         for p in path.iterdir():
74             yield from _walk(p, base_path)
75     else:
76         yield (path.resolve(), path.relative_to(base_path), path.stat().st_size)
77 
78 
79 def upload_dataset(
80     dataset: Dataset,
81     new_dataset_name: Optional[str] = None,
82     layers_to_link: Optional[List[LayerToLink]] = None,
83     jobs: Optional[int] = None,
84 ) -> str:
85     if new_dataset_name is None:
86         new_dataset_name = dataset.name
87     if layers_to_link is None:
88         layers_to_link = []
89     context = _get_context()
90     layer_names_to_link = set(i.new_layer_name or i.layer_name for i in layers_to_link)
91     if len(layer_names_to_link.intersection(dataset.layers.keys())) > 0:
92         warnings.warn(
93             "[INFO] Excluding the following layers from upload, since they will be linked: "
94             + f"{layer_names_to_link.intersection(dataset.layers.keys())}"
95         )
96         with TemporaryDirectory() as tmpdir:
97             tmp_ds = dataset.shallow_copy_dataset(
98                 tmpdir, name=dataset.name, layers_to_ignore=layer_names_to_link
99             )
100             return upload_dataset(
101                 tmp_ds,
102                 new_dataset_name=new_dataset_name,
103                 layers_to_link=layers_to_link,
104                 jobs=jobs,
105             )
106 
107     file_infos = list(_walk(dataset.path))
108     total_file_size = sum(size for _, _, size in file_infos)
109     # replicates https://github.com/scalableminds/webknossos/blob/master/frontend/javascripts/admin/dataset/dataset_upload_view.js
110     time_str = strftime("%Y-%m-%dT%H-%M-%S", gmtime())
111     upload_id = f"{time_str}__{uuid4()}"
112     datastore_token = context.datastore_required_token
113     datastore_url = _cached_get_upload_datastore(context)
114     datastore_api_client = context.get_datastore_api_client(datastore_url)
115     simultaneous_uploads = jobs if jobs is not None else DEFAULT_SIMULTANEOUS_UPLOADS
116     if "PYTEST_CURRENT_TEST" in os.environ:
117         simultaneous_uploads = 1
118     is_valid_new_name_response = context.api_client_with_auth.dataset_is_valid_new_name(
119         context.organization_id, new_dataset_name
120     )
121     if not is_valid_new_name_response.is_valid:
122         problems_str = ""
123         if is_valid_new_name_response.errors is not None:
124             problems_str = f" Problems: {is_valid_new_name_response.errors}"
125         raise Exception(
126             f"Dataset name {context.organization_id}/{new_dataset_name} is not a valid new dataset name.{problems_str}"
127         )
128 
129     datastore_api_client.dataset_reserve_upload(
130         ApiReserveDatasetUploadInformation(
131             upload_id,
132             new_dataset_name,
133             context.organization_id,
134             total_file_count=len(file_infos),
135             layers_to_link=[
136                 layer.as_api_linked_layer_identifier() for layer in layers_to_link
137             ],
138             folder_id=None,
139             initial_teams=[],
140         ),
141         token=datastore_token,
142         retry_count=MAXIMUM_RETRY_COUNT,
143     )
144     with get_rich_progress() as progress:
145         with Resumable(
146             f"{datastore_url}/data/datasets?token={datastore_token}",
147             simultaneous_uploads=simultaneous_uploads,
148             query={
149                 "owningOrganization": context.organization_id,
150                 "name": new_dataset_name,
151                 "totalFileCount": len(file_infos),
152             },
153             chunk_size=100 * 1024 * 1024,  # 100 MiB
154             generate_unique_identifier=lambda _,
155             relative_path: f"{upload_id}/{relative_path}",
156             test_chunks=False,
157             permanent_errors=[400, 403, 404, 409, 415, 500, 501],
158             client=httpx.Client(timeout=None),
159         ) as session:
160             progress_task = progress.add_task("Dataset Upload", total=total_file_size)
161             for file_path, relative_path, _ in file_infos:
162                 resumable_file = session.add_file(file_path, relative_path)
163                 resumable_file.chunk_completed.register(
164                     lambda chunk: progress.advance(progress_task, chunk.size)
165                 )
166 
167     datastore_api_client.dataset_finish_upload(
168         ApiDatasetUploadInformation(upload_id),
169         datastore_token,
170         retry_count=MAXIMUM_RETRY_COUNT,
171     )
172 
173     return new_dataset_name
174
```
--- 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/webknossos/webknossos/client/_upload_dataset.py b/webknossos/webknossos/client/_upload_dataset.py
--- a/webknossos/webknossos/client/_upload_dataset.py
+++ b/webknossos/webknossos/client/_upload_dataset.py
@@ -152,7 +152,7 @@
             },
             chunk_size=100 * 1024 * 1024,  # 100 MiB
             generate_unique_identifier=lambda _,
-            relative_path: f"{upload_id}/{relative_path}",
+            relative_path: f"{upload_id}/{relative_path.as_posix()}",
             test_chunks=False,
             permanent_errors=[400, 403, 404, 409, 415, 500, 501],
             client=httpx.Client(timeout=None),

{"golden_diff": "diff --git a/webknossos/webknossos/client/_upload_dataset.py b/webknossos/webknossos/client/_upload_dataset.py\n--- a/webknossos/webknossos/client/_upload_dataset.py\n+++ b/webknossos/webknossos/client/_upload_dataset.py\n@@ -152,7 +152,7 @@\n             },\n             chunk_size=100 * 1024 * 1024,  # 100 MiB\n             generate_unique_identifier=lambda _,\n-            relative_path: f\"{upload_id}/{relative_path}\",\n+            relative_path: f\"{upload_id}/{relative_path.as_posix()}\",\n             test_chunks=False,\n             permanent_errors=[400, 403, 404, 409, 415, 500, 501],\n             client=httpx.Client(timeout=None),\n", "issue": "upload command on windows: backslashes on server, invalid dataset\nA user created a valid dataset on a windows machine with the `webknossos convert` command, then called `webknossos upload` with a valid token. The upload went through, but the directory structure got lost: the files on the server had backslashes in the paths, like `'color\\2-2-1\\z0\\y7\\x1.wkw'`. Instead, when sending files to upload, the client should always replace the client\u2019s path separator by `/`.\n", "before_files": [{"content": "import os\nimport warnings\nfrom functools import lru_cache\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nfrom time import gmtime, strftime\nfrom typing import Iterator, List, NamedTuple, Optional, Tuple\nfrom uuid import uuid4\n\nimport httpx\n\nfrom ..dataset import Dataset, Layer, RemoteDataset\nfrom ..utils import get_rich_progress\nfrom ._resumable import Resumable\nfrom .api_client.models import (\n    ApiDatasetUploadInformation,\n    ApiLinkedLayerIdentifier,\n    ApiReserveDatasetUploadInformation,\n)\nfrom .context import _get_context, _WebknossosContext\n\nDEFAULT_SIMULTANEOUS_UPLOADS = 5\nMAXIMUM_RETRY_COUNT = 4\n\n\nclass LayerToLink(NamedTuple):\n    dataset_name: str\n    layer_name: str\n    new_layer_name: Optional[str] = None\n    organization_id: Optional[str] = (\n        None  # defaults to the user's organization before uploading\n    )\n\n    @classmethod\n    def from_remote_layer(\n        cls,\n        layer: Layer,\n        new_layer_name: Optional[str] = None,\n        organization_id: Optional[str] = None,\n    ) -> \"LayerToLink\":\n        ds = layer.dataset\n        assert isinstance(\n            ds, RemoteDataset\n        ), f\"The passed layer must belong to a RemoteDataset, but belongs to {ds}\"\n        return cls(ds._dataset_name, layer.name, new_layer_name, organization_id)\n\n    def as_api_linked_layer_identifier(self) -> ApiLinkedLayerIdentifier:\n        context = _get_context()\n        return ApiLinkedLayerIdentifier(\n            self.organization_id or context.organization_id,\n            self.dataset_name,\n            self.layer_name,\n            self.new_layer_name,\n        )\n\n\n@lru_cache(maxsize=None)\ndef _cached_get_upload_datastore(context: _WebknossosContext) -> str:\n    datastores = context.api_client_with_auth.datastore_list()\n    for datastore in datastores:\n        if datastore.allows_upload:\n            return datastore.url\n    raise ValueError(\"No datastore found where datasets can be uploaded.\")\n\n\ndef _walk(\n    path: Path,\n    base_path: Optional[Path] = None,\n) -> Iterator[Tuple[Path, Path, int]]:\n    if base_path is None:\n        base_path = path\n    if path.is_dir():\n        for p in path.iterdir():\n            yield from _walk(p, base_path)\n    else:\n        yield (path.resolve(), path.relative_to(base_path), path.stat().st_size)\n\n\ndef upload_dataset(\n    dataset: Dataset,\n    new_dataset_name: Optional[str] = None,\n    layers_to_link: Optional[List[LayerToLink]] = None,\n    jobs: Optional[int] = None,\n) -> str:\n    if new_dataset_name is None:\n        new_dataset_name = dataset.name\n    if layers_to_link is None:\n        layers_to_link = []\n    context = _get_context()\n    layer_names_to_link = set(i.new_layer_name or i.layer_name for i in layers_to_link)\n    if len(layer_names_to_link.intersection(dataset.layers.keys())) > 0:\n        warnings.warn(\n            \"[INFO] Excluding the following layers from upload, since they will be linked: \"\n            + f\"{layer_names_to_link.intersection(dataset.layers.keys())}\"\n        )\n        with TemporaryDirectory() as tmpdir:\n            tmp_ds = dataset.shallow_copy_dataset(\n                tmpdir, name=dataset.name, layers_to_ignore=layer_names_to_link\n            )\n            return upload_dataset(\n                tmp_ds,\n                new_dataset_name=new_dataset_name,\n                layers_to_link=layers_to_link,\n                jobs=jobs,\n            )\n\n    file_infos = list(_walk(dataset.path))\n    total_file_size = sum(size for _, _, size in file_infos)\n    # replicates https://github.com/scalableminds/webknossos/blob/master/frontend/javascripts/admin/dataset/dataset_upload_view.js\n    time_str = strftime(\"%Y-%m-%dT%H-%M-%S\", gmtime())\n    upload_id = f\"{time_str}__{uuid4()}\"\n    datastore_token = context.datastore_required_token\n    datastore_url = _cached_get_upload_datastore(context)\n    datastore_api_client = context.get_datastore_api_client(datastore_url)\n    simultaneous_uploads = jobs if jobs is not None else DEFAULT_SIMULTANEOUS_UPLOADS\n    if \"PYTEST_CURRENT_TEST\" in os.environ:\n        simultaneous_uploads = 1\n    is_valid_new_name_response = context.api_client_with_auth.dataset_is_valid_new_name(\n        context.organization_id, new_dataset_name\n    )\n    if not is_valid_new_name_response.is_valid:\n        problems_str = \"\"\n        if is_valid_new_name_response.errors is not None:\n            problems_str = f\" Problems: {is_valid_new_name_response.errors}\"\n        raise Exception(\n            f\"Dataset name {context.organization_id}/{new_dataset_name} is not a valid new dataset name.{problems_str}\"\n        )\n\n    datastore_api_client.dataset_reserve_upload(\n        ApiReserveDatasetUploadInformation(\n            upload_id,\n            new_dataset_name,\n            context.organization_id,\n            total_file_count=len(file_infos),\n            layers_to_link=[\n                layer.as_api_linked_layer_identifier() for layer in layers_to_link\n            ],\n            folder_id=None,\n            initial_teams=[],\n        ),\n        token=datastore_token,\n        retry_count=MAXIMUM_RETRY_COUNT,\n    )\n    with get_rich_progress() as progress:\n        with Resumable(\n            f\"{datastore_url}/data/datasets?token={datastore_token}\",\n            simultaneous_uploads=simultaneous_uploads,\n            query={\n                \"owningOrganization\": context.organization_id,\n                \"name\": new_dataset_name,\n                \"totalFileCount\": len(file_infos),\n            },\n            chunk_size=100 * 1024 * 1024,  # 100 MiB\n            generate_unique_identifier=lambda _,\n            relative_path: f\"{upload_id}/{relative_path}\",\n            test_chunks=False,\n            permanent_errors=[400, 403, 404, 409, 415, 500, 501],\n            client=httpx.Client(timeout=None),\n        ) as session:\n            progress_task = progress.add_task(\"Dataset Upload\", total=total_file_size)\n            for file_path, relative_path, _ in file_infos:\n                resumable_file = session.add_file(file_path, relative_path)\n                resumable_file.chunk_completed.register(\n                    lambda chunk: progress.advance(progress_task, chunk.size)\n                )\n\n    datastore_api_client.dataset_finish_upload(\n        ApiDatasetUploadInformation(upload_id),\n        datastore_token,\n        retry_count=MAXIMUM_RETRY_COUNT,\n    )\n\n    return new_dataset_name\n", "path": "webknossos/webknossos/client/_upload_dataset.py"}], "after_files": [{"content": "import os\nimport warnings\nfrom functools import lru_cache\nfrom pathlib import Path\nfrom tempfile import TemporaryDirectory\nfrom time import gmtime, strftime\nfrom typing import Iterator, List, NamedTuple, Optional, Tuple\nfrom uuid import uuid4\n\nimport httpx\n\nfrom ..dataset import Dataset, Layer, RemoteDataset\nfrom ..utils import get_rich_progress\nfrom ._resumable import Resumable\nfrom .api_client.models import (\n    ApiDatasetUploadInformation,\n    ApiLinkedLayerIdentifier,\n    ApiReserveDatasetUploadInformation,\n)\nfrom .context import _get_context, _WebknossosContext\n\nDEFAULT_SIMULTANEOUS_UPLOADS = 5\nMAXIMUM_RETRY_COUNT = 4\n\n\nclass LayerToLink(NamedTuple):\n    dataset_name: str\n    layer_name: str\n    new_layer_name: Optional[str] = None\n    organization_id: Optional[str] = (\n        None  # defaults to the user's organization before uploading\n    )\n\n    @classmethod\n    def from_remote_layer(\n        cls,\n        layer: Layer,\n        new_layer_name: Optional[str] = None,\n        organization_id: Optional[str] = None,\n    ) -> \"LayerToLink\":\n        ds = layer.dataset\n        assert isinstance(\n            ds, RemoteDataset\n        ), f\"The passed layer must belong to a RemoteDataset, but belongs to {ds}\"\n        return cls(ds._dataset_name, layer.name, new_layer_name, organization_id)\n\n    def as_api_linked_layer_identifier(self) -> ApiLinkedLayerIdentifier:\n        context = _get_context()\n        return ApiLinkedLayerIdentifier(\n            self.organization_id or context.organization_id,\n            self.dataset_name,\n            self.layer_name,\n            self.new_layer_name,\n        )\n\n\n@lru_cache(maxsize=None)\ndef _cached_get_upload_datastore(context: _WebknossosContext) -> str:\n    datastores = context.api_client_with_auth.datastore_list()\n    for datastore in datastores:\n        if datastore.allows_upload:\n            return datastore.url\n    raise ValueError(\"No datastore found where datasets can be uploaded.\")\n\n\ndef _walk(\n    path: Path,\n    base_path: Optional[Path] = None,\n) -> Iterator[Tuple[Path, Path, int]]:\n    if base_path is None:\n        base_path = path\n    if path.is_dir():\n        for p in path.iterdir():\n            yield from _walk(p, base_path)\n    else:\n        yield (path.resolve(), path.relative_to(base_path), path.stat().st_size)\n\n\ndef upload_dataset(\n    dataset: Dataset,\n    new_dataset_name: Optional[str] = None,\n    layers_to_link: Optional[List[LayerToLink]] = None,\n    jobs: Optional[int] = None,\n) -> str:\n    if new_dataset_name is None:\n        new_dataset_name = dataset.name\n    if layers_to_link is None:\n        layers_to_link = []\n    context = _get_context()\n    layer_names_to_link = set(i.new_layer_name or i.layer_name for i in layers_to_link)\n    if len(layer_names_to_link.intersection(dataset.layers.keys())) > 0:\n        warnings.warn(\n            \"[INFO] Excluding the following layers from upload, since they will be linked: \"\n            + f\"{layer_names_to_link.intersection(dataset.layers.keys())}\"\n        )\n        with TemporaryDirectory() as tmpdir:\n            tmp_ds = dataset.shallow_copy_dataset(\n                tmpdir, name=dataset.name, layers_to_ignore=layer_names_to_link\n            )\n            return upload_dataset(\n                tmp_ds,\n                new_dataset_name=new_dataset_name,\n                layers_to_link=layers_to_link,\n                jobs=jobs,\n            )\n\n    file_infos = list(_walk(dataset.path))\n    total_file_size = sum(size for _, _, size in file_infos)\n    # replicates https://github.com/scalableminds/webknossos/blob/master/frontend/javascripts/admin/dataset/dataset_upload_view.js\n    time_str = strftime(\"%Y-%m-%dT%H-%M-%S\", gmtime())\n    upload_id = f\"{time_str}__{uuid4()}\"\n    datastore_token = context.datastore_required_token\n    datastore_url = _cached_get_upload_datastore(context)\n    datastore_api_client = context.get_datastore_api_client(datastore_url)\n    simultaneous_uploads = jobs if jobs is not None else DEFAULT_SIMULTANEOUS_UPLOADS\n    if \"PYTEST_CURRENT_TEST\" in os.environ:\n        simultaneous_uploads = 1\n    is_valid_new_name_response = context.api_client_with_auth.dataset_is_valid_new_name(\n        context.organization_id, new_dataset_name\n    )\n    if not is_valid_new_name_response.is_valid:\n        problems_str = \"\"\n        if is_valid_new_name_response.errors is not None:\n            problems_str = f\" Problems: {is_valid_new_name_response.errors}\"\n        raise Exception(\n            f\"Dataset name {context.organization_id}/{new_dataset_name} is not a valid new dataset name.{problems_str}\"\n        )\n\n    datastore_api_client.dataset_reserve_upload(\n        ApiReserveDatasetUploadInformation(\n            upload_id,\n            new_dataset_name,\n            context.organization_id,\n            total_file_count=len(file_infos),\n            layers_to_link=[\n                layer.as_api_linked_layer_identifier() for layer in layers_to_link\n            ],\n            folder_id=None,\n            initial_teams=[],\n        ),\n        token=datastore_token,\n        retry_count=MAXIMUM_RETRY_COUNT,\n    )\n    with get_rich_progress() as progress:\n        with Resumable(\n            f\"{datastore_url}/data/datasets?token={datastore_token}\",\n            simultaneous_uploads=simultaneous_uploads,\n            query={\n                \"owningOrganization\": context.organization_id,\n                \"name\": new_dataset_name,\n                \"totalFileCount\": len(file_infos),\n            },\n            chunk_size=100 * 1024 * 1024,  # 100 MiB\n            generate_unique_identifier=lambda _,\n            relative_path: f\"{upload_id}/{relative_path.as_posix()}\",\n            test_chunks=False,\n            permanent_errors=[400, 403, 404, 409, 415, 500, 501],\n            client=httpx.Client(timeout=None),\n        ) as session:\n            progress_task = progress.add_task(\"Dataset Upload\", total=total_file_size)\n            for file_path, relative_path, _ in file_infos:\n                resumable_file = session.add_file(file_path, relative_path)\n                resumable_file.chunk_completed.register(\n                    lambda chunk: progress.advance(progress_task, chunk.size)\n                )\n\n    datastore_api_client.dataset_finish_upload(\n        ApiDatasetUploadInformation(upload_id),\n        datastore_token,\n        retry_count=MAXIMUM_RETRY_COUNT,\n    )\n\n    return new_dataset_name\n", "path": "webknossos/webknossos/client/_upload_dataset.py"}]}