# Copyright (C) 2015-2021 Regents of the University of California
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Any, Dict, Optional

from gunicorn.app.base import BaseApplication  # type: ignore


class GunicornApplication(BaseApplication):  # type: ignore
    """
    An entry point to integrate a Gunicorn WSGI server in Python. To start a
    WSGI application with callable `app`, run the following code:

        WSGIApplication(app, options={
            ...
        }).run()

    For more details, see: https://docs.gunicorn.org/en/latest/custom.html
    """
    def __init__(self, app: object, options: Optional[Dict[str, Any]] = None):
        self.options = options or {}
        self.application = app
        super().__init__()

    def init(self, *args: Any) -> None:
        pass

    def load_config(self) -> None:
        parser = self.cfg.parser()
        env_args = parser.parse_args(self.cfg.get_cmd_args_from_env())

        # TODO: also read from the Gunicorn config file?

        for key, value in {**self.options, **vars(env_args)}.items():
            if key in self.cfg.settings and value is not None:
                self.cfg.set(key.lower(), value)

    def load(self) -> object:
        return self.application


def run_app(app: object, options: Optional[Dict[str, Any]] = None) -> None:
    """
    Run a Gunicorn WSGI server.
    """
    GunicornApplication(app, options=options).run()

src/toil/server/wsgi_app.py
# Copyright (C) 2015-2021 Regents of the University of California
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Any, Dict, Optional

from gunicorn.app.base import BaseApplication  # type: ignore


class GunicornApplication(BaseApplication):  # type: ignore
    """
    An entry point to integrate a Gunicorn WSGI server in Python. To start a
    WSGI application with callable `app`, run the following code:

        WSGIApplication(app, options={
            ...
        }).run()

    For more details, see: https://docs.gunicorn.org/en/latest/custom.html
    """
    def __init__(self, app: object, options: Optional[Dict[str, Any]] = None):
        self.options = options or {}
        self.application = app
        super().__init__()

    def init(self, *args: Any) -> None:
        pass

    def load_config(self) -> None:
        parser = self.cfg.parser()
        env_args = parser.parse_args(self.cfg.get_cmd_args_from_env())

        # TODO: also read from the Gunicorn config file?

        for key, value in {**vars(env_args), **self.options}.items():
            if key in self.cfg.settings and value is not None:
                self.cfg.set(key.lower(), value)

    def load(self) -> object:
        return self.application


def run_app(app: object, options: Optional[Dict[str, Any]] = None) -> None:
    """
    Run a Gunicorn WSGI server.
    """
    GunicornApplication(app, options=options).run()

src/toil/server/wsgi_app.py
diff --git a/src/toil/server/wsgi_app.py b/src/toil/server/wsgi_app.py
index 1e9eb2a46a..4e4f2c0e96 100644
--- a/src/toil/server/wsgi_app.py
+++ b/src/toil/server/wsgi_app.py
@@ -41,7 +41,7 @@ def load_config(self) -> None:
 
         # TODO: also read from the Gunicorn config file?
 
-        for key, value in {**self.options, **vars(env_args)}.items():
+        for key, value in {**vars(env_args), **self.options}.items():
             if key in self.cfg.settings and value is not None:
                 self.cfg.set(key.lower(), value)
 

WES ignores host in production
When trying to run `toil server --host 0.0.0.0`, I noticed that it would always only listen on `127.0.0.1` no matter what `--host` is set to but running with `--debug` didn't have this problem.

```
❯ toil server --host 0.0.0.0
...
[2022-11-11 16:50:46 +0000] [7173] [INFO] Starting gunicorn 20.1.0
[2022-11-11 16:50:46 +0000] [7173] [INFO] Listening at: http://127.0.0.1:8000
...
```
vs
```
❯ toil server --host 0.0.0.0 --debug
...
INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8080
...
```

I tracked the problem down to [this line](https://github.com/DataBiosphere/toil/blob/master/src/toil/server/wsgi_app.py#L44). It appears to be overwriting the settings taken from the command line with Gunicorn's defaults before checking to see if anything has been set which `bind` won't be as it's been set to `None` in the merge.

Swapping the dictionaries around seems to have fixed it.
```python
        for key, value in {**vars(env_args), **self.options}.items():
```

┆Issue is synchronized with this [Jira Story](https://ucsc-cgl.atlassian.net/browse/TOIL-1242)
┆Issue Number: TOIL-1242

