From 7e526984e368672a194e92d2e41f79376fc09be3 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 26 May 2026 12:22:21 -0600 Subject: [PATCH 01/31] added draft of generalizing api key tools --- .../resource/utilities/api_key_tools.py | 355 ++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 h2integrate/resource/utilities/api_key_tools.py diff --git a/h2integrate/resource/utilities/api_key_tools.py b/h2integrate/resource/utilities/api_key_tools.py new file mode 100644 index 000000000..481751d6a --- /dev/null +++ b/h2integrate/resource/utilities/api_key_tools.py @@ -0,0 +1,355 @@ +import os +import warnings +from pathlib import Path + +from dotenv import load_dotenv + +from h2integrate import ROOT_DIR + + +developer_nlr_gov_key = "" +developer_nlr_gov_email = "" + +# Mapping from new env var names to deprecated old names +_ENV_KEY_NEW = "NLR_API_KEY" +_ENV_KEY_OLD = "NREL_API_KEY" +_ENV_EMAIL_NEW = "NLR_API_EMAIL" +_ENV_EMAIL_OLD = "NREL_API_EMAIL" + +_DEPRECATION_MSG = ( + "The '{old}' environment variable is deprecated and will be removed in a future release. " + "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." +) + +generic_api_key = "" + + +def set_environment_variable(env_var: str): + global generic_api_key + generic_api_key = env_var + return generic_api_key + + +def _get_env_with_fallback(new_name, old_name): + """Get an environment variable by its new name, falling back to the deprecated old name. + + If only the old name is set, a deprecation warning is issued. + + Args: + new_name (str): The new (preferred) environment variable name. + old_name (str): The deprecated environment variable name. + + Returns: + str | None: The value of the environment variable, or None if not set. + """ + value = os.getenv(new_name) + if value is not None: + return value + value = os.getenv(old_name) + if value is not None: + warnings.warn( + _DEPRECATION_MSG.format(old=old_name, new=new_name), + FutureWarning, + stacklevel=3, + ) + return value + return None + + +def load_file_with_env_variables( + fpath, + env_variable_name: str, + legacy_env_variable_name: str | None = None, + set_func=set_environment_variable, +): + """Load environment variables from a text file. + + Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable + names. If only the old names are found in the file a deprecation warning is + emitted. + + Args: + fpath (str | Path): filepath to a text file with the extension '.env' that + may contain the environment variable(s) in `variables`. + variables (list | str, optional): environment variable(s) to load from file. + Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. + + Raises: + ValueError: If an environment variable is not found or found multiple times in the file. + """ + + # open the file and read the lines + with Path(fpath).open("r") as f: + lines = f.readlines() + + # find a line containing the environment variable (try new name first, then old) + var = env_variable_name + line_w_var = [line for line in lines if var in line] + if len(line_w_var) == 0 and legacy_env_variable_name is not None: + line_w_var = [line for line in lines if legacy_env_variable_name in line] + if len(line_w_var) > 0: + warnings.warn( + _DEPRECATION_MSG.format(old=legacy_env_variable_name, new=env_variable_name), + FutureWarning, + stacklevel=2, + ) + var = legacy_env_variable_name # use old name for parsing + if len(line_w_var) != 1: + raise ValueError( + f"{var} variable in found in {fpath} file {len(line_w_var)} times. " + "Please specify this variable once." + ) + + valid_keynames = ( + (env_variable_name, legacy_env_variable_name) + if legacy_env_variable_name is not None + else (env_variable_name) + ) + # grab the line containing the variable, + # assumes the line containing the variable is formatted as "variable=variable_value" + val = line_w_var[0].split(f"{var}=").strip() + # if var is an API key, set it as a global variable + if var in valid_keynames: + set_func(val) + + return + + +def set_api_key_using_dot_env(variable_name: str, alternative_name: str, path=None): + if path and Path(path).exists(): + if Path(path).name == ".env": + load_dotenv(path) + if Path(path).suffix == ".env": + load_file_with_variables(path, variables=_ENV_KEY_NEW) + load_file_with_variables(path, variables=_ENV_EMAIL_NEW) + else: + possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] + for r in possible_locs: + if Path(r).exists(): + load_dotenv(r) + api_key = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) + api_email = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) + if api_key is not None: + set_developer_nlr_gov_key(api_key) + if api_email is not None: + set_developer_nlr_gov_email(api_email) + + +def set_developer_nlr_gov_key(key: str): + """Set `key` as the global variable `developer_nlr_gov_key`. + + Args: + key (str): API key for NLR Developer Network. Should be length 40. + """ + global developer_nlr_gov_key + developer_nlr_gov_key = key + return developer_nlr_gov_key + + +def set_developer_nlr_gov_email(email: str): + """Set `email` as the global variable `developer_nlr_gov_email`. + + Args: + email (str): email corresponding to the API key for NLR Developer Network. + """ + global developer_nlr_gov_email + developer_nlr_gov_email = email + return developer_nlr_gov_email + + +def load_file_with_variables(fpath, variables=["NLR_API_KEY", "NLR_API_EMAIL"]): + """Load environment variables from a text file. + + Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable + names. If only the old names are found in the file a deprecation warning is + emitted. + + Args: + fpath (str | Path): filepath to a text file with the extension '.env' that + may contain the environment variable(s) in `variables`. + variables (list | str, optional): environment variable(s) to load from file. + Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. + + Raises: + ValueError: If an environment variable is not found or found multiple times in the file. + """ + + # Mapping from new names to old (deprecated) names for file lookups + _new_to_old = { + _ENV_KEY_NEW: _ENV_KEY_OLD, + _ENV_EMAIL_NEW: _ENV_EMAIL_OLD, + } + + # open the file and read the lines + with Path(fpath).open("r") as f: + lines = f.readlines() + if isinstance(variables, str): + variables = [variables] + + # iterate through each variable + for var in variables: + # find a line containing the environment variable (try new name first, then old) + line_w_var = [line for line in lines if var in line] + if len(line_w_var) == 0 and var in _new_to_old: + old_var = _new_to_old[var] + line_w_var = [line for line in lines if old_var in line] + if len(line_w_var) > 0: + warnings.warn( + _DEPRECATION_MSG.format(old=old_var, new=var), + FutureWarning, + stacklevel=2, + ) + var = old_var # use old name for parsing + if len(line_w_var) != 1: + raise ValueError( + f"{var} variable in found in {fpath} file {len(line_w_var)} times. " + "Please specify this variable once." + ) + # grab the line containing the variable, + # assumes the line containing the variable is formatted as "variable=variable_value" + val = line_w_var[0].split(f"{var}=").strip() + # if var is an API key, set it as a global variable + if var in (_ENV_KEY_NEW, _ENV_KEY_OLD): + set_developer_nlr_gov_key(val) + # if var is an API email, set it as a global variable + if var in (_ENV_EMAIL_NEW, _ENV_EMAIL_OLD): + set_developer_nlr_gov_email(val) + return + + +def set_api_key_dot_env(path=None): + """Sets the environment variables NLR_API_EMAIL and NLR_API_KEY from a .env file. + + Also supports the deprecated ``NREL_API_EMAIL`` and ``NREL_API_KEY`` variable + names for backward compatibility (with deprecation warnings). + + The following logic is used if `path` is input and exists: + + 1) If the filename of the path is '.env', load the environment variables using `load_dotenv()`. + Proceed to Step 3. + 2) If the filename of the path has an extension of '.env' (such a filename of 'my_env.env'), + then load the environment variables using `load_file_with_variables()`. Proceed to step 3. + + The following logic is used if `path` is not input or does not exist: + + 1) check for possible locations of the '.env' file. Searches the current working directory, + the ROOT_DIR, and the parent of the ROOT_DIR. If the '.env' file is found in one of these + locations, load the environment variables using `load_dotenv()`. Proceed to step 3. + + The following is run after the above step(s): + + 3) Get the environment variables NLR_API_KEY and NLR_API_EMAIL (falling back to the + deprecated NREL_API_KEY / NREL_API_EMAIL). If found, set them as global variables + using `set_developer_nlr_gov_key()` / `set_developer_nlr_gov_email()`. + + Args: + path (Path | str, optional): Path to environment file. + Defaults to None. + """ + if path and Path(path).exists(): + if Path(path).name == ".env": + load_dotenv(path) + if Path(path).suffix == ".env": + load_file_with_variables(path, variables=_ENV_KEY_NEW) + load_file_with_variables(path, variables=_ENV_EMAIL_NEW) + else: + possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] + for r in possible_locs: + if Path(r).exists(): + load_dotenv(r) + api_key = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) + api_email = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) + if api_key is not None: + set_developer_nlr_gov_key(api_key) + if api_email is not None: + set_developer_nlr_gov_email(api_email) + + +def get_nlr_developer_api_key(env_path=None): + """Load the API key (NLR_API_KEY). This method does the following: + + 1) check for NLR_API_KEY (or deprecated NREL_API_KEY) environment variable, + return if found. Otherwise, proceed to Step 2. + 2) check if the key has already been set as a global variable from + running `set_api_key_dot_env()`. If not set, proceed to Step 3. + 3) Attempt to set the key by calling `set_api_key_dot_env()`. + 4) Check if the key has been set as a global variable. If found, return. + Otherwise, raises a ValueError. + + Args: + env_path (Path | str, optional): Filepath to .env file. + Defaults to None. + + Raises: + ValueError: If NLR_API_KEY was not found as an environment variable + and the path to the environment file was not input. + ValueError: If NLR_API_KEY was not found as an environment variable and not + set properly using the environment path. + + Returns: + str: API key for NLR Developer Network. + """ + + # check if set as an environment variable (new name first, then old with warning) + env_val = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) + if env_val is not None: + return env_val + + # check if set as a global variable + global developer_nlr_gov_key + if len(developer_nlr_gov_key) == 0: + # attempt to set the variable from a .env file + set_api_key_dot_env(path=env_path) + + if len(developer_nlr_gov_key) == 0: + # variable was not found + raise ValueError( + "NLR_API_KEY (or NREL_API_KEY) has not been set. " + "Please set the NLR_API_KEY environment variable." + ) + return developer_nlr_gov_key + + +def get_nlr_developer_api_email(env_path=None): + """Load the API email (NLR_API_EMAIL). This method does the following: + + 1) check for NLR_API_EMAIL (or deprecated NREL_API_EMAIL) environment variable, + return if found. Otherwise, proceed to Step 2. + 2) check if the email has already been set as a global variable from running + `set_api_key_dot_env()`. If not set, proceed to Step 3. + 3) Attempt to set the email by calling `set_api_key_dot_env()`. + 4) Check if the email has been set as a global variable. If found, return. + Otherwise, raises a ValueError. + + Args: + env_path (Path | str, optional): Filepath to .env file. + Defaults to None. + + Raises: + ValueError: If NLR_API_EMAIL was not found as an environment variable + and the path to the environment file was not input. + ValueError: If NLR_API_EMAIL was not found as an environment variable and not + set properly using the environment path. + + Returns: + str: email for NLR Developer Network API. + """ + + # check if set as an environment variable (new name first, then old with warning) + env_val = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) + if env_val is not None: + return env_val + + # check if set as a global variable + global developer_nlr_gov_email + if len(developer_nlr_gov_email) == 0: + # attempt to set the variable from a .env file + set_api_key_dot_env(path=env_path) + + if len(developer_nlr_gov_email) == 0: + # variable was not found + raise ValueError( + "NLR_API_EMAIL (or NREL_API_EMAIL) has not been set. " + "Please set the NLR_API_EMAIL environment variable." + ) + return developer_nlr_gov_email From de7ddc5741c2e62065b87800d6382af93a3d467d Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:52:31 -0600 Subject: [PATCH 02/31] generalized api methods properly --- .../resource/solar/nlr_developer_api_base.py | 2 +- h2integrate/resource/test/conftest.py | 2 +- .../resource/utilities/env_var_api_tools.py | 236 ++++++++++++++++++ .../resource/wind/nlr_developer_wtk_api.py | 2 +- test/conftest.py | 2 +- 5 files changed, 240 insertions(+), 4 deletions(-) create mode 100644 h2integrate/resource/utilities/env_var_api_tools.py diff --git a/h2integrate/resource/solar/nlr_developer_api_base.py b/h2integrate/resource/solar/nlr_developer_api_base.py index 473a1656a..2369b5f3f 100644 --- a/h2integrate/resource/solar/nlr_developer_api_base.py +++ b/h2integrate/resource/solar/nlr_developer_api_base.py @@ -3,7 +3,7 @@ import pandas as pd from h2integrate.resource.solar.solar_resource_base import SolarResourceBaseAPIModel -from h2integrate.resource.utilities.nlr_developer_api_keys import ( +from h2integrate.resource.utilities.env_var_api_tools import ( get_nlr_developer_api_key, get_nlr_developer_api_email, ) diff --git a/h2integrate/resource/test/conftest.py b/h2integrate/resource/test/conftest.py index 5cd0ab0ea..f9f3116d1 100644 --- a/h2integrate/resource/test/conftest.py +++ b/h2integrate/resource/test/conftest.py @@ -2,7 +2,7 @@ import pytest -from h2integrate.resource.utilities.nlr_developer_api_keys import set_nlr_key_dot_env +from h2integrate.resource.utilities.env_var_api_tools import set_nlr_key_dot_env from test.conftest import ( # noqa: F401 temp_dir, diff --git a/h2integrate/resource/utilities/env_var_api_tools.py b/h2integrate/resource/utilities/env_var_api_tools.py new file mode 100644 index 000000000..d3ba39157 --- /dev/null +++ b/h2integrate/resource/utilities/env_var_api_tools.py @@ -0,0 +1,236 @@ +import os +import warnings +from pathlib import Path +from functools import partial, update_wrapper + +from dotenv import load_dotenv + +from h2integrate import ROOT_DIR + + +# instantiate global variables +developer_nlr_gov_key = "" +developer_nlr_gov_email = "" + +# Mapping from new env var names to deprecated old names +_ENV_KEY_NEW = "NLR_API_KEY" +_ENV_KEY_OLD = "NREL_API_KEY" +_ENV_EMAIL_NEW = "NLR_API_EMAIL" +_ENV_EMAIL_OLD = "NREL_API_EMAIL" + +_DEPRECATION_MSG = ( + "The '{old}' environment variable is deprecated and will be removed in a future release. " + "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." +) + + +def _get_env_with_fallback(new_name, old_name): + """Get an environment variable by its new name, falling back to the deprecated old name. + + If only the old name is set, a deprecation warning is issued. + + Args: + new_name (str): The new (preferred) environment variable name. + old_name (str): The deprecated environment variable name. + + Returns: + str | None: The value of the environment variable, or None if not set. + """ + value = os.getenv(new_name) + if value is not None: + return value + if old_name is None: + return None + value = os.getenv(old_name) + if value is not None: + warnings.warn( + _DEPRECATION_MSG.format(old=old_name, new=new_name), + FutureWarning, + stacklevel=3, + ) + return value + return None + + +def set_environment_var(global_varname: str, var_value: str): + """Set `var_value` as the global variable `developer_nlr_gov_key`. + + Args: + key (str): API key for NLR Developer Network. Should be length 40. + """ + # generalized form of set_developer_nlr_gov_key + globals()[global_varname] = var_value + # eval(f"global {global_varname}") + # eval(f"{global_varname} = {var_value}") + # return eval(global_varname) + return globals()[global_varname] + + +set_developer_nlr_gov_key = partial(set_environment_var, global_varname="developer_nlr_gov_key") +set_developer_nlr_gov_email = partial(set_environment_var, global_varname="developer_nlr_gov_email") + + +def load_file_with_variables(setter_method, fpath, variables: str | list[str]): + """Load environment variables from a text file. + + Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable + names. If only the old names are found in the file a deprecation warning is + emitted. + + Args: + fpath (str | Path): filepath to a text file with the extension '.env' that + may contain the environment variable(s) in `variables`. + variables (list | str, optional): environment variable(s) to load from file. + Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. + + Raises: + ValueError: If an environment variable is not found or found multiple times in the file. + """ + + # Mapping from new names to old (deprecated) names for file lookups + _new_to_old = { + _ENV_KEY_NEW: _ENV_KEY_OLD, + _ENV_EMAIL_NEW: _ENV_EMAIL_OLD, + } + + # open the file and read the lines + with Path(fpath).open("r") as f: + lines = f.readlines() + if isinstance(variables, str): + variables = [variables] + + old_variables = [_new_to_old(v) for v in variables if v in _new_to_old] + + # iterate through each variable + for var in variables: + # find a line containing the environment variable (try new name first, then old) + line_w_var = [line for line in lines if var in line] + if len(line_w_var) == 0 and var in _new_to_old: + old_var = _new_to_old[var] + line_w_var = [line for line in lines if old_var in line] + if len(line_w_var) > 0: + warnings.warn( + _DEPRECATION_MSG.format(old=old_var, new=var), + FutureWarning, + stacklevel=2, + ) + var = old_var # use old name for parsing + if len(line_w_var) != 1: + raise ValueError( + f"{var} variable in found in {fpath} file {len(line_w_var)} times. " + "Please specify this variable once." + ) + # grab the line containing the variable, + # assumes the line containing the variable is formatted as "variable=variable_value" + val = line_w_var[0].split(f"{var}=").strip() + # if var is an API key, set it as a global variable + in_old = True if len(old_variables) > 0 and var in old_variables else False + if var in variables or in_old: + setter_method(var_value=val) + # if var is an API email, set it as a global variable + # if var in (_ENV_EMAIL_NEW, _ENV_EMAIL_OLD): + # set_developer_nlr_gov_email(val) + return + + +def set_env_var_dot_env(setter_method, varname_new: str, varname_old: str | None = None, path=None): + # generalized version of set_nlr_key_dot_env + # varname_new is like _ENV_KEY_NEW + # varname_old is like _ENV_KEY_OLD + if path and Path(path).exists(): + if Path(path).name == ".env": + load_dotenv(path) + if Path(path).suffix == ".env": + load_file_with_variables(setter_method, path, variables=varname_new) + else: + possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] + for r in possible_locs: + if Path(r).exists(): + load_dotenv(r) + val = _get_env_with_fallback(varname_new, varname_old) + if val is not None: + setter_method(var_value=val) + + +def get_environment_var( + global_varname, setter_method, varname_new: str, varname_old: str | None = None, env_path=None +): + """Load the API key (NLR_API_KEY). This method does the following: + + 1) check for NLR_API_KEY (or deprecated NREL_API_KEY) environment variable, + return if found. Otherwise, proceed to Step 2. + 2) check if the key has already been set as a global variable from + running `set_nlr_key_dot_env()`. If not set, proceed to Step 3. + 3) Attempt to set the key by calling `set_nlr_key_dot_env()`. + 4) Check if the key has been set as a global variable. If found, return. + Otherwise, raises a ValueError. + + Args: + env_path (Path | str, optional): Filepath to .env file. + Defaults to None. + + Raises: + ValueError: If NLR_API_KEY was not found as an environment variable + and the path to the environment file was not input. + ValueError: If NLR_API_KEY was not found as an environment variable and not + set properly using the environment path. + + Returns: + str: API key for NLR Developer Network. + """ + + # check if set as an environment variable (new name first, then old with warning) + env_val = _get_env_with_fallback(varname_new, varname_old) + if env_val is not None: + return env_val + + # check if set as a global variable + if len(globals()[global_varname]) == 0: + # if len(developer_nlr_gov_key) == 0: + # attempt to set the variable from a .env file + set_env_var_dot_env(setter_method, varname_new, varname_old, path=env_path) + + if len(globals()[global_varname]) == 0: + # variable was not found + raise ValueError( + f"{varname_new} (or {varname_old}) has not been set. " + f"Please set the {varname_new} environment variable." + ) + return globals()[global_varname] + + +get_nlr_developer_api_key = partial( + get_environment_var, + global_varname="developer_nlr_gov_key", + setter_method=set_developer_nlr_gov_key, + varname_new=_ENV_KEY_NEW, + varname_old=_ENV_KEY_OLD, +) +update_wrapper(get_nlr_developer_api_key, get_environment_var) + +get_nlr_developer_api_email = partial( + get_environment_var, + global_varname="developer_nlr_gov_email", + setter_method=set_developer_nlr_gov_email, + varname_new=_ENV_EMAIL_NEW, + varname_old=_ENV_EMAIL_OLD, +) +update_wrapper(get_nlr_developer_api_email, get_environment_var) + +set_nlr_api_key_dot_env = partial( + set_env_var_dot_env, + setter_method=set_developer_nlr_gov_key, + varname_new=_ENV_KEY_NEW, + varname_old=_ENV_KEY_OLD, +) +set_nlr_email_key_dot_env = partial( + set_env_var_dot_env, + setter_method=set_developer_nlr_gov_email, + varname_new=_ENV_EMAIL_NEW, + varname_old=_ENV_EMAIL_OLD, +) + + +def set_nlr_key_dot_env(path=None): + set_nlr_api_key_dot_env(path=path) + set_nlr_email_key_dot_env(path=path) diff --git a/h2integrate/resource/wind/nlr_developer_wtk_api.py b/h2integrate/resource/wind/nlr_developer_wtk_api.py index 34dc09449..5eaf3dda0 100644 --- a/h2integrate/resource/wind/nlr_developer_wtk_api.py +++ b/h2integrate/resource/wind/nlr_developer_wtk_api.py @@ -7,7 +7,7 @@ from h2integrate.core.validators import range_val from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.wind.wind_resource_base import WindResourceBaseAPIModel -from h2integrate.resource.utilities.nlr_developer_api_keys import ( +from h2integrate.resource.utilities.env_var_api_tools import ( get_nlr_developer_api_key, get_nlr_developer_api_email, ) diff --git a/test/conftest.py b/test/conftest.py index a495705b5..6785b1609 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -9,7 +9,7 @@ import pytest from h2integrate import EXAMPLE_DIR -from h2integrate.resource.utilities.nlr_developer_api_keys import set_nlr_key_dot_env +from h2integrate.resource.utilities.env_var_api_tools import set_nlr_key_dot_env def pytest_sessionstart(session): From 6c19042048f6e1b45b8b7417d9b4101c4ab81afd Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:16:19 -0600 Subject: [PATCH 03/31] moved base environment var functions to core --- h2integrate/core/env_tools.py | 208 +++++++ .../resource/solar/nlr_developer_api_base.py | 2 +- h2integrate/resource/test/conftest.py | 2 +- .../utilities/nlr_developer_api_keys.py | 540 ++++++++++-------- .../resource/wind/nlr_developer_wtk_api.py | 2 +- test/conftest.py | 2 +- 6 files changed, 501 insertions(+), 255 deletions(-) create mode 100644 h2integrate/core/env_tools.py diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py new file mode 100644 index 000000000..573b6438c --- /dev/null +++ b/h2integrate/core/env_tools.py @@ -0,0 +1,208 @@ +import os +import warnings +from pathlib import Path + +from dotenv import load_dotenv + +from h2integrate import ROOT_DIR + + +_DEPRECATION_MSG = ( + "The '{old}' environment variable is deprecated and will be removed in a future release. " + "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." +) + + +def _get_env_with_fallback(new_name, old_name): + """Get an environment variable by its new name, falling back to the deprecated old name. + + If only the old name is set, a deprecation warning is issued. + + Args: + new_name (str): The new (preferred) environment variable name. + old_name (str): The deprecated environment variable name. + + Returns: + str | None: The value of the environment variable, or None if not set. + """ + # TODO: update so depr_msg is an input + value = os.getenv(new_name) + if value is not None: + return value + if old_name is None: + return None + value = os.getenv(old_name) + if value is not None: + warnings.warn( + _DEPRECATION_MSG.format(old=old_name, new=new_name), + FutureWarning, + stacklevel=3, + ) + return value + return None + + +def set_environment_var(global_varname: str, var_value: str): + """Set `var_value` as the global variable :py:attr:`global_varname`. + + Args: + var_value (str): value to set for the environment variable + """ + globals()[global_varname] = var_value + return globals()[global_varname] + + +def load_file_with_variables(setter_method, fpath, variables: str | list[str]): + """Load environment variables from a text file. + + Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable + names. If only the old names are found in the file a deprecation warning is + emitted. + + Args: + fpath (str | Path): filepath to a text file with the extension '.env' that + may contain the environment variable(s) in `variables`. + variables (list | str, optional): environment variable(s) to load from file. + Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. + + Raises: + ValueError: If an environment variable is not found or found multiple times in the file. + """ + + # TODO: make it so it can take in alternative names, but variables should be a string + # Mapping from new names to old (deprecated) names for file lookups + _new_to_old = { + "NLR_API_KEY": "NREL_API_KEY", + "NLR_API_EMAIL": "NREL_API_EMAIL", + } + + # open the file and read the lines + with Path(fpath).open("r") as f: + lines = f.readlines() + if isinstance(variables, str): + variables = [variables] + + old_variables = [_new_to_old(v) for v in variables if v in _new_to_old] + + # iterate through each variable + for var in variables: + # find a line containing the environment variable (try new name first, then old) + line_w_var = [line for line in lines if var in line] + if len(line_w_var) == 0 and var in _new_to_old: + old_var = _new_to_old[var] + line_w_var = [line for line in lines if old_var in line] + if len(line_w_var) > 0: + warnings.warn( + _DEPRECATION_MSG.format(old=old_var, new=var), + FutureWarning, + stacklevel=2, + ) + var = old_var # use old name for parsing + if len(line_w_var) != 1: + raise ValueError( + f"{var} variable in found in {fpath} file {len(line_w_var)} times. " + "Please specify this variable once." + ) + # grab the line containing the variable, + # assumes the line containing the variable is formatted as "variable=variable_value" + val = line_w_var[0].split(f"{var}=").strip() + # if var is an API key, set it as a global variable + in_old = True if len(old_variables) > 0 and var in old_variables else False + if var in variables or in_old: + setter_method(var_value=val) + return + + +def set_env_var_dot_env(setter_method, varname_new: str, varname_old: str | None = None, path=None): + """Sets the environment variable :py:attr:`varname_new` from a .env file. + + Also supports the deprecated :py:attr:`varname_old` variable + name for backward compatibility (with deprecation warnings). + + The following logic is used if `path` is input and exists: + + 1) If the filename of the path is '.env', load the environment variables using `load_dotenv()`. + Proceed to Step 3. + 2) If the filename of the path has an extension of '.env' (such a filename of 'my_env.env'), + then load the environment variables using `load_file_with_variables()`. Proceed to step 3. + + The following logic is used if `path` is not input or does not exist: + + 1) check for possible locations of the '.env' file. Searches the current working directory, + the ROOT_DIR, and the parent of the ROOT_DIR. If the '.env' file is found in one of these + locations, load the environment variables using `load_dotenv()`. Proceed to step 3. + + The following is run after the above step(s): + + 3) Get the environment variable :py:attr:`varname_new` (falling back to the + deprecated :py:attr:`varname_old`). If found, set it as global variables + using :py:attr:`setter_method`. + + Args: + path (Path | str, optional): Path to environment file. + Defaults to None. + """ + # generalized version of set_nlr_key_dot_env + # varname_new is like _ENV_KEY_NEW + # varname_old is like _ENV_KEY_OLD + if path and Path(path).exists(): + if Path(path).name == ".env": + load_dotenv(path) + if Path(path).suffix == ".env": + load_file_with_variables(setter_method, path, variables=varname_new) + else: + possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] + for r in possible_locs: + if Path(r).exists(): + load_dotenv(r) + val = _get_env_with_fallback(varname_new, varname_old) + if val is not None: + setter_method(var_value=val) + + +def get_environment_var( + global_varname, setter_method, varname_new: str, varname_old: str | None = None, env_path=None +): + """Load the environment variable named :py:attr:`varname_new` (or :py:attr:`varname_old`). + This method does the following: + + 1) check for :py:attr:`varname_new` (or deprecated :py:attr:`varname_old`) environment variable, + return if found. Otherwise, proceed to Step 2. + 2) check if the key has already been set as a global variable from + running :py:attr:`setter_method`. If not set, proceed to Step 3. + 3) Attempt to set the key by calling :py:attr:`setter_method`. + 4) Check if the key has been set as a global variable. If found, return. + Otherwise, raises a ValueError. + + Args: + env_path (Path | str, optional): Filepath to .env file. + Defaults to None. + + Raises: + ValueError: If py:attr:`varname_old` was not found as an environment variable + and the path to the environment file was not input. + ValueError: If py:attr:`varname_old` was not found as an environment variable and not + set properly using the environment path. + + Returns: + str: value of the environment variable + """ + + # check if set as an environment variable (new name first, then old with warning) + env_val = _get_env_with_fallback(varname_new, varname_old) + if env_val is not None: + return env_val + + # check if set as a global variable + if len(globals()[global_varname]) == 0: + # if len(developer_nlr_gov_key) == 0: + # attempt to set the variable from a .env file + set_env_var_dot_env(setter_method, varname_new, varname_old, path=env_path) + + if len(globals()[global_varname]) == 0: + # variable was not found + raise ValueError( + f"{varname_new} (or {varname_old}) has not been set. " + f"Please set the {varname_new} environment variable." + ) + return globals()[global_varname] diff --git a/h2integrate/resource/solar/nlr_developer_api_base.py b/h2integrate/resource/solar/nlr_developer_api_base.py index 2369b5f3f..473a1656a 100644 --- a/h2integrate/resource/solar/nlr_developer_api_base.py +++ b/h2integrate/resource/solar/nlr_developer_api_base.py @@ -3,7 +3,7 @@ import pandas as pd from h2integrate.resource.solar.solar_resource_base import SolarResourceBaseAPIModel -from h2integrate.resource.utilities.env_var_api_tools import ( +from h2integrate.resource.utilities.nlr_developer_api_keys import ( get_nlr_developer_api_key, get_nlr_developer_api_email, ) diff --git a/h2integrate/resource/test/conftest.py b/h2integrate/resource/test/conftest.py index f9f3116d1..5cd0ab0ea 100644 --- a/h2integrate/resource/test/conftest.py +++ b/h2integrate/resource/test/conftest.py @@ -2,7 +2,7 @@ import pytest -from h2integrate.resource.utilities.env_var_api_tools import set_nlr_key_dot_env +from h2integrate.resource.utilities.nlr_developer_api_keys import set_nlr_key_dot_env from test.conftest import ( # noqa: F401 temp_dir, diff --git a/h2integrate/resource/utilities/nlr_developer_api_keys.py b/h2integrate/resource/utilities/nlr_developer_api_keys.py index 692d006ae..72cf88ec7 100644 --- a/h2integrate/resource/utilities/nlr_developer_api_keys.py +++ b/h2integrate/resource/utilities/nlr_developer_api_keys.py @@ -1,10 +1,6 @@ -import os -import warnings -from pathlib import Path +from functools import partial, update_wrapper -from dotenv import load_dotenv - -from h2integrate import ROOT_DIR +from h2integrate.core.env_tools import get_environment_var, set_env_var_dot_env, set_environment_var developer_nlr_gov_key = "" @@ -16,253 +12,295 @@ _ENV_EMAIL_NEW = "NLR_API_EMAIL" _ENV_EMAIL_OLD = "NREL_API_EMAIL" -_DEPRECATION_MSG = ( - "The '{old}' environment variable is deprecated and will be removed in a future release. " - "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." -) +set_developer_nlr_gov_key = partial(set_environment_var, global_varname="developer_nlr_gov_key") +set_developer_nlr_gov_email = partial(set_environment_var, global_varname="developer_nlr_gov_email") -def _get_env_with_fallback(new_name, old_name): - """Get an environment variable by its new name, falling back to the deprecated old name. - - If only the old name is set, a deprecation warning is issued. - - Args: - new_name (str): The new (preferred) environment variable name. - old_name (str): The deprecated environment variable name. - - Returns: - str | None: The value of the environment variable, or None if not set. - """ - value = os.getenv(new_name) - if value is not None: - return value - value = os.getenv(old_name) - if value is not None: - warnings.warn( - _DEPRECATION_MSG.format(old=old_name, new=new_name), - FutureWarning, - stacklevel=3, - ) - return value - return None - - -def set_developer_nlr_gov_key(key: str): - """Set `key` as the global variable `developer_nlr_gov_key`. - - Args: - key (str): API key for NLR Developer Network. Should be length 40. - """ - global developer_nlr_gov_key - developer_nlr_gov_key = key - return developer_nlr_gov_key - - -def set_developer_nlr_gov_email(email: str): - """Set `email` as the global variable `developer_nlr_gov_email`. - - Args: - email (str): email corresponding to the API key for NLR Developer Network. - """ - global developer_nlr_gov_email - developer_nlr_gov_email = email - return developer_nlr_gov_email - - -def load_file_with_variables(fpath, variables=["NLR_API_KEY", "NLR_API_EMAIL"]): - """Load environment variables from a text file. - - Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable - names. If only the old names are found in the file a deprecation warning is - emitted. - - Args: - fpath (str | Path): filepath to a text file with the extension '.env' that - may contain the environment variable(s) in `variables`. - variables (list | str, optional): environment variable(s) to load from file. - Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. - - Raises: - ValueError: If an environment variable is not found or found multiple times in the file. - """ - - # Mapping from new names to old (deprecated) names for file lookups - _new_to_old = { - _ENV_KEY_NEW: _ENV_KEY_OLD, - _ENV_EMAIL_NEW: _ENV_EMAIL_OLD, - } - - # open the file and read the lines - with Path(fpath).open("r") as f: - lines = f.readlines() - if isinstance(variables, str): - variables = [variables] - - # iterate through each variable - for var in variables: - # find a line containing the environment variable (try new name first, then old) - line_w_var = [line for line in lines if var in line] - if len(line_w_var) == 0 and var in _new_to_old: - old_var = _new_to_old[var] - line_w_var = [line for line in lines if old_var in line] - if len(line_w_var) > 0: - warnings.warn( - _DEPRECATION_MSG.format(old=old_var, new=var), - FutureWarning, - stacklevel=2, - ) - var = old_var # use old name for parsing - if len(line_w_var) != 1: - raise ValueError( - f"{var} variable in found in {fpath} file {len(line_w_var)} times. " - "Please specify this variable once." - ) - # grab the line containing the variable, - # assumes the line containing the variable is formatted as "variable=variable_value" - val = line_w_var[0].split(f"{var}=").strip() - # if var is an API key, set it as a global variable - if var in (_ENV_KEY_NEW, _ENV_KEY_OLD): - set_developer_nlr_gov_key(val) - # if var is an API email, set it as a global variable - if var in (_ENV_EMAIL_NEW, _ENV_EMAIL_OLD): - set_developer_nlr_gov_email(val) - return +get_nlr_developer_api_key = partial( + get_environment_var, + global_varname="developer_nlr_gov_key", + setter_method=set_developer_nlr_gov_key, + varname_new=_ENV_KEY_NEW, + varname_old=_ENV_KEY_OLD, +) +update_wrapper(get_nlr_developer_api_key, get_environment_var) + +get_nlr_developer_api_email = partial( + get_environment_var, + global_varname="developer_nlr_gov_email", + setter_method=set_developer_nlr_gov_email, + varname_new=_ENV_EMAIL_NEW, + varname_old=_ENV_EMAIL_OLD, +) +update_wrapper(get_nlr_developer_api_email, get_environment_var) + +set_nlr_api_key_dot_env = partial( + set_env_var_dot_env, + setter_method=set_developer_nlr_gov_key, + varname_new=_ENV_KEY_NEW, + varname_old=_ENV_KEY_OLD, +) +set_nlr_email_key_dot_env = partial( + set_env_var_dot_env, + setter_method=set_developer_nlr_gov_email, + varname_new=_ENV_EMAIL_NEW, + varname_old=_ENV_EMAIL_OLD, +) def set_nlr_key_dot_env(path=None): - """Sets the environment variables NLR_API_EMAIL and NLR_API_KEY from a .env file. - - Also supports the deprecated ``NREL_API_EMAIL`` and ``NREL_API_KEY`` variable - names for backward compatibility (with deprecation warnings). - - The following logic is used if `path` is input and exists: - - 1) If the filename of the path is '.env', load the environment variables using `load_dotenv()`. - Proceed to Step 3. - 2) If the filename of the path has an extension of '.env' (such a filename of 'my_env.env'), - then load the environment variables using `load_file_with_variables()`. Proceed to step 3. - - The following logic is used if `path` is not input or does not exist: - - 1) check for possible locations of the '.env' file. Searches the current working directory, - the ROOT_DIR, and the parent of the ROOT_DIR. If the '.env' file is found in one of these - locations, load the environment variables using `load_dotenv()`. Proceed to step 3. - - The following is run after the above step(s): - - 3) Get the environment variables NLR_API_KEY and NLR_API_EMAIL (falling back to the - deprecated NREL_API_KEY / NREL_API_EMAIL). If found, set them as global variables - using `set_developer_nlr_gov_key()` / `set_developer_nlr_gov_email()`. - - Args: - path (Path | str, optional): Path to environment file. - Defaults to None. - """ - if path and Path(path).exists(): - if Path(path).name == ".env": - load_dotenv(path) - if Path(path).suffix == ".env": - load_file_with_variables(path, variables=_ENV_KEY_NEW) - load_file_with_variables(path, variables=_ENV_EMAIL_NEW) - else: - possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] - for r in possible_locs: - if Path(r).exists(): - load_dotenv(r) - api_key = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) - api_email = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) - if api_key is not None: - set_developer_nlr_gov_key(api_key) - if api_email is not None: - set_developer_nlr_gov_email(api_email) - - -def get_nlr_developer_api_key(env_path=None): - """Load the API key (NLR_API_KEY). This method does the following: - - 1) check for NLR_API_KEY (or deprecated NREL_API_KEY) environment variable, - return if found. Otherwise, proceed to Step 2. - 2) check if the key has already been set as a global variable from - running `set_nlr_key_dot_env()`. If not set, proceed to Step 3. - 3) Attempt to set the key by calling `set_nlr_key_dot_env()`. - 4) Check if the key has been set as a global variable. If found, return. - Otherwise, raises a ValueError. - - Args: - env_path (Path | str, optional): Filepath to .env file. - Defaults to None. - - Raises: - ValueError: If NLR_API_KEY was not found as an environment variable - and the path to the environment file was not input. - ValueError: If NLR_API_KEY was not found as an environment variable and not - set properly using the environment path. - - Returns: - str: API key for NLR Developer Network. - """ - - # check if set as an environment variable (new name first, then old with warning) - env_val = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) - if env_val is not None: - return env_val - - # check if set as a global variable - global developer_nlr_gov_key - if len(developer_nlr_gov_key) == 0: - # attempt to set the variable from a .env file - set_nlr_key_dot_env(path=env_path) - - if len(developer_nlr_gov_key) == 0: - # variable was not found - raise ValueError( - "NLR_API_KEY (or NREL_API_KEY) has not been set. " - "Please set the NLR_API_KEY environment variable." - ) - return developer_nlr_gov_key - - -def get_nlr_developer_api_email(env_path=None): - """Load the API email (NLR_API_EMAIL). This method does the following: - - 1) check for NLR_API_EMAIL (or deprecated NREL_API_EMAIL) environment variable, - return if found. Otherwise, proceed to Step 2. - 2) check if the email has already been set as a global variable from running - `set_nlr_key_dot_env()`. If not set, proceed to Step 3. - 3) Attempt to set the email by calling `set_nlr_key_dot_env()`. - 4) Check if the email has been set as a global variable. If found, return. - Otherwise, raises a ValueError. - - Args: - env_path (Path | str, optional): Filepath to .env file. - Defaults to None. - - Raises: - ValueError: If NLR_API_EMAIL was not found as an environment variable - and the path to the environment file was not input. - ValueError: If NLR_API_EMAIL was not found as an environment variable and not - set properly using the environment path. - - Returns: - str: email for NLR Developer Network API. - """ - - # check if set as an environment variable (new name first, then old with warning) - env_val = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) - if env_val is not None: - return env_val - - # check if set as a global variable - global developer_nlr_gov_email - if len(developer_nlr_gov_email) == 0: - # attempt to set the variable from a .env file - set_nlr_key_dot_env(path=env_path) - - if len(developer_nlr_gov_email) == 0: - # variable was not found - raise ValueError( - "NLR_API_EMAIL (or NREL_API_EMAIL) has not been set. " - "Please set the NLR_API_EMAIL environment variable." - ) - return developer_nlr_gov_email + set_nlr_api_key_dot_env(path=path) + set_nlr_email_key_dot_env(path=path) + + +# _DEPRECATION_MSG = ( +# "The '{old}' environment variable is deprecated and will be removed in a future release. " +# "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." +# ) + + +# def _get_env_with_fallback(new_name, old_name): +# """Get an environment variable by its new name, falling back to the deprecated old name. + +# If only the old name is set, a deprecation warning is issued. + +# Args: +# new_name (str): The new (preferred) environment variable name. +# old_name (str): The deprecated environment variable name. + +# Returns: +# str | None: The value of the environment variable, or None if not set. +# """ +# value = os.getenv(new_name) +# if value is not None: +# return value +# value = os.getenv(old_name) +# if value is not None: +# warnings.warn( +# _DEPRECATION_MSG.format(old=old_name, new=new_name), +# FutureWarning, +# stacklevel=3, +# ) +# return value +# return None + + +# def set_developer_nlr_gov_key(key: str): +# """Set `key` as the global variable `developer_nlr_gov_key`. + +# Args: +# key (str): API key for NLR Developer Network. Should be length 40. +# """ +# global developer_nlr_gov_key +# developer_nlr_gov_key = key +# return developer_nlr_gov_key + + +# def set_developer_nlr_gov_email(email: str): +# """Set `email` as the global variable `developer_nlr_gov_email`. + +# Args: +# email (str): email corresponding to the API key for NLR Developer Network. +# """ +# global developer_nlr_gov_email +# developer_nlr_gov_email = email +# return developer_nlr_gov_email + + +# def load_file_with_variables(fpath, variables=["NLR_API_KEY", "NLR_API_EMAIL"]): +# """Load environment variables from a text file. + +# Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable +# names. If only the old names are found in the file a deprecation warning is +# emitted. + +# Args: +# fpath (str | Path): filepath to a text file with the extension '.env' that +# may contain the environment variable(s) in `variables`. +# variables (list | str, optional): environment variable(s) to load from file. +# Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. + +# Raises: +# ValueError: If an environment variable is not found or found multiple times in the file. +# """ + +# # Mapping from new names to old (deprecated) names for file lookups +# _new_to_old = { +# _ENV_KEY_NEW: _ENV_KEY_OLD, +# _ENV_EMAIL_NEW: _ENV_EMAIL_OLD, +# } + +# # open the file and read the lines +# with Path(fpath).open("r") as f: +# lines = f.readlines() +# if isinstance(variables, str): +# variables = [variables] + +# # iterate through each variable +# for var in variables: +# # find a line containing the environment variable (try new name first, then old) +# line_w_var = [line for line in lines if var in line] +# if len(line_w_var) == 0 and var in _new_to_old: +# old_var = _new_to_old[var] +# line_w_var = [line for line in lines if old_var in line] +# if len(line_w_var) > 0: +# warnings.warn( +# _DEPRECATION_MSG.format(old=old_var, new=var), +# FutureWarning, +# stacklevel=2, +# ) +# var = old_var # use old name for parsing +# if len(line_w_var) != 1: +# raise ValueError( +# f"{var} variable in found in {fpath} file {len(line_w_var)} times. " +# "Please specify this variable once." +# ) +# # grab the line containing the variable, +# # assumes the line containing the variable is formatted as "variable=variable_value" +# val = line_w_var[0].split(f"{var}=").strip() +# # if var is an API key, set it as a global variable +# if var in (_ENV_KEY_NEW, _ENV_KEY_OLD): +# set_developer_nlr_gov_key(val) +# # if var is an API email, set it as a global variable +# if var in (_ENV_EMAIL_NEW, _ENV_EMAIL_OLD): +# set_developer_nlr_gov_email(val) +# return + + +# def set_nlr_key_dot_env(path=None): +# """Sets the environment variables NLR_API_EMAIL and NLR_API_KEY from a .env file. + +# Also supports the deprecated ``NREL_API_EMAIL`` and ``NREL_API_KEY`` variable +# names for backward compatibility (with deprecation warnings). + +# The following logic is used if `path` is input and exists: + +# 1) If the filename of the path is '.env', load the environment variables using +# `load_dotenv()`. +# Proceed to Step 3. +# 2) If the filename of the path has an extension of '.env' (such a filename of 'my_env.env'), +# then load the environment variables using `load_file_with_variables()`. Proceed to step 3. + +# The following logic is used if `path` is not input or does not exist: + +# 1) check for possible locations of the '.env' file. Searches the current working directory, +# the ROOT_DIR, and the parent of the ROOT_DIR. If the '.env' file is found in one of these +# locations, load the environment variables using `load_dotenv()`. Proceed to step 3. + +# The following is run after the above step(s): + +# 3) Get the environment variables NLR_API_KEY and NLR_API_EMAIL (falling back to the +# deprecated NREL_API_KEY / NREL_API_EMAIL). If found, set them as global variables +# using `set_developer_nlr_gov_key()` / `set_developer_nlr_gov_email()`. + +# Args: +# path (Path | str, optional): Path to environment file. +# Defaults to None. +# """ +# if path and Path(path).exists(): +# if Path(path).name == ".env": +# load_dotenv(path) +# if Path(path).suffix == ".env": +# load_file_with_variables(path, variables=_ENV_KEY_NEW) +# load_file_with_variables(path, variables=_ENV_EMAIL_NEW) +# else: +# possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] +# for r in possible_locs: +# if Path(r).exists(): +# load_dotenv(r) +# api_key = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) +# api_email = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) +# if api_key is not None: +# set_developer_nlr_gov_key(api_key) +# if api_email is not None: +# set_developer_nlr_gov_email(api_email) + + +# def get_nlr_developer_api_key(env_path=None): +# """Load the API key (NLR_API_KEY). This method does the following: + +# 1) check for NLR_API_KEY (or deprecated NREL_API_KEY) environment variable, +# return if found. Otherwise, proceed to Step 2. +# 2) check if the key has already been set as a global variable from +# running `set_nlr_key_dot_env()`. If not set, proceed to Step 3. +# 3) Attempt to set the key by calling `set_nlr_key_dot_env()`. +# 4) Check if the key has been set as a global variable. If found, return. +# Otherwise, raises a ValueError. + +# Args: +# env_path (Path | str, optional): Filepath to .env file. +# Defaults to None. + +# Raises: +# ValueError: If NLR_API_KEY was not found as an environment variable +# and the path to the environment file was not input. +# ValueError: If NLR_API_KEY was not found as an environment variable and not +# set properly using the environment path. + +# Returns: +# str: API key for NLR Developer Network. +# """ + +# # check if set as an environment variable (new name first, then old with warning) +# env_val = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) +# if env_val is not None: +# return env_val + +# # check if set as a global variable +# global developer_nlr_gov_key +# if len(developer_nlr_gov_key) == 0: +# # attempt to set the variable from a .env file +# set_nlr_key_dot_env(path=env_path) + +# if len(developer_nlr_gov_key) == 0: +# # variable was not found +# raise ValueError( +# "NLR_API_KEY (or NREL_API_KEY) has not been set. " +# "Please set the NLR_API_KEY environment variable." +# ) +# return developer_nlr_gov_key + + +# def get_nlr_developer_api_email(env_path=None): +# """Load the API email (NLR_API_EMAIL). This method does the following: + +# 1) check for NLR_API_EMAIL (or deprecated NREL_API_EMAIL) environment variable, +# return if found. Otherwise, proceed to Step 2. +# 2) check if the email has already been set as a global variable from running +# `set_nlr_key_dot_env()`. If not set, proceed to Step 3. +# 3) Attempt to set the email by calling `set_nlr_key_dot_env()`. +# 4) Check if the email has been set as a global variable. If found, return. +# Otherwise, raises a ValueError. + +# Args: +# env_path (Path | str, optional): Filepath to .env file. +# Defaults to None. + +# Raises: +# ValueError: If NLR_API_EMAIL was not found as an environment variable +# and the path to the environment file was not input. +# ValueError: If NLR_API_EMAIL was not found as an environment variable and not +# set properly using the environment path. + +# Returns: +# str: email for NLR Developer Network API. +# """ + +# # check if set as an environment variable (new name first, then old with warning) +# env_val = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) +# if env_val is not None: +# return env_val + +# # check if set as a global variable +# global developer_nlr_gov_email +# if len(developer_nlr_gov_email) == 0: +# # attempt to set the variable from a .env file +# set_nlr_key_dot_env(path=env_path) + +# if len(developer_nlr_gov_email) == 0: +# # variable was not found +# raise ValueError( +# "NLR_API_EMAIL (or NREL_API_EMAIL) has not been set. " +# "Please set the NLR_API_EMAIL environment variable." +# ) +# return developer_nlr_gov_email diff --git a/h2integrate/resource/wind/nlr_developer_wtk_api.py b/h2integrate/resource/wind/nlr_developer_wtk_api.py index 5eaf3dda0..34dc09449 100644 --- a/h2integrate/resource/wind/nlr_developer_wtk_api.py +++ b/h2integrate/resource/wind/nlr_developer_wtk_api.py @@ -7,7 +7,7 @@ from h2integrate.core.validators import range_val from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.wind.wind_resource_base import WindResourceBaseAPIModel -from h2integrate.resource.utilities.env_var_api_tools import ( +from h2integrate.resource.utilities.nlr_developer_api_keys import ( get_nlr_developer_api_key, get_nlr_developer_api_email, ) diff --git a/test/conftest.py b/test/conftest.py index 6785b1609..a495705b5 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -9,7 +9,7 @@ import pytest from h2integrate import EXAMPLE_DIR -from h2integrate.resource.utilities.env_var_api_tools import set_nlr_key_dot_env +from h2integrate.resource.utilities.nlr_developer_api_keys import set_nlr_key_dot_env def pytest_sessionstart(session): From ed387c84bc77f0730d9b2bbecd4f1fee87276096 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:18:02 -0600 Subject: [PATCH 04/31] removed old work --- .../resource/utilities/api_key_tools.py | 355 ------------------ .../resource/utilities/env_var_api_tools.py | 236 ------------ 2 files changed, 591 deletions(-) delete mode 100644 h2integrate/resource/utilities/api_key_tools.py delete mode 100644 h2integrate/resource/utilities/env_var_api_tools.py diff --git a/h2integrate/resource/utilities/api_key_tools.py b/h2integrate/resource/utilities/api_key_tools.py deleted file mode 100644 index 481751d6a..000000000 --- a/h2integrate/resource/utilities/api_key_tools.py +++ /dev/null @@ -1,355 +0,0 @@ -import os -import warnings -from pathlib import Path - -from dotenv import load_dotenv - -from h2integrate import ROOT_DIR - - -developer_nlr_gov_key = "" -developer_nlr_gov_email = "" - -# Mapping from new env var names to deprecated old names -_ENV_KEY_NEW = "NLR_API_KEY" -_ENV_KEY_OLD = "NREL_API_KEY" -_ENV_EMAIL_NEW = "NLR_API_EMAIL" -_ENV_EMAIL_OLD = "NREL_API_EMAIL" - -_DEPRECATION_MSG = ( - "The '{old}' environment variable is deprecated and will be removed in a future release. " - "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." -) - -generic_api_key = "" - - -def set_environment_variable(env_var: str): - global generic_api_key - generic_api_key = env_var - return generic_api_key - - -def _get_env_with_fallback(new_name, old_name): - """Get an environment variable by its new name, falling back to the deprecated old name. - - If only the old name is set, a deprecation warning is issued. - - Args: - new_name (str): The new (preferred) environment variable name. - old_name (str): The deprecated environment variable name. - - Returns: - str | None: The value of the environment variable, or None if not set. - """ - value = os.getenv(new_name) - if value is not None: - return value - value = os.getenv(old_name) - if value is not None: - warnings.warn( - _DEPRECATION_MSG.format(old=old_name, new=new_name), - FutureWarning, - stacklevel=3, - ) - return value - return None - - -def load_file_with_env_variables( - fpath, - env_variable_name: str, - legacy_env_variable_name: str | None = None, - set_func=set_environment_variable, -): - """Load environment variables from a text file. - - Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable - names. If only the old names are found in the file a deprecation warning is - emitted. - - Args: - fpath (str | Path): filepath to a text file with the extension '.env' that - may contain the environment variable(s) in `variables`. - variables (list | str, optional): environment variable(s) to load from file. - Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. - - Raises: - ValueError: If an environment variable is not found or found multiple times in the file. - """ - - # open the file and read the lines - with Path(fpath).open("r") as f: - lines = f.readlines() - - # find a line containing the environment variable (try new name first, then old) - var = env_variable_name - line_w_var = [line for line in lines if var in line] - if len(line_w_var) == 0 and legacy_env_variable_name is not None: - line_w_var = [line for line in lines if legacy_env_variable_name in line] - if len(line_w_var) > 0: - warnings.warn( - _DEPRECATION_MSG.format(old=legacy_env_variable_name, new=env_variable_name), - FutureWarning, - stacklevel=2, - ) - var = legacy_env_variable_name # use old name for parsing - if len(line_w_var) != 1: - raise ValueError( - f"{var} variable in found in {fpath} file {len(line_w_var)} times. " - "Please specify this variable once." - ) - - valid_keynames = ( - (env_variable_name, legacy_env_variable_name) - if legacy_env_variable_name is not None - else (env_variable_name) - ) - # grab the line containing the variable, - # assumes the line containing the variable is formatted as "variable=variable_value" - val = line_w_var[0].split(f"{var}=").strip() - # if var is an API key, set it as a global variable - if var in valid_keynames: - set_func(val) - - return - - -def set_api_key_using_dot_env(variable_name: str, alternative_name: str, path=None): - if path and Path(path).exists(): - if Path(path).name == ".env": - load_dotenv(path) - if Path(path).suffix == ".env": - load_file_with_variables(path, variables=_ENV_KEY_NEW) - load_file_with_variables(path, variables=_ENV_EMAIL_NEW) - else: - possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] - for r in possible_locs: - if Path(r).exists(): - load_dotenv(r) - api_key = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) - api_email = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) - if api_key is not None: - set_developer_nlr_gov_key(api_key) - if api_email is not None: - set_developer_nlr_gov_email(api_email) - - -def set_developer_nlr_gov_key(key: str): - """Set `key` as the global variable `developer_nlr_gov_key`. - - Args: - key (str): API key for NLR Developer Network. Should be length 40. - """ - global developer_nlr_gov_key - developer_nlr_gov_key = key - return developer_nlr_gov_key - - -def set_developer_nlr_gov_email(email: str): - """Set `email` as the global variable `developer_nlr_gov_email`. - - Args: - email (str): email corresponding to the API key for NLR Developer Network. - """ - global developer_nlr_gov_email - developer_nlr_gov_email = email - return developer_nlr_gov_email - - -def load_file_with_variables(fpath, variables=["NLR_API_KEY", "NLR_API_EMAIL"]): - """Load environment variables from a text file. - - Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable - names. If only the old names are found in the file a deprecation warning is - emitted. - - Args: - fpath (str | Path): filepath to a text file with the extension '.env' that - may contain the environment variable(s) in `variables`. - variables (list | str, optional): environment variable(s) to load from file. - Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. - - Raises: - ValueError: If an environment variable is not found or found multiple times in the file. - """ - - # Mapping from new names to old (deprecated) names for file lookups - _new_to_old = { - _ENV_KEY_NEW: _ENV_KEY_OLD, - _ENV_EMAIL_NEW: _ENV_EMAIL_OLD, - } - - # open the file and read the lines - with Path(fpath).open("r") as f: - lines = f.readlines() - if isinstance(variables, str): - variables = [variables] - - # iterate through each variable - for var in variables: - # find a line containing the environment variable (try new name first, then old) - line_w_var = [line for line in lines if var in line] - if len(line_w_var) == 0 and var in _new_to_old: - old_var = _new_to_old[var] - line_w_var = [line for line in lines if old_var in line] - if len(line_w_var) > 0: - warnings.warn( - _DEPRECATION_MSG.format(old=old_var, new=var), - FutureWarning, - stacklevel=2, - ) - var = old_var # use old name for parsing - if len(line_w_var) != 1: - raise ValueError( - f"{var} variable in found in {fpath} file {len(line_w_var)} times. " - "Please specify this variable once." - ) - # grab the line containing the variable, - # assumes the line containing the variable is formatted as "variable=variable_value" - val = line_w_var[0].split(f"{var}=").strip() - # if var is an API key, set it as a global variable - if var in (_ENV_KEY_NEW, _ENV_KEY_OLD): - set_developer_nlr_gov_key(val) - # if var is an API email, set it as a global variable - if var in (_ENV_EMAIL_NEW, _ENV_EMAIL_OLD): - set_developer_nlr_gov_email(val) - return - - -def set_api_key_dot_env(path=None): - """Sets the environment variables NLR_API_EMAIL and NLR_API_KEY from a .env file. - - Also supports the deprecated ``NREL_API_EMAIL`` and ``NREL_API_KEY`` variable - names for backward compatibility (with deprecation warnings). - - The following logic is used if `path` is input and exists: - - 1) If the filename of the path is '.env', load the environment variables using `load_dotenv()`. - Proceed to Step 3. - 2) If the filename of the path has an extension of '.env' (such a filename of 'my_env.env'), - then load the environment variables using `load_file_with_variables()`. Proceed to step 3. - - The following logic is used if `path` is not input or does not exist: - - 1) check for possible locations of the '.env' file. Searches the current working directory, - the ROOT_DIR, and the parent of the ROOT_DIR. If the '.env' file is found in one of these - locations, load the environment variables using `load_dotenv()`. Proceed to step 3. - - The following is run after the above step(s): - - 3) Get the environment variables NLR_API_KEY and NLR_API_EMAIL (falling back to the - deprecated NREL_API_KEY / NREL_API_EMAIL). If found, set them as global variables - using `set_developer_nlr_gov_key()` / `set_developer_nlr_gov_email()`. - - Args: - path (Path | str, optional): Path to environment file. - Defaults to None. - """ - if path and Path(path).exists(): - if Path(path).name == ".env": - load_dotenv(path) - if Path(path).suffix == ".env": - load_file_with_variables(path, variables=_ENV_KEY_NEW) - load_file_with_variables(path, variables=_ENV_EMAIL_NEW) - else: - possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] - for r in possible_locs: - if Path(r).exists(): - load_dotenv(r) - api_key = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) - api_email = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) - if api_key is not None: - set_developer_nlr_gov_key(api_key) - if api_email is not None: - set_developer_nlr_gov_email(api_email) - - -def get_nlr_developer_api_key(env_path=None): - """Load the API key (NLR_API_KEY). This method does the following: - - 1) check for NLR_API_KEY (or deprecated NREL_API_KEY) environment variable, - return if found. Otherwise, proceed to Step 2. - 2) check if the key has already been set as a global variable from - running `set_api_key_dot_env()`. If not set, proceed to Step 3. - 3) Attempt to set the key by calling `set_api_key_dot_env()`. - 4) Check if the key has been set as a global variable. If found, return. - Otherwise, raises a ValueError. - - Args: - env_path (Path | str, optional): Filepath to .env file. - Defaults to None. - - Raises: - ValueError: If NLR_API_KEY was not found as an environment variable - and the path to the environment file was not input. - ValueError: If NLR_API_KEY was not found as an environment variable and not - set properly using the environment path. - - Returns: - str: API key for NLR Developer Network. - """ - - # check if set as an environment variable (new name first, then old with warning) - env_val = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) - if env_val is not None: - return env_val - - # check if set as a global variable - global developer_nlr_gov_key - if len(developer_nlr_gov_key) == 0: - # attempt to set the variable from a .env file - set_api_key_dot_env(path=env_path) - - if len(developer_nlr_gov_key) == 0: - # variable was not found - raise ValueError( - "NLR_API_KEY (or NREL_API_KEY) has not been set. " - "Please set the NLR_API_KEY environment variable." - ) - return developer_nlr_gov_key - - -def get_nlr_developer_api_email(env_path=None): - """Load the API email (NLR_API_EMAIL). This method does the following: - - 1) check for NLR_API_EMAIL (or deprecated NREL_API_EMAIL) environment variable, - return if found. Otherwise, proceed to Step 2. - 2) check if the email has already been set as a global variable from running - `set_api_key_dot_env()`. If not set, proceed to Step 3. - 3) Attempt to set the email by calling `set_api_key_dot_env()`. - 4) Check if the email has been set as a global variable. If found, return. - Otherwise, raises a ValueError. - - Args: - env_path (Path | str, optional): Filepath to .env file. - Defaults to None. - - Raises: - ValueError: If NLR_API_EMAIL was not found as an environment variable - and the path to the environment file was not input. - ValueError: If NLR_API_EMAIL was not found as an environment variable and not - set properly using the environment path. - - Returns: - str: email for NLR Developer Network API. - """ - - # check if set as an environment variable (new name first, then old with warning) - env_val = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) - if env_val is not None: - return env_val - - # check if set as a global variable - global developer_nlr_gov_email - if len(developer_nlr_gov_email) == 0: - # attempt to set the variable from a .env file - set_api_key_dot_env(path=env_path) - - if len(developer_nlr_gov_email) == 0: - # variable was not found - raise ValueError( - "NLR_API_EMAIL (or NREL_API_EMAIL) has not been set. " - "Please set the NLR_API_EMAIL environment variable." - ) - return developer_nlr_gov_email diff --git a/h2integrate/resource/utilities/env_var_api_tools.py b/h2integrate/resource/utilities/env_var_api_tools.py deleted file mode 100644 index d3ba39157..000000000 --- a/h2integrate/resource/utilities/env_var_api_tools.py +++ /dev/null @@ -1,236 +0,0 @@ -import os -import warnings -from pathlib import Path -from functools import partial, update_wrapper - -from dotenv import load_dotenv - -from h2integrate import ROOT_DIR - - -# instantiate global variables -developer_nlr_gov_key = "" -developer_nlr_gov_email = "" - -# Mapping from new env var names to deprecated old names -_ENV_KEY_NEW = "NLR_API_KEY" -_ENV_KEY_OLD = "NREL_API_KEY" -_ENV_EMAIL_NEW = "NLR_API_EMAIL" -_ENV_EMAIL_OLD = "NREL_API_EMAIL" - -_DEPRECATION_MSG = ( - "The '{old}' environment variable is deprecated and will be removed in a future release. " - "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." -) - - -def _get_env_with_fallback(new_name, old_name): - """Get an environment variable by its new name, falling back to the deprecated old name. - - If only the old name is set, a deprecation warning is issued. - - Args: - new_name (str): The new (preferred) environment variable name. - old_name (str): The deprecated environment variable name. - - Returns: - str | None: The value of the environment variable, or None if not set. - """ - value = os.getenv(new_name) - if value is not None: - return value - if old_name is None: - return None - value = os.getenv(old_name) - if value is not None: - warnings.warn( - _DEPRECATION_MSG.format(old=old_name, new=new_name), - FutureWarning, - stacklevel=3, - ) - return value - return None - - -def set_environment_var(global_varname: str, var_value: str): - """Set `var_value` as the global variable `developer_nlr_gov_key`. - - Args: - key (str): API key for NLR Developer Network. Should be length 40. - """ - # generalized form of set_developer_nlr_gov_key - globals()[global_varname] = var_value - # eval(f"global {global_varname}") - # eval(f"{global_varname} = {var_value}") - # return eval(global_varname) - return globals()[global_varname] - - -set_developer_nlr_gov_key = partial(set_environment_var, global_varname="developer_nlr_gov_key") -set_developer_nlr_gov_email = partial(set_environment_var, global_varname="developer_nlr_gov_email") - - -def load_file_with_variables(setter_method, fpath, variables: str | list[str]): - """Load environment variables from a text file. - - Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable - names. If only the old names are found in the file a deprecation warning is - emitted. - - Args: - fpath (str | Path): filepath to a text file with the extension '.env' that - may contain the environment variable(s) in `variables`. - variables (list | str, optional): environment variable(s) to load from file. - Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. - - Raises: - ValueError: If an environment variable is not found or found multiple times in the file. - """ - - # Mapping from new names to old (deprecated) names for file lookups - _new_to_old = { - _ENV_KEY_NEW: _ENV_KEY_OLD, - _ENV_EMAIL_NEW: _ENV_EMAIL_OLD, - } - - # open the file and read the lines - with Path(fpath).open("r") as f: - lines = f.readlines() - if isinstance(variables, str): - variables = [variables] - - old_variables = [_new_to_old(v) for v in variables if v in _new_to_old] - - # iterate through each variable - for var in variables: - # find a line containing the environment variable (try new name first, then old) - line_w_var = [line for line in lines if var in line] - if len(line_w_var) == 0 and var in _new_to_old: - old_var = _new_to_old[var] - line_w_var = [line for line in lines if old_var in line] - if len(line_w_var) > 0: - warnings.warn( - _DEPRECATION_MSG.format(old=old_var, new=var), - FutureWarning, - stacklevel=2, - ) - var = old_var # use old name for parsing - if len(line_w_var) != 1: - raise ValueError( - f"{var} variable in found in {fpath} file {len(line_w_var)} times. " - "Please specify this variable once." - ) - # grab the line containing the variable, - # assumes the line containing the variable is formatted as "variable=variable_value" - val = line_w_var[0].split(f"{var}=").strip() - # if var is an API key, set it as a global variable - in_old = True if len(old_variables) > 0 and var in old_variables else False - if var in variables or in_old: - setter_method(var_value=val) - # if var is an API email, set it as a global variable - # if var in (_ENV_EMAIL_NEW, _ENV_EMAIL_OLD): - # set_developer_nlr_gov_email(val) - return - - -def set_env_var_dot_env(setter_method, varname_new: str, varname_old: str | None = None, path=None): - # generalized version of set_nlr_key_dot_env - # varname_new is like _ENV_KEY_NEW - # varname_old is like _ENV_KEY_OLD - if path and Path(path).exists(): - if Path(path).name == ".env": - load_dotenv(path) - if Path(path).suffix == ".env": - load_file_with_variables(setter_method, path, variables=varname_new) - else: - possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] - for r in possible_locs: - if Path(r).exists(): - load_dotenv(r) - val = _get_env_with_fallback(varname_new, varname_old) - if val is not None: - setter_method(var_value=val) - - -def get_environment_var( - global_varname, setter_method, varname_new: str, varname_old: str | None = None, env_path=None -): - """Load the API key (NLR_API_KEY). This method does the following: - - 1) check for NLR_API_KEY (or deprecated NREL_API_KEY) environment variable, - return if found. Otherwise, proceed to Step 2. - 2) check if the key has already been set as a global variable from - running `set_nlr_key_dot_env()`. If not set, proceed to Step 3. - 3) Attempt to set the key by calling `set_nlr_key_dot_env()`. - 4) Check if the key has been set as a global variable. If found, return. - Otherwise, raises a ValueError. - - Args: - env_path (Path | str, optional): Filepath to .env file. - Defaults to None. - - Raises: - ValueError: If NLR_API_KEY was not found as an environment variable - and the path to the environment file was not input. - ValueError: If NLR_API_KEY was not found as an environment variable and not - set properly using the environment path. - - Returns: - str: API key for NLR Developer Network. - """ - - # check if set as an environment variable (new name first, then old with warning) - env_val = _get_env_with_fallback(varname_new, varname_old) - if env_val is not None: - return env_val - - # check if set as a global variable - if len(globals()[global_varname]) == 0: - # if len(developer_nlr_gov_key) == 0: - # attempt to set the variable from a .env file - set_env_var_dot_env(setter_method, varname_new, varname_old, path=env_path) - - if len(globals()[global_varname]) == 0: - # variable was not found - raise ValueError( - f"{varname_new} (or {varname_old}) has not been set. " - f"Please set the {varname_new} environment variable." - ) - return globals()[global_varname] - - -get_nlr_developer_api_key = partial( - get_environment_var, - global_varname="developer_nlr_gov_key", - setter_method=set_developer_nlr_gov_key, - varname_new=_ENV_KEY_NEW, - varname_old=_ENV_KEY_OLD, -) -update_wrapper(get_nlr_developer_api_key, get_environment_var) - -get_nlr_developer_api_email = partial( - get_environment_var, - global_varname="developer_nlr_gov_email", - setter_method=set_developer_nlr_gov_email, - varname_new=_ENV_EMAIL_NEW, - varname_old=_ENV_EMAIL_OLD, -) -update_wrapper(get_nlr_developer_api_email, get_environment_var) - -set_nlr_api_key_dot_env = partial( - set_env_var_dot_env, - setter_method=set_developer_nlr_gov_key, - varname_new=_ENV_KEY_NEW, - varname_old=_ENV_KEY_OLD, -) -set_nlr_email_key_dot_env = partial( - set_env_var_dot_env, - setter_method=set_developer_nlr_gov_email, - varname_new=_ENV_EMAIL_NEW, - varname_old=_ENV_EMAIL_OLD, -) - - -def set_nlr_key_dot_env(path=None): - set_nlr_api_key_dot_env(path=path) - set_nlr_email_key_dot_env(path=path) From 9a0b62dfd22cc53e05c5e348809f98522603806f Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:36:08 -0600 Subject: [PATCH 05/31] minor clean-ups to generalized stuff --- h2integrate/core/env_tools.py | 81 ++++++++----------- .../utilities/nlr_developer_api_keys.py | 30 +++---- 2 files changed, 48 insertions(+), 63 deletions(-) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index 573b6438c..853d7af2c 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -52,64 +52,50 @@ def set_environment_var(global_varname: str, var_value: str): return globals()[global_varname] -def load_file_with_variables(setter_method, fpath, variables: str | list[str]): - """Load environment variables from a text file. +def load_file_with_variables( + setter_method, fpath, varname_new: str, varname_old: str | None = None +): + """Load an environment variable from a text file. - Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable - names. If only the old names are found in the file a deprecation warning is + Supports both the new ``varname_new`` and deprecated ``varname_old`` variable + names. If only the old name is found in the file, a deprecation warning is emitted. Args: fpath (str | Path): filepath to a text file with the extension '.env' that - may contain the environment variable(s) in `variables`. - variables (list | str, optional): environment variable(s) to load from file. - Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. + may contain the environment variable in `variables`. + varname_new (str): environment variable to load from file. Raises: ValueError: If an environment variable is not found or found multiple times in the file. """ - # TODO: make it so it can take in alternative names, but variables should be a string - # Mapping from new names to old (deprecated) names for file lookups - _new_to_old = { - "NLR_API_KEY": "NREL_API_KEY", - "NLR_API_EMAIL": "NREL_API_EMAIL", - } - # open the file and read the lines with Path(fpath).open("r") as f: lines = f.readlines() - if isinstance(variables, str): - variables = [variables] - - old_variables = [_new_to_old(v) for v in variables if v in _new_to_old] - - # iterate through each variable - for var in variables: - # find a line containing the environment variable (try new name first, then old) - line_w_var = [line for line in lines if var in line] - if len(line_w_var) == 0 and var in _new_to_old: - old_var = _new_to_old[var] - line_w_var = [line for line in lines if old_var in line] - if len(line_w_var) > 0: - warnings.warn( - _DEPRECATION_MSG.format(old=old_var, new=var), - FutureWarning, - stacklevel=2, - ) - var = old_var # use old name for parsing - if len(line_w_var) != 1: - raise ValueError( - f"{var} variable in found in {fpath} file {len(line_w_var)} times. " - "Please specify this variable once." + + # find a line containing the environment variable (try new name first, then old) + line_w_var = [line for line in lines if varname_new in line] + var = varname_new + if len(line_w_var) == 0 and varname_old is not None: + line_w_var = [line for line in lines if varname_old in line] + if len(line_w_var) > 0: + warnings.warn( + _DEPRECATION_MSG.format(old=varname_old, new=varname_new), + FutureWarning, + stacklevel=2, ) - # grab the line containing the variable, - # assumes the line containing the variable is formatted as "variable=variable_value" - val = line_w_var[0].split(f"{var}=").strip() - # if var is an API key, set it as a global variable - in_old = True if len(old_variables) > 0 and var in old_variables else False - if var in variables or in_old: - setter_method(var_value=val) + var = varname_old # use old name for parsing + if len(line_w_var) != 1: + raise ValueError( + f"{var} variable in found in {fpath} file {len(line_w_var)} times. " + "Please specify this variable once." + ) + # grab the line containing the variable, + # assumes the line containing the variable is formatted as "variable=variable_value" + val = line_w_var[0].split(f"{var}=").strip() + # set variable as a global variable + setter_method(var_value=val) return @@ -142,14 +128,13 @@ def set_env_var_dot_env(setter_method, varname_new: str, varname_old: str | None path (Path | str, optional): Path to environment file. Defaults to None. """ - # generalized version of set_nlr_key_dot_env - # varname_new is like _ENV_KEY_NEW - # varname_old is like _ENV_KEY_OLD if path and Path(path).exists(): if Path(path).name == ".env": load_dotenv(path) if Path(path).suffix == ".env": - load_file_with_variables(setter_method, path, variables=varname_new) + load_file_with_variables( + setter_method, path, varname_new=varname_new, varname_old=varname_old + ) else: possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] for r in possible_locs: diff --git a/h2integrate/resource/utilities/nlr_developer_api_keys.py b/h2integrate/resource/utilities/nlr_developer_api_keys.py index 72cf88ec7..cd3cacf15 100644 --- a/h2integrate/resource/utilities/nlr_developer_api_keys.py +++ b/h2integrate/resource/utilities/nlr_developer_api_keys.py @@ -3,25 +3,23 @@ from h2integrate.core.env_tools import get_environment_var, set_env_var_dot_env, set_environment_var +# global variables developer_nlr_gov_key = "" developer_nlr_gov_email = "" -# Mapping from new env var names to deprecated old names -_ENV_KEY_NEW = "NLR_API_KEY" -_ENV_KEY_OLD = "NREL_API_KEY" -_ENV_EMAIL_NEW = "NLR_API_EMAIL" -_ENV_EMAIL_OLD = "NREL_API_EMAIL" - +# Setter methods for each NLR API variable set_developer_nlr_gov_key = partial(set_environment_var, global_varname="developer_nlr_gov_key") +update_wrapper(set_developer_nlr_gov_key, set_environment_var) set_developer_nlr_gov_email = partial(set_environment_var, global_varname="developer_nlr_gov_email") +update_wrapper(set_developer_nlr_gov_email, set_environment_var) - +# Getter methods called by NLR API resource models get_nlr_developer_api_key = partial( get_environment_var, global_varname="developer_nlr_gov_key", setter_method=set_developer_nlr_gov_key, - varname_new=_ENV_KEY_NEW, - varname_old=_ENV_KEY_OLD, + varname_new="NLR_API_KEY", + varname_old="NREL_API_KEY", ) update_wrapper(get_nlr_developer_api_key, get_environment_var) @@ -29,25 +27,27 @@ get_environment_var, global_varname="developer_nlr_gov_email", setter_method=set_developer_nlr_gov_email, - varname_new=_ENV_EMAIL_NEW, - varname_old=_ENV_EMAIL_OLD, + varname_new="NLR_API_EMAIL", + varname_old="NREL_API_EMAIL", ) update_wrapper(get_nlr_developer_api_email, get_environment_var) +# Generic setter methods set_nlr_api_key_dot_env = partial( set_env_var_dot_env, setter_method=set_developer_nlr_gov_key, - varname_new=_ENV_KEY_NEW, - varname_old=_ENV_KEY_OLD, + varname_new="NLR_API_KEY", + varname_old="NREL_API_KEY", ) set_nlr_email_key_dot_env = partial( set_env_var_dot_env, setter_method=set_developer_nlr_gov_email, - varname_new=_ENV_EMAIL_NEW, - varname_old=_ENV_EMAIL_OLD, + varname_new="NLR_API_EMAIL", + varname_old="NREL_API_EMAIL", ) +# Setter methods for both variables needed for NLR API calls def set_nlr_key_dot_env(path=None): set_nlr_api_key_dot_env(path=path) set_nlr_email_key_dot_env(path=path) From 05f04f3c1c1eeee5665992ac53b6960852d52be7 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:37:17 -0600 Subject: [PATCH 06/31] removed commented out code --- h2integrate/core/env_tools.py | 1 - .../utilities/nlr_developer_api_keys.py | 253 ------------------ 2 files changed, 254 deletions(-) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index 853d7af2c..ea51bf29b 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -180,7 +180,6 @@ def get_environment_var( # check if set as a global variable if len(globals()[global_varname]) == 0: - # if len(developer_nlr_gov_key) == 0: # attempt to set the variable from a .env file set_env_var_dot_env(setter_method, varname_new, varname_old, path=env_path) diff --git a/h2integrate/resource/utilities/nlr_developer_api_keys.py b/h2integrate/resource/utilities/nlr_developer_api_keys.py index cd3cacf15..dc4c9e2ea 100644 --- a/h2integrate/resource/utilities/nlr_developer_api_keys.py +++ b/h2integrate/resource/utilities/nlr_developer_api_keys.py @@ -51,256 +51,3 @@ def set_nlr_key_dot_env(path=None): set_nlr_api_key_dot_env(path=path) set_nlr_email_key_dot_env(path=path) - - -# _DEPRECATION_MSG = ( -# "The '{old}' environment variable is deprecated and will be removed in a future release. " -# "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." -# ) - - -# def _get_env_with_fallback(new_name, old_name): -# """Get an environment variable by its new name, falling back to the deprecated old name. - -# If only the old name is set, a deprecation warning is issued. - -# Args: -# new_name (str): The new (preferred) environment variable name. -# old_name (str): The deprecated environment variable name. - -# Returns: -# str | None: The value of the environment variable, or None if not set. -# """ -# value = os.getenv(new_name) -# if value is not None: -# return value -# value = os.getenv(old_name) -# if value is not None: -# warnings.warn( -# _DEPRECATION_MSG.format(old=old_name, new=new_name), -# FutureWarning, -# stacklevel=3, -# ) -# return value -# return None - - -# def set_developer_nlr_gov_key(key: str): -# """Set `key` as the global variable `developer_nlr_gov_key`. - -# Args: -# key (str): API key for NLR Developer Network. Should be length 40. -# """ -# global developer_nlr_gov_key -# developer_nlr_gov_key = key -# return developer_nlr_gov_key - - -# def set_developer_nlr_gov_email(email: str): -# """Set `email` as the global variable `developer_nlr_gov_email`. - -# Args: -# email (str): email corresponding to the API key for NLR Developer Network. -# """ -# global developer_nlr_gov_email -# developer_nlr_gov_email = email -# return developer_nlr_gov_email - - -# def load_file_with_variables(fpath, variables=["NLR_API_KEY", "NLR_API_EMAIL"]): -# """Load environment variables from a text file. - -# Supports both the new ``NLR_API_*`` and the deprecated ``NREL_API_*`` variable -# names. If only the old names are found in the file a deprecation warning is -# emitted. - -# Args: -# fpath (str | Path): filepath to a text file with the extension '.env' that -# may contain the environment variable(s) in `variables`. -# variables (list | str, optional): environment variable(s) to load from file. -# Defaults to ["NLR_API_KEY", "NLR_API_EMAIL"]. - -# Raises: -# ValueError: If an environment variable is not found or found multiple times in the file. -# """ - -# # Mapping from new names to old (deprecated) names for file lookups -# _new_to_old = { -# _ENV_KEY_NEW: _ENV_KEY_OLD, -# _ENV_EMAIL_NEW: _ENV_EMAIL_OLD, -# } - -# # open the file and read the lines -# with Path(fpath).open("r") as f: -# lines = f.readlines() -# if isinstance(variables, str): -# variables = [variables] - -# # iterate through each variable -# for var in variables: -# # find a line containing the environment variable (try new name first, then old) -# line_w_var = [line for line in lines if var in line] -# if len(line_w_var) == 0 and var in _new_to_old: -# old_var = _new_to_old[var] -# line_w_var = [line for line in lines if old_var in line] -# if len(line_w_var) > 0: -# warnings.warn( -# _DEPRECATION_MSG.format(old=old_var, new=var), -# FutureWarning, -# stacklevel=2, -# ) -# var = old_var # use old name for parsing -# if len(line_w_var) != 1: -# raise ValueError( -# f"{var} variable in found in {fpath} file {len(line_w_var)} times. " -# "Please specify this variable once." -# ) -# # grab the line containing the variable, -# # assumes the line containing the variable is formatted as "variable=variable_value" -# val = line_w_var[0].split(f"{var}=").strip() -# # if var is an API key, set it as a global variable -# if var in (_ENV_KEY_NEW, _ENV_KEY_OLD): -# set_developer_nlr_gov_key(val) -# # if var is an API email, set it as a global variable -# if var in (_ENV_EMAIL_NEW, _ENV_EMAIL_OLD): -# set_developer_nlr_gov_email(val) -# return - - -# def set_nlr_key_dot_env(path=None): -# """Sets the environment variables NLR_API_EMAIL and NLR_API_KEY from a .env file. - -# Also supports the deprecated ``NREL_API_EMAIL`` and ``NREL_API_KEY`` variable -# names for backward compatibility (with deprecation warnings). - -# The following logic is used if `path` is input and exists: - -# 1) If the filename of the path is '.env', load the environment variables using -# `load_dotenv()`. -# Proceed to Step 3. -# 2) If the filename of the path has an extension of '.env' (such a filename of 'my_env.env'), -# then load the environment variables using `load_file_with_variables()`. Proceed to step 3. - -# The following logic is used if `path` is not input or does not exist: - -# 1) check for possible locations of the '.env' file. Searches the current working directory, -# the ROOT_DIR, and the parent of the ROOT_DIR. If the '.env' file is found in one of these -# locations, load the environment variables using `load_dotenv()`. Proceed to step 3. - -# The following is run after the above step(s): - -# 3) Get the environment variables NLR_API_KEY and NLR_API_EMAIL (falling back to the -# deprecated NREL_API_KEY / NREL_API_EMAIL). If found, set them as global variables -# using `set_developer_nlr_gov_key()` / `set_developer_nlr_gov_email()`. - -# Args: -# path (Path | str, optional): Path to environment file. -# Defaults to None. -# """ -# if path and Path(path).exists(): -# if Path(path).name == ".env": -# load_dotenv(path) -# if Path(path).suffix == ".env": -# load_file_with_variables(path, variables=_ENV_KEY_NEW) -# load_file_with_variables(path, variables=_ENV_EMAIL_NEW) -# else: -# possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] -# for r in possible_locs: -# if Path(r).exists(): -# load_dotenv(r) -# api_key = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) -# api_email = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) -# if api_key is not None: -# set_developer_nlr_gov_key(api_key) -# if api_email is not None: -# set_developer_nlr_gov_email(api_email) - - -# def get_nlr_developer_api_key(env_path=None): -# """Load the API key (NLR_API_KEY). This method does the following: - -# 1) check for NLR_API_KEY (or deprecated NREL_API_KEY) environment variable, -# return if found. Otherwise, proceed to Step 2. -# 2) check if the key has already been set as a global variable from -# running `set_nlr_key_dot_env()`. If not set, proceed to Step 3. -# 3) Attempt to set the key by calling `set_nlr_key_dot_env()`. -# 4) Check if the key has been set as a global variable. If found, return. -# Otherwise, raises a ValueError. - -# Args: -# env_path (Path | str, optional): Filepath to .env file. -# Defaults to None. - -# Raises: -# ValueError: If NLR_API_KEY was not found as an environment variable -# and the path to the environment file was not input. -# ValueError: If NLR_API_KEY was not found as an environment variable and not -# set properly using the environment path. - -# Returns: -# str: API key for NLR Developer Network. -# """ - -# # check if set as an environment variable (new name first, then old with warning) -# env_val = _get_env_with_fallback(_ENV_KEY_NEW, _ENV_KEY_OLD) -# if env_val is not None: -# return env_val - -# # check if set as a global variable -# global developer_nlr_gov_key -# if len(developer_nlr_gov_key) == 0: -# # attempt to set the variable from a .env file -# set_nlr_key_dot_env(path=env_path) - -# if len(developer_nlr_gov_key) == 0: -# # variable was not found -# raise ValueError( -# "NLR_API_KEY (or NREL_API_KEY) has not been set. " -# "Please set the NLR_API_KEY environment variable." -# ) -# return developer_nlr_gov_key - - -# def get_nlr_developer_api_email(env_path=None): -# """Load the API email (NLR_API_EMAIL). This method does the following: - -# 1) check for NLR_API_EMAIL (or deprecated NREL_API_EMAIL) environment variable, -# return if found. Otherwise, proceed to Step 2. -# 2) check if the email has already been set as a global variable from running -# `set_nlr_key_dot_env()`. If not set, proceed to Step 3. -# 3) Attempt to set the email by calling `set_nlr_key_dot_env()`. -# 4) Check if the email has been set as a global variable. If found, return. -# Otherwise, raises a ValueError. - -# Args: -# env_path (Path | str, optional): Filepath to .env file. -# Defaults to None. - -# Raises: -# ValueError: If NLR_API_EMAIL was not found as an environment variable -# and the path to the environment file was not input. -# ValueError: If NLR_API_EMAIL was not found as an environment variable and not -# set properly using the environment path. - -# Returns: -# str: email for NLR Developer Network API. -# """ - -# # check if set as an environment variable (new name first, then old with warning) -# env_val = _get_env_with_fallback(_ENV_EMAIL_NEW, _ENV_EMAIL_OLD) -# if env_val is not None: -# return env_val - -# # check if set as a global variable -# global developer_nlr_gov_email -# if len(developer_nlr_gov_email) == 0: -# # attempt to set the variable from a .env file -# set_nlr_key_dot_env(path=env_path) - -# if len(developer_nlr_gov_email) == 0: -# # variable was not found -# raise ValueError( -# "NLR_API_EMAIL (or NREL_API_EMAIL) has not been set. " -# "Please set the NLR_API_EMAIL environment variable." -# ) -# return developer_nlr_gov_email From c965982693149a4936a3f28ed63c072ed2472162 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:29:04 -0600 Subject: [PATCH 07/31] removed usage of globals() --- h2integrate/core/env_tools.py | 30 ++++++++++++------- .../utilities/nlr_developer_api_keys.py | 22 +++++++++----- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index ea51bf29b..3b30f4478 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -7,6 +7,10 @@ from h2integrate import ROOT_DIR +# global xx_test_env_var_xx +# xx_test_env_var_xx = "" + + _DEPRECATION_MSG = ( "The '{old}' environment variable is deprecated and will be removed in a future release. " "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." @@ -42,14 +46,14 @@ def _get_env_with_fallback(new_name, old_name): return None -def set_environment_var(global_varname: str, var_value: str): - """Set `var_value` as the global variable :py:attr:`global_varname`. +# def set_environment_var(global_varname: str, var_value: str): +# """Set `var_value` as the global variable :py:attr:`global_varname`. - Args: - var_value (str): value to set for the environment variable - """ - globals()[global_varname] = var_value - return globals()[global_varname] +# Args: +# var_value (str): value to set for the environment variable +# """ +# globals()[global_varname] = var_value +# return globals()[global_varname] def load_file_with_variables( @@ -146,7 +150,7 @@ def set_env_var_dot_env(setter_method, varname_new: str, varname_old: str | None def get_environment_var( - global_varname, setter_method, varname_new: str, varname_old: str | None = None, env_path=None + setter_method, varname_new: str, varname_old: str | None = None, env_path=None ): """Load the environment variable named :py:attr:`varname_new` (or :py:attr:`varname_old`). This method does the following: @@ -178,15 +182,19 @@ def get_environment_var( if env_val is not None: return env_val + global_var_value = setter_method(var_value=None) # check if set as a global variable - if len(globals()[global_varname]) == 0: + if len(global_var_value) == 0: # attempt to set the variable from a .env file set_env_var_dot_env(setter_method, varname_new, varname_old, path=env_path) - if len(globals()[global_varname]) == 0: + global_var_value = setter_method(var_value=None) + if len(global_var_value) == 0: # variable was not found raise ValueError( f"{varname_new} (or {varname_old}) has not been set. " f"Please set the {varname_new} environment variable." ) - return globals()[global_varname] + + global_var_value = setter_method(var_value=None) + return global_var_value diff --git a/h2integrate/resource/utilities/nlr_developer_api_keys.py b/h2integrate/resource/utilities/nlr_developer_api_keys.py index dc4c9e2ea..dae3eda11 100644 --- a/h2integrate/resource/utilities/nlr_developer_api_keys.py +++ b/h2integrate/resource/utilities/nlr_developer_api_keys.py @@ -1,22 +1,31 @@ from functools import partial, update_wrapper -from h2integrate.core.env_tools import get_environment_var, set_env_var_dot_env, set_environment_var +from h2integrate.core.env_tools import get_environment_var, set_env_var_dot_env # global variables developer_nlr_gov_key = "" developer_nlr_gov_email = "" + # Setter methods for each NLR API variable -set_developer_nlr_gov_key = partial(set_environment_var, global_varname="developer_nlr_gov_key") -update_wrapper(set_developer_nlr_gov_key, set_environment_var) -set_developer_nlr_gov_email = partial(set_environment_var, global_varname="developer_nlr_gov_email") -update_wrapper(set_developer_nlr_gov_email, set_environment_var) +def set_developer_nlr_gov_key(var_value): + global developer_nlr_gov_key + if var_value is not None: + developer_nlr_gov_key = var_value + return developer_nlr_gov_key + + +def set_developer_nlr_gov_email(var_value): + global developer_nlr_gov_email + if var_value is not None: + developer_nlr_gov_email = var_value + return developer_nlr_gov_email + # Getter methods called by NLR API resource models get_nlr_developer_api_key = partial( get_environment_var, - global_varname="developer_nlr_gov_key", setter_method=set_developer_nlr_gov_key, varname_new="NLR_API_KEY", varname_old="NREL_API_KEY", @@ -25,7 +34,6 @@ get_nlr_developer_api_email = partial( get_environment_var, - global_varname="developer_nlr_gov_email", setter_method=set_developer_nlr_gov_email, varname_new="NLR_API_EMAIL", varname_old="NREL_API_EMAIL", From 14733b45739063b87b213dce1152fd2c305768b8 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:30:19 -0600 Subject: [PATCH 08/31] started adding in test file --- h2integrate/core/test/test_env_tools.py | 118 ++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 h2integrate/core/test/test_env_tools.py diff --git a/h2integrate/core/test/test_env_tools.py b/h2integrate/core/test/test_env_tools.py new file mode 100644 index 000000000..a0059b679 --- /dev/null +++ b/h2integrate/core/test/test_env_tools.py @@ -0,0 +1,118 @@ +import os +from pathlib import Path + +import pytest + +from h2integrate.core.env_tools import get_environment_var + + +# @pytest.fixture(scope="function") +# def temp_environment_var(): +# """Temporarily set the `RESOURCE_DIR` environment variable to example 11's weather folder.""" +# # NOTE: changes to this fixture can result in hard-to-debug test failures +# # in tests for resource components. Please do not modify this fixture if possible! +# resource_dir = str(EXAMPLE_DIR / "11_hybrid_energy_plant" / "tech_inputs" / "weather") +# original = os.environ.get("RESOURCE_DIR") +# os.environ["RESOURCE_DIR"] = resource_dir +# yield resource_dir +# os.environ.pop("RESOURCE_DIR", None) +# assert os.getenv("RESOURCE_DIR") is None +# if original is not None: +# os.environ["RESOURCE_DIR"] = original + +xx_test_env_var_xx = "" + + +def setter_getter_method(var_value): + global xx_test_env_var_xx + if var_value is not None: + xx_test_env_var_xx = var_value + return xx_test_env_var_xx + + +@pytest.mark.unit +def test_set_environment_var(subtests): + with subtests.test("Initialized Properly"): + assert xx_test_env_var_xx == "" + + setter_getter_method(var_value="testing_set_environment_var") + with subtests.test("Set to long string"): + assert xx_test_env_var_xx == "testing_set_environment_var" + + setter_getter_method(var_value=None) + with subtests.test("Unchanged when var_value is None"): + assert xx_test_env_var_xx == "testing_set_environment_var" + + setter_getter_method(var_value="") + with subtests.test("Set to empty string"): + assert xx_test_env_var_xx == "" + + +@pytest.mark.unit +def test_get_environment_var_with_fallback(subtests): + # below tests the get env var where it should run _get_env_with_fallback + # which doesnt set the environment variable + with subtests.test("Initialized Properly"): + assert xx_test_env_var_xx == "" + + os.environ["TEST_ENV"] = "none" + env_var_val = get_environment_var(setter_getter_method, "TEST_ENV", "TEST_ENV_OLD") + + with subtests.test("Returned TEST_ENV"): + assert env_var_val == "none" + + os.environ.pop("TEST_ENV", None) + with subtests.test("global didnt change (unsure if this is OK)"): + assert xx_test_env_var_xx == "" + + +@pytest.mark.unit +def test_get_environment_var_dot_env_provided_path(temp_dir, subtests): + # below tests the get env var where it should run _set_env_var_dot_env + # which doesnt set the environment variable + # NOTE: this fails if its run before the one above it + + with subtests.test("Initialized Properly"): + assert xx_test_env_var_xx == "" + + env_path = temp_dir / ".env" + with env_path.open("w+") as file: + file.write("TESTING_ENV=provided_a_path\n") + + get_environment_var( + setter_getter_method, "TESTING_ENV", varname_old="TEST_ENV_OLD", env_path=env_path + ) + + with subtests.test("Global variable was set"): + assert xx_test_env_var_xx == "provided_a_path" + + setter_getter_method(var_value="") + + +@pytest.mark.unit +def test_get_environment_var_dot_env(temp_dir, subtests): + current_dir = Path.cwd() + + os.chdir(temp_dir) + + env_path = temp_dir / ".env" + set_str = "did_not_provide_a_path" + with env_path.open("w+") as file: + file.write(f"TEST_ENV_OLD={set_str}\n") + + tmp_dir_path = Path.cwd() / ".env" + with subtests.test("env file exists"): + assert tmp_dir_path.is_file() + + get_environment_var( + setter_getter_method, "NOT_REAL_ENV", varname_old="TEST_ENV_OLD", env_path=None + ) + + with subtests.test("Global variable was set"): + assert xx_test_env_var_xx == set_str + + with subtests.test("Global variable was set #2"): + retrieved_val = setter_getter_method(var_value=None) + assert retrieved_val == set_str + + os.chdir(current_dir) From e48aa6740f5a802f137f4a8d5319355be49cee12 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:14:08 -0600 Subject: [PATCH 09/31] added another test and bugfix in loading environment variable from file --- h2integrate/core/env_tools.py | 7 +- h2integrate/core/test/test_env_tools.py | 88 ++++++++++++++++++++----- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index 3b30f4478..35e4a1139 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -97,7 +97,7 @@ def load_file_with_variables( ) # grab the line containing the variable, # assumes the line containing the variable is formatted as "variable=variable_value" - val = line_w_var[0].split(f"{var}=").strip() + val = line_w_var[0].split(f"{var}=")[-1].strip() # set variable as a global variable setter_method(var_value=val) return @@ -144,6 +144,8 @@ def set_env_var_dot_env(setter_method, varname_new: str, varname_old: str | None for r in possible_locs: if Path(r).exists(): load_dotenv(r) + # TODO: add in checks to run load_file_with_variables from possible locs + # list(Path.cwd().glob("*.env")) val = _get_env_with_fallback(varname_new, varname_old) if val is not None: setter_method(var_value=val) @@ -180,6 +182,7 @@ def get_environment_var( # check if set as an environment variable (new name first, then old with warning) env_val = _get_env_with_fallback(varname_new, varname_old) if env_val is not None: + # TODO: call setter method here return env_val global_var_value = setter_method(var_value=None) @@ -196,5 +199,5 @@ def get_environment_var( f"Please set the {varname_new} environment variable." ) - global_var_value = setter_method(var_value=None) + # global_var_value = setter_method(var_value=None) return global_var_value diff --git a/h2integrate/core/test/test_env_tools.py b/h2integrate/core/test/test_env_tools.py index a0059b679..f9a4ce22e 100644 --- a/h2integrate/core/test/test_env_tools.py +++ b/h2integrate/core/test/test_env_tools.py @@ -48,8 +48,38 @@ def test_set_environment_var(subtests): assert xx_test_env_var_xx == "" +@pytest.mark.unit +def test_get_environment_var_dot_env_provided_path(temp_dir, subtests): + """ + Test when an environment variable is set using a .env file and + the path to the .env file is specified. This tests `set_env_var_dot_env` + when using the `dotenv.load_dotenv()` method to load the environment variables + from a .env file with a specified path. + """ + + with subtests.test("Initialized Properly"): + assert xx_test_env_var_xx == "" + + env_path = temp_dir / ".env" + with env_path.open("w+") as file: + file.write("TESTING_ENV=provided_a_path\n") + + get_environment_var( + setter_getter_method, "TESTING_ENV", varname_old="TEST_ENV_OLD", env_path=env_path + ) + + with subtests.test("Global variable was set"): + assert xx_test_env_var_xx == "provided_a_path" + + setter_getter_method(var_value="") + + @pytest.mark.unit def test_get_environment_var_with_fallback(subtests): + """ + Test when an environment variable value is accessed using `_get_env_with_fallback()`. + The global environment variable itself is unmodified (its not set). + """ # below tests the get env var where it should run _get_env_with_fallback # which doesnt set the environment variable with subtests.test("Initialized Properly"): @@ -67,46 +97,70 @@ def test_get_environment_var_with_fallback(subtests): @pytest.mark.unit -def test_get_environment_var_dot_env_provided_path(temp_dir, subtests): - # below tests the get env var where it should run _set_env_var_dot_env - # which doesnt set the environment variable - # NOTE: this fails if its run before the one above it +def test_get_environment_var_dot_env(temp_dir, subtests): + """ + Test when an environment variable is set using a .env file and + the path to the .env file is NOT specified. This tests `set_env_var_dot_env` + when using the `dotenv.load_dotenv()` method to load the environment variables + from a .env file in one of the default possible locations (current working directory). + """ + current_dir = Path.cwd() - with subtests.test("Initialized Properly"): - assert xx_test_env_var_xx == "" + os.chdir(temp_dir) env_path = temp_dir / ".env" + set_str = "did_not_provide_a_path" with env_path.open("w+") as file: - file.write("TESTING_ENV=provided_a_path\n") + file.write(f"TEST_ENV_OLD={set_str}\n") + + tmp_dir_path = Path.cwd() / ".env" + with subtests.test("env file exists"): + assert tmp_dir_path.is_file() get_environment_var( - setter_getter_method, "TESTING_ENV", varname_old="TEST_ENV_OLD", env_path=env_path + setter_getter_method, "NOT_REAL_ENV", varname_old="TEST_ENV_OLD", env_path=None ) with subtests.test("Global variable was set"): - assert xx_test_env_var_xx == "provided_a_path" + assert xx_test_env_var_xx == set_str + + with subtests.test("Global variable was set #2"): + retrieved_val = setter_getter_method(var_value=None) + assert retrieved_val == set_str + os.chdir(current_dir) setter_getter_method(var_value="") @pytest.mark.unit -def test_get_environment_var_dot_env(temp_dir, subtests): +def test_get_environment_var_env_suffix_file(temp_dir, subtests): + """ + Test when an environment variable is set using a file with a .env suffix and + the path to the file is specified. This tests `set_env_var_dot_env` + when using the `load_file_with_variables()` method to load the environment variables + from a file with a specified path. + """ current_dir = Path.cwd() os.chdir(temp_dir) - env_path = temp_dir / ".env" - set_str = "did_not_provide_a_path" + env_key = "TESTING_ENVIRONMENT_KEY" + set_str = str(temp_dir) # set the value to the temp dir name + + env_path = temp_dir / "vars.env" + with env_path.open("w+") as file: - file.write(f"TEST_ENV_OLD={set_str}\n") + file.write(f"{env_key}={set_str}\n") + + tmp_dir_path = Path.cwd() / "vars.env" - tmp_dir_path = Path.cwd() / ".env" with subtests.test("env file exists"): assert tmp_dir_path.is_file() - get_environment_var( - setter_getter_method, "NOT_REAL_ENV", varname_old="TEST_ENV_OLD", env_path=None - ) + with subtests.test("Global variable starts as empty"): + assert xx_test_env_var_xx == "" + + get_environment_var(setter_getter_method, env_key, varname_old=None, env_path=env_path) with subtests.test("Global variable was set"): assert xx_test_env_var_xx == set_str From fc84498e72d694c1cd4ecfb9cfff6ca841b488ab Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:33:47 -0600 Subject: [PATCH 10/31] removed commented out code and added todos --- h2integrate/core/env_tools.py | 16 ++-------------- h2integrate/core/test/test_env_tools.py | 14 -------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index 35e4a1139..ed051eb27 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -7,10 +7,6 @@ from h2integrate import ROOT_DIR -# global xx_test_env_var_xx -# xx_test_env_var_xx = "" - - _DEPRECATION_MSG = ( "The '{old}' environment variable is deprecated and will be removed in a future release. " "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." @@ -46,16 +42,6 @@ def _get_env_with_fallback(new_name, old_name): return None -# def set_environment_var(global_varname: str, var_value: str): -# """Set `var_value` as the global variable :py:attr:`global_varname`. - -# Args: -# var_value (str): value to set for the environment variable -# """ -# globals()[global_varname] = var_value -# return globals()[global_varname] - - def load_file_with_variables( setter_method, fpath, varname_new: str, varname_old: str | None = None ): @@ -91,6 +77,8 @@ def load_file_with_variables( ) var = varname_old # use old name for parsing if len(line_w_var) != 1: + # TODO: add an input to toggle whether to thow an error + # If not throw an error, set the val to None raise ValueError( f"{var} variable in found in {fpath} file {len(line_w_var)} times. " "Please specify this variable once." diff --git a/h2integrate/core/test/test_env_tools.py b/h2integrate/core/test/test_env_tools.py index f9a4ce22e..289322c93 100644 --- a/h2integrate/core/test/test_env_tools.py +++ b/h2integrate/core/test/test_env_tools.py @@ -6,20 +6,6 @@ from h2integrate.core.env_tools import get_environment_var -# @pytest.fixture(scope="function") -# def temp_environment_var(): -# """Temporarily set the `RESOURCE_DIR` environment variable to example 11's weather folder.""" -# # NOTE: changes to this fixture can result in hard-to-debug test failures -# # in tests for resource components. Please do not modify this fixture if possible! -# resource_dir = str(EXAMPLE_DIR / "11_hybrid_energy_plant" / "tech_inputs" / "weather") -# original = os.environ.get("RESOURCE_DIR") -# os.environ["RESOURCE_DIR"] = resource_dir -# yield resource_dir -# os.environ.pop("RESOURCE_DIR", None) -# assert os.getenv("RESOURCE_DIR") is None -# if original is not None: -# os.environ["RESOURCE_DIR"] = original - xx_test_env_var_xx = "" From 3a1376d3f6777dc3ef0d46270705f001d694a941 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:44:39 -0600 Subject: [PATCH 11/31] updated so that setter method is called after _get_env_with_fallback --- h2integrate/core/env_tools.py | 2 +- h2integrate/core/test/test_env_tools.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index ed051eb27..318ae136b 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -170,7 +170,7 @@ def get_environment_var( # check if set as an environment variable (new name first, then old with warning) env_val = _get_env_with_fallback(varname_new, varname_old) if env_val is not None: - # TODO: call setter method here + setter_method(var_value=env_val) return env_val global_var_value = setter_method(var_value=None) diff --git a/h2integrate/core/test/test_env_tools.py b/h2integrate/core/test/test_env_tools.py index 289322c93..f64a7a8e9 100644 --- a/h2integrate/core/test/test_env_tools.py +++ b/h2integrate/core/test/test_env_tools.py @@ -78,8 +78,15 @@ def test_get_environment_var_with_fallback(subtests): assert env_var_val == "none" os.environ.pop("TEST_ENV", None) - with subtests.test("global didnt change (unsure if this is OK)"): - assert xx_test_env_var_xx == "" + + with subtests.test("Global variable was set"): + assert xx_test_env_var_xx == "none" + + with subtests.test("Global variable was set #2"): + retrieved_val = setter_getter_method(var_value=None) + assert retrieved_val == "none" + + setter_getter_method(var_value="") @pytest.mark.unit @@ -103,6 +110,9 @@ def test_get_environment_var_dot_env(temp_dir, subtests): with subtests.test("env file exists"): assert tmp_dir_path.is_file() + with subtests.test("Global variable starts as empty"): + assert xx_test_env_var_xx == "" + get_environment_var( setter_getter_method, "NOT_REAL_ENV", varname_old="TEST_ENV_OLD", env_path=None ) From 6b9a6066e35b2dc5b237a7ad8bd4d0fb9aa5096c Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:18:12 -0600 Subject: [PATCH 12/31] added doc strings to setter methods in nlr_developer_api_keys.py --- .../utilities/nlr_developer_api_keys.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/h2integrate/resource/utilities/nlr_developer_api_keys.py b/h2integrate/resource/utilities/nlr_developer_api_keys.py index dae3eda11..667fa7bef 100644 --- a/h2integrate/resource/utilities/nlr_developer_api_keys.py +++ b/h2integrate/resource/utilities/nlr_developer_api_keys.py @@ -10,6 +10,19 @@ # Setter methods for each NLR API variable def set_developer_nlr_gov_key(var_value): + """Set `var_value` as the global variable `developer_nlr_gov_key` if + `var_value` is a string. If `var_value` is None, then `developer_nlr_gov_key` + is not modified, but the value is returned. This function always acts as a + getter method for `developer_nlr_gov_key`, but also functions as a setter method + if `var_value` is a string. + + Args: + var_value (str | None): API key for NLR Developer Network. + Should be length 40. + + Returns: + str: value of `developer_nlr_gov_key` + """ global developer_nlr_gov_key if var_value is not None: developer_nlr_gov_key = var_value @@ -17,6 +30,18 @@ def set_developer_nlr_gov_key(var_value): def set_developer_nlr_gov_email(var_value): + """Set `var_value` as the global variable `developer_nlr_gov_email` if + `var_value` is a string. If `var_value` is None, then `developer_nlr_gov_email` + is not modified, but the value is returned. This function always acts as a + getter method for `developer_nlr_gov_email`, but also functions as a setter method + if `var_value` is a string. + + Args: + var_value (str | None): email corresponding to the API key for NLR Developer Network. + + Returns: + str: value of `developer_nlr_gov_email` + """ global developer_nlr_gov_email if var_value is not None: developer_nlr_gov_email = var_value @@ -40,7 +65,7 @@ def set_developer_nlr_gov_email(var_value): ) update_wrapper(get_nlr_developer_api_email, get_environment_var) -# Generic setter methods +# Generic setter methods, used to set the environment variable before running a script set_nlr_api_key_dot_env = partial( set_env_var_dot_env, setter_method=set_developer_nlr_gov_key, From a158d65946f7063be5bd2cc4d4a58c26642ca6dc Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:33:46 -0600 Subject: [PATCH 13/31] refactored environment tools with help from Rob --- h2integrate/core/env_tools.py | 260 ++++++------------ .../utilities/nlr_developer_api_keys.py | 131 +++++---- 2 files changed, 153 insertions(+), 238 deletions(-) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index 318ae136b..8dfd0fccf 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -1,191 +1,111 @@ import os -import warnings from pathlib import Path -from dotenv import load_dotenv - from h2integrate import ROOT_DIR -_DEPRECATION_MSG = ( - "The '{old}' environment variable is deprecated and will be removed in a future release. " - "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." -) - - -def _get_env_with_fallback(new_name, old_name): - """Get an environment variable by its new name, falling back to the deprecated old name. - - If only the old name is set, a deprecation warning is issued. +def set_env_var(*, overwrite: bool = False, **kwargs: str): + """Set or overwrite environment variables. Args: - new_name (str): The new (preferred) environment variable name. - old_name (str): The deprecated environment variable name. - - Returns: - str | None: The value of the environment variable, or None if not set. + overwrite (bool, optional): Indicator to overwrite existing environment variables provided + in :py:attr:`kwargs`. Defaults to False. + kwargs (str): name and value of environment variables to set. If :py:attr:`overwrite` is + False, the value will be skipped. """ - # TODO: update so depr_msg is an input - value = os.getenv(new_name) - if value is not None: - return value - if old_name is None: - return None - value = os.getenv(old_name) - if value is not None: - warnings.warn( - _DEPRECATION_MSG.format(old=old_name, new=new_name), - FutureWarning, - stacklevel=3, - ) - return value - return None - - -def load_file_with_variables( - setter_method, fpath, varname_new: str, varname_old: str | None = None -): - """Load an environment variable from a text file. + for name, value in kwargs.items(): + if os.environ.get(name) is not None and not overwrite: + continue + os.environ[name] = value + - Supports both the new ``varname_new`` and deprecated ``varname_old`` variable - names. If only the old name is found in the file, a deprecation warning is - emitted. +def load_env_vars_from_file(file_path: Path) -> dict: + """Load any dictionary-like key, value pairs from a configuration file (e.g. .env or .cdsapirc) + that uses either a ``key=value` or `key:value` format for storing data. Args: - fpath (str | Path): filepath to a text file with the extension '.env' that - may contain the environment variable in `variables`. - varname_new (str): environment variable to load from file. + file_path (Path): The full file path and name containing configuration details to be + extracted. - Raises: - ValueError: If an environment variable is not found or found multiple times in the file. + Returns: + dict: Dictionary of key, value pairs found in :py:attr:`file_path`. """ - # open the file and read the lines - with Path(fpath).open("r") as f: - lines = f.readlines() - - # find a line containing the environment variable (try new name first, then old) - line_w_var = [line for line in lines if varname_new in line] - var = varname_new - if len(line_w_var) == 0 and varname_old is not None: - line_w_var = [line for line in lines if varname_old in line] - if len(line_w_var) > 0: - warnings.warn( - _DEPRECATION_MSG.format(old=varname_old, new=varname_new), - FutureWarning, - stacklevel=2, - ) - var = varname_old # use old name for parsing - if len(line_w_var) != 1: - # TODO: add an input to toggle whether to thow an error - # If not throw an error, set the val to None - raise ValueError( - f"{var} variable in found in {fpath} file {len(line_w_var)} times. " - "Please specify this variable once." - ) - # grab the line containing the variable, - # assumes the line containing the variable is formatted as "variable=variable_value" - val = line_w_var[0].split(f"{var}=")[-1].strip() - # set variable as a global variable - setter_method(var_value=val) - return - - -def set_env_var_dot_env(setter_method, varname_new: str, varname_old: str | None = None, path=None): - """Sets the environment variable :py:attr:`varname_new` from a .env file. - - Also supports the deprecated :py:attr:`varname_old` variable - name for backward compatibility (with deprecation warnings). - - The following logic is used if `path` is input and exists: - - 1) If the filename of the path is '.env', load the environment variables using `load_dotenv()`. - Proceed to Step 3. - 2) If the filename of the path has an extension of '.env' (such a filename of 'my_env.env'), - then load the environment variables using `load_file_with_variables()`. Proceed to step 3. - - The following logic is used if `path` is not input or does not exist: - - 1) check for possible locations of the '.env' file. Searches the current working directory, - the ROOT_DIR, and the parent of the ROOT_DIR. If the '.env' file is found in one of these - locations, load the environment variables using `load_dotenv()`. Proceed to step 3. - - The following is run after the above step(s): - - 3) Get the environment variable :py:attr:`varname_new` (falling back to the - deprecated :py:attr:`varname_old`). If found, set it as global variables - using :py:attr:`setter_method`. - - Args: - path (Path | str, optional): Path to environment file. - Defaults to None. - """ - if path and Path(path).exists(): - if Path(path).name == ".env": - load_dotenv(path) - if Path(path).suffix == ".env": - load_file_with_variables( - setter_method, path, varname_new=varname_new, varname_old=varname_old - ) - else: - possible_locs = [Path.cwd() / ".env", ROOT_DIR / ".env", ROOT_DIR.parent / ".env"] - for r in possible_locs: - if Path(r).exists(): - load_dotenv(r) - # TODO: add in checks to run load_file_with_variables from possible locs - # list(Path.cwd().glob("*.env")) - val = _get_env_with_fallback(varname_new, varname_old) - if val is not None: - setter_method(var_value=val) - - -def get_environment_var( - setter_method, varname_new: str, varname_old: str | None = None, env_path=None + if isinstance(file_path, str): + file_path = Path(file_path).resolve() + env_vars = {} + if not file_path.is_file(): + return env_vars + with file_path.open("r") as f: + for line in f.readlines(): + if "=" in line: + sep = "=" + elif ":" in line: + sep = ":" + else: + # skip this line + continue + k, v = line.strip().split(sep, 1) + env_vars[k.strip()] = v.strip() + return env_vars + + +def get_environment_variables( + *args: str, + file_name: str | None = None, + file_path: str | None = None, + set_variables: bool = True, ): - """Load the environment variable named :py:attr:`varname_new` (or :py:attr:`varname_old`). - This method does the following: - - 1) check for :py:attr:`varname_new` (or deprecated :py:attr:`varname_old`) environment variable, - return if found. Otherwise, proceed to Step 2. - 2) check if the key has already been set as a global variable from - running :py:attr:`setter_method`. If not set, proceed to Step 3. - 3) Attempt to set the key by calling :py:attr:`setter_method`. - 4) Check if the key has been set as a global variable. If found, return. - Otherwise, raises a ValueError. + """Retrieve a series of credentials from a :py:attr:`file_name` in either the home directory + or H2Integrate root directory. If `:py:attr:`file_path` is provided, then :py:attr:`file_name` + and already set environment variables will be ignored. If :py:attr:`file_name` is provided, then + already set environment variables will be ignored. If neither file options are used, then an + existing environment variable will be retrieved. Args: - env_path (Path | str, optional): Filepath to .env file. - Defaults to None. - - Raises: - ValueError: If py:attr:`varname_old` was not found as an environment variable - and the path to the environment file was not input. - ValueError: If py:attr:`varname_old` was not found as an environment variable and not - set properly using the environment path. + args (str): Name(s) of the credential(s) that should be retrieved from either + :py:attr:`file_name` or environment variables. + file_name (str, optional): The name of a configuration file found in either the H2Integrate + root directory or the user's home directory that should contain the credential(s) in + :py:attr:`args`. + file_path (str | Path, optional): The full file path for where the configuration file can be + found if not using the H2Integrate root directory or user home directory + set_variables (bool, optional): If True, set the environment variables if they + haven't already been set. Returns: - str: value of the environment variable + dict: Dictionary of all :py:attr:`args` with values of either the value if found. """ - - # check if set as an environment variable (new name first, then old with warning) - env_val = _get_env_with_fallback(varname_new, varname_old) - if env_val is not None: - setter_method(var_value=env_val) - return env_val - - global_var_value = setter_method(var_value=None) - # check if set as a global variable - if len(global_var_value) == 0: - # attempt to set the variable from a .env file - set_env_var_dot_env(setter_method, varname_new, varname_old, path=env_path) - - global_var_value = setter_method(var_value=None) - if len(global_var_value) == 0: - # variable was not found - raise ValueError( - f"{varname_new} (or {varname_old}) has not been set. " - f"Please set the {varname_new} environment variable." - ) - - # global_var_value = setter_method(var_value=None) - return global_var_value + # Check if the environment variables have already been set + env_vars = {name: os.environ.get(name) for name in args if os.environ.get(name) is not None} + remaining_vars = set(env_vars) - set(args) + if len(remaining_vars) == 0: + # All environment variables have already been set + return env_vars + + if file_path is not None: + file_path = Path(file_path).resolve() + if file_path.is_file(): + env_vars = load_env_vars_from_file(file_path) + env_vars_subset = {name: env_vars.get(name) for name in args if name in env_vars} + if set_variables: + # Set the environment variables + set_env_var(overwrite=True, **env_vars_subset) + return env_vars_subset + + raise FileNotFoundError(f"Provided `file_path` is invalid: {file_path}") + + default_folders = [Path.cwd(), Path.home(), ROOT_DIR, ROOT_DIR.parent] + if file_name is None: + # If a file_name isn't provided, look for a .env file + file_name = ".env" + + for folder in default_folders: + if (file_path := (folder / file_name)).is_file(): + env_vars |= load_env_vars_from_file(file_path) + + env_vars_subset = {name: env_vars.get(name) for name in args if name in env_vars} + if set_variables: + # Set the environment variables + set_env_var(overwrite=True, **env_vars_subset) + return env_vars_subset diff --git a/h2integrate/resource/utilities/nlr_developer_api_keys.py b/h2integrate/resource/utilities/nlr_developer_api_keys.py index 667fa7bef..14729409a 100644 --- a/h2integrate/resource/utilities/nlr_developer_api_keys.py +++ b/h2integrate/resource/utilities/nlr_developer_api_keys.py @@ -1,86 +1,81 @@ -from functools import partial, update_wrapper +import warnings +from pathlib import Path -from h2integrate.core.env_tools import get_environment_var, set_env_var_dot_env +from h2integrate.core.env_tools import get_environment_variables -# global variables -developer_nlr_gov_key = "" -developer_nlr_gov_email = "" +_DEPRECATION_MSG = ( + "The '{old}' environment variable is deprecated and will be removed in a future release. " + "Please use '{new}' instead. The nrel.gov API domain has moved to nlr.gov." +) + +_ENV_MISSING_MSG = ( + "{new} (or {old}) has not been set. " "Please set the {new} environment variable." +) -# Setter methods for each NLR API variable -def set_developer_nlr_gov_key(var_value): - """Set `var_value` as the global variable `developer_nlr_gov_key` if - `var_value` is a string. If `var_value` is None, then `developer_nlr_gov_key` - is not modified, but the value is returned. This function always acts as a - getter method for `developer_nlr_gov_key`, but also functions as a setter method - if `var_value` is a string. +def get_nlr_developer_api_credential(which: str, env_path: str | Path | None) -> str: + """Get either the NLR API email or key with a fallback for the NREL credentials. Args: - var_value (str | None): API key for NLR Developer Network. - Should be length 40. + which (str): One of "email" or "key" to indicate which NLR API credential should be + retrieved. + env_path (None | Path | str, optional): Filepath to file containing NLR API credentials. + Defaults to None. + + Raises: + ValueError: Raised if an invalid value was passed to :py:attr:`which`. + KeyError: Raised if neither of the NLR or NREL credentials could be found. Returns: - str: value of `developer_nlr_gov_key` + str: API key or email for NLR Developer Network. """ - global developer_nlr_gov_key - if var_value is not None: - developer_nlr_gov_key = var_value - return developer_nlr_gov_key + if which.lower() not in ("email", "key"): + raise ValueError("`which` must be one of 'email' or 'key'.") + old_name = f"NREL_API_{which.upper()}" + new_name = f"NLR_API_{which.upper()}" + nlr_api_vars = get_environment_variables( + new_name, old_name, file_path=env_path, set_variables=True + ) + if not bool(nlr_api_vars): + # returned an empty dictionary + raise ValueError(_ENV_MISSING_MSG.format(old=old_name, new=new_name)) + if (old_name in nlr_api_vars) and (new_name not in nlr_api_vars): + warnings.warn( + _DEPRECATION_MSG.format(old=old_name, new=new_name), + FutureWarning, + stacklevel=3, + ) + return list(nlr_api_vars.values())[0] + + +def get_nlr_developer_api_key() -> str: + """Load the API key (NLR_API_KEY) for the NLR Developer Network. + + Raises: + ValueError: If NLR_API_KEY or NREL_API_KEY was not found as an environment variable + Returns: + str: API key for NLR Developer Network. Should be length 40. + """ + nlr_api_key = get_nlr_developer_api_credential(which="key") + return nlr_api_key -def set_developer_nlr_gov_email(var_value): - """Set `var_value` as the global variable `developer_nlr_gov_email` if - `var_value` is a string. If `var_value` is None, then `developer_nlr_gov_email` - is not modified, but the value is returned. This function always acts as a - getter method for `developer_nlr_gov_email`, but also functions as a setter method - if `var_value` is a string. - Args: - var_value (str | None): email corresponding to the API key for NLR Developer Network. +def get_nlr_developer_api_email() -> str: + """Load the API email (NLR_API_EMAIL) for the NLR Developer Network. + + Raises: + ValueError: If NLR_API_EMAIL or NREL_API_EMAIL was not found as an environment variable Returns: - str: value of `developer_nlr_gov_email` - """ - global developer_nlr_gov_email - if var_value is not None: - developer_nlr_gov_email = var_value - return developer_nlr_gov_email - - -# Getter methods called by NLR API resource models -get_nlr_developer_api_key = partial( - get_environment_var, - setter_method=set_developer_nlr_gov_key, - varname_new="NLR_API_KEY", - varname_old="NREL_API_KEY", -) -update_wrapper(get_nlr_developer_api_key, get_environment_var) + str: email corresponding to the API key for NLR Developer Network. -get_nlr_developer_api_email = partial( - get_environment_var, - setter_method=set_developer_nlr_gov_email, - varname_new="NLR_API_EMAIL", - varname_old="NREL_API_EMAIL", -) -update_wrapper(get_nlr_developer_api_email, get_environment_var) - -# Generic setter methods, used to set the environment variable before running a script -set_nlr_api_key_dot_env = partial( - set_env_var_dot_env, - setter_method=set_developer_nlr_gov_key, - varname_new="NLR_API_KEY", - varname_old="NREL_API_KEY", -) -set_nlr_email_key_dot_env = partial( - set_env_var_dot_env, - setter_method=set_developer_nlr_gov_email, - varname_new="NLR_API_EMAIL", - varname_old="NREL_API_EMAIL", -) + """ + nlr_api_email = get_nlr_developer_api_credential(which="email") + return nlr_api_email -# Setter methods for both variables needed for NLR API calls -def set_nlr_key_dot_env(path=None): - set_nlr_api_key_dot_env(path=path) - set_nlr_email_key_dot_env(path=path) +def set_nlr_key_dot_env(path: str | None | Path = None): + get_nlr_developer_api_credential(which="email", env_path=path) + get_nlr_developer_api_credential(which="key", env_path=path) From ea793dfa29b47b3f18e051b75448893be1abaaa6 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:18:57 -0600 Subject: [PATCH 14/31] minor changes to nlr_developer_api_keys --- h2integrate/resource/utilities/nlr_developer_api_keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2integrate/resource/utilities/nlr_developer_api_keys.py b/h2integrate/resource/utilities/nlr_developer_api_keys.py index 14729409a..57c209496 100644 --- a/h2integrate/resource/utilities/nlr_developer_api_keys.py +++ b/h2integrate/resource/utilities/nlr_developer_api_keys.py @@ -14,7 +14,7 @@ ) -def get_nlr_developer_api_credential(which: str, env_path: str | Path | None) -> str: +def get_nlr_developer_api_credential(which: str, env_path: str | Path | None = None) -> str: """Get either the NLR API email or key with a fallback for the NREL credentials. Args: From 7d0b3fd6d018c9631d4db5fa6c363affd53df525 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:43:21 -0600 Subject: [PATCH 15/31] updated tests for env_tools --- h2integrate/core/env_tools.py | 2 +- h2integrate/core/test/test_env_tools.py | 273 +++++++++++++++--------- 2 files changed, 169 insertions(+), 106 deletions(-) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index 8dfd0fccf..3b81d47a4 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -78,7 +78,7 @@ def get_environment_variables( """ # Check if the environment variables have already been set env_vars = {name: os.environ.get(name) for name in args if os.environ.get(name) is not None} - remaining_vars = set(env_vars) - set(args) + remaining_vars = set(args) - set(env_vars) if len(remaining_vars) == 0: # All environment variables have already been set return env_vars diff --git a/h2integrate/core/test/test_env_tools.py b/h2integrate/core/test/test_env_tools.py index f64a7a8e9..b90ea8b82 100644 --- a/h2integrate/core/test/test_env_tools.py +++ b/h2integrate/core/test/test_env_tools.py @@ -3,166 +3,229 @@ import pytest -from h2integrate.core.env_tools import get_environment_var +from h2integrate.core.env_tools import ( + set_env_var, + load_env_vars_from_file, + get_environment_variables, +) -xx_test_env_var_xx = "" +@pytest.fixture(scope="function") +def temp_env_var(credential_value: str): + """Temporarily set the `RESOURCE_DIR` environment variable to example 11's weather folder.""" + # NOTE: changes to this fixture can result in hard-to-debug test failures + # in tests for resource components. Please do not modify this fixture if possible! - -def setter_getter_method(var_value): - global xx_test_env_var_xx - if var_value is not None: - xx_test_env_var_xx = var_value - return xx_test_env_var_xx + original = os.environ.get("TEST_CREDENTIAL") + os.environ["TEST_CREDENTIAL"] = credential_value + yield credential_value + os.environ.pop("TEST_CREDENTIAL", None) + assert os.getenv("TEST_CREDENTIAL") is None + if original is not None: + os.environ["TEST_CREDENTIAL"] = original @pytest.mark.unit -def test_set_environment_var(subtests): - with subtests.test("Initialized Properly"): - assert xx_test_env_var_xx == "" +@pytest.mark.parametrize("credential_value", ["none"]) +def test_set_environment_var(subtests, temp_env_var): + with subtests.test("Environment variable set"): + assert os.environ["TEST_CREDENTIAL"] == "none" - setter_getter_method(var_value="testing_set_environment_var") - with subtests.test("Set to long string"): - assert xx_test_env_var_xx == "testing_set_environment_var" + kwargs = {"TEST_CREDENTIAL": "updated"} + set_env_var(overwrite=True, **kwargs) - setter_getter_method(var_value=None) - with subtests.test("Unchanged when var_value is None"): - assert xx_test_env_var_xx == "testing_set_environment_var" + with subtests.test("Environment variable updated"): + assert os.environ["TEST_CREDENTIAL"] == "updated" - setter_getter_method(var_value="") - with subtests.test("Set to empty string"): - assert xx_test_env_var_xx == "" + kwargs = {"TEST_CREDENTIAL": "overwritten"} + set_env_var(overwrite=False, **kwargs) + with subtests.test("Environment variable not overwritten"): + assert os.environ["TEST_CREDENTIAL"] == "updated" @pytest.mark.unit -def test_get_environment_var_dot_env_provided_path(temp_dir, subtests): - """ - Test when an environment variable is set using a .env file and - the path to the .env file is specified. This tests `set_env_var_dot_env` - when using the `dotenv.load_dotenv()` method to load the environment variables - from a .env file with a specified path. - """ - - with subtests.test("Initialized Properly"): - assert xx_test_env_var_xx == "" - +def test_load_env_vars_from_file(subtests, temp_dir): env_path = temp_dir / ".env" with env_path.open("w+") as file: - file.write("TESTING_ENV=provided_a_path\n") + file.write("TEST_CREDENTIAL=my_credential_value\n") + + env_vars = load_env_vars_from_file(file_path=env_path) + with subtests.test("Read using = seperator"): + assert env_vars["TEST_CREDENTIAL"] == "my_credential_value" + + # add another variable to the file + with env_path.open("a+") as file: + file.write("TEST_CREDENTIAL_B=testing@yahoo.fake\n") + + env_vars = load_env_vars_from_file(env_path) + with subtests.test("Two credential with = seperator (TEST_CREDENTIAL)"): + assert env_vars["TEST_CREDENTIAL"] == "my_credential_value" + with subtests.test("Two credential with = seperator (TEST_CREDENTIAL_B)"): + assert env_vars["TEST_CREDENTIAL_B"] == "testing@yahoo.fake" + + # add a line without a separator to the file and a line using a different separator + with env_path.open("a+") as file: + file.write("TEST_CREDENTIAL_C_IS_A_SENTENCE\nTEST_CREDENTIAL_D : testingValue\n") + + env_vars = load_env_vars_from_file(env_path) + with subtests.test("Empty line and mixed separators (TEST_CREDENTIAL)"): + assert env_vars["TEST_CREDENTIAL"] == "my_credential_value" + with subtests.test("Empty line and mixed separators (TEST_CREDENTIAL_B)"): + assert env_vars["TEST_CREDENTIAL_B"] == "testing@yahoo.fake" + with subtests.test("Empty line and mixed separators (TEST_CREDENTIAL_D)"): + assert env_vars["TEST_CREDENTIAL_D"] == "testingValue" + with subtests.test("Empty line and mixed separators (skipped sentence line)"): + extra_vars = set(env_vars) - {"TEST_CREDENTIAL", "TEST_CREDENTIAL_B", "TEST_CREDENTIAL_D"} + assert len(extra_vars) == 0 - get_environment_var( - setter_getter_method, "TESTING_ENV", varname_old="TEST_ENV_OLD", env_path=env_path - ) - with subtests.test("Global variable was set"): - assert xx_test_env_var_xx == "provided_a_path" +@pytest.mark.unit +@pytest.mark.parametrize("credential_value", ["new_value"]) +def test_get_environment_variables_already_set(subtests, temp_env_var): + with subtests.test("TEST_CREDENTIAL environment variable (starting)"): + assert os.environ.get("TEST_CREDENTIAL") == "new_value" + + with subtests.test("NLR_API_KEY environment variable (starting)"): + assert os.environ.get("NLR_API_KEY") == "a" * 40 + + env_vars = get_environment_variables("TEST_CREDENTIAL", "NLR_API_KEY", set_variables=False) + + with subtests.test("TEST_CREDENTIAL returned"): + assert env_vars["TEST_CREDENTIAL"] == "new_value" - setter_getter_method(var_value="") + with subtests.test("NLR_API_KEY returned"): + assert env_vars["NLR_API_KEY"] == "a" * 40 + + with subtests.test("TEST_CREDENTIAL environment variable (ending)"): + assert os.environ.get("TEST_CREDENTIAL") == "new_value" + + with subtests.test("NLR_API_KEY environment variable (ending)"): + assert os.environ.get("NLR_API_KEY") == "a" * 40 @pytest.mark.unit -def test_get_environment_var_with_fallback(subtests): - """ - Test when an environment variable value is accessed using `_get_env_with_fallback()`. - The global environment variable itself is unmodified (its not set). - """ - # below tests the get env var where it should run _get_env_with_fallback - # which doesnt set the environment variable - with subtests.test("Initialized Properly"): - assert xx_test_env_var_xx == "" +def test_get_environment_variables_from_filepath(subtests, temp_dir): + env_path = temp_dir / "myapi.env" + + os.environ.pop("TEST_CREDENTIAL_A", None) + os.environ.pop("TEST_CREDENTIAL_B", None) + + env_file_txt = f"TEST_CREDENTIAL_A={temp_dir}\nTEST_CREDENTIAL_B=bees\n" - os.environ["TEST_ENV"] = "none" - env_var_val = get_environment_var(setter_getter_method, "TEST_ENV", "TEST_ENV_OLD") + with env_path.open("w+") as file: + file.write(env_file_txt) + + # started off not as environment variable + with subtests.test("TEST_CREDENTIAL_A not an environment variable"): + assert os.environ.get("TEST_CREDENTIAL_A") is None - with subtests.test("Returned TEST_ENV"): - assert env_var_val == "none" + with subtests.test("TEST_CREDENTIAL_B not an environment variable"): + assert os.environ.get("TEST_CREDENTIAL_B") is None + + # values pulled from file + env_vars = get_environment_variables( + "TEST_CREDENTIAL_A", "TEST_CREDENTIAL_B", file_path=env_path, set_variables=False + ) - os.environ.pop("TEST_ENV", None) + with subtests.test("TEST_CREDENTIAL_A value"): + assert env_vars["TEST_CREDENTIAL_A"] == str(temp_dir) - with subtests.test("Global variable was set"): - assert xx_test_env_var_xx == "none" + with subtests.test("TEST_CREDENTIAL_B value"): + assert env_vars["TEST_CREDENTIAL_B"] == "bees" - with subtests.test("Global variable was set #2"): - retrieved_val = setter_getter_method(var_value=None) - assert retrieved_val == "none" + # were not set as environment variables + with subtests.test("TEST_CREDENTIAL_A not set"): + assert os.environ.get("TEST_CREDENTIAL_A") is None - setter_getter_method(var_value="") + with subtests.test("TEST_CREDENTIAL_B not set"): + assert os.environ.get("TEST_CREDENTIAL_B") is None @pytest.mark.unit -def test_get_environment_var_dot_env(temp_dir, subtests): - """ - Test when an environment variable is set using a .env file and - the path to the .env file is NOT specified. This tests `set_env_var_dot_env` - when using the `dotenv.load_dotenv()` method to load the environment variables - from a .env file in one of the default possible locations (current working directory). - """ +def test_get_environment_variables_from_default_folder(subtests, temp_dir): + """Specify the file_name arg but not the filepath""" + + os.environ.pop("TEST_CREDENTIAL_A", None) + os.environ.pop("TEST_CREDENTIAL_B", None) + + # started off not as environment variable + with subtests.test("TEST_CREDENTIAL_A not an environment variable"): + assert os.environ.get("TEST_CREDENTIAL_A") is None + + with subtests.test("TEST_CREDENTIAL_B not an environment variable"): + assert os.environ.get("TEST_CREDENTIAL_B") is None + current_dir = Path.cwd() os.chdir(temp_dir) - env_path = temp_dir / ".env" - set_str = "did_not_provide_a_path" - with env_path.open("w+") as file: - file.write(f"TEST_ENV_OLD={set_str}\n") + env_path = temp_dir / "myfile.env" - tmp_dir_path = Path.cwd() / ".env" - with subtests.test("env file exists"): - assert tmp_dir_path.is_file() + env_file_txt = f"TEST_CREDENTIAL_A={temp_dir}\nTEST_CREDENTIAL_B=howdyFolks\n" + env_file_txt += "TEST_CREDENTIAL_C=i<3H2I\n" - with subtests.test("Global variable starts as empty"): - assert xx_test_env_var_xx == "" + with env_path.open("w+") as file: + file.write(env_file_txt) - get_environment_var( - setter_getter_method, "NOT_REAL_ENV", varname_old="TEST_ENV_OLD", env_path=None + env_vars = get_environment_variables( + "TEST_CREDENTIAL_A", "TEST_CREDENTIAL_B", file_name="myfile.env", set_variables=False ) - with subtests.test("Global variable was set"): - assert xx_test_env_var_xx == set_str + with subtests.test("TEST_CREDENTIAL_A value"): + assert env_vars["TEST_CREDENTIAL_A"] == str(temp_dir) + + with subtests.test("TEST_CREDENTIAL_B value"): + assert env_vars["TEST_CREDENTIAL_B"] == "howdyFolks" - with subtests.test("Global variable was set #2"): - retrieved_val = setter_getter_method(var_value=None) - assert retrieved_val == set_str + # were not set as environment variables + with subtests.test("TEST_CREDENTIAL_A not set"): + assert os.environ.get("TEST_CREDENTIAL_A") is None + + with subtests.test("TEST_CREDENTIAL_B not set"): + assert os.environ.get("TEST_CREDENTIAL_B") is None os.chdir(current_dir) - setter_getter_method(var_value="") @pytest.mark.unit -def test_get_environment_var_env_suffix_file(temp_dir, subtests): - """ - Test when an environment variable is set using a file with a .env suffix and - the path to the file is specified. This tests `set_env_var_dot_env` - when using the `load_file_with_variables()` method to load the environment variables - from a file with a specified path. - """ +def test_get_environment_variables_from_default_cwd(subtests, temp_dir): + os.environ.pop("TEST_CREDENTIAL_B", None) + os.environ.pop("TEST_CREDENTIAL_C", None) + + # started off not as environment variable + with subtests.test("TEST_CREDENTIAL_B not an environment variable"): + assert os.environ.get("TEST_CREDENTIAL_B") is None + + with subtests.test("TEST_CREDENTIAL_B not an environment variable"): + assert os.environ.get("TEST_CREDENTIAL_C") is None + current_dir = Path.cwd() os.chdir(temp_dir) - env_key = "TESTING_ENVIRONMENT_KEY" - set_str = str(temp_dir) # set the value to the temp dir name + env_path = temp_dir / ".env" - env_path = temp_dir / "vars.env" + env_file_txt = f"TEST_CREDENTIAL_A={temp_dir}\nTEST_CREDENTIAL_B=byeFolks\n" + env_file_txt += "TEST_CREDENTIAL_C=i<3H2I\n" with env_path.open("w+") as file: - file.write(f"{env_key}={set_str}\n") - - tmp_dir_path = Path.cwd() / "vars.env" + file.write(env_file_txt) - with subtests.test("env file exists"): - assert tmp_dir_path.is_file() + env_vars = get_environment_variables( + "TEST_CREDENTIAL_B", "TEST_CREDENTIAL_C", set_variables=True + ) - with subtests.test("Global variable starts as empty"): - assert xx_test_env_var_xx == "" + with subtests.test("TEST_CREDENTIAL_B value"): + assert env_vars["TEST_CREDENTIAL_B"] == "byeFolks" - get_environment_var(setter_getter_method, env_key, varname_old=None, env_path=env_path) + with subtests.test("TEST_CREDENTIAL_C value"): + assert env_vars["TEST_CREDENTIAL_C"] == "i<3H2I" - with subtests.test("Global variable was set"): - assert xx_test_env_var_xx == set_str + # were not set as environment variables + with subtests.test("TEST_CREDENTIAL_B set as environment variable"): + assert os.environ.get("TEST_CREDENTIAL_B") == "byeFolks" - with subtests.test("Global variable was set #2"): - retrieved_val = setter_getter_method(var_value=None) - assert retrieved_val == set_str + with subtests.test("TEST_CREDENTIAL_C set as environment variable"): + assert os.environ.get("TEST_CREDENTIAL_C") == "i<3H2I" os.chdir(current_dir) From c05f63b326480320181f0e35d0c765d28969e9d1 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:46:14 -0600 Subject: [PATCH 16/31] updated conftest.py files --- h2integrate/core/test/conftest.py | 4 ++-- h2integrate/resource/test/conftest.py | 4 ++-- test/conftest.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/h2integrate/core/test/conftest.py b/h2integrate/core/test/conftest.py index 4c2755827..30475284f 100644 --- a/h2integrate/core/test/conftest.py +++ b/h2integrate/core/test/conftest.py @@ -5,7 +5,7 @@ import os from h2integrate import EXAMPLE_DIR -from h2integrate.resource.utilities.nlr_developer_api_keys import set_nlr_key_dot_env +from h2integrate.resource.utilities.nlr_developer_api_keys import get_nlr_developer_api_credential from test.conftest import ( # noqa: F401 temp_dir, @@ -35,7 +35,7 @@ def pytest_sessionstart(session): # does not mess with a user's environment # Set a dummy API key os.environ["NLR_API_KEY"] = "a" * 40 - set_nlr_key_dot_env() + get_nlr_developer_api_credential(which="key") # if user provided a resource directory, save it to a temp variable # this allows tests to run as expected while not causing diff --git a/h2integrate/resource/test/conftest.py b/h2integrate/resource/test/conftest.py index 5cd0ab0ea..e22801bfb 100644 --- a/h2integrate/resource/test/conftest.py +++ b/h2integrate/resource/test/conftest.py @@ -2,7 +2,7 @@ import pytest -from h2integrate.resource.utilities.nlr_developer_api_keys import set_nlr_key_dot_env +from h2integrate.resource.utilities.nlr_developer_api_keys import get_nlr_developer_api_credential from test.conftest import ( # noqa: F401 temp_dir, @@ -70,7 +70,7 @@ def pytest_sessionstart(session): # Set a dummy API key os.environ["NLR_API_KEY"] = "a" * 40 - set_nlr_key_dot_env() + get_nlr_developer_api_credential(which="key") # Set RESOURCE_DIR to None so pulls example files from default DIR initial_resource_dir = os.getenv("RESOURCE_DIR") diff --git a/test/conftest.py b/test/conftest.py index a495705b5..79dbbe694 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -9,7 +9,7 @@ import pytest from h2integrate import EXAMPLE_DIR -from h2integrate.resource.utilities.nlr_developer_api_keys import set_nlr_key_dot_env +from h2integrate.resource.utilities.nlr_developer_api_keys import get_nlr_developer_api_credential def pytest_sessionstart(session): @@ -21,7 +21,7 @@ def pytest_sessionstart(session): # Set a dummy API key os.environ["NLR_API_KEY"] = "a" * 40 - set_nlr_key_dot_env() + get_nlr_developer_api_credential(which="key") # Set RESOURCE_DIR to None so pulls example files from default DIR initial_resource_dir = os.getenv("RESOURCE_DIR") From c99546971b2fad339e8cada23a4e77133e39a76d Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 14 Jul 2026 17:07:32 -0600 Subject: [PATCH 17/31] updated doc page --- docs/getting_started/environment_variables.md | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/docs/getting_started/environment_variables.md b/docs/getting_started/environment_variables.md index 913048cd4..2639ab55f 100644 --- a/docs/getting_started/environment_variables.md +++ b/docs/getting_started/environment_variables.md @@ -1,17 +1,20 @@ -(environment_variables:setting-environment-variables)= -# Setting Environment Variables +(environment_variables:environment-variables)= +# Environment Variables +H2Integrate can pull data from datasets accessible with API keys or user-specific tokens. Since API keys and tokens are unique to each user, these are accessed in H2Integrate as environment variables. These environment variables only need to be set to utilize their corresponding functionality. Some environment variables can also used to customize your workflow. The list of environment variables for different types of functionality are listed below: -H2Integrate can pull weather resource datasets (e.g. data needed for wind or solar generation) automatically for a user-provided location. -To use resource datasets from the NLR developer network, you will need an NLR API key, which can be obtained from: - [https://developer.nlr.gov/signup/](https://developer.nlr.gov/signup/). +- [NLR Developer Network](environment_variables:nlr_developer) + - `NLR_API_KEY` + - `NLR_API_EMAIL` +- [EIA Natural Gas Cost Data](environment_variables:eia_ng) + - `EIA_API_KEY` +- Customized Workflow + - `RESOURCE_DIR` -You will need to set the API key and the email you used to get the API key for downloading resource data from the NLR developer network. The 40 character API key is referred to in following sections as the value for the `NLR_API_KEY` environment variable. The email used to get the API key is referred to in the following sections as the value for the `NLR_API_EMAIL` environment variable. +To utilize the functionality of the models that require environment variables, instructions on how to set environment variables are shown [below](environment_variables:setting-environment-variables). -```{note} -The old environment variable names ``NREL_API_KEY`` and ``NREL_API_EMAIL`` are still supported -for backward compatibility, but are deprecated and will be removed in a future release. -Please migrate to ``NLR_API_KEY`` and ``NLR_API_EMAIL``. -``` +(environment_variables:setting-environment-variables)= +# Setting Environment Variables +We will use the environment variables needed for the NLR Developer Network (`NLR_API_KEY` and `NLR_API_EMAIL`) to showcase different methods of setting environment variables in this section. In the following sections on setting these environment variables, `'api-key-value'` should be replaced with your NLR API key and `'email-for-api-key'` should be replaced with your email address. @@ -106,3 +109,26 @@ The ".env" file will be looked for in all of the following locations: NLR_API_EMAIL='email-for-api-key' ``` 3. Save and close the ".env" file. + + +(environment_variables:nlr_developer)= +## NLR Developer Network Environment Variables + +H2Integrate can pull weather resource datasets (e.g. data needed for wind or solar generation) automatically for a user-provided location. +To use resource datasets from the NLR developer network, you will need an NLR API key, which can be obtained from: + [https://developer.nlr.gov/signup/](https://developer.nlr.gov/signup/). + +You will need to set the API key and the email you used to get the API key for downloading resource data from the NLR developer network. The 40 character API key is referred to in following sections as the value for the `NLR_API_KEY` environment variable. The email used to get the API key is referred to in the following sections as the value for the `NLR_API_EMAIL` environment variable. + +```{note} +The old environment variable names ``NREL_API_KEY`` and ``NREL_API_EMAIL`` are still supported +for backward compatibility, but are deprecated and will be removed in a future release. +Please migrate to ``NLR_API_KEY`` and ``NLR_API_EMAIL``. +``` + +(environment_variables:eia_ng)= +## EIA Natural Gas Cost +Further documentation on the EIA natural gas cost model can be [here](https://h2integrate.readthedocs.io/en/latest/technology_models/feedstocks.html). This requires an API key obtained from the [EIA Open Data portal](https://www.eia.gov/opendata/). This API key should be set as the value for the environment variable `EIA_API_KEY`, i.e., + ```bash + EIA_API_KEY='api-key-value' + ``` From f71c24f46baff897cd28d804cecccb575ad37610 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 15 Jul 2026 11:22:24 -0600 Subject: [PATCH 18/31] updated conftest.py files and get_environment_variables --- h2integrate/core/env_tools.py | 60 +++++++++++++------ h2integrate/core/test/conftest.py | 2 +- h2integrate/resource/test/conftest.py | 2 +- .../utilities/nlr_developer_api_keys.py | 5 -- test/conftest.py | 2 +- 5 files changed, 45 insertions(+), 26 deletions(-) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index 3b81d47a4..8f6643f34 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -43,7 +43,7 @@ def load_env_vars_from_file(file_path: Path) -> dict: elif ":" in line: sep = ":" else: - # skip this line + # skip line if invalid or missing separator continue k, v = line.strip().split(sep, 1) env_vars[k.strip()] = v.strip() @@ -52,58 +52,82 @@ def load_env_vars_from_file(file_path: Path) -> dict: def get_environment_variables( *args: str, - file_name: str | None = None, + file_name: str | None = ".env", file_path: str | None = None, set_variables: bool = True, ): - """Retrieve a series of credentials from a :py:attr:`file_name` in either the home directory - or H2Integrate root directory. If `:py:attr:`file_path` is provided, then :py:attr:`file_name` - and already set environment variables will be ignored. If :py:attr:`file_name` is provided, then - already set environment variables will be ignored. If neither file options are used, then an - existing environment variable will be retrieved. + """Retrieve a series of environment variables and values from existing environment variables, + from a fully resolved :py:attr:`file_path`, or from a :py:attr:`file_name` located in either + the home directory, H2Integrate root directory, or the current working directory. + + This function does the following: + + 1) Check the existing environment variables for :py:attr:`args`. If any :py:attr:`args` + have not yet been set as environment variables, continue to 2. + 2) If :py:attr:`file_path` is provided, then load environment variables from + :py:attr:`file_path`. If :py:attr:`set_variables` is True, then set the environment + variables that were found. Return the environment variables found up to this point. + If :py:attr:`file_path` is None, continue to 3. + 3) Check default directories (home directory, H2Integrate root directory, or the current + working directory) for :py:attr:`file_name` and load environment variables if the filepath + is valid. This prioritizes environment variable values found in step 1 over environment + variables loaded from the files. If :py:attr:`set_variables` is True, then + set the environment variables that were found. Return the environment variables found + up to this point. Args: - args (str): Name(s) of the credential(s) that should be retrieved from either - :py:attr:`file_name` or environment variables. + args (str): Name(s) of the environment variable(s) that should be retrieved from + environment variables, :py:attr:`file_path` or a default location of + :py:attr:`file_name`. file_name (str, optional): The name of a configuration file found in either the H2Integrate - root directory or the user's home directory that should contain the credential(s) in - :py:attr:`args`. + root directory, the user's home directory, or the user's current working directory + that should contain the environment variable(s) in :py:attr:`args`. Defaults to '.env'. file_path (str | Path, optional): The full file path for where the configuration file can be - found if not using the H2Integrate root directory or user home directory + found if not using one of the default directories. set_variables (bool, optional): If True, set the environment variables if they haven't already been set. Returns: - dict: Dictionary of all :py:attr:`args` with values of either the value if found. + dict: Dictionary of the :py:attr:`args` that values were found for. """ + # Step 1: Get existing environment variables # Check if the environment variables have already been set - env_vars = {name: os.environ.get(name) for name in args if os.environ.get(name) is not None} + env_vars = {name: var for name in args if (var := os.environ.get(name)) is not None} remaining_vars = set(args) - set(env_vars) - if len(remaining_vars) == 0: + if not remaining_vars: # All environment variables have already been set return env_vars + # Step 2: Load environment variables from `file_path` if file_path is not None: file_path = Path(file_path).resolve() if file_path.is_file(): - env_vars = load_env_vars_from_file(file_path) + # Prioritize environment variables from specified `file_path` + env_vars |= load_env_vars_from_file(file_path) + # Remove extraneous environment variables that were loaded from the file env_vars_subset = {name: env_vars.get(name) for name in args if name in env_vars} if set_variables: # Set the environment variables set_env_var(overwrite=True, **env_vars_subset) + # NOTE: should we check here if all the environment variables were found? + # Should the next part of the code execute if theres remaining + # environment variables? return env_vars_subset raise FileNotFoundError(f"Provided `file_path` is invalid: {file_path}") - default_folders = [Path.cwd(), Path.home(), ROOT_DIR, ROOT_DIR.parent] + # Step 3: Look in the cwd, home directory, and H2Integrate ROOT folders for `file_name` + default_folders = [ROOT_DIR, ROOT_DIR.parent, Path.cwd(), Path.home()] if file_name is None: # If a file_name isn't provided, look for a .env file file_name = ".env" for folder in default_folders: if (file_path := (folder / file_name)).is_file(): - env_vars |= load_env_vars_from_file(file_path) + # Prioritize environment variables that have already been set + env_vars = load_env_vars_from_file(file_path) | env_vars + # Remove extraneous environment variables that were loaded from file(s) env_vars_subset = {name: env_vars.get(name) for name in args if name in env_vars} if set_variables: # Set the environment variables diff --git a/h2integrate/core/test/conftest.py b/h2integrate/core/test/conftest.py index 30475284f..ee6b04d85 100644 --- a/h2integrate/core/test/conftest.py +++ b/h2integrate/core/test/conftest.py @@ -35,7 +35,7 @@ def pytest_sessionstart(session): # does not mess with a user's environment # Set a dummy API key os.environ["NLR_API_KEY"] = "a" * 40 - get_nlr_developer_api_credential(which="key") + _ = get_nlr_developer_api_credential(which="key") # if user provided a resource directory, save it to a temp variable # this allows tests to run as expected while not causing diff --git a/h2integrate/resource/test/conftest.py b/h2integrate/resource/test/conftest.py index e22801bfb..e186deeb4 100644 --- a/h2integrate/resource/test/conftest.py +++ b/h2integrate/resource/test/conftest.py @@ -70,7 +70,7 @@ def pytest_sessionstart(session): # Set a dummy API key os.environ["NLR_API_KEY"] = "a" * 40 - get_nlr_developer_api_credential(which="key") + _ = get_nlr_developer_api_credential(which="key") # Set RESOURCE_DIR to None so pulls example files from default DIR initial_resource_dir = os.getenv("RESOURCE_DIR") diff --git a/h2integrate/resource/utilities/nlr_developer_api_keys.py b/h2integrate/resource/utilities/nlr_developer_api_keys.py index 57c209496..14514ff53 100644 --- a/h2integrate/resource/utilities/nlr_developer_api_keys.py +++ b/h2integrate/resource/utilities/nlr_developer_api_keys.py @@ -74,8 +74,3 @@ def get_nlr_developer_api_email() -> str: """ nlr_api_email = get_nlr_developer_api_credential(which="email") return nlr_api_email - - -def set_nlr_key_dot_env(path: str | None | Path = None): - get_nlr_developer_api_credential(which="email", env_path=path) - get_nlr_developer_api_credential(which="key", env_path=path) diff --git a/test/conftest.py b/test/conftest.py index 79dbbe694..654805809 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -21,7 +21,7 @@ def pytest_sessionstart(session): # Set a dummy API key os.environ["NLR_API_KEY"] = "a" * 40 - get_nlr_developer_api_credential(which="key") + _ = get_nlr_developer_api_credential(which="key") # Set RESOURCE_DIR to None so pulls example files from default DIR initial_resource_dir = os.getenv("RESOURCE_DIR") From 7c2c723266ea267098ce0022e529994b83667dd4 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 15 Jul 2026 12:49:31 -0600 Subject: [PATCH 19/31] added doc page for debugging environment variable problems --- docs/_toc.yml | 1 + docs/getting_started/environment_variables.md | 8 +- .../debugging_environment_variables.md | 197 ++++++++++++++++++ h2integrate/core/env_tools.py | 2 + 4 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 docs/misc_resources/debugging_environment_variables.md diff --git a/docs/_toc.yml b/docs/_toc.yml index c71a74503..4e3e39ba3 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -116,6 +116,7 @@ parts: - file: misc_resources/glossary - file: misc_resources/defining_filepaths - file: misc_resources/turbine_models_library_preprocessing + - file: misc_resources/debugging_environment_variables - caption: API Reference maxdepth: 3 chapters: diff --git a/docs/getting_started/environment_variables.md b/docs/getting_started/environment_variables.md index 2639ab55f..e8659429f 100644 --- a/docs/getting_started/environment_variables.md +++ b/docs/getting_started/environment_variables.md @@ -10,6 +10,10 @@ H2Integrate can pull data from datasets accessible with API keys or user-specifi - Customized Workflow - `RESOURCE_DIR` +```{note} +Tips on debugging environment variable related errors or issues can be found [here](https://h2integrate.readthedocs.io/en/develop/misc_resources/debugging_environment_variables.html) +``` + To utilize the functionality of the models that require environment variables, instructions on how to set environment variables are shown [below](environment_variables:setting-environment-variables). (environment_variables:setting-environment-variables)= @@ -112,7 +116,7 @@ The ".env" file will be looked for in all of the following locations: (environment_variables:nlr_developer)= -## NLR Developer Network Environment Variables +# NLR Developer Network Environment Variables H2Integrate can pull weather resource datasets (e.g. data needed for wind or solar generation) automatically for a user-provided location. To use resource datasets from the NLR developer network, you will need an NLR API key, which can be obtained from: @@ -127,7 +131,7 @@ Please migrate to ``NLR_API_KEY`` and ``NLR_API_EMAIL``. ``` (environment_variables:eia_ng)= -## EIA Natural Gas Cost +# EIA Natural Gas Cost Further documentation on the EIA natural gas cost model can be [here](https://h2integrate.readthedocs.io/en/latest/technology_models/feedstocks.html). This requires an API key obtained from the [EIA Open Data portal](https://www.eia.gov/opendata/). This API key should be set as the value for the environment variable `EIA_API_KEY`, i.e., ```bash EIA_API_KEY='api-key-value' diff --git a/docs/misc_resources/debugging_environment_variables.md b/docs/misc_resources/debugging_environment_variables.md new file mode 100644 index 000000000..337d4823c --- /dev/null +++ b/docs/misc_resources/debugging_environment_variables.md @@ -0,0 +1,197 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.18.1 +kernelspec: + display_name: native-ard-h2i + language: python + name: python3 +--- + +# Environment Variables: Non-standard Configuration and Debugging Issues + +If errors are encountered with models that use environment variables, such as a missing API key or other credential, this may be because the environment variables were set using a non-preferred method or your workflow may be different. The different approaches to setting environment variables is documented [here](https://h2integrate.readthedocs.io/en/latest/getting_started/environment_variables.html). Some of the non-preferred methods to set environment variables may require that environment variables be reset when your environment is activated. If you're setting environment variables with a .env file, then errors may occur because the file is not found. + +The following sections will demonstrate how to debug environment variable issues and alternative ways to set environment variables. + +- [Environment File: Getting Default Supported Filepaths](env_var_debug:get_default_fpaths) +- [Environment File: Check Default Supported Filepaths](env_var_debug:is_default_fpath) +- [Environment File: Setting Environment Variables prior to H2I Simulation](env_var_debug:nonstandard_fpath) +- [Debug Missing Environment Variables](env_var_debug:debug_missing) +- [Manually Setting Environment Variables](env_var_debug:manual_setting) + + +## Non-standard environment file location or name + +If your getting an error for a missing environment variable and your environment variables are stored in a `.env` file, then the `.env` file may not be found in the expected location. + +(env_var_debug:get_default_fpaths)= +### Get the supported default filepaths + +The code below shows how to print a list of the default filepaths that H2I will look for the environment file. + + +```{code-cell} ipython3 +from pathlib import Path + +from h2integrate import ROOT_DIR + +default_directories = [ROOT_DIR, ROOT_DIR.parent, Path.cwd(), Path.home()] + +print("Supported default environment file locations: \n") +for folder in default_directories: + print(folder/".env") +``` + +(env_var_debug:is_default_fpath)= +### Determine if environment file is in supported default filepath + +If you want to determine whether your environment file is placed in one of the valid locations, the code below will tell you if your environment file is found in any of the default directories: + +```{code-cell} ipython3 +from pathlib import Path + +from h2integrate import ROOT_DIR + +default_directories = [ROOT_DIR, ROOT_DIR.parent, Path.cwd(), Path.home()] + +env_fpaths = [fpath for folder in default_directories if (fpath:=folder/".env").is_file()] +if not env_fpaths: + print("Environment file not found in any supported default directories") +else: + print("Environment file(s) found in the following supported default filepath(s):\n") + txt = "\n".join(str(f) for f in env_fpaths) + print(txt) +``` + +(env_var_debug:nonstandard_fpath)= +### Setting environment variables with non-default environment filepath +If your environment file is not in any of the supported default locations or has a different name than ".env", then you can set the environment variables in your H2I run-script prior to running H2I. + +If you know the filepath of your environment file, you can replace `"/path/to/environment_file/.env"` with the filepath of your environment file in the code below. Below shows an example of setting the environment variables `NLR_API_KEY` and `NLR_API_EMAIL` prior to running Example 1. + +```python +from h2integrate import H2IntegrateModel +from h2integrate.core.env_tools import get_environment_variables + +environment_fpath = "/path/to/environment_file/.env" + +# Set the environment variables prior to running H2I +env_vars = get_environment_variables("NLR_API_KEY","NLR_API_EMAIL", file_path=environment_fpath, set_variables=True) + +model = H2IntegrateModel("01_onshore_steel_mn.yaml") +model.setup() +model.run() +``` + +If your environment file **is** in of the default supported directories but is named something different than `".env"`, you could instead set the environment variables prior to running H2I with the following code (replace `"myenv.env"` with the filename of your environment file in the code below): + +```python +from h2integrate import H2IntegrateModel +from h2integrate.core.env_tools import get_environment_variables + +environment_fname = "myenv.env" + +# Set the environment variables prior to running H2I +env_vars = get_environment_variables("NLR_API_KEY","NLR_API_EMAIL", file_name=environment_fname, set_variables=True) + +model = H2IntegrateModel("01_onshore_steel_mn.yaml") +model.setup() +model.run() +``` + +(env_var_debug:debug_missing)= +## Debugging a missing environment variable +For any method of setting environment variables, you can use the code below to determine if your environment variables are being loaded properly or being set. +```{note} +If you're setting environment variables with an environment file in a non-standard location or filename, refer to the section above on how to specify the inputs to the `get_environment_variables` function. +``` + +Below shows an example of checking if the `NLR_API_KEY` and `NLR_API_EMAIL` environment variables are being **found** and what the values are of the variables that are found: + +```python +import os +from pathlib import Path + +from h2integrate.core.env_tools import get_environment_variables + +# Check if environment variables are being found +env_vars = get_environment_variables("NLR_API_KEY","NLR_API_EMAIL",set_variables=False) +if not env_vars: + print("No environment variables found") +else: + print("Found environment variables with the values below:") + for key, val in env_vars.items(): + print(f"Environment variable `{key}` is set to `{val}`") + +``` + + +Below shows an example of checking if the `NLR_API_KEY` and `NLR_API_EMAIL` environment variables are being **set** and what the values are of the variables that are set: + +```python +import os +from pathlib import Path + +from h2integrate.core.env_tools import get_environment_variables + +# Check if environment variables are being set +get_environment_variables("NLR_API_KEY","NLR_API_EMAIL",set_variables=True) + +nlr_api_key = os.environ.get("NLR_API_KEY", None) +nlr_api_email = os.environ.get("NLR_API_EMAIL", None) + +set_env_vars = dict(zip(["NLR_API_KEY", "NLR_API_EMAIL"], [nlr_api_key, nlr_api_email])) + +for key,val in set_env_vars.items(): + if val is None: + print(f"Environment variable `{key}` was not set") + else: + print(f"Environment variable `{key}` is set to `{val}`") +``` + +(env_var_debug:manual_setting)= +## Quick-fixes to avoid missing environment variable issues + +If you're having issues with setting environment variables and need a quick-workaround, you can manually set environment variables in your runscript prior to running H2I. + +```{important} +Do not share code that includes sensitive credentials, such as API keys. The following work-around should only be used for personal code and never shared with others or pushed to a shared or public github repository. +``` + +The below example shows a case where the environment variables `NLR_API_KEY` and `NLR_API_EMAIL` are hard-coded prior to running Example 1 (`"nlr-api-key"` would be replaced with your API key and `"name@email.com"` would be replaced with your email) + +```python +import os +from h2integrate import H2IntegrateModel + +# Hard code environment variables +os.environ["NLR_API_KEY"] = "nlr-api-key" # replace with your api key +os.environ["NLR_API_EMAIL"] = "name@email.com" # replace with your email + +model = H2IntegrateModel("01_onshore_steel_mn.yaml") +model.setup() +model.run() +``` + +Another way to set environment variables is shown below: + +```python +from h2integrate.core.env_tools import set_env_var +from h2integrate import H2IntegrateModel + + +env_keys = { + "NLR_API_KEY": "nlr-api-key" # replace with your api key + "NLR_API_EMAIL": "name@email.com" # replace with your email +} + +set_env_var(overwrite=True, **env_keys) + +model = H2IntegrateModel("01_onshore_steel_mn.yaml") +model.setup() +model.run() +``` diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index 8f6643f34..27cd16a71 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -64,10 +64,12 @@ def get_environment_variables( 1) Check the existing environment variables for :py:attr:`args`. If any :py:attr:`args` have not yet been set as environment variables, continue to 2. + 2) If :py:attr:`file_path` is provided, then load environment variables from :py:attr:`file_path`. If :py:attr:`set_variables` is True, then set the environment variables that were found. Return the environment variables found up to this point. If :py:attr:`file_path` is None, continue to 3. + 3) Check default directories (home directory, H2Integrate root directory, or the current working directory) for :py:attr:`file_name` and load environment variables if the filepath is valid. This prioritizes environment variable values found in step 1 over environment From 7e1a67e2bb1e37472d9625c6c59a3741bef1d2e7 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:08:15 -0600 Subject: [PATCH 20/31] updated doc page so formatting is nice --- docs/getting_started/environment_variables.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/getting_started/environment_variables.md b/docs/getting_started/environment_variables.md index e8659429f..933c8308d 100644 --- a/docs/getting_started/environment_variables.md +++ b/docs/getting_started/environment_variables.md @@ -133,6 +133,7 @@ Please migrate to ``NLR_API_KEY`` and ``NLR_API_EMAIL``. (environment_variables:eia_ng)= # EIA Natural Gas Cost Further documentation on the EIA natural gas cost model can be [here](https://h2integrate.readthedocs.io/en/latest/technology_models/feedstocks.html). This requires an API key obtained from the [EIA Open Data portal](https://www.eia.gov/opendata/). This API key should be set as the value for the environment variable `EIA_API_KEY`, i.e., - ```bash - EIA_API_KEY='api-key-value' - ``` + +```bash +EIA_API_KEY='api-key-value' +``` From e5dc05200f543c130cb3eee57a87e5e483df943f Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:11:08 -0600 Subject: [PATCH 21/31] typo fix in model_overview.md --- docs/user_guide/model_overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user_guide/model_overview.md b/docs/user_guide/model_overview.md index 9f5b5cbc9..8c943e7a7 100644 --- a/docs/user_guide/model_overview.md +++ b/docs/user_guide/model_overview.md @@ -1,7 +1,7 @@ (model-overview)= # Model Overview -Currently, H2I recognizes four types of models: +Currently, H2I recognizes five types of models: - [Resource](#resource) - [Converter](#converters) From 1c979c837185bfd83bf85765c07baeef7a8aeb7a Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:26:16 -0600 Subject: [PATCH 22/31] updates to doc pages --- docs/getting_started/environment_variables.md | 12 ++++++------ .../debugging_environment_variables.md | 1 + docs/technology_models/feedstocks.md | 2 ++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/getting_started/environment_variables.md b/docs/getting_started/environment_variables.md index 933c8308d..1a5852263 100644 --- a/docs/getting_started/environment_variables.md +++ b/docs/getting_started/environment_variables.md @@ -1,6 +1,6 @@ (environment_variables:environment-variables)= # Environment Variables -H2Integrate can pull data from datasets accessible with API keys or user-specific tokens. Since API keys and tokens are unique to each user, these are accessed in H2Integrate as environment variables. These environment variables only need to be set to utilize their corresponding functionality. Some environment variables can also used to customize your workflow. The list of environment variables for different types of functionality are listed below: +H2Integrate can pull data (such as wind and solar resource data, feedstock prices, etc) from public datasets accessible with API keys or user-specific tokens. Since API keys and tokens are unique to each user, these are accessed in H2Integrate as environment variables. These environment variables only need to be set to utilize their corresponding functionality. Some environment variables can also be used to customize your workflow. The list of environment variables that may be used by H2Integrate are listed below: - [NLR Developer Network](environment_variables:nlr_developer) - `NLR_API_KEY` @@ -11,7 +11,7 @@ H2Integrate can pull data from datasets accessible with API keys or user-specifi - `RESOURCE_DIR` ```{note} -Tips on debugging environment variable related errors or issues can be found [here](https://h2integrate.readthedocs.io/en/develop/misc_resources/debugging_environment_variables.html) +Tips on debugging environment variable related errors or issues can be found [here](#env_var_debug:intro) ``` To utilize the functionality of the models that require environment variables, instructions on how to set environment variables are shown [below](environment_variables:setting-environment-variables). @@ -26,7 +26,7 @@ An optional environment variable is `RESOURCE_DIR`. If set, this will be used as The remaining sections outline different options for setting environment variables in H2Integrate: - [Save environment variables with conda (preferred)](#save-environment-variables-with-conda-preferred) -- [Set environment variables with a .yaml file](#set-environment-variables-with-yaml-file) +- [Set environment variables with a .yml file](#set-environment-variables-with-yaml-file) - [Set environment variables with a .env file](#set-environment-variables-with-env-file) (save-environment-variables-with-conda-preferred)= @@ -76,7 +76,7 @@ unset RESOURCE_DIR ``` (set-environment-variables-with-yaml-file)= -## Set Environment Variables with .yaml file +## Set Environment Variables with .yml file 1. In `environment.yml`, add the following lines to the bottom of the file, and replace the environment variable values with your information. Be sure that @@ -122,7 +122,7 @@ H2Integrate can pull weather resource datasets (e.g. data needed for wind or sol To use resource datasets from the NLR developer network, you will need an NLR API key, which can be obtained from: [https://developer.nlr.gov/signup/](https://developer.nlr.gov/signup/). -You will need to set the API key and the email you used to get the API key for downloading resource data from the NLR developer network. The 40 character API key is referred to in following sections as the value for the `NLR_API_KEY` environment variable. The email used to get the API key is referred to in the following sections as the value for the `NLR_API_EMAIL` environment variable. +You will need to set the API key and the email you used to get the API key to download resource data from the NLR developer network. The 40 character API key is referred to in following sections as the value for the `NLR_API_KEY` environment variable. The email used to get the API key is referred to in the following sections as the value for the `NLR_API_EMAIL` environment variable. ```{note} The old environment variable names ``NREL_API_KEY`` and ``NREL_API_EMAIL`` are still supported @@ -132,7 +132,7 @@ Please migrate to ``NLR_API_KEY`` and ``NLR_API_EMAIL``. (environment_variables:eia_ng)= # EIA Natural Gas Cost -Further documentation on the EIA natural gas cost model can be [here](https://h2integrate.readthedocs.io/en/latest/technology_models/feedstocks.html). This requires an API key obtained from the [EIA Open Data portal](https://www.eia.gov/opendata/). This API key should be set as the value for the environment variable `EIA_API_KEY`, i.e., +Further documentation on the EIA natural gas cost model can be [here](#feedstocks:eia_ng_price). This requires an API key obtained from the [EIA Open Data portal](https://www.eia.gov/opendata/). This API key should be set as the value for the environment variable `EIA_API_KEY`, i.e., ```bash EIA_API_KEY='api-key-value' diff --git a/docs/misc_resources/debugging_environment_variables.md b/docs/misc_resources/debugging_environment_variables.md index 337d4823c..134fb5387 100644 --- a/docs/misc_resources/debugging_environment_variables.md +++ b/docs/misc_resources/debugging_environment_variables.md @@ -11,6 +11,7 @@ kernelspec: name: python3 --- +(env_var_debug:intro)= # Environment Variables: Non-standard Configuration and Debugging Issues If errors are encountered with models that use environment variables, such as a missing API key or other credential, this may be because the environment variables were set using a non-preferred method or your workflow may be different. The different approaches to setting environment variables is documented [here](https://h2integrate.readthedocs.io/en/latest/getting_started/environment_variables.html). Some of the non-preferred methods to set environment variables may require that environment variables be reset when your environment is activated. If you're setting environment variables with a .env file, then errors may occur because the file is not found. diff --git a/docs/technology_models/feedstocks.md b/docs/technology_models/feedstocks.md index 8e5b373ad..35289febe 100644 --- a/docs/technology_models/feedstocks.md +++ b/docs/technology_models/feedstocks.md @@ -1,3 +1,4 @@ +(feedstocks:intro)= # Feedstock Models Feedstock models in H2Integrate represent any resource input that is consumed by technologies in your plant that comes from outside your designed system boundary (and not generated internally), such as natural gas, water, electricity from the grid, or any other material input. @@ -94,6 +95,7 @@ The feedstock model outputs cost and performance information about the consumed - `rated_{commodity}_production`: this is equal to the the `rated_capacity` of the feedstock model (in `commodity_rate_units`) - `capacity_factor`: ratio of the feedstock consumed to the maximum feedstock available +(feedstocks:eia_ng_price)= ## EIA Natural Gas Pricing A special case of the feedstock cost model `EIANaturalGasFeedstockCostModel` exists to enable users From dabdc6f49ec8f7a748b1f481e2d87255d4dd3ece Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:30:36 -0600 Subject: [PATCH 23/31] added set_vars as input to get_nlr_developer_api_credential and updated conftest.py files --- h2integrate/core/test/conftest.py | 2 +- h2integrate/resource/test/conftest.py | 2 +- h2integrate/resource/utilities/nlr_developer_api_keys.py | 6 ++++-- test/conftest.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/h2integrate/core/test/conftest.py b/h2integrate/core/test/conftest.py index ee6b04d85..75fdfb938 100644 --- a/h2integrate/core/test/conftest.py +++ b/h2integrate/core/test/conftest.py @@ -35,7 +35,7 @@ def pytest_sessionstart(session): # does not mess with a user's environment # Set a dummy API key os.environ["NLR_API_KEY"] = "a" * 40 - _ = get_nlr_developer_api_credential(which="key") + _ = get_nlr_developer_api_credential(which="key", set_vars=True) # if user provided a resource directory, save it to a temp variable # this allows tests to run as expected while not causing diff --git a/h2integrate/resource/test/conftest.py b/h2integrate/resource/test/conftest.py index e186deeb4..208993eed 100644 --- a/h2integrate/resource/test/conftest.py +++ b/h2integrate/resource/test/conftest.py @@ -70,7 +70,7 @@ def pytest_sessionstart(session): # Set a dummy API key os.environ["NLR_API_KEY"] = "a" * 40 - _ = get_nlr_developer_api_credential(which="key") + _ = get_nlr_developer_api_credential(which="key", set_vars=True) # Set RESOURCE_DIR to None so pulls example files from default DIR initial_resource_dir = os.getenv("RESOURCE_DIR") diff --git a/h2integrate/resource/utilities/nlr_developer_api_keys.py b/h2integrate/resource/utilities/nlr_developer_api_keys.py index 14514ff53..c4e4c624e 100644 --- a/h2integrate/resource/utilities/nlr_developer_api_keys.py +++ b/h2integrate/resource/utilities/nlr_developer_api_keys.py @@ -14,7 +14,9 @@ ) -def get_nlr_developer_api_credential(which: str, env_path: str | Path | None = None) -> str: +def get_nlr_developer_api_credential( + which: str, env_path: str | Path | None = None, set_vars: bool = True +) -> str: """Get either the NLR API email or key with a fallback for the NREL credentials. Args: @@ -35,7 +37,7 @@ def get_nlr_developer_api_credential(which: str, env_path: str | Path | None = N old_name = f"NREL_API_{which.upper()}" new_name = f"NLR_API_{which.upper()}" nlr_api_vars = get_environment_variables( - new_name, old_name, file_path=env_path, set_variables=True + new_name, old_name, file_path=env_path, set_variables=set_vars ) if not bool(nlr_api_vars): # returned an empty dictionary diff --git a/test/conftest.py b/test/conftest.py index 654805809..fd67d96d1 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -21,7 +21,7 @@ def pytest_sessionstart(session): # Set a dummy API key os.environ["NLR_API_KEY"] = "a" * 40 - _ = get_nlr_developer_api_credential(which="key") + _ = get_nlr_developer_api_credential(which="key", set_vars=True) # Set RESOURCE_DIR to None so pulls example files from default DIR initial_resource_dir = os.getenv("RESOURCE_DIR") From 9909098499bfbf7c4c7923f70680d0c1e6ea4cba Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:08:35 -0600 Subject: [PATCH 24/31] updated debugging environment variables doc page --- docs/misc_resources/debugging_environment_variables.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/misc_resources/debugging_environment_variables.md b/docs/misc_resources/debugging_environment_variables.md index 134fb5387..2e1d871bb 100644 --- a/docs/misc_resources/debugging_environment_variables.md +++ b/docs/misc_resources/debugging_environment_variables.md @@ -14,7 +14,7 @@ kernelspec: (env_var_debug:intro)= # Environment Variables: Non-standard Configuration and Debugging Issues -If errors are encountered with models that use environment variables, such as a missing API key or other credential, this may be because the environment variables were set using a non-preferred method or your workflow may be different. The different approaches to setting environment variables is documented [here](https://h2integrate.readthedocs.io/en/latest/getting_started/environment_variables.html). Some of the non-preferred methods to set environment variables may require that environment variables be reset when your environment is activated. If you're setting environment variables with a .env file, then errors may occur because the file is not found. +If errors are encountered with models that use environment variables, such as a missing API key or other credential, this may be because the environment variables were set using a non-preferred method or your workflow may be different. The different approaches to setting environment variables is documented [in the getting started guides](#environment_variables:environment-variables). Some of the non-preferred methods to set environment variables may require that environment variables be reset when your environment is activated. If you're setting environment variables with a .env file, then errors may occur because the file is not found. The following sections will demonstrate how to debug environment variable issues and alternative ways to set environment variables. @@ -114,7 +114,6 @@ If you're setting environment variables with an environment file in a non-standa Below shows an example of checking if the `NLR_API_KEY` and `NLR_API_EMAIL` environment variables are being **found** and what the values are of the variables that are found: ```python -import os from pathlib import Path from h2integrate.core.env_tools import get_environment_variables From e5f442f657d9de2cff457d5043fe714c74838def Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:26:22 -0600 Subject: [PATCH 25/31] cleaned up some of test_env_tools.py --- h2integrate/core/test/test_env_tools.py | 53 ++++++++++--------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/h2integrate/core/test/test_env_tools.py b/h2integrate/core/test/test_env_tools.py index b90ea8b82..7076d64d7 100644 --- a/h2integrate/core/test/test_env_tools.py +++ b/h2integrate/core/test/test_env_tools.py @@ -1,5 +1,6 @@ import os from pathlib import Path +from contextlib import chdir import pytest @@ -47,36 +48,26 @@ def test_set_environment_var(subtests, temp_env_var): def test_load_env_vars_from_file(subtests, temp_dir): env_path = temp_dir / ".env" with env_path.open("w+") as file: - file.write("TEST_CREDENTIAL=my_credential_value\n") + file.write("TEST_CREDENTIAL=my_credential_value\n") # = with no spaces + file.write("TEST_CREDENTIAL_B=testing@yahoo.fake\n") # = with spaces + file.write("TEST_CREDENTIAL_C_IS_A_SENTENCE\n") # should be skipped + file.write("TEST_CREDENTIAL_D : testingValue\n") # : with spaces + file.write("TEST_CREDENTIAL_E :: another_credential\n") # consecutive delimiter env_vars = load_env_vars_from_file(file_path=env_path) - with subtests.test("Read using = seperator"): - assert env_vars["TEST_CREDENTIAL"] == "my_credential_value" - - # add another variable to the file - with env_path.open("a+") as file: - file.write("TEST_CREDENTIAL_B=testing@yahoo.fake\n") - env_vars = load_env_vars_from_file(env_path) - with subtests.test("Two credential with = seperator (TEST_CREDENTIAL)"): + with subtests.test("4 environment variables loaded"): + assert len(env_vars) == 4 + with subtests.test("Credential using = separator without spaces"): assert env_vars["TEST_CREDENTIAL"] == "my_credential_value" - with subtests.test("Two credential with = seperator (TEST_CREDENTIAL_B)"): + with subtests.test("Credential using = separator with spaces"): assert env_vars["TEST_CREDENTIAL_B"] == "testing@yahoo.fake" - - # add a line without a separator to the file and a line using a different separator - with env_path.open("a+") as file: - file.write("TEST_CREDENTIAL_C_IS_A_SENTENCE\nTEST_CREDENTIAL_D : testingValue\n") - - env_vars = load_env_vars_from_file(env_path) - with subtests.test("Empty line and mixed separators (TEST_CREDENTIAL)"): - assert env_vars["TEST_CREDENTIAL"] == "my_credential_value" - with subtests.test("Empty line and mixed separators (TEST_CREDENTIAL_B)"): - assert env_vars["TEST_CREDENTIAL_B"] == "testing@yahoo.fake" - with subtests.test("Empty line and mixed separators (TEST_CREDENTIAL_D)"): + with subtests.test("Credential using : separator with spaces"): assert env_vars["TEST_CREDENTIAL_D"] == "testingValue" - with subtests.test("Empty line and mixed separators (skipped sentence line)"): - extra_vars = set(env_vars) - {"TEST_CREDENTIAL", "TEST_CREDENTIAL_B", "TEST_CREDENTIAL_D"} - assert len(extra_vars) == 0 + with subtests.test("Line without separator is not loaded"): + assert "TEST_CREDENTIAL_C" not in env_vars + with subtests.test("Credential using consecutive delimiter"): + assert env_vars["TEST_CREDENTIAL_E"] == ": another_credential" @pytest.mark.unit @@ -195,13 +186,12 @@ def test_get_environment_variables_from_default_cwd(subtests, temp_dir): # started off not as environment variable with subtests.test("TEST_CREDENTIAL_B not an environment variable"): assert os.environ.get("TEST_CREDENTIAL_B") is None - with subtests.test("TEST_CREDENTIAL_B not an environment variable"): assert os.environ.get("TEST_CREDENTIAL_C") is None - current_dir = Path.cwd() + # current_dir = Path.cwd() - os.chdir(temp_dir) + # os.chdir(temp_dir) env_path = temp_dir / ".env" @@ -211,9 +201,10 @@ def test_get_environment_variables_from_default_cwd(subtests, temp_dir): with env_path.open("w+") as file: file.write(env_file_txt) - env_vars = get_environment_variables( - "TEST_CREDENTIAL_B", "TEST_CREDENTIAL_C", set_variables=True - ) + with chdir(temp_dir): + env_vars = get_environment_variables( + "TEST_CREDENTIAL_B", "TEST_CREDENTIAL_C", set_variables=True + ) with subtests.test("TEST_CREDENTIAL_B value"): assert env_vars["TEST_CREDENTIAL_B"] == "byeFolks" @@ -227,5 +218,3 @@ def test_get_environment_variables_from_default_cwd(subtests, temp_dir): with subtests.test("TEST_CREDENTIAL_C set as environment variable"): assert os.environ.get("TEST_CREDENTIAL_C") == "i<3H2I" - - os.chdir(current_dir) From 02338661d7878e4cfe4b23d4bf0f4a4ee6fbb245 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:38:39 -0600 Subject: [PATCH 26/31] finished cleanups to test_env_tools.py --- h2integrate/core/test/test_env_tools.py | 57 ++++++++++++------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/h2integrate/core/test/test_env_tools.py b/h2integrate/core/test/test_env_tools.py index 7076d64d7..c5afb19af 100644 --- a/h2integrate/core/test/test_env_tools.py +++ b/h2integrate/core/test/test_env_tools.py @@ -1,5 +1,4 @@ import os -from pathlib import Path from contextlib import chdir import pytest @@ -13,9 +12,9 @@ @pytest.fixture(scope="function") def temp_env_var(credential_value: str): - """Temporarily set the `RESOURCE_DIR` environment variable to example 11's weather folder.""" + """Temporarily set the `TEST_CREDENTIAL` environment variable""" # NOTE: changes to this fixture can result in hard-to-debug test failures - # in tests for resource components. Please do not modify this fixture if possible! + # in tests for environment variables components. Please do not modify this fixture if possible! original = os.environ.get("TEST_CREDENTIAL") os.environ["TEST_CREDENTIAL"] = credential_value @@ -96,24 +95,23 @@ def test_get_environment_variables_already_set(subtests, temp_env_var): @pytest.mark.unit def test_get_environment_variables_from_filepath(subtests, temp_dir): - env_path = temp_dir / "myapi.env" - os.environ.pop("TEST_CREDENTIAL_A", None) os.environ.pop("TEST_CREDENTIAL_B", None) - env_file_txt = f"TEST_CREDENTIAL_A={temp_dir}\nTEST_CREDENTIAL_B=bees\n" - - with env_path.open("w+") as file: - file.write(env_file_txt) - - # started off not as environment variable + # environment variables were properly removed prior to rest of test with subtests.test("TEST_CREDENTIAL_A not an environment variable"): assert os.environ.get("TEST_CREDENTIAL_A") is None - with subtests.test("TEST_CREDENTIAL_B not an environment variable"): assert os.environ.get("TEST_CREDENTIAL_B") is None - # values pulled from file + # Make a file containing credentials + env_path = temp_dir / "myapi.env" + env_file_txt = f"TEST_CREDENTIAL_A={temp_dir}\nTEST_CREDENTIAL_B=bees\n" + + with env_path.open("w+") as file: + file.write(env_file_txt) + + # Get the environment variables but don't set them env_vars = get_environment_variables( "TEST_CREDENTIAL_A", "TEST_CREDENTIAL_B", file_path=env_path, set_variables=False ) @@ -124,7 +122,7 @@ def test_get_environment_variables_from_filepath(subtests, temp_dir): with subtests.test("TEST_CREDENTIAL_B value"): assert env_vars["TEST_CREDENTIAL_B"] == "bees" - # were not set as environment variables + # Check that variables were not set as environment variables with subtests.test("TEST_CREDENTIAL_A not set"): assert os.environ.get("TEST_CREDENTIAL_A") is None @@ -139,17 +137,14 @@ def test_get_environment_variables_from_default_folder(subtests, temp_dir): os.environ.pop("TEST_CREDENTIAL_A", None) os.environ.pop("TEST_CREDENTIAL_B", None) - # started off not as environment variable + # environment variables were properly removed prior to rest of test with subtests.test("TEST_CREDENTIAL_A not an environment variable"): assert os.environ.get("TEST_CREDENTIAL_A") is None with subtests.test("TEST_CREDENTIAL_B not an environment variable"): assert os.environ.get("TEST_CREDENTIAL_B") is None - current_dir = Path.cwd() - - os.chdir(temp_dir) - + # Make a file containing credentials env_path = temp_dir / "myfile.env" env_file_txt = f"TEST_CREDENTIAL_A={temp_dir}\nTEST_CREDENTIAL_B=howdyFolks\n" @@ -158,9 +153,12 @@ def test_get_environment_variables_from_default_folder(subtests, temp_dir): with env_path.open("w+") as file: file.write(env_file_txt) - env_vars = get_environment_variables( - "TEST_CREDENTIAL_A", "TEST_CREDENTIAL_B", file_name="myfile.env", set_variables=False - ) + # Change CWD to the temporary folder when loading environment variables + # Get the environment variables but don't set them + with chdir(temp_dir): + env_vars = get_environment_variables( + "TEST_CREDENTIAL_A", "TEST_CREDENTIAL_B", file_name="myfile.env", set_variables=False + ) with subtests.test("TEST_CREDENTIAL_A value"): assert env_vars["TEST_CREDENTIAL_A"] == str(temp_dir) @@ -175,24 +173,19 @@ def test_get_environment_variables_from_default_folder(subtests, temp_dir): with subtests.test("TEST_CREDENTIAL_B not set"): assert os.environ.get("TEST_CREDENTIAL_B") is None - os.chdir(current_dir) - @pytest.mark.unit def test_get_environment_variables_from_default_cwd(subtests, temp_dir): os.environ.pop("TEST_CREDENTIAL_B", None) os.environ.pop("TEST_CREDENTIAL_C", None) - # started off not as environment variable + # environment variables were properly removed prior to rest of test with subtests.test("TEST_CREDENTIAL_B not an environment variable"): assert os.environ.get("TEST_CREDENTIAL_B") is None with subtests.test("TEST_CREDENTIAL_B not an environment variable"): assert os.environ.get("TEST_CREDENTIAL_C") is None - # current_dir = Path.cwd() - - # os.chdir(temp_dir) - + # Create path to .env file and make the file env_path = temp_dir / ".env" env_file_txt = f"TEST_CREDENTIAL_A={temp_dir}\nTEST_CREDENTIAL_B=byeFolks\n" @@ -201,18 +194,22 @@ def test_get_environment_variables_from_default_cwd(subtests, temp_dir): with env_path.open("w+") as file: file.write(env_file_txt) + # Change CWD to the temporary folder when loading environment variables + # Don't specify a filepath or filename + # Get the environment variables and set them with chdir(temp_dir): env_vars = get_environment_variables( "TEST_CREDENTIAL_B", "TEST_CREDENTIAL_C", set_variables=True ) + # credentials were found and returned with subtests.test("TEST_CREDENTIAL_B value"): assert env_vars["TEST_CREDENTIAL_B"] == "byeFolks" with subtests.test("TEST_CREDENTIAL_C value"): assert env_vars["TEST_CREDENTIAL_C"] == "i<3H2I" - # were not set as environment variables + # credentials were set as environment variables with subtests.test("TEST_CREDENTIAL_B set as environment variable"): assert os.environ.get("TEST_CREDENTIAL_B") == "byeFolks" From d2185da1adc5abd4a724e8e09cf229e216c54eaf Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:45:06 -0600 Subject: [PATCH 27/31] updated conftest.py files --- h2integrate/core/test/conftest.py | 19 ++++++++++++++++--- h2integrate/resource/test/conftest.py | 19 +++++++++++++++---- test/conftest.py | 19 +++++++++++++++---- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/h2integrate/core/test/conftest.py b/h2integrate/core/test/conftest.py index 75fdfb938..e9f453e07 100644 --- a/h2integrate/core/test/conftest.py +++ b/h2integrate/core/test/conftest.py @@ -33,9 +33,6 @@ def pytest_sessionstart(session): # NOTE: the rest of this function is required so that test_utilities.py # does not mess with a user's environment - # Set a dummy API key - os.environ["NLR_API_KEY"] = "a" * 40 - _ = get_nlr_developer_api_credential(which="key", set_vars=True) # if user provided a resource directory, save it to a temp variable # this allows tests to run as expected while not causing @@ -46,6 +43,14 @@ def pytest_sessionstart(session): # Set RESOURCE_DIR to None so pulls example files from default DIR os.environ.pop("RESOURCE_DIR", None) + # If the user provided an NLR_API_KEY, save it to a temp variable + if (initial_nlr_api_key := os.getenv("NLR_API_KEY")) is not None: + os.environ["TEMP_NLR_API_KEY"] = f"{initial_nlr_api_key}" + + # Set a dummy API key + os.environ["NLR_API_KEY"] = "a" * 40 + _ = get_nlr_developer_api_credential(which="key", set_vars=True) + def pytest_sessionfinish(session, exitstatus): # if user provided a resource directory, load it from the temp variable @@ -54,6 +59,14 @@ def pytest_sessionfinish(session, exitstatus): if (user_dir := os.getenv("TEMP_RESOURCE_DIR")) is not None: os.environ["RESOURCE_DIR"] = user_dir os.environ.pop("TEMP_RESOURCE_DIR", None) + + # if the user provided an nlr_api_key, load it from the temp variable + # and reset the original environment variable to prevent unexpected + # behavior after running tests + if (user_api_key := os.getenv("TEMP_NLR_API_KEY")) is not None: + os.environ["NLR_API_KEY"] = user_api_key + os.environ.pop("TEMP_NLR_API_KEY", None) + # NOTE: the above code is required so that test_utilities.py does # not mess with a user's environment diff --git a/h2integrate/resource/test/conftest.py b/h2integrate/resource/test/conftest.py index 208993eed..4e2c9287c 100644 --- a/h2integrate/resource/test/conftest.py +++ b/h2integrate/resource/test/conftest.py @@ -68,10 +68,6 @@ def pytest_sessionstart(session): os.environ["OPENMDAO_REPORTS"] = "none" - # Set a dummy API key - os.environ["NLR_API_KEY"] = "a" * 40 - _ = get_nlr_developer_api_credential(which="key", set_vars=True) - # Set RESOURCE_DIR to None so pulls example files from default DIR initial_resource_dir = os.getenv("RESOURCE_DIR") # if user provided a resource directory, save it to a temp variable @@ -82,6 +78,14 @@ def pytest_sessionstart(session): os.environ.pop("RESOURCE_DIR", None) + # If the user provided an NLR_API_KEY, save it to a temp variable + if (initial_nlr_api_key := os.getenv("NLR_API_KEY")) is not None: + os.environ["TEMP_NLR_API_KEY"] = f"{initial_nlr_api_key}" + + # Set a dummy API key + os.environ["NLR_API_KEY"] = "a" * 40 + _ = get_nlr_developer_api_credential(which="key", set_vars=True) + def pytest_sessionfinish(session, exitstatus): # if user provided a resource directory, load it from the temp variable @@ -93,6 +97,13 @@ def pytest_sessionfinish(session, exitstatus): os.environ["RESOURCE_DIR"] = user_dir os.environ.pop("TEMP_RESOURCE_DIR", None) + # if the user provided an nlr_api_key, load it from the temp variable + # and reset the original environment variable to prevent unexpected + # behavior after running tests + if (user_api_key := os.getenv("TEMP_NLR_API_KEY")) is not None: + os.environ["NLR_API_KEY"] = user_api_key + os.environ.pop("TEMP_NLR_API_KEY", None) + initial_om_report_setting = os.getenv("TMP_OPENMDAO_REPORTS") if initial_om_report_setting is not None: os.environ["OPENMDAO_REPORTS"] = initial_om_report_setting diff --git a/test/conftest.py b/test/conftest.py index fd67d96d1..7ae1ae8be 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -19,10 +19,6 @@ def pytest_sessionstart(session): os.environ["OPENMDAO_REPORTS"] = "none" - # Set a dummy API key - os.environ["NLR_API_KEY"] = "a" * 40 - _ = get_nlr_developer_api_credential(which="key", set_vars=True) - # Set RESOURCE_DIR to None so pulls example files from default DIR initial_resource_dir = os.getenv("RESOURCE_DIR") # if user provided a resource directory, save it to a temp variable @@ -33,6 +29,14 @@ def pytest_sessionstart(session): os.environ.pop("RESOURCE_DIR", None) + # If the user provided an NLR_API_KEY, save it to a temp variable + if (initial_nlr_api_key := os.getenv("NLR_API_KEY")) is not None: + os.environ["TEMP_NLR_API_KEY"] = f"{initial_nlr_api_key}" + + # Set a dummy API key + os.environ["NLR_API_KEY"] = "a" * 40 + _ = get_nlr_developer_api_credential(which="key", set_vars=True) + def pytest_sessionfinish(session, exitstatus): # if user provided a resource directory, load it from the temp variable @@ -44,6 +48,13 @@ def pytest_sessionfinish(session, exitstatus): os.environ["RESOURCE_DIR"] = user_dir os.environ.pop("TEMP_RESOURCE_DIR", None) + # if the user provided an nlr_api_key, load it from the temp variable + # and reset the original environment variable to prevent unexpected + # behavior after running tests + if (user_api_key := os.getenv("TEMP_NLR_API_KEY")) is not None: + os.environ["NLR_API_KEY"] = user_api_key + os.environ.pop("TEMP_NLR_API_KEY", None) + initial_om_report_setting = os.getenv("TMP_OPENMDAO_REPORTS") if initial_om_report_setting is not None: os.environ["OPENMDAO_REPORTS"] = initial_om_report_setting From 643340b22fe61915960b67302e8c7eac9782eeef Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:19:19 -0600 Subject: [PATCH 28/31] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e28b7a4cb..58a369de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,6 +129,7 @@ - Added infrastructure for running models with non-hourly time steps via a class attribute `_time_step_bounds` and sets new time step bounds of 5-minutes to 1-hour for the grid components. [PR 653](https://github.com/NatLabRockies/H2Integrate/pull/653) and [PR 671](https://github.com/NatLabRockies/H2Integrate/pull/671) - Remove demand-related outputs from storage performance models and replace usage with demand components [PR 666](https://github.com/NatLabRockies/H2Integrate/pull/666) - Added a compressed gas hydrogen storage model [PR 680](https://github.com/NatLabRockies/H2Integrate/pull/680) +- Generalized functions for retrieving and setting environment variables in `h2integrate/core/test/test_env_tools.py`. The main function used to get and/or set environment variables is `get_environment_variables`, which is now used by the `get_nlr_developer_api_key` and `get_nlr_developer_api_email` functions [PR 798](https://github.com/NatLabRockies/H2Integrate/pull/798) ## 0.7.2 [April 9, 2026] From 05de2e5e4b30e811931f2bfaaeb3f11a4a28b8cb Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 22 Jul 2026 16:04:04 -0600 Subject: [PATCH 29/31] Added new method to check for duplicate defined environment variables and to raise a user warning --- h2integrate/core/env_tools.py | 54 ++++++++++++++++++++++++- h2integrate/core/test/test_env_tools.py | 51 +++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py index 27cd16a71..6563b6c53 100644 --- a/h2integrate/core/env_tools.py +++ b/h2integrate/core/env_tools.py @@ -1,9 +1,45 @@ import os +import warnings from pathlib import Path from h2integrate import ROOT_DIR +def _check_duplicate_environment_vars( + *args: str, original_env_vars: dict, new_env_vars: dict +) -> set: + """Check if any environment variables are defined twice and if so, + check for any mismatched values. + + Args: + args (str): Name(s) of the environment variable(s) that should be retrieved from + environment variable dictionaries + original_env_vars (dict): dictionary of previously loaded environment variables + new_env_vars (dict): dictionary of additionally loaded environment variables + + Returns: + set: environment variables that exist in both environment variable + dictionaries and have different values + """ + + # common variables between both sets of variables + shared_vars = set(original_env_vars) & set(new_env_vars) + if not shared_vars: + # return empty set + return shared_vars + # args that are shared variables + shared_args = set(args) & shared_vars + if not shared_args: + # return empty set, no duplicate args + return shared_args + # Check if theres a value mismatch between shared args + mismatched_args = set() + for arg in list(shared_args): + if original_env_vars[arg] != new_env_vars[arg]: + mismatched_args.add(arg) + return mismatched_args + + def set_env_var(*, overwrite: bool = False, **kwargs: str): """Set or overwrite environment variables. @@ -127,7 +163,23 @@ def get_environment_variables( for folder in default_folders: if (file_path := (folder / file_name)).is_file(): # Prioritize environment variables that have already been set - env_vars = load_env_vars_from_file(file_path) | env_vars + env_vars_from_file = load_env_vars_from_file(file_path) + mismatched_vars = _check_duplicate_environment_vars( + *args, original_env_vars=env_vars, new_env_vars=env_vars_from_file + ) + if mismatched_vars: + mismatched_txt = "\n".join( + f"Environment variable '{mis_var}' set to '{env_vars_from_file[mis_var]}' in " + f"file {file_path!s} but set as value '{env_vars[mis_var]}' earlier." + for mis_var in mismatched_vars + ) + msg = ( + f"Mismatched values found for environment variable(s) {list(mismatched_vars)}. " + f"{mismatched_txt}. Values loaded from file {file_path} will not be used." + ) + warnings.warn(msg, UserWarning, stacklevel=3) + + env_vars = env_vars_from_file | env_vars # Remove extraneous environment variables that were loaded from file(s) env_vars_subset = {name: env_vars.get(name) for name in args if name in env_vars} diff --git a/h2integrate/core/test/test_env_tools.py b/h2integrate/core/test/test_env_tools.py index c5afb19af..91de5cb71 100644 --- a/h2integrate/core/test/test_env_tools.py +++ b/h2integrate/core/test/test_env_tools.py @@ -7,6 +7,7 @@ set_env_var, load_env_vars_from_file, get_environment_variables, + _check_duplicate_environment_vars, ) @@ -25,6 +26,41 @@ def temp_env_var(credential_value: str): os.environ["TEST_CREDENTIAL"] = original +@pytest.mark.unit +def test_duplicate_defined_environment_vars(subtests): + env_vars0 = { + "A": "alphabet", + "B": "numbers", + } + + # Check that an empty set is returned when values match + duplicate_vars = _check_duplicate_environment_vars( + "A", "B", original_env_vars=env_vars0, new_env_vars=env_vars0 + ) + with subtests.test("No mismatched shared variable values"): + assert not bool(duplicate_vars) + + env_vars1 = { + "A": "alpha-bet", + "B": "numbers", + } + + # Check that mismatched variables are returned without a match + duplicate_vars = _check_duplicate_environment_vars( + "A", "B", original_env_vars=env_vars0, new_env_vars=env_vars1 + ) + with subtests.test("A has mismatched values"): + assert len(duplicate_vars) == 1 + assert list(duplicate_vars)[0] == "A" + + # Check that an empty set is returned when specified variable matches + duplicate_vars = _check_duplicate_environment_vars( + "B", original_env_vars=env_vars0, new_env_vars=env_vars1 + ) + with subtests.test("No mismatched shared args"): + assert not bool(duplicate_vars) + + @pytest.mark.unit @pytest.mark.parametrize("credential_value", ["none"]) def test_set_environment_var(subtests, temp_env_var): @@ -215,3 +251,18 @@ def test_get_environment_variables_from_default_cwd(subtests, temp_dir): with subtests.test("TEST_CREDENTIAL_C set as environment variable"): assert os.environ.get("TEST_CREDENTIAL_C") == "i<3H2I" + + # Change environment variable B to a new value + os.environ["TEST_CREDENTIAL_B"] = "byeFriends" + os.environ.pop("TEST_CREDENTIAL_C", None) + expected_str = ( + f"Environment variable 'TEST_CREDENTIAL_B' set to 'byeFolks' in file " + f"{env_path!s} but set as value 'byeFriends' earlier." + ) + with subtests.test("Mismatched environment variables"): + with chdir(temp_dir): + with pytest.warns(UserWarning) as excinfo: + env_vars = get_environment_variables( + "TEST_CREDENTIAL_B", "TEST_CREDENTIAL_C", set_variables=True + ) + assert expected_str in str(excinfo.list[0].message) From 099774111532ddf9743b2aed24183fd9d7570a29 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:02:35 -0600 Subject: [PATCH 30/31] changed executable code cell to python code block in doc page --- docs/misc_resources/debugging_environment_variables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/misc_resources/debugging_environment_variables.md b/docs/misc_resources/debugging_environment_variables.md index 2e1d871bb..d3942352f 100644 --- a/docs/misc_resources/debugging_environment_variables.md +++ b/docs/misc_resources/debugging_environment_variables.md @@ -52,7 +52,7 @@ for folder in default_directories: If you want to determine whether your environment file is placed in one of the valid locations, the code below will tell you if your environment file is found in any of the default directories: -```{code-cell} ipython3 +```python from pathlib import Path from h2integrate import ROOT_DIR From e133caf1f54009d47c7aefd4c4fba60ca141d65c Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:03:37 -0600 Subject: [PATCH 31/31] commiting johns doc changes --- docs/getting_started/environment_variables.md | 4 ++-- docs/misc_resources/debugging_environment_variables.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/getting_started/environment_variables.md b/docs/getting_started/environment_variables.md index 1a5852263..05c0ee396 100644 --- a/docs/getting_started/environment_variables.md +++ b/docs/getting_started/environment_variables.md @@ -1,6 +1,6 @@ (environment_variables:environment-variables)= # Environment Variables -H2Integrate can pull data (such as wind and solar resource data, feedstock prices, etc) from public datasets accessible with API keys or user-specific tokens. Since API keys and tokens are unique to each user, these are accessed in H2Integrate as environment variables. These environment variables only need to be set to utilize their corresponding functionality. Some environment variables can also be used to customize your workflow. The list of environment variables that may be used by H2Integrate are listed below: +H2Integrate can pull data (such as wind and solar resource data, feedstock prices, etc) from public datasets accessible with API keys or user-specific tokens. Since API keys and tokens are unique to each user, these are accessed in H2Integrate as environment variables. These environment variables need to be set to use their corresponding functionality. Some environment variables can also be used to customize your workflow. The list of environment variables that may be used by H2Integrate are listed below: - [NLR Developer Network](environment_variables:nlr_developer) - `NLR_API_KEY` @@ -14,7 +14,7 @@ H2Integrate can pull data (such as wind and solar resource data, feedstock price Tips on debugging environment variable related errors or issues can be found [here](#env_var_debug:intro) ``` -To utilize the functionality of the models that require environment variables, instructions on how to set environment variables are shown [below](environment_variables:setting-environment-variables). +To use models that require environment variables, [follow these instructions below](environment_variables:setting-environment-variables). (environment_variables:setting-environment-variables)= # Setting Environment Variables diff --git a/docs/misc_resources/debugging_environment_variables.md b/docs/misc_resources/debugging_environment_variables.md index d3942352f..035a9b6c8 100644 --- a/docs/misc_resources/debugging_environment_variables.md +++ b/docs/misc_resources/debugging_environment_variables.md @@ -14,7 +14,7 @@ kernelspec: (env_var_debug:intro)= # Environment Variables: Non-standard Configuration and Debugging Issues -If errors are encountered with models that use environment variables, such as a missing API key or other credential, this may be because the environment variables were set using a non-preferred method or your workflow may be different. The different approaches to setting environment variables is documented [in the getting started guides](#environment_variables:environment-variables). Some of the non-preferred methods to set environment variables may require that environment variables be reset when your environment is activated. If you're setting environment variables with a .env file, then errors may occur because the file is not found. +If you encounter errors with models that use environment variables, such as a missing API key or other credential, this may be because the environment variables were set using a non-recommended method. The different approaches to setting environment variables is documented [in the getting started guides](#environment_variables:environment-variables). If you're setting environment variables with a .env file, then errors may occur because the file is not found. The following sections will demonstrate how to debug environment variable issues and alternative ways to set environment variables. @@ -27,7 +27,7 @@ The following sections will demonstrate how to debug environment variable issues ## Non-standard environment file location or name -If your getting an error for a missing environment variable and your environment variables are stored in a `.env` file, then the `.env` file may not be found in the expected location. +If you're getting an error for a missing environment variable and your environment variables are stored in a `.env` file, then the `.env` file may not be found in the expected location. (env_var_debug:get_default_fpaths)= ### Get the supported default filepaths