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] 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 913048cd4..05c0ee396 100644 --- a/docs/getting_started/environment_variables.md +++ b/docs/getting_started/environment_variables.md @@ -1,25 +1,32 @@ -(environment_variables:setting-environment-variables)= -# Setting 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. +(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 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` + - `NLR_API_EMAIL` +- [EIA Natural Gas Cost Data](environment_variables:eia_ng) + - `EIA_API_KEY` +- Customized Workflow + - `RESOURCE_DIR` ```{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``. +Tips on debugging environment variable related errors or issues can be found [here](#env_var_debug:intro) ``` +To use models that require environment variables, [follow these instructions below](environment_variables:setting-environment-variables). + +(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. An optional environment variable is `RESOURCE_DIR`. If set, this will be used as the default folder to save resource data to that is downloaded from the API. If setting this, please set its value as the full filepath to the folder you'd like to save resource files to, and ensure that the folder exists. 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)= @@ -69,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 @@ -106,3 +113,27 @@ 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 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 +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](#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 new file mode 100644 index 000000000..035a9b6c8 --- /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 +--- + +(env_var_debug:intro)= +# Environment Variables: Non-standard Configuration and Debugging Issues + +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. + +- [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 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 + +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: + +```python +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 +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/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 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) diff --git a/h2integrate/core/env_tools.py b/h2integrate/core/env_tools.py new file mode 100644 index 000000000..6563b6c53 --- /dev/null +++ b/h2integrate/core/env_tools.py @@ -0,0 +1,189 @@ +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. + + Args: + 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. + """ + for name, value in kwargs.items(): + if os.environ.get(name) is not None and not overwrite: + continue + os.environ[name] = value + + +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: + file_path (Path): The full file path and name containing configuration details to be + extracted. + + Returns: + dict: Dictionary of key, value pairs found in :py:attr:`file_path`. + """ + + 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 line if invalid or missing separator + 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 = ".env", + file_path: str | None = None, + set_variables: bool = True, +): + """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 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, 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 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 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: var for name in args if (var := os.environ.get(name)) is not None} + remaining_vars = set(args) - set(env_vars) + 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(): + # 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}") + + # 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(): + # Prioritize environment variables that have already been set + 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} + if set_variables: + # Set the environment variables + set_env_var(overwrite=True, **env_vars_subset) + return env_vars_subset diff --git a/h2integrate/core/test/conftest.py b/h2integrate/core/test/conftest.py index 4c2755827..e9f453e07 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, @@ -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 - set_nlr_key_dot_env() # 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/core/test/test_env_tools.py b/h2integrate/core/test/test_env_tools.py new file mode 100644 index 000000000..91de5cb71 --- /dev/null +++ b/h2integrate/core/test/test_env_tools.py @@ -0,0 +1,268 @@ +import os +from contextlib import chdir + +import pytest + +from h2integrate.core.env_tools import ( + set_env_var, + load_env_vars_from_file, + get_environment_variables, + _check_duplicate_environment_vars, +) + + +@pytest.fixture(scope="function") +def temp_env_var(credential_value: str): + """Temporarily set the `TEST_CREDENTIAL` environment variable""" + # NOTE: changes to this fixture can result in hard-to-debug test failures + # 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 + 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_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): + with subtests.test("Environment variable set"): + assert os.environ["TEST_CREDENTIAL"] == "none" + + kwargs = {"TEST_CREDENTIAL": "updated"} + set_env_var(overwrite=True, **kwargs) + + with subtests.test("Environment variable updated"): + assert os.environ["TEST_CREDENTIAL"] == "updated" + + 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_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") # = 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("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("Credential using = separator with spaces"): + assert env_vars["TEST_CREDENTIAL_B"] == "testing@yahoo.fake" + with subtests.test("Credential using : separator with spaces"): + assert env_vars["TEST_CREDENTIAL_D"] == "testingValue" + 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 +@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" + + 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_variables_from_filepath(subtests, temp_dir): + os.environ.pop("TEST_CREDENTIAL_A", None) + os.environ.pop("TEST_CREDENTIAL_B", None) + + # 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 + + # 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 + ) + + 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"] == "bees" + + # 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 + + with subtests.test("TEST_CREDENTIAL_B not set"): + assert os.environ.get("TEST_CREDENTIAL_B") is None + + +@pytest.mark.unit +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) + + # 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 + + # 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" + env_file_txt += "TEST_CREDENTIAL_C=i<3H2I\n" + + with env_path.open("w+") as file: + file.write(env_file_txt) + + # 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) + + with subtests.test("TEST_CREDENTIAL_B value"): + assert env_vars["TEST_CREDENTIAL_B"] == "howdyFolks" + + # 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 + + +@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) + + # 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 + + # 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" + env_file_txt += "TEST_CREDENTIAL_C=i<3H2I\n" + + 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" + + # credentials were 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("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) diff --git a/h2integrate/resource/test/conftest.py b/h2integrate/resource/test/conftest.py index 5cd0ab0ea..4e2c9287c 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, @@ -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 - set_nlr_key_dot_env() - # 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/h2integrate/resource/utilities/nlr_developer_api_keys.py b/h2integrate/resource/utilities/nlr_developer_api_keys.py index 692d006ae..c4e4c624e 100644 --- a/h2integrate/resource/utilities/nlr_developer_api_keys.py +++ b/h2integrate/resource/utilities/nlr_developer_api_keys.py @@ -1,268 +1,78 @@ -import os import warnings from pathlib import Path -from dotenv import load_dotenv +from h2integrate.core.env_tools import get_environment_variables -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." ) +_ENV_MISSING_MSG = ( + "{new} (or {old}) has not been set. " "Please set the {new} environment variable." +) -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 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: - new_name (str): The new (preferred) environment variable name. - old_name (str): The deprecated environment variable name. + 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 | None: The value of the environment variable, or None if not set. + str: API key or email for NLR Developer Network. """ - value = os.getenv(new_name) - if value is not None: - return value - value = os.getenv(old_name) - if value is not None: + 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=set_vars + ) + 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 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 - + return list(nlr_api_vars.values())[0] -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. +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 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. + ValueError: If NLR_API_KEY or NREL_API_KEY was not found as an environment variable Returns: - str: API key for NLR Developer Network. + 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 - # 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. +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 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. + ValueError: If NLR_API_EMAIL or NREL_API_EMAIL was not found as an environment variable 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 + str: email corresponding to the API key for NLR Developer Network. - # 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 + """ + nlr_api_email = get_nlr_developer_api_credential(which="email") + return nlr_api_email diff --git a/test/conftest.py b/test/conftest.py index a495705b5..7ae1ae8be 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): @@ -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 - set_nlr_key_dot_env() - # 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