We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Driver resolution code can not handle dataset format being None
When attempting to load dataset without format specified `None` is used as a string

Example reported on Slack:

```
2020-07-23 17:05:21,576 48721 datacube-ingest ERROR Failed to create storage unit file (Exception: 'NoneType' object has no attribute 'lower') 
Traceback (most recent call last):
  File "/home/sopon/projects/cornucopia/odc-core/datacube/scripts/ingest.py", line 340, in process_tasks
    executor.result(future)
  File "/home/sopon/projects/cornucopia/odc-core/datacube/executor.py", line 63, in result
    return func(*args, **kwargs)
  File "/home/sopon/projects/cornucopia/odc-core/datacube/executor.py", line 33, in reraise
    raise t.with_traceback(e, traceback)
  File "/home/sopon/projects/cornucopia/odc-core/datacube/executor.py", line 36, in get_ready
    result = SerialExecutor.result(futures[0])
  File "/home/sopon/projects/cornucopia/odc-core/datacube/executor.py", line 63, in result
    return func(*args, **kwargs)
  File "/home/sopon/projects/cornucopia/odc-core/datacube/scripts/ingest.py", line 257, in ingest_work
    fuse_func=fuse_func)
  File "/home/sopon/projects/cornucopia/odc-core/datacube/api/core.py", line 589, in load_data
    progress_cbk=progress_cbk)
  File "/home/sopon/projects/cornucopia/odc-core/datacube/api/core.py", line 526, in _xr_load
    progress_cbk=_cbk)
  File "/home/sopon/projects/cornucopia/odc-core/datacube/api/core.py", line 703, in _fuse_measurement
    src = new_datasource(BandInfo(ds, measurement.name))
  File "/home/sopon/projects/cornucopia/odc-core/datacube/drivers/readers.py", line 101, in new_datasource
    source_type = choose_datasource(band)
  File "/home/sopon/projects/cornucopia/odc-core/datacube/drivers/readers.py", line 80, in choose_datasource
    return rdr_cache()(band.uri_scheme, band.format, fallback=RasterDatasetDataSource)
  File "/home/sopon/projects/cornucopia/odc-core/datacube/drivers/readers.py", line 37, in __call__
    driver = self._find_driver(uri_scheme, fmt)
  File "/home/sopon/projects/cornucopia/odc-core/datacube/drivers/readers.py", line 25, in _find_driver
    key = (uri_scheme.lower(), fmt.lower())
AttributeError: 'NoneType' object has no attribute 'lower'
2020-07-23 17:05:21,576 48721 datacube-ingest INFO Storage unit file creation status (Created_Count: 14, Failed_Count: 15)
```

I think proper solution to that is to fallback to default driver implementation. Also at index time warning should be printed if dataset format is not specified, see #955.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `datacube/drivers/readers.py`
Content:
```
1 from typing import List, Optional, Callable
2 from .driver_cache import load_drivers
3 from .datasource import DataSource
4 from ._tools import singleton_setup
5 from datacube.storage._base import BandInfo
6 
7 DatasourceFactory = Callable[[BandInfo], DataSource]  # pylint: disable=invalid-name
8 
9 
10 class ReaderDriverCache(object):
11     def __init__(self, group: str):
12         self._drivers = load_drivers(group)
13 
14         lookup = {}
15         for driver in self._drivers.values():
16             for uri_scheme in driver.protocols:
17                 for fmt in driver.formats:
18                     if driver.supports(uri_scheme, fmt):
19                         key = (uri_scheme.lower(), fmt.lower())
20                         lookup[key] = driver
21 
22         self._lookup = lookup
23 
24     def _find_driver(self, uri_scheme: str, fmt: str):
25         key = (uri_scheme.lower(), fmt.lower())
26         return self._lookup.get(key)
27 
28     def __call__(self, uri_scheme: str, fmt: str,
29                  fallback: Optional[DatasourceFactory] = None) -> DatasourceFactory:
30         """Lookup `new_datasource` constructor method from the driver. Returns
31         `fallback` method if no driver is found.
32 
33         :param uri_scheme: Protocol part of the Dataset uri
34         :param fmt: Dataset format
35         :return: Returns function `(DataSet, band_name:str) => DataSource`
36         """
37         driver = self._find_driver(uri_scheme, fmt)
38         if driver is not None:
39             return driver.new_datasource
40         if fallback is not None:
41             return fallback
42         else:
43             raise KeyError("No driver found and no fallback provided")
44 
45     def drivers(self) -> List[str]:
46         """ Returns list of driver names
47         """
48         return list(self._drivers.keys())
49 
50 
51 def rdr_cache() -> ReaderDriverCache:
52     """ Singleton for ReaderDriverCache
53     """
54     return singleton_setup(rdr_cache, '_instance',
55                            ReaderDriverCache,
56                            'datacube.plugins.io.read')
57 
58 
59 def reader_drivers() -> List[str]:
60     """ Returns list driver names
61     """
62     return rdr_cache().drivers()
63 
64 
65 def choose_datasource(band: 'BandInfo') -> DatasourceFactory:
66     """Returns appropriate `DataSource` class (or a constructor method) for loading
67     given `dataset`.
68 
69     An appropriate `DataSource` implementation is chosen based on:
70 
71     - Dataset URI (protocol part)
72     - Dataset format
73     - Current system settings
74     - Available IO plugins
75 
76     NOTE: we assume that all bands can be loaded with the same implementation.
77 
78     """
79     from datacube.storage._rio import RasterDatasetDataSource
80     return rdr_cache()(band.uri_scheme, band.format, fallback=RasterDatasetDataSource)
81 
82 
83 def new_datasource(band: BandInfo) -> Optional[DataSource]:
84     """Returns a newly constructed data source to read dataset band data.
85 
86     An appropriate `DataSource` implementation is chosen based on:
87 
88     - Dataset URI (protocol part)
89     - Dataset format
90     - Current system settings
91     - Available IO plugins
92 
93     This function will return the default :class:`RasterDatasetDataSource` if no more specific
94     ``DataSource`` can be found.
95 
96     :param dataset: The dataset to read.
97     :param str band_name: the name of the band to read.
98 
99     """
100 
101     source_type = choose_datasource(band)
102 
103     if source_type is None:
104         return None
105 
106     return source_type(band)
107
```

Path: `datacube/storage/_base.py`
Content:
```
1 from typing import Optional, Dict, Any, Tuple
2 from urllib.parse import urlparse
3 
4 from datacube.model import Dataset
5 from datacube.utils.uris import uri_resolve, pick_uri
6 
7 
8 def _get_band_and_layer(b: Dict[str, Any]) -> Tuple[Optional[int], Optional[str]]:
9     """ Encode legacy logic for extracting band/layer:
10 
11         on input:
12         band -- Int | Nothing
13         layer -- Str | Int | Nothing
14 
15     Valid combinations are:
16         band  layer  Output
17     ---------------------------
18           -     -    ( - ,  - )
19           -    int   (int,  - )
20          int    -    (int,  - )
21          int   str   (int, str)
22           -    str   ( - , str)
23 
24     """
25     band = b.get('band')
26     layer = b.get('layer')
27 
28     if band is None:
29         if isinstance(layer, int):
30             return (layer, None)
31         if layer is None or isinstance(layer, str):
32             return (None, layer)
33 
34         raise ValueError('Expect `layer` to be one of None,int,str but it is {}'.format(type(layer)))
35     else:
36         if not isinstance(band, int):
37             raise ValueError('Expect `band` to be an integer (it is {})'.format(type(band)))
38         if layer is not None and not isinstance(layer, str):
39             raise ValueError('Expect `layer` to be one of None,str but it is {}'.format(type(layer)))
40 
41         return (band, layer)
42 
43 
44 def _extract_driver_data(ds: Dataset) -> Optional[Any]:
45     return ds.metadata_doc.get('driver_data', None)
46 
47 
48 def measurement_paths(ds: Dataset) -> Dict[str, str]:
49     """
50     Returns a dictionary mapping from band name to url pointing to band storage
51     resource.
52 
53     :return: Band Name => URL
54     """
55     if ds.uris is None:
56         raise ValueError('No locations on this dataset')
57 
58     base = pick_uri(ds.uris)
59     return dict((k, uri_resolve(base, m.get('path')))
60                 for k, m in ds.measurements.items())
61 
62 
63 class BandInfo:
64     __slots__ = ('name',
65                  'uri',
66                  'band',
67                  'layer',
68                  'dtype',
69                  'nodata',
70                  'units',
71                  'crs',
72                  'transform',
73                  'center_time',
74                  'format',
75                  'driver_data')
76 
77     def __init__(self,
78                  ds: Dataset,
79                  band: str,
80                  uri_scheme: Optional[str] = None):
81         try:
82             mp, = ds.type.lookup_measurements([band]).values()
83         except KeyError:
84             raise ValueError('No such band: {}'.format(band))
85 
86         mm = ds.measurements.get(mp.canonical_name)
87 
88         if mm is None:
89             raise ValueError('No such band: {}'.format(band))
90 
91         if ds.uris is None:
92             raise ValueError('No uris defined on a dataset')
93 
94         base_uri = pick_uri(ds.uris, uri_scheme)
95 
96         bint, layer = _get_band_and_layer(mm)
97 
98         self.name = band
99         self.uri = uri_resolve(base_uri, mm.get('path'))
100         self.band = bint
101         self.layer = layer
102         self.dtype = mp.dtype
103         self.nodata = mp.nodata
104         self.units = mp.units
105         self.crs = ds.crs
106         self.transform = ds.transform
107         self.format = ds.format
108         self.driver_data = _extract_driver_data(ds)
109 
110     @property
111     def uri_scheme(self) -> str:
112         return urlparse(self.uri).scheme
113
```
--- 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/datacube/drivers/readers.py b/datacube/drivers/readers.py
--- a/datacube/drivers/readers.py
+++ b/datacube/drivers/readers.py
@@ -62,7 +62,7 @@
     return rdr_cache().drivers()
 
 
-def choose_datasource(band: 'BandInfo') -> DatasourceFactory:
+def choose_datasource(band: BandInfo) -> DatasourceFactory:
     """Returns appropriate `DataSource` class (or a constructor method) for loading
     given `dataset`.
 
diff --git a/datacube/storage/_base.py b/datacube/storage/_base.py
--- a/datacube/storage/_base.py
+++ b/datacube/storage/_base.py
@@ -104,9 +104,9 @@
         self.units = mp.units
         self.crs = ds.crs
         self.transform = ds.transform
-        self.format = ds.format
+        self.format = ds.format or ''
         self.driver_data = _extract_driver_data(ds)
 
     @property
     def uri_scheme(self) -> str:
-        return urlparse(self.uri).scheme
+        return urlparse(self.uri).scheme or ''

{"golden_diff": "diff --git a/datacube/drivers/readers.py b/datacube/drivers/readers.py\n--- a/datacube/drivers/readers.py\n+++ b/datacube/drivers/readers.py\n@@ -62,7 +62,7 @@\n     return rdr_cache().drivers()\n \n \n-def choose_datasource(band: 'BandInfo') -> DatasourceFactory:\n+def choose_datasource(band: BandInfo) -> DatasourceFactory:\n     \"\"\"Returns appropriate `DataSource` class (or a constructor method) for loading\n     given `dataset`.\n \ndiff --git a/datacube/storage/_base.py b/datacube/storage/_base.py\n--- a/datacube/storage/_base.py\n+++ b/datacube/storage/_base.py\n@@ -104,9 +104,9 @@\n         self.units = mp.units\n         self.crs = ds.crs\n         self.transform = ds.transform\n-        self.format = ds.format\n+        self.format = ds.format or ''\n         self.driver_data = _extract_driver_data(ds)\n \n     @property\n     def uri_scheme(self) -> str:\n-        return urlparse(self.uri).scheme\n+        return urlparse(self.uri).scheme or ''\n", "issue": "Driver resolution code can not handle dataset format being None\nWhen attempting to load dataset without format specified `None` is used as a string\r\n\r\nExample reported on Slack:\r\n\r\n```\r\n2020-07-23 17:05:21,576 48721 datacube-ingest ERROR Failed to create storage unit file (Exception: 'NoneType' object has no attribute 'lower') \r\nTraceback (most recent call last):\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/scripts/ingest.py\", line 340, in process_tasks\r\n    executor.result(future)\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/executor.py\", line 63, in result\r\n    return func(*args, **kwargs)\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/executor.py\", line 33, in reraise\r\n    raise t.with_traceback(e, traceback)\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/executor.py\", line 36, in get_ready\r\n    result = SerialExecutor.result(futures[0])\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/executor.py\", line 63, in result\r\n    return func(*args, **kwargs)\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/scripts/ingest.py\", line 257, in ingest_work\r\n    fuse_func=fuse_func)\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/api/core.py\", line 589, in load_data\r\n    progress_cbk=progress_cbk)\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/api/core.py\", line 526, in _xr_load\r\n    progress_cbk=_cbk)\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/api/core.py\", line 703, in _fuse_measurement\r\n    src = new_datasource(BandInfo(ds, measurement.name))\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/drivers/readers.py\", line 101, in new_datasource\r\n    source_type = choose_datasource(band)\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/drivers/readers.py\", line 80, in choose_datasource\r\n    return rdr_cache()(band.uri_scheme, band.format, fallback=RasterDatasetDataSource)\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/drivers/readers.py\", line 37, in __call__\r\n    driver = self._find_driver(uri_scheme, fmt)\r\n  File \"/home/sopon/projects/cornucopia/odc-core/datacube/drivers/readers.py\", line 25, in _find_driver\r\n    key = (uri_scheme.lower(), fmt.lower())\r\nAttributeError: 'NoneType' object has no attribute 'lower'\r\n2020-07-23 17:05:21,576 48721 datacube-ingest INFO Storage unit file creation status (Created_Count: 14, Failed_Count: 15)\r\n```\r\n\r\nI think proper solution to that is to fallback to default driver implementation. Also at index time warning should be printed if dataset format is not specified, see #955.\n", "before_files": [{"content": "from typing import List, Optional, Callable\nfrom .driver_cache import load_drivers\nfrom .datasource import DataSource\nfrom ._tools import singleton_setup\nfrom datacube.storage._base import BandInfo\n\nDatasourceFactory = Callable[[BandInfo], DataSource]  # pylint: disable=invalid-name\n\n\nclass ReaderDriverCache(object):\n    def __init__(self, group: str):\n        self._drivers = load_drivers(group)\n\n        lookup = {}\n        for driver in self._drivers.values():\n            for uri_scheme in driver.protocols:\n                for fmt in driver.formats:\n                    if driver.supports(uri_scheme, fmt):\n                        key = (uri_scheme.lower(), fmt.lower())\n                        lookup[key] = driver\n\n        self._lookup = lookup\n\n    def _find_driver(self, uri_scheme: str, fmt: str):\n        key = (uri_scheme.lower(), fmt.lower())\n        return self._lookup.get(key)\n\n    def __call__(self, uri_scheme: str, fmt: str,\n                 fallback: Optional[DatasourceFactory] = None) -> DatasourceFactory:\n        \"\"\"Lookup `new_datasource` constructor method from the driver. Returns\n        `fallback` method if no driver is found.\n\n        :param uri_scheme: Protocol part of the Dataset uri\n        :param fmt: Dataset format\n        :return: Returns function `(DataSet, band_name:str) => DataSource`\n        \"\"\"\n        driver = self._find_driver(uri_scheme, fmt)\n        if driver is not None:\n            return driver.new_datasource\n        if fallback is not None:\n            return fallback\n        else:\n            raise KeyError(\"No driver found and no fallback provided\")\n\n    def drivers(self) -> List[str]:\n        \"\"\" Returns list of driver names\n        \"\"\"\n        return list(self._drivers.keys())\n\n\ndef rdr_cache() -> ReaderDriverCache:\n    \"\"\" Singleton for ReaderDriverCache\n    \"\"\"\n    return singleton_setup(rdr_cache, '_instance',\n                           ReaderDriverCache,\n                           'datacube.plugins.io.read')\n\n\ndef reader_drivers() -> List[str]:\n    \"\"\" Returns list driver names\n    \"\"\"\n    return rdr_cache().drivers()\n\n\ndef choose_datasource(band: 'BandInfo') -> DatasourceFactory:\n    \"\"\"Returns appropriate `DataSource` class (or a constructor method) for loading\n    given `dataset`.\n\n    An appropriate `DataSource` implementation is chosen based on:\n\n    - Dataset URI (protocol part)\n    - Dataset format\n    - Current system settings\n    - Available IO plugins\n\n    NOTE: we assume that all bands can be loaded with the same implementation.\n\n    \"\"\"\n    from datacube.storage._rio import RasterDatasetDataSource\n    return rdr_cache()(band.uri_scheme, band.format, fallback=RasterDatasetDataSource)\n\n\ndef new_datasource(band: BandInfo) -> Optional[DataSource]:\n    \"\"\"Returns a newly constructed data source to read dataset band data.\n\n    An appropriate `DataSource` implementation is chosen based on:\n\n    - Dataset URI (protocol part)\n    - Dataset format\n    - Current system settings\n    - Available IO plugins\n\n    This function will return the default :class:`RasterDatasetDataSource` if no more specific\n    ``DataSource`` can be found.\n\n    :param dataset: The dataset to read.\n    :param str band_name: the name of the band to read.\n\n    \"\"\"\n\n    source_type = choose_datasource(band)\n\n    if source_type is None:\n        return None\n\n    return source_type(band)\n", "path": "datacube/drivers/readers.py"}, {"content": "from typing import Optional, Dict, Any, Tuple\nfrom urllib.parse import urlparse\n\nfrom datacube.model import Dataset\nfrom datacube.utils.uris import uri_resolve, pick_uri\n\n\ndef _get_band_and_layer(b: Dict[str, Any]) -> Tuple[Optional[int], Optional[str]]:\n    \"\"\" Encode legacy logic for extracting band/layer:\n\n        on input:\n        band -- Int | Nothing\n        layer -- Str | Int | Nothing\n\n    Valid combinations are:\n        band  layer  Output\n    ---------------------------\n          -     -    ( - ,  - )\n          -    int   (int,  - )\n         int    -    (int,  - )\n         int   str   (int, str)\n          -    str   ( - , str)\n\n    \"\"\"\n    band = b.get('band')\n    layer = b.get('layer')\n\n    if band is None:\n        if isinstance(layer, int):\n            return (layer, None)\n        if layer is None or isinstance(layer, str):\n            return (None, layer)\n\n        raise ValueError('Expect `layer` to be one of None,int,str but it is {}'.format(type(layer)))\n    else:\n        if not isinstance(band, int):\n            raise ValueError('Expect `band` to be an integer (it is {})'.format(type(band)))\n        if layer is not None and not isinstance(layer, str):\n            raise ValueError('Expect `layer` to be one of None,str but it is {}'.format(type(layer)))\n\n        return (band, layer)\n\n\ndef _extract_driver_data(ds: Dataset) -> Optional[Any]:\n    return ds.metadata_doc.get('driver_data', None)\n\n\ndef measurement_paths(ds: Dataset) -> Dict[str, str]:\n    \"\"\"\n    Returns a dictionary mapping from band name to url pointing to band storage\n    resource.\n\n    :return: Band Name => URL\n    \"\"\"\n    if ds.uris is None:\n        raise ValueError('No locations on this dataset')\n\n    base = pick_uri(ds.uris)\n    return dict((k, uri_resolve(base, m.get('path')))\n                for k, m in ds.measurements.items())\n\n\nclass BandInfo:\n    __slots__ = ('name',\n                 'uri',\n                 'band',\n                 'layer',\n                 'dtype',\n                 'nodata',\n                 'units',\n                 'crs',\n                 'transform',\n                 'center_time',\n                 'format',\n                 'driver_data')\n\n    def __init__(self,\n                 ds: Dataset,\n                 band: str,\n                 uri_scheme: Optional[str] = None):\n        try:\n            mp, = ds.type.lookup_measurements([band]).values()\n        except KeyError:\n            raise ValueError('No such band: {}'.format(band))\n\n        mm = ds.measurements.get(mp.canonical_name)\n\n        if mm is None:\n            raise ValueError('No such band: {}'.format(band))\n\n        if ds.uris is None:\n            raise ValueError('No uris defined on a dataset')\n\n        base_uri = pick_uri(ds.uris, uri_scheme)\n\n        bint, layer = _get_band_and_layer(mm)\n\n        self.name = band\n        self.uri = uri_resolve(base_uri, mm.get('path'))\n        self.band = bint\n        self.layer = layer\n        self.dtype = mp.dtype\n        self.nodata = mp.nodata\n        self.units = mp.units\n        self.crs = ds.crs\n        self.transform = ds.transform\n        self.format = ds.format\n        self.driver_data = _extract_driver_data(ds)\n\n    @property\n    def uri_scheme(self) -> str:\n        return urlparse(self.uri).scheme\n", "path": "datacube/storage/_base.py"}], "after_files": [{"content": "from typing import List, Optional, Callable\nfrom .driver_cache import load_drivers\nfrom .datasource import DataSource\nfrom ._tools import singleton_setup\nfrom datacube.storage._base import BandInfo\n\nDatasourceFactory = Callable[[BandInfo], DataSource]  # pylint: disable=invalid-name\n\n\nclass ReaderDriverCache(object):\n    def __init__(self, group: str):\n        self._drivers = load_drivers(group)\n\n        lookup = {}\n        for driver in self._drivers.values():\n            for uri_scheme in driver.protocols:\n                for fmt in driver.formats:\n                    if driver.supports(uri_scheme, fmt):\n                        key = (uri_scheme.lower(), fmt.lower())\n                        lookup[key] = driver\n\n        self._lookup = lookup\n\n    def _find_driver(self, uri_scheme: str, fmt: str):\n        key = (uri_scheme.lower(), fmt.lower())\n        return self._lookup.get(key)\n\n    def __call__(self, uri_scheme: str, fmt: str,\n                 fallback: Optional[DatasourceFactory] = None) -> DatasourceFactory:\n        \"\"\"Lookup `new_datasource` constructor method from the driver. Returns\n        `fallback` method if no driver is found.\n\n        :param uri_scheme: Protocol part of the Dataset uri\n        :param fmt: Dataset format\n        :return: Returns function `(DataSet, band_name:str) => DataSource`\n        \"\"\"\n        driver = self._find_driver(uri_scheme, fmt)\n        if driver is not None:\n            return driver.new_datasource\n        if fallback is not None:\n            return fallback\n        else:\n            raise KeyError(\"No driver found and no fallback provided\")\n\n    def drivers(self) -> List[str]:\n        \"\"\" Returns list of driver names\n        \"\"\"\n        return list(self._drivers.keys())\n\n\ndef rdr_cache() -> ReaderDriverCache:\n    \"\"\" Singleton for ReaderDriverCache\n    \"\"\"\n    return singleton_setup(rdr_cache, '_instance',\n                           ReaderDriverCache,\n                           'datacube.plugins.io.read')\n\n\ndef reader_drivers() -> List[str]:\n    \"\"\" Returns list driver names\n    \"\"\"\n    return rdr_cache().drivers()\n\n\ndef choose_datasource(band: BandInfo) -> DatasourceFactory:\n    \"\"\"Returns appropriate `DataSource` class (or a constructor method) for loading\n    given `dataset`.\n\n    An appropriate `DataSource` implementation is chosen based on:\n\n    - Dataset URI (protocol part)\n    - Dataset format\n    - Current system settings\n    - Available IO plugins\n\n    NOTE: we assume that all bands can be loaded with the same implementation.\n\n    \"\"\"\n    from datacube.storage._rio import RasterDatasetDataSource\n    return rdr_cache()(band.uri_scheme, band.format, fallback=RasterDatasetDataSource)\n\n\ndef new_datasource(band: BandInfo) -> Optional[DataSource]:\n    \"\"\"Returns a newly constructed data source to read dataset band data.\n\n    An appropriate `DataSource` implementation is chosen based on:\n\n    - Dataset URI (protocol part)\n    - Dataset format\n    - Current system settings\n    - Available IO plugins\n\n    This function will return the default :class:`RasterDatasetDataSource` if no more specific\n    ``DataSource`` can be found.\n\n    :param dataset: The dataset to read.\n    :param str band_name: the name of the band to read.\n\n    \"\"\"\n\n    source_type = choose_datasource(band)\n\n    if source_type is None:\n        return None\n\n    return source_type(band)\n", "path": "datacube/drivers/readers.py"}, {"content": "from typing import Optional, Dict, Any, Tuple\nfrom urllib.parse import urlparse\n\nfrom datacube.model import Dataset\nfrom datacube.utils.uris import uri_resolve, pick_uri\n\n\ndef _get_band_and_layer(b: Dict[str, Any]) -> Tuple[Optional[int], Optional[str]]:\n    \"\"\" Encode legacy logic for extracting band/layer:\n\n        on input:\n        band -- Int | Nothing\n        layer -- Str | Int | Nothing\n\n    Valid combinations are:\n        band  layer  Output\n    ---------------------------\n          -     -    ( - ,  - )\n          -    int   (int,  - )\n         int    -    (int,  - )\n         int   str   (int, str)\n          -    str   ( - , str)\n\n    \"\"\"\n    band = b.get('band')\n    layer = b.get('layer')\n\n    if band is None:\n        if isinstance(layer, int):\n            return (layer, None)\n        if layer is None or isinstance(layer, str):\n            return (None, layer)\n\n        raise ValueError('Expect `layer` to be one of None,int,str but it is {}'.format(type(layer)))\n    else:\n        if not isinstance(band, int):\n            raise ValueError('Expect `band` to be an integer (it is {})'.format(type(band)))\n        if layer is not None and not isinstance(layer, str):\n            raise ValueError('Expect `layer` to be one of None,str but it is {}'.format(type(layer)))\n\n        return (band, layer)\n\n\ndef _extract_driver_data(ds: Dataset) -> Optional[Any]:\n    return ds.metadata_doc.get('driver_data', None)\n\n\ndef measurement_paths(ds: Dataset) -> Dict[str, str]:\n    \"\"\"\n    Returns a dictionary mapping from band name to url pointing to band storage\n    resource.\n\n    :return: Band Name => URL\n    \"\"\"\n    if ds.uris is None:\n        raise ValueError('No locations on this dataset')\n\n    base = pick_uri(ds.uris)\n    return dict((k, uri_resolve(base, m.get('path')))\n                for k, m in ds.measurements.items())\n\n\nclass BandInfo:\n    __slots__ = ('name',\n                 'uri',\n                 'band',\n                 'layer',\n                 'dtype',\n                 'nodata',\n                 'units',\n                 'crs',\n                 'transform',\n                 'center_time',\n                 'format',\n                 'driver_data')\n\n    def __init__(self,\n                 ds: Dataset,\n                 band: str,\n                 uri_scheme: Optional[str] = None):\n        try:\n            mp, = ds.type.lookup_measurements([band]).values()\n        except KeyError:\n            raise ValueError('No such band: {}'.format(band))\n\n        mm = ds.measurements.get(mp.canonical_name)\n\n        if mm is None:\n            raise ValueError('No such band: {}'.format(band))\n\n        if ds.uris is None:\n            raise ValueError('No uris defined on a dataset')\n\n        base_uri = pick_uri(ds.uris, uri_scheme)\n\n        bint, layer = _get_band_and_layer(mm)\n\n        self.name = band\n        self.uri = uri_resolve(base_uri, mm.get('path'))\n        self.band = bint\n        self.layer = layer\n        self.dtype = mp.dtype\n        self.nodata = mp.nodata\n        self.units = mp.units\n        self.crs = ds.crs\n        self.transform = ds.transform\n        self.format = ds.format or ''\n        self.driver_data = _extract_driver_data(ds)\n\n    @property\n    def uri_scheme(self) -> str:\n        return urlparse(self.uri).scheme or ''\n", "path": "datacube/storage/_base.py"}]}