We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
DatasetConfig with empty target_folder creates invalid pathOneCompute argument
target_folder=="" will turn into PosixPath("."), but that creates problems on AML.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `hi-ml-azure/src/health_azure/datasets.py`
Content:
```
1 #  ------------------------------------------------------------------------------------------
2 #  Copyright (c) Microsoft Corporation. All rights reserved.
3 #  Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4 #  ------------------------------------------------------------------------------------------
5 import logging
6 import tempfile
7 from pathlib import Path
8 from typing import List, Optional, Tuple, Union
9 
10 from azureml.core import Dataset, Datastore, Workspace
11 from azureml.data import FileDataset, OutputFileDatasetConfig
12 from azureml.data.dataset_consumption_config import DatasetConsumptionConfig
13 from azureml.dataprep.fuse.daemon import MountContext
14 
15 from health_azure.utils import PathOrString, get_workspace
16 
17 
18 def get_datastore(workspace: Workspace, datastore_name: str) -> Datastore:
19     """
20     Retrieves a datastore of a given name from an AzureML workspace. The datastore_name argument can be omitted if
21     the workspace only contains a single datastore. Raises a ValueError if there is no datastore of the given name.
22 
23     :param workspace: The AzureML workspace to read from.
24     :param datastore_name: The name of the datastore to retrieve.
25     :return: An AzureML datastore.
26     """
27     datastores = workspace.datastores
28     existing_stores = list(datastores.keys())
29     if not datastore_name:
30         if len(existing_stores) == 1:
31             return datastores[existing_stores[0]]
32         raise ValueError("No datastore name provided. This is only possible if the workspace has a single datastore. "
33                          f"However, the workspace has {len(existing_stores)} datastores: {existing_stores}")
34     if datastore_name in datastores:
35         return datastores[datastore_name]
36     raise ValueError(f"Datastore {datastore_name} was not found in the workspace. Existing datastores: "
37                      f"{existing_stores}")
38 
39 
40 def get_or_create_dataset(workspace: Workspace, datastore_name: str, dataset_name: str) -> FileDataset:
41     """
42     Looks in the AzureML datastore for a dataset of the given name. If there is no such dataset, a dataset is
43     created and registered, assuming that the files are in a folder that has the same name as the dataset.
44     For example, if dataset_name is 'foo', then the 'foo' dataset should be pointing to the folder
45     <container_root>/datasets/dataset_name/
46     """
47     if not dataset_name:
48         raise ValueError("No dataset name provided.")
49     try:
50         logging.info(f"Trying to retrieve AzureML Dataset '{dataset_name}'")
51         azureml_dataset = Dataset.get_by_name(workspace, name=dataset_name)
52         logging.info("Dataset found.")
53     except Exception:
54         logging.info(f"Retrieving datastore '{datastore_name}' from AzureML workspace")
55         datastore = get_datastore(workspace, datastore_name)
56         logging.info(f"Creating a new dataset from data in folder '{dataset_name}' in the datastore")
57         # Ensure that there is a / at the end of the file path, otherwise folder that share a prefix could create
58         # trouble (for example, folders foo and foo_bar exist, and I'm trying to create a dataset from "foo")
59         azureml_dataset = Dataset.File.from_files(path=(datastore, dataset_name + "/"))
60         logging.info("Registering the dataset for future use.")
61         azureml_dataset.register(workspace, name=dataset_name)
62     return azureml_dataset
63 
64 
65 def _input_dataset_key(index: int) -> str:
66     return f"INPUT_{index}"
67 
68 
69 def _output_dataset_key(index: int) -> str:
70     return f"OUTPUT_{index}"
71 
72 
73 class DatasetConfig:
74     """
75     Contains information to use AzureML datasets as inputs or outputs.
76     """
77 
78     def __init__(self,
79                  name: str,
80                  datastore: str = "",
81                  version: Optional[int] = None,
82                  use_mounting: Optional[bool] = None,
83                  target_folder: Optional[PathOrString] = None,
84                  local_folder: Optional[PathOrString] = None):
85         """
86         :param name: The name of the dataset, as it was registered in the AzureML workspace. For output datasets,
87             this will be the name given to the newly created dataset.
88         :param datastore: The name of the AzureML datastore that holds the dataset. This can be empty if the AzureML
89             workspace has only a single datastore, or if the default datastore should be used.
90         :param version: The version of the dataset that should be used. This is only used for input datasets.
91             If the version is not specified, the latest version will be used.
92         :param use_mounting: If True, the dataset will be "mounted", that is, individual files will be read
93             or written on-demand over the network. If False, the dataset will be fully downloaded before the job starts,
94             respectively fully uploaded at job end for output datasets.
95             Defaults: False (downloading) for datasets that are script inputs, True (mounting) for datasets that are
96             script outputs.
97         :param target_folder: The folder into which the dataset should be downloaded or mounted. If left empty, a
98             random folder on /tmp will be chosen.
99         :param local_folder: The folder on the local machine at which the dataset is available. This
100             is used only for runs outside of AzureML. If this is empty then the target_folder will be used to
101             mount or download the dataset.
102         """
103         # This class would be a good candidate for a dataclass, but having an explicit constructor makes
104         # documentation tools in the editor work nicer.
105         name = name.strip()
106         if not name:
107             raise ValueError("The name of the dataset must be a non-empty string.")
108         self.name = name
109         self.datastore = datastore
110         self.version = version
111         self.use_mounting = use_mounting
112         self.target_folder = Path(target_folder) if target_folder is not None else None
113         self.local_folder = Path(local_folder) if local_folder is not None else None
114 
115     def to_input_dataset_local(self, workspace: Optional[Workspace]) -> Tuple[Optional[Path], Optional[MountContext]]:
116         """
117         Return a local path to the dataset when outside of an AzureML run.
118         If local_folder is supplied, then this is assumed to be a local dataset, and this is returned.
119         Otherwise the dataset is mounted or downloaded to either the target folder or a temporary folder and that is
120         returned.
121 
122         :param workspace: The AzureML workspace to read from.
123         :return: Pair of optional path to dataset and optional mountcontext.
124         """
125         status = f"Dataset {self.name} will be "
126 
127         if self.local_folder is not None:
128             status += f"obtained from local folder {str(self.local_folder)}"
129             print(status)
130             return self.local_folder, None
131 
132         if workspace is None:
133             status += "'None' - neither local_folder nor workspace available"
134             print(status)
135             return None, None
136 
137         azureml_dataset = get_or_create_dataset(workspace=workspace,
138                                                 dataset_name=self.name,
139                                                 datastore_name=self.datastore)
140 
141         target_path = self.target_folder or Path(tempfile.mkdtemp())
142         use_mounting = self.use_mounting if self.use_mounting is not None else False
143         if use_mounting:
144             status += "mounted at "
145             mount_context = azureml_dataset.mount(mount_point=str(target_path))
146             result = target_path, mount_context
147         else:
148             status += "downloaded to "
149             azureml_dataset.download(target_path=str(target_path), overwrite=False)
150             result = target_path, None
151         if self.target_folder is not None:
152             status += f"{str(self.target_folder)}."
153         else:
154             status += f"a randomly chosen folder: {target_path}."
155         print(status)
156         return result
157 
158     def to_input_dataset(self,
159                          workspace: Workspace,
160                          dataset_index: int) -> DatasetConsumptionConfig:
161         """
162         Creates a configuration for using an AzureML dataset inside of an AzureML run. This will make the AzureML
163         dataset with given name available as a named input, using INPUT_0 as the key for dataset index 0.
164 
165         :param workspace: The AzureML workspace to read from.
166         :param dataset_index: Suffix for using datasets as named inputs, the dataset will be marked INPUT_{index}
167         """
168         status = f"Dataset {self.name} (index {dataset_index}) will be "
169         azureml_dataset = get_or_create_dataset(workspace=workspace,
170                                                 dataset_name=self.name,
171                                                 datastore_name=self.datastore)
172         named_input = azureml_dataset.as_named_input(_input_dataset_key(index=dataset_index))
173         path_on_compute = str(self.target_folder) if self.target_folder is not None else None
174         use_mounting = False if self.use_mounting is None else self.use_mounting
175         if use_mounting:
176             status += "mounted at "
177             result = named_input.as_mount(path_on_compute)
178         else:
179             status += "downloaded to "
180             result = named_input.as_download(path_on_compute)
181         if path_on_compute:
182             status += f"{path_on_compute}."
183         else:
184             status += "a randomly chosen folder."
185         print(status)
186         return result
187 
188     def to_output_dataset(self,
189                           workspace: Workspace,
190                           dataset_index: int) -> OutputFileDatasetConfig:
191         """
192         Creates a configuration to write a script output to an AzureML dataset. The name and datastore of this new
193         dataset will be taken from the present object.
194 
195         :param workspace: The AzureML workspace to read from.
196         :param dataset_index: Suffix for using datasets as named inputs, the dataset will be marked OUTPUT_{index}
197         :return:
198         """
199         status = f"Output dataset {self.name} (index {dataset_index}) will be "
200         datastore = get_datastore(workspace, self.datastore)
201         dataset = OutputFileDatasetConfig(name=_output_dataset_key(index=dataset_index),
202                                           destination=(datastore, self.name + "/"))
203         # TODO: Can we get tags into here too?
204         dataset = dataset.register_on_complete(name=self.name)
205         if self.target_folder is not None:
206             raise ValueError("Output datasets can't have a target_folder set.")
207         use_mounting = True if self.use_mounting is None else self.use_mounting
208         if use_mounting:
209             status += "uploaded while the job runs."
210             result = dataset.as_mount()
211         else:
212             status += "uploaded when the job completes."
213             result = dataset.as_upload()
214         logging.info(status)
215         return result
216 
217 
218 StrOrDatasetConfig = Union[str, DatasetConfig]
219 
220 
221 def _replace_string_datasets(datasets: List[StrOrDatasetConfig],
222                              default_datastore_name: str) -> List[DatasetConfig]:
223     """
224     Processes a list of input or output datasets. All entries in the list that are strings are turned into
225     DatasetConfig objects, using the string as the dataset name, and pointing to the default datastore.
226 
227     :param datasets: A list of datasets, each given either as a string or a DatasetConfig object.
228     :param default_datastore_name: The datastore to use for all datasets that are only specified via their name.
229     :return: A list of DatasetConfig objects, in the same order as the input list.
230     """
231     return [DatasetConfig(name=d, datastore=default_datastore_name) if isinstance(d, str) else d
232             for d in datasets]
233 
234 
235 def find_workspace_for_local_datasets(aml_workspace: Optional[Workspace],
236                                       workspace_config_path: Optional[Path],
237                                       dataset_configs: List[DatasetConfig]) -> Optional[Workspace]:
238     """
239     If any of the dataset_configs require an AzureML workspace then try to get one, otherwise return None.
240 
241     :param aml_workspace: There are two optional parameters used to glean an existing AzureML Workspace. The simplest is
242         to pass it in as a parameter.
243     :param workspace_config_path: The 2nd option is to specify the path to the config.json file downloaded from the
244         Azure portal from which we can retrieve the existing Workspace.
245     :param dataset_configs: List of DatasetConfig describing the input datasets.
246     :return: Workspace if required, None otherwise.
247     """
248     workspace: Workspace = None
249     # Check whether an attempt will be made to mount or download a dataset when running locally.
250     # If so, try to get the AzureML workspace.
251     if any(dc.local_folder is None for dc in dataset_configs):
252         try:
253             workspace = get_workspace(aml_workspace, workspace_config_path)
254             logging.info(f"Found workspace for datasets: {workspace.name}")
255         except Exception:
256             logging.info("Could not find workspace for datasets.")
257     return workspace
258 
259 
260 def setup_local_datasets(aml_workspace: Optional[Workspace],
261                          workspace_config_path: Optional[Path],
262                          dataset_configs: List[DatasetConfig]) -> Tuple[List[Optional[Path]], List[MountContext]]:
263     """
264     When running outside of AzureML, setup datasets to be used locally.
265 
266     For each DatasetConfig, if local_folder is supplied, then this is assumed to be a local dataset, and this is
267     used. Otherwise the dataset is mounted or downloaded to either the target folder or a temporary folder and that is
268     used.
269 
270     :param aml_workspace: There are two optional parameters used to glean an existing AzureML Workspace. The simplest is
271         to pass it in as a parameter.
272     :param workspace_config_path: The 2nd option is to specify the path to the config.json file downloaded from the
273         Azure portal from which we can retrieve the existing Workspace.
274     :param dataset_configs: List of DatasetConfig describing the input datasets.
275     :return: Pair of: list of optional paths to the input datasets, list of mountcontexts, one for each mounted dataset.
276     """
277     workspace = find_workspace_for_local_datasets(aml_workspace, workspace_config_path, dataset_configs)
278 
279     mounted_input_datasets: List[Optional[Path]] = []
280     mount_contexts: List[MountContext] = []
281 
282     for d in dataset_configs:
283         target_path, mount_context = d.to_input_dataset_local(workspace)
284 
285         mounted_input_datasets.append(target_path)
286 
287         if mount_context is not None:
288             mount_context.start()
289             mount_contexts.append(mount_context)
290 
291     return mounted_input_datasets, mount_contexts
292
```
--- 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/hi-ml-azure/src/health_azure/datasets.py b/hi-ml-azure/src/health_azure/datasets.py
--- a/hi-ml-azure/src/health_azure/datasets.py
+++ b/hi-ml-azure/src/health_azure/datasets.py
@@ -95,7 +95,7 @@
             Defaults: False (downloading) for datasets that are script inputs, True (mounting) for datasets that are
             script outputs.
         :param target_folder: The folder into which the dataset should be downloaded or mounted. If left empty, a
-            random folder on /tmp will be chosen.
+            random folder on /tmp will be chosen. Do NOT use "." as the target_folder.
         :param local_folder: The folder on the local machine at which the dataset is available. This
             is used only for runs outside of AzureML. If this is empty then the target_folder will be used to
             mount or download the dataset.
@@ -109,8 +109,11 @@
         self.datastore = datastore
         self.version = version
         self.use_mounting = use_mounting
-        self.target_folder = Path(target_folder) if target_folder is not None else None
-        self.local_folder = Path(local_folder) if local_folder is not None else None
+        # If target_folder is "" then convert to None
+        self.target_folder = Path(target_folder) if target_folder else None
+        if str(self.target_folder) == ".":
+            raise ValueError("Can't mount or download a dataset to the current working directory.")
+        self.local_folder = Path(local_folder) if local_folder else None
 
     def to_input_dataset_local(self, workspace: Optional[Workspace]) -> Tuple[Optional[Path], Optional[MountContext]]:
         """
@@ -137,7 +140,6 @@
         azureml_dataset = get_or_create_dataset(workspace=workspace,
                                                 dataset_name=self.name,
                                                 datastore_name=self.datastore)
-
         target_path = self.target_folder or Path(tempfile.mkdtemp())
         use_mounting = self.use_mounting if self.use_mounting is not None else False
         if use_mounting:
@@ -170,7 +172,9 @@
                                                 dataset_name=self.name,
                                                 datastore_name=self.datastore)
         named_input = azureml_dataset.as_named_input(_input_dataset_key(index=dataset_index))
-        path_on_compute = str(self.target_folder) if self.target_folder is not None else None
+        # If running on windows then self.target_folder may be a WindowsPath, make sure it is
+        # in posix format for Azure.
+        path_on_compute = self.target_folder.as_posix() if self.target_folder is not None else None
         use_mounting = False if self.use_mounting is None else self.use_mounting
         if use_mounting:
             status += "mounted at "
@@ -202,7 +206,7 @@
                                           destination=(datastore, self.name + "/"))
         # TODO: Can we get tags into here too?
         dataset = dataset.register_on_complete(name=self.name)
-        if self.target_folder is not None:
+        if self.target_folder:
             raise ValueError("Output datasets can't have a target_folder set.")
         use_mounting = True if self.use_mounting is None else self.use_mounting
         if use_mounting:

{"golden_diff": "diff --git a/hi-ml-azure/src/health_azure/datasets.py b/hi-ml-azure/src/health_azure/datasets.py\n--- a/hi-ml-azure/src/health_azure/datasets.py\n+++ b/hi-ml-azure/src/health_azure/datasets.py\n@@ -95,7 +95,7 @@\n             Defaults: False (downloading) for datasets that are script inputs, True (mounting) for datasets that are\n             script outputs.\n         :param target_folder: The folder into which the dataset should be downloaded or mounted. If left empty, a\n-            random folder on /tmp will be chosen.\n+            random folder on /tmp will be chosen. Do NOT use \".\" as the target_folder.\n         :param local_folder: The folder on the local machine at which the dataset is available. This\n             is used only for runs outside of AzureML. If this is empty then the target_folder will be used to\n             mount or download the dataset.\n@@ -109,8 +109,11 @@\n         self.datastore = datastore\n         self.version = version\n         self.use_mounting = use_mounting\n-        self.target_folder = Path(target_folder) if target_folder is not None else None\n-        self.local_folder = Path(local_folder) if local_folder is not None else None\n+        # If target_folder is \"\" then convert to None\n+        self.target_folder = Path(target_folder) if target_folder else None\n+        if str(self.target_folder) == \".\":\n+            raise ValueError(\"Can't mount or download a dataset to the current working directory.\")\n+        self.local_folder = Path(local_folder) if local_folder else None\n \n     def to_input_dataset_local(self, workspace: Optional[Workspace]) -> Tuple[Optional[Path], Optional[MountContext]]:\n         \"\"\"\n@@ -137,7 +140,6 @@\n         azureml_dataset = get_or_create_dataset(workspace=workspace,\n                                                 dataset_name=self.name,\n                                                 datastore_name=self.datastore)\n-\n         target_path = self.target_folder or Path(tempfile.mkdtemp())\n         use_mounting = self.use_mounting if self.use_mounting is not None else False\n         if use_mounting:\n@@ -170,7 +172,9 @@\n                                                 dataset_name=self.name,\n                                                 datastore_name=self.datastore)\n         named_input = azureml_dataset.as_named_input(_input_dataset_key(index=dataset_index))\n-        path_on_compute = str(self.target_folder) if self.target_folder is not None else None\n+        # If running on windows then self.target_folder may be a WindowsPath, make sure it is\n+        # in posix format for Azure.\n+        path_on_compute = self.target_folder.as_posix() if self.target_folder is not None else None\n         use_mounting = False if self.use_mounting is None else self.use_mounting\n         if use_mounting:\n             status += \"mounted at \"\n@@ -202,7 +206,7 @@\n                                           destination=(datastore, self.name + \"/\"))\n         # TODO: Can we get tags into here too?\n         dataset = dataset.register_on_complete(name=self.name)\n-        if self.target_folder is not None:\n+        if self.target_folder:\n             raise ValueError(\"Output datasets can't have a target_folder set.\")\n         use_mounting = True if self.use_mounting is None else self.use_mounting\n         if use_mounting:\n", "issue": "DatasetConfig with empty target_folder creates invalid pathOneCompute argument\ntarget_folder==\"\" will turn into PosixPath(\".\"), but that creates problems on AML.\n", "before_files": [{"content": "#  ------------------------------------------------------------------------------------------\n#  Copyright (c) Microsoft Corporation. All rights reserved.\n#  Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.\n#  ------------------------------------------------------------------------------------------\nimport logging\nimport tempfile\nfrom pathlib import Path\nfrom typing import List, Optional, Tuple, Union\n\nfrom azureml.core import Dataset, Datastore, Workspace\nfrom azureml.data import FileDataset, OutputFileDatasetConfig\nfrom azureml.data.dataset_consumption_config import DatasetConsumptionConfig\nfrom azureml.dataprep.fuse.daemon import MountContext\n\nfrom health_azure.utils import PathOrString, get_workspace\n\n\ndef get_datastore(workspace: Workspace, datastore_name: str) -> Datastore:\n    \"\"\"\n    Retrieves a datastore of a given name from an AzureML workspace. The datastore_name argument can be omitted if\n    the workspace only contains a single datastore. Raises a ValueError if there is no datastore of the given name.\n\n    :param workspace: The AzureML workspace to read from.\n    :param datastore_name: The name of the datastore to retrieve.\n    :return: An AzureML datastore.\n    \"\"\"\n    datastores = workspace.datastores\n    existing_stores = list(datastores.keys())\n    if not datastore_name:\n        if len(existing_stores) == 1:\n            return datastores[existing_stores[0]]\n        raise ValueError(\"No datastore name provided. This is only possible if the workspace has a single datastore. \"\n                         f\"However, the workspace has {len(existing_stores)} datastores: {existing_stores}\")\n    if datastore_name in datastores:\n        return datastores[datastore_name]\n    raise ValueError(f\"Datastore {datastore_name} was not found in the workspace. Existing datastores: \"\n                     f\"{existing_stores}\")\n\n\ndef get_or_create_dataset(workspace: Workspace, datastore_name: str, dataset_name: str) -> FileDataset:\n    \"\"\"\n    Looks in the AzureML datastore for a dataset of the given name. If there is no such dataset, a dataset is\n    created and registered, assuming that the files are in a folder that has the same name as the dataset.\n    For example, if dataset_name is 'foo', then the 'foo' dataset should be pointing to the folder\n    <container_root>/datasets/dataset_name/\n    \"\"\"\n    if not dataset_name:\n        raise ValueError(\"No dataset name provided.\")\n    try:\n        logging.info(f\"Trying to retrieve AzureML Dataset '{dataset_name}'\")\n        azureml_dataset = Dataset.get_by_name(workspace, name=dataset_name)\n        logging.info(\"Dataset found.\")\n    except Exception:\n        logging.info(f\"Retrieving datastore '{datastore_name}' from AzureML workspace\")\n        datastore = get_datastore(workspace, datastore_name)\n        logging.info(f\"Creating a new dataset from data in folder '{dataset_name}' in the datastore\")\n        # Ensure that there is a / at the end of the file path, otherwise folder that share a prefix could create\n        # trouble (for example, folders foo and foo_bar exist, and I'm trying to create a dataset from \"foo\")\n        azureml_dataset = Dataset.File.from_files(path=(datastore, dataset_name + \"/\"))\n        logging.info(\"Registering the dataset for future use.\")\n        azureml_dataset.register(workspace, name=dataset_name)\n    return azureml_dataset\n\n\ndef _input_dataset_key(index: int) -> str:\n    return f\"INPUT_{index}\"\n\n\ndef _output_dataset_key(index: int) -> str:\n    return f\"OUTPUT_{index}\"\n\n\nclass DatasetConfig:\n    \"\"\"\n    Contains information to use AzureML datasets as inputs or outputs.\n    \"\"\"\n\n    def __init__(self,\n                 name: str,\n                 datastore: str = \"\",\n                 version: Optional[int] = None,\n                 use_mounting: Optional[bool] = None,\n                 target_folder: Optional[PathOrString] = None,\n                 local_folder: Optional[PathOrString] = None):\n        \"\"\"\n        :param name: The name of the dataset, as it was registered in the AzureML workspace. For output datasets,\n            this will be the name given to the newly created dataset.\n        :param datastore: The name of the AzureML datastore that holds the dataset. This can be empty if the AzureML\n            workspace has only a single datastore, or if the default datastore should be used.\n        :param version: The version of the dataset that should be used. This is only used for input datasets.\n            If the version is not specified, the latest version will be used.\n        :param use_mounting: If True, the dataset will be \"mounted\", that is, individual files will be read\n            or written on-demand over the network. If False, the dataset will be fully downloaded before the job starts,\n            respectively fully uploaded at job end for output datasets.\n            Defaults: False (downloading) for datasets that are script inputs, True (mounting) for datasets that are\n            script outputs.\n        :param target_folder: The folder into which the dataset should be downloaded or mounted. If left empty, a\n            random folder on /tmp will be chosen.\n        :param local_folder: The folder on the local machine at which the dataset is available. This\n            is used only for runs outside of AzureML. If this is empty then the target_folder will be used to\n            mount or download the dataset.\n        \"\"\"\n        # This class would be a good candidate for a dataclass, but having an explicit constructor makes\n        # documentation tools in the editor work nicer.\n        name = name.strip()\n        if not name:\n            raise ValueError(\"The name of the dataset must be a non-empty string.\")\n        self.name = name\n        self.datastore = datastore\n        self.version = version\n        self.use_mounting = use_mounting\n        self.target_folder = Path(target_folder) if target_folder is not None else None\n        self.local_folder = Path(local_folder) if local_folder is not None else None\n\n    def to_input_dataset_local(self, workspace: Optional[Workspace]) -> Tuple[Optional[Path], Optional[MountContext]]:\n        \"\"\"\n        Return a local path to the dataset when outside of an AzureML run.\n        If local_folder is supplied, then this is assumed to be a local dataset, and this is returned.\n        Otherwise the dataset is mounted or downloaded to either the target folder or a temporary folder and that is\n        returned.\n\n        :param workspace: The AzureML workspace to read from.\n        :return: Pair of optional path to dataset and optional mountcontext.\n        \"\"\"\n        status = f\"Dataset {self.name} will be \"\n\n        if self.local_folder is not None:\n            status += f\"obtained from local folder {str(self.local_folder)}\"\n            print(status)\n            return self.local_folder, None\n\n        if workspace is None:\n            status += \"'None' - neither local_folder nor workspace available\"\n            print(status)\n            return None, None\n\n        azureml_dataset = get_or_create_dataset(workspace=workspace,\n                                                dataset_name=self.name,\n                                                datastore_name=self.datastore)\n\n        target_path = self.target_folder or Path(tempfile.mkdtemp())\n        use_mounting = self.use_mounting if self.use_mounting is not None else False\n        if use_mounting:\n            status += \"mounted at \"\n            mount_context = azureml_dataset.mount(mount_point=str(target_path))\n            result = target_path, mount_context\n        else:\n            status += \"downloaded to \"\n            azureml_dataset.download(target_path=str(target_path), overwrite=False)\n            result = target_path, None\n        if self.target_folder is not None:\n            status += f\"{str(self.target_folder)}.\"\n        else:\n            status += f\"a randomly chosen folder: {target_path}.\"\n        print(status)\n        return result\n\n    def to_input_dataset(self,\n                         workspace: Workspace,\n                         dataset_index: int) -> DatasetConsumptionConfig:\n        \"\"\"\n        Creates a configuration for using an AzureML dataset inside of an AzureML run. This will make the AzureML\n        dataset with given name available as a named input, using INPUT_0 as the key for dataset index 0.\n\n        :param workspace: The AzureML workspace to read from.\n        :param dataset_index: Suffix for using datasets as named inputs, the dataset will be marked INPUT_{index}\n        \"\"\"\n        status = f\"Dataset {self.name} (index {dataset_index}) will be \"\n        azureml_dataset = get_or_create_dataset(workspace=workspace,\n                                                dataset_name=self.name,\n                                                datastore_name=self.datastore)\n        named_input = azureml_dataset.as_named_input(_input_dataset_key(index=dataset_index))\n        path_on_compute = str(self.target_folder) if self.target_folder is not None else None\n        use_mounting = False if self.use_mounting is None else self.use_mounting\n        if use_mounting:\n            status += \"mounted at \"\n            result = named_input.as_mount(path_on_compute)\n        else:\n            status += \"downloaded to \"\n            result = named_input.as_download(path_on_compute)\n        if path_on_compute:\n            status += f\"{path_on_compute}.\"\n        else:\n            status += \"a randomly chosen folder.\"\n        print(status)\n        return result\n\n    def to_output_dataset(self,\n                          workspace: Workspace,\n                          dataset_index: int) -> OutputFileDatasetConfig:\n        \"\"\"\n        Creates a configuration to write a script output to an AzureML dataset. The name and datastore of this new\n        dataset will be taken from the present object.\n\n        :param workspace: The AzureML workspace to read from.\n        :param dataset_index: Suffix for using datasets as named inputs, the dataset will be marked OUTPUT_{index}\n        :return:\n        \"\"\"\n        status = f\"Output dataset {self.name} (index {dataset_index}) will be \"\n        datastore = get_datastore(workspace, self.datastore)\n        dataset = OutputFileDatasetConfig(name=_output_dataset_key(index=dataset_index),\n                                          destination=(datastore, self.name + \"/\"))\n        # TODO: Can we get tags into here too?\n        dataset = dataset.register_on_complete(name=self.name)\n        if self.target_folder is not None:\n            raise ValueError(\"Output datasets can't have a target_folder set.\")\n        use_mounting = True if self.use_mounting is None else self.use_mounting\n        if use_mounting:\n            status += \"uploaded while the job runs.\"\n            result = dataset.as_mount()\n        else:\n            status += \"uploaded when the job completes.\"\n            result = dataset.as_upload()\n        logging.info(status)\n        return result\n\n\nStrOrDatasetConfig = Union[str, DatasetConfig]\n\n\ndef _replace_string_datasets(datasets: List[StrOrDatasetConfig],\n                             default_datastore_name: str) -> List[DatasetConfig]:\n    \"\"\"\n    Processes a list of input or output datasets. All entries in the list that are strings are turned into\n    DatasetConfig objects, using the string as the dataset name, and pointing to the default datastore.\n\n    :param datasets: A list of datasets, each given either as a string or a DatasetConfig object.\n    :param default_datastore_name: The datastore to use for all datasets that are only specified via their name.\n    :return: A list of DatasetConfig objects, in the same order as the input list.\n    \"\"\"\n    return [DatasetConfig(name=d, datastore=default_datastore_name) if isinstance(d, str) else d\n            for d in datasets]\n\n\ndef find_workspace_for_local_datasets(aml_workspace: Optional[Workspace],\n                                      workspace_config_path: Optional[Path],\n                                      dataset_configs: List[DatasetConfig]) -> Optional[Workspace]:\n    \"\"\"\n    If any of the dataset_configs require an AzureML workspace then try to get one, otherwise return None.\n\n    :param aml_workspace: There are two optional parameters used to glean an existing AzureML Workspace. The simplest is\n        to pass it in as a parameter.\n    :param workspace_config_path: The 2nd option is to specify the path to the config.json file downloaded from the\n        Azure portal from which we can retrieve the existing Workspace.\n    :param dataset_configs: List of DatasetConfig describing the input datasets.\n    :return: Workspace if required, None otherwise.\n    \"\"\"\n    workspace: Workspace = None\n    # Check whether an attempt will be made to mount or download a dataset when running locally.\n    # If so, try to get the AzureML workspace.\n    if any(dc.local_folder is None for dc in dataset_configs):\n        try:\n            workspace = get_workspace(aml_workspace, workspace_config_path)\n            logging.info(f\"Found workspace for datasets: {workspace.name}\")\n        except Exception:\n            logging.info(\"Could not find workspace for datasets.\")\n    return workspace\n\n\ndef setup_local_datasets(aml_workspace: Optional[Workspace],\n                         workspace_config_path: Optional[Path],\n                         dataset_configs: List[DatasetConfig]) -> Tuple[List[Optional[Path]], List[MountContext]]:\n    \"\"\"\n    When running outside of AzureML, setup datasets to be used locally.\n\n    For each DatasetConfig, if local_folder is supplied, then this is assumed to be a local dataset, and this is\n    used. Otherwise the dataset is mounted or downloaded to either the target folder or a temporary folder and that is\n    used.\n\n    :param aml_workspace: There are two optional parameters used to glean an existing AzureML Workspace. The simplest is\n        to pass it in as a parameter.\n    :param workspace_config_path: The 2nd option is to specify the path to the config.json file downloaded from the\n        Azure portal from which we can retrieve the existing Workspace.\n    :param dataset_configs: List of DatasetConfig describing the input datasets.\n    :return: Pair of: list of optional paths to the input datasets, list of mountcontexts, one for each mounted dataset.\n    \"\"\"\n    workspace = find_workspace_for_local_datasets(aml_workspace, workspace_config_path, dataset_configs)\n\n    mounted_input_datasets: List[Optional[Path]] = []\n    mount_contexts: List[MountContext] = []\n\n    for d in dataset_configs:\n        target_path, mount_context = d.to_input_dataset_local(workspace)\n\n        mounted_input_datasets.append(target_path)\n\n        if mount_context is not None:\n            mount_context.start()\n            mount_contexts.append(mount_context)\n\n    return mounted_input_datasets, mount_contexts\n", "path": "hi-ml-azure/src/health_azure/datasets.py"}], "after_files": [{"content": "#  ------------------------------------------------------------------------------------------\n#  Copyright (c) Microsoft Corporation. All rights reserved.\n#  Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.\n#  ------------------------------------------------------------------------------------------\nimport logging\nimport tempfile\nfrom pathlib import Path\nfrom typing import List, Optional, Tuple, Union\n\nfrom azureml.core import Dataset, Datastore, Workspace\nfrom azureml.data import FileDataset, OutputFileDatasetConfig\nfrom azureml.data.dataset_consumption_config import DatasetConsumptionConfig\nfrom azureml.dataprep.fuse.daemon import MountContext\n\nfrom health_azure.utils import PathOrString, get_workspace\n\n\ndef get_datastore(workspace: Workspace, datastore_name: str) -> Datastore:\n    \"\"\"\n    Retrieves a datastore of a given name from an AzureML workspace. The datastore_name argument can be omitted if\n    the workspace only contains a single datastore. Raises a ValueError if there is no datastore of the given name.\n\n    :param workspace: The AzureML workspace to read from.\n    :param datastore_name: The name of the datastore to retrieve.\n    :return: An AzureML datastore.\n    \"\"\"\n    datastores = workspace.datastores\n    existing_stores = list(datastores.keys())\n    if not datastore_name:\n        if len(existing_stores) == 1:\n            return datastores[existing_stores[0]]\n        raise ValueError(\"No datastore name provided. This is only possible if the workspace has a single datastore. \"\n                         f\"However, the workspace has {len(existing_stores)} datastores: {existing_stores}\")\n    if datastore_name in datastores:\n        return datastores[datastore_name]\n    raise ValueError(f\"Datastore {datastore_name} was not found in the workspace. Existing datastores: \"\n                     f\"{existing_stores}\")\n\n\ndef get_or_create_dataset(workspace: Workspace, datastore_name: str, dataset_name: str) -> FileDataset:\n    \"\"\"\n    Looks in the AzureML datastore for a dataset of the given name. If there is no such dataset, a dataset is\n    created and registered, assuming that the files are in a folder that has the same name as the dataset.\n    For example, if dataset_name is 'foo', then the 'foo' dataset should be pointing to the folder\n    <container_root>/datasets/dataset_name/\n    \"\"\"\n    if not dataset_name:\n        raise ValueError(\"No dataset name provided.\")\n    try:\n        logging.info(f\"Trying to retrieve AzureML Dataset '{dataset_name}'\")\n        azureml_dataset = Dataset.get_by_name(workspace, name=dataset_name)\n        logging.info(\"Dataset found.\")\n    except Exception:\n        logging.info(f\"Retrieving datastore '{datastore_name}' from AzureML workspace\")\n        datastore = get_datastore(workspace, datastore_name)\n        logging.info(f\"Creating a new dataset from data in folder '{dataset_name}' in the datastore\")\n        # Ensure that there is a / at the end of the file path, otherwise folder that share a prefix could create\n        # trouble (for example, folders foo and foo_bar exist, and I'm trying to create a dataset from \"foo\")\n        azureml_dataset = Dataset.File.from_files(path=(datastore, dataset_name + \"/\"))\n        logging.info(\"Registering the dataset for future use.\")\n        azureml_dataset.register(workspace, name=dataset_name)\n    return azureml_dataset\n\n\ndef _input_dataset_key(index: int) -> str:\n    return f\"INPUT_{index}\"\n\n\ndef _output_dataset_key(index: int) -> str:\n    return f\"OUTPUT_{index}\"\n\n\nclass DatasetConfig:\n    \"\"\"\n    Contains information to use AzureML datasets as inputs or outputs.\n    \"\"\"\n\n    def __init__(self,\n                 name: str,\n                 datastore: str = \"\",\n                 version: Optional[int] = None,\n                 use_mounting: Optional[bool] = None,\n                 target_folder: Optional[PathOrString] = None,\n                 local_folder: Optional[PathOrString] = None):\n        \"\"\"\n        :param name: The name of the dataset, as it was registered in the AzureML workspace. For output datasets,\n            this will be the name given to the newly created dataset.\n        :param datastore: The name of the AzureML datastore that holds the dataset. This can be empty if the AzureML\n            workspace has only a single datastore, or if the default datastore should be used.\n        :param version: The version of the dataset that should be used. This is only used for input datasets.\n            If the version is not specified, the latest version will be used.\n        :param use_mounting: If True, the dataset will be \"mounted\", that is, individual files will be read\n            or written on-demand over the network. If False, the dataset will be fully downloaded before the job starts,\n            respectively fully uploaded at job end for output datasets.\n            Defaults: False (downloading) for datasets that are script inputs, True (mounting) for datasets that are\n            script outputs.\n        :param target_folder: The folder into which the dataset should be downloaded or mounted. If left empty, a\n            random folder on /tmp will be chosen. Do NOT use \".\" as the target_folder.\n        :param local_folder: The folder on the local machine at which the dataset is available. This\n            is used only for runs outside of AzureML. If this is empty then the target_folder will be used to\n            mount or download the dataset.\n        \"\"\"\n        # This class would be a good candidate for a dataclass, but having an explicit constructor makes\n        # documentation tools in the editor work nicer.\n        name = name.strip()\n        if not name:\n            raise ValueError(\"The name of the dataset must be a non-empty string.\")\n        self.name = name\n        self.datastore = datastore\n        self.version = version\n        self.use_mounting = use_mounting\n        # If target_folder is \"\" then convert to None\n        self.target_folder = Path(target_folder) if target_folder else None\n        if str(self.target_folder) == \".\":\n            raise ValueError(\"Can't mount or download a dataset to the current working directory.\")\n        self.local_folder = Path(local_folder) if local_folder else None\n\n    def to_input_dataset_local(self, workspace: Optional[Workspace]) -> Tuple[Optional[Path], Optional[MountContext]]:\n        \"\"\"\n        Return a local path to the dataset when outside of an AzureML run.\n        If local_folder is supplied, then this is assumed to be a local dataset, and this is returned.\n        Otherwise the dataset is mounted or downloaded to either the target folder or a temporary folder and that is\n        returned.\n\n        :param workspace: The AzureML workspace to read from.\n        :return: Pair of optional path to dataset and optional mountcontext.\n        \"\"\"\n        status = f\"Dataset {self.name} will be \"\n\n        if self.local_folder is not None:\n            status += f\"obtained from local folder {str(self.local_folder)}\"\n            print(status)\n            return self.local_folder, None\n\n        if workspace is None:\n            status += \"'None' - neither local_folder nor workspace available\"\n            print(status)\n            return None, None\n\n        azureml_dataset = get_or_create_dataset(workspace=workspace,\n                                                dataset_name=self.name,\n                                                datastore_name=self.datastore)\n        target_path = self.target_folder or Path(tempfile.mkdtemp())\n        use_mounting = self.use_mounting if self.use_mounting is not None else False\n        if use_mounting:\n            status += \"mounted at \"\n            mount_context = azureml_dataset.mount(mount_point=str(target_path))\n            result = target_path, mount_context\n        else:\n            status += \"downloaded to \"\n            azureml_dataset.download(target_path=str(target_path), overwrite=False)\n            result = target_path, None\n        if self.target_folder is not None:\n            status += f\"{str(self.target_folder)}.\"\n        else:\n            status += f\"a randomly chosen folder: {target_path}.\"\n        print(status)\n        return result\n\n    def to_input_dataset(self,\n                         workspace: Workspace,\n                         dataset_index: int) -> DatasetConsumptionConfig:\n        \"\"\"\n        Creates a configuration for using an AzureML dataset inside of an AzureML run. This will make the AzureML\n        dataset with given name available as a named input, using INPUT_0 as the key for dataset index 0.\n\n        :param workspace: The AzureML workspace to read from.\n        :param dataset_index: Suffix for using datasets as named inputs, the dataset will be marked INPUT_{index}\n        \"\"\"\n        status = f\"Dataset {self.name} (index {dataset_index}) will be \"\n        azureml_dataset = get_or_create_dataset(workspace=workspace,\n                                                dataset_name=self.name,\n                                                datastore_name=self.datastore)\n        named_input = azureml_dataset.as_named_input(_input_dataset_key(index=dataset_index))\n        # If running on windows then self.target_folder may be a WindowsPath, make sure it is\n        # in posix format for Azure.\n        path_on_compute = self.target_folder.as_posix() if self.target_folder is not None else None\n        use_mounting = False if self.use_mounting is None else self.use_mounting\n        if use_mounting:\n            status += \"mounted at \"\n            result = named_input.as_mount(path_on_compute)\n        else:\n            status += \"downloaded to \"\n            result = named_input.as_download(path_on_compute)\n        if path_on_compute:\n            status += f\"{path_on_compute}.\"\n        else:\n            status += \"a randomly chosen folder.\"\n        print(status)\n        return result\n\n    def to_output_dataset(self,\n                          workspace: Workspace,\n                          dataset_index: int) -> OutputFileDatasetConfig:\n        \"\"\"\n        Creates a configuration to write a script output to an AzureML dataset. The name and datastore of this new\n        dataset will be taken from the present object.\n\n        :param workspace: The AzureML workspace to read from.\n        :param dataset_index: Suffix for using datasets as named inputs, the dataset will be marked OUTPUT_{index}\n        :return:\n        \"\"\"\n        status = f\"Output dataset {self.name} (index {dataset_index}) will be \"\n        datastore = get_datastore(workspace, self.datastore)\n        dataset = OutputFileDatasetConfig(name=_output_dataset_key(index=dataset_index),\n                                          destination=(datastore, self.name + \"/\"))\n        # TODO: Can we get tags into here too?\n        dataset = dataset.register_on_complete(name=self.name)\n        if self.target_folder:\n            raise ValueError(\"Output datasets can't have a target_folder set.\")\n        use_mounting = True if self.use_mounting is None else self.use_mounting\n        if use_mounting:\n            status += \"uploaded while the job runs.\"\n            result = dataset.as_mount()\n        else:\n            status += \"uploaded when the job completes.\"\n            result = dataset.as_upload()\n        logging.info(status)\n        return result\n\n\nStrOrDatasetConfig = Union[str, DatasetConfig]\n\n\ndef _replace_string_datasets(datasets: List[StrOrDatasetConfig],\n                             default_datastore_name: str) -> List[DatasetConfig]:\n    \"\"\"\n    Processes a list of input or output datasets. All entries in the list that are strings are turned into\n    DatasetConfig objects, using the string as the dataset name, and pointing to the default datastore.\n\n    :param datasets: A list of datasets, each given either as a string or a DatasetConfig object.\n    :param default_datastore_name: The datastore to use for all datasets that are only specified via their name.\n    :return: A list of DatasetConfig objects, in the same order as the input list.\n    \"\"\"\n    return [DatasetConfig(name=d, datastore=default_datastore_name) if isinstance(d, str) else d\n            for d in datasets]\n\n\ndef find_workspace_for_local_datasets(aml_workspace: Optional[Workspace],\n                                      workspace_config_path: Optional[Path],\n                                      dataset_configs: List[DatasetConfig]) -> Optional[Workspace]:\n    \"\"\"\n    If any of the dataset_configs require an AzureML workspace then try to get one, otherwise return None.\n\n    :param aml_workspace: There are two optional parameters used to glean an existing AzureML Workspace. The simplest is\n        to pass it in as a parameter.\n    :param workspace_config_path: The 2nd option is to specify the path to the config.json file downloaded from the\n        Azure portal from which we can retrieve the existing Workspace.\n    :param dataset_configs: List of DatasetConfig describing the input datasets.\n    :return: Workspace if required, None otherwise.\n    \"\"\"\n    workspace: Workspace = None\n    # Check whether an attempt will be made to mount or download a dataset when running locally.\n    # If so, try to get the AzureML workspace.\n    if any(dc.local_folder is None for dc in dataset_configs):\n        try:\n            workspace = get_workspace(aml_workspace, workspace_config_path)\n            logging.info(f\"Found workspace for datasets: {workspace.name}\")\n        except Exception:\n            logging.info(\"Could not find workspace for datasets.\")\n    return workspace\n\n\ndef setup_local_datasets(aml_workspace: Optional[Workspace],\n                         workspace_config_path: Optional[Path],\n                         dataset_configs: List[DatasetConfig]) -> Tuple[List[Optional[Path]], List[MountContext]]:\n    \"\"\"\n    When running outside of AzureML, setup datasets to be used locally.\n\n    For each DatasetConfig, if local_folder is supplied, then this is assumed to be a local dataset, and this is\n    used. Otherwise the dataset is mounted or downloaded to either the target folder or a temporary folder and that is\n    used.\n\n    :param aml_workspace: There are two optional parameters used to glean an existing AzureML Workspace. The simplest is\n        to pass it in as a parameter.\n    :param workspace_config_path: The 2nd option is to specify the path to the config.json file downloaded from the\n        Azure portal from which we can retrieve the existing Workspace.\n    :param dataset_configs: List of DatasetConfig describing the input datasets.\n    :return: Pair of: list of optional paths to the input datasets, list of mountcontexts, one for each mounted dataset.\n    \"\"\"\n    workspace = find_workspace_for_local_datasets(aml_workspace, workspace_config_path, dataset_configs)\n\n    mounted_input_datasets: List[Optional[Path]] = []\n    mount_contexts: List[MountContext] = []\n\n    for d in dataset_configs:\n        target_path, mount_context = d.to_input_dataset_local(workspace)\n\n        mounted_input_datasets.append(target_path)\n\n        if mount_context is not None:\n            mount_context.start()\n            mount_contexts.append(mount_context)\n\n    return mounted_input_datasets, mount_contexts\n", "path": "hi-ml-azure/src/health_azure/datasets.py"}]}