From d96973a192ed7629995efcaf0ebada61b15ae473 Mon Sep 17 00:00:00 2001 From: mmwinther Date: Thu, 25 Jan 2024 16:05:08 +0100 Subject: [PATCH] Use the config module to access app config --- .gitignore | 2 +- .vscode/settings.json | 10 +++- src/datadoc/.env.default | 1 - src/datadoc/app.py | 35 +++++------- src/datadoc/config.py | 73 +++++++++++++++++++++++-- src/datadoc/frontend/callbacks/utils.py | 9 +-- 6 files changed, 94 insertions(+), 36 deletions(-) delete mode 100644 src/datadoc/.env.default diff --git a/.gitignore b/.gitignore index d3307964..f59b5654 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Project specific ignores tests/resources/*.json -src/datadoc/.env.dev +src/datadoc/.env # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/.vscode/settings.json b/.vscode/settings.json index a3a18383..9e6321b0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,13 @@ "tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "ruff.lint.run": "onType", + "[python]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.codeActionsOnSave": { + "source.fixAll": "explicit" + } + } } diff --git a/src/datadoc/.env.default b/src/datadoc/.env.default deleted file mode 100644 index e9caa0e8..00000000 --- a/src/datadoc/.env.default +++ /dev/null @@ -1 +0,0 @@ -JUPYTERHUB_USER= diff --git a/src/datadoc/app.py b/src/datadoc/app.py index f23c44f0..ccf836b9 100644 --- a/src/datadoc/app.py +++ b/src/datadoc/app.py @@ -5,13 +5,13 @@ from __future__ import annotations import logging -import os from pathlib import Path import dash_bootstrap_components as dbc from dash import Dash from flask_healthz import healthz +from datadoc import config from datadoc import state from datadoc.backend.datadoc_metadata import DataDocMetadata from datadoc.enums import SupportedLanguages @@ -31,12 +31,9 @@ from datadoc.utils import pick_random_port from datadoc.utils import running_in_notebook +logging.basicConfig(level=config.get_log_level(), force=True) logger = logging.getLogger(__name__) -NAME = "Datadoc" -DEFAULT_PORT: int = 7002 -JUPYTERHUB_SERVICE_PREFIX_ENV = "JUPYTERHUB_SERVICE_PREFIX" - def build_app(app: type[Dash]) -> Dash: """Define the layout, register callbacks.""" @@ -75,24 +72,23 @@ def build_app(app: type[Dash]) -> Dash: def get_app(dataset_path: str | None = None) -> tuple[Dash, int]: """Centralize all the ugliness around initializing the app.""" - logging.basicConfig(level=logging.INFO, force=True) logger.info("Datadoc version v%s", get_app_version()) state.current_metadata_language = SupportedLanguages.NORSK_BOKMÃ…L state.metadata = DataDocMetadata(dataset_path) - # This must be set to run correctly on Dapla Jupyter - if JUPYTERHUB_SERVICE_PREFIX_ENV in os.environ: + # The service prefix must be set to run correctly on Dapla Jupyter + if prefix := config.get_jupyterhub_service_prefix(): port = pick_random_port() - requests_pathname_prefix = ( - f"{os.getenv(JUPYTERHUB_SERVICE_PREFIX_ENV, '/')}proxy/{port}/" - ) + requests_pathname_prefix = f"{prefix}proxy/{port}/" else: - port = DEFAULT_PORT + port = config.get_port() requests_pathname_prefix = "/" + name = config.get_app_name() + app = Dash( - name=NAME, - title=NAME, + name=name, + title=name, assets_folder=f"{Path(__file__).parent}/assets", requests_pathname_prefix=requests_pathname_prefix, ) @@ -110,22 +106,19 @@ def get_app(dataset_path: str | None = None) -> tuple[Dash, int]: def main(dataset_path: str | None = None) -> None: """Entrypoint when running as a script.""" - logging.basicConfig(level=logging.DEBUG, force=True) - logger.info("Starting app with dataset_path = %s", dataset_path) + if dataset_path: + logger.info("Starting app with dataset_path = %s", dataset_path) app, port = get_app(dataset_path) if running_in_notebook(): logger.info("Running in notebook") app.run( jupyter_mode="tab", - jupyter_server_url=os.getenv("JUPYTERHUB_HTTP_REFERER", None), + jupyter_server_url=config.get_jupyterhub_http_referrer(), jupyter_height=1000, port=port, ) else: - # Assume running in server mode is better (largely for development purposes) - logging.basicConfig(level=logging.DEBUG, force=True) - logger.debug("Starting in development mode") - app.run(debug=True, port=port) + app.run(debug=config.get_dash_development_mode(), port=port) if __name__ == "__main__": diff --git a/src/datadoc/config.py b/src/datadoc/config.py index 6c4d5852..33327bee 100644 --- a/src/datadoc/config.py +++ b/src/datadoc/config.py @@ -1,17 +1,78 @@ """Centralised configuration management for Datadoc.""" from __future__ import annotations +import logging import os +from pprint import pformat from dotenv import dotenv_values +from dotenv import load_dotenv -_config: dict[str, str | None] = { - **dotenv_values(".env.default"), # load default config - **dotenv_values(".env.dev"), # load local dev config - **os.environ, # override loaded values with environment variables -} +logging.basicConfig(level=logging.DEBUG, force=True) + +logger = logging.getLogger(__name__) + +load_dotenv() + +logger.info("Loaded .env file config: \n%s", pformat(dict(dotenv_values().items()))) + + +def _get_config_item(item: str) -> str | None: + """Get a config item. Makes sure all access is logged.""" + value = os.getenv(item) + logger.debug("Accessed config. Name: %s, Value: %s", item, value) + return value def get_jupyterhub_user() -> str | None: """Get the JupyterHub user name.""" - return _config.get("JUPYTERHUB_USER") + return _get_config_item("JUPYTERHUB_USER") + + +def get_datadoc_dataset_path() -> str | None: + """Get the path to the dataset.""" + return _get_config_item("DATADOC_DATASET_PATH") + + +def get_log_level() -> int: + """Get the log level.""" + # Magic numbers as defined in Python's stdlib logging + log_levels: dict[str, int] = { + "CRITICAL": 50, + "ERROR": 40, + "WARNING": 30, + "INFO": 20, + "DEBUG": 10, + } + if level_string := _get_config_item("DATADOC_LOG_LEVEL"): + try: + return log_levels[level_string.upper()] + except KeyError: + return log_levels["INFO"] + else: + return log_levels["INFO"] + + +def get_dash_development_mode() -> bool | None: + """Get the development mode for Dash.""" + return _get_config_item("DATADOC_DASH_DEVELOPMENT_MODE") == "True" + + +def get_jupyterhub_service_prefix() -> str | None: + """Get the JupyterHub service prefix.""" + return _get_config_item("JUPYTERHUB_SERVICE_PREFIX") + + +def get_app_name() -> str: + """Get the name of the app. Defaults to 'Datadoc'.""" + return _get_config_item("DATADOC_APP_NAME") or "Datadoc" + + +def get_jupyterhub_http_referrer() -> str | None: + """Get the JupyterHub http referrer.""" + return _get_config_item("JUPYTERHUB_HTTP_REFERER") + + +def get_port() -> int: + """Get the port to run the app on.""" + return int(_get_config_item("DATADOC_PORT") or 7002) diff --git a/src/datadoc/frontend/callbacks/utils.py b/src/datadoc/frontend/callbacks/utils.py index 5396c748..15fd6b08 100644 --- a/src/datadoc/frontend/callbacks/utils.py +++ b/src/datadoc/frontend/callbacks/utils.py @@ -3,12 +3,12 @@ from __future__ import annotations import logging -import os from typing import TYPE_CHECKING from typing import TypeAlias from datadoc_model import model +from datadoc import config from datadoc import enums from datadoc import state from datadoc.backend.datadoc_metadata import METADATA_DOCUMENT_FILE_SUFFIX @@ -25,8 +25,6 @@ logger = logging.getLogger(__name__) -DATADOC_DATASET_PATH_ENV_VAR = "DATADOC_DATASET_PATH" - MetadataInputTypes: TypeAlias = str | list[str] | int | float | bool | None @@ -89,10 +87,9 @@ def get_dataset_path() -> str | Path | None: """Extract the path to the dataset from the potential sources.""" if state.metadata.dataset is not None: return state.metadata.dataset - path_from_env = os.getenv(DATADOC_DATASET_PATH_ENV_VAR) + path_from_env = config.get_datadoc_dataset_path() logger.info( - "Dataset path from %s: '%s'", - DATADOC_DATASET_PATH_ENV_VAR, + "Dataset path from env var: '%s'", path_from_env, ) return path_from_env