We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
If DB_PORT is not set, config process sets port to an empty string
I have an existing environment that sets up the datacube connection using this:
```
      - DB_HOSTNAME=host.docker.internal
      - DB_USERNAME=opendatacube
      - DB_PASSWORD=opendatacubepassword
      - DB_DATABASE=opendatacube
```

and with the new changes to read config from environment variables over the config file, the port is required to be set with `DB_PORT=5432`.

Expected behaviour was that if the port is blank it is set to the default for Postgres.

https://github.com/opendatacube/datacube-core/blob/8481d907b198a1c8946326b8b70625a9a8523a12/datacube/config.py#L265

--- 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/config.py`
Content:
```
1 # coding=utf-8
2 """
3 User configuration.
4 """
5 
6 import os
7 from pathlib import Path
8 import configparser
9 from urllib.parse import unquote_plus, urlparse
10 from typing import Optional, Iterable, Union, Any, Tuple, Dict
11 
12 PathLike = Union[str, 'os.PathLike[Any]']
13 
14 
15 ENVIRONMENT_VARNAME = 'DATACUBE_CONFIG_PATH'
16 #: Config locations in order. Properties found in latter locations override
17 #: earlier ones.
18 #:
19 #: - `/etc/datacube.conf`
20 #: - file at `$DATACUBE_CONFIG_PATH` environment variable
21 #: - `~/.datacube.conf`
22 #: - `datacube.conf`
23 DEFAULT_CONF_PATHS = tuple(p for p in ['/etc/datacube.conf',
24                                        os.environ.get(ENVIRONMENT_VARNAME, ''),
25                                        str(os.path.expanduser("~/.datacube.conf")),
26                                        'datacube.conf'] if len(p) > 0)
27 
28 DEFAULT_ENV = 'default'
29 
30 # Default configuration options.
31 _DEFAULT_CONF = """
32 [DEFAULT]
33 # Blank implies localhost
34 db_hostname:
35 db_database: datacube
36 index_driver: default
37 # If a connection is unused for this length of time, expect it to be invalidated.
38 db_connection_timeout: 60
39 
40 [user]
41 # Which environment to use when none is specified explicitly.
42 #   note: will fail if default_environment points to non-existent section
43 # default_environment: datacube
44 """
45 
46 #: Used in place of None as a default, when None is a valid but not default parameter to a function
47 _UNSET = object()
48 
49 
50 def read_config(default_text: Optional[str] = None) -> configparser.ConfigParser:
51     config = configparser.ConfigParser()
52     if default_text is not None:
53         config.read_string(default_text)
54     return config
55 
56 
57 class LocalConfig(object):
58     """
59     System configuration for the user.
60 
61     This loads from a set of possible configuration files which define the available environments.
62     An environment contains connection details for a Data Cube Index, which provides access to
63     available data.
64 
65     """
66 
67     def __init__(self, config: configparser.ConfigParser,
68                  files_loaded: Optional[Iterable[str]] = None,
69                  env: Optional[str] = None):
70         """
71         Datacube environment resolution precedence is:
72           1. Supplied as a function argument `env`
73           2. DATACUBE_ENVIRONMENT environment variable
74           3. user.default_environment option in the config
75           4. 'default' or 'datacube' whichever is present
76 
77         If environment is supplied by any of the first 3 methods is not present
78         in the config, then throw an exception.
79         """
80         self._config = config
81         self.files_loaded = [] if files_loaded is None else list(iter(files_loaded))
82 
83         if env is None:
84             env = os.environ.get('DATACUBE_ENVIRONMENT',
85                                  config.get('user', 'default_environment', fallback=None))
86 
87         # If the user specifies a particular env, we either want to use it or Fail
88         if env:
89             if config.has_section(env):
90                 self._env = env
91                 # All is good
92                 return
93             else:
94                 raise ValueError('No config section found for environment %r' % (env,))
95         else:
96             # If an env hasn't been specifically selected, we can fall back defaults
97             fallbacks = [DEFAULT_ENV, 'datacube']
98             for fallback_env in fallbacks:
99                 if config.has_section(fallback_env):
100                     self._env = fallback_env
101                     return
102             raise ValueError('No ODC environment, checked configurations for %s' % fallbacks)
103 
104     @classmethod
105     def find(cls,
106              paths: Optional[Union[str, Iterable[PathLike]]] = None,
107              env: Optional[str] = None) -> 'LocalConfig':
108         """
109         Find config from environment variables or possible filesystem locations.
110 
111         'env' is which environment to use from the config: it corresponds to the name of a
112         config section
113         """
114         config = read_config(_DEFAULT_CONF)
115 
116         if paths is None:
117             if env is None:
118                 env_opts = parse_env_params()
119                 if env_opts:
120                     return _cfg_from_env_opts(env_opts, config)
121 
122             paths = DEFAULT_CONF_PATHS
123 
124         if isinstance(paths, str) or hasattr(paths, '__fspath__'):  # Use os.PathLike in 3.6+
125             paths = [str(paths)]
126 
127         files_loaded = config.read(str(p) for p in paths if p)
128 
129         return LocalConfig(
130             config,
131             files_loaded=files_loaded,
132             env=env,
133         )
134 
135     def get(self, item: str, fallback=_UNSET):
136         if fallback == _UNSET:
137             return self._config.get(self._env, item)
138         else:
139             return self._config.get(self._env, item, fallback=fallback)
140 
141     def __getitem__(self, item: str):
142         return self.get(item, fallback=None)
143 
144     def __str__(self) -> str:
145         return "LocalConfig<loaded_from={}, environment={!r}, config={}>".format(
146             self.files_loaded or 'defaults',
147             self._env,
148             dict(self._config[self._env]),
149         )
150 
151     def __repr__(self) -> str:
152         return str(self)
153 
154 
155 OPTIONS = {'reproject_threads': 4}
156 
157 
158 #: pylint: disable=invalid-name
159 class set_options(object):
160     """Set global state within a controlled context
161 
162     Currently, the only supported options are:
163     * reproject_threads: The number of threads to use when reprojecting
164 
165     You can use ``set_options`` either as a context manager::
166 
167         with datacube.set_options(reproject_threads=16):
168             ...
169 
170     Or to set global options::
171 
172         datacube.set_options(reproject_threads=16)
173     """
174 
175     def __init__(self, **kwargs):
176         self.old = OPTIONS.copy()
177         OPTIONS.update(kwargs)
178 
179     def __enter__(self):
180         return
181 
182     def __exit__(self, exc_type, value, traceback):
183         OPTIONS.clear()
184         OPTIONS.update(self.old)
185 
186 
187 DB_KEYS = ('hostname', 'port', 'database', 'username', 'password')
188 
189 
190 def parse_connect_url(url: str) -> Dict[str, str]:
191     """ Extract database,hostname,port,username,password from db URL.
192 
193     Example: postgresql://username:password@hostname:port/database
194 
195     For local password-less db use `postgresql:///<your db>`
196     """
197     def split2(s: str, separator: str) -> Tuple[str, str]:
198         i = s.find(separator)
199         return (s, '') if i < 0 else (s[:i], s[i+1:])
200 
201     _, netloc, path, *_ = urlparse(url)
202 
203     db = path[1:] if path else ''
204     if '@' in netloc:
205         (user, password), (host, port) = (split2(p, ':') for p in split2(netloc, '@'))
206     else:
207         user, password = '', ''
208         host, port = split2(netloc, ':')
209 
210     oo = dict(hostname=host, database=db)
211 
212     if port:
213         oo['port'] = port
214     if password:
215         oo['password'] = unquote_plus(password)
216     if user:
217         oo['username'] = user
218     return oo
219 
220 
221 def parse_env_params() -> Dict[str, str]:
222     """
223     - Extract parameters from DATACUBE_DB_URL if present
224     - Else look for DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE
225     - Return {} otherwise
226     """
227 
228     db_url = os.environ.get('DATACUBE_DB_URL', None)
229     if db_url is not None:
230         return parse_connect_url(db_url)
231 
232     params = {k: os.environ.get('DB_{}'.format(k.upper()), None)
233               for k in DB_KEYS}
234     return {k: v
235             for k, v in params.items()
236             if v is not None}
237 
238 
239 def _cfg_from_env_opts(opts: Dict[str, str],
240                        base: configparser.ConfigParser) -> LocalConfig:
241     base['default'] = {'db_'+k: v for k, v in opts.items()}
242     return LocalConfig(base, files_loaded=[], env='default')
243 
244 
245 def render_dc_config(params: Dict[str, Any],
246                      section_name: str = 'default') -> str:
247     """ Render output of parse_env_params to a string that can be written to config file.
248     """
249     oo = '[{}]\n'.format(section_name)
250     for k in DB_KEYS:
251         v = params.get(k, None)
252         if v is not None:
253             oo += 'db_{k}: {v}\n'.format(k=k, v=v)
254     return oo
255 
256 
257 def auto_config() -> str:
258     """
259     Render config to $DATACUBE_CONFIG_PATH or ~/.datacube.conf, but only if doesn't exist.
260 
261     option1:
262       DATACUBE_DB_URL  postgresql://user:password@host/database
263 
264     option2:
265       DB_{HOSTNAME|PORT|USERNAME|PASSWORD|DATABASE}
266 
267     option3:
268        default config
269     """
270     cfg_path = os.environ.get('DATACUBE_CONFIG_PATH', None)
271     cfg_path = Path(cfg_path) if cfg_path else Path.home()/'.datacube.conf'
272 
273     if cfg_path.exists():
274         return str(cfg_path)
275 
276     opts = parse_env_params()
277 
278     if len(opts) == 0:
279         opts['hostname'] = ''
280         opts['database'] = 'datacube'
281 
282     cfg_text = render_dc_config(opts)
283     with open(str(cfg_path), 'wt') as f:
284         f.write(cfg_text)
285 
286     return str(cfg_path)
287
```
--- 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/config.py b/datacube/config.py
--- a/datacube/config.py
+++ b/datacube/config.py
@@ -233,7 +233,7 @@
               for k in DB_KEYS}
     return {k: v
             for k, v in params.items()
-            if v is not None}
+            if v is not None and v != ""}
 
 
 def _cfg_from_env_opts(opts: Dict[str, str],

{"golden_diff": "diff --git a/datacube/config.py b/datacube/config.py\n--- a/datacube/config.py\n+++ b/datacube/config.py\n@@ -233,7 +233,7 @@\n               for k in DB_KEYS}\n     return {k: v\n             for k, v in params.items()\n-            if v is not None}\n+            if v is not None and v != \"\"}\n \n \n def _cfg_from_env_opts(opts: Dict[str, str],\n", "issue": "If DB_PORT is not set, config process sets port to an empty string\nI have an existing environment that sets up the datacube connection using this:\r\n```\r\n      - DB_HOSTNAME=host.docker.internal\r\n      - DB_USERNAME=opendatacube\r\n      - DB_PASSWORD=opendatacubepassword\r\n      - DB_DATABASE=opendatacube\r\n```\r\n\r\nand with the new changes to read config from environment variables over the config file, the port is required to be set with `DB_PORT=5432`.\r\n\r\nExpected behaviour was that if the port is blank it is set to the default for Postgres.\r\n\r\nhttps://github.com/opendatacube/datacube-core/blob/8481d907b198a1c8946326b8b70625a9a8523a12/datacube/config.py#L265\n", "before_files": [{"content": "# coding=utf-8\n\"\"\"\nUser configuration.\n\"\"\"\n\nimport os\nfrom pathlib import Path\nimport configparser\nfrom urllib.parse import unquote_plus, urlparse\nfrom typing import Optional, Iterable, Union, Any, Tuple, Dict\n\nPathLike = Union[str, 'os.PathLike[Any]']\n\n\nENVIRONMENT_VARNAME = 'DATACUBE_CONFIG_PATH'\n#: Config locations in order. Properties found in latter locations override\n#: earlier ones.\n#:\n#: - `/etc/datacube.conf`\n#: - file at `$DATACUBE_CONFIG_PATH` environment variable\n#: - `~/.datacube.conf`\n#: - `datacube.conf`\nDEFAULT_CONF_PATHS = tuple(p for p in ['/etc/datacube.conf',\n                                       os.environ.get(ENVIRONMENT_VARNAME, ''),\n                                       str(os.path.expanduser(\"~/.datacube.conf\")),\n                                       'datacube.conf'] if len(p) > 0)\n\nDEFAULT_ENV = 'default'\n\n# Default configuration options.\n_DEFAULT_CONF = \"\"\"\n[DEFAULT]\n# Blank implies localhost\ndb_hostname:\ndb_database: datacube\nindex_driver: default\n# If a connection is unused for this length of time, expect it to be invalidated.\ndb_connection_timeout: 60\n\n[user]\n# Which environment to use when none is specified explicitly.\n#   note: will fail if default_environment points to non-existent section\n# default_environment: datacube\n\"\"\"\n\n#: Used in place of None as a default, when None is a valid but not default parameter to a function\n_UNSET = object()\n\n\ndef read_config(default_text: Optional[str] = None) -> configparser.ConfigParser:\n    config = configparser.ConfigParser()\n    if default_text is not None:\n        config.read_string(default_text)\n    return config\n\n\nclass LocalConfig(object):\n    \"\"\"\n    System configuration for the user.\n\n    This loads from a set of possible configuration files which define the available environments.\n    An environment contains connection details for a Data Cube Index, which provides access to\n    available data.\n\n    \"\"\"\n\n    def __init__(self, config: configparser.ConfigParser,\n                 files_loaded: Optional[Iterable[str]] = None,\n                 env: Optional[str] = None):\n        \"\"\"\n        Datacube environment resolution precedence is:\n          1. Supplied as a function argument `env`\n          2. DATACUBE_ENVIRONMENT environment variable\n          3. user.default_environment option in the config\n          4. 'default' or 'datacube' whichever is present\n\n        If environment is supplied by any of the first 3 methods is not present\n        in the config, then throw an exception.\n        \"\"\"\n        self._config = config\n        self.files_loaded = [] if files_loaded is None else list(iter(files_loaded))\n\n        if env is None:\n            env = os.environ.get('DATACUBE_ENVIRONMENT',\n                                 config.get('user', 'default_environment', fallback=None))\n\n        # If the user specifies a particular env, we either want to use it or Fail\n        if env:\n            if config.has_section(env):\n                self._env = env\n                # All is good\n                return\n            else:\n                raise ValueError('No config section found for environment %r' % (env,))\n        else:\n            # If an env hasn't been specifically selected, we can fall back defaults\n            fallbacks = [DEFAULT_ENV, 'datacube']\n            for fallback_env in fallbacks:\n                if config.has_section(fallback_env):\n                    self._env = fallback_env\n                    return\n            raise ValueError('No ODC environment, checked configurations for %s' % fallbacks)\n\n    @classmethod\n    def find(cls,\n             paths: Optional[Union[str, Iterable[PathLike]]] = None,\n             env: Optional[str] = None) -> 'LocalConfig':\n        \"\"\"\n        Find config from environment variables or possible filesystem locations.\n\n        'env' is which environment to use from the config: it corresponds to the name of a\n        config section\n        \"\"\"\n        config = read_config(_DEFAULT_CONF)\n\n        if paths is None:\n            if env is None:\n                env_opts = parse_env_params()\n                if env_opts:\n                    return _cfg_from_env_opts(env_opts, config)\n\n            paths = DEFAULT_CONF_PATHS\n\n        if isinstance(paths, str) or hasattr(paths, '__fspath__'):  # Use os.PathLike in 3.6+\n            paths = [str(paths)]\n\n        files_loaded = config.read(str(p) for p in paths if p)\n\n        return LocalConfig(\n            config,\n            files_loaded=files_loaded,\n            env=env,\n        )\n\n    def get(self, item: str, fallback=_UNSET):\n        if fallback == _UNSET:\n            return self._config.get(self._env, item)\n        else:\n            return self._config.get(self._env, item, fallback=fallback)\n\n    def __getitem__(self, item: str):\n        return self.get(item, fallback=None)\n\n    def __str__(self) -> str:\n        return \"LocalConfig<loaded_from={}, environment={!r}, config={}>\".format(\n            self.files_loaded or 'defaults',\n            self._env,\n            dict(self._config[self._env]),\n        )\n\n    def __repr__(self) -> str:\n        return str(self)\n\n\nOPTIONS = {'reproject_threads': 4}\n\n\n#: pylint: disable=invalid-name\nclass set_options(object):\n    \"\"\"Set global state within a controlled context\n\n    Currently, the only supported options are:\n    * reproject_threads: The number of threads to use when reprojecting\n\n    You can use ``set_options`` either as a context manager::\n\n        with datacube.set_options(reproject_threads=16):\n            ...\n\n    Or to set global options::\n\n        datacube.set_options(reproject_threads=16)\n    \"\"\"\n\n    def __init__(self, **kwargs):\n        self.old = OPTIONS.copy()\n        OPTIONS.update(kwargs)\n\n    def __enter__(self):\n        return\n\n    def __exit__(self, exc_type, value, traceback):\n        OPTIONS.clear()\n        OPTIONS.update(self.old)\n\n\nDB_KEYS = ('hostname', 'port', 'database', 'username', 'password')\n\n\ndef parse_connect_url(url: str) -> Dict[str, str]:\n    \"\"\" Extract database,hostname,port,username,password from db URL.\n\n    Example: postgresql://username:password@hostname:port/database\n\n    For local password-less db use `postgresql:///<your db>`\n    \"\"\"\n    def split2(s: str, separator: str) -> Tuple[str, str]:\n        i = s.find(separator)\n        return (s, '') if i < 0 else (s[:i], s[i+1:])\n\n    _, netloc, path, *_ = urlparse(url)\n\n    db = path[1:] if path else ''\n    if '@' in netloc:\n        (user, password), (host, port) = (split2(p, ':') for p in split2(netloc, '@'))\n    else:\n        user, password = '', ''\n        host, port = split2(netloc, ':')\n\n    oo = dict(hostname=host, database=db)\n\n    if port:\n        oo['port'] = port\n    if password:\n        oo['password'] = unquote_plus(password)\n    if user:\n        oo['username'] = user\n    return oo\n\n\ndef parse_env_params() -> Dict[str, str]:\n    \"\"\"\n    - Extract parameters from DATACUBE_DB_URL if present\n    - Else look for DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE\n    - Return {} otherwise\n    \"\"\"\n\n    db_url = os.environ.get('DATACUBE_DB_URL', None)\n    if db_url is not None:\n        return parse_connect_url(db_url)\n\n    params = {k: os.environ.get('DB_{}'.format(k.upper()), None)\n              for k in DB_KEYS}\n    return {k: v\n            for k, v in params.items()\n            if v is not None}\n\n\ndef _cfg_from_env_opts(opts: Dict[str, str],\n                       base: configparser.ConfigParser) -> LocalConfig:\n    base['default'] = {'db_'+k: v for k, v in opts.items()}\n    return LocalConfig(base, files_loaded=[], env='default')\n\n\ndef render_dc_config(params: Dict[str, Any],\n                     section_name: str = 'default') -> str:\n    \"\"\" Render output of parse_env_params to a string that can be written to config file.\n    \"\"\"\n    oo = '[{}]\\n'.format(section_name)\n    for k in DB_KEYS:\n        v = params.get(k, None)\n        if v is not None:\n            oo += 'db_{k}: {v}\\n'.format(k=k, v=v)\n    return oo\n\n\ndef auto_config() -> str:\n    \"\"\"\n    Render config to $DATACUBE_CONFIG_PATH or ~/.datacube.conf, but only if doesn't exist.\n\n    option1:\n      DATACUBE_DB_URL  postgresql://user:password@host/database\n\n    option2:\n      DB_{HOSTNAME|PORT|USERNAME|PASSWORD|DATABASE}\n\n    option3:\n       default config\n    \"\"\"\n    cfg_path = os.environ.get('DATACUBE_CONFIG_PATH', None)\n    cfg_path = Path(cfg_path) if cfg_path else Path.home()/'.datacube.conf'\n\n    if cfg_path.exists():\n        return str(cfg_path)\n\n    opts = parse_env_params()\n\n    if len(opts) == 0:\n        opts['hostname'] = ''\n        opts['database'] = 'datacube'\n\n    cfg_text = render_dc_config(opts)\n    with open(str(cfg_path), 'wt') as f:\n        f.write(cfg_text)\n\n    return str(cfg_path)\n", "path": "datacube/config.py"}], "after_files": [{"content": "# coding=utf-8\n\"\"\"\nUser configuration.\n\"\"\"\n\nimport os\nfrom pathlib import Path\nimport configparser\nfrom urllib.parse import unquote_plus, urlparse\nfrom typing import Optional, Iterable, Union, Any, Tuple, Dict\n\nPathLike = Union[str, 'os.PathLike[Any]']\n\n\nENVIRONMENT_VARNAME = 'DATACUBE_CONFIG_PATH'\n#: Config locations in order. Properties found in latter locations override\n#: earlier ones.\n#:\n#: - `/etc/datacube.conf`\n#: - file at `$DATACUBE_CONFIG_PATH` environment variable\n#: - `~/.datacube.conf`\n#: - `datacube.conf`\nDEFAULT_CONF_PATHS = tuple(p for p in ['/etc/datacube.conf',\n                                       os.environ.get(ENVIRONMENT_VARNAME, ''),\n                                       str(os.path.expanduser(\"~/.datacube.conf\")),\n                                       'datacube.conf'] if len(p) > 0)\n\nDEFAULT_ENV = 'default'\n\n# Default configuration options.\n_DEFAULT_CONF = \"\"\"\n[DEFAULT]\n# Blank implies localhost\ndb_hostname:\ndb_database: datacube\nindex_driver: default\n# If a connection is unused for this length of time, expect it to be invalidated.\ndb_connection_timeout: 60\n\n[user]\n# Which environment to use when none is specified explicitly.\n#   note: will fail if default_environment points to non-existent section\n# default_environment: datacube\n\"\"\"\n\n#: Used in place of None as a default, when None is a valid but not default parameter to a function\n_UNSET = object()\n\n\ndef read_config(default_text: Optional[str] = None) -> configparser.ConfigParser:\n    config = configparser.ConfigParser()\n    if default_text is not None:\n        config.read_string(default_text)\n    return config\n\n\nclass LocalConfig(object):\n    \"\"\"\n    System configuration for the user.\n\n    This loads from a set of possible configuration files which define the available environments.\n    An environment contains connection details for a Data Cube Index, which provides access to\n    available data.\n\n    \"\"\"\n\n    def __init__(self, config: configparser.ConfigParser,\n                 files_loaded: Optional[Iterable[str]] = None,\n                 env: Optional[str] = None):\n        \"\"\"\n        Datacube environment resolution precedence is:\n          1. Supplied as a function argument `env`\n          2. DATACUBE_ENVIRONMENT environment variable\n          3. user.default_environment option in the config\n          4. 'default' or 'datacube' whichever is present\n\n        If environment is supplied by any of the first 3 methods is not present\n        in the config, then throw an exception.\n        \"\"\"\n        self._config = config\n        self.files_loaded = [] if files_loaded is None else list(iter(files_loaded))\n\n        if env is None:\n            env = os.environ.get('DATACUBE_ENVIRONMENT',\n                                 config.get('user', 'default_environment', fallback=None))\n\n        # If the user specifies a particular env, we either want to use it or Fail\n        if env:\n            if config.has_section(env):\n                self._env = env\n                # All is good\n                return\n            else:\n                raise ValueError('No config section found for environment %r' % (env,))\n        else:\n            # If an env hasn't been specifically selected, we can fall back defaults\n            fallbacks = [DEFAULT_ENV, 'datacube']\n            for fallback_env in fallbacks:\n                if config.has_section(fallback_env):\n                    self._env = fallback_env\n                    return\n            raise ValueError('No ODC environment, checked configurations for %s' % fallbacks)\n\n    @classmethod\n    def find(cls,\n             paths: Optional[Union[str, Iterable[PathLike]]] = None,\n             env: Optional[str] = None) -> 'LocalConfig':\n        \"\"\"\n        Find config from environment variables or possible filesystem locations.\n\n        'env' is which environment to use from the config: it corresponds to the name of a\n        config section\n        \"\"\"\n        config = read_config(_DEFAULT_CONF)\n\n        if paths is None:\n            if env is None:\n                env_opts = parse_env_params()\n                if env_opts:\n                    return _cfg_from_env_opts(env_opts, config)\n\n            paths = DEFAULT_CONF_PATHS\n\n        if isinstance(paths, str) or hasattr(paths, '__fspath__'):  # Use os.PathLike in 3.6+\n            paths = [str(paths)]\n\n        files_loaded = config.read(str(p) for p in paths if p)\n\n        return LocalConfig(\n            config,\n            files_loaded=files_loaded,\n            env=env,\n        )\n\n    def get(self, item: str, fallback=_UNSET):\n        if fallback == _UNSET:\n            return self._config.get(self._env, item)\n        else:\n            return self._config.get(self._env, item, fallback=fallback)\n\n    def __getitem__(self, item: str):\n        return self.get(item, fallback=None)\n\n    def __str__(self) -> str:\n        return \"LocalConfig<loaded_from={}, environment={!r}, config={}>\".format(\n            self.files_loaded or 'defaults',\n            self._env,\n            dict(self._config[self._env]),\n        )\n\n    def __repr__(self) -> str:\n        return str(self)\n\n\nOPTIONS = {'reproject_threads': 4}\n\n\n#: pylint: disable=invalid-name\nclass set_options(object):\n    \"\"\"Set global state within a controlled context\n\n    Currently, the only supported options are:\n    * reproject_threads: The number of threads to use when reprojecting\n\n    You can use ``set_options`` either as a context manager::\n\n        with datacube.set_options(reproject_threads=16):\n            ...\n\n    Or to set global options::\n\n        datacube.set_options(reproject_threads=16)\n    \"\"\"\n\n    def __init__(self, **kwargs):\n        self.old = OPTIONS.copy()\n        OPTIONS.update(kwargs)\n\n    def __enter__(self):\n        return\n\n    def __exit__(self, exc_type, value, traceback):\n        OPTIONS.clear()\n        OPTIONS.update(self.old)\n\n\nDB_KEYS = ('hostname', 'port', 'database', 'username', 'password')\n\n\ndef parse_connect_url(url: str) -> Dict[str, str]:\n    \"\"\" Extract database,hostname,port,username,password from db URL.\n\n    Example: postgresql://username:password@hostname:port/database\n\n    For local password-less db use `postgresql:///<your db>`\n    \"\"\"\n    def split2(s: str, separator: str) -> Tuple[str, str]:\n        i = s.find(separator)\n        return (s, '') if i < 0 else (s[:i], s[i+1:])\n\n    _, netloc, path, *_ = urlparse(url)\n\n    db = path[1:] if path else ''\n    if '@' in netloc:\n        (user, password), (host, port) = (split2(p, ':') for p in split2(netloc, '@'))\n    else:\n        user, password = '', ''\n        host, port = split2(netloc, ':')\n\n    oo = dict(hostname=host, database=db)\n\n    if port:\n        oo['port'] = port\n    if password:\n        oo['password'] = unquote_plus(password)\n    if user:\n        oo['username'] = user\n    return oo\n\n\ndef parse_env_params() -> Dict[str, str]:\n    \"\"\"\n    - Extract parameters from DATACUBE_DB_URL if present\n    - Else look for DB_HOSTNAME, DB_USERNAME, DB_PASSWORD, DB_DATABASE\n    - Return {} otherwise\n    \"\"\"\n\n    db_url = os.environ.get('DATACUBE_DB_URL', None)\n    if db_url is not None:\n        return parse_connect_url(db_url)\n\n    params = {k: os.environ.get('DB_{}'.format(k.upper()), None)\n              for k in DB_KEYS}\n    return {k: v\n            for k, v in params.items()\n            if v is not None and v != \"\"}\n\n\ndef _cfg_from_env_opts(opts: Dict[str, str],\n                       base: configparser.ConfigParser) -> LocalConfig:\n    base['default'] = {'db_'+k: v for k, v in opts.items()}\n    return LocalConfig(base, files_loaded=[], env='default')\n\n\ndef render_dc_config(params: Dict[str, Any],\n                     section_name: str = 'default') -> str:\n    \"\"\" Render output of parse_env_params to a string that can be written to config file.\n    \"\"\"\n    oo = '[{}]\\n'.format(section_name)\n    for k in DB_KEYS:\n        v = params.get(k, None)\n        if v is not None:\n            oo += 'db_{k}: {v}\\n'.format(k=k, v=v)\n    return oo\n\n\ndef auto_config() -> str:\n    \"\"\"\n    Render config to $DATACUBE_CONFIG_PATH or ~/.datacube.conf, but only if doesn't exist.\n\n    option1:\n      DATACUBE_DB_URL  postgresql://user:password@host/database\n\n    option2:\n      DB_{HOSTNAME|PORT|USERNAME|PASSWORD|DATABASE}\n\n    option3:\n       default config\n    \"\"\"\n    cfg_path = os.environ.get('DATACUBE_CONFIG_PATH', None)\n    cfg_path = Path(cfg_path) if cfg_path else Path.home()/'.datacube.conf'\n\n    if cfg_path.exists():\n        return str(cfg_path)\n\n    opts = parse_env_params()\n\n    if len(opts) == 0:\n        opts['hostname'] = ''\n        opts['database'] = 'datacube'\n\n    cfg_text = render_dc_config(opts)\n    with open(str(cfg_path), 'wt') as f:\n        f.write(cfg_text)\n\n    return str(cfg_path)\n", "path": "datacube/config.py"}]}